@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha

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 (202) hide show
  1. package/README.md +27 -868
  2. package/build/actions.mjs +27 -15
  3. package/build/analyze.mjs +180 -24
  4. package/build/app-config.mjs +13 -0
  5. package/build/app-static.mjs +34 -12
  6. package/build/app-styles.mjs +79 -0
  7. package/build/app.mjs +122 -46
  8. package/build/bundle-runtime.mjs +22 -9
  9. package/build/cache-components.mjs +1 -1
  10. package/build/cache-handlers.mjs +1 -1
  11. package/build/compact-css.mjs +53 -0
  12. package/build/compilation-cache.mjs +269 -0
  13. package/build/compilation-store.mjs +142 -0
  14. package/build/compiler.mjs +34 -10
  15. package/build/config.mjs +34 -3
  16. package/build/create.mjs +239 -0
  17. package/build/css-modules.mjs +56 -12
  18. package/build/deployment-assets.mjs +89 -0
  19. package/build/deployment-config.mjs +18 -0
  20. package/build/dev-css-assets.mjs +173 -0
  21. package/build/dev-css-update.mjs +18 -14
  22. package/build/dev-dependencies.mjs +1 -1
  23. package/build/dev-discovery.mjs +3 -3
  24. package/build/dev-lazy-build.mjs +77 -0
  25. package/build/dev.mjs +15 -8
  26. package/build/dynamic.mjs +14 -7
  27. package/build/edge.mjs +3 -3
  28. package/build/fonts.mjs +1 -1
  29. package/build/framework-imports.mjs +15 -5
  30. package/build/html-only-config.mjs +53 -0
  31. package/build/html-only-graph.mjs +67 -0
  32. package/build/incremental.mjs +112 -18
  33. package/build/index.mjs +90 -28
  34. package/build/instrumentation.mjs +30 -0
  35. package/build/lazy-dynamic-imports.mjs +137 -0
  36. package/build/metadata-routes.mjs +1 -1
  37. package/build/migrate.mjs +4 -4
  38. package/build/migration-audit.mjs +19 -31
  39. package/build/migration-imports.mjs +245 -0
  40. package/build/migration-scenarios-runner.mjs +73 -0
  41. package/build/migration-scenarios.mjs +72 -0
  42. package/build/native-discovery.mjs +65 -5
  43. package/build/native-packages.mjs +2 -0
  44. package/build/native-type-config.mjs +37 -0
  45. package/build/npm-packages.mjs +1 -0
  46. package/build/persistent-cache.mjs +18 -3
  47. package/build/precompress.mjs +1 -1
  48. package/build/prerender-cache.mjs +70 -0
  49. package/build/prerender-config.mjs +29 -0
  50. package/build/prerender-pages.mjs +19 -9
  51. package/build/prerender-pool.mjs +165 -0
  52. package/build/prerender-process-worker.mjs +58 -0
  53. package/build/prerender-worker.mjs +5 -5
  54. package/build/production-builder.mjs +59 -10
  55. package/build/react-compiler-report.mjs +225 -0
  56. package/build/react-compiler.mjs +139 -0
  57. package/build/runtime-contract.mjs +2 -2
  58. package/build/standalone.mjs +21 -3
  59. package/build/static-export.mjs +4 -4
  60. package/build/styles.mjs +6 -4
  61. package/build/transform.mjs +1 -1
  62. package/build/typed-routes.mjs +4 -0
  63. package/build/typescript-dev.mjs +35 -8
  64. package/build/typescript-native-worker.mjs +55 -0
  65. package/build/typescript-native.mjs +18 -0
  66. package/build/typescript-toolchain.mjs +19 -0
  67. package/build/typescript.mjs +6 -4
  68. package/build/webpack-graph.mjs +19 -6
  69. package/build/worker-memory.mjs +9 -0
  70. package/cli.mjs +70 -5
  71. package/compat/activity-cache.cjs +26 -0
  72. package/compat/after.cjs +6 -4
  73. package/compat/app-dynamic.cjs +12 -1
  74. package/compat/app-layout-context.cjs +52 -2
  75. package/compat/base64.cjs +10 -0
  76. package/compat/cache-handlers.cjs +15 -3
  77. package/compat/cache-wire.cjs +28 -0
  78. package/compat/cache.cjs +2 -1
  79. package/compat/cache.d.cts +4 -0
  80. package/compat/cookies.cjs +21 -5
  81. package/compat/data-cache.cjs +12 -8
  82. package/compat/flight-delta.cjs +152 -0
  83. package/compat/headers.cjs +4 -2
  84. package/compat/incremental-cache.cjs +20 -7
  85. package/compat/index.d.ts +69 -4
  86. package/compat/link.cjs +13 -10
  87. package/compat/navigation-authorization.cjs +9 -0
  88. package/compat/navigation-stages.cjs +19 -0
  89. package/compat/offline-store.cjs +11 -0
  90. package/compat/offline.cjs +5 -0
  91. package/compat/offline.d.cts +2 -0
  92. package/compat/redis-cache.cjs +201 -0
  93. package/compat/redis-cache.d.cts +37 -0
  94. package/compat/server-primitives.cjs +31 -11
  95. package/compat/server.d.cts +15 -2
  96. package/compat/static-generation.cjs +7 -0
  97. package/compat/use-cache.cjs +3 -0
  98. package/docs/ai/compatibility.md +248 -2
  99. package/docs/ai/data.md +165 -5
  100. package/docs/ai/deployment.md +158 -0
  101. package/docs/ai/quickstart.md +29 -4
  102. package/docs/ai/routing.md +93 -0
  103. package/docs/ai/troubleshooting.md +116 -2
  104. package/package.json +40 -9
  105. package/runtime/action-client.mjs +10 -4
  106. package/runtime/action-server.mjs +2 -0
  107. package/runtime/app-client.mjs +316 -92
  108. package/runtime/app-content.mjs +4 -3
  109. package/runtime/app-partial-client.mjs +3 -0
  110. package/runtime/app-partial.mjs +1 -1
  111. package/runtime/app-prefetch-constants.mjs +4 -0
  112. package/runtime/app-prefetch.mjs +2 -3
  113. package/runtime/app-render-key.mjs +5 -0
  114. package/runtime/app-render.mjs +88 -28
  115. package/runtime/app-routing.mjs +22 -9
  116. package/runtime/app-static.mjs +18 -8
  117. package/runtime/app-stream-html.mjs +10 -53
  118. package/runtime/app-style-selection.mjs +34 -0
  119. package/runtime/app-styles.mjs +154 -0
  120. package/runtime/atomic-file.mjs +27 -0
  121. package/runtime/cache-components-rsc.mjs +7 -2
  122. package/runtime/cache-request.mjs +2 -1
  123. package/runtime/deployment-client.mjs +19 -0
  124. package/runtime/dev-client.mjs +13 -3
  125. package/runtime/dev-compiler-broker.mjs +42 -0
  126. package/runtime/dev-file-changes.mjs +123 -8
  127. package/runtime/dev-indicator.mjs +8 -3
  128. package/runtime/dev-lazy-imports.mjs +121 -0
  129. package/runtime/dev-lazy-staging.mjs +14 -0
  130. package/runtime/dev-lazy-tool.mjs +16 -0
  131. package/runtime/dev-style-retention.mjs +15 -0
  132. package/runtime/development.mjs +83 -25
  133. package/runtime/document.mjs +1 -1
  134. package/runtime/edge.mjs +6 -5
  135. package/runtime/fetch-cache.mjs +10 -8
  136. package/runtime/flight-delta.mjs +74 -0
  137. package/runtime/flight-tap.mjs +28 -0
  138. package/runtime/fs.mjs +15 -8
  139. package/runtime/html-only.mjs +27 -0
  140. package/runtime/http.mjs +2 -0
  141. package/runtime/incremental-cache.mjs +25 -4
  142. package/runtime/instrumentation.mjs +49 -0
  143. package/runtime/lazy-import-client.mjs +38 -0
  144. package/runtime/navigation-recovery.mjs +36 -0
  145. package/runtime/navigation.mjs +7 -0
  146. package/runtime/node-html-stream.mjs +47 -0
  147. package/runtime/pages-client.mjs +14 -7
  148. package/runtime/pages-navigation.mjs +48 -63
  149. package/runtime/prefetch-scheduler.mjs +110 -0
  150. package/runtime/profile.mjs +14 -0
  151. package/runtime/profiles.json +2 -2
  152. package/runtime/render.mjs +3 -0
  153. package/runtime/route-pattern.mjs +44 -0
  154. package/runtime/rsc-worker.mjs +34 -12
  155. package/runtime/trace.mjs +78 -0
  156. package/runtime/watch-project.mjs +19 -15
  157. package/runtime/worker.mjs +70 -26
  158. package/vendor/nft/LICENSE +7 -0
  159. package/vendor/nft/README.md +16 -0
  160. package/vendor/nft/out/analyze.d.ts +8 -0
  161. package/vendor/nft/out/analyze.js +1216 -0
  162. package/vendor/nft/out/analyze.js.map +1 -0
  163. package/vendor/nft/out/cli.d.ts +2 -0
  164. package/vendor/nft/out/cli.js +94 -0
  165. package/vendor/nft/out/cli.js.map +1 -0
  166. package/vendor/nft/out/fs.d.ts +22 -0
  167. package/vendor/nft/out/fs.js +106 -0
  168. package/vendor/nft/out/fs.js.map +1 -0
  169. package/vendor/nft/out/index.d.ts +4 -0
  170. package/vendor/nft/out/index.js +25 -0
  171. package/vendor/nft/out/index.js.map +1 -0
  172. package/vendor/nft/out/node-file-trace.d.ts +41 -0
  173. package/vendor/nft/out/node-file-trace.js +380 -0
  174. package/vendor/nft/out/node-file-trace.js.map +1 -0
  175. package/vendor/nft/out/resolve-dependency.d.ts +6 -0
  176. package/vendor/nft/out/resolve-dependency.js +427 -0
  177. package/vendor/nft/out/resolve-dependency.js.map +1 -0
  178. package/vendor/nft/out/types.d.ts +66 -0
  179. package/vendor/nft/out/types.js +2 -0
  180. package/vendor/nft/out/types.js.map +1 -0
  181. package/vendor/nft/out/utils/ast-helpers.js +49 -0
  182. package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
  183. package/vendor/nft/out/utils/binary-locators.js +88 -0
  184. package/vendor/nft/out/utils/binary-locators.js.map +1 -0
  185. package/vendor/nft/out/utils/get-package-base.js +30 -0
  186. package/vendor/nft/out/utils/get-package-base.js.map +1 -0
  187. package/vendor/nft/out/utils/interop-require.js +24 -0
  188. package/vendor/nft/out/utils/interop-require.js.map +1 -0
  189. package/vendor/nft/out/utils/node-version.js +10 -0
  190. package/vendor/nft/out/utils/node-version.js.map +1 -0
  191. package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
  192. package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
  193. package/vendor/nft/out/utils/special-cases.js +347 -0
  194. package/vendor/nft/out/utils/special-cases.js.map +1 -0
  195. package/vendor/nft/out/utils/static-eval.js +579 -0
  196. package/vendor/nft/out/utils/static-eval.js.map +1 -0
  197. package/vendor/nft/out/utils/types.js +2 -0
  198. package/vendor/nft/out/utils/types.js.map +1 -0
  199. package/vendor/nft/out/utils/wrappers.js +659 -0
  200. package/vendor/nft/out/utils/wrappers.js.map +1 -0
  201. package/vendor/nft/package.json +4 -0
  202. package/vendor/nft/upstream.json +51 -0
