@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,81 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { access, stat } from 'node:fs/promises';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
export const packageRoot = fileURLToPath(new URL('../', import.meta.url));
|
|
9
|
+
export const packageManifest = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
10
|
+
export const platforms = JSON.parse(readFileSync(new URL('./platforms.json', import.meta.url), 'utf8'));
|
|
11
|
+
|
|
12
|
+
// A dependency installed in node_modules must never mistake its consumer for
|
|
13
|
+
// the framework checkout, even if that consumer also has a Cargo workspace.
|
|
14
|
+
export function sourceCheckout(root = packageRoot) {
|
|
15
|
+
try {
|
|
16
|
+
const repository = path.resolve(root, '../..');
|
|
17
|
+
const manifest = JSON.parse(readFileSync(path.join(repository, 'package.json'), 'utf8'));
|
|
18
|
+
if (manifest.name !== 'prnext-monorepo' || manifest.private !== true) return null;
|
|
19
|
+
if (realpathSync(root) !== realpathSync(path.join(repository, 'packages/prnext'))) return null;
|
|
20
|
+
readFileSync(path.join(repository, 'crates/prnext/Cargo.toml'));
|
|
21
|
+
return repository;
|
|
22
|
+
} catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function nativePlatform({ platform = process.platform, arch = process.arch, libc } = {}) {
|
|
26
|
+
if (platform === 'linux' && libc === undefined) {
|
|
27
|
+
libc = process.report?.getReport().header.glibcVersionRuntime ? 'glibc' : 'musl';
|
|
28
|
+
}
|
|
29
|
+
const result = platforms.find(item => item.os === platform && item.cpu === arch && (!item.libc || item.libc === libc));
|
|
30
|
+
if (!result) throw new Error(`PRNext has no prebuilt server for ${platform}/${arch}${libc ? `/${libc}` : ''}. This alpha provides macOS arm64/x64 and Linux glibc arm64/x64. Use a supported system (WSL on Windows), or set PRNEXT_BINARY to your own compatible build.`);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function executableFile(file) {
|
|
35
|
+
const info = await stat(file);
|
|
36
|
+
if (!info.isFile()) throw new Error(`Native executable is not a file: ${file}`);
|
|
37
|
+
await access(file, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
|
|
38
|
+
return file;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let compiling;
|
|
42
|
+
export async function resolveNativeBinary({ env = process.env, root = packageRoot, allowBuild = true, platform } = {}) {
|
|
43
|
+
if (env.PRNEXT_BINARY) {
|
|
44
|
+
const file = path.resolve(env.PRNEXT_BINARY);
|
|
45
|
+
try { return await executableFile(file); }
|
|
46
|
+
catch (cause) { throw new Error(`PRNEXT_BINARY is not an executable file: ${file}`, { cause }); }
|
|
47
|
+
}
|
|
48
|
+
const checkout = sourceCheckout(root);
|
|
49
|
+
if (checkout) {
|
|
50
|
+
const binary = path.join(checkout, 'target/release', process.platform === 'win32' ? 'prnext.exe' : 'prnext');
|
|
51
|
+
try { return await executableFile(binary); }
|
|
52
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
53
|
+
if (!allowBuild) throw new Error('Native server missing from this checkout. Run npm run build:native.');
|
|
54
|
+
compiling ||= (async () => {
|
|
55
|
+
const { cargo } = await import(pathToFileURL(path.join(checkout, 'scripts/cargo.mjs')).href);
|
|
56
|
+
console.error('Compiling native PRNext server for this source checkout…');
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
const child = cargo(['build', '--release', '--locked']);
|
|
59
|
+
child.once('error', reject);
|
|
60
|
+
child.once('exit', (code, signal) => code === 0 ? resolve() : reject(new Error(`Native build failed (${signal || code}).`)));
|
|
61
|
+
});
|
|
62
|
+
})().finally(() => { compiling = undefined; });
|
|
63
|
+
await compiling;
|
|
64
|
+
return executableFile(binary);
|
|
65
|
+
}
|
|
66
|
+
const target = nativePlatform(platform);
|
|
67
|
+
const manifest = root === packageRoot ? packageManifest : JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
68
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
69
|
+
let nativeManifestFile;
|
|
70
|
+
try { nativeManifestFile = require.resolve(`${target.package}/package.json`); }
|
|
71
|
+
catch (cause) {
|
|
72
|
+
throw new Error(`Missing PRNext native package ${target.package}@${manifest.version}. Reinstall with optional dependencies enabled (npm install --include=optional), or install ${target.package}@${manifest.version} explicitly. Do not copy node_modules between operating systems.`, { cause });
|
|
73
|
+
}
|
|
74
|
+
const native = JSON.parse(readFileSync(nativeManifestFile, 'utf8'));
|
|
75
|
+
if (native.name !== target.package || native.version !== manifest.version) {
|
|
76
|
+
throw new Error(`PRNext native version mismatch: expected ${target.package}@${manifest.version}, found ${native.name}@${native.version}. Reinstall matching versions.`);
|
|
77
|
+
}
|
|
78
|
+
const binary = path.join(path.dirname(nativeManifestFile), 'bin', 'prnext');
|
|
79
|
+
try { return await executableFile(binary); }
|
|
80
|
+
catch (cause) { throw new Error(`PRNext native executable is missing or not executable in ${target.package}. Reinstall the package.`, { cause }); }
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thomas.f/prnext",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Experimental Rust-powered runtime for Next.js apps, React and npm",
|
|
5
|
+
"homepage": "https://prnext.dev",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Lorkydey/PRNext.git",
|
|
9
|
+
"directory": "packages/prnext"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Lorkydey/PRNext/issues",
|
|
13
|
+
"email": "contact@heythomas.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Thomas",
|
|
17
|
+
"url": "https://github.com/Lorkydey"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["prnext", "nextjs", "react", "rust", "framework", "ssr", "server-components"],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"tag": "alpha",
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
26
|
+
"files": ["cli.mjs", "index.mjs", "build/", "runtime/", "compat/", "native/", "release/"],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepublishOnly": "node release/verify.mjs"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"prnext": "./cli.mjs",
|
|
32
|
+
"prn": "./cli.mjs"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
"./package.json": "./package.json",
|
|
36
|
+
"./og": {
|
|
37
|
+
"types": "./compat/og.d.cts",
|
|
38
|
+
"default": "./compat/og.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./font/local": {
|
|
41
|
+
"types": "./compat/font-local.d.cts",
|
|
42
|
+
"default": "./compat/font-local.cjs"
|
|
43
|
+
},
|
|
44
|
+
"./font/google": {
|
|
45
|
+
"types": "./compat/font-google.d.cts",
|
|
46
|
+
"default": "./compat/font-google.cjs"
|
|
47
|
+
},
|
|
48
|
+
".": {
|
|
49
|
+
"types": "./compat/index.d.ts",
|
|
50
|
+
"default": "./index.mjs"
|
|
51
|
+
},
|
|
52
|
+
"./link": "./compat/link.cjs",
|
|
53
|
+
"./head": "./compat/head.cjs",
|
|
54
|
+
"./script": {
|
|
55
|
+
"types": "./compat/script.d.cts",
|
|
56
|
+
"default": "./compat/script.cjs"
|
|
57
|
+
},
|
|
58
|
+
"./app": {
|
|
59
|
+
"types": "./compat/app.d.cts",
|
|
60
|
+
"default": "./compat/app.cjs"
|
|
61
|
+
},
|
|
62
|
+
"./document": {
|
|
63
|
+
"types": "./compat/document.d.cts",
|
|
64
|
+
"default": "./compat/document.cjs"
|
|
65
|
+
},
|
|
66
|
+
"./error": {
|
|
67
|
+
"types": "./compat/error.d.cts",
|
|
68
|
+
"default": "./compat/error.cjs"
|
|
69
|
+
},
|
|
70
|
+
"./dynamic": {
|
|
71
|
+
"types": "./compat/dynamic.d.cts",
|
|
72
|
+
"default": "./compat/dynamic.cjs"
|
|
73
|
+
},
|
|
74
|
+
"./image": {
|
|
75
|
+
"types": "./compat/image.d.cts",
|
|
76
|
+
"default": "./compat/image.cjs"
|
|
77
|
+
},
|
|
78
|
+
"./router": "./compat/router.cjs",
|
|
79
|
+
"./compat/router": {
|
|
80
|
+
"types": "./compat/compat/router.d.cts",
|
|
81
|
+
"default": "./compat/compat-router.cjs"
|
|
82
|
+
},
|
|
83
|
+
"./navigation": "./compat/navigation.cjs",
|
|
84
|
+
"./headers": "./compat/headers.cjs",
|
|
85
|
+
"./server": "./compat/server.cjs",
|
|
86
|
+
"./cache": "./compat/cache.cjs",
|
|
87
|
+
"./constants": "./compat/constants.cjs"
|
|
88
|
+
},
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"@babel/core": "7.29.7",
|
|
91
|
+
"@babel/generator": "^7.29.0",
|
|
92
|
+
"@babel/parser": "^7.29.0",
|
|
93
|
+
"@babel/traverse": "^7.29.0",
|
|
94
|
+
"@babel/types": "^7.29.0",
|
|
95
|
+
"@edge-runtime/vm": "5.0.0",
|
|
96
|
+
"@vercel/nft": "1.11.0",
|
|
97
|
+
"@vercel/og": "1.0.3",
|
|
98
|
+
"dotenv": "16.6.1",
|
|
99
|
+
"dotenv-expand": "11.0.7",
|
|
100
|
+
"esbuild": "^0.28.2",
|
|
101
|
+
"fontkit": "2.0.4",
|
|
102
|
+
"loader-runner": "4.3.2",
|
|
103
|
+
"path-to-regexp": "6.3.0",
|
|
104
|
+
"picomatch": "4.0.7",
|
|
105
|
+
"postcss": "8.5.28",
|
|
106
|
+
"postcss-flexbugs-fixes": "5.0.2",
|
|
107
|
+
"postcss-preset-env": "11.5.3",
|
|
108
|
+
"postcss-value-parser": "4.2.0",
|
|
109
|
+
"react-refresh": "0.19.0",
|
|
110
|
+
"schema-utils": "4.3.3",
|
|
111
|
+
"webpack": "5.111.1"
|
|
112
|
+
},
|
|
113
|
+
"peerDependencies": {
|
|
114
|
+
"react": "19.3.0",
|
|
115
|
+
"react-dom": "19.3.0",
|
|
116
|
+
"react-server-dom-webpack": "19.3.0"
|
|
117
|
+
},
|
|
118
|
+
"optionalDependencies": {
|
|
119
|
+
"prnext-darwin-arm64": "0.1.0-alpha.1",
|
|
120
|
+
"prnext-darwin-x64": "0.1.0-alpha.1",
|
|
121
|
+
"prnext-linux-arm64-gnu": "0.1.0-alpha.1",
|
|
122
|
+
"prnext-linux-x64-gnu": "0.1.0-alpha.1"
|
|
123
|
+
},
|
|
124
|
+
"engines": {
|
|
125
|
+
"node": ">=22"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile, access } from 'node:fs/promises';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { packageRoot, packageManifest, platforms, sourceCheckout } from '../native/resolve.mjs';
|
|
5
|
+
|
|
6
|
+
export async function verifyPackage() {
|
|
7
|
+
const pkg = packageManifest;
|
|
8
|
+
if (pkg.private === true) throw new Error(`${pkg.name} is marked private.`);
|
|
9
|
+
if (!/^\d+\.\d+\.\d+-alpha\.\d+$/.test(pkg.version)) throw new Error('This release workflow requires an explicit x.y.z-alpha.N version.');
|
|
10
|
+
if (pkg.publishConfig?.tag !== 'alpha' || pkg.publishConfig?.access !== 'public') throw new Error('Alpha releases require publishConfig.tag=alpha and access=public.');
|
|
11
|
+
if (pkg.bin?.prn !== './cli.mjs' || pkg.bin?.prnext !== './cli.mjs') throw new Error('Both prn and prnext must point to cli.mjs.');
|
|
12
|
+
const expected = Object.fromEntries(platforms.map(item => [item.package, pkg.version]));
|
|
13
|
+
if (JSON.stringify(Object.entries(pkg.optionalDependencies || {}).sort()) !== JSON.stringify(Object.entries(expected).sort())) throw new Error('All native optional dependencies must match the framework version and platform manifest exactly.');
|
|
14
|
+
for (const [name, value] of Object.entries({ ...pkg.dependencies, ...pkg.peerDependencies })) {
|
|
15
|
+
if (/^(?:file:|link:|portal:|workspace:|\.|\/)/.test(value)) throw new Error(`Local dependency cannot be published: ${name}=${value}`);
|
|
16
|
+
}
|
|
17
|
+
const rsc = pkg.peerDependencies['react-server-dom-webpack'];
|
|
18
|
+
if (pkg.peerDependencies.react !== rsc || pkg.peerDependencies['react-dom'] !== rsc) throw new Error('React, React DOM and the RSC runtime must have the same exact version.');
|
|
19
|
+
async function exported(value) {
|
|
20
|
+
if (typeof value === 'string') { await access(path.join(packageRoot, value)); return; }
|
|
21
|
+
for (const target of Object.values(value)) await exported(target);
|
|
22
|
+
}
|
|
23
|
+
await exported(pkg.exports);
|
|
24
|
+
await access(path.join(packageRoot, 'runtime/profiles.json'));
|
|
25
|
+
const source = sourceCheckout();
|
|
26
|
+
if (source) {
|
|
27
|
+
const cargo = await readFile(path.join(source, 'crates/prnext/Cargo.toml'), 'utf8');
|
|
28
|
+
if (!cargo.includes(`version = "${pkg.version}"`)) throw new Error('Cargo and npm versions differ. Update crates/prnext/Cargo.toml and Cargo.lock before building release binaries.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function verifyPublishedNatives({ registry = 'https://registry.npmjs.org/', fetcher = fetch } = {}) {
|
|
33
|
+
// A successful npm install may silently omit a failed optional dependency.
|
|
34
|
+
// Do not release the JS wrapper until every advertised native is available.
|
|
35
|
+
const problems = [];
|
|
36
|
+
for (const target of platforms) {
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(`${target.package}/${packageManifest.version}`, registry);
|
|
39
|
+
const response = await fetcher(url, { signal: AbortSignal.timeout(15_000) });
|
|
40
|
+
if (!response.ok) throw new Error(`registry returned HTTP ${response.status}`);
|
|
41
|
+
const pkg = await response.json();
|
|
42
|
+
if (pkg.name !== target.package || pkg.version !== packageManifest.version || !pkg.dist?.integrity || !pkg.dist?.tarball ||
|
|
43
|
+
JSON.stringify(pkg.os) !== JSON.stringify([target.os]) || JSON.stringify(pkg.cpu) !== JSON.stringify([target.cpu]) ||
|
|
44
|
+
(target.libc && JSON.stringify(pkg.libc) !== JSON.stringify([target.libc]))) throw new Error('published metadata does not match the release');
|
|
45
|
+
} catch (error) { problems.push(`${target.package}@${packageManifest.version}: ${error.message}`); }
|
|
46
|
+
}
|
|
47
|
+
if (problems.length) throw new Error(`Publish the native packages first, then publish ${packageManifest.name}:\n${problems.join('\n')}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
51
|
+
try {
|
|
52
|
+
await verifyPackage();
|
|
53
|
+
if (!process.argv.includes('--local')) {
|
|
54
|
+
if (process.env.npm_config_tag && process.env.npm_config_tag !== 'alpha') throw new Error('Publish this prerelease with --tag alpha.');
|
|
55
|
+
await verifyPublishedNatives();
|
|
56
|
+
}
|
|
57
|
+
console.log('PRNext release checks passed.');
|
|
58
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
59
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let referenceFactory;
|
|
2
|
+
let transport;
|
|
3
|
+
|
|
4
|
+
// App bootstrap installs the official Flight client after its module loader is
|
|
5
|
+
// ready. Action proxy modules load through that same client graph afterwards.
|
|
6
|
+
export function configureServerActions({ createServerReference, callServer }) {
|
|
7
|
+
referenceFactory = createServerReference;
|
|
8
|
+
transport = callServer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function callServer(id, args) {
|
|
12
|
+
if (!transport) return Promise.reject(new Error('The PRNext Server Action transport is not initialized'));
|
|
13
|
+
return transport(id, args);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createServerReference(id) {
|
|
17
|
+
if (!referenceFactory) throw new Error('Server Action references must load after the PRNext Flight client');
|
|
18
|
+
return referenceFactory(id, callServer);
|
|
19
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
2
|
+
import { serialize, deserialize } from 'node:v8';
|
|
3
|
+
import { currentRequest } from '../compat/headers.cjs';
|
|
4
|
+
|
|
5
|
+
const LIMIT = 16 * 1024 * 1024;
|
|
6
|
+
function key() {
|
|
7
|
+
const encoded = currentRequest().actionKey;
|
|
8
|
+
if (typeof encoded !== 'string') throw new Error('Server Action encryption key is unavailable in this request');
|
|
9
|
+
const value = Buffer.from(encoded, 'base64');
|
|
10
|
+
if (![16,24,32].includes(value.length)) throw new Error('Invalid Server Action encryption key');
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The graph envelope preserves cycles and rich capture values without confusing
|
|
15
|
+
// user object properties with transport tags. Functions and class instances are
|
|
16
|
+
// rejected instead of being silently reduced to incomplete plain objects.
|
|
17
|
+
async function encodeCaptures(value) {
|
|
18
|
+
const nodes = [];
|
|
19
|
+
const seen = new Map();
|
|
20
|
+
async function encode(value) {
|
|
21
|
+
if (typeof value === 'symbol') {
|
|
22
|
+
const name = Symbol.keyFor(value);
|
|
23
|
+
if (name === undefined) throw new Error('Server Action closures cannot capture a non-global Symbol');
|
|
24
|
+
return { symbol: name };
|
|
25
|
+
}
|
|
26
|
+
const serverReference=typeof value === 'function' && value.$$typeof===Symbol.for('react.server.reference') && typeof value.$$id==='string';
|
|
27
|
+
if (typeof value === 'function' && !serverReference) throw new Error('Server Action closures cannot capture functions; move helpers to module scope');
|
|
28
|
+
if (value === null || (typeof value !== 'object' && !serverReference)) return { value };
|
|
29
|
+
if (seen.has(value)) return { reference: seen.get(value) };
|
|
30
|
+
const index = nodes.length;
|
|
31
|
+
seen.set(value, index);
|
|
32
|
+
const node = {};
|
|
33
|
+
nodes.push(node);
|
|
34
|
+
if (value instanceof Promise) {
|
|
35
|
+
node.type='promise';
|
|
36
|
+
let result;
|
|
37
|
+
try { result=await value; } catch(error) { node.rejected=true;result=error; }
|
|
38
|
+
node.result=await encode(result);
|
|
39
|
+
}
|
|
40
|
+
else if (serverReference) { node.type='action'; node.id=value.$$id; node.bound=await encode(await value.$$bound); }
|
|
41
|
+
else if (Array.isArray(value)) { node.type='array'; node.items=await Promise.all(Array.from(value,encode)); }
|
|
42
|
+
else if (value instanceof Map) { node.type='map'; node.items=await Promise.all([...value].map(async ([key,value])=>[await encode(key),await encode(value)])); }
|
|
43
|
+
else if (value instanceof Set) { node.type='set'; node.items=await Promise.all([...value].map(encode)); }
|
|
44
|
+
else if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
45
|
+
if(value.size>LIMIT) throw new Error('Server Action closure exceeds the 16 MiB limit');
|
|
46
|
+
node.type='blob'; node.bytes=Buffer.from(await value.arrayBuffer()); node.mime=value.type;
|
|
47
|
+
if (typeof File !== 'undefined' && value instanceof File) { node.name=value.name; node.lastModified=value.lastModified; }
|
|
48
|
+
} else if (typeof FormData !== 'undefined' && value instanceof FormData) { node.type='form'; node.items=await Promise.all([...value].map(async ([key,value])=>[key,await encode(value)])); }
|
|
49
|
+
else if (value instanceof Date || value instanceof Error || value instanceof RegExp || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) { node.type='value'; node.value=value; }
|
|
50
|
+
else {
|
|
51
|
+
const prototype = Object.getPrototypeOf(value);
|
|
52
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error('Server Action closures can only capture plain objects and supported serializable values');
|
|
53
|
+
node.type='object'; node.nullPrototype=prototype===null;
|
|
54
|
+
node.items=await Promise.all(Object.keys(value).map(async name=>[name,await encode(value[name])]));
|
|
55
|
+
if (Object.getOwnPropertySymbols(value).length) throw new Error('Server Action closures cannot capture objects with symbol keys');
|
|
56
|
+
}
|
|
57
|
+
return { reference: index };
|
|
58
|
+
}
|
|
59
|
+
return { root: await encode(value), nodes };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function decodeCaptures(graph) {
|
|
63
|
+
const actionTargets=[];
|
|
64
|
+
const promises=[];
|
|
65
|
+
const values = graph.nodes.map((node,index) => {
|
|
66
|
+
if (node.type==='action') return function(...args){return actionTargets[index](...args);};
|
|
67
|
+
if (node.type==='promise') {
|
|
68
|
+
const promise=new Promise((resolve,reject)=>{promises[index]={resolve,reject};});
|
|
69
|
+
promise.catch(()=>{});
|
|
70
|
+
return promise;
|
|
71
|
+
}
|
|
72
|
+
if (node.type==='array') return [];
|
|
73
|
+
if (node.type==='map') return new Map();
|
|
74
|
+
if (node.type==='set') return new Set();
|
|
75
|
+
if (node.type==='form') return new FormData();
|
|
76
|
+
if (node.type==='blob') return node.name === undefined ? new Blob([node.bytes],{type:node.mime}) : new File([node.bytes],node.name,{type:node.mime,lastModified:node.lastModified});
|
|
77
|
+
if (node.type==='value') return node.value;
|
|
78
|
+
return node.nullPrototype ? Object.create(null) : {};
|
|
79
|
+
});
|
|
80
|
+
const decode = value => Object.hasOwn(value,'reference') ? values[value.reference] : Object.hasOwn(value,'symbol') ? Symbol.for(value.symbol) : value.value;
|
|
81
|
+
for (let index=0;index<graph.nodes.length;index++) {
|
|
82
|
+
const node=graph.nodes[index], value=values[index];
|
|
83
|
+
if (node.type==='promise') promises[index][node.rejected?'reject':'resolve'](decode(node.result));
|
|
84
|
+
if (node.type==='array') for (const item of node.items) value.push(decode(item));
|
|
85
|
+
if (node.type==='map') for (const [key,item] of node.items) value.set(decode(key),decode(item));
|
|
86
|
+
if (node.type==='set') for (const item of node.items) value.add(decode(item));
|
|
87
|
+
if (node.type==='form') for (const [key,item] of node.items) value.append(key,decode(item));
|
|
88
|
+
if (node.type==='object') for (const [key,item] of node.items) Object.defineProperty(value,key,{value:decode(item),enumerable:true,configurable:true,writable:true});
|
|
89
|
+
}
|
|
90
|
+
if(graph.nodes.some(node=>node.type==='action')) {
|
|
91
|
+
const {loadActionReference}=await import('./action-reference.mjs');
|
|
92
|
+
for(let index=0;index<graph.nodes.length;index++) {
|
|
93
|
+
const node=graph.nodes[index];
|
|
94
|
+
if(node.type!=='action') continue;
|
|
95
|
+
const reference=await loadActionReference(node.id,decode(node.bound));
|
|
96
|
+
actionTargets[index]=reference;
|
|
97
|
+
for(const property of ['$$typeof','$$id','$$bound','$$location','bind']) {
|
|
98
|
+
const descriptor=Object.getOwnPropertyDescriptor(reference,property);
|
|
99
|
+
if(descriptor) Object.defineProperty(values[index],property,descriptor);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return decode(graph.root);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function encryptBoundArgs(id, captures) {
|
|
107
|
+
const secret = key();
|
|
108
|
+
const bytes = serialize(await encodeCaptures(captures));
|
|
109
|
+
if (bytes.length > LIMIT) throw new Error('Server Action closure exceeds the 16 MiB limit');
|
|
110
|
+
const nonce = randomBytes(12);
|
|
111
|
+
const cipher = createCipheriv(`aes-${secret.length*8}-gcm`, secret, nonce);
|
|
112
|
+
cipher.setAAD(Buffer.from('prnext-server-action:' + id));
|
|
113
|
+
const ciphertext = Buffer.concat([cipher.update(bytes), cipher.final()]);
|
|
114
|
+
return 'v1.' + Buffer.concat([nonce,cipher.getAuthTag(),ciphertext]).toString('base64url');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function decryptBoundArgs(id, encrypted) {
|
|
118
|
+
const secret = key();
|
|
119
|
+
encrypted = await encrypted;
|
|
120
|
+
try {
|
|
121
|
+
if (typeof encrypted !== 'string' || !encrypted.startsWith('v1.') || encrypted.length > Math.ceil((LIMIT+28)*4/3)+3 || !/^[A-Za-z0-9_-]+$/.test(encrypted.slice(3))) throw new Error();
|
|
122
|
+
const bytes = Buffer.from(encrypted.slice(3),'base64url');
|
|
123
|
+
if (bytes.length < 28) throw new Error();
|
|
124
|
+
const decipher = createDecipheriv(`aes-${secret.length*8}-gcm`,secret,bytes.subarray(0,12));
|
|
125
|
+
decipher.setAAD(Buffer.from('prnext-server-action:' + id));
|
|
126
|
+
decipher.setAuthTag(bytes.subarray(12,28));
|
|
127
|
+
return await decodeCaptures(deserialize(Buffer.concat([decipher.update(bytes.subarray(28)),decipher.final()])));
|
|
128
|
+
} catch { throw new Error('Invalid encrypted Server Action closure'); }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function bindEncryptedReference(reference, id, capture) {
|
|
132
|
+
let bound;
|
|
133
|
+
Object.defineProperty(reference,'$$bound',{configurable:true,get(){
|
|
134
|
+
if (!bound) bound=[encryptBoundArgs(id,capture())];
|
|
135
|
+
return bound;
|
|
136
|
+
}});
|
|
137
|
+
return reference;
|
|
138
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { decodeReply, decodeAction, decodeFormState, createTemporaryReferenceSet } from 'react-server-dom-webpack/server.node';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { runRequestContext, currentRequest } from '../compat/headers.cjs';
|
|
6
|
+
import { navigationResponse } from './navigation.mjs';
|
|
7
|
+
import { appTimeoutError } from './app-errors.mjs';
|
|
8
|
+
import { flushCacheWork } from '../compat/data-cache.cjs';
|
|
9
|
+
|
|
10
|
+
export const MAX_ACTION_BYTES = 1024 * 1024;
|
|
11
|
+
const modules = new Map();
|
|
12
|
+
const loaders = new Map();
|
|
13
|
+
const manifests = new WeakMap();
|
|
14
|
+
|
|
15
|
+
function badRequest(message) { return Object.assign(new Error(message), { statusCode: 400 }); }
|
|
16
|
+
|
|
17
|
+
globalThis.__webpack_require__ = id => {
|
|
18
|
+
if (!modules.has(id)) throw badRequest('Unknown Server Action module');
|
|
19
|
+
return modules.get(id);
|
|
20
|
+
};
|
|
21
|
+
globalThis.__webpack_chunk_load__ = id => {
|
|
22
|
+
if (!loaders.has(id)) return Promise.reject(badRequest('Unknown Server Action module'));
|
|
23
|
+
return loaders.get(id)();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function actionManifest(request) {
|
|
27
|
+
const root = path.resolve(request.distDir);
|
|
28
|
+
const actions = request.actions;
|
|
29
|
+
const edge = request.cacheConfig?.runtime === 'edge';
|
|
30
|
+
const cached = request.production && actions && manifests.get(actions);
|
|
31
|
+
if (cached && cached.root === root && cached.edge === edge) return cached.value;
|
|
32
|
+
const entries = Object.create(null);
|
|
33
|
+
for (const [id, info] of Object.entries(request.actions || {})) {
|
|
34
|
+
if (typeof info?.module !== 'string' || typeof info?.export !== 'string') throw new Error('Invalid Server Action manifest');
|
|
35
|
+
const selected = request.cacheConfig?.runtime === 'edge' && info.edgeModule !== undefined ? info.edgeModule : info.module;
|
|
36
|
+
if (typeof selected !== 'string') throw new Error('Invalid Server Action runtime module');
|
|
37
|
+
const filename = path.resolve(root, selected);
|
|
38
|
+
if (!filename.startsWith(root + path.sep)) throw new Error('Server Action module is outside the build directory');
|
|
39
|
+
const url = pathToFileURL(filename).href;
|
|
40
|
+
if (!loaders.has(url)) loaders.set(url, async () => {
|
|
41
|
+
if (!modules.has(url)) modules.set(url, await import(url));
|
|
42
|
+
});
|
|
43
|
+
entries[id] = { id: url, chunks: [url, url], name: info.export };
|
|
44
|
+
}
|
|
45
|
+
// React's webpack decoder permits an "id#export" fallback. Reject unknown
|
|
46
|
+
// IDs on the first lookup, including inherited names and fabricated exports.
|
|
47
|
+
const value = new Proxy(entries, { get(target, id) {
|
|
48
|
+
if (typeof id !== 'string' || !Object.hasOwn(target, id)) throw badRequest('Unknown Server Action reference');
|
|
49
|
+
return target[id];
|
|
50
|
+
} });
|
|
51
|
+
if (request.production && actions) manifests.set(actions, { root, edge, value });
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function registerCacheClientModule(id, namespace) { modules.set(id, namespace); }
|
|
56
|
+
|
|
57
|
+
export async function loadActionReference(id, bound = null) {
|
|
58
|
+
const manifest = actionManifest(currentRequest());
|
|
59
|
+
const reference = manifest[id];
|
|
60
|
+
await loaders.get(reference.id)();
|
|
61
|
+
const namespace = modules.get(reference.id);
|
|
62
|
+
if (!Object.hasOwn(namespace, reference.name) || typeof namespace[reference.name] !== 'function') throw badRequest('Invalid Server Action export');
|
|
63
|
+
const fn = namespace[reference.name];
|
|
64
|
+
if (bound === null) return fn;
|
|
65
|
+
const values = await bound;
|
|
66
|
+
if (!Array.isArray(values) || values.length > 1000) throw badRequest('Invalid Server Action bound arguments');
|
|
67
|
+
return fn.bind(null, ...values);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function errorPayload(error, production) {
|
|
71
|
+
const digest = createHash('sha256').update(String(error?.stack || error?.message || error)).digest('hex').slice(0, 16);
|
|
72
|
+
return { message: production ? 'An error occurred while executing this action. See the server logs for details.' : error?.message || String(error), digest };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function actionBody(action, limit = MAX_ACTION_BYTES) {
|
|
76
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 8 * 1024 * 1024) throw badRequest('Invalid Server Action body limit');
|
|
77
|
+
if ((action.body || '').length > Math.ceil(limit / 3) * 4) throw badRequest(`Server Action body exceeds the ${limit} byte limit`);
|
|
78
|
+
const bytes = Buffer.from(action.body || '', 'base64');
|
|
79
|
+
if (bytes.byteLength > limit) throw badRequest(`Server Action body exceeds the ${limit} byte limit`);
|
|
80
|
+
const contentType = action.contentType || 'text/plain';
|
|
81
|
+
if (/^(multipart\/form-data|application\/x-www-form-urlencoded)(?:;|$)/i.test(contentType)) {
|
|
82
|
+
try { return await new Response(bytes, { headers: { 'content-type': contentType } }).formData(); }
|
|
83
|
+
catch { throw badRequest('Malformed Server Action form data'); }
|
|
84
|
+
}
|
|
85
|
+
if (action.id && /^(text\/plain|application\/json)(?:;|$)/i.test(contentType)) return bytes.toString('utf8');
|
|
86
|
+
throw badRequest('Unsupported Server Action content type');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function invokeAction(request) {
|
|
90
|
+
const manifest = actionManifest(request);
|
|
91
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
92
|
+
const body = await actionBody(request.action, request.serverActions?.bodySizeLimit);
|
|
93
|
+
return runRequestContext({ ...request, phase: 'action', mutableCookies: true }, async () => {
|
|
94
|
+
const context = currentRequest();
|
|
95
|
+
let operation;
|
|
96
|
+
try {
|
|
97
|
+
if (request.action.id) {
|
|
98
|
+
const reference = manifest[request.action.id];
|
|
99
|
+
const args = await decodeReply(body, manifest, { temporaryReferences, arraySizeLimit: 10_000 });
|
|
100
|
+
if (!Array.isArray(args) || args.length > 1000) throw badRequest('Invalid Server Action arguments');
|
|
101
|
+
await loaders.get(reference.id)();
|
|
102
|
+
const namespace = modules.get(reference.id);
|
|
103
|
+
if (!Object.hasOwn(namespace, reference.name) || typeof namespace[reference.name] !== 'function') throw badRequest('Invalid Server Action export');
|
|
104
|
+
operation = () => namespace[reference.name](...args);
|
|
105
|
+
} else {
|
|
106
|
+
operation = await decodeAction(body, manifest);
|
|
107
|
+
if (typeof operation !== 'function') throw badRequest('No Server Action was selected by the form');
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error?.statusCode === 504) throw error;
|
|
111
|
+
if (error?.statusCode === 400) throw error;
|
|
112
|
+
throw badRequest('Invalid Server Action payload');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let actionResult;
|
|
116
|
+
let actionError;
|
|
117
|
+
let actionRedirect;
|
|
118
|
+
let actionNotFound;
|
|
119
|
+
let failure;
|
|
120
|
+
try { actionResult = await operation(); }
|
|
121
|
+
catch (error) {
|
|
122
|
+
const navigation = navigationResponse(error);
|
|
123
|
+
if (navigation?.headers?.location) {
|
|
124
|
+
const type = error.digest.split(';')[1];
|
|
125
|
+
actionRedirect = { url: navigation.headers.location, type: type === 'push' ? 'push' : 'replace' };
|
|
126
|
+
} else if (navigation?.status === 404) {
|
|
127
|
+
actionNotFound = true;
|
|
128
|
+
} else {
|
|
129
|
+
console.error('[prnext]', error?.stack || error);
|
|
130
|
+
actionError = errorPayload(error, request.production);
|
|
131
|
+
failure = { message: error?.message || String(error), stack: error?.stack, digest: error?.digest };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// An invalidation must finish before the updated component tree is read,
|
|
135
|
+
// including actions that redirect or report an application error.
|
|
136
|
+
await flushCacheWork(context);
|
|
137
|
+
const headers = new Headers(request.headers || {});
|
|
138
|
+
headers.set('cookie', context.cookies.toString());
|
|
139
|
+
let formState = null;
|
|
140
|
+
if (!request.action.id && !failure && !actionRedirect && !actionNotFound) formState = await decodeFormState(actionResult, body, manifest);
|
|
141
|
+
return { actionResult, actionError, actionRedirect, actionNotFound, failure, formState, temporaryReferences,
|
|
142
|
+
headers: Object.fromEntries(headers), setCookies: [...context.outgoingCookies.values()], native: !request.action.id };
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function runAction(request) {
|
|
147
|
+
const timeoutMs = request.renderTimeoutMs ?? 25_000;
|
|
148
|
+
let timeout;
|
|
149
|
+
try {
|
|
150
|
+
return await Promise.race([
|
|
151
|
+
invokeAction(request),
|
|
152
|
+
new Promise((_, reject) => { timeout = setTimeout(() => {
|
|
153
|
+
const error = appTimeoutError('Server Action', timeoutMs);
|
|
154
|
+
// An async mutation cannot be cancelled safely; retire the isolate so
|
|
155
|
+
// its pending work never overlaps a subsequent request.
|
|
156
|
+
error.retireWorker = true;
|
|
157
|
+
reject(error);
|
|
158
|
+
}, timeoutMs); }),
|
|
159
|
+
]);
|
|
160
|
+
} finally { clearTimeout(timeout); }
|
|
161
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createServerReference as createReference } from 'react-server-dom-webpack/client.node';
|
|
2
|
+
|
|
3
|
+
// The official proxy carries React's form action and useActionState signatures.
|
|
4
|
+
// Invoking it during SSR is rejected by React instead of running a mutation.
|
|
5
|
+
export function createServerReference(id) { return createReference(id); }
|