feat(core-shared): add defineErrorMiddleware factory + export t

Factory takes [[ErrorCtor, TRPC_CODE], ...] tuples and returns a tRPC
middleware that translates matching domain errors to TRPCError. Discrim-
inates by instanceof; preserves original error as cause; unmapped
errors propagate (tRPC then wraps them as INTERNAL_SERVER_ERROR with
the original error as .cause — middleware does not interfere).

core-shared never enumerates feature errors — each feature passes its
own constructors in via integrations/api/procedures.ts (Tasks 3-7).

Also exports the `t` instance from trpc/init.ts so feature procedure
files can do t.procedure.use(...).

Also fixes tsconfig.json: rootDir set to "." and @/* path alias added
so test files using @/ resolve correctly under tsc --noEmit.

Refactor log: §1, §2, §4
Spec: R13–R17
This commit is contained in:
2026-05-06 11:43:45 +02:00
parent 61baaae99a
commit e25b1f7a1c
6 changed files with 142 additions and 6 deletions

View File

@@ -0,0 +1,36 @@
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. The middleware
* runs the procedure body inside a try/catch; on `instanceof Ctor`,
* it throws a TRPCError with the configured code and the original error
* preserved as `.cause`. Unmapped errors propagate untouched (tRPC's
* default INTERNAL_SERVER_ERROR handling applies).
*
* Owned by features: each feature passes its own constructors in.
* core-shared never enumerates feature-specific error classes.
*/
export function defineErrorMiddleware(
map: ReadonlyArray<readonly [ErrorCtor, TRPC_ERROR_CODE_KEY]>,
) {
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;
});
}