@thomas.f/prnext 0.1.16-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 (215) hide show
  1. package/AI.md +63 -0
  2. package/README.md +35 -503
  3. package/build/ADAPTERS.md +27 -2
  4. package/build/actions.mjs +6 -1
  5. package/build/analyze.mjs +340 -0
  6. package/build/app-config.mjs +20 -8
  7. package/build/app-static.mjs +41 -14
  8. package/build/app-styles.mjs +79 -0
  9. package/build/app.mjs +99 -31
  10. package/build/build-lock.mjs +6 -6
  11. package/build/bundle-runtime.mjs +39 -11
  12. package/build/cache-components.mjs +1 -1
  13. package/build/cache-handlers.mjs +1 -1
  14. package/build/check.mjs +56 -11
  15. package/build/compact-css.mjs +53 -0
  16. package/build/compilation-cache.mjs +269 -0
  17. package/build/compilation-store.mjs +142 -0
  18. package/build/compiler.mjs +51 -4
  19. package/build/concurrent.mjs +12 -0
  20. package/build/config.mjs +50 -13
  21. package/build/create.mjs +239 -0
  22. package/build/css-modules.mjs +65 -10
  23. package/build/deployment-assets.mjs +89 -0
  24. package/build/deployment-config.mjs +18 -0
  25. package/build/dev-compiler-health.mjs +11 -0
  26. package/build/dev-css-update.mjs +85 -0
  27. package/build/dev-dependencies.mjs +44 -0
  28. package/build/dev-discovery.mjs +28 -0
  29. package/build/dev-worker.mjs +14 -5
  30. package/build/dev.mjs +64 -6
  31. package/build/dynamic.mjs +1 -1
  32. package/build/edge.mjs +5 -4
  33. package/build/fonts.mjs +1 -1
  34. package/build/framework-imports.mjs +50 -0
  35. package/build/html-only-config.mjs +53 -0
  36. package/build/html-only-graph.mjs +67 -0
  37. package/build/incremental.mjs +8 -4
  38. package/build/index.mjs +140 -112
  39. package/build/inspect.mjs +3 -0
  40. package/build/inspection-summary.mjs +37 -4
  41. package/build/instrumentation.mjs +30 -0
  42. package/build/loader-cache.mjs +53 -26
  43. package/build/metadata-routes.mjs +3 -4
  44. package/build/migrate.mjs +60 -57
  45. package/build/migration-audit.mjs +1 -1
  46. package/build/native-type-config.mjs +37 -0
  47. package/build/npm-packages.mjs +3 -18
  48. package/build/package-manager.mjs +308 -0
  49. package/build/persistent-cache.mjs +112 -0
  50. package/build/precompress.mjs +62 -30
  51. package/build/prerender-cache.mjs +70 -0
  52. package/build/prerender-config.mjs +29 -0
  53. package/build/prerender-pages.mjs +100 -0
  54. package/build/prerender-pool.mjs +165 -0
  55. package/build/prerender-process-worker.mjs +58 -0
  56. package/build/prerender-worker.mjs +10 -0
  57. package/build/production-builder.mjs +129 -0
  58. package/build/react-compiler-report.mjs +225 -0
  59. package/build/react-compiler.mjs +139 -0
  60. package/build/runtime-contract.mjs +2 -2
  61. package/build/scan.mjs +4 -4
  62. package/build/source-analysis.mjs +42 -0
  63. package/build/static-adapter.mjs +13 -5
  64. package/build/static-export.mjs +4 -4
  65. package/build/static-input.mjs +48 -0
  66. package/build/styles.mjs +17 -3
  67. package/build/transform-cache.mjs +37 -4
  68. package/build/transform.mjs +1 -1
  69. package/build/type-command.mjs +41 -0
  70. package/build/typed-routes.mjs +95 -0
  71. package/build/typescript-dev-worker.mjs +18 -0
  72. package/build/typescript-dev.mjs +49 -0
  73. package/build/typescript-native-worker.mjs +55 -0
  74. package/build/typescript-native.mjs +18 -0
  75. package/build/typescript-toolchain.mjs +19 -0
  76. package/build/typescript-watch.mjs +22 -0
  77. package/build/typescript.mjs +11 -8
  78. package/build/webpack-graph.mjs +170 -30
  79. package/build/worker-memory.mjs +9 -0
  80. package/build/worker-preload.mjs +9 -0
  81. package/cli.mjs +162 -122
  82. package/compat/activity-cache.cjs +26 -0
  83. package/compat/after.cjs +6 -4
  84. package/compat/app-layout-context.cjs +52 -2
  85. package/compat/base64.cjs +10 -0
  86. package/compat/cache-handlers.cjs +12 -2
  87. package/compat/cache-wire.cjs +28 -0
  88. package/compat/cache.cjs +2 -1
  89. package/compat/cache.d.cts +4 -0
  90. package/compat/cookies.cjs +21 -5
  91. package/compat/data-cache.cjs +12 -8
  92. package/compat/flight-delta.cjs +150 -0
  93. package/compat/headers.cjs +4 -2
  94. package/compat/incremental-cache.cjs +20 -7
  95. package/compat/index.d.ts +89 -10
  96. package/compat/link.cjs +12 -9
  97. package/compat/link.d.cts +4 -3
  98. package/compat/navigation-authorization.cjs +9 -0
  99. package/compat/navigation-stages.cjs +19 -0
  100. package/compat/navigation.d.cts +4 -3
  101. package/compat/offline-store.cjs +11 -0
  102. package/compat/offline.cjs +5 -0
  103. package/compat/offline.d.cts +2 -0
  104. package/compat/redis-cache.cjs +201 -0
  105. package/compat/redis-cache.d.cts +37 -0
  106. package/compat/router.d.cts +4 -3
  107. package/compat/server-primitives.cjs +31 -11
  108. package/compat/server.d.cts +15 -2
  109. package/compat/static-generation.cjs +7 -0
  110. package/compat/typed-route.d.cts +17 -0
  111. package/compat/use-cache.cjs +3 -0
  112. package/docs/ai/compatibility.md +275 -0
  113. package/docs/ai/data.md +288 -0
  114. package/docs/ai/deployment.md +269 -0
  115. package/docs/ai/manifest.json +11 -0
  116. package/docs/ai/quickstart.md +171 -0
  117. package/docs/ai/routing.md +214 -0
  118. package/docs/ai/troubleshooting.md +155 -0
  119. package/index.mjs +2 -0
  120. package/native/diagnostics.mjs +34 -0
  121. package/native/resolve.mjs +7 -2
  122. package/package.json +44 -7
  123. package/release/ai-docs.mjs +48 -0
  124. package/release/verify.mjs +2 -0
  125. package/runtime/action-client.mjs +10 -4
  126. package/runtime/action-server.mjs +2 -0
  127. package/runtime/ai-docs.mjs +202 -0
  128. package/runtime/app-client.mjs +226 -68
  129. package/runtime/app-content.mjs +4 -3
  130. package/runtime/app-partial-client.mjs +3 -0
  131. package/runtime/app-partial.mjs +1 -1
  132. package/runtime/app-prefetch-constants.mjs +4 -0
  133. package/runtime/app-prefetch.mjs +2 -3
  134. package/runtime/app-render-key.mjs +5 -0
  135. package/runtime/app-render.mjs +100 -28
  136. package/runtime/app-routing.mjs +22 -9
  137. package/runtime/app-static.mjs +18 -8
  138. package/runtime/app-stream-html.mjs +10 -53
  139. package/runtime/app-style-selection.mjs +34 -0
  140. package/runtime/app-styles.mjs +154 -0
  141. package/runtime/cache-components-rsc.mjs +4 -1
  142. package/runtime/cache-request.mjs +2 -1
  143. package/runtime/cloud/archive.mjs +5 -6
  144. package/runtime/compile-cache.mjs +8 -0
  145. package/runtime/deployment-client.mjs +19 -0
  146. package/runtime/dev-ai-brands.mjs +26 -0
  147. package/runtime/dev-ai-style.mjs +99 -0
  148. package/runtime/dev-build-reasons.mjs +25 -0
  149. package/runtime/dev-cache-control.mjs +53 -0
  150. package/runtime/dev-client.mjs +245 -46
  151. package/runtime/dev-compiler-broker.mjs +42 -0
  152. package/runtime/dev-console-copy.mjs +19 -0
  153. package/runtime/dev-console.mjs +106 -0
  154. package/runtime/dev-dependency-graph.mjs +40 -0
  155. package/runtime/dev-element-picker-style.mjs +27 -0
  156. package/runtime/dev-element-picker.mjs +213 -0
  157. package/runtime/dev-error-overlay-style.mjs +41 -0
  158. package/runtime/dev-error-overlay.mjs +167 -0
  159. package/runtime/dev-error-report.mjs +121 -0
  160. package/runtime/dev-file-changes.mjs +1 -1
  161. package/runtime/dev-gateway.mjs +44 -0
  162. package/runtime/dev-host-relay.mjs +66 -0
  163. package/runtime/dev-indicator.mjs +191 -0
  164. package/runtime/dev-issue-motion.mjs +214 -0
  165. package/runtime/dev-lazy-tool.mjs +34 -0
  166. package/runtime/dev-logo.mjs +2 -0
  167. package/runtime/dev-notification.mjs +81 -0
  168. package/runtime/dev-popout.mjs +34 -0
  169. package/runtime/dev-position.mjs +107 -0
  170. package/runtime/dev-preview-touch.mjs +122 -0
  171. package/runtime/dev-runtime-pool.mjs +45 -0
  172. package/runtime/dev-runtime-spare.mjs +57 -0
  173. package/runtime/dev-tools-client.mjs +155 -0
  174. package/runtime/dev-tools-mcp.mjs +126 -0
  175. package/runtime/dev-tools-metrics.mjs +31 -0
  176. package/runtime/dev-tools-model.mjs +42 -0
  177. package/runtime/dev-tools-panel.mjs +341 -0
  178. package/runtime/dev-tools-result.mjs +37 -0
  179. package/runtime/dev-tools-server.mjs +256 -0
  180. package/runtime/dev-tools-source.mjs +104 -0
  181. package/runtime/dev-tooltip.mjs +94 -0
  182. package/runtime/dev-ui-icons.mjs +8 -0
  183. package/runtime/dev-workbench-style.mjs +106 -0
  184. package/runtime/dev-workbench.mjs +428 -0
  185. package/runtime/dev-worker-logs.mjs +31 -0
  186. package/runtime/development.mjs +324 -0
  187. package/runtime/document.mjs +1 -1
  188. package/runtime/edge.mjs +6 -5
  189. package/runtime/env.mjs +2 -1
  190. package/runtime/fetch-cache.mjs +10 -8
  191. package/runtime/flight-delta.mjs +50 -0
  192. package/runtime/flight-tap.mjs +28 -0
  193. package/runtime/html-only.mjs +27 -0
  194. package/runtime/http.mjs +2 -0
  195. package/runtime/incremental-cache.mjs +25 -4
  196. package/runtime/instrumentation.mjs +49 -0
  197. package/runtime/navigation-recovery.mjs +36 -0
  198. package/runtime/navigation.mjs +7 -0
  199. package/runtime/node-html-stream.mjs +47 -0
  200. package/runtime/pages-client.mjs +24 -8
  201. package/runtime/pages-navigation.mjs +54 -64
  202. package/runtime/persistent/client.mjs +6 -5
  203. package/runtime/persistent/daemon.mjs +66 -56
  204. package/runtime/persistent/native-proxy.mjs +63 -0
  205. package/runtime/prefetch-scheduler.mjs +110 -0
  206. package/runtime/preload.mjs +12 -0
  207. package/runtime/profile.mjs +14 -0
  208. package/runtime/profiles.json +2 -2
  209. package/runtime/render.mjs +3 -0
  210. package/runtime/route-pattern.mjs +44 -0
  211. package/runtime/rsc-worker.mjs +41 -12
  212. package/runtime/trace.mjs +78 -0
  213. package/runtime/typed-route.mjs +19 -0
  214. package/runtime/watch-project.mjs +29 -18
  215. package/runtime/worker.mjs +102 -24