@@ -4,7 +4,10 @@ import { rewriteQuery } from '../compat/rewrite.cjs';
4
4
  import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
5
5
  import { publicNavigationURL, isApplicationURL } from './client-navigation.mjs';
6
6
  import DefaultError from '../compat/error.cjs';
7
+ import { createPrefetchScheduler, prefetchCacheVictim } from './prefetch-scheduler.mjs';
7
8
  import { applicationProps, createInitialPropsRunner } from './pages-initial-props.mjs';
9
+ import { matchPagePattern } from './route-pattern.mjs';
10
+ export { matchPagePattern } from './route-pattern.mjs';
8
11
 
9
12
  const PREFETCH_LIMIT = 8;
10
13
  const PREFETCH_TTL = 30_000;
@@ -51,33 +54,6 @@ export function pageRouterSnapshot(router, requestURL, visibleURL, rewrite) {
51
54
  ...(rewrite ? { rewrite } : {}) };
52
55
  }
53
56
 
54
- export function matchPagePattern(pattern, pathname) {
55
- let segments;
56
- try { segments = pathname.replace(/\/+$/, '').split('/').slice(1).map(decodeURIComponent); }
57
- catch { return null; }
58
- if (segments.length === 1 && !segments[0]) segments = [];
59
- if (segments.some(segment => !segment || /[/\\\0]/.test(segment) || segment === '.' || segment === '..')) return null;
60
- const parts = pattern === '/' ? [] : pattern.split('/').slice(1), params = Object.create(null), rank = [];
61
- let index = 0;
62
- for (const part of parts) {
63
- const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(part);
64
- const catchall = /^\[\.\.\.([^\]]+)\]$/.exec(part);
65
- const parameter = /^\[([^\]]+)\]$/.exec(part);
66
- if (optional || catchall) {
67
- if (catchall && index === segments.length) return null;
68
- if (index !== segments.length) params[(optional || catchall)[1]] = segments.slice(index);
69
- index = segments.length; rank.push(optional ? 0 : 1);
70
- } else if (parameter) {
71
- if (index === segments.length) return null;
72
- params[parameter[1]] = segments[index++]; rank.push(2);
73
- } else {
74
- if (segments[index++] !== part) return null;
75
- rank.push(3);
76
- }
77
- }
78
- return index === segments.length ? { params, rank } : null;
79
- }
80
-
81
57
  export function resolvePageRoute(manifest, pathname) {
82
58
  let winner;
83
59
  for (const [routes, pages] of [[manifest.routes, true], [manifest.nonPagesRoutes || [], false]]) {
@@ -129,7 +105,8 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
129
105
  initial = undefined;
130
106
  let manifestPromise, active, renderingError, sequence = 0, beforePop = () => true;
131
107
  let currentKey = win.history.state?.__prnextPages?.key || 0, nextKey = currentKey + 1;
132
- const prefetches = new Map(), queue = []; let prefetchRunning = 0, prefetchBytes = 0;
108
+ const prefetches = new Map(), scheduler = createPrefetchScheduler({ window: win, document: doc });
109
+ let prefetchBytes = 0;
133
110
 
134
111
  function assetURL(value) {
135
112
  const url = new URL(value, win.location.href);
@@ -147,7 +124,7 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
147
124
  const abort = new AbortController(), timer = setTimeout(() => abort.abort(), 30_000);
148
125
  try {
149
126
  const value = JSON.parse(await consume(await fetchAsset(manifestUrl, abort.signal), ASSET_LIMIT));
150
- if (value.buildId !== buildId || !Array.isArray(value.routes) || value.routes.length > 10_000) throw new Error('Invalid Pages manifest');
127
+ if (value.buildId !== buildId || current.deploymentId && value.deploymentId !== current.deploymentId || !Array.isArray(value.routes) || value.routes.length > 10_000) throw new Error('Invalid Pages manifest');
151
128
  for (const route of [...value.routes, ...Object.values(value.errors || {}).filter(Boolean)]) {
152
129
  if (typeof route.pattern !== 'string' || !Array.isArray(route.css)) throw new Error('Invalid page route');
153
130
  assetURL(route.client); for (const css of route.css) assetURL(css);
@@ -278,44 +255,45 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
278
255
  prefetchBytes -= entry.bytes || 0; entry.bytes = 0;
279
256
  if (abort) entry.abort.abort(); else entry.consumed = true;
280
257
  }
281
- function pumpPrefetch() {
282
- while (prefetchRunning < 2 && queue.length) {
283
- const entry = queue.shift();
284
- if (entry.abort.signal.aborted) { entry.resolve(); continue; }
285
- prefetchRunning++;
286
- (async () => {
287
- const map = await abortable(manifest(), entry.abort.signal), matched = resolvePageRoute(map, internalPath(entry.request));
288
- if (!matched?.pages) return;
289
- // Fetch rather than evaluate speculative modules: fetch is cancellable,
290
- // whereas the browser's import() cannot cancel a blocked module request.
291
- await consume(await fetchAsset(matched.route.client, entry.abort.signal), ASSET_LIMIT);
292
- if (entry.abort.signal.aborted) return;
293
- // Warm browser HTTP caches without applying another page's CSS early.
294
- for (const css of matched.route.css) await consume(await fetchAsset(css, entry.abort.signal), ASSET_LIMIT);
295
- if (matched.route.ssg) {
296
- const decoded = await requestData(entry.request, entry.abort.signal, true);
297
- if (entry.abort.signal.aborted || (!prefetches.has(entry.key) && !entry.consumed) || decoded.cacheable === false) return;
298
- entry.data = decoded;
299
- if (entry.consumed) return;
300
- entry.bytes = decoded.bytes || 0; prefetchBytes += entry.bytes;
301
- while (prefetchBytes > PREFETCH_BYTES) dropPrefetch(prefetches.keys().next().value);
302
- }
303
- })().catch(() => { dropPrefetch(entry.key); }).finally(() => { prefetchRunning--; entry.resolve(); pumpPrefetch(); });
258
+ async function runPrefetch(entry) {
259
+ const map = await abortable(manifest(), entry.abort.signal), matched = resolvePageRoute(map, internalPath(entry.request));
260
+ if (!matched?.pages) return;
261
+ // Fetch rather than evaluate speculative modules: fetch can be cancelled,
262
+ // whereas import() can leave an uncancellable module request in flight.
263
+ await consume(await fetchAsset(matched.route.client, entry.abort.signal), ASSET_LIMIT);
264
+ if (entry.abort.signal.aborted) return;
265
+ for (const css of matched.route.css) await consume(await fetchAsset(css, entry.abort.signal), ASSET_LIMIT);
266
+ if (matched.route.ssg) {
267
+ const decoded = await requestData(entry.request, entry.abort.signal, true);
268
+ if (entry.abort.signal.aborted || (prefetches.get(entry.key) !== entry && !entry.consumed) || decoded.cacheable === false) return;
269
+ entry.data = decoded;
270
+ if (entry.consumed) return;
271
+ entry.bytes = decoded.bytes || 0; prefetchBytes += entry.bytes;
272
+ while (prefetchBytes > PREFETCH_BYTES) {
273
+ const victim = prefetchCacheVictim([...prefetches].filter(([, value]) => value.bytes > 0), 1);
274
+ if (victim === undefined) break;
275
+ dropPrefetch(victim);
276
+ }
304
277
  }
305
278
  }
306
- function prefetch(url, as, options) {
279
+ function prefetch(url, as, options = {}) {
307
280
  let request;
308
281
  try { ({ request } = targets(url, as, options)); } catch { return Promise.resolve(); }
309
282
  if (!isApplicationURL(request, win.location.origin, basePath) || process.env.NODE_ENV !== 'production') return Promise.resolve();
310
283
  request.hash = ''; const key = request.href;
311
- if (prefetches.has(key)) return prefetches.get(key).promise;
312
- while (prefetches.size >= PREFETCH_LIMIT) dropPrefetch(prefetches.keys().next().value);
313
- // Queued cancelled entries must not grow with an unbounded list of hovered links.
314
- for (let index = queue.length - 1; index >= 0; index--) if (queue[index].abort.signal.aborted) { queue[index].resolve(); queue.splice(index, 1); }
315
- const entry = { key, request, abort: new AbortController() };
316
- entry.promise = new Promise(resolve => { entry.resolve = resolve; });
284
+ const priority = options.priority === 'viewport' ? 0 : 1;
285
+ const previous = prefetches.get(key);
286
+ if (previous && !previous.abort.signal.aborted) { previous.priority = Math.max(previous.priority, priority); previous.task.promote(options.priority); return previous.promise; }
287
+ if (previous) dropPrefetch(key);
288
+ const victim = prefetches.size >= PREFETCH_LIMIT ? prefetchCacheVictim(prefetches, priority) : undefined;
289
+ if (prefetches.size >= PREFETCH_LIMIT && victim === undefined) return Promise.resolve();
290
+ const entry = { key, request, priority, abort: new AbortController() };
291
+ entry.task = scheduler.schedule(() => runPrefetch(entry), { abort: entry.abort, priority: options.priority });
292
+ if (!entry.task) return Promise.resolve();
293
+ if (victim !== undefined) dropPrefetch(victim);
294
+ entry.promise = entry.task.promise.catch(() => { if (prefetches.get(key) === entry) dropPrefetch(key); });
317
295
  entry.timer = setTimeout(() => dropPrefetch(key), PREFETCH_TTL);
318
- prefetches.set(key, entry); queue.push(entry); pumpPrefetch(); return entry.promise;
296
+ prefetches.set(key, entry); return entry.promise;
319
297
  }
320
298
  function cancelActive() {
321
299
  if (!active) return;
@@ -332,6 +310,9 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
332
310
  cancelActive(); renderingError?.abort.abort(); renderingError = undefined; saveScroll();
333
311
  const job = { id: ++sequence, abort: new AbortController(), asPath: destination.pathname + destination.search + destination.hash, shallow: !!options.shallow };
334
312
  active = job;
313
+ const prefetchKey = new URL(request); prefetchKey.hash = '';
314
+ const releaseNavigation = scheduler.navigation(prefetches.get(prefetchKey.href)?.task);
315
+ job.abort.signal.addEventListener('abort', releaseNavigation, { once: true });
335
316
  const timer = setTimeout(() => job.abort.abort(new Error('Page navigation timed out')), 30_000);
336
317
  const hashOnly = destination.pathname === currentURL.pathname && destination.search === currentURL.search &&
337
318
  (destination.hash !== currentURL.hash || !!destination.hash) && !options.shallow;
@@ -459,7 +440,7 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
459
440
  job.finished = true; active = undefined;
460
441
  events.emit('routeChangeError', error, job.asPath, { shallow: job.shallow });
461
442
  hard(destination, options.replace || options.pop); return false;
462
- } finally { clearTimeout(timer); }
443
+ } finally { clearTimeout(timer); job.abort.signal.removeEventListener('abort', releaseNavigation); releaseNavigation(); }
463
444
  }
464
445
  async function reportRenderError(error, failedView) {
465
446
  if (failedView !== current && (failedView.clientSnapshot || failedView.Page !== current.Page || failedView.props !== current.props)) return false;
@@ -550,7 +531,11 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
550
531
  };
551
532
  let frame;
552
533
  const onScroll = () => { if (frame !== undefined) return; frame = win.requestAnimationFrame(() => { frame = undefined; saveScroll(); }); };
553
- const pagehide = () => { saveScroll(); active?.abort.abort(); renderingError?.abort.abort(); for (const key of prefetches.keys()) dropPrefetch(key); };
534
+ const pagehide = () => {
535
+ saveScroll(); cancelActive(); active = undefined;
536
+ renderingError?.abort.abort(); renderingError = undefined;
537
+ for (const key of prefetches.keys()) dropPrefetch(key);
538
+ };
554
539
  win.history.replaceState(historyState(currentURL, currentRequest, currentKey, {}, [win.scrollX, win.scrollY]), '', currentURL.href);
555
540
  win.history.scrollRestoration = 'manual';
556
541
  win.addEventListener('popstate', popstate); win.addEventListener('scroll', onScroll, { passive: true }); win.addEventListener('pagehide', pagehide);
@@ -565,6 +550,6 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
565
550
  await navigate(currentRequest, currentURL, { replace: true, scroll: false, devRefresh:true });
566
551
  } } : {}),
567
552
  refreshFallback,
568
- dispose() { pagehide(); win.removeEventListener('popstate', popstate); win.removeEventListener('scroll', onScroll); win.removeEventListener('pagehide', pagehide); if (frame !== undefined) win.cancelAnimationFrame(frame); },
553
+ dispose() { pagehide(); scheduler.dispose(); win.removeEventListener('popstate', popstate); win.removeEventListener('scroll', onScroll); win.removeEventListener('pagehide', pagehide); if (frame !== undefined) win.cancelAnimationFrame(frame); },
569
554
  };
570
555
  }
