vinext 0.1.0 → 0.1.2

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 (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -0,0 +1,70 @@
1
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildCachedRevalidateCacheControl, buildRevalidateCacheControl } from "./cache-control.js";
2
+ //#region src/server/isr-decision.ts
3
+ /** Resolve effective revalidate/expire, preferring per-entry metadata. */
4
+ function resolveRevalidate(options) {
5
+ return {
6
+ effectiveRevalidate: options.cacheControlMeta?.revalidate ?? options.revalidateSeconds,
7
+ effectiveExpire: options.cacheControlMeta === void 0 ? void 0 : options.cacheControlMeta.expire ?? options.expireSeconds
8
+ };
9
+ }
10
+ function buildCacheControl(disposition, kind, revalidate, expire) {
11
+ if (kind === "app-route") {
12
+ if (revalidate === 0) return NEVER_CACHE_CONTROL;
13
+ if (revalidate === Infinity) return STATIC_CACHE_CONTROL;
14
+ }
15
+ return buildCachedRevalidateCacheControl(disposition, revalidate, expire);
16
+ }
17
+ /**
18
+ * Derive the `Cache-Control` string for an ISR response.
19
+ *
20
+ * Content guards (kind mismatch, query-variant-unproven, empty body) are the
21
+ * caller's responsibility and must happen *before* this call. `cacheState`
22
+ * must only be `"HIT"` or `"STALE"` when those guards have already passed.
23
+ */
24
+ function decideIsr(options) {
25
+ if (options.cacheState === "MISS") return {
26
+ disposition: "MISS",
27
+ scheduleRegeneration: false,
28
+ cacheControl: ""
29
+ };
30
+ const { effectiveRevalidate, effectiveExpire } = resolveRevalidate(options);
31
+ if (options.cacheState === "HIT") return {
32
+ disposition: "HIT",
33
+ scheduleRegeneration: false,
34
+ cacheControl: buildCacheControl("HIT", options.kind, effectiveRevalidate, effectiveExpire)
35
+ };
36
+ return {
37
+ disposition: "STALE",
38
+ scheduleRegeneration: true,
39
+ cacheControl: buildCacheControl("STALE", options.kind, effectiveRevalidate, effectiveExpire)
40
+ };
41
+ }
42
+ /**
43
+ * Build the `Cache-Control` string for a fresh MISS response whose ISR policy
44
+ * is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
45
+ * no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
46
+ *
47
+ * Separate from `decideIsr` because a MISS doesn't read a cache entry and
48
+ * therefore never has `cacheControlMeta`. `expireSeconds` here is the route
49
+ * config ceiling passed directly from the caller (not a per-entry fallback).
50
+ */
51
+ function buildMissIsrCacheControl(revalidateSeconds, expireSeconds) {
52
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
53
+ }
54
+ /**
55
+ * Build the `Cache-Control` string for a fresh (MISS) app-route response.
56
+ *
57
+ * Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
58
+ * that `decideIsr` uses for app-route cached responses. `expireSeconds` is
59
+ * the route config ceiling passed directly (not per-entry metadata fallback).
60
+ *
61
+ * Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
62
+ * response that has no per-entry cache metadata.
63
+ */
64
+ function buildAppRouteMissIsrCacheControl(revalidateSeconds, expireSeconds) {
65
+ if (revalidateSeconds === 0) return NEVER_CACHE_CONTROL;
66
+ if (revalidateSeconds === Infinity) return STATIC_CACHE_CONTROL;
67
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
68
+ }
69
+ //#endregion
70
+ export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
@@ -53,9 +53,9 @@ function getMetadataRouteFunctions(route) {
53
53
  routeFunctionCache.set(route, functions);
54
54
  return functions;
55
55
  }
