@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.
- package/AI.md +63 -0
- package/README.md +35 -503
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +6 -1
- package/build/analyze.mjs +340 -0
- package/build/app-config.mjs +20 -8
- package/build/app-static.mjs +41 -14
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +99 -31
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +39 -11
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/check.mjs +56 -11
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +51 -4
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +50 -13
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +65 -10
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +85 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +64 -6
- package/build/dynamic.mjs +1 -1
- package/build/edge.mjs +5 -4
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +50 -0
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +140 -112
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/instrumentation.mjs +30 -0
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +3 -4
- package/build/migrate.mjs +60 -57
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +112 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +100 -0
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +129 -0
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-export.mjs +4 -4
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +17 -3
- package/build/transform-cache.mjs +37 -4
- package/build/transform.mjs +1 -1
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +95 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +49 -0
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +11 -8
- package/build/webpack-graph.mjs +170 -30
- package/build/worker-memory.mjs +9 -0
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +162 -122
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +89 -10
- package/compat/link.cjs +12 -9
- package/compat/link.d.cts +4 -3
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/navigation.d.cts +4 -3
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/router.d.cts +4 -3
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/typed-route.d.cts +17 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +275 -0
- package/docs/ai/data.md +288 -0
- package/docs/ai/deployment.md +269 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +171 -0
- package/docs/ai/routing.md +214 -0
- package/docs/ai/troubleshooting.md +155 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +44 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +226 -68
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +100 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +245 -46
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +191 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +34 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +324 -0
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/env.mjs +2 -1
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +24 -8
- package/runtime/pages-navigation.mjs +54 -64
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +41 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +29 -18
- package/runtime/worker.mjs +102 -24
package/runtime/rsc-worker.mjs
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
-
|
|
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'
|
|
13
|
-
//
|
|
14
|
-
// subscriptions
|
|
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
|
|
32
|
-
let closed = false, ready = false, timer, running
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
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
|
|
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
|
-
|
|
133
|
-
pending.add('');
|
|
144
|
+
pending.set('', true);
|
|
134
145
|
await drain();
|
|
135
146
|
},
|
|
136
147
|
};
|
package/runtime/worker.mjs
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let
|
|
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
|
|
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
|
|
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
|
|
163
|
+
const appStaticRuntime = await renderer('app-static');
|
|
90
164
|
result = await appStaticRuntime.renderAppIsrPage(options);
|
|
91
165
|
} else {
|
|
92
|
-
appRuntime
|
|
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
|
|
170
|
+
const routeStaticRuntime = await renderer('route-static');
|
|
97
171
|
result = await routeStaticRuntime.renderRouteHandlerIsr(options);
|
|
98
172
|
} else if (route.kind === 'api') {
|
|
99
|
-
apiRuntime
|
|
173
|
+
const apiRuntime = await renderer('api');
|
|
100
174
|
result = await apiRuntime.runApi(options);
|
|
101
175
|
} else {
|
|
102
|
-
pagesRuntime
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
286
|
-
const runtime = appRuntime || await runtimeModule('app-render');
|
|
287
|
-
await runtime.drainAppRuntime();
|
|
288
|
-
}
|
|
366
|
+
await drainRuntime();
|
|
289
367
|
}
|