@@ -1156,47 +1156,38 @@ describe("tracing channels for H3Core instances", () => {
11561156 listener . cleanup ( ) ;
11571157 }
11581158 } ) ;
1159+ } ) ;
11591160
1160- it ( "traces handlers returned from a custom ~findRoute (nitro-style file routes)" , async ( ) => {
1161+ describe ( "wrapFindRouteWithTracing" , ( ) => {
1162+ it ( "wraps route handlers returned by findRoute" , async ( ) => {
11611163 const listener = createTracingListener ( ) ;
11621164 const { H3Core } = await import ( "../src/h3.ts" ) ;
1163- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1165+ const { wrapFindRouteWithTracing } = await import ( "../src/tracing.ts" ) ;
11641166 const { H3Event } = await import ( "../src/event.ts" ) ;
11651167
11661168 try {
11671169 const app = new H3Core ( ) ;
11681170 const routeHandler = ( ) => "file-based response" ;
11691171
1170- // Simulate a framework that resolves routes at request time and never
1171- // pushes them into app["~routes"]. The handler only exists inside the
1172- // custom ~findRoute implementation.
1173- app [ "~findRoute" ] = ( event : any ) => {
1172+ const baseFindRoute = ( event : any ) => {
11741173 if ( event . url . pathname === "/file-route" && event . req . method === "GET" ) {
11751174 return {
1176- data : {
1177- method : "GET" ,
1178- route : "/file-route" ,
1179- handler : routeHandler ,
1180- } ,
1175+ data : { method : "GET" , route : "/file-route" , handler : routeHandler } ,
11811176 params : { } ,
11821177 } ;
11831178 }
11841179 return undefined ;
11851180 } ;
11861181
1187- // Apply tracing plugin after ~findRoute is set to verify the wrapper
1188- // picks up the already-installed resolver.
1189- tracingPlugin ( ) ( app as any ) ;
1182+ app [ "~findRoute" ] = wrapFindRouteWithTracing ( baseFindRoute as any ) ;
11901183
11911184 const request = new Request ( "http://localhost/file-route" , { method : "GET" } ) ;
11921185 const event = new H3Event ( request , undefined , app as any ) ;
11931186
11941187 await app . handler ( event ) ;
1195-
11961188 await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
11971189
11981190 const routeEvents = listener . events . filter ( ( e ) => e . asyncStart ?. data . type === "route" ) ;
1199-
12001191 expect ( routeEvents . some ( ( e ) => e . asyncStart ?. data . event . url . pathname === "/file-route" ) ) . toBe (
12011192 true ,
12021193 ) ;
@@ -1205,124 +1196,22 @@ describe("tracing channels for H3Core instances", () => {
12051196 }
12061197 } ) ;
12071198
1208- it ( "traces handlers when ~findRoute is reassigned after tracingPlugin is applied" , async ( ) => {
1209- const listener = createTracingListener ( ) ;
1210- const { H3Core } = await import ( "../src/h3.ts" ) ;
1211- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1212- const { H3Event } = await import ( "../src/event.ts" ) ;
1213-
1214- try {
1215- const app = new H3Core ( ) ;
1216- const routeHandler = ( ) => "late-bound response" ;
1217-
1218- // Apply plugin BEFORE the framework wires up routing (typical nitro
1219- // order: plugins register, then file-based routing installs ~findRoute).
1220- tracingPlugin ( ) ( app as any ) ;
1221-
1222- app [ "~findRoute" ] = ( event : any ) => {
1223- if ( event . url . pathname === "/late-route" && event . req . method === "GET" ) {
1224- return {
1225- data : {
1226- method : "GET" ,
1227- route : "/late-route" ,
1228- handler : routeHandler ,
1229- } ,
1230- params : { } ,
1231- } ;
1232- }
1233- return undefined ;
1234- } ;
1235-
1236- const request = new Request ( "http://localhost/late-route" , { method : "GET" } ) ;
1237- const event = new H3Event ( request , undefined , app as any ) ;
1238-
1239- await app . handler ( event ) ;
1240- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
1241-
1242- const routeEvents = listener . events . filter ( ( e ) => e . asyncStart ?. data . type === "route" ) ;
1243- expect ( routeEvents . some ( ( e ) => e . asyncStart ?. data . event . url . pathname === "/late-route" ) ) . toBe (
1244- true ,
1245- ) ;
1246- } finally {
1247- listener . cleanup ( ) ;
1248- }
1249- } ) ;
1250-
1251- it ( "keeps ~findRoute non-enumerable after tracingPlugin is applied" , async ( ) => {
1252- const { H3Core } = await import ( "../src/h3.ts" ) ;
1253- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1254-
1255- const app = new H3Core ( ) ;
1256- tracingPlugin ( ) ( app as any ) ;
1257-
1258- expect ( Object . keys ( app ) ) . not . toContain ( "~findRoute" ) ;
1259- const descriptor = Object . getOwnPropertyDescriptor ( app , "~findRoute" ) ;
1260- expect ( descriptor ?. enumerable ) . toBe ( false ) ;
1261- } ) ;
1262-
1263- it ( "does not recurse when a framework wraps ~findRoute via captured reference" , async ( ) => {
1264- const listener = createTracingListener ( ) ;
1265- const { H3Core } = await import ( "../src/h3.ts" ) ;
1266- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1267- const { H3Event } = await import ( "../src/event.ts" ) ;
1268-
1269- try {
1270- const app = new H3Core ( ) ;
1271- const routeHandler = ( ) => "ok" ;
1272-
1273- tracingPlugin ( ) ( app as any ) ;
1274-
1275- // Common framework pattern: capture current ~findRoute then replace with
1276- // a wrapper that delegates to the captured reference. Must not recurse.
1277- const prev = app [ "~findRoute" ] ;
1278- app [ "~findRoute" ] = ( event : any ) => {
1279- const matched = prev . call ( app , event ) ;
1280- if ( matched ) return matched ;
1281- if ( event . url . pathname === "/wrapped" && event . req . method === "GET" ) {
1282- return {
1283- data : { method : "GET" , route : "/wrapped" , handler : routeHandler } ,
1284- params : { } ,
1285- } ;
1286- }
1287- return undefined ;
1288- } ;
1289-
1290- const request = new Request ( "http://localhost/wrapped" , { method : "GET" } ) ;
1291- const event = new H3Event ( request , undefined , app as any ) ;
1292-
1293- await app . handler ( event ) ;
1294- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
1295-
1296- const routeEvents = listener . events . filter ( ( e ) => e . asyncStart ?. data . type === "route" ) ;
1297- expect ( routeEvents . length ) . toBe ( 1 ) ;
1298- } finally {
1299- listener . cleanup ( ) ;
1300- }
1301- } ) ;
1302-
1303- it ( "does not double-wrap handlers across repeated ~findRoute calls" , async ( ) => {
1199+ it ( "does not double-wrap handlers across repeated calls" , async ( ) => {
13041200 const listener = createTracingListener ( ) ;
13051201 const { H3Core } = await import ( "../src/h3.ts" ) ;
1306- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1202+ const { wrapFindRouteWithTracing } = await import ( "../src/tracing.ts" ) ;
13071203 const { H3Event } = await import ( "../src/event.ts" ) ;
13081204
13091205 try {
13101206 const app = new H3Core ( ) ;
13111207 const routeHandler = ( ) => "ok" ;
13121208
1313- // Cached route object returned from every ~findRoute call — mirrors a
1314- // framework that memoizes resolved routes.
13151209 const cachedRoute = {
1316- data : {
1317- method : "GET" as const ,
1318- route : "/cached" ,
1319- handler : routeHandler ,
1320- } ,
1210+ data : { method : "GET" as const , route : "/cached" , handler : routeHandler } ,
13211211 params : { } ,
13221212 } ;
1323- app [ "~findRoute" ] = ( ) => cachedRoute ;
13241213
1325- tracingPlugin ( ) ( app as any ) ;
1214+ app [ "~findRoute" ] = wrapFindRouteWithTracing ( ( ( ) => cachedRoute ) as any ) ;
13261215
13271216 const run = async ( ) => {
13281217 const request = new Request ( "http://localhost/cached" , { method : "GET" } ) ;
@@ -1331,24 +1220,27 @@ describe("tracing channels for H3Core instances", () => {
13311220 } ;
13321221
13331222 await run ( ) ;
1334- // Handler wrapped in place on first call; reference should stay stable
1335- // across subsequent calls (wrapEventHandler short-circuits on __traced__).
13361223 const wrappedHandler = cachedRoute . data . handler ;
13371224 expect ( ( wrappedHandler as any ) . __traced__ ) . toBe ( true ) ;
13381225
13391226 await run ( ) ;
13401227 await run ( ) ;
13411228
13421229 expect ( cachedRoute . data . handler ) . toBe ( wrappedHandler ) ;
1230+
1231+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
1232+
1233+ const routeAsyncStarts = listener . events . filter ( ( e ) => e . asyncStart ?. data . type === "route" ) ;
1234+ expect ( routeAsyncStarts . length ) . toBe ( 3 ) ;
13431235 } finally {
13441236 listener . cleanup ( ) ;
13451237 }
13461238 } ) ;
13471239
1348- it ( "mutates cached route.data.middleware in place without reallocating the array " , async ( ) => {
1240+ it ( "mutates cached route.data.middleware in place without reallocating" , async ( ) => {
13491241 const listener = createTracingListener ( ) ;
13501242 const { H3Core } = await import ( "../src/h3.ts" ) ;
1351- const { tracingPlugin } = await import ( "../src/tracing.ts" ) ;
1243+ const { wrapFindRouteWithTracing } = await import ( "../src/tracing.ts" ) ;
13521244 const { H3Event } = await import ( "../src/event.ts" ) ;
13531245
13541246 try {
@@ -1365,9 +1257,8 @@ describe("tracing channels for H3Core instances", () => {
13651257 } ,
13661258 params : { } ,
13671259 } ;
1368- app [ "~findRoute" ] = ( ) => cachedRoute ;
13691260
1370- tracingPlugin ( ) ( app as any ) ;
1261+ app [ "~findRoute" ] = wrapFindRouteWithTracing ( ( ( ) => cachedRoute ) as any ) ;
13711262
13721263 const run = async ( ) => {
13731264 const request = new Request ( "http://localhost/cached" , { method : "GET" } ) ;
@@ -1383,15 +1274,12 @@ describe("tracing channels for H3Core instances", () => {
13831274 await run ( ) ;
13841275 await run ( ) ;
13851276
1386- // Array identity and entry identity must remain stable — no per-request
1387- // allocations once the middleware is already traced.
13881277 expect ( cachedRoute . data . middleware ) . toBe ( middlewareArray ) ;
13891278 expect ( cachedRoute . data . middleware [ 0 ] ) . toBe ( wrappedMw ) ;
13901279
13911280 await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
13921281
13931282 const routeAsyncStarts = listener . events . filter ( ( e ) => e . asyncStart ?. data . type === "route" ) ;
1394- // Exactly one route trace per request — no double-wrapping.
13951283 expect ( routeAsyncStarts . length ) . toBe ( 3 ) ;
13961284 } finally {
13971285 listener . cleanup ( ) ;
0 commit comments