Skip to content

fix(nest): apply method-level decorators to router-contract @Implement routes - #1776

Merged
dinwwwh merged 5 commits into
middleapi:mainfrom
dinwwwh:claude/implement-guards-bypass-ffbdfe
Aug 4, 2026
Merged

fix(nest): apply method-level decorators to router-contract @Implement routes#1776
dinwwwh merged 5 commits into
middleapi:mainfrom
dinwwwh:claude/implement-guards-bypass-ffbdfe

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 4, 2026

Copy link
Copy Markdown
Member

Method-level NestJS enhancers (@UseGuards, @UsePipes, @UseFilters, @SetMetadata, ...) on a router-contract @Implement method never reached the synthesized route handlers — guards silently never ran, leaving those routes unprotected regardless of decorator order. Enhancer metadata now reaches every synthesized route, decorator placement around @Implement no longer matters for whether enhancers apply, and interceptor execution order follows decorator order exactly like on plain NestJS methods.

Fixes

  • Synthesized methods inherit from the original method via the prototype chain, so guards, pipes, filters, and @SetMetadata resolve through NestJS's Reflect.getMetadata lookups and now run on every route, including nested routers.
  • ImplementInterceptor is registered at the decorated method level, so the interceptor list carries user interceptors and ImplementInterceptor in native decorator-evaluation order: interceptors below @Implement observe the encoded response, interceptors above it run inside and observe the raw implemented procedure — identical between single-procedure and router-contract implementations.
  • The router branch no longer recurses through Implement itself: routing and status decorators are applied directly to synthesized methods, deferred (with the method-name-keyed metadata copies) until the whole decorator stack has run, so late-running decorators are picked up and route metadata is always written last.
  • The documented ordering restriction is gone; the docs warning is replaced with a note that decorators combine with @Implement in any order and that execution order follows decorator order.

Testing

  • Header-based AuthGuard e2e test: requests without the token are rejected (403) and with it succeed (200) on all synthesized routes including a nested procedure, in both decorator orders. Before the fix the guard never executed.
  • Interceptor-order e2e tests for both single-procedure and router-contract @Implement, in both decorator orders, asserting what the user interceptor observes (encoded response vs raw procedure).
  • The conflict-method-names test boots a real app: routes serve correctly, @Req() injection works on synthesized methods, and @SetMetadata from both sides of @Implement is visible via Reflector on ctx.getHandler() at runtime.

dinwwwh added 2 commits August 4, 2026 08:18
…methods

NestJS method-level enhancers (@UseGuards, @UsePipes, @UseFilters,
@SetMetadata, ...) store metadata on the method's function object, but
router-contract @implement registers synthesized functions as routes, so
guards on the decorated method silently never ran. Synthesized methods
now inherit from the original method via the prototype chain, which
NestJS's Reflect.getMetadata lookups traverse.
…ement

Decorators evaluate bottom-up, so decorators placed above @implement run
after it and their metadata was invisible to the synthesized methods.
Metadata copying and the ImplementInterceptor registration are now
deferred with queueMicrotask, which fires after the whole decorator
stack has been applied but before NestJS reads any handler metadata
during app initialization. The documented ordering restriction is gone.
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
orpc Ready Ready Preview Aug 4, 2026 3:29am

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1776

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1776

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@1776

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1776

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@1776

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1776

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@1776

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@1776

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@1776

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1776

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1776

@orpc/next

npm i https://pkg.pr.new/@orpc/next@1776

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1776

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@1776

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@1776

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@1776

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@1776

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@1776

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1776

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1776

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@1776

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1776

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1776

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1776

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1776

commit: b7ab54c

@codspeed-hq

codspeed-hq Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 25 untouched benchmarks


Comparing dinwwwh:claude/implement-guards-bypass-ffbdfe (b7ab54c) with main (fec4b62)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (5965f87) during the generation of this report, so fec4b62 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The fix is correct, well-verified, and the test coverage is strong.

