@nimbus-sh/worker 0.10.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/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +9 -4
- package/dist/facets/compose.d.ts +11 -7
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +8 -38
- 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-transform.d.ts +36 -10
- package/dist/facets/esbuild-transform.d.ts.map +1 -1
- package/dist/facets/esbuild-transform.js +216 -20
- 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 +179 -78
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +1221 -649
- 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/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 +16 -9
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +1818 -188
- package/dist/git/network-facet.d.ts +10 -2
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +62 -16
- 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 +62 -243
- package/dist/hosted/runtime.d.ts +19 -13
- package/dist/hosted/runtime.d.ts.map +1 -1
- package/dist/hosted/runtime.js +71 -14
- package/dist/hosted/services.d.ts +6 -7
- package/dist/hosted/services.d.ts.map +1 -1
- package/dist/hosted/services.js +10 -14
- 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 +5 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +6 -3
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +0 -27
- 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 +5 -0
- package/dist/npm/cache.d.ts.map +1 -1
- package/dist/npm/cache.js +13 -1
- package/dist/npm/install-batch-facet.d.ts +2 -2
- package/dist/npm/install-batch-facet.js +3 -3
- package/dist/npm/installer.d.ts +17 -1
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +159 -20
- 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/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 +2 -0
- package/dist/npm/r2-cache.d.ts.map +1 -1
- package/dist/npm/r2-cache.js +2 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +14 -3
- package/dist/npm/resolver.d.ts +0 -7
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +0 -22
- 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/esbuild-wasm-bytes.d.ts +17 -5
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/esbuild-wasm-bytes.js +43 -94
- 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 +1567 -864
- 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 +2 -2
- 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 +36 -35
- 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.map +1 -1
- package/dist/runtime/wasi/preamble.js +0 -24
- 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 +34 -11
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +46 -29
- 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 +41 -15
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +64 -70
- package/dist/session/npm-install-port.d.ts.map +1 -1
- package/dist/session/npm-install-port.js +1 -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 +7 -10
- package/dist/session/rpc.d.ts +104 -20
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +184 -37
- 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 +104 -15
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +188 -71
- 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 +2 -0
- 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 +5 -22
- package/package.json +8 -7
- 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-7b4dd7a76d95d21e.js → node-shims-06565b121555c194.js} +2089 -861
- 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-facet-workers.mjs +114 -3
- 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
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) => {
|
|
@@ -738,29 +814,205 @@ const __fsMod = (() => {
|
|
|
738
814
|
// ── VFS bundle lookup (fast path — in-memory) ──
|
|
739
815
|
function _bundleLookup(absPath) {
|
|
740
816
|
const k = _strip(absPath);
|
|
741
|
-
if (
|
|
742
|
-
//
|
|
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).
|
|
743
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
|
+
}
|
|
744
829
|
return undefined;
|
|
745
830
|
}
|
|
746
831
|
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
function _metadataTable() {
|
|
750
|
-
return (typeof __vfsMetadata !== "undefined" && __vfsMetadata) ? __vfsMetadata : null;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// Same guard, same reason: the SQLite resident store is spliced into the
|
|
754
|
-
// resident-process body only. The one-shot exec path runs in a stateless
|
|
755
|
-
// loaded worker with no facet storage at all, so these shims must still work
|
|
756
|
-
// 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.
|
|
757
834
|
function _residentStorePresent() {
|
|
758
835
|
return typeof __residentAdmit === "function" && typeof __residentReady !== "undefined" && __residentReady;
|
|
759
836
|
}
|
|
760
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
|
+
|
|
761
1012
|
function _metadata(absPath) {
|
|
762
|
-
|
|
763
|
-
|
|
1013
|
+
_nsRequire("stat", absPath, "fs.promises.stat");
|
|
1014
|
+
const meta = _nsMeta(_strip(absPath), true);
|
|
1015
|
+
return meta === "absent" || meta === "ELOOP" ? undefined : meta;
|
|
764
1016
|
}
|
|
765
1017
|
|
|
766
1018
|
function _denialCode(cell) {
|
|
@@ -768,52 +1020,13 @@ const __fsMod = (() => {
|
|
|
768
1020
|
typeof cell.error === "string" ? cell.error : null;
|
|
769
1021
|
}
|
|
770
1022
|
|
|
771
|
-
// Removal retracts the path from the sync existence view
|
|
772
|
-
//
|
|
773
|
-
//
|
|
774
|
-
// 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.
|
|
775
1026
|
function _forgetSyncPath(k) {
|
|
1027
|
+
_createdHere.delete(k);
|
|
776
1028
|
_announcedDirs.delete(k);
|
|
777
|
-
|
|
778
|
-
if (metadata) delete metadata[k];
|
|
779
|
-
if (!__vfsManifest) return;
|
|
780
|
-
delete __vfsManifest[k];
|
|
781
|
-
const slash = k.lastIndexOf("/");
|
|
782
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
783
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
784
|
-
const siblings = __vfsManifest[parent];
|
|
785
|
-
if (!siblings) return;
|
|
786
|
-
const at = siblings.indexOf(name);
|
|
787
|
-
if (at !== -1) siblings.splice(at, 1);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* Put a path into the sync EXISTENCE view — the inverse of _forgetSyncPath.
|
|
792
|
-
*
|
|
793
|
-
* A content cell is not existence. An invalidation drops content and is
|
|
794
|
-
* documented to leave the name behind so existsSync cannot fabricate an
|
|
795
|
-
* ENOENT for a file that was merely rewritten — but a file this process just
|
|
796
|
-
* created has no manifest entry and no spawn-time stat record, so its cell
|
|
797
|
-
* WAS the name, and evicting it retracted a file the program had written
|
|
798
|
-
* itself. Recording the name where every other name lives is what makes that
|
|
799
|
-
* documented asymmetry true.
|
|
800
|
-
*
|
|
801
|
-
* The parent's listing is the right home for it. Where the parent has no
|
|
802
|
-
* listing the entry is created only for a directory this process made, since
|
|
803
|
-
* that is the only case in which it knows the whole contents and calling the
|
|
804
|
-
* directory enumerated is honest.
|
|
805
|
-
*/
|
|
806
|
-
function _announceSyncPath(k) {
|
|
807
|
-
if (!__vfsManifest || k === "") return;
|
|
808
|
-
const slash = k.lastIndexOf("/");
|
|
809
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
810
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
811
|
-
let siblings = __vfsManifest[parent];
|
|
812
|
-
if (!Array.isArray(siblings)) {
|
|
813
|
-
if (!__vfsDirs || !(parent in __vfsDirs)) return;
|
|
814
|
-
siblings = __vfsManifest[parent] = [];
|
|
815
|
-
}
|
|
816
|
-
if (siblings.indexOf(name) === -1) siblings.push(name);
|
|
1029
|
+
_nsOwnSet(k, "absent");
|
|
817
1030
|
}
|
|
818
1031
|
|
|
819
1032
|
/**
|
|
@@ -822,41 +1035,67 @@ const __fsMod = (() => {
|
|
|
822
1035
|
* content carried. One place, because every sync mutation owes all three.
|
|
823
1036
|
*/
|
|
824
1037
|
function _parkWrite(k, cell) {
|
|
1038
|
+
_noteCreation(k);
|
|
825
1039
|
__vfsWrites[k] = cell;
|
|
826
1040
|
if (__vfsBundle) __vfsBundle[k] = cell;
|
|
827
1041
|
delete __vfsBundleRevisions[k];
|
|
828
|
-
|
|
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);
|
|
829
1066
|
}
|
|
830
1067
|
|
|
831
1068
|
function _forgetSyncTree(k) {
|
|
832
1069
|
const prefix = k + "/";
|
|
1070
|
+
_forgetCreation(k);
|
|
1071
|
+
_nsOwnSet(k, "absentTree");
|
|
833
1072
|
for (const dir of _announcedDirs) if (dir.startsWith(prefix)) _announcedDirs.delete(dir);
|
|
834
|
-
const metadata = _metadataTable();
|
|
835
|
-
if (metadata) {
|
|
836
|
-
for (const mk of Object.keys(metadata)) if (mk.startsWith(prefix)) delete metadata[mk];
|
|
837
|
-
}
|
|
838
|
-
if (__vfsManifest) {
|
|
839
|
-
for (const dk of Object.keys(__vfsManifest)) if (dk.startsWith(prefix)) delete __vfsManifest[dk];
|
|
840
|
-
}
|
|
841
1073
|
_forgetSyncPath(k);
|
|
842
1074
|
}
|
|
843
1075
|
|
|
844
1076
|
/**
|
|
845
1077
|
* The honest error for a sync read the resident view cannot serve. A facet
|
|
846
1078
|
* has no synchronous I/O primitive, so a sync read is limited to content
|
|
847
|
-
* staged into this process
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* 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.
|
|
852
1083
|
*/
|
|
853
1084
|
function _notResidentError(absPath, displayPath, syscall, asyncForm) {
|
|
854
1085
|
const st = _statLadder(absPath);
|
|
855
|
-
if (st === undefined)
|
|
856
|
-
if (!_absenceIsKnown(absPath)) _recordUnmapped(absPath, syscall);
|
|
857
|
-
return _fsErr("ENOENT", syscall, displayPath);
|
|
858
|
-
}
|
|
1086
|
+
if (st === undefined) return _fsErr("ENOENT", syscall, displayPath);
|
|
859
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
|
+
}
|
|
860
1099
|
_recordResidencyMiss(absPath);
|
|
861
1100
|
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
862
1101
|
err.message += " — '" + String(displayPath) + "' exists but its content is not " +
|
|
@@ -990,7 +1229,9 @@ const __fsMod = (() => {
|
|
|
990
1229
|
let meta;
|
|
991
1230
|
// A thrown stat is the authority failing to answer, not an answer:
|
|
992
1231
|
// nothing is learned, and the miss stands.
|
|
993
|
-
|
|
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()); }
|
|
994
1235
|
catch { return; }
|
|
995
1236
|
if (meta === null || meta === undefined) { _observedAbsent.add(k); return; }
|
|
996
1237
|
if (meta.type === "directory") return;
|
|
@@ -1028,193 +1269,46 @@ const __fsMod = (() => {
|
|
|
1028
1269
|
}
|
|
1029
1270
|
};
|
|
1030
1271
|
|
|
1031
|
-
/**
|
|
1032
|
-
* Has this directory's content been enumerated, or merely mentioned?
|
|
1033
|
-
*
|
|
1034
|
-
* __vfsManifest is a walk of SELECTED roots — the cwd subtree, its
|
|
1035
|
-
* node_modules, the entry script's own package — not of the filesystem. A
|
|
1036
|
-
* directory with an entry there was listed by that walk, so its child list
|
|
1037
|
-
* is complete and a name that is not in it is genuinely not there. A
|
|
1038
|
-
* directory with no entry was never opened, and knows nothing. __vfsDirs
|
|
1039
|
-
* holds the directories this process created itself, which it therefore
|
|
1040
|
-
* knows the contents of by construction.
|
|
1041
|
-
*
|
|
1042
|
-
* Conflating the two is what let a synchronous readdir of a real directory
|
|
1043
|
-
* outside the walk answer with an empty array — not an error, not a refusal,
|
|
1044
|
-
* a positive assertion that a populated directory is empty. Every caller
|
|
1045
|
-
* that used to end in "not found, so absent" goes through here first.
|
|
1046
|
-
*/
|
|
1047
|
-
function _dirEnumerated(k) {
|
|
1048
|
-
return (!!__vfsManifest && k in __vfsManifest) || (!!__vfsDirs && k in __vfsDirs);
|
|
1049
|
-
}
|
|
1050
1272
|
|
|
1051
|
-
/** Does the content bundle itself describe children under this directory? */
|
|
1052
|
-
function _bundleHasChildren(k) {
|
|
1053
|
-
if (!__vfsBundle) return false;
|
|
1054
|
-
return __residentAnyUnder(k ? k + "/" : "");
|
|
1055
|
-
}
|
|
1056
1273
|
|
|
1057
|
-
/**
|
|
1058
|
-
* Can the view say this path is ABSENT, as opposed to say nothing at all?
|
|
1059
|
-
*
|
|
1060
|
-
* With no supervisor bound there is no authority to be ignorant of: the
|
|
1061
|
-
* staged tables ARE the filesystem, the async form reads out of those same
|
|
1062
|
-
* tables, and a name that is not in them is not anywhere. Ignorance is a
|
|
1063
|
-
* property of the gap between a process and an authority it cannot reach
|
|
1064
|
-
* synchronously, so where there is no authority there is no gap.
|
|
1065
|
-
*/
|
|
1066
|
-
function _absenceIsKnown(absPath) {
|
|
1067
|
-
if (!_supervisor()) return true;
|
|
1068
|
-
const k = _strip(absPath);
|
|
1069
|
-
if (k === "") return true;
|
|
1070
|
-
const segments = k.split("/");
|
|
1071
|
-
// Deepest enumerated ancestor, because that is the only one that can
|
|
1072
|
-
// testify. Walking up rather than looking only at the immediate parent is
|
|
1073
|
-
// what makes a whole missing subtree answerable from one listing: if
|
|
1074
|
-
// $HOME was enumerated and holds no .config, then nothing under .config
|
|
1075
|
-
// is there either, and a program probing three levels down deserves that
|
|
1076
|
-
// answer rather than three refusals.
|
|
1077
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1078
|
-
const dir = segments.slice(0, i).join("/");
|
|
1079
|
-
if (!_dirEnumerated(dir)) continue;
|
|
1080
|
-
const child = segments.slice(0, i + 1).join("/");
|
|
1081
|
-
const children = (__vfsManifest && __vfsManifest[dir]) || [];
|
|
1082
|
-
const present = children.indexOf(segments[i]) !== -1
|
|
1083
|
-
|| (!!__vfsDirs && child in __vfsDirs)
|
|
1084
|
-
|| (!!__vfsBundle && child in __vfsBundle);
|
|
1085
|
-
// The component is not in a directory that was listed: absent, and so is
|
|
1086
|
-
// everything below it. If it IS there, only its own enumeration settles
|
|
1087
|
-
// what it contains, so nothing short of the immediate parent will do.
|
|
1088
|
-
if (!present) return true;
|
|
1089
|
-
return i === segments.length - 1;
|
|
1090
|
-
}
|
|
1091
|
-
return false;
|
|
1092
|
-
}
|
|
1093
1274
|
|
|
1094
|
-
/**
|
|
1095
|
-
* A path the sync view never mapped: record the ignorance, and repair it.
|
|
1096
|
-
*
|
|
1097
|
-
* Sibling of _notResidentError and the same bargain — the file may well be
|
|
1098
|
-
* there, the process simply cannot look without blocking. The callers answer
|
|
1099
|
-
* ENOENT, which is provisional rather than fabricated: this records the miss
|
|
1100
|
-
* and pulls in the listing that settles the question, and the exit report
|
|
1101
|
-
* reads the ledger. If the path was genuinely absent the repair proves it
|
|
1102
|
-
* and the entry retires — the program's not-found branch was the right
|
|
1103
|
-
* branch and it reached it through the right errno. If the path WAS there,
|
|
1104
|
-
* the entry survives and the run is failed by name.
|
|
1105
|
-
*
|
|
1106
|
-
* EAGAIN was tried here and is the wrong answer to hand a program even
|
|
1107
|
-
* though it is the honest description of the situation. It cannot arise from
|
|
1108
|
-
* a real POSIX filesystem, so nothing branches on it, and the catch block
|
|
1109
|
-
* that receives it was written for a missing file and rethrows instead:
|
|
1110
|
-
* measured, create-next-app reads $HOME/.config/<tool>/config.json through
|
|
1111
|
-
* the conf package, is handed EAGAIN for a file that was never there, and
|
|
1112
|
-
* dies before writing one template file. Read a config, treat ENOENT as "no
|
|
1113
|
-
* config yet", rethrow anything else is the near-universal shape. Refusing
|
|
1114
|
-
* bought nothing the ledger was not already delivering, and cost every
|
|
1115
|
-
* program that idiom.
|
|
1116
|
-
*
|
|
1117
|
-
* A LISTING is the exception and still refuses — see readdirSync. An array is
|
|
1118
|
-
* a complete enumeration by definition, so there is no provisional form of
|
|
1119
|
-
* it: an empty one asserts that a directory nothing ever opened is empty,
|
|
1120
|
-
* which is how a scaffolder was told its template directory held nothing,
|
|
1121
|
-
* wrote nothing, and exited 0.
|
|
1122
|
-
*/
|
|
1123
|
-
function _recordUnmapped(absPath, syscall) {
|
|
1124
|
-
const k = _strip(absPath);
|
|
1125
|
-
_recordMiss(k);
|
|
1126
|
-
// A listing wants the path's own contents; a read wants its bytes. Both
|
|
1127
|
-
// also want the boundary listing, which is the only thing that can prove
|
|
1128
|
-
// the path was not there — and fetching one repair and then the other
|
|
1129
|
-
// would refuse the same access twice, once for being unmapped and once for
|
|
1130
|
-
// having no resident content. _faultOnce collapses them where they meet.
|
|
1131
|
-
_faultInBoundary(k);
|
|
1132
|
-
if (syscall === "scandir") { _faultInDirectory(k); return; }
|
|
1133
|
-
_faultIn(k);
|
|
1134
|
-
}
|
|
1135
1275
|
|
|
1136
|
-
/**
|
|
1137
|
-
* The shallowest directory on this path whose contents the view does not
|
|
1138
|
-
* know: the child of the deepest ancestor that WAS enumerated.
|
|
1139
|
-
*
|
|
1140
|
-
* That, not the immediate parent, is the listing a refusal has to pull in.
|
|
1141
|
-
* For a path that is not there the parent is usually not there either, so
|
|
1142
|
-
* its readdir fails, the view learns nothing, and the ledger keeps an entry
|
|
1143
|
-
* no repair can ever retire — the run is failed over a file that was simply
|
|
1144
|
-
* absent. The boundary directory is different: the enumerated ancestor's own
|
|
1145
|
-
* listing already proved it is present, so it can be read, and reading it
|
|
1146
|
-
* either names the next component or proves the whole subtree absent.
|
|
1147
|
-
*
|
|
1148
|
-
* With nothing enumerated at all the boundary is the root, which is one
|
|
1149
|
-
* listing away from making the next level answerable.
|
|
1150
|
-
*/
|
|
1151
|
-
function _unknownBoundary(k) {
|
|
1152
|
-
const segments = k.split("/");
|
|
1153
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1154
|
-
if (_dirEnumerated(segments.slice(0, i).join("/"))) {
|
|
1155
|
-
return segments.slice(0, i + 1).join("/");
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
return "";
|
|
1159
|
-
}
|
|
1160
1276
|
|
|
1161
|
-
function _faultInBoundary(k) {
|
|
1162
|
-
_faultInDirectory(_unknownBoundary(k));
|
|
1163
|
-
}
|
|
1164
1277
|
|
|
1165
|
-
/**
|
|
1166
|
-
* The refusal, for the two callers that have no provisional answer to give.
|
|
1167
|
-
*
|
|
1168
|
-
* A listing cannot say "not found" usefully, and neither can a write that
|
|
1169
|
-
* needs the prior content it is splicing onto. Everywhere else the sync view
|
|
1170
|
-
* answers ENOENT and lets the exit report settle whether that was true; here
|
|
1171
|
-
* there is no branch the program could take that would be right, so the
|
|
1172
|
-
* honest EAGAIN is also the useful one.
|
|
1173
|
-
*/
|
|
1174
|
-
function _refuseUnmapped(absPath, displayPath, syscall, asyncForm) {
|
|
1175
|
-
_recordUnmapped(absPath, syscall);
|
|
1176
|
-
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
1177
|
-
err.message += " — '" + String(displayPath) + "' lies outside the filesystem view "
|
|
1178
|
-
+ "staged into the process, so a synchronous call cannot tell what is there, "
|
|
1179
|
-
+ "and synchronous I/O cannot block to find out"
|
|
1180
|
-
+ (_supervisor() ? "; " + asyncForm + " answers from the live filesystem" : "");
|
|
1181
|
-
return err;
|
|
1182
|
-
}
|
|
1183
1278
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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);
|
|
1193
1298
|
}
|
|
1194
1299
|
|
|
1195
1300
|
/**
|
|
1196
|
-
* Retire the
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
1199
|
-
* was
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1202
|
-
* over that would fail every module resolver, which probes dozens of paths
|
|
1203
|
-
* that do not exist by design. So what stays in the ledger is exactly the
|
|
1204
|
-
* 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.
|
|
1205
1307
|
*/
|
|
1206
1308
|
function _settleProvenAbsences() {
|
|
1207
1309
|
if (_residencyMisses.size === 0) return;
|
|
1208
1310
|
for (const k of [..._residencyMisses]) {
|
|
1209
|
-
|
|
1210
|
-
// The authority did not have it when we asked, so nothing was withheld —
|
|
1211
|
-
// whatever is at the path now, this process put there.
|
|
1212
|
-
if (_observedAbsent.has(k)) { _residencyMisses.delete(k); continue; }
|
|
1213
|
-
if (_statLadder(absPath) !== undefined) continue;
|
|
1214
|
-
if (_absenceIsKnown(absPath)) { _residencyMisses.delete(k); continue; }
|
|
1215
|
-
// Still unprovable, because the listing that would settle it lies below
|
|
1216
|
-
// the one already fetched. Ask for the next one; the settle loop waits.
|
|
1217
|
-
_faultInBoundary(k);
|
|
1311
|
+
if (_observedAbsent.has(k) || (_nsActive() && _statLadder("/" + k) === undefined)) _residencyMisses.delete(k);
|
|
1218
1312
|
}
|
|
1219
1313
|
}
|
|
1220
1314
|
|
|
@@ -1283,6 +1377,10 @@ const __fsMod = (() => {
|
|
|
1283
1377
|
|
|
1284
1378
|
const _localTimes = globalThis.__nimbusVfsTimes || (globalThis.__nimbusVfsTimes = Object.create(null));
|
|
1285
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());
|
|
1286
1384
|
|
|
1287
1385
|
function _coerceMode(value, syscall, p) {
|
|
1288
1386
|
const n = typeof value === "string" ? parseInt(value, 8) : Number(value);
|
|
@@ -1307,11 +1405,9 @@ const __fsMod = (() => {
|
|
|
1307
1405
|
atimeMs: _coerceTimeMs(atime, syscall, p),
|
|
1308
1406
|
mtimeMs: _coerceTimeMs(mtime, syscall, p),
|
|
1309
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.
|
|
1310
1410
|
_localTimes[k] = time;
|
|
1311
|
-
// A spawn-time stat record answers statSync ahead of _localTimes, so it
|
|
1312
|
-
// must carry the new times too or the sync view keeps the old mtime.
|
|
1313
|
-
const meta = _metadata(absPath);
|
|
1314
|
-
if (meta) { meta.atime = time.atimeMs; meta.mtime = time.mtimeMs; }
|
|
1315
1411
|
return time;
|
|
1316
1412
|
}
|
|
1317
1413
|
|
|
@@ -1365,7 +1461,7 @@ const __fsMod = (() => {
|
|
|
1365
1461
|
// ══ Cache coherence: ACQUIRE + read-through fill ═════════════════════
|
|
1366
1462
|
//
|
|
1367
1463
|
// The supervisor's SQLite VFS is the only authority. Everything in this
|
|
1368
|
-
// facet —
|
|
1464
|
+
// facet — the resident store, __vfsBundle, __vfsDirs — is a
|
|
1369
1465
|
// cache of it, and __vfsWrites is this process's own not-yet-flushed
|
|
1370
1466
|
// mutations.
|
|
1371
1467
|
//
|
|
@@ -1393,6 +1489,24 @@ const __fsMod = (() => {
|
|
|
1393
1489
|
// a frame arrives as a supervisor reply the same barrier
|
|
1394
1490
|
// rides on (see the relayed WebSocket below).
|
|
1395
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
|
+
//
|
|
1396
1510
|
// Keep this list empty. An entry here is a documented hole in the owner's
|
|
1397
1511
|
// invariant, not a TODO: it means some process can be woken by something
|
|
1398
1512
|
// this system does not mediate, and its next synchronous read can serve
|
|
@@ -1414,8 +1528,18 @@ const __fsMod = (() => {
|
|
|
1414
1528
|
// envelope — there is no reason to invent a second surface for it.
|
|
1415
1529
|
const _stats = globalThis.__nimbusVfsCoherence
|
|
1416
1530
|
|| (globalThis.__nimbusVfsCoherence = {
|
|
1417
|
-
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0,
|
|
1531
|
+
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0, pushes: 0,
|
|
1418
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,
|
|
1419
1543
|
});
|
|
1420
1544
|
|
|
1421
1545
|
/**
|
|
@@ -1448,8 +1572,6 @@ const __fsMod = (() => {
|
|
|
1448
1572
|
let evicted = false;
|
|
1449
1573
|
delete __vfsBundleRevisions[k];
|
|
1450
1574
|
if (__vfsBundle && k in __vfsBundle) { delete __vfsBundle[k]; evicted = true; }
|
|
1451
|
-
const metadata = _metadataTable();
|
|
1452
|
-
if (metadata && k in metadata) { delete metadata[k]; evicted = true; }
|
|
1453
1575
|
return evicted;
|
|
1454
1576
|
}
|
|
1455
1577
|
|
|
@@ -1464,15 +1586,107 @@ const __fsMod = (() => {
|
|
|
1464
1586
|
const _owedRefetch = new Set();
|
|
1465
1587
|
|
|
1466
1588
|
// An own-mutation lease can end in an eviction (a peer wrote inside the
|
|
1467
|
-
// window)
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
-
//
|
|
1471
|
-
|
|
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) {
|
|
1472
1595
|
if (!_evictResident(k)) return;
|
|
1473
1596
|
_stats.invalidations++;
|
|
1474
1597
|
_owedRefetch.add(k);
|
|
1475
|
-
}
|
|
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
|
+
}
|
|
1476
1690
|
|
|
1477
1691
|
/**
|
|
1478
1692
|
* Install bytes the supervisor just served as the resident cell for a
|
|
@@ -1486,42 +1700,30 @@ const __fsMod = (() => {
|
|
|
1486
1700
|
* returned, so it has nothing to invalidate. The bytes are already paid
|
|
1487
1701
|
* for; caching them costs one map insert.
|
|
1488
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
|
+
*
|
|
1489
1709
|
* The parent's manifest entry gains the name too, so the existence view
|
|
1490
1710
|
* cannot go on denying a file whose bytes this process is holding.
|
|
1491
1711
|
*/
|
|
1492
|
-
function _installResident(absPath, bytes) {
|
|
1712
|
+
function _installResident(absPath, bytes, fill) {
|
|
1493
1713
|
if (!__vfsBundle) return;
|
|
1494
1714
|
const k = _strip(absPath);
|
|
1495
1715
|
if (k === "") return;
|
|
1716
|
+
if (fill.reported > fill.rev) return;
|
|
1496
1717
|
// Never over a cell this facet owns.
|
|
1497
1718
|
//
|
|
1498
1719
|
// A pending write is strictly newer than anything the authority can
|
|
1499
1720
|
// report, and __vfsBundle is what sync reads consult first, so installing
|
|
1500
1721
|
// over it would serve the program bytes older than its own write.
|
|
1501
|
-
//
|
|
1502
|
-
// A STAMPED cell is this facet's own flushed write, and the barrier this
|
|
1503
|
-
// read passed on the way in reported nobody else has touched the path
|
|
1504
|
-
// since — so the bytes are already what is being installed, and the only
|
|
1505
|
-
// thing the install would change is to drop the stamp. It did: a
|
|
1506
|
-
// speculative repair issued by an earlier refused read landed after the
|
|
1507
|
-
// flush, unstamped the cell, and the next ACQUIRE evicted the facet's own
|
|
1508
|
-
// output. Measured at selfWrites 0 / invalidations 1 / fills 2 for one
|
|
1509
|
-
// written file, against 1 / 0 / 0 with the repair absent.
|
|
1510
1722
|
if (__vfsWrites && k in __vfsWrites) return;
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
//
|
|
1514
|
-
|
|
1515
|
-
// spawn-time length, so a program can read N bytes and be told the
|
|
1516
|
-
// file is M — an inconsistency introduced BY the fill. Only the size
|
|
1517
|
-
// is corrected: type, mode, ownership and timestamps stay as the
|
|
1518
|
-
// authority last reported them, so mtime-based change detection
|
|
1519
|
-
// (make, tsc --build, watchers) does not see every read as a change.
|
|
1520
|
-
const metadata = _metadataTable();
|
|
1521
|
-
if (metadata && k in metadata) {
|
|
1522
|
-
metadata[k] = { ...metadata[k], size: _byteLen(bytes) };
|
|
1523
|
-
}
|
|
1524
|
-
_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;
|
|
1525
1727
|
_stats.fills++;
|
|
1526
1728
|
_stats.filledBytes += _byteLen(bytes);
|
|
1527
1729
|
}
|
|
@@ -1557,25 +1759,86 @@ const __fsMod = (() => {
|
|
|
1557
1759
|
return _residentRepair;
|
|
1558
1760
|
}
|
|
1559
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
|
+
*/
|
|
1560
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();
|
|
1561
1816
|
let repaired;
|
|
1562
1817
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1563
1818
|
catch { repaired = null; }
|
|
1564
1819
|
if (!repaired || !repaired.cursor) {
|
|
1565
1820
|
// The pass could not vouch for a single row — a supervisor replaced
|
|
1566
|
-
// mid-enumeration, an enumeration that came back short
|
|
1567
|
-
// cannot be dated must not be served, so the
|
|
1568
|
-
//
|
|
1569
|
-
// it just dropped.
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
+
}
|
|
1573
1834
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1574
1835
|
catch { repaired = null; }
|
|
1575
1836
|
} else if (repaired.reconciled) {
|
|
1576
1837
|
_stats.reconciles++;
|
|
1577
1838
|
}
|
|
1578
|
-
|
|
1839
|
+
_settleSkippedReports(ownAtStart, repaired);
|
|
1840
|
+
_storeRepairOwed = !(repaired && repaired.cursor);
|
|
1841
|
+
if (_storeRepairOwed) return;
|
|
1579
1842
|
_cursor.epoch = repaired.cursor.epoch;
|
|
1580
1843
|
_cursor.rev = repaired.cursor.rev;
|
|
1581
1844
|
_stats.invalidations += repaired.dropped;
|
|
@@ -1583,6 +1846,41 @@ const __fsMod = (() => {
|
|
|
1583
1846
|
_stats.filledBytes += repaired.bytes;
|
|
1584
1847
|
}
|
|
1585
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
|
+
|
|
1586
1884
|
/**
|
|
1587
1885
|
* ACQUIRE. Apply every invalidation the supervisor has for this facet,
|
|
1588
1886
|
* then re-stamp the cursor.
|
|
@@ -1608,65 +1906,247 @@ const __fsMod = (() => {
|
|
|
1608
1906
|
* left. Comparing revisions rather than names keeps the peer case intact:
|
|
1609
1907
|
* a peer writing the same path afterwards reports a HIGHER revision than
|
|
1610
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.
|
|
1611
1945
|
*/
|
|
1612
|
-
async function _acquireBarrier(supervisor) {
|
|
1946
|
+
async function _acquireBarrier(supervisor, delivered) {
|
|
1613
1947
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return [];
|
|
1614
|
-
|
|
1615
|
-
//
|
|
1616
|
-
//
|
|
1617
|
-
//
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
// .then never ran, no error, no output, intermittently, and never when a
|
|
1621
|
-
// pending timer happened to hold the program open.
|
|
1622
|
-
try { result = await __nimbusUseRpcResult(supervisor.fsAcquire(_cursor.epoch, _cursor.rev), (r) => r); }
|
|
1623
|
-
catch { return []; }
|
|
1624
|
-
if (!result || typeof result.rev !== "number") return [];
|
|
1625
|
-
// Paths that held content before this eviction are the ones worth
|
|
1626
|
-
// refetching at a resumption boundary — a later sync read of any of them
|
|
1627
|
-
// would otherwise miss. Collected before the delete so the membership
|
|
1628
|
-
// test is against the pre-eviction bundle.
|
|
1629
|
-
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);
|
|
1630
1954
|
// When the resident set lives in the facet's own SQLite, the STORE applies
|
|
1631
1955
|
// the delta. That is not an optimisation, it is where provenance has to
|
|
1632
1956
|
// live: a facet's SQLite outlives its module scope, so a new incarnation
|
|
1633
1957
|
// opens onto rows a previous one wrote while every heap-side stamp that
|
|
1634
1958
|
// described them is gone. Rows carry their own revision, __vfsBundleRevisions
|
|
1635
1959
|
// cannot follow them there, and two provenance stores would be one too many.
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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);
|
|
1643
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);
|
|
1644
2014
|
const applied = __residentAdmit(result);
|
|
1645
|
-
_cursor.epoch =
|
|
1646
|
-
_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);
|
|
1647
2020
|
_stats.invalidations += applied.dropped.length;
|
|
2021
|
+
_stats.selfWrites += applied.kept;
|
|
2022
|
+
_stats.pushes += applied.pushed || 0;
|
|
2023
|
+
await _awaitReportedOwnWrites();
|
|
1648
2024
|
return applied.dropped;
|
|
1649
2025
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
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");
|
|
1665
2104
|
}
|
|
2105
|
+
return _currentAnswer(result);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
_stats.barrierFailures++;
|
|
2108
|
+
_stats.lastBarrierFailure = (error && error.message) || String(error);
|
|
2109
|
+
return null;
|
|
1666
2110
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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);
|
|
1670
2150
|
}
|
|
1671
2151
|
|
|
1672
2152
|
/**
|
|
@@ -1688,9 +2168,22 @@ const __fsMod = (() => {
|
|
|
1688
2168
|
* This closes staleness of RESIDENT cells across an untrusted resumption.
|
|
1689
2169
|
* A first synchronous touch of a NON-resident path is the separate
|
|
1690
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.
|
|
1691
2184
|
*/
|
|
1692
|
-
async function _acquireAndRefetch(supervisor) {
|
|
1693
|
-
const stale = await _acquireBarrier(supervisor);
|
|
2185
|
+
async function _acquireAndRefetch(supervisor, delivered) {
|
|
2186
|
+
const stale = await _acquireBarrier(supervisor, delivered);
|
|
1694
2187
|
// Plus what an own-mutation lease dropped since the last resumption:
|
|
1695
2188
|
// the same debt this function exists to settle, owed by an eviction
|
|
1696
2189
|
// that had no barrier to report it (see _owedRefetch).
|
|
@@ -1698,8 +2191,58 @@ const __fsMod = (() => {
|
|
|
1698
2191
|
for (const k of _owedRefetch) if (stale.indexOf(k) === -1) stale.push(k);
|
|
1699
2192
|
_owedRefetch.clear();
|
|
1700
2193
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
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.
|
|
2209
|
+
*/
|
|
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;
|
|
1703
2246
|
}
|
|
1704
2247
|
|
|
1705
2248
|
/**
|
|
@@ -1711,14 +2254,15 @@ const __fsMod = (() => {
|
|
|
1711
2254
|
* its fetch handler; binding the supervisor when the wrappers are
|
|
1712
2255
|
* installed would silently leave every resident-TUI timer unbarriered.
|
|
1713
2256
|
*
|
|
1714
|
-
* Published on globalThis because the
|
|
1715
|
-
*
|
|
1716
|
-
*
|
|
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.
|
|
1717
2261
|
*/
|
|
1718
|
-
async function _resumptionAcquire() {
|
|
2262
|
+
async function _resumptionAcquire(delivered) {
|
|
1719
2263
|
const supervisor = _supervisor();
|
|
1720
2264
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return;
|
|
1721
|
-
await _acquireAndRefetch(supervisor);
|
|
2265
|
+
await _acquireAndRefetch(supervisor, delivered);
|
|
1722
2266
|
}
|
|
1723
2267
|
|
|
1724
2268
|
/**
|
|
@@ -1752,6 +2296,7 @@ const __fsMod = (() => {
|
|
|
1752
2296
|
if (globalThis.__nimbusResumptionBarriersInstalled) return;
|
|
1753
2297
|
globalThis.__nimbusResumptionBarriersInstalled = true;
|
|
1754
2298
|
globalThis.__nimbusVfsAcquireBarrier = _resumptionAcquire;
|
|
2299
|
+
globalThis.__nimbusVfsAcquireArgs = _acquireArgs;
|
|
1755
2300
|
globalThis.__nimbusVfsReleaseBarrier = _resumptionRelease;
|
|
1756
2301
|
const _setTimeout = globalThis.setTimeout;
|
|
1757
2302
|
const _setInterval = globalThis.setInterval;
|
|
@@ -1859,8 +2404,9 @@ const __fsMod = (() => {
|
|
|
1859
2404
|
// \`method\` names the supervisor RPC when it differs from the syscall the
|
|
1860
2405
|
// caller reports (lchown rides \`chown\`, rm rides \`fsRemove\`).
|
|
1861
2406
|
function _queueStructuralMutation(absPath, syscall, displayPath, rpc, after, method) {
|
|
2407
|
+
const settle = _nsTakeFresh();
|
|
1862
2408
|
const supervisor = _supervisor();
|
|
1863
|
-
if (!supervisor || typeof supervisor[method || syscall] !== "function") return null;
|
|
2409
|
+
if (!supervisor || typeof supervisor[method || syscall] !== "function") { settle(); return null; }
|
|
1864
2410
|
const queued = _hasVfsMutationQueue();
|
|
1865
2411
|
const before = queued && after ? after() : null;
|
|
1866
2412
|
const run = async () => {
|
|
@@ -1876,7 +2422,11 @@ const __fsMod = (() => {
|
|
|
1876
2422
|
);
|
|
1877
2423
|
_markVfsStale();
|
|
1878
2424
|
};
|
|
1879
|
-
|
|
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;
|
|
1880
2430
|
}
|
|
1881
2431
|
|
|
1882
2432
|
/**
|
|
@@ -1889,25 +2439,33 @@ const __fsMod = (() => {
|
|
|
1889
2439
|
* effect while the lease still holds the cell, so the stamp the end
|
|
1890
2440
|
* settles describes a cell that already carries the mutation.
|
|
1891
2441
|
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
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.
|
|
1894
2450
|
*/
|
|
1895
|
-
|
|
2451
|
+
function _ownMutation(absPath, rpc, apply) {
|
|
1896
2452
|
const key = _strip(absPath);
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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
|
+
});
|
|
1911
2469
|
}
|
|
1912
2470
|
|
|
1913
2471
|
// A sync caller has no frame to receive the outcome. The ledger already
|
|
@@ -1942,6 +2500,8 @@ const __fsMod = (() => {
|
|
|
1942
2500
|
"write", absPath,
|
|
1943
2501
|
(result) => result,
|
|
1944
2502
|
),
|
|
2503
|
+
true,
|
|
2504
|
+
true,
|
|
1945
2505
|
);
|
|
1946
2506
|
}
|
|
1947
2507
|
|
|
@@ -1952,13 +2512,18 @@ const __fsMod = (() => {
|
|
|
1952
2512
|
await _flushParkedWrite(absPath, supervisor);
|
|
1953
2513
|
_markVfsStale();
|
|
1954
2514
|
}
|
|
1955
|
-
//
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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
|
+
}
|
|
1962
2527
|
_markVfsStale();
|
|
1963
2528
|
}
|
|
1964
2529
|
}
|
|
@@ -2046,8 +2611,11 @@ const __fsMod = (() => {
|
|
|
2046
2611
|
const atime = new Date(Number(meta?.atime || meta?.mtime || Date.now()));
|
|
2047
2612
|
const mode = Number(meta?.mode ?? (isDir ? 0o755 : 0o644));
|
|
2048
2613
|
const stat = _localStatObject(key, isDir, isSymlink, size, mode, meta?.uid, meta?.gid);
|
|
2049
|
-
|
|
2050
|
-
|
|
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;
|
|
2051
2619
|
stat.ctime = new Date(Number(meta?.ctime ?? meta?.mtime ?? Date.now()));
|
|
2052
2620
|
stat.birthtime = stat.ctime;
|
|
2053
2621
|
return stat;
|
|
@@ -2080,24 +2648,32 @@ const __fsMod = (() => {
|
|
|
2080
2648
|
// multi-MB file for one RPC frame.
|
|
2081
2649
|
const READ_STREAM_CHUNK_BYTES = 65536;
|
|
2082
2650
|
|
|
2083
|
-
// 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.
|
|
2084
2653
|
//
|
|
2085
2654
|
// A round trip costs an order of magnitude more than the read behind it,
|
|
2086
2655
|
// so a program awaiting reads one at a time pays for round trips and
|
|
2087
2656
|
// nothing else. Nothing here changes what a read sees: every request is
|
|
2088
|
-
// the same live ranged read, executed in order, in the caller's
|
|
2089
|
-
// gather window is one microtask, so it can only capture
|
|
2090
|
-
// had already issued concurrently — a sequential
|
|
2091
|
-
// 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.
|
|
2092
2661
|
const READ_BATCH_PATH_LIMIT = ${FS_READ_BATCH_PATH_LIMIT};
|
|
2093
2662
|
const READ_BATCH_REQUEST_BYTES = ${FS_READ_BATCH_REQUEST_BYTES};
|
|
2094
2663
|
let _openReadBatch = null;
|
|
2095
2664
|
|
|
2096
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) {
|
|
2097
2673
|
let batch = _openReadBatch;
|
|
2098
2674
|
if (batch && (
|
|
2099
2675
|
batch.requests.length >= READ_BATCH_PATH_LIMIT
|
|
2100
|
-
|| batch.bytes +
|
|
2676
|
+
|| batch.bytes + bytes > READ_BATCH_REQUEST_BYTES
|
|
2101
2677
|
)) {
|
|
2102
2678
|
_openReadBatch = null;
|
|
2103
2679
|
_flushReadBatch(batch);
|
|
@@ -2107,17 +2683,24 @@ const __fsMod = (() => {
|
|
|
2107
2683
|
batch = { supervisor, requests: [], settlers: [], bytes: 0 };
|
|
2108
2684
|
_openReadBatch = batch;
|
|
2109
2685
|
queueMicrotask(() => {
|
|
2110
|
-
|
|
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;
|
|
2111
2690
|
_flushReadBatch(batch);
|
|
2112
2691
|
});
|
|
2113
2692
|
}
|
|
2114
|
-
batch.bytes +=
|
|
2115
|
-
batch.requests.push(
|
|
2116
|
-
|
|
2693
|
+
batch.bytes += bytes;
|
|
2694
|
+
batch.requests.push(request);
|
|
2695
|
+
const settler = Promise.withResolvers();
|
|
2696
|
+
batch.settlers.push(settler);
|
|
2697
|
+
return settler.promise;
|
|
2117
2698
|
}
|
|
2118
2699
|
|
|
2119
2700
|
async function _flushReadBatch(batch) {
|
|
2120
|
-
|
|
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++;
|
|
2121
2704
|
try {
|
|
2122
2705
|
const entries = await __nimbusUseRpcResult(
|
|
2123
2706
|
batch.supervisor.fsReadBatch(batch.requests), (r) => r,
|
|
@@ -2128,6 +2711,8 @@ const __fsMod = (() => {
|
|
|
2128
2711
|
const err = new Error(entry.error.message);
|
|
2129
2712
|
if (entry.error.code) err.code = entry.error.code;
|
|
2130
2713
|
batch.settlers[i].reject(err);
|
|
2714
|
+
} else if (batch.requests[i].lstat === true) {
|
|
2715
|
+
batch.settlers[i].resolve(entry ? entry.stat : null);
|
|
2131
2716
|
} else {
|
|
2132
2717
|
batch.settlers[i].resolve(entry ? entry.bytes : null);
|
|
2133
2718
|
}
|
|
@@ -2192,59 +2777,83 @@ const __fsMod = (() => {
|
|
|
2192
2777
|
return parts;
|
|
2193
2778
|
}
|
|
2194
2779
|
|
|
2195
|
-
async function _liveReadFile(p, opts,
|
|
2780
|
+
async function _liveReadFile(p, opts, refetch) {
|
|
2196
2781
|
const absPath = _resolve(p);
|
|
2197
2782
|
const encoding = typeof opts === "string" ? opts : opts?.encoding;
|
|
2198
2783
|
const supervisor = _supervisor();
|
|
2199
2784
|
if (!supervisor) throw _fsErr("ENOENT", "open", p);
|
|
2200
|
-
//
|
|
2201
|
-
//
|
|
2202
|
-
// that always returns "still R". Every other caller
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
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;
|
|
2228
2817
|
}
|
|
2229
|
-
|
|
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);
|
|
2230
2822
|
}
|
|
2231
|
-
const bytes = parts.length === 1 ? parts[0] : _concatBytes(parts, total);
|
|
2232
|
-
_installResident(absPath, bytes);
|
|
2233
|
-
return encoding ? _asString(bytes) : __BufferMod.from(bytes);
|
|
2234
|
-
}
|
|
2235
2823
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
+
}
|
|
2242
2832
|
}
|
|
2833
|
+
} finally {
|
|
2834
|
+
if (!refetch) _endFill(fill);
|
|
2243
2835
|
}
|
|
2244
2836
|
|
|
2245
2837
|
throw _fsErr("ENOENT", "open", p);
|
|
2246
2838
|
}
|
|
2247
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
|
+
|
|
2248
2857
|
function _concatBytes(parts, total) {
|
|
2249
2858
|
const out = new Uint8Array(total);
|
|
2250
2859
|
let off = 0;
|
|
@@ -2316,24 +2925,11 @@ const __fsMod = (() => {
|
|
|
2316
2925
|
// its shape: the sync existence view must not go on contradicting a
|
|
2317
2926
|
// directory this very process has enumerated. Names only — size, mode
|
|
2318
2927
|
// and ownership are not observable here and are never invented.
|
|
2319
|
-
|
|
2320
|
-
//
|
|
2321
|
-
//
|
|
2322
|
-
//
|
|
2323
|
-
//
|
|
2324
|
-
// recording it invents nothing; file children are left alone, because
|
|
2325
|
-
// their size is not knowable from a listing and must not be guessed.
|
|
2326
|
-
const metadataTable = _metadataTable();
|
|
2327
|
-
if (metadataTable) {
|
|
2328
|
-
for (const entry of entries) {
|
|
2329
|
-
if (entry.type !== "directory") continue;
|
|
2330
|
-
const child = prefix + entry.name;
|
|
2331
|
-
if (child in metadataTable) continue;
|
|
2332
|
-
metadataTable[child] = {
|
|
2333
|
-
type: "directory", size: 0, mode: 0o755, uid: cred.uid, gid: cred.gid,
|
|
2334
|
-
};
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
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).
|
|
2337
2933
|
if (opts?.withFileTypes) {
|
|
2338
2934
|
return entries
|
|
2339
2935
|
.map((entry) => _direntObject(entry.name, entry.type, absPath))
|
|
@@ -2391,6 +2987,8 @@ const __fsMod = (() => {
|
|
|
2391
2987
|
_fsRpc(supervisor.writeFile(absPath, content), "write", p, (result) => result)
|
|
2392
2988
|
);
|
|
2393
2989
|
_markVfsStale();
|
|
2990
|
+
// Accepted: the authority's stat is what the sync view keeps for it.
|
|
2991
|
+
await _learnLive(absPath, supervisor);
|
|
2394
2992
|
}
|
|
2395
2993
|
}
|
|
2396
2994
|
|
|
@@ -2409,6 +3007,7 @@ const __fsMod = (() => {
|
|
|
2409
3007
|
),
|
|
2410
3008
|
);
|
|
2411
3009
|
_markVfsStale();
|
|
3010
|
+
await _learnLive(absPath, supervisor);
|
|
2412
3011
|
}
|
|
2413
3012
|
|
|
2414
3013
|
// The async structural calls ARE the sync ones, awaited: the same local
|
|
@@ -2516,11 +3115,27 @@ const __fsMod = (() => {
|
|
|
2516
3115
|
if (!supervisor && !existsSync(p)) throw _fsErr("ENOENT", syscall, p);
|
|
2517
3116
|
}
|
|
2518
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
|
+
|
|
2519
3131
|
function chmodSync(p, mode) {
|
|
2520
3132
|
if (!existsSync(p)) throw _fsErr("ENOENT", "chmod", p);
|
|
3133
|
+
_ensureModeOwner(_resolve(p), "chmod", p);
|
|
2521
3134
|
// Local-visible immediately (statSync overlay); the live write-through
|
|
2522
3135
|
// rides the next flush of the same path — same fidelity as utimesSync.
|
|
2523
|
-
|
|
3136
|
+
const k = _strip(_resolve(p));
|
|
3137
|
+
_localModes[k] = _coerceMode(mode, "chmod", p);
|
|
3138
|
+
_pendingModes.add(k);
|
|
2524
3139
|
}
|
|
2525
3140
|
|
|
2526
3141
|
async function _chmodAsync(p, mode) {
|
|
@@ -2532,7 +3147,9 @@ const __fsMod = (() => {
|
|
|
2532
3147
|
throw _fsErr("ENOENT", "chmod", p);
|
|
2533
3148
|
}
|
|
2534
3149
|
const m = _coerceMode(mode, "chmod", p);
|
|
3150
|
+
if (localExists) _ensureModeOwner(absPath, "chmod", p);
|
|
2535
3151
|
_localModes[_strip(absPath)] = m;
|
|
3152
|
+
_pendingModes.add(_strip(absPath));
|
|
2536
3153
|
if (supervisor && typeof supervisor.chmod === "function") {
|
|
2537
3154
|
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2538
3155
|
_markVfsStale();
|
|
@@ -2584,6 +3201,18 @@ const __fsMod = (() => {
|
|
|
2584
3201
|
}
|
|
2585
3202
|
const nextUid = _coerceId(uid, syscall, p);
|
|
2586
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
|
+
}
|
|
2587
3216
|
const meta = _metadata(absPath);
|
|
2588
3217
|
if (meta) { meta.uid = nextUid; meta.gid = nextGid; }
|
|
2589
3218
|
// The parked write must be registered on the tail BEFORE the chown is
|
|
@@ -2600,21 +3229,25 @@ const __fsMod = (() => {
|
|
|
2600
3229
|
function lchownSync(p, uid, gid) { _detachStructuralMutation(_chownQueued(p, uid, gid, { followSymlinks: false }, "lchown")); }
|
|
2601
3230
|
function lchmodSync(p, mode) { chmodSync(p, mode); }
|
|
2602
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
|
+
*/
|
|
2603
3239
|
function _modeAllows(meta, want) {
|
|
2604
3240
|
if (want === 0) return true;
|
|
2605
|
-
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;
|
|
2606
3246
|
const currentUid = Number(cred.uid);
|
|
2607
|
-
|
|
3247
|
+
if (currentUid === 0) return (want & 1) === 0 || (bits & 0o111) !== 0;
|
|
2608
3248
|
const groups = cred.groups.map(Number);
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
return true;
|
|
2612
|
-
}
|
|
2613
|
-
const shift = currentUid === Number(meta?.uid ?? 1000)
|
|
2614
|
-
? 6
|
|
2615
|
-
: (currentGid === Number(meta?.gid ?? 1000) || groups.includes(Number(meta?.gid ?? 1000))) ? 3 : 0;
|
|
2616
|
-
const available = (mode >> shift) & 7;
|
|
2617
|
-
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;
|
|
2618
3251
|
}
|
|
2619
3252
|
|
|
2620
3253
|
function _ensureAncestorsTraversable(absPath, syscall, p) {
|
|
@@ -2628,34 +3261,31 @@ const __fsMod = (() => {
|
|
|
2628
3261
|
function _ensureWritable(absPath, syscall, p) {
|
|
2629
3262
|
_ensureAncestorsTraversable(absPath, syscall, p);
|
|
2630
3263
|
const cell = _bundleLookup(absPath);
|
|
2631
|
-
const
|
|
2632
|
-
|
|
2633
|
-
//
|
|
2634
|
-
// the
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
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);
|
|
2638
3271
|
return;
|
|
2639
3272
|
}
|
|
3273
|
+
// Nothing there: a create, judged by the parent the namespace describes.
|
|
2640
3274
|
|
|
2641
3275
|
const parent = __pathMod.dirname(absPath);
|
|
2642
|
-
const
|
|
2643
|
-
if (
|
|
2644
|
-
if (
|
|
2645
|
-
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);
|
|
2646
3280
|
return;
|
|
2647
3281
|
}
|
|
2648
3282
|
|
|
2649
|
-
const parentKey = _strip(parent);
|
|
2650
|
-
const parentIsLocal = parent === cwd || parent === "/" ||
|
|
2651
|
-
(!!__vfsDirs && parentKey in __vfsDirs) ||
|
|
2652
|
-
(!!__vfsManifest && parentKey in __vfsManifest);
|
|
2653
|
-
if (parentIsLocal || _supervisor()) return;
|
|
2654
3283
|
throw _fsErr("ENOENT", syscall, p);
|
|
2655
3284
|
}
|
|
2656
3285
|
|
|
2657
3286
|
function accessSync(p, mode) {
|
|
2658
3287
|
const absPath = _resolve(p);
|
|
3288
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2659
3289
|
const cell = _bundleLookup(absPath);
|
|
2660
3290
|
const meta = _metadata(absPath);
|
|
2661
3291
|
if (cell === undefined && meta === undefined && !existsSync(p)) throw _fsErr("ENOENT", "access", p);
|
|
@@ -2666,7 +3296,10 @@ const __fsMod = (() => {
|
|
|
2666
3296
|
_ensureAncestorsTraversable(absPath, "access", p);
|
|
2667
3297
|
const denial = _denialCode(cell);
|
|
2668
3298
|
if ((requested & 4) !== 0 && denial) throw _fsErr(denial, "access", p);
|
|
2669
|
-
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);
|
|
2670
3303
|
}
|
|
2671
3304
|
|
|
2672
3305
|
async function _accessAsync(p, mode) {
|
|
@@ -2777,42 +3410,12 @@ const __fsMod = (() => {
|
|
|
2777
3410
|
// ── existsSync ──
|
|
2778
3411
|
function existsSync(p) {
|
|
2779
3412
|
const absPath = _resolve(p);
|
|
2780
|
-
|
|
2781
|
-
if (_metadata(absPath) !== undefined) { _residencySatisfied(absPath); return true; }
|
|
2782
|
-
if (__vfsBundle && k in __vfsBundle) { _residencySatisfied(absPath); return true; }
|
|
2783
|
-
if (__vfsWrites && k in __vfsWrites) { _residencySatisfied(absPath); return true; }
|
|
2784
|
-
if (__vfsDirs && k in __vfsDirs) { _residencySatisfied(absPath); return true; }
|
|
2785
|
-
// W2.5b root-cause fix: consult the manifest BEFORE falling back to
|
|
2786
|
-
// the O(N) bundle-prefix scan. The manifest is uncapped and always
|
|
2787
|
-
// reflects the real directory shape, even when the file content for
|
|
2788
|
-
// a directory's contents was excluded by the 4 MiB / 500-file content
|
|
2789
|
-
if (__vfsManifest) {
|
|
2790
|
-
if (k in __vfsManifest) { _residencySatisfied(absPath); return true; }
|
|
2791
|
-
// Path may be a file listed by its parent's manifest entry.
|
|
2792
|
-
const slash = k.lastIndexOf("/");
|
|
2793
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2794
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2795
|
-
const sib = __vfsManifest[parent];
|
|
2796
|
-
if (sib && sib.indexOf(name) !== -1) { _residencySatisfied(absPath); return true; }
|
|
2797
|
-
}
|
|
2798
|
-
// Last-resort: bundle dir entries
|
|
2799
|
-
if (__vfsBundle && (k in __vfsBundle || __residentAnyUnder(k + "/"))) {
|
|
2800
|
-
_residencySatisfied(absPath);
|
|
2801
|
-
return true;
|
|
2802
|
-
}
|
|
2803
|
-
// Nothing found — which is only an answer if the enclosing directory was
|
|
2804
|
-
// enumerated. Node's existsSync never throws, so the false below is
|
|
2805
|
-
// provisional: the miss is recorded, the listing that settles it is pulled
|
|
2806
|
-
// in, and the exit report fails the run if the path turns out to have been
|
|
2807
|
-
// there all along.
|
|
2808
|
-
if (!_absenceIsKnown(absPath)) {
|
|
2809
|
-
_recordUnmapped(absPath, "access");
|
|
2810
|
-
return false;
|
|
2811
|
-
}
|
|
3413
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2812
3414
|
_residencySatisfied(absPath);
|
|
2813
|
-
return
|
|
3415
|
+
return _statLadder(absPath) !== undefined;
|
|
2814
3416
|
}
|
|
2815
3417
|
|
|
3418
|
+
|
|
2816
3419
|
// ── statSync ──
|
|
2817
3420
|
function statSync(p, opts) {
|
|
2818
3421
|
const absPath = _resolve(p);
|
|
@@ -2825,16 +3428,12 @@ const __fsMod = (() => {
|
|
|
2825
3428
|
// classify a miss without redoing the resolve and ancestor walk it just
|
|
2826
3429
|
// performed — that path runs on every module-resolution probe.
|
|
2827
3430
|
function _statResolved(absPath, p, opts) {
|
|
3431
|
+
_nsRequire("stat", p, "fs.promises.stat");
|
|
2828
3432
|
const stat = _statLadder(absPath);
|
|
2829
|
-
// An answer settles the path, including the honest "not there"
|
|
2830
|
-
//
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
if (stat !== undefined) { _residencySatisfied(absPath); return stat; }
|
|
2834
|
-
// Nothing in the view describes the path, and there are two very different
|
|
2835
|
-
// reasons for that. Only one of them means the file is not there.
|
|
2836
|
-
if (_absenceIsKnown(absPath)) _residencySatisfied(absPath);
|
|
2837
|
-
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;
|
|
2838
3437
|
// Node's statSync honors { throwIfNoEntry: false } by returning undefined
|
|
2839
3438
|
// for a missing path instead of throwing.
|
|
2840
3439
|
if (opts && opts.throwIfNoEntry === false) return undefined;
|
|
@@ -2847,154 +3446,45 @@ const __fsMod = (() => {
|
|
|
2847
3446
|
* MEANS — absence and ignorance are different answers and only one of them
|
|
2848
3447
|
* is a condition a program can act on, so the callers classify.
|
|
2849
3448
|
*/
|
|
2850
|
-
function _statLadder(absPath) {
|
|
3449
|
+
function _statLadder(absPath, noFollow) {
|
|
2851
3450
|
const k = _strip(absPath);
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
const size = _byteLen(__vfsWrites[k]);
|
|
2859
|
-
// Correct only the size — reusing the metadata record keeps the type
|
|
2860
|
-
// and the recorded timestamps stable, so mtime-based change detection
|
|
2861
|
-
// (make, tsc --build, watchers) does not see every call as a change.
|
|
2862
|
-
if (meta) return _statObject({ ...meta, size }, k);
|
|
2863
|
-
return _localStatObject(
|
|
2864
|
-
k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid,
|
|
2865
|
-
);
|
|
2866
|
-
}
|
|
2867
|
-
const metadata = _metadata(absPath);
|
|
2868
|
-
if (metadata) return _statObject(metadata, k);
|
|
2869
|
-
// Check if it's a known directory written this exec session
|
|
2870
|
-
if (__vfsDirs && k in __vfsDirs) {
|
|
2871
|
-
return _localStatObject(k, true, false, 0, 0o777 & ~__processUmask, cred.uid, cred.gid);
|
|
2872
|
-
}
|
|
2873
|
-
// W2.5b: consult uncapped manifest first for directory shape.
|
|
2874
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2875
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2876
|
-
}
|
|
2877
|
-
// File with content embedded?
|
|
2878
|
-
const content = _bundleLookup(absPath);
|
|
2879
|
-
if (content !== undefined) {
|
|
2880
|
-
// _byteLen handles both string (UTF-8 encode) and Uint8Array
|
|
2881
|
-
// (byteLength) — fixes binary writes from reporting the
|
|
2882
|
-
// post-corruption byte count.
|
|
2883
|
-
const size = _byteLen(content);
|
|
2884
|
-
return _localStatObject(k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid);
|
|
2885
|
-
}
|
|
2886
|
-
// File listed in parent's manifest but content was capped out — return
|
|
2887
|
-
// a zero-size file stat so callers like fs.stat / fs.statSync see the
|
|
2888
|
-
// file as present (downstream readFileSync will surface ENOENT if it
|
|
2889
|
-
// actually tries to read content; many consumers only need stat).
|
|
2890
|
-
if (__vfsManifest) {
|
|
2891
|
-
const slash = k.lastIndexOf("/");
|
|
2892
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2893
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2894
|
-
const sib = __vfsManifest[parent];
|
|
2895
|
-
if (sib && sib.indexOf(name) !== -1) {
|
|
2896
|
-
return _localStatObject(k, false, false, 0, 0o644, cred.uid, cred.gid);
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
// Last-resort: bundle prefix scan (legacy path).
|
|
2900
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) {
|
|
2901
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2902
|
-
}
|
|
2903
|
-
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);
|
|
2904
3457
|
}
|
|
2905
3458
|
|
|
2906
3459
|
// ── lstatSync (alias for statSync in our VFS — no symlinks) ──
|
|
2907
|
-
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
|
+
}
|
|
2908
3469
|
|
|
2909
3470
|
// ── readdirSync ──
|
|
2910
|
-
//
|
|
2911
|
-
//
|
|
2912
|
-
// not in the manifest (e.g. dirs created at exec time via mkdirSync) and
|
|
2913
|
-
// 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).
|
|
2914
3473
|
function readdirSync(p, opts) {
|
|
2915
3474
|
const absPath = _resolve(p);
|
|
3475
|
+
_nsRequire("scandir", p, "fs.promises.readdir");
|
|
2916
3476
|
_ensureAncestorsTraversable(absPath, "scandir", p);
|
|
2917
3477
|
const metadata = _metadata(absPath);
|
|
2918
3478
|
if (metadata && !_modeAllows(metadata, 4)) throw _fsErr("EACCES", "scandir", p);
|
|
2919
3479
|
const k = _strip(absPath);
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
// enumerated, or one this process created, can honestly be listed.
|
|
2924
|
-
if (!_dirEnumerated(k) && !_bundleHasChildren(k)) {
|
|
2925
|
-
// An authority exists that this call cannot reach, so an empty array
|
|
2926
|
-
// would be a claim about a directory nothing ever opened. Refuse, and
|
|
2927
|
-
// pull the listing in for the next one.
|
|
2928
|
-
//
|
|
2929
|
-
// This is the one caller that keeps refusing. Everywhere else the sync
|
|
2930
|
-
// view answers a path it cannot map with the not-found the program was
|
|
2931
|
-
// written to handle, provisionally, and lets the exit report settle
|
|
2932
|
-
// whether that answer was true. A listing has no such answer to give:
|
|
2933
|
-
// "no config yet, write one" is an idiom every program implements, and
|
|
2934
|
-
// there is no counterpart for a directory — a caller handed ENOENT for a
|
|
2935
|
-
// template tree has nowhere to go but the same wrong branch an empty
|
|
2936
|
-
// array sent it down, and the array at least it could not detect.
|
|
2937
|
-
if (_supervisor()) {
|
|
2938
|
-
throw _refuseUnmapped(absPath, p, "scandir", "fs.promises.readdir");
|
|
2939
|
-
}
|
|
2940
|
-
// No authority: the staged tables are the whole filesystem. A path they
|
|
2941
|
-
// do not describe is missing, and one they describe with no children is
|
|
2942
|
-
// genuinely empty.
|
|
2943
|
-
if (_statLadder(absPath) === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
2944
|
-
}
|
|
2945
|
-
const prefix = k ? k + "/" : "";
|
|
2946
|
-
const names = new Set();
|
|
2947
|
-
// 1. Manifest-supplied children (the authoritative source for installed pkgs).
|
|
2948
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2949
|
-
for (const n of __vfsManifest[k]) names.add(n);
|
|
2950
|
-
}
|
|
2951
|
-
// 2. Bundle-prefix fallback (covers older paths or runtime-mkdir'd ones
|
|
2952
|
-
// that aren't in the manifest yet).
|
|
2953
|
-
if (__vfsBundle) {
|
|
2954
|
-
for (const bk of __residentUnder(prefix)) {
|
|
2955
|
-
const seg = bk.substring(prefix.length).split("/")[0];
|
|
2956
|
-
if (seg) names.add(seg);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
// 3. Files written during this exec session.
|
|
2960
|
-
if (__vfsWrites) {
|
|
2961
|
-
for (const wk in __vfsWrites) {
|
|
2962
|
-
if (wk.startsWith(prefix)) {
|
|
2963
|
-
const rest = wk.substring(prefix.length);
|
|
2964
|
-
const seg = rest.split("/")[0];
|
|
2965
|
-
if (seg) names.add(seg);
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
// 4. Dirs created during this exec session.
|
|
2970
|
-
if (__vfsDirs) {
|
|
2971
|
-
for (const dk in __vfsDirs) {
|
|
2972
|
-
if (dk.startsWith(prefix)) {
|
|
2973
|
-
const rest = dk.substring(prefix.length);
|
|
2974
|
-
const seg = rest.split("/")[0];
|
|
2975
|
-
if (seg) names.add(seg);
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
3480
|
+
const st = _statLadder(absPath);
|
|
3481
|
+
if (st === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
3482
|
+
if (!st.isDirectory()) throw _fsErr("ENOTDIR", "scandir", p);
|
|
2979
3483
|
_residencySatisfied(absPath);
|
|
2980
|
-
const
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
// Manifest is the definitive isDir source; fall back to bundle scan.
|
|
2985
|
-
// The metadata record sits between them: a directory that was listed
|
|
2986
|
-
// but never walked has no manifest entry of its own, and calling it a
|
|
2987
|
-
// file is how a template tree full of subdirectories reads back as a
|
|
2988
|
-
// tree full of nothing.
|
|
2989
|
-
const isDir =
|
|
2990
|
-
(!!__vfsManifest && fp in __vfsManifest) ||
|
|
2991
|
-
(!!__vfsDirs && fp in __vfsDirs) ||
|
|
2992
|
-
_metadata(fp)?.type === "directory" ||
|
|
2993
|
-
(!!__vfsBundle && __residentAnyUnder(fp + "/"));
|
|
2994
|
-
return _direntObject(n, isDir ? "directory" : "file", absPath);
|
|
2995
|
-
});
|
|
2996
|
-
}
|
|
2997
|
-
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));
|
|
2998
3488
|
}
|
|
2999
3489
|
|
|
3000
3490
|
// ── mkdirSync ──
|
|
@@ -3008,11 +3498,16 @@ const __fsMod = (() => {
|
|
|
3008
3498
|
if (opts?.recursive) {
|
|
3009
3499
|
const parts = k.split("/").filter(Boolean);
|
|
3010
3500
|
let cur = "";
|
|
3011
|
-
for (const part of parts) { cur = cur ? cur + "/" + part : part; __vfsDirs[cur] = true; created.push(cur); }
|
|
3501
|
+
for (const part of parts) { cur = cur ? cur + "/" + part : part; _noteCreation(cur); __vfsDirs[cur] = true; created.push(cur); }
|
|
3012
3502
|
} else {
|
|
3503
|
+
_noteCreation(k);
|
|
3013
3504
|
__vfsDirs[k] = true;
|
|
3014
3505
|
created.push(k);
|
|
3015
3506
|
}
|
|
3507
|
+
for (const dir of created) {
|
|
3508
|
+
const was = _nsMeta(dir, true);
|
|
3509
|
+
if (was === "absent") _nsOwnSet(dir, "dir", { hide: true });
|
|
3510
|
+
}
|
|
3016
3511
|
const queued = _queueStructuralMutation(absPath, "mkdir", p, (supervisor) => supervisor.mkdir(absPath));
|
|
3017
3512
|
// Told, not merely known locally: _announceLocalDirs must not issue a
|
|
3018
3513
|
// second mkdir for a directory whose own RPC is already in the queue.
|
|
@@ -3022,9 +3517,30 @@ const __fsMod = (() => {
|
|
|
3022
3517
|
function mkdirSync(p, opts) { _detachStructuralMutation(_mkdirQueued(p, opts)); }
|
|
3023
3518
|
|
|
3024
3519
|
// ── unlinkSync ──
|
|
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
|
+
|
|
3025
3540
|
function _unlinkQueued(p) {
|
|
3026
3541
|
const absPath = _resolve(p);
|
|
3027
3542
|
const k = _strip(absPath);
|
|
3543
|
+
_ensureRemovable(absPath, "unlink", p);
|
|
3028
3544
|
if (__vfsBundle) delete __vfsBundle[k];
|
|
3029
3545
|
if (__vfsWrites) delete __vfsWrites[k];
|
|
3030
3546
|
_forgetSyncPath(k);
|
|
@@ -3036,6 +3552,7 @@ const __fsMod = (() => {
|
|
|
3036
3552
|
function _rmdirQueued(p) {
|
|
3037
3553
|
const absPath = _resolve(p);
|
|
3038
3554
|
const k = _strip(absPath);
|
|
3555
|
+
_ensureRemovable(absPath, "rmdir", p);
|
|
3039
3556
|
if (__vfsDirs) delete __vfsDirs[k];
|
|
3040
3557
|
_forgetSyncPath(k);
|
|
3041
3558
|
return _queueStructuralMutation(
|
|
@@ -3050,21 +3567,29 @@ const __fsMod = (() => {
|
|
|
3050
3567
|
function _renameQueued(oldP, newP) {
|
|
3051
3568
|
const oldAbs = _resolve(oldP);
|
|
3052
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);
|
|
3053
3575
|
const oldK = _strip(oldAbs);
|
|
3054
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));
|
|
3055
3586
|
const content = __vfsBundle?.[oldK] ?? __vfsWrites?.[oldK];
|
|
3056
3587
|
if (content !== undefined) {
|
|
3057
3588
|
_parkWrite(newK, content);
|
|
3058
3589
|
if (__vfsBundle) delete __vfsBundle[oldK];
|
|
3059
3590
|
delete __vfsBundleRevisions[oldK];
|
|
3060
3591
|
delete __vfsWrites[oldK];
|
|
3061
|
-
// The stat record travels with the content: dropping it would lose the
|
|
3062
|
-
// mode/ownership, and leaving it behind would keep the source path
|
|
3063
|
-
// reporting as an existing file this rename already moved away.
|
|
3064
|
-
const metadata = _metadataTable();
|
|
3065
|
-
const moved = metadata ? metadata[oldK] : undefined;
|
|
3066
3592
|
_forgetSyncPath(oldK);
|
|
3067
|
-
if (moved) metadata[newK] = moved;
|
|
3068
3593
|
} else if (__vfsDirs && oldK in __vfsDirs) {
|
|
3069
3594
|
// A directory this process made travels under its new name, so the
|
|
3070
3595
|
// sync view stops listing the old one and _announceLocalDirs cannot
|
|
@@ -3079,6 +3604,16 @@ const __fsMod = (() => {
|
|
|
3079
3604
|
}
|
|
3080
3605
|
_forgetSyncTree(oldK);
|
|
3081
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
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
if (nsFrom !== null && !_nsOwn.has(newK)) _nsOwnSet(newK, "alias", { from: nsFrom });
|
|
3616
|
+
_nsOwnSet(oldK, "absentTree");
|
|
3082
3617
|
const queued = _queueStructuralMutation(
|
|
3083
3618
|
oldAbs, "rename", oldP,
|
|
3084
3619
|
(supervisor) => supervisor.rename(oldAbs, newAbs),
|
|
@@ -3245,7 +3780,16 @@ const __fsMod = (() => {
|
|
|
3245
3780
|
// Sync realpath stays local and identity-resolves. Async symlink
|
|
3246
3781
|
// operations use the live supervisor bridge below.
|
|
3247
3782
|
// .native static is required by TypeScript's getNodeSystem at
|
|
3248
|
-
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
|
+
}
|
|
3249
3793
|
realpathSync.native = realpathSync;
|
|
3250
3794
|
|
|
3251
3795
|
// ── Async variants (thin wrappers returning via callback) ──
|
|
@@ -3273,6 +3817,11 @@ const __fsMod = (() => {
|
|
|
3273
3817
|
_mkdirAsync(p, opts).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); });
|
|
3274
3818
|
}
|
|
3275
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
|
+
}
|
|
3276
3825
|
function rename(oldP, newP, cb) { _renameAsync(oldP, newP).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3277
3826
|
function utimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3278
3827
|
function lutimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime, { followSymlinks: false }).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
@@ -3329,11 +3878,8 @@ const __fsMod = (() => {
|
|
|
3329
3878
|
// Non-resident and non-empty: writing onto a zero-filled base would
|
|
3330
3879
|
// silently destroy the bytes we cannot see. Refuse instead.
|
|
3331
3880
|
if (st !== undefined && st.size !== 0) throw _notResidentError(absPath, p, syscall, asyncForm);
|
|
3332
|
-
// Absent or empty
|
|
3333
|
-
//
|
|
3334
|
-
// this process cannot see, and an O_APPEND descriptor onto a zero-filled
|
|
3335
|
-
// base would overwrite the file with the fragment it meant to add.
|
|
3336
|
-
if (st === undefined && !_absenceIsKnown(absPath)) throw _refuseUnmapped(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).
|
|
3337
3883
|
return new Uint8Array(0);
|
|
3338
3884
|
}
|
|
3339
3885
|
|
|
@@ -3596,6 +4142,10 @@ const __fsMod = (() => {
|
|
|
3596
4142
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3597
4143
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3598
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));
|
|
3599
4149
|
if (!exists) {
|
|
3600
4150
|
await _writeFileAsync(path, new Uint8Array(0));
|
|
3601
4151
|
if (mode !== undefined && mode !== null) {
|
|
@@ -3620,6 +4170,7 @@ const __fsMod = (() => {
|
|
|
3620
4170
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3621
4171
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3622
4172
|
if (fl.write || !exists) _ensureWritable(absPath, "open", path);
|
|
4173
|
+
if (!exists) _noteCreation(_strip(absPath));
|
|
3623
4174
|
let size = exists ? st.size : 0;
|
|
3624
4175
|
// O_TRUNC means "make it empty", so zeroing is the requested semantic,
|
|
3625
4176
|
// and creating a genuinely absent file is too. O_APPEND must NEVER
|
|
@@ -3748,7 +4299,10 @@ const __fsMod = (() => {
|
|
|
3748
4299
|
function fchmodSync(fd, mode) {
|
|
3749
4300
|
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchmod", fd);
|
|
3750
4301
|
const handle = _fdHandle(fd, "fchmod");
|
|
3751
|
-
|
|
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);
|
|
3752
4306
|
}
|
|
3753
4307
|
function fchownSync(fd, uid, gid) {
|
|
3754
4308
|
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchown", fd);
|
|
@@ -4088,15 +4642,16 @@ const __fsMod = (() => {
|
|
|
4088
4642
|
// not just the local cache — is required so a subsequent async fs op
|
|
4089
4643
|
// (e.g. fs.promises.rename of a copied file, as create-cloudflare
|
|
4090
4644
|
// does for __dot__gitignore) sees the copy in the VFS instead of
|
|
4091
|
-
// ENOENT.
|
|
4092
|
-
|
|
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 });
|
|
4093
4648
|
const walk = async (relDir) => {
|
|
4094
4649
|
const absDir = relDir ? srcAbs + "/" + relDir : srcAbs;
|
|
4095
4650
|
const ents = await _readdirAsync(absDir, { withFileTypes: true });
|
|
4096
4651
|
for (const ent of ents) {
|
|
4097
4652
|
const rel = relDir ? relDir + "/" + ent.name : ent.name;
|
|
4098
4653
|
if (ent.isDirectory && ent.isDirectory()) {
|
|
4099
|
-
|
|
4654
|
+
await _mkdirAsync("/" + destK + "/" + rel, { recursive: true });
|
|
4100
4655
|
await walk(rel);
|
|
4101
4656
|
} else {
|
|
4102
4657
|
await _writeFileAsync("/" + destK + "/" + rel, await _readFileAsync(srcAbs + "/" + rel));
|
|
@@ -4269,7 +4824,7 @@ const __fsMod = (() => {
|
|
|
4269
4824
|
openSync, closeSync, readSync, writeSync, fstatSync, ftruncateSync, fsyncSync, fdatasyncSync,
|
|
4270
4825
|
futimesSync, fchmodSync, fchownSync, readvSync, writevSync,
|
|
4271
4826
|
open, close, read, write, fstat, ftruncate, fsync, fdatasync, fchmod, futimes, readv, writev,
|
|
4272
|
-
readFile, writeFile, appendFile, stat, lstat, readdir, exists, mkdir, unlink, rename, utimes, lutimes, chmod, lchmod, chown, lchown, fchown, access,
|
|
4827
|
+
readFile, writeFile, appendFile, stat, lstat, readdir, exists, mkdir, unlink, rmdir, rename, utimes, lutimes, chmod, lchmod, chown, lchown, fchown, access,
|
|
4273
4828
|
rm, cp, truncate, copyFile, mkdtemp, link, symlink, readlink, realpath, opendir, statfs,
|
|
4274
4829
|
Dirent: __Dirent,
|
|
4275
4830
|
Dir: __Dir,
|
|
@@ -4446,8 +5001,7 @@ const __NimbusRelayedWebSocket = (() => {
|
|
|
4446
5001
|
// The barrier the whole relay exists for. Every frame is now a
|
|
4447
5002
|
// supervisor reply, so it can carry the invalidation delta, and
|
|
4448
5003
|
// user code runs only after the resident set has caught up.
|
|
4449
|
-
|
|
4450
|
-
if (typeof acquire === "function") await acquire();
|
|
5004
|
+
await __nimbusInboundBarrier();
|
|
4451
5005
|
this._deliver(event);
|
|
4452
5006
|
}
|
|
4453
5007
|
}
|
|
@@ -4850,9 +5404,14 @@ ${UNDICI_SHIM_CODE}
|
|
|
4850
5404
|
// ── util module ────────────────────────────────────────────────────
|
|
4851
5405
|
// ═══════════════════════════════════════════════════════════════════════
|
|
4852
5406
|
const __utilMod = {
|
|
4853
|
-
inspect: (o, opts) => {
|
|
4854
|
-
|
|
4855
|
-
|
|
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(" ");
|
|
4856
5415
|
let i = 0;
|
|
4857
5416
|
return fmt.replace(/%[sdifjoO%]/g, (m) => {
|
|
4858
5417
|
if (m === "%%") return "%";
|
|
@@ -4861,7 +5420,7 @@ const __utilMod = {
|
|
|
4861
5420
|
if (m === "%s") return String(v);
|
|
4862
5421
|
if (m === "%d" || m === "%i" || m === "%f") return Number(v).toString();
|
|
4863
5422
|
if (m === "%j") { try { return JSON.stringify(v); } catch { return "[Circular]"; } }
|
|
4864
|
-
if (m === "%o" || m === "%O")
|
|
5423
|
+
if (m === "%o" || m === "%O") return __utilMod.inspect(v);
|
|
4865
5424
|
return String(v);
|
|
4866
5425
|
}) + (i < a.length ? " " + a.slice(i).map(__fmt).join(" ") : "");
|
|
4867
5426
|
},
|
|
@@ -5128,11 +5687,31 @@ const __utilMod = {
|
|
|
5128
5687
|
// (set+restored by __loadModule per call). When set, the fallback becomes
|
|
5129
5688
|
// "file:///" + __currentModulePath so relative URLs resolve against
|
|
5130
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.
|
|
5131
5710
|
(() => {
|
|
5132
5711
|
const _Orig = globalThis.URL;
|
|
5133
5712
|
class _Shim extends _Orig {
|
|
5134
5713
|
constructor(input, base) {
|
|
5135
|
-
if (base == null && typeof input === "string") {
|
|
5714
|
+
if (arguments.length >= 2 && base == null && typeof input === "string") {
|
|
5136
5715
|
try { super(input); return; }
|
|
5137
5716
|
catch {
|
|
5138
5717
|
// X.5-M3: prefer current module path when known, so
|
|
@@ -5714,6 +6293,13 @@ const __childProcessMod = (() => {
|
|
|
5714
6293
|
/**
|
|
5715
6294
|
* Read-loop for a single fd. Long-polls cpReadOutput, pushes chunks
|
|
5716
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.
|
|
5717
6303
|
*/
|
|
5718
6304
|
async function _runReadLoop(child, fd, stream, sinceSeqRef) {
|
|
5719
6305
|
// Exponential backoff for idle children: start at 100ms, double up
|
|
@@ -5726,12 +6312,14 @@ const __childProcessMod = (() => {
|
|
|
5726
6312
|
while (HAS_SUPERVISOR && child.pid && !child._streamsClosed) {
|
|
5727
6313
|
try {
|
|
5728
6314
|
const r = await __nimbusUseRpcResult(
|
|
5729
|
-
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff),
|
|
6315
|
+
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff, __nimbusVfsAcquireArgs()),
|
|
5730
6316
|
(result) => result,
|
|
5731
6317
|
);
|
|
5732
|
-
|
|
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) {
|
|
5733
6321
|
backoff = 100; // reset — child is producing
|
|
5734
|
-
for (const c of
|
|
6322
|
+
for (const c of chunks) {
|
|
5735
6323
|
// The queue hands back bytes; a Readable given a string would
|
|
5736
6324
|
// encode it again.
|
|
5737
6325
|
stream.write(__BufferMod.from(c.data));
|
|
@@ -5759,16 +6347,19 @@ const __childProcessMod = (() => {
|
|
|
5759
6347
|
|
|
5760
6348
|
/**
|
|
5761
6349
|
* Wait-loop: long-poll cpWait until the child reports exit. Emits
|
|
5762
|
-
* '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.
|
|
5763
6353
|
*/
|
|
5764
6354
|
async function _runWaitLoop(child) {
|
|
5765
6355
|
while (HAS_SUPERVISOR && child.pid && !child._exitFired) {
|
|
5766
6356
|
try {
|
|
5767
6357
|
const r = await __nimbusUseRpcResult(
|
|
5768
|
-
__supervisor.cpWait(child.pid, 1000),
|
|
6358
|
+
__supervisor.cpWait(child.pid, 1000, __nimbusVfsAcquireArgs()),
|
|
5769
6359
|
(result) => result,
|
|
5770
6360
|
);
|
|
5771
6361
|
if (r && r.done) {
|
|
6362
|
+
await __nimbusInboundBarrier(r.acquired);
|
|
5772
6363
|
child.exitCode = r.exitCode;
|
|
5773
6364
|
child.signalCode = r.signal;
|
|
5774
6365
|
child._exitFired = true;
|
|
@@ -5949,7 +6540,7 @@ const __childProcessMod = (() => {
|
|
|
5949
6540
|
* stdout/stderr/exit events fire. Callers like cross-spawn.sync
|
|
5950
6541
|
* that read result.status get null until the spawn settles.
|
|
5951
6542
|
* 3. When the parent facet's main drain settles __pendingIO before
|
|
5952
|
-
* reportExit (
|
|
6543
|
+
* reportExit (facets/manager.ts), the result object's fields are
|
|
5953
6544
|
* filled in by the time the supervisor sees the parent exit.
|
|
5954
6545
|
*
|
|
5955
6546
|
* Cross-spawn.sync's typical pattern is "const r = spawnSync(...);
|
|
@@ -6292,7 +6883,7 @@ function __makeProcessStdin() {
|
|
|
6292
6883
|
// facet, so counting it as in-flight work would mean the entry drain
|
|
6293
6884
|
// never sees the program finish.
|
|
6294
6885
|
packet = await __nimbusUseRpcResultUnref(
|
|
6295
|
-
__supervisor.cpReadStdin(liveChildPid, 1000),
|
|
6886
|
+
__supervisor.cpReadStdin(liveChildPid, 1000, __nimbusVfsAcquireArgs()),
|
|
6296
6887
|
(result) => result,
|
|
6297
6888
|
);
|
|
6298
6889
|
readFailures = 0;
|
|
@@ -6309,6 +6900,18 @@ function __makeProcessStdin() {
|
|
|
6309
6900
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
6310
6901
|
continue;
|
|
6311
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
|
+
}
|
|
6312
6915
|
if (packet && packet.resize) {
|
|
6313
6916
|
__nimbusTtyColumns = Number(packet.resize.columns) || __nimbusTtyColumns;
|
|
6314
6917
|
__nimbusTtyRows = Number(packet.resize.rows) || __nimbusTtyRows;
|
|
@@ -6425,6 +7028,7 @@ function __makeProcessOutputStream(streamName) {
|
|
|
6425
7028
|
errored: null,
|
|
6426
7029
|
write(d, enc, cb) {
|
|
6427
7030
|
if (typeof enc === "function") cb = enc;
|
|
7031
|
+
if (__nimbusProgramStopped) return true;
|
|
6428
7032
|
// The reported result is text; decode the bytes at this edge only.
|
|
6429
7033
|
const s = __nimbusOutText(streamName, __nimbusOutBytes(d, enc));
|
|
6430
7034
|
if (streamName === "stderr") stderr += s;
|
|
@@ -6470,6 +7074,8 @@ function __makeProcessOutputStream(streamName) {
|
|
|
6470
7074
|
function __nimbusReportProcessExit(code, reason) {
|
|
6471
7075
|
if (__nimbusProcessExitReported) return;
|
|
6472
7076
|
__nimbusProcessExitCode = Number(code ?? 0);
|
|
7077
|
+
__nimbusProgramStopped = true;
|
|
7078
|
+
try { if (typeof globalThis.__nimbusStopProgramTimers === "function") globalThis.__nimbusStopProgramTimers(); } catch {}
|
|
6473
7079
|
try { if (__nimbusProcessExitResolve) __nimbusProcessExitResolve(__nimbusProcessExitCode); } catch {}
|
|
6474
7080
|
// Generated lifecycle owners defer the terminal supervisor report until
|
|
6475
7081
|
// their durability boundary has drained. Reporting here would retire the
|
|
@@ -6509,7 +7115,7 @@ const __processMod = {
|
|
|
6509
7115
|
chdir: (d) => { cwd = __pathMod.resolve(cwd || "/home/user", d); },
|
|
6510
7116
|
exit: (code) => {
|
|
6511
7117
|
exitCode = code ?? 0;
|
|
6512
|
-
|
|
7118
|
+
__nimbusEmitExit(exitCode);
|
|
6513
7119
|
__nimbusReportProcessExit(exitCode, "");
|
|
6514
7120
|
throw new __ProcessExit(exitCode);
|
|
6515
7121
|
},
|
|
@@ -6923,14 +7529,27 @@ builtins.http = (() => {
|
|
|
6923
7529
|
return new __hostResponse("Nimbus: no HTTP server is listening in this process", { status: 502 });
|
|
6924
7530
|
}
|
|
6925
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 {}
|
|
6926
7536
|
const headers = {};
|
|
6927
|
-
request.headers.forEach((v, k) => { headers[k] = v; });
|
|
7537
|
+
request.headers.forEach((v, k) => { if (k !== "x-nimbus-vfs-acquired") headers[k] = v; });
|
|
6928
7538
|
// Bytes, not text: a UTF-8 decode corrupts every binary upload, and the
|
|
6929
7539
|
// decoded string is not the TypedArray receiver Buffer methods require.
|
|
6930
7540
|
let body = null;
|
|
6931
7541
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
6932
7542
|
body = new Uint8Array(await request.arrayBuffer());
|
|
6933
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);
|
|
6934
7553
|
const res = server._handleRequest(url.pathname + url.search, request.method, headers, body);
|
|
6935
7554
|
// Return once headers are known. A handler that never sends headers is
|
|
6936
7555
|
// bounded by a header timeout (NOT a body-finish cap) so a hung handler
|
|
@@ -7765,38 +8384,12 @@ builtins["node:util/types"] = builtins["util/types"];
|
|
|
7765
8384
|
// skips it via the same FACET_PROVIDED_PACKAGES list.
|
|
7766
8385
|
builtins.undici = __undiciMod;
|
|
7767
8386
|
|
|
7768
|
-
/**
|
|
7769
|
-
* A cell's dynamic import() call.
|
|
7770
|
-
*
|
|
7771
|
-
* A cell is compiled with new Function, so an import() left in its body
|
|
7772
|
-
* is the RUNTIME's: it resolves against the module map and hands back the
|
|
7773
|
-
* platform's own builtins, not this process's. Real Vite reaches
|
|
7774
|
-
* node:http that way (config.js:14968), got a server whose listen bound no
|
|
7775
|
-
* port here, and the facet exited with no handles the moment boot returned.
|
|
7776
|
-
*
|
|
7777
|
-
* The ESM→CJS rewriter routes those calls here with the cell's own
|
|
7778
|
-
* require, so resolution is the one the process already has. The result is
|
|
7779
|
-
* shaped as a module namespace, since import() resolves to one and callers
|
|
7780
|
-
* destructure named exports off it.
|
|
7781
|
-
*/
|
|
7782
|
-
globalThis.__nimbusCellImport = (req, id) => Promise.resolve().then(() => {
|
|
7783
|
-
const loaded = req(id);
|
|
7784
|
-
if (loaded && (loaded.__esModule || loaded[Symbol.toStringTag] === "Module")) return loaded;
|
|
7785
|
-
// A CJS module's exports ARE the namespace's named exports, with the whole
|
|
7786
|
-
// object as the default export — what an ESM importer of a CJS module sees.
|
|
7787
|
-
if (loaded && (typeof loaded === "object" || typeof loaded === "function")) {
|
|
7788
|
-
const ns = Object.create(null);
|
|
7789
|
-
for (const key of Object.keys(loaded)) ns[key] = loaded[key];
|
|
7790
|
-
if (!("default" in ns)) ns.default = loaded;
|
|
7791
|
-
Object.defineProperty(ns, Symbol.toStringTag, { value: "Module" });
|
|
7792
|
-
return ns;
|
|
7793
|
-
}
|
|
7794
|
-
return { default: loaded };
|
|
7795
|
-
});
|
|
7796
8387
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7797
8388
|
// ── require() — full Node.js module resolution ─────────────────────
|
|
7798
8389
|
// ═══════════════════════════════════════════════════════════════════════
|
|
7799
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});
|
|
7800
8393
|
|
|
7801
8394
|
/**
|
|
7802
8395
|
* Direct VFS bundle access for module resolution.
|
|
@@ -7822,24 +8415,8 @@ function __readFileOr(path, fallback) {
|
|
|
7822
8415
|
}
|
|
7823
8416
|
function __fileExists(path) {
|
|
7824
8417
|
const k = path.replace(/^\\/+/, "");
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
if (__vfsDirs && k in __vfsDirs) return true;
|
|
7828
|
-
// Consult the uncapped manifest (directory shape) so resolution sees
|
|
7829
|
-
// installed files whose content was excluded from the bounded snapshot
|
|
7830
|
-
// (e.g. web-streams-polyfill's ponyfill/package.json, reached via a
|
|
7831
|
-
// parent-relative main). Mirrors existsSync's manifest probe.
|
|
7832
|
-
if (__vfsManifest) {
|
|
7833
|
-
if (k in __vfsManifest) return true;
|
|
7834
|
-
const slash = k.lastIndexOf("/");
|
|
7835
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
7836
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
7837
|
-
const sib = __vfsManifest[parent];
|
|
7838
|
-
if (sib && sib.indexOf(name) !== -1) return true;
|
|
7839
|
-
}
|
|
7840
|
-
// Check for directory by looking for any key with this prefix
|
|
7841
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) return true;
|
|
7842
|
-
return false;
|
|
8418
|
+
// The namespace answers exactly (vfs/facet-resident-store.ts).
|
|
8419
|
+
return __fsMod.existsSync("/" + k);
|
|
7843
8420
|
}
|
|
7844
8421
|
// W3.5 Fix A: strict-file membership probe. __fileExists also returns true for
|
|
7845
8422
|
// directories (it has to — __resolveNodeModule and __resolveImportsField call
|
|
@@ -7851,26 +8428,8 @@ function __fileExists(path) {
|
|
|
7851
8428
|
// the fastify ret/dist/types failure.
|
|
7852
8429
|
function __pathIsFile(path) {
|
|
7853
8430
|
const k = path.replace(/^\\/+/, "");
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
// Consult the manifest as a strict-file probe: a name listed in its
|
|
7857
|
-
// parent's manifest entry is a file (directories are manifest KEYS).
|
|
7858
|
-
// This lets resolution find files whose content was excluded from the
|
|
7859
|
-
// bounded snapshot without re-introducing the directory short-circuit
|
|
7860
|
-
// (__vfsManifest[k] being a directory key is deliberately NOT matched).
|
|
7861
|
-
if (__vfsManifest) {
|
|
7862
|
-
const slash = k.lastIndexOf("/");
|
|
7863
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
7864
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
7865
|
-
const sib = __vfsManifest[parent];
|
|
7866
|
-
if (sib && sib.indexOf(name) !== -1 && !(k in __vfsManifest)) return true;
|
|
7867
|
-
}
|
|
7868
|
-
// Last resort: the full fs view (live SQLite VFS) for paths outside the
|
|
7869
|
-
// bounded snapshot/manifest entirely — e.g. the /tmp/.npx-cache tree,
|
|
7870
|
-
// which the project snapshot never covers. stat-as-file only.
|
|
7871
|
-
try { const st = __fsMod.statSync("/" + k); if (st && st.isFile && st.isFile()) return true; } catch {}
|
|
7872
|
-
// Deliberately does NOT consult __vfsDirs nor do the prefix scan.
|
|
7873
|
-
return false;
|
|
8431
|
+
const st = __fsMod.statSync("/" + k, { throwIfNoEntry: false });
|
|
8432
|
+
return !!st && st.isFile();
|
|
7874
8433
|
}
|
|
7875
8434
|
function __resolveFile(base) {
|
|
7876
8435
|
// Mirrors Node's LOAD_AS_FILE + LOAD_AS_DIRECTORY (require_2 spec):
|
|
@@ -7943,15 +8502,12 @@ function __resolveFile(base) {
|
|
|
7943
8502
|
}
|
|
7944
8503
|
|
|
7945
8504
|
// __compiledModules is defined at MODULE TOP LEVEL in the generator code
|
|
7946
|
-
// (
|
|
8505
|
+
// (facets/manager.ts) so new Function() runs during module evaluation.
|
|
7947
8506
|
|
|
7948
8507
|
// ── Single-source-of-truth exports/imports resolver (W2) ───────────────
|
|
7949
8508
|
// Emitted from src/_shared/exports-resolver.ts via getExportsResolverJS().
|
|
7950
8509
|
// Declares: resolveExports, resolveConditionValue, resolvePackageEntry,
|
|
7951
8510
|
// DEFAULT_ESM_CONDITIONS, DEFAULT_CJS_CONDITIONS.
|
|
7952
|
-
// the prior hand-rolled __resolvePkgEntry only honoured top-level
|
|
7953
|
-
// require|default|import and dropped subpath maps, wildcards, nested
|
|
7954
|
-
// conditions, the imports field, and null-target enforcement.
|
|
7955
8511
|
${EXPORTS_RESOLVER_JS}
|
|
7956
8512
|
|
|
7957
8513
|
// ── TypeScript specifier fallbacks ────────────────────────────────────────
|
|
@@ -7989,8 +8545,8 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
7989
8545
|
}
|
|
7990
8546
|
let entry = resolvePackageEntry(pkg, subpath, __NIMBUS_CJS_CONDITIONS);
|
|
7991
8547
|
// X.5-F R3: ESM-condition fallback for pure-ESM packages whose
|
|
7992
|
-
// dist/.mjs files were transformed to CJS
|
|
7993
|
-
//
|
|
8548
|
+
// dist/.mjs files were transformed to CJS
|
|
8549
|
+
// by transformEsmInBundle (facets/manager.ts). Without this,
|
|
7994
8550
|
// packages like nuxt — whose exports map only contains
|
|
7995
8551
|
// {types, import} for the root subpath — return null from the CJS
|
|
7996
8552
|
// walk and dead-end with "Cannot find module 'nuxt'" even though
|
|
@@ -8033,10 +8589,6 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
8033
8589
|
return __resolveFile(pkgDir + "/" + rel);
|
|
8034
8590
|
}
|
|
8035
8591
|
|
|
8036
|
-
/** Back-compat name used elsewhere in this file. */
|
|
8037
|
-
function __resolvePkgEntry(pkgDir) {
|
|
8038
|
-
return __resolvePkgSubpath(pkgDir, null, ".");
|
|
8039
|
-
}
|
|
8040
8592
|
|
|
8041
8593
|
/**
|
|
8042
8594
|
* Resolve a bare specifier (e.g. "react", "@scope/pkg", "pkg/sub/path")
|
|
@@ -8085,32 +8637,23 @@ function __resolveNodeModule(name, fromDir) {
|
|
|
8085
8637
|
}
|
|
8086
8638
|
|
|
8087
8639
|
/**
|
|
8088
|
-
*
|
|
8089
|
-
* 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.
|
|
8090
8649
|
*/
|
|
8091
|
-
function
|
|
8092
|
-
// Walk up looking for the nearest package.json. Stop at the first one
|
|
8093
|
-
// (Node spec: imports field of the importing module's package).
|
|
8650
|
+
function __nearestPackageScope(fromDir) {
|
|
8094
8651
|
let dir = (fromDir || "").replace(/^\\/+/, "");
|
|
8095
8652
|
while (true) {
|
|
8653
|
+
if (dir === "node_modules" || dir.endsWith("/node_modules")) return null;
|
|
8096
8654
|
const pkgJsonPath = (dir ? dir + "/" : "") + "package.json";
|
|
8097
8655
|
if (__fileExists(pkgJsonPath)) {
|
|
8098
|
-
|
|
8099
|
-
if (pkg && pkg.imports) {
|
|
8100
|
-
const target = resolveExports(pkg.imports, name, __NIMBUS_CJS_CONDITIONS);
|
|
8101
|
-
if (target) {
|
|
8102
|
-
// Imports targets are relative to the package root (dir)
|
|
8103
|
-
if (target.startsWith("./")) {
|
|
8104
|
-
return __resolveFile((dir ? dir + "/" : "") + target.slice(2));
|
|
8105
|
-
}
|
|
8106
|
-
if (target.startsWith("/")) {
|
|
8107
|
-
return __resolveFile(target.slice(1));
|
|
8108
|
-
}
|
|
8109
|
-
// Bare specifier — re-resolve as a node_module from this dir
|
|
8110
|
-
return __resolveNodeModule(target, dir);
|
|
8111
|
-
}
|
|
8112
|
-
}
|
|
8113
|
-
return null; // first package.json wins, even if no imports field
|
|
8656
|
+
return { dir, pkg: __readPkgJson(dir) };
|
|
8114
8657
|
}
|
|
8115
8658
|
if (!dir) return null;
|
|
8116
8659
|
const lastSlash = dir.lastIndexOf("/");
|
|
@@ -8118,37 +8661,68 @@ function __resolveImportsField(name, fromDir) {
|
|
|
8118
8661
|
}
|
|
8119
8662
|
}
|
|
8120
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
|
+
|
|
8121
8718
|
// ═══════════════════════════════════════════════════════════════════════
|
|
8122
|
-
// ──
|
|
8719
|
+
// ── __mkCompiledFn — the request-time fallback compile ──────────────
|
|
8123
8720
|
// ═══════════════════════════════════════════════════════════════════════
|
|
8124
|
-
//
|
|
8125
|
-
//
|
|
8126
|
-
//
|
|
8127
|
-
//
|
|
8128
|
-
|
|
8129
|
-
// the body in \`new Function("exports","require","module","__filename","__dirname", code)\`
|
|
8130
|
-
// then collides at parse time:
|
|
8131
|
-
//
|
|
8132
|
-
// SyntaxError: Identifier '__dirname' has already been declared
|
|
8133
|
-
//
|
|
8134
|
-
// (VERIFY-23417C5 §4 #1 / X5M3-retro §"Next bucket".) The helper RENAMES
|
|
8135
|
-
// the conflicting param to a placeholder name so the body's own
|
|
8136
|
-
// \`const __dirname\` becomes the single declarer. We rename rather than
|
|
8137
|
-
// drop because callers pass 5 positional arguments and dropping a slot
|
|
8138
|
-
// would mis-align downstream slots (e.g. the USER_CODE wrap appends
|
|
8139
|
-
// \`console\` / \`process\` / etc. after \`__dirname\`). Renaming preserves
|
|
8140
|
-
// slot alignment while letting the body's binding win.
|
|
8141
|
-
//
|
|
8142
|
-
// Symmetric for \`__filename\` because open@10's idiom often emits both.
|
|
8143
|
-
function __mkCompiledFn(code) {
|
|
8144
|
-
const reFn = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+__filename\\s*=/m;
|
|
8145
|
-
const reDn = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+__dirname\\s*=/m;
|
|
8146
|
-
const reRequire = /(?:^|\\n|;)\\s*(?:const|let|var)\\s+require\\s*=/m;
|
|
8147
|
-
const fnName = reFn.test(code) ? "__filename__nimbus_unused" : "__filename";
|
|
8148
|
-
const dnName = reDn.test(code) ? "__dirname__nimbus_unused" : "__dirname";
|
|
8149
|
-
const requireName = reRequire.test(code) ? "require__nimbus_unused" : "require";
|
|
8150
|
-
return new Function("exports", requireName, "module", fnName, dnName, code);
|
|
8151
|
-
}
|
|
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}
|
|
8152
8726
|
|
|
8153
8727
|
function __exportsTarget(mod) {
|
|
8154
8728
|
const value = mod.exports;
|
|
@@ -8200,11 +8774,12 @@ function __makeLoadingExports(mod) {
|
|
|
8200
8774
|
* Load and execute a JS/JSON module from VFS.
|
|
8201
8775
|
* Returns the module.exports value.
|
|
8202
8776
|
*/
|
|
8203
|
-
function __loadModule(resolvedPath) {
|
|
8204
|
-
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);
|
|
8205
8780
|
|
|
8206
8781
|
const mod = { exports: {} };
|
|
8207
|
-
__moduleCache.set(
|
|
8782
|
+
__moduleCache.set(evaluationKey, __makeLoadingExports(mod));
|
|
8208
8783
|
|
|
8209
8784
|
const code = __readFileOr(resolvedPath, null);
|
|
8210
8785
|
if (code === null) throw new Error("Cannot read module: " + resolvedPath);
|
|
@@ -8212,7 +8787,7 @@ function __loadModule(resolvedPath) {
|
|
|
8212
8787
|
// JSON
|
|
8213
8788
|
if (resolvedPath.endsWith(".json")) {
|
|
8214
8789
|
mod.exports = JSON.parse(code);
|
|
8215
|
-
__moduleCache.set(
|
|
8790
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
8216
8791
|
return mod.exports;
|
|
8217
8792
|
}
|
|
8218
8793
|
|
|
@@ -8235,6 +8810,16 @@ function __loadModule(resolvedPath) {
|
|
|
8235
8810
|
// const import_meta = {}. Save+restore for recursive __loadModule.
|
|
8236
8811
|
const __prevModulePath = globalThis.__currentModulePath;
|
|
8237
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 });
|
|
8238
8823
|
try {
|
|
8239
8824
|
// Use pre-compiled function from startup (new Function allowed at module eval time)
|
|
8240
8825
|
// Normalize path to match VFS bundle key format (no leading /)
|
|
@@ -8259,7 +8844,7 @@ function __loadModule(resolvedPath) {
|
|
|
8259
8844
|
} catch (evalErr) {
|
|
8260
8845
|
// W3.5 Fix C: if the file was in the bundle but its pre-compile
|
|
8261
8846
|
// failed at facet startup, surface the original SyntaxError
|
|
8262
|
-
// instead of the misleading "
|
|
8847
|
+
// instead of the misleading "not in this launch's module map" text.
|
|
8263
8848
|
const normalizedPath2 = resolvedPath.replace(/^\\/+/, "");
|
|
8264
8849
|
const compileErr =
|
|
8265
8850
|
(typeof __compileFailures !== "undefined" && __compileFailures &&
|
|
@@ -8271,22 +8856,27 @@ function __loadModule(resolvedPath) {
|
|
|
8271
8856
|
);
|
|
8272
8857
|
}
|
|
8273
8858
|
if (evalErr.message && evalErr.message.includes("Code generation from strings disallowed")) {
|
|
8274
|
-
|
|
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.");
|
|
8275
8863
|
}
|
|
8276
8864
|
throw evalErr;
|
|
8277
8865
|
}
|
|
8278
8866
|
}
|
|
8279
8867
|
} catch (e) {
|
|
8280
|
-
__moduleCache.delete(
|
|
8868
|
+
__moduleCache.delete(evaluationKey);
|
|
8281
8869
|
if (e && typeof e === "object" && !e.__nimbusModulePath) {
|
|
8282
8870
|
try {
|
|
8283
|
-
e
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
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;
|
|
8290
8880
|
} catch {}
|
|
8291
8881
|
}
|
|
8292
8882
|
throw e;
|
|
@@ -8295,7 +8885,7 @@ function __loadModule(resolvedPath) {
|
|
|
8295
8885
|
}
|
|
8296
8886
|
|
|
8297
8887
|
// Update cache with final exports (module.exports may have been reassigned)
|
|
8298
|
-
__moduleCache.set(
|
|
8888
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
8299
8889
|
return mod.exports;
|
|
8300
8890
|
}
|
|
8301
8891
|
|
|
@@ -8304,6 +8894,25 @@ function __loadModule(resolvedPath) {
|
|
|
8304
8894
|
* Returns the resolved VFS path, or null.
|
|
8305
8895
|
*/
|
|
8306
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
|
+
}
|
|
8307
8916
|
// X.5-P: literal "." / ".." are CommonJS aliases for "./" / "../".
|
|
8308
8917
|
// Pre-fix they slipped past the startsWith("./")/("../") guards (which
|
|
8309
8918
|
// require >= 3 / >= 4 chars respectively) and fell into the bare-spec
|
|
@@ -8333,43 +8942,137 @@ function __resolveFrom(id, fromDir) {
|
|
|
8333
8942
|
if (id.startsWith("#")) {
|
|
8334
8943
|
return __resolveImportsField(id, fromDir);
|
|
8335
8944
|
}
|
|
8336
|
-
// 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;
|
|
8337
8952
|
return __resolveNodeModule(id, fromDir);
|
|
8338
8953
|
}
|
|
8339
8954
|
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
const
|
|
8367
|
-
|
|
8368
|
-
);
|
|
8369
|
-
|
|
8370
|
-
|
|
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
|
+
}
|
|
8371
9043
|
}
|
|
8372
|
-
|
|
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);
|
|
8373
9076
|
};
|
|
8374
9077
|
|
|
8375
9078
|
/**
|