@nimbus-sh/core 0.5.0 → 0.7.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/README.md +47 -26
- package/dist/_shared/byte-stream.d.ts +5 -4
- package/dist/_shared/byte-stream.d.ts.map +1 -1
- package/dist/_shared/cache-stats.d.ts +23 -1
- package/dist/_shared/cache-stats.d.ts.map +1 -1
- package/dist/_shared/cache-stats.js +13 -1
- package/dist/_shared/exports-resolver.d.ts +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/real-node-imports.d.ts +7 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.js +8 -0
- package/dist/_shared/retry.js +1 -1
- package/dist/_shared/tarball.d.ts +23 -5
- package/dist/_shared/tarball.d.ts.map +1 -1
- package/dist/_shared/tarball.js +38 -5
- package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
- package/dist/_shared/vfs-write-ledger.js +22 -16
- package/dist/constants.d.ts +5 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -56
- package/dist/runtime/assets-loader.js +1 -1
- package/dist/runtime/comment-strip.d.ts +0 -35
- package/dist/runtime/comment-strip.d.ts.map +1 -1
- package/dist/runtime/comment-strip.js +48 -20
- package/dist/runtime/esbuild-service.d.ts +38 -42
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +494 -197
- package/dist/runtime/javascript-ast.d.ts.map +1 -1
- package/dist/runtime/javascript-ast.js +45 -7
- package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-backend.js +18 -16
- package/dist/runtime/port-registry.d.ts +4 -2
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +60 -8
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +1 -1
- package/dist/shell/stdin-adapter.d.ts +11 -0
- package/dist/shell/stdin-adapter.d.ts.map +1 -0
- package/dist/shell/stdin-adapter.js +59 -0
- package/dist/shell/unix-commands.d.ts +2 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +1078 -50
- package/dist/substrate/lifo/commands/io/dd.js +1 -1
- package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/net/curl.js +270 -54
- package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
- package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/npm.js +26 -43
- package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/head.js +2 -1
- package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/sed.js +499 -117
- package/dist/substrate/lifo/commands/types.d.ts +8 -4
- package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
- package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.js +122 -23
- package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
- package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/interpreter.js +79 -29
- package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
- package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/pipe.js +62 -18
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +1 -1
- package/dist/vfs/sqlite-vfs.d.ts +21 -30
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +113 -29
- package/dist/workspace/nimbus-workspace.d.ts +15 -0
- package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
- package/dist/workspace/nimbus-workspace.js +20 -3
- package/dist/workspace/supervisor-op.d.ts +23 -0
- package/dist/workspace/supervisor-op.d.ts.map +1 -0
- package/dist/workspace/supervisor-op.js +92 -0
- package/package.json +3 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/cache-stats.ts +27 -1
- package/src/_shared/exports-resolver.ts +1 -1
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/retry.ts +1 -1
- package/src/_shared/tarball.ts +56 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- package/src/constants.ts +30 -60
- package/src/runtime/assets-loader.ts +1 -1
- package/src/runtime/comment-strip.ts +42 -23
- package/src/runtime/esbuild-service.ts +566 -217
- package/src/runtime/javascript-ast.ts +40 -9
- package/src/runtime/opentui-wasm-backend.ts +19 -17
- package/src/runtime/port-registry.ts +66 -8
- package/src/runtime/virtual-socket-kernel.generated.ts +1 -1
- package/src/shell/stdin-adapter.ts +58 -0
- package/src/shell/unix-commands.ts +1054 -50
- package/src/substrate/lifo/commands/io/dd.ts +2 -2
- package/src/substrate/lifo/commands/net/curl.ts +310 -58
- package/src/substrate/lifo/commands/system/npm.ts +58 -38
- package/src/substrate/lifo/commands/text/head.ts +2 -1
- package/src/substrate/lifo/commands/text/sed.ts +519 -109
- package/src/substrate/lifo/commands/types.ts +8 -5
- package/src/substrate/lifo/node-compat/zlib.ts +155 -26
- package/src/substrate/lifo/shell/interpreter.ts +74 -27
- package/src/substrate/lifo/shell/pipe.ts +65 -24
- package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
- package/src/vfs/seed-project.ts +3 -3
- package/src/vfs/sqlite-vfs.ts +128 -64
- package/src/workspace/nimbus-workspace.ts +26 -2
- package/src/workspace/supervisor-op.ts +117 -0
- package/dist/_shared/install-phase.d.ts +0 -47
- package/dist/_shared/install-phase.d.ts.map +0 -1
- package/dist/_shared/install-phase.js +0 -1
- package/dist/_shared/rpc-dispose.d.ts +0 -4
- package/dist/_shared/rpc-dispose.d.ts.map +0 -1
- package/dist/_shared/rpc-dispose.js +0 -30
- package/dist/_shared/w7-frame.d.ts +0 -79
- package/dist/_shared/w7-frame.d.ts.map +0 -1
- package/dist/_shared/w7-frame.js +0 -840
- package/dist/_shared/weighted-credit-pool.d.ts +0 -61
- package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
- package/dist/_shared/weighted-credit-pool.js +0 -196
- package/dist/observability/diag-counters.d.ts +0 -236
- package/dist/observability/diag-counters.d.ts.map +0 -1
- package/dist/observability/diag-counters.js +0 -195
- package/dist/observability/heap-estimate.d.ts +0 -225
- package/dist/observability/heap-estimate.d.ts.map +0 -1
- package/dist/observability/heap-estimate.js +0 -185
- package/dist/observability/heavy-alloc-coord.d.ts +0 -93
- package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
- package/dist/observability/heavy-alloc-coord.js +0 -181
- package/dist/observability/oom-classify.d.ts +0 -138
- package/dist/observability/oom-classify.d.ts.map +0 -1
- package/dist/observability/oom-classify.js +0 -268
- package/dist/observability/oom-discriminator.d.ts +0 -176
- package/dist/observability/oom-discriminator.d.ts.map +0 -1
- package/dist/observability/oom-discriminator.js +0 -272
- package/src/_shared/install-phase.ts +0 -56
- package/src/_shared/rpc-dispose.ts +0 -31
- package/src/_shared/w7-frame.ts +0 -1013
- package/src/_shared/weighted-credit-pool.ts +0 -244
- package/src/observability/diag-counters.ts +0 -343
- package/src/observability/heap-estimate.ts +0 -331
- package/src/observability/heavy-alloc-coord.ts +0 -241
- package/src/observability/oom-classify.ts +0 -260
- package/src/observability/oom-discriminator.ts +0 -405
package/dist/vfs/sqlite-vfs.js
CHANGED
|
@@ -41,13 +41,14 @@
|
|
|
41
41
|
*/
|
|
42
42
|
import { VfsEventEmitter } from './events.js';
|
|
43
43
|
import { normalizeVfsPath } from './path.js';
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
44
|
+
import { LRU_MAX_ENTRIES, FS_LIST_PAGE_LIMIT, } from '../constants.js';
|
|
45
|
+
import { CHUNK_SIZE, MAX_TX_BLOB_BYTES, MAX_TX_LOGICAL_ROWS, MAX_TX_SQL_EXECS, MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES, } from '@nimbus-sh/platform/limits.js';
|
|
46
|
+
import { recordFailure } from '@nimbus-sh/platform/oom-discriminator.js';
|
|
47
|
+
import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
|
|
48
|
+
import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
48
49
|
import { enc, dec } from '../_shared/bytes.js';
|
|
49
|
-
import { decodeWriteBatchStream, } from '
|
|
50
|
-
import { WeightedCreditPool, } from '
|
|
50
|
+
import { decodeWriteBatchStream, } from '@nimbus-sh/platform/w7-frame.js';
|
|
51
|
+
import { WeightedCreditPool, } from '@nimbus-sh/platform/weighted-credit-pool.js';
|
|
51
52
|
import { LEGACY_SYMLINK_REGISTRY_PATH } from './symlink-registry.js';
|
|
52
53
|
import { CRED_KERNEL, } from '../runtime/os-contracts.js';
|
|
53
54
|
const CONTENT_ID_ALLOCATION_ATTEMPTS = 8;
|
|
@@ -305,10 +306,9 @@ export class SqliteVFS {
|
|
|
305
306
|
_lruShrinkRefcount = 0;
|
|
306
307
|
// ── Running counters for O(1) getStats() (B3 / AUDIT M10 / M-S8) ──
|
|
307
308
|
// Replaces the triple scan of this.inodes on every /api/stats poll.
|
|
308
|
-
// Bootstrapped in loadInodes(); maintained at
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
// walk of this.inodes. Unit-tested in the A5/B3 runtime tests.
|
|
309
|
+
// Bootstrapped in loadInodes(); maintained at committed publication by
|
|
310
|
+
// mkdir, batch writes/deletes and rename. These match a fresh O(N)
|
|
311
|
+
// walk of this.inodes; rollback reloads them from the durable rows.
|
|
312
312
|
_totalFiles = 0;
|
|
313
313
|
_totalDirs = 0;
|
|
314
314
|
_usedBytes = 0;
|
|
@@ -322,6 +322,7 @@ export class SqliteVFS {
|
|
|
322
322
|
// so unrelated writes no longer invalidate them. In-memory only — the
|
|
323
323
|
// clock resets with the DO lifetime, exactly like the caches keyed on it.
|
|
324
324
|
_pathRevisions = new Map();
|
|
325
|
+
transactionPublication = null;
|
|
325
326
|
// ── Invalidation log (facet cache coherence) ──────────────────────────
|
|
326
327
|
// A facet's resident set is a cache of this VFS, and it learns what to
|
|
327
328
|
// drop by asking `invalidatedSince(cursor)` for the delta. _pathRevisions
|
|
@@ -1254,6 +1255,11 @@ export class SqliteVFS {
|
|
|
1254
1255
|
* additional coverage, since no facet view keys on a grandparent.
|
|
1255
1256
|
*/
|
|
1256
1257
|
bumpRevision(paths) {
|
|
1258
|
+
if (this.transactionPublication) {
|
|
1259
|
+
for (const path of paths)
|
|
1260
|
+
this.transactionPublication.paths.add(path);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1257
1263
|
const rev = ++this._revision;
|
|
1258
1264
|
for (const path of paths) {
|
|
1259
1265
|
let p = normalizeVfsPath(path);
|
|
@@ -1454,7 +1460,7 @@ export class SqliteVFS {
|
|
|
1454
1460
|
this._addToChildrenIndex(pp, path);
|
|
1455
1461
|
this._totalDirs++; // B3
|
|
1456
1462
|
this.bumpRevision([path]);
|
|
1457
|
-
this.
|
|
1463
|
+
this.emitMutation('addDir', path);
|
|
1458
1464
|
}
|
|
1459
1465
|
writeFile(path, content, options, cred, onCommit) {
|
|
1460
1466
|
this.assertMutationsAllowed([path]);
|
|
@@ -2223,11 +2229,11 @@ export class SqliteVFS {
|
|
|
2223
2229
|
}
|
|
2224
2230
|
const atime = atimeMs !== null && Number.isFinite(atimeMs) ? Math.trunc(atimeMs) : this.now();
|
|
2225
2231
|
const mtime = mtimeMs !== null && Number.isFinite(mtimeMs) ? Math.trunc(mtimeMs) : this.now();
|
|
2232
|
+
this.sql.exec("UPDATE inodes SET atime = ?, mtime = ? WHERE path = ?", atime, mtime, inode.path);
|
|
2226
2233
|
inode.atime = atime;
|
|
2227
2234
|
inode.mtime = mtime;
|
|
2228
|
-
this.sql.exec("UPDATE inodes SET atime = ?, mtime = ? WHERE path = ?", atime, mtime, inode.path);
|
|
2229
2235
|
this.bumpRevision([inode.path]);
|
|
2230
|
-
this.
|
|
2236
|
+
this.emitMutation('change', inode.path);
|
|
2231
2237
|
}
|
|
2232
2238
|
/**
|
|
2233
2239
|
* Set the permission bits durably. Follows symlinks (POSIX chmod).
|
|
@@ -2265,10 +2271,10 @@ export class SqliteVFS {
|
|
|
2265
2271
|
}
|
|
2266
2272
|
this.assertMutationsAllowed([inode.path]);
|
|
2267
2273
|
const full = inodeTypeBits(inode.kind) | (mode & 0o7777);
|
|
2268
|
-
inode.mode = full;
|
|
2269
2274
|
this.sql.exec("UPDATE inodes SET mode = ? WHERE path = ?", full, inode.path);
|
|
2275
|
+
inode.mode = full;
|
|
2270
2276
|
this.bumpRevision([inode.path]);
|
|
2271
|
-
this.
|
|
2277
|
+
this.emitMutation('change', inode.path);
|
|
2272
2278
|
}
|
|
2273
2279
|
chown(path, uid, gid, cred, followLeaf) {
|
|
2274
2280
|
const resolved = this.checkAccess(path, 0, cred, { followLeaf });
|
|
@@ -2290,13 +2296,15 @@ export class SqliteVFS {
|
|
|
2290
2296
|
if (gid !== null && gid === inode.gid && cred.uid !== 0 && !owner)
|
|
2291
2297
|
throw vfsError('EPERM', resolved.path);
|
|
2292
2298
|
this.assertMutationsAllowed([inode.path]);
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2299
|
+
const nextUid = uid ?? inode.uid;
|
|
2300
|
+
const nextGid = gid ?? inode.gid;
|
|
2301
|
+
const nextMode = cred.uid === 0 ? inode.mode : inode.mode & ~0o6000;
|
|
2302
|
+
this.sql.exec('UPDATE inodes SET uid = ?, gid = ?, mode = ? WHERE path = ?', nextUid, nextGid, nextMode, inode.path);
|
|
2303
|
+
inode.uid = nextUid;
|
|
2304
|
+
inode.gid = nextGid;
|
|
2305
|
+
inode.mode = nextMode;
|
|
2298
2306
|
this.bumpRevision([inode.path]);
|
|
2299
|
-
this.
|
|
2307
|
+
this.emitMutation('change', inode.path);
|
|
2300
2308
|
}
|
|
2301
2309
|
/**
|
|
2302
2310
|
* Enumerate the filesystem, one bounded page at a time.
|
|
@@ -2506,7 +2514,14 @@ export class SqliteVFS {
|
|
|
2506
2514
|
}
|
|
2507
2515
|
rename(oldPath, newPath, cred) {
|
|
2508
2516
|
this.assertMutationsAllowed([oldPath, newPath]);
|
|
2509
|
-
|
|
2517
|
+
// Resolve private /tmp names to storage keys before reading or mutating.
|
|
2518
|
+
const source = this.checkAccess(oldPath, 0, cred, { followLeaf: false });
|
|
2519
|
+
const inode = source.inode;
|
|
2520
|
+
if (!inode)
|
|
2521
|
+
throw vfsError('ENOENT', oldPath);
|
|
2522
|
+
const target = this.checkAccess(newPath, 0, cred, { followLeaf: false, allowMissingLeaf: true });
|
|
2523
|
+
oldPath = source.path;
|
|
2524
|
+
newPath = target.path;
|
|
2510
2525
|
this.checkParentAccess(oldPath, cred);
|
|
2511
2526
|
this.checkParentAccess(newPath, cred);
|
|
2512
2527
|
if (oldPath === newPath)
|
|
@@ -2514,9 +2529,6 @@ export class SqliteVFS {
|
|
|
2514
2529
|
if (newPath.startsWith(`${oldPath}/`)) {
|
|
2515
2530
|
throw new Error(`EINVAL: cannot move ${oldPath} inside itself`);
|
|
2516
2531
|
}
|
|
2517
|
-
const inode = this.inodes.get(oldPath);
|
|
2518
|
-
if (!inode)
|
|
2519
|
-
throw new Error("ENOENT: " + oldPath);
|
|
2520
2532
|
this.checkStickyParentMutation(oldPath, inode, cred);
|
|
2521
2533
|
// W-3 (WASI filesystem WASI): if newPath already exists, unlink it first so the
|
|
2522
2534
|
// SQL UPDATE doesn't conflict on inodes.path uniqueness. POSIX rename(2)
|
|
@@ -2525,7 +2537,7 @@ export class SqliteVFS {
|
|
|
2525
2537
|
// a 2nd `make` after a prior successful build throws on UPDATE
|
|
2526
2538
|
// failure. Pre-unlink covers both file-over-file and file-over-dir
|
|
2527
2539
|
// (the latter is rare but POSIX permits it for empty dirs).
|
|
2528
|
-
const destInode =
|
|
2540
|
+
const destInode = target.inode;
|
|
2529
2541
|
if (destInode) {
|
|
2530
2542
|
this.checkStickyParentMutation(newPath, destInode, cred);
|
|
2531
2543
|
if (destInode.isDir) {
|
|
@@ -2697,7 +2709,7 @@ export class SqliteVFS {
|
|
|
2697
2709
|
}
|
|
2698
2710
|
}
|
|
2699
2711
|
this.bumpRevision([...touchedPaths]);
|
|
2700
|
-
this.
|
|
2712
|
+
this.emitMutation('rename', newPath, oldPath);
|
|
2701
2713
|
this.runContentMaintenanceSafely(1);
|
|
2702
2714
|
}
|
|
2703
2715
|
/**
|
|
@@ -3346,6 +3358,76 @@ export class SqliteVFS {
|
|
|
3346
3358
|
}
|
|
3347
3359
|
return this.errorMessage(error).toUpperCase().includes('SQLITE_NOMEM');
|
|
3348
3360
|
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Participate in an embedder's synchronous transaction: VFS writes and SQL
|
|
3363
|
+
* issued by callback commit together; on rollback the in-memory mirror
|
|
3364
|
+
* returns to the committed state before the error is rethrown with cause.
|
|
3365
|
+
*
|
|
3366
|
+
* This method MUST own the outermost transaction on this VFS's SQL host;
|
|
3367
|
+
* use it instead of wrapping VFS calls in storage.transactionSync. Do not
|
|
3368
|
+
* nest it, return a Promise/thenable, or start asynchronous work inside it.
|
|
3369
|
+
* The callback may read its writes. Revisions and events publish only on
|
|
3370
|
+
* commit, once for the combined mutation. Existing credential checks apply.
|
|
3371
|
+
*
|
|
3372
|
+
* Inodes are an always-resident cache (absence means ENOENT), so rollback
|
|
3373
|
+
* discards cached chunks and rebuilds metadata/children/counters from SQL.
|
|
3374
|
+
* No inode snapshot or undo log is retained. Recovery failure is surfaced
|
|
3375
|
+
* with both errors; the embedder must discard this VFS in that case.
|
|
3376
|
+
*/
|
|
3377
|
+
withTransaction(callback) {
|
|
3378
|
+
if (this.transactionPublication !== null) {
|
|
3379
|
+
throw new Error('[sqlite-vfs] nested embedder transactions are not supported');
|
|
3380
|
+
}
|
|
3381
|
+
const storage = this.ctx.storage;
|
|
3382
|
+
if (!storage)
|
|
3383
|
+
throw new Error('[sqlite-vfs] atomic storage operation requires transactionSync');
|
|
3384
|
+
const publication = {
|
|
3385
|
+
paths: new Set(),
|
|
3386
|
+
events: new Array(),
|
|
3387
|
+
};
|
|
3388
|
+
const maintenancePending = this.maintenancePending;
|
|
3389
|
+
this.transactionPublication = publication;
|
|
3390
|
+
let result;
|
|
3391
|
+
try {
|
|
3392
|
+
result = storage.transactionSync(() => {
|
|
3393
|
+
const value = callback();
|
|
3394
|
+
if (value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
3395
|
+
&& 'then' in value && typeof value.then === 'function') {
|
|
3396
|
+
throw new TypeError('[sqlite-vfs] transaction callback must be synchronous');
|
|
3397
|
+
}
|
|
3398
|
+
return value;
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
catch (error) {
|
|
3402
|
+
this.evictAll();
|
|
3403
|
+
this.maintenancePending = maintenancePending;
|
|
3404
|
+
try {
|
|
3405
|
+
this.loadInodes();
|
|
3406
|
+
}
|
|
3407
|
+
catch (reloadError) {
|
|
3408
|
+
throw new AggregateError([error, reloadError], '[sqlite-vfs] transaction rollback reload failed', { cause: error });
|
|
3409
|
+
}
|
|
3410
|
+
throw new Error('[sqlite-vfs] embedder transaction rolled back', { cause: error });
|
|
3411
|
+
}
|
|
3412
|
+
finally {
|
|
3413
|
+
this.transactionPublication = null;
|
|
3414
|
+
}
|
|
3415
|
+
if (publication.paths.size > 0)
|
|
3416
|
+
this.bumpRevision([...publication.paths]);
|
|
3417
|
+
for (const event of publication.events) {
|
|
3418
|
+
this.events.emit(event.type, event.path, event.oldPath);
|
|
3419
|
+
}
|
|
3420
|
+
this.runContentMaintenanceSafely(1);
|
|
3421
|
+
return result;
|
|
3422
|
+
}
|
|
3423
|
+
emitMutation(type, path, oldPath) {
|
|
3424
|
+
if (this.transactionPublication) {
|
|
3425
|
+
this.transactionPublication.events.push({ type, path, oldPath });
|
|
3426
|
+
}
|
|
3427
|
+
else {
|
|
3428
|
+
this.events.emit(type, path, oldPath);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3349
3431
|
transactionSync(callback) {
|
|
3350
3432
|
if (!this.ctx?.storage?.transactionSync) {
|
|
3351
3433
|
throw new Error('[sqlite-vfs] atomic storage operation requires transactionSync');
|
|
@@ -3601,6 +3683,8 @@ export class SqliteVFS {
|
|
|
3601
3683
|
return { transactions };
|
|
3602
3684
|
}
|
|
3603
3685
|
runContentMaintenanceSafely(maxTransactions, force = false) {
|
|
3686
|
+
if (this.transactionPublication)
|
|
3687
|
+
return;
|
|
3604
3688
|
if (!force && !this.maintenancePending)
|
|
3605
3689
|
return;
|
|
3606
3690
|
const startedAt = performance.now();
|
|
@@ -3906,10 +3990,10 @@ export class SqliteVFS {
|
|
|
3906
3990
|
}
|
|
3907
3991
|
// 5. Events observe the already-published metadata and revision.
|
|
3908
3992
|
for (const inode of deletedInodes) {
|
|
3909
|
-
this.
|
|
3993
|
+
this.emitMutation(inode.isDir ? 'unlinkDir' : 'unlink', inode.path);
|
|
3910
3994
|
}
|
|
3911
3995
|
for (const entry of plan.inodes) {
|
|
3912
|
-
this.
|
|
3996
|
+
this.emitMutation(entry.isDir ? 'addDir' : replacedPaths.has(entry.path) ? 'change' : 'add', entry.path);
|
|
3913
3997
|
}
|
|
3914
3998
|
this.recordDuration(this._postCommitDuration, performance.now() - postCommitStartedAt);
|
|
3915
3999
|
return { inodes: inodeCount, chunks: chunkCount };
|
|
@@ -27,8 +27,11 @@ import type { CommandResult, RunOptions, SandboxFs } from '../substrate/lifo/san
|
|
|
27
27
|
import type { ITerminal } from '../substrate/lifo/terminal/ITerminal.js';
|
|
28
28
|
import { SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
29
29
|
import type { SqlDatabase, TransactionHost } from '../runtime/os-contracts.js';
|
|
30
|
+
import { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
|
|
30
31
|
import type { FacetHost } from '../runtime/facet-host.js';
|
|
31
32
|
import { type RuntimePackage } from '../runtime/runtime-package.js';
|
|
33
|
+
import { type CtxExports, type FabricComposition } from '@nimbus-sh/platform/composition.js';
|
|
34
|
+
import { type SupervisorOpEnvelope, type SupervisorOpHandler } from './supervisor-op.js';
|
|
32
35
|
export interface NimbusWorkspaceOptions {
|
|
33
36
|
/** The host's SQLite. In a Durable Object: `ctx.storage.sql`. */
|
|
34
37
|
readonly sql: SqlDatabase;
|
|
@@ -106,6 +109,15 @@ export interface NimbusWorkspaceOptions {
|
|
|
106
109
|
* carry REPLs and a resident-process substrate this cannot reach.
|
|
107
110
|
*/
|
|
108
111
|
readonly facets?: FacetHost;
|
|
112
|
+
/** Isolate-wide fabric composition, including the hosting namespace. */
|
|
113
|
+
readonly fabric?: FabricComposition;
|
|
114
|
+
/** Explicit exports override the bag on the transaction host. */
|
|
115
|
+
readonly ctxExports?: CtxExports;
|
|
116
|
+
/** The process table that allocated supervisor-binding pids. */
|
|
117
|
+
readonly processes?: SessionProcessSupervisor;
|
|
118
|
+
readonly processOutput?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
|
|
119
|
+
/** Host operations, including overrides for host-specific accounting. */
|
|
120
|
+
readonly supervisorOps?: Readonly<Record<string, SupervisorOpHandler>>;
|
|
109
121
|
}
|
|
110
122
|
/**
|
|
111
123
|
* A durable filesystem and a shell over it.
|
|
@@ -120,6 +132,7 @@ export interface NimbusWorkspaceOptions {
|
|
|
120
132
|
*/
|
|
121
133
|
export declare class NimbusWorkspace {
|
|
122
134
|
private readonly sql;
|
|
135
|
+
private readonly supervisorOps;
|
|
123
136
|
/**
|
|
124
137
|
* Credentialed and mount-aware. Acts as the session user, never as the
|
|
125
138
|
* kernel: a pid-less caller must not gain more authority than the shell it
|
|
@@ -142,6 +155,8 @@ export declare class NimbusWorkspace {
|
|
|
142
155
|
private constructor();
|
|
143
156
|
static create(options: NimbusWorkspaceOptions): Promise<NimbusWorkspace>;
|
|
144
157
|
exec(command: string, options?: RunOptions): Promise<CommandResult>;
|
|
158
|
+
/** The hosting object forwards its supervisorOp RPC to this method. */
|
|
159
|
+
supervisorOp(envelope: SupervisorOpEnvelope): Promise<unknown>;
|
|
145
160
|
/**
|
|
146
161
|
* Apply the login files, and begin reading the terminal when there is one.
|
|
147
162
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,kCAAkC,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAI9E,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,SAAS,EAAqB,MAAM,sBAAsB,CAAC;AAMpE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,8 @@ import { rehydrateInstalledRuntimesView, } from '../runtime/installed-runtimes.j
|
|
|
34
34
|
import { seedRuntimePackage } from '../runtime/runtime-package.js';
|
|
35
35
|
import { registerUnixCommands } from '../shell/unix-commands.js';
|
|
36
36
|
import { installPathExecResolver } from '../shell/exec-dispatch.js';
|
|
37
|
+
import { adoptCtxExports, composeFabric } from '@nimbus-sh/platform/composition.js';
|
|
38
|
+
import { createSupervisorOpHandler } from './supervisor-op.js';
|
|
37
39
|
/**
|
|
38
40
|
* A durable filesystem and a shell over it.
|
|
39
41
|
*
|
|
@@ -47,6 +49,7 @@ import { installPathExecResolver } from '../shell/exec-dispatch.js';
|
|
|
47
49
|
*/
|
|
48
50
|
export class NimbusWorkspace {
|
|
49
51
|
sql;
|
|
52
|
+
supervisorOps;
|
|
50
53
|
/**
|
|
51
54
|
* Credentialed and mount-aware. Acts as the session user, never as the
|
|
52
55
|
* kernel: a pid-less caller must not gain more authority than the shell it
|
|
@@ -66,8 +69,9 @@ export class NimbusWorkspace {
|
|
|
66
69
|
*/
|
|
67
70
|
env;
|
|
68
71
|
commands;
|
|
69
|
-
constructor(vfs, kernel, shell, registry, env, sql) {
|
|
72
|
+
constructor(vfs, kernel, shell, registry, env, sql, supervisorOps) {
|
|
70
73
|
this.sql = sql;
|
|
74
|
+
this.supervisorOps = supervisorOps;
|
|
71
75
|
this.vfs = vfs;
|
|
72
76
|
this.kernel = kernel;
|
|
73
77
|
this.shell = shell;
|
|
@@ -80,6 +84,12 @@ export class NimbusWorkspace {
|
|
|
80
84
|
this.fs = new SandboxFsImpl(kernel.vfs.as(CRED_SESSION_USER), () => shell.getCwd());
|
|
81
85
|
}
|
|
82
86
|
static async create(options) {
|
|
87
|
+
if (options.fabric)
|
|
88
|
+
composeFabric(options.fabric);
|
|
89
|
+
const exports = options.ctxExports
|
|
90
|
+
?? options.transactions?.exports;
|
|
91
|
+
if (exports)
|
|
92
|
+
adoptCtxExports(exports);
|
|
83
93
|
const vfs = options.vfs ?? openFilesystem(options);
|
|
84
94
|
const mounts = options.mounts ?? DEFAULT_MOUNT_POINTS;
|
|
85
95
|
seedBaseFilesystem(vfs, mounts);
|
|
@@ -118,11 +128,18 @@ export class NimbusWorkspace {
|
|
|
118
128
|
home,
|
|
119
129
|
});
|
|
120
130
|
}
|
|
121
|
-
|
|
131
|
+
const supervisorOps = createSupervisorOpHandler({
|
|
132
|
+
vfs, processes: options.processes, output: options.processOutput, extend: options.supervisorOps,
|
|
133
|
+
});
|
|
134
|
+
return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql, supervisorOps);
|
|
122
135
|
}
|
|
123
136
|
exec(command, options) {
|
|
124
137
|
return this.commands.run(command, options);
|
|
125
138
|
}
|
|
139
|
+
/** The hosting object forwards its supervisorOp RPC to this method. */
|
|
140
|
+
supervisorOp(envelope) {
|
|
141
|
+
return this.supervisorOps(envelope);
|
|
142
|
+
}
|
|
126
143
|
/**
|
|
127
144
|
* Apply the login files, and begin reading the terminal when there is one.
|
|
128
145
|
*
|
|
@@ -243,7 +260,7 @@ async function registerWasmRuntimes(deps) {
|
|
|
243
260
|
getEsbuild: () => {
|
|
244
261
|
if (!esbuild) {
|
|
245
262
|
esbuild = import('../runtime/esbuild-service.js')
|
|
246
|
-
.then((module) => new module.EsbuildService(deps.vfs));
|
|
263
|
+
.then((module) => new module.EsbuildService(deps.vfs.as(CRED_KERNEL)));
|
|
247
264
|
}
|
|
248
265
|
return esbuild;
|
|
249
266
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
2
|
+
import type { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
|
|
3
|
+
/** Identity comes from the supervisor binding, never from facet arguments. */
|
|
4
|
+
export interface SupervisorOpEnvelope {
|
|
5
|
+
readonly op: string;
|
|
6
|
+
readonly args?: readonly unknown[];
|
|
7
|
+
readonly pid?: number;
|
|
8
|
+
readonly writerId?: string;
|
|
9
|
+
readonly mutationOwner?: string;
|
|
10
|
+
readonly stream?: ReadableStream<Uint8Array>;
|
|
11
|
+
}
|
|
12
|
+
export type SupervisorOpHandler = (envelope: SupervisorOpEnvelope) => unknown;
|
|
13
|
+
export interface SupervisorOpDeps {
|
|
14
|
+
readonly vfs: SqliteVFS;
|
|
15
|
+
/** Absent a process table, operations use the unprivileged session user. */
|
|
16
|
+
readonly processes?: SessionProcessSupervisor;
|
|
17
|
+
readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
|
|
18
|
+
/** Host handlers override defaults, for example to account for stream drains. */
|
|
19
|
+
readonly extend?: Readonly<Record<string, SupervisorOpHandler>>;
|
|
20
|
+
}
|
|
21
|
+
/** One dispatch method lets any host serve its workspace to process facets. */
|
|
22
|
+
export declare function createSupervisorOpHandler(deps: SupervisorOpDeps): (envelope: SupervisorOpEnvelope) => Promise<unknown>;
|
|
23
|
+
//# sourceMappingURL=supervisor-op.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor-op.d.ts","sourceRoot":"","sources":["../../src/workspace/supervisor-op.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAEzF,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CAC9C;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,OAAO,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,wBAAwB,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnF,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;CACjE;AAiCD,+EAA+E;AAC/E,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,gBAAgB,GACrB,CAAC,QAAQ,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAuDtD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CRED_SESSION_USER } from '../runtime/os-contracts.js';
|
|
2
|
+
import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
|
|
3
|
+
import { getSymlinkRegistry } from '../vfs/symlink-registry.js';
|
|
4
|
+
function stringArg(envelope, index) {
|
|
5
|
+
const value = envelope.args?.[index];
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a string`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function numberArg(envelope, index) {
|
|
12
|
+
const value = envelope.args?.[index];
|
|
13
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
14
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a number`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function contentArg(envelope, index) {
|
|
19
|
+
const value = envelope.args?.[index];
|
|
20
|
+
if (typeof value === 'string' || value instanceof Uint8Array)
|
|
21
|
+
return value;
|
|
22
|
+
if (value instanceof ArrayBuffer)
|
|
23
|
+
return new Uint8Array(value);
|
|
24
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be bytes or text`);
|
|
25
|
+
}
|
|
26
|
+
function credFor(deps, pid) {
|
|
27
|
+
if (pid === undefined)
|
|
28
|
+
return CRED_SESSION_USER;
|
|
29
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
30
|
+
throw new Error('supervisor op: filesystem operation requires a valid process pid');
|
|
31
|
+
}
|
|
32
|
+
return deps.processes ? deps.processes.cred(pid) : CRED_SESSION_USER;
|
|
33
|
+
}
|
|
34
|
+
/** One dispatch method lets any host serve its workspace to process facets. */
|
|
35
|
+
export function createSupervisorOpHandler(deps) {
|
|
36
|
+
const bridges = new Map();
|
|
37
|
+
const bridgeFor = (pid) => {
|
|
38
|
+
const key = pid ?? 0;
|
|
39
|
+
const credentialed = deps.vfs.as(credFor(deps, pid));
|
|
40
|
+
const held = bridges.get(key);
|
|
41
|
+
if (held) {
|
|
42
|
+
held.updateCredential(credentialed);
|
|
43
|
+
return held;
|
|
44
|
+
}
|
|
45
|
+
const built = new SqliteRuntimeFsBridge(credentialed, deps.vfs);
|
|
46
|
+
bridges.set(key, built);
|
|
47
|
+
return built;
|
|
48
|
+
};
|
|
49
|
+
const ops = {
|
|
50
|
+
readFile: async (e) => {
|
|
51
|
+
const bytes = await bridgeFor(e.pid).readFile(stringArg(e, 0));
|
|
52
|
+
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
53
|
+
},
|
|
54
|
+
readFileBytes: (e) => bridgeFor(e.pid).readFile(stringArg(e, 0)),
|
|
55
|
+
stat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0)),
|
|
56
|
+
lstat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0), { followSymlinks: false }),
|
|
57
|
+
exists: async (e) => (await bridgeFor(e.pid).stat(stringArg(e, 0))) !== null,
|
|
58
|
+
readdir: (e) => bridgeFor(e.pid).readdir(stringArg(e, 0)),
|
|
59
|
+
readlink: (e) => bridgeFor(e.pid).readlink(stringArg(e, 0)),
|
|
60
|
+
fsReadRange: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
|
|
61
|
+
fsReadRangeUncached: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2), { cached: false }),
|
|
62
|
+
fsRevision: (e) => bridgeFor(e.pid).revision(e.args?.[0] === undefined ? undefined : stringArg(e, 0)),
|
|
63
|
+
hasLegacySymlinkUnder: (e) => getSymlinkRegistry(deps.vfs).hasAtOrBelow(stringArg(e, 0)),
|
|
64
|
+
writeFile: (e) => bridgeFor(e.pid).writeFile(stringArg(e, 0), contentArg(e, 1)),
|
|
65
|
+
mkdir: (e) => bridgeFor(e.pid).mkdir(stringArg(e, 0), { recursive: true }),
|
|
66
|
+
rmdir: (e) => bridgeFor(e.pid).rmdir(stringArg(e, 0)),
|
|
67
|
+
unlink: (e) => bridgeFor(e.pid).unlink(stringArg(e, 0)),
|
|
68
|
+
rename: (e) => bridgeFor(e.pid).rename(stringArg(e, 0), stringArg(e, 1)),
|
|
69
|
+
symlink: (e) => bridgeFor(e.pid).symlink(stringArg(e, 0), stringArg(e, 1)),
|
|
70
|
+
chmod: (e) => bridgeFor(e.pid).chmod(stringArg(e, 0), numberArg(e, 1)),
|
|
71
|
+
utimes: (e) => bridgeFor(e.pid).utimes(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
|
|
72
|
+
fsTruncate: (e) => bridgeFor(e.pid).truncate(stringArg(e, 0), numberArg(e, 1)),
|
|
73
|
+
writeBatchStream: (e) => {
|
|
74
|
+
if (!e.stream)
|
|
75
|
+
throw new Error('supervisor op writeBatchStream: no stream');
|
|
76
|
+
return deps.vfs.as(credFor(deps, e.pid)).writeStream(e.stream, { mutationOwner: e.mutationOwner });
|
|
77
|
+
},
|
|
78
|
+
stdout: (e) => { deps.output?.('stdout', e.pid ?? 0, stringArg(e, 0)); },
|
|
79
|
+
stderr: (e) => { deps.output?.('stderr', e.pid ?? 0, stringArg(e, 0)); },
|
|
80
|
+
};
|
|
81
|
+
const extend = deps.extend ?? {};
|
|
82
|
+
return async (envelope) => {
|
|
83
|
+
if (!envelope || typeof envelope.op !== 'string') {
|
|
84
|
+
throw new Error('supervisor op: envelope names no operation');
|
|
85
|
+
}
|
|
86
|
+
const handler = Object.hasOwn(extend, envelope.op) ? extend[envelope.op]
|
|
87
|
+
: Object.hasOwn(ops, envelope.op) ? ops[envelope.op] : undefined;
|
|
88
|
+
if (!handler)
|
|
89
|
+
throw new Error(`supervisor op: '${envelope.op}' is not served by this host`);
|
|
90
|
+
return handler(envelope);
|
|
91
|
+
};
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Backend-agnostic half of Nimbus
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Backend-agnostic half of Nimbus — the durable filesystem, the shell, and the OS/process contracts, over a host-supplied SQL port.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
7
7
|
"shell",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
+
"@nimbus-sh/platform": "^0.2.0",
|
|
58
59
|
"@renovatebot/pep440": "^5.0.0",
|
|
59
60
|
"acorn": "^8.15.0",
|
|
60
61
|
"acorn-walk": "^8.3.5",
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
/**
|
|
19
19
|
* A command's output destination.
|
|
20
20
|
*
|
|
21
|
-
* `writeBytes` is present on sinks that store bytes verbatim
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* fallback: a terminal has no way to hold a byte that is
|
|
21
|
+
* `writeBytes` is present on sinks that store bytes verbatim — files,
|
|
22
|
+
* `/dev/null`, and shell pipes, which carry the producer's exact bytes.
|
|
23
|
+
* Sinks without it take decoded text instead. This is a real capability
|
|
24
|
+
* difference, not a fallback: a terminal has no way to hold a byte that is
|
|
25
|
+
* not text.
|
|
25
26
|
*/
|
|
26
27
|
export interface ByteSink {
|
|
27
28
|
write(text: string): void;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* L4 — registry.npmjs.org origin ~100-300 ms cross-region
|
|
12
12
|
*
|
|
13
13
|
* Pre-cache metrics support: hit/miss counters lived in
|
|
14
|
-
*
|
|
14
|
+
* @nimbus-sh/platform/diag-counters.js but only tracked the RPC-call
|
|
15
15
|
* boundary, not per-tier. Those flat counters have since been removed.
|
|
16
16
|
*
|
|
17
17
|
* This module adds per-tier × per-kind counters. SAME singleton-per-
|
|
@@ -229,3 +229,29 @@ export function reset(): void {
|
|
|
229
229
|
}
|
|
230
230
|
_lastResetAt = Date.now();
|
|
231
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* cache-obs-2: fold facet-collected per-tier cache events into the
|
|
235
|
+
* DO-side cache-stats singleton. Called from installer.ts after a
|
|
236
|
+
* batch-facet / resolve-facet returns — mirrors recordR2RaceCounters
|
|
237
|
+
* (the facet collects metrics in its result and the supervisor folds
|
|
238
|
+
* them into the DO isolate).
|
|
239
|
+
*
|
|
240
|
+
* Each event has shape:
|
|
241
|
+
* { kind: 'hit', tier: 'L2'|'L3'|'L4', cacheKind: 'tarball'|'packument'|'asset', bytes: number }
|
|
242
|
+
* { kind: 'miss', tier: ..., cacheKind: ... }
|
|
243
|
+
*/
|
|
244
|
+
export type CacheStatEvent =
|
|
245
|
+
| { kind: 'hit'; tier: CacheTier; cacheKind: CacheKind; bytes: number }
|
|
246
|
+
| { kind: 'miss'; tier: CacheTier; cacheKind: CacheKind };
|
|
247
|
+
|
|
248
|
+
export function recordCacheStatEvents(events: readonly CacheStatEvent[] | undefined): void {
|
|
249
|
+
if (!events || events.length === 0) return;
|
|
250
|
+
for (const e of events) {
|
|
251
|
+
if (e.kind === 'hit') {
|
|
252
|
+
recordHit(e.tier, e.cacheKind, e.bytes);
|
|
253
|
+
} else {
|
|
254
|
+
recordMiss(e.tier, e.cacheKind);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 1. Install-time supervisor (TS) — `src/npm-resolver.ts` re-exports the
|
|
8
8
|
* typed functions for tree resolution.
|
|
9
9
|
*
|
|
10
|
-
* 2.
|
|
10
|
+
* 2. IsolatePool isolates (JS string) — `src/loaders/pre-bundle-preamble.ts`
|
|
11
11
|
* embeds `getExportsResolverJS()` as part of the pool preamble so the
|
|
12
12
|
* pre-bundle facet uses identical resolution semantics to the supervisor.
|
|
13
13
|
*
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
* constructed Session's connect/post are inert.
|
|
39
39
|
* Tools (e.g. nuxi) that open a Session purely
|
|
40
40
|
* for optional profiling degrade cleanly.
|
|
41
|
+
* - node:zlib — full surface: every *Sync variant, brotli/zstd,
|
|
42
|
+
* crc32, constants, and streaming create* factories
|
|
43
|
+
* (probe-verified 2026-08-23 at compat date
|
|
44
|
+
* 2026-04-01). Forwarded verbatim by the zlib
|
|
45
|
+
* block in node-shims.ts; results are the host
|
|
46
|
+
* realm's own Buffers, which the widened
|
|
47
|
+
* __BufferMod.isBuffer recognizes.
|
|
41
48
|
*/
|
|
42
49
|
|
|
43
50
|
export function getRealNodeImportsCode(): string {
|
|
@@ -49,5 +56,6 @@ import * as __real_diagnostics_channel from 'node:diagnostics_channel';
|
|
|
49
56
|
import * as __real_repl from 'node:repl';
|
|
50
57
|
import * as __real_vm from 'node:vm';
|
|
51
58
|
import * as __real_inspector from 'node:inspector';
|
|
59
|
+
import * as __real_zlib from 'node:zlib';
|
|
52
60
|
`.trim();
|
|
53
61
|
}
|
package/src/_shared/retry.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* equivalent retry loops inside their isolated function bodies.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { disposeRpcResource } from '
|
|
26
|
+
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
27
27
|
import { errorText } from './error-text.js';
|
|
28
28
|
|
|
29
29
|
/** Default retry count AFTER the first attempt (3 = up to 4 total attempts). */
|