feat(tracing): export wrapHandlerWithTracing for manual handler wrapping#1369
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe tracing plugin now intercepts Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/tracing.ts (1)
91-100: Mutatingroute.datais idempotent but shared — confirm intent.
route.data.handler = wrapEventHandler(...)and the middleware remap mutate the returned route's data object. If~findRoutereturns a reference to an entry inh3["~routes"](the typical radix router behavior), the handler/middleware are replaced in-place. This is safe here becausewrapEventHandler/wrapMiddlewareare idempotent via__traced__, and it mirrors the existingh3.onwrapping (Line 121-122). Worth a one-line comment noting the intentional mutation so a future refactor doesn’t accidentally clone the route and break the idempotence on subsequent requests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tracing.ts` around lines 91 - 100, Add a one-line explanatory comment inside the wrappedFindRoute function above the mutations to route.data (where route.data.handler is set with wrapEventHandler and route.data.middleware is remapped with wrapMiddleware) stating that this in-place mutation is intentional because findRoute returns shared references (e.g., h3["~routes"]) and the wrappers are idempotent via __traced__; mention it mirrors the existing h3.on wrapping behavior (originalFindRoute / h3.on) and warn future maintainers not to clone the route object or change to non-idempotent wrappers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/tracing.ts`:
- Around line 101-108: The property descriptor for the private method
"~findRoute" is currently making it enumerable; update the Object.defineProperty
call for h3 with key "~findRoute" to use enumerable: false (keep configurable
and the getter/setter semantics intact) so the non-enumerable convention for
~-prefixed members is preserved; locate the defineProperty that references h3,
"~findRoute", wrappedFindRoute and originalFindRoute and change only the
enumerable flag to false.
- Around line 90-108: The setter for h3["~findRoute"] mutates the closure
variable originalFindRoute which breaks safe fallback chains and enables
infinite recursion; instead, make originalFindRoute immutable and expose it as a
separate property callers can capture (e.g., h3["~findRouteUnwrapped"] or
h3["~findRouteOriginal"]) and change the setter to not reassign
originalFindRoute (either ignore assignments or store the user-supplied function
in a separate variable that the getter can wrap without overwriting
originalFindRoute); update wrappedFindRoute to call the immutable
originalFindRoute (or the composed wrapper) so fallback chains that capture the
original function remain safe.
---
Nitpick comments:
In `@src/tracing.ts`:
- Around line 91-100: Add a one-line explanatory comment inside the
wrappedFindRoute function above the mutations to route.data (where
route.data.handler is set with wrapEventHandler and route.data.middleware is
remapped with wrapMiddleware) stating that this in-place mutation is intentional
because findRoute returns shared references (e.g., h3["~routes"]) and the
wrappers are idempotent via __traced__; mention it mirrors the existing h3.on
wrapping behavior (originalFindRoute / h3.on) and warn future maintainers not to
clone the route object or change to non-idempotent wrappers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3cb1fcb0-a31c-47be-a055-dbe51d985dd6
📒 Files selected for processing (2)
src/tracing.tstest/tracing.test.ts
There was a problem hiding this comment.
Pull request overview
Adds tracing support for route handlers returned via a custom ~findRoute implementation (e.g., Nitro-style file-based routes) so that “route” tracing spans are emitted even when routes are resolved dynamically at request time.
Changes:
- Wrap
~findRoutein the tracing plugin to trace handlers/middleware returned from dynamic route resolution. - Add a regression test covering Nitro-style file-based routing where routes are not pushed into
~routes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/tracing.ts |
Wrapes ~findRoute results so returned handlers/middleware get tracing instrumentation. |
test/tracing.test.ts |
Adds a test ensuring route traces are emitted for handlers returned only from a custom ~findRoute. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
test/tracing.test.ts (2)
1206-1248: Strong test for the double-wrapping guard.Exactly-3 assertion on
routeAsyncStartsis the right shape — it pins the__traced__short-circuit inwrapEventHandler(src/tracing.ts:61-75) against the memoized-route case from frameworks like nitro.Two minor notes:
- Because
wrappedFindRoutemutatesroute.data.handlerin place (src/tracing.ts context snippet 1), after the first requestcachedRoute.data.handleris replaced with the traced wrapper. An additional assertion thatcachedRoute.data.handler.__traced__ === trueand that it's the same reference across runs would make the non-double-wrap contract explicit rather than inferred from the count.- If the wrapper ever regresses to returning a new wrapper each call, the count would still be 3 (one per request) — the current assertion wouldn't catch handler re-wrapping per se, only per-request duplication. Consider also spying on
wrapEventHandlerbehavior indirectly by asserting handler identity stability:Optional stronger assertion
await run(); await run(); await run(); + + // Handler should be wrapped exactly once and reused across calls. + expect((cachedRoute.data.handler as any).__traced__).toBe(true); await new Promise((resolve) => setTimeout(resolve, 10));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/tracing.test.ts` around lines 1206 - 1248, Add explicit assertions that the memoized route handler is mutated to the traced wrapper and remains the same reference across runs: after the first run assert cachedRoute.data.handler.__traced__ === true and save a reference (e.g., const firstHandler = cachedRoute.data.handler) then after subsequent runs assert cachedRoute.data.handler === firstHandler; this verifies wrapEventHandler/wrappedFindRoute actually short-circuits re-wrapping rather than relying solely on the asyncStart count. Use the existing cachedRoute, tracingPlugin, and wrapEventHandler/wrappedFindRoute behavior to locate where to add these checks in the test.
1160-1204: LGTM — good coverage for nitro-style~findRoute.Test correctly verifies route tracing works when handlers are only exposed via a custom
~findRoute(not pushed into~routes), which is exactly the regression from nitrojs/nitro#4211.One small optional tightening: the test only asserts
routeEvents.length > 0but doesn't verify the handler actually ran / returned its value. Adding an assertion onapp.handler(event)'s resolved value (or onrouteEvents[0].asyncStart?.data.event.url.pathname === "/file-route") would make a regression where the wrapper swallows the handler result more obvious.Also, the comment on Line 1187-1188 referring to "the order documented in the suggested fix" reads as review-context leakage — consider rephrasing to something self-contained like "Apply tracing plugin after
~findRouteis set to verify the getter/setter wrapper picks up the reassigned function."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/tracing.test.ts` around lines 1160 - 1204, Add a concrete assertion that the handler actually executed and returned the expected value and make the comment self-contained: after awaiting app.handler(event) capture its resolved result and assert it equals "file-based response" (or assert routeEvents[0].asyncStart?.data.event.url.pathname === "/file-route") to ensure the wrapper didn't swallow the return value; also change the comment referencing "the order documented in the suggested fix" to a self-contained sentence like "Apply tracing plugin after ~findRoute is set to verify the getter/setter wrapper picks up the reassigned function." Reference H3Core, tracingPlugin, app["~findRoute"], H3Event, and routeEvents in the test to locate the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/tracing.test.ts`:
- Around line 1206-1248: Add explicit assertions that the memoized route handler
is mutated to the traced wrapper and remains the same reference across runs:
after the first run assert cachedRoute.data.handler.__traced__ === true and save
a reference (e.g., const firstHandler = cachedRoute.data.handler) then after
subsequent runs assert cachedRoute.data.handler === firstHandler; this verifies
wrapEventHandler/wrappedFindRoute actually short-circuits re-wrapping rather
than relying solely on the asyncStart count. Use the existing cachedRoute,
tracingPlugin, and wrapEventHandler/wrappedFindRoute behavior to locate where to
add these checks in the test.
- Around line 1160-1204: Add a concrete assertion that the handler actually
executed and returned the expected value and make the comment self-contained:
after awaiting app.handler(event) capture its resolved result and assert it
equals "file-based response" (or assert
routeEvents[0].asyncStart?.data.event.url.pathname === "/file-route") to ensure
the wrapper didn't swallow the return value; also change the comment referencing
"the order documented in the suggested fix" to a self-contained sentence like
"Apply tracing plugin after ~findRoute is set to verify the getter/setter
wrapper picks up the reassigned function." Reference H3Core, tracingPlugin,
app["~findRoute"], H3Event, and routeEvents in the test to locate the changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8f4b6b2d-f990-4370-8df6-fce6e46313f9
📒 Files selected for processing (1)
test/tracing.test.ts
Previously the ~findRoute getter returned a singleton wrapper that delegated to a mutable `originalFindRoute`. Framework code that captures the current ~findRoute and reassigns it to a delegating wrapper (`const prev = app["~findRoute"]; app["~findRoute"] = (e) => prev(e)`) would then infinite-recurse: `prev` pointed at the singleton, which now called back into the new delegator. Build a fresh wrapper closure on every set so captured references stay stable. Adds regression tests for the recursion scenario and for double-wrap avoidance across repeated calls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses PR review feedback: - Keep the ~ prefix convention: the descriptor installed by tracingPlugin was enumerable, leaking ~findRoute into Object.keys/spread/for…in. - Add coverage for ~findRoute assigned *after* the plugin registers (the typical nitro ordering), complementing the existing before-plugin test. - Tighten the nitro-style test to assert the route trace matches the requested pathname instead of only checking event count. - Assert the descriptor stays non-enumerable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/tracing.ts (1)
98-100: Optional: skip reallocatingroute.data.middlewarewhen all entries are already traced.On every request the wrapper unconditionally allocates a fresh array via
.map(wrapMiddleware), even whenroute.datais a cached object whose middleware entries are already__traced__.wrapMiddlewareis a no-op in that case, but the array allocation/assignment still runs per request. Minor and safe to defer.♻️ Proposed micro-optimization
- if (route?.data.middleware) { - route.data.middleware = route.data.middleware.map((m) => wrapMiddleware(m)); + if (route?.data.middleware && route.data.middleware.some((m) => !(m as MaybeTracedMiddleware).__traced__)) { + route.data.middleware = route.data.middleware.map((m) => wrapMiddleware(m)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tracing.ts` around lines 98 - 100, The code currently always reassigns route.data.middleware = route.data.middleware.map((m) => wrapMiddleware(m)); avoid allocating a new array when all middleware are already traced by checking the __traced__ flag first: inspect route.data.middleware (use Array.prototype.some or every) to detect any entry without m.__traced__, and only call .map(wrapMiddleware) and reassign when at least one middleware is not traced; leave route.data.middleware untouched otherwise. Ensure you reference route.data.middleware, wrapMiddleware, and the __traced__ property in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/tracing.ts`:
- Around line 98-100: The code currently always reassigns route.data.middleware
= route.data.middleware.map((m) => wrapMiddleware(m)); avoid allocating a new
array when all middleware are already traced by checking the __traced__ flag
first: inspect route.data.middleware (use Array.prototype.some or every) to
detect any entry without m.__traced__, and only call .map(wrapMiddleware) and
reassign when at least one middleware is not traced; leave route.data.middleware
untouched otherwise. Ensure you reference route.data.middleware, wrapMiddleware,
and the __traced__ property in your change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 16b32424-95da-45d3-b264-2ae24841ef60
📒 Files selected for processing (2)
src/tracing.tstest/tracing.test.ts
…p test - Comment in wrapFindRoute noting the in-place mutation of route.data is intentional (shared references + idempotent wrappers) so a future refactor doesn't accidentally clone the route. - Double-wrap test now pins handler reference stability and the __traced__ flag explicitly, not just the per-request event count. - Drop review-context phrasing from the after-plugin test comment. Skipped: micro-opt to avoid reallocating route.data.middleware when all entries are already traced (wrapMiddleware already no-ops on __traced__; the extra .some() check is a wash) and a return-value assertion in the nitro-style test (already indirectly covered via tracePromise emission). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the handler path's in-place mutation pattern so repeated resolves of a cached route.data don't allocate a fresh middleware array per request. wrapMiddleware remains idempotent via __traced__, so the loop is a cheap no-op after the first call. Add a test pinning array and entry identity across requests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
findRoute with tracing wrapperfindRoute with tracing wrapper
…~findRoute patching Per maintainer feedback, move ~findRoute wrapping out of the tracing plugin and into a standalone exported helper. Frameworks like nitro can opt in by calling wrapFindRouteWithTracing() on their own ~findRoute function, keeping h3's plugin lean and the runtime cost explicit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export a simpler `wrapHandlerWithTracing` that wraps a single EventHandler at init/codegen time instead of wrapping the entire ~findRoute function at runtime. This eliminates per-request overhead — tracing is applied once during build, not on every route lookup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
findRoute with tracing wrapperwrapHandlerWithTracing for manual handler wrapping
Summary
Exports
wrapHandlerWithTracingfromh3/tracing— a utility that wraps a singleEventHandlerwith diagnostics_channel tracing (type: "route"). Designed to be called once at init/codegen time, not per request.This enables frameworks like Nitro to wrap file-based route handlers at build time in their virtual routing module, ensuring route traces are emitted for handlers resolved via custom
~findRouteimplementations — without any per-request overhead.API
Companion PR
Test plan
🤖 Generated with Claude Code