Repository navigation
Conversation
AugmentedRouter and AugmentedRouterWithMiddlewares now match a procedure through its `~orpc` definition instead of the Procedure class, and test each key against AnyRouter before trying the `Lazy<infer $>` inference, so only lazy keys pay for it. Both resolve to the same types as before. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SLCp5Vav859sAFS6N
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/mcp
@orpc/msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
|
No actionable comments were generated in the recent review. Summary
Merge Risk:
|
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
ℹ️ No critical issues — one small suggestion inline, one optional follow-up below.
Reviewed changes
- Structural procedure match —
AugmentedRouterandAugmentedRouterWithMiddlewaresnow match a procedure through{ '~orpc': ProcedureDefinition<infer ...> }instead of theProcedureclass, soDecoratedProcedure(and any other subclass) no longer goes through a class-vs-subclass comparison and noProcedure<infer ...>is instantiated per procedure. - Router-before-lazy key dispatch — in the mapped branch,
T[K] extends AnyRouteris tested beforeT[K] extends Lazy<infer $>, so the inference-bearingLazycheck only runs for keys that are not routers.
I re-ran the equivalence claim independently with a throwaway Equal<Old, New> probe covering plain procedures, AnyProcedure, a bare ProcedureDefinition-shaped object, nested routers, Lazy<router>, optional and readonly keys, Procedure | Lazy<Procedure> unions, Lazyable<...> keys, AnyRouter, an index-signature router, any and never — all identical, for both augmented types. I also reproduced the perf direction on a 300-procedure router (two files identical except for the definition, TS 7.0.2, repo config): 49,677 → 48,726 instantiations, and exactly +1 type per procedure, matching the numbers in the PR body. router-utils/builder/implementer-router tests and oxlint are clean. The probe artifacts were removed, tree is clean.
ℹ️ The same class match is still in the InferRouter* helpers
packages/server/src/router.ts still uses T extends Procedure<infer ...> in InferRouterInitialContexts, InferRouterFinalContexts, InferRouterInputs, InferRouterOutputs, InferRouterError and InferRouterErrors, and each recurses per key on every client inference. The same ~orpc-structural rewrite is presumably applicable there — out of scope for this PR, but a natural follow-up if the class match is a measurable share of those types too (it would need its own measurement, since these helpers do not feed augmentRouter).
Technical details
# Follow-up: apply the structural match to router inference helpers
## Affected sites
- `packages/server/src/router.ts:41` — `InferRouterInitialContexts`
- `packages/server/src/router.ts:58` — `InferRouterFinalContexts`
- `packages/server/src/router.ts:75` — `InferRouterInputs`
- `packages/server/src/router.ts:92` — `InferRouterOutputs`
- `packages/server/src/router.ts:106` — `InferRouterError`
- `packages/server/src/router.ts:120` — `InferRouterErrors`
## Required outcome
- Measure whether replacing `T extends Procedure<infer ...>` with
`T extends { '~orpc': ProcedureDefinition<infer ...> }` in these helpers moves
the client-side inference cost before changing them; they are unaffected by
this PR's benchmark.
- Keep the result type-equal to today's for procedures, nested routers, lazy
routers and `AnyRouter`.
## Suggested approach (optional)
- Reuse the same probe shape used for `AugmentedRouter` (`Equal<Old, New>` over
the shapes listed above) to confirm equivalence before landing.deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 791d354 | Commit Preview URL Branch Preview URL |
Nov 13 2026, 12:35 AM |

Summary
AugmentedRouterandAugmentedRouterWithMiddlewaresare the types returned byos.router(),os.use(...).router(),os.lazy()and the implementer's.router(). Two changes make them cheaper:~orpcdefinition instead of theProcedureclass..handler()returns aDecoratedProcedure, a different class, so the class match compared the two classes structurally for every procedure.AnyRouterfirst, which has noinfer. TheLazy<infer $>inference now runs only for keys that are not plain routers.Both types still recurse on themselves; there is no new helper type.
Measurements
The workload is the
typecheckproject of orpc-benchmarks (3,000 procedures plus a typed client call for each), with routers wrapped as described in each row.tsc --singleThreaded --extendedDiagnostics, TypeScript 7.0.2. "Before" is the published2.0.0-beta.50, "after" is@orpc/serverbuilt from this branch. These counts are deterministic.os.router()at the rootos.router()in each of the 500 router filesos.use(middleware).router()at the rootThe type count rises by one per procedure. For scale: the same project with plain object routers is 1,040,001 instantiations, so
os.router()at the root accounts for about 309,800 of them before and 234,200 after (−24%).This is independent of #2451 and #2452.
Behavior
No difference from
main. I compared the old and new definitions directly on procedures, nested routers, lazy routers and procedures, keys typed asLazyable<...>, optional and readonly keys,os.router()output,AnyRouter,anyandnever, with and without an error map: all resolve to identical types.Emitted declarations are unchanged too. A consumer installed from packed tarballs (pnpm, depending only on
@orpc/server) and built withcomposite: true, including generic code that passes a router type parameter to.router(), emits a.d.tsbyte-identical to the one built againstmain, on TypeScript 7.0.2, 6.0.3 and 5.9.3.Verification
pnpm check,pnpm test(4,585 tests) andpnpm docs:validatepass. The existing type tests inrouter-utils.test-d.tscover a single procedure, a nested router and a lazy router for both types.Generated by Claude Code