@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,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseCookieHeader(value = '') {
|
|
4
|
+
const values = new Map();
|
|
5
|
+
for (const part of String(value).split(';')) {
|
|
6
|
+
const separator = part.indexOf('=');
|
|
7
|
+
if (separator < 1) continue;
|
|
8
|
+
const name = part.slice(0, separator).trim();
|
|
9
|
+
let value = part.slice(separator + 1).trim();
|
|
10
|
+
try { value = decodeURIComponent(value); } catch { /* Preserve malformed user input. */ }
|
|
11
|
+
values.set(name, { name, value });
|
|
12
|
+
}
|
|
13
|
+
return values;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function serializeCookie(cookie) {
|
|
17
|
+
const { name, value = '', path = '/', domain, expires, maxAge, httpOnly, secure, sameSite, priority, partitioned } = cookie;
|
|
18
|
+
if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name)) throw new TypeError('Invalid cookie name');
|
|
19
|
+
const result = [`${name}=${encodeURIComponent(String(value))}`];
|
|
20
|
+
for (const [key, attribute] of [['Path', path], ['Domain', domain]]) {
|
|
21
|
+
if (attribute !== undefined && attribute !== false) {
|
|
22
|
+
if (/[;\r\n\u0000]/.test(String(attribute))) throw new TypeError(`Invalid cookie ${key}`);
|
|
23
|
+
result.push(`${key}=${attribute}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (expires !== undefined) {
|
|
27
|
+
const date = expires instanceof Date ? expires : new Date(expires);
|
|
28
|
+
if (!Number.isFinite(date.getTime())) throw new TypeError('Invalid cookie expiry');
|
|
29
|
+
result.push(`Expires=${date.toUTCString()}`);
|
|
30
|
+
}
|
|
31
|
+
if (maxAge !== undefined) {
|
|
32
|
+
if (!Number.isFinite(maxAge)) throw new TypeError('Invalid cookie maxAge');
|
|
33
|
+
result.push(`Max-Age=${Math.floor(maxAge)}`);
|
|
34
|
+
}
|
|
35
|
+
if (httpOnly) result.push('HttpOnly');
|
|
36
|
+
if (secure) result.push('Secure');
|
|
37
|
+
if (sameSite) {
|
|
38
|
+
const normalized = sameSite === true ? 'strict' : String(sameSite).toLowerCase();
|
|
39
|
+
if (!['lax', 'strict', 'none'].includes(normalized)) throw new TypeError('Invalid cookie sameSite');
|
|
40
|
+
result.push(`SameSite=${normalized[0].toUpperCase()}${normalized.slice(1)}`);
|
|
41
|
+
}
|
|
42
|
+
if (priority) {
|
|
43
|
+
const normalized = String(priority).toLowerCase();
|
|
44
|
+
if (!['low', 'medium', 'high'].includes(normalized)) throw new TypeError('Invalid cookie priority');
|
|
45
|
+
result.push(`Priority=${normalized[0].toUpperCase()}${normalized.slice(1)}`);
|
|
46
|
+
}
|
|
47
|
+
if (partitioned) result.push('Partitioned');
|
|
48
|
+
return result.join('; ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseResponseCookie(header) {
|
|
52
|
+
const [pair, ...attributes] = header.split(';');
|
|
53
|
+
const cookie = parseCookieHeader(pair).values().next().value;
|
|
54
|
+
if (!cookie) return;
|
|
55
|
+
for (const attribute of attributes) {
|
|
56
|
+
const separator = attribute.indexOf('=');
|
|
57
|
+
const key = (separator < 0 ? attribute : attribute.slice(0, separator)).trim().toLowerCase();
|
|
58
|
+
const value = separator < 0 ? '' : attribute.slice(separator + 1).trim();
|
|
59
|
+
if (['path', 'domain'].includes(key)) cookie[key] = value;
|
|
60
|
+
else if (key === 'expires') {
|
|
61
|
+
const date = new Date(value);
|
|
62
|
+
if (Number.isFinite(date.getTime())) cookie.expires = date;
|
|
63
|
+
} else if (key === 'max-age' && /^-?\d+$/.test(value)) cookie.maxAge = Number(value);
|
|
64
|
+
else if (key === 'httponly') cookie.httpOnly = true;
|
|
65
|
+
else if (key === 'secure' || key === 'partitioned') cookie[key] = true;
|
|
66
|
+
else if (key === 'samesite' && ['strict', 'lax', 'none'].includes(value.toLowerCase())) cookie.sameSite = value.toLowerCase();
|
|
67
|
+
else if (key === 'priority' && ['low', 'medium', 'high'].includes(value.toLowerCase())) cookie.priority = value.toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
return cookie;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class CookieStore {
|
|
73
|
+
constructor(header, { mutable = false, onChange } = {}) {
|
|
74
|
+
this._cookies = parseCookieHeader(header);
|
|
75
|
+
this._mutable = mutable;
|
|
76
|
+
this._onChange = onChange;
|
|
77
|
+
}
|
|
78
|
+
get size() { return this._cookies.size; }
|
|
79
|
+
get(name) { const value = this._cookies.get(typeof name === 'object' ? name.name : name); return value && { ...value }; }
|
|
80
|
+
getAll(name) { return [...this._cookies.values()].filter(cookie => !name || cookie.name === (typeof name === 'object' ? name.name : name)).map(cookie => ({ ...cookie })); }
|
|
81
|
+
has(name) { return this._cookies.has(name); }
|
|
82
|
+
set(name, value, options = {}) {
|
|
83
|
+
if (!this._mutable) throw new Error('Cookies can only be modified in a Route Handler or Server Action');
|
|
84
|
+
const cookie = typeof name === 'object' ? { ...name } : { ...options, name, value };
|
|
85
|
+
serializeCookie(cookie);
|
|
86
|
+
this._cookies.set(cookie.name, cookie);
|
|
87
|
+
this._onChange?.(cookie, false);
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
delete(name) {
|
|
91
|
+
if (!this._mutable) throw new Error('Cookies can only be modified in a Route Handler or Server Action');
|
|
92
|
+
const cookie = typeof name === 'object' ? name : { name };
|
|
93
|
+
this._cookies.delete(cookie.name);
|
|
94
|
+
this._onChange?.({ ...cookie, value: '', expires: new Date(0), maxAge: 0 }, true);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
clear() { for (const name of this._cookies.keys()) this.delete(name); return this; }
|
|
98
|
+
toString() { return [...this._cookies.values()].map(({ name, value }) => `${name}=${encodeURIComponent(value)}`).join('; '); }
|
|
99
|
+
*[Symbol.iterator]() { for (const [name, cookie] of this._cookies) yield [name, { ...cookie }]; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class ResponseCookieStore extends CookieStore {
|
|
103
|
+
constructor(headers) {
|
|
104
|
+
super('', { mutable: true });
|
|
105
|
+
this._headers = headers;
|
|
106
|
+
for (const header of headers.getSetCookie()) {
|
|
107
|
+
const cookie = parseResponseCookie(header);
|
|
108
|
+
if (cookie) this._cookies.set(cookie.name, cookie);
|
|
109
|
+
}
|
|
110
|
+
this._onChange = () => {
|
|
111
|
+
headers.delete('set-cookie');
|
|
112
|
+
for (const cookie of this._cookies.values()) headers.append('set-cookie', serializeCookie(cookie));
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
set(name, value, options = {}) {
|
|
116
|
+
const cookie = typeof name === 'object' ? { ...name } : { ...options, name, value };
|
|
117
|
+
cookie.path ??= '/';
|
|
118
|
+
if (typeof cookie.expires === 'number') cookie.expires = new Date(cookie.expires);
|
|
119
|
+
if (cookie.maxAge) cookie.expires = new Date(Date.now() + cookie.maxAge * 1000);
|
|
120
|
+
return super.set(cookie);
|
|
121
|
+
}
|
|
122
|
+
delete(name) {
|
|
123
|
+
const cookie = typeof name === 'object' ? name : { name };
|
|
124
|
+
return this.set({ ...cookie, value: '', expires: new Date(0) });
|
|
125
|
+
}
|
|
126
|
+
toString() { return [...this._cookies.values()].map(serializeCookie).join('; '); }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { CookieStore, ResponseCookieStore, parseCookieHeader, serializeCookie };
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks');
|
|
3
|
+
const http = require('node:http');
|
|
4
|
+
const { setTimeout: delay } = require('node:timers/promises');
|
|
5
|
+
const { trackStaticDependency } = require('./static-generation.cjs');
|
|
6
|
+
|
|
7
|
+
// Capture before the runtime installs its application-facing fetch wrapper.
|
|
8
|
+
const nativeFetchSymbol = Symbol.for('prnext.nativeFetch');
|
|
9
|
+
const nativeFetch = globalThis[nativeFetchSymbol] ||= globalThis.fetch.bind(globalThis);
|
|
10
|
+
const cacheStorage = new AsyncLocalStorage();
|
|
11
|
+
const owners = new Map();
|
|
12
|
+
const MAX_CACHE_VALUE_BYTES = 2 * 1024 * 1024;
|
|
13
|
+
const MAX_PENDING_OWNERS = 1024;
|
|
14
|
+
const WAIT_TIMEOUT_MS = 25_000;
|
|
15
|
+
const DEFERRED_VALUE = Symbol('PRNext deferred cache value');
|
|
16
|
+
// Cache RPC is a local JSON protocol, not an application fetch. Keep its socket
|
|
17
|
+
// pool small and outside request AsyncLocalStorage stores.
|
|
18
|
+
const rpcScope = new AsyncResource('prnext-cache-transport');
|
|
19
|
+
// Keep the bounded active pool reusable across bursts: trimming it to four
|
|
20
|
+
// idle sockets churns ephemeral ports under concurrent PPR traffic. Idle
|
|
21
|
+
// connections expire after 30 seconds and retain no request context.
|
|
22
|
+
const rpcAgent = new http.Agent({ keepAlive: true, maxSockets: 32, maxTotalSockets: 32, maxFreeSockets: 32, timeout: 30_000 });
|
|
23
|
+
function localRpc(target, body, signal) {
|
|
24
|
+
return rpcScope.runInAsyncScope(() => new Promise((resolve, reject) => {
|
|
25
|
+
let settled = false;
|
|
26
|
+
const finish = (error, value) => {
|
|
27
|
+
if (settled) return;
|
|
28
|
+
settled = true;
|
|
29
|
+
signal?.removeEventListener('abort', abort);
|
|
30
|
+
if (error) reject(error); else resolve(value);
|
|
31
|
+
};
|
|
32
|
+
const request = http.request(target.url, { method: 'POST', agent: rpcAgent,
|
|
33
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}`, 'content-length': Buffer.byteLength(body) },
|
|
34
|
+
}, response => {
|
|
35
|
+
let size = 0;
|
|
36
|
+
const chunks = [];
|
|
37
|
+
response.on('error', error => finish(error));
|
|
38
|
+
response.on('data', chunk => {
|
|
39
|
+
size += chunk.length;
|
|
40
|
+
if (size > 4 * 1024 * 1024) request.destroy(new Error('Cache RPC response exceeds 4 MiB'));
|
|
41
|
+
else if (response.statusCode >= 200 && response.statusCode < 300) chunks.push(chunk);
|
|
42
|
+
});
|
|
43
|
+
response.on('end', () => {
|
|
44
|
+
const ok = response.statusCode >= 200 && response.statusCode < 300;
|
|
45
|
+
try { finish(null, { ok, status: response.statusCode, value: ok ? JSON.parse(Buffer.concat(chunks, size).toString()) : undefined }); }
|
|
46
|
+
catch (error) { finish(error); }
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const abort = () => request.destroy(signal.reason instanceof Error ? signal.reason : new Error('Cache RPC aborted'));
|
|
50
|
+
request.on('error', error => finish(error));
|
|
51
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
52
|
+
if (signal?.aborted) abort(); else request.end(body);
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createCacheState() {
|
|
57
|
+
return { pending: new Set(), invalidations: Promise.resolve(), noStore: false, refresh: false };
|
|
58
|
+
}
|
|
59
|
+
let requestReader;
|
|
60
|
+
function optionalRequestContext() {
|
|
61
|
+
// Lazy access avoids a cycle when headers.cjs imports the scope guard.
|
|
62
|
+
try { requestReader ||= require('./headers.cjs').currentRequest; return requestReader?.(); } catch { return undefined; }
|
|
63
|
+
}
|
|
64
|
+
function cacheState(context = optionalRequestContext()) {
|
|
65
|
+
if (!context) return undefined;
|
|
66
|
+
return context.cacheState ||= createCacheState();
|
|
67
|
+
}
|
|
68
|
+
function inCacheScope() { return Boolean(cacheStorage.getStore()); }
|
|
69
|
+
function currentCacheScope() { return cacheStorage.getStore(); }
|
|
70
|
+
function runCacheScope(callback, scope = true) { return cacheStorage.run(scope, callback); }
|
|
71
|
+
|
|
72
|
+
function validateTags(tags = []) {
|
|
73
|
+
if (!Array.isArray(tags) || tags.length > 128) throw new TypeError('Cache tags must be an array with at most 128 entries');
|
|
74
|
+
for (const tag of tags) {
|
|
75
|
+
if (typeof tag !== 'string' || tag.length > 256) throw new TypeError('Cache tags must be strings of at most 256 characters');
|
|
76
|
+
}
|
|
77
|
+
return [...new Set(tags)];
|
|
78
|
+
}
|
|
79
|
+
function validateRevalidate(value, { allowZero = false } = {}) {
|
|
80
|
+
if (value === undefined || value === false || value === null) return null;
|
|
81
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || (allowZero ? value < 0 : value <= 0)) {
|
|
82
|
+
throw new TypeError(`Cache revalidate must be ${allowZero ? 'a nonnegative' : 'a positive'} number of seconds or false`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeCachePath(value, { groups = false } = {}) {
|
|
88
|
+
if (typeof value !== 'string' || !value.startsWith('/') || value.startsWith('//') || value.includes('\\')) {
|
|
89
|
+
throw new TypeError('Cache paths must be absolute application paths');
|
|
90
|
+
}
|
|
91
|
+
if (value.length > 1024) throw new TypeError('Cache paths must not exceed 1024 characters');
|
|
92
|
+
if (groups) value = value.split('/').filter(segment => !/^\([^/]+\)$/.test(segment)).join('/') || '/';
|
|
93
|
+
const pathname = new URL(value, 'http://prnext.local').pathname
|
|
94
|
+
.replace(/%[0-9a-f]{2}/gi, escape => {
|
|
95
|
+
const character = String.fromCharCode(parseInt(escape.slice(1), 16));
|
|
96
|
+
return /[a-z\d_.~-]/i.test(character) ? character : escape.toUpperCase();
|
|
97
|
+
});
|
|
98
|
+
return pathname.replace(/\/+$/, '') || '/';
|
|
99
|
+
}
|
|
100
|
+
function getCachePaths(context = optionalRequestContext()) {
|
|
101
|
+
if (!context) return [];
|
|
102
|
+
const paths = new Set();
|
|
103
|
+
const add = pathname => {
|
|
104
|
+
paths.add(`page:${pathname}`);
|
|
105
|
+
paths.add('layout:/');
|
|
106
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
107
|
+
for (let index = 1; index <= segments.length; index++) paths.add(`layout:/${segments.slice(0, index).join('/')}`);
|
|
108
|
+
};
|
|
109
|
+
if (context.url) add(normalizeCachePath(new URL(context.url, 'http://prnext.local').pathname));
|
|
110
|
+
else if (context.pathname) add(normalizeCachePath(context.pathname));
|
|
111
|
+
const pattern = context.route?.pattern || context.routePattern;
|
|
112
|
+
if (pattern) add(normalizeCachePath(pattern, { groups: true }));
|
|
113
|
+
return [...paths];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function endpoint() {
|
|
117
|
+
const url = process.env.PRNEXT_CACHE_URL;
|
|
118
|
+
const token = process.env.PRNEXT_CACHE_TOKEN;
|
|
119
|
+
return url && token ? { url, token } : undefined;
|
|
120
|
+
}
|
|
121
|
+
async function cacheGeneration(signal) {
|
|
122
|
+
const target = endpoint();
|
|
123
|
+
if (!target) return null;
|
|
124
|
+
const result = await rpc(target, { op: 'generation' }, signal);
|
|
125
|
+
if (!Number.isSafeInteger(result.generation) || result.generation < 0) throw new Error('Invalid PRNext cache generation');
|
|
126
|
+
return result.generation;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Coordinate external backend fills across local Node workers without storing a second value. */
|
|
130
|
+
async function acquireExternalCacheLease(key, signal) {
|
|
131
|
+
const target = endpoint();
|
|
132
|
+
if (!target) return undefined;
|
|
133
|
+
const digest = require('node:crypto').createHash('sha256').update('prnext-external-lease\0').update(key).digest('hex');
|
|
134
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
135
|
+
let waited = false;
|
|
136
|
+
while (Date.now() < deadline) {
|
|
137
|
+
throwIfAborted(signal);
|
|
138
|
+
let entry;
|
|
139
|
+
try { entry = await rpc(target, { op: 'read', key: digest, tags: [], paths: [], revalidate: null, forceFresh: true }, signal); }
|
|
140
|
+
catch { throwIfAborted(signal); return undefined; }
|
|
141
|
+
if (entry.state === 'miss' && entry.lease) return { waited, release: () => release(target, digest, entry.lease),
|
|
142
|
+
valid: async () => { try { return (await rpc(target, { op: 'leaseStatus', key: digest, lease: entry.lease }, signal)).valid === true; } catch { return false; } },
|
|
143
|
+
};
|
|
144
|
+
if (entry.state !== 'pending') return undefined;
|
|
145
|
+
waited = true;
|
|
146
|
+
await delay(Math.min(Math.max(Number(entry.retryAfterMs) || 20, 5), 100), undefined, { signal });
|
|
147
|
+
}
|
|
148
|
+
throw new Error('Timed out waiting for a native incremental cache lease');
|
|
149
|
+
}
|
|
150
|
+
async function rpc(target, operation, signal) {
|
|
151
|
+
const body = JSON.stringify(operation);
|
|
152
|
+
const url = new URL(target.url);
|
|
153
|
+
const local = url.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const timer = setTimeout(() => controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')), 5000);
|
|
156
|
+
timer.unref();
|
|
157
|
+
try {
|
|
158
|
+
const operationSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
159
|
+
let retryMs = 10;
|
|
160
|
+
while (true) {
|
|
161
|
+
const response = local ? await localRpc(target, body, operationSignal) : await nativeFetch(target.url, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${target.token}` },
|
|
164
|
+
body,
|
|
165
|
+
signal: operationSignal,
|
|
166
|
+
});
|
|
167
|
+
// The deadline covers the JSON body as well as response headers. A
|
|
168
|
+
// completed RPC must release its timer and inherited request context.
|
|
169
|
+
if (response.ok) return local ? response.value : await response.json();
|
|
170
|
+
await response.body?.cancel();
|
|
171
|
+
// The native service bounds admission before doing any operation. Retry
|
|
172
|
+
// temporary saturation instead of turning a burst of misses into origin work.
|
|
173
|
+
if (response.status !== 503) throw new Error(`PRNext data cache ${operation.op} failed (${response.status})`);
|
|
174
|
+
await delay(retryMs + Math.floor(Math.random() * 10), undefined, { signal: operationSignal });
|
|
175
|
+
retryMs = Math.min(retryMs * 2, 200);
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function throwIfAborted(signal) { signal?.throwIfAborted(); }
|
|
182
|
+
function deferredValue(value, cacheValue, dispose) {
|
|
183
|
+
return { [DEFERRED_VALUE]: true, value, cacheValue: Promise.resolve(cacheValue), dispose };
|
|
184
|
+
}
|
|
185
|
+
function untilAborted(promise, signal) {
|
|
186
|
+
if (!signal) return Promise.resolve(promise);
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
const abort = () => { cleanup(); reject(signal.reason); };
|
|
189
|
+
const cleanup = () => signal.removeEventListener('abort', abort);
|
|
190
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
191
|
+
if (signal.aborted) abort();
|
|
192
|
+
Promise.resolve(promise).then(value => { cleanup(); resolve(value); }, error => { cleanup(); reject(error); });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function bufferValue(value) {
|
|
196
|
+
if (!Buffer.isBuffer(value) && !(value instanceof Uint8Array)) throw new TypeError('Cache producers must return bytes');
|
|
197
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
198
|
+
}
|
|
199
|
+
function decodeValue(value) {
|
|
200
|
+
if (typeof value !== 'string' || value.length > Math.ceil(MAX_CACHE_VALUE_BYTES / 3) * 4) throw new Error('Invalid PRNext data cache value');
|
|
201
|
+
const bytes = Buffer.from(value, 'base64');
|
|
202
|
+
if (bytes.length > MAX_CACHE_VALUE_BYTES) throw new Error('Invalid PRNext data cache value');
|
|
203
|
+
return bytes;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function release(target, key, lease) {
|
|
207
|
+
try { await rpc(target, { op: 'release', key, lease }, AbortSignal.timeout(1000)); } catch { /* A native lease expires even if its owner disappears. */ }
|
|
208
|
+
}
|
|
209
|
+
function trackBackground(state, work) {
|
|
210
|
+
const pending = work.catch(async error => {
|
|
211
|
+
// Fetch may use a thrown sentinel to return an uncacheable live Response.
|
|
212
|
+
// Background refresh has no consumer, so release that response body too.
|
|
213
|
+
try { await untilAborted(error?.dispose?.(), AbortSignal.timeout(1000)); } catch { /* Preserve the previous cached value. */ }
|
|
214
|
+
});
|
|
215
|
+
if (state) {
|
|
216
|
+
state.pending.add(pending);
|
|
217
|
+
void pending.finally(() => state.pending.delete(pending));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function compute(target, key, lease, producer, options) {
|
|
222
|
+
const identity = `${target.url}\n${key}`;
|
|
223
|
+
const controller = new AbortController();
|
|
224
|
+
const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
|
|
225
|
+
const timer = setTimeout(() => controller.abort(new Error('PRNext data cache producer timed out')), Math.max(1, options.deadline - Date.now()));
|
|
226
|
+
timer.unref();
|
|
227
|
+
let completed;
|
|
228
|
+
const completion = new Promise(resolve => { completed = resolve; });
|
|
229
|
+
if (owners.size < MAX_PENDING_OWNERS) owners.set(identity, completion);
|
|
230
|
+
const finish = () => {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
if (owners.get(identity) === completion) owners.delete(identity);
|
|
233
|
+
completed();
|
|
234
|
+
};
|
|
235
|
+
const store = async value => {
|
|
236
|
+
try {
|
|
237
|
+
throwIfAborted(signal);
|
|
238
|
+
if (value === null) { await release(target, key, lease); return; }
|
|
239
|
+
const bytes = bufferValue(value);
|
|
240
|
+
if (bytes.length > MAX_CACHE_VALUE_BYTES) {
|
|
241
|
+
await release(target, key, lease);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// A failed or superseded commit never replays the producer (which may
|
|
245
|
+
// perform an explicitly cached POST). Return the successful origin value.
|
|
246
|
+
try {
|
|
247
|
+
const metadata = options.resolveMetadata?.();
|
|
248
|
+
if (metadata?.externalCache) { await release(target, key, lease); return; }
|
|
249
|
+
const result = await rpc(target, { op: 'commit', key, lease, value: bytes.toString('base64'), revalidate: options.revalidate,
|
|
250
|
+
...(metadata ? { tags: validateTags(metadata.tags), paths: metadata.paths || [], revalidate: validateRevalidate(metadata.revalidate, { allowZero: true }), expire: metadata.expire } : {}) }, signal);
|
|
251
|
+
if (!result.stored) await release(target, key, lease);
|
|
252
|
+
} catch {
|
|
253
|
+
await release(target, key, lease);
|
|
254
|
+
throwIfAborted(signal);
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
await release(target, key, lease);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
try {
|
|
262
|
+
throwIfAborted(signal);
|
|
263
|
+
const result = await untilAborted(Promise.resolve().then(() => producer({ background: Boolean(options.background), signal })), signal);
|
|
264
|
+
if (result?.[DEFERRED_VALUE]) {
|
|
265
|
+
const fill = (async () => {
|
|
266
|
+
try { await store(await untilAborted(result.cacheValue, signal)); }
|
|
267
|
+
catch (error) {
|
|
268
|
+
await release(target, key, lease);
|
|
269
|
+
throw error;
|
|
270
|
+
} finally {
|
|
271
|
+
if (options.background) { try { await untilAborted(result.dispose?.(), signal); } catch {} }
|
|
272
|
+
finish();
|
|
273
|
+
}
|
|
274
|
+
})();
|
|
275
|
+
trackBackground(options.state, fill);
|
|
276
|
+
return result.value;
|
|
277
|
+
}
|
|
278
|
+
const bytes = bufferValue(result);
|
|
279
|
+
await store(bytes);
|
|
280
|
+
finish();
|
|
281
|
+
return bytes;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
await release(target, key, lease);
|
|
284
|
+
finish();
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function uncachedValue(producer, state, signal) {
|
|
290
|
+
const result = await producer({ background: false, signal, cache: false });
|
|
291
|
+
if (result?.[DEFERRED_VALUE]) {
|
|
292
|
+
// A fetch may already have started its bounded capture before discovering
|
|
293
|
+
// there is no cache service. Observe it without retaining the live response.
|
|
294
|
+
trackBackground(state, untilAborted(result.cacheValue, AbortSignal.timeout(WAIT_TIMEOUT_MS)));
|
|
295
|
+
return result.value;
|
|
296
|
+
}
|
|
297
|
+
return bufferValue(result);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function cachedValue(key, producer, options = {}) {
|
|
301
|
+
if (typeof key !== 'string' || !/^[a-f\d]{64}$/.test(key)) throw new TypeError('Cache keys must be a SHA-256 hex digest');
|
|
302
|
+
const context = options.context || optionalRequestContext();
|
|
303
|
+
const state = cacheState(context);
|
|
304
|
+
const tags = validateTags(options.tags);
|
|
305
|
+
const paths = options.paths || getCachePaths(context);
|
|
306
|
+
const revalidate = validateRevalidate(options.revalidate);
|
|
307
|
+
trackStaticDependency({ tags, paths, revalidate }, context);
|
|
308
|
+
const signal = options.signal;
|
|
309
|
+
if (state) await state.invalidations;
|
|
310
|
+
throwIfAborted(signal);
|
|
311
|
+
if (options.incremental && (context?.cacheHandler || context?.manifest?.config?.cacheHandler)) {
|
|
312
|
+
return require('./incremental-cache.cjs').cachedIncrementalValue(key, producer, { ...options, context, tags, paths, revalidate });
|
|
313
|
+
}
|
|
314
|
+
const target = endpoint();
|
|
315
|
+
// Builds and standalone runtime use have no native cache server. Compute
|
|
316
|
+
// without claiming persistence; production workers always receive its URL.
|
|
317
|
+
if (!target) return uncachedValue(producer, state, signal);
|
|
318
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
319
|
+
while (true) {
|
|
320
|
+
throwIfAborted(signal);
|
|
321
|
+
if (Date.now() >= deadline) throw new Error('Timed out waiting for a PRNext data cache lease');
|
|
322
|
+
let entry;
|
|
323
|
+
try { entry = await rpc(target, { op: 'read', key, tags, paths, revalidate, forceFresh: Boolean(options.forceFresh || context?.staticState), ...(options.versioned ? { versioned: true } : {}) }, signal); }
|
|
324
|
+
catch (error) {
|
|
325
|
+
throwIfAborted(signal);
|
|
326
|
+
// A temporarily unavailable cache must not make an origin unavailable.
|
|
327
|
+
return uncachedValue(producer, state, signal);
|
|
328
|
+
}
|
|
329
|
+
if (options.versioned && (typeof entry.key !== 'string' || !/^[a-f\d]{64}$/.test(entry.key) || !Number.isSafeInteger(entry.generation) || entry.generation < 0)) throw new Error('Invalid PRNext versioned cache response');
|
|
330
|
+
const entryKey = options.versioned ? entry.key : key;
|
|
331
|
+
const produce = options.versioned ? info => producer({ ...info, generation: entry.generation }) : producer;
|
|
332
|
+
if (entry.state === 'fresh') return decodeValue(entry.value);
|
|
333
|
+
if (entry.state === 'stale') {
|
|
334
|
+
const bytes = decodeValue(entry.value);
|
|
335
|
+
if (entry.lease) {
|
|
336
|
+
if (owners.size >= MAX_PENDING_OWNERS) trackBackground(state, release(target, entryKey, entry.lease));
|
|
337
|
+
else trackBackground(state, compute(target, entryKey, entry.lease, produce, { revalidate, signal, state, background: true, deadline, resolveMetadata: options.resolveMetadata }));
|
|
338
|
+
}
|
|
339
|
+
return bytes;
|
|
340
|
+
}
|
|
341
|
+
if (entry.state === 'miss' && entry.lease) {
|
|
342
|
+
if (owners.size >= MAX_PENDING_OWNERS) { await release(target, entryKey, entry.lease); return uncachedValue(producer, state, signal); }
|
|
343
|
+
return compute(target, entryKey, entry.lease, produce, { revalidate, signal, state, deadline, resolveMetadata: options.resolveMetadata });
|
|
344
|
+
}
|
|
345
|
+
if (entry.state !== 'pending') throw new Error('Invalid PRNext data cache lease response');
|
|
346
|
+
const waitMs = Math.min(Math.max(Number(entry.retryAfterMs) || 20, 5), 100, deadline - Date.now());
|
|
347
|
+
const owner = owners.get(`${target.url}\n${entryKey}`);
|
|
348
|
+
if (owner) {
|
|
349
|
+
// Wait for local work, then read again so tags/path attachments and
|
|
350
|
+
// invalidation revisions are checked. Never share an uncacheable Response.
|
|
351
|
+
await Promise.race([owner.catch(() => {}), delay(waitMs, undefined, { signal })]);
|
|
352
|
+
} else await delay(waitMs, undefined, { signal });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function queueInvalidation(context, operation) {
|
|
357
|
+
const state = cacheState(context);
|
|
358
|
+
const target = endpoint();
|
|
359
|
+
const custom = Object.keys(context.cacheHandlers || context.manifest?.config?.cacheHandlers || {}).length > 0;
|
|
360
|
+
const incremental = context.cacheHandler || context.manifest?.config?.cacheHandler;
|
|
361
|
+
if (!target && !custom && !incremental) throw new Error('Cache invalidation requires a running PRNext server');
|
|
362
|
+
state.refresh = true;
|
|
363
|
+
state.invalidations = state.invalidations.then(async () => {
|
|
364
|
+
if (target) await rpc(target, { op: 'invalidate', ...operation });
|
|
365
|
+
if (custom) await require('./cache-handlers.cjs').invalidateHandlers(context, operation);
|
|
366
|
+
if (incremental) await require('./incremental-cache.cjs').invalidate(context, operation);
|
|
367
|
+
});
|
|
368
|
+
// Public invalidation functions return void. Retain errors for the next read
|
|
369
|
+
// or runtime flush without producing an unhandled rejection in the meantime.
|
|
370
|
+
void state.invalidations.catch(() => {});
|
|
371
|
+
}
|
|
372
|
+
async function flushCacheWork(context = optionalRequestContext()) {
|
|
373
|
+
const state = cacheState(context);
|
|
374
|
+
if (!state) return;
|
|
375
|
+
await state.invalidations;
|
|
376
|
+
while (state.pending.size) await Promise.all([...state.pending]);
|
|
377
|
+
await state.invalidations;
|
|
378
|
+
}
|
|
379
|
+
async function flushCacheInvalidations(context = optionalRequestContext()) {
|
|
380
|
+
const state = cacheState(context);
|
|
381
|
+
if (state) await state.invalidations;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = {
|
|
385
|
+
cacheGeneration, acquireExternalCacheLease, DEFERRED_VALUE,
|
|
386
|
+
MAX_CACHE_VALUE_BYTES, nativeFetch, cachedValue, deferredValue, createCacheState, cacheState,
|
|
387
|
+
flushCacheWork, flushCacheInvalidations, runCacheScope, inCacheScope, currentCacheScope, optionalRequestContext,
|
|
388
|
+
getCachePaths, normalizeCachePath, validateTags, validateRevalidate, queueInvalidation,
|
|
389
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {RouterContext} from './router-context.shared-runtime.cjs';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const React = require('react');
|
|
3
|
+
const DocumentContext = React.createContext(null);
|
|
4
|
+
function context(value) {
|
|
5
|
+
if (!value) throw new Error('Html, Head, Main and NextScript from next/document must only be rendered in pages/_document');
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
function safeJSON(value) {
|
|
9
|
+
return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, character => ({ '<': '\\u003c', '>': '\\u003e', '&': '\\u0026', '\u2028': '\\u2028', '\u2029': '\\u2029' })[character]);
|
|
10
|
+
}
|
|
11
|
+
function Html(props) {
|
|
12
|
+
const state = context(React.useContext(DocumentContext));
|
|
13
|
+
state.rendered.add('Html');
|
|
14
|
+
return React.createElement('html', { lang: state.__NEXT_DATA__?.locale, ...props });
|
|
15
|
+
}
|
|
16
|
+
class Head extends React.Component {
|
|
17
|
+
static contextType = DocumentContext;
|
|
18
|
+
getCssLinks() {
|
|
19
|
+
const state = context(this.context);
|
|
20
|
+
return state.css.map(href => React.createElement('link', { key: href, rel: 'stylesheet', href,
|
|
21
|
+
nonce: this.props.nonce || state.nonce, crossOrigin: this.props.crossOrigin || state.crossOrigin }));
|
|
22
|
+
}
|
|
23
|
+
getScripts() {
|
|
24
|
+
const state = context(this.context);
|
|
25
|
+
return state.client ? [React.createElement('script', { key: state.client, type: 'module', src: state.client,
|
|
26
|
+
nonce: this.props.nonce || state.nonce, crossOrigin: this.props.crossOrigin || state.crossOrigin })] : [];
|
|
27
|
+
}
|
|
28
|
+
getFontLinks() {
|
|
29
|
+
const state = context(this.context);
|
|
30
|
+
return (state.fonts || []).map(font => React.createElement('link', { key: font.href, rel: 'preload', as: 'font',
|
|
31
|
+
href: font.href, type: font.type, crossOrigin: 'anonymous', nonce: this.props.nonce || state.nonce }));
|
|
32
|
+
}
|
|
33
|
+
render() {
|
|
34
|
+
const state = context(this.context);
|
|
35
|
+
state.rendered.add('Head');
|
|
36
|
+
const { children, nonce, crossOrigin, ...props } = this.props;
|
|
37
|
+
state.headNonce = nonce || state.nonce;
|
|
38
|
+
state.headCrossOrigin = crossOrigin || state.crossOrigin;
|
|
39
|
+
return React.createElement('head', props, state.head, children, this.getFontLinks(), this.getCssLinks(),
|
|
40
|
+
React.createElement('prnext-document-script-target'), this.getScripts(), state.styles);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function Main() {
|
|
44
|
+
const state = context(React.useContext(DocumentContext));
|
|
45
|
+
state.rendered.add('Main');
|
|
46
|
+
return React.createElement('prnext-document-body-target');
|
|
47
|
+
}
|
|
48
|
+
class NextScript extends React.Component {
|
|
49
|
+
static contextType = DocumentContext;
|
|
50
|
+
static getInlineScriptSource(state) { return safeJSON(state.__NEXT_DATA__); }
|
|
51
|
+
render() {
|
|
52
|
+
const state = context(this.context);
|
|
53
|
+
state.rendered.add('NextScript');
|
|
54
|
+
const nonce = this.props.nonce || state.nonce;
|
|
55
|
+
const crossOrigin = this.props.crossOrigin || state.crossOrigin;
|
|
56
|
+
const payload = `window.__PRNEXT_DATA__=JSON.parse(${safeJSON(JSON.stringify(state.payload))});`;
|
|
57
|
+
return React.createElement('script', { nonce, crossOrigin, dangerouslySetInnerHTML: { __html: payload } });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
class Document extends React.Component {
|
|
61
|
+
static getInitialProps(ctx) { return ctx.defaultGetInitialProps(ctx); }
|
|
62
|
+
render() {
|
|
63
|
+
return React.createElement(Html, null, React.createElement(Head, { nonce: this.props.nonce }),
|
|
64
|
+
React.createElement('body', null, React.createElement(Main), React.createElement(NextScript, { nonce: this.props.nonce })));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
module.exports = Document;
|
|
68
|
+
module.exports.default = Document;
|
|
69
|
+
module.exports.Html = Html;
|
|
70
|
+
module.exports.Head = Head;
|
|
71
|
+
module.exports.Main = Main;
|
|
72
|
+
module.exports.NextScript = NextScript;
|
|
73
|
+
module.exports.DocumentContext = DocumentContext;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Component, type ComponentType, type ReactElement, type ReactNode, type HTMLAttributes, type HtmlHTMLAttributes } from 'react';
|
|
2
|
+
import type { AppProps, NextPageContext } from './index.d.ts';
|
|
3
|
+
|
|
4
|
+
export type Enhancer<C> = (Component: C) => C;
|
|
5
|
+
export type ComponentsEnhancer = {
|
|
6
|
+
enhanceApp?: Enhancer<ComponentType<AppProps<any>>>;
|
|
7
|
+
enhanceComponent?: Enhancer<ComponentType<any>>;
|
|
8
|
+
} | Enhancer<ComponentType<any>>;
|
|
9
|
+
export interface RenderPageResult { html: string; head?: Array<ReactElement | null> }
|
|
10
|
+
export interface DocumentInitialProps extends RenderPageResult {
|
|
11
|
+
styles?: ReactElement[] | Iterable<ReactNode> | ReactElement;
|
|
12
|
+
}
|
|
13
|
+
export type RenderPage = (options?: ComponentsEnhancer) => DocumentInitialProps | Promise<DocumentInitialProps>;
|
|
14
|
+
export interface DocumentContext extends NextPageContext {
|
|
15
|
+
renderPage: RenderPage;
|
|
16
|
+
defaultGetInitialProps(context: DocumentContext, options?: { nonce?: string }): Promise<DocumentInitialProps>;
|
|
17
|
+
}
|
|
18
|
+
export interface DocumentProps extends DocumentInitialProps {
|
|
19
|
+
__NEXT_DATA__: { props: { pageProps: any }; page: string; query: NextPageContext['query']; buildId?: string; assetPrefix?: string; isFallback?: boolean; gsp?: boolean; gssp?: boolean };
|
|
20
|
+
assetPrefix: string;
|
|
21
|
+
dangerousAsPath: string;
|
|
22
|
+
isDevelopment: boolean;
|
|
23
|
+
nonce?: string;
|
|
24
|
+
crossOrigin?: 'anonymous' | 'use-credentials' | '';
|
|
25
|
+
}
|
|
26
|
+
export interface OriginProps { nonce?: string; crossOrigin?: 'anonymous' | 'use-credentials' | ''; children?: ReactNode }
|
|
27
|
+
export function Html(props: HtmlHTMLAttributes<HTMLHtmlElement>): ReactElement;
|
|
28
|
+
export class Head extends Component<OriginProps & HTMLAttributes<HTMLHeadElement>> {
|
|
29
|
+
getCssLinks(): ReactElement[];
|
|
30
|
+
getScripts(): ReactElement[];
|
|
31
|
+
render(): ReactElement;
|
|
32
|
+
}
|
|
33
|
+
export function Main(): ReactElement;
|
|
34
|
+
export class NextScript extends Component<OriginProps> {
|
|
35
|
+
static getInlineScriptSource(context: Readonly<{ __NEXT_DATA__: DocumentProps['__NEXT_DATA__'] }>): string;
|
|
36
|
+
render(): ReactElement;
|
|
37
|
+
}
|
|
38
|
+
export default class Document<Props = {}> extends Component<DocumentProps & Props> {
|
|
39
|
+
static getInitialProps(context: DocumentContext): Promise<DocumentInitialProps>;
|
|
40
|
+
render(): ReactElement;
|
|
41
|
+
}
|