@@ -0,0 +1,110 @@
1
+ const INTENT = 1;
2
+ const abortError = () => Object.assign(new Error('Speculative navigation cancelled'), { name: 'AbortError' });
3
+
4
+ // The completed-result cache obeys the same priority as the producer queue.
5
+ // Otherwise a viewport scan could evict the hovered route immediately after
6
+ // the scheduler had correctly protected its request.
7
+ export function prefetchCacheVictim(cache, priority) {
8
+ let candidate;
9
+ for (const [key, entry] of cache) {
10
+ if (entry.abort.signal.aborted) return key;
11
+ if (!candidate || entry.priority < candidate[1].priority) candidate = [key, entry];
12
+ }
13
+ return candidate && candidate[1].priority <= priority ? candidate[0] : undefined;
14
+ }
15
+
16
+ // Speculative work never competes with a committed navigation. The same policy
17
+ // is used by both routers; data and authorization remain in their own caches.
18
+ export function createPrefetchScheduler({ window: win = globalThis.window, document: doc = globalThis.document,
19
+ concurrency = 2, limit = 8 } = {}) {
20
+ const tasks = new Set();
21
+ let serial = 0, active = 0, navigating = 0, disposed = false, scheduled = false;
22
+ const connection = win?.navigator?.connection;
23
+ const priorityOf = value => value === 'viewport' ? 0 : INTENT;
24
+ function policy(priority) {
25
+ const network = win?.navigator?.connection;
26
+ if (doc?.visibilityState === 'hidden' || win?.navigator?.onLine === false || network?.saveData || /^(slow-)?2g$/.test(network?.effectiveType || '')) return 0;
27
+ const slow = network?.effectiveType === '3g' || (network?.downlink > 0 && network.downlink < 1.5);
28
+ return slow ? (priority < INTENT ? 0 : 1) : concurrency;
29
+ }
30
+ function finish(task, error, value) {
31
+ if (!tasks.delete(task)) return;
32
+ if (task.running) active--;
33
+ task.abort.signal.removeEventListener('abort', task.cancelled);
34
+ const { resolve, reject } = task;
35
+ task.run = task.resolve = task.reject = task.cancelled = undefined;
36
+ error ? reject(error) : resolve(value);
37
+ wake();
38
+ }
39
+ function start(task) {
40
+ task.running = true; active++;
41
+ let result;
42
+ try { result = task.run(task.abort.signal); } catch (error) { finish(task, error); return; }
43
+ Promise.resolve(result).then(value => finish(task, null, value), error => finish(task, error));
44
+ }
45
+ function pump() {
46
+ scheduled = false;
47
+ if (disposed) return;
48
+ const queued = [...tasks].filter(task => !task.running).sort((a, b) => b.priority - a.priority || a.order - b.order);
49
+ for (const task of queued) {
50
+ if (!tasks.has(task)) continue;
51
+ if (task.priority === 2) { start(task); continue; }
52
+ if (navigating) break;
53
+ const slots = policy(task.priority);
54
+ if (!slots) { task.abort.abort(abortError()); continue; }
55
+ if (active >= slots) continue;
56
+ start(task);
57
+ }
58
+ }
59
+ function wake() {
60
+ if (scheduled || disposed) return;
61
+ scheduled = true; queueMicrotask(pump);
62
+ }
63
+ function changed() {
64
+ for (const task of tasks) if (task.priority !== 2 && !policy(task.priority)) task.abort.abort(abortError());
65
+ wake();
66
+ }
67
+ doc?.addEventListener?.('visibilitychange', changed);
68
+ win?.addEventListener?.('online', changed);
69
+ win?.addEventListener?.('offline', changed);
70
+ connection?.addEventListener?.('change', changed);
71
+ return {
72
+ schedule(run, { priority, abort = new AbortController() } = {}) {
73
+ const rank = priorityOf(priority);
74
+ if (disposed || abort.signal.aborted || !policy(rank)) return undefined;
75
+ if (tasks.size >= limit) {
76
+ // Keep intent ahead of broad viewport scans. Never evict demand work.
77
+ const oldest = [...tasks].filter(task => task.priority < 2).sort((a, b) => a.priority - b.priority || a.order - b.order)[0];
78
+ if (!oldest || oldest.priority > rank) return undefined;
79
+ oldest.abort.abort(abortError());
80
+ }
81
+ const task = { run, abort, priority: rank, order: serial++ };
82
+ const promise = new Promise((resolve, reject) => { task.resolve = resolve; task.reject = reject; });
83
+ // Cancellation also settles a task whose producer ignores AbortSignal.
84
+ // A late result cannot regain a queue slot or resurrect an invalid cache.
85
+ task.cancelled = () => finish(task, abort.signal.reason || abortError());
86
+ tasks.add(task); abort.signal.addEventListener('abort', task.cancelled, { once: true });
87
+ wake();
88
+ return { abort, promise,
89
+ promote(priority = 'intent') { if (tasks.has(task)) { task.priority = Math.max(task.priority, priorityOf(priority)); wake(); } },
90
+ demand() { if (tasks.has(task)) { task.priority = 2; wake(); } },
91
+ };
92
+ },
93
+ navigation(target) {
94
+ navigating++;
95
+ target?.demand();
96
+ for (const task of tasks) if (task.abort !== target?.abort && task.priority !== 2) task.abort.abort(abortError());
97
+ let released = false;
98
+ return () => { if (!released) { released = true; navigating--; wake(); } };
99
+ },
100
+ dispose() {
101
+ if (disposed) return;
102
+ disposed = true;
103
+ doc?.removeEventListener?.('visibilitychange', changed);
104
+ win?.removeEventListener?.('online', changed);
105
+ win?.removeEventListener?.('offline', changed);
106
+ connection?.removeEventListener?.('change', changed);
107
+ for (const task of tasks) task.abort.abort(abortError());
108
+ },
109
+ };
110
+ }
@@ -1,9 +1,23 @@
1
1
  import presets from './profiles.json' with { type: 'json' };
