@reckona/mreact-router 0.0.188 → 0.0.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +16 -3
  2. package/bin/mreact-router.js +23 -0
  3. package/dist/adapters/aws-lambda.d.ts +33 -13
  4. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  5. package/dist/adapters/aws-lambda.js +128 -20
  6. package/dist/adapters/aws-lambda.js.map +1 -1
  7. package/dist/adapters/cloudflare.d.ts +18 -0
  8. package/dist/adapters/cloudflare.d.ts.map +1 -1
  9. package/dist/adapters/cloudflare.js.map +1 -1
  10. package/dist/adapters/edge.d.ts +2 -0
  11. package/dist/adapters/edge.d.ts.map +1 -1
  12. package/dist/adapters/edge.js.map +1 -1
  13. package/dist/adapters/node.d.ts +16 -2
  14. package/dist/adapters/node.d.ts.map +1 -1
  15. package/dist/adapters/node.js.map +1 -1
  16. package/dist/cache.d.ts +8 -0
  17. package/dist/cache.d.ts.map +1 -1
  18. package/dist/cache.js +34 -13
  19. package/dist/cache.js.map +1 -1
  20. package/dist/cli-options.d.ts +1 -1
  21. package/dist/cli-options.d.ts.map +1 -1
  22. package/dist/cli-options.js +21 -3
  23. package/dist/cli-options.js.map +1 -1
  24. package/dist/cli.js +0 -0
  25. package/dist/client.d.ts +1 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +1 -0
  28. package/dist/client.js.map +1 -1
  29. package/dist/config.d.ts +4 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +6 -2
  32. package/dist/config.js.map +1 -1
  33. package/dist/index.d.ts +19 -16
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +15 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/link.d.ts +22 -1
  38. package/dist/link.d.ts.map +1 -1
  39. package/dist/link.js +130 -5
  40. package/dist/link.js.map +1 -1
  41. package/dist/render.d.ts.map +1 -1
  42. package/dist/render.js +514 -517
  43. package/dist/render.js.map +1 -1
  44. package/dist/request.d.ts +10 -0
  45. package/dist/request.d.ts.map +1 -0
  46. package/dist/request.js +6 -0
  47. package/dist/request.js.map +1 -0
  48. package/dist/typed-routes.d.ts +4 -5
  49. package/dist/typed-routes.d.ts.map +1 -1
  50. package/dist/typed-routes.js.map +1 -1
  51. package/dist/vite.d.ts +6 -0
  52. package/dist/vite.d.ts.map +1 -1
  53. package/dist/vite.js +4 -1
  54. package/dist/vite.js.map +1 -1
  55. package/package.json +19 -13
  56. package/src/adapters/aws-lambda.ts +227 -56
  57. package/src/adapters/cloudflare.ts +59 -0
  58. package/src/adapters/edge.ts +14 -0
  59. package/src/adapters/node.ts +44 -2
  60. package/src/cache.ts +39 -17
  61. package/src/cli-options.ts +28 -6
  62. package/src/client.ts +2 -1
  63. package/src/config.ts +10 -2
  64. package/src/index.ts +56 -10
  65. package/src/link.ts +205 -10
  66. package/src/render.ts +5 -7
  67. package/src/request.ts +35 -0
  68. package/src/typed-routes.ts +4 -4
  69. package/src/vite.ts +30 -1
