feat: tRPC v11 API layer + X-API-Version response header#1444
feat: tRPC v11 API layer + X-API-Version response header#1444jaypatrick merged 11 commits intomainfrom
Conversation
Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/b33ce10d-01bc-4189-b7b2-1993bf10a473 Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
…uest.ts Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/b33ce10d-01bc-4189-b7b2-1993bf10a473 Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Introduces a typed tRPC v11 API layer on the Worker (mounted at /api/trpc/*, namespaced under v1.*) alongside existing REST endpoints, and adds a global X-API-Version: v1 response header.
Changes:
- Added a new
worker/trpc/module (context, router/procedures, fetch adapter handler, typed client) with initial v1 routers (health/compile/version). - Mounted the tRPC handler in
worker/hono-app.tsand added a global API version response header middleware. - Added docs and Deno tasks/deps for type-checking the new tRPC layer, plus unit tests via
createCallerFactory.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| worker/utils/synthetic-request.ts | New helper to construct minimal JSON POST Request objects for calling legacy handlers outside Hono. |
| worker/trpc/trpc.test.ts | New unit tests covering v1 procedures and auth enforcement. |
| worker/trpc/routers/v1/version.router.ts | Adds v1.version.get procedure returning Worker/app API version info. |
| worker/trpc/routers/v1/index.ts | Assembles the v1 router namespace (health/compile/version). |
| worker/trpc/routers/v1/health.router.ts | Adds v1.health.get procedure wrapping existing health handler. |
| worker/trpc/routers/v1/compile.router.ts | Adds v1.compile.json protected mutation wrapping handleCompileJson. |
| worker/trpc/router.ts | Defines the top-level appRouter with a v1 namespace. |
| worker/trpc/init.ts | Initializes tRPC and defines publicProcedure, protectedProcedure, and adminProcedure. |
| worker/trpc/handler.ts | Adds a Hono-compatible fetch adapter handler for /api/trpc/* with security telemetry hooks. |
| worker/trpc/context.ts | Defines TrpcContext and createTrpcContext() from Hono context variables. |
| worker/trpc/client.ts | Adds a typed tRPC client factory using httpBatchLink. |
| worker/hono-app.ts | Adds global X-API-Version header middleware, mounts tRPC handler, and refactors synthetic request helper naming/re-export. |
| docs/architecture/trpc.md | New architecture doc for tRPC procedures, usage, and ZTA notes. |
| docs/architecture/hono-routing.md | Documents the tRPC mount location and middleware implications. |
| deno.lock | Locks new npm deps for @trpc/server/@trpc/client (and transitive changes). |
| deno.json | Adds tRPC deps and task aliases for tRPC type-checking. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot Ensure you are importing Trpc via deno if possible deno add npm:@trpc/server npm:@trpc/client if it is compatible with our tech stack |
Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/dafe5bad-e683-4bd6-a16a-b350e24ed102 Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Done in dd2852d. Ran |
|
@copilot CI error Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile" Failure reason:
Error: Process completed with exit code 1. |
Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/8913dd42-4a6f-46a8-9392-0369c745fba8 Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Fixed in ab330a4. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
@copilot please fix the merge conflicts. |
…pi-versioning # Conflicts: # docs/architecture/hono-routing.md # worker/hono-app.ts Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Resolved in 134fcc1. Merged |
|
@copilot apply changes based on the comments in this thread |
…order, type inference, docs Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/5fbd6aa3-5013-460b-8d40-82db88073b51 Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Applied all 6 review comments in 3b4de55:
|
|
|
||
| const accessDenied = await checkUserApiAccess(authContext, c.env); | ||
| if (accessDenied) { | ||
| analytics.trackSecurityEvent({ | ||
| eventType: 'auth_failure', | ||
| path: c.req.path, | ||
| method: c.req.method, | ||
| clientIpHash: AnalyticsService.hashIp(ip), | ||
| reason: 'api_disabled', | ||
| }); | ||
| return accessDenied; | ||
| } | ||
| c.executionCtx.waitUntil(trackApiUsage(authContext, c.req.path, c.req.method, c.env)); |
There was a problem hiding this comment.
The tRPC access-gate middleware uses c.req.path for security telemetry and trackApiUsage(), but the REST equivalent normalizes paths via routesPath(c) (strips the /api prefix). This will make analytics/usage buckets inconsistent (e.g. REST stores /compile while tRPC stores /api/trpc/...). Consider using routesPath(c) (or a similar normalization) for both the trackSecurityEvent.path field and the trackApiUsage() call so dashboards/quotas group consistently.
| const accessDenied = await checkUserApiAccess(authContext, c.env); | |
| if (accessDenied) { | |
| analytics.trackSecurityEvent({ | |
| eventType: 'auth_failure', | |
| path: c.req.path, | |
| method: c.req.method, | |
| clientIpHash: AnalyticsService.hashIp(ip), | |
| reason: 'api_disabled', | |
| }); | |
| return accessDenied; | |
| } | |
| c.executionCtx.waitUntil(trackApiUsage(authContext, c.req.path, c.req.method, c.env)); | |
| const path = routesPath(c); | |
| const accessDenied = await checkUserApiAccess(authContext, c.env); | |
| if (accessDenied) { | |
| analytics.trackSecurityEvent({ | |
| eventType: 'auth_failure', | |
| path, | |
| method: c.req.method, | |
| clientIpHash: AnalyticsService.hashIp(ip), | |
| reason: 'api_disabled', | |
| }); | |
| return accessDenied; | |
| } | |
| c.executionCtx.waitUntil(trackApiUsage(authContext, path, c.req.method, c.env)); |
|
@copilot apply changes based on the comments in this thread |
Adds a typed tRPC v11 API at
/api/trpc/*alongside the existing REST endpoints, namespaced underv1.*. Every response now carriesX-API-Version: v1. Also resolves merge conflicts withorigin/main(Phase 4 domain route module split) and addresses review feedback to close ZTA gaps introduced by mounting tRPC outside theroutessub-app.Description
Adds a typed tRPC v11 API at
/api/trpc/*alongside the existing REST endpoints, namespaced underv1.*. Every response now carriesX-API-Version: v1. Also resolves merge conflicts withorigin/main(Phase 4 domain route module split) so the branch is fully up to date, and applies review-driven fixes to the tRPC layer's ZTA posture, rate-limiting, header reliability, type safety, and documentation accuracy.Changes
worker/trpc/(all new)context.ts—TrpcContext+createTrpcContext()from Hono contextinit.ts—publicProcedure,protectedProcedure(requiresuserId),adminProcedure(requiresrole === 'admin'),createCallerFactoryrouters/v1/health.router.ts—v1.health.getwrapshandleHealthrouters/v1/compile.router.ts—v1.compile.jsonmutation (protectedProcedure) wrapshandleCompileJson; input uses a parser function instead ofCompileRequestSchema as anyto preserve full TypeScript type inference on both client and serverrouters/v1/version.router.ts—v1.version.getreturns{ version, apiVersion }router.ts/handler.ts— top-levelappRouter;fetchRequestHandleradapter with ZTA telemetry onUNAUTHORIZED/FORBIDDENusing the actual HTTP request methodclient.ts—createTrpcClient(baseUrl, getToken?)typed client for Angularworker/hono-app.tsX-API-Version: v1header is now set beforeawait next()so error responses handled byapp.onErroralso carry the header (previously post-next()placement could miss error paths)app.use('/api/trpc/*', rateLimitMiddleware())— tiered rate limiting (includingrate_limitsecurity telemetry) for all tRPC calls, matching REST write endpointsapp.use('/api/trpc/*', ...)ZTA access-gate middleware that callscheckUserApiAccess()(blocks banned/suspended users withauth_failuresecurity event) andtrackApiUsage()— mirrors theroutes.use('*', ...)checks applied to every REST endpointapp.all('/api/trpc/*', handleTrpcRequest)directly onapp(beforeapp.route('/api', routes)) — bypassescompress/loggermiddleware scoped to the routes sub-apporigin/main: inline route handlers replaced with domain module mounts; stale helper functions removedworker/utils/synthetic-request.ts(new)Shared utility extracted to avoid a circular import; tRPC's compile router imports
buildSyntheticRequestdirectly from this module. Updated JSDoc to correctly referencebuildSyntheticRequest(c, validatedBody)inworker/routes/shared.tsfor Hono route handlers (removed stale reference to non-existentbuildHonoRequest).worker/routes/(fromorigin/mainPhase 4 merge)Domain-scoped route modules (
compile.routes.ts,rules.routes.ts,queue.routes.ts,configuration.routes.ts,admin.routes.ts,monitoring.routes.ts,api-keys.routes.ts,webhook.routes.ts,workflow.routes.ts,browser.routes.ts) and shared helpers inshared.ts/index.ts.deno.json/package.json/pnpm-lock.yaml@trpc/server@11.15.1and@trpc/client@11.15.1viadeno add npm:@trpc/server npm:@trpc/client— packages are pinned indeno.lockand managed by Deno (not via manual imports map entries)pnpm-lock.yamlto include the new tRPC packages, fixingpnpm install --frozen-lockfileCI failurestrpc:typesandcheck:trpctask aliasesDocs
docs/architecture/trpc.md— procedure catalogue, Mermaid flow, Angular client example (using correctCompileRequestSchemashape), ZTA notes updated to accurately describe the dedicatedapp.use('/api/trpc/*', ...)rate-limit and access-gate middlewaredocs/architecture/hono-routing.md— added tRPC endpoint section and Phase 4 domain route module sectionTests
worker/trpc/trpc.test.ts— 8 unit tests viacreateCallerFactory(no HTTP): health, version, compile auth enforcement,protectedProcedureUNAUTHORIZED,adminProcedureFORBIDDEN.Testing
Zero Trust Architecture Checklist
Worker / Backend
protectedProcedureandadminProcedureenforce auth at the procedure level; global unified-auth middleware populatesauthContextbefore/api/trpc/*is reached;app.use('/api/trpc/*', ...)ZTA gate blocks banned/suspended users beforehandleTrpcRequest*) on write/authenticated endpoints — tRPC inherits globalcors()middleware already onapp[vars]) — no new secrets introducedv1.compile.jsonuses a typed parser function overCompileRequestSchema.safeParse()(throwsTRPCError BAD_REQUESTon failure); tRPC validates all procedure inputs at the framework level.prepare().bind()(no string interpolation) — N/A, no new D1 querieshandleTrpcRequestonErrorhook emitstrackSecurityEvent()onUNAUTHORIZED/FORBIDDEN; ZTA access-gate middleware emitsauth_failureon banned/suspended users;rateLimitMiddleware()emitsrate_limittelemetry on 429Frontend / Angular
CanActivateFnauth guards — N/AlocalStorage) — N/AcreateTrpcClientaccepts agetTokencallback; token attachment is in thehttpBatchLinkheaders factory, not component codeOriginal prompt
PR B — tRPC v1 + API Versioning (must be merged AFTER PR A)
This PR adds tRPC v1 on top of the domain-route-split from PR A (which adds
worker/routes/). It also adds API versioning via theX-API-Versionresponse header and adeno.jsontask alias.Tech stack context
hono@^4.12.8) +@hono/zod-openapi@^1.2.2zod(jsr:@zod/zod@^4.3.6)get-sessionresponse)frontend/— the only current API consumer, but external clients will be addeddeno.jsonimportsPart 1: tRPC integration
New dependencies (add to
deno.jsonimports)New files to create
worker/trpc/context.ts(Import
Variablesfromworker/hono-app.ts— or define a minimal local type alias that matches the fields used.)worker/trpc/init.tsworker/trpc/routers/v1/health.router.tsExpose a
healthprocedure that callshandleHealth(ctx.env)and returns the parsed JSON:worker/trpc/routers/v1/compile.router.tsExpose a
compile.jsonmutation that wrapshandleCompileJson. Use the existingCompileRequestSchemafromsrc/configuration/schemas.tsas the input Zod schema. Return the raw JSON body from the handler response.Note:
buildSyntheticRequestis currently a private helper inhono-app.ts. Export it fromhono-app.tsso the tRPC router can import it.worker/trpc/routers/v1/version.router.tsworker/trpc/routers/v1/index.ts— barrelworker/trpc/router.ts— top-level versioned router