@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.
Files changed (88) hide show
  1. package/README.md +47 -26
  2. package/dist/_shared/byte-stream.d.ts +5 -4
  3. package/dist/_shared/byte-stream.d.ts.map +1 -1
  4. package/dist/_shared/real-node-imports.d.ts +7 -0
  5. package/dist/_shared/real-node-imports.d.ts.map +1 -1
  6. package/dist/_shared/real-node-imports.js +8 -0
  7. package/dist/_shared/tarball.d.ts +23 -5
  8. package/dist/_shared/tarball.d.ts.map +1 -1
  9. package/dist/_shared/tarball.js +38 -5
  10. package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
  11. package/dist/_shared/vfs-write-ledger.js +22 -16
  12. package/dist/runtime/comment-strip.d.ts +0 -35
  13. package/dist/runtime/comment-strip.d.ts.map +1 -1
  14. package/dist/runtime/comment-strip.js +48 -20
  15. package/dist/runtime/esbuild-service.d.ts +38 -42
  16. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  17. package/dist/runtime/esbuild-service.js +494 -197
  18. package/dist/runtime/javascript-ast.d.ts.map +1 -1
  19. package/dist/runtime/javascript-ast.js +45 -7
  20. package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
  21. package/dist/runtime/opentui-wasm-backend.js +18 -16
  22. package/dist/runtime/port-registry.d.ts +4 -2
  23. package/dist/runtime/port-registry.d.ts.map +1 -1
  24. package/dist/runtime/port-registry.js +60 -8
  25. package/dist/shell/stdin-adapter.d.ts +11 -0
  26. package/dist/shell/stdin-adapter.d.ts.map +1 -0
  27. package/dist/shell/stdin-adapter.js +59 -0
  28. package/dist/shell/unix-commands.d.ts +2 -1
  29. package/dist/shell/unix-commands.d.ts.map +1 -1
  30. package/dist/shell/unix-commands.js +1078 -50
  31. package/dist/substrate/lifo/commands/io/dd.js +1 -1
  32. package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
  33. package/dist/substrate/lifo/commands/net/curl.js +270 -54
  34. package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
  35. package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
  36. package/dist/substrate/lifo/commands/system/npm.js +26 -43
  37. package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
  38. package/dist/substrate/lifo/commands/text/head.js +2 -1
  39. package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
  40. package/dist/substrate/lifo/commands/text/sed.js +499 -117
  41. package/dist/substrate/lifo/commands/types.d.ts +8 -4
  42. package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
  43. package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
  44. package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
  45. package/dist/substrate/lifo/node-compat/zlib.js +122 -23
  46. package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
  47. package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
  48. package/dist/substrate/lifo/shell/interpreter.js +79 -29
  49. package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
  50. package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
  51. package/dist/substrate/lifo/shell/pipe.js +62 -18
  52. package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
  53. package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
  54. package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
  55. package/dist/vfs/sqlite-vfs.d.ts +19 -0
  56. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  57. package/dist/vfs/sqlite-vfs.js +106 -23
  58. package/dist/workspace/nimbus-workspace.d.ts +15 -0
  59. package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
  60. package/dist/workspace/nimbus-workspace.js +20 -3
  61. package/dist/workspace/supervisor-op.d.ts +23 -0
  62. package/dist/workspace/supervisor-op.d.ts.map +1 -0
  63. package/dist/workspace/supervisor-op.js +92 -0
  64. package/package.json +2 -2
  65. package/src/_shared/byte-stream.ts +5 -4
  66. package/src/_shared/real-node-imports.ts +8 -0
  67. package/src/_shared/tarball.ts +56 -5
  68. package/src/_shared/vfs-write-ledger.ts +22 -16
  69. package/src/runtime/comment-strip.ts +42 -23
  70. package/src/runtime/esbuild-service.ts +566 -217
  71. package/src/runtime/javascript-ast.ts +40 -9
  72. package/src/runtime/opentui-wasm-backend.ts +19 -17
  73. package/src/runtime/port-registry.ts +66 -8
  74. package/src/shell/stdin-adapter.ts +58 -0
  75. package/src/shell/unix-commands.ts +1054 -50
  76. package/src/substrate/lifo/commands/io/dd.ts +2 -2
  77. package/src/substrate/lifo/commands/net/curl.ts +310 -58
  78. package/src/substrate/lifo/commands/system/npm.ts +58 -38
  79. package/src/substrate/lifo/commands/text/head.ts +2 -1
  80. package/src/substrate/lifo/commands/text/sed.ts +519 -109
  81. package/src/substrate/lifo/commands/types.ts +8 -5
  82. package/src/substrate/lifo/node-compat/zlib.ts +155 -26
  83. package/src/substrate/lifo/shell/interpreter.ts +74 -27
  84. package/src/substrate/lifo/shell/pipe.ts +65 -24
  85. package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
  86. package/src/vfs/sqlite-vfs.ts +104 -23
  87. package/src/workspace/nimbus-workspace.ts +26 -2
  88. 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":"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"}
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
- return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql);
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.6.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.1.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 (files). Sinks
22
- * that are inherently textual — the terminal, shell pipes omit it and
23
- * receive decoded text instead. This is a real capability difference, not a
24
- * fallback: a terminal has no way to hold a byte that is not text.
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
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tarball.ts a whole tarball, in memory, as a path→bytes map.
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
- * This half is for a caller that already holds the bytes: `gem install`, which
11
- * fetches a `.gem` (a tar holding a gzipped tar) and needs both layers open at
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 everything parked, keeping any failure for the exit drain.
320
+ * Write back the cells parked at THIS instant, and only those.
321
321
  *
322
- * The write-back sites that are not the exit drain — the debounce below and
323
- * the RELEASE barrier ahead of egress have no caller who could act on a
324
- * failure: there is no user frame to throw into, and rejecting the fetch that
325
- * happened to trigger the flush would blame the wrong operation. Retaining
326
- * the failure in the channel \`__nimbusDrainVfsMutations\` already drains means
327
- * the exit path still reports it, so a lost write is loud exactly once and
328
- * never silent.
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
- try {
333
- await __nimbusDrainVfsWrites(supervisor);
334
- } catch (error) {
335
- if (!__nimbusHasPendingVfsMutationFailure) {
336
- __nimbusHasPendingVfsMutationFailure = true;
337
- __nimbusPendingVfsMutationFailure = error;
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
- const c = src[i];
42
- // Line comment: `//` to end-of-line.
43
- if (c === '/' && src[i + 1] === '/') {
44
- out += ' ';
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[i] !== '\n') i++;
47
- // Newline preserved by falling through; do NOT consume it.
65
+ while (i < N && src.charCodeAt(i) !== NEWLINE) i++;
66
+ spanStart = i;
48
67
  continue;
49
68
  }
50
- // Block comment: `/*` to `*/`.
51
- if (c === '/' && src[i + 1] === '*') {
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
- if (src[i] === '\n') {
55
- // Preserve newlines so line numbers stay aligned.
56
- out += '\n';
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
- return out;
90
+ parts.push(src.slice(spanStart));
91
+ return parts.join('');
73
92
  }