@thomas.f/prnext 0.1.0-alpha.1

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 (211) hide show
  1. package/README.md +54 -0
  2. package/build/action-exports.mjs +107 -0
  3. package/build/actions.mjs +214 -0
  4. package/build/app-config.mjs +138 -0
  5. package/build/app-routing.mjs +119 -0
  6. package/build/app-static.mjs +158 -0
  7. package/build/app.mjs +536 -0
  8. package/build/cache-components.mjs +91 -0
  9. package/build/cache-handlers.mjs +54 -0
  10. package/build/check.mjs +40 -0
  11. package/build/compiler.mjs +222 -0
  12. package/build/config.mjs +193 -0
  13. package/build/css-modules.mjs +114 -0
  14. package/build/custom-routes.mjs +363 -0
  15. package/build/dev-worker.mjs +18 -0
  16. package/build/dev.mjs +76 -0
  17. package/build/directory-cache.mjs +29 -0
  18. package/build/dynamic.mjs +125 -0
  19. package/build/edge.mjs +141 -0
  20. package/build/font-data.LICENSE.txt +25 -0
  21. package/build/font-data.json +1 -0
  22. package/build/font-files.mjs +221 -0
  23. package/build/fonts.mjs +131 -0
  24. package/build/framework-imports.mjs +17 -0
  25. package/build/i18n.mjs +39 -0
  26. package/build/images-config.mjs +62 -0
  27. package/build/images.mjs +86 -0
  28. package/build/incremental.mjs +76 -0
  29. package/build/index.mjs +356 -0
  30. package/build/loader-cache.mjs +69 -0
  31. package/build/metadata-images.mjs +72 -0
  32. package/build/metadata-routes.mjs +77 -0
  33. package/build/middleware.mjs +231 -0
  34. package/build/migrate.mjs +289 -0
  35. package/build/module-resolution.mjs +78 -0
  36. package/build/native-discovery.mjs +134 -0
  37. package/build/native-packages.mjs +105 -0
  38. package/build/npm-packages.mjs +110 -0
  39. package/build/og.mjs +14 -0
  40. package/build/page-extensions.mjs +12 -0
  41. package/build/passthrough-loader.cjs +4 -0
  42. package/build/precompress.mjs +122 -0
  43. package/build/scan.mjs +178 -0
  44. package/build/script-workers.mjs +25 -0
  45. package/build/server-actions-config.mjs +27 -0
  46. package/build/source-maps.mjs +34 -0
  47. package/build/standalone-config.mjs +19 -0
  48. package/build/standalone-runner.mjs +21 -0
  49. package/build/standalone-worker.mjs +5 -0
  50. package/build/standalone.mjs +240 -0
  51. package/build/static-export.mjs +93 -0
  52. package/build/styles.mjs +174 -0
  53. package/build/transform-cache.mjs +50 -0
  54. package/build/transform.mjs +155 -0
  55. package/build/turbopack-conditions.mjs +59 -0
  56. package/build/webpack-bridge.cjs +3 -0
  57. package/build/webpack-graph.mjs +256 -0
  58. package/build/webpack-loader.cjs +9 -0
  59. package/cli.mjs +210 -0
  60. package/compat/app-client-page.cjs +32 -0
  61. package/compat/app-context.cjs +40 -0
  62. package/compat/app-context.d.cts +20 -0
  63. package/compat/app-dynamic.cjs +34 -0
  64. package/compat/app-error-boundary.cjs +53 -0
  65. package/compat/app-global-error-boundary.cjs +34 -0
  66. package/compat/app-layout-context.cjs +27 -0
  67. package/compat/app-navigation-boundary.cjs +47 -0
  68. package/compat/app.cjs +33 -0
  69. package/compat/app.d.cts +9 -0
  70. package/compat/cache-handlers.cjs +223 -0
  71. package/compat/cache-life.cjs +34 -0
  72. package/compat/cache.cjs +99 -0
  73. package/compat/cache.d.cts +36 -0
  74. package/compat/compat/router.d.cts +2 -0
  75. package/compat/compat-router.cjs +7 -0
  76. package/compat/constants.cjs +8 -0
  77. package/compat/constants.d.cts +7 -0
  78. package/compat/cookies.cjs +129 -0
  79. package/compat/data-cache.cjs +389 -0
  80. package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
  81. package/compat/dist/shared/lib/router-context.d.cts +1 -0
  82. package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
  83. package/compat/document.cjs +73 -0
  84. package/compat/document.d.cts +41 -0
  85. package/compat/draft.cjs +28 -0
  86. package/compat/dynamic-bailout.cjs +13 -0
  87. package/compat/dynamic.cjs +119 -0
  88. package/compat/dynamic.d.cts +32 -0
  89. package/compat/error.cjs +36 -0
  90. package/compat/error.d.cts +15 -0
  91. package/compat/fetch.d.cts +10 -0
  92. package/compat/font/google.d.cts +1 -0
  93. package/compat/font/local.d.cts +2 -0
  94. package/compat/font-google.cjs +2 -0
  95. package/compat/font-google.d.cts +1944 -0
  96. package/compat/font-local.cjs +5 -0
  97. package/compat/font-local.d.cts +3 -0
  98. package/compat/font-types.d.cts +25 -0
  99. package/compat/head.cjs +180 -0
  100. package/compat/head.d.cts +3 -0
  101. package/compat/headers.cjs +89 -0
  102. package/compat/headers.d.cts +26 -0
  103. package/compat/image-config.cjs +3 -0
  104. package/compat/image-imports.d.ts +12 -0
  105. package/compat/image-shared.cjs +62 -0
  106. package/compat/image.cjs +64 -0
  107. package/compat/image.d.cts +35 -0
  108. package/compat/incremental-cache.cjs +186 -0
  109. package/compat/index.d.ts +159 -0
  110. package/compat/instant-samples.cjs +39 -0
  111. package/compat/instant.cjs +13 -0
  112. package/compat/link.cjs +68 -0
  113. package/compat/link.d.cts +17 -0
  114. package/compat/locale.cjs +30 -0
  115. package/compat/metadata.d.cts +77 -0
  116. package/compat/navigation-controls.cjs +27 -0
  117. package/compat/navigation-server.cjs +25 -0
  118. package/compat/navigation.cjs +52 -0
  119. package/compat/navigation.d.cts +28 -0
  120. package/compat/next-app-router-context.cjs +6 -0
  121. package/compat/next-router-context.cjs +3 -0
  122. package/compat/og.cjs +32 -0
  123. package/compat/og.d.cts +1 -0
  124. package/compat/paths.cjs +33 -0
  125. package/compat/preview.cjs +41 -0
  126. package/compat/rewrite.cjs +41 -0
  127. package/compat/route-request.cjs +64 -0
  128. package/compat/router.cjs +137 -0
  129. package/compat/router.d.cts +58 -0
  130. package/compat/script-context.cjs +5 -0
  131. package/compat/script-loader.cjs +135 -0
  132. package/compat/script.cjs +69 -0
  133. package/compat/script.d.cts +17 -0
  134. package/compat/server-primitives.cjs +109 -0
  135. package/compat/server.cjs +13 -0
  136. package/compat/server.d.cts +37 -0
  137. package/compat/static-generation.cjs +108 -0
  138. package/compat/use-cache.cjs +129 -0
  139. package/index.mjs +1 -0
  140. package/native/platforms.json +6 -0
  141. package/native/resolve.mjs +81 -0
  142. package/package.json +127 -0
  143. package/release/verify.mjs +59 -0
  144. package/runtime/action-client.mjs +19 -0
  145. package/runtime/action-crypto.mjs +138 -0
  146. package/runtime/action-reference.mjs +2 -0
  147. package/runtime/action-server.mjs +161 -0
  148. package/runtime/action-ssr.mjs +5 -0
  149. package/runtime/api.mjs +326 -0
  150. package/runtime/app-client.mjs +539 -0
  151. package/runtime/app-content.mjs +8 -0
  152. package/runtime/app-errors.mjs +12 -0
  153. package/runtime/app-instant.mjs +30 -0
  154. package/runtime/app-metadata.mjs +410 -0
  155. package/runtime/app-partial-model.mjs +228 -0
  156. package/runtime/app-partial.mjs +130 -0
  157. package/runtime/app-prefetch.mjs +34 -0
  158. package/runtime/app-recovery.mjs +87 -0
  159. package/runtime/app-render.mjs +544 -0
  160. package/runtime/app-routing-access.mjs +143 -0
  161. package/runtime/app-routing-context.mjs +35 -0
  162. package/runtime/app-routing-state.mjs +61 -0
  163. package/runtime/app-routing.mjs +323 -0
  164. package/runtime/app-segments.mjs +22 -0
  165. package/runtime/app-static-params.mjs +120 -0
  166. package/runtime/app-static.mjs +86 -0
  167. package/runtime/app-stream-channel.mjs +92 -0
  168. package/runtime/app-stream-html.mjs +279 -0
  169. package/runtime/build-directory.mjs +48 -0
  170. package/runtime/cache-arguments.mjs +33 -0
  171. package/runtime/cache-components-rsc.mjs +64 -0
  172. package/runtime/cache-request.mjs +23 -0
  173. package/runtime/client-navigation.mjs +40 -0
  174. package/runtime/dev-client.mjs +129 -0
  175. package/runtime/dev-file-changes.mjs +110 -0
  176. package/runtime/document.mjs +96 -0
  177. package/runtime/edge.mjs +105 -0
  178. package/runtime/env.mjs +68 -0
  179. package/runtime/fetch-cache.mjs +379 -0
  180. package/runtime/font-preload.mjs +4 -0
  181. package/runtime/http.mjs +190 -0
  182. package/runtime/incremental-cache.mjs +83 -0
  183. package/runtime/metadata-route.mjs +83 -0
  184. package/runtime/middleware.mjs +186 -0
  185. package/runtime/module-loader.mjs +21 -0
  186. package/runtime/navigation.mjs +16 -0
  187. package/runtime/og-loader.mjs +17 -0
  188. package/runtime/pages-client.mjs +197 -0
  189. package/runtime/pages-error-props.mjs +6 -0
  190. package/runtime/pages-errors.mjs +85 -0
  191. package/runtime/pages-initial-props.mjs +43 -0
  192. package/runtime/pages-navigation.mjs +565 -0
  193. package/runtime/pages-paths.mjs +80 -0
  194. package/runtime/pages-revalidate.mjs +33 -0
  195. package/runtime/partial-artifact-cache.mjs +37 -0
  196. package/runtime/partial-template.mjs +174 -0
  197. package/runtime/profile.mjs +9 -0
  198. package/runtime/profiles.json +47 -0
  199. package/runtime/render.mjs +304 -0
  200. package/runtime/request-transport.mjs +41 -0
  201. package/runtime/request-work.mjs +7 -0
  202. package/runtime/route-static.mjs +117 -0
  203. package/runtime/rsc-worker.mjs +426 -0
  204. package/runtime/script-html.mjs +61 -0
  205. package/runtime/stream-select.mjs +25 -0
  206. package/runtime/stream-utils.mjs +127 -0
  207. package/runtime/transport.mjs +108 -0
  208. package/runtime/watch-project.mjs +130 -0
  209. package/runtime/worker-artifacts.mjs +22 -0
  210. package/runtime/worker-metadata.mjs +31 -0
  211. package/runtime/worker.mjs +197 -0
