Skip to content

Commit c5d0b6a

Browse files
committed
feat(nest): add async configuration support for ORPCModule
1 parent 1a17b94 commit c5d0b6a

File tree

3 files changed

+83
-7
lines changed

3 files changed

+83
-7
lines changed

apps/content/docs/openapi/integrations/implement-contract-in-nest.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,23 @@ oRPC will use NestJS parsed body when it's available, and only use the oRPC pars
224224
Configure the `@orpc/nest` module by importing `ORPCModule` in your NestJS application:
225225

226226
```ts
227+
import { REQUEST } from '@nestjs/core'
227228
import { onError, ORPCModule } from '@orpc/nest'
229+
import { Request } from 'express' // if you use express adapter
228230
229231
@Module({
230232
imports: [
231-
ORPCModule.forRoot({
232-
interceptors: [
233-
onError((error) => {
234-
console.error(error)
235-
}),
236-
],
237-
eventIteratorKeepAliveInterval: 5000, // 5 seconds
233+
ORPCModule.forRootAsync({ // or .forRoot
234+
useFactory: (request: Request) => ({
235+
interceptors: [
236+
onError((error) => {
237+
console.error(error)
238+
}),
239+
],
240+
context: { request }, // oRPC context, accessible from middlewares, etc.
241+
eventIteratorKeepAliveInterval: 5000, // 5 seconds
242+
}),
243+
inject: [REQUEST],
238244
}),
239245
],
240246
})

packages/nest/src/implement.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { NodeHttpRequest } from '@orpc/standard-server-node'
2+
import type { Request } from 'express'
23
import { Controller, Req } from '@nestjs/common'
4+
import { REQUEST } from '@nestjs/core'
35
import { FastifyAdapter } from '@nestjs/platform-fastify'
46
import { Test } from '@nestjs/testing'
57
import { oc, ORPCError } from '@orpc/contract'
@@ -412,4 +414,52 @@ describe('@Implement', async () => {
412414
eventIteratorKeepAliveComment: '__TEST__',
413415
}))
414416
})
417+
418+
it('works with ORPCModule.forRootAsync', async () => {
419+
const interceptor = vi.fn(({ next }) => next())
420+
const moduleRef = await Test.createTestingModule({
421+
imports: [
422+
ORPCModule.forRootAsync({
423+
useFactory: async (request: Request) => ({
424+
interceptors: [interceptor],
425+
eventIteratorKeepAliveComment: '__TEST__',
426+
context: {
427+
request,
428+
},
429+
}),
430+
inject: [REQUEST],
431+
}),
432+
],
433+
controllers: [ImplProcedureController],
434+
}).compile()
435+
436+
const app = moduleRef.createNestApplication()
437+
await app.init()
438+
439+
const httpServer = app.getHttpServer()
440+
441+
const res = await supertest(httpServer)
442+
.post('/ping?param=value&param2[]=value2&param2[]=value3')
443+
.set('x-custom', 'value')
444+
.send({ hello: 'world' })
445+
446+
expect(res.statusCode).toEqual(200)
447+
expect(res.body).toEqual('pong')
448+
449+
expect(interceptor).toHaveBeenCalledTimes(1)
450+
expect(interceptor).toHaveBeenCalledWith(expect.objectContaining({
451+
context: expect.objectContaining({
452+
request: expect.objectContaining({
453+
url: '/ping?param=value&param2[]=value2&param2[]=value3',
454+
headers: expect.objectContaining({
455+
'x-custom': 'value',
456+
}),
457+
}),
458+
}),
459+
}))
460+
expect(sendStandardResponseSpy).toHaveBeenCalledTimes(1)
461+
expect(sendStandardResponseSpy).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({
462+
eventIteratorKeepAliveComment: '__TEST__',
463+
}))
464+
})
415465
})

packages/nest/src/module.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AnySchema } from '@orpc/contract'
33
import type { CreateProcedureClientOptions } from '@orpc/server'
44
import type { SendStandardResponseOptions } from '@orpc/standard-server-node'
55
import { Module } from '@nestjs/common'
6+
import { toArray } from '@orpc/shared'
67
import { ImplementInterceptor } from './implement'
78

89
export const ORPC_MODULE_CONFIG_SYMBOL = Symbol('ORPC_MODULE_CONFIG')
@@ -28,4 +29,23 @@ export class ORPCModule {
2829
global: true,
2930
}
3031
}
32+
33+
static forRootAsync(options: {
34+
useFactory: (...args: any[]) => Promise<ORPCModuleConfig> | ORPCModuleConfig
35+
inject?: any[]
36+
}): DynamicModule {
37+
return {
38+
module: ORPCModule,
39+
providers: [
40+
{
41+
provide: ORPC_MODULE_CONFIG_SYMBOL,
42+
useFactory: options.useFactory,
43+
inject: toArray(options.inject),
44+
},
45+
ImplementInterceptor,
46+
],
47+
exports: [ORPC_MODULE_CONFIG_SYMBOL, ImplementInterceptor],
48+
global: true,
49+
}
50+
}
3151
}

0 commit comments

Comments
 (0)