@thomas.f/prnext 0.1.17-alpha → 0.1.18-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 (137) hide show
  1. package/README.md +26 -869
  2. package/build/actions.mjs +1 -1
  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 +81 -26
  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 +31 -8
  15. package/build/config.mjs +30 -1
  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-update.mjs +4 -2
  21. package/build/dev-dependencies.mjs +1 -1
  22. package/build/dev-discovery.mjs +3 -3
  23. package/build/dev.mjs +15 -8
  24. package/build/dynamic.mjs +1 -1
  25. package/build/edge.mjs +3 -3
  26. package/build/fonts.mjs +1 -1
  27. package/build/framework-imports.mjs +15 -5
  28. package/build/html-only-config.mjs +53 -0
  29. package/build/html-only-graph.mjs +67 -0
  30. package/build/index.mjs +59 -26
  31. package/build/instrumentation.mjs +30 -0
  32. package/build/metadata-routes.mjs +1 -1
  33. package/build/migration-audit.mjs +1 -1
  34. package/build/native-type-config.mjs +37 -0
  35. package/build/persistent-cache.mjs +18 -3
  36. package/build/precompress.mjs +1 -1
  37. package/build/prerender-cache.mjs +70 -0
  38. package/build/prerender-config.mjs +29 -0
  39. package/build/prerender-pages.mjs +19 -9
  40. package/build/prerender-pool.mjs +165 -0
  41. package/build/prerender-process-worker.mjs +58 -0
  42. package/build/prerender-worker.mjs +5 -5
  43. package/build/production-builder.mjs +58 -9
  44. package/build/react-compiler-report.mjs +225 -0
  45. package/build/react-compiler.mjs +139 -0
  46. package/build/runtime-contract.mjs +2 -2
  47. package/build/static-export.mjs +4 -4
  48. package/build/styles.mjs +6 -4
  49. package/build/transform.mjs +1 -1
  50. package/build/typed-routes.mjs +4 -0
  51. package/build/typescript-dev.mjs +35 -8
  52. package/build/typescript-native-worker.mjs +55 -0
  53. package/build/typescript-native.mjs +18 -0
  54. package/build/typescript-toolchain.mjs +19 -0
  55. package/build/typescript.mjs +6 -4
  56. package/build/webpack-graph.mjs +6 -3
  57. package/build/worker-memory.mjs +9 -0
  58. package/cli.mjs +60 -4
  59. package/compat/activity-cache.cjs +26 -0
  60. package/compat/after.cjs +6 -4
  61. package/compat/app-layout-context.cjs +52 -2
  62. package/compat/base64.cjs +10 -0
  63. package/compat/cache-handlers.cjs +12 -2
  64. package/compat/cache-wire.cjs +28 -0
  65. package/compat/cache.cjs +2 -1
  66. package/compat/cache.d.cts +4 -0
  67. package/compat/cookies.cjs +21 -5
  68. package/compat/data-cache.cjs +12 -8
  69. package/compat/flight-delta.cjs +150 -0
  70. package/compat/headers.cjs +4 -2
  71. package/compat/incremental-cache.cjs +20 -7
  72. package/compat/index.d.ts +68 -3
  73. package/compat/link.cjs +12 -9
  74. package/compat/navigation-authorization.cjs +9 -0
  75. package/compat/navigation-stages.cjs +19 -0
  76. package/compat/offline-store.cjs +11 -0
  77. package/compat/offline.cjs +5 -0
  78. package/compat/offline.d.cts +2 -0
  79. package/compat/redis-cache.cjs +201 -0
  80. package/compat/redis-cache.d.cts +37 -0
  81. package/compat/server-primitives.cjs +31 -11
  82. package/compat/server.d.cts +15 -2
  83. package/compat/static-generation.cjs +7 -0
  84. package/compat/use-cache.cjs +3 -0
  85. package/docs/ai/compatibility.md +171 -2
  86. package/docs/ai/data.md +165 -5
  87. package/docs/ai/deployment.md +158 -0
  88. package/docs/ai/quickstart.md +29 -4
  89. package/docs/ai/routing.md +82 -0
  90. package/docs/ai/troubleshooting.md +37 -2
  91. package/package.json +26 -7
  92. package/runtime/action-client.mjs +10 -4
  93. package/runtime/action-server.mjs +2 -0
  94. package/runtime/app-client.mjs +212 -65
  95. package/runtime/app-content.mjs +4 -3
  96. package/runtime/app-partial-client.mjs +3 -0
  97. package/runtime/app-partial.mjs +1 -1
  98. package/runtime/app-prefetch-constants.mjs +4 -0
  99. package/runtime/app-prefetch.mjs +2 -3
  100. package/runtime/app-render-key.mjs +5 -0
  101. package/runtime/app-render.mjs +88 -28
  102. package/runtime/app-routing.mjs +22 -9
  103. package/runtime/app-static.mjs +18 -8
  104. package/runtime/app-stream-html.mjs +10 -53
  105. package/runtime/app-style-selection.mjs +34 -0
  106. package/runtime/app-styles.mjs +154 -0
  107. package/runtime/cache-components-rsc.mjs +4 -1
  108. package/runtime/cache-request.mjs +2 -1
  109. package/runtime/deployment-client.mjs +19 -0
  110. package/runtime/dev-client.mjs +7 -2
  111. package/runtime/dev-compiler-broker.mjs +42 -0
  112. package/runtime/dev-indicator.mjs +8 -3
  113. package/runtime/dev-lazy-tool.mjs +16 -0
  114. package/runtime/development.mjs +29 -12
  115. package/runtime/document.mjs +1 -1
  116. package/runtime/edge.mjs +6 -5
  117. package/runtime/fetch-cache.mjs +10 -8
  118. package/runtime/flight-delta.mjs +50 -0
  119. package/runtime/flight-tap.mjs +28 -0
  120. package/runtime/html-only.mjs +27 -0
  121. package/runtime/http.mjs +2 -0
  122. package/runtime/incremental-cache.mjs +25 -4
  123. package/runtime/instrumentation.mjs +49 -0
  124. package/runtime/navigation-recovery.mjs +36 -0
  125. package/runtime/navigation.mjs +7 -0
  126. package/runtime/node-html-stream.mjs +47 -0
  127. package/runtime/pages-client.mjs +14 -7
  128. package/runtime/pages-navigation.mjs +48 -63
  129. package/runtime/prefetch-scheduler.mjs +110 -0
  130. package/runtime/profile.mjs +14 -0
  131. package/runtime/profiles.json +2 -2
  132. package/runtime/render.mjs +3 -0
  133. package/runtime/route-pattern.mjs +44 -0
  134. package/runtime/rsc-worker.mjs +34 -12
  135. package/runtime/trace.mjs +78 -0
  136. package/runtime/watch-project.mjs +19 -15
  137. package/runtime/worker.mjs +70 -26
