@nimbus-sh/core 0.6.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/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/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/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/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/sqlite-vfs.d.ts +19 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +106 -23
- 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 +2 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/tarball.ts +56 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- 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/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/sqlite-vfs.ts +104 -23
- package/src/workspace/nimbus-workspace.ts +26 -2
- package/src/workspace/supervisor-op.ts +117 -0
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@nimbus-sh/platform": "^0.
|
|
58
|
+
"@nimbus-sh/platform": "^0.2.0",
|
|
59
59
|
"@renovatebot/pep440": "^5.0.0",
|
|
60
60
|
"acorn": "^8.15.0",
|
|
61
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;
|
|
@@ -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/tarball.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tarball extraction for streaming installers and buffered archive consumers.
|
|
3
3
|
*
|
|
4
4
|
* The streaming primitives it walks with (`parseTarHeader`, `streamTarEntries`,
|
|
5
5
|
* `readableStreamToAsyncIterable`) live in `./tarball-stream.ts` — a
|
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
* into a string the loader pool injects into dynamic workers, where an import
|
|
8
8
|
* would not resolve.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* once. An installer streaming a tarball it is still downloading should drive
|
|
13
|
-
* `streamTarEntries` itself and never hold the whole thing.
|
|
10
|
+
* Installers use writeTarballStream. extractTarball retains a map for callers
|
|
11
|
+
* such as gem install, which must open an archive nested inside another one.
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import {
|
|
@@ -18,6 +16,59 @@ import {
|
|
|
18
16
|
readableStreamToAsyncIterable,
|
|
19
17
|
} from './tarball-stream.js';
|
|
20
18
|
|
|
19
|
+
export interface TarballWriteTarget {
|
|
20
|
+
exists(path: string): boolean;
|
|
21
|
+
mkdir(path: string, options?: { recursive?: boolean }): void;
|
|
22
|
+
writeFile(path: string, data: Uint8Array | string): unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TarballWriteResult {
|
|
26
|
+
/** Regular files written, including the manifest. */
|
|
27
|
+
files: number;
|
|
28
|
+
/** Total decompressed bytes written. */
|
|
29
|
+
bytes: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PACKAGE_MANIFEST = 'package.json';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stream a gzipped npm archive into a package directory. Entry names are
|
|
36
|
+
* already canonical and prefix-stripped by streamTarEntries. Hold only the
|
|
37
|
+
* current entry and the manifest; write the manifest last so a failed install
|
|
38
|
+
* is not mistaken for a complete package on retry. Filesystem failures reject.
|
|
39
|
+
*/
|
|
40
|
+
export async function writeTarballStream(
|
|
41
|
+
body: ReadableStream<Uint8Array>,
|
|
42
|
+
targetDir: string,
|
|
43
|
+
vfs: TarballWriteTarget,
|
|
44
|
+
): Promise<TarballWriteResult> {
|
|
45
|
+
const ensureDir = (path: string): void => {
|
|
46
|
+
if (!vfs.exists(path)) vfs.mkdir(path, { recursive: true });
|
|
47
|
+
};
|
|
48
|
+
ensureDir(targetDir);
|
|
49
|
+
let files = 0;
|
|
50
|
+
let bytes = 0;
|
|
51
|
+
let manifest: Uint8Array | null = null;
|
|
52
|
+
const entries = streamTarEntries(
|
|
53
|
+
readableStreamToAsyncIterable(body.pipeThrough(new DecompressionStream('gzip'))),
|
|
54
|
+
);
|
|
55
|
+
for await (const entry of entries) {
|
|
56
|
+
if (entry.name === PACKAGE_MANIFEST) {
|
|
57
|
+
manifest = entry.data;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const fullPath = `${targetDir}/${entry.name}`;
|
|
61
|
+
const cut = fullPath.lastIndexOf('/');
|
|
62
|
+
if (cut > 0) ensureDir(fullPath.slice(0, cut));
|
|
63
|
+
await vfs.writeFile(fullPath, entry.data);
|
|
64
|
+
files++;
|
|
65
|
+
bytes += entry.data.length;
|
|
66
|
+
}
|
|
67
|
+
if (!manifest) throw new Error(`tarball for ${targetDir} carried no ${PACKAGE_MANIFEST}`);
|
|
68
|
+
await vfs.writeFile(`${targetDir}/${PACKAGE_MANIFEST}`, manifest);
|
|
69
|
+
return { files: files + 1, bytes: bytes + manifest.length };
|
|
70
|
+
}
|
|
71
|
+
|
|
21
72
|
/** Extract every regular file. Gzipped input is decompressed first. */
|
|
22
73
|
export async function extractTarball(
|
|
23
74
|
tarball: ArrayBuffer,
|
|
@@ -317,26 +317,32 @@ async function __nimbusPersistVfsWrite(supervisor, path, content, snapshot) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
/**
|
|
320
|
-
* Write back
|
|
320
|
+
* Write back the cells parked at THIS instant, and only those.
|
|
321
321
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
322
|
+
* Bounded on purpose, and deliberately not routed through
|
|
323
|
+
* \`__nimbusDrainVfsWrites\`, whose \`while (pending > 0)\` waits for the mutation
|
|
324
|
+
* queue to be EMPTY. That wait is correct at process exit, where no new writes
|
|
325
|
+
* are coming. Anywhere else it is a livelock: a facet unpacking a tarball adds
|
|
326
|
+
* mutations faster than the loop retires them, so the loop never returns.
|
|
327
|
+
* Sited ahead of egress — where it was — that stopped the request from ever
|
|
328
|
+
* leaving the facet, and \`npx sv create\` ran, printed its intro, and then
|
|
329
|
+
* never reported an exit at all. A barrier may delay a request; it may not
|
|
330
|
+
* wait on a condition a busy process never reaches.
|
|
331
|
+
*
|
|
332
|
+
* Failures are retained rather than thrown. The two callers — the debounce
|
|
333
|
+
* below, and the RELEASE barrier ahead of egress — have no frame that could
|
|
334
|
+
* act on one: rejecting the fetch that happened to trigger the flush would
|
|
335
|
+
* blame the wrong operation. The exit drain reports what is retained, so a
|
|
336
|
+
* lost write is loud exactly once and never silent.
|
|
329
337
|
*/
|
|
330
338
|
async function __nimbusFlushVfsWriteBack(supervisor) {
|
|
331
339
|
if (!supervisor) return;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
+
const paths = Object.keys(__vfsWrites);
|
|
341
|
+
if (paths.length === 0) return;
|
|
342
|
+
await Promise.allSettled(paths.map((path) => __nimbusFlushVfsWrite(
|
|
343
|
+
path,
|
|
344
|
+
(content, snapshot) => __nimbusPersistVfsWrite(supervisor, path, content, snapshot),
|
|
345
|
+
)));
|
|
340
346
|
}
|
|
341
347
|
|
|
342
348
|
/**
|
|
@@ -27,47 +27,66 @@
|
|
|
27
27
|
* so it's a minor wasted-work cost — see IMPORT_RE header comment in
|
|
28
28
|
* `require-resolver.ts:84-86`.
|
|
29
29
|
*
|
|
30
|
+
* Memory: the output is assembled from slices of the input — one per
|
|
31
|
+
* comment-free span — and joined once. It used to be built one character
|
|
32
|
+
* at a time with `out += c`, which every JS engine represents as a rope
|
|
33
|
+
* of one node per append: ~30 bytes of heap per character of output, all
|
|
34
|
+
* of it live until the string is first read. The require walk runs this
|
|
35
|
+
* over every file in the closure, and on typescript's 6.15 MB `lib/_tsc.js`
|
|
36
|
+
* that rope measured 174 MB (V8) / 172 MB (JSC) against a 128 MB isolate —
|
|
37
|
+
* the session Durable Object was killed inside `tsc`'s spawn before the
|
|
38
|
+
* facet existed. A span-sliced build holds the input, the output and a
|
|
39
|
+
* short array of slices, and nothing else.
|
|
40
|
+
*
|
|
30
41
|
* TODO(CLN-X): when the `src/runtime/esbuild-service.ts` anti-touch
|
|
31
42
|
* window opens, unify with the canonical
|
|
32
43
|
* `stripCommentsAndStrings(src)` helper there. Both implement the
|
|
33
44
|
* same logical pass over comments; only the string/regex-literal
|
|
34
45
|
* handling differs.
|
|
35
46
|
*/
|
|
47
|
+
const SLASH = 0x2f;
|
|
48
|
+
const STAR = 0x2a;
|
|
49
|
+
const NEWLINE = 0x0a;
|
|
50
|
+
|
|
36
51
|
export function stripCommentsForImports(src: string): string {
|
|
37
|
-
let out = '';
|
|
38
|
-
let i = 0;
|
|
39
52
|
const N = src.length;
|
|
53
|
+
const parts: string[] = [];
|
|
54
|
+
// Start of the comment-free span not yet copied to `parts`.
|
|
55
|
+
let spanStart = 0;
|
|
56
|
+
let i = 0;
|
|
40
57
|
while (i < N) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
if (src.charCodeAt(i) !== SLASH) { i++; continue; }
|
|
59
|
+
const next = src.charCodeAt(i + 1);
|
|
60
|
+
// Line comment: `//` to end-of-line, replaced by one space. The
|
|
61
|
+
// newline is not part of the comment and stays in the next span.
|
|
62
|
+
if (next === SLASH) {
|
|
63
|
+
parts.push(src.slice(spanStart, i), ' ');
|
|
45
64
|
i += 2;
|
|
46
|
-
while (i < N && src
|
|
47
|
-
|
|
65
|
+
while (i < N && src.charCodeAt(i) !== NEWLINE) i++;
|
|
66
|
+
spanStart = i;
|
|
48
67
|
continue;
|
|
49
68
|
}
|
|
50
|
-
// Block comment: `/*` to
|
|
51
|
-
|
|
69
|
+
// Block comment: `/*` to `*/`, replaced by its own newlines (so line
|
|
70
|
+
// numbers stay aligned) and one space. An unterminated one runs to
|
|
71
|
+
// the end of input and gets no closing space.
|
|
72
|
+
if (next === STAR) {
|
|
73
|
+
parts.push(src.slice(spanStart, i));
|
|
52
74
|
i += 2;
|
|
75
|
+
let newlines = 0;
|
|
76
|
+
let closed = false;
|
|
53
77
|
while (i < N) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
i++;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (src[i] === '*' && src[i + 1] === '/') {
|
|
61
|
-
i += 2;
|
|
62
|
-
out += ' ';
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
78
|
+
const c = src.charCodeAt(i);
|
|
79
|
+
if (c === NEWLINE) newlines++;
|
|
80
|
+
else if (c === STAR && src.charCodeAt(i + 1) === SLASH) { i += 2; closed = true; break; }
|
|
65
81
|
i++;
|
|
66
82
|
}
|
|
83
|
+
if (newlines > 0) parts.push('\n'.repeat(newlines));
|
|
84
|
+
if (closed) parts.push(' ');
|
|
85
|
+
spanStart = i;
|
|
67
86
|
continue;
|
|
68
87
|
}
|
|
69
|
-
out += c;
|
|
70
88
|
i++;
|
|
71
89
|
}
|
|
72
|
-
|
|
90
|
+
parts.push(src.slice(spanStart));
|
|
91
|
+
return parts.join('');
|
|
73
92
|
}
|