Skip to content

fix(server): apply the contract to routers implemented with .lazy() - #2070

Merged
dinwwwh merged 2 commits into
middleapi:mainfrom
dinwwwh:claude/lazy-router-contract-leak-68a4b4
Sep 24, 2026
Merged

dinwwwh merged 2 commits into
middleapi:mainfrom
dinwwwh:claude/lazy-router-contract-leak-68a4b4

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Sep 24, 2026

Copy link
Copy Markdown
Member

Routers implemented with implement(contract).lazy(...) now follow the contract's error map and meta, the same as routers passed to .router(...). Before this, the loaded router had no contract attached, so its procedures used the implementation's own error map and meta. For example, an error the contract never declares, such as INTERNAL_DEBUG with a { sql } payload, reached clients as a defined, typed error.

Fixes

  • A router mounted like { sub: implement(contract).lazy(() => import('./impl')) } now has errors and meta the contract does not declare stripped, as .router(...) already did.
  • Middlewares added with .use(...) still apply to lazily loaded routers.
  • A lazy router inside implement(contract).router(...) was never affected, and it behaves as before.

Testing

  • The updated .lazy tests fail on main and pass with the fix.
  • All packages/server, packages/openapi and packages/nest tests pass.

@pkg-pr-new

pkg-pr-new Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

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

@orpc/arktype

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

@orpc/bun

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

@orpc/client

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

@orpc/cloudflare

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

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2070

@orpc/evlog

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

@orpc/hibernation

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

@orpc/json-schema

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

@orpc/experimental-lock

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

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2070

@orpc/nest

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

@orpc/next

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

@orpc/node

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

@orpc/openapi

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

@orpc/opentelemetry

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

@orpc/pinia-colada

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

@orpc/pino

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

@orpc/publisher

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

@orpc/ratelimit

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

@orpc/server

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

@orpc/shared

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

@orpc/swr

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: e4f229f

@codecov

codecov Bot commented Sep 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing dinwwwh:claude/lazy-router-contract-leak-68a4b4 (e4f229f) with main (31e7076)

Open in CodSpeed

@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 new issues found.

Reviewed changes

Routers loaded through implement(contract).lazy(loader) now carry the hidden contract, so their procedures follow the contract's error map and meta exactly like .router(...) does.

  • implementer-router.ts — lazy no longer conditionally re-wraps the loader itself; it builds new Lazy({ loader: async () => ({ default: this.router(router) }), meta: {} }), so every load augments middlewares when present and always applies withHiddenRouterContract(router, this.contract).
  • implementer-router.test.ts — .lazy tests now assert deferred loading, a single loader call, identity preservation, and getHiddenRouterContract(applied) === contract, in both the plain and with-middlewares suites.

I traced the mechanism end to end: walkProcedureContractsSync swaps the loaded router for the hidden contract, so the RPC/OpenAPI matchers now index contract procedures and route through createContractProcedure, which overrides errorMap/meta/metaPlugins while keeping orderedMiddlewares intact (middlewares still apply). The .lazy variant collapsing to this.router(...) also matches the builder's .lazy, which already does this.

I confirmed the updated assertions genuinely fail on main (reverting only the lazy body yields expected undefined to be { ... }), and that packages/server + packages/openapi (1022 tests), packages/nest (96 tests), and @orpc/server type:check all pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit 8e9a455 into middleapi:main Sep 24, 2026
11 checks passed
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.

1 participant