Files
EpicNext-Cms/src/lib/safe-action-shared.ts
T

52 lines
1.9 KiB
TypeScript

import { ZodError } from 'zod'
// ── Action Result Type ───────────────────────────────────────────────
export type ActionResult<T = Record<string, unknown>> =
| { ok: true; data?: T }
| { ok: false; error: string; fieldErrors?: Record<string, string[]> }
// ── Helper to build results ──────────────────────────────────────────
export function actionOk<T = Record<string, unknown>>(data?: T): ActionResult<T> {
return { ok: true, data: data ?? ({} as T) }
}
export function actionError(message: string): ActionResult<never> {
return { ok: false, error: message }
}
// ── Throwable error ──────────────────────────────────────────────────
export class ActionError extends Error {
constructor(message: string) {
super(message)
this.name = 'ActionError'
}
}
// ── Error handler ────────────────────────────────────────────────────
export function handleActionError(error: unknown): ActionResult<never> {
if (error instanceof ZodError) {
return {
ok: false,
error: 'Validation failed',
fieldErrors: error.flatten().fieldErrors as Record<string, string[]>,
}
}
if (error instanceof Error && error.name === 'ActionError') {
return { ok: false, error: error.message }
}
// Prisma P2025
if (
error instanceof Error &&
error.constructor.name === 'PrismaClientKnownRequestError' &&
(error as Error & { code?: string }).code === 'P2025'
) {
return { ok: false, error: 'Not found' }
}
console.error('[Action error]', error)
return { ok: false, error: 'Internal server error' }
}