56
- function matchMetadataRoute(route, cleanPathname, functions) {
56
+ function matchMetadataRoute(route, cleanPathname, functions, getUrlParts) {
57
57
  if (route.patternParts) {
58
- const urlParts = cleanPathname.split("/").filter(Boolean);
58
+ const urlParts = getUrlParts();
59
59
  if (functions.hasGeneratedImageMetadata && urlParts.length > 0) {
60
60
  const params = matchMetadataRoutePattern(urlParts.slice(0, -1), route.patternParts);
61
61
  if (params) return {
@@ -184,6 +184,8 @@ function serveStaticMetadataRoute(route) {
184
184
  }
185
185
  }
186
186
  async function handleMetadataRouteRequest(options) {
187
+ let urlParts;
188
+ const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
187
189
  for (const route of options.metadataRoutes) {
188
190
  const functions = getMetadataRouteFunctions(route);
189
191
  if (route.type === "sitemap" && route.isDynamic) {
@@ -193,7 +195,7 @@ async function handleMetadataRouteRequest(options) {
193
195
  continue;
194
196
  }
195
197
  }
196
- const match = matchMetadataRoute(route, options.cleanPathname, functions);
198
+ const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
197
199
  if (!match) continue;
198
200
  return route.isDynamic ? callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route);
199
201
  }
@@ -19,6 +19,19 @@ type MiddlewareHandler = (request: NextRequest, event: NextFetchEvent) => Respon
19
19
  type ExecuteMiddlewareOptions = {
20
20
  basePath?: string;
21
21
  filePath?: string;
22
+ /**
23
+ * Whether the incoming request was inside the configured basePath. Drives
24
+ * the `nextUrl.basePath` the middleware observes: in-basePath requests are
25
+ * re-prefixed so NextURL reports the configured basePath, while
26
+ * out-of-basePath ("absolute path") requests stay un-prefixed so middleware
27
+ * sees `nextUrl.basePath === ""` (Next.js `getNextPathnameInfo` semantics —
28
+ * see test/e2e/middleware-base-path "should execute from absolute paths").
29
+ * When omitted it is derived from the request URL, which is correct for the
30
+ * Pages prod/deploy adapters because they pass the original (un-stripped)
31
+ * URL. Callers that pass an already-stripped URL (dev server, App Router)
32
+ * must set this explicitly.
33
+ */
34
+ hadBasePath?: boolean;
22
35
  i18nConfig?: NextI18nConfig | null;
23
36
  includeErrorDetails?: boolean;
24
37
  /**
@@ -1,4 +1,5 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
+ import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
2
3
  import "./server-globals.js";
3
4
  import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
4
5
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
@@ -99,12 +100,13 @@ function resolveMiddlewarePathname(request) {
99
100
  return badRequestResponse();
100
101
  }
101
102
  }
102
- function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash) {
103
+ function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash, hadBasePath) {
103
104
  const url = new URL(request.url);
104
105
  let mwRequest = request.body && !request.bodyUsed ? request.clone() : request;
105
- if (normalizedPathname !== url.pathname) {
106
+ const mwPathname = basePath && hadBasePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
107
+ if (mwPathname !== url.pathname) {
106
108
  const mwUrl = new URL(url);
107
- mwUrl.pathname = normalizedPathname;
109
+ mwUrl.pathname = mwPathname;
108
110
  mwRequest = new Request(mwUrl, mwRequest);
109
111
  }
110
112
  const nextConfig = basePath || i18nConfig || trailingSlash ? {
@@ -124,9 +126,11 @@ async function executeMiddleware(options) {
124
126
  continue: false,
125
127
  response: normalizedPathname
126
128
  };
127
- if (!matchesMiddleware(normalizedPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
128
- const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash);
129
- const fetchEvent = new NextFetchEvent({ page: normalizedPathname });
129
+ const hadBasePath = options.hadBasePath ?? (!options.basePath || hasBasePath(new URL(options.request.url).pathname, options.basePath));
130
+ const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
131
+ if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
132
+ const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
133
+ const fetchEvent = new NextFetchEvent({ page: matchPathname });
130
134
  let response;
131
135
  try {
132
136
  response = await middlewareFn(nextRequest, fetchEvent);
@@ -195,7 +199,7 @@ async function executeMiddleware(options) {
195
199
  try {
196
200
  const rewriteParsed = new URL(rewriteUrl, options.request.url);
197
201
  const requestOrigin = new URL(options.request.url).origin;
198
- if (rewriteParsed.origin === requestOrigin) rewritePath = rewriteParsed.pathname + rewriteParsed.search;
202
+ if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
199
203
  else rewritePath = rewriteParsed.href;
200
204
  } catch {
201
205
  rewritePath = rewriteUrl;
@@ -85,6 +85,7 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
85
85
  return runGeneratedMiddleware({
86
86
  basePath,
87
87
  filePath: middlewarePath,
88
+ hadBasePath: true,
88
89
  i18nConfig,
89
90
  includeErrorDetails: process.env.NODE_ENV !== "production",
90
91
  isDataRequest,
@@ -114,6 +114,63 @@ type FlightResultV0 = {
114
114
  href: string;
115
115
  targetSnapshot: RouteSnapshotV0;
116
116
  };
117
+ type RscFetchResultSource = "cached" | "live";
118
+ type RscFetchResultFactsV0 = {
119
+ source: RscFetchResultSource;
120
+ currentHref: string;
121
+ origin: string;
122
+ effectiveHistoryUpdateMode: "push" | "replace";
123
+ redirectDepth: number;
124
+ requestPreviousNextUrl: string | null;
125
+ clientCompatibilityId: string | null;
126
+ responseOk: boolean;
127
+ isRscContentType: boolean;
128
+ hasBody: boolean;
129
+ compatibilityIdHeader: string | null;
130
+ responseUrl: string | null;
131
+ streamedRedirectTarget: string | null;
132
+ };
133
+ type RscRedirectFollowV0 = {
134
+ href: string;
135
+ historyUpdateMode: "push" | "replace";
136
+ previousNextUrl: string | null;
137
+ redirectDepth: number;
138
+ };
139
+ type RscFetchResultHardNavReason = "invalidRscPayload" | "rscCompatibilityMismatch" | "externalRedirectTarget" | "redirectDepthExhausted" | "streamedRedirectLoop";
140
+ type RscFetchResultDecisionV0 = {
141
+ kind: "proceedToCommit";
142
+ discardBody: false;
143
+ trace: NavigationTrace;
144
+ } | {
145
+ kind: "followRedirect";
146
+ discardBody: boolean;
147
+ redirect: RscRedirectFollowV0;
148
+ trace: NavigationTrace;
149
+ } | {
150
+ kind: "hardNavigate";
151
+ discardBody: boolean;
152
+ url: string;
153
+ reason: RscFetchResultHardNavReason;
154
+ trace: NavigationTrace;
155
+ };
156
+ type EarlyNavigationIntentFactsV0 = {
157
+ basePath: string;
158
+ currentHref: string;
159
+ mode: "push" | "replace";
160
+ scroll: boolean;
161
+ targetHref: string;
162
+ };
163
+ type EarlyNavigationIntentDecisionV0 = {
164
+ kind: "sameDocumentScroll";
165
+ hash: string;
166
+ mode: "push" | "replace";
167
+ scroll: boolean;
168
+ trace: NavigationTrace;
169
+ } | {
170
+ kind: "flightNavigation";
171
+ bypassNavigationCache: boolean;
172
+ trace: NavigationTrace;
173
+ };
117
174
  type NavigationPlannerInput = {
118
175
  routeManifest: RouteManifest | null;
119
176
  state: NavigationPlannerStateV0;
@@ -122,6 +179,8 @@ type NavigationPlannerInput = {
122
179
  type AcceptedCacheEntryReuseDecision = Extract<CacheEntryReuseDecision, {
123
180
  canReuse: true;
124
181
  }>;
182
+ declare function classifyRscFetchResult(facts: RscFetchResultFactsV0): RscFetchResultDecisionV0;
183
+ declare function classifyEarlyNavigationIntent(facts: EarlyNavigationIntentFactsV0): EarlyNavigationIntentDecisionV0;
125
184
  declare function classifyRootBoundaryTransition(currentRootBoundaryId: string | null, nextRootBoundaryId: string | null): RootBoundaryTransition;
126
185
  declare function resolveSameLayoutAncestorPersistence(currentSnapshot: RouteSnapshotV0, targetSnapshot: RouteSnapshotV0): readonly string[];
127
186
  declare function resolveMountedParallelSlotPersistence(currentSnapshot: RouteSnapshotV0, targetSnapshot: RouteSnapshotV0): readonly string[];
@@ -143,6 +202,8 @@ declare function resolveDefaultOrUnmatchedSlotPersistenceForLayouts(options: {
143
202
  }): readonly string[];
144
203
  declare function planNavigation(input: NavigationPlannerInput): NavigationDecisionV0;
145
204
  declare const navigationPlanner: {
205
+ classifyEarlyNavigationIntent: typeof classifyEarlyNavigationIntent;
206
+ classifyRscFetchResult: typeof classifyRscFetchResult;
146
207
  classifyRootBoundaryTransition: typeof classifyRootBoundaryTransition;
147
208
  plan: typeof planNavigation;
148
209
  resolveCurrentRootBoundaryElementPersistence: typeof resolveCurrentRootBoundaryElementPersistence;
@@ -150,4 +211,4 @@ declare const navigationPlanner: {
150
211
  resolveSameLayoutAncestorPersistence: typeof resolveSameLayoutAncestorPersistence;
151
212
  };
152
213
  //#endregion
153
- export { FlightResultV0, InterceptionSnapshotV0, MountedParallelSlotSnapshotV0, NavigationDecisionV0, NavigationEvent, NavigationPlannerInput, NavigationPlannerStateV0, OperationLane, OperationToken, ParallelSlotBindingSnapshotV0, RefreshScope, RootBoundaryTransition, RouteSnapshotV0, TraverseDirection, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
214
+ export { EarlyNavigationIntentDecisionV0, EarlyNavigationIntentFactsV0, FlightResultV0, InterceptionSnapshotV0, MountedParallelSlotSnapshotV0, NavigationDecisionV0, NavigationEvent, NavigationPlannerInput, NavigationPlannerStateV0, OperationLane, OperationToken, ParallelSlotBindingSnapshotV0, RefreshScope, RootBoundaryTransition, RouteSnapshotV0, RscFetchResultDecisionV0, RscFetchResultFactsV0, TraverseDirection, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
@@ -1,7 +1,10 @@
1
1
  import { splitPathnameForRouteMatch } from "../routing/utils.js";
2
- import { matchRoutePattern, matchRoutePatternPrefix } from "../routing/route-pattern.js";
2
+ import { stripBasePath } from "../utils/base-path.js";
3
+ import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
3
4
  import { compareAppElementsSlotIds } from "./app-elements-wire.js";
4
5
  import "./app-elements.js";
6
+ import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
7
+ import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
5
8
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
6
9
  //#region src/server/navigation-planner.ts
7
10
  const ROUTE_INTERCEPTION_CONTEXT_SEPARATOR = "\0";
@@ -27,6 +30,189 @@ function getRequestedWorkTargetHref(work) {
27
30
  default: throw new Error("[vinext] Unknown requested navigation work: " + String(work));
28
31
  }
29
32
  }
33
+ function createRscFetchResultTraceFields(facts, fields = {}) {
34
+ return {
35
+ fetchResultSource: facts.source,
36
+ ...fields
37
+ };
38
+ }
39
+ function createRscFetchResultHardNavigationDecision(options) {
40
+ return {
41
+ discardBody: options.discardBody,
42
+ kind: "hardNavigate",
43
+ reason: options.reason,
44
+ trace: createNavigationTrace(options.reasonCode, createRscFetchResultTraceFields(options.facts, {
45
+ ...options.redirectSignal !== void 0 ? { redirectSignal: options.redirectSignal } : {},
46
+ redirectDepth: options.facts.redirectDepth,
47
+ targetHref: options.url
48
+ })),
49
+ url: options.url
50
+ };
51
+ }
52
+ function createRscFetchResultFollowRedirectDecision(options) {
53
+ return {
54
+ discardBody: options.discardBody,
55
+ kind: "followRedirect",
56
+ redirect: options.redirect,
57
+ trace: createNavigationTrace(NavigationTraceReasonCodes.redirectFollow, createRscFetchResultTraceFields(options.facts, {
58
+ redirectDepth: options.redirect.redirectDepth,
59
+ redirectSignal: options.redirectSignal,
60
+ targetHref: options.redirect.href
61
+ }))
62
+ };
63
+ }
64
+ function mapRscRedirectTerminalReason(reason) {
65
+ switch (reason) {
66
+ case "externalRedirect": return {
67
+ hardNavigationReason: "externalRedirectTarget",
68
+ traceReasonCode: NavigationTraceReasonCodes.redirectTerminalExternal
69
+ };
70
+ case "maxRedirectsExceeded": return {
71
+ hardNavigationReason: "redirectDepthExhausted",
72
+ traceReasonCode: NavigationTraceReasonCodes.redirectTerminalDepth
73
+ };
74
+ default: throw new Error("[vinext] Unknown RSC redirect terminal reason: " + String(reason));
75
+ }
76
+ }
77
+ function classifyRscFetchResult(facts) {
78
+ if (!facts.responseOk || !facts.isRscContentType || !facts.hasBody) {
79
+ const url = resolveHardNavigationTargetFromRscResponse(facts.responseUrl, facts.currentHref, facts.origin);
80
+ return createRscFetchResultHardNavigationDecision({
81
+ discardBody: false,
82
+ facts,
83
+ reason: "invalidRscPayload",
84
+ reasonCode: NavigationTraceReasonCodes.invalidRscPayload,
85
+ url
86
+ });
87
+ }
88
+ const compatibilityDecision = resolveRscCompatibilityNavigationDecision({
89
+ clientCompatibilityId: facts.clientCompatibilityId,
90
+ currentHref: facts.currentHref,
91
+ origin: facts.origin,
92
+ responseCompatibilityId: facts.compatibilityIdHeader,
93
+ responseUrl: facts.responseUrl
94
+ });
95
+ if (compatibilityDecision.kind === "hard-navigate") return createRscFetchResultHardNavigationDecision({
96
+ discardBody: false,
97
+ facts,
98
+ reason: "rscCompatibilityMismatch",
99
+ reasonCode: NavigationTraceReasonCodes.rscCompatibilityMismatch,
100
+ url: compatibilityDecision.hardNavigationTarget
101
+ });
102
+ if (facts.responseUrl !== null) {
103
+ const redirectDecision = resolveRscRedirectLifecycleHop({
104
+ currentHref: facts.currentHref,
105
+ historyUpdateMode: facts.effectiveHistoryUpdateMode,
106
+ origin: facts.origin,
107
+ redirectDepth: facts.redirectDepth,
108
+ requestPreviousNextUrl: facts.requestPreviousNextUrl,
109
+ responseUrl: facts.responseUrl
110
+ });
111
+ if (redirectDecision.kind === "terminal-hard-navigation") {
112
+ const terminalReason = mapRscRedirectTerminalReason(redirectDecision.reason);
113
+ return createRscFetchResultHardNavigationDecision({
114
+ discardBody: false,
115
+ facts,
116
+ reason: terminalReason.hardNavigationReason,
117
+ reasonCode: terminalReason.traceReasonCode,
118
+ redirectSignal: "response-url",
119
+ url: redirectDecision.href
120
+ });
121
+ }
122
+ if (redirectDecision.kind === "follow") return createRscFetchResultFollowRedirectDecision({
123
+ discardBody: false,
124
+ facts,
125
+ redirect: {
126
+ href: redirectDecision.href,
127
+ historyUpdateMode: facts.effectiveHistoryUpdateMode,
128
+ previousNextUrl: redirectDecision.previousNextUrl,
129
+ redirectDepth: redirectDecision.redirectDepth
130
+ },
131
+ redirectSignal: "response-url"
132
+ });
133
+ }
134
+ if (facts.streamedRedirectTarget !== null) {
135
+ const redirectDecision = resolveStreamedRscRedirectLifecycleHop({
136
+ currentHref: facts.currentHref,
137
+ historyUpdateMode: facts.effectiveHistoryUpdateMode,
138
+ origin: facts.origin,
139
+ redirectDepth: facts.redirectDepth,
140
+ requestPreviousNextUrl: facts.requestPreviousNextUrl,
141
+ streamedRedirectTarget: facts.streamedRedirectTarget
142
+ });
143
+ if (redirectDecision.kind === "terminal-hard-navigation") {
144
+ const terminalReason = mapRscRedirectTerminalReason(redirectDecision.reason);
145
+ return createRscFetchResultHardNavigationDecision({
146
+ discardBody: true,
147
+ facts,
148
+ reason: terminalReason.hardNavigationReason,
149
+ reasonCode: terminalReason.traceReasonCode,
150
+ redirectSignal: "streamed-header",
151
+ url: redirectDecision.href
152
+ });
153
+ }
154
+ if (redirectDecision.kind === "follow") return createRscFetchResultFollowRedirectDecision({
155
+ discardBody: true,
156
+ facts,
157
+ redirect: {
158
+ href: redirectDecision.href,
159
+ historyUpdateMode: facts.effectiveHistoryUpdateMode,
160
+ previousNextUrl: redirectDecision.previousNextUrl,
161
+ redirectDepth: redirectDecision.redirectDepth
162
+ },
163
+ redirectSignal: "streamed-header"
164
+ });
165
+ return createRscFetchResultHardNavigationDecision({
166
+ discardBody: true,
167
+ facts,
168
+ reason: "streamedRedirectLoop",
169
+ reasonCode: NavigationTraceReasonCodes.streamedRedirectLoop,
170
+ redirectSignal: "streamed-header",
171
+ url: redirectDecision.href
172
+ });
173
+ }
174
+ return {
175
+ discardBody: false,
176
+ kind: "proceedToCommit",
177
+ trace: createNavigationTrace(NavigationTraceReasonCodes.proceedToCommit, createRscFetchResultTraceFields(facts))
178
+ };
179
+ }
180
+ function createEarlyNavigationIntentTrace(reasonCode, facts) {
181
+ return createNavigationTrace(reasonCode, { targetHref: facts.targetHref });
182
+ }
183
+ function classifyEarlyNavigationIntent(facts) {
184
+ let current;
185
+ let next;
186
+ try {
187
+ current = new URL(facts.currentHref);
188
+ next = new URL(facts.targetHref, facts.currentHref);
189
+ } catch {
190
+ return {
191
+ bypassNavigationCache: false,
192
+ kind: "flightNavigation",
193
+ trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.crossDocumentFlight, facts)
194
+ };
195
+ }
196
+ const samePathname = current.origin === next.origin && stripBasePath(current.pathname, facts.basePath) === stripBasePath(next.pathname, facts.basePath);
197
+ const sameSearch = current.searchParams.toString() === next.searchParams.toString();
198
+ if (samePathname && sameSearch && next.hash !== "") return {
199
+ hash: next.hash,
200
+ kind: "sameDocumentScroll",
201
+ mode: facts.mode,
202
+ scroll: facts.scroll,
203
+ trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.sameDocumentScroll, facts)
204
+ };
205
+ if (samePathname && !sameSearch) return {
206
+ bypassNavigationCache: true,
207
+ kind: "flightNavigation",
208
+ trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.samePageSearch, facts)
209
+ };
210
+ return {
211
+ bypassNavigationCache: false,
212
+ kind: "flightNavigation",
213
+ trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.crossDocumentFlight, facts)
214
+ };
215
+ }
30
216
  function createSnapshotRouteTopology(snapshot) {
31
217
  return {
32
218
  layoutIds: snapshot.layoutIds,
@@ -119,8 +305,10 @@ function findRouteManifestInterceptionForProof(routeManifest, proof) {
119
305
  const candidateInterceptions = routeManifest.segmentGraph.interceptionsBySlotId.get(proof.slotId) ?? [];
120
306
  for (const interception of candidateInterceptions) {
121
307
  if (!matchRoutePatternPrefix(sourceParts, interception.sourcePatternParts)) continue;
122
- if (matchRoutePattern(targetParts, interception.targetPatternParts) === null) continue;
123
- if (interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
308
+ const exactTargetParams = matchRoutePattern(targetParts, interception.targetPatternParts);
309
+ const allowsMiddlewareRewriteTarget = exactTargetParams === null && matchRoutePatternWithOptionalDynamicSegments(targetParts, interception.targetPatternParts);
310
+ if (exactTargetParams === null && !allowsMiddlewareRewriteTarget) continue;
311
+ if (!allowsMiddlewareRewriteTarget && interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
124
312
  return interception;
125
313
  }
126
314
  return null;
@@ -493,6 +681,8 @@ function planNavigation(input) {
493
681
  }
494
682
  }
495
683
  const navigationPlanner = {
684
+ classifyEarlyNavigationIntent,
685
+ classifyRscFetchResult,
496
686
  classifyRootBoundaryTransition,
497
687
  plan: planNavigation,
498
688
  resolveCurrentRootBoundaryElementPersistence,
@@ -4,6 +4,8 @@ type NavigationTraceSchemaVersion = 0;
4
4
  declare const NavigationTraceReasonCodes: {
5
5
  cacheProofRejected: "NC_CACHE_REJECT";
6
6
  commitCurrent: "NC_COMMIT";
7
+ crossDocumentFlight: "NC_CROSS_DOC_FLIGHT";
8
+ invalidRscPayload: "NC_RSC_INVALID";
7
9
  interceptedCommitCurrent: "NC_INTERCEPT_COMMIT";
8
10
  interceptedRejectedIncompatibleRoot: "NC_INTERCEPT_REJECT_ROOT";
9
11
  interceptedRejectedMissingProof: "NC_INTERCEPT_REJECT_MISSING_PROOF";
@@ -12,10 +14,18 @@ declare const NavigationTraceReasonCodes: {
12
14
  interceptedRejectedUndeclaredTopology: "NC_INTERCEPT_REJECT_GRAPH";
13
15
  interceptedRejectedUnknownSource: "NC_INTERCEPT_REJECT_SOURCE";
14
16
  prefetchOnly: "NC_PREFETCH_ONLY";
17
+ proceedToCommit: "NC_RSC_PROCEED";
18
+ redirectFollow: "NC_RSC_REDIRECT_FOLLOW";
19
+ redirectTerminalDepth: "NC_RSC_REDIRECT_DEPTH";
20
+ redirectTerminalExternal: "NC_RSC_REDIRECT_EXTERNAL";
15
21
  requestWork: "NC_REQUEST";
16
22
  rootBoundaryChanged: "NC_ROOT";
17
23
  rootBoundaryUnknown: "NC_ROOT_UNKNOWN";
24
+ rscCompatibilityMismatch: "NC_RSC_COMPAT_MISMATCH";
25
+ sameDocumentScroll: "NC_SAME_DOC_SCROLL";
26
+ samePageSearch: "NC_SAME_PAGE_SEARCH";
18
27
  staleOperation: "NC_STALE";
28
+ streamedRedirectLoop: "NC_RSC_STREAMED_REDIRECT_LOOP";
19
29
  };
20
30
  declare const NavigationTraceTransactionCodes: {
21
31
  hardNavigate: "NT_HARD_NAVIGATE";
@@ -25,7 +35,7 @@ declare const NavigationTraceTransactionCodes: {
25
35
  type NavigationTraceReasonCode = (typeof NavigationTraceReasonCodes)[keyof typeof NavigationTraceReasonCodes];
26
36
  type NavigationTraceTransactionCode = (typeof NavigationTraceTransactionCodes)[keyof typeof NavigationTraceTransactionCodes];
27
37
  type NavigationTraceCode = NavigationTraceReasonCode | NavigationTraceTransactionCode;
28
- type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "operationLane" | "pendingOperationId" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
38
+ type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "fetchResultSource" | "operationLane" | "pendingOperationId" | "redirectDepth" | "redirectSignal" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
29
39
  type NavigationTraceFieldValue = string | number | boolean | null;
30
40
  type NavigationTraceFields = Readonly<Partial<Record<NavigationTraceFieldName, NavigationTraceFieldValue>>>;
31
41
  type NavigationTraceEntry = Readonly<{
@@ -47,4 +57,4 @@ declare function createNavigationLifecycleTraceFields(options: {
47
57
  declare function createNavigationTrace(code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
48
58
  declare function prependNavigationTraceEntry(trace: NavigationTrace, code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
49
59
  //#endregion
50
- export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceCode, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
60
+ export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
@@ -3,6 +3,8 @@ const NAVIGATION_TRACE_SCHEMA_VERSION = 0;
3
3
  const NavigationTraceReasonCodes = {
4
4
  cacheProofRejected: "NC_CACHE_REJECT",
5
5
  commitCurrent: "NC_COMMIT",
6
+ crossDocumentFlight: "NC_CROSS_DOC_FLIGHT",
7
+ invalidRscPayload: "NC_RSC_INVALID",
6
8
  interceptedCommitCurrent: "NC_INTERCEPT_COMMIT",
7
9
  interceptedRejectedIncompatibleRoot: "NC_INTERCEPT_REJECT_ROOT",
8
10
  interceptedRejectedMissingProof: "NC_INTERCEPT_REJECT_MISSING_PROOF",
@@ -11,10 +13,18 @@ const NavigationTraceReasonCodes = {
11
13
  interceptedRejectedUndeclaredTopology: "NC_INTERCEPT_REJECT_GRAPH",
12
14
  interceptedRejectedUnknownSource: "NC_INTERCEPT_REJECT_SOURCE",
13
15
  prefetchOnly: "NC_PREFETCH_ONLY",
16
+ proceedToCommit: "NC_RSC_PROCEED",
17
+ redirectFollow: "NC_RSC_REDIRECT_FOLLOW",
18
+ redirectTerminalDepth: "NC_RSC_REDIRECT_DEPTH",
19
+ redirectTerminalExternal: "NC_RSC_REDIRECT_EXTERNAL",
14
20
  requestWork: "NC_REQUEST",
15
21
  rootBoundaryChanged: "NC_ROOT",
16
22
  rootBoundaryUnknown: "NC_ROOT_UNKNOWN",
17
- staleOperation: "NC_STALE"
23
+ rscCompatibilityMismatch: "NC_RSC_COMPAT_MISMATCH",
24
+ sameDocumentScroll: "NC_SAME_DOC_SCROLL",
25
+ samePageSearch: "NC_SAME_PAGE_SEARCH",
26
+ staleOperation: "NC_STALE",
27
+ streamedRedirectLoop: "NC_RSC_STREAMED_REDIRECT_LOOP"
18
28
  };
19
29
  const NavigationTraceTransactionCodes = {
20
30
  hardNavigate: "NT_HARD_NAVIGATE",
@@ -1,12 +1,4 @@
1
1
  //#region src/server/normalize-path.d.ts
2
- /**
3
- * Re-encode path delimiter characters that were decoded by decodeURIComponent.
4
- * After decoding a URL segment, characters like / # ? \ need to be re-encoded
5
- * so they don't change the path structure.
6
- *
7
- * Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
8
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
9
- */
10
2
  declare function escapePathDelimiters(segment: string, escapeEncoded?: boolean): string;
11
3
  /**
12
4
  * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
@@ -7,8 +7,10 @@
7
7
  * Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
8
8
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
9
9
  */
10
+ const PATH_DELIMITERS_RE = /([/#?])/gi;
11
+ const PATH_DELIMITERS_ENCODED_RE = /([/#?]|%(2f|23|3f|5c))/gi;
10
12
  function escapePathDelimiters(segment, escapeEncoded) {
11
- return segment.replace(new RegExp(`([/#?]${escapeEncoded ? "|%(2f|23|3f|5c)" : ""})`, "gi"), (char) => encodeURIComponent(char));
13
+ return segment.replace(escapeEncoded ? PATH_DELIMITERS_ENCODED_RE : PATH_DELIMITERS_RE, (char) => encodeURIComponent(char));
12
14
  }
13
15
  /**
14
16
  * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
@@ -0,0 +1,45 @@
1
+ //#region src/server/otel-tracer-extension.d.ts
2
+ /**
3
+ * OpenTelemetry tracer provider extension for Cache Components.
4
+ *
5
+ * When `cacheComponents: true` is enabled in next.config, component renders
6
+ * go through multiple phases (warmup → resume). During these phases, the
7
+ * `workUnitAsyncStorage` carries a prerender or cache store. Without this
8
+ * extension, calls to `tracer.startSpan()` / `tracer.startActiveSpan()` from
9
+ * inside user RSC code would inherit that prerender context, causing:
10
+ *
11
+ * 1. Spans to reuse the same trace ID across requests (the frozen prerender
12
+ * context bleeds into the runtime resume render).
13
+ * 2. Spans not being created at all during fallback resume (the work unit
14
+ * context gates span creation in some OTel SDK implementations).
15
+ *
16
+ * The fix mirrors Next.js's `instrumentation-node-extensions.ts`:
17
+ * - Wrap `tracer.startSpan` to exit `workUnitAsyncStorage` before creating
18
+ * the span, ensuring a clean context for span ID generation.
19
+ * - Wrap `tracer.startActiveSpan` similarly and re-enter the work unit store
20
+ * for the callback, so that the callback runs with the correct request
21
+ * context restored.
22
+ *
23
+ * This extension is intentionally a no-op when:
24
+ * - `@opentelemetry/api` is not installed (graceful degradation).
25
+ * - No OTel tracer provider has been registered (provider is the noop provider).
26
+ * - `workUnitAsyncStorage` has no active store (non-render contexts).
27
+ *
28
+ * References:
29
+ * - packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
30
+ * - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
31
+ */
32
+ /**
33
+ * Extend the registered OTel tracer provider so that `startSpan` and
34
+ * `startActiveSpan` exit the `workUnitAsyncStorage` context before creating
35
+ * spans. This prevents the prerender/cache work unit store from leaking into
36
+ * span ID generation during Cache Component fallback resumes.
37
+ *
38
+ * Safe to call multiple times — subsequent calls are no-ops once the provider
39
+ * has been wrapped.
40
+ *
41
+ * Must only be called in Node.js environments (not Edge runtime).
42
+ */
43
+ declare function extendTracerProviderForCacheComponents(): void;
44
+ //#endregion
45
+ export { extendTracerProviderForCacheComponents };