vinext 0.0.39 → 0.0.40

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 (50) hide show
  1. package/dist/build/standalone.js +7 -0
  2. package/dist/build/standalone.js.map +1 -1
  3. package/dist/entries/app-rsc-entry.d.ts +2 -1
  4. package/dist/entries/app-rsc-entry.js +131 -245
  5. package/dist/entries/app-rsc-entry.js.map +1 -1
  6. package/dist/index.d.ts +32 -1
  7. package/dist/index.js +80 -6
  8. package/dist/index.js.map +1 -1
  9. package/dist/plugins/server-externals-manifest.d.ts +11 -1
  10. package/dist/plugins/server-externals-manifest.js +10 -3
  11. package/dist/plugins/server-externals-manifest.js.map +1 -1
  12. package/dist/routing/app-router.d.ts +10 -2
  13. package/dist/routing/app-router.js +37 -22
  14. package/dist/routing/app-router.js.map +1 -1
  15. package/dist/server/app-page-response.d.ts +12 -1
  16. package/dist/server/app-page-response.js +26 -7
  17. package/dist/server/app-page-response.js.map +1 -1
  18. package/dist/server/app-page-route-wiring.d.ts +79 -0
  19. package/dist/server/app-page-route-wiring.js +165 -0
  20. package/dist/server/app-page-route-wiring.js.map +1 -0
  21. package/dist/server/app-page-stream.js +3 -0
  22. package/dist/server/app-page-stream.js.map +1 -1
  23. package/dist/server/app-route-handler-response.js +4 -1
  24. package/dist/server/app-route-handler-response.js.map +1 -1
  25. package/dist/server/app-router-entry.d.ts +6 -1
  26. package/dist/server/app-router-entry.js +9 -2
  27. package/dist/server/app-router-entry.js.map +1 -1
  28. package/dist/server/prod-server.d.ts +1 -1
  29. package/dist/server/prod-server.js +37 -11
  30. package/dist/server/prod-server.js.map +1 -1
  31. package/dist/server/worker-utils.d.ts +4 -1
  32. package/dist/server/worker-utils.js +31 -1
  33. package/dist/server/worker-utils.js.map +1 -1
  34. package/dist/shims/error-boundary.d.ts +13 -4
  35. package/dist/shims/error-boundary.js +23 -3
  36. package/dist/shims/error-boundary.js.map +1 -1
  37. package/dist/shims/head.js.map +1 -1
  38. package/dist/shims/navigation.d.ts +16 -1
  39. package/dist/shims/navigation.js +18 -3
  40. package/dist/shims/navigation.js.map +1 -1
  41. package/dist/shims/router.js +127 -38
  42. package/dist/shims/router.js.map +1 -1
  43. package/dist/shims/script.js.map +1 -1
  44. package/dist/shims/server.d.ts +17 -4
  45. package/dist/shims/server.js +91 -73
  46. package/dist/shims/server.js.map +1 -1
  47. package/dist/shims/slot.d.ts +28 -0
  48. package/dist/shims/slot.js +49 -0
  49. package/dist/shims/slot.js.map +1 -0
  50. package/package.json +1 -2
