vinext 1.2.1 → 1.3.0

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 (212) hide show
  1. package/README.md +12 -9
  2. package/dist/build/client-build-config.d.ts +64 -3
  3. package/dist/build/client-build-config.js +131 -4
  4. package/dist/build/client-reference-groups.d.ts +163 -0
  5. package/dist/build/client-reference-groups.js +534 -0
  6. package/dist/build/module-dependency-cache.d.ts +31 -0
  7. package/dist/build/module-dependency-cache.js +51 -1
  8. package/dist/build/next-client-runtime-manifests.d.ts +10 -1
  9. package/dist/build/next-client-runtime-manifests.js +19 -4
  10. package/dist/build/nitro-public-files.d.ts +24 -0
  11. package/dist/build/nitro-public-files.js +60 -0
  12. package/dist/build/pages-client-assets-module.js +1 -1
  13. package/dist/build/prerender-paths.js +5 -2
  14. package/dist/build/server-actions-encryption-key.d.ts +23 -0
  15. package/dist/build/server-actions-encryption-key.js +101 -0
  16. package/dist/build/ssr-manifest.d.ts +3 -0
  17. package/dist/build/ssr-manifest.js +10 -1
  18. package/dist/client/client-rewrite-params.d.ts +9 -0
  19. package/dist/client/client-rewrite-params.js +20 -0
  20. package/dist/client/dev-error-overlay.js +2 -1
  21. package/dist/client/navigation-runtime.d.ts +25 -2
  22. package/dist/client/navigation-runtime.js +42 -11
  23. package/dist/client/vinext-next-data.d.ts +0 -1
  24. package/dist/config/config-matchers.d.ts +15 -40
  25. package/dist/config/config-matchers.js +183 -255
  26. package/dist/config/config-source.d.ts +34 -0
  27. package/dist/config/config-source.js +141 -0
  28. package/dist/config/header-params.d.ts +29 -0
  29. package/dist/config/header-params.js +106 -0
  30. package/dist/config/next-config.d.ts +7 -5
  31. package/dist/config/next-config.js +120 -7
  32. package/dist/entries/app-browser-entry.d.ts +14 -1
  33. package/dist/entries/app-browser-entry.js +26 -36
  34. package/dist/entries/app-rsc-entry.d.ts +2 -0
  35. package/dist/entries/app-rsc-entry.js +92 -57
  36. package/dist/entries/app-rsc-manifest.d.ts +10 -0
  37. package/dist/entries/app-rsc-manifest.js +20 -1
  38. package/dist/entries/app-ssr-entry.d.ts +4 -1
  39. package/dist/entries/app-ssr-entry.js +9 -2
  40. package/dist/entries/pages-client-entry.d.ts +1 -0
  41. package/dist/entries/pages-client-entry.js +6 -6
  42. package/dist/entries/pages-server-entry.js +17 -14
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.js +239 -83
  45. package/dist/init.js +17 -0
  46. package/dist/plugins/optimize-imports.d.ts +25 -2
  47. package/dist/plugins/optimize-imports.js +106 -20
  48. package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
  49. package/dist/plugins/rsc-client-reference-loaders.js +336 -18
  50. package/dist/plugins/use-cache-callable.d.ts +5 -0
  51. package/dist/plugins/use-cache-callable.js +30 -8
  52. package/dist/plugins/worker-image-imports.d.ts +1 -0
  53. package/dist/plugins/worker-image-imports.js +1 -1
  54. package/dist/routing/app-route-graph.d.ts +1 -1
  55. package/dist/routing/browser-route-manifest.d.ts +43 -0
  56. package/dist/routing/browser-route-manifest.js +195 -0
  57. package/dist/routing/hashed-route-patterns.d.ts +4 -0
  58. package/dist/routing/hashed-route-patterns.js +22 -0
  59. package/dist/routing/optimistic-route-trie.d.ts +27 -0
  60. package/dist/routing/optimistic-route-trie.js +102 -0
  61. package/dist/routing/route-validation.d.ts +6 -1
  62. package/dist/routing/route-validation.js +26 -10
  63. package/dist/server/app-action-forwarding.d.ts +8 -0
  64. package/dist/server/app-action-forwarding.js +15 -1
  65. package/dist/server/app-browser-entry.js +147 -60
  66. package/dist/server/app-browser-error.d.ts +3 -1
  67. package/dist/server/app-browser-error.js +7 -2
  68. package/dist/server/app-browser-interception-context.d.ts +2 -2
  69. package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
  70. package/dist/server/app-browser-mpa-navigation.js +6 -5
  71. package/dist/server/app-browser-navigation-controller.d.ts +17 -6
  72. package/dist/server/app-browser-navigation-controller.js +88 -26
  73. package/dist/server/app-browser-state.d.ts +2 -2
  74. package/dist/server/app-browser-visible-commit.d.ts +4 -4
  75. package/dist/server/app-client-reference-preloader.d.ts +23 -0
  76. package/dist/server/app-client-reference-preloader.js +30 -7
  77. package/dist/server/app-fallback-renderer.js +2 -1
  78. package/dist/server/app-middleware.d.ts +6 -0
  79. package/dist/server/app-middleware.js +8 -10
  80. package/dist/server/app-optimistic-routing.d.ts +8 -10
  81. package/dist/server/app-optimistic-routing.js +4 -95
  82. package/dist/server/app-page-boundary-render.js +8 -1
  83. package/dist/server/app-page-cache-render.js +1 -0
  84. package/dist/server/app-page-dispatch.js +2 -0
  85. package/dist/server/app-page-render.js +3 -1
  86. package/dist/server/app-page-rsc-render-status.d.ts +1 -0
  87. package/dist/server/app-page-rsc-render-status.js +1 -0
  88. package/dist/server/app-page-search-params-observation.d.ts +1 -4
  89. package/dist/server/app-page-search-params-observation.js +8 -10
  90. package/dist/server/app-page-stream.d.ts +7 -0
  91. package/dist/server/app-page-stream.js +1 -0
  92. package/dist/server/app-pages-bridge.d.ts +2 -1
  93. package/dist/server/app-pages-bridge.js +11 -7
  94. package/dist/server/app-prerender-static-params.js +3 -2
  95. package/dist/server/app-render-dependency.js +42 -2
  96. package/dist/server/app-request-stage-independent-entry.js +2 -2
  97. package/dist/server/app-response-stage-entry.js +3 -3
  98. package/dist/server/app-route-handler-dispatch.js +3 -1
  99. package/dist/server/app-route-handler-execution.d.ts +2 -0
  100. package/dist/server/app-route-handler-execution.js +9 -2
  101. package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
  102. package/dist/server/app-route-handler-middleware-context.js +3 -3
  103. package/dist/server/app-route-handler-response.d.ts +2 -1
  104. package/dist/server/app-route-handler-response.js +2 -2
  105. package/dist/server/app-route-handler-runtime.d.ts +2 -0
  106. package/dist/server/app-route-handler-runtime.js +20 -3
  107. package/dist/server/app-route-module-loader.js +7 -6
  108. package/dist/server/app-route-tree.d.ts +20 -0
  109. package/dist/server/app-route-tree.js +18 -0
  110. package/dist/server/app-router-entry.js +2 -2
  111. package/dist/server/app-rsc-combined-handler.js +8 -1
  112. package/dist/server/app-rsc-errors.d.ts +7 -0
  113. package/dist/server/app-rsc-errors.js +9 -2
  114. package/dist/server/app-rsc-handler.d.ts +4 -1
  115. package/dist/server/app-rsc-handler.js +146 -130
  116. package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
  117. package/dist/server/app-rsc-response-finalizer.js +4 -1
  118. package/dist/server/app-rsc-response-stage.js +3 -1
  119. package/dist/server/app-rsc-route-matching.d.ts +1 -0
  120. package/dist/server/app-rsc-route-matching.js +1 -1
  121. package/dist/server/app-server-action-execution.d.ts +23 -6
  122. package/dist/server/app-server-action-execution.js +50 -56
  123. package/dist/server/app-ssr-entry.d.ts +6 -0
  124. package/dist/server/app-ssr-entry.js +16 -7
  125. package/dist/server/app-ssr-stream.d.ts +7 -0
  126. package/dist/server/app-ssr-stream.js +13 -1
  127. package/dist/server/config-headers.js +12 -3
  128. package/dist/server/isolate-shared-promise.d.ts +10 -0
  129. package/dist/server/isolate-shared-promise.js +33 -0
  130. package/dist/server/isr-cache.js +3 -2
  131. package/dist/server/middleware-matcher-pattern.d.ts +0 -1
  132. package/dist/server/middleware-matcher-pattern.js +11 -140
  133. package/dist/server/middleware-matcher.js +5 -6
  134. package/dist/server/middleware-response-headers.d.ts +31 -0
  135. package/dist/server/middleware-response-headers.js +57 -1
  136. package/dist/server/middleware-runtime.js +26 -13
  137. package/dist/server/navigation-planner.d.ts +2 -2
  138. package/dist/server/nitro-public-files-plugin.d.ts +38 -0
  139. package/dist/server/nitro-public-files-plugin.js +106 -0
  140. package/dist/server/nitro-public-files.d.ts +21 -0
  141. package/dist/server/nitro-public-files.js +39 -0
  142. package/dist/server/pages-asset-tags.d.ts +5 -0
  143. package/dist/server/pages-asset-tags.js +9 -2
  144. package/dist/server/pages-client-assets.d.ts +4 -1
  145. package/dist/server/pages-i18n.d.ts +18 -0
  146. package/dist/server/pages-i18n.js +18 -2
  147. package/dist/server/pages-node-compat.d.ts +5 -3
  148. package/dist/server/pages-node-compat.js +35 -16
  149. package/dist/server/pages-request-pipeline.d.ts +6 -1
  150. package/dist/server/pages-request-pipeline.js +31 -35
  151. package/dist/server/pages-request-stage-entry.js +16 -6
  152. package/dist/server/pages-response-stage-entry.js +1 -1
  153. package/dist/server/pages-revalidate.js +1 -0
  154. package/dist/server/pages-router-entry.js +1 -1
  155. package/dist/server/prod-server.d.ts +2 -2
  156. package/dist/server/prod-server.js +71 -33
  157. package/dist/server/proxy-trust.d.ts +6 -0
  158. package/dist/server/proxy-trust.js +12 -6
  159. package/dist/server/request-pipeline.d.ts +5 -0
  160. package/dist/server/request-pipeline.js +12 -1
  161. package/dist/server/server-reference-loader.js +10 -1
  162. package/dist/server/static-file-cache.js +15 -3
  163. package/dist/server/worker-utils.js +1 -1
  164. package/dist/shims/cache-handler.js +3 -2
  165. package/dist/shims/cache-request-state.d.ts +40 -3
  166. package/dist/shims/cache-request-state.js +70 -5
  167. package/dist/shims/cache-runtime.js +47 -19
  168. package/dist/shims/cache.d.ts +2 -2
  169. package/dist/shims/cache.js +17 -8
  170. package/dist/shims/cdn-purge-routes.d.ts +24 -0
  171. package/dist/shims/cdn-purge-routes.js +77 -0
  172. package/dist/shims/dynamic.js +17 -4
  173. package/dist/shims/form.js +8 -3
  174. package/dist/shims/image-props.d.ts +93 -0
  175. package/dist/shims/image-props.js +323 -0
  176. package/dist/shims/image.d.ts +4 -18
  177. package/dist/shims/image.js +4 -319
  178. package/dist/shims/internal/headers-state.d.ts +7 -0
  179. package/dist/shims/internal/headers-state.js +14 -2
  180. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
  181. package/dist/shims/internal/hybrid-client-route-owner-direct.js +31 -14
  182. package/dist/shims/internal/hybrid-client-route-owner.js +8 -8
  183. package/dist/shims/link.js +5 -3
  184. package/dist/shims/navigation.d.ts +1 -1
  185. package/dist/shims/navigation.js +17 -5
  186. package/dist/shims/og.js +35 -10
  187. package/dist/shims/request-context.d.ts +13 -0
  188. package/dist/shims/router.js +19 -40
  189. package/dist/shims/server.d.ts +31 -4
  190. package/dist/shims/server.js +58 -10
  191. package/dist/shims/thenable-params.d.ts +6 -1
  192. package/dist/shims/thenable-params.js +11 -2
  193. package/dist/shims/unified-request-context.d.ts +5 -0
  194. package/dist/shims/unified-request-context.js +2 -0
  195. package/dist/utils/asset-prefix.d.ts +7 -0
  196. package/dist/utils/asset-prefix.js +10 -1
  197. package/dist/utils/client-build-manifest.js +12 -5
  198. package/dist/utils/client-runtime-metadata.d.ts +1 -0
  199. package/dist/utils/client-runtime-metadata.js +15 -2
  200. package/dist/utils/lazy-chunks.d.ts +4 -0
  201. package/dist/utils/manifest-paths.d.ts +14 -0
  202. package/dist/utils/manifest-paths.js +22 -1
  203. package/dist/utils/path-to-regexp.d.ts +21 -0
  204. package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
  205. package/dist/utils/plugin-react-version.d.ts +15 -0
  206. package/dist/utils/plugin-react-version.js +64 -0
  207. package/dist/utils/public-routes.d.ts +2 -0
  208. package/dist/utils/public-routes.js +18 -7
  209. package/package.json +1 -1
  210. package/dist/server/middleware-path-to-regexp.d.ts +0 -13
  211. package/dist/utils/regex-safety.d.ts +0 -17
  212. package/dist/utils/regex-safety.js +0 -789
