vinext 0.0.46 → 0.0.47

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 (171) hide show
  1. package/README.md +7 -5
  2. package/dist/build/prerender.d.ts +2 -1
  3. package/dist/build/prerender.js +70 -14
  4. package/dist/build/prerender.js.map +1 -1
  5. package/dist/build/report.d.ts +1 -1
  6. package/dist/build/route-classification-injector.d.ts +35 -0
  7. package/dist/build/route-classification-injector.js +61 -0
  8. package/dist/build/route-classification-injector.js.map +1 -0
  9. package/dist/build/route-classification-manifest.d.ts +1 -1
  10. package/dist/build/static-export.d.ts +1 -1
  11. package/dist/cli-args.d.ts +31 -0
  12. package/dist/cli-args.js +104 -0
  13. package/dist/cli-args.js.map +1 -0
  14. package/dist/cli.js +2 -19
  15. package/dist/cli.js.map +1 -1
  16. package/dist/cloudflare/kv-cache-handler.js +29 -9
  17. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  18. package/dist/config/next-config.d.ts +4 -2
  19. package/dist/config/next-config.js +3 -0
  20. package/dist/config/next-config.js.map +1 -1
  21. package/dist/entries/app-rsc-entry.d.ts +4 -3
  22. package/dist/entries/app-rsc-entry.js +373 -854
  23. package/dist/entries/app-rsc-entry.js.map +1 -1
  24. package/dist/entries/app-rsc-manifest.d.ts +1 -1
  25. package/dist/entries/app-rsc-manifest.js +2 -0
  26. package/dist/entries/app-rsc-manifest.js.map +1 -1
  27. package/dist/entries/pages-server-entry.js +5 -2
  28. package/dist/entries/pages-server-entry.js.map +1 -1
  29. package/dist/index.js +28 -51
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/fonts.js +54 -32
  32. package/dist/plugins/fonts.js.map +1 -1
  33. package/dist/plugins/rsc-client-shim-excludes.js +1 -0
  34. package/dist/plugins/rsc-client-shim-excludes.js.map +1 -1
  35. package/dist/routing/app-route-graph.d.ts +109 -0
  36. package/dist/routing/app-route-graph.js +819 -0
  37. package/dist/routing/app-route-graph.js.map +1 -0
  38. package/dist/routing/app-router.d.ts +2 -88
  39. package/dist/routing/app-router.js +6 -694
  40. package/dist/routing/app-router.js.map +1 -1
  41. package/dist/server/app-browser-entry.js +86 -252
  42. package/dist/server/app-browser-entry.js.map +1 -1
  43. package/dist/server/app-browser-error.d.ts +3 -4
  44. package/dist/server/app-browser-error.js +8 -4
  45. package/dist/server/app-browser-error.js.map +1 -1
  46. package/dist/server/app-browser-navigation-controller.d.ts +73 -0
  47. package/dist/server/app-browser-navigation-controller.js +282 -0
  48. package/dist/server/app-browser-navigation-controller.js.map +1 -0
  49. package/dist/server/app-browser-state.d.ts +1 -1
  50. package/dist/server/app-elements.js +1 -5
  51. package/dist/server/app-elements.js.map +1 -1
  52. package/dist/server/app-fallback-renderer.d.ts +57 -0
  53. package/dist/server/app-fallback-renderer.js +79 -0
  54. package/dist/server/app-fallback-renderer.js.map +1 -0
  55. package/dist/server/app-hook-warning-suppression.d.ts +7 -0
  56. package/dist/server/app-hook-warning-suppression.js +12 -0
  57. package/dist/server/app-hook-warning-suppression.js.map +1 -0
  58. package/dist/server/app-mounted-slots-header.d.ts +17 -0
  59. package/dist/server/app-mounted-slots-header.js +21 -0
  60. package/dist/server/app-mounted-slots-header.js.map +1 -0
  61. package/dist/server/app-page-boundary-render.d.ts +2 -2
  62. package/dist/server/app-page-boundary-render.js.map +1 -1
  63. package/dist/server/app-page-cache.d.ts +18 -4
  64. package/dist/server/app-page-cache.js +53 -10
  65. package/dist/server/app-page-cache.js.map +1 -1
  66. package/dist/server/app-page-dispatch.d.ts +7 -4
  67. package/dist/server/app-page-dispatch.js +24 -8
  68. package/dist/server/app-page-dispatch.js.map +1 -1
  69. package/dist/server/app-page-element-builder.d.ts +61 -0
  70. package/dist/server/app-page-element-builder.js +139 -0
  71. package/dist/server/app-page-element-builder.js.map +1 -0
  72. package/dist/server/app-page-params.d.ts +2 -1
  73. package/dist/server/app-page-params.js +3 -3
  74. package/dist/server/app-page-params.js.map +1 -1
  75. package/dist/server/app-page-render.d.ts +5 -1
  76. package/dist/server/app-page-render.js +80 -27
  77. package/dist/server/app-page-render.js.map +1 -1
  78. package/dist/server/app-page-request.d.ts +19 -4
  79. package/dist/server/app-page-request.js +51 -6
  80. package/dist/server/app-page-request.js.map +1 -1
  81. package/dist/server/app-page-response.d.ts +1 -0
  82. package/dist/server/app-page-response.js +3 -7
  83. package/dist/server/app-page-response.js.map +1 -1
  84. package/dist/server/app-page-route-wiring.d.ts +15 -2
  85. package/dist/server/app-page-route-wiring.js.map +1 -1
  86. package/dist/server/app-post-middleware-context.d.ts +16 -0
  87. package/dist/server/app-post-middleware-context.js +28 -0
  88. package/dist/server/app-post-middleware-context.js.map +1 -0
  89. package/dist/server/app-request-context.d.ts +22 -0
  90. package/dist/server/app-request-context.js +30 -0
  91. package/dist/server/app-request-context.js.map +1 -0
  92. package/dist/server/app-route-handler-cache.d.ts +1 -0
  93. package/dist/server/app-route-handler-cache.js +5 -1
  94. package/dist/server/app-route-handler-cache.js.map +1 -1
  95. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  96. package/dist/server/app-route-handler-dispatch.js +2 -0
  97. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  98. package/dist/server/app-route-handler-execution.d.ts +2 -1
  99. package/dist/server/app-route-handler-execution.js +2 -2
  100. package/dist/server/app-route-handler-execution.js.map +1 -1
  101. package/dist/server/app-route-handler-response.d.ts +4 -2
  102. package/dist/server/app-route-handler-response.js +8 -7
  103. package/dist/server/app-route-handler-response.js.map +1 -1
  104. package/dist/server/app-rsc-error-handler.d.ts +21 -0
  105. package/dist/server/app-rsc-error-handler.js +30 -0
  106. package/dist/server/app-rsc-error-handler.js.map +1 -0
  107. package/dist/server/app-rsc-handler.d.ts +117 -0
  108. package/dist/server/app-rsc-handler.js +260 -0
  109. package/dist/server/app-rsc-handler.js.map +1 -0
  110. package/dist/server/app-rsc-request-normalization.d.ts +40 -0
  111. package/dist/server/app-rsc-request-normalization.js +63 -0
  112. package/dist/server/app-rsc-request-normalization.js.map +1 -0
  113. package/dist/server/app-rsc-response-finalizer.d.ts +30 -0
  114. package/dist/server/app-rsc-response-finalizer.js +38 -0
  115. package/dist/server/app-rsc-response-finalizer.js.map +1 -0
  116. package/dist/server/app-segment-config.d.ts +33 -0
  117. package/dist/server/app-segment-config.js +86 -0
  118. package/dist/server/app-segment-config.js.map +1 -0
  119. package/dist/server/app-server-action-execution.d.ts +2 -0
  120. package/dist/server/app-server-action-execution.js +2 -0
  121. package/dist/server/app-server-action-execution.js.map +1 -1
  122. package/dist/server/cache-control.d.ts +24 -0
  123. package/dist/server/cache-control.js +33 -0
  124. package/dist/server/cache-control.js.map +1 -0
  125. package/dist/server/dev-error-overlay-store.d.ts +23 -0
  126. package/dist/server/dev-error-overlay-store.js +67 -0
  127. package/dist/server/dev-error-overlay-store.js.map +1 -0
  128. package/dist/server/dev-error-overlay.d.ts +15 -0
  129. package/dist/server/dev-error-overlay.js +548 -0
  130. package/dist/server/dev-error-overlay.js.map +1 -0
  131. package/dist/server/instrumentation-runtime.d.ts +44 -0
  132. package/dist/server/instrumentation-runtime.js +29 -0
  133. package/dist/server/instrumentation-runtime.js.map +1 -0
  134. package/dist/server/isr-cache.d.ts +2 -7
  135. package/dist/server/isr-cache.js +7 -10
  136. package/dist/server/isr-cache.js.map +1 -1
  137. package/dist/server/pages-page-data.d.ts +2 -1
  138. package/dist/server/pages-page-data.js +6 -5
  139. package/dist/server/pages-page-data.js.map +1 -1
  140. package/dist/server/pages-page-response.d.ts +2 -1
  141. package/dist/server/pages-page-response.js +3 -2
  142. package/dist/server/pages-page-response.js.map +1 -1
  143. package/dist/server/rsc-stream-hints.d.ts +3 -1
  144. package/dist/server/rsc-stream-hints.js +4 -1
  145. package/dist/server/rsc-stream-hints.js.map +1 -1
  146. package/dist/server/seed-cache.js +19 -8
  147. package/dist/server/seed-cache.js.map +1 -1
  148. package/dist/shims/cache-runtime.js +28 -11
  149. package/dist/shims/cache-runtime.js.map +1 -1
  150. package/dist/shims/cache.d.ts +15 -3
  151. package/dist/shims/cache.js +42 -15
  152. package/dist/shims/cache.js.map +1 -1
  153. package/dist/shims/error-boundary.d.ts +17 -1
  154. package/dist/shims/error-boundary.js +31 -1
  155. package/dist/shims/error-boundary.js.map +1 -1
  156. package/dist/shims/fetch-cache.d.ts +4 -1
  157. package/dist/shims/fetch-cache.js +55 -13
  158. package/dist/shims/fetch-cache.js.map +1 -1
  159. package/dist/shims/image.js +93 -5
  160. package/dist/shims/image.js.map +1 -1
  161. package/dist/shims/request-state-types.d.ts +1 -1
  162. package/dist/shims/unified-request-context.d.ts +1 -1
  163. package/dist/shims/unified-request-context.js +1 -0
  164. package/dist/shims/unified-request-context.js.map +1 -1
  165. package/dist/shims/use-merged-ref.d.ts +7 -0
  166. package/dist/shims/use-merged-ref.js +40 -0
  167. package/dist/shims/use-merged-ref.js.map +1 -0
  168. package/dist/utils/cache-control-metadata.d.ts +6 -0
  169. package/dist/utils/cache-control-metadata.js +16 -0
  170. package/dist/utils/cache-control-metadata.js.map +1 -0
  171. package/package.json +1 -1
