Skip to content

perf(server): make augmented router types cheaper to type-check - #2453

Open
dinwwwh wants to merge 1 commit into
mainfrom
claude/perf-augmented-router-type
Open

dinwwwh wants to merge 1 commit into
mainfrom
claude/perf-augmented-router-type

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Nov 13, 2026

Copy link
Copy Markdown
Member

Summary

AugmentedRouter and AugmentedRouterWithMiddlewares are the types returned by os.router(), os.use(...).router(), os.lazy() and the implementer's .router(). Two changes make them cheaper:

  • A procedure is matched through its ~orpc definition instead of the Procedure class. .handler() returns a DecoratedProcedure, a different class, so the class match compared the two classes structurally for every procedure.
  • Each key is tested against AnyRouter first, which has no infer. The Lazy<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 typecheck project 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 published 2.0.0-beta.50, "after" is @orpc/server built from this branch. These counts are deterministic.

Routers wrapped with Instantiations Types
os.router() at the root 1,349,789 → 1,274,217 (−5.6%) 262,932 → 265,928 (+1.1%)
os.router() in each of the 500 router files 1,362,253 → 1,307,191 (−4.0%) 279,425 → 282,421 (+1.1%)
os.use(middleware).router() at the root 1,388,983 → 1,313,411 (−5.4%) 263,503 → 266,499 (+1.1%)
nothing (plain objects, the unmodified benchmark) 1,040,001 → 1,040,001 247,841 → 247,841

The 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 as Lazyable<...>, optional and readonly keys, os.router() output, AnyRouter, any and never, 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 with composite: true, including generic code that passes a router type parameter to .router(), emits a .d.ts byte-identical to the one built against main, on TypeScript 7.0.2, 6.0.3 and 5.9.3.

Verification

pnpm check, pnpm test (4,585 tests) and pnpm docs:validate pass. The existing type tests in router-utils.test-d.ts cover a single procedure, a nested router and a lazy router for both types.


Generated by Claude Code

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
@pkg-pr-new

pkg-pr-new Bot commented Nov 13, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2453

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2453

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2453

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2453

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2453

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2453

@orpc/effect

npm i https://pkg.pr.new/@orpc/effect@2453

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2453

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2453

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2453

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2453

@orpc/mcp

npm i https://pkg.pr.new/@orpc/mcp@2453

@orpc/msw

npm i https://pkg.pr.new/@orpc/msw@2453

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2453

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2453

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2453

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2453

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2453

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2453

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2453

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2453

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2453

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2453

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2453

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2453

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2453

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2453

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2453

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2453

commit: 791d354

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Refactor
    • Improved type inference for procedures and nested or lazy routers while preserving existing error-map and context typing behavior. These changes affect internal type handling; no user-facing behavior changes are reported.

The exported router mapping types now identify procedure nodes through ~orpc metadata and infer their schema and context types from ProcedureDefinition. Recursive mappings check router children before lazy children while preserving lazy wrapping and merged types.

Changes: Router Type Mapping

Layer / File(s) Summary
Procedure and recursive router mapping
packages/server/src/router-utils.ts
The exported mappings use ProcedureDefinition metadata for procedure types. They check AnyRouter children before lazy children and retain lazy wrapping and merged error-map and context types.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: jameskranz

Merge Risk: Minimal · up to 791d3

This is a type-checking performance change to the router types returned by os.router() and related helpers. The supplied context shows no concrete behavioral or compatibility risk, so it looks safe to merge after the normal checks.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: a performance improvement to the augmented router types in @orpc/server, which the changeset makes by matching procedures through their ~orpc definition and checking A…
Description check Passed The description covers the two type changes, the motivation (class comparison cost versus definition matching), the benchmark measurements, the behavior and emitted-declaration checks, and the verific…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: a performance improvement to the augmented router types in @orpc/server, which the changeset makes by matching procedures through their ~orpc definition and checking AnyRouter before lazy inference. The title is specific and accurate, and it stays short and in one sentence.

Full details: Description check

Explanation

The description covers the two type changes, the motivation (class comparison cost versus definition matching), the benchmark measurements, the behavior and emitted-declaration checks, and the verification steps. It relates directly to the router-utils.ts changes.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 91f5781e-7ba1-4103-8426-beb5dd0b9e6c
Commits

Reviewing files that changed from the base of the PR and between 5c3b5c0 and 791d354.

Files selected for processing: 1

  • packages/server/src/router-utils.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review.

@codspeed

codspeed Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/perf-augmented-router-type (791d354) with main (5c3b5c0)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@codecov

codecov Bot commented Nov 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one small suggestion inline, one optional follow-up below.

Reviewed changes

  • Structural procedure match — AugmentedRouter and AugmentedRouterWithMiddlewares now match a procedure through { '~orpc': ProcedureDefinition<infer ...> } instead of the Procedure class, so DecoratedProcedure (and any other subclass) no longer goes through a class-vs-subclass comparison and no Procedure<infer ...> is instantiated per procedure.
  • Router-before-lazy key dispatch — in the mapped branch, T[K] extends AnyRouter is tested before T[K] extends Lazy<infer $>, so the inference-bearing Lazy check 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Comment thread packages/server/src/router-utils.ts Resolved
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants