@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/compat/cookies.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function parseCookieHeader(value = '') {
|
|
3
|
+
function parseCookieHeader(value = '', { ignoreMalformed = false } = {}) {
|
|
4
4
|
const values = new Map();
|
|
5
5
|
for (const part of String(value).split(';')) {
|
|
6
6
|
const separator = part.indexOf('=');
|
|
7
7
|
if (separator < 1) continue;
|
|
8
8
|
const name = part.slice(0, separator).trim();
|
|
9
9
|
let value = part.slice(separator + 1).trim();
|
|
10
|
-
try { value = decodeURIComponent(value); } catch {
|
|
10
|
+
try { value = decodeURIComponent(value); } catch { if (ignoreMalformed) continue; }
|
|
11
11
|
values.set(name, { name, value });
|
|
12
12
|
}
|
|
13
13
|
return values;
|
|
@@ -50,7 +50,7 @@ function serializeCookie(cookie) {
|
|
|
50
50
|
|
|
51
51
|
function parseResponseCookie(header) {
|
|
52
52
|
const [pair, ...attributes] = header.split(';');
|
|
53
|
-
const cookie = parseCookieHeader(pair).values().next().value;
|
|
53
|
+
const cookie = parseCookieHeader(pair, { ignoreMalformed: true }).values().next().value;
|
|
54
54
|
if (!cookie) return;
|
|
55
55
|
for (const attribute of attributes) {
|
|
56
56
|
const separator = attribute.indexOf('=');
|
|
@@ -71,7 +71,7 @@ function parseResponseCookie(header) {
|
|
|
71
71
|
|
|
72
72
|
class CookieStore {
|
|
73
73
|
constructor(header, { mutable = false, onChange } = {}) {
|
|
74
|
-
this._cookies = parseCookieHeader(header);
|
|
74
|
+
this._cookies = parseCookieHeader(header, { ignoreMalformed: true });
|
|
75
75
|
this._mutable = mutable;
|
|
76
76
|
this._onChange = onChange;
|
|
77
77
|
}
|
|
@@ -99,6 +99,22 @@ class CookieStore {
|
|
|
99
99
|
*[Symbol.iterator]() { for (const [name, cookie] of this._cookies) yield [name, { ...cookie }]; }
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
class RequestCookieStore extends CookieStore {
|
|
103
|
+
constructor(headers) {
|
|
104
|
+
super(headers.get('cookie') || '', { mutable: true, onChange: () => headers.set('cookie', this.toString()) });
|
|
105
|
+
}
|
|
106
|
+
set(name, value) {
|
|
107
|
+
const cookie = typeof name === 'object' ? { name: name.name, value: name.value } : { name, value };
|
|
108
|
+
return super.set(cookie);
|
|
109
|
+
}
|
|
110
|
+
delete(names) {
|
|
111
|
+
const result = Array.isArray(names) ? names.map(name => this._cookies.delete(name)) : this._cookies.delete(names);
|
|
112
|
+
this._onChange?.();
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
clear() { this.delete([...this._cookies.keys()]); return this; }
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
class ResponseCookieStore extends CookieStore {
|
|
103
119
|
constructor(headers) {
|
|
104
120
|
super('', { mutable: true });
|
|
@@ -126,4 +142,4 @@ class ResponseCookieStore extends CookieStore {
|
|
|
126
142
|
toString() { return [...this._cookies.values()].map(serializeCookie).join('; '); }
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
module.exports = { CookieStore, ResponseCookieStore, parseCookieHeader, serializeCookie };
|
|
145
|
+
module.exports = { CookieStore, RequestCookieStore, ResponseCookieStore, parseCookieHeader, serializeCookie };
|
package/compat/data-cache.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks');
|
|
3
3
|
const http = require('node:http');
|
|
4
|
+
const cacheWire = require('./cache-wire.cjs');
|
|
4
5
|
const { setTimeout: delay } = require('node:timers/promises');
|
|
5
6
|
const { trackStaticDependency } = require('./static-generation.cjs');
|
|
6
7
|
const diagnostics = require('./diagnostics.cjs');
|
|
@@ -21,7 +22,8 @@ const rpcScope = new AsyncResource('prnext-cache-transport');
|
|
|
21
22
|
// idle sockets churns ephemeral ports under concurrent PPR traffic. Idle
|
|
22
23
|
// connections expire after 30 seconds and retain no request context.
|
|
23
24
|
const rpcAgent = new http.Agent({ keepAlive: true, maxSockets: 32, maxTotalSockets: 32, maxFreeSockets: 32, timeout: 30_000 });
|
|
24
|
-
function localRpc(target,
|
|
25
|
+
function localRpc(target, parts, signal, binary) {
|
|
26
|
+
const length = parts.reduce((sum,part)=>sum+Buffer.byteLength(part),0);
|
|
25
27
|
return rpcScope.runInAsyncScope(() => new Promise((resolve, reject) => {
|
|
26
28
|
let settled = false;
|
|
27
29
|
const finish = (error, value) => {
|
|
@@ -31,7 +33,7 @@ function localRpc(target, body, signal) {
|
|
|
31
33
|
if (error) reject(error); else resolve(value);
|
|
32
34
|
};
|
|
33
35
|
const request = http.request(target.url, { method: 'POST', agent: rpcAgent,
|
|
34
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}`, 'content-length':
|
|
36
|
+
headers: { 'content-type': binary ? cacheWire.TYPE : 'application/json', authorization: `Bearer ${target.token}`, 'content-length': length },
|
|
35
37
|
}, response => {
|
|
36
38
|
let size = 0;
|
|
37
39
|
const chunks = [];
|
|
@@ -43,14 +45,14 @@ function localRpc(target, body, signal) {
|
|
|
43
45
|
});
|
|
44
46
|
response.on('end', () => {
|
|
45
47
|
const ok = response.statusCode >= 200 && response.statusCode < 300;
|
|
46
|
-
try { finish(null, { ok, status: response.statusCode, value: ok ? JSON.parse(Buffer.concat(chunks, size).toString()) : undefined }); }
|
|
48
|
+
try { finish(null, { ok, status: response.statusCode, value: ok ? (binary ? (response.headers['content-type'] === cacheWire.TYPE ? cacheWire.decode(Buffer.concat(chunks,size)) : (()=>{throw new Error('Cache protocol mismatch')})()) : JSON.parse(Buffer.concat(chunks, size).toString())) : undefined }); }
|
|
47
49
|
catch (error) { finish(error); }
|
|
48
50
|
});
|
|
49
51
|
});
|
|
50
52
|
const abort = () => request.destroy(signal.reason instanceof Error ? signal.reason : new Error('Cache RPC aborted'));
|
|
51
53
|
request.on('error', error => finish(error));
|
|
52
54
|
signal?.addEventListener('abort', abort, { once: true });
|
|
53
|
-
if (signal?.aborted) abort(); else request.end(
|
|
55
|
+
if (signal?.aborted) abort(); else { request.cork(); for(const part of parts) request.write(part); request.end(); request.uncork(); }
|
|
54
56
|
}));
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -149,9 +151,10 @@ async function acquireExternalCacheLease(key, signal) {
|
|
|
149
151
|
throw new Error('Timed out waiting for a native incremental cache lease');
|
|
150
152
|
}
|
|
151
153
|
async function rpc(target, operation, signal) {
|
|
152
|
-
const body = JSON.stringify(operation);
|
|
153
154
|
const url = new URL(target.url);
|
|
154
155
|
const local = url.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
|
|
156
|
+
const binary = local && process.env.PRNEXT_CACHE_PROTOCOL === '2';
|
|
157
|
+
const body = binary ? cacheWire.encode(operation) : [JSON.stringify(operation.value === undefined ? operation : { ...operation, value: bufferValue(operation.value).toString('base64') })];
|
|
155
158
|
const controller = new AbortController();
|
|
156
159
|
const timer = setTimeout(() => controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')), 5000);
|
|
157
160
|
timer.unref();
|
|
@@ -159,10 +162,10 @@ async function rpc(target, operation, signal) {
|
|
|
159
162
|
const operationSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
160
163
|
let retryMs = 10;
|
|
161
164
|
while (true) {
|
|
162
|
-
const response = local ? await localRpc(target, body, operationSignal) : await nativeFetch(target.url, {
|
|
165
|
+
const response = local ? await localRpc(target, body, operationSignal, binary) : await nativeFetch(target.url, {
|
|
163
166
|
method: 'POST',
|
|
164
167
|
headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}` },
|
|
165
|
-
body,
|
|
168
|
+
body: body[0],
|
|
166
169
|
signal: operationSignal,
|
|
167
170
|
});
|
|
168
171
|
// The deadline covers the JSON body as well as response headers. A
|
|
@@ -198,6 +201,7 @@ function bufferValue(value) {
|
|
|
198
201
|
return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
199
202
|
}
|
|
200
203
|
function decodeValue(value) {
|
|
204
|
+
if (Buffer.isBuffer(value)) { if(value.length > MAX_CACHE_VALUE_BYTES) throw new Error('Invalid PRNext data cache value'); return value; }
|
|
201
205
|
if (typeof value !== 'string' || value.length > Math.ceil(MAX_CACHE_VALUE_BYTES / 3) * 4) throw new Error('Invalid PRNext data cache value');
|
|
202
206
|
const bytes = Buffer.from(value, 'base64');
|
|
203
207
|
if (bytes.length > MAX_CACHE_VALUE_BYTES) throw new Error('Invalid PRNext data cache value');
|
|
@@ -247,7 +251,7 @@ async function compute(target, key, lease, producer, options) {
|
|
|
247
251
|
try {
|
|
248
252
|
const metadata = options.resolveMetadata?.();
|
|
249
253
|
if (metadata?.externalCache) { await release(target, key, lease); return; }
|
|
250
|
-
const result = await rpc(target, { op: 'commit', key, lease, value: bytes
|
|
254
|
+
const result = await rpc(target, { op: 'commit', key, lease, value: bytes, revalidate: options.revalidate,
|
|
251
255
|
...(metadata ? { tags: validateTags(metadata.tags), paths: metadata.paths || [], revalidate: validateRevalidate(metadata.revalidate, { allowZero: true }), expire: metadata.expire } : {}) }, signal);
|
|
252
256
|
if (!result.stored) await release(target, key, lease);
|
|
253
257
|
} catch {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const WINDOW = 32, MAX_VALUE = 2 * 1024 * 1024, MAX_RESPONSE = 16 * 1024 * 1024;
|
|
4
|
+
const TYPE = 'application/x-prnext-flight-delta';
|
|
5
|
+
const MAGIC = new Uint8Array([80,82,68,49]);
|
|
6
|
+
const BLOCK_BYTES = 32 * 1024, MAX_COMMANDS_PER_CHUNK = 256, MAX_COMMANDS = 2048;
|
|
7
|
+
let POWER = 1; for(let n=1;n<WINDOW;n++) POWER = Math.imul(POWER,31) >>> 0;
|
|
8
|
+
function hash(bytes,offset) { let result=0; for(let n=0;n<WINDOW;n++) result=(Math.imul(result,31)+bytes[offset+n])>>>0; return result; }
|
|
9
|
+
function createIndex(base) {
|
|
10
|
+
const index=new Map();
|
|
11
|
+
for(let offset=0;offset+WINDOW<=base.length;offset+=WINDOW) {
|
|
12
|
+
const key=hash(base,offset), candidates=index.get(key)||[];
|
|
13
|
+
if(candidates.length<4)candidates.push(offset); index.set(key,candidates);
|
|
14
|
+
}
|
|
15
|
+
return index;
|
|
16
|
+
}
|
|
17
|
+
function frame(tag,a,b) { const value=new Uint8Array(tag===1?9:5), view=new DataView(value.buffer); value[0]=tag;view.setUint32(1,a);if(tag===1)view.setUint32(5,b);return value; }
|
|
18
|
+
function encodeChunk(bytes,base,index) {
|
|
19
|
+
const output=[];let cursor=0,literal=0,key=bytes.length>=WINDOW?hash(bytes,0):0,work=0,commands=0;
|
|
20
|
+
const budget=bytes.length*8+32768;
|
|
21
|
+
const emit=end=>{if(end>literal){commands++;output.push(frame(0,end-literal),bytes.subarray(literal,end));}};
|
|
22
|
+
while(cursor+WINDOW<=bytes.length) {
|
|
23
|
+
if(work>budget||commands>=MAX_COMMANDS_PER_CHUNK)return null;
|
|
24
|
+
let match=-1;
|
|
25
|
+
for(const offset of index.get(key)||[]) {let n=0;while(n<WINDOW&&bytes[cursor+n]===base[offset+n]){n++;work++;}if(n===WINDOW){match=offset;break;}}
|
|
26
|
+
if(match!==-1) {
|
|
27
|
+
emit(cursor);let length=WINDOW;
|
|
28
|
+
while(cursor+length<bytes.length&&match+length<base.length&&bytes[cursor+length]===base[match+length]){length++;work++;}
|
|
29
|
+
commands++;output.push(frame(1,match,length));cursor+=length;literal=cursor;
|
|
30
|
+
if(cursor+WINDOW<=bytes.length)key=hash(bytes,cursor);
|
|
31
|
+
} else {
|
|
32
|
+
key=(Math.imul((key-Math.imul(bytes[cursor],POWER))>>>0,31)+bytes[cursor+WINDOW])>>>0;cursor++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
emit(bytes.length);if(commands>MAX_COMMANDS_PER_CHUNK)return null;output.commands=commands;return output;
|
|
36
|
+
}
|
|
37
|
+
function join(parts,size) { const result=new Uint8Array(size);let offset=0;for(const part of parts){result.set(part,offset);offset+=part.length;}return result; }
|
|
38
|
+
// Commands are a wire format, not scheduling units. Thousands of tiny writes
|
|
39
|
+
// defeat gzip and multiply worker IPC/socket work even when the raw delta saves
|
|
40
|
+
// bytes. Bound each outgoing block without reading ahead into a delayed chunk.
|
|
41
|
+
function blocks(parts) {
|
|
42
|
+
const output=[];let pending=[],size=0;
|
|
43
|
+
const flush=()=>{if(size)output.push(pending.length===1?pending[0]:join(pending,size));pending=[];size=0;};
|
|
44
|
+
for(const part of parts)for(let offset=0;offset<part.length;){const n=Math.min(BLOCK_BYTES-size,part.length-offset);pending.push(part.subarray(offset,offset+n));size+=n;offset+=n;if(size===BLOCK_BYTES)flush();}
|
|
45
|
+
flush();return output;
|
|
46
|
+
}
|
|
47
|
+
function collectStream(source,{onComplete,onCancel,onFinally,capture=true}={}) {
|
|
48
|
+
const reader=source.getReader(), parts=[];let size=0,complete=false,finished=false;
|
|
49
|
+
const finish=()=>{if(!finished){finished=true;onFinally?.();}};
|
|
50
|
+
return new ReadableStream({
|
|
51
|
+
async pull(controller) {try{const next=await reader.read();if(next.done){complete=true;if(capture&&size<=MAX_VALUE)onComplete?.(join(parts,size));parts.length=0;finish();controller.close();return;}if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight bytes');size+=next.value.length;if(size>MAX_RESPONSE)throw new Error('Flight exceeds transport budget');if(capture&&size<=MAX_VALUE)parts.push(next.value.slice());else parts.length=0;controller.enqueue(next.value);}catch(error){parts.length=0;finish();controller.error(error);await reader.cancel(error).catch(()=>{});}},
|
|
52
|
+
async cancel(reason){parts.length=0;finish();if(!complete)onCancel?.();await reader.cancel(reason);},
|
|
53
|
+
},{highWaterMark:0});
|
|
54
|
+
}
|
|
55
|
+
async function deltaStream(source,base,{assess}={}) {
|
|
56
|
+
const reader=source.getReader(), first=await reader.read();
|
|
57
|
+
if(first.done)return {delta:false,body:new ReadableStream({start(c){c.close();}})};
|
|
58
|
+
let index=base instanceof Uint8Array&&base.length<=MAX_VALUE?createIndex(base):null;
|
|
59
|
+
const profitable=(raw,frames,initial)=>frames&&frames.reduce((n,b)=>n+b.length,0)+(initial?5:1)<raw.length*0.9&&(!assess||assess(raw,join(initial?[MAGIC,...frames,new Uint8Array([2])]:frames,frames.reduce((n,b)=>n+b.length,0)+(initial?5:0)))===true);
|
|
60
|
+
const firstFrames=index&&first.value.length>=WINDOW?encodeChunk(first.value,base,index):null;
|
|
61
|
+
let useDelta;
|
|
62
|
+
try{useDelta=!!profitable(first.value,firstFrames,true);}catch(error){await reader.cancel(error).catch(()=>{});throw error;}
|
|
63
|
+
let pending=useDelta?blocks([MAGIC,...firstFrames]):[first.value], pendingAt=0,ended=false,tail=false,commands=firstFrames?.commands||0;
|
|
64
|
+
if(!useDelta){base=undefined;index=undefined;}
|
|
65
|
+
return {delta:!!useDelta,body:new ReadableStream({
|
|
66
|
+
async pull(controller){try{
|
|
67
|
+
if(pendingAt===pending.length&&!ended){pending=[];pendingAt=0;const next=await reader.read();
|
|
68
|
+
if(next.done){ended=true;if(useDelta&&!tail)pending=[new Uint8Array([2])];}
|
|
69
|
+
else if(useDelta&&!tail){const encoded=encodeChunk(next.value,base,index);commands+=encoded?.commands||0;
|
|
70
|
+
if(commands>MAX_COMMANDS||!profitable(next.value,encoded,false)){tail=true;index=undefined;base=undefined;pending=blocks([new Uint8Array([3]),next.value]);}
|
|
71
|
+
else pending=blocks(encoded);
|
|
72
|
+
}else pending=[next.value];
|
|
73
|
+
}
|
|
74
|
+
if(pendingAt<pending.length)controller.enqueue(pending[pendingAt++]);else if(ended)controller.close();
|
|
75
|
+
}catch(error){pending=[];base=undefined;index=undefined;controller.error(error);await reader.cancel(error).catch(()=>{});}},
|
|
76
|
+
cancel(reason){pending=[];base=undefined;index=undefined;ended=true;return reader.cancel(reason);},
|
|
77
|
+
},{highWaterMark:0})};
|
|
78
|
+
}
|
|
79
|
+
function decodeStream(source,base) {
|
|
80
|
+
const reader=source.getReader();let chunks=[],head=0,available=0,offset=0,total=0,wireTotal=0,started=false,ended=false,tail=false,commands=0;
|
|
81
|
+
function consume(size){
|
|
82
|
+
const first=chunks[head];
|
|
83
|
+
if(first&&first.length-offset>=size){const result=first.subarray(offset,offset+size);offset+=size;available-=size;if(offset===first.length){head++;offset=0;}return result;}
|
|
84
|
+
const result=new Uint8Array(size);let written=0;
|
|
85
|
+
while(written<size){const value=chunks[head],count=Math.min(size-written,value.length-offset);result.set(value.subarray(offset,offset+count),written);written+=count;offset+=count;available-=count;if(offset===value.length){head++;offset=0;}}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
async function take(size) {
|
|
89
|
+
if(size>MAX_RESPONSE)throw new Error('Invalid Flight delta frame size');
|
|
90
|
+
while(available<size){const next=await reader.read();if(next.done)throw new Error('Truncated Flight delta');if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight delta bytes');wireTotal+=next.value.length;if(wireTotal>MAX_RESPONSE+1024*1024)throw new Error('Flight delta wire budget exceeded');if(next.value.length){if(head){chunks=chunks.slice(head);head=0;}chunks.push(next.value);available+=next.value.length;}}
|
|
91
|
+
return consume(size);
|
|
92
|
+
}
|
|
93
|
+
function peek(at){let index=head,position=offset+at;while(index<chunks.length){if(position<chunks[index].length)return chunks[index][position];position-=chunks[index++].length;}}
|
|
94
|
+
function bufferedCommand(){
|
|
95
|
+
if(!available)return false;
|
|
96
|
+
const tag=peek(0);if(tag!==0&&tag!==1)return true;
|
|
97
|
+
if(available<(tag===1?9:5))return false;
|
|
98
|
+
const length=tag===1?0:((peek(1)*0x1000000)+(peek(2)<<16)+(peek(3)<<8)+peek(4));
|
|
99
|
+
return length>MAX_RESPONSE||available>=5+length;
|
|
100
|
+
}
|
|
101
|
+
async function pullTail(controller){
|
|
102
|
+
const next=available?{value:await take(available),done:false}:await reader.read();
|
|
103
|
+
if(next.done){ended=true;controller.close();return;}
|
|
104
|
+
if(!(next.value instanceof Uint8Array))throw new Error('Invalid Flight delta bytes');
|
|
105
|
+
total+=next.value.length;if(total>MAX_RESPONSE)throw new Error('Decoded Flight exceeds transport budget');controller.enqueue(next.value);
|
|
106
|
+
}
|
|
107
|
+
return new ReadableStream({
|
|
108
|
+
async pull(controller){try{
|
|
109
|
+
if(tail)return await pullTail(controller);
|
|
110
|
+
if(!started){const magic=await take(4);if(!magic.every((value,n)=>value===MAGIC[n]))throw new Error('Unsupported Flight delta version');started=true;}
|
|
111
|
+
if(ended)return controller.close();
|
|
112
|
+
const output=[];let size=0;
|
|
113
|
+
do {
|
|
114
|
+
// Publish decoded data before waiting for another source chunk or the
|
|
115
|
+
// final EOF. This preserves Suspense flushes and downstream backpressure.
|
|
116
|
+
if(output.length&&(!bufferedCommand()||peek(0)===2||peek(0)===3))break;
|
|
117
|
+
if(++commands>65536)throw new Error('Flight delta command budget exceeded');
|
|
118
|
+
const [tag]=await take(1);let value;
|
|
119
|
+
if(tag===3){tail=true;return await pullTail(controller);}
|
|
120
|
+
if(tag===2){ended=true;if(available)throw new Error('Trailing Flight delta data');const rest=await reader.read();if(!rest.done)throw new Error('Trailing Flight delta data');controller.close();return;}
|
|
121
|
+
if(tag===0){const header=await take(4),length=new DataView(header.buffer,header.byteOffset,header.byteLength).getUint32(0);if(!length)throw new Error('Empty Flight delta literal');value=await take(length);}
|
|
122
|
+
else if(tag===1){const header=await take(8),view=new DataView(header.buffer,header.byteOffset,header.byteLength),start=view.getUint32(0),length=view.getUint32(4);if(!length||start+length>base.length)throw new Error('Invalid Flight delta reference');value=base.subarray(start,start+length);}
|
|
123
|
+
else throw new Error('Invalid Flight delta command');
|
|
124
|
+
total+=value.length;if(total>MAX_RESPONSE)throw new Error('Decoded Flight exceeds transport budget');output.push(value);size+=value.length;
|
|
125
|
+
}while(size<BLOCK_BYTES);
|
|
126
|
+
controller.enqueue(output.length===1?output[0]:join(output,size));
|
|
127
|
+
}catch(error){chunks=[];controller.error(error);await reader.cancel(error).catch(()=>{});}},
|
|
128
|
+
cancel:reason=>{chunks=[];return reader.cancel(reason);},
|
|
129
|
+
},{highWaterMark:0});
|
|
130
|
+
}
|
|
131
|
+
function createFlightTransport() {
|
|
132
|
+
let current,serial=0;
|
|
133
|
+
return {
|
|
134
|
+
snapshot(){return current;},
|
|
135
|
+
async response(response,base) {
|
|
136
|
+
const version=++serial;
|
|
137
|
+
const token=response.headers.get('x-prnext-flight-token'), headers=new Headers(response.headers);
|
|
138
|
+
let source=response.body;
|
|
139
|
+
if(!source)return response;
|
|
140
|
+
if(headers.get('content-type')?.startsWith(TYPE)) {
|
|
141
|
+
if(!base||headers.get('x-prnext-flight-base')!==base.token){await source.cancel();throw new Error('Missing Flight delta base');}
|
|
142
|
+
source=decodeStream(source,base.bytes);
|
|
143
|
+
}
|
|
144
|
+
headers.set('content-type','text/x-component; charset=utf-8');headers.delete('content-length');headers.delete('content-encoding');
|
|
145
|
+
return new Response(collectStream(source,{capture:!!token&&/^[a-f0-9]{48}$/.test(token),onComplete:bytes=>{if(version===serial&&token&&/^[a-f0-9]{48}$/.test(token))current={token,bytes};}}),{status:response.status,statusText:response.statusText,headers});
|
|
146
|
+
},
|
|
147
|
+
clear(){serial++;current=undefined;},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
module.exports={TYPE,MAX_VALUE,createIndex,encodeChunk,deltaStream,decodeStream,collectStream,createFlightTransport};
|
package/compat/headers.cjs
CHANGED
|
@@ -63,12 +63,14 @@ function runRequestContext(input, callback) {
|
|
|
63
63
|
return requestStorage.run(context, callback);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function assertAfterRequestData(name) { const context = currentRequest(); if (context.phase === 'after' && context.afterRestricted) throw new Error(`${name}() cannot be called inside after() registered by a Server Component; read it before registering the callback`); }
|
|
66
67
|
function assertUncached(name) {
|
|
67
68
|
if (inCacheScope()) throw new Error(`${name}() cannot be accessed inside unstable_cache; read it outside and pass the required values as arguments`);
|
|
68
69
|
}
|
|
69
|
-
async function headers() { if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('headers'); return sampleMethods(currentRequest(), 'headers', currentRequest().headers); } if (currentCacheScope()?.kind !== 'private') assertUncached('headers'); dynamicUsage('headers()'); return currentRequest().headers; }
|
|
70
|
-
async function cookies() { if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('cookies'); return sampleMethods(currentRequest(), 'cookies', currentRequest().cookies); } if (currentCacheScope()?.kind !== 'private') assertUncached('cookies'); dynamicUsage('cookies()'); return currentRequest().cookies; }
|
|
70
|
+
async function headers() { assertAfterRequestData('headers'); if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('headers'); return sampleMethods(currentRequest(), 'headers', currentRequest().headers); } if (currentCacheScope()?.kind !== 'private') assertUncached('headers'); dynamicUsage('headers()'); return currentRequest().headers; }
|
|
71
|
+
async function cookies() { assertAfterRequestData('cookies'); if (currentRequest().instantValidation && currentRequest().instantSample) { assertUncached('cookies'); return sampleMethods(currentRequest(), 'cookies', currentRequest().cookies); } if (currentCacheScope()?.kind !== 'private') assertUncached('cookies'); dynamicUsage('cookies()'); return currentRequest().cookies; }
|
|
71
72
|
async function draftMode() {
|
|
73
|
+
assertAfterRequestData('draftMode');
|
|
72
74
|
const context = currentRequest();
|
|
73
75
|
if (!context.draftProvider) context.draftProvider = {
|
|
74
76
|
get isEnabled() { return context.draftMode; },
|
|
@@ -78,6 +78,7 @@ async function invalidate(context, operation) {
|
|
|
78
78
|
await invoke(handler, 'revalidateTag', [[...new Set(tags)], durations], context);
|
|
79
79
|
}
|
|
80
80
|
function fresh(entry, revalidate) {
|
|
81
|
+
if (entry?.isStale === true) return false;
|
|
81
82
|
const timestamp = entry?.lastModified;
|
|
82
83
|
if (!Number.isFinite(timestamp) || timestamp < 0 || timestamp > Date.now() + 60_000) return false;
|
|
83
84
|
const stored = entry.value?.revalidate;
|
|
@@ -124,14 +125,18 @@ async function cachedIncrementalValue(key, producer, options) {
|
|
|
124
125
|
let complete;
|
|
125
126
|
const owner = { revision, done: new Promise(resolve => { complete = resolve; }) };
|
|
126
127
|
if (ownersCount < 64) { record.owners.set(key, owner); ownersCount++; }
|
|
127
|
-
let lease;
|
|
128
|
+
let lease, sharedLease;
|
|
128
129
|
const cleanup = async () => {
|
|
129
130
|
if (owner.finished) return;
|
|
130
131
|
owner.finished = true;
|
|
131
|
-
try {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
try {
|
|
133
|
+
if (sharedLease && handler.prnextLeaseVersion === 1 && typeof handler.release === 'function') await invoke(handler, 'release', [key, sharedLease], { ...context, signal: undefined });
|
|
134
|
+
} finally {
|
|
135
|
+
try { await lease?.release(); } finally {
|
|
136
|
+
if (record.owners.get(key) === owner) record.owners.delete(key);
|
|
137
|
+
if (owner.tracked) ownersCount--;
|
|
138
|
+
complete();
|
|
139
|
+
}
|
|
135
140
|
}
|
|
136
141
|
};
|
|
137
142
|
owner.tracked = record.owners.get(key) === owner;
|
|
@@ -140,7 +145,7 @@ async function cachedIncrementalValue(key, producer, options) {
|
|
|
140
145
|
if (!(value instanceof Uint8Array) || value.byteLength > MAX_BYTES) return;
|
|
141
146
|
if (lease && !await lease.valid()) return;
|
|
142
147
|
const data = incremental.encode(Buffer.from(value));
|
|
143
|
-
const setContext = { ...getContext, tags: [...new Set([...tags, ...implicit])], cacheControl: { revalidate } };
|
|
148
|
+
const setContext = { ...getContext, tags: [...new Set([...tags, ...implicit])], cacheControl: { revalidate }, prnextLease: sharedLease };
|
|
144
149
|
try {
|
|
145
150
|
await invoke(handler, 'set', [key, { kind: 'FETCH', data, revalidate: revalidate === false ? 31536000 : revalidate }, setContext], context);
|
|
146
151
|
// A mutation in any local worker can revoke the native lease while the
|
|
@@ -152,7 +157,15 @@ async function cachedIncrementalValue(key, producer, options) {
|
|
|
152
157
|
const work = (async () => {
|
|
153
158
|
try {
|
|
154
159
|
lease = await cache.acquireExternalCacheLease(key, signal);
|
|
155
|
-
if (
|
|
160
|
+
if (handler.prnextLeaseVersion === 1 && typeof handler.acquire === 'function') {
|
|
161
|
+
const until = Date.now() + 25000;
|
|
162
|
+
for (;;) {
|
|
163
|
+
signal?.throwIfAborted();
|
|
164
|
+
try { sharedLease = await invoke(handler, 'acquire', [key, { signal }], context); break; }
|
|
165
|
+
catch (error) { if (error.code !== 'PRNEXT_REDIS_BUSY' || Date.now() >= until) throw error; }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (lease || sharedLease) {
|
|
156
169
|
// Even an immediately granted lease may follow a producer that finished
|
|
157
170
|
// between our first backend read and this native admission.
|
|
158
171
|
let shared;
|
package/compat/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { ComponentType } from 'react';
|
|
3
3
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
4
|
export type { Metadata, Viewport, ResolvingMetadata, ResolvingViewport, MetadataRoute } from './metadata.cjs';
|
|
5
|
+
export {route} from './typed-route.cjs';
|
|
6
|
+
export type {Route, RoutePattern, RouteParameters} from './typed-route.cjs';
|
|
5
7
|
|
|
6
8
|
export interface InstantSample {
|
|
7
9
|
cookies?: Array<{name: string; value: string | null}>;
|
|
@@ -11,6 +13,13 @@ export interface InstantSample {
|
|
|
11
13
|
}
|
|
12
14
|
export type InstantConfig = boolean | { level?: 'warning' | 'experimental-error'; unstable_samples?: InstantSample[]; unstable_disableValidation?: true; unstable_disableDevValidation?: true; unstable_disableBuildValidation?: true };
|
|
13
15
|
|
|
16
|
+
export interface ReactCompilerOptions {
|
|
17
|
+
compilationMode?: 'infer' | 'annotation';
|
|
18
|
+
panicThreshold?: 'none' | 'critical_errors' | 'all_errors';
|
|
19
|
+
/** PRNext requires matching React 19 peers. */
|
|
20
|
+
target?: '19';
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
export type TurbopackCondition = 'browser' | 'foreign' | 'development' | 'production' | 'node' | 'edge-light'
|
|
15
24
|
| {all: TurbopackCondition[]} | {any: TurbopackCondition[]} | {not: TurbopackCondition}
|
|
16
25
|
| {path: string | RegExp; content?: RegExp} | {path?: string | RegExp; content: RegExp};
|
|
@@ -115,24 +124,39 @@ export interface NextConfig {
|
|
|
115
124
|
/** PRNext only. CLI > environment > build configuration > profile.
|
|
116
125
|
* 0 disables the corresponding retirement; keepWarm retains one lane per used host.
|
|
117
126
|
*/
|
|
127
|
+
/** Soft RSS retirement after three one-second samples; active work drains. */
|
|
128
|
+
workerMemory?: false | { maxRssMb: number };
|
|
118
129
|
workerRetirement?: { idleSeconds?: number; laneIdleSeconds?: number; keepWarm?: boolean };
|
|
119
|
-
/**
|
|
130
|
+
/** Import selected Node routes on startup; top-level module code executes. RSS is a soft import budget. */
|
|
131
|
+
workerPreload?: false | { routes: string[]; maxRssMb?: number; timeoutMs?: number };
|
|
132
|
+
/** Keep bounded previous public hashed assets. false disables carry-over. Defaults: one hour, two generations, 64 MiB. */
|
|
133
|
+
deploymentRetention?: false | {maxAge?: number; maxGenerations?: number; maxBytes?: number};
|
|
134
|
+
/** PRNext production prerender processes; maxMemoryMb bounds their combined observed memory. */
|
|
135
|
+
prerender?: {workers?: number; maxMemoryMb?: number};
|
|
136
|
+
/** Explicit App route patterns that must produce complete static HTML without browser JavaScript. */
|
|
137
|
+
htmlOnly?: string[];
|
|
138
|
+
/** Bounded active development routes; default retains five requested routes. */
|
|
120
139
|
onDemandEntries?: { maxInactiveAge?: number; pagesBufferLength?: number };
|
|
121
|
-
/**
|
|
122
|
-
devIndicators?: false;
|
|
140
|
+
/** PRNext development menu; bottom-left by default. false hides it without disabling errors or Fast Refresh. */
|
|
141
|
+
devIndicators?: false | { position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' };
|
|
123
142
|
i18n?: { locales: string[]; defaultLocale: string; localeDetection?: boolean; domains?: Array<{domain: string; defaultLocale: string; locales?: string[]; http?: boolean}> };
|
|
124
143
|
webpack?: (config: import('webpack').Configuration, context: { buildId: string; dev: boolean; isServer: boolean; nextRuntime?: 'nodejs' | 'edge'; webpack: typeof import('webpack'); defaultLoaders: {babel: {loader: string}} }) => import('webpack').Configuration | Promise<import('webpack').Configuration>;
|
|
125
144
|
turbopack?: { root?: string; rules?: Record<string, TurbopackRule | TurbopackRule[]>; resolveAlias?: Record<string, string | { browser: string }>; resolveExtensions?: string[] };
|
|
126
145
|
images?: import('./image.cjs').ImageConfig;
|
|
127
146
|
pageExtensions?: string[];
|
|
128
147
|
reactStrictMode?: boolean;
|
|
129
|
-
/**
|
|
130
|
-
reactCompiler?:
|
|
131
|
-
/**
|
|
132
|
-
typedRoutes?:
|
|
148
|
+
/** Opt in to the bundled official React Compiler for application components. Disabled by default. */
|
|
149
|
+
reactCompiler?: boolean | ReactCompilerOptions;
|
|
150
|
+
/** Generate route completions and validate Link/router destinations. */
|
|
151
|
+
typedRoutes?: boolean;
|
|
152
|
+
/** Native CSS for default styles; custom PostCSS pipelines remain authoritative. */
|
|
153
|
+
cssEngine?: 'auto'|'postcss'|'lightningcss';
|
|
133
154
|
/** Production checks use the project's installed TypeScript without emitting files. */
|
|
134
|
-
typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string };
|
|
155
|
+
typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string; devChecks?: boolean };
|
|
135
156
|
cacheComponents?: boolean;
|
|
157
|
+
partialPrefetching?: boolean;
|
|
158
|
+
/** App Router inactive layout branches. Defaults to 4 entries for 120 seconds. */
|
|
159
|
+
navigationCache?: false | {maxEntries?: number; ttlMs?: number; authCookies?: readonly string[]};
|
|
136
160
|
cacheLife?: Record<string, import('./cache.cjs').CacheLife>;
|
|
137
161
|
transpilePackages?: string[];
|
|
138
162
|
serverExternalPackages?: string[];
|
|
@@ -190,11 +214,66 @@ export interface StaticBuildOptions {
|
|
|
190
214
|
distDir?: string;
|
|
191
215
|
dev?: false;
|
|
192
216
|
}
|
|
193
|
-
export interface
|
|
217
|
+
export interface ReactCompilerReport {
|
|
218
|
+
version: 1;
|
|
219
|
+
compilerVersion: string | null;
|
|
220
|
+
enabled: boolean;
|
|
221
|
+
buildId: string | null;
|
|
222
|
+
mode: 'development' | 'production';
|
|
223
|
+
/** Incremental graphs can reuse inputs without observing their transforms. */
|
|
224
|
+
scope: 'observed-transforms';
|
|
225
|
+
options: boolean | ReactCompilerOptions | null;
|
|
226
|
+
truncated: boolean;
|
|
227
|
+
summary: {files: number; functions: number; optimized: number; skipped: number; errors: number; diagnostics: number; mixed: number; transforms: number; cacheReuses: number};
|
|
228
|
+
files: Array<{
|
|
229
|
+
file: string; transforms: number; cacheReuses: number; targets: string[]; notes: string[];
|
|
230
|
+
functions: Array<{
|
|
231
|
+
name: string | null;
|
|
232
|
+
start: {line: number; column: number} | null;
|
|
233
|
+
end: {line: number; column: number} | null;
|
|
234
|
+
status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
|
|
235
|
+
targets: string[];
|
|
236
|
+
events: Array<{
|
|
237
|
+
kind: 'CompileSuccess' | 'CompileSkip' | 'CompileError' | 'CompileDiagnostic' | 'PipelineError';
|
|
238
|
+
status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
|
|
239
|
+
reason: string | null; category: string | null; targets: string[]; policy?: boolean;
|
|
240
|
+
memoSlots?: number; memoBlocks?: number; memoValues?: number;
|
|
241
|
+
}>;
|
|
242
|
+
}>;
|
|
243
|
+
}>;
|
|
244
|
+
graphs: Array<{target: string; metadataAvailable: boolean; projectJavaScriptInputs: number; observedFiles: number; unobservedInputs: number}>;
|
|
245
|
+
}
|
|
246
|
+
export interface CompilationCacheReport {
|
|
247
|
+
version: 1;
|
|
248
|
+
scope: 'eligible-production-compilation-graphs';
|
|
249
|
+
hits: number;
|
|
250
|
+
misses: number;
|
|
251
|
+
bypasses: number;
|
|
252
|
+
reusedBytes: number;
|
|
253
|
+
kinds: Array<{kind: string; hits: number; misses: number; bypasses: number; writes: number; reusedBytes: number; reasons: Record<string, number>}>;
|
|
254
|
+
}
|
|
255
|
+
export interface BuildResult { buildId: string; outputDirectory: string; reactCompilerReport?: ReactCompilerReport; compilationCache?: CompilationCacheReport; }
|
|
256
|
+
export interface ProductionBuilder {
|
|
257
|
+
build(options?: {envMode?: 'production' | 'test'}): Promise<BuildResult>;
|
|
258
|
+
stats(): {contexts: number; created: number; reused: number};
|
|
259
|
+
dispose(): Promise<void>;
|
|
260
|
+
}
|
|
261
|
+
export function createBuilder(projectRoot: string, options?: {envMode?: 'production' | 'test'}): Promise<ProductionBuilder>;
|
|
194
262
|
export interface StaticBuildResult extends BuildResult { files: number; bytes: number; }
|
|
195
263
|
export function build(projectRoot: string, options: StaticBuildOptions & { adapter: 'static' }): Promise<StaticBuildResult>;
|
|
196
|
-
|
|
264
|
+
/** Detect a completed browser output or a PRNext React project; does not run another builder. */
|
|
265
|
+
export function build(projectRoot: string, options: { adapter: 'auto'; dev?: false; spa?: boolean }): Promise<BuildResult | StaticBuildResult>;
|
|
266
|
+
export function build(projectRoot: string, options?: { adapter?: 'react'; dev?: boolean; incremental?: boolean; envMode?: 'production' | 'development' | 'test' }): Promise<BuildResult>;
|
|
197
267
|
export function buildStatic(projectRoot: string, options?: StaticBuildOptions): Promise<StaticBuildResult>;
|
|
198
268
|
export function readBuildOutput(buildDirectory: string): Promise<BuildOutput>;
|
|
199
269
|
export function validateBuildOutput(value: unknown): BuildOutput;
|
|
200
270
|
export type { DocumentContext, DocumentInitialProps, DocumentProps } from './document.cjs';
|
|
271
|
+
|
|
272
|
+
/** Server instrumentation hooks, compatible with Next.js instrumentation files. */
|
|
273
|
+
export namespace Instrumentation {
|
|
274
|
+
export type onRequestError = (error: unknown, request: Readonly<{path: string; method: string; headers: Record<string,string|string[]>}>, context: {
|
|
275
|
+
routerKind: 'App Router' | 'Pages Router'; routePath: string; routeType: 'render' | 'route' | 'action' | 'proxy';
|
|
276
|
+
renderSource: 'react-server-components' | 'react-server-components-payload' | 'server-rendering';
|
|
277
|
+
revalidateReason: 'on-demand' | 'stale' | undefined; renderType: 'dynamic' | 'dynamic-resume';
|
|
278
|
+
}) => void | Promise<void>;
|
|
279
|
+
}
|
package/compat/link.cjs
CHANGED
|
@@ -7,7 +7,7 @@ const { addBasePath, hasBasePath, normalizeTrailingSlash } = require('./paths.cj
|
|
|
7
7
|
const { localizedHref } = require('./locale.cjs');
|
|
8
8
|
|
|
9
9
|
const Link = React.forwardRef(function Link({ href, as, children, replace, scroll, shallow, prefetch,
|
|
10
|
-
locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, ...props }, ref) {
|
|
10
|
+
locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, onFocus, ...props }, ref) {
|
|
11
11
|
const app = React.useContext(AppRouterContext);
|
|
12
12
|
const pages = React.useContext(RouterContext);
|
|
13
13
|
const anchorRef = React.useRef(null);
|
|
@@ -19,14 +19,15 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
|
|
|
19
19
|
: target.startsWith('//') ? target : addBasePath(target, basePath);
|
|
20
20
|
const publicTarget = normalizeTrailingSlash(mounted, policy);
|
|
21
21
|
const source = formatUrl(href);
|
|
22
|
-
const requestPrefetch = React.useCallback(() => {
|
|
23
|
-
if (
|
|
24
|
-
if (
|
|
25
|
-
|
|
22
|
+
const requestPrefetch = React.useCallback((priority = 'intent') => {
|
|
23
|
+
if (prefetch === false || process.env.NODE_ENV !== 'production') return;
|
|
24
|
+
if (app?.router.partialPrefetch) app.router.prefetch(target, { priority, full: prefetch === true });
|
|
25
|
+
if (!app && pages) void pages.prefetch(source, as ? target : undefined, { locale, priority }).catch(() => {});
|
|
26
|
+
}, [app, pages, source, target, as, locale, prefetch]);
|
|
26
27
|
React.useEffect(() => {
|
|
27
28
|
if ((app ? !app.router.partialPrefetch : !pages) || prefetch === false || process.env.NODE_ENV !== 'production' || typeof IntersectionObserver === 'undefined' || !anchorRef.current) return;
|
|
28
29
|
const observer = new IntersectionObserver(entries => {
|
|
29
|
-
if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch(); }
|
|
30
|
+
if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch('viewport'); }
|
|
30
31
|
}, { rootMargin: '200px' });
|
|
31
32
|
observer.observe(anchorRef.current);
|
|
32
33
|
return () => observer.disconnect();
|
|
@@ -53,16 +54,18 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
|
|
|
53
54
|
void pages[replace ? 'replace' : 'push'](href, as, { scroll, shallow, locale }).catch(error => console.error(error));
|
|
54
55
|
} else if (replace) { event.preventDefault(); window.location.replace(publicTarget); }
|
|
55
56
|
}
|
|
56
|
-
function hover(event) { onMouseEnter?.(event); requestPrefetch(); }
|
|
57
|
-
function touch(event) { onTouchStart?.(event); requestPrefetch(); }
|
|
57
|
+
function hover(event) { onMouseEnter?.(event); if (!event.defaultPrevented) requestPrefetch(); }
|
|
58
|
+
function touch(event) { onTouchStart?.(event); if (!event.defaultPrevented) requestPrefetch(); }
|
|
59
|
+
function focus(event) { onFocus?.(event); if (!event.defaultPrevented) requestPrefetch(); }
|
|
58
60
|
if (legacyBehavior) {
|
|
59
61
|
const child = React.Children.only(children);
|
|
60
62
|
return React.cloneElement(child, { ...props, href: publicTarget, ref: setRef,
|
|
61
63
|
onMouseEnter(event) { child.props.onMouseEnter?.(event); hover(event); },
|
|
62
64
|
onTouchStart(event) { child.props.onTouchStart?.(event); touch(event); },
|
|
65
|
+
onFocus(event) { child.props.onFocus?.(event); focus(event); },
|
|
63
66
|
onClick(event) { child.props.onClick?.(event); if (!event.defaultPrevented) click(event); } });
|
|
64
67
|
}
|
|
65
|
-
return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch }, children);
|
|
68
|
+
return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch, onFocus: focus }, children);
|
|
66
69
|
});
|
|
67
70
|
module.exports = Link;
|
|
68
71
|
module.exports.default = Link;
|
package/compat/link.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AnchorHTMLAttributes, ForwardRefExoticComponent, ReactNode, RefAttributes } from 'react';
|
|
2
2
|
import type { UrlObject } from './router.cjs';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type {Route,TypedUrlObject} from './typed-route.cjs';
|
|
4
|
+
export interface LinkProps<T extends string=string> extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
|
5
|
+
href: Route<T> | TypedUrlObject;
|
|
5
6
|
as?: string | UrlObject;
|
|
6
7
|
replace?: boolean;
|
|
7
8
|
scroll?: boolean;
|
|
@@ -13,5 +14,5 @@ export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
|
13
14
|
children?: ReactNode;
|
|
14
15
|
onNavigate?: (event: { preventDefault(): void }) => void;
|
|
15
16
|
}
|
|
16
|
-
declare
|
|
17
|
+
declare function Link<T extends string>(props:LinkProps<T> & RefAttributes<HTMLAnchorElement>): import('react').ReactElement|null;
|
|
17
18
|
export default Link;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createHmac } = require('node:crypto');
|
|
3
|
+
function navigationAuthorization(context, secret, cookieNames) {
|
|
4
|
+
if (!secret || context?.staticState) return undefined;
|
|
5
|
+
const names = cookieNames === undefined ? null : new Set(cookieNames);
|
|
6
|
+
const cookies = context.cookies.getAll().filter(cookie => !names || names.has(cookie.name)).map(({ name, value }) => [name, value]).sort((a, b) => a[0].localeCompare(b[0]));
|
|
7
|
+
return createHmac('sha256', secret).update(JSON.stringify([cookies, context.headers.get('authorization') || ''])).digest('hex');
|
|
8
|
+
}
|
|
9
|
+
module.exports = { navigationAuthorization };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { optionalRequestContext, inCacheScope } = require('./data-cache.cjs');
|
|
3
|
+
|
|
4
|
+
function postponeStage(name) {
|
|
5
|
+
throw Object.assign(new Error(`Deferred until ${name}.`), { code: 'PRNEXT_PPR_DYNAMIC', digest: 'PRNEXT_PPR_DYNAMIC' });
|
|
6
|
+
}
|
|
7
|
+
function stage(name) {
|
|
8
|
+
const context = optionalRequestContext();
|
|
9
|
+
if (!context?.cacheComponents) throw new Error(`${name}() requires cacheComponents: true.`);
|
|
10
|
+
if (inCacheScope()) throw new Error(`${name}() cannot run inside a cached function. Call it before invoking the cached function.`);
|
|
11
|
+
// Static prerenders deliberately continue through both gates. Cacheable work
|
|
12
|
+
// remains prerenderable and is not made request-dependent by either API.
|
|
13
|
+
if (context.staticState) return;
|
|
14
|
+
if (context.navigationStage === 'shell' && (name === 'navigation' || context.partialPrefetching)) postponeStage(name);
|
|
15
|
+
if (context.navigationStage === 'prefetch' && name === 'navigation') postponeStage(name);
|
|
16
|
+
}
|
|
17
|
+
async function navigation() { stage('navigation'); }
|
|
18
|
+
async function prefetch() { stage('prefetch'); }
|
|
19
|
+
module.exports = { navigation, prefetch, postponeStage };
|