@@ -0,0 +1,282 @@
1
+ import { activateNavigationSnapshot, clearPendingPathname, commitClientNavigationState } from "../shims/navigation.js";
2
+ import { createPendingNavigationCommit, resolveAndClassifyNavigationCommit, resolvePendingNavigationCommitDisposition, routerReducer } from "./app-browser-state.js";
3
+ import { startTransition, useLayoutEffect } from "react";
4
+ //#region src/server/app-browser-navigation-controller.ts
5
+ function createAppBrowserNavigationController(deps = {}) {
6
+ const commitClientNavigationStateImpl = deps.commitClientNavigationState ?? commitClientNavigationState;
7
+ let nextNavigationRenderId = 0;
8
+ let activeNavigationId = 0;
9
+ const pendingNavigationCommits = /* @__PURE__ */ new Map();
10
+ const pendingNavigationPrePaintEffects = /* @__PURE__ */ new Map();
11
+ let setBrowserRouterState = null;
12
+ let browserRouterStateRef = null;
13
+ let activePendingBrowserRouterState = null;
14
+ let resolveBrowserRouterStateReady = null;
15
+ let browserRouterStateReadyPromise = null;
16
+ let browserRouterStateHasCommitted = false;
17
+ function getBrowserRouterStateSetter() {
18
+ if (!setBrowserRouterState) throw new Error("[vinext] Browser router state setter is not initialized");
19
+ return setBrowserRouterState;
20
+ }
21
+ function getBrowserRouterState() {
22
+ if (!browserRouterStateRef) throw new Error("[vinext] Browser router state is not initialized");
23
+ return browserRouterStateRef.current;
24
+ }
25
+ function waitForBrowserRouterStateReady() {
26
+ if (browserRouterStateRef || browserRouterStateHasCommitted) return Promise.resolve();
27
+ if (!browserRouterStateReadyPromise) browserRouterStateReadyPromise = new Promise((resolve) => {
28
+ resolveBrowserRouterStateReady = resolve;
29
+ });
30
+ return browserRouterStateReadyPromise;
31
+ }
32
+ function markBrowserRouterStateReady() {
33
+ browserRouterStateHasCommitted = true;
34
+ const resolveReady = resolveBrowserRouterStateReady;
35
+ resolveBrowserRouterStateReady = null;
36
+ browserRouterStateReadyPromise = null;
37
+ resolveReady?.();
38
+ }
39
+ function beginNavigation() {
40
+ activeNavigationId += 1;
41
+ return activeNavigationId;
42
+ }
43
+ function allocateRenderId() {
44
+ nextNavigationRenderId += 1;
45
+ return nextNavigationRenderId;
46
+ }
47
+ function hasBrowserRouterState() {
48
+ return browserRouterStateRef !== null;
49
+ }
50
+ function isCurrentNavigation(navId) {
51
+ return navId === activeNavigationId;
52
+ }
53
+ function beginPendingBrowserRouterState() {
54
+ const setter = getBrowserRouterStateSetter();
55
+ if (activePendingBrowserRouterState && !activePendingBrowserRouterState.settled) {
56
+ activePendingBrowserRouterState.settled = true;
57
+ activePendingBrowserRouterState.resolve(getBrowserRouterState());
58
+ }
59
+ let resolvePending;
60
+ const promise = new Promise((resolve) => {
61
+ resolvePending = resolve;
62
+ });
63
+ if (!resolvePending) throw new Error("[vinext] Failed to initialize browser router promise");
64
+ const pending = {
65
+ promise,
66
+ resolve: resolvePending,
67
+ settled: false
68
+ };
69
+ activePendingBrowserRouterState = pending;
70
+ setter(promise);
71
+ return pending;
72
+ }
73
+ function settlePendingBrowserRouterState(pending) {
74
+ if (!pending || pending.settled) return;
75
+ pending.settled = true;
76
+ pending.resolve(getBrowserRouterState());
77
+ if (activePendingBrowserRouterState === pending) activePendingBrowserRouterState = null;
78
+ }
79
+ function finalizeNavigation(navId, pending) {
80
+ settlePendingBrowserRouterState(pending);
81
+ if (isCurrentNavigation(navId)) clearPendingPathname(navId);
82
+ }
83
+ function resolvePendingBrowserRouterState(pending, action) {
84
+ if (!pending || pending.settled) return;
85
+ pending.settled = true;
86
+ pending.resolve(routerReducer(getBrowserRouterState(), action));
87
+ if (activePendingBrowserRouterState === pending) activePendingBrowserRouterState = null;
88
+ }
89
+ function queuePrePaintNavigationEffect(renderId, effect) {
90
+ if (!effect) return;
91
+ pendingNavigationPrePaintEffects.set(renderId, effect);
92
+ }
93
+ /**
94
+ * Run all queued pre-paint effects for renderIds up to and including the
95
+ * given renderId. When React supersedes a startTransition update (rapid
96
+ * clicks on same-route links), the superseded NavigationCommitSignal never
97
+ * mounts, so its pre-paint effect never fires. By draining all effects
98
+ * <= the committed renderId here, the winning transition cleans up after
99
+ * any superseded ones, keeping the counter balanced.
100
+ *
101
+ * Invariant: each superseded navigation gets a commitClientNavigationState()
102
+ * to balance the activateNavigationSnapshot() from its renderNavigationPayload call.
103
+ */
104
+ function drainPrePaintEffects(upToRenderId) {
105
+ for (const [id, effect] of pendingNavigationPrePaintEffects) {
106
+ if (id > upToRenderId) continue;
107
+ pendingNavigationPrePaintEffects.delete(id);
108
+ if (id === upToRenderId) effect();
109
+ else commitClientNavigationStateImpl(void 0, { releaseSnapshot: true });
110
+ }
111
+ }
112
+ /**
113
+ * Resolve all pending navigation commits with renderId <= the committed renderId.
114
+ * Note: Map iteration handles concurrent deletion safely — entries are visited in
115
+ * insertion order and deletion doesn't affect the iterator's view of remaining entries.
116
+ * This pattern is also used in drainPrePaintEffects with the same semantics.
117
+ */
118
+ function resolveCommittedNavigations(renderId) {
119
+ for (const [pendingId, resolve] of pendingNavigationCommits) {
120
+ if (pendingId > renderId) continue;
121
+ pendingNavigationCommits.delete(pendingId);
122
+ resolve();
123
+ }
124
+ }
125
+ async function hmrReplaceTree(nextElements, navigationSnapshot) {
126
+ if (!hasBrowserRouterState()) return;
127
+ const pending = await createPendingNavigationCommit({
128
+ currentState: getBrowserRouterState(),
129
+ nextElements,
130
+ navigationSnapshot,
131
+ renderId: allocateRenderId(),
132
+ type: "replace"
133
+ });
134
+ if (!hasBrowserRouterState()) return;
135
+ dispatchBrowserTree(pending.action.elements, navigationSnapshot, pending.action.renderId, "replace", pending.interceptionContext, pending.action.layoutFlags, pending.previousNextUrl, pending.routeId, pending.rootLayoutTreePath, null, false);
136
+ }
137
+ function NavigationCommitSignal({ renderId, children }) {
138
+ useLayoutEffect(() => {
139
+ drainPrePaintEffects(renderId);
140
+ const frame = requestAnimationFrame(() => {
141
+ resolveCommittedNavigations(renderId);
142
+ });
143
+ return () => {
144
+ cancelAnimationFrame(frame);
145
+ resolveCommittedNavigations(renderId);
146
+ };
147
+ }, [renderId]);
148
+ return children;
149
+ }
150
+ function dispatchBrowserTree(elements, navigationSnapshot, renderId, actionType, interceptionContext, layoutFlags, previousNextUrl, routeId, rootLayoutTreePath, pendingRouterState, useTransitionMode) {
151
+ const setter = getBrowserRouterStateSetter();
152
+ const action = {
153
+ elements,
154
+ interceptionContext,
155
+ layoutFlags,
156
+ navigationSnapshot,
157
+ previousNextUrl,
158
+ renderId,
159
+ rootLayoutTreePath,
160
+ routeId,
161
+ type: actionType
162
+ };
163
+ const applyAction = () => {
164
+ if (pendingRouterState) {
165
+ resolvePendingBrowserRouterState(pendingRouterState, action);
166
+ return;
167
+ }
168
+ setter(routerReducer(getBrowserRouterState(), action));
169
+ };
170
+ if (useTransitionMode) startTransition(applyAction);
171
+ else applyAction();
172
+ }
173
+ async function renderNavigationPayload(options) {
174
+ const renderId = allocateRenderId();
175
+ let resolveCommitted;
176
+ const committed = new Promise((resolve) => {
177
+ resolveCommitted = resolve;
178
+ pendingNavigationCommits.set(renderId, resolve);
179
+ });
180
+ let snapshotActivated = false;
181
+ try {
182
+ const currentState = getBrowserRouterState();
183
+ const pending = await createPendingNavigationCommit({
184
+ currentState,
185
+ nextElements: options.nextElements,
186
+ navigationSnapshot: options.navigationSnapshot,
187
+ previousNextUrl: options.previousNextUrl,
188
+ renderId,
189
+ type: options.actionType
190
+ });
191
+ const disposition = resolvePendingNavigationCommitDisposition({
192
+ activeNavigationId,
193
+ currentRootLayoutTreePath: currentState.rootLayoutTreePath,
194
+ nextRootLayoutTreePath: pending.rootLayoutTreePath,
195
+ startedNavigationId: options.navId
196
+ });
197
+ if (disposition === "skip") {
198
+ settlePendingBrowserRouterState(options.pendingRouterState);
199
+ pendingNavigationCommits.delete(renderId);
200
+ resolveCommitted?.();
201
+ return;
202
+ }
203
+ if (disposition === "hard-navigate") {
204
+ settlePendingBrowserRouterState(options.pendingRouterState);
205
+ pendingNavigationCommits.delete(renderId);
206
+ window.location.assign(options.targetHref);
207
+ return;
208
+ }
209
+ queuePrePaintNavigationEffect(renderId, options.createNavigationCommitEffect({
210
+ href: options.targetHref,
211
+ historyUpdateMode: options.historyUpdateMode,
212
+ navId: options.navId,
213
+ params: options.params,
214
+ previousNextUrl: pending.previousNextUrl
215
+ }));
216
+ activateNavigationSnapshot();
217
+ snapshotActivated = true;
218
+ dispatchBrowserTree(pending.action.elements, options.navigationSnapshot, renderId, options.actionType, pending.interceptionContext, pending.action.layoutFlags, pending.previousNextUrl, pending.routeId, pending.rootLayoutTreePath, options.pendingRouterState, options.useTransition ?? true);
219
+ } catch (error) {
220
+ pendingNavigationPrePaintEffects.delete(renderId);
221
+ pendingNavigationCommits.delete(renderId);
222
+ if (snapshotActivated) commitClientNavigationStateImpl(options.navId);
223
+ settlePendingBrowserRouterState(options.pendingRouterState);
224
+ resolveCommitted?.();
225
+ throw error;
226
+ }
227
+ return committed;
228
+ }
229
+ async function commitSameUrlNavigatePayload(nextElements, navigationSnapshot, returnValue) {
230
+ const currentState = getBrowserRouterState();
231
+ const startedNavigationId = activeNavigationId;
232
+ const { disposition, pending } = await resolveAndClassifyNavigationCommit({
233
+ activeNavigationId,
234
+ currentState,
235
+ navigationSnapshot,
236
+ nextElements,
237
+ renderId: allocateRenderId(),
238
+ startedNavigationId,
239
+ type: "navigate"
240
+ });
241
+ if (disposition === "hard-navigate") {
242
+ window.location.assign(window.location.href);
243
+ return;
244
+ }
245
+ if (disposition === "dispatch") dispatchBrowserTree(pending.action.elements, navigationSnapshot, pending.action.renderId, "navigate", pending.interceptionContext, pending.action.layoutFlags, pending.previousNextUrl, pending.routeId, pending.rootLayoutTreePath, null, false);
246
+ if (returnValue) {
247
+ if (!returnValue.ok) throw returnValue.data;
248
+ return returnValue.data;
249
+ }
250
+ }
251
+ function attachBrowserRouterState(setter, stateRef) {
252
+ setBrowserRouterState = setter;
253
+ browserRouterStateRef = stateRef;
254
+ markBrowserRouterStateReady();
255
+ return () => {
256
+ if (setBrowserRouterState === setter) setBrowserRouterState = null;
257
+ if (browserRouterStateRef === stateRef) {
258
+ browserRouterStateRef = null;
259
+ browserRouterStateHasCommitted = false;
260
+ }
261
+ };
262
+ }
263
+ return {
264
+ beginNavigation,
265
+ hasBrowserRouterState,
266
+ getBrowserRouterState,
267
+ isCurrentNavigation,
268
+ waitForBrowserRouterStateReady,
269
+ attachBrowserRouterState,
270
+ beginPendingBrowserRouterState,
271
+ finalizeNavigation,
272
+ renderNavigationPayload,
273
+ commitSameUrlNavigatePayload,
274
+ hmrReplaceTree,
275
+ drainPrePaintEffects,
276
+ NavigationCommitSignal
277
+ };
278
+ }
279
+ //#endregion
280
+ export { createAppBrowserNavigationController };
281
+
282
+ //# sourceMappingURL=app-browser-navigation-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-browser-navigation-controller.js","names":[],"sources":["../../src/server/app-browser-navigation-controller.ts"],"sourcesContent":["import { startTransition, useLayoutEffect, type Dispatch, type ReactNode } from \"react\";\nimport {\n activateNavigationSnapshot,\n clearPendingPathname,\n commitClientNavigationState,\n} from \"vinext/shims/navigation\";\nimport type { ClientNavigationRenderSnapshot } from \"vinext/shims/navigation\";\nimport {\n createPendingNavigationCommit,\n resolveAndClassifyNavigationCommit,\n resolvePendingNavigationCommitDisposition,\n routerReducer,\n type AppRouterAction,\n type AppRouterState,\n} from \"./app-browser-state.js\";\nimport type { AppElements, LayoutFlags } from \"./app-elements.js\";\n\nexport type HistoryUpdateMode = \"push\" | \"replace\";\n\nexport type PendingBrowserRouterState = {\n promise: Promise<AppRouterState>;\n resolve: (state: AppRouterState) => void;\n settled: boolean;\n};\n\ntype BrowserNavigationCommitEffectFactory = (options: {\n href: string;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navId: number;\n params: Record<string, string | string[]>;\n previousNextUrl: string | null;\n}) => () => void;\n\ntype BrowserRouterStateRef = {\n current: AppRouterState;\n};\n\ntype BrowserNavigationControllerDeps = {\n commitClientNavigationState?: typeof commitClientNavigationState;\n};\n\ntype BrowserNavigationController = {\n beginNavigation(): number;\n hasBrowserRouterState(): boolean;\n getBrowserRouterState(): AppRouterState;\n isCurrentNavigation(navId: number): boolean;\n waitForBrowserRouterStateReady(): Promise<void>;\n attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void;\n beginPendingBrowserRouterState(): PendingBrowserRouterState;\n finalizeNavigation(navId: number, pending: PendingBrowserRouterState | null | undefined): void;\n renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHref: string;\n navId: number;\n useTransition?: boolean;\n }): Promise<void>;\n commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n ): Promise<unknown>;\n hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void>;\n /**\n * Force-drain the queued pre-paint effect for the given renderId without\n * waiting for NavigationCommitSignal to commit. Used by the dev recovery\n * boundary in app-browser-entry.ts: when a render error replaces\n * NavigationCommitSignal with the boundary's null fallback, its\n * useLayoutEffect never fires, so the URL update for the in-flight\n * navigation would otherwise be lost.\n */\n drainPrePaintEffects(renderId: number): void;\n NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode;\n};\n\nexport function createAppBrowserNavigationController(\n deps: BrowserNavigationControllerDeps = {},\n): BrowserNavigationController {\n const commitClientNavigationStateImpl =\n deps.commitClientNavigationState ?? commitClientNavigationState;\n\n // These are plain module-level variables (inside the controller closure),\n // unlike ClientNavigationState which uses Symbol.for to survive multiple\n // Vite module instances. The browser entry is loaded exactly once (via the\n // RSC plugin's generated bootstrap), so the controller running in a single\n // module instance is safe. If that assumption ever changes, these should be\n // migrated to a Symbol.for-backed global.\n //\n // The most severe consequence of multiple instances would be Map fragmentation:\n // pendingNavigationCommits and pendingNavigationPrePaintEffects would split\n // across instances, so drainPrePaintEffects in one instance could never drain\n // effects queued by the other, permanently leaking navigationSnapshotActiveCount\n // and causing hooks to prefer stale snapshot values indefinitely.\n let nextNavigationRenderId = 0;\n let activeNavigationId = 0;\n const pendingNavigationCommits = new Map<number, () => void>();\n const pendingNavigationPrePaintEffects = new Map<number, () => void>();\n\n let setBrowserRouterState: Dispatch<AppRouterState | Promise<AppRouterState>> | null = null;\n let browserRouterStateRef: BrowserRouterStateRef | null = null;\n let activePendingBrowserRouterState: PendingBrowserRouterState | null = null;\n let resolveBrowserRouterStateReady: (() => void) | null = null;\n let browserRouterStateReadyPromise: Promise<void> | null = null;\n let browserRouterStateHasCommitted = false;\n\n function getBrowserRouterStateSetter(): Dispatch<AppRouterState | Promise<AppRouterState>> {\n if (!setBrowserRouterState) {\n throw new Error(\"[vinext] Browser router state setter is not initialized\");\n }\n return setBrowserRouterState;\n }\n\n function getBrowserRouterState(): AppRouterState {\n if (!browserRouterStateRef) {\n throw new Error(\"[vinext] Browser router state is not initialized\");\n }\n return browserRouterStateRef.current;\n }\n\n function waitForBrowserRouterStateReady(): Promise<void> {\n if (browserRouterStateRef || browserRouterStateHasCommitted) {\n return Promise.resolve();\n }\n\n if (!browserRouterStateReadyPromise) {\n browserRouterStateReadyPromise = new Promise((resolve) => {\n resolveBrowserRouterStateReady = resolve;\n });\n }\n\n return browserRouterStateReadyPromise;\n }\n\n function markBrowserRouterStateReady(): void {\n browserRouterStateHasCommitted = true;\n const resolveReady = resolveBrowserRouterStateReady;\n resolveBrowserRouterStateReady = null;\n browserRouterStateReadyPromise = null;\n resolveReady?.();\n }\n\n function beginNavigation(): number {\n activeNavigationId += 1;\n return activeNavigationId;\n }\n\n function allocateRenderId(): number {\n nextNavigationRenderId += 1;\n return nextNavigationRenderId;\n }\n\n function hasBrowserRouterState(): boolean {\n return browserRouterStateRef !== null;\n }\n\n function isCurrentNavigation(navId: number): boolean {\n return navId === activeNavigationId;\n }\n\n function beginPendingBrowserRouterState(): PendingBrowserRouterState {\n const setter = getBrowserRouterStateSetter();\n\n if (activePendingBrowserRouterState && !activePendingBrowserRouterState.settled) {\n activePendingBrowserRouterState.settled = true;\n activePendingBrowserRouterState.resolve(getBrowserRouterState());\n }\n\n let resolvePending: ((state: AppRouterState) => void) | undefined;\n const promise = new Promise<AppRouterState>((resolve) => {\n resolvePending = resolve;\n });\n\n if (!resolvePending) {\n throw new Error(\"[vinext] Failed to initialize browser router promise\");\n }\n\n const pending: PendingBrowserRouterState = {\n promise,\n resolve: resolvePending,\n settled: false,\n };\n\n activePendingBrowserRouterState = pending;\n setter(promise);\n\n return pending;\n }\n\n function settlePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(getBrowserRouterState());\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function finalizeNavigation(\n navId: number,\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n settlePendingBrowserRouterState(pending);\n\n if (isCurrentNavigation(navId)) {\n clearPendingPathname(navId);\n }\n }\n\n function resolvePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n action: AppRouterAction,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(routerReducer(getBrowserRouterState(), action));\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function queuePrePaintNavigationEffect(renderId: number, effect: (() => void) | null): void {\n if (!effect) {\n return;\n }\n pendingNavigationPrePaintEffects.set(renderId, effect);\n }\n\n /**\n * Run all queued pre-paint effects for renderIds up to and including the\n * given renderId. When React supersedes a startTransition update (rapid\n * clicks on same-route links), the superseded NavigationCommitSignal never\n * mounts, so its pre-paint effect never fires. By draining all effects\n * <= the committed renderId here, the winning transition cleans up after\n * any superseded ones, keeping the counter balanced.\n *\n * Invariant: each superseded navigation gets a commitClientNavigationState()\n * to balance the activateNavigationSnapshot() from its renderNavigationPayload call.\n */\n function drainPrePaintEffects(upToRenderId: number): void {\n for (const [id, effect] of pendingNavigationPrePaintEffects) {\n if (id > upToRenderId) {\n continue;\n }\n\n pendingNavigationPrePaintEffects.delete(id);\n if (id === upToRenderId) {\n effect();\n } else {\n // Superseded navigations still need to balance the snapshot counter.\n commitClientNavigationStateImpl(undefined, { releaseSnapshot: true });\n }\n }\n }\n\n /**\n * Resolve all pending navigation commits with renderId <= the committed renderId.\n * Note: Map iteration handles concurrent deletion safely — entries are visited in\n * insertion order and deletion doesn't affect the iterator's view of remaining entries.\n * This pattern is also used in drainPrePaintEffects with the same semantics.\n */\n function resolveCommittedNavigations(renderId: number): void {\n for (const [pendingId, resolve] of pendingNavigationCommits) {\n if (pendingId > renderId) {\n continue;\n }\n\n pendingNavigationCommits.delete(pendingId);\n resolve();\n }\n }\n\n async function hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void> {\n if (!hasBrowserRouterState()) return;\n\n const currentState = getBrowserRouterState();\n const renderId = allocateRenderId();\n const pending = await createPendingNavigationCommit({\n currentState,\n nextElements,\n navigationSnapshot,\n renderId,\n type: \"replace\",\n });\n\n // createPendingNavigationCommit awaits the new RSC payload. While\n // suspended, the prior broken render can unmount BrowserRoot. Re-check\n // before dispatching so a racing unmount doesn't surface as an\n // initialized-setter error.\n if (!hasBrowserRouterState()) return;\n\n dispatchBrowserTree(\n pending.action.elements,\n navigationSnapshot,\n pending.action.renderId,\n \"replace\",\n pending.interceptionContext,\n pending.action.layoutFlags,\n pending.previousNextUrl,\n pending.routeId,\n pending.rootLayoutTreePath,\n null,\n false,\n );\n }\n\n function NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode {\n useLayoutEffect(() => {\n drainPrePaintEffects(renderId);\n\n const frame = requestAnimationFrame(() => {\n resolveCommittedNavigations(renderId);\n });\n\n return () => {\n cancelAnimationFrame(frame);\n // Resolve pending commits to prevent callers from hanging if React\n // unmounts this component without committing (e.g., error boundary).\n resolveCommittedNavigations(renderId);\n };\n }, [renderId]);\n\n return children;\n }\n\n function dispatchBrowserTree(\n elements: AppElements,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n renderId: number,\n actionType: \"navigate\" | \"replace\" | \"traverse\",\n interceptionContext: string | null,\n layoutFlags: LayoutFlags,\n previousNextUrl: string | null,\n routeId: string,\n rootLayoutTreePath: string | null,\n pendingRouterState: PendingBrowserRouterState | null,\n useTransitionMode: boolean,\n ): void {\n const setter = getBrowserRouterStateSetter();\n const action: AppRouterAction = {\n elements,\n interceptionContext,\n layoutFlags,\n navigationSnapshot,\n previousNextUrl,\n renderId,\n rootLayoutTreePath,\n routeId,\n type: actionType,\n };\n\n const applyAction = () => {\n if (pendingRouterState) {\n // The programmatic navigation is already running inside React.startTransition\n // (from router.push/replace/refresh), so resolving the deferred promise is\n // sufficient — no additional startTransition wrapper is needed below.\n resolvePendingBrowserRouterState(pendingRouterState, action);\n return;\n }\n\n setter(routerReducer(getBrowserRouterState(), action));\n };\n\n if (useTransitionMode) {\n startTransition(applyAction);\n } else {\n applyAction();\n }\n }\n\n async function renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHref: string;\n navId: number;\n useTransition?: boolean;\n }): Promise<void> {\n const renderId = allocateRenderId();\n let resolveCommitted: (() => void) | undefined;\n const committed = new Promise<void>((resolve) => {\n resolveCommitted = resolve;\n pendingNavigationCommits.set(renderId, resolve);\n });\n\n let snapshotActivated = false;\n try {\n const currentState = getBrowserRouterState();\n const pending = await createPendingNavigationCommit({\n currentState,\n nextElements: options.nextElements,\n navigationSnapshot: options.navigationSnapshot,\n previousNextUrl: options.previousNextUrl,\n renderId,\n type: options.actionType,\n });\n\n const disposition = resolvePendingNavigationCommitDisposition({\n activeNavigationId,\n currentRootLayoutTreePath: currentState.rootLayoutTreePath,\n nextRootLayoutTreePath: pending.rootLayoutTreePath,\n startedNavigationId: options.navId,\n });\n\n if (disposition === \"skip\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n resolveCommitted?.();\n return;\n }\n\n if (disposition === \"hard-navigate\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n window.location.assign(options.targetHref);\n return;\n }\n\n queuePrePaintNavigationEffect(\n renderId,\n options.createNavigationCommitEffect({\n href: options.targetHref,\n historyUpdateMode: options.historyUpdateMode,\n navId: options.navId,\n params: options.params,\n previousNextUrl: pending.previousNextUrl,\n }),\n );\n activateNavigationSnapshot();\n snapshotActivated = true;\n dispatchBrowserTree(\n pending.action.elements,\n options.navigationSnapshot,\n renderId,\n options.actionType,\n pending.interceptionContext,\n pending.action.layoutFlags,\n pending.previousNextUrl,\n pending.routeId,\n pending.rootLayoutTreePath,\n options.pendingRouterState,\n options.useTransition ?? true,\n );\n } catch (error) {\n pendingNavigationPrePaintEffects.delete(renderId);\n pendingNavigationCommits.delete(renderId);\n if (snapshotActivated) {\n commitClientNavigationStateImpl(options.navId);\n }\n settlePendingBrowserRouterState(options.pendingRouterState);\n resolveCommitted?.();\n throw error;\n }\n\n return committed;\n }\n\n async function commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n ): Promise<unknown> {\n const currentState = getBrowserRouterState();\n const startedNavigationId = activeNavigationId;\n // Known limitation: if a same-URL navigation fully commits while this\n // server action is awaiting resolveAndClassifyNavigationCommit(), the action\n // can still dispatch its older payload afterward. The old pre-2c code had\n // the same race, and Next.js has similar behavior. Tightening this would\n // need a stronger commit-version gate than activeNavigationId alone.\n const { disposition, pending } = await resolveAndClassifyNavigationCommit({\n activeNavigationId,\n currentState,\n navigationSnapshot,\n nextElements,\n renderId: allocateRenderId(),\n startedNavigationId,\n type: \"navigate\",\n });\n\n if (disposition === \"hard-navigate\") {\n window.location.assign(window.location.href);\n return undefined;\n }\n\n if (disposition === \"dispatch\") {\n dispatchBrowserTree(\n pending.action.elements,\n navigationSnapshot,\n pending.action.renderId,\n \"navigate\",\n pending.interceptionContext,\n pending.action.layoutFlags,\n pending.previousNextUrl,\n pending.routeId,\n pending.rootLayoutTreePath,\n null,\n false,\n );\n }\n\n // Same-URL server actions still return their action value even if the UI\n // update was skipped due to a superseding navigation. That preserves the\n // existing caller contract; a future Phase 2 router state model could make\n // skipped UI updates observable to the caller without conflating them here.\n if (returnValue) {\n if (!returnValue.ok) {\n throw returnValue.data;\n }\n return returnValue.data;\n }\n\n return undefined;\n }\n\n function attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void {\n setBrowserRouterState = setter;\n browserRouterStateRef = stateRef;\n markBrowserRouterStateReady();\n\n return () => {\n if (setBrowserRouterState === setter) {\n setBrowserRouterState = null;\n }\n if (browserRouterStateRef === stateRef) {\n browserRouterStateRef = null;\n browserRouterStateHasCommitted = false;\n }\n };\n }\n\n return {\n beginNavigation,\n hasBrowserRouterState,\n getBrowserRouterState,\n isCurrentNavigation,\n waitForBrowserRouterStateReady,\n attachBrowserRouterState,\n beginPendingBrowserRouterState,\n finalizeNavigation,\n renderNavigationPayload,\n commitSameUrlNavigatePayload,\n hmrReplaceTree,\n drainPrePaintEffects,\n NavigationCommitSignal,\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { AppElements, LayoutFlags } from "./app-elements.js";
2
1
  import { ClientNavigationRenderSnapshot } from "../shims/navigation.js";