@@ -0,0 +1,195 @@
1
+ import { buildOptimisticRouteTrie } from "./optimistic-route-trie.js";
2
+ //#region src/routing/browser-route-manifest.ts
3
+ function createRouteIdFromPatternParts(patternParts) {
4
+ return `route:/${patternParts.join("/")}`;
5
+ }
6
+ /**
7
+ * Route matching precedence of one pattern part, lower wins: the URL ending
8
+ * here, then a static, dynamic, catch-all and optional catch-all part.
9
+ */
10
+ function patternPartRank(part) {
11
+ if (part === void 0) return 0;
12
+ if (!part.startsWith(":")) return 1;
13
+ if (part.endsWith("+")) return 3;
14
+ if (part.endsWith("*")) return 4;
15
+ return 2;
16
+ }
17
+ /**
18
+ * Whether the router sends a URL pathname that both patterns match to the
19
+ * route handler: it wins at the first part whose kind differs. Two patterns
20
+ * that never differ would be the same route shape, which cannot hold both a
21
+ * handler and a page.
22
+ */
23
+ function routeHandlerOutranks(handler, rendered) {
24
+ for (let index = 0;; index++) {
25
+ const handlerRank = patternPartRank(handler[index]);
26
+ const renderedRank = patternPartRank(rendered[index]);
27
+ if (handlerRank !== renderedRank) return handlerRank < renderedRank;
28
+ if (handlerRank === 0 || handlerRank >= 3) return false;
29
+ }
30
+ }
31
+ /**
32
+ * Whether the optimistic trie of the rendered routes predicts a rendered
33
+ * route that the route handler outranks for some URL pathname the handler
34
+ * pattern matches. The trie match descends one path and only backtracks to a
35
+ * catch-all on its way up, so this follows that path for every URL: a param
36
+ * segment either names one of the node's static children or none of them,
37
+ * and a catch-all takes any number of such segments.
38
+ */
39
+ function triePredictsOutrankedRoute(trie, handler) {
40
+ const lastPart = handler.at(-1);
41
+ const isCatchAll = lastPart?.startsWith(":") === true && /[+*]$/.test(lastPart);
42
+ const fixedLength = isCatchAll ? handler.length - 1 : handler.length;
43
+ const minLength = lastPart?.endsWith("+") === true ? handler.length : fixedLength;
44
+ const predict = (route) => route === null ? {
45
+ outranked: false,
46
+ unmatched: true
47
+ } : {
48
+ outranked: routeHandlerOutranks(handler, route.patternParts),
49
+ unmatched: false
50
+ };
51
+ const afterOtherSegment = (node, depth) => {
52
+ const dynamic = node.dynamicChild ? predictFrom(node.dynamicChild.node, depth) : {
53
+ outranked: false,
54
+ unmatched: true
55
+ };
56
+ if (!dynamic.unmatched) return dynamic;
57
+ const fallback = predict(node.catchAllChild?.route ?? node.optionalCatchAllChild?.route ?? null);
58
+ return {
59
+ outranked: dynamic.outranked || fallback.outranked,
60
+ unmatched: fallback.unmatched
61
+ };
62
+ };
63
+ const predictFrom = (node, depth) => {
64
+ const predictions = {
65
+ outranked: false,
66
+ unmatched: false
67
+ };
68
+ const add = (more) => {
69
+ predictions.outranked ||= more.outranked;
70
+ predictions.unmatched ||= more.unmatched;
71
+ };
72
+ if (isCatchAll ? depth >= minLength : depth === fixedLength) add(predict(node.route ?? node.optionalCatchAllChild?.route ?? null));
73
+ if (depth < fixedLength && !handler[depth].startsWith(":")) {
74
+ const child = node.staticChildren.get(handler[depth]);
75
+ add(child ? predictFrom(child, depth + 1) : afterOtherSegment(node, depth + 1));
76
+ } else if (depth < fixedLength || isCatchAll) {
77
+ for (const child of node.staticChildren.values()) add(predictFrom(child, depth + 1));
78
+ add(afterOtherSegment(node, depth + 1));
79
+ }
80
+ return predictions;
81
+ };
82
+ return predictFrom(trie, 0).outranked;
83
+ }
84
+ /**
85
+ * The browser only needs a route handler where the optimistic route trie of
86
+ * the rendered routes would predict a route the handler outranks: the matcher
87
+ * must find the handler there instead of rendering that route's shell. Any
88
+ * other handler URL matches no rendered route on the client, or one the
89
+ * router prefers, and its non-Flight response already turns the navigation
90
+ * into a document load, so shipping its path would only publish the app's
91
+ * API surface (issue #3624).
92
+ */
93
+ function selectBrowserRoutes(routes) {
94
+ const allRoutes = [...routes];
95
+ const renderedTrie = buildOptimisticRouteTrie(allRoutes.filter((route) => route.routeHandlerId === null));
96
+ return allRoutes.filter((route) => route.routeHandlerId === null || triePredictsOutrankedRoute(renderedTrie, route.patternParts));
97
+ }
98
+ /**
99
+ * The graph records the browser receives: the shipped routes, the rendered
100
+ * routes' slot bindings, and the layouts and root boundaries those and the
101
+ * interceptions reach. A shipped route handler only stops an optimistic
102
+ * match, and its non-Flight response never renders a tree, so it ships as a
103
+ * bare pattern; its layouts, slots and root boundary would only name its
104
+ * directories.
105
+ */
106
+ function selectBrowserGraph(graph) {
107
+ const routes = selectBrowserRoutes(graph.routes.values()).map((route) => route.routeHandlerId === null ? route : {
108
+ ...route,
109
+ layoutIds: [],
110
+ rootBoundaryId: null,
111
+ slotIds: []
112
+ });
113
+ const renderedRouteIds = new Set(routes.filter((route) => route.routeHandlerId === null).map((route) => route.id));
114
+ const slotBindings = [...graph.slotBindings.values()].filter((binding) => renderedRouteIds.has(binding.routeId));
115
+ const layoutIds = /* @__PURE__ */ new Set([
116
+ ...routes.flatMap((route) => route.layoutIds),
117
+ ...slotBindings.flatMap((binding) => binding.ownerLayoutId ?? []),
118
+ ...[...graph.interceptions.values()].flatMap((interception) => interception.ownerLayoutId ?? [])
119
+ ]);
120
+ const layouts = [...graph.layouts.values()].filter((layout) => layoutIds.has(layout.id));
121
+ const rootBoundaryIds = new Set([...routes, ...layouts].flatMap((record) => record.rootBoundaryId ?? []));
122
+ return {
123
+ routes,
124
+ layouts,
125
+ slotBindings,
126
+ rootBoundaries: [...graph.rootBoundaries.values()].filter((rootBoundary) => rootBoundaryIds.has(rootBoundary.id))
127
+ };
128
+ }
129
+ function toBrowserRouteManifestWire(routeManifest) {
130
+ const graph = routeManifest.segmentGraph;
131
+ const browserGraph = selectBrowserGraph(graph);
132
+ return {
133
+ graphVersion: routeManifest.graphVersion,
134
+ routes: browserGraph.routes.map((route) => ({
135
+ ...route.id === createRouteIdFromPatternParts(route.patternParts) ? {} : { id: route.id },
136
+ patternParts: route.patternParts,
137
+ isDynamic: route.isDynamic,
138
+ paramNames: route.paramNames,
139
+ rootBoundaryId: route.rootBoundaryId,
140
+ pageId: route.pageId,
141
+ layoutIds: route.layoutIds,
142
+ slotIds: route.slotIds
143
+ })),
144
+ layouts: browserGraph.layouts.map((layout) => ({
145
+ id: layout.id,
146
+ patternParts: layout.patternParts,
147
+ rootBoundaryId: layout.rootBoundaryId
148
+ })),
149
+ slotBindings: browserGraph.slotBindings.map((binding) => ({
150
+ id: binding.id,
151
+ routeId: binding.routeId,
152
+ slotId: binding.slotId,
153
+ ownerLayoutId: binding.ownerLayoutId,
154
+ state: binding.state,
155
+ routeSegments: binding.routeSegments,
156
+ slotPatternParts: binding.slotPatternParts,
157
+ slotParamNames: binding.slotParamNames
158
+ })),
159
+ interceptions: Array.from(graph.interceptions.values(), (interception) => ({
160
+ id: interception.id,
161
+ sourcePatternParts: interception.sourcePatternParts,
162
+ targetPatternParts: interception.targetPatternParts,
163
+ slotId: interception.slotId,
164
+ ownerLayoutId: interception.ownerLayoutId,
165
+ targetRouteId: interception.targetRouteId
166
+ })),
167
+ interceptionsBySlotId: Array.from(graph.interceptionsBySlotId, ([slotId, interceptions]) => [slotId, interceptions.map((interception) => interception.id)]),
168
+ rootBoundaries: browserGraph.rootBoundaries.map((rootBoundary) => ({
169
+ id: rootBoundary.id,
170
+ treePath: rootBoundary.treePath
171
+ }))
172
+ };
173
+ }
174
+ function mapById(values) {
175
+ return new Map(values.map((value) => [value.id, value]));
176
+ }
177
+ function decodeBrowserRouteManifest(wire) {
178
+ const interceptions = mapById(wire.interceptions);
179
+ return {
180
+ graphVersion: wire.graphVersion,
181
+ segmentGraph: {
182
+ routes: mapById(wire.routes.map((route) => ({
183
+ ...route,
184
+ id: route.id ?? createRouteIdFromPatternParts(route.patternParts)
185
+ }))),
186
+ layouts: mapById(wire.layouts),
187
+ slotBindings: mapById(wire.slotBindings),
188
+ interceptions,
189
+ interceptionsBySlotId: new Map(wire.interceptionsBySlotId.map(([slotId, interceptionIds]) => [slotId, interceptionIds.flatMap((id) => interceptions.get(id) ?? [])])),
190
+ rootBoundaries: mapById(wire.rootBoundaries)
191
+ }
192
+ };
193
+ }
194
+ //#endregion
195
+ export { decodeBrowserRouteManifest, toBrowserRouteManifestWire };
@@ -0,0 +1,4 @@
1
+ //#region src/routing/hashed-route-patterns.d.ts
2
+ export declare function hashRoutePatternParts(patternParts: readonly string[]): string[];
3
+ export declare function hashRoutePathname(pathname: string): string;
4
+ //#endregion
@@ -0,0 +1,22 @@
1
+ import { splitPathnameForRouteMatch } from "./utils.js";
2
+ import { fnv1a64 } from "../utils/hash.js";
3
+ //#region src/routing/hashed-route-patterns.ts
4
+ /**
5
+ * Hybrid App/Pages clients need App route handler patterns only to decide
6
+ * route ownership, so they receive them with every static segment hashed and
7
+ * never see the handlers' paths in plain text (issue #3624). Dynamic parts
8
+ * stay as they are, so a hashed pattern matches a hashed URL with the usual
9
+ * route trie. A collision can only add a false handler match, which sends the
10
+ * navigation to the document.
11
+ */
12
+ function hashRouteSegment(segment) {
13
+ return fnv1a64(segment).slice(0, 8);
14
+ }
15
+ function hashRoutePatternParts(patternParts) {
16
+ return patternParts.map((part) => part.startsWith(":") ? part : hashRouteSegment(part));
17
+ }
18
+ function hashRoutePathname(pathname) {
19
+ return "/" + splitPathnameForRouteMatch(pathname).map(hashRouteSegment).join("/");
20
+ }
21
+ //#endregion
22
+ export { hashRoutePathname, hashRoutePatternParts };
@@ -0,0 +1,27 @@
1
+ //#region src/routing/optimistic-route-trie.d.ts
2
+ type OptimisticTrieRoute = {
3
+ readonly patternParts: readonly string[];
4
+ };
5
+ export type OptimisticRouteTrieNode<Route extends OptimisticTrieRoute> = {
6
+ catchAllChild: {
7
+ paramName: string;
8
+ route: Route;
9
+ } | null;
10
+ dynamicChild: {
11
+ node: OptimisticRouteTrieNode<Route>;
12
+ paramName: string;
13
+ } | null;
14
+ optionalCatchAllChild: {
15
+ paramName: string;
16
+ route: Route;
17
+ } | null;
18
+ route: Route | null;
19
+ staticChildren: Map<string, OptimisticRouteTrieNode<Route>>;
20
+ };
21
+ export type OptimisticRouteTrieMatch<Route extends OptimisticTrieRoute> = {
22
+ params: Record<string, string | string[]>;
23
+ route: Route;
24
+ };
25
+ export declare function buildOptimisticRouteTrie<Route extends OptimisticTrieRoute>(routes: Iterable<Route>, onDynamicSegmentConflict?: (existingParamName: string, part: string) => void): OptimisticRouteTrieNode<Route>;
26
+ export declare function matchOptimisticRouteTrie<Route extends OptimisticTrieRoute>(trie: OptimisticRouteTrieNode<Route>, urlParts: readonly string[]): OptimisticRouteTrieMatch<Route> | null;
27
+ //#endregion
@@ -0,0 +1,102 @@
1
+ import { buildParams } from "./utils.js";
2
+ //#region src/routing/optimistic-route-trie.ts
3
+ function createNode() {
4
+ return {
5
+ catchAllChild: null,
6
+ dynamicChild: null,
7
+ optionalCatchAllChild: null,
8
+ route: null,
9
+ staticChildren: /* @__PURE__ */ new Map()
10
+ };
11
+ }
12
+ function buildOptimisticRouteTrie(routes, onDynamicSegmentConflict) {
13
+ const root = createNode();
14
+ for (const route of routes) {
15
+ let node = root;
16
+ const parts = route.patternParts;
17
+ if (parts.length === 0) {
18
+ node.route ??= route;
19
+ continue;
20
+ }
21
+ for (const [index, part] of parts.entries()) {
22
+ const isTerminal = index === parts.length - 1;
23
+ if (part.startsWith(":") && part.endsWith("+")) {
24
+ if (isTerminal && node.catchAllChild === null) node.catchAllChild = {
25
+ paramName: part.slice(1, -1),
26
+ route
27
+ };
28
+ break;
29
+ }
30
+ if (part.startsWith(":") && part.endsWith("*")) {
31
+ if (isTerminal && node.optionalCatchAllChild === null) node.optionalCatchAllChild = {
32
+ paramName: part.slice(1, -1),
33
+ route
34
+ };
35
+ break;
36
+ }
37
+ if (part.startsWith(":")) {
38
+ const paramName = part.slice(1);
39
+ if (node.dynamicChild === null) node.dynamicChild = {
40
+ node: createNode(),
41
+ paramName
42
+ };
43
+ else if (node.dynamicChild.paramName !== paramName) onDynamicSegmentConflict?.(node.dynamicChild.paramName, part);
44
+ node = node.dynamicChild.node;
45
+ if (isTerminal) node.route ??= route;
46
+ continue;
47
+ }
48
+ let staticChild = node.staticChildren.get(part);
49
+ if (staticChild === void 0) {
50
+ staticChild = createNode();
51
+ node.staticChildren.set(part, staticChild);
52
+ }
53
+ node = staticChild;
54
+ if (isTerminal) node.route ??= route;
55
+ }
56
+ }
57
+ return root;
58
+ }
59
+ function matchNode(node, urlParts, index, entries) {
60
+ if (index === urlParts.length) {
61
+ if (node.route !== null) return {
62
+ route: node.route,
63
+ params: buildParams(entries)
64
+ };
65
+ if (node.optionalCatchAllChild !== null) return {
66
+ route: node.optionalCatchAllChild.route,
67
+ params: buildParams(entries)
68
+ };
69
+ return null;
70
+ }
71
+ const segment = urlParts[index];
72
+ const staticChild = node.staticChildren.get(segment);
73
+ if (staticChild !== void 0) return matchNode(staticChild, urlParts, index + 1, entries);
74
+ if (node.dynamicChild !== null) {
75
+ entries.push([node.dynamicChild.paramName, segment]);
76
+ const match = matchNode(node.dynamicChild.node, urlParts, index + 1, entries);
77
+ if (match !== null) return match;
78
+ entries.pop();
79
+ }
80
+ if (node.catchAllChild !== null) {
81
+ const params = buildParams(entries);
82
+ params[node.catchAllChild.paramName] = urlParts.slice(index);
83
+ return {
84
+ route: node.catchAllChild.route,
85
+ params
86
+ };
87
+ }
88
+ if (node.optionalCatchAllChild !== null) {
89
+ const params = buildParams(entries);
90
+ params[node.optionalCatchAllChild.paramName] = urlParts.slice(index);
91
+ return {
92
+ route: node.optionalCatchAllChild.route,
93
+ params
94
+ };
95
+ }
96
+ return null;
97
+ }
98
+ function matchOptimisticRouteTrie(trie, urlParts) {
99
+ return matchNode(trie, urlParts, 0, []);
100
+ }
101
+ //#endregion
102
+ export { buildOptimisticRouteTrie, matchOptimisticRouteTrie };
@@ -1,9 +1,14 @@
1
1
  //#region src/routing/route-validation.d.ts
