@thomas.f/prnext 0.1.0-alpha.1
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/README.md +54 -0
- package/build/action-exports.mjs +107 -0
- package/build/actions.mjs +214 -0
- package/build/app-config.mjs +138 -0
- package/build/app-routing.mjs +119 -0
- package/build/app-static.mjs +158 -0
- package/build/app.mjs +536 -0
- package/build/cache-components.mjs +91 -0
- package/build/cache-handlers.mjs +54 -0
- package/build/check.mjs +40 -0
- package/build/compiler.mjs +222 -0
- package/build/config.mjs +193 -0
- package/build/css-modules.mjs +114 -0
- package/build/custom-routes.mjs +363 -0
- package/build/dev-worker.mjs +18 -0
- package/build/dev.mjs +76 -0
- package/build/directory-cache.mjs +29 -0
- package/build/dynamic.mjs +125 -0
- package/build/edge.mjs +141 -0
- package/build/font-data.LICENSE.txt +25 -0
- package/build/font-data.json +1 -0
- package/build/font-files.mjs +221 -0
- package/build/fonts.mjs +131 -0
- package/build/framework-imports.mjs +17 -0
- package/build/i18n.mjs +39 -0
- package/build/images-config.mjs +62 -0
- package/build/images.mjs +86 -0
- package/build/incremental.mjs +76 -0
- package/build/index.mjs +356 -0
- package/build/loader-cache.mjs +69 -0
- package/build/metadata-images.mjs +72 -0
- package/build/metadata-routes.mjs +77 -0
- package/build/middleware.mjs +231 -0
- package/build/migrate.mjs +289 -0
- package/build/module-resolution.mjs +78 -0
- package/build/native-discovery.mjs +134 -0
- package/build/native-packages.mjs +105 -0
- package/build/npm-packages.mjs +110 -0
- package/build/og.mjs +14 -0
- package/build/page-extensions.mjs +12 -0
- package/build/passthrough-loader.cjs +4 -0
- package/build/precompress.mjs +122 -0
- package/build/scan.mjs +178 -0
- package/build/script-workers.mjs +25 -0
- package/build/server-actions-config.mjs +27 -0
- package/build/source-maps.mjs +34 -0
- package/build/standalone-config.mjs +19 -0
- package/build/standalone-runner.mjs +21 -0
- package/build/standalone-worker.mjs +5 -0
- package/build/standalone.mjs +240 -0
- package/build/static-export.mjs +93 -0
- package/build/styles.mjs +174 -0
- package/build/transform-cache.mjs +50 -0
- package/build/transform.mjs +155 -0
- package/build/turbopack-conditions.mjs +59 -0
- package/build/webpack-bridge.cjs +3 -0
- package/build/webpack-graph.mjs +256 -0
- package/build/webpack-loader.cjs +9 -0
- package/cli.mjs +210 -0
- package/compat/app-client-page.cjs +32 -0
- package/compat/app-context.cjs +40 -0
- package/compat/app-context.d.cts +20 -0
- package/compat/app-dynamic.cjs +34 -0
- package/compat/app-error-boundary.cjs +53 -0
- package/compat/app-global-error-boundary.cjs +34 -0
- package/compat/app-layout-context.cjs +27 -0
- package/compat/app-navigation-boundary.cjs +47 -0
- package/compat/app.cjs +33 -0
- package/compat/app.d.cts +9 -0
- package/compat/cache-handlers.cjs +223 -0
- package/compat/cache-life.cjs +34 -0
- package/compat/cache.cjs +99 -0
- package/compat/cache.d.cts +36 -0
- package/compat/compat/router.d.cts +2 -0
- package/compat/compat-router.cjs +7 -0
- package/compat/constants.cjs +8 -0
- package/compat/constants.d.cts +7 -0
- package/compat/cookies.cjs +129 -0
- package/compat/data-cache.cjs +389 -0
- package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
- package/compat/dist/shared/lib/router-context.d.cts +1 -0
- package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
- package/compat/document.cjs +73 -0
- package/compat/document.d.cts +41 -0
- package/compat/draft.cjs +28 -0
- package/compat/dynamic-bailout.cjs +13 -0
- package/compat/dynamic.cjs +119 -0
- package/compat/dynamic.d.cts +32 -0
- package/compat/error.cjs +36 -0
- package/compat/error.d.cts +15 -0
- package/compat/fetch.d.cts +10 -0
- package/compat/font/google.d.cts +1 -0
- package/compat/font/local.d.cts +2 -0
- package/compat/font-google.cjs +2 -0
- package/compat/font-google.d.cts +1944 -0
- package/compat/font-local.cjs +5 -0
- package/compat/font-local.d.cts +3 -0
- package/compat/font-types.d.cts +25 -0
- package/compat/head.cjs +180 -0
- package/compat/head.d.cts +3 -0
- package/compat/headers.cjs +89 -0
- package/compat/headers.d.cts +26 -0
- package/compat/image-config.cjs +3 -0
- package/compat/image-imports.d.ts +12 -0
- package/compat/image-shared.cjs +62 -0
- package/compat/image.cjs +64 -0
- package/compat/image.d.cts +35 -0
- package/compat/incremental-cache.cjs +186 -0
- package/compat/index.d.ts +159 -0
- package/compat/instant-samples.cjs +39 -0
- package/compat/instant.cjs +13 -0
- package/compat/link.cjs +68 -0
- package/compat/link.d.cts +17 -0
- package/compat/locale.cjs +30 -0
- package/compat/metadata.d.cts +77 -0
- package/compat/navigation-controls.cjs +27 -0
- package/compat/navigation-server.cjs +25 -0
- package/compat/navigation.cjs +52 -0
- package/compat/navigation.d.cts +28 -0
- package/compat/next-app-router-context.cjs +6 -0
- package/compat/next-router-context.cjs +3 -0
- package/compat/og.cjs +32 -0
- package/compat/og.d.cts +1 -0
- package/compat/paths.cjs +33 -0
- package/compat/preview.cjs +41 -0
- package/compat/rewrite.cjs +41 -0
- package/compat/route-request.cjs +64 -0
- package/compat/router.cjs +137 -0
- package/compat/router.d.cts +58 -0
- package/compat/script-context.cjs +5 -0
- package/compat/script-loader.cjs +135 -0
- package/compat/script.cjs +69 -0
- package/compat/script.d.cts +17 -0
- package/compat/server-primitives.cjs +109 -0
- package/compat/server.cjs +13 -0
- package/compat/server.d.cts +37 -0
- package/compat/static-generation.cjs +108 -0
- package/compat/use-cache.cjs +129 -0
- package/index.mjs +1 -0
- package/native/platforms.json +6 -0
- package/native/resolve.mjs +81 -0
- package/package.json +127 -0
- package/release/verify.mjs +59 -0
- package/runtime/action-client.mjs +19 -0
- package/runtime/action-crypto.mjs +138 -0
- package/runtime/action-reference.mjs +2 -0
- package/runtime/action-server.mjs +161 -0
- package/runtime/action-ssr.mjs +5 -0
- package/runtime/api.mjs +326 -0
- package/runtime/app-client.mjs +539 -0
- package/runtime/app-content.mjs +8 -0
- package/runtime/app-errors.mjs +12 -0
- package/runtime/app-instant.mjs +30 -0
- package/runtime/app-metadata.mjs +410 -0
- package/runtime/app-partial-model.mjs +228 -0
- package/runtime/app-partial.mjs +130 -0
- package/runtime/app-prefetch.mjs +34 -0
- package/runtime/app-recovery.mjs +87 -0
- package/runtime/app-render.mjs +544 -0
- package/runtime/app-routing-access.mjs +143 -0
- package/runtime/app-routing-context.mjs +35 -0
- package/runtime/app-routing-state.mjs +61 -0
- package/runtime/app-routing.mjs +323 -0
- package/runtime/app-segments.mjs +22 -0
- package/runtime/app-static-params.mjs +120 -0
- package/runtime/app-static.mjs +86 -0
- package/runtime/app-stream-channel.mjs +92 -0
- package/runtime/app-stream-html.mjs +279 -0
- package/runtime/build-directory.mjs +48 -0
- package/runtime/cache-arguments.mjs +33 -0
- package/runtime/cache-components-rsc.mjs +64 -0
- package/runtime/cache-request.mjs +23 -0
- package/runtime/client-navigation.mjs +40 -0
- package/runtime/dev-client.mjs +129 -0
- package/runtime/dev-file-changes.mjs +110 -0
- package/runtime/document.mjs +96 -0
- package/runtime/edge.mjs +105 -0
- package/runtime/env.mjs +68 -0
- package/runtime/fetch-cache.mjs +379 -0
- package/runtime/font-preload.mjs +4 -0
- package/runtime/http.mjs +190 -0
- package/runtime/incremental-cache.mjs +83 -0
- package/runtime/metadata-route.mjs +83 -0
- package/runtime/middleware.mjs +186 -0
- package/runtime/module-loader.mjs +21 -0
- package/runtime/navigation.mjs +16 -0
- package/runtime/og-loader.mjs +17 -0
- package/runtime/pages-client.mjs +197 -0
- package/runtime/pages-error-props.mjs +6 -0
- package/runtime/pages-errors.mjs +85 -0
- package/runtime/pages-initial-props.mjs +43 -0
- package/runtime/pages-navigation.mjs +565 -0
- package/runtime/pages-paths.mjs +80 -0
- package/runtime/pages-revalidate.mjs +33 -0
- package/runtime/partial-artifact-cache.mjs +37 -0
- package/runtime/partial-template.mjs +174 -0
- package/runtime/profile.mjs +9 -0
- package/runtime/profiles.json +47 -0
- package/runtime/render.mjs +304 -0
- package/runtime/request-transport.mjs +41 -0
- package/runtime/request-work.mjs +7 -0
- package/runtime/route-static.mjs +117 -0
- package/runtime/rsc-worker.mjs +426 -0
- package/runtime/script-html.mjs +61 -0
- package/runtime/stream-select.mjs +25 -0
- package/runtime/stream-utils.mjs +127 -0
- package/runtime/transport.mjs +108 -0
- package/runtime/watch-project.mjs +130 -0
- package/runtime/worker-artifacts.mjs +22 -0
- package/runtime/worker-metadata.mjs +31 -0
- package/runtime/worker.mjs +197 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { runtimeProfile } from './profile.mjs';
|
|
3
|
+
import { fontPreloads } from './font-preload.mjs';
|
|
4
|
+
import { advancedRoutingSources } from './app-routing-state.mjs';
|
|
5
|
+
import { addBasePath, removeBasePath } from '../compat/paths.cjs';
|
|
6
|
+
import { isDynamicBailout } from '../compat/dynamic-bailout.cjs';
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
|
+
import { Worker } from 'node:worker_threads';
|
|
9
|
+
import { workerMetadata } from './worker-metadata.mjs';
|
|
10
|
+
import { workerArtifacts } from './worker-artifacts.mjs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_BYTES } from './app-prefetch.mjs';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { AppRouterProvider } from '../compat/app-context.cjs';
|
|
16
|
+
import { appContent } from './app-content.mjs';
|
|
17
|
+
import { escapeHtml, MAX_RESPONSE_BYTES, errorResponse } from './http.mjs';
|
|
18
|
+
import { navigationResponse } from './navigation.mjs';
|
|
19
|
+
import { appTimeoutError, appErrorDigest } from './app-errors.mjs';
|
|
20
|
+
import { parentStreamChannel } from './app-stream-channel.mjs';
|
|
21
|
+
import { staticPath } from './pages-paths.mjs';
|
|
22
|
+
import { scriptNonce } from './script-html.mjs';
|
|
23
|
+
import { installFetchCache } from './fetch-cache.mjs';
|
|
24
|
+
import { partialArtifact, partialShell } from './app-partial.mjs';
|
|
25
|
+
import { partialKeyMap } from './app-partial-model.mjs';
|
|
26
|
+
import { partialIdentity } from './partial-artifact-cache.mjs';
|
|
27
|
+
import { isDraftRequest } from '../compat/draft.cjs';
|
|
28
|
+
|
|
29
|
+
installFetchCache();
|
|
30
|
+
|
|
31
|
+
const workers = new Map();
|
|
32
|
+
const metadata = new WeakMap();
|
|
33
|
+
const artifacts = new WeakMap();
|
|
34
|
+
const edgeManifests = new WeakMap();
|
|
35
|
+
const emptyManifest = Object.freeze({});
|
|
36
|
+
let nextId = 0;
|
|
37
|
+
const pending = new Map();
|
|
38
|
+
// Avoid temporary arrays on every completion/cancellation at high concurrency.
|
|
39
|
+
function hasPending(worker, minimum = 1, cancelledOnly = false) {
|
|
40
|
+
let count = 0;
|
|
41
|
+
for (const request of pending.values()) {
|
|
42
|
+
if (request.worker === worker && (!cancelledOnly || request.cancelled) && ++count >= minimum) return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const ssrModules = new Map();
|
|
47
|
+
const ssrModuleLoaders = new Map();
|
|
48
|
+
const ssrManifests = new WeakMap();
|
|
49
|
+
const require = createRequire(import.meta.url);
|
|
50
|
+
const flightDecoders = new Map();
|
|
51
|
+
let htmlRuntime, recoveryRuntime, domRuntime;
|
|
52
|
+
async function renderProgressiveAppHtml(options) {
|
|
53
|
+
htmlRuntime ??= await import('./app-stream-html.mjs');
|
|
54
|
+
return htmlRuntime.renderProgressiveAppHtml(options);
|
|
55
|
+
}
|
|
56
|
+
async function renderAppRecoveryShell(...args) {
|
|
57
|
+
recoveryRuntime ??= await import('./app-recovery.mjs');
|
|
58
|
+
return recoveryRuntime.renderAppRecoveryShell(...args);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function flightDecoder(production) {
|
|
62
|
+
if (flightDecoders.has(production)) return flightDecoders.get(production);
|
|
63
|
+
const filename = path.join(path.dirname(require.resolve('react-server-dom-webpack/client.node')),
|
|
64
|
+
`cjs/react-server-dom-webpack-client.node.${production ? 'production' : 'development'}.js`);
|
|
65
|
+
const previous = process.env.NODE_ENV;
|
|
66
|
+
try {
|
|
67
|
+
// React's development decoder guards module initialization with NODE_ENV.
|
|
68
|
+
// This require is synchronous; restore the host before any stream work.
|
|
69
|
+
process.env.NODE_ENV = production ? 'production' : 'development';
|
|
70
|
+
let decoder = require(filename).createFromNodeStream;
|
|
71
|
+
if (typeof decoder !== 'function') {
|
|
72
|
+
// A consumer may have previously required the guarded development file
|
|
73
|
+
// in production, leaving an empty module in Node's cache.
|
|
74
|
+
delete require.cache[filename];
|
|
75
|
+
decoder = require(filename).createFromNodeStream;
|
|
76
|
+
}
|
|
77
|
+
if (typeof decoder !== 'function') throw new Error('React Flight decoder is unavailable for the requested render mode');
|
|
78
|
+
flightDecoders.set(production, decoder);
|
|
79
|
+
return decoder;
|
|
80
|
+
} finally {
|
|
81
|
+
if (previous === undefined) delete process.env.NODE_ENV;
|
|
82
|
+
else process.env.NODE_ENV = previous;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// React's official Flight decoder uses this bundler interface. Module IDs are
|
|
87
|
+
// absolute SSR bundle URLs here, so applications cannot collide in the registry.
|
|
88
|
+
globalThis.__webpack_require__ = id => {
|
|
89
|
+
if (!ssrModules.has(id)) throw new Error(`Unknown App Router client module: ${id}`);
|
|
90
|
+
return ssrModules.get(id);
|
|
91
|
+
};
|
|
92
|
+
globalThis.__webpack_chunk_load__ = id => {
|
|
93
|
+
const load = ssrModuleLoaders.get(id);
|
|
94
|
+
if (!load) return Promise.reject(new Error(`Unknown App Router client module: ${id}`));
|
|
95
|
+
return load();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function getWorker(production) {
|
|
99
|
+
if (workers.has(production)) return workers.get(production);
|
|
100
|
+
const current = new Worker(new URL('./rsc-worker.mjs', import.meta.url), {
|
|
101
|
+
execArgv: ['--conditions=react-server'], stdout: true, stderr: true,
|
|
102
|
+
// Production nursery policy; an explicit Node semi-space flag takes
|
|
103
|
+
// precedence. This does not cap the old generation or process RSS.
|
|
104
|
+
...(production ? { resourceLimits: { maxYoungGenerationSizeMb: runtimeProfile(process.env, production).youngGenerationMiB } } : {}),
|
|
105
|
+
env: { ...process.env, NODE_ENV: production ? 'production' : 'development' },
|
|
106
|
+
});
|
|
107
|
+
workers.set(production, current);
|
|
108
|
+
metadata.set(current, workerMetadata(current));
|
|
109
|
+
artifacts.set(current, workerArtifacts(current));
|
|
110
|
+
// The HTTP worker's stdout belongs to its Rust transport. User code and React
|
|
111
|
+
// diagnostics in the separate RSC environment must never corrupt that stream.
|
|
112
|
+
current.stdout.pipe(process.stderr);
|
|
113
|
+
current.stderr.pipe(process.stderr);
|
|
114
|
+
function fail(error) {
|
|
115
|
+
if (workers.get(production) === current) workers.delete(production);
|
|
116
|
+
for (const [id, request] of pending) {
|
|
117
|
+
if (request.worker !== current) continue;
|
|
118
|
+
clearTimeout(request.timeout);
|
|
119
|
+
pending.delete(id);
|
|
120
|
+
request.detach?.();
|
|
121
|
+
request.channel?.error(error);
|
|
122
|
+
request.reject(error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
current.on('message', ({ id, type, chunk, byteOffset, byteLength, done, body, status, rscError, boundaryIndex, headers, error, staticParams, staticMetadata, navigation }) => {
|
|
126
|
+
const request = pending.get(id);
|
|
127
|
+
if (!request || request.worker !== current) return;
|
|
128
|
+
// A timed-out mutation can still be running after its HTTP caller leaves.
|
|
129
|
+
// Its retirement marker must be honored before discarding cancelled output.
|
|
130
|
+
if (error?.retireWorker) {
|
|
131
|
+
if (workers.get(production) === current) workers.delete(production);
|
|
132
|
+
void current.terminate();
|
|
133
|
+
}
|
|
134
|
+
if (request.cancelled && type !== 'settled') return;
|
|
135
|
+
if (type === 'settled') {
|
|
136
|
+
if (!request.cancelled) return;
|
|
137
|
+
pending.delete(id); clearTimeout(request.timeout);
|
|
138
|
+
if (!hasPending(current)) current.unref();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (type === 'start') {
|
|
142
|
+
if (chunk) request.channel.chunk(chunk, byteOffset, byteLength);
|
|
143
|
+
request.resolve({ body: request.channel.body, status: status || 200, rscError, boundaryIndex, headers });
|
|
144
|
+
if (!done) return;
|
|
145
|
+
}
|
|
146
|
+
if (type === 'chunk') {
|
|
147
|
+
try { request.channel.chunk(chunk, byteOffset, byteLength); }
|
|
148
|
+
catch (error) { request.channel.error(error); current.postMessage({ id, type: 'cancel' }); }
|
|
149
|
+
if (!done) return;
|
|
150
|
+
}
|
|
151
|
+
pending.delete(id);
|
|
152
|
+
clearTimeout(request.timeout);
|
|
153
|
+
request.detach?.();
|
|
154
|
+
if (type === 'end' || done) request.channel.end();
|
|
155
|
+
else if (error) {
|
|
156
|
+
const failure = new Error(error.message);
|
|
157
|
+
if (error.stack) failure.stack = error.stack;
|
|
158
|
+
if (error.digest) failure.digest = error.digest;
|
|
159
|
+
if ([400, 504].includes(error.statusCode)) failure.statusCode = error.statusCode;
|
|
160
|
+
if (error.responseHeaders) failure.responseHeaders = error.responseHeaders;
|
|
161
|
+
if (error.code) failure.code = error.code;
|
|
162
|
+
if (error.staticMode) failure.staticMode = error.staticMode;
|
|
163
|
+
if (error.dynamicReason) failure.dynamicReason = error.dynamicReason;
|
|
164
|
+
request.channel?.error(failure);
|
|
165
|
+
request.reject(failure);
|
|
166
|
+
} else if (staticParams && request.operation === 'static-params') {
|
|
167
|
+
request.resolve(staticParams);
|
|
168
|
+
} else if (!(body instanceof ArrayBuffer) || body.byteLength > MAX_RESPONSE_BYTES) {
|
|
169
|
+
request.reject(new Error('Invalid App Router Flight response buffer'));
|
|
170
|
+
} else {
|
|
171
|
+
// ArrayBuffer overload: a view of the transferred allocation, no copy.
|
|
172
|
+
request.resolve({ body: Buffer.from(body), status: status || 200, rscError, boundaryIndex, headers, staticMetadata, navigation });
|
|
173
|
+
}
|
|
174
|
+
if (!hasPending(current)) current.unref();
|
|
175
|
+
});
|
|
176
|
+
current.on('error', fail);
|
|
177
|
+
current.on('exit', code => fail(new Error(`App Router worker exited (${code})`)));
|
|
178
|
+
current.unref();
|
|
179
|
+
return current;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function closeAppRuntime() {
|
|
183
|
+
const current = [...workers.values()];
|
|
184
|
+
workers.clear();
|
|
185
|
+
return Promise.all(current.map(worker => worker.terminate()));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function renderFlight(request, { softTimeoutMs = 25_000, hardTimeoutMs = 28_000, signal } = {}) {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const current = getWorker(Boolean(request.production));
|
|
191
|
+
current.ref();
|
|
192
|
+
const id = ++nextId;
|
|
193
|
+
const timeout = setTimeout(() => {
|
|
194
|
+
// A blocking npm dependency cannot honor an AbortSignal. Retire the
|
|
195
|
+
// isolate at the hard deadline so subsequent requests can recover.
|
|
196
|
+
if (workers.get(Boolean(request.production)) === current) workers.delete(Boolean(request.production));
|
|
197
|
+
void current.terminate();
|
|
198
|
+
pending.delete(id);
|
|
199
|
+
const error = appTimeoutError('RSC worker', hardTimeoutMs);
|
|
200
|
+
entry.channel?.error(error);
|
|
201
|
+
entry.detach?.();
|
|
202
|
+
reject(error);
|
|
203
|
+
}, hardTimeoutMs);
|
|
204
|
+
const entry = { worker: current, resolve, reject, timeout, operation: request.operation };
|
|
205
|
+
function cancel(reason) {
|
|
206
|
+
// Keep the independent deadline until the isolate confirms settlement.
|
|
207
|
+
// A disconnected client must not disable recovery of blocked npm code.
|
|
208
|
+
entry.cancelled = true;
|
|
209
|
+
entry.detach?.();
|
|
210
|
+
// HTTP callers can leave faster than arbitrary npm work can unwind.
|
|
211
|
+
// Bound abandoned work independently of the admission of live requests.
|
|
212
|
+
if (hasPending(current, 16, true)) {
|
|
213
|
+
if (workers.get(Boolean(request.production)) === current) workers.delete(Boolean(request.production));
|
|
214
|
+
void current.terminate();
|
|
215
|
+
}
|
|
216
|
+
if (!hasPending(current)) current.unref();
|
|
217
|
+
reject(reason instanceof Error ? reason : new Error('App Router response was cancelled'));
|
|
218
|
+
}
|
|
219
|
+
if (request.stream) entry.channel = parentStreamChannel(current, id, cancel);
|
|
220
|
+
if (signal) {
|
|
221
|
+
const onAbort = () => {
|
|
222
|
+
current.postMessage({ id, type: 'cancel' });
|
|
223
|
+
entry.channel?.error(signal.reason);
|
|
224
|
+
cancel(signal.reason);
|
|
225
|
+
};
|
|
226
|
+
entry.detach = () => signal.removeEventListener('abort', onAbort);
|
|
227
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
228
|
+
if (signal.aborted) { clearTimeout(timeout); onAbort(); return; }
|
|
229
|
+
}
|
|
230
|
+
pending.set(id, entry);
|
|
231
|
+
try {
|
|
232
|
+
const wire = { ...request, renderTimeoutMs: softTimeoutMs };
|
|
233
|
+
if (request.production) {
|
|
234
|
+
const artifactId = artifacts.get(current)(wire.partialFlight);
|
|
235
|
+
if (artifactId !== undefined) { wire.partialFlightId = artifactId; delete wire.partialFlight; }
|
|
236
|
+
for (const field of ['clientModules', 'actions']) {
|
|
237
|
+
const key = metadata.get(current)(wire[field]);
|
|
238
|
+
if (key !== undefined) { wire[field + 'Id'] = key; delete wire[field]; }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
current.postMessage({ id, request: wire });
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
pending.delete(id);
|
|
246
|
+
entry.detach?.();
|
|
247
|
+
reject(error);
|
|
248
|
+
if (!hasPending(current)) current.unref();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function decodeFlight(body, clientModules = {}, distDir, { production = process.env.NODE_ENV === 'production' } = {}) {
|
|
254
|
+
const ids = Object.keys(clientModules);
|
|
255
|
+
let cached = ssrManifests.get(clientModules);
|
|
256
|
+
// Keep only the manifest's module mapping, never a decoded React model or
|
|
257
|
+
// request context. Weak ownership releases it with the build manifest.
|
|
258
|
+
if (!cached || cached.distDir !== distDir || ids.length !== cached.paths.size || ids.some(id => cached.paths.get(id) !== clientModules[id].ssrModule)) {
|
|
259
|
+
const moduleMap = Object.create(null), paths = new Map();
|
|
260
|
+
for (const id of ids) {
|
|
261
|
+
const item = clientModules[id];
|
|
262
|
+
const moduleUrl = pathToFileURL(path.resolve(distDir, item.ssrModule)).href;
|
|
263
|
+
if (!ssrModuleLoaders.has(moduleUrl)) ssrModuleLoaders.set(moduleUrl, async () => {
|
|
264
|
+
if (!ssrModules.has(moduleUrl)) ssrModules.set(moduleUrl, await import(moduleUrl));
|
|
265
|
+
});
|
|
266
|
+
moduleMap[id] = { '*': { id: moduleUrl, chunks: [moduleUrl, moduleUrl], name: '*' } };
|
|
267
|
+
paths.set(id, item.ssrModule);
|
|
268
|
+
}
|
|
269
|
+
cached = { distDir, paths, moduleMap };
|
|
270
|
+
ssrManifests.set(clientModules, cached);
|
|
271
|
+
}
|
|
272
|
+
// Hosts can build/render both modes in one process. The encoded protocol,
|
|
273
|
+
// rather than the host's current environment or import cache, selects React.
|
|
274
|
+
const decoder = flightDecoder(Boolean(production));
|
|
275
|
+
return decoder(body instanceof ReadableStream ? Readable.fromWeb(body) : Readable.from([body]), { moduleMap: cached.moduleMap, serverModuleMap: null, moduleLoading: null });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function renderHtml(tree, router, { timeoutMs = 25_000, formState = null, staticGeneration = false, nonce, strictMode = true } = {}) {
|
|
279
|
+
const abort = new AbortController();
|
|
280
|
+
const timeout = setTimeout(() => abort.abort(appTimeoutError('HTML render', timeoutMs)), timeoutMs);
|
|
281
|
+
let renderError;
|
|
282
|
+
try {
|
|
283
|
+
domRuntime ??= await import('react-dom/server');
|
|
284
|
+
const stream = await domRuntime.renderToReadableStream(React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppRouterProvider, { router, nonce }, appContent(tree))), {
|
|
285
|
+
nonce,
|
|
286
|
+
signal: abort.signal, onError(error) {
|
|
287
|
+
if (isDynamicBailout(error)) return error.digest;
|
|
288
|
+
// Suspense can recover client SSR failures by emitting its fallback and
|
|
289
|
+
// retrying in the browser. Static generation must still reject them:
|
|
290
|
+
// publishing that fallback would replace the last valid cached page.
|
|
291
|
+
if (staticGeneration || navigationResponse(error)) renderError ??= error;
|
|
292
|
+
else if (!abort.signal.aborted) console.error('[prnext]', error?.stack || error);
|
|
293
|
+
return appErrorDigest(error);
|
|
294
|
+
},
|
|
295
|
+
formState,
|
|
296
|
+
});
|
|
297
|
+
const reader = stream.getReader();
|
|
298
|
+
const chunks = [];
|
|
299
|
+
let length = 0;
|
|
300
|
+
try {
|
|
301
|
+
for (;;) {
|
|
302
|
+
const { value, done } = await reader.read();
|
|
303
|
+
if (done) break;
|
|
304
|
+
length += value.byteLength;
|
|
305
|
+
if (length > MAX_RESPONSE_BYTES) {
|
|
306
|
+
const error = new Error('HTML response exceeds the 16 MiB PRNext limit');
|
|
307
|
+
abort.abort(error);
|
|
308
|
+
await reader.cancel(error);
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
chunks.push(Buffer.from(value));
|
|
312
|
+
}
|
|
313
|
+
} finally { reader.releaseLock(); }
|
|
314
|
+
if (abort.signal.aborted) throw abort.signal.reason;
|
|
315
|
+
if (renderError) throw renderError;
|
|
316
|
+
return Buffer.concat(chunks, length).toString('utf8');
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (abort.signal.aborted && abort.signal.reason?.statusCode === 504) throw abort.signal.reason;
|
|
319
|
+
throw error;
|
|
320
|
+
} finally { clearTimeout(timeout); }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function beforeClosingTag(html, tag, content) {
|
|
324
|
+
const index = html.lastIndexOf(`</${tag}>`);
|
|
325
|
+
if (index < 0) throw new Error(`App Router root layout must render a <${tag}> element`);
|
|
326
|
+
return `${html.slice(0, index)}${content}${html.slice(index)}`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function completeAppHtml(html, flight, route = {}) {
|
|
330
|
+
if (!/<html(?:\s|>)/i.test(html) || !/<body(?:\s|>)/i.test(html)) {
|
|
331
|
+
throw new Error('App Router root layout must render <html> and <body> elements');
|
|
332
|
+
}
|
|
333
|
+
const styles = fontPreloads(route.fonts, route.nonce) + (route.css || []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
|
|
334
|
+
if (styles) html = beforeClosingTag(html, 'head', styles);
|
|
335
|
+
const payload = `<script id="__PRNEXT_FLIGHT__" type="application/octet-stream">${flight.toString('base64')}</script>`;
|
|
336
|
+
const bootstrap = route.client ? `<script type="module"${route.nonce ? ` nonce="${escapeHtml(route.nonce)}"` : ''} src="${escapeHtml(route.client)}"></script>` : '';
|
|
337
|
+
html = beforeClosingTag(html, 'body', `${payload}${bootstrap}`);
|
|
338
|
+
if (!html.startsWith('<!DOCTYPE html>')) html = `<!DOCTYPE html>${html}`;
|
|
339
|
+
if (Buffer.byteLength(html) > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit');
|
|
340
|
+
return html;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const devStaticChecks = new Map();
|
|
344
|
+
|
|
345
|
+
export async function renderAppPage(options) {
|
|
346
|
+
const response = await renderAppPageInner(options);
|
|
347
|
+
if (options.method !== 'POST' && response.headers?.location) response.headers.location = addBasePath(response.headers.location, options.manifest?.config?.basePath || '');
|
|
348
|
+
return response;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function renderAppPageInner({ modulePath, route = {}, manifest = {}, distDir = path.dirname(path.dirname(modulePath)), url = 'http://localhost/', originalUrl, params = {}, method = 'GET', headers = {}, routingRequestHeaders, routingResolver, body = '', production = process.env.NODE_ENV === 'production', stream = false, signal }) {
|
|
352
|
+
let clientModules = manifest.app?.clientModules || emptyManifest;
|
|
353
|
+
if (route.cacheConfig?.runtime === 'edge') {
|
|
354
|
+
let edge = !manifest.dev && edgeManifests.get(clientModules);
|
|
355
|
+
if (!edge) {
|
|
356
|
+
edge = Object.fromEntries(Object.entries(clientModules).map(([id, item]) => [id, item.edgeSsrModule ? { ...item, ssrModule: item.edgeSsrModule } : item]));
|
|
357
|
+
if (!manifest.dev) edgeManifests.set(clientModules, edge);
|
|
358
|
+
}
|
|
359
|
+
clientModules = edge;
|
|
360
|
+
}
|
|
361
|
+
const responseHeaders = { 'cache-control': 'private, no-cache, no-store, max-age=0', vary: 'RSC' };
|
|
362
|
+
const request = { modulePath, routePattern: route.pattern, cacheConfig: route.cacheConfig, css: route.css || [], fonts: route.fonts || [], nonce: scriptNonce(headers), basePath: manifest.config?.basePath || '', trailingSlash: manifest?.config?.trailingSlash || false, skipTrailingSlashRedirect: manifest?.config?.skipTrailingSlashRedirect || false, url, originalUrl, params, method, headers, clientModules, production,
|
|
363
|
+
distDir, strictMode: manifest.config?.reactStrictMode !== false, cacheComponents: manifest.config?.cacheComponents, cacheLife: manifest.config?.cacheLife, cacheHandlers: manifest.config?.cacheHandlers, cacheHandler: manifest.config?.cacheHandler, cacheMaxMemorySize: manifest.config?.cacheMaxMemorySize, serverActions: manifest.config?.serverActions, previewModeId: manifest.previewModeId, actions: manifest.app?.actions || emptyManifest, actionKey: manifest.app?.actionKey };
|
|
364
|
+
if (route.parallel) {
|
|
365
|
+
request.routingResolver = routingResolver;
|
|
366
|
+
request.skipProxyUrlNormalize = manifest.config?.skipProxyUrlNormalize;
|
|
367
|
+
request.skipMiddlewareUrlNormalize = manifest.config?.skipMiddlewareUrlNormalize;
|
|
368
|
+
request.routingRequestHeaders = routingRequestHeaders;
|
|
369
|
+
request.routingSources = advancedRoutingSources(request, manifest.routes || []);
|
|
370
|
+
request.routingMiddleware = manifest.middleware;
|
|
371
|
+
request.interception = route.interception;
|
|
372
|
+
}
|
|
373
|
+
if (manifest.dev && method !== 'POST' && !(manifest.appNotFound && route.id === manifest.appNotFound)) {
|
|
374
|
+
const listing = await renderFlight({ ...request, operation: 'static-params' }, { signal });
|
|
375
|
+
if (route.pattern?.includes('[') && route.cacheConfig?.dynamicParams === false) {
|
|
376
|
+
const requested = staticPath(route.pattern, new URL(url).pathname).path;
|
|
377
|
+
if (!listing.generated || !listing.params.some(params => staticPath(route.pattern, { params }).path === requested)) {
|
|
378
|
+
return { status: 404, headers: { ...responseHeaders, 'content-type': 'text/plain; charset=utf-8' }, body: Buffer.from('Not Found') };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (request.cacheComponents) {
|
|
382
|
+
const key = `${manifest.cacheId}:${route.id}:${new URL(url).pathname}`;
|
|
383
|
+
let check = devStaticChecks.get(key);
|
|
384
|
+
if (!check) {
|
|
385
|
+
if (listing.generated && listing.params.length === 0) throw new Error(`generateStaticParams for ${route.pattern} must return at least one parameter object when cacheComponents is enabled.`);
|
|
386
|
+
check = import('./app-static.mjs').then(({ prerenderAppRoute }) => prerenderAppRoute({ modulePath, distDir, route, manifest,
|
|
387
|
+
production, path: new URL(url).pathname, params, partialParams: listing.generated ? [] : Object.keys(params) })).then(() => import('./app-instant.mjs')).then(({ validateInstantRoute }) => validateInstantRoute({ modulePath, distDir, route, manifest, production, path: new URL(url).pathname, params }));
|
|
388
|
+
if (devStaticChecks.size >= 128) devStaticChecks.delete(devStaticChecks.keys().next().value);
|
|
389
|
+
devStaticChecks.set(key, check);
|
|
390
|
+
}
|
|
391
|
+
request.instantDiagnostics = await check;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const header = name => Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];
|
|
395
|
+
if (method === 'POST') {
|
|
396
|
+
const id = header('next-action');
|
|
397
|
+
if (id && !Object.hasOwn(request.actions, id)) return { status: 404, headers: responseHeaders, body: Buffer.from('Unknown Server Action') };
|
|
398
|
+
const limit = request.serverActions?.bodySizeLimit || 1024 * 1024;
|
|
399
|
+
const oversized = () => ({ status: 413, headers: responseHeaders, body: Buffer.from(`Server Action body exceeds the ${limit} byte limit`) });
|
|
400
|
+
if (!Buffer.isBuffer(body) && (body || '').length > Math.ceil(limit / 3) * 4) return oversized();
|
|
401
|
+
const bytes = Buffer.isBuffer(body) ? body : Buffer.from(body || '', 'base64');
|
|
402
|
+
if (bytes.byteLength > limit) return oversized();
|
|
403
|
+
request.action = { id, contentType: header('content-type'), body: bytes };
|
|
404
|
+
}
|
|
405
|
+
if (!manifest.dev && request.cacheComponents && (route.ppr || route.pprFallback) && ['GET', 'HEAD'].includes(method) &&
|
|
406
|
+
!request.nonce && !(route.parallel && header('x-prnext-router-state')) && !isDraftRequest(request)) {
|
|
407
|
+
const pathname = staticPath(route.pattern, new URL(url).pathname).path;
|
|
408
|
+
const generic = route.pprGeneric?.find(item => Object.entries(item.params).every(([name, value]) => JSON.stringify(params[name]) === JSON.stringify(value)));
|
|
409
|
+
const filename = (!originalUrl ? route.ppr?.[pathname] : undefined) || generic?.file;
|
|
410
|
+
if (filename || route.pprFallback) {
|
|
411
|
+
const artifact = await partialArtifact({ request, route, manifest, filename, identity: filename === generic?.file ? filename : `${pathname}:${originalUrl ? new URL(originalUrl).pathname : ''}`, signal,
|
|
412
|
+
regenerate: async build => (await import('./app-static.mjs')).prerenderAppRoute({ modulePath, route, manifest, distDir, production,
|
|
413
|
+
path: build?.generic?.path || pathname, originalUrl: build?.generic ? undefined : originalUrl,
|
|
414
|
+
params: build?.generic?.params || params, partialParams: build?.generic?.unknown }),
|
|
415
|
+
});
|
|
416
|
+
if (!artifact.dynamic) return renderPartialAppPage({ request, route, artifact, responseHeaders, stream, signal, rsc: header('rsc') === '1', prefetch: header('x-prnext-prefetch') === '1', knownPrefetch: header('x-prnext-prefetch-known'),
|
|
417
|
+
concrete: artifact.generic ? (continuationSignal => partialArtifact({ request, route, manifest, identity: `concrete:${pathname}:${originalUrl ? new URL(originalUrl).pathname : ''}`, signal: continuationSignal,
|
|
418
|
+
regenerate: async () => (await import('./app-static.mjs')).prerenderAppRoute({ modulePath, route, manifest, distDir, production, path: pathname, originalUrl, params, seed: artifact }),
|
|
419
|
+
})) : undefined });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// An unsupported speculative request must not execute private components.
|
|
423
|
+
if (header('x-prnext-prefetch') === '1' && method === 'GET') return { status: 204, headers: responseHeaders, body: Buffer.alloc(0) };
|
|
424
|
+
if (stream && (!request.action || request.action.id)) {
|
|
425
|
+
let progressive;
|
|
426
|
+
try {
|
|
427
|
+
progressive = await renderFlight({ ...request, stream: true }, { signal });
|
|
428
|
+
const rsc = Object.entries(headers).some(([name, value]) => name.toLowerCase() === 'rsc' && value === '1');
|
|
429
|
+
if (rsc || request.action?.id) {
|
|
430
|
+
if (method === 'HEAD') await progressive.body.cancel();
|
|
431
|
+
return { ...progressive, headers: { ...responseHeaders, ...progressive.headers, 'content-type': 'text/x-component; charset=utf-8' }, ...(method === 'HEAD' ? { body: Buffer.alloc(0) } : {}) };
|
|
432
|
+
}
|
|
433
|
+
const html = await renderProgressiveAppHtml({ result: progressive, request, route, responseHeaders, decodeFlight, signal });
|
|
434
|
+
if (method === 'HEAD') { await html.cancel(); return { ...html, body: Buffer.alloc(0) }; }
|
|
435
|
+
return html;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
await progressive?.body.cancel(error).catch(() => {});
|
|
438
|
+
if (signal?.aborted || error?.statusCode === 504 || error?.code === 'PRNEXT_INVALID_ROOT_LAYOUT') throw error;
|
|
439
|
+
const navigation = navigationResponse(error);
|
|
440
|
+
if (navigation) return { ...navigation, headers: { ...responseHeaders, ...navigation.headers, ...error.responseHeaders } };
|
|
441
|
+
if (request.action) {
|
|
442
|
+
const result = errorResponse(error, production);
|
|
443
|
+
return { ...result, headers: { ...responseHeaders, ...result.headers, ...error.responseHeaders } };
|
|
444
|
+
}
|
|
445
|
+
// The HTML stream already retains the original Flight data for recovery.
|
|
446
|
+
// Transport, import and resource-limit failures must not replay user code.
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
let result;
|
|
451
|
+
try { result = await renderFlight(request, { signal }); }
|
|
452
|
+
catch (error) {
|
|
453
|
+
const result = navigationResponse(error);
|
|
454
|
+
if (result) return { ...result, headers: { ...responseHeaders, ...result.headers, ...error.responseHeaders } };
|
|
455
|
+
if (request.action) {
|
|
456
|
+
const result = errorResponse(error, production);
|
|
457
|
+
return { ...result, headers: { ...responseHeaders, ...result.headers, ...error.responseHeaders } };
|
|
458
|
+
}
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
Object.assign(responseHeaders, result.headers);
|
|
462
|
+
if (result.status === 303) return { status: 303, headers: responseHeaders, body: Buffer.alloc(0) };
|
|
463
|
+
const rsc = Object.entries(headers).some(([name, value]) => name.toLowerCase() === 'rsc' && value === '1');
|
|
464
|
+
if (rsc || request.action?.id) return { status: result.status, headers: { ...responseHeaders, 'content-type': 'text/x-component; charset=utf-8' }, body: result.body };
|
|
465
|
+
let html, model, recovery = false;
|
|
466
|
+
try {
|
|
467
|
+
model = await decodeFlight(result.body, clientModules, distDir, { production });
|
|
468
|
+
html = await renderHtml(model.tree, model.router, { formState: model.formState, nonce: request.nonce, strictMode: manifest.config?.reactStrictMode !== false });
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error?.statusCode === 504) throw error;
|
|
471
|
+
const navigation = navigationResponse(error);
|
|
472
|
+
if (navigation && !(route.parallel && navigation.status === 404)) return { ...navigation, headers: { ...responseHeaders, ...navigation.headers } };
|
|
473
|
+
// React boundaries run in the browser, including for SSR failures. Keep
|
|
474
|
+
// the original Flight tree so the correct local/global boundary handles
|
|
475
|
+
// its error without rerunning layouts, data fetches or Server Actions.
|
|
476
|
+
console.error('[prnext]', error?.stack || error);
|
|
477
|
+
html = await renderAppRecoveryShell(model?.head, { signal });
|
|
478
|
+
recovery = true;
|
|
479
|
+
}
|
|
480
|
+
const { body: flight, status } = result;
|
|
481
|
+
html = completeAppHtml(html, flight, { ...route, nonce: request.nonce, ...(recovery ? { css: [], fonts: [] } : {}) });
|
|
482
|
+
// A Flight error inside Suspense need not fail the HTML shell. Only shell
|
|
483
|
+
// failure selects 500 for an ordinary GET; action/navigation statuses remain.
|
|
484
|
+
const htmlStatus = result.rscError && status === 500 && ['GET', 'HEAD'].includes(method) ? 200 : status;
|
|
485
|
+
return { status: recovery ? route.parallel && status === 404 ? 404 : 500 : htmlStatus, headers: { ...responseHeaders, 'content-type': 'text/html; charset=utf-8' }, body: Buffer.from(html) };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function renderPartialAppPage({ request, route, artifact, responseHeaders, stream, signal, rsc, concrete, prefetch, knownPrefetch }) {
|
|
489
|
+
const keys = [...partialKeyMap(artifact.keyScopes, request.params, removeBasePath(new URL(request.url).pathname, request.basePath))];
|
|
490
|
+
const partialRequest = { ...request, partialFlight: artifact.flight, ...(keys.length ? { partialKeys: keys } : {}) };
|
|
491
|
+
const headers = { ...artifact.headers, ...responseHeaders, 'x-prnext-prerender': artifact.complete ? 'complete' : 'partial', 'content-type': rsc ? 'text/x-component; charset=utf-8' : 'text/html; charset=utf-8' };
|
|
492
|
+
const identity = partialIdentity(artifact);
|
|
493
|
+
headers['x-prnext-ppr-id'] = identity;
|
|
494
|
+
if (prefetch) {
|
|
495
|
+
if (route.parallel) return { status: 204, headers: responseHeaders, body: Buffer.alloc(0) };
|
|
496
|
+
const value = { version: 1, id: identity, ...(knownPrefetch?.split(',').includes(identity) ? {} : { flight: artifact.flight }), keys, unknownParams: !!artifact.generic,
|
|
497
|
+
router: { pathname: new URL(request.originalUrl || request.url).pathname, search: new URL(request.originalUrl || request.url).search, params: request.params, basePath: request.basePath } };
|
|
498
|
+
const body = Buffer.from(JSON.stringify(value));
|
|
499
|
+
if (body.byteLength > PARTIAL_PREFETCH_BYTES) return { status: 204, headers: responseHeaders, body: Buffer.alloc(0) };
|
|
500
|
+
return { status: 200, headers: { ...headers, 'content-type': PARTIAL_PREFETCH_TYPE }, body };
|
|
501
|
+
}
|
|
502
|
+
if (artifact.complete) return { status: artifact.status || 200, headers, body: request.method === 'HEAD' ? Buffer.alloc(0) : rsc ? Buffer.from(artifact.flight, 'base64') : Buffer.from(artifact.shell) };
|
|
503
|
+
if (request.method === 'HEAD') return { status: 200, headers, body: Buffer.alloc(0) };
|
|
504
|
+
async function continueFlight(continuationSignal, streaming) {
|
|
505
|
+
const resolved = concrete ? await concrete(continuationSignal) : artifact;
|
|
506
|
+
if (resolved.complete) {
|
|
507
|
+
const bytes = Buffer.from(resolved.flight, 'base64');
|
|
508
|
+
return { status: resolved.status || 200, headers: resolved.headers || {}, body: streaming
|
|
509
|
+
? new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } }) : bytes };
|
|
510
|
+
}
|
|
511
|
+
return renderFlight({ ...partialRequest, partialFlight: resolved.flight || artifact.flight, stream: streaming }, { signal: continuationSignal });
|
|
512
|
+
}
|
|
513
|
+
if (rsc) {
|
|
514
|
+
const result = await continueFlight(signal, stream);
|
|
515
|
+
return { ...result, headers: { ...headers, ...result.headers } };
|
|
516
|
+
}
|
|
517
|
+
const shell = partialShell(artifact, route);
|
|
518
|
+
const abort = new AbortController();
|
|
519
|
+
const combinedSignal = signal ? AbortSignal.any([signal, abort.signal]) : abort.signal;
|
|
520
|
+
let rendered;
|
|
521
|
+
const cancel = async reason => { abort.abort(reason || new Error('Partial response cancelled')); await rendered?.cancel?.(reason); };
|
|
522
|
+
const body = (async function* () {
|
|
523
|
+
let sent = 0;
|
|
524
|
+
const bounded = chunk => {
|
|
525
|
+
sent += chunk.byteLength;
|
|
526
|
+
if (sent > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit');
|
|
527
|
+
return chunk;
|
|
528
|
+
};
|
|
529
|
+
try {
|
|
530
|
+
// The committed bytes come straight from prerender. Even a blocked
|
|
531
|
+
// request component cannot hold back this shared document shell.
|
|
532
|
+
yield bounded(Buffer.from(shell.replace(/<\/body><\/html>$/i, '')));
|
|
533
|
+
const result = await continueFlight(combinedSignal, true);
|
|
534
|
+
rendered = await renderProgressiveAppHtml({ result, request, route, responseHeaders, decodeFlight, signal: combinedSignal,
|
|
535
|
+
partial: { shell, postponed: artifact.postponed, shellSent: true, resumeKeys: keys.map(([from, to]) => [to, from]) } });
|
|
536
|
+
for await (const chunk of rendered.body) yield bounded(chunk);
|
|
537
|
+
} finally { await cancel(); }
|
|
538
|
+
})();
|
|
539
|
+
if (stream) return { status: 200, headers, body, cancel };
|
|
540
|
+
const chunks = [];
|
|
541
|
+
let size = 0;
|
|
542
|
+
for await (const chunk of body) { size += chunk.byteLength; if (size > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit'); chunks.push(chunk); }
|
|
543
|
+
return { status: 200, headers, body: Buffer.concat(chunks, size) };
|
|
544
|
+
}
|