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..eb78e234d 100644 --- a/packages/nest/src/module.ts +++ b/packages/nest/src/module.ts @@ -28,4 +28,25 @@ export class ORPCModule { global: true, } } + + 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: options.inject, + }, + ImplementInterceptor, + ], + exports: [ORPC_MODULE_CONFIG_SYMBOL, ImplementInterceptor], + global: true, + } + } }