2
2
  /**
3
- * Dynamic route validation adapted from Next.js' sorted-routes implementation.
3
+ * Dynamic route validation and sorting adapted from Next.js' sorted-routes implementation.
4
4
  * Source:
5
5
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/sorted-routes.ts
6
6
  */
7
+ /**
8
+ * Sort Next.js-format page paths (e.g. "/posts/[id]") into route-priority
9
+ * order, matching Next.js' `getSortedRoutes`.
10
+ */
11
+ export declare function getSortedRoutes(pages: readonly string[]): string[];
7
12
  export declare function patternToNextFormat(pattern: string): string;
8
13
  export declare function validateRoutePatterns(patterns: readonly string[]): void;
9
14
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { removeTrailingSlash } from "../utils/base-path.js";
2
2
  //#region src/routing/route-validation.ts
3
3
  /**
4
- * Dynamic route validation adapted from Next.js' sorted-routes implementation.
4
+ * Dynamic route validation and sorting adapted from Next.js' sorted-routes implementation.
5
5
  * Source:
6
6
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/sorted-routes.ts
7
7
  */
@@ -69,17 +69,33 @@ var UrlNode = class UrlNode {
69
69
  }
70
70
  child.insertSegments(urlPaths.slice(1), slugNames, isCatchAll);