@@ -0,0 +1,143 @@
1
+ import path from 'node:path';
2
+ import { addBasePath, removeBasePath } from '../compat/paths.cjs';
3
+ import { readRouterState } from './app-routing-state.mjs';
4
+ import { nativeFetch } from '../compat/data-cache.cjs';
5
+
6
+ async function nativeResolution(resolver, url, headers, signal) {
7
+ const response = await nativeFetch(resolver.url, { method: 'POST', redirect: 'error', signal,
8
+ headers: { authorization: `Bearer ${resolver.token}`, 'content-type': 'application/json' },
9
+ body: JSON.stringify({ url: url.pathname + url.search, headers: Object.fromEntries(headers) }) });
10
+ if (!response.ok) { await response.body?.cancel(); return null; }
11
+ const reader = response.body.getReader(), chunks = [];
12
+ let size = 0;
13
+ try {
14
+ for (;;) {
15
+ const { done, value } = await reader.read();
16
+ if (done) break;
17
+ size += value.byteLength;
18
+ if (size > 192 * 1024) { await reader.cancel(); return null; }
19
+ chunks.push(Buffer.from(value));
20
+ }
21
+ } finally { reader.releaseLock(); }
22
+ const result = JSON.parse(Buffer.concat(chunks, size));
23
+ return result.status >= 200 && result.status < 300 && result.resolved ? result : null;
24
+ }
25
+
26
+ function matches(rule, url, headers) {
27
+ if (!new RegExp(rule.regex, 'i').test(url.pathname)) return false;
28
+ const cookies = new Map();
29
+ for (const part of (headers.get('cookie') || '').split(';')) {
30
+ const equals = part.indexOf('=');
31
+ if (equals < 0) continue;
32
+ const key = part.slice(0, equals).trim();
33
+ let value = part.slice(equals + 1).trim().replace(/^"|"$/g, '');
34
+ try { value = decodeURIComponent(value); } catch { /* Keep malformed literal escapes. */ }
35
+ if (!cookies.has(key)) cookies.set(key, value);
36
+ }
37
+ function condition(item) {
38
+ const query = item.type === 'query' ? url.searchParams.getAll(item.key) : [];
39
+ const value = item.type === 'host' ? (headers.get('host') || url.host).split(':')[0].toLowerCase()
40
+ : item.type === 'query' ? query.at(-1) : item.type === 'cookie' ? cookies.get(item.key) : headers.get(item.key);
41
+ return value !== null && value !== undefined && (value !== '' || query.length > 1) && (!item.regex || new RegExp(item.regex).test(value));
42
+ }
43
+ return (rule.has || []).every(condition) && !(rule.missing || []).some(condition);
44
+ }
45
+
46
+ /** Recheck saved route access under current credentials before any restored module runs. */
47
+ export async function authorizeAdvancedRouting(request, signal) {
48
+ const middleware = request.routingMiddleware;
49
+ const state = readRouterState(request);
50
+ if (!state) return true;
51
+ // Builds and programmatic rendering have no native bridge. Keep the bounded
52
+ // middleware fallback for those callers; production workers use the full
53
+ // native pipeline, including configuration-only redirects and rewrites.
54
+ if (!request.routingResolver && !middleware) return !Object.values(state.slots).some(slot => slot.accessUrl && slot.accessUrl !== slot.url);
55
+ const current = new URL(request.url);
56
+ const visible = new URL(request.originalUrl || request.url);
57
+ const urls = new Map();
58
+ for (const slot of Object.values(state.slots)) {
59
+ if (!urls.has(slot.url) || slot.accessUrl) urls.set(slot.url, slot.accessUrl || slot.url);
60
+ }
61
+ for (const source of Object.values(state.slots).map(slot => slot.source).filter(Boolean)) if (!urls.has(source)) urls.set(source, source);
62
+ // `state.source` is a pathname for tree matching, without search parameters.
63
+ // When a saved branch already covers it, authorize that branch's full access
64
+ // URL rather than also executing its physical path outside the rewrite.
65
+ if (![...urls.keys()].some(saved => new URL(saved, current).pathname === state.source)) urls.set(state.source, state.source);
66
+ if (urls.size > 32) return false;
67
+ const deadline = AbortSignal.timeout(10_000);
68
+ signal = signal ? AbortSignal.any([signal, deadline]) : deadline;
69
+ const headers = new Headers(request.routingRequestHeaders || request.headers || {});
70
+ request.routingContexts = new Map();
71
+ function internal(url) { return removeBasePath(decodeURI(url.pathname), request.basePath) + url.search; }
72
+ function saveContext(saved, url, values) {
73
+ // Rendering separately is needed only for genuinely different contexts.
74
+ const actual = new Headers(request.headers || {});
75
+ if (JSON.stringify([...values].sort()) !== JSON.stringify([...actual].sort()) || url.href !== current.href) {
76
+ request.routingContexts.set(saved, { headers: Object.fromEntries(values), url: url.href, method: 'GET', mutableCookies: false });
77
+ }
78
+ }
79
+ for (const [saved, access] of urls) {
80
+ signal.throwIfAborted();
81
+ const url = new URL(access, current);
82
+ // App route matching decodes segment literals. Apply the same spelling to
83
+ // access checks so an encoded /%61dmin source cannot skip /admin middleware.
84
+ try { url.pathname = decodeURI(url.pathname); } catch { return false; }
85
+ url.pathname = addBasePath(url.pathname, request.basePath);
86
+ // The unmounted root is `/`, while its slash-free mounted spelling is the
87
+ // basePath itself. Do not manufacture a redirect during this translation.
88
+ if (request.basePath && url.pathname === request.basePath + '/' && !request.trailingSlash && !request.skipTrailingSlashRedirect) url.pathname = request.basePath;
89
+ if (request.method !== 'POST' && internal(url) === internal(visible) && internal(new URL(saved, current)) === internal(current)) continue;
90
+ if (request.routingResolver) {
91
+ const result = await nativeResolution(request.routingResolver, url, headers, signal);
92
+ if (!result) return false;
93
+ const effective = new URL(result.resolved.url, current);
94
+ if (internal(effective) !== internal(new URL(saved, current))) return false;
95
+ const transformed = new Headers(result.resolved.headers);
96
+ const cookies = result.responseHeaders?.['set-cookie'];
97
+ if (cookies) {
98
+ const lines = Array.isArray(cookies) ? cookies : [cookies];
99
+ request.responseHeaders ||= {};
100
+ request.responseHeaders['set-cookie'] = [...new Set([...(request.responseHeaders['set-cookie'] || []), ...lines])];
101
+ }
102
+ saveContext(saved, effective, transformed);
103
+ continue;
104
+ }
105
+ if (!middleware.matchers.some(rule => matches(rule, url, headers))) {
106
+ if (internal(url) !== internal(new URL(saved, current))) return false;
107
+ saveContext(saved, url, headers);
108
+ continue;
109
+ }
110
+ // A regular RSC render must not initialize the middleware/Web Request graph.
111
+ const { runMiddleware } = await import('./middleware.mjs');
112
+ const result = await runMiddleware({ modulePath: path.join(request.distDir, middleware.module), middleware,
113
+ manifest: { middleware, config: { basePath: request.basePath, trailingSlash: request.trailingSlash,
114
+ skipProxyUrlNormalize: request.skipProxyUrlNormalize, skipMiddlewareUrlNormalize: request.skipMiddlewareUrlNormalize } }, url: url.href, method: 'GET', headers: Object.fromEntries(headers),
115
+ production: request.production, signal, timeoutMs: 5000 });
116
+ try {
117
+ if (result.status < 200 || result.status >= 300 || result.headers.location || (!result.headers['x-middleware-next'] && !result.headers['x-middleware-rewrite'])) return false;
118
+ const transformed = new Headers(headers);
119
+ const override = result.headers['x-middleware-override-headers'];
120
+ const cookies = result.headers['set-cookie'];
121
+ if ((override || cookies) && !request.routingRequestHeaders) return false;
122
+ if (override !== undefined) {
123
+ const names = new Set(override.split(',').map(name => name.trim().toLowerCase()).filter(Boolean));
124
+ for (const name of [...transformed.keys()]) if (!names.has(name)) transformed.delete(name);
125
+ for (const name of names) {
126
+ const value = result.headers['x-middleware-request-' + name];
127
+ if (value === undefined) transformed.delete(name); else transformed.set(name, value);
128
+ }
129
+ }
130
+ const effective = new URL(result.headers['x-middleware-rewrite'] || url.href, url);
131
+ if (effective.origin !== current.origin || internal(effective) !== internal(new URL(saved, current))) return false;
132
+ if (cookies) {
133
+ const lines = Array.isArray(cookies) ? cookies : [cookies];
134
+ transformed.set('x-middleware-set-cookie', lines.join(','));
135
+ request.responseHeaders ||= {};
136
+ request.responseHeaders['set-cookie'] = [...new Set([...(request.responseHeaders['set-cookie'] || []), ...lines])];
137
+ }
138
+ saveContext(saved, effective, transformed);
139
+ } finally { result.cancel?.(); result.finalizeCache?.(); }
140
+ }
141
+ if (request.routingContexts.size) request.routingContexts.set(internal(current), { headers: request.headers, url: current.href, method: 'GET', mutableCookies: false });
142
+ return true;
143
+ }
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { renderToReadableStream } from 'react-server-dom-webpack/server.node';
3
+ import { createFromReadableStream } from 'react-server-dom-webpack/client.edge';
4
+ import { runRequestContext } from '../compat/headers.cjs';
5
+ import { flightConsumer } from './cache-components-rsc.mjs';
6
+
7
+ /** A separate React render preserves AsyncLocalStorage for every nested Server
8
+ * Component, including async descendants and components imported by packages.
9
+ * Merely wrapping a layout function in ALS does not isolate its descendants. */
10
+ export function routingBranchRenderer(context, bundler, signal, errorDigest, byteLimit = 16 * 1024 * 1024) {
11
+ const consumer = { serverConsumerManifest: flightConsumer(context), replayConsoleLogs: false };
12
+ const abort = new AbortController();
13
+ const combinedSignal = signal ? AbortSignal.any([signal, abort.signal]) : abort.signal;
14
+ let bytes = 0;
15
+ function bounded(stream) {
16
+ return stream.pipeThrough(new TransformStream({ transform(chunk, controller) {
17
+ bytes += chunk.byteLength;
18
+ if (bytes > byteLimit) {
19
+ const error = new Error('Combined routing branch Flight exceeds the 16 MiB rendering limit');
20
+ abort.abort(error);
21
+ throw error;
22
+ }
23
+ controller.enqueue(chunk);
24
+ } }));
25
+ }
26
+ return (tree, branch) => {
27
+ async function RoutingBranch() {
28
+ return runRequestContext({ ...context, ...branch, signal: combinedSignal, privateCache: undefined, privateCacheBytes: 0, draftProvider: undefined }, () => {
29
+ const stream = renderToReadableStream(tree, bundler, { signal: combinedSignal, onError: errorDigest });
30
+ return createFromReadableStream(bounded(stream), consumer);
31
+ });
32
+ }
33
+ return React.createElement(RoutingBranch);
34
+ };
35
+ }
@@ -0,0 +1,61 @@
1
+ // Route matching and navigation state do not need React or metadata rendering.
2
+ import { removeBasePath } from '../compat/paths.cjs';
3
+
4
+ export function matchAppPattern(pattern, pathname, prefix = false) {
5
+ const expected = pattern.split('/').filter(Boolean), raw = pathname.split('/').filter(Boolean);
6
+ let parts;
7
+ try { parts = raw.map(decodeURIComponent); } catch { return null; }
8
+ const params = {};
9
+ for (let index = 0; index < expected.length; index++) {
10
+ const segment = expected[index];
11
+ const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(segment);
12
+ const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(segment);
13
+ if (optional || catchAll) {
14
+ if (catchAll && index >= parts.length) return null;
15
+ if (index < parts.length) params[(optional || catchAll)[1]] = parts.slice(index);
16
+ return params;
17
+ }
18
+ const dynamic = /^\[([^\]]+)\]$/.exec(segment);
19
+ if (index >= parts.length) return null;
20
+ if (dynamic) params[dynamic[1]] = parts[index];
21
+ else if (segment !== parts[index]) return null;
22
+ }
23
+ return prefix || expected.length === parts.length ? params : null;
24
+ }
25
+
26
+ export function readRouterState(request) {
27
+ if (!Object.entries(request.headers || {}).some(([name, value]) => name.toLowerCase() === 'rsc' && value === '1')) return null;
28
+ const value = Object.entries(request.headers || {}).find(([name]) => name.toLowerCase() === 'x-prnext-router-state')?.[1];
29
+ if (typeof value !== 'string' || value.length > 24 * 1024) return null;
30
+ try {
31
+ const state = JSON.parse(value.startsWith('{') ? value : decodeURIComponent(value));
32
+ const localURL = value => typeof value === 'string' && value.startsWith('/') && !value.startsWith('//') && !/[\\\u0000-\u0020\u007f]/.test(value) && value.length <= 4096;
33
+ if (!state || typeof state !== 'object' || !localURL(state.source) || !state.slots || typeof state.slots !== 'object' || Array.isArray(state.slots) || Object.keys(state.slots).length > 256) return null;
34
+ const slots = {};
35
+ for (const [key, slot] of Object.entries(state.slots)) {
36
+ if (key.length > 4096 || !slot || typeof slot.branch !== 'string' || slot.branch.length > 4096 || !localURL(slot.url)) return null;
37
+ if (slot.source !== undefined && !localURL(slot.source)) return null;
38
+ if (slot.accessUrl !== undefined && !localURL(slot.accessUrl)) return null;
39
+ slots[key] = { branch: slot.branch, url: slot.url, ...(slot.source ? { source: slot.source } : {}), ...(slot.accessUrl ? { accessUrl: slot.accessUrl } : {}) };
40
+ }
41
+ return { source: removeBasePath(new URL(state.source, 'http://prnext.local').pathname, request.basePath), slots, restore: state.restore === true, refresh: state.refresh === true };
42
+ } catch { return null; }
43
+ }
44
+
45
+ /** Resolve saved URLs only through the trusted build manifest, never to user-provided module paths. */
46
+ export function advancedRoutingSources(request, routes) {
47
+ const state = readRouterState(request);
48
+ if (!state) return [];
49
+ const modules = new Set();
50
+ for (const saved of Object.values(state.slots)) {
51
+ const pathname = new URL(saved.url, 'http://prnext.local').pathname;
52
+ const route = routes.filter(item => item.router === 'app' && item.kind === 'page' && !item.internal && matchAppPattern(item.pattern, pathname))
53
+ .sort((a, b) => score(b.pattern) - score(a.pattern))[0];
54
+ if (route) modules.add(route.module);
55
+ }
56
+ return [...modules];
57
+ }
58
+
59
+ function score(pattern) {
60
+ return pattern.split('/').filter(Boolean).reduce((sum, value) => sum + (value.startsWith('[[...') ? 0 : value.startsWith('[...') ? 1 : value.startsWith('[') ? 3 : 6), 0);
61
+ }
@@ -0,0 +1,323 @@
1
+ import React from 'react';
2
+ import { instantEnabled } from '../compat/instant.cjs';
3
+ import { Metadata } from './app-metadata.mjs';
4
+ import { removeBasePath } from '../compat/paths.cjs';
5
+ import path from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { currentRequest, runRequestContext } from '../compat/headers.cjs';
8
+ import { staticParams } from '../compat/static-generation.cjs';
9
+
10
+ import { matchAppPattern, readRouterState } from './app-routing-state.mjs';
11
+ export { matchAppPattern, readRouterState, advancedRoutingSources } from './app-routing-state.mjs';
12
+
13
+ export async function loadAdvancedRouting(entry, request) {
14
+ if (!entry.routing || !request.routingSources?.length) return entry;
15
+ function merge(current, extra) {
16
+ if (!extra) return current;
17
+ if (!current) return extra;
18
+ const children = new Map(current.children.map(child => [child.path, child]));
19
+ for (const child of extra.children) children.set(child.path, merge(children.get(child.path), child));
20
+ const slots = { ...current.slots };
21
+ for (const [name, child] of Object.entries(extra.slots)) slots[name] = merge(slots[name], child);
22
+ return { ...current, files: { ...extra.files, ...current.files }, children: [...children.values()], slots };
23
+ }
24
+ let routing = entry.routing;
25
+ const metadataFiles = new Map((entry.metadataFiles || []).map(file => [JSON.stringify([file.path, file.pattern]), file]));
26
+ for (const module of request.routingSources) {
27
+ const extra = await import(pathToFileURL(path.join(request.distDir, module)).href);
28
+ if (extra.routing) routing = merge(routing, extra.routing);
29
+ for (const file of extra.metadataFiles || []) metadataFiles.set(JSON.stringify([file.path, file.pattern]), file);
30
+ }
31
+ return { ...entry, routing, metadataFiles: [...metadataFiles.values()] };
32
+ }
33
+
34
+ function component(module, label) {
35
+ if (!module?.default) throw new Error(`App Router ${label} must export a default component`);
36
+ return module.default;
37
+ }
38
+ function score(pattern) {
39
+ return pattern.split('/').filter(Boolean).reduce((sum, value) => sum + (value.startsWith('[[...') ? 0 : value.startsWith('[...') ? 1 : value.startsWith('[') ? 3 : 6), 0);
40
+ }
41
+ function selectedSegments(chain, params) {
42
+ return chain.map(node => {
43
+ const value = node.segment.replace(/^(?:\(\.\.\.\)|\(\.\)|(?:\(\.\.\))+)/, '');
44
+ if (!value || value.startsWith('@')) return null;
45
+ const name = /^\[\[?(?:\.\.\.)?([^\]]+)\]\]?$/.exec(value)?.[1];
46
+ if (name) return Array.isArray(params[name]) ? params[name].join('/') : params[name] ?? null;
47
+ return value;
48
+ }).filter(value => value !== null);
49
+ }
50
+
51
+ /** Parallel slots retain their original Flight children on soft navigation. */
52
+ export function createAdvancedTree(entry, request, searchParams) {
53
+ const pathname = removeBasePath(new URL(request.url).pathname, request.basePath || '');
54
+ const old = readRouterState(request), slots = {}, activeParams = { ...request.params };
55
+ const rebuild = !!old && (old.refresh || old.restore || request.method === 'POST');
56
+ let invalid = false;
57
+ const root = entry.routing;
58
+ let metadataHead;
59
+ const keyScopes = new Map();
60
+ const instantTrees = [];
61
+ function registerKey(key, node, prefix, suffix = '', liveProps, values = scopedParams(node), segments) {
62
+ if (request.partialParams?.length) keyScopes.set(key, { key, prefix, suffix, names: Object.keys(values), pattern: node.pattern, ...(liveProps ? { liveProps } : {}), ...(segments ? { segments } : {}) });
63
+ return key;
64
+ }
65
+ const rootLayouts = new Set();
66
+ const metadataSelections = new Map();
67
+ let missing = entry.interceptionOnly && !old, rootPage;
68
+ const all = [];
69
+ function walk(node, chain = []) {
70
+ const next = [...chain, node];
71
+ if (node.files.layout && !chain.some(parent => parent.files.layout)) rootLayouts.add(node.path);
72
+ if (node.files.page) all.push({ node, chain: next, params: matchAppPattern(node.pattern, pathname) });
73
+ for (const child of node.children) walk(child, next);
74
+ for (const child of Object.values(node.slots)) walk(child, next);
75
+ }
76
+ walk(root);
77
+ let activeInterceptions;
78
+ function canIntercept(candidate) {
79
+ if (!candidate.node.interception || !old) return false;
80
+ for (let parent = candidate.node.interception.parent; parent; parent = parent.parent) {
81
+ const prefix = parent.anchor ? `${parent.anchor}/${parent.marker}` : parent.marker;
82
+ if (!Object.values(old.slots).some(slot => slot.branch.startsWith(prefix))) return false;
83
+ }
84
+ const { source, anchor } = candidate.node.interception;
85
+ return (!activeInterceptions || activeInterceptions.has(candidate.node.path)) && (!!matchAppPattern(source, old.source, true) || Object.entries(old.slots).some(([key, value]) => key.startsWith(`${anchor}::`) && matchAppPattern(source, new URL(value.source || value.url, request.url).pathname, true)));
86
+ }
87
+ const eligibleInterceptions = all.filter(candidate => candidate.params && canIntercept(candidate));
88
+ const priority = candidate => candidate.node.interception.source.split('/').filter(Boolean).length * 1000 + candidate.node.interception.anchor.split('/').filter(Boolean).length;
89
+ const mostSpecific = Math.max(-1, ...eligibleInterceptions.map(priority));
90
+ const interceptions = eligibleInterceptions.filter(candidate => priority(candidate) === mostSpecific);
91
+ activeInterceptions = new Set(interceptions.map(candidate => candidate.node.path));
92
+ const interceptOwners = new Set(interceptions.map(candidate => {
93
+ const parts = candidate.node.interception.anchor.split('/');
94
+ const index = parts.findLastIndex(part => part.startsWith('@'));
95
+ return index < 0 ? null : parts.slice(0, index).join('/');
96
+ }).filter(value => value !== null));
97
+ function candidates(node, chain = []) {
98
+ const result = [], next = [...chain, node];
99
+ if (node.files.page) {
100
+ const params = matchAppPattern(node.pattern, pathname);
101
+ if (params && (!node.interception || canIntercept({ node }))) result.push({ node, chain: next, params });
102
+ }
103
+ for (const child of node.children) result.push(...candidates(child, next));
104
+ return result.sort((left, right) => Number(!!right.node.interception) - Number(!!left.node.interception) || score(right.node.pattern) - score(left.node.pattern));
105
+ }
106
+ function scopedParams(node) {
107
+ const current = matchAppPattern(node.pattern, pathname, true) || (old && matchAppPattern(node.pattern, old.source, true));
108
+ if (current) return current;
109
+ for (const [key, value] of Object.entries(old?.slots || {})) {
110
+ if (!key.startsWith(`${node.path}::`)) continue;
111
+ const saved = matchAppPattern(node.pattern, new URL(value.source || value.url, request.url).pathname, true);
112
+ if (saved) return saved;
113
+ }
114
+ return {};
115
+ }
116
+ function scope(node) {
117
+ return registerKey(`${node.path}::${JSON.stringify(scopedParams(node))}`, node, `${node.path}::`);
118
+ }
119
+ function sourceURL(node, previous) {
120
+ return matchAppPattern(node.pattern, pathname, true) ? pathname + new URL(request.url).search : previous?.source || previous?.url || old?.source || pathname;
121
+ }
122
+ function accessURL(url, previous) {
123
+ if (previous?.url === url && previous.accessUrl) return previous.accessUrl;
124
+ const current = new URL(request.url), visible = new URL(request.originalUrl || request.url);
125
+ const access = removeBasePath(visible.pathname, request.basePath) + visible.search;
126
+ if (url === pathname + current.search && access !== url) return access;
127
+ }
128
+ function savedBranch(node, saved, chain = []) {
129
+ const next = [...chain, node];
130
+ const fallback = saved.branch.endsWith('#default');
131
+ const target = fallback ? saved.branch.slice(0, -8) : saved.branch;
132
+ if (node.path === target && node.files[fallback ? 'default' : 'page']) {
133
+ const url = new URL(saved.url, request.url);
134
+ const params = matchAppPattern(node.pattern, url.pathname, fallback);
135
+ if (!params) return;
136
+ const query = {};
137
+ for (const [name, value] of url.searchParams) {
138
+ if (name === '_rsc') continue;
139
+ Object.defineProperty(query, name, { value: Object.hasOwn(query, name) ? [...(Array.isArray(query[name]) ? query[name] : [query[name]]), value] : value, enumerable: true, configurable: true });
140
+ }
141
+ return { node, chain: next, params, default: fallback, url: saved.url, accessUrl: saved.accessUrl, searchParams: Promise.resolve(query) };
142
+ }
143
+ for (const child of node.children) {
144
+ const selected = savedBranch(child, saved, next);
145
+ if (selected) return selected;
146
+ }
147
+ }
148
+ function slot(node, owner, name) {
149
+ const key = registerKey(`${scope(owner)}::${name}`, owner, `${owner.path}::`, `::${name}`, ['id']);
150
+ const previous = old?.slots[key];
151
+ let choices = candidates(node);
152
+ let selected = rebuild && previous ? savedBranch(node, previous) : choices[0];
153
+ if (rebuild && previous && !selected) { invalid = true; return { tree: null, segments: [] }; }
154
+ if (!rebuild) {
155
+ for (const candidate of interceptions) {
156
+ const start = candidate.chain.indexOf(node);
157
+ const slotIndex = candidate.chain.findIndex((item, index) => index > start && item.segment.startsWith('@'));
158
+ if (start < 0 || slotIndex < 0) continue;
159
+ const owner = candidate.chain[slotIndex - 1];
160
+ selected = { node: owner, chain: candidate.chain.slice(start, slotIndex), params: candidate.params,
161
+ default: !!owner.files.default, preserve: !owner.files.default, scaffold: true };
162
+ break;
163
+ }
164
+ }
165
+ if (!selected) {
166
+ for (const candidate of all) {
167
+ if (!candidate.params || candidate.node.interception) continue;
168
+ const start = candidate.chain.indexOf(node);
169
+ if (start < 0) continue;
170
+ const slotIndex = candidate.chain.findIndex((item, index) => index > start && item.segment.startsWith('@'));
171
+ if (slotIndex < 0) continue;
172
+ const owner = candidate.chain[slotIndex - 1];
173
+ selected = { node: owner, chain: candidate.chain.slice(start, slotIndex), params: candidate.params,
174
+ default: !!owner.files.default, preserve: !owner.files.default, scaffold: true };
175
+ break;
176
+ }
177
+ }
178
+ const preserve = !!previous && !rebuild && (!selected || (name === 'children' && interceptOwners.has(owner.path)));
179
+ if (preserve) {
180
+ slots[key] = previous;
181
+ const saved = savedBranch(node, previous);
182
+ if (saved) {
183
+ metadataSelections.set(saved.node.path, saved);
184
+ if (name === 'children' && owner === root) rootPage = saved;
185
+ }
186
+ return { tree: React.createElement(entry.LayoutSlot, { key, id: key, preserve: true }), segments: null };
187
+ }
188
+ if (!selected) {
189
+ // Find the nearest default whose route prefix contains the destination.
190
+ const defaults = [];
191
+ function findDefault(value, chain = []) {
192
+ if (!matchAppPattern(value.pattern, pathname, true) || (value.interception && !old)) return;
193
+ const next = [...chain, value];
194
+ if (value.files.default) defaults.push({ node: value, chain: next, params: matchAppPattern(value.pattern, pathname, true), default: true });
195
+ for (const child of value.children) findDefault(child, next);
196
+ }
197
+ findDefault(node);
198
+ selected = defaults.sort((a, b) => b.chain.length - a.chain.length)[0];
199
+ }
200
+ if (!selected) { missing = true; return { tree: null, segments: [] }; }
201
+ if (selected.scaffold) selected.url = previous?.url || sourceURL(selected.node, previous);
202
+ Object.assign(activeParams, selected.params);
203
+ metadataSelections.set(selected.node.path, selected);
204
+ const selectedURL = selected.url || pathname + new URL(request.url).search;
205
+ slots[key] = { branch: selected.node.path + (selected.default ? '#default' : ''), url: selectedURL, source: sourceURL(owner, previous), accessUrl: selected.accessUrl || accessURL(selectedURL, previous) };
206
+ if (name === 'children' && owner === root) rootPage = selected;
207
+ let selectedTree = renderPath(selected.chain, selected, 0, name === 'children' && owner === node);
208
+ const context = request.routingContexts?.get(selectedURL);
209
+ if (context && request.renderRoutingBranch) selectedTree = request.renderRoutingBranch(selectedTree, context);
210
+ const instant=selected.node.config?.page?.instant ?? [...selected.chain].reverse().find(item=>item.config?.layout?.instant!==undefined)?.config.layout.instant;
211
+ if(request.instantValidation && name!=='children' && instantEnabled(instant,request.production)){
212
+ instantTrees.push({path:(owner.path || '/')+' @'+name,tree:selectedTree});
213
+ }
214
+ return { tree: React.createElement(entry.LayoutSlot, { key, id: key, preserve: false }, selectedTree), segments: selected.default ? [] : selectedSegments(selected.chain.slice(node === owner ? 1 : 0), selected.params) };
215
+ }
216
+ function renderPath(chain, selection, index, skipOwner) {
217
+ const node = chain[index];
218
+ const key = scope(node, selection.params);
219
+ const props = { params: staticParams(selection.params, currentRequest()), searchParams: selection.searchParams || searchParams };
220
+ let children;
221
+ if (index + 1 < chain.length) children = renderPath(chain, selection, index + 1, false);
222
+ else if (!selection.preserve) {
223
+ const Page = component(node.files[selection.default ? 'default' : 'page'], selection.default ? 'default' : 'page');
224
+ children = entry.ClientPageRoot && Page.$$typeof === Symbol.for('react.client.reference')
225
+ ? React.createElement(entry.ClientPageRoot, { Component: Page, ...props, key: registerKey(`${node.path}:${JSON.stringify(selection.params)}`, node, `${node.path}:`, '', undefined, selection.params) })
226
+ : React.createElement(Page, { ...props, key: registerKey(`${node.path}:${JSON.stringify(selection.params)}`, node, `${node.path}:`, '', undefined, selection.params) });
227
+ } else children = null;
228
+ if (skipOwner) return children;
229
+ const childKey = registerKey(`${key}::children`, node, `${node.path}::`, '::children', ['id']);
230
+ const previousChild = old?.slots[childKey];
231
+ const preserveChild = !!previousChild && !rebuild && (interceptOwners.has(node.path) || (selection.scaffold && index === chain.length - 1));
232
+ if (rebuild && previousChild && previousChild.branch !== selection.node.path + (selection.default ? '#default' : '')) {
233
+ const saved = savedBranch(node, previousChild);
234
+ if (!saved) invalid = true;
235
+ else {
236
+ metadataSelections.set(saved.node.path, saved);
237
+ children = renderPath(saved.chain, saved, 0, true);
238
+ }
239
+ }
240
+ if (selection.preserve && index === chain.length - 1 && !preserveChild) missing = true;
241
+ const childURL = selection.url || pathname + new URL(request.url).search;
242
+ slots[childKey] = preserveChild || (rebuild && previousChild) ? previousChild : { branch: selection.node.path + (selection.default ? '#default' : ''), url: childURL, source: sourceURL(node, previousChild), accessUrl: selection.accessUrl || accessURL(childURL, previousChild) };
243
+ if (preserveChild) {
244
+ const saved = savedBranch(node, previousChild);
245
+ if (saved) metadataSelections.set(saved.node.path, saved);
246
+ }
247
+ children = React.createElement(entry.LayoutSlot, { key: childKey, id: childKey, preserve: preserveChild }, preserveChild ? null : children);
248
+ const selected = { children: selection.default ? [] : selectedSegments(chain.slice(index + 1), selection.params) };
249
+ if (preserveChild) selected.children = null;
250
+ const parallel = {};
251
+ for (const [name, child] of Object.entries(node.slots)) {
252
+ const rendered = slot(child, node, name);
253
+ parallel[name] = rendered.tree; selected[name] = rendered.segments;
254
+ }
255
+ if (request.instantValidation && node.files.layout) {
256
+ const nextConfig = chain.slice(index + 1).find(item => item.config?.layout?.instant !== undefined)?.config.layout.instant ?? selection.node.config?.page?.instant;
257
+ if (instantEnabled(nextConfig, request.production)) instantTrees.push({ path: node.path || '/', tree: node.files.loading ? React.createElement(React.Suspense, { fallback: React.createElement(component(node.files.loading, 'loading')) }, children) : children });
258
+ }
259
+ return wrap(node, children, parallel, selected, key, selection.params, chain.slice(index + 1).map(item => item.segment), selection.node);
260
+ }
261
+ function wrap(node, children, parallel, selected, key, params, templateSegments, selectionNode) {
262
+ if (node.files.loading) children = React.createElement(React.Suspense, { fallback: React.createElement(component(node.files.loading, 'loading')) }, children);
263
+ if (node.files.error) children = React.createElement(entry.ErrorBoundary, { key: registerKey(`error:${key}`, node, `error:${node.path}::`), errorComponent: component(node.files.error, 'error'), resetKey: request.renderKey }, children);
264
+ if (node.files.notFound || node === root) children = React.createElement(entry.NavigationBoundary, { key: registerKey(`navigation:${key}`, node, `navigation:${node.path}::`), resetKey: request.renderKey,
265
+ notFound: node.files.notFound ? React.createElement(component(node.files.notFound, 'not-found')) : React.createElement('h1', null, '404: This page could not be found.') }, children);
266
+ if (node.files.template) children = React.createElement(component(node.files.template, 'template'), { key: registerKey(selected.children?.join('/') || key, selectionNode || node, `${node.path}::`, '', undefined, scopedParams(node), templateSegments) }, children);
267
+ if (node.files.layout) {
268
+ const scoped = scopedParams(node);
269
+ Object.assign(activeParams, scoped);
270
+ if (rootLayouts.has(node.path) && request.cacheComponents) children = React.createElement(React.Fragment, null, metadataHead, children);
271
+ children = React.createElement(component(node.files.layout, 'layout'), { params: staticParams(scoped, currentRequest()), key, ...parallel }, children);
272
+ children = React.createElement(entry.LayoutProvider, { key: registerKey(`layout:${key}`, node, `layout:${node.path}::`, '', ['id', 'segments']), id: key, segments: selected }, children);
273
+ }
274
+ return children;
275
+ }
276
+ async function RouteMetadata() {
277
+ const layouts = new Map(), pages = new Map();
278
+ for (const selection of metadataSelections.values()) {
279
+ const source = new URL(selection.url || pathname, request.url).pathname;
280
+ for (const node of selection.chain) if (!layouts.has(node.path)) layouts.set(node.path, { node, params: matchAppPattern(node.pattern, source, true) || scopedParams(node), context: node === root ? undefined : request.routingContexts?.get(selection.url) });
281
+ if (!selection.preserve) pages.set(selection.node.path, selection);
282
+ }
283
+ const metadataItems = [];
284
+ function visit(node) {
285
+ const layout = layouts.get(node.path), page = pages.get(node.path);
286
+ if (layout) metadataItems.push({ module: node.files.layout, params: staticParams(layout.params, currentRequest()), filePath: page ? undefined : node.path, context: layout.context });
287
+ // Next orders paths containing named slots before ordinary children,
288
+ // with slot names in locale order (compareAppPaths in its app loader).
289
+ for (const [, child] of Object.entries(node.slots).sort(([a], [b]) => a.localeCompare(b))) visit(child);
290
+ if (page) metadataItems.push({ module: node.files[page.default ? 'default' : 'page'], params: staticParams(page.params, currentRequest()), searchParams: page.searchParams || searchParams, filePath: node.path, context: request.routingContexts?.get(page.url) });
291
+ for (const child of node.children) visit(child);
292
+ }
293
+ visit(root);
294
+ const metadataEntry = { ...entry, metadataItems };
295
+ return runRequestContext({ ...currentRequest(), metadataRendering: true }, () => Metadata({ entry: metadataEntry, params: staticParams(rootPage?.params || request.params || {}, currentRequest()), searchParams,
296
+ pathname, notFoundIndex: missing ? 0 : -1 }));
297
+ }
298
+ const metadata = React.createElement(RouteMetadata, { key: 'metadata' });
299
+ const head = request.cacheComponents ? React.createElement(React.Suspense, { fallback: null, key: 'metadata-boundary' }, metadata) : metadata;
300
+ metadataHead = head;
301
+ const main = slot(root, root, 'children');
302
+ const parallel = {}, selected = { children: main.segments };
303
+ for (const [name, child] of Object.entries(root.slots)) {
304
+ const result = slot(child, root, name);
305
+ parallel[name] = result.tree; selected[name] = result.segments;
306
+ }
307
+ if (invalid) { request.responseStatus = 400; return { tree: null, head: null, routingInvalid: true }; }
308
+ const rootLayoutNode = rootPage?.chain.find(node => node.files.layout) || (root.files.layout ? root : undefined);
309
+ if (request.instantValidation && root.files.layout && instantEnabled(rootPage?.chain.slice(1).find(item => item.config?.layout?.instant !== undefined)?.config.layout.instant ?? rootPage?.node.config?.page?.instant, request.production)) {
310
+ const content = React.createElement(React.Fragment, null, main.tree, ...Object.values(parallel));
311
+ instantTrees.push({ path: root.path || '/', tree: root.files.loading ? React.createElement(React.Suspense, { fallback: React.createElement(component(root.files.loading, 'loading')) }, content) : content });
312
+ }
313
+ const instantDisabled = all.flatMap(item => item.chain).some(node => Object.values(node.config || {}).some(config => config.instant && typeof config.instant === 'object' && (config.instant.unstable_disableValidation || (request.production ? config.instant.unstable_disableBuildValidation : config.instant.unstable_disableDevValidation))));
314
+ let tree = wrap(root, main.tree, parallel, selected, scope(root), {}, rootPage?.chain.slice(1).map(item => item.segment), rootPage?.node);
315
+ if (missing) {
316
+ request.responseStatus = 404;
317
+ const notFound = rootLayoutNode?.files.notFound || root.files.notFound;
318
+ const missingPage = notFound ? React.createElement(component(notFound, 'not-found')) : React.createElement('h1', null, '404: This page could not be found.');
319
+ tree = rootLayoutNode ? React.createElement(component(rootLayoutNode.files.layout, 'layout'), { params: staticParams(scopedParams(rootLayoutNode), currentRequest()), ...Object.fromEntries(Object.keys(rootLayoutNode.slots).map(name => [name, null])) }, request.cacheComponents ? React.createElement(React.Fragment, null, head, missingPage) : missingPage) : missingPage;
320
+ }
321
+ return { tree: request.cacheComponents && rootLayoutNode ? tree : React.createElement(React.Fragment, null, head, tree), head,
322
+ ...(request.instantValidation && !instantDisabled ? { instantTrees } : {}), ...(keyScopes.size ? { keyScopes: [...keyScopes.values()] } : {}), rootLayout: rootLayoutNode?.layoutId || entry.rootLayout, routing: { source: pathname, slots, params: activeParams } };
323
+ }
@@ -0,0 +1,22 @@
1
+ /** Each layout sees only dynamic parameters declared at or above its segment. */
2
+ export function paramsBySegment(segments, params = {}) {
3
+ const result = new Map();
4
+ const ancestors = Object.create(null);
5
+ for (const segment of segments) {
6
+ const match = /^\[\[?(?:\.\.\.)?([^\]]+)\]\]?$/.exec(segment.segment || '');
7
+ if (match && Object.hasOwn(params, match[1])) ancestors[match[1]] = params[match[1]];
8
+ result.set(segment, { ...ancestors });
9
+ }
10
+ return result;
11
+ }
12
+
13
+ export function selectedLayoutSegments(segments, params = {}) {
14
+ return segments.flatMap(segment => {
15
+ const value = segment.segment || '';
16
+ if (!value || value.startsWith('@')) return [];
17
+ const match = /^\[\[?(?:\.\.\.)?([^\]]+)\]\]?$/.exec(value);
18
+ if (!match) return [value];
19
+ const parameter = params[match[1]];
20
+ return parameter === undefined ? [] : [Array.isArray(parameter) ? parameter.join('/') : parameter];
21
+ });
22
+ }