@@ -1,4 +1,8 @@
1
+ import {drainTraces} from './trace.mjs';
2
+ import {withInstrumentation,reportRequestError,wasRequestErrorReported} from './instrumentation.mjs';
1
3
  import React from 'react';
4
+ import {scheduleCompileCacheFlush} from './compile-cache.mjs';
5
+ import {preloadModules} from './preload.mjs';
2
6
  import { flightBundler } from './worker-metadata.mjs';
3
7
  import { removeBasePath, normalizeTrailingSlash } from '../compat/paths.cjs';
4
8
  import { renderToReadableStream } from 'react-server-dom-webpack/server.node';
@@ -6,6 +10,7 @@ import { createFromReadableStream } from 'react-server-dom-webpack/client.edge';
6
10
  import { parentPort } from 'node:worker_threads';
7
11
  import { pathToFileURL } from 'node:url';
8
12
  import { createHash } from 'node:crypto';
13
+ import { navigationAuthorization } from '../compat/navigation-authorization.cjs';
9
14
  import { runRequestContext, currentRequest } from '../compat/headers.cjs';
10
15
  import { Metadata } from './app-metadata.mjs';
11
16
  import { appTimeoutError } from './app-errors.mjs';
@@ -25,6 +30,8 @@ import { flightConsumer } from './cache-components-rsc.mjs';
25
30
  import { partialModel, isPartialHole, boundedPartialFlight } from './app-partial-model.mjs';
