From c031e425239a5fdd959e9c56ceef96b4847d9a90 Mon Sep 17 00:00:00 2001 From: Dinh Le Date: Wed, 1 Jul 2026 19:53:08 +0700 Subject: [PATCH 1/2] feat(nest): rename toStandardLazyRequest to toNestStandardLazyRequest with additional params --- apps/content/docs/integrations/nest.md | 14 +++++++++---- packages/nest/src/implement.test.ts | 29 ++++++++++++++++---------- packages/nest/src/implement.ts | 18 ++++++++-------- packages/nest/src/module.ts | 11 ++++++++-- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/apps/content/docs/integrations/nest.md b/apps/content/docs/integrations/nest.md index caa49cef4..15e3603b1 100644 --- a/apps/content/docs/integrations/nest.md +++ b/apps/content/docs/integrations/nest.md @@ -279,16 +279,22 @@ Configure how [event iterators](/docs/event-iterator) are streamed to the client export class AppModule {} ``` -### `toStandardLazyRequest` option +### `toNestStandardLazyRequest` option -By default, `@orpc/nest` supports the Express and Fastify adapters. If you use another adapter, you may need to customize how a NestJS request is converted into a standard request. For details, see [Standard Server](http://standardserver.dev/). +By default, `@orpc/nest` supports the Express and Fastify adapters. If you use another adapter, you may need to customize how a NestJS request is converted into a standard request (including additional params). For details, see [Standard Server](http://standardserver.dev/). ```ts +import { NestStandardLazyRequest } from '@orpc/nest' +import { toStandardLazyRequest } from '@standardserver/fetch' + @Module({ imports: [ ORPCModule.forRoot({ - toStandardLazyRequest: (req, res) => { - // your custom implementation + toNestStandardLazyRequest: (req, res) => { + // example Hono platform support + const standardRequest: NestStandardLazyRequest = toStandardLazyRequest(req.raw) + standardRequest.params = req.params + return standardRequest }, }), ], diff --git a/packages/nest/src/implement.test.ts b/packages/nest/src/implement.test.ts index 634de850e..4c61c86c7 100644 --- a/packages/nest/src/implement.test.ts +++ b/packages/nest/src/implement.test.ts @@ -1,7 +1,7 @@ import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common' -import type { StandardLazyRequest } from '@standardserver/core' import type { Request as ExpressRequest } from 'express' import type { FastifyReply } from 'fastify' +import type { NestStandardLazyRequest } from './module' import { Buffer } from 'node:buffer' import FastifyCookie from '@fastify/cookie' import { Controller, HttpException, Req, Res, StreamableFile } from '@nestjs/common' @@ -907,9 +907,10 @@ describe('compatibility', () => { expect(res.status).toBe(200) }) - it('can custom request parser with toStandardLazyRequest option', async () => { + it('can custom request parser with toNestStandardLazyRequest option', async () => { const contract = oc.meta(openapi({ - path: '/parser', + path: '/parser/{param}', + inputStructure: 'detailed', })) const handler = vi.fn(({ input }) => input) @@ -922,18 +923,19 @@ describe('compatibility', () => { } } - const toStandardLazyRequest = vi.fn(() => ({ + const toNestStandardLazyRequest = vi.fn(() => ({ url: '/test', method: 'POST', headers: {}, resolveBody: async () => '__OVERRIDED__', - } satisfies StandardLazyRequest)) + params: { param: '__PARAM__' }, + } satisfies NestStandardLazyRequest)) const moduleRef = await Test.createTestingModule({ controllers: [ImplController], imports: [ ORPCModule.forRoot({ - toStandardLazyRequest, + toNestStandardLazyRequest, }), ], }).compile() @@ -941,14 +943,19 @@ describe('compatibility', () => { const app = moduleRef.createNestApplication() await app.init() - const res = await supertest(app.getHttpServer()).post('/parser') + const res = await supertest(app.getHttpServer()).post('/parser/value') expect(res.statusCode).toEqual(200) - expect(res.body).toEqual('__OVERRIDED__') + expect(res.body).toMatchObject({ + body: '__OVERRIDED__', + params: { + param: '__PARAM__', + }, + }) - expect(toStandardLazyRequest).toHaveBeenCalledTimes(1) - expect(toStandardLazyRequest).toHaveBeenCalledWith( - expect.objectContaining({ url: '/parser' }), + expect(toNestStandardLazyRequest).toHaveBeenCalledTimes(1) + expect(toNestStandardLazyRequest).toHaveBeenCalledWith( + expect.objectContaining({ url: '/parser/value' }), expect.objectContaining({ end: expect.any(Function) }), ) }) diff --git a/packages/nest/src/implement.ts b/packages/nest/src/implement.ts index 76fe337bd..209a44fd8 100644 --- a/packages/nest/src/implement.ts +++ b/packages/nest/src/implement.ts @@ -6,7 +6,7 @@ import type { StandardBodyHint } from '@standardserver/core' import type { Request as ExpressRequest, Response as ExpressResponse } from 'express' import type { FastifyReply, FastifyRequest } from 'fastify' import type { Observable } from 'rxjs' -import type { ORPCModuleConfig } from './module' +import type { NestStandardLazyRequest, ORPCModuleConfig } from './module' import { Readable } from 'node:stream' import { applyDecorators, Delete, Get, Head, HttpCode, HttpException, Inject, Injectable, Optional, Options, Patch, Post, Put, StreamableFile, UseInterceptors } from '@nestjs/common' import { HttpAdapterHost } from '@nestjs/core' @@ -94,7 +94,7 @@ export function Implement( export class ImplementInterceptor implements NestInterceptor { private readonly config: ORPCModuleConfig private readonly codec: OpenAPIHandlerCodecCore - private readonly toStandardLazyRequest: Exclude + private readonly toNestStandardLazyRequest: Exclude private readonly httpAdapterHost: HttpAdapterHost constructor( @@ -106,8 +106,8 @@ export class ImplementInterceptor implements NestInterceptor { this.httpAdapterHost = httpAdapterHost this.codec = new OpenAPIHandlerCodecCore(this.config) - this.toStandardLazyRequest = this.config.toStandardLazyRequest ?? ((req: ExpressRequest | FastifyRequest, res: ExpressResponse | FastifyReply) => { - const standardRequest = toStandardLazyRequest( + this.toNestStandardLazyRequest = this.config.toNestStandardLazyRequest ?? ((req: ExpressRequest | FastifyRequest, res: ExpressResponse | FastifyReply) => { + const standardRequest: NestStandardLazyRequest = toStandardLazyRequest( 'raw' in req ? req.raw : req, 'raw' in res ? res.raw : res, ) @@ -117,6 +117,8 @@ export class ImplementInterceptor implements NestInterceptor { standardRequest.resolveBody = () => Promise.resolve(req.body) } + standardRequest.params = req.params as NestStandardLazyRequest['params'] + return standardRequest }) } @@ -135,7 +137,7 @@ export class ImplementInterceptor implements NestInterceptor { const req: ExpressRequest | FastifyRequest = ctx.switchToHttp().getRequest() const res: ExpressResponse | FastifyReply = ctx.switchToHttp().getResponse() - const standardRequest = this.toStandardLazyRequest(req, res) + const standardRequest = this.toNestStandardLazyRequest(req, res) const handler = new StandardHandler({ resolveProcedure: () => Promise.resolve({ @@ -143,7 +145,7 @@ export class ImplementInterceptor implements NestInterceptor { procedure, decodeInput: () => this.codec.decodeInput({ procedure, - params: toORPCOpenAPIParams(procedure, req.params as NestParams), + params: toORPCOpenAPIParams(procedure, standardRequest.params), }, standardRequest), }), encodeError: this.codec.encodeError.bind(this.codec), @@ -248,9 +250,7 @@ function flattenParamValue(value: undefined | string | string[]): undefined | st return Array.isArray(value) ? value.join('/') : value } -type NestParams = Record - -function toORPCOpenAPIParams(contract: AnyProcedureContract, params: NestParams | undefined): undefined | Record { +function toORPCOpenAPIParams(contract: AnyProcedureContract, params: NestStandardLazyRequest['params']): undefined | Record { const meta = getOpenAPIMeta(contract) /* c8 ignore start - there cases almost never happen only for type guard purpose */ diff --git a/packages/nest/src/module.ts b/packages/nest/src/module.ts index be19e7130..0e7ce16f5 100644 --- a/packages/nest/src/module.ts +++ b/packages/nest/src/module.ts @@ -9,16 +9,23 @@ import { ImplementInterceptor } from './implement' export const ORPC_MODULE_CONFIG_SYMBOL = Symbol.for('ORPC_NEST_MODULE_CONFIG') +export interface NestStandardLazyRequest extends StandardLazyRequest { + /** + * Route parameters extracted from the request path. + */ + params?: undefined | Record +} + export type ORPCModuleConfig = & OpenAPIHandlerCodecCoreOptions & StandardHandlerOptions & (object extends DefaultInitialContext ? { context?: DefaultInitialContext } : { context: DefaultInitialContext }) & { /** - * Customize how convert next.js req and res to StandardLazyRequest, + * Customize how convert nestjs req and res to StandardLazyRequest, * You might need define this if you not using express or fastify adapters */ - toStandardLazyRequest?: undefined | ((req: any, res: any) => StandardLazyRequest) + toNestStandardLazyRequest?: undefined | ((req: any, res: any) => NestStandardLazyRequest) /** * Options for how to convert the Standard Response to a Nest Response (returning value), like event iterator options, etc. From db705c7a0cd53cf573025ad333e0b9bccbd747b8 Mon Sep 17 00:00:00 2001 From: Dinh Le Date: Wed, 1 Jul 2026 20:00:46 +0700 Subject: [PATCH 2/2] improve comments --- packages/nest/src/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nest/src/module.ts b/packages/nest/src/module.ts index 0e7ce16f5..fe2e25d11 100644 --- a/packages/nest/src/module.ts +++ b/packages/nest/src/module.ts @@ -22,8 +22,8 @@ export type ORPCModuleConfig & (object extends DefaultInitialContext ? { context?: DefaultInitialContext } : { context: DefaultInitialContext }) & { /** - * Customize how convert nestjs req and res to StandardLazyRequest, - * You might need define this if you not using express or fastify adapters + * Customize how to convert NestJS `req` and `res` to {@link NestStandardLazyRequest}. + * You might need to define this if you are not using express or fastify adapters. */ toNestStandardLazyRequest?: undefined | ((req: any, res: any) => NestStandardLazyRequest)