71
71
  }
72
- assertOptionalCatchAllSpecificity(prefix = "/") {
73
- if (!this.placeholder && this.optionalRestSlugName !== null) {
72
+ /**
73
+ * Flatten the tree into route-priority order: static children
74
+ * alphabetically, then the dynamic, catch-all and optional catch-all
75
+ * children. Throws when a route shares a level with an optional catch-all.
76
+ */
77
+ smoosh(prefix = "/") {
78
+ const routes = [...this.children.keys()].filter((segment) => segment !== "[]" && segment !== "[...]" && segment !== "[[...]]").sort().flatMap((segment) => this.children.get(segment).smoosh(`${prefix}${segment}/`));
79
+ if (this.slugName !== null) routes.push(...this.children.get("[]").smoosh(`${prefix}[${this.slugName}]/`));
80
+ if (!this.placeholder) {
74
81
  const route = prefix === "/" ? "/" : prefix.slice(0, -1);
75
- throw new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${route}" and "${route}[[...${this.optionalRestSlugName}]]").`);
76
- }
77
- for (const [segment, child] of this.children) {
78
- const nextPrefixSegment = segment === "[]" ? `[${this.slugName}]` : segment === "[...]" ? `[...${this.restSlugName}]` : segment === "[[...]]" ? `[[...${this.optionalRestSlugName}]]` : segment;
79
- child.assertOptionalCatchAllSpecificity(`${prefix}${nextPrefixSegment}/`);
82
+ if (this.optionalRestSlugName !== null) throw new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${route}" and "${route}[[...${this.optionalRestSlugName}]]").`);
83
+ routes.unshift(route);
80
84
  }
85
+ if (this.restSlugName !== null) routes.push(...this.children.get("[...]").smoosh(`${prefix}[...${this.restSlugName}]/`));
86
+ if (this.optionalRestSlugName !== null) routes.push(...this.children.get("[[...]]").smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`));
87
+ return routes;
81
88
  }
82
89
  };
90
+ /**
91
+ * Sort Next.js-format page paths (e.g. "/posts/[id]") into route-priority
92
+ * order, matching Next.js' `getSortedRoutes`.
93
+ */
94
+ function getSortedRoutes(pages) {
95
+ const root = new UrlNode();
96
+ for (const page of pages) root.insert(page);
97
+ return root.smoosh();
98
+ }
83
99
  function patternToNextFormat(pattern) {
84
100
  if (pattern === "/") return "/";
85
101
  return pattern.split("/").map((segment) => {
@@ -102,7 +118,7 @@ function validateRoutePatterns(patterns) {
102
118
  seenPatterns.add(normalizedPattern);
103
119
  root.insert(patternToNextFormat(normalizedPattern));
104
120
  }
105
- root.assertOptionalCatchAllSpecificity();
121
+ root.smoosh();
106
122
  }
107
123
  //#endregion
108
- export { patternToNextFormat, validateRoutePatterns };
124
+ export { getSortedRoutes, patternToNextFormat, validateRoutePatterns };
@@ -1,3 +1,4 @@
1
+ import { ExecutionContextLike } from "../shims/request-context.js";
1
2
  import { AppMiddlewareContext } from "./app-middleware.js";
2
3
  //#region src/server/app-action-forwarding.d.ts
3
4
  type ActionOwnerManifest = Readonly<Record<string, readonly string[]>>;
@@ -13,5 +14,12 @@ type ForwardServerActionOptions = {
13
14
  request: Request;
14
15
  };
15
16
  export declare function areServerActionsOwnedByRoute(actionOwners: ActionOwnerManifest | null, actionIds: readonly string[], currentRoutePattern: string | null): boolean;
17
+ /**
18
+ * The execution context a forwarded action re-enters the app handler with.
19
+ * Middleware runs again there, so it keeps the server-owned middleware origin.
20
+ */
21
+ export declare function createForwardedActionContext(executionContext: ExecutionContextLike | null | undefined): {
22
+ actionForwarded: true;
23
+ } & Partial<ExecutionContextLike>;
16
24
  export declare function forwardServerActionIfNeeded(options: ForwardServerActionOptions): Promise<Response | null>;
17
25
  //#endregion
@@ -98,6 +98,20 @@ function areServerActionsOwnedByRoute(actionOwners, actionIds, currentRoutePatte
98
98
  return ownerPatterns?.includes("*") === true || currentRoutePattern !== null && ownerPatterns?.includes(currentRoutePattern) === true;
99
99
  });
100
100
  }
101
+ /**
102
+ * The execution context a forwarded action re-enters the app handler with.
103
+ * Middleware runs again there, so it keeps the server-owned middleware origin.
104
+ */
105
+ function createForwardedActionContext(executionContext) {
106
+ if (!executionContext) return { actionForwarded: true };
107
+ return {
108
+ actionForwarded: true,
109
+ cache: executionContext.cache,
110
+ middlewareOrigin: executionContext.middlewareOrigin,
111
+ passThroughOnException: executionContext.passThroughOnException?.bind(executionContext),
112
+ waitUntil: executionContext.waitUntil.bind(executionContext)
113
+ };
114
+ }
101
115
  async function forwardServerActionIfNeeded(options) {
102
116
  if (!options.actionOwners) return null;
103
117
  const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
@@ -137,4 +151,4 @@ async function forwardServerActionIfNeeded(options) {
137
151
  return emptyActionForwardResponse(options.middlewareContext);
138
152
  }
139
153
  //#endregion
140
- export { areServerActionsOwnedByRoute, forwardServerActionIfNeeded };
154
+ export { areServerActionsOwnedByRoute, createForwardedActionContext, forwardServerActionIfNeeded };