@@ -1 +1 @@
1
- {"version":3,"file":"app-page-response.js","names":[],"sources":["../../src/server/app-page-response.ts"],"sourcesContent":["export type AppPageMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type AppPageResponseTiming = {\n compileEnd?: number;\n handlerStart: number;\n renderEnd?: number;\n responseKind: \"html\" | \"rsc\";\n};\n\nexport type AppPageResponsePolicy = {\n cacheControl?: string;\n cacheState?: \"MISS\" | \"STATIC\";\n};\n\ntype ResolveAppPageResponsePolicyBaseOptions = {\n isDynamicError: boolean;\n isForceDynamic: boolean;\n isForceStatic: boolean;\n isProduction: boolean;\n revalidateSeconds: number | null;\n};\n\nexport type ResolveAppPageRscResponsePolicyOptions = {\n dynamicUsedDuringBuild: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type ResolveAppPageHtmlResponsePolicyOptions = {\n dynamicUsedDuringRender: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type AppPageHtmlResponsePolicy = {\n shouldWriteToCache: boolean;\n} & AppPageResponsePolicy;\n\nexport type BuildAppPageRscResponseOptions = {\n middlewareContext: AppPageMiddlewareContext;\n params?: Record<string, unknown>;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nexport type BuildAppPageHtmlResponseOptions = {\n draftCookie?: string | null;\n fontLinkHeader?: string;\n middlewareContext: AppPageMiddlewareContext;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nconst STATIC_CACHE_CONTROL = \"s-maxage=31536000, stale-while-revalidate\";\nconst NO_STORE_CACHE_CONTROL = \"no-store, must-revalidate\";\n\nfunction buildRevalidateCacheControl(revalidateSeconds: number): string {\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nfunction applyTimingHeader(headers: Headers, timing?: AppPageResponseTiming): void {\n if (!timing) {\n return;\n }\n\n const handlerStart = Math.round(timing.handlerStart);\n const compileMs =\n timing.compileEnd !== undefined ? Math.round(timing.compileEnd - timing.handlerStart) : -1;\n const renderMs =\n timing.responseKind === \"html\" &&\n timing.renderEnd !== undefined &&\n timing.compileEnd !== undefined\n ? Math.round(timing.renderEnd - timing.compileEnd)\n : -1;\n\n headers.set(\"x-vinext-timing\", `${handlerStart},${compileMs},${renderMs}`);\n}\n\nexport function resolveAppPageRscResponsePolicy(\n options: ResolveAppPageRscResponsePolicyOptions,\n): AppPageResponsePolicy {\n if (options.isForceDynamic || options.dynamicUsedDuringBuild) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n if (\n ((options.isForceStatic || options.isDynamicError) && !options.revalidateSeconds) ||\n options.revalidateSeconds === Infinity\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n };\n }\n\n if (options.revalidateSeconds) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n // Emit MISS as part of the initial RSC response shape rather than bolting\n // it on later in the cache-write block so response construction stays\n // centralized in this helper. This matches the eventual write path: the\n // first ISR-eligible production response is a cache miss.\n cacheState: options.isProduction ? \"MISS\" : undefined,\n };\n }\n\n return {};\n}\n\nexport function resolveAppPageHtmlResponsePolicy(\n options: ResolveAppPageHtmlResponsePolicyOptions,\n): AppPageHtmlResponsePolicy {\n if (options.isForceDynamic) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n (options.isForceStatic || options.isDynamicError) &&\n (options.revalidateSeconds === null || options.revalidateSeconds === 0)\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n if (options.dynamicUsedDuringRender) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n options.revalidateSeconds !== null &&\n options.revalidateSeconds > 0 &&\n options.revalidateSeconds !== Infinity\n ) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n cacheState: options.isProduction ? \"MISS\" : undefined,\n shouldWriteToCache: options.isProduction,\n };\n }\n\n if (options.revalidateSeconds === Infinity) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n return { shouldWriteToCache: false };\n}\n\nexport function buildAppPageRscResponse(\n body: ReadableStream,\n options: BuildAppPageRscResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/x-component; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.params && Object.keys(options.params).length > 0) {\n // encodeURIComponent so non-ASCII params (e.g. Korean slugs) survive the\n // HTTP ByteString constraint — Headers.set() rejects chars above U+00FF.\n headers.set(\"X-Vinext-Params\", encodeURIComponent(JSON.stringify(options.params)));\n }\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" || lowerKey === \"vary\") {\n headers.append(key, value);\n } else {\n // Keep parity with the old inline RSC path: middleware owns singular\n // response headers like Cache-Control here, while Set-Cookie and Vary\n // are accumulated. The HTML helper intentionally keeps its legacy\n // append-for-everything behavior below.\n headers.set(key, value);\n }\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n\nexport function buildAppPageHtmlResponse(\n body: ReadableStream,\n options: BuildAppPageHtmlResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n if (options.draftCookie) {\n headers.append(\"Set-Cookie\", options.draftCookie);\n }\n if (options.fontLinkHeader) {\n headers.set(\"Link\", options.fontLinkHeader);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n headers.append(key, value);\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-page-response.js","names":[],"sources":["../../src/server/app-page-response.ts"],"sourcesContent":["export type AppPageMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type AppPageResponseTiming = {\n compileEnd?: number;\n handlerStart: number;\n renderEnd?: number;\n responseKind: \"html\" | \"rsc\";\n};\n\nexport type AppPageResponsePolicy = {\n cacheControl?: string;\n cacheState?: \"MISS\" | \"STATIC\";\n};\n\ntype ResolveAppPageResponsePolicyBaseOptions = {\n isDynamicError: boolean;\n isForceDynamic: boolean;\n isForceStatic: boolean;\n isProduction: boolean;\n revalidateSeconds: number | null;\n};\n\nexport type ResolveAppPageRscResponsePolicyOptions = {\n dynamicUsedDuringBuild: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type ResolveAppPageHtmlResponsePolicyOptions = {\n dynamicUsedDuringRender: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type AppPageHtmlResponsePolicy = {\n shouldWriteToCache: boolean;\n} & AppPageResponsePolicy;\n\nexport type BuildAppPageRscResponseOptions = {\n middlewareContext: AppPageMiddlewareContext;\n params?: Record<string, unknown>;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nexport type BuildAppPageHtmlResponseOptions = {\n draftCookie?: string | null;\n fontLinkHeader?: string;\n middlewareContext: AppPageMiddlewareContext;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nconst STATIC_CACHE_CONTROL = \"s-maxage=31536000, stale-while-revalidate\";\nconst NO_STORE_CACHE_CONTROL = \"no-store, must-revalidate\";\n\nfunction buildRevalidateCacheControl(revalidateSeconds: number): string {\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nfunction applyTimingHeader(headers: Headers, timing?: AppPageResponseTiming): void {\n if (!timing) {\n return;\n }\n\n const handlerStart = Math.round(timing.handlerStart);\n const compileMs =\n timing.compileEnd !== undefined ? Math.round(timing.compileEnd - timing.handlerStart) : -1;\n const renderMs =\n timing.responseKind === \"html\" &&\n timing.renderEnd !== undefined &&\n timing.compileEnd !== undefined\n ? Math.round(timing.renderEnd - timing.compileEnd)\n : -1;\n\n headers.set(\"x-vinext-timing\", `${handlerStart},${compileMs},${renderMs}`);\n}\n\nexport function resolveAppPageRscResponsePolicy(\n options: ResolveAppPageRscResponsePolicyOptions,\n): AppPageResponsePolicy {\n if (options.isForceDynamic || options.dynamicUsedDuringBuild) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n // revalidate = 0 means \"always dynamic, never cache\" — equivalent to\n // force-dynamic for caching purposes. Must be checked before the\n // isForceStatic/isDynamicError branch below, which uses !revalidateSeconds\n // and would incorrectly catch 0 as a falsy value.\n if (options.revalidateSeconds === 0) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n if (\n ((options.isForceStatic || options.isDynamicError) && !options.revalidateSeconds) ||\n options.revalidateSeconds === Infinity\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n };\n }\n\n if (options.revalidateSeconds) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n // Emit MISS as part of the initial RSC response shape rather than bolting\n // it on later in the cache-write block so response construction stays\n // centralized in this helper. This matches the eventual write path: the\n // first ISR-eligible production response is a cache miss.\n cacheState: options.isProduction ? \"MISS\" : undefined,\n };\n }\n\n return {};\n}\n\nexport function resolveAppPageHtmlResponsePolicy(\n options: ResolveAppPageHtmlResponsePolicyOptions,\n): AppPageHtmlResponsePolicy {\n if (options.isForceDynamic) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n // revalidate = 0 means \"always dynamic, never cache\" — equivalent to\n // force-dynamic for caching purposes. Must be checked before the\n // isForceStatic/isDynamicError branch below, which matches revalidateSeconds\n // === 0 and would incorrectly return a static Cache-Control.\n if (options.revalidateSeconds === 0) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if ((options.isForceStatic || options.isDynamicError) && options.revalidateSeconds === null) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n if (options.dynamicUsedDuringRender) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n options.revalidateSeconds !== null &&\n options.revalidateSeconds > 0 &&\n options.revalidateSeconds !== Infinity\n ) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n cacheState: options.isProduction ? \"MISS\" : undefined,\n shouldWriteToCache: options.isProduction,\n };\n }\n\n if (options.revalidateSeconds === Infinity) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n return { shouldWriteToCache: false };\n}\n\n/**\n * Merge middleware response headers into a target Headers object.\n *\n * Set-Cookie and Vary are accumulated (append) since multiple sources can\n * contribute values. All other headers use set() so middleware owns singular\n * response headers like Cache-Control.\n *\n * Used by buildAppPageRscResponse and the generated entry for intercepting\n * route and server action responses that bypass the normal page render path.\n */\nexport function mergeMiddlewareResponseHeaders(\n target: Headers,\n middlewareHeaders: Headers | null,\n): void {\n if (!middlewareHeaders) {\n return;\n }\n\n for (const [key, value] of middlewareHeaders) {\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" || lowerKey === \"vary\") {\n target.append(key, value);\n } else {\n target.set(key, value);\n }\n }\n}\n\nexport function buildAppPageRscResponse(\n body: ReadableStream,\n options: BuildAppPageRscResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/x-component; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.params && Object.keys(options.params).length > 0) {\n // encodeURIComponent so non-ASCII params (e.g. Korean slugs) survive the\n // HTTP ByteString constraint — Headers.set() rejects chars above U+00FF.\n headers.set(\"X-Vinext-Params\", encodeURIComponent(JSON.stringify(options.params)));\n }\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n\n mergeMiddlewareResponseHeaders(headers, options.middlewareContext.headers);\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n\nexport function buildAppPageHtmlResponse(\n body: ReadableStream,\n options: BuildAppPageHtmlResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n if (options.draftCookie) {\n headers.append(\"Set-Cookie\", options.draftCookie);\n }\n if (options.fontLinkHeader) {\n headers.set(\"Link\", options.fontLinkHeader);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n headers.append(key, value);\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { AppPageParams } from "./app-page-boundary.js";
2
+ import { Metadata, Viewport } from "../shims/metadata.js";
3
+ import { ComponentType, ReactNode } from "react";
4
+
5
+ //#region src/server/app-page-route-wiring.d.ts
6
+ type AppPageComponentProps = {
7
+ children?: ReactNode;
8
+ error?: Error;
9
+ params?: unknown;
10
+ reset?: () => void;
11
+ } & Record<string, unknown>;
12
+ type AppPageComponent = ComponentType<AppPageComponentProps>;
13
+ type AppPageErrorComponent = ComponentType<{
14
+ error: Error;
15
+ reset: () => void;
16
+ }>;
17
+ type AppPageModule = Record<string, unknown> & {
18
+ default?: AppPageComponent | null | undefined;
19
+ };
20
+ type AppPageErrorModule = Record<string, unknown> & {
21
+ default?: AppPageErrorComponent | null | undefined;
22
+ };
23
+ type AppPageRouteWiringSlot<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
24
+ default?: TModule | null;
25
+ error?: TErrorModule | null;
26
+ layout?: TModule | null;
27
+ layoutIndex: number;
28
+ loading?: TModule | null;
29
+ page?: TModule | null;
30
+ /**
31
+ * Filesystem segments from the slot's root to its active page.
32
+ * Used to populate the LayoutSegmentProvider's segmentMap for this slot.
33
+ * null when the slot has no active page (showing default.tsx fallback).
34
+ */
35
+ routeSegments?: readonly string[] | null;
36
+ };
37
+ type AppPageRouteWiringRoute<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
38
+ error?: TErrorModule | null;
39
+ errors?: readonly (TErrorModule | null | undefined)[] | null;
40
+ layoutTreePositions?: readonly number[] | null;
41
+ layouts: readonly (TModule | null | undefined)[];
42
+ loading?: TModule | null;
43
+ notFound?: TModule | null;
44
+ notFounds?: readonly (TModule | null | undefined)[] | null;
45
+ routeSegments?: readonly string[];
46
+ slots?: Readonly<Record<string, AppPageRouteWiringSlot<TModule, TErrorModule>>> | null;
47
+ templates?: readonly (TModule | null | undefined)[] | null;
48
+ };
49
+ type AppPageSlotOverride<TModule extends AppPageModule = AppPageModule> = {
50
+ pageModule: TModule;
51
+ params?: AppPageParams;
52
+ props?: Readonly<Record<string, unknown>>;
53
+ };
54
+ type AppPageLayoutEntry<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
55
+ errorModule?: TErrorModule | null | undefined;
56
+ id: string;
57
+ layoutModule?: TModule | null | undefined;
58
+ notFoundModule?: TModule | null | undefined;
59
+ treePath: string;
60
+ treePosition: number;
61
+ };
62
+ type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
63
+ element: ReactNode;
64
+ globalErrorModule?: TErrorModule | null;
65
+ makeThenableParams: (params: AppPageParams) => unknown;
66
+ matchedParams: AppPageParams;
67
+ resolvedMetadata: Metadata | null;
68
+ resolvedViewport: Viewport;
69
+ rootNotFoundModule?: TModule | null;
70
+ route: AppPageRouteWiringRoute<TModule, TErrorModule>;
71
+ slotOverrides?: Readonly<Record<string, AppPageSlotOverride<TModule>>> | null;
72
+ };
73
+ declare function createAppPageTreePath(routeSegments: readonly string[] | null | undefined, treePosition: number): string;
74
+ declare function createAppPageLayoutEntries<TModule extends AppPageModule, TErrorModule extends AppPageErrorModule>(route: Pick<AppPageRouteWiringRoute<TModule, TErrorModule>, "errors" | "layoutTreePositions" | "layouts" | "notFounds" | "routeSegments">): AppPageLayoutEntry<TModule, TErrorModule>[];
75
+ declare function resolveAppPageChildSegments(routeSegments: readonly string[], treePosition: number, params: Readonly<Record<string, string | string[] | undefined>>): string[];
76
+ declare function buildAppPageRouteElement<TModule extends AppPageModule, TErrorModule extends AppPageErrorModule>(options: BuildAppPageRouteElementOptions<TModule, TErrorModule>): ReactNode;
77
+ //#endregion
78
+ export { AppPageErrorModule, AppPageLayoutEntry, AppPageModule, AppPageRouteWiringRoute, AppPageRouteWiringSlot, AppPageSlotOverride, BuildAppPageRouteElementOptions, buildAppPageRouteElement, createAppPageLayoutEntries, createAppPageTreePath, resolveAppPageChildSegments };
79
+ //# sourceMappingURL=app-page-route-wiring.d.ts.map
@@ -0,0 +1,165 @@
1
+ import { ErrorBoundary, NotFoundBoundary } from "../shims/error-boundary.js";
2
+ import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
3
+ import { MetadataHead, ViewportHead } from "../shims/metadata.js";
4
+ import { Suspense } from "react";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/server/app-page-route-wiring.tsx
7
+ function getDefaultExport(module) {
8
+ return module?.default ?? null;
9
+ }
10
+ function getErrorBoundaryExport(module) {
11
+ return module?.default ?? null;
12
+ }
13
+ function createAppPageTreePath(routeSegments, treePosition) {
14
+ const treePathSegments = routeSegments?.slice(0, treePosition) ?? [];
15
+ if (treePathSegments.length === 0) return "/";
16
+ return `/${treePathSegments.join("/")}`;
17
+ }
18
+ function createAppPageLayoutEntries(route) {
19
+ return route.layouts.map((layoutModule, index) => {
20
+ const treePosition = route.layoutTreePositions?.[index] ?? 0;
21
+ const treePath = createAppPageTreePath(route.routeSegments, treePosition);
22
+ return {
23
+ errorModule: route.errors?.[index] ?? null,
24
+ id: `layout:${treePath}`,
25
+ layoutModule,
26
+ notFoundModule: route.notFounds?.[index] ?? null,
27
+ treePath,
28
+ treePosition
29
+ };
30
+ });
31
+ }
32
+ function resolveAppPageChildSegments(routeSegments, treePosition, params) {
33
+ const rawSegments = routeSegments.slice(treePosition);
34
+ const resolvedSegments = [];
35
+ for (const segment of rawSegments) {
36
+ if (segment.startsWith("[[...") && segment.endsWith("]]") && segment.length >= 8) {
37
+ const paramValue = params[segment.slice(5, -2)];
38
+ if (Array.isArray(paramValue) && paramValue.length === 0) continue;
39
+ if (paramValue === void 0) continue;
40
+ resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join("/") : paramValue);
41
+ continue;
42
+ }
43
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
44
+ const paramValue = params[segment.slice(4, -1)];
45
+ if (Array.isArray(paramValue)) {
46
+ resolvedSegments.push(paramValue.join("/"));
47
+ continue;
48
+ }
49
+ resolvedSegments.push(paramValue ?? segment);
50
+ continue;
51
+ }
52
+ if (segment.startsWith("[") && segment.endsWith("]") && !segment.includes(".")) {
53
+ const paramValue = params[segment.slice(1, -1)];
54
+ resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join("/") : paramValue ?? segment);
55
+ continue;
56
+ }
57
+ resolvedSegments.push(segment);
58
+ }
59
+ return resolvedSegments;
60
+ }
61
+ function buildAppPageRouteElement(options) {
62
+ let element = /* @__PURE__ */ jsx(LayoutSegmentProvider, {
63
+ segmentMap: { children: [] },
64
+ children: options.element
65
+ });
66
+ element = /* @__PURE__ */ jsxs(Fragment$1, { children: [
67
+ /* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
68
+ options.resolvedMetadata ? /* @__PURE__ */ jsx(MetadataHead, { metadata: options.resolvedMetadata }) : null,
69
+ /* @__PURE__ */ jsx(ViewportHead, { viewport: options.resolvedViewport }),
70
+ element
71
+ ] });
72
+ const loadingComponent = getDefaultExport(options.route.loading);
73
+ if (loadingComponent) element = /* @__PURE__ */ jsx(Suspense, {
74
+ fallback: /* @__PURE__ */ jsx(loadingComponent, {}),
75
+ children: element
76
+ });
77
+ const lastLayoutErrorModule = options.route.errors && options.route.errors.length > 0 ? options.route.errors[options.route.errors.length - 1] : null;
78
+ const pageErrorComponent = getErrorBoundaryExport(options.route.error);
79
+ if (pageErrorComponent && options.route.error !== lastLayoutErrorModule) element = /* @__PURE__ */ jsx(ErrorBoundary, {
80
+ fallback: pageErrorComponent,
81
+ children: element
82
+ });
83
+ const notFoundComponent = getDefaultExport(options.route.notFound) ?? getDefaultExport(options.rootNotFoundModule);
84
+ if (notFoundComponent) element = /* @__PURE__ */ jsx(NotFoundBoundary, {
85
+ fallback: /* @__PURE__ */ jsx(notFoundComponent, {}),
86
+ children: element
87
+ });
88
+ const templates = options.route.templates ?? [];
89
+ const routeSlots = options.route.slots ?? {};
90
+ const layoutEntries = createAppPageLayoutEntries(options.route);
91
+ const routeThenableParams = options.makeThenableParams(options.matchedParams);
92
+ for (let index = layoutEntries.length - 1; index >= 0; index--) {
93
+ const layoutEntry = layoutEntries[index];
94
+ const layoutNotFoundComponent = getDefaultExport(layoutEntry.notFoundModule);
95
+ if (layoutNotFoundComponent) element = /* @__PURE__ */ jsx(NotFoundBoundary, {
96
+ fallback: /* @__PURE__ */ jsx(layoutNotFoundComponent, {}),
97
+ children: element
98
+ });
99
+ const layoutErrorComponent = getErrorBoundaryExport(layoutEntry.errorModule);
100
+ if (layoutErrorComponent) element = /* @__PURE__ */ jsx(ErrorBoundary, {
101
+ fallback: layoutErrorComponent,
102
+ children: element
103
+ });
104
+ const templateComponent = getDefaultExport(templates[index]);
105
+ if (templateComponent) element = /* @__PURE__ */ jsx(templateComponent, {
106
+ params: options.matchedParams,
107
+ children: element
108
+ });
109
+ const layoutComponent = getDefaultExport(layoutEntry.layoutModule);
110
+ if (!layoutComponent) continue;
111
+ const layoutProps = { params: routeThenableParams };
112
+ for (const [slotName, slot] of Object.entries(routeSlots)) {
113
+ const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;
114
+ if (index !== targetIndex) continue;
115
+ const slotOverride = options.slotOverrides?.[slotName];
116
+ const slotParams = slotOverride?.params ?? options.matchedParams;
117
+ const slotComponent = getDefaultExport(slotOverride?.pageModule) ?? getDefaultExport(slot.page) ?? getDefaultExport(slot.default);
118
+ if (!slotComponent) continue;
119
+ const slotProps = { params: options.makeThenableParams(slotParams) };
120
+ if (slotOverride?.props) Object.assign(slotProps, slotOverride.props);
121
+ let slotElement = /* @__PURE__ */ jsx(slotComponent, { ...slotProps });
122
+ const slotLayoutComponent = getDefaultExport(slot.layout);
123
+ if (slotLayoutComponent) slotElement = /* @__PURE__ */ jsx(slotLayoutComponent, {
124
+ params: options.makeThenableParams(slotParams),
125
+ children: slotElement
126
+ });
127
+ const slotLoadingComponent = getDefaultExport(slot.loading);
128
+ if (slotLoadingComponent) slotElement = /* @__PURE__ */ jsx(Suspense, {
129
+ fallback: /* @__PURE__ */ jsx(slotLoadingComponent, {}),
130
+ children: slotElement
131
+ });
132
+ const slotErrorComponent = getErrorBoundaryExport(slot.error);
133
+ if (slotErrorComponent) slotElement = /* @__PURE__ */ jsx(ErrorBoundary, {
134
+ fallback: slotErrorComponent,
135
+ children: slotElement
136
+ });
137
+ layoutProps[slotName] = slotElement;
138
+ }
139
+ element = /* @__PURE__ */ jsx(layoutComponent, {
140
+ ...layoutProps,
141
+ children: element
142
+ });
143
+ const segmentMap = { children: resolveAppPageChildSegments(options.route.routeSegments ?? [], layoutEntry.treePosition, options.matchedParams) };
144
+ for (const [slotName, slot] of Object.entries(routeSlots)) {
145
+ const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;
146
+ if (index !== targetIndex) continue;
147
+ if (slot.routeSegments) segmentMap[slotName] = resolveAppPageChildSegments(slot.routeSegments, 0, options.matchedParams);
148
+ else segmentMap[slotName] = [];
149
+ }
150
+ element = /* @__PURE__ */ jsx(LayoutSegmentProvider, {
151
+ segmentMap,
152
+ children: element
153
+ });
154
+ }
155
+ const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
156
+ if (globalErrorComponent) element = /* @__PURE__ */ jsx(ErrorBoundary, {
157
+ fallback: globalErrorComponent,
158
+ children: element
159
+ });
160
+ return element;
161
+ }
162
+ //#endregion
163
+ export { buildAppPageRouteElement, createAppPageLayoutEntries, createAppPageTreePath, resolveAppPageChildSegments };
164
+
165
+ //# sourceMappingURL=app-page-route-wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-page-route-wiring.js","names":[],"sources":["../../src/server/app-page-route-wiring.tsx"],"sourcesContent":["import { Suspense, type ComponentType, type ReactNode } from \"react\";\nimport { ErrorBoundary, NotFoundBoundary } from \"../shims/error-boundary.js\";\nimport { LayoutSegmentProvider } from \"../shims/layout-segment-context.js\";\nimport { MetadataHead, ViewportHead, type Metadata, type Viewport } from \"../shims/metadata.js\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\n\ntype AppPageComponentProps = {\n children?: ReactNode;\n error?: Error;\n params?: unknown;\n reset?: () => void;\n} & Record<string, unknown>;\n\ntype AppPageComponent = ComponentType<AppPageComponentProps>;\ntype AppPageErrorComponent = ComponentType<{ error: Error; reset: () => void }>;\n\nexport type AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\n\nexport type AppPageErrorModule = Record<string, unknown> & {\n default?: AppPageErrorComponent | null | undefined;\n};\n\nexport type AppPageRouteWiringSlot<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n default?: TModule | null;\n error?: TErrorModule | null;\n layout?: TModule | null;\n layoutIndex: number;\n loading?: TModule | null;\n page?: TModule | null;\n /**\n * Filesystem segments from the slot's root to its active page.\n * Used to populate the LayoutSegmentProvider's segmentMap for this slot.\n * null when the slot has no active page (showing default.tsx fallback).\n */\n routeSegments?: readonly string[] | null;\n};\n\nexport type AppPageRouteWiringRoute<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n error?: TErrorModule | null;\n errors?: readonly (TErrorModule | null | undefined)[] | null;\n layoutTreePositions?: readonly number[] | null;\n layouts: readonly (TModule | null | undefined)[];\n loading?: TModule | null;\n notFound?: TModule | null;\n notFounds?: readonly (TModule | null | undefined)[] | null;\n routeSegments?: readonly string[];\n slots?: Readonly<Record<string, AppPageRouteWiringSlot<TModule, TErrorModule>>> | null;\n templates?: readonly (TModule | null | undefined)[] | null;\n};\n\nexport type AppPageSlotOverride<TModule extends AppPageModule = AppPageModule> = {\n pageModule: TModule;\n params?: AppPageParams;\n props?: Readonly<Record<string, unknown>>;\n};\n\nexport type AppPageLayoutEntry<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n errorModule?: TErrorModule | null | undefined;\n id: string;\n layoutModule?: TModule | null | undefined;\n notFoundModule?: TModule | null | undefined;\n treePath: string;\n treePosition: number;\n};\n\nexport type BuildAppPageRouteElementOptions<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n element: ReactNode;\n globalErrorModule?: TErrorModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n matchedParams: AppPageParams;\n resolvedMetadata: Metadata | null;\n resolvedViewport: Viewport;\n rootNotFoundModule?: TModule | null;\n route: AppPageRouteWiringRoute<TModule, TErrorModule>;\n slotOverrides?: Readonly<Record<string, AppPageSlotOverride<TModule>>> | null;\n};\n\nfunction getDefaultExport<TModule extends AppPageModule>(\n module: TModule | null | undefined,\n): AppPageComponent | null {\n return module?.default ?? null;\n}\n\nfunction getErrorBoundaryExport<TModule extends AppPageErrorModule>(\n module: TModule | null | undefined,\n): AppPageErrorComponent | null {\n return module?.default ?? null;\n}\n\nexport function createAppPageTreePath(\n routeSegments: readonly string[] | null | undefined,\n treePosition: number,\n): string {\n const treePathSegments = routeSegments?.slice(0, treePosition) ?? [];\n if (treePathSegments.length === 0) {\n return \"/\";\n }\n return `/${treePathSegments.join(\"/\")}`;\n}\n\nexport function createAppPageLayoutEntries<\n TModule extends AppPageModule,\n TErrorModule extends AppPageErrorModule,\n>(\n route: Pick<\n AppPageRouteWiringRoute<TModule, TErrorModule>,\n \"errors\" | \"layoutTreePositions\" | \"layouts\" | \"notFounds\" | \"routeSegments\"\n >,\n): AppPageLayoutEntry<TModule, TErrorModule>[] {\n return route.layouts.map((layoutModule, index) => {\n const treePosition = route.layoutTreePositions?.[index] ?? 0;\n const treePath = createAppPageTreePath(route.routeSegments, treePosition);\n return {\n errorModule: route.errors?.[index] ?? null,\n id: `layout:${treePath}`,\n layoutModule,\n notFoundModule: route.notFounds?.[index] ?? null,\n treePath,\n treePosition,\n };\n });\n}\n\nexport function resolveAppPageChildSegments(\n routeSegments: readonly string[],\n treePosition: number,\n params: Readonly<Record<string, string | string[] | undefined>>,\n): string[] {\n const rawSegments = routeSegments.slice(treePosition);\n const resolvedSegments: string[] = [];\n\n for (const segment of rawSegments) {\n if (\n segment.startsWith(\"[[...\") &&\n segment.endsWith(\"]]\") &&\n segment.length >= \"[[...x]]\".length\n ) {\n const paramName = segment.slice(5, -2);\n const paramValue = params[paramName];\n if (Array.isArray(paramValue) && paramValue.length === 0) {\n continue;\n }\n if (paramValue === undefined) {\n continue;\n }\n resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join(\"/\") : paramValue);\n continue;\n }\n\n if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\n const paramName = segment.slice(4, -1);\n const paramValue = params[paramName];\n if (Array.isArray(paramValue)) {\n resolvedSegments.push(paramValue.join(\"/\"));\n continue;\n }\n resolvedSegments.push(paramValue ?? segment);\n continue;\n }\n\n if (segment.startsWith(\"[\") && segment.endsWith(\"]\") && !segment.includes(\".\")) {\n const paramName = segment.slice(1, -1);\n const paramValue = params[paramName];\n resolvedSegments.push(\n Array.isArray(paramValue) ? paramValue.join(\"/\") : (paramValue ?? segment),\n );\n continue;\n }\n\n resolvedSegments.push(segment);\n }\n\n return resolvedSegments;\n}\n\nexport function buildAppPageRouteElement<\n TModule extends AppPageModule,\n TErrorModule extends AppPageErrorModule,\n>(options: BuildAppPageRouteElementOptions<TModule, TErrorModule>): ReactNode {\n let element: ReactNode = (\n <LayoutSegmentProvider segmentMap={{ children: [] }}>{options.element}</LayoutSegmentProvider>\n );\n\n element = (\n <>\n <meta charSet=\"utf-8\" />\n {options.resolvedMetadata ? <MetadataHead metadata={options.resolvedMetadata} /> : null}\n <ViewportHead viewport={options.resolvedViewport} />\n {element}\n </>\n );\n\n const loadingComponent = getDefaultExport(options.route.loading);\n if (loadingComponent) {\n const LoadingComponent = loadingComponent;\n element = <Suspense fallback={<LoadingComponent />}>{element}</Suspense>;\n }\n\n const lastLayoutErrorModule =\n options.route.errors && options.route.errors.length > 0\n ? options.route.errors[options.route.errors.length - 1]\n : null;\n const pageErrorComponent = getErrorBoundaryExport(options.route.error);\n if (pageErrorComponent && options.route.error !== lastLayoutErrorModule) {\n element = <ErrorBoundary fallback={pageErrorComponent}>{element}</ErrorBoundary>;\n }\n\n const notFoundComponent =\n getDefaultExport(options.route.notFound) ?? getDefaultExport(options.rootNotFoundModule);\n if (notFoundComponent) {\n const NotFoundComponent = notFoundComponent;\n element = <NotFoundBoundary fallback={<NotFoundComponent />}>{element}</NotFoundBoundary>;\n }\n\n const templates = options.route.templates ?? [];\n const routeSlots = options.route.slots ?? {};\n const layoutEntries = createAppPageLayoutEntries(options.route);\n const routeThenableParams = options.makeThenableParams(options.matchedParams);\n\n for (let index = layoutEntries.length - 1; index >= 0; index--) {\n const layoutEntry = layoutEntries[index];\n\n // Next.js nesting per segment (outer to inner): Layout > Template > Error > NotFound > children\n // Building bottom-up: NotFoundBoundary is the innermost wrapper, then ErrorBoundary, then Template.\n const layoutNotFoundComponent = getDefaultExport(layoutEntry.notFoundModule);\n if (layoutNotFoundComponent) {\n const LayoutNotFoundComponent = layoutNotFoundComponent;\n element = (\n <NotFoundBoundary fallback={<LayoutNotFoundComponent />}>{element}</NotFoundBoundary>\n );\n }\n\n const layoutErrorComponent = getErrorBoundaryExport(layoutEntry.errorModule);\n if (layoutErrorComponent) {\n element = <ErrorBoundary fallback={layoutErrorComponent}>{element}</ErrorBoundary>;\n }\n\n const templateComponent = getDefaultExport(templates[index]);\n if (templateComponent) {\n const TemplateComponent = templateComponent;\n element = <TemplateComponent params={options.matchedParams}>{element}</TemplateComponent>;\n }\n\n const layoutComponent = getDefaultExport(layoutEntry.layoutModule);\n if (!layoutComponent) {\n continue;\n }\n\n const layoutProps: Record<string, unknown> = {\n params: routeThenableParams,\n };\n\n for (const [slotName, slot] of Object.entries(routeSlots)) {\n const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;\n if (index !== targetIndex) {\n continue;\n }\n\n const slotOverride = options.slotOverrides?.[slotName];\n const slotParams = slotOverride?.params ?? options.matchedParams;\n const slotComponent =\n getDefaultExport(slotOverride?.pageModule) ??\n getDefaultExport(slot.page) ??\n getDefaultExport(slot.default);\n if (!slotComponent) {\n continue;\n }\n\n const slotProps: Record<string, unknown> = {\n params: options.makeThenableParams(slotParams),\n };\n if (slotOverride?.props) {\n Object.assign(slotProps, slotOverride.props);\n }\n\n const SlotComponent = slotComponent;\n let slotElement: ReactNode = <SlotComponent {...slotProps} />;\n\n const slotLayoutComponent = getDefaultExport(slot.layout);\n if (slotLayoutComponent) {\n const SlotLayoutComponent = slotLayoutComponent;\n slotElement = (\n <SlotLayoutComponent params={options.makeThenableParams(slotParams)}>\n {slotElement}\n </SlotLayoutComponent>\n );\n }\n\n const slotLoadingComponent = getDefaultExport(slot.loading);\n if (slotLoadingComponent) {\n const SlotLoadingComponent = slotLoadingComponent;\n slotElement = <Suspense fallback={<SlotLoadingComponent />}>{slotElement}</Suspense>;\n }\n\n const slotErrorComponent = getErrorBoundaryExport(slot.error);\n if (slotErrorComponent) {\n slotElement = <ErrorBoundary fallback={slotErrorComponent}>{slotElement}</ErrorBoundary>;\n }\n\n layoutProps[slotName] = slotElement;\n }\n\n const LayoutComponent = layoutComponent;\n element = <LayoutComponent {...layoutProps}>{element}</LayoutComponent>;\n\n // Build the segment map for this layout level. The \"children\" key always\n // contains the route segments below this layout. Named parallel slots at\n // this layout level add their own keys with per-slot segment data.\n const segmentMap: { children: string[] } & Record<string, string[]> = {\n children: resolveAppPageChildSegments(\n options.route.routeSegments ?? [],\n layoutEntry.treePosition,\n options.matchedParams,\n ),\n };\n for (const [slotName, slot] of Object.entries(routeSlots)) {\n const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;\n if (index !== targetIndex) {\n continue;\n }\n if (slot.routeSegments) {\n // Slot has an active page — resolve its segments (dynamic params → values)\n segmentMap[slotName] = resolveAppPageChildSegments(\n slot.routeSegments,\n 0, // Slot segments are already relative to the slot root\n options.matchedParams,\n );\n } else {\n // Slot is showing default.tsx or has no page — empty segments\n segmentMap[slotName] = [];\n }\n }\n element = <LayoutSegmentProvider segmentMap={segmentMap}>{element}</LayoutSegmentProvider>;\n }\n\n const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);\n if (globalErrorComponent) {\n element = <ErrorBoundary fallback={globalErrorComponent}>{element}</ErrorBoundary>;\n }\n\n return element;\n}\n"],"mappings":"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"}
@@ -33,6 +33,9 @@ function deferUntilStreamConsumed(stream, onFlush) {
33
33
  return reader.read().then(({ done, value }) => {
34
34
  if (done) controller.close();
35
35
  else controller.enqueue(value);
36
+ }, (error) => {
37
+ once();
38
+ controller.error(error);
36
39
  });
37
40
  },
