feat(nest): allow context as async fn(ExecutionContext)#1666
Conversation
- Export `ContextFactory` type from `module.ts` - Widen `context` union in `ORPCModuleConfig` to accept `ContextFactory` - Resolve context per-request in `ImplementInterceptor.intercept()` via `typeof` check + `await` — module and interceptor remain singletons - Add 6 test cases: static object, sync factory, async factory, handler integration, x-tenant-id header extraction, forRootAsync compat - Update NestJS integration docs
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — Adds support for a per-request context factory in @orpc/nest, keeping ORPCModule and ImplementInterceptor as singletons.
- Adds
ContextFactorytype topackages/nest/src/module.tsand widensORPCModuleConfig.contextto acceptDefaultInitialContext | ContextFactory. - Resolves the factory at request time in
ImplementInterceptor.intercept()and passes the resulting context to the standard handler. - Adds 6 regression and feature tests covering static objects, sync/async factories,
forRootAsync, and header-based dynamic context. - Updates the Initial Context section of the NestJS integration docs with factory examples.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
More templates
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/json-schema
@orpc/nest
@orpc/next
@orpc/openapi
@orpc/opentelemetry
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/tanstack-query
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
I just simply implementation, if you have time please review it again @Wadiou |
There was a problem hiding this comment.
Important
The latest commit refactors factory resolution onto the shared value helper, but it also regresses the documentation and trims test coverage for the feature.
Reviewed changes — Refactored the per-request context factory to use @orpc/shared and simplified the type/test/docs surface around it.
- Updated
packages/nest/src/implement.tsto resolvecontextviaawait value(..., ctx), removing the localtypeof ... === 'function'branch. - Reworked
packages/nest/src/module.tsfrom a localContextFactoryexport to the genericValue<Promisable<DefaultInitialContext>, [ctx: ExecutionContext]>union. - Replaced the six dynamic-context integration tests in
packages/nest/src/implement.test.tswith a single isolation test inpackages/nest/src/module.test.ts. - Removed the dedicated
ORPCModule.forRootfactory example fromapps/content/docs/integrations/nest.mdand rewrote the existingforRootAsyncexample to use a factory while stillinjectingREQUEST.
⚠️ Docs example keeps the request-scoped pattern the feature is meant to replace
The Initial Context example now shows a context factory inside ORPCModule.forRootAsync while still using inject: [REQUEST]. That keeps the module request-scoped and does not demonstrate the primary use case in the PR description: a singleton ORPCModule.forRoot({ context: (ctx) => ... }).
The standalone section that demonstrated the forRoot factory and an async variant was also removed in this commit.
Restore a clean forRoot factory example in the Initial Context section. If a forRootAsync example is useful, show it as a specific fallback rather than the only example.
Technical details
# Docs example no longer demonstrates the main factory use case
## Affected sites
- `apps/content/docs/integrations/nest.md:187-205` — the only Initial Context example uses `forRootAsync` + `inject: [REQUEST]`, which forces the module into request scope.
- `apps/content/docs/integrations/nest.md:206-244` (deleted in this commit) — previously held the dedicated `forRoot` factory and async examples that showed the singleton use case.
## Required outcome
- The Initial Context section should clearly show `ORPCModule.forRoot({ context: (ctx) => ... })` as the primary factory usage.
- The request-scoped `forRootAsync` example should be clearly separated or labeled as the legacy/optional route.
## Suggested approach
- Reintroduce the removed `forRoot` factory example at the start of the Initial Context section.
- Keep the new inline factory example inside `forRootAsync` but annotate that `inject: [REQUEST]` still makes the module request-scoped and that the factory is optional there.
## Open questions for the human
- Do you want to document `forRoot` and `forRootAsync` factory usage, or simplify to only one pattern?⚠️ Test coverage dropped for async factories and forRootAsync
The new test in packages/nest/src/module.test.ts only exercises a synchronous factory via ORPCModule.forRoot. The PR description claims support for async factories and forRootAsync, and the previous commit covered both in packages/nest/src/implement.test.ts (now deleted).
Move those missing cases into module.test.ts so the feature keeps regression coverage.
Technical details
# Test coverage dropped for async factories and forRootAsync
## Affected sites
- `packages/nest/src/module.test.ts:100` — added test title says async, but the factory is synchronous.
- `packages/nest/src/implement.test.ts` — previous `dynamic context factory` block covered static regression, sync factory, async factory, handler input, header tenant id, and `forRootAsync` compatibility; it was removed in this commit.
## Required outcome
- `module.test.ts` should cover async factory resolution (awaited in `intercept()`) and `ORPCModule.forRootAsync` with a factory `context`.
## Suggested approach
- Add a test where the factory returns a `Promise`.
- Add a `forRootAsync` test whose `useFactory` returns a config with a factory `context`.
- If the deleted `implement.test.ts` cases were intentionally removed for weight, recreate lightweight versions in `module.test.ts` instead.Kimi K2 (free via Pullfrog for OSS) | 𝕏

Adds support for passing a factory function as
contextinORPCModuleConfig(works with both
forRootandforRootAsync):context: (ctx) => {
const req = ctx.switchToHttp().getRequest()
return { tenantId: req.headers['x-tenant-id'] ?? 'default' }
}
The factory receives the NestJS
ExecutionContexton each request andcan be sync or async.
ORPCModuleandImplementInterceptorremainsingletons regardless — only the factory runs per request.
Previously, injecting per-request data required
forRootAsyncwithinject: [REQUEST], which forces the entire module into request scope.Changes:
module.ts— exportContextFactorytype, widencontextunionimplement.ts— resolve factory at request time inintercept()implement.test.ts— 6 new test cases (incl. forRootAsync compat)docs/integrations/nest.md— updated Initial Context sectionFully backward compatible. Existing static object usage is unchanged.