2
2
 
3
+ export function rscResourceLimits(environment=process.env,production=environment.NODE_ENV!=='development'){
4
+ if(!production||/--max[-_]semi[-_]space[-_]size(?:[=\s]|$)/.test(environment.NODE_OPTIONS||''))return undefined;
5
+ return {maxYoungGenerationSizeMb:runtimeProfile(environment,production).youngGenerationMiB};
6
+ }
7
+
3
8
  export function runtimeProfile(environment = process.env, production = environment.NODE_ENV !== 'development') {
4
9
  const requested = environment.PRNEXT_PROFILE || (environment.PRNEXT_MEMORY_PROFILE === 'compact' ? 'compact' : 'balanced');
5
10
  const name = requested === 'standard' ? 'classic' : requested;
6
11
  if (!Object.hasOwn(presets, name)) throw new Error(`Unknown runtime profile ${JSON.stringify(name)}; use ${Object.keys(presets).join(', ')}`);
7
12
  const effective = production ? name : 'classic';
13
+ if(production&&environment.PRNEXT_EFFECTIVE_PROFILE){
14
+ let value;
15
+ try{value=JSON.parse(environment.PRNEXT_EFFECTIVE_PROFILE);}catch{throw new Error('Invalid effective runtime profile');}
16
+ const settings=value?.[1],original=presets[effective];
17
+ if(!Array.isArray(value)||value.length!==2||value[0]!==effective||!settings||
18
+ Object.keys(settings).length!==Object.keys(original).length||Object.keys(original).some(key=>!Object.hasOwn(settings,key)||
19
+ (original[key]===null?settings[key]!==null&&(!Number.isSafeInteger(settings[key])||settings[key]<1):typeof original[key]==='boolean'?typeof settings[key]!=='boolean':!Number.isSafeInteger(settings[key])||settings[key]<1||settings[key]>Math.max(512,original[key]))))throw new Error('Invalid effective runtime profile');
20
+ return {name:effective,...settings};
21
+ }
8
22
  return { name: effective, ...presets[effective] };
9
23
  }
