@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
package/public/_assets/runtime/{node-shims-7b4dd7a76d95d21e.js → node-shims-06565b121555c194.js}
RENAMED
|
@@ -2,10 +2,37 @@
|
|
|
2
2
|
// ═══════════════════════════════════════════════════════════════════════
|
|
3
3
|
// ── Format helper ──────────────────────────────────────────────────
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════════════
|
|
5
|
+
function __isErrorValue(v) {
|
|
6
|
+
return v instanceof Error || Object.prototype.toString.call(v) === "[object Error]";
|
|
7
|
+
}
|
|
8
|
+
// Node's util.inspect(err); JSON.stringify drops non-enumerable name/message/stack.
|
|
9
|
+
function __fmtError(e, seen) {
|
|
10
|
+
if (seen.has(e)) return "[Circular *]";
|
|
11
|
+
seen.add(e);
|
|
12
|
+
let text;
|
|
13
|
+
try { text = typeof e.stack === "string" && e.stack ? e.stack : Error.prototype.toString.call(e); }
|
|
14
|
+
catch { text = String(e); }
|
|
15
|
+
const fields = [];
|
|
16
|
+
for (const key of Object.keys(e)) {
|
|
17
|
+
if (key === "cause") continue;
|
|
18
|
+
let value;
|
|
19
|
+
try { value = e[key]; } catch { continue; }
|
|
20
|
+
fields.push(key + ": " + __fmtField(value, seen));
|
|
21
|
+
}
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(e, "cause")) fields.push("[cause]: " + __fmtField(e.cause, seen));
|
|
23
|
+
if (fields.length === 0) return text;
|
|
24
|
+
return text + " {\n" + fields.map((f) => " " + f.split("\n").join("\n ")).join(",\n") + "\n}";
|
|
25
|
+
}
|
|
26
|
+
function __fmtField(v, seen) {
|
|
27
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
28
|
+
if (v !== null && typeof v === "object" && __isErrorValue(v)) return __fmtError(v, seen);
|
|
29
|
+
return __fmt(v);
|
|
30
|
+
}
|
|
5
31
|
function __fmt(v) {
|
|
6
32
|
if (v === null) return "null";
|
|
7
33
|
if (v === undefined) return "undefined";
|
|
8
34
|
if (typeof v === "object") {
|
|
35
|
+
if (__isErrorValue(v)) return __fmtError(v, new Set());
|
|
9
36
|
try { return JSON.stringify(v); } catch { return String(v); }
|
|
10
37
|
}
|
|
11
38
|
return String(v);
|
|
@@ -58,6 +85,41 @@ async function __nimbusUseRpcResultUnref(promise, use) {
|
|
|
58
85
|
finally { __nimbusDisposeRpcResult(value); }
|
|
59
86
|
}
|
|
60
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The ACQUIRE barrier, taken before the program sees anything that arrived
|
|
90
|
+
* from outside it: a response or body from the network, a relayed socket
|
|
91
|
+
* frame, a request routed to one of its ports, a stdin packet or signal, a
|
|
92
|
+
* child's output or exit. Each of those can be the second half of a causal
|
|
93
|
+
* chain that began with a write somewhere else — `echo v2 > f; curl :3000`,
|
|
94
|
+
* a child that writes a file and then exits — and the handler's synchronous
|
|
95
|
+
* reads must see that write. Nothing but this barrier carries it.
|
|
96
|
+
*
|
|
97
|
+
* What the supervisor delivers — a request, a stdin packet, a child's output
|
|
98
|
+
* or exit — carries the answer to this barrier with it (`delivered`,
|
|
99
|
+
* session/rpc.ts _acquireOnDelivery), computed after the thing delivered was
|
|
100
|
+
* queued, so the barrier applies that answer instead of asking, and the
|
|
101
|
+
* program resumes with no round trip of its own. It asks when there is none
|
|
102
|
+
* or it cannot use the one delivered. A resumption the supervisor does not
|
|
103
|
+
* deliver — a timer, the network — always asks.
|
|
104
|
+
*
|
|
105
|
+
* The barrier is installed by the fs module, which is evaluated after this
|
|
106
|
+
* point; a facet with no supervisor has none and nothing to be coherent with.
|
|
107
|
+
*/
|
|
108
|
+
async function __nimbusInboundBarrier(delivered) {
|
|
109
|
+
const acquire = globalThis.__nimbusVfsAcquireBarrier;
|
|
110
|
+
if (typeof acquire === "function") await acquire(delivered);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The arguments this process asks its ACQUIRE with (the fs module's
|
|
115
|
+
* _acquireArgs), for a delivery to be answered from: a long poll sends them,
|
|
116
|
+
* and what it delivers comes back carrying fsAcquire's answer to them.
|
|
117
|
+
*/
|
|
118
|
+
function __nimbusVfsAcquireArgs() {
|
|
119
|
+
const args = globalThis.__nimbusVfsAcquireArgs;
|
|
120
|
+
return typeof args === "function" ? args() : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
61
123
|
// ═══════════════════════════════════════════════════════════════════════
|
|
62
124
|
// ── Precompiled wasm ───────────────────────────────────────────────
|
|
63
125
|
// A wasm image can only become a WebAssembly.Module through the Worker
|
|
@@ -287,13 +349,11 @@ function __nimbusWasmDigest(bytes) {
|
|
|
287
349
|
// supervisor round trip per outbound request, not a hidden one. It is the
|
|
288
350
|
// price of the guarantee.
|
|
289
351
|
//
|
|
290
|
-
// The
|
|
291
|
-
// is evaluated after this one;
|
|
292
|
-
// and there is nothing to be coherent with.
|
|
352
|
+
// The RELEASE barrier lives on globalThis because the fs module installs it
|
|
353
|
+
// and is evaluated after this one; the ACQUIRE is __nimbusInboundBarrier.
|
|
293
354
|
const __resumeCoherent = async (pending) => {
|
|
294
355
|
const value = await pending;
|
|
295
|
-
|
|
296
|
-
if (typeof acquire === "function") await acquire();
|
|
356
|
+
await __nimbusInboundBarrier();
|
|
297
357
|
return value;
|
|
298
358
|
};
|
|
299
359
|
const __barriered = async (input, init) => {
|
|
@@ -323,6 +383,15 @@ function __nimbusWasmDigest(bytes) {
|
|
|
323
383
|
|
|
324
384
|
let __nimbusLiveStdinPump = null;
|
|
325
385
|
let __nimbusProcessExitReported = false;
|
|
386
|
+
// Set when the program has exited: its timers are cleared and its writes refused.
|
|
387
|
+
let __nimbusProgramStopped = false;
|
|
388
|
+
let __nimbusExitEmitted = false;
|
|
389
|
+
// 'exit' listeners run once, synchronously, before the program is stopped.
|
|
390
|
+
function __nimbusEmitExit(code) {
|
|
391
|
+
if (__nimbusExitEmitted) return;
|
|
392
|
+
__nimbusExitEmitted = true;
|
|
393
|
+
try { __processEvents.emit("exit", code); } catch {}
|
|
394
|
+
}
|
|
326
395
|
let __nimbusProcessExitResolve = null;
|
|
327
396
|
let __nimbusProcessExitCode = null;
|
|
328
397
|
const __nimbusProcessExitPromise = new Promise((resolve) => {
|
|
@@ -673,29 +742,205 @@ const __fsMod = (() => {
|
|
|
673
742
|
// ── VFS bundle lookup (fast path — in-memory) ──
|
|
674
743
|
function _bundleLookup(absPath) {
|
|
675
744
|
const k = _strip(absPath);
|
|
676
|
-
if (
|
|
677
|
-
//
|
|
745
|
+
if (globalThis.__nimbusProfileStaged) globalThis.__nimbusProfileStaged.delete(k);
|
|
746
|
+
// The parked write first, as _writtenCell reads: it is this process's own,
|
|
747
|
+
// newer than anything the store holds, and it is the live cell (a store
|
|
748
|
+
// read reassembles a copy, which a write loop would pay for per write).
|
|
678
749
|
if (__vfsWrites && k in __vfsWrites) return __vfsWrites[k];
|
|
750
|
+
if (__vfsBundle && k in __vfsBundle) return __vfsBundle[k];
|
|
751
|
+
// The same bytes under the name they are held by: through a symlink, or
|
|
752
|
+
// under the old name of a rename this process has not seen land yet.
|
|
753
|
+
if (__vfsBundle && _nsActive()) {
|
|
754
|
+
const real = _nsRealKey(k);
|
|
755
|
+
if (real !== null && real !== k && real in __vfsBundle) return __vfsBundle[real];
|
|
756
|
+
}
|
|
679
757
|
return undefined;
|
|
680
758
|
}
|
|
681
759
|
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
function _metadataTable() {
|
|
685
|
-
return (typeof __vfsMetadata !== "undefined" && __vfsMetadata) ? __vfsMetadata : null;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// Same guard, same reason: the SQLite resident store is spliced into the
|
|
689
|
-
// resident-process body only. The one-shot exec path runs in a stateless
|
|
690
|
-
// loaded worker with no facet storage at all, so these shims must still work
|
|
691
|
-
// with the resident set on the heap and this is the single place that asks.
|
|
760
|
+
// Whether the store is bound: every embedding of these shims boots one (a
|
|
761
|
+
// facet's SQLite, or a one-shot's heap), and this is the single place that asks.
|
|
692
762
|
function _residentStorePresent() {
|
|
693
763
|
return typeof __residentAdmit === "function" && typeof __residentReady !== "undefined" && __residentReady;
|
|
694
764
|
}
|
|
695
765
|
|
|
766
|
+
// ── The namespace (resident facets) ──
|
|
767
|
+
//
|
|
768
|
+
// A resident facet's store holds every name the credential can see, with
|
|
769
|
+
// its stat, exact at the cursor (vfs/facet-resident-store.ts). While it
|
|
770
|
+
// does, it answers every synchronous metadata question — stat, exists,
|
|
771
|
+
// readdir, realpath, access — and an absent name is known absent, so none
|
|
772
|
+
// of them records a miss or refuses. While it does not (a relist failed,
|
|
773
|
+
// until the next barrier repairs it), each of them is refused by name
|
|
774
|
+
// (_nsRequire).
|
|
775
|
+
//
|
|
776
|
+
// What the table cannot know is this process's own structural effects that
|
|
777
|
+
// the authority has not reported back yet, so those sit in an overlay, one
|
|
778
|
+
// entry per path: "absent" / "absentTree" (unlink, rmdir, rm), "dir"
|
|
779
|
+
// (mkdir; `hide` when nothing was there, so no stale child shows through)
|
|
780
|
+
// and "alias" (a rename: the new name denotes what the old one did). An
|
|
781
|
+
// entry retires once its mutation has settled AND a barrier begun after
|
|
782
|
+
// that has applied, because such a barrier's delta is answered after the
|
|
783
|
+
// mutation committed, so the table then shows it or anything later.
|
|
784
|
+
function _nsActive() {
|
|
785
|
+
return typeof __nsReady === "function" && __nsReady();
|
|
786
|
+
}
|
|
787
|
+
// The namespace answers every synchronous metadata question, or none. While
|
|
788
|
+
// it is not ready (a relist failed, until the next barrier repairs it) a
|
|
789
|
+
// synchronous call is refused, naming why and the asynchronous form that
|
|
790
|
+
// answers now. A launch never runs user code before it is ready.
|
|
791
|
+
function _nsRequire(syscall, displayPath, asyncForm) {
|
|
792
|
+
if (_nsActive()) return;
|
|
793
|
+
_stats.namespaceRefusals++;
|
|
794
|
+
const why = typeof __nsNotReadyCause === "function" ? __nsNotReadyCause() : "the process was started without one";
|
|
795
|
+
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
796
|
+
err.message = "EAGAIN: " + syscall + " '" + String(displayPath) + "': the namespace is being rebuilt after " + why
|
|
797
|
+
+ (_supervisor() ? "; use " + asyncForm : "");
|
|
798
|
+
throw err;
|
|
799
|
+
}
|
|
800
|
+
const _nsOwn = new Map();
|
|
801
|
+
let _nsFresh = [];
|
|
802
|
+
let _barrierBegins = 0;
|
|
803
|
+
function _nsOwnSet(k, state, extra) {
|
|
804
|
+
const before = _nsOwn.get(k);
|
|
805
|
+
const entry = { state, from: extra?.from, hide: !!extra?.hide, settled: null };
|
|
806
|
+
try { __residentNamespaceOverlayDelta(_nsOwnCost(k, entry) - (before ? _nsOwnCost(k, before) : 0)); }
|
|
807
|
+
catch (error) {
|
|
808
|
+
// The view is sealed by the store. Let the already-projected structural
|
|
809
|
+
// operation reach its authority/ack; aborting midway would strand
|
|
810
|
+
// earlier overlays with no owning mutation to retire them.
|
|
811
|
+
if (__residentInHeap && error?.code === "ENOSPC") return;
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
_nsOwn.set(k, entry);
|
|
815
|
+
_nsFresh.push(entry);
|
|
816
|
+
}
|
|
817
|
+
function _nsOwnCost(k, entry) { return __namespaceRowBytes("", k, entry.from); }
|
|
818
|
+
/** Hand the overlay entries made since the last mutation to the one being queued. */
|
|
819
|
+
function _nsTakeFresh() {
|
|
820
|
+
const mine = _nsFresh;
|
|
821
|
+
_nsFresh = [];
|
|
822
|
+
return () => { for (const entry of mine) if (entry.settled === null) entry.settled = _barrierBegins; };
|
|
823
|
+
}
|
|
824
|
+
function _nsRetire(begin) {
|
|
825
|
+
for (const [k, entry] of _nsOwn) if (entry.settled !== null && entry.settled < begin) {
|
|
826
|
+
_nsOwn.delete(k);
|
|
827
|
+
__residentNamespaceOverlayDelta(-_nsOwnCost(k, entry));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* The overlay's word on `k`: "absent", { alias } (look the table up at
|
|
833
|
+
* this key instead), { dir } (own directory), { hide } (the table knows
|
|
834
|
+
* nothing under an own fresh directory), or null (ask the table).
|
|
835
|
+
*/
|
|
836
|
+
function _nsOwnView(k) {
|
|
837
|
+
if (_nsOwn.size === 0) return null;
|
|
838
|
+
const own = _nsOwn.get(k);
|
|
839
|
+
if (own) {
|
|
840
|
+
if (own.state === "absent" || own.state === "absentTree") return "absent";
|
|
841
|
+
if (own.state === "alias") return { alias: own.from };
|
|
842
|
+
return { dir: true };
|
|
843
|
+
}
|
|
844
|
+
for (let i = k.lastIndexOf("/"); i > 0; i = k.lastIndexOf("/", i - 1)) {
|
|
845
|
+
const above = _nsOwn.get(k.slice(0, i));
|
|
846
|
+
if (!above) continue;
|
|
847
|
+
if (above.state === "absent" || above.state === "absentTree") return "absent";
|
|
848
|
+
if (above.state === "alias") return { alias: above.from + k.slice(i) };
|
|
849
|
+
if (above.hide) return { hide: true };
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** The table key `k` denotes (overlay and symlinks applied), or null. */
|
|
855
|
+
function _nsRealKey(k) {
|
|
856
|
+
const own = _nsOwnView(k);
|
|
857
|
+
if (own === "absent" || (own && (own.dir || own.hide))) return null;
|
|
858
|
+
const found = __nsResolve(own && own.alias !== undefined ? own.alias : k, true);
|
|
859
|
+
return found && found !== "ELOOP" ? found.path : null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function _nsRowMeta(row) {
|
|
863
|
+
const kind = Number(row.kind);
|
|
864
|
+
return {
|
|
865
|
+
type: kind === 1 ? "directory" : kind === 2 ? "symlink" : "file",
|
|
866
|
+
size: Number(row.size), mode: Number(row.mode), uid: Number(row.uid), gid: Number(row.gid),
|
|
867
|
+
atime: Number(row.atime), mtime: Number(row.mtime), ctime: Number(row.ctime),
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Everything the namespace says about `k`: a stat record, "absent", or
|
|
873
|
+
* "ELOOP". Own pending writes and directories first, then the overlay,
|
|
874
|
+
* then the table, then content this process wrote that the table has not
|
|
875
|
+
* caught up with.
|
|
876
|
+
*/
|
|
877
|
+
function _nsMeta(k, follow) {
|
|
878
|
+
if (k === "") return _nsRowMeta(__nsResolve("", true).row);
|
|
879
|
+
if (__vfsWrites && k in __vfsWrites && _denialCode(__vfsWrites[k]) === null) {
|
|
880
|
+
const size = _byteLen(__vfsWrites[k]);
|
|
881
|
+
if (_createdHere.has(k)) {
|
|
882
|
+
return { type: "file", size, mode: 0o100666 & ~__processUmask, uid: cred.uid, gid: cred.gid, own: true };
|
|
883
|
+
}
|
|
884
|
+
// A rewrite of a file that was there: its owner and mode stay what the
|
|
885
|
+
// authority says they are.
|
|
886
|
+
const found = __nsResolve(k, follow);
|
|
887
|
+
if (found && found !== "ELOOP" && Number(found.row.kind) === 0) return { ..._nsRowMeta(found.row), size };
|
|
888
|
+
}
|
|
889
|
+
const own = _nsOwnView(k);
|
|
890
|
+
if (own === "absent") return "absent";
|
|
891
|
+
if (own && own.dir) return { type: "directory", size: 0, mode: 0o40777 & ~__processUmask, uid: cred.uid, gid: cred.gid, own: true };
|
|
892
|
+
// A directory this process made hides what the namespace held under its
|
|
893
|
+
// name before, but not what this process has put there since: those rows
|
|
894
|
+
// are its own writes, recorded when the authority accepted them.
|
|
895
|
+
if (!own || own.alias !== undefined || (own.hide && _createdHere.has(k))) {
|
|
896
|
+
const found = __nsResolve(own && own.alias !== undefined ? own.alias : k, follow);
|
|
897
|
+
if (found === "ELOOP") return "ELOOP";
|
|
898
|
+
if (found) return _nsRowMeta(found.row);
|
|
899
|
+
}
|
|
900
|
+
// The namespace is exact, so a name it lacks is not there, whatever bytes
|
|
901
|
+
// the module map carries under it (a file removed since the map was
|
|
902
|
+
// built). Content never stands in for metadata.
|
|
903
|
+
return "absent";
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/** Names directly under directory `k`: Map name → type. */
|
|
907
|
+
function _nsList(k) {
|
|
908
|
+
const names = new Map();
|
|
909
|
+
const own = _nsOwnView(k);
|
|
910
|
+
if (own !== "absent" && !(own && own.hide) && !(own && own.dir && _nsOwn.get(k)?.hide)) {
|
|
911
|
+
const real = __nsResolve(own && own.alias !== undefined ? own.alias : k, true);
|
|
912
|
+
if (real && real !== "ELOOP") {
|
|
913
|
+
for (const child of __nsChildren(real.path)) {
|
|
914
|
+
names.set(child.name, child.kind === 1 ? "directory" : child.kind === 2 ? "symlink" : "file");
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const prefix = k ? k + "/" : "";
|
|
919
|
+
if (__vfsWrites) {
|
|
920
|
+
for (const wk in __vfsWrites) {
|
|
921
|
+
if (!wk.startsWith(prefix)) continue;
|
|
922
|
+
const rest = wk.slice(prefix.length);
|
|
923
|
+
const slash = rest.indexOf("/");
|
|
924
|
+
if (rest) names.set(slash < 0 ? rest : rest.slice(0, slash), slash < 0 ? "file" : "directory");
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
for (const [ok, entry] of _nsOwn) {
|
|
928
|
+
if (!ok.startsWith(prefix) || ok.slice(prefix.length).includes("/")) continue;
|
|
929
|
+
const name = ok.slice(prefix.length);
|
|
930
|
+
if (entry.state === "absent" || entry.state === "absentTree") { if (!(__vfsWrites && ok in __vfsWrites)) names.delete(name); }
|
|
931
|
+
else if (entry.state === "dir") names.set(name, "directory");
|
|
932
|
+
else {
|
|
933
|
+
const meta = _nsMeta(ok, false);
|
|
934
|
+
if (meta !== "absent" && meta !== "ELOOP") names.set(name, meta.type);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return names;
|
|
938
|
+
}
|
|
939
|
+
|
|
696
940
|
function _metadata(absPath) {
|
|
697
|
-
|
|
698
|
-
|
|
941
|
+
_nsRequire("stat", absPath, "fs.promises.stat");
|
|
942
|
+
const meta = _nsMeta(_strip(absPath), true);
|
|
943
|
+
return meta === "absent" || meta === "ELOOP" ? undefined : meta;
|
|
699
944
|
}
|
|
700
945
|
|
|
701
946
|
function _denialCode(cell) {
|
|
@@ -703,52 +948,13 @@ const __fsMod = (() => {
|
|
|
703
948
|
typeof cell.error === "string" ? cell.error : null;
|
|
704
949
|
}
|
|
705
950
|
|
|
706
|
-
// Removal retracts the path from the sync existence view
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
// the sync read path reports that file as merely non-resident.
|
|
951
|
+
// Removal retracts the path from the sync existence view: the namespace's
|
|
952
|
+
// overlay of this process's own effects says it is gone until a barrier's
|
|
953
|
+
// delta does.
|
|
710
954
|
function _forgetSyncPath(k) {
|
|
955
|
+
_createdHere.delete(k);
|
|
711
956
|
_announcedDirs.delete(k);
|
|
712
|
-
|
|
713
|
-
if (metadata) delete metadata[k];
|
|
714
|
-
if (!__vfsManifest) return;
|
|
715
|
-
delete __vfsManifest[k];
|
|
716
|
-
const slash = k.lastIndexOf("/");
|
|
717
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
718
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
719
|
-
const siblings = __vfsManifest[parent];
|
|
720
|
-
if (!siblings) return;
|
|
721
|
-
const at = siblings.indexOf(name);
|
|
722
|
-
if (at !== -1) siblings.splice(at, 1);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Put a path into the sync EXISTENCE view — the inverse of _forgetSyncPath.
|
|
727
|
-
*
|
|
728
|
-
* A content cell is not existence. An invalidation drops content and is
|
|
729
|
-
* documented to leave the name behind so existsSync cannot fabricate an
|
|
730
|
-
* ENOENT for a file that was merely rewritten — but a file this process just
|
|
731
|
-
* created has no manifest entry and no spawn-time stat record, so its cell
|
|
732
|
-
* WAS the name, and evicting it retracted a file the program had written
|
|
733
|
-
* itself. Recording the name where every other name lives is what makes that
|
|
734
|
-
* documented asymmetry true.
|
|
735
|
-
*
|
|
736
|
-
* The parent's listing is the right home for it. Where the parent has no
|
|
737
|
-
* listing the entry is created only for a directory this process made, since
|
|
738
|
-
* that is the only case in which it knows the whole contents and calling the
|
|
739
|
-
* directory enumerated is honest.
|
|
740
|
-
*/
|
|
741
|
-
function _announceSyncPath(k) {
|
|
742
|
-
if (!__vfsManifest || k === "") return;
|
|
743
|
-
const slash = k.lastIndexOf("/");
|
|
744
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
745
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
746
|
-
let siblings = __vfsManifest[parent];
|
|
747
|
-
if (!Array.isArray(siblings)) {
|
|
748
|
-
if (!__vfsDirs || !(parent in __vfsDirs)) return;
|
|
749
|
-
siblings = __vfsManifest[parent] = [];
|
|
750
|
-
}
|
|
751
|
-
if (siblings.indexOf(name) === -1) siblings.push(name);
|
|
957
|
+
_nsOwnSet(k, "absent");
|
|
752
958
|
}
|
|
753
959
|
|
|
754
960
|
/**
|
|
@@ -757,41 +963,67 @@ const __fsMod = (() => {
|
|
|
757
963
|
* content carried. One place, because every sync mutation owes all three.
|
|
758
964
|
*/
|
|
759
965
|
function _parkWrite(k, cell) {
|
|
966
|
+
_noteCreation(k);
|
|
760
967
|
__vfsWrites[k] = cell;
|
|
761
968
|
if (__vfsBundle) __vfsBundle[k] = cell;
|
|
762
969
|
delete __vfsBundleRevisions[k];
|
|
763
|
-
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* The paths this process itself created, whose owner and mode are its own:
|
|
974
|
+
* its uid and gid, and what its umask leaves. Every other stat answer comes
|
|
975
|
+
* from the authority's metadata, and a path the authority never described
|
|
976
|
+
* is not given one. A stat invented from the reader's own credential made a
|
|
977
|
+
* root-owned file read as the reader's own and writable.
|
|
978
|
+
*
|
|
979
|
+
* A path counts as created when nothing was there before the write and the
|
|
980
|
+
* view KNOWS nothing was there. Unknown is not created: a file the process
|
|
981
|
+
* never staged may exist at the authority, owned by someone else.
|
|
982
|
+
*/
|
|
983
|
+
const _createdHere = new Set();
|
|
984
|
+
function _noteCreation(k) {
|
|
985
|
+
if (_createdHere.has(k)) return;
|
|
986
|
+
const absPath = "/" + k;
|
|
987
|
+
// Known absent: the namespace names everything this credential can see.
|
|
988
|
+
if (_statLadder(absPath) !== undefined) return;
|
|
989
|
+
_createdHere.add(k);
|
|
990
|
+
}
|
|
991
|
+
function _forgetCreation(k) {
|
|
992
|
+
const prefix = k + "/";
|
|
993
|
+
for (const key of [..._createdHere]) if (key === k || key.startsWith(prefix)) _createdHere.delete(key);
|
|
764
994
|
}
|
|
765
995
|
|
|
766
996
|
function _forgetSyncTree(k) {
|
|
767
997
|
const prefix = k + "/";
|
|
998
|
+
_forgetCreation(k);
|
|
999
|
+
_nsOwnSet(k, "absentTree");
|
|
768
1000
|
for (const dir of _announcedDirs) if (dir.startsWith(prefix)) _announcedDirs.delete(dir);
|
|
769
|
-
const metadata = _metadataTable();
|
|
770
|
-
if (metadata) {
|
|
771
|
-
for (const mk of Object.keys(metadata)) if (mk.startsWith(prefix)) delete metadata[mk];
|
|
772
|
-
}
|
|
773
|
-
if (__vfsManifest) {
|
|
774
|
-
for (const dk of Object.keys(__vfsManifest)) if (dk.startsWith(prefix)) delete __vfsManifest[dk];
|
|
775
|
-
}
|
|
776
1001
|
_forgetSyncPath(k);
|
|
777
1002
|
}
|
|
778
1003
|
|
|
779
1004
|
/**
|
|
780
1005
|
* The honest error for a sync read the resident view cannot serve. A facet
|
|
781
1006
|
* has no synchronous I/O primitive, so a sync read is limited to content
|
|
782
|
-
* staged into this process
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
* instead of awaiting the read that would return it.
|
|
1007
|
+
* staged into this process, but the namespace names every path the
|
|
1008
|
+
* credential can see. ENOENT is correct only when it names none; for a path
|
|
1009
|
+
* that exists it would send the caller hunting for a missing file instead
|
|
1010
|
+
* of awaiting the read that would return it.
|
|
787
1011
|
*/
|
|
788
1012
|
function _notResidentError(absPath, displayPath, syscall, asyncForm) {
|
|
789
1013
|
const st = _statLadder(absPath);
|
|
790
|
-
if (st === undefined)
|
|
791
|
-
if (!_absenceIsKnown(absPath)) _recordUnmapped(absPath, syscall);
|
|
792
|
-
return _fsErr("ENOENT", syscall, displayPath);
|
|
793
|
-
}
|
|
1014
|
+
if (st === undefined) return _fsErr("ENOENT", syscall, displayPath);
|
|
794
1015
|
if (st.isDirectory()) return _fsErr("EISDIR", syscall, displayPath);
|
|
1016
|
+
// Unreadable to this credential: the namespace's mode says so, and no
|
|
1017
|
+
// bytes could have been staged for it.
|
|
1018
|
+
if (!_modeAllows(st, 4)) return _fsErr("EACCES", syscall, displayPath);
|
|
1019
|
+
if (typeof __residentStorageMiss === "function" && __residentStorageMiss(_strip(absPath))) {
|
|
1020
|
+
// The process's own write, which neither its store nor its heap budget
|
|
1021
|
+
// could hold: the workspace's storage is full.
|
|
1022
|
+
const full = _fsErr("ENOSPC", syscall, displayPath);
|
|
1023
|
+
full.message = "ENOSPC: workspace storage is full; '" + String(displayPath) + "' is readable asynchronously"
|
|
1024
|
+
+ (_supervisor() ? " (" + asyncForm + ")" : "");
|
|
1025
|
+
return full;
|
|
1026
|
+
}
|
|
795
1027
|
_recordResidencyMiss(absPath);
|
|
796
1028
|
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
797
1029
|
err.message += " — '" + String(displayPath) + "' exists but its content is not " +
|
|
@@ -925,7 +1157,9 @@ const __fsMod = (() => {
|
|
|
925
1157
|
let meta;
|
|
926
1158
|
// A thrown stat is the authority failing to answer, not an answer:
|
|
927
1159
|
// nothing is learned, and the miss stands.
|
|
928
|
-
|
|
1160
|
+
// Queued on the path: the program's own later mutations of it must not overtake the stat.
|
|
1161
|
+
const observe = () => __nimbusUseRpcResult(supervisor.stat(absPath), (r) => r);
|
|
1162
|
+
try { meta = await (_hasVfsMutationQueue() ? __nimbusQueueVfsMutation(absPath, observe, false) : observe()); }
|
|
929
1163
|
catch { return; }
|
|
930
1164
|
if (meta === null || meta === undefined) { _observedAbsent.add(k); return; }
|
|
931
1165
|
if (meta.type === "directory") return;
|
|
@@ -963,193 +1197,46 @@ const __fsMod = (() => {
|
|
|
963
1197
|
}
|
|
964
1198
|
};
|
|
965
1199
|
|
|
966
|
-
/**
|
|
967
|
-
* Has this directory's content been enumerated, or merely mentioned?
|
|
968
|
-
*
|
|
969
|
-
* __vfsManifest is a walk of SELECTED roots — the cwd subtree, its
|
|
970
|
-
* node_modules, the entry script's own package — not of the filesystem. A
|
|
971
|
-
* directory with an entry there was listed by that walk, so its child list
|
|
972
|
-
* is complete and a name that is not in it is genuinely not there. A
|
|
973
|
-
* directory with no entry was never opened, and knows nothing. __vfsDirs
|
|
974
|
-
* holds the directories this process created itself, which it therefore
|
|
975
|
-
* knows the contents of by construction.
|
|
976
|
-
*
|
|
977
|
-
* Conflating the two is what let a synchronous readdir of a real directory
|
|
978
|
-
* outside the walk answer with an empty array — not an error, not a refusal,
|
|
979
|
-
* a positive assertion that a populated directory is empty. Every caller
|
|
980
|
-
* that used to end in "not found, so absent" goes through here first.
|
|
981
|
-
*/
|
|
982
|
-
function _dirEnumerated(k) {
|
|
983
|
-
return (!!__vfsManifest && k in __vfsManifest) || (!!__vfsDirs && k in __vfsDirs);
|
|
984
|
-
}
|
|
985
1200
|
|
|
986
|
-
/** Does the content bundle itself describe children under this directory? */
|
|
987
|
-
function _bundleHasChildren(k) {
|
|
988
|
-
if (!__vfsBundle) return false;
|
|
989
|
-
return __residentAnyUnder(k ? k + "/" : "");
|
|
990
|
-
}
|
|
991
1201
|
|
|
992
|
-
/**
|
|
993
|
-
* Can the view say this path is ABSENT, as opposed to say nothing at all?
|
|
994
|
-
*
|
|
995
|
-
* With no supervisor bound there is no authority to be ignorant of: the
|
|
996
|
-
* staged tables ARE the filesystem, the async form reads out of those same
|
|
997
|
-
* tables, and a name that is not in them is not anywhere. Ignorance is a
|
|
998
|
-
* property of the gap between a process and an authority it cannot reach
|
|
999
|
-
* synchronously, so where there is no authority there is no gap.
|
|
1000
|
-
*/
|
|
1001
|
-
function _absenceIsKnown(absPath) {
|
|
1002
|
-
if (!_supervisor()) return true;
|
|
1003
|
-
const k = _strip(absPath);
|
|
1004
|
-
if (k === "") return true;
|
|
1005
|
-
const segments = k.split("/");
|
|
1006
|
-
// Deepest enumerated ancestor, because that is the only one that can
|
|
1007
|
-
// testify. Walking up rather than looking only at the immediate parent is
|
|
1008
|
-
// what makes a whole missing subtree answerable from one listing: if
|
|
1009
|
-
// $HOME was enumerated and holds no .config, then nothing under .config
|
|
1010
|
-
// is there either, and a program probing three levels down deserves that
|
|
1011
|
-
// answer rather than three refusals.
|
|
1012
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1013
|
-
const dir = segments.slice(0, i).join("/");
|
|
1014
|
-
if (!_dirEnumerated(dir)) continue;
|
|
1015
|
-
const child = segments.slice(0, i + 1).join("/");
|
|
1016
|
-
const children = (__vfsManifest && __vfsManifest[dir]) || [];
|
|
1017
|
-
const present = children.indexOf(segments[i]) !== -1
|
|
1018
|
-
|| (!!__vfsDirs && child in __vfsDirs)
|
|
1019
|
-
|| (!!__vfsBundle && child in __vfsBundle);
|
|
1020
|
-
// The component is not in a directory that was listed: absent, and so is
|
|
1021
|
-
// everything below it. If it IS there, only its own enumeration settles
|
|
1022
|
-
// what it contains, so nothing short of the immediate parent will do.
|
|
1023
|
-
if (!present) return true;
|
|
1024
|
-
return i === segments.length - 1;
|
|
1025
|
-
}
|
|
1026
|
-
return false;
|
|
1027
|
-
}
|
|
1028
1202
|
|
|
1029
|
-
/**
|
|
1030
|
-
* A path the sync view never mapped: record the ignorance, and repair it.
|
|
1031
|
-
*
|
|
1032
|
-
* Sibling of _notResidentError and the same bargain — the file may well be
|
|
1033
|
-
* there, the process simply cannot look without blocking. The callers answer
|
|
1034
|
-
* ENOENT, which is provisional rather than fabricated: this records the miss
|
|
1035
|
-
* and pulls in the listing that settles the question, and the exit report
|
|
1036
|
-
* reads the ledger. If the path was genuinely absent the repair proves it
|
|
1037
|
-
* and the entry retires — the program's not-found branch was the right
|
|
1038
|
-
* branch and it reached it through the right errno. If the path WAS there,
|
|
1039
|
-
* the entry survives and the run is failed by name.
|
|
1040
|
-
*
|
|
1041
|
-
* EAGAIN was tried here and is the wrong answer to hand a program even
|
|
1042
|
-
* though it is the honest description of the situation. It cannot arise from
|
|
1043
|
-
* a real POSIX filesystem, so nothing branches on it, and the catch block
|
|
1044
|
-
* that receives it was written for a missing file and rethrows instead:
|
|
1045
|
-
* measured, create-next-app reads $HOME/.config/<tool>/config.json through
|
|
1046
|
-
* the conf package, is handed EAGAIN for a file that was never there, and
|
|
1047
|
-
* dies before writing one template file. Read a config, treat ENOENT as "no
|
|
1048
|
-
* config yet", rethrow anything else is the near-universal shape. Refusing
|
|
1049
|
-
* bought nothing the ledger was not already delivering, and cost every
|
|
1050
|
-
* program that idiom.
|
|
1051
|
-
*
|
|
1052
|
-
* A LISTING is the exception and still refuses — see readdirSync. An array is
|
|
1053
|
-
* a complete enumeration by definition, so there is no provisional form of
|
|
1054
|
-
* it: an empty one asserts that a directory nothing ever opened is empty,
|
|
1055
|
-
* which is how a scaffolder was told its template directory held nothing,
|
|
1056
|
-
* wrote nothing, and exited 0.
|
|
1057
|
-
*/
|
|
1058
|
-
function _recordUnmapped(absPath, syscall) {
|
|
1059
|
-
const k = _strip(absPath);
|
|
1060
|
-
_recordMiss(k);
|
|
1061
|
-
// A listing wants the path's own contents; a read wants its bytes. Both
|
|
1062
|
-
// also want the boundary listing, which is the only thing that can prove
|
|
1063
|
-
// the path was not there — and fetching one repair and then the other
|
|
1064
|
-
// would refuse the same access twice, once for being unmapped and once for
|
|
1065
|
-
// having no resident content. _faultOnce collapses them where they meet.
|
|
1066
|
-
_faultInBoundary(k);
|
|
1067
|
-
if (syscall === "scandir") { _faultInDirectory(k); return; }
|
|
1068
|
-
_faultIn(k);
|
|
1069
|
-
}
|
|
1070
1203
|
|
|
1071
|
-
/**
|
|
1072
|
-
* The shallowest directory on this path whose contents the view does not
|
|
1073
|
-
* know: the child of the deepest ancestor that WAS enumerated.
|
|
1074
|
-
*
|
|
1075
|
-
* That, not the immediate parent, is the listing a refusal has to pull in.
|
|
1076
|
-
* For a path that is not there the parent is usually not there either, so
|
|
1077
|
-
* its readdir fails, the view learns nothing, and the ledger keeps an entry
|
|
1078
|
-
* no repair can ever retire — the run is failed over a file that was simply
|
|
1079
|
-
* absent. The boundary directory is different: the enumerated ancestor's own
|
|
1080
|
-
* listing already proved it is present, so it can be read, and reading it
|
|
1081
|
-
* either names the next component or proves the whole subtree absent.
|
|
1082
|
-
*
|
|
1083
|
-
* With nothing enumerated at all the boundary is the root, which is one
|
|
1084
|
-
* listing away from making the next level answerable.
|
|
1085
|
-
*/
|
|
1086
|
-
function _unknownBoundary(k) {
|
|
1087
|
-
const segments = k.split("/");
|
|
1088
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
1089
|
-
if (_dirEnumerated(segments.slice(0, i).join("/"))) {
|
|
1090
|
-
return segments.slice(0, i + 1).join("/");
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
return "";
|
|
1094
|
-
}
|
|
1095
1204
|
|
|
1096
|
-
function _faultInBoundary(k) {
|
|
1097
|
-
_faultInDirectory(_unknownBoundary(k));
|
|
1098
|
-
}
|
|
1099
1205
|
|
|
1100
|
-
/**
|
|
1101
|
-
* The refusal, for the two callers that have no provisional answer to give.
|
|
1102
|
-
*
|
|
1103
|
-
* A listing cannot say "not found" usefully, and neither can a write that
|
|
1104
|
-
* needs the prior content it is splicing onto. Everywhere else the sync view
|
|
1105
|
-
* answers ENOENT and lets the exit report settle whether that was true; here
|
|
1106
|
-
* there is no branch the program could take that would be right, so the
|
|
1107
|
-
* honest EAGAIN is also the useful one.
|
|
1108
|
-
*/
|
|
1109
|
-
function _refuseUnmapped(absPath, displayPath, syscall, asyncForm) {
|
|
1110
|
-
_recordUnmapped(absPath, syscall);
|
|
1111
|
-
const err = _fsErr("EAGAIN", syscall, displayPath);
|
|
1112
|
-
err.message += " — '" + String(displayPath) + "' lies outside the filesystem view "
|
|
1113
|
-
+ "staged into the process, so a synchronous call cannot tell what is there, "
|
|
1114
|
-
+ "and synchronous I/O cannot block to find out"
|
|
1115
|
-
+ (_supervisor() ? "; " + asyncForm + " answers from the live filesystem" : "");
|
|
1116
|
-
return err;
|
|
1117
|
-
}
|
|
1118
1206
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
// What the authority says of a path after this process's own async request
|
|
1210
|
+
// (a stat, read or accepted write) is what the sync view answers for it
|
|
1211
|
+
// from then on, as it would after the delta that reports it
|
|
1212
|
+
// (facet-resident-store's __nsNoteLiveStat).
|
|
1213
|
+
//
|
|
1214
|
+
// The lstat rides the read batch (_queueBatchRequest) rather than a call
|
|
1215
|
+
// of its own: a resumption refetches every path it owes at once
|
|
1216
|
+
// (_acquireAndRefetch), and a learn per path made that one lstat call per
|
|
1217
|
+
// path — 1,601 in flight from one facet after a program wrote 1,600 files,
|
|
1218
|
+
// some of which stayed pending and never reached the session
|
|
1219
|
+
// (preview/new/lucide-barrel-cache-widens, measured 2026-09-28). Batched,
|
|
1220
|
+
// the learns cost a round trip per batch, as the reads before them do.
|
|
1221
|
+
async function _learnLive(absPath, supervisor) {
|
|
1222
|
+
if (!supervisor || typeof supervisor.fsReadBatch !== "function") return;
|
|
1223
|
+
let stat;
|
|
1224
|
+
try { stat = await _queueBatchRequest(supervisor, { path: absPath, lstat: true }, 0); } catch { return; }
|
|
1225
|
+
__nsNoteLiveStat(_strip(absPath), stat ?? null);
|
|
1128
1226
|
}
|
|
1129
1227
|
|
|
1130
1228
|
/**
|
|
1131
|
-
* Retire the
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
* was
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
* over that would fail every module resolver, which probes dozens of paths
|
|
1138
|
-
* that do not exist by design. So what stays in the ledger is exactly the
|
|
1139
|
-
* harmful set: content that exists and was not served.
|
|
1229
|
+
* Retire the misses a repair turned into honest absence. A miss is a wrong
|
|
1230
|
+
* answer only when the path was THERE and the process was denied it: one on
|
|
1231
|
+
* a name the namespace does not have (or the authority did not have when it
|
|
1232
|
+
* was asked) was the program's not-found branch, taken for the right
|
|
1233
|
+
* reason. Failing the run over those would fail every module resolver,
|
|
1234
|
+
* which probes dozens of paths that do not exist by design.
|
|
1140
1235
|
*/
|
|
1141
1236
|
function _settleProvenAbsences() {
|
|
1142
1237
|
if (_residencyMisses.size === 0) return;
|
|
1143
1238
|
for (const k of [..._residencyMisses]) {
|
|
1144
|
-
|
|
1145
|
-
// The authority did not have it when we asked, so nothing was withheld —
|
|
1146
|
-
// whatever is at the path now, this process put there.
|
|
1147
|
-
if (_observedAbsent.has(k)) { _residencyMisses.delete(k); continue; }
|
|
1148
|
-
if (_statLadder(absPath) !== undefined) continue;
|
|
1149
|
-
if (_absenceIsKnown(absPath)) { _residencyMisses.delete(k); continue; }
|
|
1150
|
-
// Still unprovable, because the listing that would settle it lies below
|
|
1151
|
-
// the one already fetched. Ask for the next one; the settle loop waits.
|
|
1152
|
-
_faultInBoundary(k);
|
|
1239
|
+
if (_observedAbsent.has(k) || (_nsActive() && _statLadder("/" + k) === undefined)) _residencyMisses.delete(k);
|
|
1153
1240
|
}
|
|
1154
1241
|
}
|
|
1155
1242
|
|
|
@@ -1218,6 +1305,10 @@ const __fsMod = (() => {
|
|
|
1218
1305
|
|
|
1219
1306
|
const _localTimes = globalThis.__nimbusVfsTimes || (globalThis.__nimbusVfsTimes = Object.create(null));
|
|
1220
1307
|
const _localModes = globalThis.__nimbusVfsModes || (globalThis.__nimbusVfsModes = Object.create(null));
|
|
1308
|
+
// Modes set locally that the authority has not received yet. Delivered once:
|
|
1309
|
+
// re-sending on every flush made each read of the path a chmod, which bumped
|
|
1310
|
+
// its revision and evicted the process's own cell (create-astro EAGAIN).
|
|
1311
|
+
const _pendingModes = globalThis.__nimbusVfsPendingModes || (globalThis.__nimbusVfsPendingModes = new Set());
|
|
1221
1312
|
|
|
1222
1313
|
function _coerceMode(value, syscall, p) {
|
|
1223
1314
|
const n = typeof value === "string" ? parseInt(value, 8) : Number(value);
|
|
@@ -1242,11 +1333,9 @@ const __fsMod = (() => {
|
|
|
1242
1333
|
atimeMs: _coerceTimeMs(atime, syscall, p),
|
|
1243
1334
|
mtimeMs: _coerceTimeMs(mtime, syscall, p),
|
|
1244
1335
|
};
|
|
1336
|
+
// The sync view answers these times ahead of the namespace's (_statObject)
|
|
1337
|
+
// until the utimes is reported back, as a write's own bytes are.
|
|
1245
1338
|
_localTimes[k] = time;
|
|
1246
|
-
// A spawn-time stat record answers statSync ahead of _localTimes, so it
|
|
1247
|
-
// must carry the new times too or the sync view keeps the old mtime.
|
|
1248
|
-
const meta = _metadata(absPath);
|
|
1249
|
-
if (meta) { meta.atime = time.atimeMs; meta.mtime = time.mtimeMs; }
|
|
1250
1339
|
return time;
|
|
1251
1340
|
}
|
|
1252
1341
|
|
|
@@ -1300,7 +1389,7 @@ const __fsMod = (() => {
|
|
|
1300
1389
|
// ══ Cache coherence: ACQUIRE + read-through fill ═════════════════════
|
|
1301
1390
|
//
|
|
1302
1391
|
// The supervisor's SQLite VFS is the only authority. Everything in this
|
|
1303
|
-
// facet —
|
|
1392
|
+
// facet — the resident store, __vfsBundle, __vfsDirs — is a
|
|
1304
1393
|
// cache of it, and __vfsWrites is this process's own not-yet-flushed
|
|
1305
1394
|
// mutations.
|
|
1306
1395
|
//
|
|
@@ -1328,6 +1417,24 @@ const __fsMod = (() => {
|
|
|
1328
1417
|
// a frame arrives as a supervisor reply the same barrier
|
|
1329
1418
|
// rides on (see the relayed WebSocket below).
|
|
1330
1419
|
//
|
|
1420
|
+
// The supervisor's own deliveries are resumptions too. Each takes
|
|
1421
|
+
// __nimbusInboundBarrier before the program sees it, and each carries the
|
|
1422
|
+
// barrier's answer, computed by the supervisor after the thing delivered
|
|
1423
|
+
// was queued (protocol §3's piggybacked inv), so the barrier applies it
|
|
1424
|
+
// instead of asking and the delivery costs no round trip of its own:
|
|
1425
|
+
// - request: a request routed to one of the process's ports, in the
|
|
1426
|
+
// dispatch every resident's server shares (__nimbusServeHttp).
|
|
1427
|
+
// The supervisor starts it, so it holds no cursor of this
|
|
1428
|
+
// process's and answers "nothing since now", which stands in
|
|
1429
|
+
// only when no write preceded the request since this process
|
|
1430
|
+
// last caught up (protocol §9.2); otherwise the barrier asks.
|
|
1431
|
+
// - stdin: a stdin packet — input, its end, a signal, a resize — for an
|
|
1432
|
+
// attached process (__makeProcessStdin's pump).
|
|
1433
|
+
// - child: a spawned child's output and its exit (_runReadLoop,
|
|
1434
|
+
// _runWaitLoop).
|
|
1435
|
+
// Stdin and child output are long polls, so they send this process's
|
|
1436
|
+
// ACQUIRE arguments (_acquireArgs) and are answered from exactly those.
|
|
1437
|
+
//
|
|
1331
1438
|
// Keep this list empty. An entry here is a documented hole in the owner's
|
|
1332
1439
|
// invariant, not a TODO: it means some process can be woken by something
|
|
1333
1440
|
// this system does not mediate, and its next synchronous read can serve
|
|
@@ -1349,8 +1456,18 @@ const __fsMod = (() => {
|
|
|
1349
1456
|
// envelope — there is no reason to invent a second surface for it.
|
|
1350
1457
|
const _stats = globalThis.__nimbusVfsCoherence
|
|
1351
1458
|
|| (globalThis.__nimbusVfsCoherence = {
|
|
1352
|
-
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0,
|
|
1459
|
+
fills: 0, filledBytes: 0, invalidations: 0, poisons: 0, pushes: 0,
|
|
1353
1460
|
reconciles: 0, selfWrites: 0, misses: 0,
|
|
1461
|
+
// ACQUIREs that got no answer (see _acquireBarrier), and the last reason.
|
|
1462
|
+
barrierFailures: 0, lastBarrierFailure: "",
|
|
1463
|
+
// Barriers that held their resumption on an own write's acknowledgement
|
|
1464
|
+
// (_awaitReportedOwnWrites).
|
|
1465
|
+
ownWriteWaits: 0,
|
|
1466
|
+
// The namespace's failure state (a relist failed): synchronous calls
|
|
1467
|
+
// refused with its cause (_nsRequire), and barriers that repaired it.
|
|
1468
|
+
namespaceRefusals: 0, namespaceRepairs: 0,
|
|
1469
|
+
// Repairs a delta from the store's durable floor answered, with no listing.
|
|
1470
|
+
floorRepairs: 0,
|
|
1354
1471
|
});
|
|
1355
1472
|
|
|
1356
1473
|
/**
|
|
@@ -1383,8 +1500,6 @@ const __fsMod = (() => {
|
|
|
1383
1500
|
let evicted = false;
|
|
1384
1501
|
delete __vfsBundleRevisions[k];
|
|
1385
1502
|
if (__vfsBundle && k in __vfsBundle) { delete __vfsBundle[k]; evicted = true; }
|
|
1386
|
-
const metadata = _metadataTable();
|
|
1387
|
-
if (metadata && k in metadata) { delete metadata[k]; evicted = true; }
|
|
1388
1503
|
return evicted;
|
|
1389
1504
|
}
|
|
1390
1505
|
|
|
@@ -1399,15 +1514,107 @@ const __fsMod = (() => {
|
|
|
1399
1514
|
const _owedRefetch = new Set();
|
|
1400
1515
|
|
|
1401
1516
|
// An own-mutation lease can end in an eviction (a peer wrote inside the
|
|
1402
|
-
// window)
|
|
1403
|
-
//
|
|
1404
|
-
//
|
|
1405
|
-
//
|
|
1406
|
-
|
|
1517
|
+
// window), and so can a flush whose revision a barrier's report outran.
|
|
1518
|
+
// The ledger owning those decisions is spliced AHEAD of this closure, so it
|
|
1519
|
+
// is handed the one eviction path rather than keeping a second copy of it:
|
|
1520
|
+
// content view, stat view, the invalidation count and the refetch debt
|
|
1521
|
+
// move together here or not at all.
|
|
1522
|
+
function _evictOwed(k) {
|
|
1407
1523
|
if (!_evictResident(k)) return;
|
|
1408
1524
|
_stats.invalidations++;
|
|
1409
1525
|
_owedRefetch.add(k);
|
|
1410
|
-
}
|
|
1526
|
+
}
|
|
1527
|
+
globalThis.__nimbusEvictResidentCell = _evictOwed;
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Live reads in flight, per path: each one's fill ticket, which carries
|
|
1531
|
+
* the highest revision a barrier reported for the path while that read was
|
|
1532
|
+
* outstanding.
|
|
1533
|
+
*
|
|
1534
|
+
* A barrier that reports a path nobody holds has nothing to evict and
|
|
1535
|
+
* CONSUMES the report: the cursor moves past it and no later delta names it
|
|
1536
|
+
* again. A read of that path already in flight may have been served before
|
|
1537
|
+
* the mutation the report describes, and its bytes would then be installed
|
|
1538
|
+
* behind the only message that could have evicted them. So the report is
|
|
1539
|
+
* kept on the read until it lands, and the read installs only if nothing
|
|
1540
|
+
* newer than its cursor was reported under it (_installResident).
|
|
1541
|
+
*
|
|
1542
|
+
* One ticket per read, not one per path: a read that begins after a report
|
|
1543
|
+
* or a poison is judged only by what is reported after it began. Sharing the
|
|
1544
|
+
* earlier read's record condemned the read issued to replace it.
|
|
1545
|
+
*/
|
|
1546
|
+
const _fillReports = new Map();
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Begin a live read of `k`: the cursor it is issued under — every mutation
|
|
1550
|
+
* at or below it is already in the bytes it returns — and what has been
|
|
1551
|
+
* reported against it since, nothing yet. Called after the read's barrier
|
|
1552
|
+
* and before its RPC.
|
|
1553
|
+
*/
|
|
1554
|
+
function _beginFill(k) {
|
|
1555
|
+
const fill = { k, rev: _cursor.rev, reported: -1 };
|
|
1556
|
+
let live = _fillReports.get(k);
|
|
1557
|
+
if (!live) {
|
|
1558
|
+
live = new Set();
|
|
1559
|
+
_fillReports.set(k, live);
|
|
1560
|
+
}
|
|
1561
|
+
live.add(fill);
|
|
1562
|
+
return fill;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function _endFill(fill) {
|
|
1566
|
+
const live = _fillReports.get(fill.k);
|
|
1567
|
+
if (!live) return;
|
|
1568
|
+
live.delete(fill);
|
|
1569
|
+
if (live.size === 0) _fillReports.delete(fill.k);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/** A barrier reported `k` at `rev`: remember it on every read of it in flight. */
|
|
1573
|
+
function _noteFillReport(k, rev) {
|
|
1574
|
+
const live = _fillReports.get(k);
|
|
1575
|
+
if (live) for (const fill of live) if (rev > fill.reported) fill.reported = rev;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* Note one delta entry on everything in flight that it covers: its path,
|
|
1580
|
+
* or, for a subtree-scoped or structural entry, every path at or under it.
|
|
1581
|
+
* The ledger does the same for this facet's own writes and mutations.
|
|
1582
|
+
*/
|
|
1583
|
+
function _noteReport(entry) {
|
|
1584
|
+
if (!entry.subtree && !entry.structural) {
|
|
1585
|
+
__nimbusNoteVfsReport(entry.path, entry.rev);
|
|
1586
|
+
_noteFillReport(entry.path, entry.rev);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
__nimbusNoteVfsReportUnder(entry.path, entry.rev);
|
|
1590
|
+
const under = entry.path + "/";
|
|
1591
|
+
for (const k of [..._fillReports.keys()]) {
|
|
1592
|
+
if (k === entry.path || k.startsWith(under)) _noteFillReport(k, entry.rev);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* The resident cells a delta entry covers: its path, or, for a
|
|
1598
|
+
* subtree-scoped or structural entry, every cell at or under it. Such an
|
|
1599
|
+
* entry stands for changes it does not name (a directory that went, one
|
|
1600
|
+
* whose mode, owner or group changed, or something beneath it this facet
|
|
1601
|
+
* may not see), so each cell there is judged as a named one would be.
|
|
1602
|
+
*/
|
|
1603
|
+
function _coveredCells(entry) {
|
|
1604
|
+
if (!entry.subtree && !entry.structural) return [entry.path];
|
|
1605
|
+
const under = entry.path + "/";
|
|
1606
|
+
const cells = new Set();
|
|
1607
|
+
for (const table of [__vfsBundle, __vfsBundleRevisions]) {
|
|
1608
|
+
if (!table) continue;
|
|
1609
|
+
for (const k of Object.keys(table)) if (k === entry.path || k.startsWith(under)) cells.add(k);
|
|
1610
|
+
}
|
|
1611
|
+
return [...cells];
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
/** The cursor moved without a delta (a poison), so no read in flight can be dated. */
|
|
1615
|
+
function _spoilFills() {
|
|
1616
|
+
for (const live of _fillReports.values()) for (const fill of live) fill.reported = Infinity;
|
|
1617
|
+
}
|
|
1411
1618
|
|
|
1412
1619
|
/**
|
|
1413
1620
|
* Install bytes the supervisor just served as the resident cell for a
|
|
@@ -1421,42 +1628,30 @@ const __fsMod = (() => {
|
|
|
1421
1628
|
* returned, so it has nothing to invalidate. The bytes are already paid
|
|
1422
1629
|
* for; caching them costs one map insert.
|
|
1423
1630
|
*
|
|
1631
|
+
* `fill` (_beginFill) dates them, and it is also the reason an install can
|
|
1632
|
+
* be declined: a barrier that reported the path above the read's cursor
|
|
1633
|
+
* while the read was in flight may describe a mutation the bytes predate,
|
|
1634
|
+
* and that report is gone once consumed. Declining costs the next sync
|
|
1635
|
+
* read a miss; installing would cost a stale byte nothing ever evicts.
|
|
1636
|
+
*
|
|
1424
1637
|
* The parent's manifest entry gains the name too, so the existence view
|
|
1425
1638
|
* cannot go on denying a file whose bytes this process is holding.
|
|
1426
1639
|
*/
|
|
1427
|
-
function _installResident(absPath, bytes) {
|
|
1640
|
+
function _installResident(absPath, bytes, fill) {
|
|
1428
1641
|
if (!__vfsBundle) return;
|
|
1429
1642
|
const k = _strip(absPath);
|
|
1430
1643
|
if (k === "") return;
|
|
1644
|
+
if (fill.reported > fill.rev) return;
|
|
1431
1645
|
// Never over a cell this facet owns.
|
|
1432
1646
|
//
|
|
1433
1647
|
// A pending write is strictly newer than anything the authority can
|
|
1434
1648
|
// report, and __vfsBundle is what sync reads consult first, so installing
|
|
1435
1649
|
// over it would serve the program bytes older than its own write.
|
|
1436
|
-
//
|
|
1437
|
-
// A STAMPED cell is this facet's own flushed write, and the barrier this
|
|
1438
|
-
// read passed on the way in reported nobody else has touched the path
|
|
1439
|
-
// since — so the bytes are already what is being installed, and the only
|
|
1440
|
-
// thing the install would change is to drop the stamp. It did: a
|
|
1441
|
-
// speculative repair issued by an earlier refused read landed after the
|
|
1442
|
-
// flush, unstamped the cell, and the next ACQUIRE evicted the facet's own
|
|
1443
|
-
// output. Measured at selfWrites 0 / invalidations 1 / fills 2 for one
|
|
1444
|
-
// written file, against 1 / 0 / 0 with the repair absent.
|
|
1445
1650
|
if (__vfsWrites && k in __vfsWrites) return;
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
//
|
|
1449
|
-
|
|
1450
|
-
// spawn-time length, so a program can read N bytes and be told the
|
|
1451
|
-
// file is M — an inconsistency introduced BY the fill. Only the size
|
|
1452
|
-
// is corrected: type, mode, ownership and timestamps stay as the
|
|
1453
|
-
// authority last reported them, so mtime-based change detection
|
|
1454
|
-
// (make, tsc --build, watchers) does not see every read as a change.
|
|
1455
|
-
const metadata = _metadataTable();
|
|
1456
|
-
if (metadata && k in metadata) {
|
|
1457
|
-
metadata[k] = { ...metadata[k], size: _byteLen(bytes) };
|
|
1458
|
-
}
|
|
1459
|
-
_announceSyncPath(k);
|
|
1651
|
+
// The store dates the row at the read's cursor, so the next delta that
|
|
1652
|
+
// names the path above it evicts it. It declines, itself, to replace own
|
|
1653
|
+
// bytes or a row dated later than this read can vouch for.
|
|
1654
|
+
if (!__residentFill(k, bytes, fill.rev)) return;
|
|
1460
1655
|
_stats.fills++;
|
|
1461
1656
|
_stats.filledBytes += _byteLen(bytes);
|
|
1462
1657
|
}
|
|
@@ -1492,25 +1687,86 @@ const __fsMod = (() => {
|
|
|
1492
1687
|
return _residentRepair;
|
|
1493
1688
|
}
|
|
1494
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Set while the store holds only what a repair could NOT vouch for: rows it
|
|
1692
|
+
* had to drop rather than prove current. Nothing but another repair brings
|
|
1693
|
+
* those back — a delta names what changed, not what this facet lost — so
|
|
1694
|
+
* every barrier repairs until one vouches, however the ACQUIRE is answered.
|
|
1695
|
+
*/
|
|
1696
|
+
let _storeRepairOwed = false;
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* The repair's first try: the delta from the store's durable floor
|
|
1700
|
+
* (__residentCursor), asked as every barrier asks it. True when it applied
|
|
1701
|
+
* (the repair is done); false when a listing is needed after all.
|
|
1702
|
+
*/
|
|
1703
|
+
async function _repairFromFloor(supervisor, result) {
|
|
1704
|
+
if (!supervisor || typeof supervisor.fsAcquire !== "function" || !_nsActive()) return false;
|
|
1705
|
+
if (result !== null && result.poison === true) return false;
|
|
1706
|
+
const floor = __residentCursor();
|
|
1707
|
+
if (floor === null || (result !== null && result.epoch !== floor.epoch)) return false;
|
|
1708
|
+
let delta;
|
|
1709
|
+
try {
|
|
1710
|
+
delta = await __nimbusUseRpcResult(supervisor.fsAcquire(floor.epoch, floor.rev, { namespace: true }), (r) => r);
|
|
1711
|
+
} catch { return false; }
|
|
1712
|
+
if (!delta || delta.poison === true || typeof delta.rev !== "number" || delta.epoch !== floor.epoch) return false;
|
|
1713
|
+
if (Array.isArray(delta.paths)) for (const entry of delta.paths) _noteReport(entry);
|
|
1714
|
+
await __residentRoomForPushed(delta);
|
|
1715
|
+
const applied = __residentAdmit(delta);
|
|
1716
|
+
for (const dir of applied.relist) await __nsRelist(supervisor, dir);
|
|
1717
|
+
if (!_nsActive()) return false;
|
|
1718
|
+
_cursor.epoch = applied.cursor.epoch;
|
|
1719
|
+
_cursor.rev = applied.cursor.rev;
|
|
1720
|
+
_stats.invalidations += applied.dropped.length;
|
|
1721
|
+
// What it should hold and lost (dropped by this delta, or by the barrier
|
|
1722
|
+
// that could not vouch for its rows), refetched by the namespace's stats.
|
|
1723
|
+
const refilled = await __residentRefillFromNamespace(supervisor, applied.dropped);
|
|
1724
|
+
_stats.fills += refilled.filled;
|
|
1725
|
+
_stats.filledBytes += refilled.bytes;
|
|
1726
|
+
_stats.floorRepairs++;
|
|
1727
|
+
_storeRepairOwed = false;
|
|
1728
|
+
return true;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* `result` is the ACQUIRE answer that asked for the repair: a poison, a
|
|
1733
|
+
* delta arriving while a repair is owed, or null for a barrier that got no
|
|
1734
|
+
* answer at all.
|
|
1735
|
+
*/
|
|
1495
1736
|
async function _runResidentRepair(supervisor, result) {
|
|
1737
|
+
// Within one epoch, with the change log intact and the namespace
|
|
1738
|
+
// answering, the store's rows are dated through its own durable cursor, so
|
|
1739
|
+
// the delta from there names everything that moved since. Only an epoch
|
|
1740
|
+
// change, a poison (the log truncated past the cursor), or a namespace
|
|
1741
|
+
// that stopped answering needs the listing.
|
|
1742
|
+
if (await _repairFromFloor(supervisor, result)) return;
|
|
1743
|
+
const ownAtStart = __residentOwnPaths();
|
|
1496
1744
|
let repaired;
|
|
1497
1745
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1498
1746
|
catch { repaired = null; }
|
|
1499
1747
|
if (!repaired || !repaired.cursor) {
|
|
1500
1748
|
// The pass could not vouch for a single row — a supervisor replaced
|
|
1501
|
-
// mid-enumeration, an enumeration that came back short
|
|
1502
|
-
// cannot be dated must not be served, so the
|
|
1503
|
-
//
|
|
1504
|
-
// it just dropped.
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1749
|
+
// mid-enumeration, an enumeration that came back short, an authority it
|
|
1750
|
+
// could not reach. A row that cannot be dated must not be served, so the
|
|
1751
|
+
// cold cache is taken after all, and the pass runs once more to
|
|
1752
|
+
// repopulate what it just dropped. An answer with a cursor is admitted as
|
|
1753
|
+
// it would have been; a barrier with no answer drops every dated row and
|
|
1754
|
+
// keeps the cursor it had, which the next delta is still owed from.
|
|
1755
|
+
if (result) {
|
|
1756
|
+
__residentAdmit(result);
|
|
1757
|
+
_cursor.epoch = result.epoch;
|
|
1758
|
+
_cursor.rev = result.rev;
|
|
1759
|
+
} else {
|
|
1760
|
+
__residentDropDated();
|
|
1761
|
+
}
|
|
1508
1762
|
try { repaired = await __residentSynchronizeFromSupervisor(supervisor); }
|
|
1509
1763
|
catch { repaired = null; }
|
|
1510
1764
|
} else if (repaired.reconciled) {
|
|
1511
1765
|
_stats.reconciles++;
|
|
1512
1766
|
}
|
|
1513
|
-
|
|
1767
|
+
_settleSkippedReports(ownAtStart, repaired);
|
|
1768
|
+
_storeRepairOwed = !(repaired && repaired.cursor);
|
|
1769
|
+
if (_storeRepairOwed) return;
|
|
1514
1770
|
_cursor.epoch = repaired.cursor.epoch;
|
|
1515
1771
|
_cursor.rev = repaired.cursor.rev;
|
|
1516
1772
|
_stats.invalidations += repaired.dropped;
|
|
@@ -1518,6 +1774,41 @@ const __fsMod = (() => {
|
|
|
1518
1774
|
_stats.filledBytes += repaired.bytes;
|
|
1519
1775
|
}
|
|
1520
1776
|
|
|
1777
|
+
/**
|
|
1778
|
+
* Deliver the reports a repair moved the cursor over.
|
|
1779
|
+
*
|
|
1780
|
+
* A repair moves the cursor without a delta, so what a delta would have
|
|
1781
|
+
* reported reaches nothing on its own. A dated row the pass kept is proven
|
|
1782
|
+
* current by its listed revision and needs nothing more. A row of this
|
|
1783
|
+
* facet's own bytes is kept whatever the listing says — and its report is
|
|
1784
|
+
* exactly what the write or mutation that owns it needs, to judge its own
|
|
1785
|
+
* acknowledgement against. The pass hands those back (`own`), and they are
|
|
1786
|
+
* noted here as a delta's would have been; one acknowledged since the pass
|
|
1787
|
+
* looked was dated by a receipt that could not see the report, so it is
|
|
1788
|
+
* judged here directly.
|
|
1789
|
+
*
|
|
1790
|
+
* With nothing datable — across incarnations, against a short listing, or
|
|
1791
|
+
* a pass that failed outright — every own row, and every row that was own
|
|
1792
|
+
* when the repair began, is judged as though a peer wrote it last: an
|
|
1793
|
+
* eviction and a refetch, never a stale byte.
|
|
1794
|
+
*/
|
|
1795
|
+
function _settleSkippedReports(ownAtStart, repaired) {
|
|
1796
|
+
const reports = new Map();
|
|
1797
|
+
if (repaired && repaired.cursor && repaired.reconciled && Array.isArray(repaired.own)) {
|
|
1798
|
+
for (const entry of repaired.own) reports.set(entry.path, entry.rev);
|
|
1799
|
+
} else {
|
|
1800
|
+
for (const k of ownAtStart) reports.set(k, Infinity);
|
|
1801
|
+
if (repaired && Array.isArray(repaired.own)) for (const entry of repaired.own) reports.set(entry.path, Infinity);
|
|
1802
|
+
for (const k of Object.keys(__vfsOwnLeases)) reports.set(k, Infinity);
|
|
1803
|
+
for (const k of Object.keys(__vfsWrites)) reports.set(k, Infinity);
|
|
1804
|
+
}
|
|
1805
|
+
for (const [k, rev] of reports) {
|
|
1806
|
+
__nimbusNoteVfsReport(k, rev);
|
|
1807
|
+
const provenance = __residentProvenance(k);
|
|
1808
|
+
if (provenance !== undefined && provenance !== -1 && provenance < rev) _evictOwed(k);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1521
1812
|
/**
|
|
1522
1813
|
* ACQUIRE. Apply every invalidation the supervisor has for this facet,
|
|
1523
1814
|
* then re-stamp the cursor.
|
|
@@ -1543,65 +1834,247 @@ const __fsMod = (() => {
|
|
|
1543
1834
|
* left. Comparing revisions rather than names keeps the peer case intact:
|
|
1544
1835
|
* a peer writing the same path afterwards reports a HIGHER revision than
|
|
1545
1836
|
* our stamp, so that invalidation still lands.
|
|
1837
|
+
*
|
|
1838
|
+
* An ACQUIRE that gets no answer is a poison too, never an empty delta. It
|
|
1839
|
+
* has learned nothing about what changed, and the resumption behind it is
|
|
1840
|
+
* about to serve every row it holds. The call is a pure read, so a dropped
|
|
1841
|
+
* one is already retried where the supervisor makes it (SupervisorRPC,
|
|
1842
|
+
* fabric's idempotent), and what arrives here failed those retries, came
|
|
1843
|
+
* from a host that could not serve it, or answered without a cursor. The
|
|
1844
|
+
* rows are repaired against the listing exactly as for a poison; with no
|
|
1845
|
+
* cursor to adopt, the one held is kept and still owed every change since.
|
|
1846
|
+
* The barrier itself never throws: the operation or callback behind it
|
|
1847
|
+
* runs either way, against rows that were vouched for or a colder cache.
|
|
1848
|
+
*
|
|
1849
|
+
* A repair is single-flight, and while one runs no delta may be admitted
|
|
1850
|
+
* at all: the repair has already dropped the rows it could not vouch for,
|
|
1851
|
+
* so a report of one of them finds nothing to evict and is consumed, and
|
|
1852
|
+
* the repair then installs it at its listing's revision and publishes the
|
|
1853
|
+
* listing's cursor over the one the delta advanced. So any barrier answered
|
|
1854
|
+
* while a repair runs joins it, whether its own answer needed a repair or
|
|
1855
|
+
* was an ordinary delta, and the listing may predate that answer — the rows
|
|
1856
|
+
* it vouches for are another barrier's answer, not this one's — so a joiner
|
|
1857
|
+
* waits for it and then asks again: a delta from the repaired cursor names
|
|
1858
|
+
* everything since. Only the barrier that started a repair resumes on it
|
|
1859
|
+
* alone.
|
|
1860
|
+
*
|
|
1861
|
+
* `delivered` is an answer the supervisor delivered with the resumption
|
|
1862
|
+
* this barrier is taken for; it stands in for the first ACQUIRE when it can
|
|
1863
|
+
* (_deliveredAnswer). Everything after is the same for an answer delivered
|
|
1864
|
+
* and one asked for, so a repair, a join or a poison follows exactly the
|
|
1865
|
+
* path it always has.
|
|
1866
|
+
*
|
|
1867
|
+
* Last, the barrier waits out every own write or mutation that a report —
|
|
1868
|
+
* this answer's or an earlier one's — is outstanding against, until its
|
|
1869
|
+
* acknowledgement says which came first (__nimbusReportedOwnAcknowledgements).
|
|
1870
|
+
* Until then the resumption could run on own bytes the authority applied
|
|
1871
|
+
* before a peer overwrote them. Nothing is waited on unless such a report
|
|
1872
|
+
* exists.
|
|
1546
1873
|
*/
|
|
1547
|
-
async function _acquireBarrier(supervisor) {
|
|
1874
|
+
async function _acquireBarrier(supervisor, delivered) {
|
|
1548
1875
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return [];
|
|
1549
|
-
|
|
1550
|
-
//
|
|
1551
|
-
//
|
|
1552
|
-
//
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
// .then never ran, no error, no output, intermittently, and never when a
|
|
1556
|
-
// pending timer happened to hold the program open.
|
|
1557
|
-
try { result = await __nimbusUseRpcResult(supervisor.fsAcquire(_cursor.epoch, _cursor.rev), (r) => r); }
|
|
1558
|
-
catch { return []; }
|
|
1559
|
-
if (!result || typeof result.rev !== "number") return [];
|
|
1560
|
-
// Paths that held content before this eviction are the ones worth
|
|
1561
|
-
// refetching at a resumption boundary — a later sync read of any of them
|
|
1562
|
-
// would otherwise miss. Collected before the delete so the membership
|
|
1563
|
-
// test is against the pre-eviction bundle.
|
|
1564
|
-
const wereResident = [];
|
|
1876
|
+
const fromDelivery = _deliveredAnswer(delivered);
|
|
1877
|
+
// The overlay of this facet's own structural effects retires what settled
|
|
1878
|
+
// before the answer was asked for: a delivered answer was asked for when
|
|
1879
|
+
// its arguments were built, a fetched one now.
|
|
1880
|
+
const begin = fromDelivery ? _argsBegin(delivered.args) : ++_barrierBegins;
|
|
1881
|
+
let result = fromDelivery || await _acquire(supervisor);
|
|
1565
1882
|
// When the resident set lives in the facet's own SQLite, the STORE applies
|
|
1566
1883
|
// the delta. That is not an optimisation, it is where provenance has to
|
|
1567
1884
|
// live: a facet's SQLite outlives its module scope, so a new incarnation
|
|
1568
1885
|
// opens onto rows a previous one wrote while every heap-side stamp that
|
|
1569
1886
|
// described them is gone. Rows carry their own revision, __vfsBundleRevisions
|
|
1570
1887
|
// cannot follow them there, and two provenance stores would be one too many.
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1888
|
+
{
|
|
1889
|
+
// A namespace that stopped answering (a relist failed) is a failure
|
|
1890
|
+
// state, not a mode: every barrier the process reaches repairs it, once,
|
|
1891
|
+
// until a listing restores it.
|
|
1892
|
+
let namespaceAsked = false;
|
|
1893
|
+
for (let joins = 0; ; joins++) {
|
|
1894
|
+
const joining = _residentRepair !== null;
|
|
1895
|
+
const namespaceOwed = !namespaceAsked && !_nsActive();
|
|
1896
|
+
// A sealed heap view can release acknowledged overlays before its
|
|
1897
|
+
// listing; otherwise the old overlay and replacement rows would
|
|
1898
|
+
// consume the same scarce allowance while preventing each other's
|
|
1899
|
+
// repair. Nothing synchronous may consult the sealed view here.
|
|
1900
|
+
if (namespaceOwed && __residentInHeap) _nsRetire(begin);
|
|
1901
|
+
const needsRepair = result === null || result.poison === true || _storeRepairOwed || namespaceOwed;
|
|
1902
|
+
if (!joining && !needsRepair) break;
|
|
1903
|
+
if (namespaceOwed) { namespaceAsked = true; _stats.namespaceRepairs++; }
|
|
1904
|
+
if (needsRepair) {
|
|
1905
|
+
if (result !== null && result.poison === true) _stats.poisons++;
|
|
1906
|
+
_spoilFills();
|
|
1907
|
+
}
|
|
1908
|
+
await (joining ? _residentRepair : _repairPoisonedStore(supervisor, result));
|
|
1909
|
+
// A repair's listing is walked page by page at successive revisions,
|
|
1910
|
+
// so even the barrier that started it asks once more: the delta from
|
|
1911
|
+
// the listing's cursor leaves the namespace exact. Unanswered, it
|
|
1912
|
+
// resumes on the repair's rows as they are, which the listing vouched
|
|
1913
|
+
// for; the namespace then waits for the next barrier.
|
|
1914
|
+
if (!joining) {
|
|
1915
|
+
if (_storeRepairOwed) return [];
|
|
1916
|
+
result = await _acquire(supervisor);
|
|
1917
|
+
if (result === null || result.poison === true) { _nsRetire(begin); return []; }
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
if (joins + 1 >= _REPAIR_JOINS_MAX) {
|
|
1921
|
+
// Every repair this barrier waited on was someone else's. Rather
|
|
1922
|
+
// than resume on rows none of them vouched for as of its own
|
|
1923
|
+
// answer, it drops them and leaves the repair owed: a colder cache,
|
|
1924
|
+
// never a stale byte. Any repair still to finish lists after this.
|
|
1925
|
+
__residentDropDated();
|
|
1926
|
+
_storeRepairOwed = true;
|
|
1927
|
+
__nimbusNoteUnnamedReports();
|
|
1928
|
+
await _awaitReportedOwnWrites();
|
|
1929
|
+
return [];
|
|
1930
|
+
}
|
|
1931
|
+
result = await _acquire(supervisor);
|
|
1932
|
+
}
|
|
1933
|
+
// The store keeps a row of this facet's own bytes over any report, so
|
|
1934
|
+
// the report is noted first: the write or mutation that owns the row
|
|
1935
|
+
// judges it against its own revision when that arrives, as it does for
|
|
1936
|
+
// a heap cell. A read in flight gets the same note.
|
|
1937
|
+
if (Array.isArray(result.paths)) {
|
|
1938
|
+
for (const entry of result.paths) _noteReport(entry);
|
|
1578
1939
|
}
|
|
1940
|
+
// N18: room for what it pushes, before it is held.
|
|
1941
|
+
await __residentRoomForPushed(result);
|
|
1579
1942
|
const applied = __residentAdmit(result);
|
|
1580
|
-
_cursor.epoch =
|
|
1581
|
-
_cursor.rev =
|
|
1943
|
+
_cursor.epoch = applied.cursor.epoch;
|
|
1944
|
+
_cursor.rev = applied.cursor.rev;
|
|
1945
|
+
// A directory that became searchable has descendants no delta names.
|
|
1946
|
+
for (const dir of applied.relist) await __nsRelist(supervisor, dir);
|
|
1947
|
+
_nsRetire(begin);
|
|
1582
1948
|
_stats.invalidations += applied.dropped.length;
|
|
1949
|
+
_stats.selfWrites += applied.kept;
|
|
1950
|
+
_stats.pushes += applied.pushed || 0;
|
|
1951
|
+
await _awaitReportedOwnWrites();
|
|
1583
1952
|
return applied.dropped;
|
|
1584
1953
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Hold the resumption behind a barrier on every own write or mutation a
|
|
1958
|
+
* report is outstanding against, until its acknowledgement says which came
|
|
1959
|
+
* first (__nimbusReportedOwnAcknowledgements). Counted, because the wait is
|
|
1960
|
+
* also paid when the report was the write itself coming back, which the
|
|
1961
|
+
* facet cannot tell apart until the acknowledgement lands.
|
|
1962
|
+
*/
|
|
1963
|
+
async function _awaitReportedOwnWrites() {
|
|
1964
|
+
const acks = __nimbusReportedOwnAcknowledgements();
|
|
1965
|
+
if (acks === null) return;
|
|
1966
|
+
_stats.ownWriteWaits++;
|
|
1967
|
+
await acks;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* Repairs someone else started that one barrier waits on, in a row, before
|
|
1972
|
+
* it stops asking again and drops what it cannot vouch for. A bound on the
|
|
1973
|
+
* barrier's own liveness, not a tuning knob: each join waits a whole repair,
|
|
1974
|
+
* so the fourth means repairs keep being started under it.
|
|
1975
|
+
*/
|
|
1976
|
+
const _REPAIR_JOINS_MAX = 4;
|
|
1977
|
+
|
|
1978
|
+
/**
|
|
1979
|
+
* What this facet asks its ACQUIRE with: its cursor. Built in one place, so
|
|
1980
|
+
* what a delivery is answered from (a long poll sends these,
|
|
1981
|
+
* `__nimbusVfsAcquireArgs`) is exactly what fsAcquire would be asked.
|
|
1982
|
+
*/
|
|
1983
|
+
function _acquireArgs() {
|
|
1984
|
+
// Every build is the start of an answer the overlay may retire against.
|
|
1985
|
+
const begin = ++_barrierBegins;
|
|
1986
|
+
const key = _cursor.epoch + "@" + _cursor.rev;
|
|
1987
|
+
if (!_argsBegins.has(key)) _argsBegins.set(key, begin);
|
|
1988
|
+
const options = _residentStorePresent() && typeof __residentAcquireOptions === "function"
|
|
1989
|
+
? __residentAcquireOptions() : undefined;
|
|
1990
|
+
return options
|
|
1991
|
+
? { epoch: _cursor.epoch, cursor: _cursor.rev, options }
|
|
1992
|
+
: { epoch: _cursor.epoch, cursor: _cursor.rev };
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* The earliest begin of any arguments built at a cursor: a delivered answer
|
|
1996
|
+
* may answer any of them, and retiring against the earliest is the one
|
|
1997
|
+
* that is never too late. Cursors left behind are forgotten.
|
|
1998
|
+
*/
|
|
1999
|
+
const _argsBegins = new Map();
|
|
2000
|
+
function _argsBegin(args) {
|
|
2001
|
+
const key = args.epoch + "@" + args.cursor;
|
|
2002
|
+
const begin = _argsBegins.get(key) ?? 0;
|
|
2003
|
+
for (const k of [..._argsBegins.keys()]) {
|
|
2004
|
+
const [epoch, rev] = [k.slice(0, k.lastIndexOf("@")), Number(k.slice(k.lastIndexOf("@") + 1))];
|
|
2005
|
+
if (epoch !== _cursor.epoch || rev < _cursor.rev) _argsBegins.delete(k);
|
|
2006
|
+
}
|
|
2007
|
+
return begin;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* One ACQUIRE: the authority's answer, or null when there is none — the
|
|
2012
|
+
* call failed past the supervisor's own retries, the host could not serve
|
|
2013
|
+
* it, or the answer carried no cursor. A null is counted where the
|
|
2014
|
+
* coherence stats are read, and is never an empty delta (_acquireBarrier).
|
|
2015
|
+
*/
|
|
2016
|
+
async function _acquire(supervisor) {
|
|
2017
|
+
// Through the RPC helper like every other supervisor call, because it IS
|
|
2018
|
+
// one: the barrier is the first thing an async read issues, and while it
|
|
2019
|
+
// was uncounted __nimbusPendingOps read zero for a whole round trip. A
|
|
2020
|
+
// one-shot facet's event loop, which exits when no handle is live, then
|
|
2021
|
+
// ended the program mid-read — measured as an fs.promises.readFile whose
|
|
2022
|
+
// .then never ran, no error, no output, intermittently, and never when a
|
|
2023
|
+
// pending timer happened to hold the program open.
|
|
2024
|
+
try {
|
|
2025
|
+
const args = _acquireArgs();
|
|
2026
|
+
const result = await __nimbusUseRpcResult(
|
|
2027
|
+
args.options ? supervisor.fsAcquire(args.epoch, args.cursor, args.options) : supervisor.fsAcquire(args.epoch, args.cursor),
|
|
2028
|
+
(r) => r,
|
|
2029
|
+
);
|
|
2030
|
+
if (!result || typeof result.rev !== "number" || typeof result.epoch !== "string") {
|
|
2031
|
+
throw new Error("fsAcquire answered without a cursor");
|
|
1600
2032
|
}
|
|
2033
|
+
return _currentAnswer(result);
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
_stats.barrierFailures++;
|
|
2036
|
+
_stats.lastBarrierFailure = (error && error.message) || String(error);
|
|
2037
|
+
return null;
|
|
1601
2038
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* An answer as it stands against this facet's cursor now.
|
|
2043
|
+
*
|
|
2044
|
+
* Answers can be admitted in another order than they were given: an
|
|
2045
|
+
* answer a long poll delivers was asked for before the barrier that
|
|
2046
|
+
* overtook it. Everything at or below the cursor is admitted already, by
|
|
2047
|
+
* whichever answer took the cursor past it — an answer is complete from
|
|
2048
|
+
* the cursor it was asked from, which is never ahead of the cursor now, and
|
|
2049
|
+
* names each path at its newest revision in its range. So only the entries
|
|
2050
|
+
* above the cursor are news, and the cursor never moves back. An answer in
|
|
2051
|
+
* another epoch, or a poison, is left as it is.
|
|
2052
|
+
*/
|
|
2053
|
+
function _currentAnswer(result) {
|
|
2054
|
+
if (result.poison === true || result.epoch !== _cursor.epoch) return result;
|
|
2055
|
+
if (result.rev <= _cursor.rev) return { ...result, rev: _cursor.rev, paths: [] };
|
|
2056
|
+
const paths = result.paths.filter((entry) => entry.rev > _cursor.rev);
|
|
2057
|
+
return paths.length === result.paths.length ? result : { ...result, paths };
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
/**
|
|
2061
|
+
* The answer a delivery carried (session/rpc.ts _acquireOnDelivery), as it
|
|
2062
|
+
* stands against the cursor now, or null when it cannot stand in for
|
|
2063
|
+
* asking. It can when it answers from a cursor this facet has reached, in
|
|
2064
|
+
* this epoch: everything at or below that cursor is admitted, and the
|
|
2065
|
+
* answer names everything after it up to the moment of delivery. The
|
|
2066
|
+
* barrier asks, as it always has, when there is no answer or it is a
|
|
2067
|
+
* poison, in another epoch, or from a cursor this facet has not reached — a
|
|
2068
|
+
* routed request is answered from the supervisor's own cursor, "nothing
|
|
2069
|
+
* since now", which stands in only for a facet that is already there.
|
|
2070
|
+
*/
|
|
2071
|
+
function _deliveredAnswer(delivered) {
|
|
2072
|
+
if (!delivered || typeof delivered !== "object") return null;
|
|
2073
|
+
const { args, answer } = delivered;
|
|
2074
|
+
if (!args || !answer || answer.poison === true || !Array.isArray(answer.paths)) return null;
|
|
2075
|
+
if (typeof answer.rev !== "number" || answer.epoch !== _cursor.epoch || args.epoch !== _cursor.epoch) return null;
|
|
2076
|
+
if (!(args.cursor <= _cursor.rev)) return null;
|
|
2077
|
+
return _currentAnswer(answer);
|
|
1605
2078
|
}
|
|
1606
2079
|
|
|
1607
2080
|
/**
|
|
@@ -1623,9 +2096,22 @@ const __fsMod = (() => {
|
|
|
1623
2096
|
* This closes staleness of RESIDENT cells across an untrusted resumption.
|
|
1624
2097
|
* A first synchronous touch of a NON-resident path is the separate
|
|
1625
2098
|
* residency floor and is unaffected — it still raises EAGAIN, correctly.
|
|
2099
|
+
*
|
|
2100
|
+
* Two resumptions can barrier at once — a child's output and its exit
|
|
2101
|
+
* arrive together — and both deltas name the same changed path. The first
|
|
2102
|
+
* to land evicts it and starts the refetch; the second finds no row and
|
|
2103
|
+
* would release its callback while that refetch is still in flight, onto
|
|
2104
|
+
* a miss. So a barrier also waits for every refetch still in flight.
|
|
2105
|
+
* It does not read those paths again. A refetch in flight installs its
|
|
2106
|
+
* bytes unless a barrier has since reported the path above the cursor it
|
|
2107
|
+
* was issued under, or a poison spoiled it (_installResident). Only then
|
|
2108
|
+
* does a later barrier read the path itself, and only if nothing holds it
|
|
2109
|
+
* by now. Re-reading every in-flight path at every resumption cost a dev
|
|
2110
|
+
* server 50 reads per request while a peer's refetch of 50 files ran: 500
|
|
2111
|
+
* reads over ten resumptions, and under steady load the set never drained.
|
|
1626
2112
|
*/
|
|
1627
|
-
async function _acquireAndRefetch(supervisor) {
|
|
1628
|
-
const stale = await _acquireBarrier(supervisor);
|
|
2113
|
+
async function _acquireAndRefetch(supervisor, delivered) {
|
|
2114
|
+
const stale = await _acquireBarrier(supervisor, delivered);
|
|
1629
2115
|
// Plus what an own-mutation lease dropped since the last resumption:
|
|
1630
2116
|
// the same debt this function exists to settle, owed by an eviction
|
|
1631
2117
|
// that had no barrier to report it (see _owedRefetch).
|
|
@@ -1633,8 +2119,58 @@ const __fsMod = (() => {
|
|
|
1633
2119
|
for (const k of _owedRefetch) if (stale.indexOf(k) === -1) stale.push(k);
|
|
1634
2120
|
_owedRefetch.clear();
|
|
1635
2121
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
2122
|
+
// Taken before this barrier's own refetches join the map.
|
|
2123
|
+
const inFlight = [..._refetching];
|
|
2124
|
+
const reads = stale.map(_refetch);
|
|
2125
|
+
for (const [k, refetch] of inFlight) {
|
|
2126
|
+
if (stale.indexOf(k) !== -1) continue;
|
|
2127
|
+
if (!(refetch.fill.reported > refetch.fill.rev)) reads.push(refetch.done);
|
|
2128
|
+
else if (!(__vfsBundle && k in __vfsBundle)) reads.push(_refetch(k));
|
|
2129
|
+
}
|
|
2130
|
+
if (reads.length > 0) await Promise.all(reads);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Refetches in flight, per path: the newest read of it, and the fill
|
|
2135
|
+
* ticket that read installs under — the cursor it was issued at, and the
|
|
2136
|
+
* reports noted against it since.
|
|
2137
|
+
*/
|
|
2138
|
+
const _refetching = new Map();
|
|
2139
|
+
|
|
2140
|
+
/**
|
|
2141
|
+
* Read `k` live and install it, behind the barrier just taken. Settles
|
|
2142
|
+
* either way: a refetch that fails leaves the path missing, which the next
|
|
2143
|
+
* read of it answers.
|
|
2144
|
+
*
|
|
2145
|
+
* `read` settles once this read has landed and its install was made or
|
|
2146
|
+
* declined. `done`, which the resumptions wait on, may wait once more. An
|
|
2147
|
+
* install is declined when a barrier reported the path above this read's
|
|
2148
|
+
* cursor, or a poison spoiled it, and settling there would release every
|
|
2149
|
+
* waiter onto the miss it left while something else is already filling
|
|
2150
|
+
* the path: the newer refetch a barrier issued to replace this one, or the
|
|
2151
|
+
* repair that poison started. So `done` waits on what is in flight at the
|
|
2152
|
+
* moment this read settles, that refetch's own read and install and that
|
|
2153
|
+
* repair, and then settles whether or not the path is held. It never
|
|
2154
|
+
* follows a refetch issued after that. A peer that rewrites the path
|
|
2155
|
+
* faster than one read round trip outdates every refetch with the next
|
|
2156
|
+
* resumption's report, and following them held every resumption, including
|
|
2157
|
+
* those that never read the path, until the writer stopped (1528 ms against
|
|
2158
|
+
* 33 ms). A path left unheld is read live by its next read.
|
|
2159
|
+
*/
|
|
2160
|
+
function _refetch(k) {
|
|
2161
|
+
const fill = _beginFill(k);
|
|
2162
|
+
const refetch = { fill, read: null, done: null };
|
|
2163
|
+
refetch.read = _liveReadFile("/" + k, undefined, fill).then(() => {}, () => {}).then(() => {
|
|
2164
|
+
_endFill(fill);
|
|
2165
|
+
if (_refetching.get(k) === refetch) _refetching.delete(k);
|
|
2166
|
+
});
|
|
2167
|
+
refetch.done = refetch.read.then(() => {
|
|
2168
|
+
if (__vfsBundle && k in __vfsBundle) return undefined;
|
|
2169
|
+
const newer = _refetching.get(k);
|
|
2170
|
+
return Promise.all([newer && newer.read, _residentRepair]);
|
|
2171
|
+
});
|
|
2172
|
+
_refetching.set(k, refetch);
|
|
2173
|
+
return refetch.done;
|
|
1638
2174
|
}
|
|
1639
2175
|
|
|
1640
2176
|
/**
|
|
@@ -1646,14 +2182,15 @@ const __fsMod = (() => {
|
|
|
1646
2182
|
* its fetch handler; binding the supervisor when the wrappers are
|
|
1647
2183
|
* installed would silently leave every resident-TUI timer unbarriered.
|
|
1648
2184
|
*
|
|
1649
|
-
* Published on globalThis because the
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
2185
|
+
* Published on globalThis because the other resumptions — an outbound
|
|
2186
|
+
* fetch response, a relayed socket frame, and everything the supervisor
|
|
2187
|
+
* delivers (`__nimbusInboundBarrier`, with the answer it delivered) — are
|
|
2188
|
+
* handed to the program by code outside this closure.
|
|
1652
2189
|
*/
|
|
1653
|
-
async function _resumptionAcquire() {
|
|
2190
|
+
async function _resumptionAcquire(delivered) {
|
|
1654
2191
|
const supervisor = _supervisor();
|
|
1655
2192
|
if (!supervisor || typeof supervisor.fsAcquire !== "function") return;
|
|
1656
|
-
await _acquireAndRefetch(supervisor);
|
|
2193
|
+
await _acquireAndRefetch(supervisor, delivered);
|
|
1657
2194
|
}
|
|
1658
2195
|
|
|
1659
2196
|
/**
|
|
@@ -1687,6 +2224,7 @@ const __fsMod = (() => {
|
|
|
1687
2224
|
if (globalThis.__nimbusResumptionBarriersInstalled) return;
|
|
1688
2225
|
globalThis.__nimbusResumptionBarriersInstalled = true;
|
|
1689
2226
|
globalThis.__nimbusVfsAcquireBarrier = _resumptionAcquire;
|
|
2227
|
+
globalThis.__nimbusVfsAcquireArgs = _acquireArgs;
|
|
1690
2228
|
globalThis.__nimbusVfsReleaseBarrier = _resumptionRelease;
|
|
1691
2229
|
const _setTimeout = globalThis.setTimeout;
|
|
1692
2230
|
const _setInterval = globalThis.setInterval;
|
|
@@ -1794,8 +2332,9 @@ const __fsMod = (() => {
|
|
|
1794
2332
|
// `method` names the supervisor RPC when it differs from the syscall the
|
|
1795
2333
|
// caller reports (lchown rides `chown`, rm rides `fsRemove`).
|
|
1796
2334
|
function _queueStructuralMutation(absPath, syscall, displayPath, rpc, after, method) {
|
|
2335
|
+
const settle = _nsTakeFresh();
|
|
1797
2336
|
const supervisor = _supervisor();
|
|
1798
|
-
if (!supervisor || typeof supervisor[method || syscall] !== "function") return null;
|
|
2337
|
+
if (!supervisor || typeof supervisor[method || syscall] !== "function") { settle(); return null; }
|
|
1799
2338
|
const queued = _hasVfsMutationQueue();
|
|
1800
2339
|
const before = queued && after ? after() : null;
|
|
1801
2340
|
const run = async () => {
|
|
@@ -1811,7 +2350,11 @@ const __fsMod = (() => {
|
|
|
1811
2350
|
);
|
|
1812
2351
|
_markVfsStale();
|
|
1813
2352
|
};
|
|
1814
|
-
|
|
2353
|
+
const out = queued ? __nimbusQueueVfsMutation(absPath, run) : run();
|
|
2354
|
+
// Settled either way: a failed mutation leaves the authority as it was,
|
|
2355
|
+
// which the next barrier's table shows.
|
|
2356
|
+
Promise.resolve(out).then(settle, settle);
|
|
2357
|
+
return out;
|
|
1815
2358
|
}
|
|
1816
2359
|
|
|
1817
2360
|
/**
|
|
@@ -1824,25 +2367,33 @@ const __fsMod = (() => {
|
|
|
1824
2367
|
* effect while the lease still holds the cell, so the stamp the end
|
|
1825
2368
|
* settles describes a cell that already carries the mutation.
|
|
1826
2369
|
*
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
2370
|
+
* The ledger takes the lease on whichever holds the stamp: a heap stamp,
|
|
2371
|
+
* or the resident store's row. Without it a store row the mutation
|
|
2372
|
+
* overlaid would be left as own bytes nobody ever dates, which the barrier
|
|
2373
|
+
* keeps against every later write by anyone.
|
|
2374
|
+
*
|
|
2375
|
+
* From lease to receipt it is an acknowledgement in flight
|
|
2376
|
+
* (__nimbusOwnAcknowledgement): a barrier that reports the path waits for
|
|
2377
|
+
* it, rather than resume on bytes the receipt may yet show a peer outran.
|
|
1829
2378
|
*/
|
|
1830
|
-
|
|
2379
|
+
function _ownMutation(absPath, rpc, apply) {
|
|
1831
2380
|
const key = _strip(absPath);
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2381
|
+
return __nimbusOwnAcknowledgement(key, async () => {
|
|
2382
|
+
const held = __nimbusBeginOwnMutation(key);
|
|
2383
|
+
let receipt;
|
|
2384
|
+
let landed = false;
|
|
2385
|
+
try { receipt = await rpc(); landed = true; }
|
|
2386
|
+
finally {
|
|
2387
|
+
// The local effect follows the mutation landing, not the receipt: the
|
|
2388
|
+
// receipt only settles the stamp, and a supervisor that answers
|
|
2389
|
+
// without one still applied the bytes. The end runs even if applying
|
|
2390
|
+
// throws: a lease left open pins its cell at Infinity, which is the one
|
|
2391
|
+
// state in this protocol that can serve a stale byte forever.
|
|
2392
|
+
try { if (landed && apply) apply(receipt); }
|
|
2393
|
+
finally { __nimbusEndOwnMutation(key, held, receipt); }
|
|
2394
|
+
}
|
|
2395
|
+
return receipt;
|
|
2396
|
+
});
|
|
1846
2397
|
}
|
|
1847
2398
|
|
|
1848
2399
|
// A sync caller has no frame to receive the outcome. The ledger already
|
|
@@ -1877,6 +2428,8 @@ const __fsMod = (() => {
|
|
|
1877
2428
|
"write", absPath,
|
|
1878
2429
|
(result) => result,
|
|
1879
2430
|
),
|
|
2431
|
+
true,
|
|
2432
|
+
true,
|
|
1880
2433
|
);
|
|
1881
2434
|
}
|
|
1882
2435
|
|
|
@@ -1887,13 +2440,18 @@ const __fsMod = (() => {
|
|
|
1887
2440
|
await _flushParkedWrite(absPath, supervisor);
|
|
1888
2441
|
_markVfsStale();
|
|
1889
2442
|
}
|
|
1890
|
-
//
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2443
|
+
// A pending sync chmod rides along with the next flush of the same path.
|
|
2444
|
+
if (_pendingModes.has(k) && typeof supervisor.chmod === "function") {
|
|
2445
|
+
_pendingModes.delete(k);
|
|
2446
|
+
try {
|
|
2447
|
+
await _ownMutation(
|
|
2448
|
+
absPath,
|
|
2449
|
+
() => _fsRpc(supervisor.chmod(absPath, _localModes[k]), "chmod", absPath, (result) => result),
|
|
2450
|
+
);
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
_pendingModes.add(k);
|
|
2453
|
+
throw error;
|
|
2454
|
+
}
|
|
1897
2455
|
_markVfsStale();
|
|
1898
2456
|
}
|
|
1899
2457
|
}
|
|
@@ -1981,8 +2539,11 @@ const __fsMod = (() => {
|
|
|
1981
2539
|
const atime = new Date(Number(meta?.atime || meta?.mtime || Date.now()));
|
|
1982
2540
|
const mode = Number(meta?.mode ?? (isDir ? 0o755 : 0o644));
|
|
1983
2541
|
const stat = _localStatObject(key, isDir, isSymlink, size, mode, meta?.uid, meta?.gid);
|
|
1984
|
-
|
|
1985
|
-
|
|
2542
|
+
// This process's own utimes, until it is reported back, ahead of the
|
|
2543
|
+
// namespace's (_recordLocalTimes).
|
|
2544
|
+
const own = key === undefined ? undefined : _localTimes[key];
|
|
2545
|
+
stat.atime = own && Number.isFinite(own.atimeMs) ? new Date(own.atimeMs) : atime;
|
|
2546
|
+
stat.mtime = own && Number.isFinite(own.mtimeMs) ? new Date(own.mtimeMs) : mtime;
|
|
1986
2547
|
stat.ctime = new Date(Number(meta?.ctime ?? meta?.mtime ?? Date.now()));
|
|
1987
2548
|
stat.birthtime = stat.ctime;
|
|
1988
2549
|
return stat;
|
|
@@ -2015,24 +2576,32 @@ const __fsMod = (() => {
|
|
|
2015
2576
|
// multi-MB file for one RPC frame.
|
|
2016
2577
|
const READ_STREAM_CHUNK_BYTES = 65536;
|
|
2017
2578
|
|
|
2018
|
-
// Ranged reads issued in the same microtask turn travel as ONE batch
|
|
2579
|
+
// Ranged reads issued in the same microtask turn travel as ONE batch, and
|
|
2580
|
+
// so do lstats (_learnLive): one request kind each, one round trip.
|
|
2019
2581
|
//
|
|
2020
2582
|
// A round trip costs an order of magnitude more than the read behind it,
|
|
2021
2583
|
// so a program awaiting reads one at a time pays for round trips and
|
|
2022
2584
|
// nothing else. Nothing here changes what a read sees: every request is
|
|
2023
|
-
// the same live ranged read, executed in order, in the caller's
|
|
2024
|
-
// gather window is one microtask, so it can only capture
|
|
2025
|
-
// had already issued concurrently — a sequential
|
|
2026
|
-
// because it has issued nothing else to batch.
|
|
2585
|
+
// the same live ranged read or lstat, executed in order, in the caller's
|
|
2586
|
+
// turn. The gather window is one microtask, so it can only capture
|
|
2587
|
+
// requests the program had already issued concurrently — a sequential
|
|
2588
|
+
// loop batches nothing because it has issued nothing else to batch.
|
|
2027
2589
|
const READ_BATCH_PATH_LIMIT = 1024;
|
|
2028
2590
|
const READ_BATCH_REQUEST_BYTES = 4194304;
|
|
2029
2591
|
let _openReadBatch = null;
|
|
2030
2592
|
|
|
2031
2593
|
function _queueRangeRead(supervisor, absPath, pos, want) {
|
|
2594
|
+
return _queueBatchRequest(supervisor, { path: absPath, offset: pos, length: want }, want);
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// `request` in the open batch, `bytes` of file content counted against the
|
|
2598
|
+
// batch's bound. Settles with the entry's bytes for a range, its stat for
|
|
2599
|
+
// an lstat.
|
|
2600
|
+
function _queueBatchRequest(supervisor, request, bytes) {
|
|
2032
2601
|
let batch = _openReadBatch;
|
|
2033
2602
|
if (batch && (
|
|
2034
2603
|
batch.requests.length >= READ_BATCH_PATH_LIMIT
|
|
2035
|
-
|| batch.bytes +
|
|
2604
|
+
|| batch.bytes + bytes > READ_BATCH_REQUEST_BYTES
|
|
2036
2605
|
)) {
|
|
2037
2606
|
_openReadBatch = null;
|
|
2038
2607
|
_flushReadBatch(batch);
|
|
@@ -2042,17 +2611,24 @@ const __fsMod = (() => {
|
|
|
2042
2611
|
batch = { supervisor, requests: [], settlers: [], bytes: 0 };
|
|
2043
2612
|
_openReadBatch = batch;
|
|
2044
2613
|
queueMicrotask(() => {
|
|
2045
|
-
|
|
2614
|
+
// A batch a bound closed was flushed then; sending it here too would
|
|
2615
|
+
// read every entry of it twice.
|
|
2616
|
+
if (_openReadBatch !== batch) return;
|
|
2617
|
+
_openReadBatch = null;
|
|
2046
2618
|
_flushReadBatch(batch);
|
|
2047
2619
|
});
|
|
2048
2620
|
}
|
|
2049
|
-
batch.bytes +=
|
|
2050
|
-
batch.requests.push(
|
|
2051
|
-
|
|
2621
|
+
batch.bytes += bytes;
|
|
2622
|
+
batch.requests.push(request);
|
|
2623
|
+
const settler = Promise.withResolvers();
|
|
2624
|
+
batch.settlers.push(settler);
|
|
2625
|
+
return settler.promise;
|
|
2052
2626
|
}
|
|
2053
2627
|
|
|
2054
2628
|
async function _flushReadBatch(batch) {
|
|
2055
|
-
|
|
2629
|
+
// A read round trip is one that carries a range; a batch of learns
|
|
2630
|
+
// (lstat requests) is metadata, which the read count leaves out.
|
|
2631
|
+
if (batch.requests.some((request) => request.lstat !== true)) globalThis.__nimbusFsRpcReads++;
|
|
2056
2632
|
try {
|
|
2057
2633
|
const entries = await __nimbusUseRpcResult(
|
|
2058
2634
|
batch.supervisor.fsReadBatch(batch.requests), (r) => r,
|
|
@@ -2063,6 +2639,8 @@ const __fsMod = (() => {
|
|
|
2063
2639
|
const err = new Error(entry.error.message);
|
|
2064
2640
|
if (entry.error.code) err.code = entry.error.code;
|
|
2065
2641
|
batch.settlers[i].reject(err);
|
|
2642
|
+
} else if (batch.requests[i].lstat === true) {
|
|
2643
|
+
batch.settlers[i].resolve(entry ? entry.stat : null);
|
|
2066
2644
|
} else {
|
|
2067
2645
|
batch.settlers[i].resolve(entry ? entry.bytes : null);
|
|
2068
2646
|
}
|
|
@@ -2127,59 +2705,83 @@ const __fsMod = (() => {
|
|
|
2127
2705
|
return parts;
|
|
2128
2706
|
}
|
|
2129
2707
|
|
|
2130
|
-
async function _liveReadFile(p, opts,
|
|
2708
|
+
async function _liveReadFile(p, opts, refetch) {
|
|
2131
2709
|
const absPath = _resolve(p);
|
|
2132
2710
|
const encoding = typeof opts === "string" ? opts : opts?.encoding;
|
|
2133
2711
|
const supervisor = _supervisor();
|
|
2134
2712
|
if (!supervisor) throw _fsErr("ENOENT", "open", p);
|
|
2135
|
-
//
|
|
2136
|
-
//
|
|
2137
|
-
// that always returns "still R". Every other caller
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2713
|
+
// A refetch (_refetch) has already acquired against a fresh cursor and
|
|
2714
|
+
// holds the fill ticket issued under it, so re-acquiring here would be a
|
|
2715
|
+
// redundant round trip that always returns "still R". Every other caller
|
|
2716
|
+
// acquires, and its fill ticket is its own.
|
|
2717
|
+
if (!refetch) await _acquireBarrier(supervisor);
|
|
2718
|
+
const fill = refetch || _beginFill(_strip(absPath));
|
|
2719
|
+
try {
|
|
2720
|
+
if (typeof supervisor.fsReadRange === "function") {
|
|
2721
|
+
// Chunked: the caller wants the whole file, but nothing upstream has
|
|
2722
|
+
// to hold it all at once to produce it.
|
|
2723
|
+
//
|
|
2724
|
+
// A short chunk is the only signal the file ended, so this walk could
|
|
2725
|
+
// only ever have one chunk in flight — 65 sequential round trips for a
|
|
2726
|
+
// 4 MiB file, each costing far more than the read behind it. The FIRST
|
|
2727
|
+
// chunk still costs exactly one trip and settles it for every file
|
|
2728
|
+
// that fits in one. When it comes back full there is demonstrably
|
|
2729
|
+
// more, and a stat says how much, so the remainder is issued together
|
|
2730
|
+
// and the read batch carries it in a single trip.
|
|
2731
|
+
const parts = [];
|
|
2732
|
+
let total = 0;
|
|
2733
|
+
for (;;) {
|
|
2734
|
+
const chunk = await _readRangeAt(absPath, p, total, READ_STREAM_CHUNK_BYTES);
|
|
2735
|
+
if (chunk === null) break;
|
|
2736
|
+
parts.push(chunk);
|
|
2737
|
+
total += chunk.byteLength;
|
|
2738
|
+
if (chunk.byteLength < READ_STREAM_CHUNK_BYTES) break;
|
|
2739
|
+
if (typeof supervisor.stat !== "function") continue;
|
|
2740
|
+
for (const rest of await _readChunksFrom(absPath, p, supervisor, total)) {
|
|
2741
|
+
parts.push(rest);
|
|
2742
|
+
total += rest.byteLength;
|
|
2743
|
+
}
|
|
2744
|
+
break;
|
|
2163
2745
|
}
|
|
2164
|
-
|
|
2746
|
+
const bytes = parts.length === 1 ? parts[0] : _concatBytes(parts, total);
|
|
2747
|
+
_installResident(absPath, bytes, fill);
|
|
2748
|
+
await _learnLive(absPath, supervisor);
|
|
2749
|
+
return encoding ? _asString(bytes) : __BufferMod.from(bytes);
|
|
2165
2750
|
}
|
|
2166
|
-
const bytes = parts.length === 1 ? parts[0] : _concatBytes(parts, total);
|
|
2167
|
-
_installResident(absPath, bytes);
|
|
2168
|
-
return encoding ? _asString(bytes) : __BufferMod.from(bytes);
|
|
2169
|
-
}
|
|
2170
2751
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2752
|
+
if (typeof supervisor.readFile === "function") {
|
|
2753
|
+
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2754
|
+
const text = await _fsReadRpc(supervisor.readFile(absPath), "open", p, (result) => result);
|
|
2755
|
+
if (text !== null && text !== undefined) {
|
|
2756
|
+
_installResident(absPath, text, fill);
|
|
2757
|
+
await _learnLive(absPath, supervisor);
|
|
2758
|
+
return encoding ? _asString(text) : __BufferMod.from(text);
|
|
2759
|
+
}
|
|
2177
2760
|
}
|
|
2761
|
+
} finally {
|
|
2762
|
+
if (!refetch) _endFill(fill);
|
|
2178
2763
|
}
|
|
2179
2764
|
|
|
2180
2765
|
throw _fsErr("ENOENT", "open", p);
|
|
2181
2766
|
}
|
|
2182
2767
|
|
|
2768
|
+
|
|
2769
|
+
|
|
2770
|
+
/** Paths whose write-back this process issued and the authority accepted. */
|
|
2771
|
+
const _acceptedHere = new Set();
|
|
2772
|
+
globalThis.__nimbusVfsWriteLanded = (key) => {
|
|
2773
|
+
const k = String(key).replace(/^\/+/, "");
|
|
2774
|
+
_acceptedHere.add(k);
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
// The authority refused a parked write (vfs-write-ledger): the record may
|
|
2778
|
+
// be that write's, so it is forgotten and the next access asks the authority.
|
|
2779
|
+
globalThis.__nimbusVfsWriteRefused = (key) => {
|
|
2780
|
+
const k = String(key).replace(/^\/+/, "");
|
|
2781
|
+
_createdHere.delete(k);
|
|
2782
|
+
_acceptedHere.delete(k);
|
|
2783
|
+
};
|
|
2784
|
+
|
|
2183
2785
|
function _concatBytes(parts, total) {
|
|
2184
2786
|
const out = new Uint8Array(total);
|
|
2185
2787
|
let off = 0;
|
|
@@ -2251,24 +2853,11 @@ const __fsMod = (() => {
|
|
|
2251
2853
|
// its shape: the sync existence view must not go on contradicting a
|
|
2252
2854
|
// directory this very process has enumerated. Names only — size, mode
|
|
2253
2855
|
// and ownership are not observable here and are never invented.
|
|
2254
|
-
|
|
2255
|
-
//
|
|
2256
|
-
//
|
|
2257
|
-
//
|
|
2258
|
-
//
|
|
2259
|
-
// recording it invents nothing; file children are left alone, because
|
|
2260
|
-
// their size is not knowable from a listing and must not be guessed.
|
|
2261
|
-
const metadataTable = _metadataTable();
|
|
2262
|
-
if (metadataTable) {
|
|
2263
|
-
for (const entry of entries) {
|
|
2264
|
-
if (entry.type !== "directory") continue;
|
|
2265
|
-
const child = prefix + entry.name;
|
|
2266
|
-
if (child in metadataTable) continue;
|
|
2267
|
-
metadataTable[child] = {
|
|
2268
|
-
type: "directory", size: 0, mode: 0o755, uid: cred.uid, gid: cred.gid,
|
|
2269
|
-
};
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2856
|
+
// The listed directory and its child directories get the authority's
|
|
2857
|
+
// own stat: a later synchronous stat or listing of them needs a record
|
|
2858
|
+
// saying what they are, and a record says who owns them, which a
|
|
2859
|
+
// listing does not. So it is asked for, never assumed (one lstat per
|
|
2860
|
+
// child directory, concurrently).
|
|
2272
2861
|
if (opts?.withFileTypes) {
|
|
2273
2862
|
return entries
|
|
2274
2863
|
.map((entry) => _direntObject(entry.name, entry.type, absPath))
|
|
@@ -2326,6 +2915,8 @@ const __fsMod = (() => {
|
|
|
2326
2915
|
_fsRpc(supervisor.writeFile(absPath, content), "write", p, (result) => result)
|
|
2327
2916
|
);
|
|
2328
2917
|
_markVfsStale();
|
|
2918
|
+
// Accepted: the authority's stat is what the sync view keeps for it.
|
|
2919
|
+
await _learnLive(absPath, supervisor);
|
|
2329
2920
|
}
|
|
2330
2921
|
}
|
|
2331
2922
|
|
|
@@ -2344,6 +2935,7 @@ const __fsMod = (() => {
|
|
|
2344
2935
|
),
|
|
2345
2936
|
);
|
|
2346
2937
|
_markVfsStale();
|
|
2938
|
+
await _learnLive(absPath, supervisor);
|
|
2347
2939
|
}
|
|
2348
2940
|
|
|
2349
2941
|
// The async structural calls ARE the sync ones, awaited: the same local
|
|
@@ -2451,11 +3043,27 @@ const __fsMod = (() => {
|
|
|
2451
3043
|
if (!supervisor && !existsSync(p)) throw _fsErr("ENOENT", syscall, p);
|
|
2452
3044
|
}
|
|
2453
3045
|
|
|
3046
|
+
/**
|
|
3047
|
+
* chmod(2): only the owner or root changes a mode (EPERM). The local
|
|
3048
|
+
* overlay is what statSync reports and what the local permission checks
|
|
3049
|
+
* read, so a mode set here without that check made a root-owned file
|
|
3050
|
+
* writable in the process's own view.
|
|
3051
|
+
*/
|
|
3052
|
+
function _ensureModeOwner(absPath, syscall, p) {
|
|
3053
|
+
if (Number(cred.uid) === 0) return;
|
|
3054
|
+
const stat = _statLadder(absPath);
|
|
3055
|
+
if (stat === undefined) throw _fsErr("ENOENT", syscall, p);
|
|
3056
|
+
if (Number(stat.uid) !== Number(cred.uid)) throw _fsErr("EPERM", syscall, p);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
2454
3059
|
function chmodSync(p, mode) {
|
|
2455
3060
|
if (!existsSync(p)) throw _fsErr("ENOENT", "chmod", p);
|
|
3061
|
+
_ensureModeOwner(_resolve(p), "chmod", p);
|
|
2456
3062
|
// Local-visible immediately (statSync overlay); the live write-through
|
|
2457
3063
|
// rides the next flush of the same path — same fidelity as utimesSync.
|
|
2458
|
-
|
|
3064
|
+
const k = _strip(_resolve(p));
|
|
3065
|
+
_localModes[k] = _coerceMode(mode, "chmod", p);
|
|
3066
|
+
_pendingModes.add(k);
|
|
2459
3067
|
}
|
|
2460
3068
|
|
|
2461
3069
|
async function _chmodAsync(p, mode) {
|
|
@@ -2467,7 +3075,9 @@ const __fsMod = (() => {
|
|
|
2467
3075
|
throw _fsErr("ENOENT", "chmod", p);
|
|
2468
3076
|
}
|
|
2469
3077
|
const m = _coerceMode(mode, "chmod", p);
|
|
3078
|
+
if (localExists) _ensureModeOwner(absPath, "chmod", p);
|
|
2470
3079
|
_localModes[_strip(absPath)] = m;
|
|
3080
|
+
_pendingModes.add(_strip(absPath));
|
|
2471
3081
|
if (supervisor && typeof supervisor.chmod === "function") {
|
|
2472
3082
|
await _flushLocalPathToSupervisor(absPath, supervisor);
|
|
2473
3083
|
_markVfsStale();
|
|
@@ -2519,6 +3129,18 @@ const __fsMod = (() => {
|
|
|
2519
3129
|
}
|
|
2520
3130
|
const nextUid = _coerceId(uid, syscall, p);
|
|
2521
3131
|
const nextGid = _coerceId(gid, syscall, p);
|
|
3132
|
+
// chown(2): root changes anything; the owner may only set the group to
|
|
3133
|
+
// one of its own groups, and never the owner. Checked before the local
|
|
3134
|
+
// row changes: that row is what the local permission checks read.
|
|
3135
|
+
if (Number(cred.uid) !== 0) {
|
|
3136
|
+
const stat = _statLadder(absPath);
|
|
3137
|
+
if (stat === undefined) throw _fsErr("ENOENT", syscall, p);
|
|
3138
|
+
const groups = [Number(cred.gid), ...cred.groups.map(Number)];
|
|
3139
|
+
if (Number(stat.uid) !== Number(cred.uid) || nextUid !== Number(stat.uid)
|
|
3140
|
+
|| (nextGid !== Number(stat.gid) && !groups.includes(nextGid))) {
|
|
3141
|
+
throw _fsErr("EPERM", syscall, p);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
2522
3144
|
const meta = _metadata(absPath);
|
|
2523
3145
|
if (meta) { meta.uid = nextUid; meta.gid = nextGid; }
|
|
2524
3146
|
// The parked write must be registered on the tail BEFORE the chown is
|
|
@@ -2535,21 +3157,25 @@ const __fsMod = (() => {
|
|
|
2535
3157
|
function lchownSync(p, uid, gid) { _detachStructuralMutation(_chownQueued(p, uid, gid, { followSymlinks: false }, "lchown")); }
|
|
2536
3158
|
function lchmodSync(p, mode) { chmodSync(p, mode); }
|
|
2537
3159
|
|
|
3160
|
+
/**
|
|
3161
|
+
* Whether this process may WANT (r=4, w=2, x=1) a path whose stat is
|
|
3162
|
+
* META, by POSIX's owner/group/other rule. Only a stat that states the
|
|
3163
|
+
* owner, group and mode can be judged; one that does not is not judged in
|
|
3164
|
+
* the process's favour: absent fields used to default to mode 0644 and
|
|
3165
|
+
* owner 1000, which made any undescribed file the reader's own.
|
|
3166
|
+
*/
|
|
2538
3167
|
function _modeAllows(meta, want) {
|
|
2539
3168
|
if (want === 0) return true;
|
|
2540
|
-
const mode = Number(meta?.mode
|
|
3169
|
+
const mode = Number(meta?.mode);
|
|
3170
|
+
const uid = Number(meta?.uid);
|
|
3171
|
+
const gid = Number(meta?.gid);
|
|
3172
|
+
if (!Number.isInteger(mode) || !Number.isInteger(uid) || !Number.isInteger(gid)) return false;
|
|
3173
|
+
const bits = mode & 0o777;
|
|
2541
3174
|
const currentUid = Number(cred.uid);
|
|
2542
|
-
|
|
3175
|
+
if (currentUid === 0) return (want & 1) === 0 || (bits & 0o111) !== 0;
|
|
2543
3176
|
const groups = cred.groups.map(Number);
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
return true;
|
|
2547
|
-
}
|
|
2548
|
-
const shift = currentUid === Number(meta?.uid ?? 1000)
|
|
2549
|
-
? 6
|
|
2550
|
-
: (currentGid === Number(meta?.gid ?? 1000) || groups.includes(Number(meta?.gid ?? 1000))) ? 3 : 0;
|
|
2551
|
-
const available = (mode >> shift) & 7;
|
|
2552
|
-
return (available & want) === want;
|
|
3177
|
+
const shift = currentUid === uid ? 6 : (Number(cred.gid) === gid || groups.includes(gid)) ? 3 : 0;
|
|
3178
|
+
return (((bits >> shift) & 7) & want) === want;
|
|
2553
3179
|
}
|
|
2554
3180
|
|
|
2555
3181
|
function _ensureAncestorsTraversable(absPath, syscall, p) {
|
|
@@ -2563,34 +3189,31 @@ const __fsMod = (() => {
|
|
|
2563
3189
|
function _ensureWritable(absPath, syscall, p) {
|
|
2564
3190
|
_ensureAncestorsTraversable(absPath, syscall, p);
|
|
2565
3191
|
const cell = _bundleLookup(absPath);
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
//
|
|
2569
|
-
// the
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
if (
|
|
3192
|
+
const denial = _denialCode(cell);
|
|
3193
|
+
if (denial) throw _fsErr(denial, syscall, p);
|
|
3194
|
+
// Judged on what the authority says of the path (or of a file this
|
|
3195
|
+
// process made), never on the bytes held under its name.
|
|
3196
|
+
const stat = _statLadder(absPath);
|
|
3197
|
+
if (stat !== undefined) {
|
|
3198
|
+
if (!_modeAllows(stat, 2)) throw _fsErr("EACCES", syscall, p);
|
|
2573
3199
|
return;
|
|
2574
3200
|
}
|
|
3201
|
+
// Nothing there: a create, judged by the parent the namespace describes.
|
|
2575
3202
|
|
|
2576
3203
|
const parent = __pathMod.dirname(absPath);
|
|
2577
|
-
const
|
|
2578
|
-
if (
|
|
2579
|
-
if (
|
|
2580
|
-
if (!_modeAllows(
|
|
3204
|
+
const parentStat = _statLadder(parent);
|
|
3205
|
+
if (parentStat !== undefined) {
|
|
3206
|
+
if (!parentStat.isDirectory()) throw _fsErr("ENOTDIR", syscall, p);
|
|
3207
|
+
if (!_modeAllows(parentStat, 3)) throw _fsErr("EACCES", syscall, p);
|
|
2581
3208
|
return;
|
|
2582
3209
|
}
|
|
2583
3210
|
|
|
2584
|
-
const parentKey = _strip(parent);
|
|
2585
|
-
const parentIsLocal = parent === cwd || parent === "/" ||
|
|
2586
|
-
(!!__vfsDirs && parentKey in __vfsDirs) ||
|
|
2587
|
-
(!!__vfsManifest && parentKey in __vfsManifest);
|
|
2588
|
-
if (parentIsLocal || _supervisor()) return;
|
|
2589
3211
|
throw _fsErr("ENOENT", syscall, p);
|
|
2590
3212
|
}
|
|
2591
3213
|
|
|
2592
3214
|
function accessSync(p, mode) {
|
|
2593
3215
|
const absPath = _resolve(p);
|
|
3216
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2594
3217
|
const cell = _bundleLookup(absPath);
|
|
2595
3218
|
const meta = _metadata(absPath);
|
|
2596
3219
|
if (cell === undefined && meta === undefined && !existsSync(p)) throw _fsErr("ENOENT", "access", p);
|
|
@@ -2601,7 +3224,10 @@ const __fsMod = (() => {
|
|
|
2601
3224
|
_ensureAncestorsTraversable(absPath, "access", p);
|
|
2602
3225
|
const denial = _denialCode(cell);
|
|
2603
3226
|
if ((requested & 4) !== 0 && denial) throw _fsErr(denial, "access", p);
|
|
2604
|
-
if (
|
|
3227
|
+
if (requested === 0) return;
|
|
3228
|
+
const stat = _statLadder(absPath);
|
|
3229
|
+
if (stat === undefined) throw _fsErr("ENOENT", "access", p);
|
|
3230
|
+
if (!_modeAllows(stat, requested)) throw _fsErr("EACCES", "access", p);
|
|
2605
3231
|
}
|
|
2606
3232
|
|
|
2607
3233
|
async function _accessAsync(p, mode) {
|
|
@@ -2712,42 +3338,12 @@ const __fsMod = (() => {
|
|
|
2712
3338
|
// ── existsSync ──
|
|
2713
3339
|
function existsSync(p) {
|
|
2714
3340
|
const absPath = _resolve(p);
|
|
2715
|
-
|
|
2716
|
-
if (_metadata(absPath) !== undefined) { _residencySatisfied(absPath); return true; }
|
|
2717
|
-
if (__vfsBundle && k in __vfsBundle) { _residencySatisfied(absPath); return true; }
|
|
2718
|
-
if (__vfsWrites && k in __vfsWrites) { _residencySatisfied(absPath); return true; }
|
|
2719
|
-
if (__vfsDirs && k in __vfsDirs) { _residencySatisfied(absPath); return true; }
|
|
2720
|
-
// W2.5b root-cause fix: consult the manifest BEFORE falling back to
|
|
2721
|
-
// the O(N) bundle-prefix scan. The manifest is uncapped and always
|
|
2722
|
-
// reflects the real directory shape, even when the file content for
|
|
2723
|
-
// a directory's contents was excluded by the 4 MiB / 500-file content
|
|
2724
|
-
if (__vfsManifest) {
|
|
2725
|
-
if (k in __vfsManifest) { _residencySatisfied(absPath); return true; }
|
|
2726
|
-
// Path may be a file listed by its parent's manifest entry.
|
|
2727
|
-
const slash = k.lastIndexOf("/");
|
|
2728
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2729
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2730
|
-
const sib = __vfsManifest[parent];
|
|
2731
|
-
if (sib && sib.indexOf(name) !== -1) { _residencySatisfied(absPath); return true; }
|
|
2732
|
-
}
|
|
2733
|
-
// Last-resort: bundle dir entries
|
|
2734
|
-
if (__vfsBundle && (k in __vfsBundle || __residentAnyUnder(k + "/"))) {
|
|
2735
|
-
_residencySatisfied(absPath);
|
|
2736
|
-
return true;
|
|
2737
|
-
}
|
|
2738
|
-
// Nothing found — which is only an answer if the enclosing directory was
|
|
2739
|
-
// enumerated. Node's existsSync never throws, so the false below is
|
|
2740
|
-
// provisional: the miss is recorded, the listing that settles it is pulled
|
|
2741
|
-
// in, and the exit report fails the run if the path turns out to have been
|
|
2742
|
-
// there all along.
|
|
2743
|
-
if (!_absenceIsKnown(absPath)) {
|
|
2744
|
-
_recordUnmapped(absPath, "access");
|
|
2745
|
-
return false;
|
|
2746
|
-
}
|
|
3341
|
+
_nsRequire("access", p, "fs.promises.access");
|
|
2747
3342
|
_residencySatisfied(absPath);
|
|
2748
|
-
return
|
|
3343
|
+
return _statLadder(absPath) !== undefined;
|
|
2749
3344
|
}
|
|
2750
3345
|
|
|
3346
|
+
|
|
2751
3347
|
// ── statSync ──
|
|
2752
3348
|
function statSync(p, opts) {
|
|
2753
3349
|
const absPath = _resolve(p);
|
|
@@ -2760,16 +3356,12 @@ const __fsMod = (() => {
|
|
|
2760
3356
|
// classify a miss without redoing the resolve and ancestor walk it just
|
|
2761
3357
|
// performed — that path runs on every module-resolution probe.
|
|
2762
3358
|
function _statResolved(absPath, p, opts) {
|
|
3359
|
+
_nsRequire("stat", p, "fs.promises.stat");
|
|
2763
3360
|
const stat = _statLadder(absPath);
|
|
2764
|
-
// An answer settles the path, including the honest "not there"
|
|
2765
|
-
//
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
if (stat !== undefined) { _residencySatisfied(absPath); return stat; }
|
|
2769
|
-
// Nothing in the view describes the path, and there are two very different
|
|
2770
|
-
// reasons for that. Only one of them means the file is not there.
|
|
2771
|
-
if (_absenceIsKnown(absPath)) _residencySatisfied(absPath);
|
|
2772
|
-
else _recordUnmapped(absPath, "stat");
|
|
3361
|
+
// An answer settles the path, including the honest "not there": the
|
|
3362
|
+
// namespace names every path this credential can see.
|
|
3363
|
+
_residencySatisfied(absPath);
|
|
3364
|
+
if (stat !== undefined) return stat;
|
|
2773
3365
|
// Node's statSync honors { throwIfNoEntry: false } by returning undefined
|
|
2774
3366
|
// for a missing path instead of throwing.
|
|
2775
3367
|
if (opts && opts.throwIfNoEntry === false) return undefined;
|
|
@@ -2782,154 +3374,45 @@ const __fsMod = (() => {
|
|
|
2782
3374
|
* MEANS — absence and ignorance are different answers and only one of them
|
|
2783
3375
|
* is a condition a program can act on, so the callers classify.
|
|
2784
3376
|
*/
|
|
2785
|
-
function _statLadder(absPath) {
|
|
3377
|
+
function _statLadder(absPath, noFollow) {
|
|
2786
3378
|
const k = _strip(absPath);
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
const size = _byteLen(__vfsWrites[k]);
|
|
2794
|
-
// Correct only the size — reusing the metadata record keeps the type
|
|
2795
|
-
// and the recorded timestamps stable, so mtime-based change detection
|
|
2796
|
-
// (make, tsc --build, watchers) does not see every call as a change.
|
|
2797
|
-
if (meta) return _statObject({ ...meta, size }, k);
|
|
2798
|
-
return _localStatObject(
|
|
2799
|
-
k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid,
|
|
2800
|
-
);
|
|
2801
|
-
}
|
|
2802
|
-
const metadata = _metadata(absPath);
|
|
2803
|
-
if (metadata) return _statObject(metadata, k);
|
|
2804
|
-
// Check if it's a known directory written this exec session
|
|
2805
|
-
if (__vfsDirs && k in __vfsDirs) {
|
|
2806
|
-
return _localStatObject(k, true, false, 0, 0o777 & ~__processUmask, cred.uid, cred.gid);
|
|
2807
|
-
}
|
|
2808
|
-
// W2.5b: consult uncapped manifest first for directory shape.
|
|
2809
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2810
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2811
|
-
}
|
|
2812
|
-
// File with content embedded?
|
|
2813
|
-
const content = _bundleLookup(absPath);
|
|
2814
|
-
if (content !== undefined) {
|
|
2815
|
-
// _byteLen handles both string (UTF-8 encode) and Uint8Array
|
|
2816
|
-
// (byteLength) — fixes binary writes from reporting the
|
|
2817
|
-
// post-corruption byte count.
|
|
2818
|
-
const size = _byteLen(content);
|
|
2819
|
-
return _localStatObject(k, false, false, size, 0o666 & ~__processUmask, cred.uid, cred.gid);
|
|
2820
|
-
}
|
|
2821
|
-
// File listed in parent's manifest but content was capped out — return
|
|
2822
|
-
// a zero-size file stat so callers like fs.stat / fs.statSync see the
|
|
2823
|
-
// file as present (downstream readFileSync will surface ENOENT if it
|
|
2824
|
-
// actually tries to read content; many consumers only need stat).
|
|
2825
|
-
if (__vfsManifest) {
|
|
2826
|
-
const slash = k.lastIndexOf("/");
|
|
2827
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
2828
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
2829
|
-
const sib = __vfsManifest[parent];
|
|
2830
|
-
if (sib && sib.indexOf(name) !== -1) {
|
|
2831
|
-
return _localStatObject(k, false, false, 0, 0o644, cred.uid, cred.gid);
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
// Last-resort: bundle prefix scan (legacy path).
|
|
2835
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) {
|
|
2836
|
-
return _localStatObject(k, true, false, 0, 0o755, cred.uid, cred.gid);
|
|
2837
|
-
}
|
|
2838
|
-
return undefined;
|
|
3379
|
+
_nsRequire("stat", absPath, "fs.promises.stat");
|
|
3380
|
+
const meta = _nsMeta(k, !noFollow);
|
|
3381
|
+
if (meta === "absent" || meta === "ELOOP") return undefined;
|
|
3382
|
+
return meta.own
|
|
3383
|
+
? _localStatObject(k, meta.type === "directory", false, meta.size, meta.mode & 0o7777, meta.uid, meta.gid)
|
|
3384
|
+
: _statObject(meta, k);
|
|
2839
3385
|
}
|
|
2840
3386
|
|
|
2841
3387
|
// ── lstatSync (alias for statSync in our VFS — no symlinks) ──
|
|
2842
|
-
function lstatSync(p, opts) {
|
|
3388
|
+
function lstatSync(p, opts) {
|
|
3389
|
+
const absPath = _resolve(p);
|
|
3390
|
+
_nsRequire("lstat", p, "fs.promises.lstat");
|
|
3391
|
+
_ensureAncestorsTraversable(absPath, "lstat", p);
|
|
3392
|
+
const stat = _statLadder(absPath, true);
|
|
3393
|
+
if (stat !== undefined) return stat;
|
|
3394
|
+
if (opts && opts.throwIfNoEntry === false) return undefined;
|
|
3395
|
+
throw _fsErr("ENOENT", "lstat", p);
|
|
3396
|
+
}
|
|
2843
3397
|
|
|
2844
3398
|
// ── readdirSync ──
|
|
2845
|
-
//
|
|
2846
|
-
//
|
|
2847
|
-
// not in the manifest (e.g. dirs created at exec time via mkdirSync) and
|
|
2848
|
-
// is unioned with __vfsWrites so newly-written files become visible.
|
|
3399
|
+
// The namespace's listing of the directory, with this process's own
|
|
3400
|
+
// structural effects over it (_nsList).
|
|
2849
3401
|
function readdirSync(p, opts) {
|
|
2850
3402
|
const absPath = _resolve(p);
|
|
3403
|
+
_nsRequire("scandir", p, "fs.promises.readdir");
|
|
2851
3404
|
_ensureAncestorsTraversable(absPath, "scandir", p);
|
|
2852
3405
|
const metadata = _metadata(absPath);
|
|
2853
3406
|
if (metadata && !_modeAllows(metadata, 4)) throw _fsErr("EACCES", "scandir", p);
|
|
2854
3407
|
const k = _strip(absPath);
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
// enumerated, or one this process created, can honestly be listed.
|
|
2859
|
-
if (!_dirEnumerated(k) && !_bundleHasChildren(k)) {
|
|
2860
|
-
// An authority exists that this call cannot reach, so an empty array
|
|
2861
|
-
// would be a claim about a directory nothing ever opened. Refuse, and
|
|
2862
|
-
// pull the listing in for the next one.
|
|
2863
|
-
//
|
|
2864
|
-
// This is the one caller that keeps refusing. Everywhere else the sync
|
|
2865
|
-
// view answers a path it cannot map with the not-found the program was
|
|
2866
|
-
// written to handle, provisionally, and lets the exit report settle
|
|
2867
|
-
// whether that answer was true. A listing has no such answer to give:
|
|
2868
|
-
// "no config yet, write one" is an idiom every program implements, and
|
|
2869
|
-
// there is no counterpart for a directory — a caller handed ENOENT for a
|
|
2870
|
-
// template tree has nowhere to go but the same wrong branch an empty
|
|
2871
|
-
// array sent it down, and the array at least it could not detect.
|
|
2872
|
-
if (_supervisor()) {
|
|
2873
|
-
throw _refuseUnmapped(absPath, p, "scandir", "fs.promises.readdir");
|
|
2874
|
-
}
|
|
2875
|
-
// No authority: the staged tables are the whole filesystem. A path they
|
|
2876
|
-
// do not describe is missing, and one they describe with no children is
|
|
2877
|
-
// genuinely empty.
|
|
2878
|
-
if (_statLadder(absPath) === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
2879
|
-
}
|
|
2880
|
-
const prefix = k ? k + "/" : "";
|
|
2881
|
-
const names = new Set();
|
|
2882
|
-
// 1. Manifest-supplied children (the authoritative source for installed pkgs).
|
|
2883
|
-
if (__vfsManifest && k in __vfsManifest) {
|
|
2884
|
-
for (const n of __vfsManifest[k]) names.add(n);
|
|
2885
|
-
}
|
|
2886
|
-
// 2. Bundle-prefix fallback (covers older paths or runtime-mkdir'd ones
|
|
2887
|
-
// that aren't in the manifest yet).
|
|
2888
|
-
if (__vfsBundle) {
|
|
2889
|
-
for (const bk of __residentUnder(prefix)) {
|
|
2890
|
-
const seg = bk.substring(prefix.length).split("/")[0];
|
|
2891
|
-
if (seg) names.add(seg);
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
// 3. Files written during this exec session.
|
|
2895
|
-
if (__vfsWrites) {
|
|
2896
|
-
for (const wk in __vfsWrites) {
|
|
2897
|
-
if (wk.startsWith(prefix)) {
|
|
2898
|
-
const rest = wk.substring(prefix.length);
|
|
2899
|
-
const seg = rest.split("/")[0];
|
|
2900
|
-
if (seg) names.add(seg);
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
// 4. Dirs created during this exec session.
|
|
2905
|
-
if (__vfsDirs) {
|
|
2906
|
-
for (const dk in __vfsDirs) {
|
|
2907
|
-
if (dk.startsWith(prefix)) {
|
|
2908
|
-
const rest = dk.substring(prefix.length);
|
|
2909
|
-
const seg = rest.split("/")[0];
|
|
2910
|
-
if (seg) names.add(seg);
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
3408
|
+
const st = _statLadder(absPath);
|
|
3409
|
+
if (st === undefined) throw _fsErr("ENOENT", "scandir", p);
|
|
3410
|
+
if (!st.isDirectory()) throw _fsErr("ENOTDIR", "scandir", p);
|
|
2914
3411
|
_residencySatisfied(absPath);
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
// Manifest is the definitive isDir source; fall back to bundle scan.
|
|
2920
|
-
// The metadata record sits between them: a directory that was listed
|
|
2921
|
-
// but never walked has no manifest entry of its own, and calling it a
|
|
2922
|
-
// file is how a template tree full of subdirectories reads back as a
|
|
2923
|
-
// tree full of nothing.
|
|
2924
|
-
const isDir =
|
|
2925
|
-
(!!__vfsManifest && fp in __vfsManifest) ||
|
|
2926
|
-
(!!__vfsDirs && fp in __vfsDirs) ||
|
|
2927
|
-
_metadata(fp)?.type === "directory" ||
|
|
2928
|
-
(!!__vfsBundle && __residentAnyUnder(fp + "/"));
|
|
2929
|
-
return _direntObject(n, isDir ? "directory" : "file", absPath);
|
|
2930
|
-
});
|
|
2931
|
-
}
|
|
2932
|
-
return arr;
|
|
3412
|
+
const listed = _nsList(k);
|
|
3413
|
+
const sorted = [...listed.keys()].sort();
|
|
3414
|
+
if (!opts?.withFileTypes) return sorted;
|
|
3415
|
+
return sorted.map((n) => _direntObject(n, listed.get(n), absPath));
|
|
2933
3416
|
}
|
|
2934
3417
|
|
|
2935
3418
|
// ── mkdirSync ──
|
|
@@ -2943,11 +3426,16 @@ const __fsMod = (() => {
|
|
|
2943
3426
|
if (opts?.recursive) {
|
|
2944
3427
|
const parts = k.split("/").filter(Boolean);
|
|
2945
3428
|
let cur = "";
|
|
2946
|
-
for (const part of parts) { cur = cur ? cur + "/" + part : part; __vfsDirs[cur] = true; created.push(cur); }
|
|
3429
|
+
for (const part of parts) { cur = cur ? cur + "/" + part : part; _noteCreation(cur); __vfsDirs[cur] = true; created.push(cur); }
|
|
2947
3430
|
} else {
|
|
3431
|
+
_noteCreation(k);
|
|
2948
3432
|
__vfsDirs[k] = true;
|
|
2949
3433
|
created.push(k);
|
|
2950
3434
|
}
|
|
3435
|
+
for (const dir of created) {
|
|
3436
|
+
const was = _nsMeta(dir, true);
|
|
3437
|
+
if (was === "absent") _nsOwnSet(dir, "dir", { hide: true });
|
|
3438
|
+
}
|
|
2951
3439
|
const queued = _queueStructuralMutation(absPath, "mkdir", p, (supervisor) => supervisor.mkdir(absPath));
|
|
2952
3440
|
// Told, not merely known locally: _announceLocalDirs must not issue a
|
|
2953
3441
|
// second mkdir for a directory whose own RPC is already in the queue.
|
|
@@ -2957,9 +3445,30 @@ const __fsMod = (() => {
|
|
|
2957
3445
|
function mkdirSync(p, opts) { _detachStructuralMutation(_mkdirQueued(p, opts)); }
|
|
2958
3446
|
|
|
2959
3447
|
// ── unlinkSync ──
|
|
3448
|
+
/**
|
|
3449
|
+
* unlink(2)/rmdir(2)/rename(2) of an existing name: the parent must be
|
|
3450
|
+
* writable and searchable, and in a sticky directory (/tmp, 1777) only the
|
|
3451
|
+
* file's owner, the directory's owner or root may remove it. Checked before
|
|
3452
|
+
* the local view changes, so a refusal leaves the process's view intact. A
|
|
3453
|
+
* name whose owner this view does not know is refused as a miss.
|
|
3454
|
+
*/
|
|
3455
|
+
function _ensureRemovable(absPath, syscall, p) {
|
|
3456
|
+
_ensureAncestorsTraversable(absPath, syscall, p);
|
|
3457
|
+
const k = _strip(absPath);
|
|
3458
|
+
const parent = _statLadder(__pathMod.dirname(absPath));
|
|
3459
|
+
if (parent === undefined) return;
|
|
3460
|
+
if (!_modeAllows(parent, 3)) throw _fsErr("EACCES", syscall, p);
|
|
3461
|
+
if ((Number(parent.mode) & 0o1000) === 0 || Number(cred.uid) === 0 || Number(parent.uid) === Number(cred.uid)) return;
|
|
3462
|
+
if (_createdHere.has(k)) return;
|
|
3463
|
+
const target = _statLadder(absPath, true);
|
|
3464
|
+
if (target === undefined) return;
|
|
3465
|
+
if (Number(target.uid) !== Number(cred.uid)) throw _fsErr("EPERM", syscall, p);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
2960
3468
|
function _unlinkQueued(p) {
|
|
2961
3469
|
const absPath = _resolve(p);
|
|
2962
3470
|
const k = _strip(absPath);
|
|
3471
|
+
_ensureRemovable(absPath, "unlink", p);
|
|
2963
3472
|
if (__vfsBundle) delete __vfsBundle[k];
|
|
2964
3473
|
if (__vfsWrites) delete __vfsWrites[k];
|
|
2965
3474
|
_forgetSyncPath(k);
|
|
@@ -2971,6 +3480,7 @@ const __fsMod = (() => {
|
|
|
2971
3480
|
function _rmdirQueued(p) {
|
|
2972
3481
|
const absPath = _resolve(p);
|
|
2973
3482
|
const k = _strip(absPath);
|
|
3483
|
+
_ensureRemovable(absPath, "rmdir", p);
|
|
2974
3484
|
if (__vfsDirs) delete __vfsDirs[k];
|
|
2975
3485
|
_forgetSyncPath(k);
|
|
2976
3486
|
return _queueStructuralMutation(
|
|
@@ -2985,21 +3495,29 @@ const __fsMod = (() => {
|
|
|
2985
3495
|
function _renameQueued(oldP, newP) {
|
|
2986
3496
|
const oldAbs = _resolve(oldP);
|
|
2987
3497
|
const newAbs = _resolve(newP);
|
|
3498
|
+
// The name leaves its directory and lands in another (replacing what is
|
|
3499
|
+
// there): both are removals by POSIX's rule.
|
|
3500
|
+
_ensureRemovable(oldAbs, "rename", oldP);
|
|
3501
|
+
if (_statLadder(newAbs, true) !== undefined) _ensureRemovable(newAbs, "rename", newP);
|
|
3502
|
+
else _ensureWritable(newAbs, "rename", newP);
|
|
2988
3503
|
const oldK = _strip(oldAbs);
|
|
2989
3504
|
const newK = _strip(newAbs);
|
|
3505
|
+
// The table still holds the old name until the rename is reported, so the
|
|
3506
|
+
// new name reads through to it and the old one reads as gone.
|
|
3507
|
+
const nsFrom = _nsRealKey(oldK);
|
|
3508
|
+
// What this process created travels with the name; what it did not stays
|
|
3509
|
+
// the authority's, whatever the move does to the local tables.
|
|
3510
|
+
const oldPrefix = oldK + "/";
|
|
3511
|
+
const createdMoved = [..._createdHere]
|
|
3512
|
+
.filter((key) => key === oldK || key.startsWith(oldPrefix))
|
|
3513
|
+
.map((key) => newK + key.slice(oldK.length));
|
|
2990
3514
|
const content = __vfsBundle?.[oldK] ?? __vfsWrites?.[oldK];
|
|
2991
3515
|
if (content !== undefined) {
|
|
2992
3516
|
_parkWrite(newK, content);
|
|
2993
3517
|
if (__vfsBundle) delete __vfsBundle[oldK];
|
|
2994
3518
|
delete __vfsBundleRevisions[oldK];
|
|
2995
3519
|
delete __vfsWrites[oldK];
|
|
2996
|
-
// The stat record travels with the content: dropping it would lose the
|
|
2997
|
-
// mode/ownership, and leaving it behind would keep the source path
|
|
2998
|
-
// reporting as an existing file this rename already moved away.
|
|
2999
|
-
const metadata = _metadataTable();
|
|
3000
|
-
const moved = metadata ? metadata[oldK] : undefined;
|
|
3001
3520
|
_forgetSyncPath(oldK);
|
|
3002
|
-
if (moved) metadata[newK] = moved;
|
|
3003
3521
|
} else if (__vfsDirs && oldK in __vfsDirs) {
|
|
3004
3522
|
// A directory this process made travels under its new name, so the
|
|
3005
3523
|
// sync view stops listing the old one and _announceLocalDirs cannot
|
|
@@ -3014,6 +3532,16 @@ const __fsMod = (() => {
|
|
|
3014
3532
|
}
|
|
3015
3533
|
_forgetSyncTree(oldK);
|
|
3016
3534
|
}
|
|
3535
|
+
_forgetCreation(oldK);
|
|
3536
|
+
_forgetCreation(newK);
|
|
3537
|
+
for (const key of createdMoved) _createdHere.add(key);
|
|
3538
|
+
for (const [ok, entry] of [..._nsOwn]) {
|
|
3539
|
+
if ((ok === oldK || ok.startsWith(oldPrefix)) && entry.state === "dir") {
|
|
3540
|
+
_nsOwnSet(newK + ok.slice(oldK.length), "dir", { hide: entry.hide });
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
if (nsFrom !== null && !_nsOwn.has(newK)) _nsOwnSet(newK, "alias", { from: nsFrom });
|
|
3544
|
+
_nsOwnSet(oldK, "absentTree");
|
|
3017
3545
|
const queued = _queueStructuralMutation(
|
|
3018
3546
|
oldAbs, "rename", oldP,
|
|
3019
3547
|
(supervisor) => supervisor.rename(oldAbs, newAbs),
|
|
@@ -3180,7 +3708,16 @@ const __fsMod = (() => {
|
|
|
3180
3708
|
// Sync realpath stays local and identity-resolves. Async symlink
|
|
3181
3709
|
// operations use the live supervisor bridge below.
|
|
3182
3710
|
// .native static is required by TypeScript's getNodeSystem at
|
|
3183
|
-
function realpathSync(p, opts) {
|
|
3711
|
+
function realpathSync(p, opts) {
|
|
3712
|
+
const absPath = _resolve(String(p));
|
|
3713
|
+
_nsRequire("realpath", p, "fs.promises.realpath");
|
|
3714
|
+
const k = _strip(absPath);
|
|
3715
|
+
const meta = _nsMeta(k, true);
|
|
3716
|
+
if (meta === "ELOOP") throw _fsErr("ELOOP", "realpath", p);
|
|
3717
|
+
if (meta === "absent") throw _fsErr("ENOENT", "realpath", p);
|
|
3718
|
+
const real = _nsRealKey(k);
|
|
3719
|
+
return real === null ? absPath : "/" + real;
|
|
3720
|
+
}
|
|
3184
3721
|
realpathSync.native = realpathSync;
|
|
3185
3722
|
|
|
3186
3723
|
// ── Async variants (thin wrappers returning via callback) ──
|
|
@@ -3208,6 +3745,11 @@ const __fsMod = (() => {
|
|
|
3208
3745
|
_mkdirAsync(p, opts).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); });
|
|
3209
3746
|
}
|
|
3210
3747
|
function unlink(p, cb) { _unlinkAsync(p).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3748
|
+
// The callback form proper-lockfile (via graceful-fs) releases its lock directory with.
|
|
3749
|
+
function rmdir(p, opts, cb) {
|
|
3750
|
+
if (typeof opts === "function") { cb = opts; opts = undefined; }
|
|
3751
|
+
_rmdirAsync(p).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); });
|
|
3752
|
+
}
|
|
3211
3753
|
function rename(oldP, newP, cb) { _renameAsync(oldP, newP).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3212
3754
|
function utimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
3213
3755
|
function lutimes(p, atime, mtime, cb) { _utimesAsync(p, atime, mtime, { followSymlinks: false }).then(() => { if (cb) cb(null); }).catch((e) => { if (cb) cb(e); }); }
|
|
@@ -3264,11 +3806,8 @@ const __fsMod = (() => {
|
|
|
3264
3806
|
// Non-resident and non-empty: writing onto a zero-filled base would
|
|
3265
3807
|
// silently destroy the bytes we cannot see. Refuse instead.
|
|
3266
3808
|
if (st !== undefined && st.size !== 0) throw _notResidentError(absPath, p, syscall, asyncForm);
|
|
3267
|
-
// Absent or empty
|
|
3268
|
-
//
|
|
3269
|
-
// this process cannot see, and an O_APPEND descriptor onto a zero-filled
|
|
3270
|
-
// base would overwrite the file with the fragment it meant to add.
|
|
3271
|
-
if (st === undefined && !_absenceIsKnown(absPath)) throw _refuseUnmapped(absPath, p, syscall, asyncForm);
|
|
3809
|
+
// Absent or empty: an empty base is the true prior content (the namespace
|
|
3810
|
+
// names every path this credential can see).
|
|
3272
3811
|
return new Uint8Array(0);
|
|
3273
3812
|
}
|
|
3274
3813
|
|
|
@@ -3531,6 +4070,10 @@ const __fsMod = (() => {
|
|
|
3531
4070
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3532
4071
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3533
4072
|
let size = liveMeta ? (Number(liveMeta.size) || 0) : (localStat ? localStat.size : 0);
|
|
4073
|
+
// The authority answered, so what it said is what the sync view keeps:
|
|
4074
|
+
// its stat when something is there, and when nothing is, the file this
|
|
4075
|
+
// open creates is the process's own.
|
|
4076
|
+
if (!liveMeta && supervisor && !localStat) _createdHere.add(_strip(absPath));
|
|
3534
4077
|
if (!exists) {
|
|
3535
4078
|
await _writeFileAsync(path, new Uint8Array(0));
|
|
3536
4079
|
if (mode !== undefined && mode !== null) {
|
|
@@ -3555,6 +4098,7 @@ const __fsMod = (() => {
|
|
|
3555
4098
|
if (!exists && !fl.create) throw _fsErr("ENOENT", "open", path);
|
|
3556
4099
|
if (exists && fl.create && fl.exclusive) throw _fsErr("EEXIST", "open", path);
|
|
3557
4100
|
if (fl.write || !exists) _ensureWritable(absPath, "open", path);
|
|
4101
|
+
if (!exists) _noteCreation(_strip(absPath));
|
|
3558
4102
|
let size = exists ? st.size : 0;
|
|
3559
4103
|
// O_TRUNC means "make it empty", so zeroing is the requested semantic,
|
|
3560
4104
|
// and creating a genuinely absent file is too. O_APPEND must NEVER
|
|
@@ -3683,7 +4227,10 @@ const __fsMod = (() => {
|
|
|
3683
4227
|
function fchmodSync(fd, mode) {
|
|
3684
4228
|
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchmod", fd);
|
|
3685
4229
|
const handle = _fdHandle(fd, "fchmod");
|
|
3686
|
-
|
|
4230
|
+
const k = _strip(handle._abs);
|
|
4231
|
+
_ensureModeOwner(handle._abs, "fchmod", handle._path);
|
|
4232
|
+
_localModes[k] = _coerceMode(mode, "fchmod", handle._path);
|
|
4233
|
+
_pendingModes.add(k);
|
|
3687
4234
|
}
|
|
3688
4235
|
function fchownSync(fd, uid, gid) {
|
|
3689
4236
|
if (_isStdioFd(fd)) throw _fsErr("EINVAL", "fchown", fd);
|
|
@@ -4023,15 +4570,16 @@ const __fsMod = (() => {
|
|
|
4023
4570
|
// not just the local cache — is required so a subsequent async fs op
|
|
4024
4571
|
// (e.g. fs.promises.rename of a copied file, as create-cloudflare
|
|
4025
4572
|
// does for __dot__gitignore) sees the copy in the VFS instead of
|
|
4026
|
-
// ENOENT.
|
|
4027
|
-
|
|
4573
|
+
// ENOENT. Each directory is made as mkdir makes it, so the namespace's
|
|
4574
|
+
// overlay lists it for the writes beneath it.
|
|
4575
|
+
await _mkdirAsync("/" + destK, { recursive: true });
|
|
4028
4576
|
const walk = async (relDir) => {
|
|
4029
4577
|
const absDir = relDir ? srcAbs + "/" + relDir : srcAbs;
|
|
4030
4578
|
const ents = await _readdirAsync(absDir, { withFileTypes: true });
|
|
4031
4579
|
for (const ent of ents) {
|
|
4032
4580
|
const rel = relDir ? relDir + "/" + ent.name : ent.name;
|
|
4033
4581
|
if (ent.isDirectory && ent.isDirectory()) {
|
|
4034
|
-
|
|
4582
|
+
await _mkdirAsync("/" + destK + "/" + rel, { recursive: true });
|
|
4035
4583
|
await walk(rel);
|
|
4036
4584
|
} else {
|
|
4037
4585
|
await _writeFileAsync("/" + destK + "/" + rel, await _readFileAsync(srcAbs + "/" + rel));
|
|
@@ -4204,7 +4752,7 @@ const __fsMod = (() => {
|
|
|
4204
4752
|
openSync, closeSync, readSync, writeSync, fstatSync, ftruncateSync, fsyncSync, fdatasyncSync,
|
|
4205
4753
|
futimesSync, fchmodSync, fchownSync, readvSync, writevSync,
|
|
4206
4754
|
open, close, read, write, fstat, ftruncate, fsync, fdatasync, fchmod, futimes, readv, writev,
|
|
4207
|
-
readFile, writeFile, appendFile, stat, lstat, readdir, exists, mkdir, unlink, rename, utimes, lutimes, chmod, lchmod, chown, lchown, fchown, access,
|
|
4755
|
+
readFile, writeFile, appendFile, stat, lstat, readdir, exists, mkdir, unlink, rmdir, rename, utimes, lutimes, chmod, lchmod, chown, lchown, fchown, access,
|
|
4208
4756
|
rm, cp, truncate, copyFile, mkdtemp, link, symlink, readlink, realpath, opendir, statfs,
|
|
4209
4757
|
Dirent: __Dirent,
|
|
4210
4758
|
Dir: __Dir,
|
|
@@ -4381,8 +4929,7 @@ const __NimbusRelayedWebSocket = (() => {
|
|
|
4381
4929
|
// The barrier the whole relay exists for. Every frame is now a
|
|
4382
4930
|
// supervisor reply, so it can carry the invalidation delta, and
|
|
4383
4931
|
// user code runs only after the resident set has caught up.
|
|
4384
|
-
|
|
4385
|
-
if (typeof acquire === "function") await acquire();
|
|
4932
|
+
await __nimbusInboundBarrier();
|
|
4386
4933
|
this._deliver(event);
|
|
4387
4934
|
}
|
|
4388
4935
|
}
|
|
@@ -6138,9 +6685,14 @@ const __undiciMod = (() => {
|
|
|
6138
6685
|
// ── util module ────────────────────────────────────────────────────
|
|
6139
6686
|
// ═══════════════════════════════════════════════════════════════════════
|
|
6140
6687
|
const __utilMod = {
|
|
6141
|
-
inspect: (o, opts) => {
|
|
6142
|
-
|
|
6143
|
-
|
|
6688
|
+
inspect: (o, opts) => {
|
|
6689
|
+
if (o !== null && typeof o === "object" && __isErrorValue(o)) return __fmtError(o, new Set());
|
|
6690
|
+
try { return JSON.stringify(o, null, 2); } catch { return String(o); }
|
|
6691
|
+
},
|
|
6692
|
+
format: (...args) => {
|
|
6693
|
+
if (args.length === 0) return "";
|
|
6694
|
+
const [fmt, ...a] = args;
|
|
6695
|
+
if (typeof fmt !== "string") return args.map(__fmt).join(" ");
|
|
6144
6696
|
let i = 0;
|
|
6145
6697
|
return fmt.replace(/%[sdifjoO%]/g, (m) => {
|
|
6146
6698
|
if (m === "%%") return "%";
|
|
@@ -6149,7 +6701,7 @@ const __utilMod = {
|
|
|
6149
6701
|
if (m === "%s") return String(v);
|
|
6150
6702
|
if (m === "%d" || m === "%i" || m === "%f") return Number(v).toString();
|
|
6151
6703
|
if (m === "%j") { try { return JSON.stringify(v); } catch { return "[Circular]"; } }
|
|
6152
|
-
if (m === "%o" || m === "%O")
|
|
6704
|
+
if (m === "%o" || m === "%O") return __utilMod.inspect(v);
|
|
6153
6705
|
return String(v);
|
|
6154
6706
|
}) + (i < a.length ? " " + a.slice(i).map(__fmt).join(" ") : "");
|
|
6155
6707
|
},
|
|
@@ -6416,11 +6968,31 @@ const __utilMod = {
|
|
|
6416
6968
|
// (set+restored by __loadModule per call). When set, the fallback becomes
|
|
6417
6969
|
// "file:///" + __currentModulePath so relative URLs resolve against
|
|
6418
6970
|
// the real on-VFS module location — restoring proper import.meta.url
|
|
6971
|
+
//
|
|
6972
|
+
// The leniency is scoped to calls that PASSED a base. `new URL(x)` with one
|
|
6973
|
+
// argument is Node's strict absolute-URL parse: it throws
|
|
6974
|
+
// TypeError [ERR_INVALID_URL] for anything that is not already a URL, and
|
|
6975
|
+
// that throw is load-bearing rather than incidental. Node's own ESM
|
|
6976
|
+
// resolver — and every vendored copy of it, including exsolve, which is what
|
|
6977
|
+
// `nuxt dev` resolves `@nuxt/kit` and `nuxt` with — spells the
|
|
6978
|
+
// bare-specifier test as
|
|
6979
|
+
// try { resolved = new URL(specifier); } catch { packageResolve(…); }
|
|
6980
|
+
// Swallowing the throw made `new URL("@nuxt/kit")` answer
|
|
6981
|
+
// file:///@nuxt/kit, so moduleResolve never reached packageResolve and
|
|
6982
|
+
// finalizeResolution stat'd /@nuxt/kit — the FILESYSTEM ROOT — instead of
|
|
6983
|
+
// walking <from>/node_modules. Two syscalls and every bare import in the
|
|
6984
|
+
// project was unresolvable. The bundler breakage this wrapper exists for
|
|
6985
|
+
// (`new URL(rel, import.meta.url)` where the rolldown/esbuild polyfill
|
|
6986
|
+
// reduced import.meta.url to null/undefined) is always a two-argument call,
|
|
6987
|
+
// so requiring the base argument keeps that fix and restores Node's
|
|
6988
|
+
// contract for the one-argument form. URL.canParse, which is bound
|
|
6989
|
+
// straight off the native constructor below, already answered false for
|
|
6990
|
+
// these strings; the constructor now agrees with it.
|
|
6419
6991
|
(() => {
|
|
6420
6992
|
const _Orig = globalThis.URL;
|
|
6421
6993
|
class _Shim extends _Orig {
|
|
6422
6994
|
constructor(input, base) {
|
|
6423
|
-
if (base == null && typeof input === "string") {
|
|
6995
|
+
if (arguments.length >= 2 && base == null && typeof input === "string") {
|
|
6424
6996
|
try { super(input); return; }
|
|
6425
6997
|
catch {
|
|
6426
6998
|
// X.5-M3: prefer current module path when known, so
|
|
@@ -7002,6 +7574,13 @@ const __childProcessMod = (() => {
|
|
|
7002
7574
|
/**
|
|
7003
7575
|
* Read-loop for a single fd. Long-polls cpReadOutput, pushes chunks
|
|
7004
7576
|
* into the Readable via .push, handles closure.
|
|
7577
|
+
*
|
|
7578
|
+
* What the child printed, and that it closed the stream, is news from
|
|
7579
|
+
* another process, which may have written files before it printed. So a
|
|
7580
|
+
* reply that delivers anything takes the barrier first, and a 'data'
|
|
7581
|
+
* handler that reads what the child wrote reads it current. The poll sends
|
|
7582
|
+
* this process's ACQUIRE arguments and the reply carries the answer, so the
|
|
7583
|
+
* barrier costs no round trip of its own.
|
|
7005
7584
|
*/
|
|
7006
7585
|
async function _runReadLoop(child, fd, stream, sinceSeqRef) {
|
|
7007
7586
|
// Exponential backoff for idle children: start at 100ms, double up
|
|
@@ -7014,12 +7593,14 @@ const __childProcessMod = (() => {
|
|
|
7014
7593
|
while (HAS_SUPERVISOR && child.pid && !child._streamsClosed) {
|
|
7015
7594
|
try {
|
|
7016
7595
|
const r = await __nimbusUseRpcResult(
|
|
7017
|
-
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff),
|
|
7596
|
+
__supervisor.cpReadOutput(child.pid, fd, sinceSeqRef.value, backoff, __nimbusVfsAcquireArgs()),
|
|
7018
7597
|
(result) => result,
|
|
7019
7598
|
);
|
|
7020
|
-
|
|
7599
|
+
const chunks = r && Array.isArray(r.chunks) ? r.chunks : [];
|
|
7600
|
+
if (chunks.length > 0 || (r && r.closed)) await __nimbusInboundBarrier(r.acquired);
|
|
7601
|
+
if (chunks.length > 0) {
|
|
7021
7602
|
backoff = 100; // reset — child is producing
|
|
7022
|
-
for (const c of
|
|
7603
|
+
for (const c of chunks) {
|
|
7023
7604
|
// The queue hands back bytes; a Readable given a string would
|
|
7024
7605
|
// encode it again.
|
|
7025
7606
|
stream.write(__BufferMod.from(c.data));
|
|
@@ -7047,16 +7628,19 @@ const __childProcessMod = (() => {
|
|
|
7047
7628
|
|
|
7048
7629
|
/**
|
|
7049
7630
|
* Wait-loop: long-poll cpWait until the child reports exit. Emits
|
|
7050
|
-
* 'exit' once stamped
|
|
7631
|
+
* 'exit' once stamped, behind the barrier: a child's exit is how a parent
|
|
7632
|
+
* learns the files it wrote are there. Answered on the reply, as for the
|
|
7633
|
+
* child's output.
|
|
7051
7634
|
*/
|
|
7052
7635
|
async function _runWaitLoop(child) {
|
|
7053
7636
|
while (HAS_SUPERVISOR && child.pid && !child._exitFired) {
|
|
7054
7637
|
try {
|
|
7055
7638
|
const r = await __nimbusUseRpcResult(
|
|
7056
|
-
__supervisor.cpWait(child.pid, 1000),
|
|
7639
|
+
__supervisor.cpWait(child.pid, 1000, __nimbusVfsAcquireArgs()),
|
|
7057
7640
|
(result) => result,
|
|
7058
7641
|
);
|
|
7059
7642
|
if (r && r.done) {
|
|
7643
|
+
await __nimbusInboundBarrier(r.acquired);
|
|
7060
7644
|
child.exitCode = r.exitCode;
|
|
7061
7645
|
child.signalCode = r.signal;
|
|
7062
7646
|
child._exitFired = true;
|
|
@@ -7237,7 +7821,7 @@ const __childProcessMod = (() => {
|
|
|
7237
7821
|
* stdout/stderr/exit events fire. Callers like cross-spawn.sync
|
|
7238
7822
|
* that read result.status get null until the spawn settles.
|
|
7239
7823
|
* 3. When the parent facet's main drain settles __pendingIO before
|
|
7240
|
-
* reportExit (
|
|
7824
|
+
* reportExit (facets/manager.ts), the result object's fields are
|
|
7241
7825
|
* filled in by the time the supervisor sees the parent exit.
|
|
7242
7826
|
*
|
|
7243
7827
|
* Cross-spawn.sync's typical pattern is "const r = spawnSync(...);
|
|
@@ -7580,7 +8164,7 @@ function __makeProcessStdin() {
|
|
|
7580
8164
|
// facet, so counting it as in-flight work would mean the entry drain
|
|
7581
8165
|
// never sees the program finish.
|
|
7582
8166
|
packet = await __nimbusUseRpcResultUnref(
|
|
7583
|
-
__supervisor.cpReadStdin(liveChildPid, 1000),
|
|
8167
|
+
__supervisor.cpReadStdin(liveChildPid, 1000, __nimbusVfsAcquireArgs()),
|
|
7584
8168
|
(result) => result,
|
|
7585
8169
|
);
|
|
7586
8170
|
readFailures = 0;
|
|
@@ -7597,6 +8181,18 @@ function __makeProcessStdin() {
|
|
|
7597
8181
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
7598
8182
|
continue;
|
|
7599
8183
|
}
|
|
8184
|
+
// A packet that delivers anything — input, its end, a signal, a resize
|
|
8185
|
+
// — hands the program news from outside it, and whatever sent the news
|
|
8186
|
+
// may have written files first (a shell's `echo v2 > f` before the
|
|
8187
|
+
// keystroke, the editor's save before the signal). So it takes the
|
|
8188
|
+
// barrier before any handler sees it. The poll sent this process's
|
|
8189
|
+
// ACQUIRE arguments and the packet carries the answer, so a keystroke
|
|
8190
|
+
// costs no round trip of its own. An empty poll delivers nothing and
|
|
8191
|
+
// costs nothing.
|
|
8192
|
+
if (packet && (packet.resize || packet.signal || packet.ended
|
|
8193
|
+
|| (packet.data && packet.data.byteLength > 0))) {
|
|
8194
|
+
await __nimbusInboundBarrier(packet.acquired);
|
|
8195
|
+
}
|
|
7600
8196
|
if (packet && packet.resize) {
|
|
7601
8197
|
__nimbusTtyColumns = Number(packet.resize.columns) || __nimbusTtyColumns;
|
|
7602
8198
|
__nimbusTtyRows = Number(packet.resize.rows) || __nimbusTtyRows;
|
|
@@ -7713,6 +8309,7 @@ function __makeProcessOutputStream(streamName) {
|
|
|
7713
8309
|
errored: null,
|
|
7714
8310
|
write(d, enc, cb) {
|
|
7715
8311
|
if (typeof enc === "function") cb = enc;
|
|
8312
|
+
if (__nimbusProgramStopped) return true;
|
|
7716
8313
|
// The reported result is text; decode the bytes at this edge only.
|
|
7717
8314
|
const s = __nimbusOutText(streamName, __nimbusOutBytes(d, enc));
|
|
7718
8315
|
if (streamName === "stderr") stderr += s;
|
|
@@ -7758,6 +8355,8 @@ function __makeProcessOutputStream(streamName) {
|
|
|
7758
8355
|
function __nimbusReportProcessExit(code, reason) {
|
|
7759
8356
|
if (__nimbusProcessExitReported) return;
|
|
7760
8357
|
__nimbusProcessExitCode = Number(code ?? 0);
|
|
8358
|
+
__nimbusProgramStopped = true;
|
|
8359
|
+
try { if (typeof globalThis.__nimbusStopProgramTimers === "function") globalThis.__nimbusStopProgramTimers(); } catch {}
|
|
7761
8360
|
try { if (__nimbusProcessExitResolve) __nimbusProcessExitResolve(__nimbusProcessExitCode); } catch {}
|
|
7762
8361
|
// Generated lifecycle owners defer the terminal supervisor report until
|
|
7763
8362
|
// their durability boundary has drained. Reporting here would retire the
|
|
@@ -7797,7 +8396,7 @@ const __processMod = {
|
|
|
7797
8396
|
chdir: (d) => { cwd = __pathMod.resolve(cwd || "/home/user", d); },
|
|
7798
8397
|
exit: (code) => {
|
|
7799
8398
|
exitCode = code ?? 0;
|
|
7800
|
-
|
|
8399
|
+
__nimbusEmitExit(exitCode);
|
|
7801
8400
|
__nimbusReportProcessExit(exitCode, "");
|
|
7802
8401
|
throw new __ProcessExit(exitCode);
|
|
7803
8402
|
},
|
|
@@ -8211,14 +8810,27 @@ builtins.http = (() => {
|
|
|
8211
8810
|
return new __hostResponse("Nimbus: no HTTP server is listening in this process", { status: 502 });
|
|
8212
8811
|
}
|
|
8213
8812
|
const url = new URL(request.url);
|
|
8813
|
+
// The request carries the answer to its own barrier (below). It is
|
|
8814
|
+
// Nimbus's, not the program's, so the program does not see it.
|
|
8815
|
+
let delivered;
|
|
8816
|
+
try { delivered = JSON.parse(request.headers.get("X-Nimbus-Vfs-Acquired") || "null"); } catch {}
|
|
8214
8817
|
const headers = {};
|
|
8215
|
-
request.headers.forEach((v, k) => { headers[k] = v; });
|
|
8818
|
+
request.headers.forEach((v, k) => { if (k !== "x-nimbus-vfs-acquired") headers[k] = v; });
|
|
8216
8819
|
// Bytes, not text: a UTF-8 decode corrupts every binary upload, and the
|
|
8217
8820
|
// decoded string is not the TypedArray receiver Buffer methods require.
|
|
8218
8821
|
let body = null;
|
|
8219
8822
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
8220
8823
|
body = new Uint8Array(await request.arrayBuffer());
|
|
8221
8824
|
}
|
|
8825
|
+
// A request is a resumption like any other, and often the second half of
|
|
8826
|
+
// a causal chain — `echo v2 > f; curl :3000` — so the handler runs behind
|
|
8827
|
+
// the barrier, after the body has arrived. Sited here, in the dispatch
|
|
8828
|
+
// every resident's server shares, not in a caller that happens to yield
|
|
8829
|
+
// through a barriered timer on the way. The supervisor routed it, so it
|
|
8830
|
+
// arrives with an answer, which is enough when no write preceded the
|
|
8831
|
+
// request since this process last caught up (session/rpc.ts
|
|
8832
|
+
// _acquireForRoutedRequest); otherwise the barrier asks.
|
|
8833
|
+
await __nimbusInboundBarrier(delivered);
|
|
8222
8834
|
const res = server._handleRequest(url.pathname + url.search, request.method, headers, body);
|
|
8223
8835
|
// Return once headers are known. A handler that never sends headers is
|
|
8224
8836
|
// bounded by a header timeout (NOT a body-finish cap) so a hung handler
|
|
@@ -9053,38 +9665,12 @@ builtins["node:util/types"] = builtins["util/types"];
|
|
|
9053
9665
|
// skips it via the same FACET_PROVIDED_PACKAGES list.
|
|
9054
9666
|
builtins.undici = __undiciMod;
|
|
9055
9667
|
|
|
9056
|
-
/**
|
|
9057
|
-
* A cell's dynamic import() call.
|
|
9058
|
-
*
|
|
9059
|
-
* A cell is compiled with new Function, so an import() left in its body
|
|
9060
|
-
* is the RUNTIME's: it resolves against the module map and hands back the
|
|
9061
|
-
* platform's own builtins, not this process's. Real Vite reaches
|
|
9062
|
-
* node:http that way (config.js:14968), got a server whose listen bound no
|
|
9063
|
-
* port here, and the facet exited with no handles the moment boot returned.
|
|
9064
|
-
*
|
|
9065
|
-
* The ESM→CJS rewriter routes those calls here with the cell's own
|
|
9066
|
-
* require, so resolution is the one the process already has. The result is
|
|
9067
|
-
* shaped as a module namespace, since import() resolves to one and callers
|
|
9068
|
-
* destructure named exports off it.
|
|
9069
|
-
*/
|
|
9070
|
-
globalThis.__nimbusCellImport = (req, id) => Promise.resolve().then(() => {
|
|
9071
|
-
const loaded = req(id);
|
|
9072
|
-
if (loaded && (loaded.__esModule || loaded[Symbol.toStringTag] === "Module")) return loaded;
|
|
9073
|
-
// A CJS module's exports ARE the namespace's named exports, with the whole
|
|
9074
|
-
// object as the default export — what an ESM importer of a CJS module sees.
|
|
9075
|
-
if (loaded && (typeof loaded === "object" || typeof loaded === "function")) {
|
|
9076
|
-
const ns = Object.create(null);
|
|
9077
|
-
for (const key of Object.keys(loaded)) ns[key] = loaded[key];
|
|
9078
|
-
if (!("default" in ns)) ns.default = loaded;
|
|
9079
|
-
Object.defineProperty(ns, Symbol.toStringTag, { value: "Module" });
|
|
9080
|
-
return ns;
|
|
9081
|
-
}
|
|
9082
|
-
return { default: loaded };
|
|
9083
|
-
});
|
|
9084
9668
|
// ═══════════════════════════════════════════════════════════════════════
|
|
9085
9669
|
// ── require() — full Node.js module resolution ─────────────────────
|
|
9086
9670
|
// ═══════════════════════════════════════════════════════════════════════
|
|
9087
9671
|
const __moduleCache = new Map();
|
|
9672
|
+
// package → why the package ABI policy says it cannot run here (wasm-swap-registry.ts).
|
|
9673
|
+
const __nimbusAbiAdvisories = new Map([["sharp","Native libvips bindings; not portable to Workers. … try: no Workers-compatible target — render server-side or use Cloudflare Images. For the wasm32 build see @img/sharp-wasm32 entry below."],["sqlite3","Native sqlite3 .node binding. … try: better-sqlite3-wasm (untested by Nimbus) or sql.js once wasm asset loading is available."],["better-sqlite3","Native sqlite .node binding. … try: better-sqlite3-wasm (untested by Nimbus) or @libsql/client if its subpath exports resolve in your project."],["canvas","Native Cairo bindings. … try: canvaskit-wasm (Skia -> WASM, canvas-API-compatible, ~7MB; untested by Nimbus) or @resvg/resvg-wasm for SVG."],["sodium-native","Native libsodium. … try: tweetnacl (pure JS, untested by Nimbus) or libsodium-wrappers (WASM, untested by Nimbus)."],["node-pty","PTY syscalls unavailable in workerd. … try: no Workers-compatible target — use the Nimbus built-in shell."],["robotjs","Desktop automation; sandboxed Workers cannot access OS UI. … try: no Workers-compatible target."],["electron","Embedded Chromium runtime; not applicable to Workers. … try: no Workers-compatible target."],["bcrypt","Native bcrypt; pure-JS bcryptjs has an equivalent sync API but the require() name differs and Nimbus does not yet support npm aliases. … try: change `require(\"bcrypt\")` to `require(\"bcryptjs\")`, then `npm install bcryptjs`. APIs are sync-compatible."],["argon2","Native Argon2 C bindings. … try: hash-wasm for argon2d, argon2i, and argon2id."],["node-sass","Native libsass; deprecated upstream. … try: sass (dart-sass, pure JS)."],["grpc","Deprecated native gRPC. … try: @grpc/grpc-js (pure JS, untested end-to-end in Nimbus)."],["@swc/core","Native Rust SWC. … try: @swc/wasm-web for transform/parse only; it does not provide the native Plugin API."],["prisma","Native query engine; not portable to Workers in this configuration. … try: @prisma/adapter-d1 (Prisma official Workers adapter, untested by Nimbus), or migrate to drizzle-orm + @libsql/client (untested by Nimbus)."],["@prisma/client","Same as `prisma` (native query engine). … try: @prisma/adapter-d1 (untested by Nimbus), or drizzle-orm + @libsql/client (untested)."],["puppeteer","Bundled Chromium binary (~150 MB). … try: no Workers-compatible target for the bundled binary — use puppeteer-core + Cloudflare Browser Rendering (untested by Nimbus)."],["playwright","Bundled browsers (~300 MB). … try: no Workers-compatible target for bundled browsers — use @playwright/test against a remote browser endpoint (untested by Nimbus)."],["sql.js","Installs but fails at runtime because dist/sql-wasm.wasm is not available to the runtime loader. … try: For SQL in Workers, consider Cloudflare D1 or @libsql/client."],["@swc/wasm-web","Installs but fails at runtime because its generated code path depends on workerd-blocked dynamic code generation. … try: For ESM transforms consider esbuild-wasm."],["@img/sharp-wasm32","WASM build of sharp; package is wasm32-cpu-only and libvips initThreads() requires pthread support unavailable in Workers. … try: wasm-vips may work for simple pipelines; for complex pipelines, render server-side and ship pixels."],["@napi-rs/canvas","Native bindings only (linux-x64-gnu/musl, darwin-arm64/x64, android-arm64, linux-arm64-gnu/musl, win32-x64-msvc, linux-arm-gnueabihf). No WASM build published. … try: canvaskit-wasm (Skia -> WASM, canvas-API-compatible, ~7MB; untested by Nimbus) or @resvg/resvg-wasm for SVG."],["@napi-rs/canvas-wasm32-wasi","@napi-rs/canvas does not publish a wasm32-wasi variant on npm (404). The @napi-rs/canvas project ships only native bindings. No WASM/WASI build exists. … try: canvaskit-wasm (Skia -> WASM, canvas-API-compatible; untested by Nimbus) or @resvg/resvg-wasm for SVG."],["@tailwindcss/oxide","Native Rust Tailwind v4 oxide engine; ships only platform-specific .node bindings plus a wasm32-wasi shard. workerd has no node:wasi, and bare native bindings cannot dlopen. … try: no Workers-compatible target — Tailwind v3 (`tailwindcss@^3`) is pure JS and works in Workers (untested by Nimbus). Tailwind v4 inherently requires the Rust oxide engine."],["lightningcss","Native Rust CSS parser; ships platform-specific .node bindings plus a wasm32-wasi-only `lightningcss-wasm` package. workerd has no node:wasi, and the package probes libc through child_process.execSync. … try: no Workers-compatible target today — postcss + cssnano (pure JS, untested by Nimbus) cover most lightningcss use cases. For CSS minification only: clean-css (pure JS, untested by Nimbus)."],["rolldown","Native Rust bundler that Vite 8 loads at startup. Its only non-native build, @rolldown/binding-wasm32-wasi, is a wasm32-wasip1-threads binary, and Workers run one thread per isolate with Atomics.wait disabled. … try: no Workers-compatible target — rolldown publishes no single-threaded build, so tools that load Vite 8 themselves (Astro 7) cannot start here."],["@rolldown/binding-wasm32-wasi","wasm32-wasip1-threads build of rolldown: it imports a shared memory and wasi thread-spawn, its Rust locks and thread parking execute memory.atomic.wait32, and its loader needs node:wasi and worker_threads. Workers run one thread per isolate with Atomics.wait disabled. … try: no Workers-compatible target — rolldown publishes no single-threaded build."]]);
|
|
9088
9674
|
|
|
9089
9675
|
/**
|
|
9090
9676
|
* Direct VFS bundle access for module resolution.
|
|
@@ -9110,24 +9696,8 @@ function __readFileOr(path, fallback) {
|
|
|
9110
9696
|
}
|
|
9111
9697
|
function __fileExists(path) {
|
|
9112
9698
|
const k = path.replace(/^\/+/, "");
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
if (__vfsDirs && k in __vfsDirs) return true;
|
|
9116
|
-
// Consult the uncapped manifest (directory shape) so resolution sees
|
|
9117
|
-
// installed files whose content was excluded from the bounded snapshot
|
|
9118
|
-
// (e.g. web-streams-polyfill's ponyfill/package.json, reached via a
|
|
9119
|
-
// parent-relative main). Mirrors existsSync's manifest probe.
|
|
9120
|
-
if (__vfsManifest) {
|
|
9121
|
-
if (k in __vfsManifest) return true;
|
|
9122
|
-
const slash = k.lastIndexOf("/");
|
|
9123
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
9124
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
9125
|
-
const sib = __vfsManifest[parent];
|
|
9126
|
-
if (sib && sib.indexOf(name) !== -1) return true;
|
|
9127
|
-
}
|
|
9128
|
-
// Check for directory by looking for any key with this prefix
|
|
9129
|
-
if (__vfsBundle && __residentAnyUnder(k + "/")) return true;
|
|
9130
|
-
return false;
|
|
9699
|
+
// The namespace answers exactly (vfs/facet-resident-store.ts).
|
|
9700
|
+
return __fsMod.existsSync("/" + k);
|
|
9131
9701
|
}
|
|
9132
9702
|
// W3.5 Fix A: strict-file membership probe. __fileExists also returns true for
|
|
9133
9703
|
// directories (it has to — __resolveNodeModule and __resolveImportsField call
|
|
@@ -9139,26 +9709,8 @@ function __fileExists(path) {
|
|
|
9139
9709
|
// the fastify ret/dist/types failure.
|
|
9140
9710
|
function __pathIsFile(path) {
|
|
9141
9711
|
const k = path.replace(/^\/+/, "");
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
// Consult the manifest as a strict-file probe: a name listed in its
|
|
9145
|
-
// parent's manifest entry is a file (directories are manifest KEYS).
|
|
9146
|
-
// This lets resolution find files whose content was excluded from the
|
|
9147
|
-
// bounded snapshot without re-introducing the directory short-circuit
|
|
9148
|
-
// (__vfsManifest[k] being a directory key is deliberately NOT matched).
|
|
9149
|
-
if (__vfsManifest) {
|
|
9150
|
-
const slash = k.lastIndexOf("/");
|
|
9151
|
-
const parent = slash >= 0 ? k.slice(0, slash) : "";
|
|
9152
|
-
const name = slash >= 0 ? k.slice(slash + 1) : k;
|
|
9153
|
-
const sib = __vfsManifest[parent];
|
|
9154
|
-
if (sib && sib.indexOf(name) !== -1 && !(k in __vfsManifest)) return true;
|
|
9155
|
-
}
|
|
9156
|
-
// Last resort: the full fs view (live SQLite VFS) for paths outside the
|
|
9157
|
-
// bounded snapshot/manifest entirely — e.g. the /tmp/.npx-cache tree,
|
|
9158
|
-
// which the project snapshot never covers. stat-as-file only.
|
|
9159
|
-
try { const st = __fsMod.statSync("/" + k); if (st && st.isFile && st.isFile()) return true; } catch {}
|
|
9160
|
-
// Deliberately does NOT consult __vfsDirs nor do the prefix scan.
|
|
9161
|
-
return false;
|
|
9712
|
+
const st = __fsMod.statSync("/" + k, { throwIfNoEntry: false });
|
|
9713
|
+
return !!st && st.isFile();
|
|
9162
9714
|
}
|
|
9163
9715
|
function __resolveFile(base) {
|
|
9164
9716
|
// Mirrors Node's LOAD_AS_FILE + LOAD_AS_DIRECTORY (require_2 spec):
|
|
@@ -9231,15 +9783,12 @@ function __resolveFile(base) {
|
|
|
9231
9783
|
}
|
|
9232
9784
|
|
|
9233
9785
|
// __compiledModules is defined at MODULE TOP LEVEL in the generator code
|
|
9234
|
-
// (
|
|
9786
|
+
// (facets/manager.ts) so new Function() runs during module evaluation.
|
|
9235
9787
|
|
|
9236
9788
|
// ── Single-source-of-truth exports/imports resolver (W2) ───────────────
|
|
9237
9789
|
// Emitted from src/_shared/exports-resolver.ts via getExportsResolverJS().
|
|
9238
9790
|
// Declares: resolveExports, resolveConditionValue, resolvePackageEntry,
|
|
9239
9791
|
// DEFAULT_ESM_CONDITIONS, DEFAULT_CJS_CONDITIONS.
|
|
9240
|
-
// the prior hand-rolled __resolvePkgEntry only honoured top-level
|
|
9241
|
-
// require|default|import and dropped subpath maps, wildcards, nested
|
|
9242
|
-
// conditions, the imports field, and null-target enforcement.
|
|
9243
9792
|
|
|
9244
9793
|
// ── exports-resolver.js (auto-generated; keep in sync with src/_shared/exports-resolver.ts) ──
|
|
9245
9794
|
const DEFAULT_ESM_CONDITIONS = ['import', 'module', 'browser', 'default'];
|
|
@@ -9335,6 +9884,14 @@ function resolvePackageEntry(pkg, subpath, conditions) {
|
|
|
9335
9884
|
}
|
|
9336
9885
|
return subpath;
|
|
9337
9886
|
}
|
|
9887
|
+
|
|
9888
|
+
function packageSelfReferenceSubpath(pkg, specifier) {
|
|
9889
|
+
if (!pkg || typeof pkg.name !== 'string' || pkg.name.length === 0) return null;
|
|
9890
|
+
if (pkg.exports === undefined || pkg.exports === null) return null;
|
|
9891
|
+
if (specifier === pkg.name) return '.';
|
|
9892
|
+
if (!specifier.startsWith(pkg.name + '/')) return null;
|
|
9893
|
+
return '.' + specifier.slice(pkg.name.length);
|
|
9894
|
+
}
|
|
9338
9895
|
// ── end exports-resolver.js ────────────────────────────────────────────
|
|
9339
9896
|
|
|
9340
9897
|
|
|
@@ -9386,8 +9943,8 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
9386
9943
|
}
|
|
9387
9944
|
let entry = resolvePackageEntry(pkg, subpath, __NIMBUS_CJS_CONDITIONS);
|
|
9388
9945
|
// X.5-F R3: ESM-condition fallback for pure-ESM packages whose
|
|
9389
|
-
// dist/.mjs files were transformed to CJS
|
|
9390
|
-
//
|
|
9946
|
+
// dist/.mjs files were transformed to CJS
|
|
9947
|
+
// by transformEsmInBundle (facets/manager.ts). Without this,
|
|
9391
9948
|
// packages like nuxt — whose exports map only contains
|
|
9392
9949
|
// {types, import} for the root subpath — return null from the CJS
|
|
9393
9950
|
// walk and dead-end with "Cannot find module 'nuxt'" even though
|
|
@@ -9430,10 +9987,6 @@ function __resolvePkgSubpath(pkgDir, pkg, subpath) {
|
|
|
9430
9987
|
return __resolveFile(pkgDir + "/" + rel);
|
|
9431
9988
|
}
|
|
9432
9989
|
|
|
9433
|
-
/** Back-compat name used elsewhere in this file. */
|
|
9434
|
-
function __resolvePkgEntry(pkgDir) {
|
|
9435
|
-
return __resolvePkgSubpath(pkgDir, null, ".");
|
|
9436
|
-
}
|
|
9437
9990
|
|
|
9438
9991
|
/**
|
|
9439
9992
|
* Resolve a bare specifier (e.g. "react", "@scope/pkg", "pkg/sub/path")
|
|
@@ -9482,32 +10035,23 @@ function __resolveNodeModule(name, fromDir) {
|
|
|
9482
10035
|
}
|
|
9483
10036
|
|
|
9484
10037
|
/**
|
|
9485
|
-
*
|
|
9486
|
-
* package.json
|
|
10038
|
+
* Node's "package scope" of a directory (readPackageScope): the nearest
|
|
10039
|
+
* enclosing package.json, walking up from fromDir. The FIRST package.json
|
|
10040
|
+
* found is the scope, even when it lacks the field the caller wants — the
|
|
10041
|
+
* imports field and the self-reference rule both belong to the importing
|
|
10042
|
+
* module's own package, never to an ancestor past it. The walk never
|
|
10043
|
+
* crosses a node_modules directory: a file that sits directly under one
|
|
10044
|
+
* belongs to no package, not to the project above it. Returns { dir, pkg }
|
|
10045
|
+
* (pkg null when the file is unparseable) or null when no package.json
|
|
10046
|
+
* encloses fromDir.
|
|
9487
10047
|
*/
|
|
9488
|
-
function
|
|
9489
|
-
// Walk up looking for the nearest package.json. Stop at the first one
|
|
9490
|
-
// (Node spec: imports field of the importing module's package).
|
|
10048
|
+
function __nearestPackageScope(fromDir) {
|
|
9491
10049
|
let dir = (fromDir || "").replace(/^\/+/, "");
|
|
9492
10050
|
while (true) {
|
|
10051
|
+
if (dir === "node_modules" || dir.endsWith("/node_modules")) return null;
|
|
9493
10052
|
const pkgJsonPath = (dir ? dir + "/" : "") + "package.json";
|
|
9494
10053
|
if (__fileExists(pkgJsonPath)) {
|
|
9495
|
-
|
|
9496
|
-
if (pkg && pkg.imports) {
|
|
9497
|
-
const target = resolveExports(pkg.imports, name, __NIMBUS_CJS_CONDITIONS);
|
|
9498
|
-
if (target) {
|
|
9499
|
-
// Imports targets are relative to the package root (dir)
|
|
9500
|
-
if (target.startsWith("./")) {
|
|
9501
|
-
return __resolveFile((dir ? dir + "/" : "") + target.slice(2));
|
|
9502
|
-
}
|
|
9503
|
-
if (target.startsWith("/")) {
|
|
9504
|
-
return __resolveFile(target.slice(1));
|
|
9505
|
-
}
|
|
9506
|
-
// Bare specifier — re-resolve as a node_module from this dir
|
|
9507
|
-
return __resolveNodeModule(target, dir);
|
|
9508
|
-
}
|
|
9509
|
-
}
|
|
9510
|
-
return null; // first package.json wins, even if no imports field
|
|
10054
|
+
return { dir, pkg: __readPkgJson(dir) };
|
|
9511
10055
|
}
|
|
9512
10056
|
if (!dir) return null;
|
|
9513
10057
|
const lastSlash = dir.lastIndexOf("/");
|
|
@@ -9515,38 +10059,96 @@ function __resolveImportsField(name, fromDir) {
|
|
|
9515
10059
|
}
|
|
9516
10060
|
}
|
|
9517
10061
|
|
|
10062
|
+
/**
|
|
10063
|
+
* Resolve a #name imports-field specifier from the nearest enclosing
|
|
10064
|
+
* package.json. Returns the resolved file or null.
|
|
10065
|
+
*/
|
|
10066
|
+
function __resolveImportsField(name, fromDir) {
|
|
10067
|
+
// First package.json wins, even if no imports field (Node spec: imports
|
|
10068
|
+
// field of the importing module's package).
|
|
10069
|
+
const scope = __nearestPackageScope(fromDir);
|
|
10070
|
+
if (!scope || !scope.pkg || !scope.pkg.imports) return null;
|
|
10071
|
+
const dir = scope.dir;
|
|
10072
|
+
const target = resolveExports(scope.pkg.imports, name, __NIMBUS_CJS_CONDITIONS);
|
|
10073
|
+
if (!target) return null;
|
|
10074
|
+
// Imports targets are relative to the package root (dir)
|
|
10075
|
+
if (target.startsWith("./")) {
|
|
10076
|
+
return __resolveFile((dir ? dir + "/" : "") + target.slice(2));
|
|
10077
|
+
}
|
|
10078
|
+
if (target.startsWith("/")) {
|
|
10079
|
+
return __resolveFile(target.slice(1));
|
|
10080
|
+
}
|
|
10081
|
+
// Bare specifier — re-resolve as a node_module from this dir
|
|
10082
|
+
return __resolveNodeModule(target, dir);
|
|
10083
|
+
}
|
|
10084
|
+
|
|
10085
|
+
/**
|
|
10086
|
+
* Node's LOAD_PACKAGE_SELF: a bare specifier naming the enclosing package
|
|
10087
|
+
* itself (`require('<its-name>')`, `require('<its-name>/sub')`) resolves
|
|
10088
|
+
* through that package's own `exports` map — only when the nearest
|
|
10089
|
+
* package.json has `exports` AND its `name` matches, and only through
|
|
10090
|
+
* `exports` (no main/index probing: a subpath the map does not expose is
|
|
10091
|
+
* not exported). Sits between the imports-field branch and the
|
|
10092
|
+
* node_modules walk, where Node puts it. Returns the resolved file or null.
|
|
10093
|
+
*
|
|
10094
|
+
* Conditions are the ones the node_modules walk uses for the same
|
|
10095
|
+
* require: the runtime's CJS set first, the ESM set when the map exposes
|
|
10096
|
+
* the subpath only under `import` (dynamic import() is lowered onto this
|
|
10097
|
+
* require chain, see __resolvePkgSubpath).
|
|
10098
|
+
*
|
|
10099
|
+
* Tri-state, as in Node: null when the rule does not apply (the caller
|
|
10100
|
+
* walks node_modules); { resolved: null } when the enclosing package claims
|
|
10101
|
+
* the name but its map does not expose the subpath or the target is
|
|
10102
|
+
* missing — Node throws ERR_PACKAGE_PATH_NOT_EXPORTED / MODULE_NOT_FOUND
|
|
10103
|
+
* there and never consults node_modules, so neither does the caller.
|
|
10104
|
+
*/
|
|
10105
|
+
function __resolvePackageSelf(name, fromDir) {
|
|
10106
|
+
const scope = __nearestPackageScope(fromDir);
|
|
10107
|
+
if (!scope || !scope.pkg) return null;
|
|
10108
|
+
const subpath = packageSelfReferenceSubpath(scope.pkg, name);
|
|
10109
|
+
if (subpath === null) return null;
|
|
10110
|
+
let entry = resolveExports(scope.pkg.exports, subpath, __NIMBUS_CJS_CONDITIONS);
|
|
10111
|
+
if (entry == null) entry = resolveExports(scope.pkg.exports, subpath, DEFAULT_ESM_CONDITIONS);
|
|
10112
|
+
if (entry == null) return { resolved: null };
|
|
10113
|
+
return { resolved: __resolveFile((scope.dir ? scope.dir + "/" : "") + entry.replace(/^\.\/+/, "")) };
|
|
10114
|
+
}
|
|
10115
|
+
|
|
9518
10116
|
// ═══════════════════════════════════════════════════════════════════════
|
|
9519
|
-
// ──
|
|
10117
|
+
// ── __mkCompiledFn — the request-time fallback compile ──────────────
|
|
9520
10118
|
// ═══════════════════════════════════════════════════════════════════════
|
|
9521
|
-
//
|
|
9522
|
-
//
|
|
9523
|
-
//
|
|
9524
|
-
//
|
|
9525
|
-
|
|
9526
|
-
// the body in `new Function("exports","require","module","__filename","__dirname", code)`
|
|
9527
|
-
// then collides at parse time:
|
|
9528
|
-
//
|
|
9529
|
-
// SyntaxError: Identifier '__dirname' has already been declared
|
|
9530
|
-
//
|
|
9531
|
-
// (VERIFY-23417C5 §4 #1 / X5M3-retro §"Next bucket".) The helper RENAMES
|
|
9532
|
-
// the conflicting param to a placeholder name so the body's own
|
|
9533
|
-
// `const __dirname` becomes the single declarer. We rename rather than
|
|
9534
|
-
// drop because callers pass 5 positional arguments and dropping a slot
|
|
9535
|
-
// would mis-align downstream slots (e.g. the USER_CODE wrap appends
|
|
9536
|
-
// `console` / `process` / etc. after `__dirname`). Renaming preserves
|
|
9537
|
-
// slot alignment while letting the body's binding win.
|
|
9538
|
-
//
|
|
9539
|
-
// Symmetric for `__filename` because open@10's idiom often emits both.
|
|
10119
|
+
// One definition, from core/_shared/compiled-fn.ts (interpolated above as
|
|
10120
|
+
// MK_COMPILED_FN_SOURCE); here it serves a cell the startup precompile set
|
|
10121
|
+
// did not include, where the runtime permits it. See that file for why the
|
|
10122
|
+
// collision is detected by compiling rather than by scanning the source.
|
|
10123
|
+
|
|
9540
10124
|
function __mkCompiledFn(code) {
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
10125
|
+
// Node strips a leading shebang from every module before evaluation;
|
|
10126
|
+
// bin scripts are commonly bundled verbatim with their
|
|
10127
|
+
// "#!/usr/bin/env node" line, which is a SyntaxError under new Function.
|
|
10128
|
+
if (typeof code === "string" && code.charCodeAt(0) === 35 && code.charCodeAt(1) === 33) {
|
|
10129
|
+
const __nl = code.indexOf("\n");
|
|
10130
|
+
code = __nl >= 0 ? code.slice(__nl + 1) : "";
|
|
10131
|
+
}
|
|
10132
|
+
const __base = ["exports", "require", "module", "__filename", "__dirname"];
|
|
10133
|
+
const __params = __base.slice();
|
|
10134
|
+
let __error;
|
|
10135
|
+
// Each pass renames exactly the parameter the parser reported; a module
|
|
10136
|
+
// may declare more than one, so the loop runs once per slot at most.
|
|
10137
|
+
for (let __attempt = 0; __attempt <= __base.length; __attempt++) {
|
|
10138
|
+
try { return new Function(...__params, code); }
|
|
10139
|
+
catch (e) {
|
|
10140
|
+
__error = e;
|
|
10141
|
+
// V8 (workerd) and JavaScriptCore (bun, the unit tests) word it differently.
|
|
10142
|
+
const __m = /Identifier '([$\w]+)' has already been declared|Cannot declare a \w+ variable twice: '([$\w]+)'/.exec((e && e.message) || "");
|
|
10143
|
+
const __slot = __m ? __base.indexOf(__m[1] || __m[2]) : -1;
|
|
10144
|
+
if (__slot < 0 || __params[__slot] !== __base[__slot]) throw e;
|
|
10145
|
+
__params[__slot] = __base[__slot] + "__nimbus_unused";
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
throw __error;
|
|
9548
10149
|
}
|
|
9549
10150
|
|
|
10151
|
+
|
|
9550
10152
|
function __exportsTarget(mod) {
|
|
9551
10153
|
const value = mod.exports;
|
|
9552
10154
|
if (value && (typeof value === "object" || typeof value === "function")) return value;
|
|
@@ -9597,11 +10199,12 @@ function __makeLoadingExports(mod) {
|
|
|
9597
10199
|
* Load and execute a JS/JSON module from VFS.
|
|
9598
10200
|
* Returns the module.exports value.
|
|
9599
10201
|
*/
|
|
9600
|
-
function __loadModule(resolvedPath) {
|
|
9601
|
-
if (
|
|
10202
|
+
function __loadModule(resolvedPath, evaluationKey = resolvedPath) {
|
|
10203
|
+
if (globalThis.__nimbusProfileStaged) globalThis.__nimbusProfileStaged.delete(String(resolvedPath).replace(/^\/+/, ""));
|
|
10204
|
+
if (__moduleCache.has(evaluationKey)) return __moduleCache.get(evaluationKey);
|
|
9602
10205
|
|
|
9603
10206
|
const mod = { exports: {} };
|
|
9604
|
-
__moduleCache.set(
|
|
10207
|
+
__moduleCache.set(evaluationKey, __makeLoadingExports(mod));
|
|
9605
10208
|
|
|
9606
10209
|
const code = __readFileOr(resolvedPath, null);
|
|
9607
10210
|
if (code === null) throw new Error("Cannot read module: " + resolvedPath);
|
|
@@ -9609,7 +10212,7 @@ function __loadModule(resolvedPath) {
|
|
|
9609
10212
|
// JSON
|
|
9610
10213
|
if (resolvedPath.endsWith(".json")) {
|
|
9611
10214
|
mod.exports = JSON.parse(code);
|
|
9612
|
-
__moduleCache.set(
|
|
10215
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
9613
10216
|
return mod.exports;
|
|
9614
10217
|
}
|
|
9615
10218
|
|
|
@@ -9632,6 +10235,16 @@ function __loadModule(resolvedPath) {
|
|
|
9632
10235
|
// const import_meta = {}. Save+restore for recursive __loadModule.
|
|
9633
10236
|
const __prevModulePath = globalThis.__currentModulePath;
|
|
9634
10237
|
globalThis.__currentModulePath = resolvedPath;
|
|
10238
|
+
const moduleUrl = evaluationKey.startsWith("file:")
|
|
10239
|
+
? evaluationKey : builtins.url.pathToFileURL("/" + resolvedPath).href;
|
|
10240
|
+
// Evaluation metadata lives on the module, not in source text. Keeping
|
|
10241
|
+
// it here preserves the five CommonJS arguments and captures the parent
|
|
10242
|
+
// even when import.meta.resolve is extracted and called later.
|
|
10243
|
+
const importMeta = Object.assign(Object.create(null), {
|
|
10244
|
+
url: moduleUrl,
|
|
10245
|
+
resolve: (specifier) => globalThis.__nimbusImportMetaResolve(specifier, moduleUrl),
|
|
10246
|
+
});
|
|
10247
|
+
Object.defineProperty(mod, "__nimbusImportMeta", { value: importMeta });
|
|
9635
10248
|
try {
|
|
9636
10249
|
// Use pre-compiled function from startup (new Function allowed at module eval time)
|
|
9637
10250
|
// Normalize path to match VFS bundle key format (no leading /)
|
|
@@ -9656,7 +10269,7 @@ function __loadModule(resolvedPath) {
|
|
|
9656
10269
|
} catch (evalErr) {
|
|
9657
10270
|
// W3.5 Fix C: if the file was in the bundle but its pre-compile
|
|
9658
10271
|
// failed at facet startup, surface the original SyntaxError
|
|
9659
|
-
// instead of the misleading "
|
|
10272
|
+
// instead of the misleading "not in this launch's module map" text.
|
|
9660
10273
|
const normalizedPath2 = resolvedPath.replace(/^\/+/, "");
|
|
9661
10274
|
const compileErr =
|
|
9662
10275
|
(typeof __compileFailures !== "undefined" && __compileFailures &&
|
|
@@ -9668,22 +10281,27 @@ function __loadModule(resolvedPath) {
|
|
|
9668
10281
|
);
|
|
9669
10282
|
}
|
|
9670
10283
|
if (evalErr.message && evalErr.message.includes("Code generation from strings disallowed")) {
|
|
9671
|
-
|
|
10284
|
+
// Not a read miss (the text is here) and nothing in the process can
|
|
10285
|
+
// answer it, so it is kept apart from the read ledger that settles those.
|
|
10286
|
+
(globalThis.__nimbusModuleMisses ??= new Set()).add(normalizedPath2);
|
|
10287
|
+
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.");
|
|
9672
10288
|
}
|
|
9673
10289
|
throw evalErr;
|
|
9674
10290
|
}
|
|
9675
10291
|
}
|
|
9676
10292
|
} catch (e) {
|
|
9677
|
-
__moduleCache.delete(
|
|
10293
|
+
__moduleCache.delete(evaluationKey);
|
|
9678
10294
|
if (e && typeof e === "object" && !e.__nimbusModulePath) {
|
|
9679
10295
|
try {
|
|
9680
|
-
e
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
10296
|
+
Object.defineProperty(e, "__nimbusModulePath", { value: resolvedPath, configurable: true, writable: true });
|
|
10297
|
+
const at = resolvedPath.lastIndexOf("node_modules/");
|
|
10298
|
+
const parts = at < 0 ? [] : resolvedPath.slice(at + 13).split("/");
|
|
10299
|
+
const pkg = parts[0] && parts[0].startsWith("@") ? parts[0] + "/" + parts[1] : parts[0];
|
|
10300
|
+
const advisory = pkg ? __nimbusAbiAdvisories.get(pkg) : undefined;
|
|
10301
|
+
const note = "\nNimbus module: " + resolvedPath
|
|
10302
|
+
+ (advisory ? "\nNimbus: " + pkg + " has no Workers-compatible build: " + advisory : "");
|
|
10303
|
+
if (typeof e.message === "string") e.message += note;
|
|
10304
|
+
if (typeof e.stack === "string" && !e.stack.includes("Nimbus module:")) e.stack += note;
|
|
9687
10305
|
} catch {}
|
|
9688
10306
|
}
|
|
9689
10307
|
throw e;
|
|
@@ -9692,7 +10310,7 @@ function __loadModule(resolvedPath) {
|
|
|
9692
10310
|
}
|
|
9693
10311
|
|
|
9694
10312
|
// Update cache with final exports (module.exports may have been reassigned)
|
|
9695
|
-
__moduleCache.set(
|
|
10313
|
+
__moduleCache.set(evaluationKey, mod.exports);
|
|
9696
10314
|
return mod.exports;
|
|
9697
10315
|
}
|
|
9698
10316
|
|
|
@@ -9701,6 +10319,25 @@ function __loadModule(resolvedPath) {
|
|
|
9701
10319
|
* Returns the resolved VFS path, or null.
|
|
9702
10320
|
*/
|
|
9703
10321
|
function __resolveFrom(id, fromDir) {
|
|
10322
|
+
// An absolute file: URL is a specifier Node accepts: `import(href)` is the
|
|
10323
|
+
// portable way to load a path a resolver just handed back, and it is what
|
|
10324
|
+
// every package that resolves before it imports emits — @nuxt/cli's
|
|
10325
|
+
// loadKit does `import(pathToFileURL(resolveModulePath('@nuxt/kit', …)).href)`.
|
|
10326
|
+
// The ESM→CJS transform funnels those imports through this one resolver, so
|
|
10327
|
+
// the scheme has to come off before the specifier is classified: with it on,
|
|
10328
|
+
// the file: URL misses the absolute-path branch below and gets looked up as
|
|
10329
|
+
// if it were the name of a package.
|
|
10330
|
+
if (typeof id === "string" && id.startsWith("file:")) {
|
|
10331
|
+
let filePath;
|
|
10332
|
+
try {
|
|
10333
|
+
const u = new URL(id);
|
|
10334
|
+
// .pathname drops the query a cache-busting importer appends
|
|
10335
|
+
// (`import(href + "?t=" + Date.now())` is the standard HMR spelling),
|
|
10336
|
+
// and the decode is what node:url's fileURLToPath does with it.
|
|
10337
|
+
filePath = decodeURIComponent(u.pathname);
|
|
10338
|
+
} catch { filePath = id.replace(/^file:\/\//, ""); }
|
|
10339
|
+
id = filePath;
|
|
10340
|
+
}
|
|
9704
10341
|
// X.5-P: literal "." / ".." are CommonJS aliases for "./" / "../".
|
|
9705
10342
|
// Pre-fix they slipped past the startsWith("./")/("../") guards (which
|
|
9706
10343
|
// require >= 3 / >= 4 chars respectively) and fell into the bare-spec
|
|
@@ -9730,43 +10367,634 @@ function __resolveFrom(id, fromDir) {
|
|
|
9730
10367
|
if (id.startsWith("#")) {
|
|
9731
10368
|
return __resolveImportsField(id, fromDir);
|
|
9732
10369
|
}
|
|
9733
|
-
// Bare specifier
|
|
10370
|
+
// Bare specifier: the enclosing package's own name resolves through its
|
|
10371
|
+
// exports map (Node's LOAD_PACKAGE_SELF), then node_modules resolution.
|
|
10372
|
+
// Once the enclosing package claims the name, its map is the whole
|
|
10373
|
+
// answer: a subpath it does not expose is not found, never a
|
|
10374
|
+
// node_modules copy's.
|
|
10375
|
+
const self = __resolvePackageSelf(id, fromDir);
|
|
10376
|
+
if (self) return self.resolved;
|
|
9734
10377
|
return __resolveNodeModule(id, fromDir);
|
|
9735
10378
|
}
|
|
9736
10379
|
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
10380
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
10381
|
+
// ── import() — Node's ESM loader ────────────────────────────────────
|
|
10382
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
10383
|
+
// A cell's dynamic import() reaches here with its module's URL
|
|
10384
|
+
// (core/runtime/dynamic-import-rewrite.ts). Resolution is Node's ESM
|
|
10385
|
+
// algorithm (core/_shared/esm-resolver.ts, interpolated below): the "import"
|
|
10386
|
+
// conditions, no extension probing, a directory refused by name, file: URLs,
|
|
10387
|
+
// Node's error codes and messages. What resolves loads through the same
|
|
10388
|
+
// module cache require uses, shaped as the namespace Node would give.
|
|
10389
|
+
// Node's builtins that exist only with the scheme; the rest of the table is
|
|
10390
|
+
// the process's own builtins, undici among them, which the process provides
|
|
10391
|
+
// in place of any installed copy (see builtins.undici above).
|
|
10392
|
+
const __ESM_SCHEME_ONLY_BUILTINS = new Set(["test", "test/reporters", "sqlite", "sea"]);
|
|
10393
|
+
// Node's ESM resolver (core/_shared/esm-resolver.ts, compiled once by
|
|
10394
|
+
// scripts/bundle-facet-workers.mjs): declares createEsmResolver.
|
|
10395
|
+
function createEsmResolver(host) {
|
|
10396
|
+
const conditions = new Set(["node", "import", "module-sync"]);
|
|
10397
|
+
const ask = {
|
|
10398
|
+
*kind(path) {
|
|
10399
|
+
const kind = yield host.kind(path);
|
|
10400
|
+
return kind === "file" || kind === "directory" ? kind : null;
|
|
10401
|
+
},
|
|
10402
|
+
*readText(path) {
|
|
10403
|
+
const text = yield host.readText(path);
|
|
10404
|
+
return typeof text === "string" ? text : null;
|
|
10405
|
+
},
|
|
10406
|
+
*realpath(path) {
|
|
10407
|
+
const real = yield host.realpath(path);
|
|
10408
|
+
return typeof real === "string" ? real : path;
|
|
10409
|
+
},
|
|
10410
|
+
*cjsResolve(specifier, parentPath) {
|
|
10411
|
+
const found = yield host.cjsResolve(specifier, parentPath);
|
|
10412
|
+
return typeof found === "string" ? found : null;
|
|
10413
|
+
}
|
|
10414
|
+
};
|
|
10415
|
+
function codedError(Ctor, code, message) {
|
|
10416
|
+
return Object.assign(new Ctor(message), { code });
|
|
10417
|
+
}
|
|
10418
|
+
const codeOf = (error) => error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
10419
|
+
const filePath = (url) => decodeURIComponent(new URL(String(url)).pathname);
|
|
10420
|
+
const fileUrl = (path) => {
|
|
10421
|
+
const url = new URL("file://");
|
|
10422
|
+
url.pathname = path;
|
|
10423
|
+
return url;
|
|
10424
|
+
};
|
|
10425
|
+
function isRelativeSpecifier(specifier) {
|
|
10426
|
+
if (specifier[0] !== ".") return false;
|
|
10427
|
+
if (specifier.length === 1 || specifier[1] === "/") return true;
|
|
10428
|
+
return specifier[1] === "." && (specifier.length === 2 || specifier[2] === "/");
|
|
10429
|
+
}
|
|
10430
|
+
const isRelativeOrAbsolute = (specifier) => specifier !== "" && (specifier[0] === "/" || isRelativeSpecifier(specifier));
|
|
10431
|
+
function* readPackageConfig(pjsonPath, specifier, base) {
|
|
10432
|
+
const text = (yield* ask.kind(pjsonPath)) === "file" ? yield* ask.readText(pjsonPath) : null;
|
|
10433
|
+
if (text === null) return { exists: false, pjsonPath, type: "none" };
|
|
10434
|
+
let parsed;
|
|
10435
|
+
try {
|
|
10436
|
+
parsed = JSON.parse(text);
|
|
10437
|
+
} catch {
|
|
10438
|
+
throw codedError(
|
|
10439
|
+
Error,
|
|
10440
|
+
"ERR_INVALID_PACKAGE_CONFIG",
|
|
10441
|
+
`Invalid package config ${pjsonPath}` + (base ? ` while importing ${JSON.stringify(specifier)} from ${base}` : "") + "."
|
|
10442
|
+
);
|
|
10443
|
+
}
|
|
10444
|
+
const config = { exists: true, pjsonPath, type: "none" };
|
|
10445
|
+
if (parsed === null || typeof parsed !== "object") return config;
|
|
10446
|
+
if (typeof parsed.name === "string") config.name = parsed.name;
|
|
10447
|
+
if (typeof parsed.main === "string") config.main = parsed.main;
|
|
10448
|
+
if ("exports" in parsed) config.exports = parsed.exports;
|
|
10449
|
+
if (parsed.imports !== null && typeof parsed.imports === "object") config.imports = parsed.imports;
|
|
10450
|
+
if (parsed.type === "module" || parsed.type === "commonjs") config.type = parsed.type;
|
|
10451
|
+
return config;
|
|
10452
|
+
}
|
|
10453
|
+
function* packageScopeConfig(resolved) {
|
|
10454
|
+
let pjsonUrl = new URL("./package.json", resolved);
|
|
10455
|
+
while (true) {
|
|
10456
|
+
if (pjsonUrl.pathname.endsWith("node_modules/package.json")) break;
|
|
10457
|
+
const config = yield* readPackageConfig(filePath(pjsonUrl), resolved.href, void 0);
|
|
10458
|
+
if (config.exists) return config;
|
|
10459
|
+
const last = pjsonUrl;
|
|
10460
|
+
pjsonUrl = new URL("../package.json", pjsonUrl);
|
|
10461
|
+
if (pjsonUrl.pathname === last.pathname) break;
|
|
10462
|
+
}
|
|
10463
|
+
return { exists: false, pjsonPath: filePath(pjsonUrl), type: "none" };
|
|
10464
|
+
}
|
|
10465
|
+
function invalidPackageTarget(key, target, pjsonUrl, internal, base) {
|
|
10466
|
+
const text = typeof target === "object" && target !== null ? JSON.stringify(target, null, "") : `${target}`;
|
|
10467
|
+
const pkgPath = filePath(new URL(".", pjsonUrl));
|
|
10468
|
+
const related = !internal && text.length > 0 && !text.startsWith("./");
|
|
10469
|
+
const tail = `in the package config ${pkgPath}package.json imported from ${base}${related ? '; targets must start with "./"' : ""}`;
|
|
10470
|
+
return codedError(
|
|
10471
|
+
Error,
|
|
10472
|
+
"ERR_INVALID_PACKAGE_TARGET",
|
|
10473
|
+
key === "." ? `Invalid "exports" main target ${JSON.stringify(text)} defined ${tail}` : `Invalid "${internal ? "imports" : "exports"}" target ${JSON.stringify(text)} defined for '${key}' ${tail}`
|
|
9765
10474
|
);
|
|
9766
|
-
err.code = "ERR_MODULE_NOT_FOUND";
|
|
9767
|
-
throw err;
|
|
9768
10475
|
}
|
|
9769
|
-
|
|
10476
|
+
const invalidSegment = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
|
|
10477
|
+
const deprecatedInvalidSegment = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
|
|
10478
|
+
function* resolveTargetString(target, subpath, match, pjsonUrl, base, pattern, internal, isPathMap) {
|
|
10479
|
+
if (subpath !== "" && !pattern && target[target.length - 1] !== "/") {
|
|
10480
|
+
throw invalidPackageTarget(match, target, pjsonUrl, internal, base);
|
|
10481
|
+
}
|
|
10482
|
+
if (!target.startsWith("./")) {
|
|
10483
|
+
if (internal && !target.startsWith("../") && !target.startsWith("/")) {
|
|
10484
|
+
let isUrl = false;
|
|
10485
|
+
try {
|
|
10486
|
+
new URL(target);
|
|
10487
|
+
isUrl = true;
|
|
10488
|
+
} catch {
|
|
10489
|
+
}
|
|
10490
|
+
if (!isUrl) {
|
|
10491
|
+
const exportTarget = pattern ? target.replace(/\*/g, () => subpath) : target + subpath;
|
|
10492
|
+
return yield* packageResolve(exportTarget, pjsonUrl.href);
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
throw invalidPackageTarget(match, target, pjsonUrl, internal, base);
|
|
10496
|
+
}
|
|
10497
|
+
if (invalidSegment.test(target.slice(2)) && deprecatedInvalidSegment.test(target.slice(2))) {
|
|
10498
|
+
throw invalidPackageTarget(match, target, pjsonUrl, internal, base);
|
|
10499
|
+
}
|
|
10500
|
+
const resolved = new URL(target, pjsonUrl);
|
|
10501
|
+
if (!resolved.pathname.startsWith(new URL(".", pjsonUrl).pathname)) {
|
|
10502
|
+
throw invalidPackageTarget(match, target, pjsonUrl, internal, base);
|
|
10503
|
+
}
|
|
10504
|
+
if (subpath === "") return resolved;
|
|
10505
|
+
if (invalidSegment.test(subpath) && deprecatedInvalidSegment.test(subpath) && !isPathMap) {
|
|
10506
|
+
const request = pattern ? match.replace("*", () => subpath) : match + subpath;
|
|
10507
|
+
throw codedError(
|
|
10508
|
+
TypeError,
|
|
10509
|
+
"ERR_INVALID_MODULE_SPECIFIER",
|
|
10510
|
+
`Invalid module "${request}" request is not a valid match in pattern "${match}" for the "${internal ? "imports" : "exports"}" resolution of ${filePath(pjsonUrl)} imported from ${base}`
|
|
10511
|
+
);
|
|
10512
|
+
}
|
|
10513
|
+
if (pattern) return new URL(resolved.href.replace(/\*/g, () => subpath));
|
|
10514
|
+
return new URL(subpath, resolved);
|
|
10515
|
+
}
|
|
10516
|
+
function* resolveTarget(pjsonUrl, target, subpath, key, base, pattern, internal, isPathMap) {
|
|
10517
|
+
if (typeof target === "string") {
|
|
10518
|
+
return yield* resolveTargetString(target, subpath, key, pjsonUrl, base, pattern, internal, isPathMap);
|
|
10519
|
+
}
|
|
10520
|
+
if (Array.isArray(target)) {
|
|
10521
|
+
if (target.length === 0) return null;
|
|
10522
|
+
let lastException;
|
|
10523
|
+
for (const item of target) {
|
|
10524
|
+
let result;
|
|
10525
|
+
try {
|
|
10526
|
+
result = yield* resolveTarget(pjsonUrl, item, subpath, key, base, pattern, internal, isPathMap);
|
|
10527
|
+
} catch (error) {
|
|
10528
|
+
lastException = error;
|
|
10529
|
+
if (codeOf(error) === "ERR_INVALID_PACKAGE_TARGET") continue;
|
|
10530
|
+
throw error;
|
|
10531
|
+
}
|
|
10532
|
+
if (result === void 0) continue;
|
|
10533
|
+
if (result === null) {
|
|
10534
|
+
lastException = null;
|
|
10535
|
+
continue;
|
|
10536
|
+
}
|
|
10537
|
+
return result;
|
|
10538
|
+
}
|
|
10539
|
+
if (lastException === void 0 || lastException === null) return lastException;
|
|
10540
|
+
throw lastException;
|
|
10541
|
+
}
|
|
10542
|
+
if (typeof target === "object" && target !== null) {
|
|
10543
|
+
const keys = Object.getOwnPropertyNames(target);
|
|
10544
|
+
for (const condition of keys) {
|
|
10545
|
+
if (/^\d+$/.test(condition) && String(Number(condition)) === condition && Number(condition) < 4294967295) {
|
|
10546
|
+
throw codedError(
|
|
10547
|
+
Error,
|
|
10548
|
+
"ERR_INVALID_PACKAGE_CONFIG",
|
|
10549
|
+
`Invalid package config ${filePath(pjsonUrl)} while importing ${fileUrl(base).href}. "exports" cannot contain numeric property keys.`
|
|
10550
|
+
);
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
for (const condition of keys) {
|
|
10554
|
+
if (condition !== "default" && !conditions.has(condition)) continue;
|
|
10555
|
+
const result = yield* resolveTarget(
|
|
10556
|
+
pjsonUrl,
|
|
10557
|
+
Reflect.get(target, condition),
|
|
10558
|
+
subpath,
|
|
10559
|
+
key,
|
|
10560
|
+
base,
|
|
10561
|
+
pattern,
|
|
10562
|
+
internal,
|
|
10563
|
+
isPathMap
|
|
10564
|
+
);
|
|
10565
|
+
if (result === void 0) continue;
|
|
10566
|
+
return result;
|
|
10567
|
+
}
|
|
10568
|
+
return void 0;
|
|
10569
|
+
}
|
|
10570
|
+
if (target === null) return null;
|
|
10571
|
+
throw invalidPackageTarget(key, target, pjsonUrl, internal, base);
|
|
10572
|
+
}
|
|
10573
|
+
function patternKeyCompare(a, b) {
|
|
10574
|
+
const aStar = a.indexOf("*");
|
|
10575
|
+
const bStar = b.indexOf("*");
|
|
10576
|
+
const baseA = aStar === -1 ? a.length : aStar + 1;
|
|
10577
|
+
const baseB = bStar === -1 ? b.length : bStar + 1;
|
|
10578
|
+
if (baseA > baseB) return -1;
|
|
10579
|
+
if (baseB > baseA) return 1;
|
|
10580
|
+
if (aStar === -1) return 1;
|
|
10581
|
+
if (bStar === -1) return -1;
|
|
10582
|
+
if (a.length > b.length) return -1;
|
|
10583
|
+
if (b.length > a.length) return 1;
|
|
10584
|
+
return 0;
|
|
10585
|
+
}
|
|
10586
|
+
function bestPattern(map, name) {
|
|
10587
|
+
let best = "";
|
|
10588
|
+
let bestSubpath = "";
|
|
10589
|
+
for (const key of Object.getOwnPropertyNames(map)) {
|
|
10590
|
+
const star = key.indexOf("*");
|
|
10591
|
+
if (star === -1 || !name.startsWith(key.slice(0, star))) continue;
|
|
10592
|
+
const trailer = key.slice(star + 1);
|
|
10593
|
+
if (name.length >= key.length && name.endsWith(trailer) && patternKeyCompare(best, key) === 1 && key.lastIndexOf("*") === star) {
|
|
10594
|
+
best = key;
|
|
10595
|
+
bestSubpath = name.slice(star, name.length - trailer.length);
|
|
10596
|
+
}
|
|
10597
|
+
}
|
|
10598
|
+
return best ? { key: best, subpath: bestSubpath } : null;
|
|
10599
|
+
}
|
|
10600
|
+
function exportsNotFound(subpath, pjsonUrl, base) {
|
|
10601
|
+
const pkgPath = filePath(new URL(".", pjsonUrl));
|
|
10602
|
+
return codedError(
|
|
10603
|
+
Error,
|
|
10604
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
10605
|
+
subpath === "." ? `No "exports" main defined in ${pkgPath}package.json imported from ${base}` : `Package subpath '${subpath}' is not defined by "exports" in ${pkgPath}package.json imported from ${base}`
|
|
10606
|
+
);
|
|
10607
|
+
}
|
|
10608
|
+
function* packageExportsResolve(pjsonUrl, subpath, config, base) {
|
|
10609
|
+
let exports = config.exports;
|
|
10610
|
+
const isSugar = (() => {
|
|
10611
|
+
if (typeof exports === "string" || Array.isArray(exports)) return true;
|
|
10612
|
+
if (typeof exports !== "object" || exports === null) return false;
|
|
10613
|
+
let sugar = false;
|
|
10614
|
+
let i = 0;
|
|
10615
|
+
for (const key of Object.getOwnPropertyNames(exports)) {
|
|
10616
|
+
const current = key === "" || key[0] !== ".";
|
|
10617
|
+
if (i++ === 0) sugar = current;
|
|
10618
|
+
else if (sugar !== current) {
|
|
10619
|
+
throw codedError(
|
|
10620
|
+
Error,
|
|
10621
|
+
"ERR_INVALID_PACKAGE_CONFIG",
|
|
10622
|
+
`Invalid package config ${filePath(pjsonUrl)} while importing ${fileUrl(base).href}. "exports" cannot contain some keys starting with '.' and some not. The exports object must either be an object of package subpath keys or an object of main entry condition name keys only.`
|
|
10623
|
+
);
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return sugar;
|
|
10627
|
+
})();
|
|
10628
|
+
if (isSugar) exports = { ".": exports };
|
|
10629
|
+
const map = exports;
|
|
10630
|
+
if (Object.prototype.hasOwnProperty.call(map, subpath) && !subpath.includes("*") && !subpath.endsWith("/")) {
|
|
10631
|
+
const result = yield* resolveTarget(pjsonUrl, map[subpath], "", subpath, base, false, false, false);
|
|
10632
|
+
if (result == null) throw exportsNotFound(subpath, pjsonUrl, base);
|
|
10633
|
+
return result;
|
|
10634
|
+
}
|
|
10635
|
+
const best = bestPattern(map, subpath);
|
|
10636
|
+
if (best) {
|
|
10637
|
+
const result = yield* resolveTarget(pjsonUrl, map[best.key], best.subpath, best.key, base, true, false, subpath.endsWith("/"));
|
|
10638
|
+
if (result == null) throw exportsNotFound(subpath, pjsonUrl, base);
|
|
10639
|
+
return result;
|
|
10640
|
+
}
|
|
10641
|
+
throw exportsNotFound(subpath, pjsonUrl, base);
|
|
10642
|
+
}
|
|
10643
|
+
function* packageImportsResolve(name, baseUrl) {
|
|
10644
|
+
const base = filePath(baseUrl);
|
|
10645
|
+
if (name === "#" || name.startsWith("#/") || name.endsWith("/")) {
|
|
10646
|
+
throw codedError(TypeError, "ERR_INVALID_MODULE_SPECIFIER", `Invalid module "${name}" is not a valid internal imports specifier name imported from ${base}`);
|
|
10647
|
+
}
|
|
10648
|
+
const config = yield* packageScopeConfig(new URL(baseUrl));
|
|
10649
|
+
let pjsonUrl;
|
|
10650
|
+
if (config.exists) {
|
|
10651
|
+
pjsonUrl = fileUrl(config.pjsonPath);
|
|
10652
|
+
const imports = config.imports;
|
|
10653
|
+
if (imports) {
|
|
10654
|
+
if (Object.prototype.hasOwnProperty.call(imports, name) && !name.includes("*")) {
|
|
10655
|
+
const result = yield* resolveTarget(pjsonUrl, imports[name], "", name, base, false, true, false);
|
|
10656
|
+
if (result != null) return result;
|
|
10657
|
+
} else {
|
|
10658
|
+
const best = bestPattern(imports, name);
|
|
10659
|
+
if (best) {
|
|
10660
|
+
const result = yield* resolveTarget(pjsonUrl, imports[best.key], best.subpath, best.key, base, true, true, false);
|
|
10661
|
+
if (result != null) return result;
|
|
10662
|
+
}
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
}
|
|
10666
|
+
const where = pjsonUrl ? ` in package ${filePath(new URL(".", pjsonUrl))}package.json` : "";
|
|
10667
|
+
throw codedError(TypeError, "ERR_PACKAGE_IMPORT_NOT_DEFINED", `Package import specifier "${name}" is not defined${where} imported from ${base}`);
|
|
10668
|
+
}
|
|
10669
|
+
function* legacyMainResolve(pjsonUrl, config, base) {
|
|
10670
|
+
const tries = [];
|
|
10671
|
+
if (config.main !== void 0) {
|
|
10672
|
+
for (const suffix of ["", ".js", ".json", ".node", "/index.js", "/index.json", "/index.node"]) tries.push(`./${config.main}${suffix}`);
|
|
10673
|
+
}
|
|
10674
|
+
tries.push("./index.js", "./index.json", "./index.node");
|
|
10675
|
+
for (const candidate of tries) {
|
|
10676
|
+
const url = new URL(candidate, pjsonUrl);
|
|
10677
|
+
if ((yield* ask.kind(filePath(url))) === "file") return url;
|
|
10678
|
+
}
|
|
10679
|
+
const dir = fileUrl(filePath(new URL(".", pjsonUrl)).replace(/\/+/g, "/"));
|
|
10680
|
+
const missing = filePath(new URL(config.main ?? "index.js", dir));
|
|
10681
|
+
throw codedError(Error, "ERR_MODULE_NOT_FOUND", `Cannot find package '${missing}' imported from ${base}`);
|
|
10682
|
+
}
|
|
10683
|
+
function* packageResolve(specifier, baseUrl) {
|
|
10684
|
+
if (host.isBuiltin(specifier)) return new URL("node:" + specifier);
|
|
10685
|
+
const base = filePath(baseUrl);
|
|
10686
|
+
let separator = specifier.indexOf("/");
|
|
10687
|
+
let valid = true;
|
|
10688
|
+
let scoped = false;
|
|
10689
|
+
if (specifier[0] === "@") {
|
|
10690
|
+
scoped = true;
|
|
10691
|
+
if (separator === -1 || specifier.length === 0) valid = false;
|
|
10692
|
+
else separator = specifier.indexOf("/", separator + 1);
|
|
10693
|
+
}
|
|
10694
|
+
const name = separator === -1 ? specifier : specifier.slice(0, separator);
|
|
10695
|
+
if (/^\.|%|\\/.test(name)) valid = false;
|
|
10696
|
+
if (!valid) {
|
|
10697
|
+
throw codedError(TypeError, "ERR_INVALID_MODULE_SPECIFIER", `Invalid module "${specifier}" is not a valid package name imported from ${base}`);
|
|
10698
|
+
}
|
|
10699
|
+
const subpath = "." + (separator === -1 ? "" : specifier.slice(separator));
|
|
10700
|
+
const self = yield* packageScopeConfig(new URL(baseUrl));
|
|
10701
|
+
if (self.exists && self.exports != null && self.name === name) {
|
|
10702
|
+
return yield* packageExportsResolve(fileUrl(self.pjsonPath), subpath, self, base);
|
|
10703
|
+
}
|
|
10704
|
+
let pjsonUrl = new URL("./node_modules/" + name + "/package.json", baseUrl);
|
|
10705
|
+
let pjsonPath = filePath(pjsonUrl);
|
|
10706
|
+
let lastPath;
|
|
10707
|
+
do {
|
|
10708
|
+
if ((yield* ask.kind(pjsonPath.slice(0, pjsonPath.length - 13))) !== "directory") {
|
|
10709
|
+
lastPath = pjsonPath;
|
|
10710
|
+
pjsonUrl = new URL((scoped ? "../../../../node_modules/" : "../../../node_modules/") + name + "/package.json", pjsonUrl);
|
|
10711
|
+
pjsonPath = filePath(pjsonUrl);
|
|
10712
|
+
continue;
|
|
10713
|
+
}
|
|
10714
|
+
const config = yield* readPackageConfig(pjsonPath, specifier, base);
|
|
10715
|
+
if (config.exports != null) return yield* packageExportsResolve(pjsonUrl, subpath, config, base);
|
|
10716
|
+
if (subpath === ".") return yield* legacyMainResolve(pjsonUrl, config, base);
|
|
10717
|
+
return new URL(subpath, pjsonUrl);
|
|
10718
|
+
} while (pjsonPath.length !== lastPath.length);
|
|
10719
|
+
throw codedError(Error, "ERR_MODULE_NOT_FOUND", `Cannot find package '${name}' imported from ${base}`);
|
|
10720
|
+
}
|
|
10721
|
+
function* formatOf(url, path) {
|
|
10722
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
10723
|
+
const dot = base.lastIndexOf(".");
|
|
10724
|
+
const ext = dot > 0 ? base.slice(dot) : "";
|
|
10725
|
+
if (ext === ".mjs" || ext === ".mts") return "module";
|
|
10726
|
+
if (ext === ".cjs" || ext === ".cts") return "commonjs";
|
|
10727
|
+
if (ext === ".json") return "json";
|
|
10728
|
+
if (ext === ".js" || ext === ".ts" || ext === "") {
|
|
10729
|
+
const type = (yield* packageScopeConfig(url)).type;
|
|
10730
|
+
if (type === "module") return "module";
|
|
10731
|
+
if (type === "commonjs") return "commonjs";
|
|
10732
|
+
return "detect";
|
|
10733
|
+
}
|
|
10734
|
+
throw codedError(TypeError, "ERR_UNKNOWN_FILE_EXTENSION", `Unknown file extension "${ext}" for ${path}`);
|
|
10735
|
+
}
|
|
10736
|
+
function* finalizeResolution(resolved, baseUrl) {
|
|
10737
|
+
const base = filePath(baseUrl);
|
|
10738
|
+
if (/%2f|%5c/i.test(resolved.pathname)) {
|
|
10739
|
+
throw codedError(
|
|
10740
|
+
TypeError,
|
|
10741
|
+
"ERR_INVALID_MODULE_SPECIFIER",
|
|
10742
|
+
`Invalid module "${resolved.pathname}" must not include encoded "/" or "\\" characters imported from ${base}`
|
|
10743
|
+
);
|
|
10744
|
+
}
|
|
10745
|
+
const path = filePath(resolved);
|
|
10746
|
+
const kind = path.endsWith("/") ? "directory" : yield* ask.kind(path);
|
|
10747
|
+
if (kind === "directory") {
|
|
10748
|
+
throw Object.assign(
|
|
10749
|
+
codedError(Error, "ERR_UNSUPPORTED_DIR_IMPORT", `Directory import '${path}' is not supported resolving ES modules imported from ${base}`),
|
|
10750
|
+
{ url: resolved.href }
|
|
10751
|
+
);
|
|
10752
|
+
}
|
|
10753
|
+
if (kind !== "file") {
|
|
10754
|
+
throw Object.assign(
|
|
10755
|
+
codedError(Error, "ERR_MODULE_NOT_FOUND", `Cannot find module '${path}' imported from ${base}`),
|
|
10756
|
+
{ url: resolved.href }
|
|
10757
|
+
);
|
|
10758
|
+
}
|
|
10759
|
+
const real = yield* ask.realpath(path);
|
|
10760
|
+
const url = fileUrl(real);
|
|
10761
|
+
url.search = resolved.search;
|
|
10762
|
+
url.hash = resolved.hash;
|
|
10763
|
+
return { url: url.href, path: real };
|
|
10764
|
+
}
|
|
10765
|
+
function* moduleResolve(specifier, parentUrl) {
|
|
10766
|
+
let resolved;
|
|
10767
|
+
if (isRelativeOrAbsolute(specifier)) resolved = new URL(specifier, parentUrl);
|
|
10768
|
+
else if (specifier[0] === "#") resolved = yield* packageImportsResolve(specifier, parentUrl);
|
|
10769
|
+
else {
|
|
10770
|
+
try {
|
|
10771
|
+
resolved = new URL(specifier);
|
|
10772
|
+
} catch {
|
|
10773
|
+
resolved = yield* packageResolve(specifier, parentUrl);
|
|
10774
|
+
}
|
|
10775
|
+
}
|
|
10776
|
+
if (resolved.protocol === "node:") return { url: "node:" + resolved.pathname, builtin: resolved.pathname };
|
|
10777
|
+
if (resolved.protocol !== "file:") return { url: resolved.href };
|
|
10778
|
+
return yield* finalizeResolution(resolved, parentUrl);
|
|
10779
|
+
}
|
|
10780
|
+
function* loadable(resolved) {
|
|
10781
|
+
if (resolved.builtin !== void 0) {
|
|
10782
|
+
if (!host.isBuiltin("node:" + resolved.builtin)) {
|
|
10783
|
+
throw codedError(Error, "ERR_UNKNOWN_BUILTIN_MODULE", `No such built-in module: node:${resolved.builtin}`);
|
|
10784
|
+
}
|
|
10785
|
+
return { url: resolved.url, builtin: resolved.builtin, format: "builtin" };
|
|
10786
|
+
}
|
|
10787
|
+
if (resolved.url.startsWith("data:")) return { url: resolved.url, format: "data" };
|
|
10788
|
+
if (resolved.path === void 0) {
|
|
10789
|
+
throw codedError(
|
|
10790
|
+
Error,
|
|
10791
|
+
"ERR_UNSUPPORTED_ESM_URL_SCHEME",
|
|
10792
|
+
`Only URLs with a scheme in: file and data are supported by the default ESM loader. Received protocol '${new URL(resolved.url).protocol}'`
|
|
10793
|
+
);
|
|
10794
|
+
}
|
|
10795
|
+
return { url: resolved.url, path: resolved.path, format: yield* formatOf(new URL(resolved.url), resolved.path) };
|
|
10796
|
+
}
|
|
10797
|
+
function* commonJsHint(specifier, parentUrl) {
|
|
10798
|
+
let found = yield* ask.cjsResolve(specifier, filePath(parentUrl));
|
|
10799
|
+
if (found === null) return null;
|
|
10800
|
+
if (isRelativeSpecifier(specifier)) {
|
|
10801
|
+
const from = parentUrl.slice("file://".length, parentUrl.lastIndexOf("/")).split("/").filter(Boolean);
|
|
10802
|
+
const to = fileUrl(found).pathname.split("/").filter(Boolean);
|
|
10803
|
+
let common = 0;
|
|
10804
|
+
while (common < from.length && common < to.length && from[common] === to[common]) common++;
|
|
10805
|
+
found = [...from.slice(common).map(() => ".."), ...to.slice(common)].join("/");
|
|
10806
|
+
if (!found.startsWith("../")) found = `./${found}`;
|
|
10807
|
+
} else if (specifier[0] && specifier[0] !== "/" && specifier[0] !== ".") {
|
|
10808
|
+
const slash = specifier.indexOf("/");
|
|
10809
|
+
const pkg = slash === -1 ? specifier : specifier.slice(0, slash);
|
|
10810
|
+
const needle = `/node_modules/${pkg}/`;
|
|
10811
|
+
const at = found.lastIndexOf(needle);
|
|
10812
|
+
found = at !== -1 ? pkg + "/" + found.slice(at + needle.length).split("/").map(encodeURIComponent).join("/") : fileUrl(found).href;
|
|
10813
|
+
}
|
|
10814
|
+
return found;
|
|
10815
|
+
}
|
|
10816
|
+
function* resolveWithHint(specifier, parentUrl) {
|
|
10817
|
+
try {
|
|
10818
|
+
return yield* moduleResolve(specifier, parentUrl);
|
|
10819
|
+
} catch (error) {
|
|
10820
|
+
const code = codeOf(error);
|
|
10821
|
+
if (error instanceof Error && (code === "ERR_MODULE_NOT_FOUND" || code === "ERR_UNSUPPORTED_DIR_IMPORT")) {
|
|
10822
|
+
const asGiven = specifier.startsWith("file://") ? filePath(specifier) : specifier;
|
|
10823
|
+
const found = yield* commonJsHint(asGiven, parentUrl);
|
|
10824
|
+
if (found && found !== asGiven) error.message += `
|
|
10825
|
+
Did you mean to import ${JSON.stringify(found)}?`;
|
|
10826
|
+
}
|
|
10827
|
+
throw error;
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
function* importTarget(specifier, parentUrl) {
|
|
10831
|
+
return yield* loadable(yield* resolveWithHint(specifier, parentUrl));
|
|
10832
|
+
}
|
|
10833
|
+
function* metaResolve(specifier, parentUrl) {
|
|
10834
|
+
try {
|
|
10835
|
+
return (yield* moduleResolve(specifier, parentUrl)).url;
|
|
10836
|
+
} catch (error) {
|
|
10837
|
+
const code = codeOf(error);
|
|
10838
|
+
if ((code === "ERR_MODULE_NOT_FOUND" || code === "ERR_UNSUPPORTED_DIR_IMPORT") && error !== null && typeof error === "object" && "url" in error && typeof error.url === "string") return error.url;
|
|
10839
|
+
throw error;
|
|
10840
|
+
}
|
|
10841
|
+
}
|
|
10842
|
+
function runSync(steps) {
|
|
10843
|
+
let next = steps.next();
|
|
10844
|
+
while (!next.done) {
|
|
10845
|
+
if (next.value instanceof Promise) throw new Error("resolveSync: the host answered asynchronously");
|
|
10846
|
+
next = steps.next(next.value);
|
|
10847
|
+
}
|
|
10848
|
+
return next.value;
|
|
10849
|
+
}
|
|
10850
|
+
return {
|
|
10851
|
+
async resolve(specifier, parentUrl) {
|
|
10852
|
+
const steps = importTarget(specifier, parentUrl);
|
|
10853
|
+
let next = steps.next();
|
|
10854
|
+
while (!next.done) {
|
|
10855
|
+
let answer;
|
|
10856
|
+
try {
|
|
10857
|
+
answer = await next.value;
|
|
10858
|
+
} catch (error) {
|
|
10859
|
+
next = steps.throw(error);
|
|
10860
|
+
continue;
|
|
10861
|
+
}
|
|
10862
|
+
next = steps.next(answer);
|
|
10863
|
+
}
|
|
10864
|
+
return next.value;
|
|
10865
|
+
},
|
|
10866
|
+
resolveSync: (specifier, parentUrl) => runSync(importTarget(specifier, parentUrl)),
|
|
10867
|
+
metaResolveSync: (specifier, parentUrl) => runSync(metaResolve(specifier, parentUrl)),
|
|
10868
|
+
validateAttributes(url, format, attributes) {
|
|
10869
|
+
for (const key of Object.keys(attributes)) {
|
|
10870
|
+
if (key !== "type") {
|
|
10871
|
+
throw codedError(TypeError, "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED", `Import attribute "${key}" with value "${attributes[key]}" is not supported in ${url}`);
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
const type = attributes.type;
|
|
10875
|
+
if (format === "json" || format === "data" && /^data:application\/json(?:;[^,]*)?,/.test(url)) {
|
|
10876
|
+
if (type === "json") return;
|
|
10877
|
+
if (!("type" in attributes)) {
|
|
10878
|
+
throw codedError(TypeError, "ERR_IMPORT_ATTRIBUTE_MISSING", `Module "${url}" needs an import attribute of "type: json"`);
|
|
10879
|
+
}
|
|
10880
|
+
} else if (type == null) {
|
|
10881
|
+
return;
|
|
10882
|
+
}
|
|
10883
|
+
if (typeof type !== "string") {
|
|
10884
|
+
throw codedError(TypeError, "ERR_INVALID_ARG_TYPE", `The "type" argument must be of type string. Received ${typeof type}`);
|
|
10885
|
+
}
|
|
10886
|
+
if (type !== "json") {
|
|
10887
|
+
throw codedError(TypeError, "ERR_IMPORT_ATTRIBUTE_UNSUPPORTED", `Import attribute "type" with value "${type}" is not supported in ${url}`);
|
|
10888
|
+
}
|
|
10889
|
+
throw codedError(TypeError, "ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE", `Module "${url}" is not of type "json"`);
|
|
10890
|
+
}
|
|
10891
|
+
};
|
|
10892
|
+
}
|
|
10893
|
+
const __esmResolver = createEsmResolver({
|
|
10894
|
+
kind(path) {
|
|
10895
|
+
const st = __fsMod.statSync(path, { throwIfNoEntry: false });
|
|
10896
|
+
return !st ? null : st.isDirectory() ? "directory" : st.isFile() ? "file" : null;
|
|
10897
|
+
},
|
|
10898
|
+
realpath(path) {
|
|
10899
|
+
try { return __fsMod.realpathSync(path); } catch { return path; }
|
|
10900
|
+
},
|
|
10901
|
+
readText(path) { return __readFileOr(path, null); },
|
|
10902
|
+
isBuiltin(specifier) {
|
|
10903
|
+
const scheme = specifier.startsWith("node:");
|
|
10904
|
+
const name = scheme ? specifier.slice(5) : specifier;
|
|
10905
|
+
if (!scheme && __ESM_SCHEME_ONLY_BUILTINS.has(name)) return false;
|
|
10906
|
+
return Object.prototype.hasOwnProperty.call(builtins, name);
|
|
10907
|
+
},
|
|
10908
|
+
cjsResolve(specifier, parentPath) {
|
|
10909
|
+
try {
|
|
10910
|
+
const dir = parentPath.slice(0, parentPath.lastIndexOf("/")).replace(/^\/+/, "");
|
|
10911
|
+
const found = __resolveFrom(specifier, dir);
|
|
10912
|
+
return found ? "/" + String(found).replace(/^\/+/, "") : null;
|
|
10913
|
+
} catch { return null; }
|
|
10914
|
+
},
|
|
10915
|
+
});
|
|
10916
|
+
const __esmNamespaces = new Map();
|
|
10917
|
+
/** A module namespace: its names sorted, read through to the exports. */
|
|
10918
|
+
function __esmNamespaceOf(names, read) {
|
|
10919
|
+
const ns = Object.create(null);
|
|
10920
|
+
for (const name of [...names].sort()) {
|
|
10921
|
+
Object.defineProperty(ns, name, { enumerable: true, get: () => read(name) });
|
|
10922
|
+
}
|
|
10923
|
+
Object.defineProperty(ns, Symbol.toStringTag, { value: "Module" });
|
|
10924
|
+
return Object.preventExtensions(ns);
|
|
10925
|
+
}
|
|
10926
|
+
function __esmLoad(resolution) {
|
|
10927
|
+
const cached = __esmNamespaces.get(resolution.url);
|
|
10928
|
+
if (cached) return cached;
|
|
10929
|
+
let ns;
|
|
10930
|
+
if (resolution.format === "builtin") {
|
|
10931
|
+
const mod = __requireFrom("node:" + resolution.builtin, "");
|
|
10932
|
+
const names = new Set(mod && (typeof mod === "object" || typeof mod === "function") ? Object.keys(mod) : []);
|
|
10933
|
+
names.add("default");
|
|
10934
|
+
ns = __esmNamespaceOf(names, (name) => name === "default" ? mod : mod[name]);
|
|
10935
|
+
} else if (resolution.format === "data") {
|
|
10936
|
+
const match = /^data:application\/json(;[^,]*)?,/.exec(resolution.url);
|
|
10937
|
+
if (!match) {
|
|
10938
|
+
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" });
|
|
10939
|
+
}
|
|
10940
|
+
const body = resolution.url.slice(match[0].length);
|
|
10941
|
+
const value = JSON.parse(match[1] && match[1].includes(";base64") ? atob(body) : decodeURIComponent(body));
|
|
10942
|
+
ns = __esmNamespaceOf(["default"], () => value);
|
|
10943
|
+
} else {
|
|
10944
|
+
const key = resolution.path.replace(/^\/+/, "");
|
|
10945
|
+
const esm = resolution.format === "module"
|
|
10946
|
+
|| (resolution.format === "detect" && globalThis.__nimbusEsmModules && globalThis.__nimbusEsmModules.has(key));
|
|
10947
|
+
// Canonical queryless ESM shares evaluation with require() and static
|
|
10948
|
+
// imports lowered to require(). Queries/fragments are distinct jobs.
|
|
10949
|
+
const variant = esm && (resolution.url.includes("?") || resolution.url.includes("#"));
|
|
10950
|
+
const exports = __loadModule(key, variant ? resolution.url : key);
|
|
10951
|
+
if (resolution.format === "json") {
|
|
10952
|
+
ns = __esmNamespaceOf(["default"], () => exports);
|
|
10953
|
+
} else if (esm) {
|
|
10954
|
+
// The ESM→CJS transform's exports: the module's own names, live.
|
|
10955
|
+
const names = exports && typeof exports === "object" ? Object.keys(exports).filter((name) => name !== "__esModule") : [];
|
|
10956
|
+
ns = __esmNamespaceOf(names, (name) => exports[name]);
|
|
10957
|
+
} else {
|
|
10958
|
+
// CommonJS: module.exports is the default, its names the named exports.
|
|
10959
|
+
const mod = exports;
|
|
10960
|
+
const names = new Set(mod && (typeof mod === "object" || typeof mod === "function") ? Object.keys(mod) : []);
|
|
10961
|
+
names.delete("default");
|
|
10962
|
+
names.add("default");
|
|
10963
|
+
ns = __esmNamespaceOf(names, (name) => name === "default" ? mod : mod[name]);
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
__esmNamespaces.set(resolution.url, ns);
|
|
10967
|
+
return ns;
|
|
10968
|
+
}
|
|
10969
|
+
globalThis.__nimbusDynamicImport = function __nimbusDynamicImport(parentUrl, specifier, options) {
|
|
10970
|
+
return Promise.resolve().then(() => {
|
|
10971
|
+
const text = String(specifier);
|
|
10972
|
+
const attributes = {};
|
|
10973
|
+
// V8's own checks and messages (node 22), which run before resolution.
|
|
10974
|
+
if (options !== undefined) {
|
|
10975
|
+
if (options === null || typeof options !== "object") throw new TypeError("The second argument to import() must be an object");
|
|
10976
|
+
const withAttributes = options.with;
|
|
10977
|
+
if (withAttributes !== undefined) {
|
|
10978
|
+
if (withAttributes === null || typeof withAttributes !== "object") throw new TypeError("The 'assert' option must be an object");
|
|
10979
|
+
for (const key of Object.keys(withAttributes)) {
|
|
10980
|
+
if (typeof withAttributes[key] !== "string") throw new TypeError("Import assertion value must be a string");
|
|
10981
|
+
attributes[key] = withAttributes[key];
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10985
|
+
const resolution = __esmResolver.resolveSync(text, parentUrl);
|
|
10986
|
+
__esmResolver.validateAttributes(resolution.url, resolution.format, attributes);
|
|
10987
|
+
return __esmLoad(resolution);
|
|
10988
|
+
});
|
|
10989
|
+
};
|
|
10990
|
+
|
|
10991
|
+
// Node's import.meta.resolve, synchronous as in Node: the URL a specifier
|
|
10992
|
+
// names, even for a file or directory that will not load.
|
|
10993
|
+
globalThis.__nimbusImportMetaResolve = function __nimbusImportMetaResolve(specifier, parentUrl) {
|
|
10994
|
+
const parent = typeof parentUrl === "string" && parentUrl.startsWith("file:")
|
|
10995
|
+
? parentUrl
|
|
10996
|
+
: "file:///" + String(globalThis.__currentModulePath || "[eval]").replace(/^\/+/, "");
|
|
10997
|
+
return __esmResolver.metaResolveSync(String(specifier), parent);
|
|
9770
10998
|
};
|
|
9771
10999
|
|
|
9772
11000
|
/**
|