38
41
  cancel(reason) {
@@ -1 +1 @@
1
- {"version":3,"file":"app-page-stream.js","names":[],"sources":["../../src/server/app-page-stream.ts"],"sourcesContent":["import type { AppPageFontPreload } from \"./app-page-execution.js\";\n\nexport type AppPageFontData = {\n links: string[];\n preloads: readonly AppPageFontPreload[];\n styles: string[];\n};\n\nexport type CreateAppPageFontDataOptions = {\n getLinks: () => string[];\n getPreloads: () => AppPageFontPreload[];\n getStyles: () => string[];\n};\n\nexport type AppPageSsrHandler = {\n handleSsr: (\n rscStream: ReadableStream<Uint8Array>,\n navigationContext: unknown,\n fontData: AppPageFontData,\n ) => Promise<ReadableStream<Uint8Array>>;\n};\n\nexport type RenderAppPageHtmlStreamOptions = {\n fontData: AppPageFontData;\n navigationContext: unknown;\n rscStream: ReadableStream<Uint8Array>;\n ssrHandler: AppPageSsrHandler;\n};\n\nexport type RenderAppPageHtmlResponseOptions = {\n clearRequestContext: () => void;\n fontLinkHeader?: string;\n status: number;\n} & RenderAppPageHtmlStreamOptions;\n\nexport type AppPageHtmlStreamRecoveryResult = {\n htmlStream: ReadableStream<Uint8Array> | null;\n response: Response | null;\n};\n\nexport type RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError> = {\n onShellRendered?: () => void;\n renderErrorBoundaryResponse: (error: unknown) => Promise<Response | null>;\n renderHtmlStream: () => Promise<ReadableStream<Uint8Array>>;\n renderSpecialErrorResponse: (specialError: TSpecialError) => Promise<Response>;\n resolveSpecialError: (error: unknown) => TSpecialError | null;\n};\n\nexport type AppPageRscErrorTracker = {\n getCapturedError: () => unknown;\n onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;\n};\n\nexport type ShouldRerenderAppPageWithGlobalErrorOptions = {\n capturedError: unknown;\n hasLocalBoundary: boolean;\n};\n\nexport function createAppPageFontData(options: CreateAppPageFontDataOptions): AppPageFontData {\n return {\n links: options.getLinks(),\n preloads: options.getPreloads(),\n styles: options.getStyles(),\n };\n}\n\nexport async function renderAppPageHtmlStream(\n options: RenderAppPageHtmlStreamOptions,\n): Promise<ReadableStream<Uint8Array>> {\n return options.ssrHandler.handleSsr(\n options.rscStream,\n options.navigationContext,\n options.fontData,\n );\n}\n\n/**\n * Wraps a stream so that `onFlush` is called when the last byte has been read\n * by the downstream consumer (i.e. when the HTTP layer finishes draining the\n * response body). This is the correct place to clear per-request context,\n * because the RSC/SSR pipeline is lazy — components execute while the stream\n * is being consumed, not when the stream handle is first obtained.\n */\nexport function deferUntilStreamConsumed(\n stream: ReadableStream<Uint8Array>,\n onFlush: () => void,\n): ReadableStream<Uint8Array> {\n let called = false;\n const once = () => {\n if (!called) {\n called = true;\n onFlush();\n }\n };\n\n const cleanup = new TransformStream<Uint8Array, Uint8Array>({\n flush() {\n once();\n },\n });\n\n const piped = stream.pipeThrough(cleanup);\n\n // Wrap with a ReadableStream so we can intercept cancel() — the TransformStream\n // Transformer interface does not expose a cancel hook in the Web Streams spec.\n const reader = piped.getReader();\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n return reader.read().then(({ done, value }) => {\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n });\n },\n cancel(reason) {\n // Stream cancelled before fully consumed (e.g. client disconnected).\n // Still clear per-request context to avoid leaks.\n once();\n return reader.cancel(reason);\n },\n });\n}\n\nexport async function renderAppPageHtmlResponse(\n options: RenderAppPageHtmlResponseOptions,\n): Promise<Response> {\n const htmlStream = await renderAppPageHtmlStream(options);\n\n // Defer clearRequestContext() until the stream is fully consumed by the HTTP\n // layer. Calling it synchronously here would race the lazy RSC/SSR pipeline:\n // components execute while the stream is being pulled, not when the handle\n // is first returned. See: https://github.com/cloudflare/vinext/issues/660\n const safeStream = deferUntilStreamConsumed(htmlStream, () => {\n options.clearRequestContext();\n });\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n };\n\n if (options.fontLinkHeader) {\n headers.Link = options.fontLinkHeader;\n }\n\n return new Response(safeStream, {\n status: options.status,\n headers,\n });\n}\n\nexport async function renderAppPageHtmlStreamWithRecovery<TSpecialError>(\n options: RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError>,\n): Promise<AppPageHtmlStreamRecoveryResult> {\n try {\n const htmlStream = await options.renderHtmlStream();\n options.onShellRendered?.();\n return {\n htmlStream,\n response: null,\n };\n } catch (error) {\n const specialError = options.resolveSpecialError(error);\n if (specialError) {\n return {\n htmlStream: null,\n response: await options.renderSpecialErrorResponse(specialError),\n };\n }\n\n const boundaryResponse = await options.renderErrorBoundaryResponse(error);\n if (boundaryResponse) {\n return {\n htmlStream: null,\n response: boundaryResponse,\n };\n }\n\n throw error;\n }\n}\n\nexport function createAppPageRscErrorTracker(\n baseOnError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown,\n): AppPageRscErrorTracker {\n let capturedError: unknown = null;\n\n return {\n getCapturedError() {\n return capturedError;\n },\n onRenderError(error, requestInfo, errorContext) {\n if (!(error && typeof error === \"object\" && \"digest\" in error)) {\n capturedError = error;\n }\n return baseOnError(error, requestInfo, errorContext);\n },\n };\n}\n\nexport function shouldRerenderAppPageWithGlobalError(\n options: ShouldRerenderAppPageWithGlobalErrorOptions,\n): boolean {\n return Boolean(options.capturedError) && !options.hasLocalBoundary;\n}\n"],"mappings":";AA0DA,SAAgB,sBAAsB,SAAwD;AAC5F,QAAO;EACL,OAAO,QAAQ,UAAU;EACzB,UAAU,QAAQ,aAAa;EAC/B,QAAQ,QAAQ,WAAW;EAC5B;;AAGH,eAAsB,wBACpB,SACqC;AACrC,QAAO,QAAQ,WAAW,UACxB,QAAQ,WACR,QAAQ,mBACR,QAAQ,SACT;;;;;;;;;AAUH,SAAgB,yBACd,QACA,SAC4B;CAC5B,IAAI,SAAS;CACb,MAAM,aAAa;AACjB,MAAI,CAAC,QAAQ;AACX,YAAS;AACT,YAAS;;;CAIb,MAAM,UAAU,IAAI,gBAAwC,EAC1D,QAAQ;AACN,QAAM;IAET,CAAC;CAMF,MAAM,SAJQ,OAAO,YAAY,QAAQ,CAIpB,WAAW;AAChC,QAAO,IAAI,eAA2B;EACpC,KAAK,YAAY;AACf,UAAO,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,YAAY;AAC7C,QAAI,KACF,YAAW,OAAO;QAElB,YAAW,QAAQ,MAAM;KAE3B;;EAEJ,OAAO,QAAQ;AAGb,SAAM;AACN,UAAO,OAAO,OAAO,OAAO;;EAE/B,CAAC;;AAGJ,eAAsB,0BACpB,SACmB;CAOnB,MAAM,aAAa,yBANA,MAAM,wBAAwB,QAAQ,QAMK;AAC5D,UAAQ,qBAAqB;GAC7B;CAEF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,MAAM;EACP;AAED,KAAI,QAAQ,eACV,SAAQ,OAAO,QAAQ;AAGzB,QAAO,IAAI,SAAS,YAAY;EAC9B,QAAQ,QAAQ;EAChB;EACD,CAAC;;AAGJ,eAAsB,oCACpB,SAC0C;AAC1C,KAAI;EACF,MAAM,aAAa,MAAM,QAAQ,kBAAkB;AACnD,UAAQ,mBAAmB;AAC3B,SAAO;GACL;GACA,UAAU;GACX;UACM,OAAO;EACd,MAAM,eAAe,QAAQ,oBAAoB,MAAM;AACvD,MAAI,aACF,QAAO;GACL,YAAY;GACZ,UAAU,MAAM,QAAQ,2BAA2B,aAAa;GACjE;EAGH,MAAM,mBAAmB,MAAM,QAAQ,4BAA4B,MAAM;AACzE,MAAI,iBACF,QAAO;GACL,YAAY;GACZ,UAAU;GACX;AAGH,QAAM;;;AAIV,SAAgB,6BACd,aACwB;CACxB,IAAI,gBAAyB;AAE7B,QAAO;EACL,mBAAmB;AACjB,UAAO;;EAET,cAAc,OAAO,aAAa,cAAc;AAC9C,OAAI,EAAE,SAAS,OAAO,UAAU,YAAY,YAAY,OACtD,iBAAgB;AAElB,UAAO,YAAY,OAAO,aAAa,aAAa;;EAEvD;;AAGH,SAAgB,qCACd,SACS;AACT,QAAO,QAAQ,QAAQ,cAAc,IAAI,CAAC,QAAQ"}
1
+ {"version":3,"file":"app-page-stream.js","names":[],"sources":["../../src/server/app-page-stream.ts"],"sourcesContent":["import type { AppPageFontPreload } from \"./app-page-execution.js\";\n\nexport type AppPageFontData = {\n links: string[];\n preloads: readonly AppPageFontPreload[];\n styles: string[];\n};\n\nexport type CreateAppPageFontDataOptions = {\n getLinks: () => string[];\n getPreloads: () => AppPageFontPreload[];\n getStyles: () => string[];\n};\n\nexport type AppPageSsrHandler = {\n handleSsr: (\n rscStream: ReadableStream<Uint8Array>,\n navigationContext: unknown,\n fontData: AppPageFontData,\n ) => Promise<ReadableStream<Uint8Array>>;\n};\n\nexport type RenderAppPageHtmlStreamOptions = {\n fontData: AppPageFontData;\n navigationContext: unknown;\n rscStream: ReadableStream<Uint8Array>;\n ssrHandler: AppPageSsrHandler;\n};\n\nexport type RenderAppPageHtmlResponseOptions = {\n clearRequestContext: () => void;\n fontLinkHeader?: string;\n status: number;\n} & RenderAppPageHtmlStreamOptions;\n\nexport type AppPageHtmlStreamRecoveryResult = {\n htmlStream: ReadableStream<Uint8Array> | null;\n response: Response | null;\n};\n\nexport type RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError> = {\n onShellRendered?: () => void;\n renderErrorBoundaryResponse: (error: unknown) => Promise<Response | null>;\n renderHtmlStream: () => Promise<ReadableStream<Uint8Array>>;\n renderSpecialErrorResponse: (specialError: TSpecialError) => Promise<Response>;\n resolveSpecialError: (error: unknown) => TSpecialError | null;\n};\n\nexport type AppPageRscErrorTracker = {\n getCapturedError: () => unknown;\n onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;\n};\n\nexport type ShouldRerenderAppPageWithGlobalErrorOptions = {\n capturedError: unknown;\n hasLocalBoundary: boolean;\n};\n\nexport function createAppPageFontData(options: CreateAppPageFontDataOptions): AppPageFontData {\n return {\n links: options.getLinks(),\n preloads: options.getPreloads(),\n styles: options.getStyles(),\n };\n}\n\nexport async function renderAppPageHtmlStream(\n options: RenderAppPageHtmlStreamOptions,\n): Promise<ReadableStream<Uint8Array>> {\n return options.ssrHandler.handleSsr(\n options.rscStream,\n options.navigationContext,\n options.fontData,\n );\n}\n\n/**\n * Wraps a stream so that `onFlush` is called when the last byte has been read\n * by the downstream consumer (i.e. when the HTTP layer finishes draining the\n * response body). This is the correct place to clear per-request context,\n * because the RSC/SSR pipeline is lazy — components execute while the stream\n * is being consumed, not when the stream handle is first obtained.\n */\nexport function deferUntilStreamConsumed(\n stream: ReadableStream<Uint8Array>,\n onFlush: () => void,\n): ReadableStream<Uint8Array> {\n let called = false;\n const once = () => {\n if (!called) {\n called = true;\n onFlush();\n }\n };\n\n const cleanup = new TransformStream<Uint8Array, Uint8Array>({\n flush() {\n once();\n },\n });\n\n const piped = stream.pipeThrough(cleanup);\n\n // Wrap with a ReadableStream so we can intercept cancel() — the TransformStream\n // Transformer interface does not expose a cancel hook in the Web Streams spec.\n const reader = piped.getReader();\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n return reader.read().then(\n ({ done, value }) => {\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n },\n (error) => {\n once();\n controller.error(error);\n },\n );\n },\n cancel(reason) {\n // Stream cancelled before fully consumed (e.g. client disconnected).\n // Still clear per-request context to avoid leaks.\n once();\n return reader.cancel(reason);\n },\n });\n}\n\nexport async function renderAppPageHtmlResponse(\n options: RenderAppPageHtmlResponseOptions,\n): Promise<Response> {\n const htmlStream = await renderAppPageHtmlStream(options);\n\n // Defer clearRequestContext() until the stream is fully consumed by the HTTP\n // layer. Calling it synchronously here would race the lazy RSC/SSR pipeline:\n // components execute while the stream is being pulled, not when the handle\n // is first returned. See: https://github.com/cloudflare/vinext/issues/660\n const safeStream = deferUntilStreamConsumed(htmlStream, () => {\n options.clearRequestContext();\n });\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n };\n\n if (options.fontLinkHeader) {\n headers.Link = options.fontLinkHeader;\n }\n\n return new Response(safeStream, {\n status: options.status,\n headers,\n });\n}\n\nexport async function renderAppPageHtmlStreamWithRecovery<TSpecialError>(\n options: RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError>,\n): Promise<AppPageHtmlStreamRecoveryResult> {\n try {\n const htmlStream = await options.renderHtmlStream();\n options.onShellRendered?.();\n return {\n htmlStream,\n response: null,\n };\n } catch (error) {\n const specialError = options.resolveSpecialError(error);\n if (specialError) {\n return {\n htmlStream: null,\n response: await options.renderSpecialErrorResponse(specialError),\n };\n }\n\n const boundaryResponse = await options.renderErrorBoundaryResponse(error);\n if (boundaryResponse) {\n return {\n htmlStream: null,\n response: boundaryResponse,\n };\n }\n\n throw error;\n }\n}\n\nexport function createAppPageRscErrorTracker(\n baseOnError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown,\n): AppPageRscErrorTracker {\n let capturedError: unknown = null;\n\n return {\n getCapturedError() {\n return capturedError;\n },\n onRenderError(error, requestInfo, errorContext) {\n if (!(error && typeof error === \"object\" && \"digest\" in error)) {\n capturedError = error;\n }\n return baseOnError(error, requestInfo, errorContext);\n },\n };\n}\n\nexport function shouldRerenderAppPageWithGlobalError(\n options: ShouldRerenderAppPageWithGlobalErrorOptions,\n): boolean {\n return Boolean(options.capturedError) && !options.hasLocalBoundary;\n}\n"],"mappings":";AA0DA,SAAgB,sBAAsB,SAAwD;AAC5F,QAAO;EACL,OAAO,QAAQ,UAAU;EACzB,UAAU,QAAQ,aAAa;EAC/B,QAAQ,QAAQ,WAAW;EAC5B;;AAGH,eAAsB,wBACpB,SACqC;AACrC,QAAO,QAAQ,WAAW,UACxB,QAAQ,WACR,QAAQ,mBACR,QAAQ,SACT;;;;;;;;;AAUH,SAAgB,yBACd,QACA,SAC4B;CAC5B,IAAI,SAAS;CACb,MAAM,aAAa;AACjB,MAAI,CAAC,QAAQ;AACX,YAAS;AACT,YAAS;;;CAIb,MAAM,UAAU,IAAI,gBAAwC,EAC1D,QAAQ;AACN,QAAM;IAET,CAAC;CAMF,MAAM,SAJQ,OAAO,YAAY,QAAQ,CAIpB,WAAW;AAChC,QAAO,IAAI,eAA2B;EACpC,KAAK,YAAY;AACf,UAAO,OAAO,MAAM,CAAC,MAClB,EAAE,MAAM,YAAY;AACnB,QAAI,KACF,YAAW,OAAO;QAElB,YAAW,QAAQ,MAAM;OAG5B,UAAU;AACT,UAAM;AACN,eAAW,MAAM,MAAM;KAE1B;;EAEH,OAAO,QAAQ;AAGb,SAAM;AACN,UAAO,OAAO,OAAO,OAAO;;EAE/B,CAAC;;AAGJ,eAAsB,0BACpB,SACmB;CAOnB,MAAM,aAAa,yBANA,MAAM,wBAAwB,QAAQ,QAMK;AAC5D,UAAQ,qBAAqB;GAC7B;CAEF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,MAAM;EACP;AAED,KAAI,QAAQ,eACV,SAAQ,OAAO,QAAQ;AAGzB,QAAO,IAAI,SAAS,YAAY;EAC9B,QAAQ,QAAQ;EAChB;EACD,CAAC;;AAGJ,eAAsB,oCACpB,SAC0C;AAC1C,KAAI;EACF,MAAM,aAAa,MAAM,QAAQ,kBAAkB;AACnD,UAAQ,mBAAmB;AAC3B,SAAO;GACL;GACA,UAAU;GACX;UACM,OAAO;EACd,MAAM,eAAe,QAAQ,oBAAoB,MAAM;AACvD,MAAI,aACF,QAAO;GACL,YAAY;GACZ,UAAU,MAAM,QAAQ,2BAA2B,aAAa;GACjE;EAGH,MAAM,mBAAmB,MAAM,QAAQ,4BAA4B,MAAM;AACzE,MAAI,iBACF,QAAO;GACL,YAAY;GACZ,UAAU;GACX;AAGH,QAAM;;;AAIV,SAAgB,6BACd,aACwB;CACxB,IAAI,gBAAyB;AAE7B,QAAO;EACL,mBAAmB;AACjB,UAAO;;EAET,cAAc,OAAO,aAAa,cAAc;AAC9C,OAAI,EAAE,SAAS,OAAO,UAAU,YAAY,YAAY,OACtD,iBAAgB;AAElB,UAAO,YAAY,OAAO,aAAa,aAAa;;EAEvD;;AAGH,SAAgB,qCACd,SACS;AACT,QAAO,QAAQ,QAAQ,cAAc,IAAI,CAAC,QAAQ"}
@@ -34,8 +34,11 @@ async function buildAppRouteCacheValue(response) {
34
34
  const body = await response.arrayBuffer();
35
35
  const headers = {};
36
36
  response.headers.forEach((value, key) => {
37
- if (key !== "x-vinext-cache" && key !== "cache-control") headers[key] = value;
37
+ if (key === "set-cookie" || key === "x-vinext-cache" || key === "cache-control") return;
38
+ headers[key] = value;
38
39
  });
40
+ const setCookies = response.headers.getSetCookie?.() ?? [];
41
+ if (setCookies.length > 0) headers["set-cookie"] = setCookies;
39
42
  return {
40
43
  kind: "APP_ROUTE",
41
44
  body,
@@ -1 +1 @@
1
- {"version":3,"file":"app-route-handler-response.js","names":[],"sources":["../../src/server/app-route-handler-response.ts"],"sourcesContent":["import type { CachedRouteValue } from \"../shims/cache.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type BuildRouteHandlerCachedResponseOptions = {\n cacheState: \"HIT\" | \"STALE\";\n isHead: boolean;\n revalidateSeconds: number;\n};\n\nexport type FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n): string {\n if (cacheState === \"STALE\") {\n return \"s-maxage=0, stale-while-revalidate\";\n }\n\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nexport function applyRouteHandlerMiddlewareContext(\n response: Response,\n middlewareContext: RouteHandlerMiddlewareContext,\n): Response {\n if (!middlewareContext.headers && middlewareContext.status == null) {\n return response;\n }\n\n const responseHeaders = new Headers(response.headers);\n if (middlewareContext.headers) {\n for (const [key, value] of middlewareContext.headers) {\n responseHeaders.append(key, value);\n }\n }\n\n return new Response(response.body, {\n status: middlewareContext.status ?? response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n}\n\nexport function buildRouteHandlerCachedResponse(\n cachedValue: CachedRouteValue,\n options: BuildRouteHandlerCachedResponseOptions,\n): Response {\n const headers = new Headers();\n for (const [key, value] of Object.entries(cachedValue.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) {\n headers.append(key, entry);\n }\n } else {\n headers.set(key, value);\n }\n }\n headers.set(\"X-Vinext-Cache\", options.cacheState);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, options.revalidateSeconds),\n );\n\n return new Response(options.isHead ? null : cachedValue.body, {\n status: cachedValue.status,\n headers,\n });\n}\n\nexport function applyRouteHandlerRevalidateHeader(\n response: Response,\n revalidateSeconds: number,\n): void {\n response.headers.set(\"cache-control\", buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds));\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n response.headers.set(\"X-Vinext-Cache\", \"MISS\");\n}\n\nexport async function buildAppRouteCacheValue(response: Response): Promise<CachedRouteValue> {\n const body = await response.arrayBuffer();\n const headers: CachedRouteValue[\"headers\"] = {};\n\n response.headers.forEach((value, key) => {\n if (key !== \"x-vinext-cache\" && key !== \"cache-control\") {\n headers[key] = value;\n }\n });\n\n return {\n kind: \"APP_ROUTE\",\n body,\n status: response.status,\n headers,\n };\n}\n\nexport function finalizeRouteHandlerResponse(\n response: Response,\n options: FinalizeRouteHandlerResponseOptions,\n): Response {\n const { pendingCookies, draftCookie, isHead } = options;\n if (pendingCookies.length === 0 && !draftCookie && !isHead) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const cookie of pendingCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n if (draftCookie) {\n headers.append(\"Set-Cookie\", draftCookie);\n }\n\n return new Response(isHead ? null : response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n"],"mappings":";AAmBA,SAAS,8BACP,YACA,mBACQ;AACR,KAAI,eAAe,QACjB,QAAO;AAGT,QAAO,YAAY,kBAAkB;;AAGvC,SAAgB,mCACd,UACA,mBACU;AACV,KAAI,CAAC,kBAAkB,WAAW,kBAAkB,UAAU,KAC5D,QAAO;CAGT,MAAM,kBAAkB,IAAI,QAAQ,SAAS,QAAQ;AACrD,KAAI,kBAAkB,QACpB,MAAK,MAAM,CAAC,KAAK,UAAU,kBAAkB,QAC3C,iBAAgB,OAAO,KAAK,MAAM;AAItC,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,kBAAkB,UAAU,SAAS;EAC7C,YAAY,SAAS;EACrB,SAAS;EACV,CAAC;;AAGJ,SAAgB,gCACd,aACA,SACU;CACV,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,QAAQ,CAC5D,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAO,KAAK,MAAM;KAG5B,SAAQ,IAAI,KAAK,MAAM;AAG3B,SAAQ,IAAI,kBAAkB,QAAQ,WAAW;AACjD,SAAQ,IACN,iBACA,8BAA8B,QAAQ,YAAY,QAAQ,kBAAkB,CAC7E;AAED,QAAO,IAAI,SAAS,QAAQ,SAAS,OAAO,YAAY,MAAM;EAC5D,QAAQ,YAAY;EACpB;EACD,CAAC;;AAGJ,SAAgB,kCACd,UACA,mBACM;AACN,UAAS,QAAQ,IAAI,iBAAiB,8BAA8B,OAAO,kBAAkB,CAAC;;AAGhG,SAAgB,0BAA0B,UAA0B;AAClE,UAAS,QAAQ,IAAI,kBAAkB,OAAO;;AAGhD,eAAsB,wBAAwB,UAA+C;CAC3F,MAAM,OAAO,MAAM,SAAS,aAAa;CACzC,MAAM,UAAuC,EAAE;AAE/C,UAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,MAAI,QAAQ,oBAAoB,QAAQ,gBACtC,SAAQ,OAAO;GAEjB;AAEF,QAAO;EACL,MAAM;EACN;EACA,QAAQ,SAAS;EACjB;EACD;;AAGH,SAAgB,6BACd,UACA,SACU;CACV,MAAM,EAAE,gBAAgB,aAAa,WAAW;AAChD,KAAI,eAAe,WAAW,KAAK,CAAC,eAAe,CAAC,OAClD,QAAO;CAGT,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAC7C,MAAK,MAAM,UAAU,eACnB,SAAQ,OAAO,cAAc,OAAO;AAEtC,KAAI,YACF,SAAQ,OAAO,cAAc,YAAY;AAG3C,QAAO,IAAI,SAAS,SAAS,OAAO,SAAS,MAAM;EACjD,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB;EACD,CAAC"}
1
+ {"version":3,"file":"app-route-handler-response.js","names":[],"sources":["../../src/server/app-route-handler-response.ts"],"sourcesContent":["import type { CachedRouteValue } from \"../shims/cache.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type BuildRouteHandlerCachedResponseOptions = {\n cacheState: \"HIT\" | \"STALE\";\n isHead: boolean;\n revalidateSeconds: number;\n};\n\nexport type FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n): string {\n if (cacheState === \"STALE\") {\n return \"s-maxage=0, stale-while-revalidate\";\n }\n\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nexport function applyRouteHandlerMiddlewareContext(\n response: Response,\n middlewareContext: RouteHandlerMiddlewareContext,\n): Response {\n if (!middlewareContext.headers && middlewareContext.status == null) {\n return response;\n }\n\n const responseHeaders = new Headers(response.headers);\n if (middlewareContext.headers) {\n for (const [key, value] of middlewareContext.headers) {\n responseHeaders.append(key, value);\n }\n }\n\n return new Response(response.body, {\n status: middlewareContext.status ?? response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n}\n\nexport function buildRouteHandlerCachedResponse(\n cachedValue: CachedRouteValue,\n options: BuildRouteHandlerCachedResponseOptions,\n): Response {\n const headers = new Headers();\n for (const [key, value] of Object.entries(cachedValue.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) {\n headers.append(key, entry);\n }\n } else {\n headers.set(key, value);\n }\n }\n headers.set(\"X-Vinext-Cache\", options.cacheState);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, options.revalidateSeconds),\n );\n\n return new Response(options.isHead ? null : cachedValue.body, {\n status: cachedValue.status,\n headers,\n });\n}\n\nexport function applyRouteHandlerRevalidateHeader(\n response: Response,\n revalidateSeconds: number,\n): void {\n response.headers.set(\"cache-control\", buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds));\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n response.headers.set(\"X-Vinext-Cache\", \"MISS\");\n}\n\nexport async function buildAppRouteCacheValue(response: Response): Promise<CachedRouteValue> {\n const body = await response.arrayBuffer();\n const headers: CachedRouteValue[\"headers\"] = {};\n\n response.headers.forEach((value, key) => {\n if (key === \"set-cookie\" || key === \"x-vinext-cache\" || key === \"cache-control\") return;\n headers[key] = value;\n });\n const setCookies = response.headers.getSetCookie?.() ?? [];\n if (setCookies.length > 0) {\n headers[\"set-cookie\"] = setCookies;\n }\n\n return {\n kind: \"APP_ROUTE\",\n body,\n status: response.status,\n headers,\n };\n}\n\nexport function finalizeRouteHandlerResponse(\n response: Response,\n options: FinalizeRouteHandlerResponseOptions,\n): Response {\n const { pendingCookies, draftCookie, isHead } = options;\n if (pendingCookies.length === 0 && !draftCookie && !isHead) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const cookie of pendingCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n if (draftCookie) {\n headers.append(\"Set-Cookie\", draftCookie);\n }\n\n return new Response(isHead ? null : response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n"],"mappings":";AAmBA,SAAS,8BACP,YACA,mBACQ;AACR,KAAI,eAAe,QACjB,QAAO;AAGT,QAAO,YAAY,kBAAkB;;AAGvC,SAAgB,mCACd,UACA,mBACU;AACV,KAAI,CAAC,kBAAkB,WAAW,kBAAkB,UAAU,KAC5D,QAAO;CAGT,MAAM,kBAAkB,IAAI,QAAQ,SAAS,QAAQ;AACrD,KAAI,kBAAkB,QACpB,MAAK,MAAM,CAAC,KAAK,UAAU,kBAAkB,QAC3C,iBAAgB,OAAO,KAAK,MAAM;AAItC,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,kBAAkB,UAAU,SAAS;EAC7C,YAAY,SAAS;EACrB,SAAS;EACV,CAAC;;AAGJ,SAAgB,gCACd,aACA,SACU;CACV,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,QAAQ,CAC5D,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAO,KAAK,MAAM;KAG5B,SAAQ,IAAI,KAAK,MAAM;AAG3B,SAAQ,IAAI,kBAAkB,QAAQ,WAAW;AACjD,SAAQ,IACN,iBACA,8BAA8B,QAAQ,YAAY,QAAQ,kBAAkB,CAC7E;AAED,QAAO,IAAI,SAAS,QAAQ,SAAS,OAAO,YAAY,MAAM;EAC5D,QAAQ,YAAY;EACpB;EACD,CAAC;;AAGJ,SAAgB,kCACd,UACA,mBACM;AACN,UAAS,QAAQ,IAAI,iBAAiB,8BAA8B,OAAO,kBAAkB,CAAC;;AAGhG,SAAgB,0BAA0B,UAA0B;AAClE,UAAS,QAAQ,IAAI,kBAAkB,OAAO;;AAGhD,eAAsB,wBAAwB,UAA+C;CAC3F,MAAM,OAAO,MAAM,SAAS,aAAa;CACzC,MAAM,UAAuC,EAAE;AAE/C,UAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,MAAI,QAAQ,gBAAgB,QAAQ,oBAAoB,QAAQ,gBAAiB;AACjF,UAAQ,OAAO;GACf;CACF,MAAM,aAAa,SAAS,QAAQ,gBAAgB,IAAI,EAAE;AAC1D,KAAI,WAAW,SAAS,EACtB,SAAQ,gBAAgB;AAG1B,QAAO;EACL,MAAM;EACN;EACA,QAAQ,SAAS;EACjB;EACD;;AAGH,SAAgB,6BACd,UACA,SACU;CACV,MAAM,EAAE,gBAAgB,aAAa,WAAW;AAChD,KAAI,eAAe,WAAW,KAAK,CAAC,eAAe,CAAC,OAClD,QAAO;CAGT,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAC7C,MAAK,MAAM,UAAU,eACnB,SAAQ,OAAO,cAAc,OAAO;AAEtC,KAAI,YACF,SAAQ,OAAO,cAAc,YAAY;AAG3C,QAAO,IAAI,SAAS,SAAS,OAAO,SAAS,MAAM;EACjD,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB;EACD,CAAC"}
@@ -1,8 +1,13 @@
1
1
  import { ExecutionContextLike } from "../shims/request-context.js";
2
2
 
3
3
  //#region src/server/app-router-entry.d.ts
4
+ type WorkerAssetEnv = {
5
+ ASSETS?: {
6
+ fetch(request: Request): Promise<Response> | Response;
7
+ };
8
+ };
4
9
  declare const _default: {
5
- fetch(request: Request, _env?: unknown, ctx?: ExecutionContextLike): Promise<Response>;
10
+ fetch(request: Request, env?: WorkerAssetEnv, ctx?: ExecutionContextLike): Promise<Response>;
6
11
  };
7
12
  //#endregion
8
13
  export { _default as default };
@@ -1,4 +1,5 @@
1
1
  import { runWithExecutionContext } from "../shims/request-context.js";
2
+ import { resolveStaticAssetSignal } from "./worker-utils.js";
2
3
  import rscHandler from "virtual:vinext-rsc-entry";
3
4
  //#region src/server/app-router-entry.ts
4
5
  /**
@@ -14,11 +15,17 @@ import rscHandler from "virtual:vinext-rsc-entry";
14
15
  * This file runs in the RSC environment. Configure the Cloudflare plugin with:
15
16
  * cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } })
16
17
  */
17
- var app_router_entry_default = { async fetch(request, _env, ctx) {
18
+ var app_router_entry_default = { async fetch(request, env, ctx) {
18
19
  if (new URL(request.url).pathname.replaceAll("\\", "/").startsWith("//")) return new Response("404 Not Found", { status: 404 });
19
20
  const handleFn = () => rscHandler(request, ctx);
20
21
  const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());
21
- if (result instanceof Response) return result;
22
+ if (result instanceof Response) {
23
+ if (env?.ASSETS) {
24
+ const assetResponse = await resolveStaticAssetSignal(result, { fetchAsset: (path) => Promise.resolve(env.ASSETS.fetch(new Request(new URL(path, request.url)))) });
25
+ if (assetResponse) return assetResponse;
26
+ }
27
+ return result;
28
+ }
22
29
  if (result === null || result === void 0) return new Response("Not Found", { status: 404 });
23
30
  return new Response(String(result), { status: 200 });
24
31
  } };
@@ -1 +1 @@
1
- {"version":3,"file":"app-router-entry.js","names":[],"sources":["../../src/server/app-router-entry.ts"],"sourcesContent":["/**\n * Default Cloudflare Worker entry point for vinext App Router.\n *\n * Use this directly in wrangler.jsonc:\n * \"main\": \"vinext/server/app-router-entry\"\n *\n * Or import and delegate to it from a custom worker:\n * import handler from \"vinext/server/app-router-entry\";\n * return handler.fetch(request, env, ctx);\n *\n * This file runs in the RSC environment. Configure the Cloudflare plugin with:\n * cloudflare({ viteEnvironment: { name: \"rsc\", childEnvironments: [\"ssr\"] } })\n */\n\n// @ts-expect-error — virtual module resolved by vinext\nimport rscHandler from \"virtual:vinext-rsc-entry\";\nimport { runWithExecutionContext, type ExecutionContextLike } from \"../shims/request-context.js\";\n\nexport default {\n async fetch(request: Request, _env?: unknown, ctx?: ExecutionContextLike): Promise<Response> {\n const url = new URL(request.url);\n\n // Normalize backslashes (browsers treat /\\ as //) before any other checks.\n const rawPathname = url.pathname.replaceAll(\"\\\\\", \"/\");\n\n // Block protocol-relative URL open redirects (//evil.com/ or /\\evil.com/).\n // Check rawPathname BEFORE decode so the guard fires before normalization.\n if (rawPathname.startsWith(\"//\")) {\n return new Response(\"404 Not Found\", { status: 404 });\n }\n\n // Validate that percent-encoding is well-formed. The RSC handler performs\n // the actual decode + normalize; we only check here to return a clean 400\n // instead of letting a malformed sequence crash downstream.\n try {\n decodeURIComponent(rawPathname);\n } catch {\n // Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of throwing.\n return new Response(\"Bad Request\", { status: 400 });\n }\n\n // Do NOT decode/normalize the pathname here. The RSC handler\n // (virtual:vinext-rsc-entry) is the single point of decoding — it calls\n // decodeURIComponent + normalizePath on the incoming URL. Decoding here\n // AND in the handler would double-decode, causing inconsistent path\n // matching between middleware and routing.\n\n // Delegate to RSC handler (which decodes + normalizes the pathname itself),\n // wrapping in the ExecutionContext ALS scope so downstream code can reach\n // ctx.waitUntil() without having ctx threaded through every call site.\n const handleFn = () => rscHandler(request, ctx);\n const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());\n\n if (result instanceof Response) {\n return result;\n }\n\n if (result === null || result === undefined) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return new Response(String(result), { status: 200 });\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,IAAA,2BAAe,EACb,MAAM,MAAM,SAAkB,MAAgB,KAA+C;AAQ3F,KAPY,IAAI,IAAI,QAAQ,IAAI,CAGR,SAAS,WAAW,MAAM,IAAI,CAItC,WAAW,KAAK,CAC9B,QAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,KAAK,CAAC;CAsBvD,MAAM,iBAAiB,WAAW,SAAS,IAAI;CAC/C,MAAM,SAAS,OAAO,MAAM,wBAAwB,KAAK,SAAS,GAAG,UAAU;AAE/E,KAAI,kBAAkB,SACpB,QAAO;AAGT,KAAI,WAAW,QAAQ,WAAW,KAAA,EAChC,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;AAGnD,QAAO,IAAI,SAAS,OAAO,OAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;GAEvD"}
1
+ {"version":3,"file":"app-router-entry.js","names":[],"sources":["../../src/server/app-router-entry.ts"],"sourcesContent":["/**\n * Default Cloudflare Worker entry point for vinext App Router.\n *\n * Use this directly in wrangler.jsonc:\n * \"main\": \"vinext/server/app-router-entry\"\n *\n * Or import and delegate to it from a custom worker:\n * import handler from \"vinext/server/app-router-entry\";\n * return handler.fetch(request, env, ctx);\n *\n * This file runs in the RSC environment. Configure the Cloudflare plugin with:\n * cloudflare({ viteEnvironment: { name: \"rsc\", childEnvironments: [\"ssr\"] } })\n */\n\n// @ts-expect-error — virtual module resolved by vinext\nimport rscHandler from \"virtual:vinext-rsc-entry\";\nimport { runWithExecutionContext, type ExecutionContextLike } from \"../shims/request-context.js\";\nimport { resolveStaticAssetSignal } from \"./worker-utils.js\";\n\ntype WorkerAssetEnv = {\n ASSETS?: {\n fetch(request: Request): Promise<Response> | Response;\n };\n};\n\nexport default {\n async fetch(\n request: Request,\n env?: WorkerAssetEnv,\n ctx?: ExecutionContextLike,\n ): Promise<Response> {\n const url = new URL(request.url);\n\n // Normalize backslashes (browsers treat /\\ as //) before any other checks.\n const rawPathname = url.pathname.replaceAll(\"\\\\\", \"/\");\n\n // Block protocol-relative URL open redirects (//evil.com/ or /\\evil.com/).\n // Check rawPathname BEFORE decode so the guard fires before normalization.\n if (rawPathname.startsWith(\"//\")) {\n return new Response(\"404 Not Found\", { status: 404 });\n }\n\n // Validate that percent-encoding is well-formed. The RSC handler performs\n // the actual decode + normalize; we only check here to return a clean 400\n // instead of letting a malformed sequence crash downstream.\n try {\n decodeURIComponent(rawPathname);\n } catch {\n // Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of throwing.\n return new Response(\"Bad Request\", { status: 400 });\n }\n\n // Do NOT decode/normalize the pathname here. The RSC handler\n // (virtual:vinext-rsc-entry) is the single point of decoding — it calls\n // decodeURIComponent + normalizePath on the incoming URL. Decoding here\n // AND in the handler would double-decode, causing inconsistent path\n // matching between middleware and routing.\n\n // Delegate to RSC handler (which decodes + normalizes the pathname itself),\n // wrapping in the ExecutionContext ALS scope so downstream code can reach\n // ctx.waitUntil() without having ctx threaded through every call site.\n const handleFn = () => rscHandler(request, ctx);\n const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());\n\n if (result instanceof Response) {\n if (env?.ASSETS) {\n const assetResponse = await resolveStaticAssetSignal(result, {\n fetchAsset: (path) =>\n Promise.resolve(env.ASSETS!.fetch(new Request(new URL(path, request.url)))),\n });\n if (assetResponse) return assetResponse;\n }\n return result;\n }\n\n if (result === null || result === undefined) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return new Response(String(result), { status: 200 });\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,IAAA,2BAAe,EACb,MAAM,MACJ,SACA,KACA,KACmB;AAQnB,KAPY,IAAI,IAAI,QAAQ,IAAI,CAGR,SAAS,WAAW,MAAM,IAAI,CAItC,WAAW,KAAK,CAC9B,QAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,KAAK,CAAC;CAsBvD,MAAM,iBAAiB,WAAW,SAAS,IAAI;CAC/C,MAAM,SAAS,OAAO,MAAM,wBAAwB,KAAK,SAAS,GAAG,UAAU;AAE/E,KAAI,kBAAkB,UAAU;AAC9B,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,MAAM,yBAAyB,QAAQ,EAC3D,aAAa,SACX,QAAQ,QAAQ,IAAI,OAAQ,MAAM,IAAI,QAAQ,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC,EAC9E,CAAC;AACF,OAAI,cAAe,QAAO;;AAE5B,SAAO;;AAGT,KAAI,WAAW,QAAQ,WAAW,KAAA,EAChC,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;AAGnD,QAAO,IAAI,SAAS,OAAO,OAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;GAEvD"}
@@ -43,7 +43,7 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
43
43
  * Without a cache, falls back to async filesystem probing (still non-blocking,
44
44
  * unlike the old sync existsSync/statSync approach).
45
45
  */
46
- declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>): Promise<boolean>;
46
+ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number): Promise<boolean>;
47
47
  /**
48
48
  * Resolve the host for a request, ignoring X-Forwarded-Host to prevent
49
49
  * host header poisoning attacks (open redirects, cache poisoning).
@@ -248,8 +248,10 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
248
248
  * Without a cache, falls back to async filesystem probing (still non-blocking,
249
249
  * unlike the old sync existsSync/statSync approach).
250
250
  */
251
- async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders) {
251
+ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode) {
252
252
  if (pathname === "/") return false;
253
+ const responseStatus = statusCode ?? 200;
254
+ const omitBody = isNoBodyResponseStatus(responseStatus);
253
255
  if (cache) {
254
256
  let lookupPath;
255
257
  if (pathname.includes("%")) {
@@ -266,7 +268,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
266
268
  const entry = cache.lookup(lookupPath);
267
269
  if (!entry) return false;
268
270
  const ifNoneMatch = req.headers["if-none-match"];
269
- if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
271
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
270
272
  if (extraHeaders) res.writeHead(304, {
271
273
  ...entry.notModifiedHeaders,
272
274
  ...extraHeaders
@@ -278,12 +280,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
278
280
  const rawAe = compress ? req.headers["accept-encoding"] : void 0;
279
281
  const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
280
282
  const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
281
- if (extraHeaders) res.writeHead(200, {
283
+ if (extraHeaders) res.writeHead(responseStatus, {
282
284
  ...variant.headers,
283
285
  ...extraHeaders
284
286
  });
285
- else res.writeHead(200, variant.headers);
286
- if (req.method === "HEAD") {
287
+ else res.writeHead(responseStatus, variant.headers);
288
+ if (omitBody || req.method === "HEAD") {
287
289
  res.end();
288
290
  return true;
289
291
  }
@@ -316,7 +318,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
316
318
  const baseType = ct.split(";")[0].trim();
317
319
  const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
318
320
  const ifNoneMatch = req.headers["if-none-match"];
319
- if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
321
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
320
322
  const notModifiedHeaders = {
321
323
  ETag: etag,
322
324
  "Cache-Control": cacheControl,
@@ -336,12 +338,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
336
338
  if (isCompressible) {
337
339
  const encoding = negotiateEncoding(req);
338
340
  if (encoding) {
339
- res.writeHead(200, {
341
+ res.writeHead(responseStatus, {
340
342
  ...baseHeaders,
341
343
  "Content-Encoding": encoding,
342
344
  Vary: "Accept-Encoding"
343
345
  });
344
- if (req.method === "HEAD") {
346
+ if (omitBody || req.method === "HEAD") {
345
347
  res.end();
346
348
  return true;
347
349
  }
@@ -355,11 +357,11 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
355
357
  return true;
356
358
  }
357
359
  }
358
- res.writeHead(200, {
360
+ res.writeHead(responseStatus, {
359
361
  ...baseHeaders,
360
362
  "Content-Length": String(resolved.size)
361
363
  });
362
- if (req.method === "HEAD") {
364
+ if (omitBody || req.method === "HEAD") {
363
365
  res.end();
364
366
  return true;
365
367
  }
@@ -644,7 +646,31 @@ async function startAppRouterServer(options) {
644
646
  }
645
647
  try {
646
648
  const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
647
- await sendWebResponse(await rscHandler(nodeToWebRequest(req, pathname + qs)), req, res, compress);
649
+ const response = await rscHandler(nodeToWebRequest(req, pathname + qs));
650
+ const staticFileSignal = response.headers.get("x-vinext-static-file");
651
+ if (staticFileSignal) {
652
+ let staticFilePath = "/";
653
+ try {
654
+ staticFilePath = decodeURIComponent(staticFileSignal);
655
+ } catch {
656
+ staticFilePath = staticFileSignal;
657
+ }
658
+ const staticResponseHeaders = omitHeadersCaseInsensitive(mergeResponseHeaders({}, response), [
659
+ "x-vinext-static-file",
660
+ "content-encoding",
661
+ "content-length",
662
+ "content-type"
663
+ ]);
664
+ const served = await tryServeStatic(req, res, clientDir, staticFilePath, compress, staticCache, staticResponseHeaders, response.status);
665
+ cancelResponseBody(response);
666
+ if (served) return;
667
+ await sendWebResponse(new Response("Not Found", {
668
+ status: 404,
669
+ headers: toWebHeaders(staticResponseHeaders)
670
+ }), req, res, compress);
671
+ return;
672
+ }
673
+ await sendWebResponse(response, req, res, compress);
648
674
  } catch (e) {
649
675
  console.error("[vinext] Server error:", e);
650
676
  if (!res.headersSent) {