2
+ import { AppElements, LayoutFlags } from "./app-elements.js";
3
3
 
4
4
  //#region src/server/app-browser-state.d.ts
5
5
  type HistoryStateRecord = {
@@ -1,3 +1,4 @@
1
+ import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
1
2
  import { isValidElement } from "react";
2
3
  //#region src/server/app-elements.ts
3
4
  const APP_INTERCEPTION_SEPARATOR = "\0";
@@ -7,11 +8,6 @@ const APP_ROUTE_KEY = "__route";
7
8
  const APP_ROOT_LAYOUT_KEY = "__rootLayout";
8
9
  const APP_UNMATCHED_SLOT_WIRE_VALUE = "__VINEXT_UNMATCHED_SLOT__";
9
10
  const UNMATCHED_SLOT = Symbol.for("vinext.unmatchedSlot");
10
- function normalizeMountedSlotsHeader(header) {
11
- if (!header) return null;
12
- const slotIds = Array.from(new Set(header.split(/\s+/).filter(Boolean))).sort();
13
- return slotIds.length > 0 ? slotIds.join(" ") : null;
14
- }
15
11
  function getMountedSlotIds(elements) {
16
12
  return Object.keys(elements).filter((key) => {
17
13
  const value = elements[key];
@@ -1 +1 @@
1
- {"version":3,"file":"app-elements.js","names":[],"sources":["../../src/server/app-elements.ts"],"sourcesContent":["import { isValidElement, type ReactNode } from \"react\";\n\nconst APP_INTERCEPTION_SEPARATOR = \"\\0\";\n\nexport const APP_INTERCEPTION_CONTEXT_KEY = \"__interceptionContext\";\nexport const APP_LAYOUT_FLAGS_KEY = \"__layoutFlags\";\nexport const APP_ROUTE_KEY = \"__route\";\nexport const APP_ROOT_LAYOUT_KEY = \"__rootLayout\";\nexport const APP_UNMATCHED_SLOT_WIRE_VALUE = \"__VINEXT_UNMATCHED_SLOT__\";\n\nexport const UNMATCHED_SLOT = Symbol.for(\"vinext.unmatchedSlot\");\n\nexport type AppElementValue = ReactNode | typeof UNMATCHED_SLOT | string | null;\ntype AppWireElementValue = ReactNode | string | null;\n\nexport type AppElements = Readonly<Record<string, AppElementValue>>;\nexport type AppWireElements = Readonly<Record<string, AppWireElementValue>>;\n\n/**\n * Per-layout static/dynamic flags. `\"s\"` = static (skippable on next nav);\n * `\"d\"` = dynamic (must always render).\n *\n * Lifecycle (partial — later PRs extend this):\n *\n * 1. PROBE — probeAppPageLayouts (server/app-page-execution.ts) returns\n * LayoutFlags for every layout in the route at render time.\n *\n * 2. ATTACH — withLayoutFlags (this file) writes `__layoutFlags` into the\n * outgoing App Router payload record.\n *\n * 3. WIRE — renderToReadableStream serializes the record as RSC row 0.\n *\n * 4. PARSE — readAppElementsMetadata (this file) extracts layoutFlags from\n * the wire payload on the client side.\n */\nexport type LayoutFlags = Readonly<Record<string, \"s\" | \"d\">>;\n\ntype AppElementsMetadata = {\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n routeId: string;\n rootLayoutTreePath: string | null;\n};\n\nfunction normalizeMountedSlotsHeader(header: string | null | undefined): string | null {\n if (!header) {\n return null;\n }\n\n const slotIds = Array.from(new Set(header.split(/\\s+/).filter(Boolean))).sort();\n\n return slotIds.length > 0 ? slotIds.join(\" \") : null;\n}\n\nexport function getMountedSlotIds(elements: AppElements): string[] {\n return Object.keys(elements)\n .filter((key) => {\n const value = elements[key];\n return (\n key.startsWith(\"slot:\") && value !== null && value !== undefined && value !== UNMATCHED_SLOT\n );\n })\n .sort();\n}\n\nexport function getMountedSlotIdsHeader(elements: AppElements): string | null {\n return normalizeMountedSlotsHeader(getMountedSlotIds(elements).join(\" \"));\n}\n\nfunction appendInterceptionContext(identity: string, interceptionContext: string | null): string {\n return interceptionContext === null\n ? identity\n : `${identity}${APP_INTERCEPTION_SEPARATOR}${interceptionContext}`;\n}\n\nexport function createAppPayloadRouteId(\n routePath: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(`route:${routePath}`, interceptionContext);\n}\n\nexport function createAppPayloadPageId(\n routePath: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(`page:${routePath}`, interceptionContext);\n}\n\nexport function createAppPayloadCacheKey(\n rscUrl: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(rscUrl, interceptionContext);\n}\n\nexport function resolveVisitedResponseInterceptionContext(\n requestInterceptionContext: string | null,\n payloadInterceptionContext: string | null,\n): string | null {\n return payloadInterceptionContext ?? requestInterceptionContext;\n}\n\nexport function normalizeAppElements(elements: AppWireElements): AppElements {\n let needsNormalization = false;\n for (const [key, value] of Object.entries(elements)) {\n if (key.startsWith(\"slot:\") && value === APP_UNMATCHED_SLOT_WIRE_VALUE) {\n needsNormalization = true;\n break;\n }\n }\n\n if (!needsNormalization) {\n return elements;\n }\n\n const normalized: Record<string, AppElementValue> = {};\n for (const [key, value] of Object.entries(elements)) {\n normalized[key] =\n key.startsWith(\"slot:\") && value === APP_UNMATCHED_SLOT_WIRE_VALUE ? UNMATCHED_SLOT : value;\n }\n\n return normalized;\n}\n\nfunction isLayoutFlagsRecord(value: unknown): value is LayoutFlags {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n for (const v of Object.values(value)) {\n if (v !== \"s\" && v !== \"d\") return false;\n }\n return true;\n}\n\nfunction parseLayoutFlags(value: unknown): LayoutFlags {\n if (isLayoutFlagsRecord(value)) return value;\n return {};\n}\n\n/**\n * Type predicate for a plain (non-null, non-array) record of app payload values.\n * Used to distinguish the App Router payload object from bare React elements at\n * the render boundary. Narrows to `Readonly<Record<string, unknown>>` because\n * the outgoing payload carries heterogeneous values (ReactNodes for the rendered\n * tree, plus metadata like `__layoutFlags` which is a plain object). Delegates\n * to React's canonical `isValidElement` so we don't depend on React's internal\n * `$$typeof` marker scheme.\n */\nexport function isAppElementsRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n if (typeof value !== \"object\" || value === null) return false;\n if (Array.isArray(value)) return false;\n if (isValidElement(value)) return false;\n return true;\n}\n\n/**\n * Pure: returns a new record with `__layoutFlags` attached. Owns the write\n * boundary for the layout flags key so the write side sits next to\n * `readAppElementsMetadata`.\n *\n * See `LayoutFlags` type docblock in this file for lifecycle.\n */\nexport function withLayoutFlags<T extends Record<string, unknown>>(\n elements: T,\n layoutFlags: LayoutFlags,\n): T & { [APP_LAYOUT_FLAGS_KEY]: LayoutFlags } {\n return { ...elements, [APP_LAYOUT_FLAGS_KEY]: layoutFlags };\n}\n\n/**\n * The outgoing wire payload shape. Includes ReactNode values for the\n * rendered tree plus metadata values like LayoutFlags attached under\n * known keys (e.g. __layoutFlags). Distinct from AppElements / AppWireElements\n * which only carry render-time values.\n */\nexport type AppOutgoingElements = Readonly<Record<string, ReactNode | LayoutFlags>>;\n\n/**\n * Pure: builds the outgoing payload for the wire. Non-record inputs (e.g. a\n * bare React element) are returned unchanged. Record inputs get a fresh copy\n * with `__layoutFlags` attached. Never mutates `input.element`.\n */\nexport function buildOutgoingAppPayload(input: {\n element: ReactNode | Readonly<Record<string, ReactNode>>;\n layoutFlags: LayoutFlags;\n}): ReactNode | AppOutgoingElements {\n if (!isAppElementsRecord(input.element)) {\n return input.element;\n }\n return withLayoutFlags(input.element, input.layoutFlags);\n}\n\n/**\n * Parses metadata from the wire payload. Accepts `Record<string, unknown>`\n * because the RSC payload carries heterogeneous values (React elements,\n * strings, and plain objects like layout flags) under the same record type.\n *\n * See `LayoutFlags` type docblock in this file for lifecycle.\n */\nexport function readAppElementsMetadata(\n elements: Readonly<Record<string, unknown>>,\n): AppElementsMetadata {\n const routeId = elements[APP_ROUTE_KEY];\n if (typeof routeId !== \"string\") {\n throw new Error(\"[vinext] Missing __route string in App Router payload\");\n }\n\n const interceptionContext = elements[APP_INTERCEPTION_CONTEXT_KEY];\n if (\n interceptionContext !== undefined &&\n interceptionContext !== null &&\n typeof interceptionContext !== \"string\"\n ) {\n throw new Error(\"[vinext] Invalid __interceptionContext in App Router payload\");\n }\n\n const rootLayoutTreePath = elements[APP_ROOT_LAYOUT_KEY];\n if (rootLayoutTreePath === undefined) {\n throw new Error(\"[vinext] Missing __rootLayout key in App Router payload\");\n }\n if (rootLayoutTreePath !== null && typeof rootLayoutTreePath !== \"string\") {\n throw new Error(\"[vinext] Invalid __rootLayout in App Router payload: expected string or null\");\n }\n\n const layoutFlags = parseLayoutFlags(elements[APP_LAYOUT_FLAGS_KEY]);\n\n return {\n interceptionContext: interceptionContext ?? null,\n layoutFlags,\n routeId,\n rootLayoutTreePath,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-elements.js","names":[],"sources":["../../src/server/app-elements.ts"],"sourcesContent":["import { isValidElement, type ReactNode } from \"react\";\nimport { normalizeMountedSlotsHeader } from \"./app-mounted-slots-header.js\";\n\nconst APP_INTERCEPTION_SEPARATOR = \"\\0\";\n\nexport const APP_INTERCEPTION_CONTEXT_KEY = \"__interceptionContext\";\nexport const APP_LAYOUT_FLAGS_KEY = \"__layoutFlags\";\nexport const APP_ROUTE_KEY = \"__route\";\nexport const APP_ROOT_LAYOUT_KEY = \"__rootLayout\";\nexport const APP_UNMATCHED_SLOT_WIRE_VALUE = \"__VINEXT_UNMATCHED_SLOT__\";\n\nexport const UNMATCHED_SLOT = Symbol.for(\"vinext.unmatchedSlot\");\n\nexport type AppElementValue = ReactNode | typeof UNMATCHED_SLOT | string | null;\ntype AppWireElementValue = ReactNode | string | null;\n\nexport type AppElements = Readonly<Record<string, AppElementValue>>;\nexport type AppWireElements = Readonly<Record<string, AppWireElementValue>>;\n\n/**\n * Per-layout static/dynamic flags. `\"s\"` = static (skippable on next nav);\n * `\"d\"` = dynamic (must always render).\n *\n * Lifecycle (partial — later PRs extend this):\n *\n * 1. PROBE — probeAppPageLayouts (server/app-page-execution.ts) returns\n * LayoutFlags for every layout in the route at render time.\n *\n * 2. ATTACH — withLayoutFlags (this file) writes `__layoutFlags` into the\n * outgoing App Router payload record.\n *\n * 3. WIRE — renderToReadableStream serializes the record as RSC row 0.\n *\n * 4. PARSE — readAppElementsMetadata (this file) extracts layoutFlags from\n * the wire payload on the client side.\n */\nexport type LayoutFlags = Readonly<Record<string, \"s\" | \"d\">>;\n\ntype AppElementsMetadata = {\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n routeId: string;\n rootLayoutTreePath: string | null;\n};\n\nexport function getMountedSlotIds(elements: AppElements): string[] {\n return Object.keys(elements)\n .filter((key) => {\n const value = elements[key];\n return (\n key.startsWith(\"slot:\") && value !== null && value !== undefined && value !== UNMATCHED_SLOT\n );\n })\n .sort();\n}\n\nexport function getMountedSlotIdsHeader(elements: AppElements): string | null {\n return normalizeMountedSlotsHeader(getMountedSlotIds(elements).join(\" \"));\n}\n\nfunction appendInterceptionContext(identity: string, interceptionContext: string | null): string {\n return interceptionContext === null\n ? identity\n : `${identity}${APP_INTERCEPTION_SEPARATOR}${interceptionContext}`;\n}\n\nexport function createAppPayloadRouteId(\n routePath: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(`route:${routePath}`, interceptionContext);\n}\n\nexport function createAppPayloadPageId(\n routePath: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(`page:${routePath}`, interceptionContext);\n}\n\nexport function createAppPayloadCacheKey(\n rscUrl: string,\n interceptionContext: string | null,\n): string {\n return appendInterceptionContext(rscUrl, interceptionContext);\n}\n\nexport function resolveVisitedResponseInterceptionContext(\n requestInterceptionContext: string | null,\n payloadInterceptionContext: string | null,\n): string | null {\n return payloadInterceptionContext ?? requestInterceptionContext;\n}\n\nexport function normalizeAppElements(elements: AppWireElements): AppElements {\n let needsNormalization = false;\n for (const [key, value] of Object.entries(elements)) {\n if (key.startsWith(\"slot:\") && value === APP_UNMATCHED_SLOT_WIRE_VALUE) {\n needsNormalization = true;\n break;\n }\n }\n\n if (!needsNormalization) {\n return elements;\n }\n\n const normalized: Record<string, AppElementValue> = {};\n for (const [key, value] of Object.entries(elements)) {\n normalized[key] =\n key.startsWith(\"slot:\") && value === APP_UNMATCHED_SLOT_WIRE_VALUE ? UNMATCHED_SLOT : value;\n }\n\n return normalized;\n}\n\nfunction isLayoutFlagsRecord(value: unknown): value is LayoutFlags {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n for (const v of Object.values(value)) {\n if (v !== \"s\" && v !== \"d\") return false;\n }\n return true;\n}\n\nfunction parseLayoutFlags(value: unknown): LayoutFlags {\n if (isLayoutFlagsRecord(value)) return value;\n return {};\n}\n\n/**\n * Type predicate for a plain (non-null, non-array) record of app payload values.\n * Used to distinguish the App Router payload object from bare React elements at\n * the render boundary. Narrows to `Readonly<Record<string, unknown>>` because\n * the outgoing payload carries heterogeneous values (ReactNodes for the rendered\n * tree, plus metadata like `__layoutFlags` which is a plain object). Delegates\n * to React's canonical `isValidElement` so we don't depend on React's internal\n * `$$typeof` marker scheme.\n */\nexport function isAppElementsRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n if (typeof value !== \"object\" || value === null) return false;\n if (Array.isArray(value)) return false;\n if (isValidElement(value)) return false;\n return true;\n}\n\n/**\n * Pure: returns a new record with `__layoutFlags` attached. Owns the write\n * boundary for the layout flags key so the write side sits next to\n * `readAppElementsMetadata`.\n *\n * See `LayoutFlags` type docblock in this file for lifecycle.\n */\nexport function withLayoutFlags<T extends Record<string, unknown>>(\n elements: T,\n layoutFlags: LayoutFlags,\n): T & { [APP_LAYOUT_FLAGS_KEY]: LayoutFlags } {\n return { ...elements, [APP_LAYOUT_FLAGS_KEY]: layoutFlags };\n}\n\n/**\n * The outgoing wire payload shape. Includes ReactNode values for the\n * rendered tree plus metadata values like LayoutFlags attached under\n * known keys (e.g. __layoutFlags). Distinct from AppElements / AppWireElements\n * which only carry render-time values.\n */\nexport type AppOutgoingElements = Readonly<Record<string, ReactNode | LayoutFlags>>;\n\n/**\n * Pure: builds the outgoing payload for the wire. Non-record inputs (e.g. a\n * bare React element) are returned unchanged. Record inputs get a fresh copy\n * with `__layoutFlags` attached. Never mutates `input.element`.\n */\nexport function buildOutgoingAppPayload(input: {\n element: ReactNode | Readonly<Record<string, ReactNode>>;\n layoutFlags: LayoutFlags;\n}): ReactNode | AppOutgoingElements {\n if (!isAppElementsRecord(input.element)) {\n return input.element;\n }\n return withLayoutFlags(input.element, input.layoutFlags);\n}\n\n/**\n * Parses metadata from the wire payload. Accepts `Record<string, unknown>`\n * because the RSC payload carries heterogeneous values (React elements,\n * strings, and plain objects like layout flags) under the same record type.\n *\n * See `LayoutFlags` type docblock in this file for lifecycle.\n */\nexport function readAppElementsMetadata(\n elements: Readonly<Record<string, unknown>>,\n): AppElementsMetadata {\n const routeId = elements[APP_ROUTE_KEY];\n if (typeof routeId !== \"string\") {\n throw new Error(\"[vinext] Missing __route string in App Router payload\");\n }\n\n const interceptionContext = elements[APP_INTERCEPTION_CONTEXT_KEY];\n if (\n interceptionContext !== undefined &&\n interceptionContext !== null &&\n typeof interceptionContext !== \"string\"\n ) {\n throw new Error(\"[vinext] Invalid __interceptionContext in App Router payload\");\n }\n\n const rootLayoutTreePath = elements[APP_ROOT_LAYOUT_KEY];\n if (rootLayoutTreePath === undefined) {\n throw new Error(\"[vinext] Missing __rootLayout key in App Router payload\");\n }\n if (rootLayoutTreePath !== null && typeof rootLayoutTreePath !== \"string\") {\n throw new Error(\"[vinext] Invalid __rootLayout in App Router payload: expected string or null\");\n }\n\n const layoutFlags = parseLayoutFlags(elements[APP_LAYOUT_FLAGS_KEY]);\n\n return {\n interceptionContext: interceptionContext ?? null,\n layoutFlags,\n routeId,\n rootLayoutTreePath,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { MetadataFileRoute } from "./metadata-routes.js";
2
+ import { AppElements } from "./app-elements.js";
3
+ import { AppPageParams } from "./app-page-boundary.js";
4
+ import { AppPageFontPreload } from "./app-page-execution.js";
5
+ import { AppPageMiddlewareContext } from "./app-page-response.js";
6
+ import { AppPageSsrHandler } from "./app-page-stream.js";
7
+ import { AppPageBoundaryRoute } from "./app-page-boundary-render.js";
8
+ import * as _$react from "react";
9
+ import { ReactNode } from "react";
10
+
11
+ //#region src/server/app-fallback-renderer.d.ts
12
+ type AppPageComponent = _$react.ComponentType<any>;
13
+ type AppPageModule = Record<string, unknown> & {
14
+ default?: AppPageComponent | null | undefined;
15
+ };
16
+ type AppPageBoundaryOnError = (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;
17
+ type AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {
18
+ rootForbiddenModule?: TModule | null;
19
+ rootLayouts: readonly (TModule | null | undefined)[];
20
+ rootNotFoundModule?: TModule | null;
21
+ rootUnauthorizedModule?: TModule | null;
22
+ };
23
+ type AppFallbackRendererFontProviders = {
24
+ buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;
25
+ getFontLinks: () => string[];
26
+ getFontPreloads: () => AppPageFontPreload[];
27
+ getFontStyles: () => string[];
28
+ };
29
+ type AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {
30
+ clearRequestContext: () => void;
31
+ createRscOnErrorHandler: (request: Request, pathname: string, routePath: string) => AppPageBoundaryOnError;
32
+ fontProviders: AppFallbackRendererFontProviders;
33
+ getNavigationContext: () => unknown;
34
+ globalErrorModule?: TModule | null;
35
+ makeThenableParams: (params: AppPageParams) => unknown;
36
+ metadataRoutes: MetadataFileRoute[];
37
+ resolveChildSegments: (routeSegments: readonly string[], treePosition: number, params: AppPageParams) => string[];
38
+ rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;
39
+ rscRenderer: (element: ReactNode | AppElements, options: {
40
+ onError: AppPageBoundaryOnError;
41
+ }) => ReadableStream<Uint8Array>;
42
+ sanitizer: (error: Error) => Error;
43
+ ssrLoader: () => Promise<AppPageSsrHandler>;
44
+ };
45
+ type AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {
46
+ renderErrorBoundary: (route: AppPageBoundaryRoute<TModule> | null, error: unknown, isRscRequest: boolean, request: Request, matchedParams: AppPageParams | undefined, scriptNonce: string | undefined, middlewareContext: AppPageMiddlewareContext) => Promise<Response | null>;
47
+ renderHttpAccessFallback: (route: AppPageBoundaryRoute<TModule> | null, statusCode: number, isRscRequest: boolean, request: Request, opts: {
48
+ boundaryComponent?: AppPageComponent | null;
49
+ layouts?: readonly (TModule | null | undefined)[] | null;
50
+ matchedParams?: AppPageParams;
51
+ }, scriptNonce: string | undefined, middlewareContext: AppPageMiddlewareContext) => Promise<Response | null>;
52
+ renderNotFound: (route: AppPageBoundaryRoute<TModule> | null, isRscRequest: boolean, request: Request, matchedParams: AppPageParams | undefined, scriptNonce: string | undefined, middlewareContext: AppPageMiddlewareContext) => Promise<Response | null>;
53
+ };
54
+ declare function createAppFallbackRenderer<TModule extends AppPageModule>(options: AppFallbackRendererOptions<TModule>): AppFallbackRenderer<TModule>;
55
+ //#endregion
56
+ export { createAppFallbackRenderer };
57
+ //# sourceMappingURL=app-fallback-renderer.d.ts.map
@@ -0,0 +1,79 @@
1
+ import { renderAppPageErrorBoundary, renderAppPageHttpAccessFallback } from "./app-page-boundary-render.js";
2
+ //#region src/server/app-fallback-renderer.ts
3
+ const EMPTY_MW_CTX = {
4
+ headers: null,
5
+ status: null
6
+ };
7
+ function createAppFallbackRenderer(options) {
8
+ const { clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader } = options;
9
+ const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } = rootBoundaries;
10
+ return {
11
+ renderHttpAccessFallback(route, statusCode, isRscRequest, request, opts, scriptNonce, middlewareContext) {
12
+ return renderAppPageHttpAccessFallback({
13
+ boundaryComponent: opts?.boundaryComponent ?? null,
14
+ buildFontLinkHeader: fontProviders.buildFontLinkHeader,
15
+ clearRequestContext,
16
+ createRscOnErrorHandler(pathname, routePath) {
17
+ return buildRscOnErrorHandler(request, pathname, routePath);
18
+ },
19
+ getFontLinks: fontProviders.getFontLinks,
20
+ getFontPreloads: fontProviders.getFontPreloads,
21
+ getFontStyles: fontProviders.getFontStyles,
22
+ getNavigationContext,
23
+ globalErrorModule,
24
+ isRscRequest,
25
+ layoutModules: opts?.layouts ?? null,
26
+ loadSsrHandler: ssrLoader,
27
+ makeThenableParams,
28
+ matchedParams: opts?.matchedParams ?? route?.params ?? {},
29
+ middlewareContext: middlewareContext ?? EMPTY_MW_CTX,
30
+ metadataRoutes,
31
+ requestUrl: request.url,
32
+ resolveChildSegments,
33
+ rootForbiddenModule,
34
+ rootLayouts,
35
+ rootNotFoundModule,
36
+ rootUnauthorizedModule,
37
+ route,
38
+ renderToReadableStream: rscRenderer,
39
+ scriptNonce,
40
+ statusCode
41
+ });
42
+ },
43
+ renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {
44
+ return this.renderHttpAccessFallback(route, 404, isRscRequest, request, { matchedParams }, scriptNonce, middlewareContext);
45
+ },
46
+ renderErrorBoundary(route, error, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {
47
+ return renderAppPageErrorBoundary({
48
+ buildFontLinkHeader: fontProviders.buildFontLinkHeader,
49
+ clearRequestContext,
50
+ createRscOnErrorHandler(pathname, routePath) {
51
+ return buildRscOnErrorHandler(request, pathname, routePath);
52
+ },
53
+ error,
54
+ getFontLinks: fontProviders.getFontLinks,
55
+ getFontPreloads: fontProviders.getFontPreloads,
56
+ getFontStyles: fontProviders.getFontStyles,
57
+ getNavigationContext,
58
+ globalErrorModule,
59
+ isRscRequest,
60
+ loadSsrHandler: ssrLoader,
61
+ makeThenableParams,
62
+ matchedParams: matchedParams ?? route?.params ?? {},
63
+ middlewareContext: middlewareContext ?? EMPTY_MW_CTX,
64
+ metadataRoutes,
65
+ requestUrl: request.url,
66
+ resolveChildSegments,
67
+ rootLayouts,
68
+ route,
69
+ renderToReadableStream: rscRenderer,
70
+ sanitizeErrorForClient: sanitizer,
71
+ scriptNonce
72
+ });
73
+ }
74
+ };
75
+ }
76
+ //#endregion
77
+ export { createAppFallbackRenderer };
78
+
79
+ //# sourceMappingURL=app-fallback-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-fallback-renderer.js","names":[],"sources":["../../src/server/app-fallback-renderer.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\nimport {\n renderAppPageErrorBoundary,\n renderAppPageHttpAccessFallback,\n type AppPageBoundaryRoute,\n} from \"./app-page-boundary-render.js\";\nimport type { AppPageFontPreload } from \"./app-page-execution.js\";\nimport type { AppPageMiddlewareContext } from \"./app-page-response.js\";\nimport type { AppPageSsrHandler } from \"./app-page-stream.js\";\nimport type { MetadataFileRoute } from \"./metadata-routes.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype AppPageComponent = import(\"react\").ComponentType<any>;\ntype AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\ntype AppPageBoundaryOnError = (\n error: unknown,\n requestInfo: unknown,\n errorContext: unknown,\n) => unknown;\n\ntype AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {\n rootForbiddenModule?: TModule | null;\n rootLayouts: readonly (TModule | null | undefined)[];\n rootNotFoundModule?: TModule | null;\n rootUnauthorizedModule?: TModule | null;\n};\n\ntype AppFallbackRendererFontProviders = {\n buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;\n getFontLinks: () => string[];\n getFontPreloads: () => AppPageFontPreload[];\n getFontStyles: () => string[];\n};\n\ntype AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {\n clearRequestContext: () => void;\n createRscOnErrorHandler: (\n request: Request,\n pathname: string,\n routePath: string,\n ) => AppPageBoundaryOnError;\n fontProviders: AppFallbackRendererFontProviders;\n getNavigationContext: () => unknown;\n globalErrorModule?: TModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n metadataRoutes: MetadataFileRoute[];\n resolveChildSegments: (\n routeSegments: readonly string[],\n treePosition: number,\n params: AppPageParams,\n ) => string[];\n rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;\n rscRenderer: (\n element: ReactNode | AppElements,\n options: { onError: AppPageBoundaryOnError },\n ) => ReadableStream<Uint8Array>;\n sanitizer: (error: Error) => Error;\n ssrLoader: () => Promise<AppPageSsrHandler>;\n};\n\ntype AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {\n renderErrorBoundary: (\n route: AppPageBoundaryRoute<TModule> | null,\n error: unknown,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderHttpAccessFallback: (\n route: AppPageBoundaryRoute<TModule> | null,\n statusCode: number,\n isRscRequest: boolean,\n request: Request,\n opts: {\n boundaryComponent?: AppPageComponent | null;\n layouts?: readonly (TModule | null | undefined)[] | null;\n matchedParams?: AppPageParams;\n },\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderNotFound: (\n route: AppPageBoundaryRoute<TModule> | null,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n};\n\nconst EMPTY_MW_CTX: AppPageMiddlewareContext = { headers: null, status: null };\n\nexport function createAppFallbackRenderer<TModule extends AppPageModule>(\n options: AppFallbackRendererOptions<TModule>,\n): AppFallbackRenderer<TModule> {\n const {\n clearRequestContext,\n createRscOnErrorHandler: buildRscOnErrorHandler,\n fontProviders,\n getNavigationContext,\n globalErrorModule,\n makeThenableParams,\n metadataRoutes,\n resolveChildSegments,\n rootBoundaries,\n rscRenderer,\n sanitizer,\n ssrLoader,\n } = options;\n\n const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } =\n rootBoundaries;\n\n return {\n renderHttpAccessFallback(\n route,\n statusCode,\n isRscRequest,\n request,\n opts,\n scriptNonce,\n middlewareContext,\n ) {\n return renderAppPageHttpAccessFallback({\n boundaryComponent: opts?.boundaryComponent ?? null,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule,\n isRscRequest,\n layoutModules: opts?.layouts ?? null,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule,\n rootLayouts,\n rootNotFoundModule,\n rootUnauthorizedModule,\n route,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n statusCode,\n });\n },\n\n renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {\n return this.renderHttpAccessFallback(\n route,\n 404,\n isRscRequest,\n request,\n { matchedParams },\n scriptNonce,\n middlewareContext,\n );\n },\n\n renderErrorBoundary(\n route,\n error,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n ) {\n return renderAppPageErrorBoundary({\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n error,\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule,\n isRscRequest,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootLayouts,\n route,\n renderToReadableStream: rscRenderer,\n sanitizeErrorForClient: sanitizer,\n scriptNonce,\n });\n },\n };\n}\n"],"mappings":";;AAiGA,MAAM,eAAyC;CAAE,SAAS;CAAM,QAAQ;CAAM;AAE9E,SAAgB,0BACd,SAC8B;CAC9B,MAAM,EACJ,qBACA,yBAAyB,wBACzB,eACA,sBACA,mBACA,oBACA,gBACA,sBACA,gBACA,aACA,WACA,cACE;CAEJ,MAAM,EAAE,qBAAqB,aAAa,oBAAoB,2BAC5D;AAEF,QAAO;EACL,yBACE,OACA,YACA,cACA,SACA,MACA,aACA,mBACA;AACA,UAAO,gCAAgC;IACrC,mBAAmB,MAAM,qBAAqB;IAC9C,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;AAC3C,YAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA;IACA;IACA,eAAe,MAAM,WAAW;IAChC,gBAAgB;IAChB;IACA,eAAe,MAAM,iBAAiB,OAAO,UAAU,EAAE;IACzD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA;IACA;IACA;IACA,wBAAwB;IACxB;IACA;IACD,CAAC;;EAGJ,eAAe,OAAO,cAAc,SAAS,eAAe,aAAa,mBAAmB;AAC1F,UAAO,KAAK,yBACV,OACA,KACA,cACA,SACA,EAAE,eAAe,EACjB,aACA,kBACD;;EAGH,oBACE,OACA,OACA,cACA,SACA,eACA,aACA,mBACA;AACA,UAAO,2BAA2B;IAChC,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;AAC3C,YAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D;IACA,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA;IACA;IACA,gBAAgB;IAChB;IACA,eAAe,iBAAiB,OAAO,UAAU,EAAE;IACnD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,wBAAwB;IACxB,wBAAwB;IACxB;IACD,CAAC;;EAEL"}
@@ -0,0 +1,7 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ //#region src/server/app-hook-warning-suppression.d.ts
4
+ declare const suppressHookWarningAls: AsyncLocalStorage<boolean>;
5
+ //#endregion
6
+ export { suppressHookWarningAls };
7
+ //# sourceMappingURL=app-hook-warning-suppression.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/server/app-hook-warning-suppression.ts
3
+ const suppressHookWarningAls = new AsyncLocalStorage();
4
+ const _origConsoleError = console.error;
5
+ console.error = (...args) => {
6
+ if (suppressHookWarningAls.getStore() === true && typeof args[0] === "string" && args[0].includes("Invalid hook call")) return;
7
+ _origConsoleError.apply(console, args);
8
+ };
9
+ //#endregion
10
+ export { suppressHookWarningAls };
11
+
12
+ //# sourceMappingURL=app-hook-warning-suppression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-hook-warning-suppression.js","names":[],"sources":["../../src/server/app-hook-warning-suppression.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\nexport const suppressHookWarningAls = new AsyncLocalStorage<boolean>();\n\nconst _origConsoleError = console.error;\nconsole.error = (...args: unknown[]) => {\n if (\n suppressHookWarningAls.getStore() === true &&\n typeof args[0] === \"string\" &&\n args[0].includes(\"Invalid hook call\")\n )\n return;\n _origConsoleError.apply(console, args);\n};\n"],"mappings":";;AAEA,MAAa,yBAAyB,IAAI,mBAA4B;AAEtE,MAAM,oBAAoB,QAAQ;AAClC,QAAQ,SAAS,GAAG,SAAoB;AACtC,KACE,uBAAuB,UAAU,KAAK,QACtC,OAAO,KAAK,OAAO,YACnB,KAAK,GAAG,SAAS,oBAAoB,CAErC;AACF,mBAAkB,MAAM,SAAS,KAAK"}
@@ -0,0 +1,17 @@
1
+ //#region src/server/app-mounted-slots-header.d.ts
2
+ /**
3
+ * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.
4
+ *
5
+ * The browser sends mounted slot ids as a space-separated list in the order slots were
6
+ * rendered, which changes across navigations. This normalizes to a canonical form
7
+ * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.
8
+ *
9
+ * Consumed by:
10
+ * - app-rsc-request-normalization (request lifecycle, reads incoming header)
11
+ * - app-elements (outgoing x-vinext-mounted-slots construction)
12
+ * - isr-cache (RSC cache key generation)
13
+ */
14
+ declare function normalizeMountedSlotsHeader(raw: string | null | undefined): string | null;
15
+ //#endregion
16
+ export { normalizeMountedSlotsHeader };
17
+ //# sourceMappingURL=app-mounted-slots-header.d.ts.map
@@ -0,0 +1,21 @@
1
+ //#region src/server/app-mounted-slots-header.ts
2
+ /**
3
+ * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.
4
+ *
5
+ * The browser sends mounted slot ids as a space-separated list in the order slots were
6
+ * rendered, which changes across navigations. This normalizes to a canonical form
7
+ * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.
8
+ *
9
+ * Consumed by:
10
+ * - app-rsc-request-normalization (request lifecycle, reads incoming header)
11
+ * - app-elements (outgoing x-vinext-mounted-slots construction)
12
+ * - isr-cache (RSC cache key generation)
13
+ */
14
+ function normalizeMountedSlotsHeader(raw) {
15
+ if (!raw) return null;
16
+ return Array.from(new Set(raw.split(/\s+/).filter(Boolean))).sort().join(" ") || null;
17
+ }
18
+ //#endregion
19
+ export { normalizeMountedSlotsHeader };
20
+
21
+ //# sourceMappingURL=app-mounted-slots-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-mounted-slots-header.js","names":[],"sources":["../../src/server/app-mounted-slots-header.ts"],"sourcesContent":["/**\n * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.\n *\n * The browser sends mounted slot ids as a space-separated list in the order slots were\n * rendered, which changes across navigations. This normalizes to a canonical form\n * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.\n *\n * Consumed by:\n * - app-rsc-request-normalization (request lifecycle, reads incoming header)\n * - app-elements (outgoing x-vinext-mounted-slots construction)\n * - isr-cache (RSC cache key generation)\n */\nexport function normalizeMountedSlotsHeader(raw: string | null | undefined): string | null {\n if (!raw) return null;\n const normalized = Array.from(new Set(raw.split(/\\s+/).filter(Boolean)))\n .sort()\n .join(\" \");\n return normalized || null;\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,4BAA4B,KAA+C;AACzF,KAAI,CAAC,IAAK,QAAO;AAIjB,QAHmB,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC,CACrE,MAAM,CACN,KAAK,IAAI,IACS"}