vinext 0.0.54 → 0.0.55

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 (135) hide show
  1. package/README.md +1 -0
  2. package/dist/check.js +15 -3
  3. package/dist/check.js.map +1 -1
  4. package/dist/client/navigation-runtime.d.ts +1 -0
  5. package/dist/client/navigation-runtime.js +1 -1
  6. package/dist/client/navigation-runtime.js.map +1 -1
  7. package/dist/config/next-config.d.ts +14 -1
  8. package/dist/config/next-config.js +24 -4
  9. package/dist/config/next-config.js.map +1 -1
  10. package/dist/config/tsconfig-paths.d.ts +12 -3
  11. package/dist/config/tsconfig-paths.js +55 -24
  12. package/dist/config/tsconfig-paths.js.map +1 -1
  13. package/dist/entries/app-rsc-entry.d.ts +2 -1
  14. package/dist/entries/app-rsc-entry.js +12 -0
  15. package/dist/entries/app-rsc-entry.js.map +1 -1
  16. package/dist/entries/app-rsc-manifest.js +22 -5
  17. package/dist/entries/app-rsc-manifest.js.map +1 -1
  18. package/dist/entries/pages-server-entry.js +41 -4
  19. package/dist/entries/pages-server-entry.js.map +1 -1
  20. package/dist/index.js +81 -39
  21. package/dist/index.js.map +1 -1
  22. package/dist/plugins/import-meta-url.d.ts +16 -0
  23. package/dist/plugins/import-meta-url.js +193 -0
  24. package/dist/plugins/import-meta-url.js.map +1 -0
  25. package/dist/server/app-browser-action-result.d.ts +9 -16
  26. package/dist/server/app-browser-action-result.js +25 -14
  27. package/dist/server/app-browser-action-result.js.map +1 -1
  28. package/dist/server/app-browser-entry.js +171 -45
  29. package/dist/server/app-browser-entry.js.map +1 -1
  30. package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
  31. package/dist/server/app-browser-mpa-navigation.js +36 -0
  32. package/dist/server/app-browser-mpa-navigation.js.map +1 -0
  33. package/dist/server/app-browser-popstate.d.ts +3 -1
  34. package/dist/server/app-browser-popstate.js +15 -1
  35. package/dist/server/app-browser-popstate.js.map +1 -1
  36. package/dist/server/app-browser-state.js +2 -1
  37. package/dist/server/app-browser-state.js.map +1 -1
  38. package/dist/server/app-layout-param-observation.d.ts +30 -0
  39. package/dist/server/app-layout-param-observation.js +130 -0
  40. package/dist/server/app-layout-param-observation.js.map +1 -0
  41. package/dist/server/app-page-boundary-render.js +2 -2
  42. package/dist/server/app-page-boundary-render.js.map +1 -1
  43. package/dist/server/app-page-dispatch.js +1 -1
  44. package/dist/server/app-page-params.d.ts +2 -1
  45. package/dist/server/app-page-params.js +14 -1
  46. package/dist/server/app-page-params.js.map +1 -1
  47. package/dist/server/app-page-probe.d.ts +12 -1
  48. package/dist/server/app-page-probe.js +116 -1
  49. package/dist/server/app-page-probe.js.map +1 -1
  50. package/dist/server/app-route-handler-response.js +1 -1
  51. package/dist/server/app-route-handler-response.js.map +1 -1
  52. package/dist/server/app-rsc-cache-busting.d.ts +3 -2
  53. package/dist/server/app-rsc-cache-busting.js +9 -7
  54. package/dist/server/app-rsc-cache-busting.js.map +1 -1
  55. package/dist/server/app-rsc-handler.js +11 -1
  56. package/dist/server/app-rsc-handler.js.map +1 -1
  57. package/dist/server/app-segment-config.d.ts +1 -1
  58. package/dist/server/app-segment-config.js +4 -1
  59. package/dist/server/app-segment-config.js.map +1 -1
  60. package/dist/server/app-server-action-execution.d.ts +5 -0
  61. package/dist/server/app-server-action-execution.js +198 -22
  62. package/dist/server/app-server-action-execution.js.map +1 -1
  63. package/dist/server/artifact-compatibility.d.ts +2 -1
  64. package/dist/server/artifact-compatibility.js +10 -1
  65. package/dist/server/artifact-compatibility.js.map +1 -1
  66. package/dist/server/client-reuse-manifest.d.ts +9 -4
  67. package/dist/server/client-reuse-manifest.js +2 -1
  68. package/dist/server/client-reuse-manifest.js.map +1 -1
  69. package/dist/server/dev-server.js +52 -10
  70. package/dist/server/dev-server.js.map +1 -1
  71. package/dist/server/document-initial-head.d.ts +7 -0
  72. package/dist/server/document-initial-head.js +35 -0
  73. package/dist/server/document-initial-head.js.map +1 -0
  74. package/dist/server/pages-document-initial-props.d.ts +84 -2
  75. package/dist/server/pages-document-initial-props.js +127 -1
  76. package/dist/server/pages-document-initial-props.js.map +1 -1
  77. package/dist/server/pages-node-compat.js +1 -1
  78. package/dist/server/pages-page-response.d.ts +14 -0
  79. package/dist/server/pages-page-response.js +31 -8
  80. package/dist/server/pages-page-response.js.map +1 -1
  81. package/dist/server/prod-server.js +13 -6
  82. package/dist/server/prod-server.js.map +1 -1
  83. package/dist/server/skip-cache-proof.d.ts +23 -2
  84. package/dist/server/skip-cache-proof.js +81 -12
  85. package/dist/server/skip-cache-proof.js.map +1 -1
  86. package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
  87. package/dist/server/static-layout-client-reuse-proof.js +35 -0
  88. package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
  89. package/dist/shims/cache.d.ts +21 -1
  90. package/dist/shims/cache.js +101 -6
  91. package/dist/shims/cache.js.map +1 -1
  92. package/dist/shims/document.d.ts +6 -0
  93. package/dist/shims/document.js +7 -8
  94. package/dist/shims/document.js.map +1 -1
  95. package/dist/shims/error-boundary.d.ts +4 -4
  96. package/dist/shims/error-boundary.js +27 -28
  97. package/dist/shims/error-boundary.js.map +1 -1
  98. package/dist/shims/fetch-cache.d.ts +3 -1
  99. package/dist/shims/fetch-cache.js +16 -5
  100. package/dist/shims/fetch-cache.js.map +1 -1
  101. package/dist/shims/hash-scroll.d.ts +4 -1
  102. package/dist/shims/hash-scroll.js +13 -1
  103. package/dist/shims/hash-scroll.js.map +1 -1
  104. package/dist/shims/head-state.d.ts +1 -0
  105. package/dist/shims/head-state.js +18 -3
  106. package/dist/shims/head-state.js.map +1 -1
  107. package/dist/shims/head.d.ts +35 -1
  108. package/dist/shims/head.js +113 -14
  109. package/dist/shims/head.js.map +1 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
  112. package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
  113. package/dist/shims/link.js +28 -2
  114. package/dist/shims/link.js.map +1 -1
  115. package/dist/shims/navigation.d.ts +39 -1
  116. package/dist/shims/navigation.js +61 -13
  117. package/dist/shims/navigation.js.map +1 -1
  118. package/dist/shims/router.js +37 -17
  119. package/dist/shims/router.js.map +1 -1
  120. package/dist/shims/thenable-params.d.ts +5 -2
  121. package/dist/shims/thenable-params.js +25 -1
  122. package/dist/shims/thenable-params.js.map +1 -1
  123. package/dist/shims/unified-request-context.js +3 -0
  124. package/dist/shims/unified-request-context.js.map +1 -1
  125. package/dist/utils/client-build-manifest.d.ts +15 -0
  126. package/dist/utils/client-build-manifest.js +54 -0
  127. package/dist/utils/client-build-manifest.js.map +1 -0
  128. package/dist/utils/hash.js +1 -1
  129. package/dist/utils/hash.js.map +1 -1
  130. package/dist/utils/lazy-chunks.d.ts +1 -1
  131. package/dist/utils/lazy-chunks.js.map +1 -1
  132. package/dist/utils/vite-version.d.ts +11 -0
  133. package/dist/utils/vite-version.js +36 -0
  134. package/dist/utils/vite-version.js.map +1 -0
  135. package/package.json +2 -2
@@ -1,7 +1,122 @@
1
1
  import { makeThenableParams } from "../shims/thenable-params.js";
2
2
  import { probeAppPageComponent, probeAppPageLayouts } from "./app-page-execution.js";
3
3
  import { collectAppPageSearchParams } from "./app-page-head.js";
4
+ import { Fragment, isValidElement } from "react";
4
5
  //#region src/server/app-page-probe.ts
