11import { getOriginalFunction , markFunctionWrapped } from '@sentry/core' ;
22import type { WrappedFunction } from '@sentry/core' ;
3- import type { Env , Hono , MiddlewareHandler } from 'hono' ;
3+ import type { Hono , MiddlewareHandler } from 'hono' ;
4+ import { Hono as HonoClass } from 'hono' ;
45import { wrapMiddlewareWithSpan } from './wrapMiddlewareSpan' ;
56
67interface HonoRoute {
@@ -15,18 +16,20 @@ interface HonoBaseProto {
1516}
1617
1718/**
18- * Patches `HonoBase.prototype.route` so that when a sub-app is mounted via `app.route('/prefix', subApp)`, its middleware handlers
19- * are retroactively wrapped in Sentry spans before the parent copies them.
19+ * Patches `route()` on the Hono base prototype once, globally.
2020 *
21- * `route` lives on the prototype (unlike `use` which is a class field)
21+ * Wraps sub-app middleware at mount time so that `app.route('/prefix', subApp)` is traced.
22+ * Idempotent: safe to call multiple times.
2223 */
23- export function patchRoute < E extends Env > ( app : Hono < E > ) : void {
24- const honoBaseProto = Object . getPrototypeOf ( Object . getPrototypeOf ( app ) ) as HonoBaseProto ;
24+ export function installRouteHookOnPrototype ( ) : void {
25+ // `route` is on the base prototype, not the concrete subclass, walk up one level
26+ const honoBaseProto = Object . getPrototypeOf ( HonoClass . prototype ) as HonoBaseProto ;
2527 if ( ! honoBaseProto || typeof honoBaseProto ?. route !== 'function' ) {
2628 return ;
2729 }
2830
29- if ( getOriginalFunction ( honoBaseProto . route as WrappedFunction ) ) {
31+ // Already patched: return
32+ if ( getOriginalFunction ( honoBaseProto . route as unknown as WrappedFunction ) ) {
3033 return ;
3134 }
3235
@@ -45,18 +48,13 @@ export function patchRoute<E extends Env>(app: Hono<E>): void {
4548}
4649
4750/**
48- * Figures out which handlers in a sub-app's flat routes array are middleware ( and should get a span), then wraps them.
51+ * Identifies middleware handlers in a sub-app's flat routes array and wraps them in spans .
4952 *
50- * The challenge: Hono stores every handler as a plain { method, path, handler } entry. There is no "isMiddleware" flag.
51- * Two heuristics identify middleware:
52- *
53- * 1. Position within a group. `app.get('/path', mw, handler)` produces two entries with the same method+path.
54- * All but the last one must be middleware, because only middleware calls `next()` to pass control to the next handler.
55- *
56- * 2. Function arity (# of params) for method 'ALL'. Both `.use()` and `.all()` store their handlers under method 'ALL',
57- * so we can't use position alone to tell them apart when one is the last (or only) entry in its group.
58- * The deciding factor: Hono's `.use()` only accepts `(context, next)` (handlers with 2+ params). While `.all()` route
59- * handlers typically only accept `(context)`.
53+ * Heuristics (since Hono has no "isMiddleware" flag):
54+ * 1. Position: `app.get('/path', mw, handler)` produces entries with the same method+path.
55+ * All but the LAST are middleware (they call `next()`).
56+ * 2. Arity (# of params) for method 'ALL': `.use()` handlers always have 2+ params (context, next),
57+ * while `.all()` route handlers typically have 1 (`context` only).
6058 * See: https://github.com/honojs/hono/blob/18fe604c8cefc2628240651b1af219692e1918c1/src/hono-base.ts#L156-L168
6159 */
6260export function wrapSubAppMiddleware ( routes : HonoRoute [ ] ) : void {
0 commit comments