@@ -40,9 +40,9 @@
40
40
  "renderIdleSeconds": null
41
41
  },
42
42
  "memory": {
43
- "optimizeForSize": true,
43
+ "optimizeForSize": false,
44
44
  "semiSpaceMiB": 2,
45
- "youngGenerationMiB": 4,
45
+ "youngGenerationMiB": 8,
46
46
  "renderStarts": 8,
47
47
  "liveResponses": 28,
48
48
  "apiConcurrency": 64,
@@ -1,3 +1,4 @@
1
+ import {reportRequestError} from './instrumentation.mjs';
1
2
  import { localePath } from '../compat/locale.cjs';
2
3
  import React from 'react';
3
4
  import DefaultApp, { loadGetInitialProps } from '../compat/app.cjs';
@@ -94,6 +95,7 @@ export async function renderPageRequest(options) {
94
95
  try { result = await renderPage({ ...options, capturedResponse }); }
95
96
  catch (error) {
96
97
  if ([400, 504].includes(error?.statusCode) || options.signal?.aborted) throw error;
98
+ reportRequestError(error);
97
99
  console.error('[prnext]', error?.stack || error);
98
100
  return renderPageError(options, { statusCode: 500, error, responseHeaders: capturedResponse.getHeaders() });
99
101
  }
@@ -266,6 +268,7 @@ export async function renderIsrPage(options) {
266
268
  catch (error) {
267
269
  if (!options.capturePageFailure) throw error;
268
270
  const pageFailure = summarizePageFailure(error);
271
+ reportRequestError(error);
269
272
  console.error('[prnext] Page generation failed:', pageFailure.stack || pageFailure.message);
270
273
  return { status: 500, headers: {}, body: Buffer.alloc(0), pageFailure };
271
274
  }
@@ -0,0 +1,44 @@
1
+ /** Shared browser route matching; importing this never loads either router. */
2
+ export function matchPagePattern(pattern, pathname) {
3
+ let segments;
4
+ try { segments = pathname.replace(/\/+$/, '').split('/').slice(1).map(decodeURIComponent); }
5
+ catch { return null; }
6
+ if (segments.length === 1 && !segments[0]) segments = [];
7
+ if (segments.some(segment => !segment || /[/\\\0]/.test(segment) || segment === '.' || segment === '..')) return null;
8
+ const parts = pattern === '/' ? [] : pattern.split('/').slice(1), params = Object.create(null), rank = [];
9
+ let index = 0;
10
+ for (const part of parts) {
11
+ const optional = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(part);
12
+ const catchall = /^\[\.\.\.([^\]]+)\]$/.exec(part);
13
+ const parameter = /^\[([^\]]+)\]$/.exec(part);
14
+ if (optional || catchall) {
15
+ if (catchall && index === segments.length) return null;
16
+ if (index !== segments.length) params[(optional || catchall)[1]] = segments.slice(index);
17
+ index = segments.length; rank.push(optional ? 0 : 1);
18
+ } else if (parameter) {
19
+ if (index === segments.length) return null;
20
+ params[parameter[1]] = segments[index++]; rank.push(2);
21
+ } else {
22
+ if (segments[index++] !== part) return null;
23
+ rank.push(3);
24
+ }
25
+ }
26
+ return index === segments.length ? { params, rank } : null;
27
+ }
28
+
29
+ export function isHtmlDocumentRoute(routes, pathname) {
30
+ let winner;
31
+ for (const route of routes || []) {
32
+ const match = matchPagePattern(route.pattern, pathname);
33
+ if (!match) continue;
34
+ const rank = winner && (() => {
35
+ for (let index = 0; index < Math.max(match.rank.length, winner.rank.length); index++) {
36
+ const difference = (match.rank[index] ?? 4) - (winner.rank[index] ?? 4);
37
+ if (difference) return difference;
38
+ }
39
+ return 0;
40
+ })();
41
+ if (!winner || rank > 0) winner = { ...match, route };
42
+ }
43
+ return winner?.route.htmlOnly === true;
44
+ }
@@ -1,3 +1,5 @@
1
+ import {drainTraces} from './trace.mjs';
2
+ import {withInstrumentation,reportRequestError,wasRequestErrorReported} from './instrumentation.mjs';
1
3
  import React from 'react';
2
4
  import {scheduleCompileCacheFlush} from './compile-cache.mjs';
3
5
  import {preloadModules} from './preload.mjs';
@@ -8,6 +10,7 @@ import { createFromReadableStream } from 'react-server-dom-webpack/client.edge';
8
10
  import { parentPort } from 'node:worker_threads';
9
11
  import { pathToFileURL } from 'node:url';
10
12
  import { createHash } from 'node:crypto';
13
+ import { navigationAuthorization } from '../compat/navigation-authorization.cjs';
11
14
  import { runRequestContext, currentRequest } from '../compat/headers.cjs';
12
15
  import { Metadata } from './app-metadata.mjs';
13
16
  import { appTimeoutError } from './app-errors.mjs';
@@ -27,6 +30,8 @@ import { flightConsumer } from './cache-components-rsc.mjs';
27
30
  import { partialModel, isPartialHole, boundedPartialFlight } from './app-partial-model.mjs';
28
31
  import { PartialTemplates, canResumePartialDirectly, partialTemplateReuse } from './partial-template.mjs';
29
32
  import { createAfterScope } from '../compat/after.cjs';
33
+ import {renderKeyFor} from './app-render-key.mjs';
34
+ import { selectAppStyles } from './app-style-selection.mjs';
30
35
 
31
36
  installFetchCache();
32
37
  if (process.env.PRNEXT_INSPECT_DIR) parentPort.postMessage({type:'rsc-ready'});
@@ -71,6 +76,9 @@ function queryObject(url) {
71
76
  }
72
77
 
73
78
  function createTree(entry, request, fallback = null) {
79
+ function RetainedBranch(props) {
80
+ return React.createElement(entry.ActivitySlot, { ...props, authorizationKey: navigationAuthorization(currentRequest(), request.authorizationSecret || request.actionKey, request.navigationCache?.authCookies) });
81
+ }
74
82
  const params = staticParams(request.params || {});
75
83
  const searchParams = staticSearchParams(queryObject(new URL(request.url)), currentRequest(), entry.page?.default?.$$typeof === Symbol.for('react.client.reference'));
76
84
  if (entry.routing && !fallback) return createAdvancedTree(entry, request, searchParams);
@@ -100,7 +108,7 @@ function createTree(entry, request, fallback = null) {
100
108
  const scopedParams = paramsBySegment(segments, request.params || {});
101
109
  for (const segment of segments) {
102
110
  scopes.set(segment, `${segment.path || ''}:${JSON.stringify(scopedParams.get(segment))}`);
103
- if (request.partialParams?.length) for (const prefix of ['', 'error:', 'navigation:', 'layout:']) keyScope(`${prefix}${segment.path || ''}:`, scopedParams.get(segment), prefix === 'layout:' ? ['segments'] : undefined);
111
+ if (request.partialParams?.length) for (const prefix of ['', 'error:', 'navigation:', 'layout:', 'activity:']) keyScope(`${prefix}${segment.path || ''}:`, scopedParams.get(segment), prefix === 'layout:' ? ['segments'] : prefix === 'activity:' ? ['identity', 'authorizationKey'] : ['error:', 'navigation:'].includes(prefix) ? ['resetKey'] : undefined);
104
112
  }
105
113
  let instantConfig = request.instantValidation ? entry.pageConfig?.instant : undefined;
106
114
  for (let index = fallback ? Math.min(fallback.index, segments.length - 1) : segments.length - 1; index >= 0; index--) {
@@ -128,6 +136,10 @@ function createTree(entry, request, fallback = null) {
128
136
  tree = React.createElement(component(segment.template, 'template'), { key: child ? scopes.get(child) : pageKey }, tree);
129
137
  }
130
138
  if (segment.layout) {
139
+ if (entry.ActivitySlot) tree = React.createElement(RetainedBranch, {
140
+ identity: segments.slice(index + 1).some(child => child.layout) ? scopes.get(segments[index + 1]) : pageKey + new URL(request.originalUrl || request.url).search,
141
+ key: `activity:${scopes.get(segment)}`,
142
+ }, tree);
131
143
  if (request.cacheComponents && segment === rootLayout) tree = React.createElement(React.Fragment, null, head, tree);
132
144
  tree = React.createElement(component(segment.layout, 'layout'), { params: staticParams(scopedParams.get(segment)), key: scopes.get(segment) }, tree);
133
145
  if (entry.LayoutProvider) tree = React.createElement(entry.LayoutProvider, { key: `layout:${scopes.get(segment)}`,
@@ -196,8 +208,9 @@ async function renderFlight(request, fallback = null, channel = null) {
196
208
  try {
197
209
  if (request.routingContexts?.size) request.renderRoutingBranch = routingBranchRenderer(cacheContext, bundlerConfig,
198
210
  renderSignal, error => publicError(error, request.production).digest);
199
- let model = request.controlModel || { ...(allowedRouting ? createTree(entry, request, fallback) : { tree: null, routingInvalid: true }), router, css: request.css || [], fonts: request.fonts || [], ...(request.instantDiagnostics?.length ? { instantDiagnostics: request.instantDiagnostics } : {}), ...request.actionModel };
211
+ let model = request.controlModel || { ...(allowedRouting ? createTree(entry, request, fallback) : { tree: null, routingInvalid: true }), router, styleRoute: request.routePattern, css: request.css || [], ...(request.cssScopes?.length ? { cssScopes: request.cssScopes } : {}), fonts: request.fonts || [], ...(request.instantDiagnostics?.length ? { instantDiagnostics: request.instantDiagnostics } : {}), ...request.actionModel };
200
212
  if (model.routing?.params) model.router.params = model.routing.params;
213
+ selectAppStyles(model);
201
214
  if (request.partialFlight) {
202
215
  const consumer = { serverConsumerManifest: flightConsumer(request), replayConsoleLogs: false };
203
216
  const decode = () => {
@@ -214,14 +227,16 @@ async function renderFlight(request, fallback = null, channel = null) {
214
227
  const keyMap = request.partialKeys ? new Map(request.partialKeys) : undefined;
215
228
  model = partialModel(stored, { live, keyMap, reuse: partialTemplateReuse(stored, keyMap) });
216
229
  }
230
+ model.authorizationKey = navigationAuthorization(cacheContext, request.authorizationSecret || request.actionKey, request.navigationCache?.authCookies);
217
231
  const stream = renderToReadableStream(model, bundlerConfig, {
218
232
  signal: renderSignal,
219
233
  temporaryReferences: request.temporaryReferences,
220
234
  onError(error) {
221
235
  const failure = error && typeof error === 'object' ? error : new Error(String(error));
222
- if (cacheContext.staticState?.partial && isPartialHole(failure)) return 'PRNEXT_PPR_DYNAMIC';
236
+ if ((cacheContext.staticState?.partial || request.navigationStage) && isPartialHole(failure)) return 'PRNEXT_PPR_DYNAMIC';
223
237
  renderError ??= failure;
224
238
  const digest = publicError(failure, request.production).digest;
239
+ if(ordinaryRenderError(failure)&&!abort.signal.aborted&&!channel?.signal.aborted){if(!failure.digest&&Object.isExtensible(failure))failure.digest=digest;const reporting=reportRequestError(failure);if(reporting&&digest.length<=1024&&request.instrumentationId!==undefined)parentPort.postMessage({id:request.instrumentationId,type:'instrumentation-error',value:digest});}
225
240
  if (!abort.signal.aborted && !channel?.signal.aborted && ordinaryRenderError(failure)) console.error(`[prnext] ${digest}`, failure?.stack || failure);
226
241
  return digest;
227
242
  },
@@ -342,7 +357,12 @@ async function renderFlight(request, fallback = null, channel = null) {
342
357
  });
343
358
  }
344
359
 
345
- async function handleRequest(request, channel = null) {
360
+ async function handleRequest(request,channel=null){
361
+ process.env.NEXT_RUNTIME='nodejs';
362
+ if(!request.runtimeInstrumentation&&(request.operation==='static-params'||request.staticGeneration||request.staticState))return handleRequestInner(request,channel);
363
+ return withInstrumentation({...request,signal:channel?.signal,manifest:{instrumentation:request.instrumentation},route:{router:'app',kind:'page',pattern:request.routePattern,cacheConfig:request.cacheConfig}},()=>handleRequestInner(request,channel),{realm:'rsc'});
364
+ }
365
+ async function handleRequestInner(request, channel = null) {
346
366
  if (request.operation === 'static-params') {
347
367
  return runRequestContext({ ...request, phase: 'render' }, async () => {
348
368
  const context = currentRequest();
@@ -390,6 +410,7 @@ const responseCompletions = new Map();
390
410
  const manifests = new Map();
391
411
  const artifacts = new Map();
392
412
  parentPort.on('message', async ({ id, request, type, value, token }) => {
413
+ if(type==='telemetry-flush'){await drainTraces();parentPort.postMessage({type:'telemetry-flushed',token});return;}
393
414
  if(type==='preload'){
394
415
  try{await preloadModules(value.files,value.options,file=>routeEntry({modulePath:file,production:true}));parentPort.postMessage({type:'preloaded',id});}
395
416
  catch(error){parentPort.postMessage({type:'preloaded',id,error:error.message});}return;
@@ -410,17 +431,18 @@ parentPort.on('message', async ({ id, request, type, value, token }) => {
410
431
  for (const field of ['clientModules', 'actions']) {
411
432
  if (request[field + 'Id'] !== undefined) request[field] = manifests.get(request[field + 'Id']);
412
433
  }
434
+ request.instrumentationId=id;
413
435
  const channel = request.stream && (!request.action || request.action.id) ? workerStreamChannel(parentPort, id) : null;
414
436
  let responseDone, responseTimer;
415
- if (request.action && request.cacheConfig?.runtime !== 'edge') {
437
+ if (request.cacheConfig?.runtime !== 'edge') {
416
438
  const timeoutMs = request.renderTimeoutMs ?? 25000;
417
- request.afterScope = createAfterScope({ timeoutMs, onComplete: flushCacheWork,
418
- onTimeout: error => parentPort.postMessage({ id, error: { message: error.message, retireWorker: true } }) });
419
- if (request.waitForResponse) responseDone = new Promise(resolve => {
439
+ request.afterScope = createAfterScope({report:error=>{console.error('[prnext] after() failed:',error?.stack||error);return reportRequestError(error);}, timeoutMs, onComplete: flushCacheWork,
440
+ onTimeout: error => parentPort.postMessage({ id, error: { message: error.message, retireWorker: true } }),
441
+ onRegistered() { if (request.waitForResponse) responseDone = new Promise(resolve => {
420
442
  const finish = () => { clearTimeout(responseTimer); resolve(); };
421
443
  responseCompletions.set(id, finish);
422
444
  responseTimer = setTimeout(finish, timeoutMs);
423
- });
445
+ }); } });
424
446
  }
425
447
  activeRequests.add(id);
426
448
  if (channel) streamChannels.set(id, channel);
@@ -432,14 +454,14 @@ parentPort.on('message', async ({ id, request, type, value, token }) => {
432
454
  delete request.partialFlightId;
433
455
  }
434
456
  if (channel) {
435
- await handleRequest({ ...request, renderKey: id }, channel);
457
+ await handleRequest({ ...request, renderKey: renderKeyFor(request, id) }, channel);
436
458
  return;
437
459
  }
438
- const result = await handleRequest({ ...request, renderKey: id });
460
+ const result = await handleRequest({ ...request, renderKey: renderKeyFor(request, id) });
439
461
  parentPort.postMessage({ id, ...result }, result.body instanceof ArrayBuffer ? [result.body] : []);
440
462
  } catch (error) {
441
463
  parentPort.postMessage({ id, error: {
442
- message: error?.message || String(error), stack: error?.stack, digest: error?.digest, statusCode: error?.statusCode,
464
+ instrumentationReported:wasRequestErrorReported(error), message: error?.message || String(error), stack: error?.stack, digest: error?.digest, statusCode: error?.statusCode,
443
465
  retireWorker: error?.retireWorker, responseHeaders: error?.responseHeaders,
444
466
  code: error?.code, staticMode: error?.staticMode, dynamicReason: error?.dynamicReason,
445
467
  } });
@@ -0,0 +1,78 @@
1
+ import {isControlError} from './navigation.mjs';
2
+ import {AsyncLocalStorage} from 'node:async_hooks';
3
+ import {randomBytes} from 'node:crypto';
4
+ import {createRequire} from 'node:module';
5
+ const scope=new AsyncLocalStorage(),rawFetch=globalThis.fetch;
6
+ let api;
7
+ try{api=createRequire(import.meta.url)('@opentelemetry/api');}catch{}
8
+ const endpoint=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT||(process.env.OTEL_EXPORTER_OTLP_ENDPOINT?process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/$/,'')+'/v1/traces':undefined);
9
+ const protocol=process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL||process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
10
+ const enabled=process.env.OTEL_SDK_DISABLED!=='true'&&endpoint&&(!protocol||protocol==='http/json');
11
+ const queue=[];let timer,flushing;
12
+ const epoch=BigInt(Date.now())*1000000n-process.hrtime.bigint();
13
+ const nanos=()=>String(epoch+process.hrtime.bigint());
14
+ export function parseTraceparent(value){
15
+ const match=/^00-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})$/.exec(value||'');
16
+ return match&&!/^0+$/.test(match[1])&&!/^0+$/.test(match[2])?{traceId:match[1],spanId:match[2],traceFlags:parseInt(match[3],16)&1}:undefined;
17
+ }
18
+ export function currentTraceparent(){const value=scope.getStore();return value?`00-${value.traceId}-${value.spanId}-${value.traceFlags?'01':'00'}`:undefined;}
19
+ export function propagatedFetch(input,init){
20
+ const parent=currentTraceparent();if(!parent)return [input,init];
21
+ const headers=new Headers(init?.headers??(input instanceof Request?input.headers:undefined));
22
+ if(!headers.has('traceparent'))headers.set('traceparent',parent);
23
+ return [input,{...init,headers}];
24
+ }
25
+ function enqueue(span){
26
+ if(!enabled||!span.flags||queue.length>=1024)return;
27
+ queue.push(span);if(!timer)timer=setTimeout(()=>{timer=undefined;void flushTraces();},200).unref();
28
+ }
29
+ export async function flushTraces(){
30
+ if(flushing)return flushing;
31
+ if(!queue.length)return;
32
+ const headers={'content-type':'application/json'};
33
+ for(const part of (process.env.OTEL_EXPORTER_OTLP_TRACES_HEADERS||process.env.OTEL_EXPORTER_OTLP_HEADERS||'').split(',')){
34
+ const split=part.indexOf('=');if(split>0)try{headers[part.slice(0,split).trim()]=decodeURIComponent(part.slice(split+1).trim());}catch{}
35
+ }
36
+ const spans=queue.splice(0,128);
37
+ flushing=(async()=>{
38
+ const body=JSON.stringify({resourceSpans:[{resource:{attributes:[{key:'service.name',value:{stringValue:process.env.OTEL_SERVICE_NAME||'prnext'}}]},scopeSpans:[{scope:{name:'prnext.runtime'},spans}]}]});
39
+ for(let attempt=0;attempt<3;attempt++){
40
+ let retry=false;
41
+ try{
42
+ const response=await rawFetch(endpoint,{method:'POST',redirect:'error',headers,body,signal:AbortSignal.timeout(1000)});
43
+ retry=[429,502,503,504].includes(response.status);await response.body?.cancel();
44
+ }catch{retry=true;} // Telemetry failure never changes an application response.
45
+ if(!retry)break;
46
+ if(attempt<2)await new Promise(resolve=>setTimeout(resolve,100*2**attempt).unref());
47
+ }
48
+ })().finally(()=>{flushing=undefined;if(queue.length&&!timer)timer=setTimeout(()=>{timer=undefined;void flushTraces();},200).unref();});
49
+ return flushing;
50
+ }
51
+ export async function drainTraces(){
52
+ const deadline=Date.now()+2500;let timeout;
53
+ try{
54
+ await Promise.race([(async()=>{while((flushing||queue.length)&&Date.now()<deadline)await(flushing||flushTraces());})(),new Promise(resolve=>{timeout=setTimeout(resolve,2500);})]);
55
+ }finally{clearTimeout(timeout);clearTimeout(timer);timer=undefined;}
56
+ }
57
+ export function markTraceError(){const state=scope.getStore();if(state?.span)state.span.status={code:2};state?.sdkSpan?.setStatus({code:2});}
58
+ export async function withTrace(options,name,callback){
59
+ const parent=scope.getStore()||parseTraceparent(options.traceparent||new Headers(options.headers).get('traceparent'));
60
+ if(!enabled&&!api&&!parent)return callback();
61
+ let sdkSpan;
62
+ if(!enabled&&api){
63
+ const remote=parent?api.trace.setSpanContext(api.context.active(),{traceId:parent.traceId,spanId:parent.spanId,traceFlags:parent.traceFlags,isRemote:true}):api.context.active();
64
+ sdkSpan=api.trace.getTracer('prnext.runtime').startSpan(name,{kind:options.kind||1},remote);
65
+ const context=sdkSpan.spanContext();
66
+ if(!api.trace.isSpanContextValid(context)){sdkSpan.end();return callback();}
67
+ const run=()=>scope.run({...context,sdkSpan},callback);
68
+ try{return await api.context.with(api.trace.setSpan(remote,sdkSpan),run);}catch(error){if(!isControlError(error))sdkSpan.setStatus({code:2});throw error;}finally{sdkSpan.end();}
69
+ }
70
+ const span={traceId:parent?.traceId||randomBytes(16).toString('hex'),spanId:randomBytes(8).toString('hex'),parentSpanId:parent?.spanId,
71
+ flags:parent?.traceFlags??1,name,kind:options.kind||1,startTimeUnixNano:nanos(),attributes:[]};
72
+ if(options.route?.pattern)span.attributes.push({key:'http.route',value:{stringValue:options.route.pattern}});
73
+ const state={traceId:span.traceId,spanId:span.spanId,traceFlags:span.flags,span};
74
+ const run=()=>scope.run(state,async()=>{
75
+ try{const result=await callback();if(result?.status>=500)span.status={code:2};return result;}catch(error){if(!isControlError(error))span.status={code:2};throw error;}finally{span.endTimeUnixNano=nanos();enqueue(span);}
76
+ });
77
+ return api?api.context.with(api.trace.setSpanContext(api.context.active(),{traceId:state.traceId,spanId:state.spanId,traceFlags:state.traceFlags}),run):run();
78
+ }