@nimbus-sh/worker 0.9.0 → 0.11.0
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/NOTICE.md +5 -0
- package/README.md +10 -0
- package/dist/_shared/session-router.d.ts +7 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +7 -0
- package/dist/bindings/body.d.ts +6 -0
- package/dist/bindings/body.d.ts.map +1 -0
- package/dist/bindings/body.js +37 -0
- package/dist/bindings/kv.d.ts +0 -2
- package/dist/bindings/kv.d.ts.map +1 -1
- package/dist/bindings/kv.js +3 -42
- package/dist/bindings/r2.d.ts +0 -2
- package/dist/bindings/r2.d.ts.map +1 -1
- package/dist/bindings/r2.js +4 -44
- package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
- package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
- package/dist/esbuild-cli-artifact.generated.js +17 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
- package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +24 -24
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +11 -6
- package/dist/facets/compose.d.ts +11 -7
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +8 -31
- package/dist/facets/data-plan.d.ts +79 -0
- package/dist/facets/data-plan.d.ts.map +1 -0
- package/dist/facets/data-plan.js +477 -0
- package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
- package/dist/facets/esbuild-bundle-pool.js +3 -3
- package/dist/facets/esbuild-transform.d.ts +38 -8
- package/dist/facets/esbuild-transform.d.ts.map +1 -1
- package/dist/facets/esbuild-transform.js +219 -19
- package/dist/facets/exec-telemetry.d.ts +7 -3
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +198 -69
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +1276 -652
- package/dist/facets/opencode-staging.d.ts +0 -2
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +5 -11
- package/dist/facets/process.d.ts +4 -0
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +3 -0
- package/dist/facets/read-profile.d.ts +149 -0
- package/dist/facets/read-profile.d.ts.map +1 -0
- package/dist/facets/read-profile.js +483 -0
- package/dist/facets/real-vite-hmr.d.ts +2 -0
- package/dist/facets/real-vite-hmr.d.ts.map +1 -1
- package/dist/facets/real-vite-hmr.js +3 -2
- package/dist/facets/resident-identity.d.ts +0 -1
- package/dist/facets/resident-identity.d.ts.map +1 -1
- package/dist/facets/resident-identity.js +0 -3
- package/dist/facets/wasm-swap-registry.d.ts +34 -25
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +62 -56
- package/dist/facets/ws-terminal.d.ts +4 -4
- package/dist/facets/ws-terminal.d.ts.map +1 -1
- package/dist/facets/ws-terminal.js +4 -5
- package/dist/git/commands.d.ts +33 -10
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +1891 -197
- package/dist/git/network-facet.d.ts +12 -2
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +69 -18
- package/dist/git/unified-diff.d.ts +74 -0
- package/dist/git/unified-diff.d.ts.map +1 -0
- package/dist/git/unified-diff.js +676 -0
- package/dist/git-bundle.generated.d.ts +7 -11
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +7 -11
- package/dist/hosted/commands.d.ts.map +1 -1
- package/dist/hosted/commands.js +64 -245
- package/dist/hosted/runtime.d.ts +22 -15
- package/dist/hosted/runtime.d.ts.map +1 -1
- package/dist/hosted/runtime.js +72 -14
- package/dist/hosted/services.d.ts +8 -1
- package/dist/hosted/services.d.ts.map +1 -1
- package/dist/hosted/services.js +12 -13
- package/dist/hosted/session.d.ts +165 -0
- package/dist/hosted/session.d.ts.map +1 -0
- package/dist/hosted/session.js +132 -0
- package/dist/loaders/generated-workers.d.ts +6 -3
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +8 -5
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +0 -27
- package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
- package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
- package/dist/loaders/pre-bundle-preamble.js +13 -8
- package/dist/node-shims-artifact.generated.d.ts +13 -4
- package/dist/node-shims-artifact.generated.d.ts.map +1 -1
- package/dist/node-shims-artifact.generated.js +16 -7
- package/dist/npm/bin-links.d.ts +3 -1
- package/dist/npm/bin-links.d.ts.map +1 -1
- package/dist/npm/bin-links.js +32 -8
- package/dist/npm/cache.d.ts +11 -1
- package/dist/npm/cache.d.ts.map +1 -1
- package/dist/npm/cache.js +21 -7
- package/dist/npm/install-batch-facet.d.ts +4 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +32 -18
- package/dist/npm/installer.d.ts +23 -3
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +330 -131
- package/dist/npm/npx-install.d.ts +3 -1
- package/dist/npm/npx-install.d.ts.map +1 -1
- package/dist/npm/npx-install.js +4 -1
- package/dist/npm/package-lock.d.ts +32 -0
- package/dist/npm/package-lock.d.ts.map +1 -0
- package/dist/npm/package-lock.js +65 -0
- package/dist/npm/placement.d.ts +16 -0
- package/dist/npm/placement.d.ts.map +1 -0
- package/dist/npm/placement.js +33 -0
- package/dist/npm/pre-bundle-facet.d.ts +1 -30
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +3 -5
- package/dist/npm/r2-cache.d.ts +12 -6
- package/dist/npm/r2-cache.d.ts.map +1 -1
- package/dist/npm/r2-cache.js +34 -16
- package/dist/npm/resolve-one-facet.d.ts +3 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +15 -3
- package/dist/npm/resolver.d.ts +11 -13
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +12 -25
- package/dist/opencode-artifact.generated.js +2 -2
- package/dist/replica/routing.d.ts +2 -38
- package/dist/replica/routing.d.ts.map +1 -1
- package/dist/replica/routing.js +3 -55
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +15 -2
- package/dist/router/public-directory.d.ts +18 -0
- package/dist/router/public-directory.d.ts.map +1 -1
- package/dist/router/public-directory.js +1 -1
- package/dist/router/remote-api.d.ts +3 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +42 -79
- package/dist/runtime/bash-repl.js +2 -2
- package/dist/runtime/bun-repl.d.ts.map +1 -1
- package/dist/runtime/bun-repl.js +2 -13
- package/dist/runtime/cpython-resident.d.ts.map +1 -1
- package/dist/runtime/cpython-resident.js +2 -10
- package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/esbuild-wasm-bytes.js +66 -92
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +0 -7
- package/dist/runtime/git-bundle-artifact.d.ts +17 -0
- package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
- package/dist/runtime/git-bundle-artifact.js +25 -0
- package/dist/runtime/node-repl.d.ts.map +1 -1
- package/dist/runtime/node-repl.js +2 -13
- package/dist/runtime/node-shims-artifact.d.ts +22 -23
- package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
- package/dist/runtime/node-shims-artifact.js +48 -78
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +2358 -996
- package/dist/runtime/opencode-artifact.d.ts.map +1 -1
- package/dist/runtime/opencode-artifact.js +17 -53
- package/dist/runtime/opencode-facet-runner.d.ts +3 -6
- package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
- package/dist/runtime/opencode-facet-runner.js +45 -10
- package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +16 -49
- package/dist/runtime/package-manager.d.ts +0 -2
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +0 -3
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +4 -35
- package/dist/runtime/repl-session.d.ts +10 -0
- package/dist/runtime/repl-session.d.ts.map +1 -1
- package/dist/runtime/repl-session.js +7 -0
- package/dist/runtime/ruby-repl.d.ts.map +1 -1
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/ruby-resident.d.ts.map +1 -1
- package/dist/runtime/ruby-resident.js +41 -51
- package/dist/runtime/runtime-catalog.d.ts +10 -3
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +125 -15
- package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
- package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/sqlite-wasm-bytes.js +21 -57
- package/dist/runtime/staged-source.d.ts +67 -0
- package/dist/runtime/staged-source.d.ts.map +1 -0
- package/dist/runtime/staged-source.js +113 -0
- package/dist/runtime/wasi/preamble.d.ts +0 -22
- package/dist/runtime/wasi/preamble.d.ts.map +1 -1
- package/dist/runtime/wasi/preamble.js +98 -1425
- package/dist/runtime-catalog.generated.js +1 -1
- package/dist/session/agent.d.ts +14 -2
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/agent.js +57 -19
- package/dist/session/ai.d.ts +0 -11
- package/dist/session/ai.d.ts.map +1 -1
- package/dist/session/ai.js +0 -4
- package/dist/session/fs-watch.d.ts +0 -11
- package/dist/session/fs-watch.d.ts.map +1 -1
- package/dist/session/fs-watch.js +0 -21
- package/dist/session/hibernation.d.ts +68 -2
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +95 -1
- package/dist/session/init-phases.d.ts +1 -2
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +1 -2
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +7 -20
- package/dist/session/keys.d.ts +0 -3
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +0 -3
- package/dist/session/legacy-reset.d.ts +11 -0
- package/dist/session/legacy-reset.d.ts.map +1 -0
- package/dist/session/legacy-reset.js +16 -0
- package/dist/session/nimbus-session.d.ts +50 -16
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +77 -71
- package/dist/session/npm-install-port.d.ts.map +1 -1
- package/dist/session/npm-install-port.js +2 -0
- package/dist/session/programmatic.d.ts +13 -18
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +53 -39
- package/dist/session/replica-routes.d.ts +2 -3
- package/dist/session/replica-routes.d.ts.map +1 -1
- package/dist/session/replica-routes.js +3 -10
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +11 -12
- package/dist/session/rpc.d.ts +108 -21
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +194 -39
- package/dist/session/start-real-vite.js +2 -2
- package/dist/session/state-store.d.ts +1 -17
- package/dist/session/state-store.d.ts.map +1 -1
- package/dist/session/state-store.js +3 -53
- package/dist/session/supervisor-op.d.ts +7 -0
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +6 -1
- package/dist/session/supervisor-rpc.d.ts +116 -19
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +194 -84
- package/dist/session/vite-command.d.ts.map +1 -1
- package/dist/session/vite-command.js +17 -14
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +20 -8
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +30 -5
- package/dist/vfs/facet-resident-limits.d.ts +33 -0
- package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
- package/dist/vfs/facet-resident-limits.js +33 -0
- package/dist/vfs/facet-resident-store.d.ts +2 -10
- package/dist/vfs/facet-resident-store.d.ts.map +1 -1
- package/dist/vfs/facet-resident-store.js +1594 -219
- package/dist/workspace-host.d.ts +3 -1
- package/dist/workspace-host.d.ts.map +1 -1
- package/dist/workspace-host.js +2 -0
- package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
- package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
- package/dist/wrangler/nimbus-wrangler.js +8 -22
- package/package.json +34 -7
- package/public/_assets/esbuild-0.24.2.js +2380 -0
- package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
- package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
- package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
- package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
- package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
- package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
- package/scripts/build-opencode-attach-entry.mjs +51 -1
- package/scripts/bundle-esbuild-wasm.mjs +55 -39
- package/scripts/bundle-facet-workers.mjs +159 -10
- package/scripts/bundle-git.mjs +88 -27
- package/scripts/bundle-node-shims.mjs +72 -46
- package/scripts/bundle-runtime.mjs +18 -421
- package/scripts/cf-git-patch.mjs +137 -0
- package/scripts/patch-install-deps.mjs +49 -96
- package/scripts/runtime-specs.mjs +435 -0
- package/vendor/git-http.generated.d.ts +82 -0
- package/vendor/git-upstream.generated.d.ts +4378 -0
- package/vendor/git.LICENSE +7 -0
- package/vendor/git.generated.d.mts +21 -0
- package/vendor/git.generated.mjs +103 -0
- package/dist/replica/suspension.d.ts +0 -36
- package/dist/replica/suspension.d.ts.map +0 -1
- package/dist/replica/suspension.js +0 -49
- package/dist/runtime/static-server.d.ts +0 -3
- package/dist/runtime/static-server.d.ts.map +0 -1
- package/dist/runtime/static-server.js +0 -133
- package/scripts/clean-dist.mjs +0 -7
|
@@ -41,17 +41,23 @@
|
|
|
41
41
|
import { generateStreamsCode } from '@nimbus-sh/core/runtime/streams.js';
|
|
42
42
|
import { generateSqliteShimCode } from './sqlite-shim.js';
|
|
43
43
|
import { generateUndiciShimCode } from '@nimbus-sh/core/runtime/undici-shim.js';
|
|
44
|
+
import { MK_COMPILED_FN_SOURCE } from '@nimbus-sh/core/_shared/compiled-fn.js';
|
|
44
45
|
import { getExportsResolverJS } from '@nimbus-sh/core/_shared/exports-resolver.js';
|
|
46
|
+
import { ESM_RESOLVER_PREAMBLE } from '../loaders/generated-workers.js';
|
|
45
47
|
import { getTypescriptSpecifiersJS } from '@nimbus-sh/core/_shared/typescript-specifiers.js';
|
|
46
48
|
import { NIMBUS_AI_CREDENTIAL_HEADERS, NIMBUS_AI_TOKEN_ENV } from '@nimbus-sh/core/_shared/ai-egress.js';
|
|
47
49
|
import { MAX_RPC_SAFE_PAYLOAD_BYTES } from '@nimbus-sh/platform/limits.js';
|
|
48
|
-
import { FACET_PROVIDED_PACKAGES, FS_READ_BATCH_PATH_LIMIT, FS_READ_BATCH_REQUEST_BYTES, NIMBUS_AI_GATEWAY_PORT, NODE_VERSION, NODE_VERSIONS, } from '@nimbus-sh/core/constants.js';
|
|
50
|
+
import { FACET_PROVIDED_PACKAGES, FS_READ_BATCH_PATH_LIMIT, FS_READ_BATCH_REQUEST_BYTES, NIMBUS_AI_GATEWAY_PORT, NODE_VERSION, NODE_VERSIONS, VFS_CAPACITY, } from '@nimbus-sh/core/constants.js';
|
|
51
|
+
import { PACKAGE_ABI_POLICY } from '../facets/wasm-swap-registry.js';
|
|
49
52
|
const STREAMS_CODE = generateStreamsCode();
|
|
50
53
|
const SQLITE_SHIM_CODE = generateSqliteShimCode();
|
|
51
54
|
const UNDICI_SHIM_CODE = generateUndiciShimCode();
|
|
52
55
|
const FACET_PROVIDED_PACKAGES_LITERAL = JSON.stringify(FACET_PROVIDED_PACKAGES);
|
|
53
56
|
const EXPORTS_RESOLVER_JS = getExportsResolverJS();
|
|
54
57
|
const TYPESCRIPT_SPECIFIERS_JS = getTypescriptSpecifiersJS();
|
|
58
|
+
// One module-cell compile for every facet (core/_shared/compiled-fn.ts).
|
|
59
|
+
// Interpolated into the emitted shim below as ${COMPILED_FN_JS}.
|
|
60
|
+
const COMPILED_FN_JS = MK_COMPILED_FN_SOURCE;
|
|
55
61
|
// Node version fingerprint. Single source of truth in constants.ts.
|
|
56
62
|
// Interpolated as JS literals into the emitted process shim. See
|
|
57
63
|
// constants.ts for the rationale (create-astro preflight, etc.).
|
|
@@ -62,15 +68,43 @@ const NODE_VERSIONS_LITERAL = JSON.stringify(NODE_VERSIONS);
|
|
|
62
68
|
// from _shared/ai-egress.ts at build time rather than being written twice.
|
|
63
69
|
const AI_TOKEN_ENV_LITERAL = JSON.stringify(NIMBUS_AI_TOKEN_ENV);
|
|
64
70
|
const AI_CREDENTIAL_HEADERS_LITERAL = JSON.stringify(NIMBUS_AI_CREDENTIAL_HEADERS);
|
|
71
|
+
const ABI_ADVISORIES_LITERAL = JSON.stringify(PACKAGE_ABI_POLICY.rejects.map((r) => [r.from, r.suggest ? `${r.reason} … try: ${r.suggest}` : r.reason]));
|
|
65
72
|
export function generateShimsCode() {
|
|
66
73
|
return `
|
|
67
74
|
// ═══════════════════════════════════════════════════════════════════════
|
|
68
75
|
// ── Format helper ──────────────────────────────────────────────────
|
|
69
76
|
// ═══════════════════════════════════════════════════════════════════════
|
|
77
|
+
function __isErrorValue(v) {
|
|
78
|
+
return v instanceof Error || Object.prototype.toString.call(v) === "[object Error]";
|
|
79
|
+
}
|
|
80
|
+
// Node's util.inspect(err); JSON.stringify drops non-enumerable name/message/stack.
|
|
81
|
+
function __fmtError(e, seen) {
|
|
82
|
+
if (seen.has(e)) return "[Circular *]";
|
|
83
|
+
seen.add(e);
|
|
84
|
+
let text;
|
|
85
|
+
try { text = typeof e.stack === "string" && e.stack ? e.stack : Error.prototype.toString.call(e); }
|
|
86
|
+
catch { text = String(e); }
|
|
87
|
+
const fields = [];
|
|
88
|
+
for (const key of Object.keys(e)) {
|
|
89
|
+
if (key === "cause") continue;
|
|
90
|
+
let value;
|
|
91
|
+
try { value = e[key]; } catch { continue; }
|
|
92
|
+
fields.push(key + ": " + __fmtField(value, seen));
|
|
93
|
+
}
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(e, "cause")) fields.push("[cause]: " + __fmtField(e.cause, seen));
|
|
95
|
+
if (fields.length === 0) return text;
|
|
96
|
+
return text + " {\\n" + fields.map((f) => " " + f.split("\\n").join("\\n ")).join(",\\n") + "\\n}";
|
|
97
|
+
}
|
|
98
|
+
function __fmtField(v, seen) {
|
|
99
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
100
|
+
if (v !== null && typeof v === "object" && __isErrorValue(v)) return __fmtError(v, seen);
|
|
101
|
+
return __fmt(v);
|
|
102
|
+
}
|
|
70
103
|
function __fmt(v) {
|
|
71
104
|
if (v === null) return "null";
|
|
72
105
|
if (v === undefined) return "undefined";
|
|
73
106
|
if (typeof v === "object") {
|
|
107
|
+
if (__isErrorValue(v)) return __fmtError(v, new Set());
|
|
74
108
|
try { return JSON.stringify(v); } catch { return String(v); }
|
|
75
109
|
}
|
|
76
110
|
return String(v);
|
|
@@ -123,6 +157,41 @@ async function __nimbusUseRpcResultUnref(promise, use) {
|
|
|
123
157
|
finally { __nimbusDisposeRpcResult(value); }
|
|
124
158
|
}
|
|
125
159
|
|
|
160
|
+
/**
|
|
161
|
+
* The ACQUIRE barrier, taken before the program sees anything that arrived
|
|
162
|
+
* from outside it: a response or body from the network, a relayed socket
|
|
163
|
+
* frame, a request routed to one of its ports, a stdin packet or signal, a
|
|
164
|
+
* child's output or exit. Each of those can be the second half of a causal
|
|
165
|
+
* chain that began with a write somewhere else — \`echo v2 > f; curl :3000\`,
|
|
166
|
+
* a child that writes a file and then exits — and the handler's synchronous
|
|
167
|
+
* reads must see that write. Nothing but this barrier carries it.
|
|
168
|
+
*
|
|
169
|
+
* What the supervisor delivers — a request, a stdin packet, a child's output
|
|
170
|
+
* or exit — carries the answer to this barrier with it (\`delivered\`,
|
|
171
|
+
* session/rpc.ts _acquireOnDelivery), computed after the thing delivered was
|
|
172
|
+
* queued, so the barrier applies that answer instead of asking, and the
|
|
173
|
+
* program resumes with no round trip of its own. It asks when there is none
|
|
174
|
+
* or it cannot use the one delivered. A resumption the supervisor does not
|
|
175
|
+
* deliver — a timer, the network — always asks.
|
|
176
|
+
*
|
|
177
|
+
* The barrier is installed by the fs module, which is evaluated after this
|
|
178
|
+
* point; a facet with no supervisor has none and nothing to be coherent with.
|
|
179
|
+
*/
|
|
180
|
+
async function __nimbusInboundBarrier(delivered) {
|
|
181
|
+
const acquire = globalThis.__nimbusVfsAcquireBarrier;
|
|
182
|
+
if (typeof acquire === "function") await acquire(delivered);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The arguments this process asks its ACQUIRE with (the fs module's
|
|
187
|
+
* _acquireArgs), for a delivery to be answered from: a long poll sends them,
|
|
188
|
+
* and what it delivers comes back carrying fsAcquire's answer to them.
|
|
189
|
+
*/
|
|
190
|
+
function __nimbusVfsAcquireArgs() {
|
|
191
|
+
const args = globalThis.__nimbusVfsAcquireArgs;
|
|
192
|
+
return typeof args === "function" ? args() : undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
126
195
|
// ═══════════════════════════════════════════════════════════════════════
|
|
127
196
|
// ── Precompiled wasm ───────────────────────────────────────────────
|
|
128
197
|
// A wasm image can only become a WebAssembly.Module through the Worker
|
|
@@ -352,13 +421,11 @@ function __nimbusWasmDigest(bytes) {
|
|
|
352
421
|
// supervisor round trip per outbound request, not a hidden one. It is the
|
|
353
422
|
// price of the guarantee.
|
|
354
423
|
//
|
|
355
|
-
// The
|
|
356
|
-
// is evaluated after this one;
|
|
357
|
-
// and there is nothing to be coherent with.
|
|
424
|
+
// The RELEASE barrier lives on globalThis because the fs module installs it
|
|
425
|
+
// and is evaluated after this one; the ACQUIRE is __nimbusInboundBarrier.
|
|
358
426
|
const __resumeCoherent = async (pending) => {
|
|
359
427
|
const value = await pending;
|
|
360
|
-
|
|
361
|
-
if (typeof acquire === "function") await acquire();
|
|
428
|
+
await __nimbusInboundBarrier();
|
|
362
429
|
return value;
|
|
363
430
|
};
|
|
364
431
|
const __barriered = async (input, init) => {
|
|
@@ -388,6 +455,15 @@ function __nimbusWasmDigest(bytes) {
|
|
|
388
455
|
|
|
389
456
|
let __nimbusLiveStdinPump = null;
|
|
390
457
|
let __nimbusProcessExitReported = false;
|
|
458
|
+
// Set when the program has exited: its timers are cleared and its writes refused.
|
|
459
|
+
let __nimbusProgramStopped = false;
|
|
460
|
+
let __nimbusExitEmitted = false;
|
|
461
|
+
// 'exit' listeners run once, synchronously, before the program is stopped.
|
|
462
|
+
function __nimbusEmitExit(code) {
|
|
463
|
+
if (__nimbusExitEmitted) return;
|
|
464
|
+
__nimbusExitEmitted = true;
|
|
465
|
+
try { __processEvents.emit("exit", code); } catch {}
|
|
466
|
+
}
|
|
391
467
|
let __nimbusProcessExitResolve = null;
|
|
392
468
|
let __nimbusProcessExitCode = null;
|
|
393
469
|
const __nimbusProcessExitPromise = new Promise((resolve) => {
|
|
@@ -634,6 +710,43 @@ function __residentAnyUnder(prefix) {
|
|
|
634
710
|
return false;
|
|
635
711
|
}
|
|
636
712
|
|
|
713
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
714
|
+
// ── fs.constants (linux x64) ───────────────────────────────────────
|
|
715
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
716
|
+
//
|
|
717
|
+
// The ONE table behind fs.constants, fs.promises.constants, and the fs slice
|
|
718
|
+
// of node:constants / process.binding('fs'). Values are real Node on Linux
|
|
719
|
+
// x64 (node -p 'require("fs").constants'). Packages compose open flags from
|
|
720
|
+
// this table at module init — modern-tar (create-astro via @bluwy/giget-core)
|
|
721
|
+
// computes O_WRONLY|O_CREAT|O_TRUNC|O_NOFOLLOW|O_EXCL and passes the number
|
|
722
|
+
// to fs.open — so a partial table silently degrades every such open to
|
|
723
|
+
// O_RDONLY (0) and every extraction fails ENOENT. Frozen, as Node's is.
|
|
724
|
+
const __fsConstants = Object.freeze({
|
|
725
|
+
// ── libuv fs flags ────────────────────────────────────────────────
|
|
726
|
+
UV_FS_SYMLINK_DIR: 1, UV_FS_SYMLINK_JUNCTION: 2,
|
|
727
|
+
// ── open(2) flags ─────────────────────────────────────────────────
|
|
728
|
+
O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2,
|
|
729
|
+
UV_DIRENT_UNKNOWN: 0, UV_DIRENT_FILE: 1, UV_DIRENT_DIR: 2, UV_DIRENT_LINK: 3,
|
|
730
|
+
UV_DIRENT_FIFO: 4, UV_DIRENT_SOCKET: 5, UV_DIRENT_CHAR: 6, UV_DIRENT_BLOCK: 7,
|
|
731
|
+
EXTENSIONLESS_FORMAT_JAVASCRIPT: 0, EXTENSIONLESS_FORMAT_WASM: 1,
|
|
732
|
+
// ── stat.mode file-type bits ──────────────────────────────────────
|
|
733
|
+
S_IFMT: 61440, S_IFREG: 32768, S_IFDIR: 16384, S_IFCHR: 8192,
|
|
734
|
+
S_IFBLK: 24576, S_IFIFO: 4096, S_IFLNK: 40960, S_IFSOCK: 49152,
|
|
735
|
+
O_CREAT: 64, O_EXCL: 128, UV_FS_O_FILEMAP: 0, O_NOCTTY: 256, O_TRUNC: 512,
|
|
736
|
+
O_APPEND: 1024, O_DIRECTORY: 65536, O_NOATIME: 262144, O_NOFOLLOW: 131072,
|
|
737
|
+
O_SYNC: 1052672, O_DSYNC: 4096, O_DIRECT: 16384, O_NONBLOCK: 2048,
|
|
738
|
+
// ── stat.mode permission bits ─────────────────────────────────────
|
|
739
|
+
S_IRWXU: 448, S_IRUSR: 256, S_IWUSR: 128, S_IXUSR: 64,
|
|
740
|
+
S_IRWXG: 56, S_IRGRP: 32, S_IWGRP: 16, S_IXGRP: 8,
|
|
741
|
+
S_IRWXO: 7, S_IROTH: 4, S_IWOTH: 2, S_IXOTH: 1,
|
|
742
|
+
// ── fs.access modes ───────────────────────────────────────────────
|
|
743
|
+
F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1,
|
|
744
|
+
// ── fs.copyFile modes (libuv shape + Node alias) ──────────────────
|
|
745
|
+
UV_FS_COPYFILE_EXCL: 1, COPYFILE_EXCL: 1,
|
|
746
|
+
UV_FS_COPYFILE_FICLONE: 2, COPYFILE_FICLONE: 2,
|
|
747
|
+
UV_FS_COPYFILE_FICLONE_FORCE: 4, COPYFILE_FICLONE_FORCE: 4,
|
|
748
|
+
});
|
|
749
|
+
|
|
637
750
|
// ═══════════════════════════════════════════════════════════════════════
|
|
638
751
|
// ── fs shim (VFS-backed) ───────────────────────────────────────────
|
|
639
752
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -701,29 +814,205 @@ const __fsMod = (() => {
|
|
|
701
814
|
// ── VFS bundle lookup (fast path — in-memory) ──
|
|
702
815
|
function _bundleLookup(absPath) {
|
|
703
816
|
const k = _strip(absPath);
|
|
704
|
-
if (
|
|
705
|
-
//
|
|
817
|
+
if (globalThis.__nimbusProfileStaged) globalThis.__nimbusProfileStaged.delete(k);
|
|
818
|
+
// The parked write first, as _writtenCell reads: it is this process's own,
|
|
819
|
+
// newer than anything the store holds, and it is the live cell (a store
|
|
820
|
+
// read reassembles a copy, which a write loop would pay for per write).
|
|
706
821
|
if (__vfsWrites && k in __vfsWrites) return __vfsWrites[k];
|
|
822
|
+
if (__vfsBundle && k in __vfsBundle) return __vfsBundle[k];
|
|
823
|
+
// The same bytes under the name they are held by: through a symlink, or
|
|
824
|
+
// under the old name of a rename this process has not seen land yet.
|
|
825
|
+
if (__vfsBundle && _nsActive()) {
|
|
826
|
+
const real = _nsRealKey(k);
|
|
827
|
+
if (real !== null && real !== k && real in __vfsBundle) return __vfsBundle[real];
|
|
828
|
+
}
|
|
707
829
|
return undefined;
|
|
708
830
|
}
|
|
709
831
|
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
function _metadataTable() {
|
|
713
|
-
return (typeof __vfsMetadata !== "undefined" && __vfsMetadata) ? __vfsMetadata : null;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Same guard, same reason: the SQLite resident store is spliced into the
|
|
717
|
-
// resident-process body only. The one-shot exec path runs in a stateless
|
|
718
|
-
// loaded worker with no facet storage at all, so these shims must still work
|
|
719
|
-
// with the resident set on the heap and this is the single place that asks.
|
|
832
|
+
// Whether the store is bound: every embedding of these shims boots one (a
|
|
833
|
+
// facet's SQLite, or a one-shot's heap), and this is the single place that asks.
|
|
720
834
|
function _residentStorePresent() {
|
|
721
835
|
return typeof __residentAdmit === "function" && typeof __residentReady !== "undefined" && __residentReady;
|
|
722
836
|
}
|
|
723
837
|
|
|
838
|
+
// ── The namespace (resident facets) ──
|
|
839
|
+
//
|
|
840
|
+
// A resident facet's store holds every name the credential can see, with
|
|
841
|
+
// its stat, exact at the cursor (vfs/facet-resident-store.ts). While it
|
|
842
|
+
// does, it answers every synchronous metadata question — stat, exists,
|
|
843
|
+
// readdir, realpath, access — and an absent name is known absent, so none
|
|
844
|
+
// of them records a miss or refuses. While it does not (a relist failed,
|
|
845
|
+
// until the next barrier repairs it), each of them is refused by name
|
|
846
|
+
// (_nsRequire).
|
|
847
|
+
//
|
|
848
|
+
// What the table cannot know is this process's own structural effects that
|
|
849
|
+
// the authority has not reported back yet, so those sit in an overlay, one
|
|
850
|
+
// entry per path: "absent" / "absentTree" (unlink, rmdir, rm), "dir"
|
|
851
|
+
// (mkdir; \`hide\` when nothing was there, so no stale child shows through)
|
|
852
|
+
// and "alias" (a rename: the new name denotes what the old one did). An
|
|
853
|
+
// entry retires once its mutation has settled AND a barrier begun after
|
|
854
|
+
// that has applied, because such a barrier's delta is answered after the
|
|
855
|
+
// mutation committed, so the table then shows it or anything later.
|
|
856
|
+
function _nsActive() {
|
|
857
|
+
return typeof __nsReady === "function" && __nsReady();
|
|
858
|
+
}
|
|
859
|
+
// The namespace answers every synchronous metadata question, or none. While
|
|
860
|
+
// it is not ready (a relist failed, until the next barrier repairs it) a
|
|
861
|
+
// synchronous call is refused, naming why and the asynchronous form that
|
|
862
|
+
// answers now. A launch never runs user code before it is ready.
|
|
863
|
+
function _nsRequire(syscall, displayPath, asyncForm) {
|
|
864
|
+
if (_nsActive()) return;
|
|
865
|
+
_stats.namespaceRefusals++;
|
|
866
|
+
const why = typeof __nsNotReadyCause === "function" ? __nsNotReadyCause() : "the process was started without one";
|
|
867
|
+
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
868
|
+
err.message = "EAGAIN: " + syscall + " '" + String(displayPath) + "': the namespace is being rebuilt after " + why
|
|
869
|
+
+ (_supervisor() ? "; use " + asyncForm : "");
|
|
870
|
+
throw err;
|
|
871
|
+
}
|
|
872
|
+
const _nsOwn = new Map();
|
|
873
|
+
let _nsFresh = [];
|
|
874
|
+
let _barrierBegins = 0;
|
|
875
|
+
function _nsOwnSet(k, state, extra) {
|
|
876
|
+
const before = _nsOwn.get(k);
|
|
877
|
+
const entry = { state, from: extra?.from, hide: !!extra?.hide, settled: null };
|
|
878
|
+
try { __residentNamespaceOverlayDelta(_nsOwnCost(k, entry) - (before ? _nsOwnCost(k, before) : 0)); }
|
|
879
|
+
catch (error) {
|
|
880
|
+
// The view is sealed by the store. Let the already-projected structural
|
|
881
|
+
// operation reach its authority/ack; aborting midway would strand
|
|
882
|
+
// earlier overlays with no owning mutation to retire them.
|
|
883
|
+
if (__residentInHeap && error?.code === "ENOSPC") return;
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
886
|
+
_nsOwn.set(k, entry);
|
|
887
|
+
_nsFresh.push(entry);
|
|
888
|
+
}
|
|
889
|
+
function _nsOwnCost(k, entry) { return __namespaceRowBytes("", k, entry.from); }
|
|
890
|
+
/** Hand the overlay entries made since the last mutation to the one being queued. */
|
|
891
|
+
function _nsTakeFresh() {
|
|
892
|
+
const mine = _nsFresh;
|
|
893
|
+
_nsFresh = [];
|
|
894
|
+
return () => { for (const entry of mine) if (entry.settled === null) entry.settled = _barrierBegins; };
|
|
895
|
+
}
|
|
896
|
+
function _nsRetire(begin) {
|
|
897
|
+
for (const [k, entry] of _nsOwn) if (entry.settled !== null && entry.settled < begin) {
|
|
898
|
+
_nsOwn.delete(k);
|
|
899
|
+
__residentNamespaceOverlayDelta(-_nsOwnCost(k, entry));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* The overlay's word on \`k\`: "absent", { alias } (look the table up at
|
|
905
|
+
* this key instead), { dir } (own directory), { hide } (the table knows
|
|
906
|
+
* nothing under an own fresh directory), or null (ask the table).
|
|
907
|
+
*/
|
|
908
|
+
function _nsOwnView(k) {
|
|
909
|
+
if (_nsOwn.size === 0) return null;
|
|
910
|
+
const own = _nsOwn.get(k);
|
|
911
|
+
if (own) {
|
|
912
|
+
if (own.state === "absent" || own.state === "absentTree") return "absent";
|
|
913
|
+
if (own.state === "alias") return { alias: own.from };
|
|
914
|
+
return { dir: true };
|
|
915
|
+
}
|
|
916
|
+
for (let i = k.lastIndexOf("/"); i > 0; i = k.lastIndexOf("/", i - 1)) {
|
|
917
|
+
const above = _nsOwn.get(k.slice(0, i));
|
|
918
|
+
if (!above) continue;
|
|
919
|
+
if (above.state === "absent" || above.state === "absentTree") return "absent";
|
|
920
|
+
if (above.state === "alias") return { alias: above.from + k.slice(i) };
|
|
921
|
+
if (above.hide) return { hide: true };
|
|
922
|
+
}
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** The table key \`k\` denotes (overlay and symlinks applied), or null. */
|
|
927
|
+
function _nsRealKey(k) {
|
|
928
|
+
const own = _nsOwnView(k);
|
|
929
|
+
if (own === "absent" || (own && (own.dir || own.hide))) return null;
|
|
930
|
+
const found = __nsResolve(own && own.alias !== undefined ? own.alias : k, true);
|
|
931
|
+
return found && found !== "ELOOP" ? found.path : null;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function _nsRowMeta(row) {
|
|
935
|
+
const kind = Number(row.kind);
|
|
936
|
+
return {
|
|
937
|
+
type: kind === 1 ? "directory" : kind === 2 ? "symlink" : "file",
|
|
938
|
+
size: Number(row.size), mode: Number(row.mode), uid: Number(row.uid), gid: Number(row.gid),
|
|
939
|
+
atime: Number(row.atime), mtime: Number(row.mtime), ctime: Number(row.ctime),
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Everything the namespace says about \`k\`: a stat record, "absent", or
|
|
945
|
+
* "ELOOP". Own pending writes and directories first, then the overlay,
|
|
946
|
+
* then the table, then content this process wrote that the table has not
|
|
947
|
+
* caught up with.
|
|
948
|
+
*/
|
|
949
|
+
function _nsMeta(k, follow) {
|
|
950
|
+
if (k === "") return _nsRowMeta(__nsResolve("", true).row);
|
|
951
|
+
if (__vfsWrites && k in __vfsWrites && _denialCode(__vfsWrites[k]) === null) {
|
|
952
|
+
const size = _byteLen(__vfsWrites[k]);
|
|
953
|
+
if (_createdHere.has(k)) {
|
|
954
|
+
return { type: "file", size, mode: 0o100666 & ~__processUmask, uid: cred.uid, gid: cred.gid, own: true };
|
|
955
|
+
}
|
|
956
|
+
// A rewrite of a file that was there: its owner and mode stay what the
|
|
957
|
+
// authority says they are.
|
|
958
|
+
const found = __nsResolve(k, follow);
|
|
959
|
+
if (found && found !== "ELOOP" && Number(found.row.kind) === 0) return { ..._nsRowMeta(found.row), size };
|
|
960
|
+
}
|
|
961
|
+
const own = _nsOwnView(k);
|
|
962
|
+
if (own === "absent") return "absent";
|
|
963
|
+
if (own && own.dir) return { type: "directory", size: 0, mode: 0o40777 & ~__processUmask, uid: cred.uid, gid: cred.gid, own: true };
|
|
964
|
+
// A directory this process made hides what the namespace held under its
|
|
965
|
+
// name before, but not what this process has put there since: those rows
|
|
966
|
+
// are its own writes, recorded when the authority accepted them.
|
|
967
|
+
if (!own || own.alias !== undefined || (own.hide && _createdHere.has(k))) {
|
|
968
|
+
const found = __nsResolve(own && own.alias !== undefined ? own.alias : k, follow);
|
|
969
|
+
if (found === "ELOOP") return "ELOOP";
|
|
970
|
+
if (found) return _nsRowMeta(found.row);
|
|
971
|
+
}
|
|
972
|
+
// The namespace is exact, so a name it lacks is not there, whatever bytes
|
|
973
|
+
// the module map carries under it (a file removed since the map was
|
|
974
|
+
// built). Content never stands in for metadata.
|
|
975
|
+
return "absent";
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** Names directly under directory \`k\`: Map name → type. */
|
|
979
|
+
function _nsList(k) {
|
|
980
|
+
const names = new Map();
|
|
981
|
+
const own = _nsOwnView(k);
|
|
982
|
+
if (own !== "absent" && !(own && own.hide) && !(own && own.dir && _nsOwn.get(k)?.hide)) {
|
|
983
|
+
const real = __nsResolve(own && own.alias !== undefined ? own.alias : k, true);
|
|
984
|
+
if (real && real !== "ELOOP") {
|
|
985
|
+
for (const child of __nsChildren(real.path)) {
|
|
986
|
+
names.set(child.name, child.kind === 1 ? "directory" : child.kind === 2 ? "symlink" : "file");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const prefix = k ? k + "/" : "";
|
|
991
|
+
if (__vfsWrites) {
|
|
992
|
+
for (const wk in __vfsWrites) {
|
|
993
|
+
if (!wk.startsWith(prefix)) continue;
|
|
994
|
+
const rest = wk.slice(prefix.length);
|
|
995
|
+
const slash = rest.indexOf("/");
|
|
996
|
+
if (rest) names.set(slash < 0 ? rest : rest.slice(0, slash), slash < 0 ? "file" : "directory");
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
for (const [ok, entry] of _nsOwn) {
|
|
1000
|
+
if (!ok.startsWith(prefix) || ok.slice(prefix.length).includes("/")) continue;
|
|
1001
|
+
const name = ok.slice(prefix.length);
|
|
1002
|
+
if (entry.state === "absent" || entry.state === "absentTree") { if (!(__vfsWrites && ok in __vfsWrites)) names.delete(name); }
|
|
1003
|
+
else if (entry.state === "dir") names.set(name, "directory");
|
|
1004
|
+
else {
|
|
1005
|
+
const meta = _nsMeta(ok, false);
|
|
1006
|
+
if (meta !== "absent" && meta !== "ELOOP") names.set(name, meta.type);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return names;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
724
1012
|
function _metadata(absPath) {
|
|
725
|
-
|
|
726
|
-
|
|
1013
|
+
_nsRequire("stat", absPath, "fs.promises.stat");
|
|
1014
|
+
const meta = _nsMeta(_strip(absPath), true);
|
|
1015
|
+
return meta === "absent" || meta === "ELOOP" ? undefined : meta;
|
|
727
1016
|
}
|
|
728
1017
|
|
|
729
1018
|
function _denialCode(cell) {
|
|
@@ -731,52 +1020,13 @@ const __fsMod = (() => {
|
|
|
731
1020
|
typeof cell.error === "string" ? cell.error : null;
|
|
732
1021
|
}
|
|
733
1022
|
|
|
734
|
-
// Removal retracts the path from the sync existence view
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
// the sync read path reports that file as merely non-resident.
|
|
1023
|
+
// Removal retracts the path from the sync existence view: the namespace's
|
|
1024
|
+
// overlay of this process's own effects says it is gone until a barrier's
|
|
1025
|
+
// delta does.
|
|
738
1026
|
function _forgetSyncPath(k) {
|
|
1027
|
+
_createdHere.delete(k);
|
|
739
1028
|
_announcedDirs.delete(k);
|
|
740
|
-
|
|
741
|
-
if (metadata) delete metadata[k];
|
|
742
|
-
if (!__vfsManifest) return;
|
|
743
|
-
delete __vfsManifest[k];
|
|
744
|
-
const slash = k.lastIndexOf("/");
|
|
745
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
746
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
747
|
-
const siblings = __vfsManifest[parent];
|
|
748
|
-
if (!siblings) return;
|
|
749
|
-
const at = siblings.indexOf(name);
|
|
750
|
-
if (at !== -1) siblings.splice(at, 1);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* Put a path into the sync EXISTENCE view — the inverse of _forgetSyncPath.
|
|
755
|
-
*
|
|
756
|
-
* A content cell is not existence. An invalidation drops content and is
|
|
757
|
-
* documented to leave the name behind so existsSync cannot fabricate an
|
|
758
|
-
* ENOENT for a file that was merely rewritten — but a file this process just
|
|
759
|
-
* created has no manifest entry and no spawn-time stat record, so its cell
|
|
760
|
-
* WAS the name, and evicting it retracted a file the program had written
|
|
761
|
-
* itself. Recording the name where every other name lives is what makes that
|
|
762
|
-
* documented asymmetry true.
|
|
763
|
-
*
|
|
764
|
-
* The parent's listing is the right home for it. Where the parent has no
|
|
765
|
-
* listing the entry is created only for a directory this process made, since
|
|
766
|
-
* that is the only case in which it knows the whole contents and calling the
|
|
767
|
-
* directory enumerated is honest.
|
|
768
|
-
*/
|
|
769
|
-
function _announceSyncPath(k) {
|
|
770
|
-
if (!__vfsManifest || k === "") return;
|
|
771
|
-
const slash = k.lastIndexOf("/");
|
|
772
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
773
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
774
|
-
let siblings = __vfsManifest[parent];
|
|
775
|
-
if (!Array.isArray(siblings)) {
|
|
776
|
-
if (!__vfsDirs || !(parent in __vfsDirs)) return;
|
|
777
|
-
siblings = __vfsManifest[parent] = [];
|
|
778
|
-
}
|
|
779
|
-
if (siblings.indexOf(name) === -1) siblings.push(name);
|
|
1029
|
+
_nsOwnSet(k, "absent");
|
|
780
1030
|
}
|
|
781
1031
|
|
|
782
1032
|
/**
|
|
@@ -785,41 +1035,67 @@ const __fsMod = (() => {
|
|
|
785
1035
|
* content carried. One place, because every sync mutation owes all three.
|
|
786
1036
|
*/
|
|
787
1037
|
function _parkWrite(k, cell) {
|
|
1038
|
+
_noteCreation(k);
|
|
788
1039
|
__vfsWrites[k] = cell;
|
|
789
1040
|
if (__vfsBundle) __vfsBundle[k] = cell;
|
|
790
1041
|
delete __vfsBundleRevisions[k];
|
|
791
|
-
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* The paths this process itself created, whose owner and mode are its own:
|
|
1046
|
+
* its uid and gid, and what its umask leaves. Every other stat answer comes
|
|
1047
|
+
* from the authority's metadata, and a path the authority never described
|
|
1048
|
+
* is not given one. A stat invented from the reader's own credential made a
|
|
1049
|
+
* root-owned file read as the reader's own and writable.
|
|
1050
|
+
*
|
|
1051
|
+
* A path counts as created when nothing was there before the write and the
|
|
1052
|
+
* view KNOWS nothing was there. Unknown is not created: a file the process
|
|
1053
|
+
* never staged may exist at the authority, owned by someone else.
|
|
1054
|
+
*/
|
|
1055
|
+
const _createdHere = new Set();
|
|
1056
|
+
function _noteCreation(k) {
|
|
1057
|
+
if (_createdHere.has(k)) return;
|
|
1058
|
+
const absPath = "/" + k;
|
|
1059
|
+
// Known absent: the namespace names everything this credential can see.
|
|
1060
|
+
if (_statLadder(absPath) !== undefined) return;
|
|
1061
|
+
_createdHere.add(k);
|
|
1062
|
+
}
|
|
1063
|
+
function _forgetCreation(k) {
|
|
1064
|
+
const prefix = k + "/";
|
|
1065
|
+
for (const key of [..._createdHere]) if (key === k || key.startsWith(prefix)) _createdHere.delete(key);
|
|
792
1066
|
}
|
|
793
1067
|
|
|
794
1068
|
function _forgetSyncTree(k) {
|
|
795
1069
|
const prefix = k + "/";
|
|
1070
|
+
_forgetCreation(k);
|
|
1071
|
+
_nsOwnSet(k, "absentTree");
|
|
796
1072
|
for (const dir of _announcedDirs) if (dir.startsWith(prefix)) _announcedDirs.delete(dir);
|
|
797
|
-
const metadata = _metadataTable();
|
|
798
|
-
if (metadata) {
|
|
799
|
-
for (const mk of Object.keys(metadata)) if (mk.startsWith(prefix)) delete metadata[mk];
|
|
800
|
-
}
|
|
801
|
-
if (__vfsManifest) {
|
|
802
|
-
for (const dk of Object.keys(__vfsManifest)) if (dk.startsWith(prefix)) delete __vfsManifest[dk];
|
|
803
|
-
}
|
|
804
1073
|
_forgetSyncPath(k);
|
|
805
1074
|
}
|
|
806
1075
|
|
|
807
1076
|
/**
|
|
808
1077
|
* The honest error for a sync read the resident view cannot serve. A facet
|
|
809
1078
|
* has no synchronous I/O primitive, so a sync read is limited to content
|
|
810
|
-
* staged into this process
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
* instead of awaiting the read that would return it.
|
|
1079
|
+
* staged into this process, but the namespace names every path the
|
|
1080
|
+
* credential can see. ENOENT is correct only when it names none; for a path
|
|
1081
|
+
* that exists it would send the caller hunting for a missing file instead
|
|
1082
|
+
* of awaiting the read that would return it.
|
|
815
1083
|
*/
|
|
816
1084
|
function _notResidentError(absPath, displayPath, syscall, asyncForm) {
|
|
817
1085
|
const st = _statLadder(absPath);
|
|
818
|
-
if (st === undefined)
|
|
819
|
-
if (!_absenceIsKnown(absPath)) _recordUnmapped(absPath, syscall);
|
|
820
|
-
return _fsErr("ENOENT", syscall, displayPath);
|
|
821
|
-
}
|
|
1086
|
+
if (st === undefined) return _fsErr("ENOENT", syscall, displayPath);
|
|
822
1087
|
if (st.isDirectory()) return _fsErr("EISDIR", syscall, displayPath);
|
|
1088
|
+
// Unreadable to this credential: the namespace's mode says so, and no
|
|
1089
|
+
// bytes could have been staged for it.
|
|
1090
|
+
if (!_modeAllows(st, 4)) return _fsErr("EACCES", syscall, displayPath);
|
|
1091
|
+
if (typeof __residentStorageMiss === "function" && __residentStorageMiss(_strip(absPath))) {
|
|
1092
|
+
// The process's own write, which neither its store nor its heap budget
|
|
1093
|
+
// could hold: the workspace's storage is full.
|
|
1094
|
+
const full = _fsErr("ENOSPC", syscall, displayPath);
|
|
1095
|
+
full.message = "ENOSPC: workspace storage is full; '" + String(displayPath) + "' is readable asynchronously"
|
|
1096
|
+
+ (_supervisor() ? " (" + asyncForm + ")" : "");
|
|
1097
|
+
return full;
|
|
1098
|
+
}
|
|
823
1099
|
_recordResidencyMiss(absPath);
|
|
824
1100
|
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
825
1101
|
err.message += " — '" + String(displayPath) + "' exists but its content is not " +
|
|
@@ -953,7 +1229,9 @@ const __fsMod = (() => {
|
|
|
953
1229
|
let meta;
|
|
954
1230
|
// A thrown stat is the authority failing to answer, not an answer:
|
|
955
1231
|
// nothing is learned, and the miss stands.
|
|
956
|
-
|
|
1232
|
+
// Queued on the path: the program's own later mutations of it must not overtake the stat.
|
|
1233
|
+
const observe = () => __nimbusUseRpcResult(supervisor.stat(absPath), (r) => r);
|
|
1234
|
+
try { meta = await (_hasVfsMutationQueue() ? __nimbusQueueVfsMutation(absPath, observe, false) : observe()); }
|
|
957
1235
|
catch { return; }
|
|
958
1236
|
if (meta === null || meta === undefined) { _observedAbsent.add(k); return; }
|
|
959
1237
|
if (meta.type === "directory") return;
|
|
@@ -991,193 +1269,46 @@ const __fsMod = (() => {
|
|
|
991
1269
|
}
|
|
992
1270
|
};
|
|
993
1271
|
|
|
994
|
-
/**
|
|
995
|
-
* Has this directory's content been enumerated, or merely mentioned?
|
|
996
|
-
*
|
|
997
|
-
* __vfsManifest is a walk of SELECTED roots — the cwd subtree, its
|
|
998
|
-
* node_modules, the entry script's own package — not of the filesystem. A
|
|
999
|
-
* directory with an entry there was listed by that walk, so its child list
|
|
1000
|
-
* is complete and a name that is not in it is genuinely not there. A
|
|
1001
|
-
* directory with no entry was never opened, and knows nothing. __vfsDirs
|
|
1002
|
-
* holds the directories this process created itself, which it therefore
|
|
1003
|
-
* knows the contents of by construction.
|
|
1004
|
-
*
|
|
1005
|
-
* Conflating the two is what let a synchronous readdir of a real directory
|
|
1006
|
-
* outside the walk answer with an empty array — not an error, not a refusal,
|
|
1007
|
-
* a positive assertion that a populated directory is empty. Every caller
|
|
1008
|
-
* that used to end in "not found, so absent" goes through here first.
|
|
1009
|
-
*/
|
|
1010
|
-
function _dirEnumerated(k) {
|
|
1011
|
-
return (!!__vfsManifest && k in __vfsManifest) || (!!__vfsDirs && k in __vfsDirs);
|
|
1012
|
-
}
|
|
1013
1272
|
|
|
1014
|
-
/** Does the content bundle itself describe children under this directory? */
|
|
1015
|
-
function _bundleHasChildren(k) {
|
|
1016
|
-
if (!__vfsBundle) return false;
|
|
1017
|
-
return __residentAnyUnder(k ? k + "/" : "");
|
|
1018
|
-
}
|
|
1019
1273
|
|
|
1020
|
-
/**
|
|
1021
|
-
* Can the view say this path is ABSENT, as opposed to say nothing at all?
|
|
1022
|
-
*
|
|
1023
|
-
* With no supervisor bound there is no authority to be ignorant of: the
|
|
1024
|
-
* staged tables ARE the filesystem, the async form reads out of those same
|
|
1025
|
-
* tables, and a name that is not in them is not anywhere. Ignorance is a
|
|
1026
|
-
* property of the gap between a process and an authority it cannot reach
|
|
1027
|
-
* synchronously, so where there is no authority there is no gap.
|
|
1028
|
-
*/
|
|
1029
|
-
function _absenceIsKnown(absPath) {
|
|
1030
|
-
if (!_supervisor()) return true;
|
|
1031
|
-
const k = _strip(absPath);
|
|
1032
|
-
if (k === "") return true;
|
|
1033
|
-
const segments = k.split("/");
|
|
1034
|
-
// Deepest enumerated ancestor, because that is the only one that can
|
|
1035
|
-
// testify. Walking up rather than looking only at the immediate parent is
|
|
1036
|
-
// what makes a whole missing subtree answerable from one listing: if
|
|
1037
|
-
// $HOME was enumerated and holds no .config, then nothing under .config
|
|
1038
|
-
// is there either, and a program probing three levels down deserves that
|
|
1039
|
-
// answer rather than three refusals.
|
|
1040
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1041
|
-
const dir = segments.slice(0, i).join("/");
|
|
1042
|
-
if (!_dirEnumerated(dir)) continue;
|
|
1043
|
-
const child = segments.slice(0, i + 1).join("/");
|
|
1044
|
-
const children = (__vfsManifest && __vfsManifest[dir]) || [];
|
|
1045
|
-
const present = children.indexOf(segments[i]) !== -1
|
|
1046
|
-
|| (!!__vfsDirs && child in __vfsDirs)
|
|
1047
|
-
|| (!!__vfsBundle && child in __vfsBundle);
|
|
1048
|
-
// The component is not in a directory that was listed: absent, and so is
|
|
1049
|
-
// everything below it. If it IS there, only its own enumeration settles
|
|
1050
|
-
// what it contains, so nothing short of the immediate parent will do.
|
|
1051
|
-
if (!present) return true;
|
|
1052
|
-
return i === segments.length - 1;
|
|
1053
|
-
}
|
|
1054
|
-
return false;
|
|
1055
|
-
}
|
|
1056
1274
|
|
|
1057
|
-
/**
|
|
1058
|
-
* A path the sync view never mapped: record the ignorance, and repair it.
|
|
1059
|
-
*
|
|
1060
|
-
* Sibling of _notResidentError and the same bargain — the file may well be
|
|
1061
|
-
* there, the process simply cannot look without blocking. The callers answer
|
|
1062
|
-
* ENOENT, which is provisional rather than fabricated: this records the miss
|
|
1063
|
-
* and pulls in the listing that settles the question, and the exit report
|
|
1064
|
-
* reads the ledger. If the path was genuinely absent the repair proves it
|
|
1065
|
-
* and the entry retires — the program's not-found branch was the right
|
|
1066
|
-
* branch and it reached it through the right errno. If the path WAS there,
|
|
1067
|
-
* the entry survives and the run is failed by name.
|
|
1068
|
-
*
|
|
1069
|
-
* EAGAIN was tried here and is the wrong answer to hand a program even
|
|
1070
|
-
* though it is the honest description of the situation. It cannot arise from
|
|
1071
|
-
* a real POSIX filesystem, so nothing branches on it, and the catch block
|
|
1072
|
-
* that receives it was written for a missing file and rethrows instead:
|
|
1073
|
-
* measured, create-next-app reads $HOME/.config/<tool>/config.json through
|
|
1074
|
-
* the conf package, is handed EAGAIN for a file that was never there, and
|
|
1075
|
-
* dies before writing one template file. Read a config, treat ENOENT as "no
|
|
1076
|
-
* config yet", rethrow anything else is the near-universal shape. Refusing
|
|
1077
|
-
* bought nothing the ledger was not already delivering, and cost every
|
|
1078
|
-
* program that idiom.
|
|
1079
|
-
*
|
|
1080
|
-
* A LISTING is the exception and still refuses — see readdirSync. An array is
|
|
1081
|
-
* a complete enumeration by definition, so there is no provisional form of
|
|
1082
|
-
* it: an empty one asserts that a directory nothing ever opened is empty,
|
|
1083
|
-
* which is how a scaffolder was told its template directory held nothing,
|
|
1084
|
-
* wrote nothing, and exited 0.
|
|
1085
|
-
*/
|
|
1086
|
-
function _recordUnmapped(absPath, syscall) {
|
|
1087
|
-
const k = _strip(absPath);
|
|
1088
|
-
_recordMiss(k);
|
|
1089
|
-
// A listing wants the path's own contents; a read wants its bytes. Both
|
|
1090
|
-
// also want the boundary listing, which is the only thing that can prove
|
|
1091
|
-
// the path was not there — and fetching one repair and then the other
|
|
1092
|
-
// would refuse the same access twice, once for being unmapped and once for
|
|
1093
|
-
// having no resident content. _faultOnce collapses them where they meet.
|
|
1094
|
-
_faultInBoundary(k);
|
|
1095
|
-
if (syscall === "scandir") { _faultInDirectory(k); return; }
|
|
1096
|
-
_faultIn(k);
|
|
1097
|
-
}
|
|
1098
1275
|
|
|
1099
|
-
/**
|
|
1100
|
-
* The shallowest directory on this path whose contents the view does not
|
|
1101
|
-
* know: the child of the deepest ancestor that WAS enumerated.
|
|
1102
|
-
*
|
|
1103
|
-
* That, not the immediate parent, is the listing a refusal has to pull in.
|
|
1104
|
-
* For a path that is not there the parent is usually not there either, so
|
|
1105
|
-
* its readdir fails, the view learns nothing, and the ledger keeps an entry
|
|
1106
|
-
* no repair can ever retire — the run is failed over a file that was simply
|
|
1107
|
-
* absent. The boundary directory is different: the enumerated ancestor's own
|
|
1108
|
-
* listing already proved it is present, so it can be read, and reading it
|
|
1109
|
-
* either names the next component or proves the whole subtree absent.
|
|
1110
|
-
*
|
|
1111
|
-
* With nothing enumerated at all the boundary is the root, which is one
|
|
1112
|
-
* listing away from making the next level answerable.
|
|
1113
|
-
*/
|
|
1114
|
-
function _unknownBoundary(k) {
|
|
1115
|
-
const segments = k.split("/");
|
|
1116
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1117
|
-
if (_dirEnumerated(segments.slice(0, i).join("/"))) {
|
|
1118
|
-
return segments.slice(0, i + 1).join("/");
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
return "";
|
|
1122
|
-
}
|
|
1123
1276
|
|
|
1124
|
-
function _faultInBoundary(k) {
|
|
1125
|
-
_faultInDirectory(_unknownBoundary(k));
|
|
1126
|
-
}
|
|
1127
1277
|
|
|
1128
|
-
/**
|
|
1129
|
-
* The refusal, for the two callers that have no provisional answer to give.
|
|
1130
|
-
*
|
|
1131
|
-
* A listing cannot say "not found" usefully, and neither can a write that
|
|
1132
|
-
* needs the prior content it is splicing onto. Everywhere else the sync view
|
|
1133
|
-
* answers ENOENT and lets the exit report settle whether that was true; here
|
|
1134
|
-
* there is no branch the program could take that would be right, so the
|
|
1135
|
-
* honest EAGAIN is also the useful one.
|
|
1136
|
-
*/
|
|
1137
|
-
function _refuseUnmapped(absPath, displayPath, syscall, asyncForm) {
|
|
1138
|
-
_recordUnmapped(absPath, syscall);
|
|
1139
|
-
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
1140
|
-
err.message += " — '" + String(displayPath) + "' lies outside the filesystem view "
|
|
1141
|
-
+ "staged into the process, so a synchronous call cannot tell what is there, "
|
|
1142
|
-
+ "and synchronous I/O cannot block to find out"
|
|
1143
|
-
+ (_supervisor() ? "; " + asyncForm + " answers from the live filesystem" : "");
|
|
1144
|
-
return err;
|
|
1145
|
-
}
|
|
1146
1278
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
// What the authority says of a path after this process's own async request
|
|
1282
|
+
// (a stat, read or accepted write) is what the sync view answers for it
|
|
1283
|
+
// from then on, as it would after the delta that reports it
|
|
1284
|
+
// (facet-resident-store's __nsNoteLiveStat).
|
|
1285
|
+
//
|
|
1286
|
+
// The lstat rides the read batch (_queueBatchRequest) rather than a call
|
|
1287
|
+
// of its own: a resumption refetches every path it owes at once
|
|
1288
|
+
// (_acquireAndRefetch), and a learn per path made that one lstat call per
|
|
1289
|
+
// path — 1,601 in flight from one facet after a program wrote 1,600 files,
|
|
1290
|
+
// some of which stayed pending and never reached the session
|
|
1291
|
+
// (preview/new/lucide-barrel-cache-widens, measured 2026-09-28). Batched,
|
|
1292
|
+
// the learns cost a round trip per batch, as the reads before them do.
|
|
1293
|
+
async function _learnLive(absPath, supervisor) {
|
|
1294
|
+
if (!supervisor || typeof supervisor.fsReadBatch !== "function") return;
|
|
1295
|
+
let stat;
|
|
1296
|
+
try { stat = await _queueBatchRequest(supervisor, { path: absPath, lstat: true }, 0); } catch { return; }
|
|
1297
|
+
__nsNoteLiveStat(_strip(absPath), stat ?? null);
|
|
1156
1298
|
}
|
|
1157
1299
|
|
|
1158
1300
|
/**
|
|
1159
|
-
* Retire the
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
* was
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
* over that would fail every module resolver, which probes dozens of paths
|
|
1166
|
-
* that do not exist by design. So what stays in the ledger is exactly the
|
|
1167
|
-
* harmful set: content that exists and was not served.
|
|
1301
|
+
* Retire the misses a repair turned into honest absence. A miss is a wrong
|
|
1302
|
+
* answer only when the path was THERE and the process was denied it: one on
|
|
1303
|
+
* a name the namespace does not have (or the authority did not have when it
|
|
1304
|
+
* was asked) was the program's not-found branch, taken for the right
|
|
1305
|
+
* reason. Failing the run over those would fail every module resolver,
|
|
1306
|
+
* which probes dozens of paths that do not exist by design.
|
|
1168
1307
|
*/
|
|
1169
1308
|
function _settleProvenAbsences() {
|
|
1170
1309
|
if (_residencyMisses.size === 0) return;
|
|
1171
1310
|
for (const k of [..._residencyMisses]) {
|
|
1172
|
-
|
|
1173
|
-
// The authority did not have it when we asked, so nothing was withheld —
|
|
1174
|
-
// whatever is at the path now, this process put there.
|
|
1175
|
-
if (_observedAbsent.has(k)) { _residencyMisses.delete(k); continue; }
|
|
1176
|
-
if (_statLadder(absPath) !== undefined) continue;
|
|
1177
|
-
if (_absenceIsKnown(absPath)) { _residencyMisses.delete(k); continue; }
|
|
1178
|
-
// Still unprovable, because the listing that would settle it lies below
|
|
1179
|
-
// the one already fetched. Ask for the next one; the settle loop waits.
|
|
1180
|
-
_faultInBoundary(k);
|
|
1311
|
+
if (_observedAbsent.has(k) || (_nsActive() && _statLadder("/" + k) === undefined)) _residencyMisses.delete(k);
|
|
1181
1312
|
}
|
|
1182
1313
|
}
|
|
1183
1314
|
|
|
@@ -1246,6 +1377,10 @@ const __fsMod = (() => {
|
|
|
1246
1377
|
|
|
1247
1378
|
const _localTimes = globalThis.__nimbusVfsTimes || (globalThis.__nimbusVfsTimes = Object.create(null));
|
|
1248
1379
|
const _localModes = globalThis.__nimbusVfsModes || (globalThis.__nimbusVfsModes = Object.create(null));
|
|
1380
|
+
// Modes set locally that the authority has not received yet. Delivered once:
|
|
1381
|
+
// re-sending on every flush made each read of the path a chmod, which bumped
|
|
1382
|
+
// its revision and evicted the process's own cell (create-astro EAGAIN).
|
|
1383
|
+
const _pendingModes = globalThis.__nimbusVfsPendingModes || (globalThis.__nimbusVfsPendingModes = new Set());
|
|
1249
1384
|
|
|
1250
1385
|
function _coerceMode(value, syscall, p) {
|
|
1251
1386
|
const n = typeof value === "string" ? parseInt(value, 8) : Number(value);
|
|
@@ -1270,6 +1405,8 @@ const __fsMod = (() => {
|
|
|
1270
1405
|
atimeMs: _coerceTimeMs(atime, syscall, p),
|
|
1271
1406
|
mtimeMs: _coerceTimeMs(mtime, syscall, p),
|
|
1272
1407
|
};
|
|
1408
|
+
// The sync view answers these times ahead of the namespace's (_statObject)
|
|
1409
|
+
// until the utimes is reported back, as a write's own bytes are.
|
|
1273
1410
|
_localTimes[k] = time;
|
|
1274
1411
|
return time;
|
|
1275
1412
|
}
|
|
@@ -1324,7 +1461,7 @@ const __fsMod = (() => {
|
|
|
1324
1461
|
// ══ Cache coherence: ACQUIRE + read-through fill ═════════════════════
|
|
1325
1462
|
//
|
|
1326
1463
|
// The supervisor's SQLite VFS is the only authority. Everything in this
|
|
1327
|
-
// facet —
|
|
1464
|
+
// facet — the resident store, __vfsBundle, __vfsDirs — is a
|
|
1328
1465
|
// cache of it, and __vfsWrites is this process's own not-yet-flushed
|
|
1329
1466
|
// mutations.
|
|
1330
1467
|
//
|
|
@@ -1352,6 +1489,24 @@ const __fsMod = (() => {
|
|
|
1352
1489
|
// a frame arrives as a supervisor reply the same barrier
|
|
1353
1490
|
// rides on (see the relayed WebSocket below).
|
|
1354
1491
|
//
|
|
1492
|
+
// The supervisor's own deliveries are resumptions too. Each takes
|
|
1493
|
+
// __nimbusInboundBarrier before the program sees it, and each carries the
|
|
1494
|
+
// barrier's answer, computed by the supervisor after the thing delivered
|
|
1495
|
+
// was queued (protocol §3's piggybacked inv), so the barrier applies it
|
|
1496
|
+
// instead of asking and the delivery costs no round trip of its own:
|
|
1497
|
+
// - request: a request routed to one of the process's ports, in the
|
|
1498
|
+
// dispatch every resident's server shares (__nimbusServeHttp).
|
|
1499
|
+
// The supervisor starts it, so it holds no cursor of this
|
|
1500
|
+
// process's and answers "nothing since now", which stands in
|
|
1501
|
+
// only when no write preceded the request since this process
|
|
1502
|
+
// last caught up (protocol §9.2); otherwise the barrier asks.
|
|
1503
|
+
// - stdin: a stdin packet — input, its end, a signal, a resize — for an
|
|
1504
|
+
// attached process (__makeProcessStdin's pump).
|
|
1505
|
+
// - child: a spawned child's output and its exit (_runReadLoop,
|
|
1506
|
+
// _runWaitLoop).
|
|
1507
|
+
// Stdin and child output are long polls, so they send this process's
|
|
1508
|
+
// ACQUIRE arguments (_acquireArgs) and are answered from exactly those.
|
|
1509
|
+
//
|
|
1355
1510
|
// Keep this list empty. An entry here is a documented hole in the owner's
|
|
1356
1511
|
// invariant, not a TODO: it means some process can be woken by something
|
|
1357
1512
|
// this system does not mediate, and its next synchronous read can serve
|
|
@@ -1373,8 +1528,18 @@ const __fsMod = (() => {
|
|
|
1373
1528
|
// envelope — there is no reason to invent a second surface for it.
|
|
1374
1529
|
const _stats = globalThis.__nimbusVfsCoherence
|
|
1375
1530
|
|| (globalThis.__nimbusVfsCoherence = {
|
|
1376
|
-
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0,
|
|
1531
|
+
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0, pushes: 0,
|
|
1377
1532
|
reconciles: 0, selfWrites: 0, misses: 0,
|
|
1533
|
+
// ACQUIREs that got no answer (see _acquireBarrier), and the last reason.
|
|
1534
|
+
barrierFailures: 0, lastBarrierFailure: "",
|
|
1535
|
+
// Barriers that held their resumption on an own write's acknowledgement
|
|
1536
|
+
// (_awaitReportedOwnWrites).
|
|
1537
|
+
ownWriteWaits: 0,
|
|
1538
|
+
// The namespace's failure state (a relist failed): synchronous calls
|
|
1539
|
+
// refused with its cause (_nsRequire), and barriers that repaired it.
|
|
1540
|
+
namespaceRefusals: 0, namespaceRepairs: 0,
|
|
1541
|
+
// Repairs a delta from the store's durable floor answered, with no listing.
|
|
1542
|
+
floorRepairs: 0,
|
|
1378
1543
|
});
|
|
1379
1544
|
|
|
1380
1545
|
/**
|
|
@@ -1407,11 +1572,122 @@ const __fsMod = (() => {
|
|
|
1407
1572
|
let evicted = false;
|
|
1408
1573
|
delete __vfsBundleRevisions[k];
|
|
1409
1574
|
if (__vfsBundle && k in __vfsBundle) { delete __vfsBundle[k]; evicted = true; }
|
|
1410
|
-
const metadata = _metadataTable();
|
|
1411
|
-
if (metadata && k in metadata) { delete metadata[k]; evicted = true; }
|
|
1412
1575
|
return evicted;
|
|
1413
1576
|
}
|
|
1414
1577
|
|
|
1578
|
+
// Cells an own-mutation lease evicted with no barrier in hand.
|
|
1579
|
+
//
|
|
1580
|
+
// A barrier hands _acquireAndRefetch every path it dropped, which is what
|
|
1581
|
+
// keeps a sync read inside a timer callback reading the peer's new bytes
|
|
1582
|
+
// instead of EAGAIN. A lease's end drops a cell on its own account — the
|
|
1583
|
+
// barrier that would have reported it was suppressed by the lease itself
|
|
1584
|
+
// — so the same debt is recorded here and settled by the next untrusted
|
|
1585
|
+
// resumption.
|
|
1586
|
+
const _owedRefetch = new Set();
|
|
1587
|
+
|
|
1588
|
+
// An own-mutation lease can end in an eviction (a peer wrote inside the
|
|
1589
|
+
// window), and so can a flush whose revision a barrier's report outran.
|
|
1590
|
+
// The ledger owning those decisions is spliced AHEAD of this closure, so it
|
|
1591
|
+
// is handed the one eviction path rather than keeping a second copy of it:
|
|
1592
|
+
// content view, stat view, the invalidation count and the refetch debt
|
|
1593
|
+
// move together here or not at all.
|
|
1594
|
+
function _evictOwed(k) {
|
|
1595
|
+
if (!_evictResident(k)) return;
|
|
1596
|
+
_stats.invalidations++;
|
|
1597
|
+
_owedRefetch.add(k);
|
|
1598
|
+
}
|
|
1599
|
+
globalThis.__nimbusEvictResidentCell = _evictOwed;
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Live reads in flight, per path: each one's fill ticket, which carries
|
|
1603
|
+
* the highest revision a barrier reported for the path while that read was
|
|
1604
|
+
* outstanding.
|
|
1605
|
+
*
|
|
1606
|
+
* A barrier that reports a path nobody holds has nothing to evict and
|
|
1607
|
+
* CONSUMES the report: the cursor moves past it and no later delta names it
|
|
1608
|
+
* again. A read of that path already in flight may have been served before
|
|
1609
|
+
* the mutation the report describes, and its bytes would then be installed
|
|
1610
|
+
* behind the only message that could have evicted them. So the report is
|
|
1611
|
+
* kept on the read until it lands, and the read installs only if nothing
|
|
1612
|
+
* newer than its cursor was reported under it (_installResident).
|
|
1613
|
+
*
|
|
1614
|
+
* One ticket per read, not one per path: a read that begins after a report
|
|
1615
|
+
* or a poison is judged only by what is reported after it began. Sharing the
|
|
1616
|
+
* earlier read's record condemned the read issued to replace it.
|
|
1617
|
+
*/
|
|
1618
|
+
const _fillReports = new Map();
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Begin a live read of \`k\`: the cursor it is issued under — every mutation
|
|
1622
|
+
* at or below it is already in the bytes it returns — and what has been
|
|
1623
|
+
* reported against it since, nothing yet. Called after the read's barrier
|
|
1624
|
+
* and before its RPC.
|
|
1625
|
+
*/
|
|
1626
|
+
function _beginFill(k) {
|
|
1627
|
+
const fill = { k, rev: _cursor.rev, reported: -1 };
|
|
1628
|
+
let live = _fillReports.get(k);
|
|
1629
|
+
if (!live) {
|
|
1630
|
+
live = new Set();
|
|
1631
|
+
_fillReports.set(k, live);
|
|
1632
|
+
}
|
|
1633
|
+
live.add(fill);
|
|
1634
|
+
return fill;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function _endFill(fill) {
|
|
1638
|
+
const live = _fillReports.get(fill.k);
|
|
1639
|
+
if (!live) return;
|
|
1640
|
+
live.delete(fill);
|
|
1641
|
+
if (live.size === 0) _fillReports.delete(fill.k);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/** A barrier reported \`k\` at \`rev\`: remember it on every read of it in flight. */
|
|
1645
|
+
function _noteFillReport(k, rev) {
|
|
1646
|
+
const live = _fillReports.get(k);
|
|
1647
|
+
if (live) for (const fill of live) if (rev > fill.reported) fill.reported = rev;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/**
|
|
1651
|
+
* Note one delta entry on everything in flight that it covers: its path,
|
|
1652
|
+
* or, for a subtree-scoped or structural entry, every path at or under it.
|
|
1653
|
+
* The ledger does the same for this facet's own writes and mutations.
|
|
1654
|
+
*/
|
|
1655
|
+
function _noteReport(entry) {
|
|
1656
|
+
if (!entry.subtree && !entry.structural) {
|
|
1657
|
+
__nimbusNoteVfsReport(entry.path, entry.rev);
|
|
1658
|
+
_noteFillReport(entry.path, entry.rev);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
__nimbusNoteVfsReportUnder(entry.path, entry.rev);
|
|
1662
|
+
const under = entry.path + "/";
|
|
1663
|
+
for (const k of [..._fillReports.keys()]) {
|
|
1664
|
+
if (k === entry.path || k.startsWith(under)) _noteFillReport(k, entry.rev);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* The resident cells a delta entry covers: its path, or, for a
|
|
1670
|
+
* subtree-scoped or structural entry, every cell at or under it. Such an
|
|
1671
|
+
* entry stands for changes it does not name (a directory that went, one
|
|
1672
|
+
* whose mode, owner or group changed, or something beneath it this facet
|
|
1673
|
+
* may not see), so each cell there is judged as a named one would be.
|
|
1674
|
+
*/
|
|
1675
|
+
function _coveredCells(entry) {
|
|
1676
|
+
if (!entry.subtree && !entry.structural) return [entry.path];
|
|
1677
|
+
const under = entry.path + "/";
|
|
1678
|
+
const cells = new Set();
|
|
1679
|
+
for (const table of [__vfsBundle, __vfsBundleRevisions]) {
|
|
1680
|
+
if (!table) continue;
|
|
1681
|
+
for (const k of Object.keys(table)) if (k === entry.path || k.startsWith(under)) cells.add(k);
|
|
1682
|
+
}
|
|
1683
|
+
return [...cells];
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/** The cursor moved without a delta (a poison), so no read in flight can be dated. */
|
|
1687
|
+
function _spoilFills() {
|
|
1688
|
+
for (const live of _fillReports.values()) for (const fill of live) fill.reported = Infinity;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1415
1691
|
/**
|
|
1416
1692
|
* Install bytes the supervisor just served as the resident cell for a
|
|
1417
1693
|
* path, at the cursor they were served under.
|
|
@@ -1424,42 +1700,30 @@ const __fsMod = (() => {
|
|
|
1424
1700
|
* returned, so it has nothing to invalidate. The bytes are already paid
|
|
1425
1701
|
* for; caching them costs one map insert.
|
|
1426
1702
|
*
|
|
1703
|
+
* \`fill\` (_beginFill) dates them, and it is also the reason an install can
|
|
1704
|
+
* be declined: a barrier that reported the path above the read's cursor
|
|
1705
|
+
* while the read was in flight may describe a mutation the bytes predate,
|
|
1706
|
+
* and that report is gone once consumed. Declining costs the next sync
|
|
1707
|
+
* read a miss; installing would cost a stale byte nothing ever evicts.
|
|
1708
|
+
*
|
|
1427
1709
|
* The parent's manifest entry gains the name too, so the existence view
|
|
1428
1710
|
* cannot go on denying a file whose bytes this process is holding.
|
|
1429
1711
|
*/
|
|
1430
|
-
function _installResident(absPath, bytes) {
|
|
1712
|
+
function _installResident(absPath, bytes, fill) {
|
|
1431
1713
|
if (!__vfsBundle) return;
|
|
1432
1714
|
const k = _strip(absPath);
|
|
1433
1715
|
if (k === "") return;
|
|
1716
|
+
if (fill.reported > fill.rev) return;
|
|
1434
1717
|
// Never over a cell this facet owns.
|
|
1435
1718
|
//
|
|
1436
1719
|
// A pending write is strictly newer than anything the authority can
|
|
1437
1720
|
// report, and __vfsBundle is what sync reads consult first, so installing
|
|
1438
1721
|
// over it would serve the program bytes older than its own write.
|
|
1439
|
-
//
|
|
1440
|
-
// A STAMPED cell is this facet's own flushed write, and the barrier this
|
|
1441
|
-
// read passed on the way in reported nobody else has touched the path
|
|
1442
|
-
// since — so the bytes are already what is being installed, and the only
|
|
1443
|
-
// thing the install would change is to drop the stamp. It did: a
|
|
1444
|
-
// speculative repair issued by an earlier refused read landed after the
|
|
1445
|
-
// flush, unstamped the cell, and the next ACQUIRE evicted the facet's own
|
|
1446
|
-
// output. Measured at selfWrites 0 / invalidations 1 / fills 2 for one
|
|
1447
|
-
// written file, against 1 / 0 / 0 with the repair absent.
|
|
1448
1722
|
if (__vfsWrites && k in __vfsWrites) return;
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
//
|
|
1452
|
-
|
|
1453
|
-
// spawn-time length, so a program can read N bytes and be told the
|
|
1454
|
-
// file is M — an inconsistency introduced BY the fill. Only the size
|
|
1455
|
-
// is corrected: type, mode, ownership and timestamps stay as the
|
|
1456
|
-
// authority last reported them, so mtime-based change detection
|
|
1457
|
-
// (make, tsc --build, watchers) does not see every read as a change.
|
|
1458
|
-
const metadata = _metadataTable();
|
|
1459
|
-
if (metadata && k in metadata) {
|
|
1460
|
-
metadata[k] = { ...metadata[k], size: _byteLen(bytes) };
|
|
1461
|
-
}
|
|
1462
|
-
_announceSyncPath(k);
|
|
1723
|
+
// The store dates the row at the read's cursor, so the next delta that
|
|
1724
|
+
// names the path above it evicts it. It declines, itself, to replace own
|
|
1725
|
+
// bytes or a row dated later than this read can vouch for.
|
|
1726
|
+
if (!__residentFill(k, bytes, fill.rev)) return;
|
|
1463
1727
|
_stats.fills++;
|
|
1464
1728
|
_stats.filledBytes += _byteLen(bytes);
|
|
1465
1729
|
}
|
|
@@ -1495,25 +1759,86 @@ const __fsMod = (() => {
|
|
|
1495
1759
|
return _residentRepair;
|
|
1496
1760
|
}
|
|
1497
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* Set while the store holds only what a repair could NOT vouch for: rows it
|
|
1764
|
+
* had to drop rather than prove current. Nothing but another repair brings
|
|
1765
|
+
* those back — a delta names what changed, not what this facet lost — so
|
|
1766
|
+
* every barrier repairs until one vouches, however the ACQUIRE is answered.
|
|
1767
|
+
*/
|
|
1768
|
+
let _storeRepairOwed = false;
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* The repair's first try: the delta from the store's durable floor
|
|
1772
|
+
* (__residentCursor), asked as every barrier asks it. True when it applied
|
|
1773
|
+
* (the repair is done); false when a listing is needed after all.
|
|
1774
|
+
*/
|
|
1775
|
+
async function _repairFromFloor(supervisor, result) {
|
|
1776
|
+
if (!supervisor || typeof supervisor.fsAcquire !== "function" || !_nsActive()) return false;
|
|
1777
|
+
if (result !== null && result.poison === true) return false;
|
|
1778
|
+
const floor = __residentCursor();
|
|
1779
|
+
if (floor === null || (result !== null && result.epoch !== floor.epoch)) return false;
|
|
1780
|
+
let delta;
|
|
1781
|
+
try {
|
|
1782
|
+
delta = await __nimbusUseRpcResult(supervisor.fsAcquire(floor.epoch, floor.rev, { namespace: true }), (r) => r);
|
|
1783
|
+
} catch { return false; }
|
|
1784
|
+
if (!delta || delta.poison === true || typeof delta.rev !== "number" || delta.epoch !== floor.epoch) return false;
|
|
1785
|
+
if (Array.isArray(delta.paths)) for (const entry of delta.paths) _noteReport(entry);
|
|
1786
|
+
await __residentRoomForPushed(delta);
|
|
1787
|
+
const applied = __residentAdmit(delta);
|
|
1788
|
+
for (const dir of applied.relist) await __nsRelist(supervisor, dir);
|
|
1789
|
+
if (!_nsActive()) return false;
|
|
1790
|
+
_cursor.epoch = applied.cursor.epoch;
|
|
1791
|
+
_cursor.rev = applied.cursor.rev;
|
|
1792
|
+
_stats.invalidations += applied.dropped.length;
|
|
1793
|
+
// What it should hold and lost (dropped by this delta, or by the barrier
|
|
1794
|
+
// that could not vouch for its rows), refetched by the namespace's stats.
|
|
1795
|
+
const refilled = await __residentRefillFromNamespace(supervisor, applied.dropped);
|
|
1796
|
+
_stats.fills += refilled.filled;
|
|
1797
|
+
_stats.filledBytes += refilled.bytes;
|
|
1798
|
+
_stats.floorRepairs++;
|
|
1799
|
+
_storeRepairOwed = false;
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* \`result\` is the ACQUIRE answer that asked for the repair: a poison, a
|
|
1805
|
+
* delta arriving while a repair is owed, or null for a barrier that got no
|
|
1806
|
+
* answer at all.
|
|
1807
|
+
*/
|
|
1498
1808
|
async function _runResidentRepair(supervisor, result) {
|
|
1809
|
+
// Within one epoch, with the change log intact and the namespace
|
|
1810
|
+
// answering, the store's rows are dated through its own durable cursor, so
|
|
1811
|
+
// the delta from there names everything that moved since. Only an epoch
|
|
1812
|
+
// change, a poison (the log truncated past the cursor), or a namespace
|
|
1813
|
+
// that stopped answering needs the listing.
|
|
1814
|
+
if (await _repairFromFloor(supervisor, result)) return;
|
|
1815
|
+
const ownAtStart = __residentOwnPaths();
|
|
1499
1816
|
let repaired;
|
|
1500
1817
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1501
1818
|
catch { repaired = null; }
|
|
1502
1819
|
if (!repaired || !repaired.cursor) {
|
|
1503
1820
|
// The pass could not vouch for a single row — a supervisor replaced
|
|
1504
|
-
// mid-enumeration, an enumeration that came back short
|
|
1505
|
-
// cannot be dated must not be served, so the
|
|
1506
|
-
//
|
|
1507
|
-
// it just dropped.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1821
|
+
// mid-enumeration, an enumeration that came back short, an authority it
|
|
1822
|
+
// could not reach. A row that cannot be dated must not be served, so the
|
|
1823
|
+
// cold cache is taken after all, and the pass runs once more to
|
|
1824
|
+
// repopulate what it just dropped. An answer with a cursor is admitted as
|
|
1825
|
+
// it would have been; a barrier with no answer drops every dated row and
|
|
1826
|
+
// keeps the cursor it had, which the next delta is still owed from.
|
|
1827
|
+
if (result) {
|
|
1828
|
+
__residentAdmit(result);
|
|
1829
|
+
_cursor.epoch = result.epoch;
|
|
1830
|
+
_cursor.rev = result.rev;
|
|
1831
|
+
} else {
|
|
1832
|
+
__residentDropDated();
|
|
1833
|
+
}
|
|
1511
1834
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1512
1835
|
catch { repaired = null; }
|
|
1513
1836
|
} else if (repaired.reconciled) {
|
|
1514
1837
|
_stats.reconciles++;
|
|
1515
1838
|
}
|
|
1516
|
-
|
|
1839
|
+
_settleSkippedReports(ownAtStart, repaired);
|
|
1840
|
+
_storeRepairOwed = !(repaired && repaired.cursor);
|
|
1841
|
+
if (_storeRepairOwed) return;
|
|
1517
1842
|
_cursor.epoch = repaired.cursor.epoch;
|
|
1518
1843
|
_cursor.rev = repaired.cursor.rev;
|
|
1519
1844
|
_stats.invalidations += repaired.dropped;
|
|
@@ -1521,6 +1846,41 @@ const __fsMod = (() => {
|
|
|
1521
1846
|
_stats.filledBytes += repaired.bytes;
|
|
1522
1847
|
}
|
|
1523
1848
|
|
|
1849
|
+
/**
|
|
1850
|
+
* Deliver the reports a repair moved the cursor over.
|
|
1851
|
+
*
|
|
1852
|
+
* A repair moves the cursor without a delta, so what a delta would have
|
|
1853
|
+
* reported reaches nothing on its own. A dated row the pass kept is proven
|
|
1854
|
+
* current by its listed revision and needs nothing more. A row of this
|
|
1855
|
+
* facet's own bytes is kept whatever the listing says — and its report is
|
|
1856
|
+
* exactly what the write or mutation that owns it needs, to judge its own
|
|
1857
|
+
* acknowledgement against. The pass hands those back (\`own\`), and they are
|
|
1858
|
+
* noted here as a delta's would have been; one acknowledged since the pass
|
|
1859
|
+
* looked was dated by a receipt that could not see the report, so it is
|
|
1860
|
+
* judged here directly.
|
|
1861
|
+
*
|
|
1862
|
+
* With nothing datable — across incarnations, against a short listing, or
|
|
1863
|
+
* a pass that failed outright — every own row, and every row that was own
|
|
1864
|
+
* when the repair began, is judged as though a peer wrote it last: an
|
|
1865
|
+
* eviction and a refetch, never a stale byte.
|
|
1866
|
+
*/
|
|
1867
|
+
function _settleSkippedReports(ownAtStart, repaired) {
|
|
1868
|
+
const reports = new Map();
|
|
1869
|
+
if (repaired && repaired.cursor && repaired.reconciled && Array.isArray(repaired.own)) {
|
|
1870
|
+
for (const entry of repaired.own) reports.set(entry.path, entry.rev);
|
|
1871
|
+
} else {
|
|
1872
|
+
for (const k of ownAtStart) reports.set(k, Infinity);
|
|
1873
|
+
if (repaired && Array.isArray(repaired.own)) for (const entry of repaired.own) reports.set(entry.path, Infinity);
|
|
1874
|
+
for (const k of Object.keys(__vfsOwnLeases)) reports.set(k, Infinity);
|
|
1875
|
+
for (const k of Object.keys(__vfsWrites)) reports.set(k, Infinity);
|
|
1876
|
+
}
|
|
1877
|
+
for (const [k, rev] of reports) {
|
|
1878
|
+
__nimbusNoteVfsReport(k, rev);
|
|
1879
|
+
const provenance = __residentProvenance(k);
|
|
1880
|
+
if (provenance !== undefined && provenance !== -1 && provenance < rev) _evictOwed(k);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1524
1884
|
/**
|
|
1525
1885
|
* ACQUIRE. Apply every invalidation the supervisor has for this facet,
|
|
1526
1886
|
* then re-stamp the cursor.
|
|
@@ -1546,60 +1906,247 @@ const __fsMod = (() => {
|
|
|
1546
1906
|
* left. Comparing revisions rather than names keeps the peer case intact:
|
|
1547
1907
|
* a peer writing the same path afterwards reports a HIGHER revision than
|
|
1548
1908
|
* our stamp, so that invalidation still lands.
|
|
1909
|
+
*
|
|
1910
|
+
* An ACQUIRE that gets no answer is a poison too, never an empty delta. It
|
|
1911
|
+
* has learned nothing about what changed, and the resumption behind it is
|
|
1912
|
+
* about to serve every row it holds. The call is a pure read, so a dropped
|
|
1913
|
+
* one is already retried where the supervisor makes it (SupervisorRPC,
|
|
1914
|
+
* fabric's idempotent), and what arrives here failed those retries, came
|
|
1915
|
+
* from a host that could not serve it, or answered without a cursor. The
|
|
1916
|
+
* rows are repaired against the listing exactly as for a poison; with no
|
|
1917
|
+
* cursor to adopt, the one held is kept and still owed every change since.
|
|
1918
|
+
* The barrier itself never throws: the operation or callback behind it
|
|
1919
|
+
* runs either way, against rows that were vouched for or a colder cache.
|
|
1920
|
+
*
|
|
1921
|
+
* A repair is single-flight, and while one runs no delta may be admitted
|
|
1922
|
+
* at all: the repair has already dropped the rows it could not vouch for,
|
|
1923
|
+
* so a report of one of them finds nothing to evict and is consumed, and
|
|
1924
|
+
* the repair then installs it at its listing's revision and publishes the
|
|
1925
|
+
* listing's cursor over the one the delta advanced. So any barrier answered
|
|
1926
|
+
* while a repair runs joins it, whether its own answer needed a repair or
|
|
1927
|
+
* was an ordinary delta, and the listing may predate that answer — the rows
|
|
1928
|
+
* it vouches for are another barrier's answer, not this one's — so a joiner
|
|
1929
|
+
* waits for it and then asks again: a delta from the repaired cursor names
|
|
1930
|
+
* everything since. Only the barrier that started a repair resumes on it
|
|
1931
|
+
* alone.
|
|
1932
|
+
*
|
|
1933
|
+
* \`delivered\` is an answer the supervisor delivered with the resumption
|
|
1934
|
+
* this barrier is taken for; it stands in for the first ACQUIRE when it can
|
|
1935
|
+
* (_deliveredAnswer). Everything after is the same for an answer delivered
|
|
1936
|
+
* and one asked for, so a repair, a join or a poison follows exactly the
|
|
1937
|
+
* path it always has.
|
|
1938
|
+
*
|
|
1939
|
+
* Last, the barrier waits out every own write or mutation that a report —
|
|
1940
|
+
* this answer's or an earlier one's — is outstanding against, until its
|
|
1941
|
+
* acknowledgement says which came first (__nimbusReportedOwnAcknowledgements).
|
|
1942
|
+
* Until then the resumption could run on own bytes the authority applied
|
|
1943
|
+
* before a peer overwrote them. Nothing is waited on unless such a report
|
|
1944
|
+
* exists.
|
|
1549
1945
|
*/
|
|
1550
|
-
async function _acquireBarrier(supervisor) {
|
|
1946
|
+
async function _acquireBarrier(supervisor, delivered) {
|
|
1551
1947
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return [];
|
|
1552
|
-
|
|
1553
|
-
//
|
|
1554
|
-
//
|
|
1555
|
-
//
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
// .then never ran, no error, no output, intermittently, and never when a
|
|
1559
|
-
// pending timer happened to hold the program open.
|
|
1560
|
-
try { result = await __nimbusUseRpcResult(supervisor.fsAcquire(_cursor.epoch, _cursor.rev), (r) => r); }
|
|
1561
|
-
catch { return []; }
|
|
1562
|
-
if (!result || typeof result.rev !== "number") return [];
|
|
1563
|
-
// Paths that held content before this eviction are the ones worth
|
|
1564
|
-
// refetching at a resumption boundary — a later sync read of any of them
|
|
1565
|
-
// would otherwise miss. Collected before the delete so the membership
|
|
1566
|
-
// test is against the pre-eviction bundle.
|
|
1567
|
-
const wereResident = [];
|
|
1948
|
+
const fromDelivery = _deliveredAnswer(delivered);
|
|
1949
|
+
// The overlay of this facet's own structural effects retires what settled
|
|
1950
|
+
// before the answer was asked for: a delivered answer was asked for when
|
|
1951
|
+
// its arguments were built, a fetched one now.
|
|
1952
|
+
const begin = fromDelivery ? _argsBegin(delivered.args) : ++_barrierBegins;
|
|
1953
|
+
let result = fromDelivery || await _acquire(supervisor);
|
|
1568
1954
|
// When the resident set lives in the facet's own SQLite, the STORE applies
|
|
1569
1955
|
// the delta. That is not an optimisation, it is where provenance has to
|
|
1570
1956
|
// live: a facet's SQLite outlives its module scope, so a new incarnation
|
|
1571
1957
|
// opens onto rows a previous one wrote while every heap-side stamp that
|
|
1572
1958
|
// described them is gone. Rows carry their own revision, __vfsBundleRevisions
|
|
1573
1959
|
// cannot follow them there, and two provenance stores would be one too many.
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1960
|
+
{
|
|
1961
|
+
// A namespace that stopped answering (a relist failed) is a failure
|
|
1962
|
+
// state, not a mode: every barrier the process reaches repairs it, once,
|
|
1963
|
+
// until a listing restores it.
|
|
1964
|
+
let namespaceAsked = false;
|
|
1965
|
+
for (let joins = 0; ; joins++) {
|
|
1966
|
+
const joining = _residentRepair !== null;
|
|
1967
|
+
const namespaceOwed = !namespaceAsked && !_nsActive();
|
|
1968
|
+
// A sealed heap view can release acknowledged overlays before its
|
|
1969
|
+
// listing; otherwise the old overlay and replacement rows would
|
|
1970
|
+
// consume the same scarce allowance while preventing each other's
|
|
1971
|
+
// repair. Nothing synchronous may consult the sealed view here.
|
|
1972
|
+
if (namespaceOwed && __residentInHeap) _nsRetire(begin);
|
|
1973
|
+
const needsRepair = result === null || result.poison === true || _storeRepairOwed || namespaceOwed;
|
|
1974
|
+
if (!joining && !needsRepair) break;
|
|
1975
|
+
if (namespaceOwed) { namespaceAsked = true; _stats.namespaceRepairs++; }
|
|
1976
|
+
if (needsRepair) {
|
|
1977
|
+
if (result !== null && result.poison === true) _stats.poisons++;
|
|
1978
|
+
_spoilFills();
|
|
1979
|
+
}
|
|
1980
|
+
await (joining ? _residentRepair : _repairPoisonedStore(supervisor, result));
|
|
1981
|
+
// A repair's listing is walked page by page at successive revisions,
|
|
1982
|
+
// so even the barrier that started it asks once more: the delta from
|
|
1983
|
+
// the listing's cursor leaves the namespace exact. Unanswered, it
|
|
1984
|
+
// resumes on the repair's rows as they are, which the listing vouched
|
|
1985
|
+
// for; the namespace then waits for the next barrier.
|
|
1986
|
+
if (!joining) {
|
|
1987
|
+
if (_storeRepairOwed) return [];
|
|
1988
|
+
result = await _acquire(supervisor);
|
|
1989
|
+
if (result === null || result.poison === true) { _nsRetire(begin); return []; }
|
|
1990
|
+
continue;
|
|
1991
|
+
}
|
|
1992
|
+
if (joins + 1 >= _REPAIR_JOINS_MAX) {
|
|
1993
|
+
// Every repair this barrier waited on was someone else's. Rather
|
|
1994
|
+
// than resume on rows none of them vouched for as of its own
|
|
1995
|
+
// answer, it drops them and leaves the repair owed: a colder cache,
|
|
1996
|
+
// never a stale byte. Any repair still to finish lists after this.
|
|
1997
|
+
__residentDropDated();
|
|
1998
|
+
_storeRepairOwed = true;
|
|
1999
|
+
__nimbusNoteUnnamedReports();
|
|
2000
|
+
await _awaitReportedOwnWrites();
|
|
2001
|
+
return [];
|
|
2002
|
+
}
|
|
2003
|
+
result = await _acquire(supervisor);
|
|
1581
2004
|
}
|
|
2005
|
+
// The store keeps a row of this facet's own bytes over any report, so
|
|
2006
|
+
// the report is noted first: the write or mutation that owns the row
|
|
2007
|
+
// judges it against its own revision when that arrives, as it does for
|
|
2008
|
+
// a heap cell. A read in flight gets the same note.
|
|
2009
|
+
if (Array.isArray(result.paths)) {
|
|
2010
|
+
for (const entry of result.paths) _noteReport(entry);
|
|
2011
|
+
}
|
|
2012
|
+
// N18: room for what it pushes, before it is held.
|
|
2013
|
+
await __residentRoomForPushed(result);
|
|
1582
2014
|
const applied = __residentAdmit(result);
|
|
1583
|
-
_cursor.epoch =
|
|
1584
|
-
_cursor.rev =
|
|
2015
|
+
_cursor.epoch = applied.cursor.epoch;
|
|
2016
|
+
_cursor.rev = applied.cursor.rev;
|
|
2017
|
+
// A directory that became searchable has descendants no delta names.
|
|
2018
|
+
for (const dir of applied.relist) await __nsRelist(supervisor, dir);
|
|
2019
|
+
_nsRetire(begin);
|
|
1585
2020
|
_stats.invalidations += applied.dropped.length;
|
|
2021
|
+
_stats.selfWrites += applied.kept;
|
|
2022
|
+
_stats.pushes += applied.pushed || 0;
|
|
2023
|
+
await _awaitReportedOwnWrites();
|
|
1586
2024
|
return applied.dropped;
|
|
1587
2025
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* Hold the resumption behind a barrier on every own write or mutation a
|
|
2030
|
+
* report is outstanding against, until its acknowledgement says which came
|
|
2031
|
+
* first (__nimbusReportedOwnAcknowledgements). Counted, because the wait is
|
|
2032
|
+
* also paid when the report was the write itself coming back, which the
|
|
2033
|
+
* facet cannot tell apart until the acknowledgement lands.
|
|
2034
|
+
*/
|
|
2035
|
+
async function _awaitReportedOwnWrites() {
|
|
2036
|
+
const acks = __nimbusReportedOwnAcknowledgements();
|
|
2037
|
+
if (acks === null) return;
|
|
2038
|
+
_stats.ownWriteWaits++;
|
|
2039
|
+
await acks;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/**
|
|
2043
|
+
* Repairs someone else started that one barrier waits on, in a row, before
|
|
2044
|
+
* it stops asking again and drops what it cannot vouch for. A bound on the
|
|
2045
|
+
* barrier's own liveness, not a tuning knob: each join waits a whole repair,
|
|
2046
|
+
* so the fourth means repairs keep being started under it.
|
|
2047
|
+
*/
|
|
2048
|
+
const _REPAIR_JOINS_MAX = 4;
|
|
2049
|
+
|
|
2050
|
+
/**
|
|
2051
|
+
* What this facet asks its ACQUIRE with: its cursor. Built in one place, so
|
|
2052
|
+
* what a delivery is answered from (a long poll sends these,
|
|
2053
|
+
* \`__nimbusVfsAcquireArgs\`) is exactly what fsAcquire would be asked.
|
|
2054
|
+
*/
|
|
2055
|
+
function _acquireArgs() {
|
|
2056
|
+
// Every build is the start of an answer the overlay may retire against.
|
|
2057
|
+
const begin = ++_barrierBegins;
|
|
2058
|
+
const key = _cursor.epoch + "@" + _cursor.rev;
|
|
2059
|
+
if (!_argsBegins.has(key)) _argsBegins.set(key, begin);
|
|
2060
|
+
const options = _residentStorePresent() && typeof __residentAcquireOptions === "function"
|
|
2061
|
+
? __residentAcquireOptions() : undefined;
|
|
2062
|
+
return options
|
|
2063
|
+
? { epoch: _cursor.epoch, cursor: _cursor.rev, options }
|
|
2064
|
+
: { epoch: _cursor.epoch, cursor: _cursor.rev };
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* The earliest begin of any arguments built at a cursor: a delivered answer
|
|
2068
|
+
* may answer any of them, and retiring against the earliest is the one
|
|
2069
|
+
* that is never too late. Cursors left behind are forgotten.
|
|
2070
|
+
*/
|
|
2071
|
+
const _argsBegins = new Map();
|
|
2072
|
+
function _argsBegin(args) {
|
|
2073
|
+
const key = args.epoch + "@" + args.cursor;
|
|
2074
|
+
const begin = _argsBegins.get(key) ?? 0;
|
|
2075
|
+
for (const k of [..._argsBegins.keys()]) {
|
|
2076
|
+
const [epoch, rev] = [k.slice(0, k.lastIndexOf("@")), Number(k.slice(k.lastIndexOf("@") + 1))];
|
|
2077
|
+
if (epoch !== _cursor.epoch || rev < _cursor.rev) _argsBegins.delete(k);
|
|
2078
|
+
}
|
|
2079
|
+
return begin;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
/**
|
|
2083
|
+
* One ACQUIRE: the authority's answer, or null when there is none — the
|
|
2084
|
+
* call failed past the supervisor's own retries, the host could not serve
|
|
2085
|
+
* it, or the answer carried no cursor. A null is counted where the
|
|
2086
|
+
* coherence stats are read, and is never an empty delta (_acquireBarrier).
|
|
2087
|
+
*/
|
|
2088
|
+
async function _acquire(supervisor) {
|
|
2089
|
+
// Through the RPC helper like every other supervisor call, because it IS
|
|
2090
|
+
// one: the barrier is the first thing an async read issues, and while it
|
|
2091
|
+
// was uncounted __nimbusPendingOps read zero for a whole round trip. A
|
|
2092
|
+
// one-shot facet's event loop, which exits when no handle is live, then
|
|
2093
|
+
// ended the program mid-read — measured as an fs.promises.readFile whose
|
|
2094
|
+
// .then never ran, no error, no output, intermittently, and never when a
|
|
2095
|
+
// pending timer happened to hold the program open.
|
|
2096
|
+
try {
|
|
2097
|
+
const args = _acquireArgs();
|
|
2098
|
+
const result = await __nimbusUseRpcResult(
|
|
2099
|
+
args.options ? supervisor.fsAcquire(args.epoch, args.cursor, args.options) : supervisor.fsAcquire(args.epoch, args.cursor),
|
|
2100
|
+
(r) => r,
|
|
2101
|
+
);
|
|
2102
|
+
if (!result || typeof result.rev !== "number" || typeof result.epoch !== "string") {
|
|
2103
|
+
throw new Error("fsAcquire answered without a cursor");
|
|
1598
2104
|
}
|
|
2105
|
+
return _currentAnswer(result);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
_stats.barrierFailures++;
|
|
2108
|
+
_stats.lastBarrierFailure = (error && error.message) || String(error);
|
|
2109
|
+
return null;
|
|
1599
2110
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
/**
|
|
2114
|
+
* An answer as it stands against this facet's cursor now.
|
|
2115
|
+
*
|
|
2116
|
+
* Answers can be admitted in another order than they were given: an
|
|
2117
|
+
* answer a long poll delivers was asked for before the barrier that
|
|
2118
|
+
* overtook it. Everything at or below the cursor is admitted already, by
|
|
2119
|
+
* whichever answer took the cursor past it — an answer is complete from
|
|
2120
|
+
* the cursor it was asked from, which is never ahead of the cursor now, and
|
|
2121
|
+
* names each path at its newest revision in its range. So only the entries
|
|
2122
|
+
* above the cursor are news, and the cursor never moves back. An answer in
|
|
2123
|
+
* another epoch, or a poison, is left as it is.
|
|
2124
|
+
*/
|
|
2125
|
+
function _currentAnswer(result) {
|
|
2126
|
+
if (result.poison === true || result.epoch !== _cursor.epoch) return result;
|
|
2127
|
+
if (result.rev <= _cursor.rev) return { ...result, rev: _cursor.rev, paths: [] };
|
|
2128
|
+
const paths = result.paths.filter((entry) => entry.rev > _cursor.rev);
|
|
2129
|
+
return paths.length === result.paths.length ? result : { ...result, paths };
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/**
|
|
2133
|
+
* The answer a delivery carried (session/rpc.ts _acquireOnDelivery), as it
|
|
2134
|
+
* stands against the cursor now, or null when it cannot stand in for
|
|
2135
|
+
* asking. It can when it answers from a cursor this facet has reached, in
|
|
2136
|
+
* this epoch: everything at or below that cursor is admitted, and the
|
|
2137
|
+
* answer names everything after it up to the moment of delivery. The
|
|
2138
|
+
* barrier asks, as it always has, when there is no answer or it is a
|
|
2139
|
+
* poison, in another epoch, or from a cursor this facet has not reached — a
|
|
2140
|
+
* routed request is answered from the supervisor's own cursor, "nothing
|
|
2141
|
+
* since now", which stands in only for a facet that is already there.
|
|
2142
|
+
*/
|
|
2143
|
+
function _deliveredAnswer(delivered) {
|
|
2144
|
+
if (!delivered || typeof delivered !== "object") return null;
|
|
2145
|
+
const { args, answer } = delivered;
|
|
2146
|
+
if (!args || !answer || answer.poison === true || !Array.isArray(answer.paths)) return null;
|
|
2147
|
+
if (typeof answer.rev !== "number" || answer.epoch !== _cursor.epoch || args.epoch !== _cursor.epoch) return null;
|
|
2148
|
+
if (!(args.cursor <= _cursor.rev)) return null;
|
|
2149
|
+
return _currentAnswer(answer);
|
|
1603
2150
|
}
|
|
1604
2151
|
|
|
1605
2152
|
/**
|
|
@@ -1621,11 +2168,81 @@ const __fsMod = (() => {
|
|
|
1621
2168
|
* This closes staleness of RESIDENT cells across an untrusted resumption.
|
|
1622
2169
|
* A first synchronous touch of a NON-resident path is the separate
|
|
1623
2170
|
* residency floor and is unaffected — it still raises EAGAIN, correctly.
|
|
2171
|
+
*
|
|
2172
|
+
* Two resumptions can barrier at once — a child's output and its exit
|
|
2173
|
+
* arrive together — and both deltas name the same changed path. The first
|
|
2174
|
+
* to land evicts it and starts the refetch; the second finds no row and
|
|
2175
|
+
* would release its callback while that refetch is still in flight, onto
|
|
2176
|
+
* a miss. So a barrier also waits for every refetch still in flight.
|
|
2177
|
+
* It does not read those paths again. A refetch in flight installs its
|
|
2178
|
+
* bytes unless a barrier has since reported the path above the cursor it
|
|
2179
|
+
* was issued under, or a poison spoiled it (_installResident). Only then
|
|
2180
|
+
* does a later barrier read the path itself, and only if nothing holds it
|
|
2181
|
+
* by now. Re-reading every in-flight path at every resumption cost a dev
|
|
2182
|
+
* server 50 reads per request while a peer's refetch of 50 files ran: 500
|
|
2183
|
+
* reads over ten resumptions, and under steady load the set never drained.
|
|
2184
|
+
*/
|
|
2185
|
+
async function _acquireAndRefetch(supervisor, delivered) {
|
|
2186
|
+
const stale = await _acquireBarrier(supervisor, delivered);
|
|
2187
|
+
// Plus what an own-mutation lease dropped since the last resumption:
|
|
2188
|
+
// the same debt this function exists to settle, owed by an eviction
|
|
2189
|
+
// that had no barrier to report it (see _owedRefetch).
|
|
2190
|
+
if (_owedRefetch.size > 0) {
|
|
2191
|
+
for (const k of _owedRefetch) if (stale.indexOf(k) === -1) stale.push(k);
|
|
2192
|
+
_owedRefetch.clear();
|
|
2193
|
+
}
|
|
2194
|
+
// Taken before this barrier's own refetches join the map.
|
|
2195
|
+
const inFlight = [..._refetching];
|
|
2196
|
+
const reads = stale.map(_refetch);
|
|
2197
|
+
for (const [k, refetch] of inFlight) {
|
|
2198
|
+
if (stale.indexOf(k) !== -1) continue;
|
|
2199
|
+
if (!(refetch.fill.reported > refetch.fill.rev)) reads.push(refetch.done);
|
|
2200
|
+
else if (!(__vfsBundle && k in __vfsBundle)) reads.push(_refetch(k));
|
|
2201
|
+
}
|
|
2202
|
+
if (reads.length > 0) await Promise.all(reads);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/**
|
|
2206
|
+
* Refetches in flight, per path: the newest read of it, and the fill
|
|
2207
|
+
* ticket that read installs under — the cursor it was issued at, and the
|
|
2208
|
+
* reports noted against it since.
|
|
1624
2209
|
*/
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2210
|
+
const _refetching = new Map();
|
|
2211
|
+
|
|
2212
|
+
/**
|
|
2213
|
+
* Read \`k\` live and install it, behind the barrier just taken. Settles
|
|
2214
|
+
* either way: a refetch that fails leaves the path missing, which the next
|
|
2215
|
+
* read of it answers.
|
|
2216
|
+
*
|
|
2217
|
+
* \`read\` settles once this read has landed and its install was made or
|
|
2218
|
+
* declined. \`done\`, which the resumptions wait on, may wait once more. An
|
|
2219
|
+
* install is declined when a barrier reported the path above this read's
|
|
2220
|
+
* cursor, or a poison spoiled it, and settling there would release every
|
|
2221
|
+
* waiter onto the miss it left while something else is already filling
|
|
2222
|
+
* the path: the newer refetch a barrier issued to replace this one, or the
|
|
2223
|
+
* repair that poison started. So \`done\` waits on what is in flight at the
|
|
2224
|
+
* moment this read settles, that refetch's own read and install and that
|
|
2225
|
+
* repair, and then settles whether or not the path is held. It never
|
|
2226
|
+
* follows a refetch issued after that. A peer that rewrites the path
|
|
2227
|
+
* faster than one read round trip outdates every refetch with the next
|
|
2228
|
+
* resumption's report, and following them held every resumption, including
|
|
2229
|
+
* those that never read the path, until the writer stopped (1528 ms against
|
|
2230
|
+
* 33 ms). A path left unheld is read live by its next read.
|
|
2231
|
+
*/
|
|
2232
|
+
function _refetch(k) {
|
|
2233
|
+
const fill = _beginFill(k);
|
|
2234
|
+
const refetch = { fill, read: null, done: null };
|
|
2235
|
+
refetch.read = _liveReadFile("/" + k, undefined, fill).then(() => {}, () => {}).then(() => {
|
|
2236
|
+
_endFill(fill);
|
|
2237
|
+
if (_refetching.get(k) === refetch) _refetching.delete(k);
|
|
2238
|
+
});
|
|
2239
|
+
refetch.done = refetch.read.then(() => {
|
|
2240
|
+
if (__vfsBundle && k in __vfsBundle) return undefined;
|
|
2241
|
+
const newer = _refetching.get(k);
|
|
2242
|
+
return Promise.all([newer && newer.read, _residentRepair]);
|
|
2243
|
+
});
|
|
2244
|
+
_refetching.set(k, refetch);
|
|
2245
|
+
return refetch.done;
|
|
1629
2246
|
}
|
|
1630
2247
|
|
|
1631
2248
|
/**
|
|
@@ -1637,14 +2254,15 @@ const __fsMod = (() => {
|
|
|
1637
2254
|
* its fetch handler; binding the supervisor when the wrappers are
|
|
1638
2255
|
* installed would silently leave every resident-TUI timer unbarriered.
|
|
1639
2256
|
*
|
|
1640
|
-
* Published on globalThis because the
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
2257
|
+
* Published on globalThis because the other resumptions — an outbound
|
|
2258
|
+
* fetch response, a relayed socket frame, and everything the supervisor
|
|
2259
|
+
* delivers (\`__nimbusInboundBarrier\`, with the answer it delivered) — are
|
|
2260
|
+
* handed to the program by code outside this closure.
|
|
1643
2261
|
*/
|
|
1644
|
-
async function _resumptionAcquire() {
|
|
2262
|
+
async function _resumptionAcquire(delivered) {
|
|
1645
2263
|
const supervisor = _supervisor();
|
|
1646
2264
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return;
|
|
1647
|
-
await _acquireAndRefetch(supervisor);
|
|
2265
|
+
await _acquireAndRefetch(supervisor, delivered);
|
|
1648
2266
|
}
|
|
1649
2267
|
|
|
1650
2268
|
/**
|
|
@@ -1678,6 +2296,7 @@ const __fsMod = (() => {
|
|
|
1678
2296
|
if (globalThis.__nimbusResumptionBarriersInstalled) return;
|
|
1679
2297
|
globalThis.__nimbusResumptionBarriersInstalled = true;
|
|
1680
2298
|
globalThis.__nimbusVfsAcquireBarrier = _resumptionAcquire;
|
|
2299
|
+
globalThis.__nimbusVfsAcquireArgs = _acquireArgs;
|
|
1681
2300
|
globalThis.__nimbusVfsReleaseBarrier = _resumptionRelease;
|
|
1682
2301
|
const _setTimeout = globalThis.setTimeout;
|
|
1683
2302
|
const _setInterval = globalThis.setInterval;
|
|
@@ -1724,7 +2343,16 @@ const __fsMod = (() => {
|
|
|
1724
2343
|
// supervisor.mkdir is recursive, so the deepest unannounced one creates all
|
|
1725
2344
|
// of them in a single round trip; a path whose directories are already live
|
|
1726
2345
|
// (the common case) costs none at all.
|
|
2346
|
+
//
|
|
2347
|
+
// This is also the one seam every asynchronous fs entry point crosses on
|
|
2348
|
+
// its way to the authority — directly, or through
|
|
2349
|
+
// _flushLocalPathToSupervisor — so it is where a live call waits for the
|
|
2350
|
+
// structural mutations the program issued synchronously before it:
|
|
2351
|
+
// mkdirSync, rmdirSync, unlinkSync, renameSync queue their authority RPC
|
|
2352
|
+
// (below) rather than land it, and an open/stat/readdir that overtook that
|
|
2353
|
+
// queue would be answered for a tree the program has already changed.
|
|
1727
2354
|
async function _announceLocalDirs(absPath, supervisor) {
|
|
2355
|
+
await _awaitStructuralOrder(absPath);
|
|
1728
2356
|
if (!__vfsDirs || typeof supervisor.mkdir !== "function") return;
|
|
1729
2357
|
const pending = [];
|
|
1730
2358
|
let key = "";
|
|
@@ -1740,24 +2368,162 @@ const __fsMod = (() => {
|
|
|
1740
2368
|
_markVfsStale();
|
|
1741
2369
|
}
|
|
1742
2370
|
|
|
2371
|
+
// The write ledger is spliced ahead of the shims in every runner
|
|
2372
|
+
// (facets/manager.ts, opencode-facet-runner.ts). A harness that evaluates
|
|
2373
|
+
// the shims alone has no mutation queue and therefore nothing to wait for;
|
|
2374
|
+
// an absent ledger must not be an error on a read path that never needed
|
|
2375
|
+
// one before.
|
|
2376
|
+
function _hasVfsMutationQueue() {
|
|
2377
|
+
return typeof __nimbusQueueVfsMutation === "function";
|
|
2378
|
+
}
|
|
2379
|
+
function _awaitStructuralOrder(absPath) {
|
|
2380
|
+
return _hasVfsMutationQueue() ? __nimbusAwaitAncestorMutations(absPath) : Promise.resolve();
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/**
|
|
2384
|
+
* Carry a synchronous structural mutation to the authority.
|
|
2385
|
+
*
|
|
2386
|
+
* mkdirSync, rmdirSync, unlinkSync and renameSync used to edit the local
|
|
2387
|
+
* tables and stop: a sync syscall cannot make an RPC, and unlike a sync
|
|
2388
|
+
* write they parked nothing the write-back could later flush. Measured
|
|
2389
|
+
* live: node-tar's \`mkdirSync(dir)\` then \`fs.promises.open(dir/file,
|
|
2390
|
+
* "w")\` — once per extracted entry — was answered ENOENT by an authority
|
|
2391
|
+
* that had never heard of \`dir\`; that is create-astro's template copy.
|
|
2392
|
+
*
|
|
2393
|
+
* The sync effect stays exactly as it was. What is added is the same RPC
|
|
2394
|
+
* the asynchronous form of the call issues, queued through the write
|
|
2395
|
+
* ledger so it registers with the exit drain, is ordered behind pending
|
|
2396
|
+
* mutations of its ancestors (the ledger does that for every queued
|
|
2397
|
+
* mutation), and — for the two that act on a subtree — behind pending
|
|
2398
|
+
* mutations beneath it. The async forms are that same queued call, awaited.
|
|
2399
|
+
*
|
|
2400
|
+
* \`null\` when there is no authority to tell: standalone and unit contexts
|
|
2401
|
+
* keep today's local-only behaviour. Without a ledger the RPC is issued
|
|
2402
|
+
* directly, which is what the async forms did before they were queued.
|
|
2403
|
+
*/
|
|
2404
|
+
// \`method\` names the supervisor RPC when it differs from the syscall the
|
|
2405
|
+
// caller reports (lchown rides \`chown\`, rm rides \`fsRemove\`).
|
|
2406
|
+
function _queueStructuralMutation(absPath, syscall, displayPath, rpc, after, method) {
|
|
2407
|
+
const settle = _nsTakeFresh();
|
|
2408
|
+
const supervisor = _supervisor();
|
|
2409
|
+
if (!supervisor || typeof supervisor[method || syscall] !== "function") { settle(); return null; }
|
|
2410
|
+
const queued = _hasVfsMutationQueue();
|
|
2411
|
+
const before = queued && after ? after() : null;
|
|
2412
|
+
const run = async () => {
|
|
2413
|
+
if (before) await before;
|
|
2414
|
+
// Only chown answers with a receipt. The rest (mkdir, unlink, rmdir,
|
|
2415
|
+
// rename, rm) have already dropped the path's cell and stamp in their
|
|
2416
|
+
// synchronous half, so no lease is taken at all; where one is — the
|
|
2417
|
+
// stamp unlink leaves behind — an absent receipt retires it, which is
|
|
2418
|
+
// what an unstamped cell already costs.
|
|
2419
|
+
await _ownMutation(
|
|
2420
|
+
absPath,
|
|
2421
|
+
() => _fsRpc(rpc(supervisor), syscall, displayPath, (result) => result),
|
|
2422
|
+
);
|
|
2423
|
+
_markVfsStale();
|
|
2424
|
+
};
|
|
2425
|
+
const out = queued ? __nimbusQueueVfsMutation(absPath, run) : run();
|
|
2426
|
+
// Settled either way: a failed mutation leaves the authority as it was,
|
|
2427
|
+
// which the next barrier's table shows.
|
|
2428
|
+
Promise.resolve(out).then(settle, settle);
|
|
2429
|
+
return out;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/**
|
|
2433
|
+
* Run one of this facet's own partial mutations under a lease.
|
|
2434
|
+
*
|
|
2435
|
+
* See __nimbusBeginOwnMutation: the barrier KEEPS the cell while the RPC
|
|
2436
|
+
* is in flight — a barrier issued after this mutation can be answered
|
|
2437
|
+
* ahead of it, and an eviction there loses bytes no later stamp can
|
|
2438
|
+
* recover — and the receipt decides at the end. \`apply\` lands the local
|
|
2439
|
+
* effect while the lease still holds the cell, so the stamp the end
|
|
2440
|
+
* settles describes a cell that already carries the mutation.
|
|
2441
|
+
*
|
|
2442
|
+
* The ledger takes the lease on whichever holds the stamp: a heap stamp,
|
|
2443
|
+
* or the resident store's row. Without it a store row the mutation
|
|
2444
|
+
* overlaid would be left as own bytes nobody ever dates, which the barrier
|
|
2445
|
+
* keeps against every later write by anyone.
|
|
2446
|
+
*
|
|
2447
|
+
* From lease to receipt it is an acknowledgement in flight
|
|
2448
|
+
* (__nimbusOwnAcknowledgement): a barrier that reports the path waits for
|
|
2449
|
+
* it, rather than resume on bytes the receipt may yet show a peer outran.
|
|
2450
|
+
*/
|
|
2451
|
+
function _ownMutation(absPath, rpc, apply) {
|
|
2452
|
+
const key = _strip(absPath);
|
|
2453
|
+
return __nimbusOwnAcknowledgement(key, async () => {
|
|
2454
|
+
const held = __nimbusBeginOwnMutation(key);
|
|
2455
|
+
let receipt;
|
|
2456
|
+
let landed = false;
|
|
2457
|
+
try { receipt = await rpc(); landed = true; }
|
|
2458
|
+
finally {
|
|
2459
|
+
// The local effect follows the mutation landing, not the receipt: the
|
|
2460
|
+
// receipt only settles the stamp, and a supervisor that answers
|
|
2461
|
+
// without one still applied the bytes. The end runs even if applying
|
|
2462
|
+
// throws: a lease left open pins its cell at Infinity, which is the one
|
|
2463
|
+
// state in this protocol that can serve a stale byte forever.
|
|
2464
|
+
try { if (landed && apply) apply(receipt); }
|
|
2465
|
+
finally { __nimbusEndOwnMutation(key, held, receipt); }
|
|
2466
|
+
}
|
|
2467
|
+
return receipt;
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// A sync caller has no frame to receive the outcome. The ledger already
|
|
2472
|
+
// marks a queued result handled and retains what the exit drain must
|
|
2473
|
+
// report; this only keeps a ledger-less embedding from raising an
|
|
2474
|
+
// unhandled rejection for a verdict nobody could catch.
|
|
2475
|
+
function _detachStructuralMutation(mutation) {
|
|
2476
|
+
if (mutation) mutation.then(undefined, () => undefined);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// A rename lives at two names. Its mutation is queued under the source;
|
|
2480
|
+
// this parks a fence under the destination so that anything queued for the
|
|
2481
|
+
// destination, or an ancestor wait that passes through it, is ordered
|
|
2482
|
+
// behind the move as well. The fence never fails on its own account.
|
|
2483
|
+
function _fenceVfsMutation(absPath, mutation) {
|
|
2484
|
+
if (!mutation || !_hasVfsMutationQueue()) return;
|
|
2485
|
+
__nimbusQueueVfsMutation(absPath, () => mutation.then(() => undefined, () => undefined), false);
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// Queue the parked write for \`absPath\`, if any, on the path's mutation
|
|
2489
|
+
// tail. Registration is synchronous: a caller that queues its own mutation
|
|
2490
|
+
// on the same tail right after this call is ordered behind the flush.
|
|
2491
|
+
function _flushParkedWrite(absPath, supervisor) {
|
|
2492
|
+
const k = _strip(absPath);
|
|
2493
|
+
if (!__vfsWrites || !(k in __vfsWrites) || typeof supervisor.writeFile !== "function") {
|
|
2494
|
+
return Promise.resolve(undefined);
|
|
2495
|
+
}
|
|
2496
|
+
return __nimbusFlushVfsWrite(
|
|
2497
|
+
absPath,
|
|
2498
|
+
(content, snapshot) => _fsRpc(
|
|
2499
|
+
__nimbusPersistVfsWrite(supervisor, absPath, content, snapshot),
|
|
2500
|
+
"write", absPath,
|
|
2501
|
+
(result) => result,
|
|
2502
|
+
),
|
|
2503
|
+
true,
|
|
2504
|
+
true,
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
1743
2508
|
async function _flushLocalPathToSupervisor(absPath, supervisor) {
|
|
1744
2509
|
const k = _strip(absPath);
|
|
1745
2510
|
await _announceLocalDirs(absPath, supervisor);
|
|
1746
2511
|
if (__vfsWrites && k in __vfsWrites && typeof supervisor.writeFile === "function") {
|
|
1747
|
-
await
|
|
1748
|
-
absPath,
|
|
1749
|
-
(content, snapshot) => _fsRpc(
|
|
1750
|
-
__nimbusPersistVfsWrite(supervisor, absPath, content, snapshot),
|
|
1751
|
-
"write", absPath,
|
|
1752
|
-
(result) => result,
|
|
1753
|
-
),
|
|
1754
|
-
);
|
|
2512
|
+
await _flushParkedWrite(absPath, supervisor);
|
|
1755
2513
|
_markVfsStale();
|
|
1756
2514
|
}
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2515
|
+
// A pending sync chmod rides along with the next flush of the same path.
|
|
2516
|
+
if (_pendingModes.has(k) && typeof supervisor.chmod === "function") {
|
|
2517
|
+
_pendingModes.delete(k);
|
|
2518
|
+
try {
|
|
2519
|
+
await _ownMutation(
|
|
2520
|
+
absPath,
|
|
2521
|
+
() => _fsRpc(supervisor.chmod(absPath, _localModes[k]), "chmod", absPath, (result) => result),
|
|
2522
|
+
);
|
|
2523
|
+
} catch (error) {
|
|
2524
|
+
_pendingModes.add(k);
|
|
2525
|
+
throw error;
|
|
2526
|
+
}
|
|
1761
2527
|
_markVfsStale();
|
|
1762
2528
|
}
|
|
1763
2529
|
}
|
|
@@ -1845,23 +2611,36 @@ const __fsMod = (() => {
|
|
|
1845
2611
|
const atime = new Date(Number(meta?.atime || meta?.mtime || Date.now()));
|
|
1846
2612
|
const mode = Number(meta?.mode ?? (isDir ? 0o755 : 0o644));
|
|
1847
2613
|
const stat = _localStatObject(key, isDir, isSymlink, size, mode, meta?.uid, meta?.gid);
|
|
1848
|
-
|
|
1849
|
-
|
|
2614
|
+
// This process's own utimes, until it is reported back, ahead of the
|
|
2615
|
+
// namespace's (_recordLocalTimes).
|
|
2616
|
+
const own = key === undefined ? undefined : _localTimes[key];
|
|
2617
|
+
stat.atime = own && Number.isFinite(own.atimeMs) ? new Date(own.atimeMs) : atime;
|
|
2618
|
+
stat.mtime = own && Number.isFinite(own.mtimeMs) ? new Date(own.mtimeMs) : mtime;
|
|
1850
2619
|
stat.ctime = new Date(Number(meta?.ctime ?? meta?.mtime ?? Date.now()));
|
|
1851
2620
|
stat.birthtime = stat.ctime;
|
|
1852
2621
|
return stat;
|
|
1853
2622
|
}
|
|
1854
2623
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2624
|
+
// The one Dirent shape: readdir({ withFileTypes }) sync and async, and
|
|
2625
|
+
// every Dir.read(). \`parentPath\` is Node's field; \`path\` its deprecated
|
|
2626
|
+
// alias that older callers still read.
|
|
2627
|
+
class __Dirent {
|
|
2628
|
+
constructor(name, type, parentPath) {
|
|
2629
|
+
this.name = name;
|
|
2630
|
+
this.parentPath = parentPath === undefined ? "" : String(parentPath);
|
|
2631
|
+
this.path = this.parentPath;
|
|
2632
|
+
this._isDir = type === "directory" || type === "dir";
|
|
2633
|
+
this._isSymlink = type === "symlink";
|
|
2634
|
+
}
|
|
2635
|
+
isFile() { return !this._isDir && !this._isSymlink; }
|
|
2636
|
+
isDirectory() { return this._isDir; }
|
|
2637
|
+
isSymbolicLink() { return this._isSymlink; }
|
|
2638
|
+
isBlockDevice() { return false; }
|
|
2639
|
+
isCharacterDevice() { return false; }
|
|
2640
|
+
isFIFO() { return false; }
|
|
2641
|
+
isSocket() { return false; }
|
|
1864
2642
|
}
|
|
2643
|
+
function _direntObject(name, type, parentPath) { return new __Dirent(name, type, parentPath); }
|
|
1865
2644
|
|
|
1866
2645
|
// Largest single ranged read issued against the supervisor. Every live
|
|
1867
2646
|
// read path (read streams, whole-file async reads) is expressed as a
|
|
@@ -1869,24 +2648,32 @@ const __fsMod = (() => {
|
|
|
1869
2648
|
// multi-MB file for one RPC frame.
|
|
1870
2649
|
const READ_STREAM_CHUNK_BYTES = 65536;
|
|
1871
2650
|
|
|
1872
|
-
// Ranged reads issued in the same microtask turn travel as ONE batch
|
|
2651
|
+
// Ranged reads issued in the same microtask turn travel as ONE batch, and
|
|
2652
|
+
// so do lstats (_learnLive): one request kind each, one round trip.
|
|
1873
2653
|
//
|
|
1874
2654
|
// A round trip costs an order of magnitude more than the read behind it,
|
|
1875
2655
|
// so a program awaiting reads one at a time pays for round trips and
|
|
1876
2656
|
// nothing else. Nothing here changes what a read sees: every request is
|
|
1877
|
-
// the same live ranged read, executed in order, in the caller's
|
|
1878
|
-
// gather window is one microtask, so it can only capture
|
|
1879
|
-
// had already issued concurrently — a sequential
|
|
1880
|
-
// because it has issued nothing else to batch.
|
|
2657
|
+
// the same live ranged read or lstat, executed in order, in the caller's
|
|
2658
|
+
// turn. The gather window is one microtask, so it can only capture
|
|
2659
|
+
// requests the program had already issued concurrently — a sequential
|
|
2660
|
+
// loop batches nothing because it has issued nothing else to batch.
|
|
1881
2661
|
const READ_BATCH_PATH_LIMIT = ${FS_READ_BATCH_PATH_LIMIT};
|
|
1882
2662
|
const READ_BATCH_REQUEST_BYTES = ${FS_READ_BATCH_REQUEST_BYTES};
|
|
1883
2663
|
let _openReadBatch = null;
|
|
1884
2664
|
|
|
1885
2665
|
function _queueRangeRead(supervisor, absPath, pos, want) {
|
|
2666
|
+
return _queueBatchRequest(supervisor, { path: absPath, offset: pos, length: want }, want);
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
// \`request\` in the open batch, \`bytes\` of file content counted against the
|
|
2670
|
+
// batch's bound. Settles with the entry's bytes for a range, its stat for
|
|
2671
|
+
// an lstat.
|
|
2672
|
+
function _queueBatchRequest(supervisor, request, bytes) {
|
|
1886
2673
|
let batch = _openReadBatch;
|
|
1887
2674
|
if (batch && (
|
|
1888
2675
|
batch.requests.length >= READ_BATCH_PATH_LIMIT
|
|
1889
|
-
|| batch.bytes +
|
|
2676
|
+
|| batch.bytes + bytes > READ_BATCH_REQUEST_BYTES
|
|
1890
2677
|
)) {
|
|
1891
2678
|
_openReadBatch = null;
|
|
1892
2679
|
_flushReadBatch(batch);
|
|
@@ -1896,17 +2683,24 @@ const __fsMod = (() => {
|
|
|
1896
2683
|
batch = { supervisor, requests: [], settlers: [], bytes: 0 };
|
|
1897
2684
|
_openReadBatch = batch;
|
|
1898
2685
|
queueMicrotask(() => {
|
|
1899
|
-
|
|
2686
|
+
// A batch a bound closed was flushed then; sending it here too would
|
|
2687
|
+
// read every entry of it twice.
|
|
2688
|
+
if (_openReadBatch !== batch) return;
|
|
2689
|
+
_openReadBatch = null;
|
|
1900
2690
|
_flushReadBatch(batch);
|
|
1901
2691
|
});
|
|
1902
2692
|
}
|
|
1903
|
-
batch.bytes +=
|
|
1904
|
-
batch.requests.push(
|
|
1905
|
-
|
|
2693
|
+
batch.bytes += bytes;
|
|
2694
|
+
batch.requests.push(request);
|
|
2695
|
+
const settler = Promise.withResolvers();
|
|
2696
|
+
batch.settlers.push(settler);
|
|
2697
|
+
return settler.promise;
|
|
1906
2698
|
}
|
|
1907
2699
|
|
|
1908
2700
|
async function _flushReadBatch(batch) {
|
|
1909
|
-
|
|
2701
|
+
// A read round trip is one that carries a range; a batch of learns
|
|
2702
|
+
// (lstat requests) is metadata, which the read count leaves out.
|
|
2703
|
+
if (batch.requests.some((request) => request.lstat !== true)) globalThis.__nimbusFsRpcReads++;
|
|
1910
2704
|
try {
|
|
1911
2705
|
const entries = await __nimbusUseRpcResult(
|
|
1912
2706
|
batch.supervisor.fsReadBatch(batch.requests), (r) => r,
|
|
@@ -1917,6 +2711,8 @@ const __fsMod = (() => {
|
|
|
1917
2711
|
const err = new Error(entry.error.message);
|
|
1918
2712
|
if (entry.error.code) err.code = entry.error.code;
|
|
1919
2713
|
batch.settlers[i].reject(err);
|
|
2714
|
+
} else if (batch.requests[i].lstat === true) {
|
|
2715
|
+
batch.settlers[i].resolve(entry ? entry.stat : null);
|
|
1920
2716
|
} else {
|
|
1921
2717
|
batch.settlers[i].resolve(entry ? entry.bytes : null);
|
|
1922
2718
|
}
|
|
@@ -1981,59 +2777,83 @@ const __fsMod = (() => {
|
|
|
1981
2777
|
return parts;
|
|
1982
2778
|
}
|
|
1983
2779
|
|
|
1984
|
-
async function _liveReadFile(p, opts,
|
|
2780
|
+
async function _liveReadFile(p, opts, refetch) {
|
|
1985
2781
|
const absPath = _resolve(p);
|
|
1986
2782
|
const encoding = typeof opts === "string" ? opts : opts?.encoding;
|
|
1987
2783
|
const supervisor = _supervisor();
|
|
1988
2784
|
if (!supervisor) throw _fsErr("ENOENT", "open", p);
|
|
1989
|
-
//
|
|
1990
|
-
//
|
|
1991
|
-
// that always returns "still R". Every other caller
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2785
|
+
// A refetch (_refetch) has already acquired against a fresh cursor and
|
|
2786
|
+
// holds the fill ticket issued under it, so re-acquiring here would be a
|
|
2787
|
+
// redundant round trip that always returns "still R". Every other caller
|
|
2788
|
+
// acquires, and its fill ticket is its own.
|
|
2789
|
+
if (!refetch) await _acquireBarrier(supervisor);
|
|
2790
|
+
const fill = refetch || _beginFill(_strip(absPath));
|
|
2791
|
+
try {
|
|
2792
|
+
if (typeof supervisor.fsReadRange === "function") {
|
|
2793
|
+
// Chunked: the caller wants the whole file, but nothing upstream has
|
|
2794
|
+
// to hold it all at once to produce it.
|
|
2795
|
+
//
|
|
2796
|
+
// A short chunk is the only signal the file ended, so this walk could
|
|
2797
|
+
// only ever have one chunk in flight — 65 sequential round trips for a
|
|
2798
|
+
// 4 MiB file, each costing far more than the read behind it. The FIRST
|
|
2799
|
+
// chunk still costs exactly one trip and settles it for every file
|
|
2800
|
+
// that fits in one. When it comes back full there is demonstrably
|
|
2801
|
+
// more, and a stat says how much, so the remainder is issued together
|
|
2802
|
+
// and the read batch carries it in a single trip.
|
|
2803
|
+
const parts = [];
|
|
2804
|
+
let total = 0;
|
|
2805
|
+
for (;;) {
|
|
2806
|
+
const chunk = await _readRangeAt(absPath, p, total, READ_STREAM_CHUNK_BYTES);
|
|
2807
|
+
if (chunk === null) break;
|
|
2808
|
+
parts.push(chunk);
|
|
2809
|
+
total += chunk.byteLength;
|
|
2810
|
+
if (chunk.byteLength < READ_STREAM_CHUNK_BYTES) break;
|
|
2811
|
+
if (typeof supervisor.stat !== "function") continue;
|
|
2812
|
+
for (const rest of await _readChunksFrom(absPath, p, supervisor, total)) {
|
|
2813
|
+
parts.push(rest);
|
|
2814
|
+
total += rest.byteLength;
|
|
2815
|
+
}
|
|
2816
|
+
break;
|
|
2017
2817
|
}
|
|
2018
|
-
|
|
2818
|
+
const bytes = parts.length === 1 ? parts[0] : _concatBytes(parts, total);
|
|
2819
|
+
_installResident(absPath, bytes, fill);
|
|
2820
|
+
await _learnLive(absPath, supervisor);
|
|
2821
|
+
return encoding ? _asString(bytes) : __BufferMod.from(bytes);
|
|
2019
2822
|
}
|
|
2020
|
-
const bytes = parts.length === 1 ? parts[0] : _concatBytes(parts, total);
|
|
2021
|
-
_installResident(absPath, bytes);
|
|
2022
|
-
return encoding ? _asString(bytes) : __BufferMod.from(bytes);
|
|
2023
|
-
}
|
|
2024
2823
|
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2824
|
+
if (typeof supervisor.readFile === "function") {
|
|
2825
|
+
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2826
|
+
const text = await _fsReadRpc(supervisor.readFile(absPath), "open", p, (result) => result);
|
|
2827
|
+
if (text !== null && text !== undefined) {
|
|
2828
|
+
_installResident(absPath, text, fill);
|
|
2829
|
+
await _learnLive(absPath, supervisor);
|
|
2830
|
+
return encoding ? _asString(text) : __BufferMod.from(text);
|
|
2831
|
+
}
|
|
2031
2832
|
}
|
|
2833
|
+
} finally {
|
|
2834
|
+
if (!refetch) _endFill(fill);
|
|
2032
2835
|
}
|
|
2033
2836
|
|
|
2034
2837
|
throw _fsErr("ENOENT", "open", p);
|
|
2035
2838
|
}
|
|
2036
2839
|
|
|
2840
|
+
|
|
2841
|
+
|
|
2842
|
+
/** Paths whose write-back this process issued and the authority accepted. */
|
|
2843
|
+
const _acceptedHere = new Set();
|
|
2844
|
+
globalThis.__nimbusVfsWriteLanded = (key) => {
|
|
2845
|
+
const k = String(key).replace(/^\\/+/, "");
|
|
2846
|
+
_acceptedHere.add(k);
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// The authority refused a parked write (vfs-write-ledger): the record may
|
|
2850
|
+
// be that write's, so it is forgotten and the next access asks the authority.
|
|
2851
|
+
globalThis.__nimbusVfsWriteRefused = (key) => {
|
|
2852
|
+
const k = String(key).replace(/^\\/+/, "");
|
|
2853
|
+
_createdHere.delete(k);
|
|
2854
|
+
_acceptedHere.delete(k);
|
|
2855
|
+
};
|
|
2856
|
+
|
|
2037
2857
|
function _concatBytes(parts, total) {
|
|
2038
2858
|
const out = new Uint8Array(total);
|
|
2039
2859
|
let off = 0;
|
|
@@ -2105,27 +2925,14 @@ const __fsMod = (() => {
|
|
|
2105
2925
|
// its shape: the sync existence view must not go on contradicting a
|
|
2106
2926
|
// directory this very process has enumerated. Names only — size, mode
|
|
2107
2927
|
// and ownership are not observable here and are never invented.
|
|
2108
|
-
|
|
2109
|
-
//
|
|
2110
|
-
//
|
|
2111
|
-
//
|
|
2112
|
-
//
|
|
2113
|
-
// recording it invents nothing; file children are left alone, because
|
|
2114
|
-
// their size is not knowable from a listing and must not be guessed.
|
|
2115
|
-
const metadataTable = _metadataTable();
|
|
2116
|
-
if (metadataTable) {
|
|
2117
|
-
for (const entry of entries) {
|
|
2118
|
-
if (entry.type !== "directory") continue;
|
|
2119
|
-
const child = prefix + entry.name;
|
|
2120
|
-
if (child in metadataTable) continue;
|
|
2121
|
-
metadataTable[child] = {
|
|
2122
|
-
type: "directory", size: 0, mode: 0o755, uid: cred.uid, gid: cred.gid,
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2928
|
+
// The listed directory and its child directories get the authority's
|
|
2929
|
+
// own stat: a later synchronous stat or listing of them needs a record
|
|
2930
|
+
// saying what they are, and a record says who owns them, which a
|
|
2931
|
+
// listing does not. So it is asked for, never assumed (one lstat per
|
|
2932
|
+
// child directory, concurrently).
|
|
2126
2933
|
if (opts?.withFileTypes) {
|
|
2127
2934
|
return entries
|
|
2128
|
-
.map((entry) => _direntObject(entry.name, entry.type))
|
|
2935
|
+
.map((entry) => _direntObject(entry.name, entry.type, absPath))
|
|
2129
2936
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2130
2937
|
}
|
|
2131
2938
|
return entries.map((entry) => entry.name).sort();
|
|
@@ -2148,7 +2955,9 @@ const __fsMod = (() => {
|
|
|
2148
2955
|
async function _readlinkAsync(p) {
|
|
2149
2956
|
const supervisor = _supervisor();
|
|
2150
2957
|
if (supervisor && typeof supervisor.readlink === "function") {
|
|
2151
|
-
const
|
|
2958
|
+
const absPath = _resolve(p);
|
|
2959
|
+
await _awaitStructuralOrder(absPath);
|
|
2960
|
+
const target = await _fsRpc(supervisor.readlink(absPath), "readlink", p, (result) => result);
|
|
2152
2961
|
if (target !== null && target !== undefined) return target;
|
|
2153
2962
|
}
|
|
2154
2963
|
throw _fsErr("EINVAL", "readlink", p);
|
|
@@ -2157,7 +2966,9 @@ const __fsMod = (() => {
|
|
|
2157
2966
|
async function _symlinkAsync(target, path) {
|
|
2158
2967
|
const supervisor = _supervisor();
|
|
2159
2968
|
if (supervisor && typeof supervisor.symlink === "function") {
|
|
2160
|
-
|
|
2969
|
+
const absPath = _resolve(path);
|
|
2970
|
+
await _awaitStructuralOrder(absPath);
|
|
2971
|
+
await _fsRpc(supervisor.symlink(String(target), absPath), "symlink", path, () => undefined);
|
|
2161
2972
|
_markVfsStale();
|
|
2162
2973
|
return;
|
|
2163
2974
|
}
|
|
@@ -2170,10 +2981,14 @@ const __fsMod = (() => {
|
|
|
2170
2981
|
const supervisor = _supervisor();
|
|
2171
2982
|
if (supervisor && typeof supervisor.writeFile === "function") {
|
|
2172
2983
|
await _announceLocalDirs(absPath, supervisor);
|
|
2984
|
+
// The revision comes back so the ledger can stamp the cell: an async
|
|
2985
|
+
// whole write is the facet's own as much as a parked sync one is.
|
|
2173
2986
|
await __nimbusFlushVfsWrite(absPath, (content) =>
|
|
2174
|
-
_fsRpc(supervisor.writeFile(absPath, content), "write", p, () =>
|
|
2987
|
+
_fsRpc(supervisor.writeFile(absPath, content), "write", p, (result) => result)
|
|
2175
2988
|
);
|
|
2176
2989
|
_markVfsStale();
|
|
2990
|
+
// Accepted: the authority's stat is what the sync view keeps for it.
|
|
2991
|
+
await _learnLive(absPath, supervisor);
|
|
2177
2992
|
}
|
|
2178
2993
|
}
|
|
2179
2994
|
|
|
@@ -2192,43 +3007,16 @@ const __fsMod = (() => {
|
|
|
2192
3007
|
),
|
|
2193
3008
|
);
|
|
2194
3009
|
_markVfsStale();
|
|
3010
|
+
await _learnLive(absPath, supervisor);
|
|
2195
3011
|
}
|
|
2196
3012
|
|
|
2197
|
-
async
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
async function _unlinkAsync(p) {
|
|
2207
|
-
unlinkSync(p);
|
|
2208
|
-
const supervisor = _supervisor();
|
|
2209
|
-
if (supervisor && typeof supervisor.unlink === "function") {
|
|
2210
|
-
await _fsRpc(supervisor.unlink(_resolve(p)), "unlink", p, () => undefined);
|
|
2211
|
-
_markVfsStale();
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
async function _rmdirAsync(p) {
|
|
2216
|
-
rmdirSync(p);
|
|
2217
|
-
const supervisor = _supervisor();
|
|
2218
|
-
if (supervisor && typeof supervisor.rmdir === "function") {
|
|
2219
|
-
await _fsRpc(supervisor.rmdir(_resolve(p)), "rmdir", p, () => undefined);
|
|
2220
|
-
_markVfsStale();
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
async function _renameAsync(oldP, newP) {
|
|
2225
|
-
renameSync(oldP, newP);
|
|
2226
|
-
const supervisor = _supervisor();
|
|
2227
|
-
if (supervisor && typeof supervisor.rename === "function") {
|
|
2228
|
-
await _fsRpc(supervisor.rename(_resolve(oldP), _resolve(newP)), "rename", oldP, () => undefined);
|
|
2229
|
-
_markVfsStale();
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
3013
|
+
// The async structural calls ARE the sync ones, awaited: the same local
|
|
3014
|
+
// effect and the same queued authority RPC, so the two forms cannot
|
|
3015
|
+
// disagree about order, and a program mixing them sees one sequence.
|
|
3016
|
+
async function _mkdirAsync(p, opts) { await _mkdirQueued(p, opts); }
|
|
3017
|
+
async function _unlinkAsync(p) { await _unlinkQueued(p); }
|
|
3018
|
+
async function _rmdirAsync(p) { await _rmdirQueued(p); }
|
|
3019
|
+
async function _renameAsync(oldP, newP) { await _renameQueued(oldP, newP); }
|
|
2232
3020
|
|
|
2233
3021
|
async function _truncateAsync(p, len) {
|
|
2234
3022
|
const absPath = _resolve(p);
|
|
@@ -2246,6 +3034,9 @@ const __fsMod = (() => {
|
|
|
2246
3034
|
(content, snapshot) =>
|
|
2247
3035
|
__nimbusPersistVfsWrite(supervisor, absPath, content, snapshot),
|
|
2248
3036
|
);
|
|
3037
|
+
// Persisting the append drops the resident cell (the ledger
|
|
3038
|
+
// refetches it on the next live read), so there is no cell to
|
|
3039
|
+
// trim or stamp behind this truncate.
|
|
2249
3040
|
await __nimbusQueueVfsMutation(absPath, async () => {
|
|
2250
3041
|
await flush;
|
|
2251
3042
|
await _fsRpc(
|
|
@@ -2267,12 +3058,15 @@ const __fsMod = (() => {
|
|
|
2267
3058
|
// Live file is the source of truth — supervisor trims only the
|
|
2268
3059
|
// boundary chunk; ENOENT propagates when it does not exist.
|
|
2269
3060
|
const generation = __vfsWriteGenerations[k];
|
|
2270
|
-
await __nimbusQueueVfsMutation(absPath, () =>
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
3061
|
+
await __nimbusQueueVfsMutation(absPath, () => _ownMutation(
|
|
3062
|
+
absPath,
|
|
3063
|
+
() => _fsRpc(supervisor.fsTruncate(absPath, size), "truncate", p, (result) => result),
|
|
3064
|
+
() => {
|
|
3065
|
+
if (__vfsWriteGenerations[k] === generation && localCell !== undefined) {
|
|
3066
|
+
_truncateLocalCell(absPath, size);
|
|
3067
|
+
}
|
|
3068
|
+
},
|
|
3069
|
+
));
|
|
2276
3070
|
_markVfsStale();
|
|
2277
3071
|
return;
|
|
2278
3072
|
}
|
|
@@ -2292,9 +3086,9 @@ const __fsMod = (() => {
|
|
|
2292
3086
|
_recordLocalTimes(absPath, atime, mtime, "lutimes", p);
|
|
2293
3087
|
}
|
|
2294
3088
|
|
|
2295
|
-
async function _utimesAsync(p, atime, mtime, opts) {
|
|
3089
|
+
async function _utimesAsync(p, atime, mtime, opts, syscallOverride) {
|
|
2296
3090
|
const followSymlinks = !(opts && opts.followSymlinks === false);
|
|
2297
|
-
const syscall = followSymlinks ? "utimes" : "lutimes";
|
|
3091
|
+
const syscall = syscallOverride || (followSymlinks ? "utimes" : "lutimes");
|
|
2298
3092
|
const absPath = _resolve(p);
|
|
2299
3093
|
const supervisor = _supervisor();
|
|
2300
3094
|
let localExists = false;
|
|
@@ -2305,22 +3099,43 @@ const __fsMod = (() => {
|
|
|
2305
3099
|
const time = _recordLocalTimes(absPath, atime, mtime, syscall, p);
|
|
2306
3100
|
if (supervisor && typeof supervisor.utimes === "function") {
|
|
2307
3101
|
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2308
|
-
|
|
3102
|
+
// Ordered behind the path's pending mutations: an fd write queued a
|
|
3103
|
+
// moment ago (modern-tar writes, then futimes, then closes) would
|
|
3104
|
+
// otherwise land AFTER the timestamp and reset it to "now".
|
|
3105
|
+
const leased = () => _ownMutation(absPath, () => _fsRpc(
|
|
2309
3106
|
supervisor.utimes(absPath, time.atimeMs, time.mtimeMs),
|
|
2310
3107
|
syscall, p,
|
|
2311
|
-
() =>
|
|
2312
|
-
);
|
|
3108
|
+
(result) => result,
|
|
3109
|
+
));
|
|
3110
|
+
if (_hasVfsMutationQueue()) await __nimbusQueueVfsMutation(absPath, leased);
|
|
3111
|
+
else await leased();
|
|
2313
3112
|
_markVfsStale();
|
|
2314
3113
|
return;
|
|
2315
3114
|
}
|
|
2316
3115
|
if (!supervisor && !existsSync(p)) throw _fsErr("ENOENT", syscall, p);
|
|
2317
3116
|
}
|
|
2318
3117
|
|
|
3118
|
+
/**
|
|
3119
|
+
* chmod(2): only the owner or root changes a mode (EPERM). The local
|
|
3120
|
+
* overlay is what statSync reports and what the local permission checks
|
|
3121
|
+
* read, so a mode set here without that check made a root-owned file
|
|
3122
|
+
* writable in the process's own view.
|
|
3123
|
+
*/
|
|
3124
|
+
function _ensureModeOwner(absPath, syscall, p) {
|
|
3125
|
+
if (Number(cred.uid) === 0) return;
|
|
3126
|
+
const stat = _statLadder(absPath);
|
|
3127
|
+
if (stat === undefined) throw _fsErr("ENOENT", syscall, p);
|
|
3128
|
+
if (Number(stat.uid) !== Number(cred.uid)) throw _fsErr("EPERM", syscall, p);
|
|
3129
|
+
}
|
|
3130
|
+
|
|
2319
3131
|
function chmodSync(p, mode) {
|
|
2320
3132
|
if (!existsSync(p)) throw _fsErr("ENOENT", "chmod", p);
|
|
3133
|
+
_ensureModeOwner(_resolve(p), "chmod", p);
|
|
2321
3134
|
// Local-visible immediately (statSync overlay); the live write-through
|
|
2322
3135
|
// rides the next flush of the same path — same fidelity as utimesSync.
|
|
2323
|
-
|
|
3136
|
+
const k = _strip(_resolve(p));
|
|
3137
|
+
_localModes[k] = _coerceMode(mode, "chmod", p);
|
|
3138
|
+
_pendingModes.add(k);
|
|
2324
3139
|
}
|
|
2325
3140
|
|
|
2326
3141
|
async function _chmodAsync(p, mode) {
|
|
@@ -2332,7 +3147,9 @@ const __fsMod = (() => {
|
|
|
2332
3147
|
throw _fsErr("ENOENT", "chmod", p);
|
|
2333
3148
|
}
|
|
2334
3149
|
const m = _coerceMode(mode, "chmod", p);
|
|
3150
|
+
if (localExists) _ensureModeOwner(absPath, "chmod", p);
|
|
2335
3151
|
_localModes[_strip(absPath)] = m;
|
|
3152
|
+
_pendingModes.add(_strip(absPath));
|
|
2336
3153
|
if (supervisor && typeof supervisor.chmod === "function") {
|
|
2337
3154
|
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2338
3155
|
_markVfsStale();
|
|
@@ -2359,27 +3176,78 @@ const __fsMod = (() => {
|
|
|
2359
3176
|
const nextUid = _coerceId(uid, syscall, p);
|
|
2360
3177
|
const nextGid = _coerceId(gid, syscall, p);
|
|
2361
3178
|
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2362
|
-
await
|
|
3179
|
+
await _ownMutation(
|
|
3180
|
+
absPath,
|
|
3181
|
+
() => _fsRpc(supervisor.chown(absPath, nextUid, nextGid, opts), syscall, p, (result) => result),
|
|
3182
|
+
() => {
|
|
3183
|
+
const meta = _metadata(absPath);
|
|
3184
|
+
if (meta) { meta.uid = nextUid; meta.gid = nextGid; }
|
|
3185
|
+
},
|
|
3186
|
+
);
|
|
3187
|
+
_markVfsStale();
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
// Sync ownership change: the stat record is updated locally at once and
|
|
3191
|
+
// the authority RPC is queued behind the path's pending flush, the same
|
|
3192
|
+
// parked model mkdirSync/unlinkSync use. Without an authority there is no
|
|
3193
|
+
// owner table to change, so the answer is the same ENOSYS as the async form.
|
|
3194
|
+
function _chownQueued(p, uid, gid, opts, syscall) {
|
|
3195
|
+
const followSymlinks = !(opts && opts.followSymlinks === false);
|
|
3196
|
+
const absPath = _resolve(p);
|
|
3197
|
+
const supervisor = _supervisor();
|
|
3198
|
+
if (!supervisor || typeof supervisor.chown !== "function") {
|
|
3199
|
+
if (!existsSync(p)) throw _fsErr("ENOENT", syscall, p);
|
|
3200
|
+
throw _fsErr("ENOSYS", syscall, p);
|
|
3201
|
+
}
|
|
3202
|
+
const nextUid = _coerceId(uid, syscall, p);
|
|
3203
|
+
const nextGid = _coerceId(gid, syscall, p);
|
|
3204
|
+
// chown(2): root changes anything; the owner may only set the group to
|
|
3205
|
+
// one of its own groups, and never the owner. Checked before the local
|
|
3206
|
+
// row changes: that row is what the local permission checks read.
|
|
3207
|
+
if (Number(cred.uid) !== 0) {
|
|
3208
|
+
const stat = _statLadder(absPath);
|
|
3209
|
+
if (stat === undefined) throw _fsErr("ENOENT", syscall, p);
|
|
3210
|
+
const groups = [Number(cred.gid), ...cred.groups.map(Number)];
|
|
3211
|
+
if (Number(stat.uid) !== Number(cred.uid) || nextUid !== Number(stat.uid)
|
|
3212
|
+
|| (nextGid !== Number(stat.gid) && !groups.includes(nextGid))) {
|
|
3213
|
+
throw _fsErr("EPERM", syscall, p);
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
2363
3216
|
const meta = _metadata(absPath);
|
|
2364
3217
|
if (meta) { meta.uid = nextUid; meta.gid = nextGid; }
|
|
2365
|
-
|
|
3218
|
+
// The parked write must be registered on the tail BEFORE the chown is
|
|
3219
|
+
// (a flush awaited from inside the queued step would queue behind it
|
|
3220
|
+
// and wait on itself), so it is the synchronous-registering flush.
|
|
3221
|
+
return _queueStructuralMutation(
|
|
3222
|
+
absPath, syscall, p,
|
|
3223
|
+
(s) => s.chown(absPath, nextUid, nextGid, followSymlinks ? undefined : { followSymlinks: false }),
|
|
3224
|
+
() => _flushParkedWrite(absPath, supervisor),
|
|
3225
|
+
"chown",
|
|
3226
|
+
);
|
|
2366
3227
|
}
|
|
3228
|
+
function chownSync(p, uid, gid) { _detachStructuralMutation(_chownQueued(p, uid, gid, undefined, "chown")); }
|
|
3229
|
+
function lchownSync(p, uid, gid) { _detachStructuralMutation(_chownQueued(p, uid, gid, { followSymlinks: false }, "lchown")); }
|
|
3230
|
+
function lchmodSync(p, mode) { chmodSync(p, mode); }
|
|
2367
3231
|
|
|
3232
|
+
/**
|
|
3233
|
+
* Whether this process may WANT (r=4, w=2, x=1) a path whose stat is
|
|
3234
|
+
* META, by POSIX's owner/group/other rule. Only a stat that states the
|
|
3235
|
+
* owner, group and mode can be judged; one that does not is not judged in
|
|
3236
|
+
* the process's favour: absent fields used to default to mode 0644 and
|
|
3237
|
+
* owner 1000, which made any undescribed file the reader's own.
|
|
3238
|
+
*/
|
|
2368
3239
|
function _modeAllows(meta, want) {
|
|
2369
3240
|
if (want === 0) return true;
|
|
2370
|
-
const mode = Number(meta?.mode
|
|
3241
|
+
const mode = Number(meta?.mode);
|
|
3242
|
+
const uid = Number(meta?.uid);
|
|
3243
|
+
const gid = Number(meta?.gid);
|
|
3244
|
+
if (!Number.isInteger(mode) || !Number.isInteger(uid) || !Number.isInteger(gid)) return false;
|
|
3245
|
+
const bits = mode & 0o777;
|
|
2371
3246
|
const currentUid = Number(cred.uid);
|
|
2372
|
-
|
|
3247
|
+
if (currentUid === 0) return (want & 1) === 0 || (bits & 0o111) !== 0;
|
|
2373
3248
|
const groups = cred.groups.map(Number);
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
return true;
|
|
2377
|
-
}
|
|
2378
|
-
const shift = currentUid === Number(meta?.uid ?? 1000)
|
|
2379
|
-
? 6
|
|
2380
|
-
: (currentGid === Number(meta?.gid ?? 1000) || groups.includes(Number(meta?.gid ?? 1000))) ? 3 : 0;
|
|
2381
|
-
const available = (mode >> shift) & 7;
|
|
2382
|
-
return (available & want) === want;
|
|
3249
|
+
const shift = currentUid === uid ? 6 : (Number(cred.gid) === gid || groups.includes(gid)) ? 3 : 0;
|
|
3250
|
+
return (((bits >> shift) & 7) & want) === want;
|
|
2383
3251
|
}
|
|
2384
3252
|
|
|
2385
3253
|
function _ensureAncestorsTraversable(absPath, syscall, p) {
|
|
@@ -2393,34 +3261,31 @@ const __fsMod = (() => {
|
|
|
2393
3261
|
function _ensureWritable(absPath, syscall, p) {
|
|
2394
3262
|
_ensureAncestorsTraversable(absPath, syscall, p);
|
|
2395
3263
|
const cell = _bundleLookup(absPath);
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
//
|
|
2399
|
-
// the
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
if (
|
|
3264
|
+
const denial = _denialCode(cell);
|
|
3265
|
+
if (denial) throw _fsErr(denial, syscall, p);
|
|
3266
|
+
// Judged on what the authority says of the path (or of a file this
|
|
3267
|
+
// process made), never on the bytes held under its name.
|
|
3268
|
+
const stat = _statLadder(absPath);
|
|
3269
|
+
if (stat !== undefined) {
|
|
3270
|
+
if (!_modeAllows(stat, 2)) throw _fsErr("EACCES", syscall, p);
|
|
2403
3271
|
return;
|
|
2404
3272
|
}
|
|
3273
|
+
// Nothing there: a create, judged by the parent the namespace describes.
|
|
2405
3274
|
|
|
2406
3275
|
const parent = __pathMod.dirname(absPath);
|
|
2407
|
-
const
|
|
2408
|
-
if (
|
|
2409
|
-
if (
|
|
2410
|
-
if (!_modeAllows(
|
|
3276
|
+
const parentStat = _statLadder(parent);
|
|
3277
|
+
if (parentStat !== undefined) {
|
|
3278
|
+
if (!parentStat.isDirectory()) throw _fsErr("ENOTDIR", syscall, p);
|
|
3279
|
+
if (!_modeAllows(parentStat, 3)) throw _fsErr("EACCES", syscall, p);
|
|
2411
3280
|
return;
|
|
2412
3281
|
}
|
|
2413
3282
|
|
|
2414
|
-
const parentKey = _strip(parent);
|
|
2415
|
-
const parentIsLocal = parent === cwd || parent === "/" ||
|
|
2416
|
-
(!!__vfsDirs && parentKey in __vfsDirs) ||
|
|
2417
|
-
(!!__vfsManifest && parentKey in __vfsManifest);
|
|
2418
|
-
if (parentIsLocal || _supervisor()) return;
|
|
2419
3283
|
throw _fsErr("ENOENT", syscall, p);
|
|
2420
3284
|
}
|
|
2421
3285
|
|
|
2422
3286
|
function accessSync(p, mode) {
|
|
2423
3287
|
const absPath = _resolve(p);
|
|
3288
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2424
3289
|
const cell = _bundleLookup(absPath);
|
|
2425
3290
|
const meta = _metadata(absPath);
|
|
2426
3291
|
if (cell === undefined && meta === undefined && !existsSync(p)) throw _fsErr("ENOENT", "access", p);
|
|
@@ -2431,7 +3296,10 @@ const __fsMod = (() => {
|
|
|
2431
3296
|
_ensureAncestorsTraversable(absPath, "access", p);
|
|
2432
3297
|
const denial = _denialCode(cell);
|
|
2433
3298
|
if ((requested & 4) !== 0 && denial) throw _fsErr(denial, "access", p);
|
|
2434
|
-
if (
|
|
3299
|
+
if (requested === 0) return;
|
|
3300
|
+
const stat = _statLadder(absPath);
|
|
3301
|
+
if (stat === undefined) throw _fsErr("ENOENT", "access", p);
|
|
3302
|
+
if (!_modeAllows(stat, requested)) throw _fsErr("EACCES", "access", p);
|
|
2435
3303
|
}
|
|
2436
3304
|
|
|
2437
3305
|
async function _accessAsync(p, mode) {
|
|
@@ -2542,42 +3410,12 @@ const __fsMod = (() => {
|
|
|
2542
3410
|
// ── existsSync ──
|
|
2543
3411
|
function existsSync(p) {
|
|
2544
3412
|
const absPath = _resolve(p);
|
|
2545
|
-
|
|
2546
|
-
if (_metadata(absPath) !== undefined) { _residencySatisfied(absPath); return true; }
|
|
2547
|
-
if (__vfsBundle && k in __vfsBundle) { _residencySatisfied(absPath); return true; }
|
|
2548
|
-
if (__vfsWrites && k in __vfsWrites) { _residencySatisfied(absPath); return true; }
|
|
2549
|
-
if (__vfsDirs && k in __vfsDirs) { _residencySatisfied(absPath); return true; }
|
|
2550
|
-
// W2.5b root-cause fix: consult the manifest BEFORE falling back to
|
|
2551
|
-
// the O(N) bundle-prefix scan. The manifest is uncapped and always
|
|
2552
|
-
// reflects the real directory shape, even when the file content for
|
|
2553
|
-
// a directory's contents was excluded by the 4 MiB / 500-file content
|
|
2554
|
-
if (__vfsManifest) {
|
|
2555
|
-
if (k in __vfsManifest) { _residencySatisfied(absPath); return true; }
|
|
2556
|
-
// Path may be a file listed by its parent's manifest entry.
|
|
2557
|
-
const slash = k.lastIndexOf("/");
|
|
2558
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2559
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2560
|
-
const sib = __vfsManifest[parent];
|
|
2561
|
-
if (sib && sib.indexOf(name) !== -1) { _residencySatisfied(absPath); return true; }
|
|
2562
|
-
}
|
|
2563
|
-
// Last-resort: bundle dir entries
|
|
2564
|
-
if (__vfsBundle && (k in __vfsBundle || __residentAnyUnder(k + "/"))) {
|
|
2565
|
-
_residencySatisfied(absPath);
|
|
2566
|
-
return true;
|
|
2567
|
-
}
|
|
2568
|
-
// Nothing found — which is only an answer if the enclosing directory was
|
|
2569
|
-
// enumerated. Node's existsSync never throws, so the false below is
|
|
2570
|
-
// provisional: the miss is recorded, the listing that settles it is pulled
|
|
2571
|
-
// in, and the exit report fails the run if the path turns out to have been
|
|
2572
|
-
// there all along.
|
|
2573
|
-
if (!_absenceIsKnown(absPath)) {
|
|
2574
|
-
_recordUnmapped(absPath, "access");
|
|
2575
|
-
return false;
|
|
2576
|
-
}
|
|
3413
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2577
3414
|
_residencySatisfied(absPath);
|
|
2578
|
-
return
|
|
3415
|
+
return _statLadder(absPath) !== undefined;
|
|
2579
3416
|
}
|
|
2580
3417
|
|
|
3418
|
+
|
|
2581
3419
|
// ── statSync ──
|
|
2582
3420
|
function statSync(p, opts) {
|
|
2583
3421
|
const absPath = _resolve(p);
|
|
@@ -2590,16 +3428,12 @@ const __fsMod = (() => {
|
|
|
2590
3428
|
// classify a miss without redoing the resolve and ancestor walk it just
|
|
2591
3429
|
// performed — that path runs on every module-resolution probe.
|
|
2592
3430
|
function _statResolved(absPath, p, opts) {
|
|
3431
|
+
_nsRequire("stat", p, "fs.promises.stat");
|
|
2593
3432
|
const stat = _statLadder(absPath);
|
|
2594
|
-
// An answer settles the path, including the honest "not there"
|
|
2595
|
-
//
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
if (stat !== undefined) { _residencySatisfied(absPath); return stat; }
|
|
2599
|
-
// Nothing in the view describes the path, and there are two very different
|
|
2600
|
-
// reasons for that. Only one of them means the file is not there.
|
|
2601
|
-
if (_absenceIsKnown(absPath)) _residencySatisfied(absPath);
|
|
2602
|
-
else _recordUnmapped(absPath, "stat");
|
|
3433
|
+
// An answer settles the path, including the honest "not there": the
|
|
3434
|
+
// namespace names every path this credential can see.
|
|
3435
|
+
_residencySatisfied(absPath);
|
|
3436
|
+
if (stat !== undefined) return stat;
|
|
2603
3437
|
// Node's statSync honors { throwIfNoEntry: false } by returning undefined
|
|
2604
3438
|
// for a missing path instead of throwing.
|
|
2605
3439
|
if (opts && opts.throwIfNoEntry === false) return undefined;
|
|
@@ -2612,216 +3446,350 @@ const __fsMod = (() => {
|
|
|
2612
3446
|
* MEANS — absence and ignorance are different answers and only one of them
|
|
2613
3447
|
* is a condition a program can act on, so the callers classify.
|
|
2614
3448
|
*/
|
|
2615
|
-
function _statLadder(absPath) {
|
|
3449
|
+
function _statLadder(absPath, noFollow) {
|
|
2616
3450
|
const k = _strip(absPath);
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
const size = _byteLen(__vfsWrites[k]);
|
|
2624
|
-
// Correct only the size — reusing the metadata record keeps the type
|
|
2625
|
-
// and the recorded timestamps stable, so mtime-based change detection
|
|
2626
|
-
// (make, tsc --build, watchers) does not see every call as a change.
|
|
2627
|
-
if (meta) return _statObject({ ...meta, size }, k);
|
|
2628
|
-
return _localStatObject(
|
|
2629
|
-
k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid,
|
|
2630
|
-
);
|
|
2631
|
-
}
|
|
2632
|
-
const metadata = _metadata(absPath);
|
|
2633
|
-
if (metadata) return _statObject(metadata, k);
|
|
2634
|
-
// Check if it's a known directory written this exec session
|
|
2635
|
-
if (__vfsDirs && k in __vfsDirs) {
|
|
2636
|
-
return _localStatObject(k, true, false, 0, 0o777 & ~__processUmask, cred.uid, cred.gid);
|
|
2637
|
-
}
|
|
2638
|
-
// W2.5b: consult uncapped manifest first for directory shape.
|
|
2639
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2640
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2641
|
-
}
|
|
2642
|
-
// File with content embedded?
|
|
2643
|
-
const content = _bundleLookup(absPath);
|
|
2644
|
-
if (content !== undefined) {
|
|
2645
|
-
// _byteLen handles both string (UTF-8 encode) and Uint8Array
|
|
2646
|
-
// (byteLength) — fixes binary writes from reporting the
|
|
2647
|
-
// post-corruption byte count.
|
|
2648
|
-
const size = _byteLen(content);
|
|
2649
|
-
return _localStatObject(k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid);
|
|
2650
|
-
}
|
|
2651
|
-
// File listed in parent's manifest but content was capped out — return
|
|
2652
|
-
// a zero-size file stat so callers like fs.stat / fs.statSync see the
|
|
2653
|
-
// file as present (downstream readFileSync will surface ENOENT if it
|
|
2654
|
-
// actually tries to read content; many consumers only need stat).
|
|
2655
|
-
if (__vfsManifest) {
|
|
2656
|
-
const slash = k.lastIndexOf("/");
|
|
2657
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2658
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2659
|
-
const sib = __vfsManifest[parent];
|
|
2660
|
-
if (sib && sib.indexOf(name) !== -1) {
|
|
2661
|
-
return _localStatObject(k, false, false, 0, 0o644, cred.uid, cred.gid);
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
// Last-resort: bundle prefix scan (legacy path).
|
|
2665
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) {
|
|
2666
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2667
|
-
}
|
|
2668
|
-
return undefined;
|
|
3451
|
+
_nsRequire("stat", absPath, "fs.promises.stat");
|
|
3452
|
+
const meta = _nsMeta(k, !noFollow);
|
|
3453
|
+
if (meta === "absent" || meta === "ELOOP") return undefined;
|
|
3454
|
+
return meta.own
|
|
3455
|
+
? _localStatObject(k, meta.type === "directory", false, meta.size, meta.mode & 0o7777, meta.uid, meta.gid)
|
|
3456
|
+
: _statObject(meta, k);
|
|
2669
3457
|
}
|
|
2670
3458
|
|
|
2671
3459
|
// ── lstatSync (alias for statSync in our VFS — no symlinks) ──
|
|
2672
|
-
function lstatSync(p, opts) {
|
|
3460
|
+
function lstatSync(p, opts) {
|
|
3461
|
+
const absPath = _resolve(p);
|
|
3462
|
+
_nsRequire("lstat", p, "fs.promises.lstat");
|
|
3463
|
+
_ensureAncestorsTraversable(absPath, "lstat", p);
|
|
3464
|
+
const stat = _statLadder(absPath, true);
|
|
3465
|
+
if (stat !== undefined) return stat;
|
|
3466
|
+
if (opts && opts.throwIfNoEntry === false) return undefined;
|
|
3467
|
+
throw _fsErr("ENOENT", "lstat", p);
|
|
3468
|
+
}
|
|
2673
3469
|
|
|
2674
3470
|
// ── readdirSync ──
|
|
2675
|
-
//
|
|
2676
|
-
//
|
|
2677
|
-
// not in the manifest (e.g. dirs created at exec time via mkdirSync) and
|
|
2678
|
-
// is unioned with __vfsWrites so newly-written files become visible.
|
|
3471
|
+
// The namespace's listing of the directory, with this process's own
|
|
3472
|
+
// structural effects over it (_nsList).
|
|
2679
3473
|
function readdirSync(p, opts) {
|
|
2680
3474
|
const absPath = _resolve(p);
|
|
3475
|
+
_nsRequire("scandir", p, "fs.promises.readdir");
|
|
2681
3476
|
_ensureAncestorsTraversable(absPath, "scandir", p);
|
|
2682
3477
|
const metadata = _metadata(absPath);
|
|
2683
3478
|
if (metadata && !_modeAllows(metadata, 4)) throw _fsErr("EACCES", "scandir", p);
|
|
2684
3479
|
const k = _strip(absPath);
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
// enumerated, or one this process created, can honestly be listed.
|
|
2689
|
-
if (!_dirEnumerated(k) && !_bundleHasChildren(k)) {
|
|
2690
|
-
// An authority exists that this call cannot reach, so an empty array
|
|
2691
|
-
// would be a claim about a directory nothing ever opened. Refuse, and
|
|
2692
|
-
// pull the listing in for the next one.
|
|
2693
|
-
//
|
|
2694
|
-
// This is the one caller that keeps refusing. Everywhere else the sync
|
|
2695
|
-
// view answers a path it cannot map with the not-found the program was
|
|
2696
|
-
// written to handle, provisionally, and lets the exit report settle
|
|
2697
|
-
// whether that answer was true. A listing has no such answer to give:
|
|
2698
|
-
// "no config yet, write one" is an idiom every program implements, and
|
|
2699
|
-
// there is no counterpart for a directory — a caller handed ENOENT for a
|
|
2700
|
-
// template tree has nowhere to go but the same wrong branch an empty
|
|
2701
|
-
// array sent it down, and the array at least it could not detect.
|
|
2702
|
-
if (_supervisor()) {
|
|
2703
|
-
throw _refuseUnmapped(absPath, p, "scandir", "fs.promises.readdir");
|
|
2704
|
-
}
|
|
2705
|
-
// No authority: the staged tables are the whole filesystem. A path they
|
|
2706
|
-
// do not describe is missing, and one they describe with no children is
|
|
2707
|
-
// genuinely empty.
|
|
2708
|
-
if (_statLadder(absPath) === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
2709
|
-
}
|
|
2710
|
-
const prefix = k ? k + "/" : "";
|
|
2711
|
-
const names = new Set();
|
|
2712
|
-
// 1. Manifest-supplied children (the authoritative source for installed pkgs).
|
|
2713
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2714
|
-
for (const n of __vfsManifest[k]) names.add(n);
|
|
2715
|
-
}
|
|
2716
|
-
// 2. Bundle-prefix fallback (covers older paths or runtime-mkdir'd ones
|
|
2717
|
-
// that aren't in the manifest yet).
|
|
2718
|
-
if (__vfsBundle) {
|
|
2719
|
-
for (const bk of __residentUnder(prefix)) {
|
|
2720
|
-
const seg = bk.substring(prefix.length).split("/")[0];
|
|
2721
|
-
if (seg) names.add(seg);
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
// 3. Files written during this exec session.
|
|
2725
|
-
if (__vfsWrites) {
|
|
2726
|
-
for (const wk in __vfsWrites) {
|
|
2727
|
-
if (wk.startsWith(prefix)) {
|
|
2728
|
-
const rest = wk.substring(prefix.length);
|
|
2729
|
-
const seg = rest.split("/")[0];
|
|
2730
|
-
if (seg) names.add(seg);
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
// 4. Dirs created during this exec session.
|
|
2735
|
-
if (__vfsDirs) {
|
|
2736
|
-
for (const dk in __vfsDirs) {
|
|
2737
|
-
if (dk.startsWith(prefix)) {
|
|
2738
|
-
const rest = dk.substring(prefix.length);
|
|
2739
|
-
const seg = rest.split("/")[0];
|
|
2740
|
-
if (seg) names.add(seg);
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
3480
|
+
const st = _statLadder(absPath);
|
|
3481
|
+
if (st === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
3482
|
+
if (!st.isDirectory()) throw _fsErr("ENOTDIR", "scandir", p);
|
|
2744
3483
|
_residencySatisfied(absPath);
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
// Manifest is the definitive isDir source; fall back to bundle scan.
|
|
2750
|
-
// The metadata record sits between them: a directory that was listed
|
|
2751
|
-
// but never walked has no manifest entry of its own, and calling it a
|
|
2752
|
-
// file is how a template tree full of subdirectories reads back as a
|
|
2753
|
-
// tree full of nothing.
|
|
2754
|
-
const isDir =
|
|
2755
|
-
(!!__vfsManifest && fp in __vfsManifest) ||
|
|
2756
|
-
(!!__vfsDirs && fp in __vfsDirs) ||
|
|
2757
|
-
_metadata(fp)?.type === "directory" ||
|
|
2758
|
-
(!!__vfsBundle && __residentAnyUnder(fp + "/"));
|
|
2759
|
-
return { name: n, isFile: () => !isDir, isDirectory: () => isDir, isSymbolicLink: () => false };
|
|
2760
|
-
});
|
|
2761
|
-
}
|
|
2762
|
-
return arr;
|
|
3484
|
+
const listed = _nsList(k);
|
|
3485
|
+
const sorted = [...listed.keys()].sort();
|
|
3486
|
+
if (!opts?.withFileTypes) return sorted;
|
|
3487
|
+
return sorted.map((n) => _direntObject(n, listed.get(n), absPath));
|
|
2763
3488
|
}
|
|
2764
3489
|
|
|
2765
3490
|
// ── mkdirSync ──
|
|
2766
|
-
|
|
3491
|
+
// The sync effect, then the authority mutation queued behind it (see
|
|
3492
|
+
// _queueStructuralMutation). One RPC whatever the depth: the async form
|
|
3493
|
+
// sends the path alone and the authority's mkdir creates the ancestors.
|
|
3494
|
+
function _mkdirQueued(p, opts) {
|
|
2767
3495
|
const absPath = _resolve(p);
|
|
2768
3496
|
const k = _strip(absPath);
|
|
3497
|
+
const created = [];
|
|
2769
3498
|
if (opts?.recursive) {
|
|
2770
3499
|
const parts = k.split("/").filter(Boolean);
|
|
2771
3500
|
let cur = "";
|
|
2772
|
-
for (const part of parts) { cur = cur ? cur + "/" + part : part; __vfsDirs[cur] = true; }
|
|
3501
|
+
for (const part of parts) { cur = cur ? cur + "/" + part : part; _noteCreation(cur); __vfsDirs[cur] = true; created.push(cur); }
|
|
2773
3502
|
} else {
|
|
3503
|
+
_noteCreation(k);
|
|
2774
3504
|
__vfsDirs[k] = true;
|
|
3505
|
+
created.push(k);
|
|
3506
|
+
}
|
|
3507
|
+
for (const dir of created) {
|
|
3508
|
+
const was = _nsMeta(dir, true);
|
|
3509
|
+
if (was === "absent") _nsOwnSet(dir, "dir", { hide: true });
|
|
2775
3510
|
}
|
|
3511
|
+
const queued = _queueStructuralMutation(absPath, "mkdir", p, (supervisor) => supervisor.mkdir(absPath));
|
|
3512
|
+
// Told, not merely known locally: _announceLocalDirs must not issue a
|
|
3513
|
+
// second mkdir for a directory whose own RPC is already in the queue.
|
|
3514
|
+
if (queued) for (const dir of created) _announcedDirs.add(dir);
|
|
3515
|
+
return queued;
|
|
2776
3516
|
}
|
|
3517
|
+
function mkdirSync(p, opts) { _detachStructuralMutation(_mkdirQueued(p, opts)); }
|
|
2777
3518
|
|
|
2778
3519
|
// ── unlinkSync ──
|
|
2779
|
-
|
|
3520
|
+
/**
|
|
3521
|
+
* unlink(2)/rmdir(2)/rename(2) of an existing name: the parent must be
|
|
3522
|
+
* writable and searchable, and in a sticky directory (/tmp, 1777) only the
|
|
3523
|
+
* file's owner, the directory's owner or root may remove it. Checked before
|
|
3524
|
+
* the local view changes, so a refusal leaves the process's view intact. A
|
|
3525
|
+
* name whose owner this view does not know is refused as a miss.
|
|
3526
|
+
*/
|
|
3527
|
+
function _ensureRemovable(absPath, syscall, p) {
|
|
3528
|
+
_ensureAncestorsTraversable(absPath, syscall, p);
|
|
3529
|
+
const k = _strip(absPath);
|
|
3530
|
+
const parent = _statLadder(__pathMod.dirname(absPath));
|
|
3531
|
+
if (parent === undefined) return;
|
|
3532
|
+
if (!_modeAllows(parent, 3)) throw _fsErr("EACCES", syscall, p);
|
|
3533
|
+
if ((Number(parent.mode) & 0o1000) === 0 || Number(cred.uid) === 0 || Number(parent.uid) === Number(cred.uid)) return;
|
|
3534
|
+
if (_createdHere.has(k)) return;
|
|
3535
|
+
const target = _statLadder(absPath, true);
|
|
3536
|
+
if (target === undefined) return;
|
|
3537
|
+
if (Number(target.uid) !== Number(cred.uid)) throw _fsErr("EPERM", syscall, p);
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
function _unlinkQueued(p) {
|
|
2780
3541
|
const absPath = _resolve(p);
|
|
2781
3542
|
const k = _strip(absPath);
|
|
3543
|
+
_ensureRemovable(absPath, "unlink", p);
|
|
2782
3544
|
if (__vfsBundle) delete __vfsBundle[k];
|
|
2783
3545
|
if (__vfsWrites) delete __vfsWrites[k];
|
|
2784
3546
|
_forgetSyncPath(k);
|
|
3547
|
+
return _queueStructuralMutation(absPath, "unlink", p, (supervisor) => supervisor.unlink(absPath));
|
|
2785
3548
|
}
|
|
3549
|
+
function unlinkSync(p) { _detachStructuralMutation(_unlinkQueued(p)); }
|
|
2786
3550
|
|
|
2787
3551
|
// ── rmdirSync ──
|
|
2788
|
-
function
|
|
3552
|
+
function _rmdirQueued(p) {
|
|
2789
3553
|
const absPath = _resolve(p);
|
|
2790
3554
|
const k = _strip(absPath);
|
|
3555
|
+
_ensureRemovable(absPath, "rmdir", p);
|
|
2791
3556
|
if (__vfsDirs) delete __vfsDirs[k];
|
|
2792
3557
|
_forgetSyncPath(k);
|
|
3558
|
+
return _queueStructuralMutation(
|
|
3559
|
+
absPath, "rmdir", p,
|
|
3560
|
+
(supervisor) => supervisor.rmdir(absPath),
|
|
3561
|
+
() => __nimbusAwaitSubtreeMutations(absPath),
|
|
3562
|
+
);
|
|
2793
3563
|
}
|
|
3564
|
+
function rmdirSync(p) { _detachStructuralMutation(_rmdirQueued(p)); }
|
|
2794
3565
|
|
|
2795
3566
|
// ── renameSync ──
|
|
2796
|
-
function
|
|
2797
|
-
const
|
|
2798
|
-
const
|
|
3567
|
+
function _renameQueued(oldP, newP) {
|
|
3568
|
+
const oldAbs = _resolve(oldP);
|
|
3569
|
+
const newAbs = _resolve(newP);
|
|
3570
|
+
// The name leaves its directory and lands in another (replacing what is
|
|
3571
|
+
// there): both are removals by POSIX's rule.
|
|
3572
|
+
_ensureRemovable(oldAbs, "rename", oldP);
|
|
3573
|
+
if (_statLadder(newAbs, true) !== undefined) _ensureRemovable(newAbs, "rename", newP);
|
|
3574
|
+
else _ensureWritable(newAbs, "rename", newP);
|
|
3575
|
+
const oldK = _strip(oldAbs);
|
|
3576
|
+
const newK = _strip(newAbs);
|
|
3577
|
+
// The table still holds the old name until the rename is reported, so the
|
|
3578
|
+
// new name reads through to it and the old one reads as gone.
|
|
3579
|
+
const nsFrom = _nsRealKey(oldK);
|
|
3580
|
+
// What this process created travels with the name; what it did not stays
|
|
3581
|
+
// the authority's, whatever the move does to the local tables.
|
|
3582
|
+
const oldPrefix = oldK + "/";
|
|
3583
|
+
const createdMoved = [..._createdHere]
|
|
3584
|
+
.filter((key) => key === oldK || key.startsWith(oldPrefix))
|
|
3585
|
+
.map((key) => newK + key.slice(oldK.length));
|
|
2799
3586
|
const content = __vfsBundle?.[oldK] ?? __vfsWrites?.[oldK];
|
|
2800
3587
|
if (content !== undefined) {
|
|
2801
3588
|
_parkWrite(newK, content);
|
|
2802
3589
|
if (__vfsBundle) delete __vfsBundle[oldK];
|
|
2803
3590
|
delete __vfsBundleRevisions[oldK];
|
|
2804
3591
|
delete __vfsWrites[oldK];
|
|
2805
|
-
// The stat record travels with the content: dropping it would lose the
|
|
2806
|
-
// mode/ownership, and leaving it behind would keep the source path
|
|
2807
|
-
// reporting as an existing file this rename already moved away.
|
|
2808
|
-
const metadata = _metadataTable();
|
|
2809
|
-
const moved = metadata ? metadata[oldK] : undefined;
|
|
2810
3592
|
_forgetSyncPath(oldK);
|
|
2811
|
-
|
|
3593
|
+
} else if (__vfsDirs && oldK in __vfsDirs) {
|
|
3594
|
+
// A directory this process made travels under its new name, so the
|
|
3595
|
+
// sync view stops listing the old one and _announceLocalDirs cannot
|
|
3596
|
+
// re-create it at the authority behind the rename.
|
|
3597
|
+
const prefix = oldK + "/";
|
|
3598
|
+
for (const dk of Object.keys(__vfsDirs)) {
|
|
3599
|
+
if (dk !== oldK && !dk.startsWith(prefix)) continue;
|
|
3600
|
+
const moved = newK + dk.slice(oldK.length);
|
|
3601
|
+
__vfsDirs[moved] = true;
|
|
3602
|
+
delete __vfsDirs[dk];
|
|
3603
|
+
if (_announcedDirs.has(dk)) _announcedDirs.add(moved);
|
|
3604
|
+
}
|
|
3605
|
+
_forgetSyncTree(oldK);
|
|
3606
|
+
}
|
|
3607
|
+
_forgetCreation(oldK);
|
|
3608
|
+
_forgetCreation(newK);
|
|
3609
|
+
for (const key of createdMoved) _createdHere.add(key);
|
|
3610
|
+
for (const [ok, entry] of [..._nsOwn]) {
|
|
3611
|
+
if ((ok === oldK || ok.startsWith(oldPrefix)) && entry.state === "dir") {
|
|
3612
|
+
_nsOwnSet(newK + ok.slice(oldK.length), "dir", { hide: entry.hide });
|
|
3613
|
+
}
|
|
2812
3614
|
}
|
|
3615
|
+
if (nsFrom !== null && !_nsOwn.has(newK)) _nsOwnSet(newK, "alias", { from: nsFrom });
|
|
3616
|
+
_nsOwnSet(oldK, "absentTree");
|
|
3617
|
+
const queued = _queueStructuralMutation(
|
|
3618
|
+
oldAbs, "rename", oldP,
|
|
3619
|
+
(supervisor) => supervisor.rename(oldAbs, newAbs),
|
|
3620
|
+
// The queue orders the move behind the source's ancestors; the move
|
|
3621
|
+
// also needs the destination's ancestors to exist and every pending
|
|
3622
|
+
// mutation beneath the source to have landed under the old name.
|
|
3623
|
+
() => Promise.all([__nimbusAwaitAncestorMutations(newAbs), __nimbusAwaitSubtreeMutations(oldAbs)]),
|
|
3624
|
+
);
|
|
3625
|
+
_fenceVfsMutation(newAbs, queued);
|
|
3626
|
+
return queued;
|
|
2813
3627
|
}
|
|
3628
|
+
function renameSync(oldP, newP) { _detachStructuralMutation(_renameQueued(oldP, newP)); }
|
|
2814
3629
|
|
|
2815
3630
|
// ── copyFileSync ──
|
|
2816
|
-
|
|
2817
|
-
|
|
3631
|
+
// Bytes, not utf8: a utf8 round trip replaces every byte ≥ 0x80 with
|
|
3632
|
+
// U+FFFD, which is how a copied .png or .woff2 arrived corrupted.
|
|
3633
|
+
function copyFileSync(src, dest, mode) {
|
|
3634
|
+
if ((Number(mode) & __fsConstants.COPYFILE_EXCL) !== 0 && existsSync(dest)) {
|
|
3635
|
+
throw _fsErr("EEXIST", "copyfile", dest);
|
|
3636
|
+
}
|
|
3637
|
+
writeFileSync(dest, readFileSync(src));
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// ── rmSync / rm ──
|
|
3641
|
+
// The local tables are edited at once (the same retraction unlinkSync and
|
|
3642
|
+
// rmdirSync perform, over the whole subtree when recursive) and ONE
|
|
3643
|
+
// authority RPC — fsRemove, which the bridge serves as unlink or a bounded
|
|
3644
|
+
// recursive removal — is queued behind every pending mutation beneath the
|
|
3645
|
+
// path. ENOENT under \`force\` is the authority's to swallow; locally an
|
|
3646
|
+
// unknown path may still exist live, so it is asked rather than answered.
|
|
3647
|
+
function _rmQueued(p, opts, sync) {
|
|
3648
|
+
const o = opts || {};
|
|
3649
|
+
const absPath = _resolve(p);
|
|
3650
|
+
const k = _strip(absPath);
|
|
3651
|
+
const st = statSync(p, { throwIfNoEntry: false });
|
|
3652
|
+
const supervisor = _supervisor();
|
|
3653
|
+
const canRemove = !!supervisor && typeof supervisor.fsRemove === "function";
|
|
3654
|
+
if (st === undefined) {
|
|
3655
|
+
if (!canRemove) {
|
|
3656
|
+
if (o.force) return null;
|
|
3657
|
+
throw _fsErr("ENOENT", "rm", p);
|
|
3658
|
+
}
|
|
3659
|
+
// A path the sync view cannot map may still exist live (born after
|
|
3660
|
+
// boot). The async form asks the authority, whose ENOENT is the real
|
|
3661
|
+
// one; the sync form has no frame to receive that answer, so it gives
|
|
3662
|
+
// statSync's own provisional not-found unless \`force\` makes the
|
|
3663
|
+
// verdict irrelevant.
|
|
3664
|
+
if (sync && !o.force) throw _fsErr("ENOENT", "rm", p);
|
|
3665
|
+
}
|
|
3666
|
+
if (st !== undefined && st.isDirectory() && !o.recursive) {
|
|
3667
|
+
throw _fsErr("ERR_FS_EISDIR", "rm", p);
|
|
3668
|
+
}
|
|
3669
|
+
const prefix = k + "/";
|
|
3670
|
+
// A parked write is content the authority may never have seen: the
|
|
3671
|
+
// caller's file demonstrably existed here, so the removal succeeds, and
|
|
3672
|
+
// the authority is told "if present" rather than made to fail on a file
|
|
3673
|
+
// that was only ever local.
|
|
3674
|
+
let parked = false;
|
|
3675
|
+
if (__vfsBundle) {
|
|
3676
|
+
if (k in __vfsBundle) delete __vfsBundle[k];
|
|
3677
|
+
if (o.recursive) for (const bk of __residentUnder(prefix)) delete __vfsBundle[bk];
|
|
3678
|
+
}
|
|
3679
|
+
delete __vfsBundleRevisions[k];
|
|
3680
|
+
if (__vfsWrites) {
|
|
3681
|
+
for (const wk of Object.keys(__vfsWrites)) {
|
|
3682
|
+
if (wk === k || (o.recursive && wk.startsWith(prefix))) { delete __vfsWrites[wk]; parked = true; }
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
if (__vfsDirs) {
|
|
3686
|
+
for (const dk of Object.keys(__vfsDirs)) {
|
|
3687
|
+
if (dk === k || (o.recursive && dk.startsWith(prefix))) delete __vfsDirs[dk];
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
if (o.recursive) _forgetSyncTree(k); else _forgetSyncPath(k);
|
|
3691
|
+
if (!canRemove) {
|
|
3692
|
+
// No fsRemove: a plain file still has the unlink RPC.
|
|
3693
|
+
if (st !== undefined && !st.isDirectory()) {
|
|
3694
|
+
return _queueStructuralMutation(absPath, "rm", p, (s) => s.unlink(absPath), undefined, "unlink");
|
|
3695
|
+
}
|
|
3696
|
+
return null;
|
|
3697
|
+
}
|
|
3698
|
+
return _queueStructuralMutation(
|
|
3699
|
+
absPath, "rm", p,
|
|
3700
|
+
(s) => s.fsRemove(absPath, { recursive: !!o.recursive, force: !!o.force || parked }),
|
|
3701
|
+
() => __nimbusAwaitSubtreeMutations(absPath),
|
|
3702
|
+
"fsRemove",
|
|
3703
|
+
);
|
|
3704
|
+
}
|
|
3705
|
+
function rmSync(p, opts) { _detachStructuralMutation(_rmQueued(p, opts, true)); }
|
|
3706
|
+
async function _rmAsync(p, opts) { await _rmQueued(p, opts, false); }
|
|
3707
|
+
|
|
3708
|
+
// ── cpSync ──
|
|
3709
|
+
// A walk of the resident view; every file copy is a parked sync write that
|
|
3710
|
+
// the existing write-back drains, and every directory a queued mkdir.
|
|
3711
|
+
function cpSync(src, dest, opts) {
|
|
3712
|
+
const o = opts || {};
|
|
3713
|
+
const st = statSync(src, { throwIfNoEntry: false });
|
|
3714
|
+
if (st === undefined) throw _fsErr("ENOENT", "cp", src);
|
|
3715
|
+
if (typeof o.filter === "function" && !o.filter(String(src), String(dest))) return;
|
|
3716
|
+
if (!st.isDirectory()) {
|
|
3717
|
+
const destSt = statSync(dest, { throwIfNoEntry: false });
|
|
3718
|
+
if (destSt !== undefined) {
|
|
3719
|
+
if (destSt.isDirectory()) throw _fsErr("EISDIR", "cp", dest);
|
|
3720
|
+
if (o.errorOnExist) throw _fsErr("ERR_FS_CP_EEXIST", "cp", dest);
|
|
3721
|
+
if (o.force === false) return;
|
|
3722
|
+
}
|
|
3723
|
+
copyFileSync(src, dest);
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
if (!o.recursive) throw _fsErr("ERR_FS_EISDIR", "cp", src);
|
|
3727
|
+
mkdirSync(dest, { recursive: true });
|
|
3728
|
+
for (const ent of readdirSync(src, { withFileTypes: true })) {
|
|
3729
|
+
cpSync(__pathMod.join(String(src), ent.name), __pathMod.join(String(dest), ent.name), o);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
// ── mkdtemp ──
|
|
3734
|
+
// Node appends six characters from [a-zA-Z0-9]; a collision with a name
|
|
3735
|
+
// the sync view already knows is retried rather than reused.
|
|
3736
|
+
const _MKDTEMP_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
3737
|
+
function _mkdtempName(prefix) {
|
|
3738
|
+
for (let attempt = 0; attempt < 64; attempt++) {
|
|
3739
|
+
let suffix = "";
|
|
3740
|
+
for (let i = 0; i < 6; i++) suffix += _MKDTEMP_ALPHABET[Math.floor(Math.random() * _MKDTEMP_ALPHABET.length)];
|
|
3741
|
+
const name = String(prefix) + suffix;
|
|
3742
|
+
if (!existsSync(name)) return name;
|
|
3743
|
+
}
|
|
3744
|
+
throw _fsErr("EEXIST", "mkdtemp", String(prefix) + "XXXXXX");
|
|
3745
|
+
}
|
|
3746
|
+
function mkdtempSync(prefix) {
|
|
3747
|
+
const name = _mkdtempName(prefix);
|
|
3748
|
+
mkdirSync(name);
|
|
3749
|
+
return name;
|
|
3750
|
+
}
|
|
3751
|
+
async function _mkdtempAsync(prefix) {
|
|
3752
|
+
const name = _mkdtempName(prefix);
|
|
3753
|
+
await _mkdirAsync(name);
|
|
3754
|
+
return name;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
// ── truncateSync ──
|
|
3758
|
+
// The resident-view rule of ftruncateSync applied to a path: trim the
|
|
3759
|
+
// resident cell and park it, refuse EAGAIN when the bytes are not here.
|
|
3760
|
+
function truncateSync(p, len) {
|
|
3761
|
+
const absPath = _resolve(p);
|
|
3762
|
+
const st = statSync(p, { throwIfNoEntry: false });
|
|
3763
|
+
if (st === undefined) throw _fsErr("ENOENT", "truncate", p);
|
|
3764
|
+
if (st.isDirectory()) throw _fsErr("EISDIR", "truncate", p);
|
|
3765
|
+
const size = Math.max(0, Math.trunc(Number(len) || 0));
|
|
3766
|
+
_ensureWritable(absPath, "truncate", p);
|
|
3767
|
+
const base = _residentWriteBase(absPath, p, "truncate");
|
|
3768
|
+
const next = new Uint8Array(size);
|
|
3769
|
+
next.set(base.subarray(0, Math.min(size, base.byteLength)), 0);
|
|
3770
|
+
_parkWrite(_strip(absPath), next);
|
|
3771
|
+
_markVfsStale();
|
|
2818
3772
|
}
|
|
2819
3773
|
|
|
3774
|
+
// ── link / linkSync ──
|
|
3775
|
+
// The VFS has no hard links and a copy would lie about sharing an inode,
|
|
3776
|
+
// so both forms answer ENOSYS like fs.promises.link always has.
|
|
3777
|
+
function linkSync(existingPath, newPath) { throw _fsErr("ENOSYS", "link", newPath); }
|
|
3778
|
+
|
|
2820
3779
|
// ── realpathSync (X.5-T per X5Z5-plan §4.3 + X526b-retro §3.1) ──
|
|
2821
3780
|
// Sync realpath stays local and identity-resolves. Async symlink
|
|
2822
3781
|
// operations use the live supervisor bridge below.
|
|
2823
3782
|
// .native static is required by TypeScript's getNodeSystem at
|
|
2824
|
-
function realpathSync(p, opts) {
|
|
3783
|
+
function realpathSync(p, opts) {
|
|
3784
|
+
const absPath = _resolve(String(p));
|
|
3785
|
+
_nsRequire("realpath", p, "fs.promises.realpath");
|
|
3786
|
+
const k = _strip(absPath);
|
|
3787
|
+
const meta = _nsMeta(k, true);
|
|
3788
|
+
if (meta === "ELOOP") throw _fsErr("ELOOP", "realpath", p);
|
|
3789
|
+
if (meta === "absent") throw _fsErr("ENOENT", "realpath", p);
|
|
3790
|
+
const real = _nsRealKey(k);
|
|
3791
|
+
return real === null ? absPath : "/" + real;
|
|
3792
|
+
}
|
|
2825
3793
|
realpathSync.native = realpathSync;
|
|
2826
3794
|
|
|
2827
3795
|
// ── Async variants (thin wrappers returning via callback) ──
|
|
@@ -2849,6 +3817,11 @@ const __fsMod = (() => {
|
|
|
2849
3817
|
_mkdirAsync(p, opts).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); });
|
|
2850
3818
|
}
|
|
2851
3819
|
function unlink(p, cb) { _unlinkAsync(p).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3820
|
+
// The callback form proper-lockfile (via graceful-fs) releases its lock directory with.
|
|
3821
|
+
function rmdir(p, opts, cb) {
|
|
3822
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
3823
|
+
_rmdirAsync(p).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); });
|
|
3824
|
+
}
|
|
2852
3825
|
function rename(oldP, newP, cb) { _renameAsync(oldP, newP).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
2853
3826
|
function utimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
2854
3827
|
function lutimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime, { followSymlinks: false }).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
@@ -2863,7 +3836,7 @@ const __fsMod = (() => {
|
|
|
2863
3836
|
// ── open-flag parsing for fs.promises.open ──
|
|
2864
3837
|
function _parseOpenFlags(flags) {
|
|
2865
3838
|
if (typeof flags === "number") {
|
|
2866
|
-
const O_WRONLY
|
|
3839
|
+
const { O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_DIRECTORY } = __fsConstants;
|
|
2867
3840
|
return {
|
|
2868
3841
|
read: (flags & O_WRONLY) === 0,
|
|
2869
3842
|
write: (flags & (O_WRONLY | O_RDWR)) !== 0,
|
|
@@ -2871,15 +3844,16 @@ const __fsMod = (() => {
|
|
|
2871
3844
|
create: (flags & O_CREAT) !== 0,
|
|
2872
3845
|
truncate: (flags & O_TRUNC) !== 0,
|
|
2873
3846
|
exclusive: (flags & O_EXCL) !== 0,
|
|
3847
|
+
directory: (flags & O_DIRECTORY) !== 0,
|
|
2874
3848
|
};
|
|
2875
3849
|
}
|
|
2876
3850
|
const s = String(flags === undefined || flags === null ? "r" : flags);
|
|
2877
3851
|
const plus = s.indexOf("+") !== -1;
|
|
2878
3852
|
const exclusive = s.indexOf("x") !== -1;
|
|
2879
3853
|
const base = s.charAt(0);
|
|
2880
|
-
if (base === "w") return { read: plus, write: true, append: false, create: true, truncate: true, exclusive };
|
|
2881
|
-
if (base === "a") return { read: plus, write: true, append: true, create: true, truncate: false, exclusive };
|
|
2882
|
-
return { read: true, write: plus, append: false, create: false, truncate: false, exclusive };
|
|
3854
|
+
if (base === "w") return { read: plus, write: true, append: false, create: true, truncate: true, exclusive, directory: false };
|
|
3855
|
+
if (base === "a") return { read: plus, write: true, append: true, create: true, truncate: false, exclusive, directory: false };
|
|
3856
|
+
return { read: true, write: plus, append: false, create: false, truncate: false, exclusive, directory: false };
|
|
2883
3857
|
}
|
|
2884
3858
|
|
|
2885
3859
|
// ── FileHandle — returned from fs.promises.open ──
|
|
@@ -2889,6 +3863,35 @@ const __fsMod = (() => {
|
|
|
2889
3863
|
// hibernation and partial reads/writes never move whole files. Unflushed
|
|
2890
3864
|
// sync writes (__vfsWrites) take read precedence; the local sync view is
|
|
2891
3865
|
// overlaid on writes so readFileSync stays coherent.
|
|
3866
|
+
// The prior content a sync positional write or truncate lays its bytes
|
|
3867
|
+
// over. Shared by the descriptor forms and truncateSync.
|
|
3868
|
+
function _residentWriteBase(absPath, p, syscall) {
|
|
3869
|
+
const cell = _writtenCell(absPath);
|
|
3870
|
+
if (cell !== undefined) {
|
|
3871
|
+
const denial = _denialCode(cell);
|
|
3872
|
+
if (denial) throw _fsErr(denial, syscall, p);
|
|
3873
|
+
_residencySatisfied(absPath);
|
|
3874
|
+
return _asBytes(cell);
|
|
3875
|
+
}
|
|
3876
|
+
const asyncForm = "the async fs." + syscall + "/fs.promises form";
|
|
3877
|
+
const st = statSync(p, { throwIfNoEntry: false });
|
|
3878
|
+
// Non-resident and non-empty: writing onto a zero-filled base would
|
|
3879
|
+
// silently destroy the bytes we cannot see. Refuse instead.
|
|
3880
|
+
if (st !== undefined && st.size !== 0) throw _notResidentError(absPath, p, syscall, asyncForm);
|
|
3881
|
+
// Absent or empty: an empty base is the true prior content (the namespace
|
|
3882
|
+
// names every path this credential can see).
|
|
3883
|
+
return new Uint8Array(0);
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
async function _fsyncAsync(absPath, p) {
|
|
3887
|
+
const supervisor = _supervisor();
|
|
3888
|
+
if (supervisor) {
|
|
3889
|
+
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
3890
|
+
await _awaitStructuralOrder(absPath);
|
|
3891
|
+
}
|
|
3892
|
+
_markVfsStale();
|
|
3893
|
+
}
|
|
3894
|
+
|
|
2892
3895
|
let __nextFileHandleFd = 3;
|
|
2893
3896
|
const __fileHandles = new Map();
|
|
2894
3897
|
class __FileHandle {
|
|
@@ -2963,16 +3966,21 @@ const __fsMod = (() => {
|
|
|
2963
3966
|
);
|
|
2964
3967
|
if (meta && meta.type === "file") writeAt = Number(meta.size) || 0;
|
|
2965
3968
|
}
|
|
2966
|
-
await
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3969
|
+
await _ownMutation(
|
|
3970
|
+
this._abs,
|
|
3971
|
+
() => _fsRpc(
|
|
3972
|
+
supervisor.fsWriteRange(this._abs, writeAt, bytes),
|
|
3973
|
+
"write", this._path,
|
|
3974
|
+
(result) => result,
|
|
3975
|
+
),
|
|
3976
|
+
() => {
|
|
3977
|
+
const key = _strip(this._abs);
|
|
3978
|
+
if (!Object.prototype.hasOwnProperty.call(__vfsWrites, key) &&
|
|
3979
|
+
__vfsWriteGenerations[key] === overlayGeneration) {
|
|
3980
|
+
_overlayLocalCell(this._abs, writeAt, bytes);
|
|
3981
|
+
}
|
|
3982
|
+
},
|
|
2970
3983
|
);
|
|
2971
|
-
const key = _strip(this._abs);
|
|
2972
|
-
if (!Object.prototype.hasOwnProperty.call(__vfsWrites, key) &&
|
|
2973
|
-
__vfsWriteGenerations[key] === overlayGeneration) {
|
|
2974
|
-
_overlayLocalCell(this._abs, writeAt, bytes);
|
|
2975
|
-
}
|
|
2976
3984
|
});
|
|
2977
3985
|
_markVfsStale();
|
|
2978
3986
|
} else {
|
|
@@ -3028,24 +4036,7 @@ const __fsMod = (() => {
|
|
|
3028
4036
|
if (bytes === undefined) throw this._notResident(syscall);
|
|
3029
4037
|
return bytes;
|
|
3030
4038
|
}
|
|
3031
|
-
_writeBase(syscall) {
|
|
3032
|
-
const bytes = this._residentBytes(syscall);
|
|
3033
|
-
if (bytes !== undefined) return bytes;
|
|
3034
|
-
const st = statSync(this._path, { throwIfNoEntry: false });
|
|
3035
|
-
// Non-resident and non-empty: writing onto a zero-filled base would
|
|
3036
|
-
// silently destroy the bytes we cannot see. Refuse instead.
|
|
3037
|
-
if (st !== undefined && st.size !== 0) throw this._notResident(syscall);
|
|
3038
|
-
// Absent or empty — an empty base IS the true prior content, but only
|
|
3039
|
-
// where absence is knowledge. A path the view never mapped may hold bytes
|
|
3040
|
-
// this process cannot see, and an O_APPEND descriptor onto a zero-filled
|
|
3041
|
-
// base would overwrite the file with the fragment it meant to add.
|
|
3042
|
-
if (st === undefined && !_absenceIsKnown(this._abs)) {
|
|
3043
|
-
throw _refuseUnmapped(
|
|
3044
|
-
this._abs, this._path, syscall, "the async fs." + syscall + "/fs.promises form",
|
|
3045
|
-
);
|
|
3046
|
-
}
|
|
3047
|
-
return new Uint8Array(0);
|
|
3048
|
-
}
|
|
4039
|
+
_writeBase(syscall) { return _residentWriteBase(this._abs, this._path, syscall); }
|
|
3049
4040
|
_commit(next) {
|
|
3050
4041
|
_parkWrite(_strip(this._abs), next);
|
|
3051
4042
|
_markVfsStale();
|
|
@@ -3095,9 +4086,38 @@ const __fsMod = (() => {
|
|
|
3095
4086
|
}
|
|
3096
4087
|
async chmod(mode) { this._assertOpen("fchmod"); await _chmodAsync(this._path, mode); }
|
|
3097
4088
|
async chown(uid, gid) { this._assertOpen("fchown"); await _chownAsync(this._path, uid, gid, undefined, "fchown"); }
|
|
3098
|
-
async utimes(atime, mtime) { this._assertOpen("futimes"); await _utimesAsync(this._path, atime, mtime); }
|
|
3099
|
-
|
|
3100
|
-
|
|
4089
|
+
async utimes(atime, mtime) { this._assertOpen("futimes"); await _utimesAsync(this._path, atime, mtime, undefined, "futimes"); }
|
|
4090
|
+
// Durability here means: every byte parked for this path has reached
|
|
4091
|
+
// the authority and every mutation queued for it has landed. The bridge
|
|
4092
|
+
// itself is synchronously durable, so no further RPC is owed.
|
|
4093
|
+
async sync() { this._assertOpen("fsync"); await _fsyncAsync(this._abs, this._path); }
|
|
4094
|
+
async datasync() { this._assertOpen("fdatasync"); await _fsyncAsync(this._abs, this._path); }
|
|
4095
|
+
// Scatter/gather over the ranged read/write: one sequential pass per
|
|
4096
|
+
// buffer, stopping at the first short read, positions advanced by hand
|
|
4097
|
+
// when an explicit one was given so the file position stays untouched.
|
|
4098
|
+
async readv(buffers, position) {
|
|
4099
|
+
this._assertOpen("read");
|
|
4100
|
+
let bytesRead = 0;
|
|
4101
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4102
|
+
for (const buffer of buffers) {
|
|
4103
|
+
const r = await this.read(buffer, 0, buffer.byteLength, pos);
|
|
4104
|
+
bytesRead += r.bytesRead;
|
|
4105
|
+
if (pos !== null) pos += r.bytesRead;
|
|
4106
|
+
if (r.bytesRead < buffer.byteLength) break;
|
|
4107
|
+
}
|
|
4108
|
+
return { bytesRead, buffers };
|
|
4109
|
+
}
|
|
4110
|
+
async writev(buffers, position) {
|
|
4111
|
+
this._assertOpen("write");
|
|
4112
|
+
let bytesWritten = 0;
|
|
4113
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4114
|
+
for (const buffer of buffers) {
|
|
4115
|
+
const r = await this.write(buffer, 0, buffer.byteLength, pos);
|
|
4116
|
+
bytesWritten += r.bytesWritten;
|
|
4117
|
+
if (pos !== null) pos += r.bytesWritten;
|
|
4118
|
+
}
|
|
4119
|
+
return { bytesWritten, buffers };
|
|
4120
|
+
}
|
|
3101
4121
|
async close() { this._assertOpen("close"); this._closed = true; __fileHandles.delete(this.fd); }
|
|
3102
4122
|
[Symbol.asyncDispose]() { return this.close(); }
|
|
3103
4123
|
}
|
|
@@ -3118,9 +4138,14 @@ const __fsMod = (() => {
|
|
|
3118
4138
|
if (localStat && localStat.isDirectory()) throw _fsErr("EISDIR", "open", path);
|
|
3119
4139
|
}
|
|
3120
4140
|
const exists = !!liveMeta || !!localStat;
|
|
4141
|
+
if (exists && fl.directory) throw _fsErr("ENOTDIR", "open", path);
|
|
3121
4142
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3122
4143
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3123
4144
|
let size = liveMeta ? (Number(liveMeta.size) || 0) : (localStat ? localStat.size : 0);
|
|
4145
|
+
// The authority answered, so what it said is what the sync view keeps:
|
|
4146
|
+
// its stat when something is there, and when nothing is, the file this
|
|
4147
|
+
// open creates is the process's own.
|
|
4148
|
+
if (!liveMeta && supervisor && !localStat) _createdHere.add(_strip(absPath));
|
|
3124
4149
|
if (!exists) {
|
|
3125
4150
|
await _writeFileAsync(path, new Uint8Array(0));
|
|
3126
4151
|
if (mode !== undefined && mode !== null) {
|
|
@@ -3141,9 +4166,11 @@ const __fsMod = (() => {
|
|
|
3141
4166
|
const st = statSync(path, { throwIfNoEntry: false });
|
|
3142
4167
|
if (st && st.isDirectory()) throw _fsErr("EISDIR", "open", path);
|
|
3143
4168
|
const exists = st !== undefined;
|
|
4169
|
+
if (exists && fl.directory) throw _fsErr("ENOTDIR", "open", path);
|
|
3144
4170
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3145
4171
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3146
4172
|
if (fl.write || !exists) _ensureWritable(absPath, "open", path);
|
|
4173
|
+
if (!exists) _noteCreation(_strip(absPath));
|
|
3147
4174
|
let size = exists ? st.size : 0;
|
|
3148
4175
|
// O_TRUNC means "make it empty", so zeroing is the requested semantic,
|
|
3149
4176
|
// and creating a genuinely absent file is too. O_APPEND must NEVER
|
|
@@ -3261,6 +4288,52 @@ const __fsMod = (() => {
|
|
|
3261
4288
|
function fsyncSync(fd) { if (!_isStdioFd(fd)) _fdHandle(fd, "fsync"); _markVfsStale(); }
|
|
3262
4289
|
function fdatasyncSync(fd) { if (!_isStdioFd(fd)) _fdHandle(fd, "fdatasync"); _markVfsStale(); }
|
|
3263
4290
|
|
|
4291
|
+
// Descriptor metadata, sync: the path forms on the handle's path, so the
|
|
4292
|
+
// local overlay (times, modes, ownership) and the parked write-through are
|
|
4293
|
+
// exactly what utimesSync/chmodSync/chownSync give.
|
|
4294
|
+
function futimesSync(fd, atime, mtime) {
|
|
4295
|
+
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "futimes", fd);
|
|
4296
|
+
const handle = _fdHandle(fd, "futimes");
|
|
4297
|
+
_recordLocalTimes(handle._abs, atime, mtime, "futimes", handle._path);
|
|
4298
|
+
}
|
|
4299
|
+
function fchmodSync(fd, mode) {
|
|
4300
|
+
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchmod", fd);
|
|
4301
|
+
const handle = _fdHandle(fd, "fchmod");
|
|
4302
|
+
const k = _strip(handle._abs);
|
|
4303
|
+
_ensureModeOwner(handle._abs, "fchmod", handle._path);
|
|
4304
|
+
_localModes[k] = _coerceMode(mode, "fchmod", handle._path);
|
|
4305
|
+
_pendingModes.add(k);
|
|
4306
|
+
}
|
|
4307
|
+
function fchownSync(fd, uid, gid) {
|
|
4308
|
+
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchown", fd);
|
|
4309
|
+
const handle = _fdHandle(fd, "fchown");
|
|
4310
|
+
_detachStructuralMutation(_chownQueued(handle._path, uid, gid, undefined, "fchown"));
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
// Scatter/gather, sync: readSync/writeSync per buffer; stdio fds keep the
|
|
4314
|
+
// stream behaviour those two already give them.
|
|
4315
|
+
function readvSync(fd, buffers, position) {
|
|
4316
|
+
let bytesRead = 0;
|
|
4317
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4318
|
+
for (const buffer of buffers) {
|
|
4319
|
+
const n = readSync(fd, buffer, 0, buffer.byteLength, pos);
|
|
4320
|
+
bytesRead += n;
|
|
4321
|
+
if (pos !== null) pos += n;
|
|
4322
|
+
if (n < buffer.byteLength) break;
|
|
4323
|
+
}
|
|
4324
|
+
return bytesRead;
|
|
4325
|
+
}
|
|
4326
|
+
function writevSync(fd, buffers, position) {
|
|
4327
|
+
let bytesWritten = 0;
|
|
4328
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4329
|
+
for (const buffer of buffers) {
|
|
4330
|
+
const n = writeSync(fd, buffer, 0, buffer.byteLength, pos);
|
|
4331
|
+
bytesWritten += n;
|
|
4332
|
+
if (pos !== null) pos += n;
|
|
4333
|
+
}
|
|
4334
|
+
return bytesWritten;
|
|
4335
|
+
}
|
|
4336
|
+
|
|
3264
4337
|
// ── callback forms (live I/O — these CAN reach the supervisor) ──
|
|
3265
4338
|
function open(path, flags, mode, cb) {
|
|
3266
4339
|
if (typeof flags === "function") { cb = flags; flags = undefined; mode = undefined; }
|
|
@@ -3341,21 +4414,56 @@ const __fsMod = (() => {
|
|
|
3341
4414
|
if (!handle) return;
|
|
3342
4415
|
handle.truncate(len).then(() => cb(null)).catch((e) => cb(e));
|
|
3343
4416
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
// it here rather than let an EBADF vanish.
|
|
3349
|
-
if (typeof cb !== "function") { if (err) throw err; return; }
|
|
3350
|
-
queueMicrotask(() => cb(err));
|
|
3351
|
-
}
|
|
3352
|
-
function fdatasync(fd, cb) {
|
|
4417
|
+
// The async forms CAN wait for durability: the parked bytes are flushed
|
|
4418
|
+
// and the path's queued mutations awaited before the callback fires.
|
|
4419
|
+
function _fsyncCb(fd, syscall, cb) {
|
|
4420
|
+
let handle = null;
|
|
3353
4421
|
let err = null;
|
|
3354
|
-
try {
|
|
4422
|
+
try { if (!_isStdioFd(fd)) handle = _fdHandle(fd, syscall); } catch (e) { err = e; }
|
|
3355
4423
|
// Without a callback there is nowhere to deliver the failure, so raise
|
|
3356
4424
|
// it here rather than let an EBADF vanish.
|
|
3357
|
-
if (typeof cb !== "function") { if (err) throw err; return; }
|
|
3358
|
-
queueMicrotask(() => cb(err));
|
|
4425
|
+
if (typeof cb !== "function") { if (err) throw err; _markVfsStale(); return; }
|
|
4426
|
+
if (err || !handle) { _markVfsStale(); queueMicrotask(() => cb(err)); return; }
|
|
4427
|
+
_fsyncAsync(handle._abs, handle._path).then(() => cb(null)).catch((e) => cb(e));
|
|
4428
|
+
}
|
|
4429
|
+
function fsync(fd, cb) { _fsyncCb(fd, "fsync", cb); }
|
|
4430
|
+
function fdatasync(fd, cb) { _fsyncCb(fd, "fdatasync", cb); }
|
|
4431
|
+
function futimes(fd, atime, mtime, cb) {
|
|
4432
|
+
if (_isStdioFd(fd)) { queueMicrotask(() => cb(_fsErr("EINVAL", "futimes", fd))); return; }
|
|
4433
|
+
const handle = _fdFor(fd, "futimes", cb);
|
|
4434
|
+
if (!handle) return;
|
|
4435
|
+
handle.utimes(atime, mtime).then(() => cb(null)).catch((e) => cb(e));
|
|
4436
|
+
}
|
|
4437
|
+
function readv(fd, buffers, position, cb) {
|
|
4438
|
+
if (typeof position === "function") { cb = position; position = null; }
|
|
4439
|
+
const run = async () => {
|
|
4440
|
+
let bytesRead = 0;
|
|
4441
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4442
|
+
for (const buffer of buffers) {
|
|
4443
|
+
const n = await new Promise((res, rej) =>
|
|
4444
|
+
read(fd, buffer, 0, buffer.byteLength, pos, (e, count) => (e ? rej(e) : res(count))));
|
|
4445
|
+
bytesRead += n;
|
|
4446
|
+
if (pos !== null) pos += n;
|
|
4447
|
+
if (n < buffer.byteLength) break;
|
|
4448
|
+
}
|
|
4449
|
+
return bytesRead;
|
|
4450
|
+
};
|
|
4451
|
+
run().then((n) => cb(null, n, buffers)).catch((e) => cb(e));
|
|
4452
|
+
}
|
|
4453
|
+
function writev(fd, buffers, position, cb) {
|
|
4454
|
+
if (typeof position === "function") { cb = position; position = null; }
|
|
4455
|
+
const run = async () => {
|
|
4456
|
+
let bytesWritten = 0;
|
|
4457
|
+
let pos = _isCurrentPos(position) ? null : Number(position);
|
|
4458
|
+
for (const buffer of buffers) {
|
|
4459
|
+
const n = await new Promise((res, rej) =>
|
|
4460
|
+
write(fd, buffer, 0, buffer.byteLength, pos, (e, count) => (e ? rej(e) : res(count))));
|
|
4461
|
+
bytesWritten += n;
|
|
4462
|
+
if (pos !== null) pos += n;
|
|
4463
|
+
}
|
|
4464
|
+
return bytesWritten;
|
|
4465
|
+
};
|
|
4466
|
+
run().then((n) => cb(null, n, buffers)).catch((e) => cb(e));
|
|
3359
4467
|
}
|
|
3360
4468
|
function fchmod(fd, mode, cb) {
|
|
3361
4469
|
const handle = _fdFor(fd, "fchmod", cb);
|
|
@@ -3368,6 +4476,135 @@ const __fsMod = (() => {
|
|
|
3368
4476
|
handle.chown(uid, gid).then(() => cb(null)).catch((error) => cb(error));
|
|
3369
4477
|
}
|
|
3370
4478
|
|
|
4479
|
+
// ── Dir — fs.opendir / opendirSync / fs.promises.opendir ──
|
|
4480
|
+
// The listing is taken whole at open (both readdir forms already return
|
|
4481
|
+
// one) and handed out one Dirent per read(); closing a closed Dir is
|
|
4482
|
+
// ERR_DIR_CLOSED as in Node.
|
|
4483
|
+
class __Dir {
|
|
4484
|
+
constructor(path, entries) {
|
|
4485
|
+
this.path = path;
|
|
4486
|
+
this._entries = entries;
|
|
4487
|
+
this._at = 0;
|
|
4488
|
+
this._closed = false;
|
|
4489
|
+
}
|
|
4490
|
+
_assertOpen() {
|
|
4491
|
+
if (this._closed) {
|
|
4492
|
+
const err = new Error("Directory handle was closed");
|
|
4493
|
+
err.code = "ERR_DIR_CLOSED";
|
|
4494
|
+
throw err;
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
readSync() {
|
|
4498
|
+
this._assertOpen();
|
|
4499
|
+
return this._at < this._entries.length ? this._entries[this._at++] : null;
|
|
4500
|
+
}
|
|
4501
|
+
read(cb) {
|
|
4502
|
+
const result = new Promise((res, rej) => { try { res(this.readSync()); } catch (e) { rej(e); } });
|
|
4503
|
+
if (typeof cb !== "function") return result;
|
|
4504
|
+
result.then((entry) => cb(null, entry), (e) => cb(e));
|
|
4505
|
+
}
|
|
4506
|
+
closeSync() { this._assertOpen(); this._closed = true; }
|
|
4507
|
+
close(cb) {
|
|
4508
|
+
const result = new Promise((res, rej) => { try { this.closeSync(); res(); } catch (e) { rej(e); } });
|
|
4509
|
+
if (typeof cb !== "function") return result;
|
|
4510
|
+
result.then(() => cb(null), (e) => cb(e));
|
|
4511
|
+
}
|
|
4512
|
+
async *[Symbol.asyncIterator]() {
|
|
4513
|
+
try {
|
|
4514
|
+
for (;;) {
|
|
4515
|
+
const entry = this.readSync();
|
|
4516
|
+
if (entry === null) break;
|
|
4517
|
+
yield entry;
|
|
4518
|
+
}
|
|
4519
|
+
} finally {
|
|
4520
|
+
if (!this._closed) this._closed = true;
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
[Symbol.asyncDispose]() { return this._closed ? Promise.resolve() : this.close(); }
|
|
4524
|
+
[Symbol.dispose]() { if (!this._closed) this.closeSync(); }
|
|
4525
|
+
}
|
|
4526
|
+
function opendirSync(p, opts) {
|
|
4527
|
+
return new __Dir(String(p), readdirSync(p, { withFileTypes: true }));
|
|
4528
|
+
}
|
|
4529
|
+
async function _opendirAsync(p, opts) {
|
|
4530
|
+
return new __Dir(String(p), await _readdirAsync(p, { withFileTypes: true }));
|
|
4531
|
+
}
|
|
4532
|
+
function opendir(p, opts, cb) {
|
|
4533
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4534
|
+
_opendirAsync(p, opts).then((dir) => cb(null, dir)).catch((e) => cb(e));
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
// ── statfs ──
|
|
4538
|
+
// Honest constants for the VFS: the block size is the SQLite chunk unit's
|
|
4539
|
+
// page size, and blocks is the configured VFS capacity in those blocks.
|
|
4540
|
+
// The supervisor surface exposes no usage counter, so bfree/bavail report
|
|
4541
|
+
// the whole capacity; the VFS has no inode table, so files/ffree are 0
|
|
4542
|
+
// (unknown, not "none"). \`type\` is 0: this is no kernel filesystem and
|
|
4543
|
+
// no magic number would be true of it.
|
|
4544
|
+
const _STATFS_BSIZE = 4096;
|
|
4545
|
+
const _STATFS_BLOCKS = Math.floor(${VFS_CAPACITY} / 4096);
|
|
4546
|
+
function _statfsObject(opts) {
|
|
4547
|
+
const wrap = opts && opts.bigint ? BigInt : Number;
|
|
4548
|
+
return {
|
|
4549
|
+
type: wrap(0),
|
|
4550
|
+
bsize: wrap(_STATFS_BSIZE),
|
|
4551
|
+
blocks: wrap(_STATFS_BLOCKS),
|
|
4552
|
+
bfree: wrap(_STATFS_BLOCKS),
|
|
4553
|
+
bavail: wrap(_STATFS_BLOCKS),
|
|
4554
|
+
files: wrap(0),
|
|
4555
|
+
ffree: wrap(0),
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
function statfsSync(p, opts) {
|
|
4559
|
+
if (statSync(p, { throwIfNoEntry: false }) === undefined) throw _fsErr("ENOENT", "statfs", p);
|
|
4560
|
+
return _statfsObject(opts);
|
|
4561
|
+
}
|
|
4562
|
+
async function _statfsAsync(p, opts) {
|
|
4563
|
+
await _statAsync(p);
|
|
4564
|
+
return _statfsObject(opts);
|
|
4565
|
+
}
|
|
4566
|
+
function statfs(p, opts, cb) {
|
|
4567
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4568
|
+
_statfsAsync(p, opts).then((s) => cb(null, s)).catch((e) => cb(e));
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
// ── remaining callback forms of the path operations ──
|
|
4572
|
+
function rm(p, opts, cb) {
|
|
4573
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4574
|
+
_rmAsync(p, opts).then(() => cb(null)).catch((e) => cb(e));
|
|
4575
|
+
}
|
|
4576
|
+
function cp(src, dest, opts, cb) {
|
|
4577
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4578
|
+
promises.cp(src, dest, opts).then(() => cb(null)).catch((e) => cb(e));
|
|
4579
|
+
}
|
|
4580
|
+
function truncate(p, len, cb) {
|
|
4581
|
+
if (typeof len === "function") { cb = len; len = 0; }
|
|
4582
|
+
_truncateAsync(p, len || 0).then(() => cb(null)).catch((e) => cb(e));
|
|
4583
|
+
}
|
|
4584
|
+
function copyFile(src, dest, mode, cb) {
|
|
4585
|
+
if (typeof mode === "function") { cb = mode; mode = 0; }
|
|
4586
|
+
promises.copyFile(src, dest, mode).then(() => cb(null)).catch((e) => cb(e));
|
|
4587
|
+
}
|
|
4588
|
+
function mkdtemp(prefix, opts, cb) {
|
|
4589
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4590
|
+
_mkdtempAsync(prefix).then((name) => cb(null, name)).catch((e) => cb(e));
|
|
4591
|
+
}
|
|
4592
|
+
function link(existingPath, newPath, cb) { queueMicrotask(() => cb(_fsErr("ENOSYS", "link", newPath))); }
|
|
4593
|
+
function symlink(target, path, type, cb) {
|
|
4594
|
+
if (typeof type === "function") { cb = type; type = undefined; }
|
|
4595
|
+
_symlinkAsync(target, path).then(() => cb(null)).catch((e) => cb(e));
|
|
4596
|
+
}
|
|
4597
|
+
function readlink(p, opts, cb) {
|
|
4598
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4599
|
+
_readlinkAsync(p).then((target) => cb(null, target)).catch((e) => cb(e));
|
|
4600
|
+
}
|
|
4601
|
+
function realpath(p, opts, cb) {
|
|
4602
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
4603
|
+
promises.realpath(p, opts).then((r) => cb(null, r)).catch((e) => cb(e));
|
|
4604
|
+
}
|
|
4605
|
+
realpath.native = realpath;
|
|
4606
|
+
function lchmod(p, mode, cb) { chmod(p, mode, cb); }
|
|
4607
|
+
|
|
3371
4608
|
// ── promises namespace (W3: full surface, VFS-backed) ──
|
|
3372
4609
|
// We can't forward to workerd's node:fs/promises because that operates
|
|
3373
4610
|
// on a real-host filesystem, not our VFS. So every method is shim'd
|
|
@@ -3385,22 +4622,8 @@ const __fsMod = (() => {
|
|
|
3385
4622
|
// W3 additions:
|
|
3386
4623
|
appendFile: async (p, d, o) => { await _appendFileAsync(p, d, o); },
|
|
3387
4624
|
lstat: (p) => new Promise((res, rej) => lstat(p, (e, s) => e ? rej(e) : res(s))),
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
const k = _strip(_resolve(p));
|
|
3391
|
-
const prefix = k + "/";
|
|
3392
|
-
if (o.recursive) {
|
|
3393
|
-
if (__vfsBundle) {
|
|
3394
|
-
if (k in __vfsBundle) delete __vfsBundle[k];
|
|
3395
|
-
for (const bk of __residentUnder(prefix)) delete __vfsBundle[bk];
|
|
3396
|
-
}
|
|
3397
|
-
if (__vfsWrites) for (const wk of Object.keys(__vfsWrites)) if (wk === k || wk.startsWith(prefix)) delete __vfsWrites[wk];
|
|
3398
|
-
if (__vfsDirs) for (const dk of Object.keys(__vfsDirs)) if (dk === k || dk.startsWith(prefix)) delete __vfsDirs[dk];
|
|
3399
|
-
_forgetSyncTree(k);
|
|
3400
|
-
} else {
|
|
3401
|
-
try { await _unlinkAsync(p); } catch (e) { if (!o.force) throw e; }
|
|
3402
|
-
}
|
|
3403
|
-
},
|
|
4625
|
+
// The same local retraction and queued authority removal rmSync does.
|
|
4626
|
+
rm: async (p, opts) => { await _rmAsync(p, opts); },
|
|
3404
4627
|
cp: async (src, dest, opts) => {
|
|
3405
4628
|
const o = opts || {};
|
|
3406
4629
|
const srcAbs = _resolve(src);
|
|
@@ -3419,15 +4642,16 @@ const __fsMod = (() => {
|
|
|
3419
4642
|
// not just the local cache — is required so a subsequent async fs op
|
|
3420
4643
|
// (e.g. fs.promises.rename of a copied file, as create-cloudflare
|
|
3421
4644
|
// does for __dot__gitignore) sees the copy in the VFS instead of
|
|
3422
|
-
// ENOENT.
|
|
3423
|
-
|
|
4645
|
+
// ENOENT. Each directory is made as mkdir makes it, so the namespace's
|
|
4646
|
+
// overlay lists it for the writes beneath it.
|
|
4647
|
+
await _mkdirAsync("/" + destK, { recursive: true });
|
|
3424
4648
|
const walk = async (relDir) => {
|
|
3425
4649
|
const absDir = relDir ? srcAbs + "/" + relDir : srcAbs;
|
|
3426
4650
|
const ents = await _readdirAsync(absDir, { withFileTypes: true });
|
|
3427
4651
|
for (const ent of ents) {
|
|
3428
4652
|
const rel = relDir ? relDir + "/" + ent.name : ent.name;
|
|
3429
4653
|
if (ent.isDirectory && ent.isDirectory()) {
|
|
3430
|
-
|
|
4654
|
+
await _mkdirAsync("/" + destK + "/" + rel, { recursive: true });
|
|
3431
4655
|
await walk(rel);
|
|
3432
4656
|
} else {
|
|
3433
4657
|
await _writeFileAsync("/" + destK + "/" + rel, await _readFileAsync(srcAbs + "/" + rel));
|
|
@@ -3436,7 +4660,12 @@ const __fsMod = (() => {
|
|
|
3436
4660
|
};
|
|
3437
4661
|
await walk("");
|
|
3438
4662
|
},
|
|
3439
|
-
copyFile: async (src, dest) => {
|
|
4663
|
+
copyFile: async (src, dest, mode) => {
|
|
4664
|
+
if ((Number(mode) & __fsConstants.COPYFILE_EXCL) !== 0 && await _existsAsync(dest)) {
|
|
4665
|
+
throw _fsErr("EEXIST", "copyfile", dest);
|
|
4666
|
+
}
|
|
4667
|
+
await _writeFileAsync(dest, await _readFileAsync(src));
|
|
4668
|
+
},
|
|
3440
4669
|
rename: async (oldP, newP) => { await _renameAsync(oldP, newP); },
|
|
3441
4670
|
rmdir: async (p) => { await _rmdirAsync(p); },
|
|
3442
4671
|
realpath: async (p) => __pathMod.resolve(String(p)),
|
|
@@ -3450,12 +4679,10 @@ const __fsMod = (() => {
|
|
|
3450
4679
|
symlink: async (target, path) => { await _symlinkAsync(target, path); },
|
|
3451
4680
|
link: async () => { throw _fsErr("ENOSYS", "link", ""); },
|
|
3452
4681
|
readlink: async (p) => _readlinkAsync(p),
|
|
3453
|
-
mkdtemp: async (prefix) =>
|
|
3454
|
-
const name = String(prefix) + Math.random().toString(36).slice(2, 10);
|
|
3455
|
-
mkdirSync(name, { recursive: true });
|
|
3456
|
-
return name;
|
|
3457
|
-
},
|
|
4682
|
+
mkdtemp: async (prefix) => _mkdtempAsync(prefix),
|
|
3458
4683
|
open: async (path, flags, mode) => _openAsync(path, flags, mode),
|
|
4684
|
+
opendir: async (p, opts) => _opendirAsync(p, opts),
|
|
4685
|
+
statfs: async (p, opts) => _statfsAsync(p, opts),
|
|
3459
4686
|
watch: async function* (filename, opts) {
|
|
3460
4687
|
// Minimal async iter — polls _bundleLookup every 500ms and yields
|
|
3461
4688
|
// a single \`change\` event when content differs. Adequate for
|
|
@@ -3500,8 +4727,9 @@ const __fsMod = (() => {
|
|
|
3500
4727
|
},
|
|
3501
4728
|
};
|
|
3502
4729
|
|
|
3503
|
-
// ── constants ──
|
|
3504
|
-
const constants =
|
|
4730
|
+
// ── constants ── the shared linux x64 table (see __fsConstants).
|
|
4731
|
+
const constants = __fsConstants;
|
|
4732
|
+
promises.constants = constants;
|
|
3505
4733
|
|
|
3506
4734
|
// fs.ReadStream / fs.WriteStream classes. Real Node exposes these as
|
|
3507
4735
|
// constructors; graceful-fs (bundled by degit → create-cloudflare)
|
|
@@ -3591,10 +4819,15 @@ const __fsMod = (() => {
|
|
|
3591
4819
|
const __fsExports = {
|
|
3592
4820
|
readFileSync, writeFileSync, appendFileSync, existsSync, statSync, lstatSync,
|
|
3593
4821
|
readdirSync, mkdirSync, unlinkSync, rmdirSync, renameSync, copyFileSync,
|
|
3594
|
-
realpathSync, utimesSync, lutimesSync, chmodSync, accessSync,
|
|
4822
|
+
realpathSync, utimesSync, lutimesSync, chmodSync, lchmodSync, chownSync, lchownSync, accessSync,
|
|
4823
|
+
rmSync, cpSync, mkdtempSync, truncateSync, linkSync, opendirSync, statfsSync,
|
|
3595
4824
|
openSync, closeSync, readSync, writeSync, fstatSync, ftruncateSync, fsyncSync, fdatasyncSync,
|
|
3596
|
-
|
|
3597
|
-
|
|
4825
|
+
futimesSync, fchmodSync, fchownSync, readvSync, writevSync,
|
|
4826
|
+
open, close, read, write, fstat, ftruncate, fsync, fdatasync, fchmod, futimes, readv, writev,
|
|
4827
|
+
readFile, writeFile, appendFile, stat, lstat, readdir, exists, mkdir, unlink, rmdir, rename, utimes, lutimes, chmod, lchmod, chown, lchown, fchown, access,
|
|
4828
|
+
rm, cp, truncate, copyFile, mkdtemp, link, symlink, readlink, realpath, opendir, statfs,
|
|
4829
|
+
Dirent: __Dirent,
|
|
4830
|
+
Dir: __Dir,
|
|
3598
4831
|
promises, constants,
|
|
3599
4832
|
createReadStream: (p, opts) => new (__getReadStream())(p, opts),
|
|
3600
4833
|
createWriteStream: (p, opts) => {
|
|
@@ -3768,8 +5001,7 @@ const __NimbusRelayedWebSocket = (() => {
|
|
|
3768
5001
|
// The barrier the whole relay exists for. Every frame is now a
|
|
3769
5002
|
// supervisor reply, so it can carry the invalidation delta, and
|
|
3770
5003
|
// user code runs only after the resident set has caught up.
|
|
3771
|
-
|
|
3772
|
-
if (typeof acquire === "function") await acquire();
|
|
5004
|
+
await __nimbusInboundBarrier();
|
|
3773
5005
|
this._deliver(event);
|
|
3774
5006
|
}
|
|
3775
5007
|
}
|
|
@@ -3936,13 +5168,10 @@ globalThis.WebSocket = __NimbusRelayedWebSocket;
|
|
|
3936
5168
|
//
|
|
3937
5169
|
// require('node:constants') (and the legacy bare require('constants'))
|
|
3938
5170
|
// expose a FLAT object of POSIX/Linux/OpenSSL constants. Real Node ships
|
|
3939
|
-
// ~234 constants;
|
|
5171
|
+
// ~234 constants; the full fs slice comes from __fsConstants, and the rest
|
|
5172
|
+
// is what actually gets touched by:
|
|
3940
5173
|
// - create-next-app (touches UV_FS_O_FILEMAP — verified via grep on
|
|
3941
5174
|
// unpkg.com/create-next-app@latest/dist/index.js)
|
|
3942
|
-
// - typical fs.openSync flag composition (O_RDONLY, O_WRONLY, O_RDWR,
|
|
3943
|
-
// O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_DIRECTORY, etc.)
|
|
3944
|
-
// - typical fs.access mode composition (F_OK, R_OK, W_OK, X_OK)
|
|
3945
|
-
// - typical fs.copyFile mode composition (COPYFILE_EXCL etc.)
|
|
3946
5175
|
// - signal/errno tables (shared shape with __osMod.constants.signals
|
|
3947
5176
|
// etc. — flat here, nested there; both shapes are real-Node-accurate
|
|
3948
5177
|
// and we expose both via the right module).
|
|
@@ -3987,26 +5216,10 @@ const __constantsMod = {
|
|
|
3987
5216
|
SIGTSTP: 20, SIGTTIN: 21, SIGTTOU: 22, SIGURG: 23, SIGXCPU: 24,
|
|
3988
5217
|
SIGXFSZ: 25, SIGVTALRM: 26, SIGPROF: 27, SIGWINCH: 28, SIGIO: 29,
|
|
3989
5218
|
SIGPOLL: 29, SIGPWR: 30, SIGSYS: 31,
|
|
3990
|
-
// ──
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2,
|
|
3995
|
-
O_CREAT: 64, O_EXCL: 128, O_NOCTTY: 256, O_TRUNC: 512, O_APPEND: 1024,
|
|
3996
|
-
O_DIRECTORY: 65536, O_NOATIME: 262144, O_NOFOLLOW: 131072,
|
|
3997
|
-
O_SYNC: 1052672, O_DSYNC: 4096, O_DIRECT: 16384, O_NONBLOCK: 2048,
|
|
3998
|
-
// libuv-specific fs flag — create-next-app touches this directly.
|
|
3999
|
-
UV_FS_O_FILEMAP: 0,
|
|
4000
|
-
// ── File-permission bits (chmod / stat.mode user/group/other) ─────
|
|
4001
|
-
S_IRWXU: 448, S_IRUSR: 256, S_IWUSR: 128, S_IXUSR: 64,
|
|
4002
|
-
S_IRWXG: 56, S_IRGRP: 32, S_IWGRP: 16, S_IXGRP: 8,
|
|
4003
|
-
S_IRWXO: 7, S_IROTH: 4, S_IWOTH: 2, S_IXOTH: 1,
|
|
4004
|
-
// ── fs.access mode constants ──────────────────────────────────────
|
|
4005
|
-
F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1,
|
|
4006
|
-
// ── fs.copyFile mode constants (libuv shape + Node alias) ─────────
|
|
4007
|
-
UV_FS_COPYFILE_EXCL: 1, COPYFILE_EXCL: 1,
|
|
4008
|
-
UV_FS_COPYFILE_FICLONE: 2, COPYFILE_FICLONE: 2,
|
|
4009
|
-
UV_FS_COPYFILE_FICLONE_FORCE: 4, COPYFILE_FICLONE_FORCE: 4,
|
|
5219
|
+
// ── fs constants (O_*, S_IF*, S_I*, F_OK.., COPYFILE_*, UV_*) ────
|
|
5220
|
+
// Node's constants module is the union of os, fs and crypto constants;
|
|
5221
|
+
// the fs slice is __fsConstants, the same object fs.constants exposes.
|
|
5222
|
+
...__fsConstants,
|
|
4010
5223
|
// ── OpenSSL / TLS option flags ────────────────────────────────────
|
|
4011
5224
|
// Numeric values from real Node v20. Libraries probe defined-ness;
|
|
4012
5225
|
// we ship the surface so constants.SSL_OP_* doesn't undefined-throw.
|
|
@@ -4191,9 +5404,14 @@ ${UNDICI_SHIM_CODE}
|
|
|
4191
5404
|
// ── util module ────────────────────────────────────────────────────
|
|
4192
5405
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4193
5406
|
const __utilMod = {
|
|
4194
|
-
inspect: (o, opts) => {
|
|
4195
|
-
|
|
4196
|
-
|
|
5407
|
+
inspect: (o, opts) => {
|
|
5408
|
+
if (o !== null && typeof o === "object" && __isErrorValue(o)) return __fmtError(o, new Set());
|
|
5409
|
+
try { return JSON.stringify(o, null, 2); } catch { return String(o); }
|
|
5410
|
+
},
|
|
5411
|
+
format: (...args) => {
|
|
5412
|
+
if (args.length === 0) return "";
|
|
5413
|
+
const [fmt, ...a] = args;
|
|
5414
|
+
if (typeof fmt !== "string") return args.map(__fmt).join(" ");
|
|
4197
5415
|
let i = 0;
|
|
4198
5416
|
return fmt.replace(/%[sdifjoO%]/g, (m) => {
|
|
4199
5417
|
if (m === "%%") return "%";
|
|
@@ -4202,7 +5420,7 @@ const __utilMod = {
|
|
|
4202
5420
|
if (m === "%s") return String(v);
|
|
4203
5421
|
if (m === "%d" || m === "%i" || m === "%f") return Number(v).toString();
|
|
4204
5422
|
if (m === "%j") { try { return JSON.stringify(v); } catch { return "[Circular]"; } }
|
|
4205
|
-
if (m === "%o" || m === "%O")
|
|
5423
|
+
if (m === "%o" || m === "%O") return __utilMod.inspect(v);
|
|
4206
5424
|
return String(v);
|
|
4207
5425
|
}) + (i < a.length ? " " + a.slice(i).map(__fmt).join(" ") : "");
|
|
4208
5426
|
},
|
|
@@ -4469,11 +5687,31 @@ const __utilMod = {
|
|
|
4469
5687
|
// (set+restored by __loadModule per call). When set, the fallback becomes
|
|
4470
5688
|
// "file:///" + __currentModulePath so relative URLs resolve against
|
|
4471
5689
|
// the real on-VFS module location — restoring proper import.meta.url
|
|
5690
|
+
//
|
|
5691
|
+
// The leniency is scoped to calls that PASSED a base. \`new URL(x)\` with one
|
|
5692
|
+
// argument is Node's strict absolute-URL parse: it throws
|
|
5693
|
+
// TypeError [ERR_INVALID_URL] for anything that is not already a URL, and
|
|
5694
|
+
// that throw is load-bearing rather than incidental. Node's own ESM
|
|
5695
|
+
// resolver — and every vendored copy of it, including exsolve, which is what
|
|
5696
|
+
// \`nuxt dev\` resolves \`@nuxt/kit\` and \`nuxt\` with — spells the
|
|
5697
|
+
// bare-specifier test as
|
|
5698
|
+
// try { resolved = new URL(specifier); } catch { packageResolve(…); }
|
|
5699
|
+
// Swallowing the throw made \`new URL("@nuxt/kit")\` answer
|
|
5700
|
+
// file:///@nuxt/kit, so moduleResolve never reached packageResolve and
|
|
5701
|
+
// finalizeResolution stat'd /@nuxt/kit — the FILESYSTEM ROOT — instead of
|
|
5702
|
+
// walking <from>/node_modules. Two syscalls and every bare import in the
|
|
5703
|
+
// project was unresolvable. The bundler breakage this wrapper exists for
|
|
5704
|
+
// (\`new URL(rel, import.meta.url)\` where the rolldown/esbuild polyfill
|
|
5705
|
+
// reduced import.meta.url to null/undefined) is always a two-argument call,
|
|
5706
|
+
// so requiring the base argument keeps that fix and restores Node's
|
|
5707
|
+
// contract for the one-argument form. URL.canParse, which is bound
|
|
5708
|
+
// straight off the native constructor below, already answered false for
|
|
5709
|
+
// these strings; the constructor now agrees with it.
|
|
4472
5710
|
(() => {
|
|
4473
5711
|
const _Orig = globalThis.URL;
|
|
4474
5712
|
class _Shim extends _Orig {
|
|
4475
5713
|
constructor(input, base) {
|
|
4476
|
-
if (base == null && typeof input === "string") {
|
|
5714
|
+
if (arguments.length >= 2 && base == null && typeof input === "string") {
|
|
4477
5715
|
try { super(input); return; }
|
|
4478
5716
|
catch {
|
|
4479
5717
|
// X.5-M3: prefer current module path when known, so
|
|
@@ -5055,6 +6293,13 @@ const __childProcessMod = (() => {
|
|
|
5055
6293
|
/**
|
|
5056
6294
|
* Read-loop for a single fd. Long-polls cpReadOutput, pushes chunks
|
|
5057
6295
|
* into the Readable via .push, handles closure.
|
|
6296
|
+
*
|
|
6297
|
+
* What the child printed, and that it closed the stream, is news from
|
|
6298
|
+
* another process, which may have written files before it printed. So a
|
|
6299
|
+
* reply that delivers anything takes the barrier first, and a 'data'
|
|
6300
|
+
* handler that reads what the child wrote reads it current. The poll sends
|
|
6301
|
+
* this process's ACQUIRE arguments and the reply carries the answer, so the
|
|
6302
|
+
* barrier costs no round trip of its own.
|
|
5058
6303
|
*/
|
|
5059
6304
|
async function _runReadLoop(child, fd, stream, sinceSeqRef) {
|
|
5060
6305
|
// Exponential backoff for idle children: start at 100ms, double up
|
|
@@ -5067,12 +6312,14 @@ const __childProcessMod = (() => {
|
|
|
5067
6312
|
while (HAS_SUPERVISOR && child.pid && !child._streamsClosed) {
|
|
5068
6313
|
try {
|
|
5069
6314
|
const r = await __nimbusUseRpcResult(
|
|
5070
|
-
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff),
|
|
6315
|
+
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff, __nimbusVfsAcquireArgs()),
|
|
5071
6316
|
(result) => result,
|
|
5072
6317
|
);
|
|
5073
|
-
|
|
6318
|
+
const chunks = r && Array.isArray(r.chunks) ? r.chunks : [];
|
|
6319
|
+
if (chunks.length > 0 || (r && r.closed)) await __nimbusInboundBarrier(r.acquired);
|
|
6320
|
+
if (chunks.length > 0) {
|
|
5074
6321
|
backoff = 100; // reset — child is producing
|
|
5075
|
-
for (const c of
|
|
6322
|
+
for (const c of chunks) {
|
|
5076
6323
|
// The queue hands back bytes; a Readable given a string would
|
|
5077
6324
|
// encode it again.
|
|
5078
6325
|
stream.write(__BufferMod.from(c.data));
|
|
@@ -5100,16 +6347,19 @@ const __childProcessMod = (() => {
|
|
|
5100
6347
|
|
|
5101
6348
|
/**
|
|
5102
6349
|
* Wait-loop: long-poll cpWait until the child reports exit. Emits
|
|
5103
|
-
* 'exit' once stamped
|
|
6350
|
+
* 'exit' once stamped, behind the barrier: a child's exit is how a parent
|
|
6351
|
+
* learns the files it wrote are there. Answered on the reply, as for the
|
|
6352
|
+
* child's output.
|
|
5104
6353
|
*/
|
|
5105
6354
|
async function _runWaitLoop(child) {
|
|
5106
6355
|
while (HAS_SUPERVISOR && child.pid && !child._exitFired) {
|
|
5107
6356
|
try {
|
|
5108
6357
|
const r = await __nimbusUseRpcResult(
|
|
5109
|
-
__supervisor.cpWait(child.pid, 1000),
|
|
6358
|
+
__supervisor.cpWait(child.pid, 1000, __nimbusVfsAcquireArgs()),
|
|
5110
6359
|
(result) => result,
|
|
5111
6360
|
);
|
|
5112
6361
|
if (r && r.done) {
|
|
6362
|
+
await __nimbusInboundBarrier(r.acquired);
|
|
5113
6363
|
child.exitCode = r.exitCode;
|
|
5114
6364
|
child.signalCode = r.signal;
|
|
5115
6365
|
child._exitFired = true;
|
|
@@ -5290,7 +6540,7 @@ const __childProcessMod = (() => {
|
|
|
5290
6540
|
* stdout/stderr/exit events fire. Callers like cross-spawn.sync
|
|
5291
6541
|
* that read result.status get null until the spawn settles.
|
|
5292
6542
|
* 3. When the parent facet's main drain settles __pendingIO before
|
|
5293
|
-
* reportExit (
|
|
6543
|
+
* reportExit (facets/manager.ts), the result object's fields are
|
|
5294
6544
|
* filled in by the time the supervisor sees the parent exit.
|
|
5295
6545
|
*
|
|
5296
6546
|
* Cross-spawn.sync's typical pattern is "const r = spawnSync(...);
|
|
@@ -5633,7 +6883,7 @@ function __makeProcessStdin() {
|
|
|
5633
6883
|
// facet, so counting it as in-flight work would mean the entry drain
|
|
5634
6884
|
// never sees the program finish.
|
|
5635
6885
|
packet = await __nimbusUseRpcResultUnref(
|
|
5636
|
-
__supervisor.cpReadStdin(liveChildPid, 1000),
|
|
6886
|
+
__supervisor.cpReadStdin(liveChildPid, 1000, __nimbusVfsAcquireArgs()),
|
|
5637
6887
|
(result) => result,
|
|
5638
6888
|
);
|
|
5639
6889
|
readFailures = 0;
|
|
@@ -5650,6 +6900,18 @@ function __makeProcessStdin() {
|
|
|
5650
6900
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
5651
6901
|
continue;
|
|
5652
6902
|
}
|
|
6903
|
+
// A packet that delivers anything — input, its end, a signal, a resize
|
|
6904
|
+
// — hands the program news from outside it, and whatever sent the news
|
|
6905
|
+
// may have written files first (a shell's \`echo v2 > f\` before the
|
|
6906
|
+
// keystroke, the editor's save before the signal). So it takes the
|
|
6907
|
+
// barrier before any handler sees it. The poll sent this process's
|
|
6908
|
+
// ACQUIRE arguments and the packet carries the answer, so a keystroke
|
|
6909
|
+
// costs no round trip of its own. An empty poll delivers nothing and
|
|
6910
|
+
// costs nothing.
|
|
6911
|
+
if (packet && (packet.resize || packet.signal || packet.ended
|
|
6912
|
+
|| (packet.data && packet.data.byteLength > 0))) {
|
|
6913
|
+
await __nimbusInboundBarrier(packet.acquired);
|
|
6914
|
+
}
|
|
5653
6915
|
if (packet && packet.resize) {
|
|
5654
6916
|
__nimbusTtyColumns = Number(packet.resize.columns) || __nimbusTtyColumns;
|
|
5655
6917
|
__nimbusTtyRows = Number(packet.resize.rows) || __nimbusTtyRows;
|
|
@@ -5766,6 +7028,7 @@ function __makeProcessOutputStream(streamName) {
|
|
|
5766
7028
|
errored: null,
|
|
5767
7029
|
write(d, enc, cb) {
|
|
5768
7030
|
if (typeof enc === "function") cb = enc;
|
|
7031
|
+
if (__nimbusProgramStopped) return true;
|
|
5769
7032
|
// The reported result is text; decode the bytes at this edge only.
|
|
5770
7033
|
const s = __nimbusOutText(streamName, __nimbusOutBytes(d, enc));
|
|
5771
7034
|
if (streamName === "stderr") stderr += s;
|
|
@@ -5811,6 +7074,8 @@ function __makeProcessOutputStream(streamName) {
|
|
|
5811
7074
|
function __nimbusReportProcessExit(code, reason) {
|
|
5812
7075
|
if (__nimbusProcessExitReported) return;
|
|
5813
7076
|
__nimbusProcessExitCode = Number(code ?? 0);
|
|
7077
|
+
__nimbusProgramStopped = true;
|
|
7078
|
+
try { if (typeof globalThis.__nimbusStopProgramTimers === "function") globalThis.__nimbusStopProgramTimers(); } catch {}
|
|
5814
7079
|
try { if (__nimbusProcessExitResolve) __nimbusProcessExitResolve(__nimbusProcessExitCode); } catch {}
|
|
5815
7080
|
// Generated lifecycle owners defer the terminal supervisor report until
|
|
5816
7081
|
// their durability boundary has drained. Reporting here would retire the
|
|
@@ -5850,7 +7115,7 @@ const __processMod = {
|
|
|
5850
7115
|
chdir: (d) => { cwd = __pathMod.resolve(cwd || "/home/user", d); },
|
|
5851
7116
|
exit: (code) => {
|
|
5852
7117
|
exitCode = code ?? 0;
|
|
5853
|
-
|
|
7118
|
+
__nimbusEmitExit(exitCode);
|
|
5854
7119
|
__nimbusReportProcessExit(exitCode, "");
|
|
5855
7120
|
throw new __ProcessExit(exitCode);
|
|
5856
7121
|
},
|
|
@@ -6264,14 +7529,27 @@ builtins.http = (() => {
|
|
|
6264
7529
|
return new __hostResponse("Nimbus: no HTTP server is listening in this process", { status: 502 });
|
|
6265
7530
|
}
|
|
6266
7531
|
const url = new URL(request.url);
|
|
7532
|
+
// The request carries the answer to its own barrier (below). It is
|
|
7533
|
+
// Nimbus's, not the program's, so the program does not see it.
|
|
7534
|
+
let delivered;
|
|
7535
|
+
try { delivered = JSON.parse(request.headers.get("X-Nimbus-Vfs-Acquired") || "null"); } catch {}
|
|
6267
7536
|
const headers = {};
|
|
6268
|
-
request.headers.forEach((v, k) => { headers[k] = v; });
|
|
7537
|
+
request.headers.forEach((v, k) => { if (k !== "x-nimbus-vfs-acquired") headers[k] = v; });
|
|
6269
7538
|
// Bytes, not text: a UTF-8 decode corrupts every binary upload, and the
|
|
6270
7539
|
// decoded string is not the TypedArray receiver Buffer methods require.
|
|
6271
7540
|
let body = null;
|
|
6272
7541
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
6273
7542
|
body = new Uint8Array(await request.arrayBuffer());
|
|
6274
7543
|
}
|
|
7544
|
+
// A request is a resumption like any other, and often the second half of
|
|
7545
|
+
// a causal chain — \`echo v2 > f; curl :3000\` — so the handler runs behind
|
|
7546
|
+
// the barrier, after the body has arrived. Sited here, in the dispatch
|
|
7547
|
+
// every resident's server shares, not in a caller that happens to yield
|
|
7548
|
+
// through a barriered timer on the way. The supervisor routed it, so it
|
|
7549
|
+
// arrives with an answer, which is enough when no write preceded the
|
|
7550
|
+
// request since this process last caught up (session/rpc.ts
|
|
7551
|
+
// _acquireForRoutedRequest); otherwise the barrier asks.
|
|
7552
|
+
await __nimbusInboundBarrier(delivered);
|
|
6275
7553
|
const res = server._handleRequest(url.pathname + url.search, request.method, headers, body);
|
|
6276
7554
|
// Return once headers are known. A handler that never sends headers is
|
|
6277
7555
|
// bounded by a header timeout (NOT a body-finish cap) so a hung handler
|
|
@@ -7106,38 +8384,12 @@ builtins["node:util/types"] = builtins["util/types"];
|
|
|
7106
8384
|
// skips it via the same FACET_PROVIDED_PACKAGES list.
|
|
7107
8385
|
builtins.undici = __undiciMod;
|
|
7108
8386
|
|
|
7109
|
-
/**
|
|
7110
|
-
* A cell's dynamic import() call.
|
|
7111
|
-
*
|
|
7112
|
-
* A cell is compiled with new Function, so an import() left in its body
|
|
7113
|
-
* is the RUNTIME's: it resolves against the module map and hands back the
|
|
7114
|
-
* platform's own builtins, not this process's. Real Vite reaches
|
|
7115
|
-
* node:http that way (config.js:14968), got a server whose listen bound no
|
|
7116
|
-
* port here, and the facet exited with no handles the moment boot returned.
|
|
7117
|
-
*
|
|
7118
|
-
* The ESM→CJS rewriter routes those calls here with the cell's own
|
|
7119
|
-
* require, so resolution is the one the process already has. The result is
|
|
7120
|
-
* shaped as a module namespace, since import() resolves to one and callers
|
|
7121
|
-
* destructure named exports off it.
|
|
7122
|
-
*/
|
|
7123
|
-
globalThis.__nimbusCellImport = (req, id) => Promise.resolve().then(() => {
|
|
7124
|
-
const loaded = req(id);
|
|
7125
|
-
if (loaded && (loaded.__esModule || loaded[Symbol.toStringTag] === "Module")) return loaded;
|
|
7126
|
-
// A CJS module's exports ARE the namespace's named exports, with the whole
|
|
7127
|
-
// object as the default export — what an ESM importer of a CJS module sees.
|
|
7128
|
-
if (loaded && (typeof loaded === "object" || typeof loaded === "function")) {
|
|
7129
|
-
const ns = Object.create(null);
|
|
7130
|
-
for (const key of Object.keys(loaded)) ns[key] = loaded[key];
|
|
7131
|
-
if (!("default" in ns)) ns.default = loaded;
|
|
7132
|
-
Object.defineProperty(ns, Symbol.toStringTag, { value: "Module" });
|
|
7133
|
-
return ns;
|
|
7134
|
-
}
|
|
7135
|
-
return { default: loaded };
|
|
7136
|
-
});
|
|
7137
8387
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7138
8388
|
// ── require() — full Node.js module resolution ─────────────────────
|
|
7139
8389
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7140
8390
|
const __moduleCache = new Map();
|
|
8391
|
+
// package → why the package ABI policy says it cannot run here (wasm-swap-registry.ts).
|
|
8392
|
+
const __nimbusAbiAdvisories = new Map(${ABI_ADVISORIES_LITERAL});
|
|
7141
8393
|
|
|
7142
8394
|
/**
|
|
7143
8395
|
* Direct VFS bundle access for module resolution.
|
|
@@ -7163,24 +8415,8 @@ function __readFileOr(path, fallback) {
|
|
|
7163
8415
|
}
|
|
7164
8416
|
function __fileExists(path) {
|
|
7165
8417
|
const k = path.replace(/^\\/+/, "");
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
if (__vfsDirs && k in __vfsDirs) return true;
|
|
7169
|
-
// Consult the uncapped manifest (directory shape) so resolution sees
|
|
7170
|
-
// installed files whose content was excluded from the bounded snapshot
|
|
7171
|
-
// (e.g. web-streams-polyfill's ponyfill/package.json, reached via a
|
|
7172
|
-
// parent-relative main). Mirrors existsSync's manifest probe.
|
|
7173
|
-
if (__vfsManifest) {
|
|
7174
|
-
if (k in __vfsManifest) return true;
|
|
7175
|
-
const slash = k.lastIndexOf("/");
|
|
7176
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
7177
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
7178
|
-
const sib = __vfsManifest[parent];
|
|
7179
|
-
if (sib && sib.indexOf(name) !== -1) return true;
|
|
7180
|
-
}
|
|
7181
|
-
// Check for directory by looking for any key with this prefix
|
|
7182
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) return true;
|
|
7183
|
-
return false;
|
|
8418
|
+
// The namespace answers exactly (vfs/facet-resident-store.ts).
|
|
8419
|
+
return __fsMod.existsSync("/" + k);
|
|
7184
8420
|
}
|
|
7185
8421
|
// W3.5 Fix A: strict-file membership probe. __fileExists also returns true for
|
|
7186
8422
|
// directories (it has to — __resolveNodeModule and __resolveImportsField call
|
|
@@ -7192,26 +8428,8 @@ function __fileExists(path) {
|
|
|
7192
8428
|
// the fastify ret/dist/types failure.
|
|
7193
8429
|
function __pathIsFile(path) {
|
|
7194
8430
|
const k = path.replace(/^\\/+/, "");
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
// Consult the manifest as a strict-file probe: a name listed in its
|
|
7198
|
-
// parent's manifest entry is a file (directories are manifest KEYS).
|
|
7199
|
-
// This lets resolution find files whose content was excluded from the
|
|
7200
|
-
// bounded snapshot without re-introducing the directory short-circuit
|
|
7201
|
-
// (__vfsManifest[k] being a directory key is deliberately NOT matched).
|
|
7202
|
-
if (__vfsManifest) {
|
|
7203
|
-
const slash = k.lastIndexOf("/");
|
|
7204
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
7205
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
7206
|
-
const sib = __vfsManifest[parent];
|
|
7207
|
-
if (sib && sib.indexOf(name) !== -1 && !(k in __vfsManifest)) return true;
|
|
7208
|
-
}
|
|
7209
|
-
// Last resort: the full fs view (live SQLite VFS) for paths outside the
|
|
7210
|
-
// bounded snapshot/manifest entirely — e.g. the /tmp/.npx-cache tree,
|
|
7211
|
-
// which the project snapshot never covers. stat-as-file only.
|
|
7212
|
-
try { const st = __fsMod.statSync("/" + k); if (st && st.isFile && st.isFile()) return true; } catch {}
|
|
7213
|
-
// Deliberately does NOT consult __vfsDirs nor do the prefix scan.
|
|
7214
|
-
return false;
|
|
8431
|
+
const st = __fsMod.statSync("/" + k, { throwIfNoEntry: false });
|
|
8432
|
+
return !!st && st.isFile();
|
|
7215
8433
|
}
|
|
7216
8434
|
function __resolveFile(base) {
|
|
7217
8435
|
// Mirrors Node's LOAD_AS_FILE + LOAD_AS_DIRECTORY (require_2 spec):
|
|
@@ -7284,15 +8502,12 @@ function __resolveFile(base) {
|
|
|
7284
8502
|
}
|
|
7285
8503
|
|
|
7286
8504
|
// __compiledModules is defined at MODULE TOP LEVEL in the generator code
|
|
7287
|
-
// (
|
|
8505
|
+
// (facets/manager.ts) so new Function() runs during module evaluation.
|
|
7288
8506
|
|
|
7289
8507
|
// ── Single-source-of-truth exports/imports resolver (W2) ───────────────
|
|
7290
8508
|
// Emitted from src/_shared/exports-resolver.ts via getExportsResolverJS().
|
|
7291
8509
|
// Declares: resolveExports, resolveConditionValue, resolvePackageEntry,
|
|
7292
8510
|
// DEFAULT_ESM_CONDITIONS, DEFAULT_CJS_CONDITIONS.
|
|
7293
|
-
// the prior hand-rolled __resolvePkgEntry only honoured top-level
|
|
7294
|
-
// require|default|import and dropped subpath maps, wildcards, nested
|
|
7295
|
-
// conditions, the imports field, and null-target enforcement.
|
|
7296
8511
|
${EXPORTS_RESOLVER_JS}
|
|
7297
8512
|
|
|
7298
8513
|
// ── TypeScript specifier fallbacks ────────────────────────────────────────
|
|
@@ -7330,8 +8545,8 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
7330
8545
|
}
|
|
7331
8546
|
let entry = resolvePackageEntry(pkg, subpath, __NIMBUS_CJS_CONDITIONS);
|
|
7332
8547
|
// X.5-F R3: ESM-condition fallback for pure-ESM packages whose
|
|
7333
|
-
// dist/.mjs files were transformed to CJS
|
|
7334
|
-
//
|
|
8548
|
+
// dist/.mjs files were transformed to CJS
|
|
8549
|
+
// by transformEsmInBundle (facets/manager.ts). Without this,
|
|
7335
8550
|
// packages like nuxt — whose exports map only contains
|
|
7336
8551
|
// {types, import} for the root subpath — return null from the CJS
|
|
7337
8552
|
// walk and dead-end with "Cannot find module 'nuxt'" even though
|
|
@@ -7374,10 +8589,6 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
7374
8589
|
return __resolveFile(pkgDir + "/" + rel);
|
|
7375
8590
|
}
|
|
7376
8591
|
|
|
7377
|
-
/** Back-compat name used elsewhere in this file. */
|
|
7378
|
-
function __resolvePkgEntry(pkgDir) {
|
|
7379
|
-
return __resolvePkgSubpath(pkgDir, null, ".");
|
|
7380
|
-
}
|
|
7381
8592
|
|
|
7382
8593
|
/**
|
|
7383
8594
|
* Resolve a bare specifier (e.g. "react", "@scope/pkg", "pkg/sub/path")
|
|
@@ -7426,32 +8637,23 @@ function __resolveNodeModule(name, fromDir) {
|
|
|
7426
8637
|
}
|
|
7427
8638
|
|
|
7428
8639
|
/**
|
|
7429
|
-
*
|
|
7430
|
-
* package.json
|
|
8640
|
+
* Node's "package scope" of a directory (readPackageScope): the nearest
|
|
8641
|
+
* enclosing package.json, walking up from fromDir. The FIRST package.json
|
|
8642
|
+
* found is the scope, even when it lacks the field the caller wants — the
|
|
8643
|
+
* imports field and the self-reference rule both belong to the importing
|
|
8644
|
+
* module's own package, never to an ancestor past it. The walk never
|
|
8645
|
+
* crosses a node_modules directory: a file that sits directly under one
|
|
8646
|
+
* belongs to no package, not to the project above it. Returns { dir, pkg }
|
|
8647
|
+
* (pkg null when the file is unparseable) or null when no package.json
|
|
8648
|
+
* encloses fromDir.
|
|
7431
8649
|
*/
|
|
7432
|
-
function
|
|
7433
|
-
// Walk up looking for the nearest package.json. Stop at the first one
|
|
7434
|
-
// (Node spec: imports field of the importing module's package).
|
|
8650
|
+
function __nearestPackageScope(fromDir) {
|
|
7435
8651
|
let dir = (fromDir || "").replace(/^\\/+/, "");
|
|
7436
8652
|
while (true) {
|
|
8653
|
+
if (dir === "node_modules" || dir.endsWith("/node_modules")) return null;
|
|
7437
8654
|
const pkgJsonPath = (dir ? dir + "/" : "") + "package.json";
|
|
7438
8655
|
if (__fileExists(pkgJsonPath)) {
|
|
7439
|
-
|
|
7440
|
-
if (pkg && pkg.imports) {
|
|
7441
|
-
const target = resolveExports(pkg.imports, name, __NIMBUS_CJS_CONDITIONS);
|
|
7442
|
-
if (target) {
|
|
7443
|
-
// Imports targets are relative to the package root (dir)
|
|
7444
|
-
if (target.startsWith("./")) {
|
|
7445
|
-
return __resolveFile((dir ? dir + "/" : "") + target.slice(2));
|
|
7446
|
-
}
|
|
7447
|
-
if (target.startsWith("/")) {
|
|
7448
|
-
return __resolveFile(target.slice(1));
|
|
7449
|
-
}
|
|
7450
|
-
// Bare specifier — re-resolve as a node_module from this dir
|
|
7451
|
-
return __resolveNodeModule(target, dir);
|
|
7452
|
-
}
|
|
7453
|
-
}
|
|
7454
|
-
return null; // first package.json wins, even if no imports field
|
|
8656
|
+
return { dir, pkg: __readPkgJson(dir) };
|
|
7455
8657
|
}
|
|
7456
8658
|
if (!dir) return null;
|
|
7457
8659
|
const lastSlash = dir.lastIndexOf("/");
|
|
@@ -7459,37 +8661,68 @@ function __resolveImportsField(name, fromDir) {
|
|
|
7459
8661
|
}
|
|
7460
8662
|
}
|
|
7461
8663
|
|
|
8664
|
+
/**
|
|
8665
|
+
* Resolve a #name imports-field specifier from the nearest enclosing
|
|
8666
|
+
* package.json. Returns the resolved file or null.
|
|
8667
|
+
*/
|
|
8668
|
+
function __resolveImportsField(name, fromDir) {
|
|
8669
|
+
// First package.json wins, even if no imports field (Node spec: imports
|
|
8670
|
+
// field of the importing module's package).
|
|
8671
|
+
const scope = __nearestPackageScope(fromDir);
|
|
8672
|
+
if (!scope || !scope.pkg || !scope.pkg.imports) return null;
|
|
8673
|
+
const dir = scope.dir;
|
|
8674
|
+
const target = resolveExports(scope.pkg.imports, name, __NIMBUS_CJS_CONDITIONS);
|
|
8675
|
+
if (!target) return null;
|
|
8676
|
+
// Imports targets are relative to the package root (dir)
|
|
8677
|
+
if (target.startsWith("./")) {
|
|
8678
|
+
return __resolveFile((dir ? dir + "/" : "") + target.slice(2));
|
|
8679
|
+
}
|
|
8680
|
+
if (target.startsWith("/")) {
|
|
8681
|
+
return __resolveFile(target.slice(1));
|
|
8682
|
+
}
|
|
8683
|
+
// Bare specifier — re-resolve as a node_module from this dir
|
|
8684
|
+
return __resolveNodeModule(target, dir);
|
|
8685
|
+
}
|
|
8686
|
+
|
|
8687
|
+
/**
|
|
8688
|
+
* Node's LOAD_PACKAGE_SELF: a bare specifier naming the enclosing package
|
|
8689
|
+
* itself (\`require('<its-name>')\`, \`require('<its-name>/sub')\`) resolves
|
|
8690
|
+
* through that package's own \`exports\` map — only when the nearest
|
|
8691
|
+
* package.json has \`exports\` AND its \`name\` matches, and only through
|
|
8692
|
+
* \`exports\` (no main/index probing: a subpath the map does not expose is
|
|
8693
|
+
* not exported). Sits between the imports-field branch and the
|
|
8694
|
+
* node_modules walk, where Node puts it. Returns the resolved file or null.
|
|
8695
|
+
*
|
|
8696
|
+
* Conditions are the ones the node_modules walk uses for the same
|
|
8697
|
+
* require: the runtime's CJS set first, the ESM set when the map exposes
|
|
8698
|
+
* the subpath only under \`import\` (dynamic import() is lowered onto this
|
|
8699
|
+
* require chain, see __resolvePkgSubpath).
|
|
8700
|
+
*
|
|
8701
|
+
* Tri-state, as in Node: null when the rule does not apply (the caller
|
|
8702
|
+
* walks node_modules); { resolved: null } when the enclosing package claims
|
|
8703
|
+
* the name but its map does not expose the subpath or the target is
|
|
8704
|
+
* missing — Node throws ERR_PACKAGE_PATH_NOT_EXPORTED / MODULE_NOT_FOUND
|
|
8705
|
+
* there and never consults node_modules, so neither does the caller.
|
|
8706
|
+
*/
|
|
8707
|
+
function __resolvePackageSelf(name, fromDir) {
|
|
8708
|
+
const scope = __nearestPackageScope(fromDir);
|
|
8709
|
+
if (!scope || !scope.pkg) return null;
|
|
8710
|
+
const subpath = packageSelfReferenceSubpath(scope.pkg, name);
|
|
8711
|
+
if (subpath === null) return null;
|
|
8712
|
+
let entry = resolveExports(scope.pkg.exports, subpath, __NIMBUS_CJS_CONDITIONS);
|
|
8713
|
+
if (entry == null) entry = resolveExports(scope.pkg.exports, subpath, DEFAULT_ESM_CONDITIONS);
|
|
8714
|
+
if (entry == null) return { resolved: null };
|
|
8715
|
+
return { resolved: __resolveFile((scope.dir ? scope.dir + "/" : "") + entry.replace(/^\\.\\/+/, "")) };
|
|
8716
|
+
}
|
|
8717
|
+
|
|
7462
8718
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7463
|
-
// ──
|
|
8719
|
+
// ── __mkCompiledFn — the request-time fallback compile ──────────────
|
|
7464
8720
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7465
|
-
//
|
|
7466
|
-
//
|
|
7467
|
-
//
|
|
7468
|
-
//
|
|
7469
|
-
|
|
7470
|
-
// the body in \`new Function("exports","require","module","__filename","__dirname", code)\`
|
|
7471
|
-
// then collides at parse time:
|
|
7472
|
-
//
|
|
7473
|
-
// SyntaxError: Identifier '__dirname' has already been declared
|
|
7474
|
-
//
|
|
7475
|
-
// (VERIFY-23417C5 §4 #1 / X5M3-retro §"Next bucket".) The helper RENAMES
|
|
7476
|
-
// the conflicting param to a placeholder name so the body's own
|
|
7477
|
-
// \`const __dirname\` becomes the single declarer. We rename rather than
|
|
7478
|
-
// drop because callers pass 5 positional arguments and dropping a slot
|
|
7479
|
-
// would mis-align downstream slots (e.g. the USER_CODE wrap appends
|
|
7480
|
-
// \`console\` / \`process\` / etc. after \`__dirname\`). Renaming preserves
|
|
7481
|
-
// slot alignment while letting the body's binding win.
|
|
7482
|
-
//
|
|
7483
|
-
// Symmetric for \`__filename\` because open@10's idiom often emits both.
|
|
7484
|
-
function __mkCompiledFn(code) {
|
|
7485
|
-
const reFn = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+__filename\\s*=/m;
|
|
7486
|
-
const reDn = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+__dirname\\s*=/m;
|
|
7487
|
-
const reRequire = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+require\\s*=/m;
|
|
7488
|
-
const fnName = reFn.test(code) ? "__filename__nimbus_unused" : "__filename";
|
|
7489
|
-
const dnName = reDn.test(code) ? "__dirname__nimbus_unused" : "__dirname";
|
|
7490
|
-
const requireName = reRequire.test(code) ? "require__nimbus_unused" : "require";
|
|
7491
|
-
return new Function("exports", requireName, "module", fnName, dnName, code);
|
|
7492
|
-
}
|
|
8721
|
+
// One definition, from core/_shared/compiled-fn.ts (interpolated above as
|
|
8722
|
+
// MK_COMPILED_FN_SOURCE); here it serves a cell the startup precompile set
|
|
8723
|
+
// did not include, where the runtime permits it. See that file for why the
|
|
8724
|
+
// collision is detected by compiling rather than by scanning the source.
|
|
8725
|
+
${COMPILED_FN_JS}
|
|
7493
8726
|
|
|
7494
8727
|
function __exportsTarget(mod) {
|
|
7495
8728
|
const value = mod.exports;
|
|
@@ -7541,11 +8774,12 @@ function __makeLoadingExports(mod) {
|
|
|
7541
8774
|
* Load and execute a JS/JSON module from VFS.
|
|
7542
8775
|
* Returns the module.exports value.
|
|
7543
8776
|
*/
|
|
7544
|
-
function __loadModule(resolvedPath) {
|
|
7545
|
-
if (
|
|
8777
|
+
function __loadModule(resolvedPath, evaluationKey = resolvedPath) {
|
|
8778
|
+
if (globalThis.__nimbusProfileStaged) globalThis.__nimbusProfileStaged.delete(String(resolvedPath).replace(/^\\/+/, ""));
|
|
8779
|
+
if (__moduleCache.has(evaluationKey)) return __moduleCache.get(evaluationKey);
|
|
7546
8780
|
|
|
7547
8781
|
const mod = { exports: {} };
|
|
7548
|
-
__moduleCache.set(
|
|
8782
|
+
__moduleCache.set(evaluationKey, __makeLoadingExports(mod));
|
|
7549
8783
|
|
|
7550
8784
|
const code = __readFileOr(resolvedPath, null);
|
|
7551
8785
|
if (code === null) throw new Error("Cannot read module: " + resolvedPath);
|
|
@@ -7553,7 +8787,7 @@ function __loadModule(resolvedPath) {
|
|
|
7553
8787
|
// JSON
|
|
7554
8788
|
if (resolvedPath.endsWith(".json")) {
|
|
7555
8789
|
mod.exports = JSON.parse(code);
|
|
7556
|
-
__moduleCache.set(
|
|
8790
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
7557
8791
|
return mod.exports;
|
|
7558
8792
|
}
|
|
7559
8793
|
|
|
@@ -7576,6 +8810,16 @@ function __loadModule(resolvedPath) {
|
|
|
7576
8810
|
// const import_meta = {}. Save+restore for recursive __loadModule.
|
|
7577
8811
|
const __prevModulePath = globalThis.__currentModulePath;
|
|
7578
8812
|
globalThis.__currentModulePath = resolvedPath;
|
|
8813
|
+
const moduleUrl = evaluationKey.startsWith("file:")
|
|
8814
|
+
? evaluationKey : builtins.url.pathToFileURL("/" + resolvedPath).href;
|
|
8815
|
+
// Evaluation metadata lives on the module, not in source text. Keeping
|
|
8816
|
+
// it here preserves the five CommonJS arguments and captures the parent
|
|
8817
|
+
// even when import.meta.resolve is extracted and called later.
|
|
8818
|
+
const importMeta = Object.assign(Object.create(null), {
|
|
8819
|
+
url: moduleUrl,
|
|
8820
|
+
resolve: (specifier) => globalThis.__nimbusImportMetaResolve(specifier, moduleUrl),
|
|
8821
|
+
});
|
|
8822
|
+
Object.defineProperty(mod, "__nimbusImportMeta", { value: importMeta });
|
|
7579
8823
|
try {
|
|
7580
8824
|
// Use pre-compiled function from startup (new Function allowed at module eval time)
|
|
7581
8825
|
// Normalize path to match VFS bundle key format (no leading /)
|
|
@@ -7600,7 +8844,7 @@ function __loadModule(resolvedPath) {
|
|
|
7600
8844
|
} catch (evalErr) {
|
|
7601
8845
|
// W3.5 Fix C: if the file was in the bundle but its pre-compile
|
|
7602
8846
|
// failed at facet startup, surface the original SyntaxError
|
|
7603
|
-
// instead of the misleading "
|
|
8847
|
+
// instead of the misleading "not in this launch's module map" text.
|
|
7604
8848
|
const normalizedPath2 = resolvedPath.replace(/^\\/+/, "");
|
|
7605
8849
|
const compileErr =
|
|
7606
8850
|
(typeof __compileFailures !== "undefined" && __compileFailures &&
|
|
@@ -7612,22 +8856,27 @@ function __loadModule(resolvedPath) {
|
|
|
7612
8856
|
);
|
|
7613
8857
|
}
|
|
7614
8858
|
if (evalErr.message && evalErr.message.includes("Code generation from strings disallowed")) {
|
|
7615
|
-
|
|
8859
|
+
// Not a read miss (the text is here) and nothing in the process can
|
|
8860
|
+
// answer it, so it is kept apart from the read ledger that settles those.
|
|
8861
|
+
(globalThis.__nimbusModuleMisses ??= new Set()).add(normalizedPath2);
|
|
8862
|
+
throw new Error("Cannot load module '" + resolvedPath + "': it was not in this launch's module map; the next launch of the same command stages it.");
|
|
7616
8863
|
}
|
|
7617
8864
|
throw evalErr;
|
|
7618
8865
|
}
|
|
7619
8866
|
}
|
|
7620
8867
|
} catch (e) {
|
|
7621
|
-
__moduleCache.delete(
|
|
8868
|
+
__moduleCache.delete(evaluationKey);
|
|
7622
8869
|
if (e && typeof e === "object" && !e.__nimbusModulePath) {
|
|
7623
8870
|
try {
|
|
7624
|
-
e
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
8871
|
+
Object.defineProperty(e, "__nimbusModulePath", { value: resolvedPath, configurable: true, writable: true });
|
|
8872
|
+
const at = resolvedPath.lastIndexOf("node_modules/");
|
|
8873
|
+
const parts = at < 0 ? [] : resolvedPath.slice(at + 13).split("/");
|
|
8874
|
+
const pkg = parts[0] && parts[0].startsWith("@") ? parts[0] + "/" + parts[1] : parts[0];
|
|
8875
|
+
const advisory = pkg ? __nimbusAbiAdvisories.get(pkg) : undefined;
|
|
8876
|
+
const note = "\\nNimbus module: " + resolvedPath
|
|
8877
|
+
+ (advisory ? "\\nNimbus: " + pkg + " has no Workers-compatible build: " + advisory : "");
|
|
8878
|
+
if (typeof e.message === "string") e.message += note;
|
|
8879
|
+
if (typeof e.stack === "string" && !e.stack.includes("Nimbus module:")) e.stack += note;
|
|
7631
8880
|
} catch {}
|
|
7632
8881
|
}
|
|
7633
8882
|
throw e;
|
|
@@ -7636,7 +8885,7 @@ function __loadModule(resolvedPath) {
|
|
|
7636
8885
|
}
|
|
7637
8886
|
|
|
7638
8887
|
// Update cache with final exports (module.exports may have been reassigned)
|
|
7639
|
-
__moduleCache.set(
|
|
8888
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
7640
8889
|
return mod.exports;
|
|
7641
8890
|
}
|
|
7642
8891
|
|
|
@@ -7645,6 +8894,25 @@ function __loadModule(resolvedPath) {
|
|
|
7645
8894
|
* Returns the resolved VFS path, or null.
|
|
7646
8895
|
*/
|
|
7647
8896
|
function __resolveFrom(id, fromDir) {
|
|
8897
|
+
// An absolute file: URL is a specifier Node accepts: \`import(href)\` is the
|
|
8898
|
+
// portable way to load a path a resolver just handed back, and it is what
|
|
8899
|
+
// every package that resolves before it imports emits — @nuxt/cli's
|
|
8900
|
+
// loadKit does \`import(pathToFileURL(resolveModulePath('@nuxt/kit', …)).href)\`.
|
|
8901
|
+
// The ESM→CJS transform funnels those imports through this one resolver, so
|
|
8902
|
+
// the scheme has to come off before the specifier is classified: with it on,
|
|
8903
|
+
// the file: URL misses the absolute-path branch below and gets looked up as
|
|
8904
|
+
// if it were the name of a package.
|
|
8905
|
+
if (typeof id === "string" && id.startsWith("file:")) {
|
|
8906
|
+
let filePath;
|
|
8907
|
+
try {
|
|
8908
|
+
const u = new URL(id);
|
|
8909
|
+
// .pathname drops the query a cache-busting importer appends
|
|
8910
|
+
// (\`import(href + "?t=" + Date.now())\` is the standard HMR spelling),
|
|
8911
|
+
// and the decode is what node:url's fileURLToPath does with it.
|
|
8912
|
+
filePath = decodeURIComponent(u.pathname);
|
|
8913
|
+
} catch { filePath = id.replace(/^file:\\/\\//, ""); }
|
|
8914
|
+
id = filePath;
|
|
8915
|
+
}
|
|
7648
8916
|
// X.5-P: literal "." / ".." are CommonJS aliases for "./" / "../".
|
|
7649
8917
|
// Pre-fix they slipped past the startsWith("./")/("../") guards (which
|
|
7650
8918
|
// require >= 3 / >= 4 chars respectively) and fell into the bare-spec
|
|
@@ -7674,43 +8942,137 @@ function __resolveFrom(id, fromDir) {
|
|
|
7674
8942
|
if (id.startsWith("#")) {
|
|
7675
8943
|
return __resolveImportsField(id, fromDir);
|
|
7676
8944
|
}
|
|
7677
|
-
// Bare specifier
|
|
8945
|
+
// Bare specifier: the enclosing package's own name resolves through its
|
|
8946
|
+
// exports map (Node's LOAD_PACKAGE_SELF), then node_modules resolution.
|
|
8947
|
+
// Once the enclosing package claims the name, its map is the whole
|
|
8948
|
+
// answer: a subpath it does not expose is not found, never a
|
|
8949
|
+
// node_modules copy's.
|
|
8950
|
+
const self = __resolvePackageSelf(id, fromDir);
|
|
8951
|
+
if (self) return self.resolved;
|
|
7678
8952
|
return __resolveNodeModule(id, fromDir);
|
|
7679
8953
|
}
|
|
7680
8954
|
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
const
|
|
7708
|
-
|
|
7709
|
-
);
|
|
7710
|
-
|
|
7711
|
-
|
|
8955
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
8956
|
+
// ── import() — Node's ESM loader ────────────────────────────────────
|
|
8957
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
8958
|
+
// A cell's dynamic import() reaches here with its module's URL
|
|
8959
|
+
// (core/runtime/dynamic-import-rewrite.ts). Resolution is Node's ESM
|
|
8960
|
+
// algorithm (core/_shared/esm-resolver.ts, interpolated below): the "import"
|
|
8961
|
+
// conditions, no extension probing, a directory refused by name, file: URLs,
|
|
8962
|
+
// Node's error codes and messages. What resolves loads through the same
|
|
8963
|
+
// module cache require uses, shaped as the namespace Node would give.
|
|
8964
|
+
// Node's builtins that exist only with the scheme; the rest of the table is
|
|
8965
|
+
// the process's own builtins, undici among them, which the process provides
|
|
8966
|
+
// in place of any installed copy (see builtins.undici above).
|
|
8967
|
+
const __ESM_SCHEME_ONLY_BUILTINS = new Set(["test", "test/reporters", "sqlite", "sea"]);
|
|
8968
|
+
// Node's ESM resolver (core/_shared/esm-resolver.ts, compiled once by
|
|
8969
|
+
// scripts/bundle-facet-workers.mjs): declares createEsmResolver.
|
|
8970
|
+
${ESM_RESOLVER_PREAMBLE}
|
|
8971
|
+
const __esmResolver = createEsmResolver({
|
|
8972
|
+
kind(path) {
|
|
8973
|
+
const st = __fsMod.statSync(path, { throwIfNoEntry: false });
|
|
8974
|
+
return !st ? null : st.isDirectory() ? "directory" : st.isFile() ? "file" : null;
|
|
8975
|
+
},
|
|
8976
|
+
realpath(path) {
|
|
8977
|
+
try { return __fsMod.realpathSync(path); } catch { return path; }
|
|
8978
|
+
},
|
|
8979
|
+
readText(path) { return __readFileOr(path, null); },
|
|
8980
|
+
isBuiltin(specifier) {
|
|
8981
|
+
const scheme = specifier.startsWith("node:");
|
|
8982
|
+
const name = scheme ? specifier.slice(5) : specifier;
|
|
8983
|
+
if (!scheme && __ESM_SCHEME_ONLY_BUILTINS.has(name)) return false;
|
|
8984
|
+
return Object.prototype.hasOwnProperty.call(builtins, name);
|
|
8985
|
+
},
|
|
8986
|
+
cjsResolve(specifier, parentPath) {
|
|
8987
|
+
try {
|
|
8988
|
+
const dir = parentPath.slice(0, parentPath.lastIndexOf("/")).replace(/^\\/+/, "");
|
|
8989
|
+
const found = __resolveFrom(specifier, dir);
|
|
8990
|
+
return found ? "/" + String(found).replace(/^\\/+/, "") : null;
|
|
8991
|
+
} catch { return null; }
|
|
8992
|
+
},
|
|
8993
|
+
});
|
|
8994
|
+
const __esmNamespaces = new Map();
|
|
8995
|
+
/** A module namespace: its names sorted, read through to the exports. */
|
|
8996
|
+
function __esmNamespaceOf(names, read) {
|
|
8997
|
+
const ns = Object.create(null);
|
|
8998
|
+
for (const name of [...names].sort()) {
|
|
8999
|
+
Object.defineProperty(ns, name, { enumerable: true, get: () => read(name) });
|
|
9000
|
+
}
|
|
9001
|
+
Object.defineProperty(ns, Symbol.toStringTag, { value: "Module" });
|
|
9002
|
+
return Object.preventExtensions(ns);
|
|
9003
|
+
}
|
|
9004
|
+
function __esmLoad(resolution) {
|
|
9005
|
+
const cached = __esmNamespaces.get(resolution.url);
|
|
9006
|
+
if (cached) return cached;
|
|
9007
|
+
let ns;
|
|
9008
|
+
if (resolution.format === "builtin") {
|
|
9009
|
+
const mod = __requireFrom("node:" + resolution.builtin, "");
|
|
9010
|
+
const names = new Set(mod && (typeof mod === "object" || typeof mod === "function") ? Object.keys(mod) : []);
|
|
9011
|
+
names.add("default");
|
|
9012
|
+
ns = __esmNamespaceOf(names, (name) => name === "default" ? mod : mod[name]);
|
|
9013
|
+
} else if (resolution.format === "data") {
|
|
9014
|
+
const match = /^data:application\\/json(;[^,]*)?,/.exec(resolution.url);
|
|
9015
|
+
if (!match) {
|
|
9016
|
+
throw Object.assign(new Error("Nimbus: a data: URL module can only be JSON here; code cannot be compiled after the process starts: " + resolution.url.slice(0, 64)), { code: "ERR_NIMBUS_DATA_MODULE" });
|
|
9017
|
+
}
|
|
9018
|
+
const body = resolution.url.slice(match[0].length);
|
|
9019
|
+
const value = JSON.parse(match[1] && match[1].includes(";base64") ? atob(body) : decodeURIComponent(body));
|
|
9020
|
+
ns = __esmNamespaceOf(["default"], () => value);
|
|
9021
|
+
} else {
|
|
9022
|
+
const key = resolution.path.replace(/^\\/+/, "");
|
|
9023
|
+
const esm = resolution.format === "module"
|
|
9024
|
+
|| (resolution.format === "detect" && globalThis.__nimbusEsmModules && globalThis.__nimbusEsmModules.has(key));
|
|
9025
|
+
// Canonical queryless ESM shares evaluation with require() and static
|
|
9026
|
+
// imports lowered to require(). Queries/fragments are distinct jobs.
|
|
9027
|
+
const variant = esm && (resolution.url.includes("?") || resolution.url.includes("#"));
|
|
9028
|
+
const exports = __loadModule(key, variant ? resolution.url : key);
|
|
9029
|
+
if (resolution.format === "json") {
|
|
9030
|
+
ns = __esmNamespaceOf(["default"], () => exports);
|
|
9031
|
+
} else if (esm) {
|
|
9032
|
+
// The ESM→CJS transform's exports: the module's own names, live.
|
|
9033
|
+
const names = exports && typeof exports === "object" ? Object.keys(exports).filter((name) => name !== "__esModule") : [];
|
|
9034
|
+
ns = __esmNamespaceOf(names, (name) => exports[name]);
|
|
9035
|
+
} else {
|
|
9036
|
+
// CommonJS: module.exports is the default, its names the named exports.
|
|
9037
|
+
const mod = exports;
|
|
9038
|
+
const names = new Set(mod && (typeof mod === "object" || typeof mod === "function") ? Object.keys(mod) : []);
|
|
9039
|
+
names.delete("default");
|
|
9040
|
+
names.add("default");
|
|
9041
|
+
ns = __esmNamespaceOf(names, (name) => name === "default" ? mod : mod[name]);
|
|
9042
|
+
}
|
|
7712
9043
|
}
|
|
7713
|
-
|
|
9044
|
+
__esmNamespaces.set(resolution.url, ns);
|
|
9045
|
+
return ns;
|
|
9046
|
+
}
|
|
9047
|
+
globalThis.__nimbusDynamicImport = function __nimbusDynamicImport(parentUrl, specifier, options) {
|
|
9048
|
+
return Promise.resolve().then(() => {
|
|
9049
|
+
const text = String(specifier);
|
|
9050
|
+
const attributes = {};
|
|
9051
|
+
// V8's own checks and messages (node 22), which run before resolution.
|
|
9052
|
+
if (options !== undefined) {
|
|
9053
|
+
if (options === null || typeof options !== "object") throw new TypeError("The second argument to import() must be an object");
|
|
9054
|
+
const withAttributes = options.with;
|
|
9055
|
+
if (withAttributes !== undefined) {
|
|
9056
|
+
if (withAttributes === null || typeof withAttributes !== "object") throw new TypeError("The 'assert' option must be an object");
|
|
9057
|
+
for (const key of Object.keys(withAttributes)) {
|
|
9058
|
+
if (typeof withAttributes[key] !== "string") throw new TypeError("Import assertion value must be a string");
|
|
9059
|
+
attributes[key] = withAttributes[key];
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
}
|
|
9063
|
+
const resolution = __esmResolver.resolveSync(text, parentUrl);
|
|
9064
|
+
__esmResolver.validateAttributes(resolution.url, resolution.format, attributes);
|
|
9065
|
+
return __esmLoad(resolution);
|
|
9066
|
+
});
|
|
9067
|
+
};
|
|
9068
|
+
|
|
9069
|
+
// Node's import.meta.resolve, synchronous as in Node: the URL a specifier
|
|
9070
|
+
// names, even for a file or directory that will not load.
|
|
9071
|
+
globalThis.__nimbusImportMetaResolve = function __nimbusImportMetaResolve(specifier, parentUrl) {
|
|
9072
|
+
const parent = typeof parentUrl === "string" && parentUrl.startsWith("file:")
|
|
9073
|
+
? parentUrl
|
|
9074
|
+
: "file:///" + String(globalThis.__currentModulePath || "[eval]").replace(/^\\/+/, "");
|
|
9075
|
+
return __esmResolver.metaResolveSync(String(specifier), parent);
|
|
7714
9076
|
};
|
|
7715
9077
|
|
|
7716
9078
|
/**
|