From c5d0b6a92787978c5ae5894246b100e7de0fc664 Mon Sep 17 00:00:00 2001 From: unnoq Date: Fri, 22 Aug 2025 09:05:13 +0700 Subject: [PATCH 1/2] feat(nest): add async configuration support for ORPCModule --- .../implement-contract-in-nest.md | 20 +++++--- packages/nest/src/implement.test.ts | 50 +++++++++++++++++++ packages/nest/src/module.ts | 20 ++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/apps/content/docs/openapi/integrations/implement-contract-in-nest.md b/apps/content/docs/openapi/integrations/implement-contract-in-nest.md index 3b9bc287a..f2f1f3d62 100644 --- a/apps/content/docs/openapi/integrations/implement-contract-in-nest.md +++ b/apps/content/docs/openapi/integrations/implement-contract-in-nest.md @@ -224,17 +224,23 @@ oRPC will use NestJS parsed body when it's available, and only use the oRPC pars Configure the `@orpc/nest` module by importing `ORPCModule` in your NestJS application: ```ts +import { REQUEST } from '@nestjs/core' import { onError, ORPCModule } from '@orpc/nest' +import { Request } from 'express' // if you use express adapter @Module({ imports: [ - ORPCModule.forRoot({ - interceptors: [ - onError((error) => { - console.error(error) - }), - ], - eventIteratorKeepAliveInterval: 5000, // 5 seconds + ORPCModule.forRootAsync({ // or .forRoot + useFactory: (request: Request) => ({ + interceptors: [ + onError((error) => { + console.error(error) + }), + ], + context: { request }, // oRPC context, accessible from middlewares, etc. + eventIteratorKeepAliveInterval: 5000, // 5 seconds + }), + inject: [REQUEST], }), ], }) diff --git a/packages/nest/src/implement.test.ts b/packages/nest/src/implement.test.ts index 438a6478b..d600c0497 100644 --- a/packages/nest/src/implement.test.ts +++ b/packages/nest/src/implement.test.ts @@ -1,5 +1,7 @@ import type { NodeHttpRequest } from '@orpc/standard-server-node' +import type { Request } from 'express' import { Controller, Req } from '@nestjs/common' +import { REQUEST } from '@nestjs/core' import { FastifyAdapter } from '@nestjs/platform-fastify' import { Test } from '@nestjs/testing' import { oc, ORPCError } from '@orpc/contract' @@ -412,4 +414,52 @@ describe('@Implement', async () => { eventIteratorKeepAliveComment: '__TEST__', })) }) + + it('works with ORPCModule.forRootAsync', async () => { + const interceptor = vi.fn(({ next }) => next()) + const moduleRef = await Test.createTestingModule({ + imports: [ + ORPCModule.forRootAsync({ + useFactory: async (request: Request) => ({ + interceptors: [interceptor], + eventIteratorKeepAliveComment: '__TEST__', + context: { + request, + }, + }), + inject: [REQUEST], + }), + ], + controllers: [ImplProcedureController], + }).compile() + + const app = moduleRef.createNestApplication() + await app.init() + + const httpServer = app.getHttpServer() + + const res = await supertest(httpServer) + .post('/ping?param=value¶m2[]=value2¶m2[]=value3') + .set('x-custom', 'value') + .send({ hello: 'world' }) + + expect(res.statusCode).toEqual(200) + expect(res.body).toEqual('pong') + + expect(interceptor).toHaveBeenCalledTimes(1) + expect(interceptor).toHaveBeenCalledWith(expect.objectContaining({ + context: expect.objectContaining({ + request: expect.objectContaining({ + url: '/ping?param=value¶m2[]=value2¶m2[]=value3', + headers: expect.objectContaining({ + 'x-custom': 'value', + }), + }), + }), + })) + expect(sendStandardResponseSpy).toHaveBeenCalledTimes(1) + expect(sendStandardResponseSpy).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ + eventIteratorKeepAliveComment: '__TEST__', + })) + }) }) diff --git a/packages/nest/src/module.ts b/packages/nest/src/module.ts index a220ddd96..e5d37aa80 100644 --- a/packages/nest/src/module.ts +++ b/packages/nest/src/module.ts @@ -3,6 +3,7 @@ import type { AnySchema } from '@orpc/contract' import type { CreateProcedureClientOptions } from '@orpc/server' import type { SendStandardResponseOptions } from '@orpc/standard-server-node' import { Module } from '@nestjs/common' +import { toArray } from '@orpc/shared' import { ImplementInterceptor } from './implement' export const ORPC_MODULE_CONFIG_SYMBOL = Symbol('ORPC_MODULE_CONFIG') @@ -28,4 +29,23 @@ export class ORPCModule { global: true, } } + + static forRootAsync(options: { + useFactory: (...args: any[]) => Promise | ORPCModuleConfig + inject?: any[] + }): DynamicModule { + return { + module: ORPCModule, + providers: [ + { + provide: ORPC_MODULE_CONFIG_SYMBOL, + useFactory: options.useFactory, + inject: toArray(options.inject), + }, + ImplementInterceptor, + ], + exports: [ORPC_MODULE_CONFIG_SYMBOL, ImplementInterceptor], + global: true, + } + } } From 6a3ea48571ae66d709c1cff6d97c5c6b5883c44f Mon Sep 17 00:00:00 2001 From: unnoq Date: Fri, 22 Aug 2025 10:31:40 +0700 Subject: [PATCH 2/2] imports --- packages/nest/src/module.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nest/src/module.ts b/packages/nest/src/module.ts index e5d37aa80..eb78e234d 100644 --- a/packages/nest/src/module.ts +++ b/packages/nest/src/module.ts @@ -3,7 +3,6 @@ import type { AnySchema } from '@orpc/contract' import type { CreateProcedureClientOptions } from '@orpc/server' import type { SendStandardResponseOptions } from '@orpc/standard-server-node' import { Module } from '@nestjs/common' -import { toArray } from '@orpc/shared' import { ImplementInterceptor } from './implement' export const ORPC_MODULE_CONFIG_SYMBOL = Symbol('ORPC_MODULE_CONFIG') @@ -31,16 +30,18 @@ export class ORPCModule { } static forRootAsync(options: { + imports?: any[] useFactory: (...args: any[]) => Promise | ORPCModuleConfig inject?: any[] }): DynamicModule { return { module: ORPCModule, + imports: options.imports, providers: [ { provide: ORPC_MODULE_CONFIG_SYMBOL, useFactory: options.useFactory, - inject: toArray(options.inject), + inject: options.inject, }, ImplementInterceptor, ],