6
+ const DEFAULT_SUBTREE_PROBE_MAX_DEPTH = 32;
7
+ const DEFAULT_SUBTREE_PROBE_MAX_NODES = 1e3;
8
+ const REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
9
+ const REACT_LAZY_TYPE = Symbol.for("react.lazy");
10
+ const REACT_MEMO_TYPE = Symbol.for("react.memo");
11
+ var AppPageSubtreeProbeLimitError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "AppPageSubtreeProbeLimitError";
15
+ }
16
+ };
17
+ var AppPageSubtreeProbeUnsupportedIterableError = class extends Error {
18
+ constructor() {
19
+ super("App page layout subtree probe cannot safely inspect iterable children");
20
+ this.name = "AppPageSubtreeProbeUnsupportedIterableError";
21
+ }
22
+ };
23
+ function isPromiseLike(value) {
24
+ return Boolean(value && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function");
25
+ }
26
+ function isIterable(value) {
27
+ return Boolean(value && typeof value !== "string" && typeof value === "object" && Symbol.iterator in value && typeof value[Symbol.iterator] === "function");
28
+ }
29
+ function isProbeReactElement(value) {
30
+ return isValidElement(value);
31
+ }
32
+ function isObjectLike(value) {
33
+ return (typeof value === "object" || typeof value === "function") && value !== null;
34
+ }
35
+ function isUnknownFunction(value) {
36
+ return typeof value === "function";
37
+ }
38
+ function readReactMemoType(value) {
39
+ if (!isObjectLike(value) || Reflect.get(value, "$$typeof") !== REACT_MEMO_TYPE) return null;
40
+ return { innerType: Reflect.get(value, "type") };
41
+ }
42
+ function readReactLazyType(value) {
43
+ if (!isObjectLike(value) || Reflect.get(value, "$$typeof") !== REACT_LAZY_TYPE) return null;
44
+ const init = Reflect.get(value, "_init");
45
+ if (!isUnknownFunction(init)) return null;
46
+ return {
47
+ init,
48
+ payload: Reflect.get(value, "_payload")
49
+ };
50
+ }
51
+ function readReactForwardRefRender(value) {
52
+ if (!isObjectLike(value) || Reflect.get(value, "$$typeof") !== REACT_FORWARD_REF_TYPE) return null;
53
+ const render = Reflect.get(value, "render");
54
+ return isUnknownFunction(render) ? render : null;
55
+ }
56
+ async function resolveReactLazyType(lazyType) {
57
+ try {
58
+ return lazyType.init(lazyType.payload);
59
+ } catch (error) {
60
+ if (!isPromiseLike(error)) throw error;
61
+ await error;
62
+ return lazyType.init(lazyType.payload);
63
+ }
64
+ }
65
+ /**
66
+ * Invokes server-component children returned by a layout probe so per-layout
67
+ * skip eligibility observes data dependencies created below the layout's
68
+ * immediate function body. The real RSC render remains authoritative; probe
69
+ * failures only make static-layout skip fall back to render-and-send.
70
+ */
71
+ async function probeReactServerSubtree(node, options = {}) {
72
+ const maxDepth = options.maxDepth ?? DEFAULT_SUBTREE_PROBE_MAX_DEPTH;
73
+ const maxNodes = options.maxNodes ?? DEFAULT_SUBTREE_PROBE_MAX_NODES;
74
+ let visitedNodes = 0;
75
+ const enterProbeNode = (depth) => {
76
+ if (depth > maxDepth) throw new AppPageSubtreeProbeLimitError("App page layout subtree probe exceeded max depth");
77
+ visitedNodes += 1;
78
+ if (visitedNodes > maxNodes) throw new AppPageSubtreeProbeLimitError("App page layout subtree probe exceeded max nodes");
79
+ };
80
+ const renderElementType = async (type, props, depth, wrapperDepth = 0) => {
81
+ if (wrapperDepth > maxDepth) throw new AppPageSubtreeProbeLimitError("App page layout subtree probe exceeded max depth");
82
+ if (isUnknownFunction(type)) {
83
+ await visit(type(props), depth + 1);
84
+ return true;
85
+ }
86
+ const memoType = readReactMemoType(type);
87
+ if (memoType) return renderElementType(memoType.innerType, props, depth, wrapperDepth + 1);
88
+ const lazyType = readReactLazyType(type);
89
+ if (lazyType) return renderElementType(await resolveReactLazyType(lazyType), props, depth, wrapperDepth + 1);
90
+ const forwardRefRender = readReactForwardRefRender(type);
91
+ if (forwardRefRender) {
92
+ await visit(forwardRefRender(props, null), depth + 1);
93
+ return true;
94
+ }
95
+ return false;
96
+ };
97
+ const visit = async (value, depth) => {
98
+ enterProbeNode(depth);
99
+ if (value == null || typeof value === "boolean" || typeof value === "number") return;
100
+ if (typeof value === "string" || typeof value === "bigint") return;
101
+ if (isPromiseLike(value)) {
102
+ await visit(await value, depth);
103
+ return;
104
+ }
105
+ if (Array.isArray(value)) {
106
+ for (const child of value) await visit(child, depth + 1);
107
+ return;
108
+ }
109
+ if (isIterable(value) && !isProbeReactElement(value)) throw new AppPageSubtreeProbeUnsupportedIterableError();
110
+ if (!isProbeReactElement(value)) return;
111
+ if (value.type === Fragment || typeof value.type === "string") {
112
+ await visit(value.props.children, depth + 1);
113
+ return;
114
+ }
115
+ if (await renderElementType(value.type, value.props, depth)) return;
116
+ await visit(value.props.children, depth + 1);
117
+ };
118
+ await visit(node, 0);
119
+ }
5
120
  /**
6
121
  * Build a probePage() invocation for the App Router request lifecycle.
7
122
  *
@@ -71,6 +186,6 @@ async function probeAppPageBeforeRender(options) {
71
186
  };
72
187
  }
73
188
  //#endregion
74
- export { probeAppPage, probeAppPageBeforeRender };
189
+ export { probeAppPage, probeAppPageBeforeRender, probeReactServerSubtree };
75
190
 
76
191
  //# sourceMappingURL=app-page-probe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-page-probe.js","names":[],"sources":["../../src/server/app-page-probe.ts"],"sourcesContent":["import { makeThenableParams } from \"vinext/shims/thenable-params\";\nimport { collectAppPageSearchParams } from \"./app-page-head.js\";\nimport {\n probeAppPageComponent,\n probeAppPageLayouts,\n type AppPageSpecialError,\n type LayoutClassificationOptions,\n type LayoutFlags,\n} from \"./app-page-execution.js\";\n\n/**\n * Build a probePage() invocation for the App Router request lifecycle.\n *\n * The generated RSC entry calls this once per request after route matching to\n * eagerly invoke the page component. Surfacing redirect()/notFound() throws\n * here lets the probe lifecycle turn them into proper HTTP responses before\n * RSC streaming begins (see `probeAppPageBeforeRender`).\n *\n * The helper exists to keep the generated entry thin (a single delegation\n * call) and to make the search-params wiring directly unit-testable. A bug\n * here previously slipped through because the entry hand-rolled the call and\n * read a non-existent key off `collectAppPageSearchParams`'s return value\n * (see https://github.com/cloudflare/vinext/issues/1235).\n *\n * Returns `null` when the route has no page component (eg. interception-only\n * routes), matching the caller contract on `probePage`.\n */\nexport function probeAppPage(options: {\n pageComponent: unknown;\n asyncRouteParams: unknown;\n searchParams: URLSearchParams | null | undefined;\n}): unknown {\n const { pageComponent, asyncRouteParams, searchParams } = options;\n if (typeof pageComponent !== \"function\") {\n return null;\n }\n const { pageSearchParams } = collectAppPageSearchParams(searchParams);\n const asyncSearchParams = makeThenableParams(pageSearchParams);\n return (pageComponent as (props: Record<string, unknown>) => unknown)({\n params: asyncRouteParams,\n searchParams: asyncSearchParams,\n });\n}\n\ntype ProbeAppPageBeforeRenderResult = {\n response: Response | null;\n layoutFlags: LayoutFlags;\n};\n\ntype ProbeAppPageBeforeRenderOptions = {\n hasLoadingBoundary: boolean;\n layoutCount: number;\n probeLayoutAt: (layoutIndex: number) => unknown;\n probePage: () => unknown;\n renderLayoutSpecialError: (\n specialError: AppPageSpecialError,\n layoutIndex: number,\n ) => Promise<Response>;\n renderPageSpecialError: (specialError: AppPageSpecialError) => Promise<Response>;\n resolveSpecialError: (error: unknown) => AppPageSpecialError | null;\n runWithSuppressedHookWarning<T>(probe: () => Promise<T>): Promise<T>;\n /** When provided, enables per-layout static/dynamic classification. */\n classification?: LayoutClassificationOptions | null;\n};\n\nexport async function probeAppPageBeforeRender(\n options: ProbeAppPageBeforeRenderOptions,\n): Promise<ProbeAppPageBeforeRenderResult> {\n let layoutFlags: LayoutFlags = {};\n\n // Layouts render before their children in Next.js, so layout-level special\n // errors must be handled before probing the page component itself.\n if (options.layoutCount > 0) {\n const layoutProbeResult = await probeAppPageLayouts({\n layoutCount: options.layoutCount,\n async onLayoutError(layoutError, layoutIndex) {\n const specialError = options.resolveSpecialError(layoutError);\n if (!specialError) {\n return null;\n }\n\n return options.renderLayoutSpecialError(specialError, layoutIndex);\n },\n probeLayoutAt: options.probeLayoutAt,\n runWithSuppressedHookWarning(probe) {\n return options.runWithSuppressedHookWarning(probe);\n },\n classification: options.classification,\n });\n\n layoutFlags = layoutProbeResult.layoutFlags;\n\n if (layoutProbeResult.response) {\n return { response: layoutProbeResult.response, layoutFlags };\n }\n }\n\n // When a route-level loading.tsx is present, the page renders inside a\n // route-level Suspense boundary, so a thrown redirect()/notFound() during\n // page render becomes an error inside that boundary. We can't catch it\n // here without serializing on the page promise — which would defeat the\n // streaming benefit of loading.tsx for slow non-redirecting pages.\n //\n // Recovery for the redirect/notFound case happens later in\n // renderAppPageLifecycle: rscErrorTracker captures the digest from React's\n // onError callback, and a short race window after shell-ready lets the\n // lifecycle swap the response to a 307/404 before bytes are flushed.\n // This mirrors Next.js's \"until-first-byte-is-flushed\" swap behavior.\n if (options.hasLoadingBoundary) {\n return { response: null, layoutFlags };\n }\n\n // Server Components are functions, so we can probe the page ahead of stream\n // creation and only turn special throws into immediate responses.\n const pageResponse = await probeAppPageComponent({\n awaitAsyncResult: true,\n async onError(pageError) {\n const specialError = options.resolveSpecialError(pageError);\n if (specialError) {\n return options.renderPageSpecialError(specialError);\n }\n\n // Non-special probe failures (for example use() outside React's render\n // cycle or client references executing on the server) are expected here.\n // The real RSC/SSR render path will surface those properly below.\n return null;\n },\n probePage: options.probePage,\n runWithSuppressedHookWarning(probe) {\n return options.runWithSuppressedHookWarning(probe);\n },\n });\n\n return { response: pageResponse, layoutFlags };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,aAAa,SAIjB;CACV,MAAM,EAAE,eAAe,kBAAkB,iBAAiB;CAC1D,IAAI,OAAO,kBAAkB,YAC3B,OAAO;CAET,MAAM,EAAE,qBAAqB,2BAA2B,aAAa;CAErE,OAAQ,cAA8D;EACpE,QAAQ;EACR,cAHwB,mBAAmB,iBAGZ;EAChC,CAAC;;AAwBJ,eAAsB,yBACpB,SACyC;CACzC,IAAI,cAA2B,EAAE;CAIjC,IAAI,QAAQ,cAAc,GAAG;EAC3B,MAAM,oBAAoB,MAAM,oBAAoB;GAClD,aAAa,QAAQ;GACrB,MAAM,cAAc,aAAa,aAAa;IAC5C,MAAM,eAAe,QAAQ,oBAAoB,YAAY;IAC7D,IAAI,CAAC,cACH,OAAO;IAGT,OAAO,QAAQ,yBAAyB,cAAc,YAAY;;GAEpE,eAAe,QAAQ;GACvB,6BAA6B,OAAO;IAClC,OAAO,QAAQ,6BAA6B,MAAM;;GAEpD,gBAAgB,QAAQ;GACzB,CAAC;EAEF,cAAc,kBAAkB;EAEhC,IAAI,kBAAkB,UACpB,OAAO;GAAE,UAAU,kBAAkB;GAAU;GAAa;;CAehE,IAAI,QAAQ,oBACV,OAAO;EAAE,UAAU;EAAM;EAAa;CAwBxC,OAAO;EAAE,UAAU,MAnBQ,sBAAsB;GAC/C,kBAAkB;GAClB,MAAM,QAAQ,WAAW;IACvB,MAAM,eAAe,QAAQ,oBAAoB,UAAU;IAC3D,IAAI,cACF,OAAO,QAAQ,uBAAuB,aAAa;IAMrD,OAAO;;GAET,WAAW,QAAQ;GACnB,6BAA6B,OAAO;IAClC,OAAO,QAAQ,6BAA6B,MAAM;;GAErD,CAAC;EAE+B;EAAa"}
1
+ {"version":3,"file":"app-page-probe.js","names":[],"sources":["../../src/server/app-page-probe.ts"],"sourcesContent":["import { Fragment, isValidElement, type ReactElement, type ReactNode } from \"react\";\nimport { makeThenableParams } from \"vinext/shims/thenable-params\";\nimport { collectAppPageSearchParams } from \"./app-page-head.js\";\nimport {\n probeAppPageComponent,\n probeAppPageLayouts,\n type AppPageSpecialError,\n type LayoutClassificationOptions,\n type LayoutFlags,\n} from \"./app-page-execution.js\";\n\nconst DEFAULT_SUBTREE_PROBE_MAX_DEPTH = 32;\nconst DEFAULT_SUBTREE_PROBE_MAX_NODES = 1000;\nconst REACT_FORWARD_REF_TYPE = Symbol.for(\"react.forward_ref\");\nconst REACT_LAZY_TYPE = Symbol.for(\"react.lazy\");\nconst REACT_MEMO_TYPE = Symbol.for(\"react.memo\");\n\ntype ProbeReactServerSubtreeOptions = Readonly<{\n maxDepth?: number;\n maxNodes?: number;\n}>;\n\ntype ProbeReactElementProps = Readonly<{\n children?: ReactNode;\n}>;\n\ntype UnknownFunction = (...args: unknown[]) => unknown;\n\ntype ReactMemoType = Readonly<{\n innerType: unknown;\n}>;\n\ntype ReactLazyType = Readonly<{\n init: UnknownFunction;\n payload: unknown;\n}>;\n\nclass AppPageSubtreeProbeLimitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AppPageSubtreeProbeLimitError\";\n }\n}\n\nclass AppPageSubtreeProbeUnsupportedIterableError extends Error {\n constructor() {\n super(\"App page layout subtree probe cannot safely inspect iterable children\");\n this.name = \"AppPageSubtreeProbeUnsupportedIterableError\";\n }\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return Boolean(\n value &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n \"then\" in value &&\n typeof value.then === \"function\",\n );\n}\n\nfunction isIterable(value: unknown): value is Iterable<unknown> {\n return Boolean(\n value &&\n typeof value !== \"string\" &&\n typeof value === \"object\" &&\n Symbol.iterator in value &&\n typeof value[Symbol.iterator] === \"function\",\n );\n}\n\nfunction isProbeReactElement(value: unknown): value is ReactElement<ProbeReactElementProps> {\n return isValidElement<ProbeReactElementProps>(value);\n}\n\nfunction isObjectLike(value: unknown): value is object {\n return (typeof value === \"object\" || typeof value === \"function\") && value !== null;\n}\n\nfunction isUnknownFunction(value: unknown): value is UnknownFunction {\n return typeof value === \"function\";\n}\n\nfunction readReactMemoType(value: unknown): ReactMemoType | null {\n if (!isObjectLike(value) || Reflect.get(value, \"$$typeof\") !== REACT_MEMO_TYPE) {\n return null;\n }\n return { innerType: Reflect.get(value, \"type\") };\n}\n\nfunction readReactLazyType(value: unknown): ReactLazyType | null {\n if (!isObjectLike(value) || Reflect.get(value, \"$$typeof\") !== REACT_LAZY_TYPE) {\n return null;\n }\n const init = Reflect.get(value, \"_init\");\n if (!isUnknownFunction(init)) {\n return null;\n }\n return { init, payload: Reflect.get(value, \"_payload\") };\n}\n\nfunction readReactForwardRefRender(value: unknown): UnknownFunction | null {\n if (!isObjectLike(value) || Reflect.get(value, \"$$typeof\") !== REACT_FORWARD_REF_TYPE) {\n return null;\n }\n const render = Reflect.get(value, \"render\");\n return isUnknownFunction(render) ? render : null;\n}\n\nasync function resolveReactLazyType(lazyType: ReactLazyType): Promise<unknown> {\n try {\n return lazyType.init(lazyType.payload);\n } catch (error) {\n if (!isPromiseLike(error)) {\n throw error;\n }\n await error;\n return lazyType.init(lazyType.payload);\n }\n}\n\n/**\n * Invokes server-component children returned by a layout probe so per-layout\n * skip eligibility observes data dependencies created below the layout's\n * immediate function body. The real RSC render remains authoritative; probe\n * failures only make static-layout skip fall back to render-and-send.\n */\nexport async function probeReactServerSubtree(\n node: unknown,\n options: ProbeReactServerSubtreeOptions = {},\n): Promise<void> {\n const maxDepth = options.maxDepth ?? DEFAULT_SUBTREE_PROBE_MAX_DEPTH;\n const maxNodes = options.maxNodes ?? DEFAULT_SUBTREE_PROBE_MAX_NODES;\n let visitedNodes = 0;\n\n const enterProbeNode = (depth: number): void => {\n if (depth > maxDepth) {\n throw new AppPageSubtreeProbeLimitError(\"App page layout subtree probe exceeded max depth\");\n }\n visitedNodes += 1;\n if (visitedNodes > maxNodes) {\n throw new AppPageSubtreeProbeLimitError(\"App page layout subtree probe exceeded max nodes\");\n }\n };\n\n const renderElementType = async (\n type: unknown,\n props: ProbeReactElementProps,\n depth: number,\n wrapperDepth = 0,\n ): Promise<boolean> => {\n if (wrapperDepth > maxDepth) {\n throw new AppPageSubtreeProbeLimitError(\"App page layout subtree probe exceeded max depth\");\n }\n\n if (isUnknownFunction(type)) {\n await visit(type(props), depth + 1);\n return true;\n }\n\n const memoType = readReactMemoType(type);\n if (memoType) {\n return renderElementType(memoType.innerType, props, depth, wrapperDepth + 1);\n }\n\n const lazyType = readReactLazyType(type);\n if (lazyType) {\n return renderElementType(\n await resolveReactLazyType(lazyType),\n props,\n depth,\n wrapperDepth + 1,\n );\n }\n\n const forwardRefRender = readReactForwardRefRender(type);\n if (forwardRefRender) {\n await visit(forwardRefRender(props, null), depth + 1);\n return true;\n }\n\n return false;\n };\n\n const visit = async (value: unknown, depth: number): Promise<void> => {\n enterProbeNode(depth);\n if (value == null || typeof value === \"boolean\" || typeof value === \"number\") return;\n if (typeof value === \"string\" || typeof value === \"bigint\") return;\n if (isPromiseLike(value)) {\n await visit(await value, depth);\n return;\n }\n if (Array.isArray(value)) {\n for (const child of value) {\n await visit(child, depth + 1);\n }\n return;\n }\n if (isIterable(value) && !isProbeReactElement(value)) {\n throw new AppPageSubtreeProbeUnsupportedIterableError();\n }\n if (!isProbeReactElement(value)) return;\n\n if (value.type === Fragment || typeof value.type === \"string\") {\n await visit(value.props.children, depth + 1);\n return;\n }\n\n if (await renderElementType(value.type, value.props, depth)) {\n return;\n }\n\n await visit(value.props.children, depth + 1);\n };\n\n await visit(node, 0);\n}\n\n/**\n * Build a probePage() invocation for the App Router request lifecycle.\n *\n * The generated RSC entry calls this once per request after route matching to\n * eagerly invoke the page component. Surfacing redirect()/notFound() throws\n * here lets the probe lifecycle turn them into proper HTTP responses before\n * RSC streaming begins (see `probeAppPageBeforeRender`).\n *\n * The helper exists to keep the generated entry thin (a single delegation\n * call) and to make the search-params wiring directly unit-testable. A bug\n * here previously slipped through because the entry hand-rolled the call and\n * read a non-existent key off `collectAppPageSearchParams`'s return value\n * (see https://github.com/cloudflare/vinext/issues/1235).\n *\n * Returns `null` when the route has no page component (eg. interception-only\n * routes), matching the caller contract on `probePage`.\n */\nexport function probeAppPage(options: {\n pageComponent: unknown;\n asyncRouteParams: unknown;\n searchParams: URLSearchParams | null | undefined;\n}): unknown {\n const { pageComponent, asyncRouteParams, searchParams } = options;\n if (typeof pageComponent !== \"function\") {\n return null;\n }\n const { pageSearchParams } = collectAppPageSearchParams(searchParams);\n const asyncSearchParams = makeThenableParams(pageSearchParams);\n return (pageComponent as (props: Record<string, unknown>) => unknown)({\n params: asyncRouteParams,\n searchParams: asyncSearchParams,\n });\n}\n\ntype ProbeAppPageBeforeRenderResult = {\n response: Response | null;\n layoutFlags: LayoutFlags;\n};\n\ntype ProbeAppPageBeforeRenderOptions = {\n hasLoadingBoundary: boolean;\n layoutCount: number;\n probeLayoutAt: (layoutIndex: number) => unknown;\n probePage: () => unknown;\n renderLayoutSpecialError: (\n specialError: AppPageSpecialError,\n layoutIndex: number,\n ) => Promise<Response>;\n renderPageSpecialError: (specialError: AppPageSpecialError) => Promise<Response>;\n resolveSpecialError: (error: unknown) => AppPageSpecialError | null;\n runWithSuppressedHookWarning<T>(probe: () => Promise<T>): Promise<T>;\n /** When provided, enables per-layout static/dynamic classification. */\n classification?: LayoutClassificationOptions | null;\n};\n\nexport async function probeAppPageBeforeRender(\n options: ProbeAppPageBeforeRenderOptions,\n): Promise<ProbeAppPageBeforeRenderResult> {\n let layoutFlags: LayoutFlags = {};\n\n // Layouts render before their children in Next.js, so layout-level special\n // errors must be handled before probing the page component itself.\n if (options.layoutCount > 0) {\n const layoutProbeResult = await probeAppPageLayouts({\n layoutCount: options.layoutCount,\n async onLayoutError(layoutError, layoutIndex) {\n const specialError = options.resolveSpecialError(layoutError);\n if (!specialError) {\n return null;\n }\n\n return options.renderLayoutSpecialError(specialError, layoutIndex);\n },\n probeLayoutAt: options.probeLayoutAt,\n runWithSuppressedHookWarning(probe) {\n return options.runWithSuppressedHookWarning(probe);\n },\n classification: options.classification,\n });\n\n layoutFlags = layoutProbeResult.layoutFlags;\n\n if (layoutProbeResult.response) {\n return { response: layoutProbeResult.response, layoutFlags };\n }\n }\n\n // When a route-level loading.tsx is present, the page renders inside a\n // route-level Suspense boundary, so a thrown redirect()/notFound() during\n // page render becomes an error inside that boundary. We can't catch it\n // here without serializing on the page promise — which would defeat the\n // streaming benefit of loading.tsx for slow non-redirecting pages.\n //\n // Recovery for the redirect/notFound case happens later in\n // renderAppPageLifecycle: rscErrorTracker captures the digest from React's\n // onError callback, and a short race window after shell-ready lets the\n // lifecycle swap the response to a 307/404 before bytes are flushed.\n // This mirrors Next.js's \"until-first-byte-is-flushed\" swap behavior.\n if (options.hasLoadingBoundary) {\n return { response: null, layoutFlags };\n }\n\n // Server Components are functions, so we can probe the page ahead of stream\n // creation and only turn special throws into immediate responses.\n const pageResponse = await probeAppPageComponent({\n awaitAsyncResult: true,\n async onError(pageError) {\n const specialError = options.resolveSpecialError(pageError);\n if (specialError) {\n return options.renderPageSpecialError(specialError);\n }\n\n // Non-special probe failures (for example use() outside React's render\n // cycle or client references executing on the server) are expected here.\n // The real RSC/SSR render path will surface those properly below.\n return null;\n },\n probePage: options.probePage,\n runWithSuppressedHookWarning(probe) {\n return options.runWithSuppressedHookWarning(probe);\n },\n });\n\n return { response: pageResponse, layoutFlags };\n}\n"],"mappings":"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"}
@@ -83,7 +83,7 @@ function hasCookieAttribute(cookie, attributeName) {
83
83
  */
84
84
  function normalizeReturnedCookie(cookie) {
85
85
  if (hasCookieAttribute(cookie, "Path")) return cookie;
86
- return `${cookie}; Path=/`;
86
+ return `${cookie.replace(/;\s*$/, "")}; Path=/`;
87
87
  }
88
88
  function applyMutableCookieFallbacks(headers, pendingCookies) {
89
89
  if (pendingCookies.length === 0) return;
@@ -1 +1 @@
1
- {"version":3,"file":"app-route-handler-response.js","names":[],"sources":["../../src/server/app-route-handler-response.ts"],"sourcesContent":["import type { CachedRouteValue, CacheControlMetadata } from \"vinext/shims/cache\";\nimport {\n buildCachedRevalidateCacheControl,\n NEVER_CACHE_CONTROL,\n STATIC_CACHE_CONTROL,\n} from \"./cache-control.js\";\nimport {\n MIDDLEWARE_HEADER_PREFIX,\n MIDDLEWARE_NEXT_HEADER,\n MIDDLEWARE_REWRITE_HEADER,\n NEXTJS_CACHE_HEADER,\n VINEXT_CACHE_HEADER,\n} from \"./headers.js\";\nimport { setCacheStateHeaders } from \"./cache-headers.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./middleware-response-headers.js\";\nimport { processMiddlewareHeaders } from \"./request-pipeline.js\";\nimport { getSetCookieName } from \"./cookie-utils.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\ntype BuildRouteHandlerCachedResponseOptions = {\n cacheControl?: CacheControlMetadata;\n cacheState: \"HIT\" | \"STALE\";\n expireSeconds?: number;\n isHead: boolean;\n revalidateSeconds: number;\n};\n\ntype FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nconst APP_ROUTE_REWRITE_ERROR =\n \"NextResponse.rewrite() was used in a app route handler, this is not currently supported. Please remove the invocation to continue.\";\nconst APP_ROUTE_NEXT_ERROR =\n \"NextResponse.next() was used in a app route handler, this is not supported. See here for more info: https://nextjs.org/docs/messages/next-response-next-in-app-route-handler\";\n\nfunction hasMiddlewareHeader(headers: Headers): boolean {\n for (const key of headers.keys()) {\n if (key.startsWith(MIDDLEWARE_HEADER_PREFIX)) return true;\n }\n return false;\n}\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n expireSeconds?: number,\n): string {\n if (revalidateSeconds === 0) {\n // A cached response is never produced for revalidate = 0 (the ISR write\n // path skips it), so only the HIT/STALE->fresh rewrite can arrive here\n // with a 0 value, via applyRouteHandlerRevalidateHeader. In all such\n // cases the author opted out of caching entirely.\n return NEVER_CACHE_CONTROL;\n }\n\n if (revalidateSeconds === Infinity) {\n // revalidate = false / Infinity means \"cache indefinitely\" — emit the\n // same static Cache-Control used by pages, not a dynamic SWR value.\n return STATIC_CACHE_CONTROL;\n }\n\n return buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expireSeconds);\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 mergeMiddlewareResponseHeaders(responseHeaders, middlewareContext.headers);\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 assertSupportedAppRouteHandlerResponse(response: Response): void {\n // NextResponse.next() and rewrite() are middleware control-flow signals.\n // Once an App Route handler has returned, Next.js rejects those responses.\n if (response.headers.has(MIDDLEWARE_REWRITE_HEADER)) {\n throw new Error(APP_ROUTE_REWRITE_ERROR);\n }\n\n if (response.headers.get(MIDDLEWARE_NEXT_HEADER) === \"1\") {\n throw new Error(APP_ROUTE_NEXT_ERROR);\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 setCacheStateHeaders(headers, options.cacheState);\n const revalidateSeconds = options.cacheControl?.revalidate ?? options.revalidateSeconds;\n const expireSeconds =\n options.cacheControl === undefined\n ? undefined\n : (options.cacheControl.expire ?? options.expireSeconds);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, revalidateSeconds, expireSeconds),\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 expireSeconds?: number,\n): void {\n response.headers.set(\n \"cache-control\",\n buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds, expireSeconds),\n );\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n setCacheStateHeaders(response.headers, \"MISS\");\n}\n\n/**\n * Returns true when the given Set-Cookie string already declares any of the\n * attributes that follow the first `;` (case-insensitively). Used to detect\n * whether a user-emitted Set-Cookie line already carries an explicit `Path=`,\n * matching Next.js's `appendMutableCookies` which re-runs every cookie through\n * `ResponseCookies.set` (and therefore picks up the `Path=/` default for any\n * cookie that didn't supply one).\n */\nfunction hasCookieAttribute(cookie: string, attributeName: string): boolean {\n const target = attributeName.toLowerCase();\n // Skip past the first '=' (the cookie value separator) so we don't match\n // `attributeName=` inside the cookie value itself.\n let i = cookie.indexOf(\";\");\n while (i !== -1) {\n // Trim leading whitespace after the ';'\n let start = i + 1;\n while (start < cookie.length && cookie[start] === \" \") start++;\n const next = cookie.indexOf(\";\", start);\n const end = next === -1 ? cookie.length : next;\n const eq = cookie.indexOf(\"=\", start);\n const attrEnd = eq === -1 || eq > end ? end : eq;\n const attr = cookie.slice(start, attrEnd).trim().toLowerCase();\n if (attr === target) {\n return true;\n }\n i = next;\n }\n return false;\n}\n\n/**\n * Ensure each Set-Cookie line carries `Path=/` by default — Next.js's\n * `appendMutableCookies` re-runs every returned cookie through\n * `ResponseCookies.set`, which normalises a missing `path` to `/`. Without\n * this, a raw `new Response(..., { headers: [['Set-Cookie', 'bar=bar2']] })`\n * lands without `Path=/` and tests that assert on the full attribute set\n * (e.g. Next.js's `app-action.test.ts` route-handler-overrides case, see\n * issue #1484) break.\n */\nfunction normalizeReturnedCookie(cookie: string): string {\n if (hasCookieAttribute(cookie, \"Path\")) {\n return cookie;\n }\n return `${cookie}; Path=/`;\n}\n\nfunction applyMutableCookieFallbacks(headers: Headers, pendingCookies: string[]): void {\n if (pendingCookies.length === 0) {\n return;\n }\n\n const returnedCookies = headers.getSetCookie();\n const returnedCookieNames = new Set<string>();\n for (const cookie of returnedCookies) {\n const name = getSetCookieName(cookie);\n if (name) {\n returnedCookieNames.add(name);\n }\n }\n\n const fallbackCookies = new Map<string, string>();\n const unkeyedFallbackCookies: string[] = [];\n for (const cookie of pendingCookies) {\n const name = getSetCookieName(cookie);\n if (!name) {\n unkeyedFallbackCookies.push(cookie);\n continue;\n }\n\n if (!returnedCookieNames.has(name)) {\n fallbackCookies.set(name, cookie);\n }\n }\n\n headers.delete(\"Set-Cookie\");\n for (const cookie of unkeyedFallbackCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n for (const cookie of fallbackCookies.values()) {\n headers.append(\"Set-Cookie\", cookie);\n }\n for (const cookie of returnedCookies) {\n headers.append(\"Set-Cookie\", normalizeReturnedCookie(cookie));\n }\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 (\n key === \"set-cookie\" ||\n key === VINEXT_CACHE_HEADER.toLowerCase() ||\n key === NEXTJS_CACHE_HEADER.toLowerCase() ||\n key === \"cache-control\" ||\n key.startsWith(MIDDLEWARE_HEADER_PREFIX)\n ) {\n return;\n }\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 (\n pendingCookies.length === 0 &&\n !draftCookie &&\n !isHead &&\n !hasMiddlewareHeader(response.headers)\n ) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n processMiddlewareHeaders(headers);\n applyMutableCookieFallbacks(headers, pendingCookies);\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":";;;;;;;AAqCA,MAAM,0BACJ;AACF,MAAM,uBACJ;AAEF,SAAS,oBAAoB,SAA2B;CACtD,KAAK,MAAM,OAAO,QAAQ,MAAM,EAC9B,IAAI,IAAI,WAAA,gBAAoC,EAAE,OAAO;CAEvD,OAAO;;AAGT,SAAS,8BACP,YACA,mBACA,eACQ;CACR,IAAI,sBAAsB,GAKxB,OAAO;CAGT,IAAI,sBAAsB,UAGxB,OAAO;CAGT,OAAO,kCAAkC,YAAY,mBAAmB,cAAc;;AAGxF,SAAgB,mCACd,UACA,mBACU;CACV,IAAI,CAAC,kBAAkB,WAAW,kBAAkB,UAAU,MAC5D,OAAO;CAGT,MAAM,kBAAkB,IAAI,QAAQ,SAAS,QAAQ;CACrD,+BAA+B,iBAAiB,kBAAkB,QAAQ;CAE1E,OAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,kBAAkB,UAAU,SAAS;EAC7C,YAAY,SAAS;EACrB,SAAS;EACV,CAAC;;AAGJ,SAAgB,uCAAuC,UAA0B;CAG/E,IAAI,SAAS,QAAQ,IAAA,uBAA8B,EACjD,MAAM,IAAI,MAAM,wBAAwB;CAG1C,IAAI,SAAS,QAAQ,IAAA,oBAA2B,KAAK,KACnD,MAAM,IAAI,MAAM,qBAAqB;;AAIzC,SAAgB,gCACd,aACA,SACU;CACV,MAAM,UAAU,IAAI,SAAS;CAC7B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,QAAQ,EAC5D,IAAI,MAAM,QAAQ,MAAM,EACtB,KAAK,MAAM,SAAS,OAClB,QAAQ,OAAO,KAAK,MAAM;MAG5B,QAAQ,IAAI,KAAK,MAAM;CAG3B,qBAAqB,SAAS,QAAQ,WAAW;CACjD,MAAM,oBAAoB,QAAQ,cAAc,cAAc,QAAQ;CACtE,MAAM,gBACJ,QAAQ,iBAAiB,KAAA,IACrB,KAAA,IACC,QAAQ,aAAa,UAAU,QAAQ;CAC9C,QAAQ,IACN,iBACA,8BAA8B,QAAQ,YAAY,mBAAmB,cAAc,CACpF;CAED,OAAO,IAAI,SAAS,QAAQ,SAAS,OAAO,YAAY,MAAM;EAC5D,QAAQ,YAAY;EACpB;EACD,CAAC;;AAGJ,SAAgB,kCACd,UACA,mBACA,eACM;CACN,SAAS,QAAQ,IACf,iBACA,8BAA8B,OAAO,mBAAmB,cAAc,CACvE;;AAGH,SAAgB,0BAA0B,UAA0B;CAClE,qBAAqB,SAAS,SAAS,OAAO;;;;;;;;;;AAWhD,SAAS,mBAAmB,QAAgB,eAAgC;CAC1E,MAAM,SAAS,cAAc,aAAa;CAG1C,IAAI,IAAI,OAAO,QAAQ,IAAI;CAC3B,OAAO,MAAM,IAAI;EAEf,IAAI,QAAQ,IAAI;EAChB,OAAO,QAAQ,OAAO,UAAU,OAAO,WAAW,KAAK;EACvD,MAAM,OAAO,OAAO,QAAQ,KAAK,MAAM;EACvC,MAAM,MAAM,SAAS,KAAK,OAAO,SAAS;EAC1C,MAAM,KAAK,OAAO,QAAQ,KAAK,MAAM;EACrC,MAAM,UAAU,OAAO,MAAM,KAAK,MAAM,MAAM;EAE9C,IADa,OAAO,MAAM,OAAO,QAAQ,CAAC,MAAM,CAAC,aACzC,KAAK,QACX,OAAO;EAET,IAAI;;CAEN,OAAO;;;;;;;;;;;AAYT,SAAS,wBAAwB,QAAwB;CACvD,IAAI,mBAAmB,QAAQ,OAAO,EACpC,OAAO;CAET,OAAO,GAAG,OAAO;;AAGnB,SAAS,4BAA4B,SAAkB,gBAAgC;CACrF,IAAI,eAAe,WAAW,GAC5B;CAGF,MAAM,kBAAkB,QAAQ,cAAc;CAC9C,MAAM,sCAAsB,IAAI,KAAa;CAC7C,KAAK,MAAM,UAAU,iBAAiB;EACpC,MAAM,OAAO,iBAAiB,OAAO;EACrC,IAAI,MACF,oBAAoB,IAAI,KAAK;;CAIjC,MAAM,kCAAkB,IAAI,KAAqB;CACjD,MAAM,yBAAmC,EAAE;CAC3C,KAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,OAAO,iBAAiB,OAAO;EACrC,IAAI,CAAC,MAAM;GACT,uBAAuB,KAAK,OAAO;GACnC;;EAGF,IAAI,CAAC,oBAAoB,IAAI,KAAK,EAChC,gBAAgB,IAAI,MAAM,OAAO;;CAIrC,QAAQ,OAAO,aAAa;CAC5B,KAAK,MAAM,UAAU,wBACnB,QAAQ,OAAO,cAAc,OAAO;CAEtC,KAAK,MAAM,UAAU,gBAAgB,QAAQ,EAC3C,QAAQ,OAAO,cAAc,OAAO;CAEtC,KAAK,MAAM,UAAU,iBACnB,QAAQ,OAAO,cAAc,wBAAwB,OAAO,CAAC;;AAIjE,eAAsB,wBAAwB,UAA+C;CAC3F,MAAM,OAAO,MAAM,SAAS,aAAa;CACzC,MAAM,UAAuC,EAAE;CAE/C,SAAS,QAAQ,SAAS,OAAO,QAAQ;EACvC,IACE,QAAQ,gBACR,QAAA,iBAA4B,aAAa,IACzC,QAAA,iBAA4B,aAAa,IACzC,QAAQ,mBACR,IAAI,WAAA,gBAAoC,EAExC;EAEF,QAAQ,OAAO;GACf;CACF,MAAM,aAAa,SAAS,QAAQ,gBAAgB,IAAI,EAAE;CAC1D,IAAI,WAAW,SAAS,GACtB,QAAQ,gBAAgB;CAG1B,OAAO;EACL,MAAM;EACN;EACA,QAAQ,SAAS;EACjB;EACD;;AAGH,SAAgB,6BACd,UACA,SACU;CACV,MAAM,EAAE,gBAAgB,aAAa,WAAW;CAChD,IACE,eAAe,WAAW,KAC1B,CAAC,eACD,CAAC,UACD,CAAC,oBAAoB,SAAS,QAAQ,EAEtC,OAAO;CAGT,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;CAC7C,yBAAyB,QAAQ;CACjC,4BAA4B,SAAS,eAAe;CACpD,IAAI,aACF,QAAQ,OAAO,cAAc,YAAY;CAG3C,OAAO,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, CacheControlMetadata } from \"vinext/shims/cache\";\nimport {\n buildCachedRevalidateCacheControl,\n NEVER_CACHE_CONTROL,\n STATIC_CACHE_CONTROL,\n} from \"./cache-control.js\";\nimport {\n MIDDLEWARE_HEADER_PREFIX,\n MIDDLEWARE_NEXT_HEADER,\n MIDDLEWARE_REWRITE_HEADER,\n NEXTJS_CACHE_HEADER,\n VINEXT_CACHE_HEADER,\n} from \"./headers.js\";\nimport { setCacheStateHeaders } from \"./cache-headers.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./middleware-response-headers.js\";\nimport { processMiddlewareHeaders } from \"./request-pipeline.js\";\nimport { getSetCookieName } from \"./cookie-utils.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\ntype BuildRouteHandlerCachedResponseOptions = {\n cacheControl?: CacheControlMetadata;\n cacheState: \"HIT\" | \"STALE\";\n expireSeconds?: number;\n isHead: boolean;\n revalidateSeconds: number;\n};\n\ntype FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nconst APP_ROUTE_REWRITE_ERROR =\n \"NextResponse.rewrite() was used in a app route handler, this is not currently supported. Please remove the invocation to continue.\";\nconst APP_ROUTE_NEXT_ERROR =\n \"NextResponse.next() was used in a app route handler, this is not supported. See here for more info: https://nextjs.org/docs/messages/next-response-next-in-app-route-handler\";\n\nfunction hasMiddlewareHeader(headers: Headers): boolean {\n for (const key of headers.keys()) {\n if (key.startsWith(MIDDLEWARE_HEADER_PREFIX)) return true;\n }\n return false;\n}\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n expireSeconds?: number,\n): string {\n if (revalidateSeconds === 0) {\n // A cached response is never produced for revalidate = 0 (the ISR write\n // path skips it), so only the HIT/STALE->fresh rewrite can arrive here\n // with a 0 value, via applyRouteHandlerRevalidateHeader. In all such\n // cases the author opted out of caching entirely.\n return NEVER_CACHE_CONTROL;\n }\n\n if (revalidateSeconds === Infinity) {\n // revalidate = false / Infinity means \"cache indefinitely\" — emit the\n // same static Cache-Control used by pages, not a dynamic SWR value.\n return STATIC_CACHE_CONTROL;\n }\n\n return buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expireSeconds);\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 mergeMiddlewareResponseHeaders(responseHeaders, middlewareContext.headers);\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 assertSupportedAppRouteHandlerResponse(response: Response): void {\n // NextResponse.next() and rewrite() are middleware control-flow signals.\n // Once an App Route handler has returned, Next.js rejects those responses.\n if (response.headers.has(MIDDLEWARE_REWRITE_HEADER)) {\n throw new Error(APP_ROUTE_REWRITE_ERROR);\n }\n\n if (response.headers.get(MIDDLEWARE_NEXT_HEADER) === \"1\") {\n throw new Error(APP_ROUTE_NEXT_ERROR);\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 setCacheStateHeaders(headers, options.cacheState);\n const revalidateSeconds = options.cacheControl?.revalidate ?? options.revalidateSeconds;\n const expireSeconds =\n options.cacheControl === undefined\n ? undefined\n : (options.cacheControl.expire ?? options.expireSeconds);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, revalidateSeconds, expireSeconds),\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 expireSeconds?: number,\n): void {\n response.headers.set(\n \"cache-control\",\n buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds, expireSeconds),\n );\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n setCacheStateHeaders(response.headers, \"MISS\");\n}\n\n/**\n * Returns true when the given Set-Cookie string already declares any of the\n * attributes that follow the first `;` (case-insensitively). Used to detect\n * whether a user-emitted Set-Cookie line already carries an explicit `Path=`,\n * matching Next.js's `appendMutableCookies` which re-runs every cookie through\n * `ResponseCookies.set` (and therefore picks up the `Path=/` default for any\n * cookie that didn't supply one).\n */\nfunction hasCookieAttribute(cookie: string, attributeName: string): boolean {\n const target = attributeName.toLowerCase();\n // Skip past the first '=' (the cookie value separator) so we don't match\n // `attributeName=` inside the cookie value itself.\n let i = cookie.indexOf(\";\");\n while (i !== -1) {\n // Trim leading whitespace after the ';'\n let start = i + 1;\n while (start < cookie.length && cookie[start] === \" \") start++;\n const next = cookie.indexOf(\";\", start);\n const end = next === -1 ? cookie.length : next;\n const eq = cookie.indexOf(\"=\", start);\n const attrEnd = eq === -1 || eq > end ? end : eq;\n const attr = cookie.slice(start, attrEnd).trim().toLowerCase();\n if (attr === target) {\n return true;\n }\n i = next;\n }\n return false;\n}\n\n/**\n * Ensure each Set-Cookie line carries `Path=/` by default — Next.js's\n * `appendMutableCookies` re-runs every returned cookie through\n * `ResponseCookies.set`, which normalises a missing `path` to `/`. Without\n * this, a raw `new Response(..., { headers: [['Set-Cookie', 'bar=bar2']] })`\n * lands without `Path=/` and tests that assert on the full attribute set\n * (e.g. Next.js's `app-action.test.ts` route-handler-overrides case, see\n * issue #1484) break.\n */\nfunction normalizeReturnedCookie(cookie: string): string {\n if (hasCookieAttribute(cookie, \"Path\")) {\n return cookie;\n }\n const trimmed = cookie.replace(/;\\s*$/, \"\");\n return `${trimmed}; Path=/`;\n}\n\nfunction applyMutableCookieFallbacks(headers: Headers, pendingCookies: string[]): void {\n if (pendingCookies.length === 0) {\n return;\n }\n\n const returnedCookies = headers.getSetCookie();\n const returnedCookieNames = new Set<string>();\n for (const cookie of returnedCookies) {\n const name = getSetCookieName(cookie);\n if (name) {\n returnedCookieNames.add(name);\n }\n }\n\n const fallbackCookies = new Map<string, string>();\n const unkeyedFallbackCookies: string[] = [];\n for (const cookie of pendingCookies) {\n const name = getSetCookieName(cookie);\n if (!name) {\n unkeyedFallbackCookies.push(cookie);\n continue;\n }\n\n if (!returnedCookieNames.has(name)) {\n fallbackCookies.set(name, cookie);\n }\n }\n\n headers.delete(\"Set-Cookie\");\n for (const cookie of unkeyedFallbackCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n for (const cookie of fallbackCookies.values()) {\n headers.append(\"Set-Cookie\", cookie);\n }\n for (const cookie of returnedCookies) {\n headers.append(\"Set-Cookie\", normalizeReturnedCookie(cookie));\n }\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 (\n key === \"set-cookie\" ||\n key === VINEXT_CACHE_HEADER.toLowerCase() ||\n key === NEXTJS_CACHE_HEADER.toLowerCase() ||\n key === \"cache-control\" ||\n key.startsWith(MIDDLEWARE_HEADER_PREFIX)\n ) {\n return;\n }\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 (\n pendingCookies.length === 0 &&\n !draftCookie &&\n !isHead &&\n !hasMiddlewareHeader(response.headers)\n ) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n processMiddlewareHeaders(headers);\n applyMutableCookieFallbacks(headers, pendingCookies);\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":"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"}
@@ -3,7 +3,7 @@ import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
3
 
4
4
  //#region src/server/app-rsc-cache-busting.d.ts
5
5
  /**
6
- * RSC cache-busting hashes cover the headers that make a `.rsc` payload vary.
6
+ * RSC cache-busting hashes cover the headers that make an RSC payload vary.
7
7
  * Client-side variant headers must survive transit through CDNs and reverse
8
8
  * proxies; stripping them changes the server hash and turns stale URLs into
9
9
  * repeated canonicalization redirects.
@@ -48,8 +48,9 @@ declare function stripRscCacheBustingSearchParam(url: URL): void;
48
48
  declare function stripRscSuffix(pathname: string): string;
49
49
  declare function createRscRequestHeaders(options?: CreateRscRequestHeadersOptions): Headers;
50
50
  declare function createRscRequestUrl(href: string, headers: Headers): Promise<string>;
51
+ declare function createServerActionRequestUrl(href: string): string;
51
52
  declare function createRscRedirectLocation(location: string, request: Request): Promise<string>;
52
53
  declare function resolveInvalidRscCacheBustingRequest(options: ResolveInvalidRscCacheBustingRequestOptions): Promise<Response | null>;
53
54
  //#endregion
54
- export { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, computeRscCacheBustingSearchParam, createRscRedirectLocation, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId, isRscCompatibilityIdCompatible, resolveHardNavigationTargetFromRscResponse, resolveInvalidRscCacheBustingRequest, resolveRscCompatibilityNavigationDecision, setRscCacheBustingSearchParam, stripRscCacheBustingSearchParam, stripRscSuffix };
55
+ export { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, computeRscCacheBustingSearchParam, createRscRedirectLocation, createRscRequestHeaders, createRscRequestUrl, createServerActionRequestUrl, getVinextRscCompatibilityId, isRscCompatibilityIdCompatible, resolveHardNavigationTargetFromRscResponse, resolveInvalidRscCacheBustingRequest, resolveRscCompatibilityNavigationDecision, setRscCacheBustingSearchParam, stripRscCacheBustingSearchParam, stripRscSuffix };
55
56
  //# sourceMappingURL=app-rsc-cache-busting.d.ts.map
@@ -3,7 +3,7 @@ import { fnv1a64 } from "../utils/hash.js";
3
3
  import { parseAppRscRenderMode } from "./app-rsc-render-mode.js";
4
4
  //#region src/server/app-rsc-cache-busting.ts
5
5
  /**
6
- * RSC cache-busting hashes cover the headers that make a `.rsc` payload vary.
6
+ * RSC cache-busting hashes cover the headers that make an RSC payload vary.
7
7
  * Client-side variant headers must survive transit through CDNs and reverse
8
8
  * proxies; stripping them changes the server hash and turns stale URLs into
9
9
  * repeated canonicalization redirects.
@@ -146,17 +146,19 @@ function createRscRequestHeaders(options = {}) {
146
146
  }
147
147
  function toRscRequestPath(href) {
148
148
  const hashIndex = href.indexOf("#");
149
- const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);
150
- const queryIndex = beforeHash.indexOf("?");
151
- const pathname = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
152
- const query = queryIndex === -1 ? "" : beforeHash.slice(queryIndex);
153
- return `${pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname}.rsc${query}`;
149
+ return hashIndex === -1 ? href : href.slice(0, hashIndex);
154
150
  }
155
151
  async function createRscRequestUrl(href, headers) {
156
152
  const url = new URL(toRscRequestPath(href), "http://vinext.local");
157
153
  setRscCacheBustingSearchParam(url, await computeRscCacheBustingSearchParam(headers));
158
154
  return `${url.pathname}${url.search}`;
159
155
  }
156
+ function createServerActionRequestUrl(href) {
157
+ const hashIndex = href.indexOf("#");
158
+ const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);
159
+ const url = new URL(beforeHash, "http://vinext.local");
160
+ return `${url.pathname}${url.search}`;
161
+ }
160
162
  async function createRscRedirectLocation(location, request) {
161
163
  const requestUrl = new URL(request.url);
162
164
  const destinationUrl = new URL(location, requestUrl);
@@ -188,6 +190,6 @@ async function resolveInvalidRscCacheBustingRequest(options) {
188
190
  });
189
191
  }
190
192
  //#endregion
191
- export { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, computeRscCacheBustingSearchParam, createRscRedirectLocation, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId, isRscCompatibilityIdCompatible, resolveHardNavigationTargetFromRscResponse, resolveInvalidRscCacheBustingRequest, resolveRscCompatibilityNavigationDecision, setRscCacheBustingSearchParam, stripRscCacheBustingSearchParam, stripRscSuffix };
193
+ export { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader, computeRscCacheBustingSearchParam, createRscRedirectLocation, createRscRequestHeaders, createRscRequestUrl, createServerActionRequestUrl, getVinextRscCompatibilityId, isRscCompatibilityIdCompatible, resolveHardNavigationTargetFromRscResponse, resolveInvalidRscCacheBustingRequest, resolveRscCompatibilityNavigationDecision, setRscCacheBustingSearchParam, stripRscCacheBustingSearchParam, stripRscSuffix };
192
194
 
193
195
  //# sourceMappingURL=app-rsc-cache-busting.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-rsc-cache-busting.js","names":[],"sources":["../../src/server/app-rsc-cache-busting.ts"],"sourcesContent":["import { fnv1a64 } from \"../utils/hash.js\";\nimport {\n APP_RSC_RENDER_MODE_NAVIGATION,\n parseAppRscRenderMode,\n type AppRscRenderMode,\n} from \"./app-rsc-render-mode.js\";\nimport {\n NEXT_ROUTER_PREFETCH_HEADER,\n NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,\n NEXT_ROUTER_STATE_TREE_HEADER,\n NEXT_URL_HEADER,\n RSC_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n} from \"./headers.js\";\n\n/**\n * RSC cache-busting hashes cover the headers that make a `.rsc` payload vary.\n * Client-side variant headers must survive transit through CDNs and reverse\n * proxies; stripping them changes the server hash and turns stale URLs into\n * repeated canonicalization redirects.\n */\nexport const VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM = \"_rsc\";\nexport const VINEXT_RSC_COMPATIBILITY_ID_HEADER = \"X-Vinext-RSC-Compatibility-Id\";\nexport const VINEXT_RSC_CONTENT_TYPE = \"text/x-component\";\n\n// Re-export so existing consumers that import from this module keep working.\nexport { VINEXT_RSC_RENDER_MODE_HEADER } from \"./headers.js\";\n\nexport const VINEXT_RSC_VARY_HEADER = [\n RSC_HEADER,\n \"Accept\",\n NEXT_ROUTER_STATE_TREE_HEADER,\n NEXT_ROUTER_PREFETCH_HEADER,\n NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,\n NEXT_URL_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n].join(\", \");\n\nconst CACHE_BUSTING_DIGEST_BYTES = 12;\nconst textEncoder = new TextEncoder();\n\ntype CreateRscRequestHeadersOptions = {\n interceptionContext?: string | null;\n mountedSlotsHeader?: string | null;\n renderMode?: AppRscRenderMode;\n};\n\ntype ResolveInvalidRscCacheBustingRequestOptions = {\n isRscRequest: boolean;\n request: Request;\n};\n\nfunction encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\n}\n\nfunction normalizeHeaderValue(value: string | null): string {\n return value ?? \"0\";\n}\n\nfunction normalizeCompatibilityId(value: string | null | undefined): string | null {\n return value && value.length > 0 ? value : null;\n}\n\nexport function getVinextRscCompatibilityId(): string | null {\n return normalizeCompatibilityId(process.env.__VINEXT_RSC_COMPATIBILITY_ID);\n}\n\nexport function applyRscCompatibilityIdHeader(\n headers: Headers,\n compatibilityId: string | null | undefined = getVinextRscCompatibilityId(),\n): void {\n const normalized = normalizeCompatibilityId(compatibilityId);\n if (normalized) {\n headers.set(VINEXT_RSC_COMPATIBILITY_ID_HEADER, normalized);\n } else {\n headers.delete(VINEXT_RSC_COMPATIBILITY_ID_HEADER);\n }\n}\n\nexport function isRscCompatibilityIdCompatible(\n responseCompatibilityId: string | null | undefined,\n clientCompatibilityId: string | null | undefined = getVinextRscCompatibilityId(),\n): boolean {\n const normalizedResponseCompatibilityId = normalizeCompatibilityId(responseCompatibilityId);\n const normalizedClientCompatibilityId = normalizeCompatibilityId(clientCompatibilityId);\n return (\n normalizedClientCompatibilityId === null ||\n (normalizedResponseCompatibilityId !== null &&\n normalizedResponseCompatibilityId === normalizedClientCompatibilityId)\n );\n}\n\ntype RscCompatibilityNavigationDecision =\n | { kind: \"compatible\" }\n | { hardNavigationTarget: string; kind: \"hard-navigate\" };\n\nexport function resolveHardNavigationTargetFromRscResponse(\n responseUrl: string | null | undefined,\n currentHref: string,\n origin: string,\n): string {\n if (!responseUrl) {\n return currentHref;\n }\n\n const parsed = new URL(responseUrl, origin);\n stripRscCacheBustingSearchParam(parsed);\n const origUrl = new URL(currentHref, origin);\n let pathname = stripRscSuffix(parsed.pathname);\n if (origUrl.pathname.length > 1 && origUrl.pathname.endsWith(\"/\") && !pathname.endsWith(\"/\")) {\n pathname += \"/\";\n }\n\n let hardNavigationTarget = pathname + parsed.search;\n if (origUrl.hash) hardNavigationTarget += origUrl.hash;\n return hardNavigationTarget;\n}\n\nexport function resolveRscCompatibilityNavigationDecision(options: {\n clientCompatibilityId?: string | null;\n currentHref: string;\n origin: string;\n responseCompatibilityId: string | null | undefined;\n responseUrl?: string | null;\n}): RscCompatibilityNavigationDecision {\n if (\n isRscCompatibilityIdCompatible(options.responseCompatibilityId, options.clientCompatibilityId)\n ) {\n return { kind: \"compatible\" };\n }\n\n return {\n hardNavigationTarget: resolveHardNavigationTargetFromRscResponse(\n options.responseUrl,\n options.currentHref,\n options.origin,\n ),\n kind: \"hard-navigate\",\n };\n}\n\nfunction normalizeRenderModeHeaderValue(value: string | null): string | null {\n const renderMode = parseAppRscRenderMode(value);\n return renderMode === APP_RSC_RENDER_MODE_NAVIGATION ? null : renderMode;\n}\n\ntype CreateCacheBustingInputOptions = {\n includeRenderModeHeader?: boolean;\n};\n\nfunction createCacheBustingInput(\n headers: Headers,\n options: CreateCacheBustingInputOptions = {},\n): string | null {\n // The order of these values determines the hash. Changing it is a breaking\n // cache-key change and requires accepting the previous hash during rollout.\n const values = [\n headers.get(NEXT_ROUTER_PREFETCH_HEADER),\n headers.get(NEXT_ROUTER_SEGMENT_PREFETCH_HEADER),\n headers.get(NEXT_ROUTER_STATE_TREE_HEADER),\n headers.get(NEXT_URL_HEADER),\n headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER),\n headers.get(VINEXT_MOUNTED_SLOTS_HEADER),\n ...(options.includeRenderModeHeader === false\n ? []\n : [normalizeRenderModeHeaderValue(headers.get(VINEXT_RSC_RENDER_MODE_HEADER))]),\n ];\n\n if (values.every((value) => value === null)) {\n return null;\n }\n\n return values.map(normalizeHeaderValue).join(\",\");\n}\n\nasync function sha256CacheBustingHash(input: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest(\"SHA-256\", textEncoder.encode(input));\n return encodeBase64Url(new Uint8Array(digest).subarray(0, CACHE_BUSTING_DIGEST_BYTES));\n}\n\nfunction computeLegacyRscCacheBustingSearchParam(headers: Headers): string {\n const input = createCacheBustingInput(headers);\n return input === null ? \"\" : fnv1a64(input);\n}\n\nasync function computePreviousRscCacheBustingSearchParam(headers: Headers): Promise<string | null> {\n const input = createCacheBustingInput(headers, { includeRenderModeHeader: false });\n if (input === null) {\n return null;\n }\n\n return sha256CacheBustingHash(input);\n}\n\nfunction computePreviousLegacyRscCacheBustingSearchParam(headers: Headers): string | null {\n const input = createCacheBustingInput(headers, { includeRenderModeHeader: false });\n return input === null ? null : fnv1a64(input);\n}\n\nfunction getSearchPairsWithoutRscCacheBusting(url: URL): string[] {\n const rawQuery = url.search.startsWith(\"?\") ? url.search.slice(1) : url.search;\n return rawQuery\n .split(\"&\")\n .filter((pair) => pair.length > 0 && !isRscCacheBustingSearchPair(pair));\n}\n\nfunction isRscCacheBustingSearchPair(pair: string): boolean {\n const separatorIndex = pair.indexOf(\"=\");\n const rawKey = separatorIndex === -1 ? pair : pair.slice(0, separatorIndex);\n\n try {\n return (\n decodeURIComponent(rawKey.replaceAll(\"+\", \" \")) === VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM\n );\n } catch {\n return rawKey === VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM;\n }\n}\n\nexport async function computeRscCacheBustingSearchParam(headers: Headers): Promise<string> {\n const input = createCacheBustingInput(headers);\n if (input === null) {\n return \"\";\n }\n\n return sha256CacheBustingHash(input);\n}\n\nexport function setRscCacheBustingSearchParam(url: URL, hash: string): void {\n const pairs = getSearchPairsWithoutRscCacheBusting(url);\n\n pairs.push(\n hash.length > 0\n ? `${VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM}=${hash}`\n : VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM,\n );\n url.search = `?${pairs.join(\"&\")}`;\n}\n\nexport function stripRscCacheBustingSearchParam(url: URL): void {\n const pairs = getSearchPairsWithoutRscCacheBusting(url);\n url.search = pairs.length > 0 ? `?${pairs.join(\"&\")}` : \"\";\n}\n\n/**\n * Remove a trailing `.rsc` suffix from a pathname. Returns the pathname\n * unchanged when the suffix is absent.\n */\nexport function stripRscSuffix(pathname: string): string {\n return pathname.endsWith(\".rsc\") ? pathname.slice(0, -4) : pathname;\n}\n\nexport function createRscRequestHeaders(options: CreateRscRequestHeadersOptions = {}): Headers {\n const headers = new Headers({\n Accept: VINEXT_RSC_CONTENT_TYPE,\n [RSC_HEADER]: \"1\",\n });\n\n if (options.interceptionContext !== undefined && options.interceptionContext !== null) {\n headers.set(VINEXT_INTERCEPTION_CONTEXT_HEADER, options.interceptionContext);\n }\n\n if (options.mountedSlotsHeader !== undefined && options.mountedSlotsHeader !== null) {\n headers.set(VINEXT_MOUNTED_SLOTS_HEADER, options.mountedSlotsHeader);\n }\n\n const renderMode = options.renderMode ?? APP_RSC_RENDER_MODE_NAVIGATION;\n if (renderMode !== APP_RSC_RENDER_MODE_NAVIGATION) {\n headers.set(VINEXT_RSC_RENDER_MODE_HEADER, renderMode);\n }\n\n return headers;\n}\n\nfunction toRscRequestPath(href: string): string {\n const hashIndex = href.indexOf(\"#\");\n const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);\n const queryIndex = beforeHash.indexOf(\"?\");\n const pathname = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);\n const query = queryIndex === -1 ? \"\" : beforeHash.slice(queryIndex);\n const normalizedPath =\n pathname.length > 1 && pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n return `${normalizedPath}.rsc${query}`;\n}\n\nexport async function createRscRequestUrl(href: string, headers: Headers): Promise<string> {\n const url = new URL(toRscRequestPath(href), \"http://vinext.local\");\n const hash = await computeRscCacheBustingSearchParam(headers);\n setRscCacheBustingSearchParam(url, hash);\n return `${url.pathname}${url.search}`;\n}\n\nexport async function createRscRedirectLocation(\n location: string,\n request: Request,\n): Promise<string> {\n const requestUrl = new URL(request.url);\n const destinationUrl = new URL(location, requestUrl);\n\n if (destinationUrl.origin !== requestUrl.origin) {\n return destinationUrl.toString();\n }\n\n const rscPath = await createRscRequestUrl(\n `${destinationUrl.pathname}${destinationUrl.search}`,\n request.headers,\n );\n return `${destinationUrl.origin}${rscPath}`;\n}\n\nexport async function resolveInvalidRscCacheBustingRequest(\n options: ResolveInvalidRscCacheBustingRequestOptions,\n): Promise<Response | null> {\n if (\n !options.isRscRequest ||\n (options.request.method !== \"GET\" && options.request.method !== \"HEAD\")\n ) {\n return null;\n }\n\n const url = new URL(options.request.url);\n const actualHash = url.searchParams.get(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);\n const expectedHash = await computeRscCacheBustingSearchParam(options.request.headers);\n\n if (actualHash === null && expectedHash === \"\") {\n return null;\n }\n\n const acceptedHashes = new Set<string>([expectedHash]);\n if (actualHash !== null && actualHash !== expectedHash) {\n acceptedHashes.add(computeLegacyRscCacheBustingSearchParam(options.request.headers));\n if (\n normalizeRenderModeHeaderValue(options.request.headers.get(VINEXT_RSC_RENDER_MODE_HEADER)) ===\n null\n ) {\n const previousHash = await computePreviousRscCacheBustingSearchParam(options.request.headers);\n const previousLegacyHash = computePreviousLegacyRscCacheBustingSearchParam(\n options.request.headers,\n );\n if (previousHash !== null) acceptedHashes.add(previousHash);\n if (previousLegacyHash !== null) acceptedHashes.add(previousLegacyHash);\n }\n }\n\n if (actualHash !== null && acceptedHashes.has(actualHash)) {\n return null;\n }\n\n setRscCacheBustingSearchParam(url, expectedHash);\n return new Response(null, {\n status: 307,\n headers: {\n Location: `${url.pathname}${url.search}`,\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-rsc-cache-busting.js","names":[],"sources":["../../src/server/app-rsc-cache-busting.ts"],"sourcesContent":["import { fnv1a64 } from \"../utils/hash.js\";\nimport {\n APP_RSC_RENDER_MODE_NAVIGATION,\n parseAppRscRenderMode,\n type AppRscRenderMode,\n} from \"./app-rsc-render-mode.js\";\nimport {\n NEXT_ROUTER_PREFETCH_HEADER,\n NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,\n NEXT_ROUTER_STATE_TREE_HEADER,\n NEXT_URL_HEADER,\n RSC_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n} from \"./headers.js\";\n\n/**\n * RSC cache-busting hashes cover the headers that make an RSC payload vary.\n * Client-side variant headers must survive transit through CDNs and reverse\n * proxies; stripping them changes the server hash and turns stale URLs into\n * repeated canonicalization redirects.\n */\nexport const VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM = \"_rsc\";\nexport const VINEXT_RSC_COMPATIBILITY_ID_HEADER = \"X-Vinext-RSC-Compatibility-Id\";\nexport const VINEXT_RSC_CONTENT_TYPE = \"text/x-component\";\n\n// Re-export so existing consumers that import from this module keep working.\nexport { VINEXT_RSC_RENDER_MODE_HEADER } from \"./headers.js\";\n\nexport const VINEXT_RSC_VARY_HEADER = [\n RSC_HEADER,\n \"Accept\",\n NEXT_ROUTER_STATE_TREE_HEADER,\n NEXT_ROUTER_PREFETCH_HEADER,\n NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,\n NEXT_URL_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n].join(\", \");\n\nconst CACHE_BUSTING_DIGEST_BYTES = 12;\nconst textEncoder = new TextEncoder();\n\ntype CreateRscRequestHeadersOptions = {\n interceptionContext?: string | null;\n mountedSlotsHeader?: string | null;\n renderMode?: AppRscRenderMode;\n};\n\ntype ResolveInvalidRscCacheBustingRequestOptions = {\n isRscRequest: boolean;\n request: Request;\n};\n\nfunction encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\n}\n\nfunction normalizeHeaderValue(value: string | null): string {\n return value ?? \"0\";\n}\n\nfunction normalizeCompatibilityId(value: string | null | undefined): string | null {\n return value && value.length > 0 ? value : null;\n}\n\nexport function getVinextRscCompatibilityId(): string | null {\n return normalizeCompatibilityId(process.env.__VINEXT_RSC_COMPATIBILITY_ID);\n}\n\nexport function applyRscCompatibilityIdHeader(\n headers: Headers,\n compatibilityId: string | null | undefined = getVinextRscCompatibilityId(),\n): void {\n const normalized = normalizeCompatibilityId(compatibilityId);\n if (normalized) {\n headers.set(VINEXT_RSC_COMPATIBILITY_ID_HEADER, normalized);\n } else {\n headers.delete(VINEXT_RSC_COMPATIBILITY_ID_HEADER);\n }\n}\n\nexport function isRscCompatibilityIdCompatible(\n responseCompatibilityId: string | null | undefined,\n clientCompatibilityId: string | null | undefined = getVinextRscCompatibilityId(),\n): boolean {\n const normalizedResponseCompatibilityId = normalizeCompatibilityId(responseCompatibilityId);\n const normalizedClientCompatibilityId = normalizeCompatibilityId(clientCompatibilityId);\n return (\n normalizedClientCompatibilityId === null ||\n (normalizedResponseCompatibilityId !== null &&\n normalizedResponseCompatibilityId === normalizedClientCompatibilityId)\n );\n}\n\ntype RscCompatibilityNavigationDecision =\n | { kind: \"compatible\" }\n | { hardNavigationTarget: string; kind: \"hard-navigate\" };\n\nexport function resolveHardNavigationTargetFromRscResponse(\n responseUrl: string | null | undefined,\n currentHref: string,\n origin: string,\n): string {\n if (!responseUrl) {\n return currentHref;\n }\n\n const parsed = new URL(responseUrl, origin);\n stripRscCacheBustingSearchParam(parsed);\n const origUrl = new URL(currentHref, origin);\n let pathname = stripRscSuffix(parsed.pathname);\n if (origUrl.pathname.length > 1 && origUrl.pathname.endsWith(\"/\") && !pathname.endsWith(\"/\")) {\n pathname += \"/\";\n }\n\n let hardNavigationTarget = pathname + parsed.search;\n if (origUrl.hash) hardNavigationTarget += origUrl.hash;\n return hardNavigationTarget;\n}\n\nexport function resolveRscCompatibilityNavigationDecision(options: {\n clientCompatibilityId?: string | null;\n currentHref: string;\n origin: string;\n responseCompatibilityId: string | null | undefined;\n responseUrl?: string | null;\n}): RscCompatibilityNavigationDecision {\n if (\n isRscCompatibilityIdCompatible(options.responseCompatibilityId, options.clientCompatibilityId)\n ) {\n return { kind: \"compatible\" };\n }\n\n return {\n hardNavigationTarget: resolveHardNavigationTargetFromRscResponse(\n options.responseUrl,\n options.currentHref,\n options.origin,\n ),\n kind: \"hard-navigate\",\n };\n}\n\nfunction normalizeRenderModeHeaderValue(value: string | null): string | null {\n const renderMode = parseAppRscRenderMode(value);\n return renderMode === APP_RSC_RENDER_MODE_NAVIGATION ? null : renderMode;\n}\n\ntype CreateCacheBustingInputOptions = {\n includeRenderModeHeader?: boolean;\n};\n\nfunction createCacheBustingInput(\n headers: Headers,\n options: CreateCacheBustingInputOptions = {},\n): string | null {\n // The order of these values determines the hash. Changing it is a breaking\n // cache-key change and requires accepting the previous hash during rollout.\n const values = [\n headers.get(NEXT_ROUTER_PREFETCH_HEADER),\n headers.get(NEXT_ROUTER_SEGMENT_PREFETCH_HEADER),\n headers.get(NEXT_ROUTER_STATE_TREE_HEADER),\n headers.get(NEXT_URL_HEADER),\n headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER),\n headers.get(VINEXT_MOUNTED_SLOTS_HEADER),\n ...(options.includeRenderModeHeader === false\n ? []\n : [normalizeRenderModeHeaderValue(headers.get(VINEXT_RSC_RENDER_MODE_HEADER))]),\n ];\n\n if (values.every((value) => value === null)) {\n return null;\n }\n\n return values.map(normalizeHeaderValue).join(\",\");\n}\n\nasync function sha256CacheBustingHash(input: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest(\"SHA-256\", textEncoder.encode(input));\n return encodeBase64Url(new Uint8Array(digest).subarray(0, CACHE_BUSTING_DIGEST_BYTES));\n}\n\nfunction computeLegacyRscCacheBustingSearchParam(headers: Headers): string {\n const input = createCacheBustingInput(headers);\n return input === null ? \"\" : fnv1a64(input);\n}\n\nasync function computePreviousRscCacheBustingSearchParam(headers: Headers): Promise<string | null> {\n const input = createCacheBustingInput(headers, { includeRenderModeHeader: false });\n if (input === null) {\n return null;\n }\n\n return sha256CacheBustingHash(input);\n}\n\nfunction computePreviousLegacyRscCacheBustingSearchParam(headers: Headers): string | null {\n const input = createCacheBustingInput(headers, { includeRenderModeHeader: false });\n return input === null ? null : fnv1a64(input);\n}\n\nfunction getSearchPairsWithoutRscCacheBusting(url: URL): string[] {\n const rawQuery = url.search.startsWith(\"?\") ? url.search.slice(1) : url.search;\n return rawQuery\n .split(\"&\")\n .filter((pair) => pair.length > 0 && !isRscCacheBustingSearchPair(pair));\n}\n\nfunction isRscCacheBustingSearchPair(pair: string): boolean {\n const separatorIndex = pair.indexOf(\"=\");\n const rawKey = separatorIndex === -1 ? pair : pair.slice(0, separatorIndex);\n\n try {\n return (\n decodeURIComponent(rawKey.replaceAll(\"+\", \" \")) === VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM\n );\n } catch {\n return rawKey === VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM;\n }\n}\n\nexport async function computeRscCacheBustingSearchParam(headers: Headers): Promise<string> {\n const input = createCacheBustingInput(headers);\n if (input === null) {\n return \"\";\n }\n\n return sha256CacheBustingHash(input);\n}\n\nexport function setRscCacheBustingSearchParam(url: URL, hash: string): void {\n const pairs = getSearchPairsWithoutRscCacheBusting(url);\n\n pairs.push(\n hash.length > 0\n ? `${VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM}=${hash}`\n : VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM,\n );\n url.search = `?${pairs.join(\"&\")}`;\n}\n\nexport function stripRscCacheBustingSearchParam(url: URL): void {\n const pairs = getSearchPairsWithoutRscCacheBusting(url);\n url.search = pairs.length > 0 ? `?${pairs.join(\"&\")}` : \"\";\n}\n\n/**\n * Remove a trailing `.rsc` suffix from a pathname. Returns the pathname\n * unchanged when the suffix is absent.\n */\nexport function stripRscSuffix(pathname: string): string {\n return pathname.endsWith(\".rsc\") ? pathname.slice(0, -4) : pathname;\n}\n\nexport function createRscRequestHeaders(options: CreateRscRequestHeadersOptions = {}): Headers {\n const headers = new Headers({\n Accept: VINEXT_RSC_CONTENT_TYPE,\n [RSC_HEADER]: \"1\",\n });\n\n if (options.interceptionContext !== undefined && options.interceptionContext !== null) {\n headers.set(VINEXT_INTERCEPTION_CONTEXT_HEADER, options.interceptionContext);\n }\n\n if (options.mountedSlotsHeader !== undefined && options.mountedSlotsHeader !== null) {\n headers.set(VINEXT_MOUNTED_SLOTS_HEADER, options.mountedSlotsHeader);\n }\n\n const renderMode = options.renderMode ?? APP_RSC_RENDER_MODE_NAVIGATION;\n if (renderMode !== APP_RSC_RENDER_MODE_NAVIGATION) {\n headers.set(VINEXT_RSC_RENDER_MODE_HEADER, renderMode);\n }\n\n return headers;\n}\n\nfunction toRscRequestPath(href: string): string {\n const hashIndex = href.indexOf(\"#\");\n const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);\n return beforeHash;\n}\n\nexport async function createRscRequestUrl(href: string, headers: Headers): Promise<string> {\n const url = new URL(toRscRequestPath(href), \"http://vinext.local\");\n const hash = await computeRscCacheBustingSearchParam(headers);\n setRscCacheBustingSearchParam(url, hash);\n return `${url.pathname}${url.search}`;\n}\n\nexport function createServerActionRequestUrl(href: string): string {\n const hashIndex = href.indexOf(\"#\");\n const beforeHash = hashIndex === -1 ? href : href.slice(0, hashIndex);\n const url = new URL(beforeHash, \"http://vinext.local\");\n return `${url.pathname}${url.search}`;\n}\n\nexport async function createRscRedirectLocation(\n location: string,\n request: Request,\n): Promise<string> {\n const requestUrl = new URL(request.url);\n const destinationUrl = new URL(location, requestUrl);\n\n if (destinationUrl.origin !== requestUrl.origin) {\n return destinationUrl.toString();\n }\n\n const rscPath = await createRscRequestUrl(\n `${destinationUrl.pathname}${destinationUrl.search}`,\n request.headers,\n );\n return `${destinationUrl.origin}${rscPath}`;\n}\n\nexport async function resolveInvalidRscCacheBustingRequest(\n options: ResolveInvalidRscCacheBustingRequestOptions,\n): Promise<Response | null> {\n if (\n !options.isRscRequest ||\n (options.request.method !== \"GET\" && options.request.method !== \"HEAD\")\n ) {\n return null;\n }\n\n const url = new URL(options.request.url);\n const actualHash = url.searchParams.get(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);\n const expectedHash = await computeRscCacheBustingSearchParam(options.request.headers);\n\n if (actualHash === null && expectedHash === \"\") {\n return null;\n }\n\n const acceptedHashes = new Set<string>([expectedHash]);\n if (actualHash !== null && actualHash !== expectedHash) {\n acceptedHashes.add(computeLegacyRscCacheBustingSearchParam(options.request.headers));\n if (\n normalizeRenderModeHeaderValue(options.request.headers.get(VINEXT_RSC_RENDER_MODE_HEADER)) ===\n null\n ) {\n const previousHash = await computePreviousRscCacheBustingSearchParam(options.request.headers);\n const previousLegacyHash = computePreviousLegacyRscCacheBustingSearchParam(\n options.request.headers,\n );\n if (previousHash !== null) acceptedHashes.add(previousHash);\n if (previousLegacyHash !== null) acceptedHashes.add(previousLegacyHash);\n }\n }\n\n if (actualHash !== null && acceptedHashes.has(actualHash)) {\n return null;\n }\n\n setRscCacheBustingSearchParam(url, expectedHash);\n return new Response(null, {\n status: 307,\n headers: {\n Location: `${url.pathname}${url.search}`,\n },\n });\n}\n"],"mappings":"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"}
@@ -25,6 +25,16 @@ import { normalizeRscRequest } from "./app-rsc-request-normalization.js";
25
25
  import { handleMetadataRouteRequest } from "./metadata-route-response.js";
26
26
  import { runWithPrerenderWorkUnit } from "./prerender-work-unit-setup.js";
27
27
  //#region src/server/app-rsc-handler.ts
28
+ function applyMiddlewareContextToResponse(response, middlewareContext) {
29
+ if (!middlewareContext.headers && middlewareContext.status == null) return response;
30
+ const headers = new Headers(response.headers);
31
+ mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);
32
+ return new Response(response.body, {
33
+ status: middlewareContext.status ?? response.status,
34
+ statusText: response.statusText,
35
+ headers
36
+ });
37
+ }
28
38
  function hasProperty(value, key) {
29
39
  return key in value;
30
40
  }
@@ -155,7 +165,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
155
165
  cleanPathname,
156
166
  makeThenableParams: options.makeThenableParams
157
167
  });
158
- if (metadataRouteResponse) return metadataRouteResponse;
168
+ if (metadataRouteResponse) return applyMiddlewareContextToResponse(metadataRouteResponse, middlewareContext);
159
169
  const publicFileResponse = resolvePublicFileRoute({
160
170
  cleanPathname,
161
171
  middlewareContext,