26
31
  import { PartialTemplates, canResumePartialDirectly, partialTemplateReuse } from './partial-template.mjs';
27
32
  import { createAfterScope } from '../compat/after.cjs';
33
+ import {renderKeyFor} from './app-render-key.mjs';
34
+ import { selectAppStyles } from './app-style-selection.mjs';
28
35
 
29
36
  installFetchCache();
30
37
  if (process.env.PRNEXT_INSPECT_DIR) parentPort.postMessage({type:'rsc-ready'});
@@ -40,6 +47,7 @@ async function routeEntry(request) {
40
47
  return entry;
41
48
  }
42
49
  const entry = await import(pathToFileURL(key).href);
50
+ scheduleCompileCacheFlush();
43
51
  if (request.production) {
44
52
  if (routeEntries.size >= 64) routeEntries.delete(routeEntries.keys().next().value);
45
53
  routeEntries.set(key, entry);
@@ -68,6 +76,9 @@ function queryObject(url) {
68
76
  }
69
77
 
70
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
+ }
71
82
  const params = staticParams(request.params || {});
72
83
  const searchParams = staticSearchParams(queryObject(new URL(request.url)), currentRequest(), entry.page?.default?.$$typeof === Symbol.for('react.client.reference'));
73
84
  if (entry.routing && !fallback) return createAdvancedTree(entry, request, searchParams);
@@ -97,7 +108,7 @@ function createTree(entry, request, fallback = null) {
97
108
  const scopedParams = paramsBySegment(segments, request.params || {});
98
109
  for (const segment of segments) {
99
110
  scopes.set(segment, `${segment.path || ''}:${JSON.stringify(scopedParams.get(segment))}`);
100
- 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);
101
112
  }
102
113
  let instantConfig = request.instantValidation ? entry.pageConfig?.instant : undefined;
103
114
  for (let index = fallback ? Math.min(fallback.index, segments.length - 1) : segments.length - 1; index >= 0; index--) {
@@ -125,6 +136,10 @@ function createTree(entry, request, fallback = null) {
125
136
  tree = React.createElement(component(segment.template, 'template'), { key: child ? scopes.get(child) : pageKey }, tree);
126
137
  }
127
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);
128
143
  if (request.cacheComponents && segment === rootLayout) tree = React.createElement(React.Fragment, null, head, tree);
129
144
  tree = React.createElement(component(segment.layout, 'layout'), { params: staticParams(scopedParams.get(segment)), key: scopes.get(segment) }, tree);
