Replace REST module routers with a single typed tRPC appRouter served over
/trpc (HTTP + WebSocket), and rewire the frontend to call it via
@trpc/client wsLink (browser) and httpLink (RSC data layer). Existing
/api/health + /api/metrics stay as plain Express for infra scraping.
Notable fixes surfaced by the live smoke test:
- Express 5 / path-to-regexp v8 rejects the /trpc/* wildcard route; use a
prefix middleware that computes opts.path from the URL instead.
- nodeHTTPRequestHandler treats opts.path as the literal procedure path, so
it is derived per-request from req.url.
- Two ws servers on one http.Server (the /ws voice socket + /trpc) collided
and returned 400 on upgrade; both now use noServer + a manually routed
server.on('upgrade') keyed by path.
Verified: BE tsc+biome+40 vitest green; FE tsc+biome green; live
HTTP and WebSocket calls returned real prod data.
Co-Authored-By: Claude Opus 4.5 (1M context) <noreply@anthropic.com>
36 lines
953 B
TypeScript
36 lines
953 B
TypeScript
import { initTRPC } from "@trpc/server";
|
|
import type { WebSocket } from "ws";
|
|
import { createChildLogger } from "@/shared/logger/index";
|
|
|
|
const logger = createChildLogger("trpc");
|
|
|
|
/**
|
|
* tRPC context. The WebSocket transport enriches each request with the raw
|
|
* socket so procedures can, if needed, inspect connection metadata. The
|
|
* dashboard is public (no auth), mirroring the previous REST layer.
|
|
*/
|
|
export interface TRPCContext {
|
|
conn: WebSocket | null;
|
|
}
|
|
|
|
const t = initTRPC.context<TRPCContext>().create({
|
|
errorFormatter({ shape, error }) {
|
|
return {
|
|
...shape,
|
|
data: {
|
|
...shape.data,
|
|
// Surface a stable code + message for client-side handling.
|
|
code: error.code,
|
|
stack: undefined,
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
export const router = t.router;
|
|
export const publicProcedure = t.procedure;
|
|
|
|
// Re-export so routers can import z from one place if desired.
|
|
export { z } from "zod";
|
|
export { logger };
|