Reviewed changes — Reviewed the full PR against main (5965f87). Router-contract @Implement now forwards method-level NestJS enhancers to the synthesized route handlers in either decorator order.

  • packages/nest/src/implement.ts — each synthesized handler inherits from the original router method via Object.setPrototypeOf, so Nest's function-stored metadata (@UseGuards, @UsePipes, @UseFilters, @SetMetadata, interceptor/param metadata) resolves through Reflect.getMetadata(key, getHandler()). The method-metadata copy and the ImplementInterceptor registration are deferred with queueMicrotask, making decorator placement around @Implement irrelevant and guaranteeing user interceptors always wrap outside ImplementInterceptor.
  • packages/nest/src/implement.test.ts — the conflict-method-names test now boots a real Nest app; new guard and interceptor e2e tests exercise both decorator orders (plus a nested procedure), and runtime @Req injection / @SetMetadata visibility via Reflector on ctx.getHandler() are asserted. All of these genuinely fail without the fix.
  • apps/content/docs/integrations/nest.md — the ordering restriction warning is replaced with a note that decorators combine with @Implement in any order.

ℹ️ Ordering contract

The fix is sound today, but its correctness depends on the queueMicrotask-registered metadata copy and ImplementInterceptor running before Nest's route metadata scan. This holds for every realistic bootstrap (await NestFactory.create(...), await app.init(), .compile()), and all 74 tests pass — no action needed. It is a load-bearing ordering assumption that a future synchronous metadata read would silently break (guards would vanish again, reverting the security fix), so it is worth keeping in mind if this area is touched later.

Pullfrog  | View workflow run | Using openrouter/deepseek/deepseek-v4-flash-0731 (free via Pullfrog for OSS) | 𝕏

…order

ImplementInterceptor is now registered at the decorated method level, so
the original method's function object carries user interceptors and
ImplementInterceptor in native decorator-evaluation order, and synthesized
methods inherit the full ordered list through the prototype chain. The
router branch no longer recurses through Implement itself: routing and
status decorators are applied directly to synthesized methods, deferred
until after the metadata copies so route metadata is always written last.
Interceptors below @implement observe the encoded response; interceptors
above it run inside and observe the raw implemented procedure - matching
plain NestJS methods, and identical between single-procedure and
router-contract implementations.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The interceptor-order rework is correct and well-tested.

Reviewed changes — Reviewed the delta since the prior review (8f8de0ce): the new commit reworks interceptor ordering around @Implement so it follows decorator order.

  • packages/nest/src/implement.tsImplementInterceptor is now registered once on the decorated method's descriptor.value instead of being registered per synthesized leaf. For router-contract methods this merges user interceptors and ImplementInterceptor into a single order-aware list that every synthesized method inherits through the prototype chain, fixing a latent bug where a leaf's own deferred ImplementInterceptor registration would shadow the user interceptors inherited from the router method.
  • Single-procedure @Implement likewise now registers ImplementInterceptor synchronously (previously deferred), so its position — and whether a user interceptor observes the raw Procedure or the encoded response — follows decorator placement, aligned with the router-contract branch.
  • packages/nest/src/implement.test.ts — added interceptor-order e2e tests for both single-procedure and router-contract @Implement in both decorator orders, asserting what the user interceptor observes (raw Procedure vs encoded response). Extended the conflict-method-names test to boot a real app and assert @SetMetadata from both sides of @Implement is visible via Reflector on ctx.getHandler(). The ordering assertions genuinely discriminate the new behavior.
  • apps/content/docs/integrations/nest.md — the info note now documents that execution order follows decorator order.

All 76 tests pass. The queueMicrotask-ordering contract flagged in the prior review (deferred metadata copy must flush before Nest's asynchronous route scan) is unchanged by this commit, and every realistic bootstrap satisfies it.

Pullfrog  | View workflow run | Using openrouter/deepseek/deepseek-v4-flash-0731 (free via Pullfrog for OSS) | 𝕏

@dinwwwh dinwwwh changed the title fix(nest): apply method-level decorators to router-contract @Implement routes fix(nest): apply method-level decorators to router-contract @Implement routes Aug 4, 2026
@dinwwwh
dinwwwh merged commit a0afe48 into middleapi:main Aug 4, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant