import { TRPCError } from "@trpc/server"; import type { TRPC_ERROR_CODE_KEY } from "@trpc/server/rpc"; import { t } from "./init"; type ErrorCtor = new (...args: never[]) => Error; /** * Build a tRPC middleware that translates domain errors to TRPCError. * * Each tuple pairs a constructor with a TRPC error code. tRPC v11 * middleware sees procedure throws as a returned `result` (not an * exception): `next()` resolves to `{ ok: false, error: TRPCError }` * where `error.cause` is the original domain error wrapped by * `getTRPCErrorFromUnknown`. The middleware checks `result.ok`, * matches `result.error.cause` against each `instanceof Ctor`, and * on a hit throws a fresh TRPCError with the configured code and the * original error preserved as `.cause`. Unmapped errors fall through * unchanged — tRPC's default INTERNAL_SERVER_ERROR wrapping applies * with the original error still reachable via `.cause`. * * Owned by features: each feature passes its own constructors in. * core-shared never enumerates feature-specific error classes. */ export function defineErrorMiddleware( map: ReadonlyArray, ) { return t.middleware(async ({ next }) => { const result = await next(); if (!result.ok) { const cause = result.error.cause; if (cause instanceof Error) { for (const [Ctor, code] of map) { if (cause instanceof Ctor) { throw new TRPCError({ code, message: cause.message, cause }); } } } } return result; }); }