Repository navigation
Conversation
`@Implement` wrote `:<name>` for every path param and copied literal text
verbatim, so on both the Express and Fastify adapters:
- params like `{user-id}` ended at the `-` (`GET /users/42` was a 404 while
`GET /users/42-id` matched with `{ user: '42' }`), and digit-leading names
like `{1st}` made Express throw at startup;
- a literal `:` or `*` (e.g. `/models:predict`) was parsed as route syntax,
so `POST /modelsXYZ` was handled with input `{ predict: 'XYZ' }`.
Build the Nest route with a single `toNestRoute` helper shared by route
registration and param decoding. Params whose names are not identifiers are
registered under generated keys (avoiding names already used in the path)
and mapped back to their original names when decoding; identifier-safe
names and controller-level params are unchanged. Literal `:` / `*` can't be
escaped portably (Express needs `\:`, Fastify `::`, which Express rejects),
so `@Implement` now throws a descriptive TypeError at decoration time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wd5XPg3bL7UKDGdi4akyu6
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
toNestRoutehelper — replacestoNestPattern, returning{ pattern, paramNames }; both route registration and param decoding call it, so they cannot drift.- Non-identifier param names — names failing
/^[a-z_]\w*$/iare registered under a generatedorpc_p<index>key (with_prefix on collision) and mapped back to the original name during decoding; identifier-safe names stay:<name>. - Literal
:/*rejection —toNestLiteralthrows aTypeErrorat decoration time for procedure and router contracts when the merged path (prefix included) contains a literal:or*. - Decode rewrite —
toORPCOpenAPIParamsmaps generated keys and the rest key (*Fastify /pathExpress) back to original names, passing controller-level keys through unchanged. - Docs + JSDoc — warning on the literal
:/*limitation and note on generatedreq.paramsnames inintegrations/nest.mdx, plus the@Implementremark. - Tests — throw cases for both contract kinds and cross-adapter routing cases for
{user-id},{1st}, and the mixed-prefix collision path.
I verified the decode/route logic against getDynamicPathParams (packages/openapi/src/utils.ts, name regex [\w-]+) and mergeHttpPath, and ran pnpm vitest run packages/nest (110 passed), type:check, and eslint on the changed files — all clean. The collision loop is sound: generated keys are unique per index and the _-prefix loop terminates. The intended limitations called out in the description (multiple {+rest} params, non-trailing {+rest}) are pre-existing and out of scope.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 3824720 | Commit Preview URL Branch Preview URL |
Oct 12 2026, 10:11 AM |
Both call sites merged `meta.prefix` before calling `toNestRoute`. Since the generated param keys are indexed across prefix + path, the decorator and the param decoder must see the exact same full path; doing the merge in one place keeps them from drifting. Also clarify the docs note on `req.params`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd5XPg3bL7UKDGdi4akyu6
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The follow-up commit (3824720) is a behavior-preserving refactor of the route helper — routing and param decoding are unchanged.
- Prefix merged inside
toNestRoute— the helper now takes(path, prefix?)and merges internally, replacing the duplicatedmeta.prefix ? mergeHttpPath(meta.prefix, meta.path) : meta.pathat both call sites (toNestRouteDecoratorandtoORPCOpenAPIParams). Argument order tomergeHttpPathis preserved, so the resulting pattern andparamNamesare identical. - JSDoc + docs wording — the helper doc now mentions the optional prefix, and the
req.paramsinfo callout innest.mdxwas reworded so guards/pipes are described as seeing the generated names.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Mnigos
left a comment
There was a problem hiding this comment.
Tried the pkg.pr.new build on Nest 11.2.7 with Express and with a Hono adapter, fix works on both. Three small things:
- On adapters that already accept
:user-idas a label (Hono does), guards used to seereq.params['user-id']and now seeorpc_p0. Worth noting in the docs that the rename happens regardless of adapter. /dot/{user.id}isn't a valid param, so{user.id}goes through literally and Express treats{…}as an optional group:/dot/42is 404,/dot/and/dot/user.idare 200. IftoNestLiteralrejects:and*, it could reject stray{/}too.toNestRoutenow runs on every request intoORPCOpenAPIParams. A per-contractWeakMapcache would keep the hot path cheap.

Problem
@Implementturned every{param}into:<name>and copied literal text unchanged. That misroutes two kinds of path on both the Express and Fastify adapters (reproduced on Nest 12)::nameat the first-, so for/users/{user-id},GET /users/42returned 404, whileGET /users/42-idmatched with params{ user: '42' }. A name starting with a digit ({1st}) made Express throw at startup.:or*./models:predictwas registered as/modelsplus a param namedpredict, soPOST /modelsXYZran with input{ predict: 'XYZ' }.Changes
toNestRoute(path), returns{ pattern, paramNames }. BothtoNestRouteDecoratorandtoORPCOpenAPIParamsuse it, so building the route and decoding its params can't drift apart./^[a-z_]\w*$/igets a generated key (orpc_p<index>), with_prepended if a param in the same path already has that name. Decoding maps the key back to the original name.req.params.idkeep working. Controller-level params like@Controller('/:tenant')and other unknown keys pass through unchanged. The rest param is still read frompath(Express) or*(Fastify).:or*anywhere in the full path (prefix included, outside a param) now throws a descriptiveTypeErrorwhen@Implementis applied, for procedure and router contracts alike. It can't be escaped portably: Express needs\:, Fastify needs::, and Nest's Express adapter throws on::.integrations/nest.mdxnow has a warning about the literal:/*limitation and a note that non-identifier params appear under generated names inreq.params. The@ImplementJSDoc mentions the limitation too.Tests
Added to
packages/nest/src/implement.test.ts. The routing cases run on both adapters.{user-id}routes correctly:/users/42gives{ 'user-id': '42' }and/users/42-idgives{ 'user-id': '42-id' }.{1st}also routes.orpc_p0and two non-identifier params maps every value back to the right name.@Controller('/:tenant')still passestenantthrough.:or*(plain, inside a non-param{user:id}segment, or in the prefix) throws for both procedure and router contracts.All 8 new tests fail on
main; with{1st}removed, since it crashes Express at startup. They pass with this change. Removing the collision loop makes the mixed-params test fail.Validation:
pnpm vitest run packages/nest(110 passed),pnpm --filter @orpc/nest type:check,pnpm lintandpnpm docs:validateall pass.Note
Another task fixes non-trailing
{+rest}params in the same function, which this PR replaces (toNestPattern→toNestRoute). I couldn't find an open PR for it, so whichever lands second needs a rebase. The rest-param branch insidetoNestRouteis where that change belongs. For reference, a path with more than one{+rest}param already fails at startup on both adapters, before and after this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01Wd5XPg3bL7UKDGdi4akyu6
Generated by Claude Code