@@ -22,8 +22,41 @@ import {
22
22
  import type { AppRouterResponseHook } from "../render.js";
23
23
  import type { RouterInstrumentation } from "../trace.js";
24
24
 
25
- /** Re-exports router instrumentation hooks for AWS Lambda handlers. */
26
- export type { RouterInstrumentation } from "../trace.js";
25
+ /** Re-exports cache contracts used by AWS Lambda handlers. */
26
+ export type { AppRouterCache, AppRouterCacheEntry } from "../cache.js";
27
+ /** Re-exports server action contracts used by AWS Lambda handlers. */
28
+ export type {
29
+ AppRouterAllowedServerAction,
30
+ AppRouterServerActionOptions,
31
+ } from "../actions.js";
32
+ /** Re-exports logger contracts used by AWS Lambda handlers. */
33
+ export type {
34
+ AppRouterCspInlineNonceWarningLogEvent,
35
+ AppRouterLogger,
36
+ AppRouterLogError,
37
+ AppRouterLogEvent,
38
+ AppRouterRenderTimingLogEvent,
39
+ AppRouterRequestEndLogEvent,
40
+ AppRouterRequestErrorLogEvent,
41
+ AppRouterRequestStartLogEvent,
42
+ AppRouterRequestTimingLogEvent,
43
+ AppRouterRuntime,
44
+ } from "../logger.js";
45
+ /** Re-exports response hook contracts used by AWS Lambda handlers. */
46
+ export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
47
+ /** Re-exports build contracts referenced by AWS Lambda handler options. */
48
+ export type { BuiltPrerenderedRoute } from "../build.js";
49
+ /** Re-exports router instrumentation hooks and events for AWS Lambda handlers. */
50
+ export type {
51
+ RouterInstrumentation,
52
+ RouterMiddlewareEndInstrumentationEvent,
53
+ RouterMiddlewareInstrumentationEvent,
54
+ RouterRequestEndInstrumentationEvent,
55
+ RouterRequestInstrumentationEvent,
56
+ RouterRouteEndInstrumentationEvent,
57
+ RouterRouteInstrumentationEvent,
58
+ RouterTraceContext,
59
+ } from "../trace.js";
27
60
  /** Re-exports app-router import policy controls for AWS Lambda handlers. */
28
61
  export type { AppRouterImportPolicy } from "../import-policy.js";
29
62
  import {
@@ -36,6 +69,13 @@ import {
36
69
  type ResponseSinkStrategy,
37
70
  } from "../serve.js";
38
71
 
72
+ /** Re-exports request and rendering contracts used by AWS Lambda handlers. */
73
+ export type {
74
+ AppRouterPrerenderStore,
75
+ RequestHostPolicy,
76
+ ResponseSinkStrategy,
77
+ } from "../serve.js";
78
+
39
79
  /**
40
80
  * Represents the AWS API Gateway HTTP API v2 event consumed by buffered handlers.
41
81
  */
@@ -44,15 +84,15 @@ export interface AwsLambdaHttpEventV2 {
44
84
  cookies?: string[] | undefined;
45
85
  headers?: Record<string, string | undefined> | undefined;
46
86
  isBase64Encoded?: boolean | undefined;
47
- rawPath?: string | undefined;
48
- rawQueryString?: string | undefined;
49
- requestContext?: {
50
- http?: {
51
- method?: string | undefined;
87
+ rawPath: string;
88
+ rawQueryString: string;
89
+ requestContext: {
90
+ http: {
91
+ method: string;
52
92
  protocol?: string | undefined;
53
- } | undefined;
54
- } | undefined;
55
- version?: "2.0" | string | undefined;
93
+ };
94
+ };
95
+ version: "2.0";
56
96
  }
57
97
 
58
98
  /**
@@ -156,6 +196,25 @@ export type AwsLambdaStreamingRequestHandler<TContext = unknown> = (
156
196
  context: TContext,
157
197
  ) => Promise<void>;
158
198
 
199
+ const invalidAwsLambdaHttpEventV2Diagnostic =
200
+ 'Expected an AWS Lambda HTTP API payload format 2.0 event with rawPath and requestContext.http.method.';
201
+
202
+ function invalidAwsLambdaHttpEventV2Result(): AwsLambdaHttpResultV2 {
203
+ return {
204
+ body: invalidAwsLambdaHttpEventV2Diagnostic,
205
+ headers: { "content-type": "text/plain; charset=utf-8" },
206
+ isBase64Encoded: false,
207
+ statusCode: 400,
208
+ };
209
+ }
210
+
211
+ function invalidAwsLambdaHttpEventV2Response(): Response {
212
+ return new Response(invalidAwsLambdaHttpEventV2Diagnostic, {
213
+ headers: { "content-type": "text/plain; charset=utf-8" },
214
+ status: 400,
215
+ });
216
+ }
217
+
159
218
  /**
160
219
  * Creates a buffered AWS Lambda HTTP API v2 handler for built app-router output.
161
220
  *
@@ -165,35 +224,75 @@ export function createAwsLambdaRequestHandler(
165
224
  options: AwsLambdaRequestHandlerOptions,
166
225
  ): AwsLambdaRequestHandler {
167
226
  warnIfImplicitHostTrust(options);
168
- const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
169
- const runtimePreloadPromise = startAwsLambdaRuntimePreload(
170
- options,
171
- runtimeDirPromise,
172
- "middleware",
173
- );
174
- void runtimePreloadPromise?.catch(() => {});
227
+ let handler: AwsLambdaRequestHandler | undefined;
175
228
 
176
- return createAwsLambdaRequestHandlerFromRuntime(
177
- options,
178
- runtimeDirPromise,
179
- runtimePreloadPromise,
180
- "middleware",
181
- );
229
+ return async (event) => {
230
+ try {
231
+ validateAwsLambdaHttpEventV2(event);
232
+ } catch {
233
+ return invalidAwsLambdaHttpEventV2Result();
234
+ }
235
+
236
+ handler ??= (() => {
237
+ const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
238
+ const runtimePreloadPromise = startAwsLambdaRuntimePreload(options, runtimeDirPromise, "middleware");
239
+ void runtimePreloadPromise?.catch(() => {});
240
+ return createAwsLambdaRequestHandlerFromRuntime(
241
+ options,
242
+ runtimeDirPromise,
243
+ runtimePreloadPromise,
244
+ "middleware",
245
+ );
246
+ })();
247
+
248
+ return await handler(event);
249
+ };
182
250
  }
183
251
 
184
252
  /**
185
- * Creates a buffered AWS Lambda handler after eagerly preparing and preloading the built runtime.
253
+ * Creates a buffered AWS Lambda handler that starts full runtime preparation after the first valid event.
186
254
  *
187
- * Use this when startup code can await preload work before the first request instead of letting the first invocation share that cost.
255
+ * Invalid events are rejected before runtime materialization or preload work begins.
188
256
  */
189
257
  export async function createPreloadedAwsLambdaRequestHandler(
190
258
  options: AwsLambdaRequestHandlerOptions,
191
259
  ): Promise<AwsLambdaRequestHandler> {
260
+ warnIfImplicitHostTrust(options);
261
+ let handler: AwsLambdaRequestHandler | undefined;
262
+
263
+ return async (event) => {
264
+ try {
265
+ validateAwsLambdaHttpEventV2(event);
266
+ } catch {
267
+ return invalidAwsLambdaHttpEventV2Result();
268
+ }
269
+
270
+ handler ??= (() => {
271
+ const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
272
+ const runtimePreloadPromise = startAwsLambdaRuntimePreload(options, runtimeDirPromise, "all");
273
+ void runtimePreloadPromise?.catch(() => {});
274
+ return createAwsLambdaRequestHandlerFromRuntime(
275
+ options,
276
+ runtimeDirPromise,
277
+ runtimePreloadPromise,
278
+ );
279
+ })();
280
+
281
+ return await handler(event);
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Explicitly materializes and preloads a built Lambda runtime before request handling.
287
+ *
288
+ * Call this during controlled Lambda initialization only when the extra startup work is preferable to first-valid-request latency. Request handlers always validate events before they start runtime work.
289
+ */
290
+ export async function warmAwsLambdaRuntime(
291
+ options: AwsLambdaRequestHandlerOptions,
292
+ ): Promise<void> {
192
293
  warnIfImplicitHostTrust(options);
193
294
  const runtimeDir = await prepareAwsLambdaRuntimeDir(options);
194
295
  await preloadAwsLambdaRuntime(options, runtimeDir);
195
-
196
- return createAwsLambdaRequestHandlerFromRuntime(options, Promise.resolve(runtimeDir));
197
296
  }
198
297
 
199
298
  function createAwsLambdaRequestHandlerFromRuntime(
@@ -203,6 +302,12 @@ function createAwsLambdaRequestHandlerFromRuntime(
203
302
  defaultPreloadMode: AwsLambdaDefaultPreloadMode = "all",
204
303
  ): AwsLambdaRequestHandler {
205
304
  return async (event) => {
305
+ try {
306
+ validateAwsLambdaHttpEventV2(event);
307
+ } catch {
308
+ return invalidAwsLambdaHttpEventV2Result();
309
+ }
310
+
206
311
  const startedAt = logNow();
207
312
  const phases = createAwsLambdaTimingPhases(options);
208
313
  const eventToRequestStartedAt = phaseStartedAt(phases);
@@ -298,39 +403,75 @@ export function createAwsLambdaStreamingRequestHandler<TContext = unknown>(
298
403
  ): AwsLambdaStreamingRequestHandler<TContext> {
299
404
  warnIfImplicitHostTrust(options);
300
405
  const runtime = awsLambdaRuntime();
301
- const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
302
- const runtimePreloadPromise = startAwsLambdaRuntimePreload(
303
- options,
304
- runtimeDirPromise,
305
- "middleware",
306
- );
307
- void runtimePreloadPromise?.catch(() => {});
308
-
309
- return createAwsLambdaStreamingRequestHandlerFromRuntime(
310
- options,
311
- runtime,
312
- runtimeDirPromise,
313
- runtimePreloadPromise,
314
- "middleware",
315
- );
406
+ let handler: AwsLambdaStreamingRequestHandler<TContext> | undefined;
407
+
408
+ return runtime.streamifyResponse(async (event, responseStream, context) => {
409
+ try {
410
+ validateAwsLambdaHttpEventV2(event);
411
+ } catch {
412
+ await streamResponseToLambda(
413
+ invalidAwsLambdaHttpEventV2Response(),
414
+ responseStream,
415
+ runtime,
416
+ );
417
+ return;
418
+ }
419
+
420
+ handler ??= (() => {
421
+ const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
422
+ const runtimePreloadPromise = startAwsLambdaRuntimePreload(options, runtimeDirPromise, "middleware");
423
+ void runtimePreloadPromise?.catch(() => {});
424
+ return createAwsLambdaStreamingRequestHandlerFromRuntime(
425
+ options,
426
+ runtime,
427
+ runtimeDirPromise,
428
+ runtimePreloadPromise,
429
+ "middleware",
430
+ );
431
+ })();
432
+
433
+ await handler(event, responseStream, context);
434
+ });
316
435
  }
317
436
 
318
437
  /**
319
- * Creates a streaming AWS Lambda handler after eagerly preparing and preloading the built runtime.
438
+ * Creates a streaming AWS Lambda handler that starts full runtime preparation after the first valid event.
439
+ *
440
+ * Invalid events are rejected before runtime materialization or preload work begins.
320
441
  */
321
442
  export async function createPreloadedAwsLambdaStreamingRequestHandler<TContext = unknown>(
322
443
  options: AwsLambdaRequestHandlerOptions,
323
444
  ): Promise<AwsLambdaStreamingRequestHandler<TContext>> {
324
445
  warnIfImplicitHostTrust(options);
325
446
  const runtime = awsLambdaRuntime();
326
- const runtimeDir = await prepareAwsLambdaRuntimeDir(options);
327
- await preloadAwsLambdaRuntime(options, runtimeDir);
447
+ let handler: AwsLambdaStreamingRequestHandler<TContext> | undefined;
328
448
 
329
- return createAwsLambdaStreamingRequestHandlerFromRuntime<TContext>(
330
- options,
331
- runtime,
332
- Promise.resolve(runtimeDir),
333
- );
449
+ return runtime.streamifyResponse(async (event, responseStream, context) => {
450
+ try {
451
+ validateAwsLambdaHttpEventV2(event);
452
+ } catch {
453
+ await streamResponseToLambda(
454
+ invalidAwsLambdaHttpEventV2Response(),
455
+ responseStream,
456
+ runtime,
457
+ );
458
+ return;
459
+ }
460
+
461
+ handler ??= (() => {
462
+ const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
463
+ const runtimePreloadPromise = startAwsLambdaRuntimePreload(options, runtimeDirPromise, "all");
464
+ void runtimePreloadPromise?.catch(() => {});
465
+ return createAwsLambdaStreamingRequestHandlerFromRuntime<TContext>(
466
+ options,
467
+ runtime,
468
+ runtimeDirPromise,
469
+ runtimePreloadPromise,
470
+ );
471
+ })();
472
+
473
+ await handler(event, responseStream, context);
474
+ });
334
475
  }
335
476
 
336
477
  function startAwsLambdaRuntimePreload(
@@ -480,6 +621,17 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
480
621
  defaultPreloadMode: AwsLambdaDefaultPreloadMode = "all",
481
622
  ): AwsLambdaStreamingRequestHandler<TContext> {
482
623
  return runtime.streamifyResponse(async (event, responseStream, _context) => {
624
+ try {
625
+ validateAwsLambdaHttpEventV2(event);
626
+ } catch {
627
+ await streamResponseToLambda(
628
+ invalidAwsLambdaHttpEventV2Response(),
629
+ responseStream,
630
+ runtime,
631
+ );
632
+ return;
633
+ }
634
+
483
635
  const startedAt = logNow();
484
636
  const phases = createAwsLambdaTimingPhases(options);
485
637
  const eventToRequestStartedAt = phaseStartedAt(phases);
@@ -647,6 +799,7 @@ function eventToRequest(
647
799
  event: AwsLambdaHttpEventV2,
648
800
  options: AwsLambdaRequestHandlerOptions,
649
801
  ): Request {
802
+ validateAwsLambdaHttpEventV2(event);
650
803
  const headers = eventHeaders(event);
651
804
  const rawHost = lambdaRequestHost(headers, options);
652
805
  const host = resolveRequestHost({
@@ -656,12 +809,9 @@ function eventToRequest(
656
809
  rawHost: rawHost ?? undefined,
657
810
  });
658
811
  const protocol = lambdaRequestProtocol(event, headers, options);
659
- const rawPath = event.rawPath === undefined || event.rawPath === "" ? "/" : event.rawPath;
660
- const rawQueryString =
661
- event.rawQueryString === undefined || event.rawQueryString === ""
662
- ? ""
663
- : `?${event.rawQueryString}`;
664
- const method = event.requestContext?.http?.method ?? "GET";
812
+ const rawPath = event.rawPath;
813
+ const rawQueryString = event.rawQueryString === "" ? "" : `?${event.rawQueryString}`;
814
+ const method = event.requestContext.http.method;
665
815
  const init: RequestInit = {
666
816
  headers,
667
817
  method,
@@ -675,6 +825,27 @@ function eventToRequest(
675
825
  return new Request(`${protocol}://${host}${rawPath}${rawQueryString}`, init);
676
826
  }
677
827
 
828
+ function validateAwsLambdaHttpEventV2(event: AwsLambdaHttpEventV2): void {
829
+ const candidate = event as unknown;
830
+
831
+ if (
832
+ typeof candidate !== "object" ||
833
+ candidate === null ||
834
+ (candidate as { version?: unknown }).version !== "2.0" ||
835
+ typeof (candidate as { rawPath?: unknown }).rawPath !== "string" ||
836
+ !(candidate as { rawPath: string }).rawPath.startsWith("/") ||
837
+ typeof (candidate as { rawQueryString?: unknown }).rawQueryString !== "string" ||
838
+ typeof (candidate as { requestContext?: unknown }).requestContext !== "object" ||
839
+ (candidate as { requestContext?: unknown }).requestContext === null ||
840
+ typeof ((candidate as { requestContext: { http?: unknown } }).requestContext.http) !== "object" ||
841
+ (candidate as { requestContext: { http?: unknown } }).requestContext.http === null ||
842
+ typeof ((candidate as { requestContext: { http: { method?: unknown } } }).requestContext.http.method) !== "string" ||
843
+ (candidate as { requestContext: { http: { method: string } } }).requestContext.http.method === ""
844
+ ) {
845
+ throw new Error(invalidAwsLambdaHttpEventV2Diagnostic);
846
+ }
847
+ }
848
+
678
849
  function lambdaRequestHost(
679
850
  headers: Headers,
680
851
  options: AwsLambdaRequestHandlerOptions,
@@ -39,6 +39,65 @@ import type { AppRouterPrerenderStore } from "../serve.js";
39
39
  import { emitRouterDevtoolsEvent } from "./devtools.js";
40
40
  import { escapeHtmlAttribute, escapeHtmlText } from "@reckona/mreact-shared/html-escape";
41
41
 
42
+ /** Re-exports build manifest contracts used by Cloudflare handlers. */
43
+ export type {
44
+ BuiltPrerenderedRoute,
45
+ BuiltRouteSourceAnalysisSummary,
46
+ BuiltServerActionExpressionReference,
47
+ BuiltServerActionReference,
48
+ BuiltServerManifest,
49
+ BuiltServerModuleArtifact,
50
+ BuiltServerModuleOutput,
51
+ } from "../build.js";
52
+ /** Re-exports client manifest contracts used by Cloudflare handlers. */
53
+ export type { ClientRouteManifestEntry } from "../client.js";
54
+ /** Re-exports route cache policy contracts used by route manifests. */
55
+ export type { RouteCachePolicy } from "../cache.js";
56
+ /** Re-exports logger contracts used by Cloudflare handlers. */
57
+ export type {
58
+ AppRouterCspInlineNonceWarningLogEvent,
59
+ AppRouterLogger,
60
+ AppRouterLogError,
61
+ AppRouterLogEvent,
62
+ AppRouterRenderTimingLogEvent,
63
+ AppRouterRequestEndLogEvent,
64
+ AppRouterRequestErrorLogEvent,
65
+ AppRouterRequestStartLogEvent,
66
+ AppRouterRequestTimingLogEvent,
67
+ AppRouterRuntime,
68
+ } from "../logger.js";
69
+ /** Re-exports response hook contracts used by Cloudflare handlers. */
70
+ export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
71
+ /** Re-exports route contracts used by Cloudflare handlers. */
72
+ export type {
73
+ AppAssetRoute,
74
+ AppMetadataRoute,
75
+ AppRoute,
76
+ CompiledRouteMatcherEntry,
77
+ CompiledRouteMatcherArtifact,
78
+ CompiledRouteMatcherSegment,
79
+ PageRoute,
80
+ ServerRoute,
81
+ RouteSegment,
82
+ } from "../routes.js";
83
+ /** Re-exports prerender store contracts used by Cloudflare handlers. */
84
+ export type { AppRouterPrerenderStore } from "../serve.js";
85
+ /** Re-exports metadata contracts used by Cloudflare route modules. */
86
+ export type {
87
+ GenerateMetadataContext,
88
+ MetadataImage,
89
+ MetadataScalar,
90
+ MetadataThemeColor,
91
+ MetadataViewport,
92
+ RouteHeadDescriptor,
93
+ RouteMetadata,
94
+ RouteParams,
95
+ RouteSecurityHeaders,
96
+ RouteStrictTransportSecurity,
97
+ } from "../types.js";
98
+ /** Re-exports file convention contracts used by Cloudflare route manifests. */
99
+ export type { AppFileConvention } from "../file-conventions.js";
100
+
42
101
  /**
43
102
  * Represents the Cloudflare Worker execution context used by router handlers.
44
103
  */
@@ -8,6 +8,20 @@ import {
8
8
  type AppRouterLogger,
9
9
  } from "../logger.js";
10
10
 
11
+ /** Re-exports the logger contract used by edge handlers. */
12
+ export type {
13
+ AppRouterCspInlineNonceWarningLogEvent,
14
+ AppRouterLogger,
15
+ AppRouterLogError,
16
+ AppRouterLogEvent,
17
+ AppRouterRenderTimingLogEvent,
18
+ AppRouterRequestEndLogEvent,
19
+ AppRouterRequestErrorLogEvent,
20
+ AppRouterRequestStartLogEvent,
21
+ AppRouterRequestTimingLogEvent,
22
+ AppRouterRuntime,
23
+ } from "../logger.js";
24
+
11
25
  /**
12
26
  * Handles one standard Web `Request` in an edge runtime.
13
27
  */
@@ -15,8 +15,43 @@ import {
15
15
  import type { AppRouterResponseHook } from "../render.js";
16
16
  import type { RouterInstrumentation } from "../trace.js";
17
17
 
18
- /** Re-exports router instrumentation hooks for Node handlers. */
19
- export type { RouterInstrumentation } from "../trace.js";
18
+ /** Re-exports cache contracts used by Node handlers. */
19
+ export type { AppRouterCache, AppRouterCacheEntry } from "../cache.js";
20
+ /** Re-exports server action contracts used by Node handlers. */
21
+ export type {
22
+ AppRouterAllowedServerAction,
23
+ AppRouterServerActionOptions,
24
+ } from "../actions.js";
25
+ /** Re-exports import policy contracts used by Node handlers. */
26
+ export type { AppRouterImportPolicy } from "../import-policy.js";
27
+ /** Re-exports logger contracts used by Node handlers. */
28
+ export type {
29
+ AppRouterCspInlineNonceWarningLogEvent,
30
+ AppRouterLogger,
31
+ AppRouterLogError,
32
+ AppRouterLogEvent,
33
+ AppRouterRenderTimingLogEvent,
34
+ AppRouterRequestEndLogEvent,
35
+ AppRouterRequestErrorLogEvent,
36
+ AppRouterRequestStartLogEvent,
37
+ AppRouterRequestTimingLogEvent,
38
+ AppRouterRuntime,
39
+ } from "../logger.js";
40
+ /** Re-exports response hook contracts used by Node handlers. */
41
+ export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
42
+ /** Re-exports build contracts referenced by Node handler options. */
43
+ export type { BuiltPrerenderedRoute } from "../build.js";
44
+ /** Re-exports router instrumentation hooks and events for Node handlers. */
45
+ export type {
46
+ RouterInstrumentation,
47
+ RouterMiddlewareEndInstrumentationEvent,
48
+ RouterMiddlewareInstrumentationEvent,
49
+ RouterRequestEndInstrumentationEvent,
50
+ RouterRequestInstrumentationEvent,
51
+ RouterRouteEndInstrumentationEvent,
52
+ RouterRouteInstrumentationEvent,
53
+ RouterTraceContext,
54
+ } from "../trace.js";
20
55
  import {
21
56
  renderBuiltAppRequest,
22
57
  resolveRequestHost,
@@ -26,6 +61,13 @@ import {
26
61
  type ResponseSinkStrategy,
27
62
  } from "../serve.js";
28
63
 
64
+ /** Re-exports request and rendering contracts used by Node handlers. */
65
+ export type {
66
+ AppRouterPrerenderStore,
67
+ RequestHostPolicy,
68
+ ResponseSinkStrategy,
69
+ } from "../serve.js";
70
+
29
71
  /**
30
72
  * Configures a Node request handler for built app-router output.
31
73
  */
package/src/cache.ts CHANGED
@@ -58,6 +58,7 @@ interface AppRouterCacheState {
58
58
  interface RouteCacheContext {
59
59
  cache: AppRouterCache;
60
60
  cachePolicy?: RouteCachePolicy | undefined;
61
+ closed?: boolean | undefined;
61
62
  revalidatedPaths: Set<string>;
62
63
  }
63
64
 
@@ -472,15 +473,20 @@ export async function withRouteCacheContext<T>(
472
473
  };
473
474
 
474
475
  return cacheState.storage.run(context, async () => {
475
- const value = await fn();
476
- const cachePolicy = context.cachePolicy;
477
- const revalidatedPaths = Array.from(context.revalidatedPaths);
476
+ try {
477
+ const value = await fn();
478
+ context.closed = true;
479
+ const cachePolicy = context.cachePolicy;
480
+ const revalidatedPaths = Array.from(context.revalidatedPaths);
478
481
 
479
- for (const path of revalidatedPaths) {
480
- await context.cache.deleteByPath(path);
481
- }
482
+ for (const path of revalidatedPaths) {
483
+ await context.cache.deleteByPath(path);
484
+ }
482
485
 
483
- return { cachePolicy, revalidatedPaths, value };
486
+ return { cachePolicy, revalidatedPaths, value };
487
+ } finally {
488
+ context.closed = true;
489
+ }
484
490
  });
485
491
  }
486
492
 
@@ -500,15 +506,17 @@ export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
500
506
  return context.cachePolicy;
501
507
  },
502
508
  async dispose() {
509
+ context.closed = true;
503
510
  const revalidatedPaths = Array.from(context.revalidatedPaths);
504
-
505
- for (const path of revalidatedPaths) {
506
- await context.cache.deleteByPath(path);
507
- }
508
-
509
- const index = cacheState.activeContexts.lastIndexOf(context);
510
- if (index !== -1) {
511
- cacheState.activeContexts.splice(index, 1);
511
+ try {
512
+ for (const path of revalidatedPaths) {
513
+ await context.cache.deleteByPath(path);
514
+ }
515
+ } finally {
516
+ const index = cacheState.activeContexts.lastIndexOf(context);
517
+ if (index !== -1) {
518
+ cacheState.activeContexts.splice(index, 1);
519
+ }
512
520
  }
513
521
 
514
522
  return { revalidatedPaths };
@@ -516,8 +524,22 @@ export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
516
524
  };
517
525
  }
518
526
 
519
- function activeRouteCacheContext(): RouteCacheContext | undefined {
520
- return cacheState.storage.getStore() ?? cacheState.activeContexts.at(-1);
527
+ export function activeRouteCacheContext(): RouteCacheContext | undefined {
528
+ const asyncContext = cacheState.storage.getStore();
529
+
530
+ if (asyncContext !== undefined) {
531
+ return asyncContext.closed === true ? undefined : asyncContext;
532
+ }
533
+
534
+ for (let index = cacheState.activeContexts.length - 1; index >= 0; index -= 1) {
535
+ const context = cacheState.activeContexts[index];
536
+
537
+ if (context?.closed !== true) {
538
+ return context;
539
+ }
540
+ }
541
+
542
+ return undefined;
521
543
  }
522
544
 
523
545
  // Host is excluded from the cache key to prevent attacker-supplied Host