130
145
  if (entry.LayoutProvider) tree = React.createElement(entry.LayoutProvider, { key: `layout:${scopes.get(segment)}`,
@@ -193,8 +208,9 @@ async function renderFlight(request, fallback = null, channel = null) {
193
208
  try {
194
209
  if (request.routingContexts?.size) request.renderRoutingBranch = routingBranchRenderer(cacheContext, bundlerConfig,
195
210
  renderSignal, error => publicError(error, request.production).digest);
196
- 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 };
197
212
  if (model.routing?.params) model.router.params = model.routing.params;
213
+ selectAppStyles(model);
198
214
  if (request.partialFlight) {
199
215
  const consumer = { serverConsumerManifest: flightConsumer(request), replayConsoleLogs: false };
200
216
  const decode = () => {
@@ -211,14 +227,16 @@ async function renderFlight(request, fallback = null, channel = null) {
211
227
  const keyMap = request.partialKeys ? new Map(request.partialKeys) : undefined;
212
228
  model = partialModel(stored, { live, keyMap, reuse: partialTemplateReuse(stored, keyMap) });
213
229
  }
230
+ model.authorizationKey = navigationAuthorization(cacheContext, request.authorizationSecret || request.actionKey, request.navigationCache?.authCookies);
214
231
  const stream = renderToReadableStream(model, bundlerConfig, {
215
232
  signal: renderSignal,
216
233
  temporaryReferences: request.temporaryReferences,
217
234
  onError(error) {
218
235
  const failure = error && typeof error === 'object' ? error : new Error(String(error));
219
- if (cacheContext.staticState?.partial && isPartialHole(failure)) return 'PRNEXT_PPR_DYNAMIC';
236
+ if ((cacheContext.staticState?.partial || request.navigationStage) && isPartialHole(failure)) return 'PRNEXT_PPR_DYNAMIC';
220
237
  renderError ??= failure;
221
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});}
222
240
  if (!abort.signal.aborted && !channel?.signal.aborted && ordinaryRenderError(failure)) console.error(`[prnext] ${digest}`, failure?.stack || failure);
223
241
  return digest;
224
242
  },
@@ -339,7 +357,12 @@ async function renderFlight(request, fallback = null, channel = null) {
339
357
  });
340
358
  }
341
359
 
342
- 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) {
343
366
  if (request.operation === 'static-params') {
344
367
  return runRequestContext({ ...request, phase: 'render' }, async () => {
345
368
  const context = currentRequest();
@@ -387,6 +410,11 @@ const responseCompletions = new Map();
387
410
  const manifests = new Map();
388
411
  const artifacts = new Map();
389
412
  parentPort.on('message', async ({ id, request, type, value, token }) => {
413
+ if(type==='telemetry-flush'){await drainTraces();parentPort.postMessage({type:'telemetry-flushed',token});return;}
414
+ if(type==='preload'){
415
+ try{await preloadModules(value.files,value.options,file=>routeEntry({modulePath:file,production:true}));parentPort.postMessage({type:'preloaded',id});}
416
+ catch(error){parentPort.postMessage({type:'preloaded',id,error:error.message});}return;
417
+ }
390
418
  if (type === 'after-response') { responseCompletions.get(id)?.(); return; }
391
419
  // Only the host arms these probes, while completed responses still have
392
420
  // tracked cache work. There is no timer or heartbeat in an idle RSC isolate.
@@ -403,17 +431,18 @@ parentPort.on('message', async ({ id, request, type, value, token }) => {
403
431
  for (const field of ['clientModules', 'actions']) {
404
432
  if (request[field + 'Id'] !== undefined) request[field] = manifests.get(request[field + 'Id']);
405
433
  }
434
+ request.instrumentationId=id;
406
435
  const channel = request.stream && (!request.action || request.action.id) ? workerStreamChannel(parentPort, id) : null;
407
436
  let responseDone, responseTimer;
408
- if (request.action && request.cacheConfig?.runtime !== 'edge') {
437
+ if (request.cacheConfig?.runtime !== 'edge') {
409
438
  const timeoutMs = request.renderTimeoutMs ?? 25000;
410
- request.afterScope = createAfterScope({ timeoutMs, onComplete: flushCacheWork,
411
- onTimeout: error => parentPort.postMessage({ id, error: { message: error.message, retireWorker: true } }) });
412
- 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 => {
413
442
  const finish = () => { clearTimeout(responseTimer); resolve(); };
414
443
  responseCompletions.set(id, finish);
415
444
  responseTimer = setTimeout(finish, timeoutMs);
416
- });
445
+ }); } });
417
446
  }
418
447
  activeRequests.add(id);
419
448
  if (channel) streamChannels.set(id, channel);
@@ -425,14 +454,14 @@ parentPort.on('message', async ({ id, request, type, value, token }) => {
425
454
  delete request.partialFlightId;
426
455
  }
427
456
  if (channel) {
428
- await handleRequest({ ...request, renderKey: id }, channel);
457
+ await handleRequest({ ...request, renderKey: renderKeyFor(request, id) }, channel);
429
458
  return;
430
459
  }
431
- const result = await handleRequest({ ...request, renderKey: id });
460
+ const result = await handleRequest({ ...request, renderKey: renderKeyFor(request, id) });
432
461
  parentPort.postMessage({ id, ...result }, result.body instanceof ArrayBuffer ? [result.body] : []);
433
462
  } catch (error) {
434
463
  parentPort.postMessage({ id, error: {
435
- 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,
436
465
  retireWorker: error?.retireWorker, responseHeaders: error?.responseHeaders,
437
466
  code: error?.code, staticMode: error?.staticMode, dynamicReason: error?.dynamicReason,
438
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
+ }
@@ -0,0 +1,19 @@
1
+ /** Materialize a discovered route pattern without interpolating path separators. */
2
+ export function route(pattern,params={}) {
3
+ if(typeof pattern!=='string'||!pattern.startsWith('/')||pattern.startsWith('//')||pattern.length>4096||!params||typeof params!=='object'||Array.isArray(params))throw TypeError('Invalid route pattern or parameters');
4
+ const used=new Set(),parts=[];
5
+ const segment=value=>{
6
+ if(typeof value!=='string'||!value||value==='.'||value==='..'||/[/\\\0]/.test(value))throw TypeError('Route parameters must be nonempty path segments');
7
+ return encodeURIComponent(value);
8
+ };
9
+ for(const part of pattern.split('/').slice(1)){
10
+ const match=/^(?:\[\[\.\.\.([A-Za-z_$][\w$]*)\]\]|\[\.\.\.([A-Za-z_$][\w$]*)\]|\[([A-Za-z_$][\w$]*)\])$/.exec(part);
11
+ if(!match){if(/[\[\]?#\\\0]/.test(part)||part==='.'||part==='..'||!part&&pattern!=='/')throw TypeError('Invalid route segment');parts.push(part);continue;}
12
+ const name=match[1]||match[2]||match[3];used.add(name);const value=params[name];
13
+ if(match[1]&&value===undefined)continue;
14
+ if(match[1]||match[2]){if(!Array.isArray(value)||match[2]&&!value.length)throw TypeError(`Route parameter ${name} must be an array of segments`);parts.push(...value.map(segment));}
15
+ else parts.push(segment(value));
16
+ }
17
+ for(const name of Object.keys(params))if(!used.has(name))throw TypeError(`Unknown route parameter ${name}`);
18
+ const result='/'+parts.join('/');if(result.length>8192)throw TypeError('Route exceeds 8 KiB');return result;
19
+ }
@@ -9,9 +9,10 @@ const missing = error => error.code === 'ENOENT' || error.code === 'ENOTDIR';
9
9
  // including ignored build trees. Atomic build swaps can delete a descendant
10
10
  // during that scan. Watch only source directories, and handle removal races
11
11
  // in our own traversal. No file contents or node_modules watches are retained.
12
- export async function watchProject(root, { onChange, onError, ignore = () => false, watch = watchDirectory, maxDirectories = 8192, nativeRecursive = process.platform === 'darwin' || process.platform === 'win32' }) {
13
- // macOS and Windows have native recursive backends. Separate directory
14
- // subscriptions can lose coalesced events during rapid renames.
12
+ export async function watchProject(root, { onChange, onError, ignore = () => false, watch = watchDirectory, maxDirectories = 8192, nativeRecursive = process.platform === 'darwin' }) {
13
+ // Windows' recursive notification buffer can overflow while V8/webpack flush
14
+ // caches. Source-only subscriptions avoid losing edits in that disk traffic.
15
+ // macOS uses its native recursive backend for coalesced directory renames.
15
16
  if (nativeRecursive) {
16
17
  const watcher = watch(root, { recursive: true }, (event, filename) => {
17
18
  const relative = filename && String(filename).replaceAll(path.sep, '/');
@@ -22,14 +23,14 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
22
23
  // Rebuilding on those reads restarts the server (and its SSE stream).
23
24
  try { if (statSync(path.join(root, relative)).isDirectory()) return; } catch {}
24
25
  }
25
- onChange(relative);
26
+ onChange(relative,event);
26
27
  });
27
28
  watcher.on('error', error => { watcher.close(); onError(error); });
28
29
  return { close: () => watcher.close(), async refresh() {} };
29
30
  }
30
31
  const directories = new Map();
31
- const pending = new Set();
32
- let closed = false, ready = false, timer, running, refreshAll = false;
32
+ const pending = new Map();
33
+ let closed = false, ready = false, timer, running;
33
34
  const allowedDirectory = relative => !relative || (!ignore(relative) && (relative === '.contentlayer' || shouldWatchProjectFile(relative)));
34
35
  const allowedFile = relative => relative && !ignore(relative) && shouldWatchProjectFile(relative);
35
36
  function remove(relative) {
@@ -50,9 +51,9 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
50
51
  close();
51
52
  onError(error);
52
53
  }
53
- function queue(relative) {
54
+ function queue(relative, recursive = false) {
54
55
  if (closed) return;
55
- pending.add(relative);
56
+ pending.set(relative, recursive || pending.get(relative) === true);
56
57
  clearTimeout(timer);
57
58
  timer = setTimeout(() => { void drain().catch(fail); }, 20);
58
59
  }
@@ -65,7 +66,8 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
65
66
  async function scan(relative, recursive = false) {
66
67
  if (closed) return;
67
68
  if (!allowedDirectory(relative)) { remove(relative); return; }
68
- if (!directories.has(relative)) {
69
+ const fresh = !directories.has(relative);
70
+ if (fresh) {
69
71
  if (directories.size >= maxDirectories) throw new Error(`Development watcher exceeds ${maxDirectories} source directories. Narrow the application directory.`);
70
72
  let watcher;
71
73
  try {
@@ -78,18 +80,28 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
78
80
  return;
79
81
  }
80
82
  const changed = filename ? path.posix.join(relative, String(filename).replaceAll(path.sep, '/')) : relative;
81
- if (allowedFile(changed)) onChange(changed);
83
+ let metadataOnly = false;
84
+ if (process.platform === 'win32' && event === 'change') {
85
+ try { metadataOnly = statSync(path.join(root, changed)).isDirectory(); } catch {}
86
+ }
87
+ if (allowedFile(changed) && !metadataOnly) onChange(changed,event);
82
88
  // The .contentlayer container itself is ignored as a source, but
83
89
  // its generated directory may be created after the watcher starts.
84
- // Platforms can coalesce a directory rename into a change event.
85
- // Rescan only this directory, rather than relying on the event name.
86
- 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
+ }
87
99
  });
88
100
  } catch (error) { if (relative && missing(error)) { disappeared(relative); return; } throw error; }
89
101
  directories.set(relative, watcher);
90
102
  // Files may already exist by the time a newly created directory is
91
103
  // subscribed. Its discovery must also invalidate the source snapshot.
92
- if (ready && allowedFile(relative)) onChange(relative);
104
+ if (ready && allowedFile(relative)) onChange(relative,'rename');
93
105
  watcher.on('error', error => {
94
106
  if (closed) return;
95
107
  if (relative && missing(error)) disappeared(relative);
@@ -103,6 +115,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
103
115
  const children = new Set();
104
116
  for (const entry of entries) {
105
117
  const child = path.posix.join(relative, entry.name);
118
+ if (fresh && ready && entry.isFile() && allowedFile(child)) onChange(child,'rename');
106
119
  if (!entry.isDirectory() || !allowedDirectory(child)) continue;
107
120
  children.add(child);
108
121
  if (recursive || !directories.has(child)) await scan(child, recursive);
@@ -116,8 +129,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
116
129
  running = (async () => {
117
130
  while (!closed && pending.size) {
118
131
  const batch = [...pending]; pending.clear();
119
- const recursive = refreshAll; refreshAll = false;
120
- for (const relative of batch) await scan(relative, recursive);
132
+ for (const [relative, recursive] of batch) await scan(relative, recursive);
121
133
  }
122
134
  })().finally(() => { running = undefined; });
123
135
  return running;
@@ -129,8 +141,7 @@ export async function watchProject(root, { onChange, onError, ignore = () => fal
129
141
  close,
130
142
  async refresh() {
131
143
  if (closed) return;
132
- refreshAll = true;
133
- pending.add('');
144
+ pending.set('', true);
134
145
  await drain();
135
146
  },
136
147
  };
@@ -1,6 +1,12 @@
1
+ import {drainTraces} from './trace.mjs';
2
+ import {advancedRoutingSources} from './app-routing-state.mjs';
3
+ import {initializeInstrumentation,withInstrumentation} from './instrumentation.mjs';
4
+ import {deferredCompiler} from './dev-compiler-broker.mjs';
1
5
  // prnext-transport:socket-v2
2
6
  // prnext-concurrency:512
3
7
  import { requestFrames } from './request-transport.mjs';
8
+ import {scheduleCompileCacheFlush} from './compile-cache.mjs';
9
+ import {preloadModules} from './preload.mjs';
4
10
  import { requestWork } from './request-work.mjs';
5
11
  import { freezeClientModules } from './worker-metadata.mjs';
6
12
  import { AsyncResource } from 'node:async_hooks';
@@ -36,12 +42,24 @@ loadEnvConfig(projectRoot, { dev: !!manifest.dev });
36
42
  // browser graph and the RSC worker's mode, including dev launched from CI.
37
43
  process.env.NODE_ENV = manifest.dev ? 'development' : 'production';
38
44
  const production = !manifest.dev;
45
+ const compilerDescriptor=process.env.PRNEXT_DEV_COMPILER;
46
+ delete process.env.PRNEXT_DEV_COMPILER;
47
+ process.env.NEXT_RUNTIME='nodejs';
48
+ await initializeInstrumentation(distDir,manifest);
49
+ let dispatchRequest=handleRequest;
50
+ if(!production){
51
+ const {createDevWorkerConsole}=await import('./dev-worker-logs.mjs'),logs=createDevWorkerConsole();globalThis.console=logs.console;
52
+ dispatchRequest=(input,...args)=>logs.run({requestId:input.id,route:routes.get(input.routeId)?.pattern},()=>handleRequest(input,...args));
53
+ const {publishDevCacheCapability}=await import('./dev-cache-control.mjs');
54
+ await publishDevCacheCapability({root:projectRoot,distDir,manifest}).catch(()=>{console.warn('[prnext] Local cache controls are unavailable for this development generation.');});
55
+ }
39
56
  if (production) freezeClientModules(manifest.app?.clientModules);
40
57
  // Load each renderer beside the application only when that request needs it.
41
58
  // Middleware and API workers do not pay for React or the Pages/App renderers.
42
- const runtimeModule = async name => {
59
+ const runtimeModule = async (name,directory=distDir) => {
43
60
  const started = diagnostics.enabled ? performance.now() : 0;
44
- const module = await import(pathToFileURL(path.join(distDir, `runtime/${name}.mjs`)).href);
61
+ const module = await import(pathToFileURL(path.join(directory, `runtime/${name}.mjs`)).href);
62
+ scheduleCompileCacheFlush();
45
63
  if (diagnostics.enabled) diagnostics.record('wake', { phase: 'module', module: name, durationMs: performance.now() - started });
46
64
  return module;
47
65
  };
@@ -50,20 +68,69 @@ try { ({ errorResponse } = await runtimeModule('http')); }
50
68
  catch (error) { console.error('PRNext worker could not load the project runtime:', error.message); process.exit(1); }
51
69
  const routes = new Map(manifest.routes.map(route => [route.id, route]));
52
70
  const prerendered = new Set((manifest.prerendered || []).map(item => item.path));
53
- let appRuntime;
54
- let appStaticRuntime;
55
- let routeStaticRuntime;
56
- let middlewareRuntime;
57
- let pagesRuntime;
58
- let apiRuntime;
59
- let incrementalRuntime;
71
+ const deployment={distDir,manifest,routes,prerendered,modules:new Map()};
72
+ const deployments=new Map([[distDir,deployment]]);
73
+ const compileDeferred=!production&&compilerDescriptor?deferredCompiler(compilerDescriptor,{root:projectRoot,home:path.dirname(distDir)}):undefined;
74
+ async function deferredDeployment(route,signal,sources=[]){
75
+ if(!compileDeferred)throw Error('Deferred development compiler is unavailable');
76
+ const next=await compileDeferred(route.pattern,signal,sources);
77
+ let value=deployments.get(next.distDir);
78
+ if(!value){value={...next,routes:new Map(next.manifest.routes.map(route=>[route.id,route])),prerendered:new Set(),modules:new Map()};deployments.set(next.distDir,value);while(deployments.size>4)deployments.delete(deployments.keys().next().value);}
79
+ const resolved=value.routes.get(route.id);
80
+ if(!resolved||resolved.devDeferred)throw Error('Compiler did not materialize the selected route');
81
+ return value;
82
+ }
83
+
84
+ async function drainRuntime(){
85
+ for(const value of deployments.values())if(value.modules.has('app-render')||value.modules.has('app-static')){
86
+ const runtime=await(value.modules.get('app-render')||import(pathToFileURL(path.join(value.distDir,'runtime/app-render.mjs')).href));
87
+ await runtime.drainAppRuntime();
88
+ }
89
+ await drainTraces();
90
+ }
60
91
 
61
- async function handleRequest(input, output, signalController, responseComplete) {
92
+ async function handleRequest(input, output, signalController, responseComplete, selected=deployment) {
93
+ const {manifest,distDir,routes,prerendered}=selected;
94
+ const renderer=async name=>{
95
+ if(selected.modules.has(name))return selected.modules.get(name);
96
+ // Imports share one pending job, then retain only the namespace. Starting
97
+ // outside the visitor's ALS scope prevents the module cache retaining it.
98
+ const pending=workScope.runInAsyncScope(()=>runtimeModule(name,distDir));
99
+ selected.modules.set(name,pending);
100
+ try{const module=await pending;selected.modules.set(name,module);return module;}
101
+ catch(error){if(selected.modules.get(name)===pending)selected.modules.delete(name);throw error;}
102
+ };
62
103
  let responseStarted = false;
63
104
  const finalizers = new Set();
64
105
  try {
106
+ if(input.routeId==='__prnext_preload'&&['preload','prewarm'].includes(input.renderMode)){
107
+ const frameworkOnly=input.renderMode==='prewarm';
108
+ const options=manifest.config?.workerPreload||(frameworkOnly?{routes:[],maxRssMb:256,timeoutMs:15000}:undefined);
109
+ if(!production||!options){await output(input.id,{status:204,body:''},input);return;}
110
+ if(frameworkOnly){
111
+ if(manifest.routes.some(route=>route.router==='app'&&route.kind==='page'))await(await renderer('app-render')).prewarmAppRenderer(options);
112
+ if(manifest.routes.some(route=>route.kind==='page'&&route.router!=='app'))await renderer('render');
113
+ if(manifest.routes.some(route=>route.kind==='api'))await renderer('api');
114
+ scheduleCompileCacheFlush();await output(input.id,{status:204,body:''},input);return;
115
+ }
116
+ const selected=options.routes.map(pattern=>manifest.routes.find(route=>route.pattern===pattern&&!route.internal)).filter(Boolean);
117
+ const deadline=performance.now()+options.timeoutMs;
118
+ for(const route of selected){
119
+ if(performance.now()>=deadline||process.memoryUsage().rss>=options.maxRssMb*1024*1024)break;
120
+ const remaining={...options,timeoutMs:Math.max(1,deadline-performance.now())};
121
+ if(route.router==='app'&&route.kind==='page'){
122
+ const appRuntime=await renderer('app-render');
123
+ await appRuntime.preloadAppRoutes([path.resolve(distDir,route.module)],remaining);
124
+ }else{
125
+ if(route.kind==='page')await renderer('render');else await renderer('api');
126
+ await preloadModules([path.resolve(distDir,route.module)],remaining);
127
+ }
128
+ if(process.memoryUsage().rss>=options.maxRssMb*1024*1024)break;
129
+ }
130
+ scheduleCompileCacheFlush();await output(input.id,{status:204,body:''},input);return;
131
+ }
65
132
  if (input.renderMode === 'incremental-cache' && input.routeId === '__prnext_incremental_cache') {
66
- incrementalRuntime ??= await runtimeModule('incremental-cache');
133
+ const incrementalRuntime = await renderer('incremental-cache');
67
134
  const result = await incrementalRuntime.runIncrementalCache({ ...input, manifest, distDir, production, signal: signalController.signal });
68
135
  input.body = undefined;
69
136
  responseStarted = true;
@@ -75,31 +142,38 @@ async function handleRequest(input, output, signalController, responseComplete)
75
142
  ? { ...manifest.middleware, id: '__prnext_middleware', kind: 'middleware' }
76
143
  : routes.get(input.routeId);
77
144
  if (!route) { await output(input.id, { status: 404, body: 'Not Found' }, input); return; }
145
+ const sourceModules=!production&&route.router==='app'&&route.kind==='page'?advancedRoutingSources({...input,basePath:manifest.config?.basePath||''},manifest.routes):[];
146
+ const deferredSources=sourceModules.length?manifest.routes.filter(candidate=>candidate.devDeferred&&sourceModules.includes(candidate.module)).map(candidate=>candidate.pattern):[];
147
+ if(route.devDeferred||deferredSources.length){
148
+ const next=await deferredDeployment(route,signalController.signal,deferredSources);
149
+ return await handleRequest(input,output,signalController,responseComplete,next);
150
+ }
78
151
  const requestedPath = new URL(input.url).pathname;
79
152
  if (route.router !== 'app' && !['isr', 'error404', 'error500'].includes(input.renderMode) && route.fallback === false && !prerendered.has(requestedPath) && !prerendered.has(requestedPath.replace(/\/$/, '') || '/')) {
80
153
  await output(input.id, { status: 404, body: 'Not Found' }, input); return;
81
154
  }
82
155
  const options = { ...input, modulePath: path.resolve(distDir, route.module), route, production, manifest, distDir, signal: signalController.signal, deferFinalizer: finalize => finalizers.add(finalize) };
156
+ return await withInstrumentation(options,async()=>{
83
157
  let result;
84
158
  if (isMiddleware) {
85
- middlewareRuntime ??= await import(pathToFileURL(path.join(distDir, 'runtime/middleware.mjs')).href);
159
+ const middlewareRuntime = await renderer('middleware');
86
160
  result = await middlewareRuntime.runMiddleware(options);
87
161
  } else if (route.router === 'app' && route.kind === 'page') {
88
162
  if (input.renderMode === 'isr') {
89
- appStaticRuntime ??= await import(pathToFileURL(path.join(distDir, 'runtime/app-static.mjs')).href);
163
+ const appStaticRuntime = await renderer('app-static');
90
164
  result = await appStaticRuntime.renderAppIsrPage(options);
91
165
  } else {
92
- appRuntime ??= await runtimeModule('app-render');
166
+ const appRuntime = await renderer('app-render');
93
167
  result = await appRuntime.renderAppPage(options);
94
168
  }
95
169
  } else if (route.router === 'app' && route.kind === 'api' && input.renderMode === 'isr') {
96
- routeStaticRuntime ??= await import(pathToFileURL(path.join(distDir, 'runtime/route-static.mjs')).href);
170
+ const routeStaticRuntime = await renderer('route-static');
97
171
  result = await routeStaticRuntime.renderRouteHandlerIsr(options);
98
172
  } else if (route.kind === 'api') {
99
- apiRuntime ??= await runtimeModule('api');
173
+ const apiRuntime = await renderer('api');
100
174
  result = await apiRuntime.runApi(options);
101
175
  } else {
102
- pagesRuntime ??= await runtimeModule('render');
176
+ const pagesRuntime = await renderer('render');
103
177
  result = input.renderMode === 'isr' ? await pagesRuntime.renderIsrPage({ ...options, capturePageFailure: true })
104
178
  : await pagesRuntime.renderPageRequest({ ...options, nativeErrors: true });
105
179
  }
@@ -111,6 +185,7 @@ async function handleRequest(input, output, signalController, responseComplete)
111
185
  if (result.finalizeCache) finalizers.add(result.finalizeCache);
112
186
  await output(input.id, result, { ...input, production, signalController });
113
187
  responseComplete?.();
188
+ });
114
189
  } catch (error) {
115
190
  const cancelled = signalController.signal.aborted || error instanceof ProtocolWriteError;
116
191
  signalController.abort(error);
@@ -160,7 +235,8 @@ if (token && laneLimit > 1) {
160
235
  reportedBusy = true;
161
236
  writeProtocol('busy\n');
162
237
  };
163
- const exitWhenDrained = () => { if (shuttingDown && !invocations.size) process.exit(0); };
238
+ let exiting=false;
239
+ const exitWhenDrained = () => { if (shuttingDown && !invocations.size&&!exiting){exiting=true;void drainRuntime().catch(error=>console.error('[prnext] Runtime drain failed:',error?.message)).finally(()=>process.exit(0));} };
164
240
  const server = net.createServer(socket => {
165
241
  if (sockets.size >= laneLimit) { socket.destroy(); return; }
166
242
  sockets.add(socket); socket.setNoDelay(true);
@@ -223,7 +299,7 @@ if (token && laneLimit > 1) {
223
299
  workTimer.unref();
224
300
  };
225
301
  active.signal.addEventListener('abort', retireIfAbandoned, { once: true });
226
- try { await handleRequest(input, output, active, () => { controller = undefined; }); }
302
+ try { await dispatchRequest(input, output, active, () => { controller = undefined; }); }
227
303
  finally {
228
304
  finished = true;
229
305
  if (!pendingWork) { invocations.delete(active); clearTimeout(workTimer); }
@@ -262,6 +338,10 @@ if (token && laneLimit > 1) {
262
338
  else throw error;
263
339
  });
264
340
  server.listen(0, '127.0.0.1', () => {
341
+ if(!production&&typeof globalThis.__PRNEXT_DEV_WORKER_READY__==='function'){
342
+ globalThis.__PRNEXT_DEV_WORKER_READY__({port:server.address().port,binary:true,gracefulShutdown:true});
343
+ delete globalThis.__PRNEXT_DEV_WORKER_READY__;
344
+ }
265
345
  writeProtocol(JSON.stringify({ port: server.address().port, binary: true, heartbeat: true, activity: reportActivity, gracefulShutdown: true }) + '\n');
266
346
  heartbeat = setInterval(() => {
267
347
  // Coalesce the idle transition with the existing heartbeat. Normal
@@ -269,6 +349,7 @@ if (token && laneLimit > 1) {
269
349
  // cannot add two messages for every response.
270
350
  reportedBusy = reportActivity && invocations.size > 0;
271
351
  writeProtocol(reportedBusy ? 'busy\n' : 'alive\n');
352
+ if(!production)globalThis.__PRNEXT_DEV_WORKER_PULSE__?.(reportedBusy?'busy\n':'alive\n');
272
353
  }, 1000).unref();
273
354
  });
274
355
  } else {
@@ -276,14 +357,11 @@ if (token && laneLimit > 1) {
276
357
  for await (let line of readline.createInterface({ input: process.stdin, crlfDelay: Infinity })) {
277
358
  if (!line.trim()) continue;
278
359
  const input = JSON.parse(line); line = '';
279
- try { await handleRequest(input, output, new AbortController()); }
360
+ try { await dispatchRequest(input, output, new AbortController()); }
280
361
  catch (error) { console.error('[prnext] Worker transport failed:', error?.stack || error); process.exit(1); }
281
362
  }
282
363
  // The maintenance/ISR transport is serial and awaits its own finalizers.
283
364
  // RSC settlement can still outlive its terminal frame; drain it explicitly
284
365
  // before closing the isolate's MessagePorts after parent EOF.
285
- if (appRuntime || appStaticRuntime) {
286
- const runtime = appRuntime || await runtimeModule('app-render');
287
- await runtime.drainAppRuntime();
288
- }
366
+ await drainRuntime();
289
367
  }