@@ -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
+ }
@@ -23,14 +23,14 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
23
23
  // Rebuilding on those reads restarts the server (and its SSE stream).
24
24
  try { if (statSync(path.join(root, relative)).isDirectory()) return; } catch {}
25
25
  }
26
- onChange(relative);
26
+ onChange(relative,event);
27
27
  });
28
28
  watcher.on('error', error => { watcher.close(); onError(error); });
29
29
  return { close: () => watcher.close(), async refresh() {} };
30
30
  }
31
31
  const directories = new Map();
32
- const pending = new Set();
33
- let closed = false, ready = false, timer, running, refreshAll = false;
32
+ const pending = new Map();
33
+ let closed = false, ready = false, timer, running;
34
34
  const allowedDirectory = relative => !relative || (!ignore(relative) && (relative === '.contentlayer' || shouldWatchProjectFile(relative)));
35
35
  const allowedFile = relative => relative && !ignore(relative) && shouldWatchProjectFile(relative);
36
36
  function remove(relative) {
@@ -51,9 +51,9 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
51
51
  close();
52
52
  onError(error);
53
53
  }
54
- function queue(relative) {
54
+ function queue(relative, recursive = false) {
55
55
  if (closed) return;
56
- pending.add(relative);
56
+ pending.set(relative, recursive || pending.get(relative) === true);
57
57
  clearTimeout(timer);
58
58
  timer = setTimeout(() => { void drain().catch(fail); }, 20);
59
59
  }
@@ -84,18 +84,24 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
84
84
  if (process.platform === 'win32' && event === 'change') {
85
85
  try { metadataOnly = statSync(path.join(root, changed)).isDirectory(); } catch {}
86
86
  }
87
- if (allowedFile(changed) && !metadataOnly) onChange(changed);
87
+ if (allowedFile(changed) && !metadataOnly) onChange(changed,event);
88
88
  // The .contentlayer container itself is ignored as a source, but
89
89
  // its generated directory may be created after the watcher starts.
90
- // Platforms can coalesce a directory rename into a change event.
91
- // Rescan only this directory, rather than relying on the event name.
92
- if (allowedDirectory(changed) || allowedFile(changed)) queue(relative);
90
+ // Coalesced notifications can name an already subscribed ancestor,
91
+ // without a separate event from the changed descendant. Reconcile
92
+ // that subtree even when its existing directory watches are intact.
93
+ // Ordinary file edits still scan only their immediate parent.
94
+ if (!filename) queue(relative, true);
95
+ else if (allowedDirectory(changed) || allowedFile(changed)) {
96
+ queue(relative);
97
+ if (!metadataOnly && directories.has(changed)) queue(changed, true);
98
+ }
93
99
  });
94
100
  } catch (error) { if (relative && missing(error)) { disappeared(relative); return; } throw error; }
95
101
  directories.set(relative, watcher);
96
102
  // Files may already exist by the time a newly created directory is
97
103
  // subscribed. Its discovery must also invalidate the source snapshot.
98
- if (ready && allowedFile(relative)) onChange(relative);
104
+ if (ready && allowedFile(relative)) onChange(relative,'rename');
99
105
  watcher.on('error', error => {
100
106
  if (closed) return;
101
107
  if (relative && missing(error)) disappeared(relative);
@@ -109,7 +115,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
109
115
  const children = new Set();
110
116
  for (const entry of entries) {
111
117
  const child = path.posix.join(relative, entry.name);
112
- if (fresh && ready && entry.isFile() && allowedFile(child)) onChange(child);
118
+ if (fresh && ready && entry.isFile() && allowedFile(child)) onChange(child,'rename');
113
119
  if (!entry.isDirectory() || !allowedDirectory(child)) continue;
114
120
  children.add(child);
115
121
  if (recursive || !directories.has(child)) await scan(child, recursive);
@@ -123,8 +129,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
123
129
  running = (async () => {
124
130
  while (!closed && pending.size) {
125
131
  const batch = [...pending]; pending.clear();
126
- const recursive = refreshAll; refreshAll = false;
127
- for (const relative of batch) await scan(relative, recursive);
132
+ for (const [relative, recursive] of batch) await scan(relative, recursive);
128
133
  }
129
134
  })().finally(() => { running = undefined; });
130
135
  return running;
@@ -136,8 +141,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
136
141
  close,
137
142
  async refresh() {
138
143
  if (closed) return;
139
- refreshAll = true;
140
- pending.add('');
144
+ pending.set('', true);
141
145
  await drain();
142
146
  },
143
147
  };