@nimbus-sh/worker 0.2.0 → 0.2.2

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 (83) hide show
  1. package/dist/facets/manager.d.ts +47 -2
  2. package/dist/facets/manager.d.ts.map +1 -1
  3. package/dist/facets/manager.js +139 -15
  4. package/dist/loaders/generated-workers.d.ts +2 -2
  5. package/dist/loaders/generated-workers.d.ts.map +1 -1
  6. package/dist/loaders/generated-workers.js +3 -3
  7. package/dist/runtime/bash-repl.d.ts +1 -1
  8. package/dist/runtime/bash-repl.d.ts.map +1 -1
  9. package/dist/runtime/bash-repl.js +3 -2
  10. package/dist/runtime/cpython-resident.d.ts +27 -0
  11. package/dist/runtime/cpython-resident.d.ts.map +1 -0
  12. package/dist/runtime/cpython-resident.js +133 -0
  13. package/dist/runtime/facet-loader-host.d.ts +16 -1
  14. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  15. package/dist/runtime/facet-loader-host.js +42 -0
  16. package/dist/runtime/package-manager.d.ts +3 -58
  17. package/dist/runtime/package-manager.d.ts.map +1 -1
  18. package/dist/runtime/package-manager.js +6 -143
  19. package/dist/runtime/python-repl.d.ts +1 -1
  20. package/dist/runtime/python-repl.d.ts.map +1 -1
  21. package/dist/runtime/python-repl.js +2 -2
  22. package/dist/runtime/ruby-repl.js +1 -1
  23. package/dist/runtime/ruby-resident.d.ts +20 -0
  24. package/dist/runtime/ruby-resident.d.ts.map +1 -0
  25. package/dist/runtime/ruby-resident.js +278 -0
  26. package/dist/runtime/runtime-catalog.d.ts +4 -80
  27. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  28. package/dist/runtime/runtime-catalog.js +4 -67
  29. package/dist/session/init.d.ts +16 -7
  30. package/dist/session/init.d.ts.map +1 -1
  31. package/dist/session/init.js +210 -249
  32. package/dist/session/keys.d.ts +13 -0
  33. package/dist/session/keys.d.ts.map +1 -1
  34. package/dist/session/keys.js +13 -0
  35. package/dist/session/nimbus-session.d.ts +30 -2
  36. package/dist/session/nimbus-session.d.ts.map +1 -1
  37. package/dist/session/nimbus-session.js +49 -58
  38. package/dist/session/programmatic.d.ts +3 -3
  39. package/dist/session/programmatic.d.ts.map +1 -1
  40. package/dist/session/programmatic.js +3 -2
  41. package/dist/session/routes.js +1 -1
  42. package/dist/session/supervisor-rpc.d.ts +6 -13
  43. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  44. package/dist/session/supervisor-rpc.js +7 -17
  45. package/package.json +2 -2
  46. package/scripts/bundle-facet-workers.mjs +9 -8
  47. package/scripts/bundle-runtime.mjs +276 -44
  48. package/dist/npm/tarball-stream.d.ts +0 -93
  49. package/dist/npm/tarball-stream.d.ts.map +0 -1
  50. package/dist/npm/tarball-stream.js +0 -235
  51. package/dist/npm/tarball.d.ts +0 -34
  52. package/dist/npm/tarball.d.ts.map +0 -1
  53. package/dist/npm/tarball.js +0 -97
  54. package/dist/runtime/bash-runner.d.ts +0 -63
  55. package/dist/runtime/bash-runner.d.ts.map +0 -1
  56. package/dist/runtime/bash-runner.generated.d.ts +0 -14
  57. package/dist/runtime/bash-runner.generated.d.ts.map +0 -1
  58. package/dist/runtime/bash-runner.generated.js +0 -13
  59. package/dist/runtime/bash-runner.js +0 -291
  60. package/dist/runtime/clang-runner.d.ts +0 -37
  61. package/dist/runtime/clang-runner.d.ts.map +0 -1
  62. package/dist/runtime/clang-runner.js +0 -865
  63. package/dist/runtime/cpython-runner.d.ts +0 -71
  64. package/dist/runtime/cpython-runner.d.ts.map +0 -1
  65. package/dist/runtime/cpython-runner.js +0 -535
  66. package/dist/runtime/python-pip.d.ts +0 -38
  67. package/dist/runtime/python-pip.d.ts.map +0 -1
  68. package/dist/runtime/python-pip.js +0 -1063
  69. package/dist/runtime/ruby-gems.d.ts +0 -30
  70. package/dist/runtime/ruby-gems.d.ts.map +0 -1
  71. package/dist/runtime/ruby-gems.js +0 -636
  72. package/dist/runtime/ruby-runner.d.ts +0 -75
  73. package/dist/runtime/ruby-runner.d.ts.map +0 -1
  74. package/dist/runtime/ruby-runner.js +0 -1577
  75. package/dist/runtime/runtime-registry.d.ts +0 -162
  76. package/dist/runtime/runtime-registry.d.ts.map +0 -1
  77. package/dist/runtime/runtime-registry.js +0 -364
  78. package/dist/runtime/virtual-socket-kernel.generated.d.ts +0 -14
  79. package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +0 -1
  80. package/dist/runtime/virtual-socket-kernel.generated.js +0 -13
  81. package/dist/runtime/wasm-runner.d.ts +0 -70
  82. package/dist/runtime/wasm-runner.d.ts.map +0 -1
  83. package/dist/runtime/wasm-runner.js +0 -664
@@ -0,0 +1,278 @@
1
+ /**
2
+ * ruby-resident.ts — a Ruby program that keeps serving, as a Durable Object
3
+ * facet.
4
+ *
5
+ * The half of the Ruby runtime that names workerd. Everything about running
6
+ * the interpreter is in `@nimbus-sh/core`; this is the substrate a program
7
+ * that outlives its invocation needs — an actor that stays alive between
8
+ * requests, with inbound HTTP routed into the socket the program bound. A
9
+ * host without one supplies no {@link RubyResidentStart}, and such a program
10
+ * is refused rather than quietly run as a one-shot that dies with the call.
11
+ */
12
+ import { z } from 'zod';
13
+ import { buildRubyPreamble, normalizeRubyFacetResult, } from '@nimbus-sh/core/runtime/ruby-runner.js';
14
+ import { VIRTUAL_SOCKET_KERNEL_SRC } from '@nimbus-sh/core/runtime/virtual-socket-kernel.generated.js';
15
+ const RubySocketProcessBootResponseSchema = z.object({
16
+ state: z.string().optional(),
17
+ port: z.number().optional(),
18
+ stdout: z.string().optional(),
19
+ stderr: z.string().optional(),
20
+ result: z.unknown().optional(),
21
+ }).passthrough();
22
+ /**
23
+ * Start a Ruby program that binds a port as its own process, so it outlives
24
+ * the invocation that started it.
25
+ */
26
+ export function rubyResidentStart(facetMgr) {
27
+ return async function startResidentRuby(args) {
28
+ const command = args.command;
29
+ const workerCode = buildRubySocketProcessWorker(buildRubyPreamble());
30
+ const spawned = await facetMgr.spawnWorker(workerCode, command, args.cwd, {
31
+ compatibilityFlags: ['nodejs_compat'],
32
+ // By path, not by value: the image is 34.3 MiB — more than a single RPC
33
+ // value may carry — so whichever host runs this process reads it itself.
34
+ vfsWasmModules: { 'ruby+stdlib.wasm': args.wasmVfsPath },
35
+ startArgs: args.startArgs,
36
+ }).catch(() => null);
37
+ if (!spawned) {
38
+ return { exitCode: 1, stdout: '', stderr: 'ruby process boot failed\n' };
39
+ }
40
+ const parsed = RubySocketProcessBootResponseSchema.safeParse(spawned.boot);
41
+ if (!parsed.success) {
42
+ facetMgr.finishProcess(spawned.pid, 1, 'ruby process boot failed');
43
+ return {
44
+ exitCode: 1,
45
+ stdout: '',
46
+ stderr: 'ruby process boot failed\n',
47
+ };
48
+ }
49
+ const boot = parsed.data;
50
+ if (boot.state === 'listening' && typeof boot.port === 'number' && boot.port > 0) {
51
+ facetMgr.registerPort(spawned.pid, Number(boot.port));
52
+ const routeablePorts = await facetMgr.waitForRouteablePorts(spawned.pid);
53
+ const routeablePort = routeablePorts.includes(Number(boot.port)) ? Number(boot.port) : routeablePorts[0];
54
+ if (!routeablePort) {
55
+ facetMgr.kill(spawned.pid);
56
+ return {
57
+ exitCode: 1,
58
+ stdout: boot.stdout || '',
59
+ stderr: `${boot.stderr || ''}ruby: virtual socket port ${boot.port} failed to attach a route handler\n`,
60
+ };
61
+ }
62
+ return {
63
+ exitCode: 0,
64
+ stdout: `${boot.stdout || ''}\x1b[2m[started (long-running): pid=${spawned.pid} cmd="${command}" port=${routeablePort}]\x1b[0m\n`,
65
+ stderr: boot.stderr || '',
66
+ spawnedPid: spawned.pid,
67
+ port: routeablePort,
68
+ };
69
+ }
70
+ const reservedPorts = await facetMgr.waitForRouteablePorts(spawned.pid);
71
+ if (reservedPorts.length > 0) {
72
+ return {
73
+ exitCode: 0,
74
+ stdout: `${boot.stdout || ''}\x1b[2m[started (long-running): pid=${spawned.pid} cmd="${command}" port=${reservedPorts[0]}]\x1b[0m\n`,
75
+ stderr: boot.stderr || '',
76
+ spawnedPid: spawned.pid,
77
+ port: reservedPorts[0],
78
+ };
79
+ }
80
+ if (boot.state === 'exited') {
81
+ const result = normalizeRubyFacetResult(boot.result) || {
82
+ exitCode: 1,
83
+ stdout: '',
84
+ stderr: 'ruby process returned an invalid exit payload\n',
85
+ };
86
+ facetMgr.finishProcess(spawned.pid, result.exitCode, result.stderr || 'ruby process exited');
87
+ return {
88
+ exitCode: result.exitCode,
89
+ stdout: result.stdout,
90
+ stderr: result.stderr || result.error || '',
91
+ };
92
+ }
93
+ return {
94
+ exitCode: 0,
95
+ stdout: `${boot.stdout || ''}\x1b[2m[started (long-running): pid=${spawned.pid} cmd="${command}"]\x1b[0m\n`,
96
+ stderr: boot.stderr || '',
97
+ spawnedPid: spawned.pid,
98
+ };
99
+ };
100
+ }
101
+ export function buildRubySocketProcessWorker(preamble) {
102
+ return [
103
+ 'import { DurableObject } from "cloudflare:workers";',
104
+ "import __NIMBUS_WASM_ruby_stdlib from './ruby+stdlib.wasm';",
105
+ 'globalThis.__NIMBUS_WASM = globalThis.__NIMBUS_WASM || {};',
106
+ "globalThis.__NIMBUS_WASM['ruby+stdlib.wasm'] = __NIMBUS_WASM_ruby_stdlib;",
107
+ '',
108
+ VIRTUAL_SOCKET_KERNEL_SRC,
109
+ '',
110
+ // Listener lifecycle only. Socket bytes never cross this bridge: Ruby opens
111
+ // both accepted and dialed connections as file descriptors and reads and
112
+ // writes them as ordinary IO.
113
+ 'globalThis.__nimbusRubySockets = {',
114
+ ' listen(port) { return globalThis.__nimbusVirtualSockets.listen(Number(port)); },',
115
+ ' closeListener(port) { globalThis.__nimbusVirtualSockets.closeListener(Number(port)); return true; },',
116
+ ' pending(port) { return globalThis.__nimbusVirtualSockets.pending(Number(port)); },',
117
+ ' acceptNowJson(port) { const conn = globalThis.__nimbusVirtualSockets.acceptNow(Number(port)); return conn ? JSON.stringify(conn) : ""; },',
118
+ '};',
119
+ '',
120
+ // Outbound half of the same loopback the shell's curl and node's patched
121
+ // fetch use: one mechanism, reached here through the supervisor RPC.
122
+ 'globalThis.__nimbusVirtualSocketRouteLoopback = function __nimbusVirtualSocketRouteLoopback(port, request) {',
123
+ ' const supervisor = globalThis.__nimbusRubySupervisor;',
124
+ ' if (!supervisor || typeof supervisor.routeLoopback !== "function") {',
125
+ ' return Promise.reject(new Error("this Ruby process has no supervisor binding for loopback routing"));',
126
+ ' }',
127
+ ' return Promise.resolve(supervisor.routeLoopback(Number(port), request));',
128
+ '};',
129
+ '',
130
+ 'globalThis.__nimbusVirtualPortRegistrationPromises = globalThis.__nimbusVirtualPortRegistrationPromises || [];',
131
+ 'globalThis.__nimbusVirtualSocketDidListen = function __nimbusVirtualSocketDidListen(port) {',
132
+ ' const supervisor = globalThis.__nimbusRubySupervisor;',
133
+ ' if (!supervisor || typeof supervisor.registerPort !== "function") return;',
134
+ ' try {',
135
+ ' const p = supervisor.registerPort(Number(port)).catch((e) => {',
136
+ ' const msg = e && e.message ? e.message : String(e);',
137
+ ' (globalThis.__nimbusRubyStderr || (globalThis.__nimbusRubyStderr = [])).push("[ruby-runner] port registration failed: " + msg + "\\n");',
138
+ ' });',
139
+ ' globalThis.__nimbusVirtualPortRegistrationPromises.push(p);',
140
+ ' } catch (e) {',
141
+ ' const msg = e && e.message ? e.message : String(e);',
142
+ ' (globalThis.__nimbusRubyStderr || (globalThis.__nimbusRubyStderr = [])).push("[ruby-runner] port registration failed: " + msg + "\\n");',
143
+ ' }',
144
+ '};',
145
+ '',
146
+ preamble,
147
+ '',
148
+ // Drive the process when a connection is queued. This is the whole of the
149
+ // runtime's involvement in serving: it knows nothing about what is
150
+ // listening, only that the process should run until it parks.
151
+ //
152
+ // Steps go through the shared resume queue in the preamble, so a handler
153
+ // that parks does not stall other connections and no two drivers can enter
154
+ // a live fiber at once.
155
+ // Timed work the process still owes: the wall-clock moment its earliest
156
+ // sleeper is due. It lives on the global rather than in one driver's
157
+ // closure because no single request may own it. A timer belongs to the
158
+ // request context that created it, and workerd cancels that timer without
159
+ // a word when the request ends - so a driver anchored to the first
160
+ // connection stops dead the moment that connection answers, and every
161
+ // other connection waits out the response timeout instead.
162
+ 'globalThis.__nimbusRubyWakeAt = globalThis.__nimbusRubyWakeAt || null;',
163
+ 'globalThis.__nimbusRubyIdleDrivers = globalThis.__nimbusRubyIdleDrivers || new Set();',
164
+ 'function __nimbusRubyNoteWake(wakeAfter) {',
165
+ ' globalThis.__nimbusRubyWakeAt = (wakeAfter === null || wakeAfter === undefined)',
166
+ ' ? null',
167
+ ' : Date.now() + Math.max(0, wakeAfter) * 1000;',
168
+ ' if (globalThis.__nimbusRubyWakeAt === null) return;',
169
+ ' const waiting = Array.from(globalThis.__nimbusRubyIdleDrivers);',
170
+ ' globalThis.__nimbusRubyIdleDrivers.clear();',
171
+ ' for (const wake of waiting) wake();',
172
+ '}',
173
+ // So instead every live request drives, for as long as it is live, and the
174
+ // moment one of them answers the others are already carrying the work.
175
+ // Steps are serialized by the resume queue, and a driver that wakes to
176
+ // find the deadline already moved knows another one got there first.
177
+ 'async function __nimbusRubyDrive() {',
178
+ ' for (;;) {',
179
+ ' const due = globalThis.__nimbusRubyWakeAt;',
180
+ ' if (due === null) {',
181
+ ' // Nothing is due yet. Stay available anyway: this request holds a',
182
+ ' // live context, and whichever request discovers the next piece of',
183
+ ' // timed work may answer and be gone before that work comes due.',
184
+ ' await new Promise((resolve) => globalThis.__nimbusRubyIdleDrivers.add(resolve));',
185
+ ' continue;',
186
+ ' }',
187
+ ' const delay = due - Date.now();',
188
+ ' if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));',
189
+ ' if (globalThis.__nimbusRubyWakeAt !== due) continue;',
190
+ ' const step = await globalThis.__nimbusRubyStep();',
191
+ ' if (!step.resumed || !step.alive) { globalThis.__nimbusRubyWakeAt = null; return; }',
192
+ ' __nimbusRubyNoteWake(step.wakeAfter);',
193
+ ' }',
194
+ '}',
195
+ 'globalThis.__nimbusVirtualSocketRequestQueued = async function __nimbusVirtualSocketRequestQueued(port) {',
196
+ ' const step = await globalThis.__nimbusRubyStep();',
197
+ ' if (!step.resumed) return false;',
198
+ ' __nimbusRubyNoteWake(step.wakeAfter);',
199
+ ' __nimbusRubyDrive().catch((e) => {',
200
+ ' (globalThis.__nimbusRubyStderr || (globalThis.__nimbusRubyStderr = [])).push(',
201
+ ' "[ruby-runner] driving the process failed: " + ((e && e.message) || e) + "\\n");',
202
+ ' });',
203
+ ' return true;',
204
+ '};',
205
+ '',
206
+ 'async function __nimbusStartRubyProcess(args) {',
207
+ ' if (!globalThis.__nimbusRubyProcessPromise) {',
208
+ ' const stdoutStart = (globalThis.__nimbusRubyStdout || []).length;',
209
+ ' const stderrStart = (globalThis.__nimbusRubyStderr || []).length;',
210
+ ' globalThis.__nimbusRubyProcessOutputStart = { stdoutStart, stderrStart };',
211
+ ' globalThis.__nimbusRubyProcessArgs = {',
212
+ ' userCode: args.userCode,',
213
+ ' rbArgv: args.rbArgv || [],',
214
+ ' userEnv: args.userEnv || {},',
215
+ ' progName: args.progName || "ruby",',
216
+ ' cwd: args.cwd || "/home/user",',
217
+ ' fsSnapshot: args.fsSnapshot,',
218
+ ' };',
219
+ ' globalThis.__nimbusRubyProcessPromise = globalThis.__rubyRun(globalThis.__nimbusRubyProcessArgs).then((result) => {',
220
+ ' globalThis.__nimbusRubyProcessResult = result;',
221
+ ' return result;',
222
+ ' });',
223
+ ' }',
224
+ ' const started = globalThis.__nimbusRubyProcessOutputStart || { stdoutStart: 0, stderrStart: 0 };',
225
+ ' const listen = globalThis.__nimbusVirtualSockets.waitForListen(10_000).then((port) => ({ state: port ? "listening" : "pending", port }));',
226
+ ' const exit = globalThis.__nimbusRubyProcessPromise.then((result) => ({ state: "exited", result }));',
227
+ ' const first = await Promise.race([listen, exit]);',
228
+ ' const registrations = globalThis.__nimbusVirtualPortRegistrationPromises || [];',
229
+ ' if (registrations.length > 0) await Promise.allSettled(registrations.splice(0));',
230
+ ' const stdout = (globalThis.__nimbusRubyStdout || []).slice(started.stdoutStart).join("");',
231
+ ' const stderr = (globalThis.__nimbusRubyStderr || []).slice(started.stderrStart).join("");',
232
+ ' if (first.state === "listening") return { state: "listening", port: first.port, stdout, stderr };',
233
+ ' if (first.state === "exited") return { state: "exited", result: first.result, stdout, stderr };',
234
+ ' const currentPort = globalThis.__nimbusVirtualSockets.firstListeningPort();',
235
+ ' if (currentPort) return { state: "listening", port: currentPort, stdout, stderr };',
236
+ ' return { state: "running", stdout, stderr };',
237
+ '}',
238
+ '',
239
+ // Only adopt a real binding: routed handleHttpRequest/fetch hops resolve
240
+ // the entrypoint without a supervisor, and overwriting with undefined
241
+ // would drop the live stub the process needs for its whole lifetime.
242
+ 'function __nimbusAdoptRubySupervisor(env) {',
243
+ ' const supervisor = env && env.SUPERVISOR;',
244
+ ' if (supervisor) globalThis.__nimbusRubySupervisor = supervisor;',
245
+ // The WASI filesystem takes the same stub. That is what turns the
246
+ // spawn-time seed into a cache over the session VFS, so a server that
247
+ // never exits still persists its writes instead of losing them entirely.
248
+ ' __wasiAdoptSupervisor(supervisor);',
249
+ '}',
250
+ // A resident process parks between requests, and parking is the only
251
+ // moment "durable while running" can be made true: by the time the caller
252
+ // holds a response, everything the request wrote has reached the VFS.
253
+ 'async function __nimbusParkRuby(value) {',
254
+ // Revalidate drains first, then spends ONE round trip on the subtree
255
+ // revision. An unchanged subtree keeps the whole cache; a changed one
256
+ // drops the clean half so the next read sees another process's writes.
257
+ ' await __wasiRevalidateFS();',
258
+ ' return value;',
259
+ '}',
260
+ 'export class NimbusProcess extends DurableObject {',
261
+ ' async startProcess(args) {',
262
+ ' __nimbusAdoptRubySupervisor(this.env);',
263
+ ' return __nimbusParkRuby(await __nimbusStartRubyProcess(args || {}));',
264
+ ' }',
265
+ ' async fetch(request) {',
266
+ ' __nimbusAdoptRubySupervisor(this.env);',
267
+ ' return this.handleHttpRequest(request);',
268
+ ' }',
269
+ ' async handleHttpRequest(request) {',
270
+ ' __nimbusAdoptRubySupervisor(this.env);',
271
+ ' const hinted = Number(request.headers.get("X-Nimbus-Port") || 0);',
272
+ ' const port = hinted || Array.from(globalThis.__nimbusVirtualSockets.listeners.keys())[0];',
273
+ ' if (!port) return new Response("Nimbus Ruby process has no listening virtual socket", { status: 502 });',
274
+ ' return __nimbusParkRuby(await globalThis.__nimbusVirtualSockets.handleHttpRequest(port, request));',
275
+ ' }',
276
+ '}',
277
+ ].join('\n');
278
+ }
@@ -15,17 +15,9 @@
15
15
  * Catalog schema (RuntimeCatalog):
16
16
  * { version: 1, runtimes: { <name>: { default, versions: { <ver>: { manifest, manifest_sha256, size_bytes, license } } } } }
17
17
  *
18
- * Manifest schema (RuntimeManifest):
19
- * { name, version, license, wasi_namespace,
20
- * files: [{ path, content, sha256, size, mode? }],
21
- * entrypoints: [{ binName, runner, args[], kind? }],
22
- * runtime_artifacts?: [
23
- * { path, kind: "workerd-adapter", id, source_sha256?, sha256 },
24
- * { path, kind: "python-package", id, language: "python", packageName,
25
- * version, abi, pyodideVersion, pythonVersion, wheelFileName,
26
- * wheelSha256, loadMode: "startup-module", imports[], dependencies[],
27
- * extensionModules[] }
28
- * ] }
18
+ * Manifest schema: `@nimbus-sh/core` runtime/runtime-manifest.ts. What a
19
+ * runtime IS does not depend on which tier served it, so this module only
20
+ * fetches and verifies manifests; it does not describe them.
29
21
  *
30
22
  * R2 and Cache API failures throw; the shell verb formats the diagnostic for
31
23
  * the user.
@@ -53,8 +45,7 @@
53
45
  * that happens — there is no "trust the key instead" fallback. That also
54
46
  * makes a stale pin a cache miss rather than an outage.
55
47
  */
56
- import { z } from 'zod/v4';
57
- import { PYODIDE_PACKAGE_ABI } from '@nimbus-sh/core/runtime/os-contracts.js';
48
+ import { type ManifestFile, type RuntimeManifest } from '@nimbus-sh/core/runtime/runtime-manifest.js';
58
49
  /** Minimal R2Bucket shape we depend on. */
59
50
  type R2BucketLike = {
60
51
  get(key: string): Promise<{
@@ -82,74 +73,7 @@ export interface RuntimeCatalog {
82
73
  version: 1;
83
74
  runtimes: Record<string, CatalogRuntimeEntry>;
84
75
  }
85
- export interface ManifestFile {
86
- /** VFS path relative to ~/.nimbus/runtimes/<name>/<version>/. */
87
- path: string;
88
- /** R2 key for the content blob. */
89
- content: string;
90
- /** Hex sha256 of the content blob bytes. */
91
- sha256: string;
92
- /** Byte size. */
93
- size: number;
94
- /** Optional file mode hint ("exec" → registered as a shell bin). */
95
- mode?: 'exec';
96
- }
97
- export interface ManifestEntrypoint {
98
- /** Shell command name. */
99
- binName: string;
100
- /** Runner key (e.g. "clang-runner") — package manager dispatches to
101
- * the right runner factory by this. */
102
- runner: string;
103
- /** Default args prepended to user args at invocation. */
104
- args: string[];
105
- /** Optional secondary classification (e.g. "linker" for wasm-ld). */
106
- kind?: string;
107
- }
108
- export interface RuntimeArtifactMetadata {
109
- path: string;
110
- kind: string;
111
- id: string;
112
- source_sha256?: string;
113
- sha256: string;
114
- }
115
- export type RuntimePythonPackageAbi = typeof PYODIDE_PACKAGE_ABI;
116
- export interface RuntimePythonExtensionModuleMetadata {
117
- /** Path inside Python site-packages, as stored in the wheel. */
118
- path: string;
119
- /** Path inside the installed Nimbus runtime root. */
120
- runtimePath: string;
121
- sha256: string;
122
- }
123
- export interface RuntimePythonPackageArtifactMetadata extends RuntimeArtifactMetadata {
124
- kind: 'python-package';
125
- language: 'python';
126
- packageName: string;
127
- version: string;
128
- abi: RuntimePythonPackageAbi;
129
- pyodideVersion: string;
130
- pythonVersion: string;
131
- wheelFileName: string;
132
- wheelSha256: string;
133
- loadMode: 'startup-module';
134
- imports: string[];
135
- dependencies: string[];
136
- extensionModules: RuntimePythonExtensionModuleMetadata[];
137
- }
138
- export interface RuntimeManifest {
139
- name: string;
140
- version: string;
141
- license: string;
142
- /** Which WASI namespace the binaries import — `wasi_unstable` for
143
- * binji clang. `null` for non-WASI runtimes (e.g. Pyodide). */
144
- wasi_namespace: string | null;
145
- files: ManifestFile[];
146
- entrypoints: ManifestEntrypoint[];
147
- runtime_artifacts?: RuntimeArtifactMetadata[];
148
- }
149
- export declare const RuntimePythonPackageArtifactMetadataSchema: z.ZodType<RuntimePythonPackageArtifactMetadata>;
150
76
  export declare function parseRuntimeCatalog(value: unknown): RuntimeCatalog;
151
- export declare function parseRuntimeManifest(value: unknown): RuntimeManifest;
152
- export declare function isRuntimePythonPackageArtifactMetadata(artifact: RuntimeArtifactMetadata): artifact is RuntimePythonPackageArtifactMetadata;
153
77
  /**
154
78
  * Fetch the top-level catalog, verified against the build-time pin.
155
79
  *
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-catalog.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime-catalog.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAKH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,6CAA6C,CAAC;AAErD,2CAA2C;AAC3C,KAAK,YAAY,GAAG;IAClB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QACxB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;KACzB,GAAG,IAAI,CAAC,CAAC;CACX,GAAG,IAAI,GAAG,SAAS,CAAC;AAErB,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,CAAC,EAAE,YAAY,CAAC;CACrC;AAID,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB;2EACuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAiBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAElE;AA4DD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuBlF;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,UAAU,CAAC,CA4BrB"}
@@ -15,17 +15,9 @@
15
15
  * Catalog schema (RuntimeCatalog):
16
16
  * { version: 1, runtimes: { <name>: { default, versions: { <ver>: { manifest, manifest_sha256, size_bytes, license } } } } }
17
17
  *
18
- * Manifest schema (RuntimeManifest):
19
- * { name, version, license, wasi_namespace,
20
- * files: [{ path, content, sha256, size, mode? }],
21
- * entrypoints: [{ binName, runner, args[], kind? }],
22
- * runtime_artifacts?: [
23
- * { path, kind: "workerd-adapter", id, source_sha256?, sha256 },
24
- * { path, kind: "python-package", id, language: "python", packageName,
25
- * version, abi, pyodideVersion, pythonVersion, wheelFileName,
26
- * wheelSha256, loadMode: "startup-module", imports[], dependencies[],
27
- * extensionModules[] }
28
- * ] }
18
+ * Manifest schema: `@nimbus-sh/core` runtime/runtime-manifest.ts. What a
19
+ * runtime IS does not depend on which tier served it, so this module only
20
+ * fetches and verifies manifests; it does not describe them.
29
21
  *
30
22
  * R2 and Cache API failures throw; the shell verb formats the diagnostic for
31
23
  * the user.
@@ -56,8 +48,7 @@
56
48
  import { z } from 'zod/v4';
57
49
  import { sha256Hex } from '@nimbus-sh/core/_shared/crypto.js';
58
50
  import { RUNTIME_CATALOG_SHA256 } from '../runtime-catalog.generated.js';
59
- import { PYODIDE_PACKAGE_ABI } from '@nimbus-sh/core/runtime/os-contracts.js';
60
- const HexSha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
51
+ import { HexSha256Schema, parseRuntimeManifest, } from '@nimbus-sh/core/runtime/runtime-manifest.js';
61
52
  const CatalogVersionEntrySchema = z.object({
62
53
  manifest: z.string().min(1),
63
54
  manifest_sha256: HexSha256Schema.optional(),
@@ -71,63 +62,9 @@ const RuntimeCatalogSchema = z.object({
71
62
  versions: z.record(z.string(), CatalogVersionEntrySchema),
72
63
  })),
73
64
  });
74
- const ManifestFileSchema = z.object({
75
- path: z.string().min(1),
76
- content: z.string().min(1),
77
- sha256: HexSha256Schema,
78
- size: z.number().int().nonnegative(),
79
- mode: z.literal('exec').optional(),
80
- });
81
- const ManifestEntrypointSchema = z.object({
82
- binName: z.string().min(1),
83
- runner: z.string().min(1),
84
- args: z.array(z.string()),
85
- kind: z.string().optional(),
86
- });
87
- const RuntimeArtifactMetadataSchema = z.object({
88
- path: z.string().min(1),
89
- kind: z.string().min(1),
90
- id: z.string().min(1),
91
- source_sha256: HexSha256Schema.optional(),
92
- sha256: HexSha256Schema,
93
- }).passthrough();
94
- export const RuntimePythonPackageArtifactMetadataSchema = RuntimeArtifactMetadataSchema.and(z.object({
95
- kind: z.literal('python-package'),
96
- language: z.literal('python'),
97
- packageName: z.string().min(1),
98
- version: z.string().min(1),
99
- abi: z.literal(PYODIDE_PACKAGE_ABI),
100
- pyodideVersion: z.string().min(1),
101
- pythonVersion: z.string().min(1),
102
- wheelFileName: z.string().min(1),
103
- wheelSha256: HexSha256Schema,
104
- loadMode: z.literal('startup-module'),
105
- imports: z.array(z.string()),
106
- dependencies: z.array(z.string()),
107
- extensionModules: z.array(z.object({
108
- path: z.string().min(1),
109
- runtimePath: z.string().min(1),
110
- sha256: HexSha256Schema,
111
- })),
112
- }));
113
- const RuntimeManifestSchema = z.object({
114
- name: z.string().min(1),
115
- version: z.string().min(1),
116
- license: z.string(),
117
- wasi_namespace: z.string().nullable(),
118
- files: z.array(ManifestFileSchema),
119
- entrypoints: z.array(ManifestEntrypointSchema),
120
- runtime_artifacts: z.array(RuntimeArtifactMetadataSchema).optional(),
121
- });
122
65
  export function parseRuntimeCatalog(value) {
123
66
  return RuntimeCatalogSchema.parse(value);
124
67
  }
125
- export function parseRuntimeManifest(value) {
126
- return RuntimeManifestSchema.parse(value);
127
- }
128
- export function isRuntimePythonPackageArtifactMetadata(artifact) {
129
- return RuntimePythonPackageArtifactMetadataSchema.safeParse(artifact).success;
130
- }
131
68
  // ── L2 content addressing ────────────────────────────────────────────
132
69
  /** R2 key of the top-level catalog. */
133
70
  const CATALOG_R2_KEY = 'catalog/v1.json';
@@ -3,12 +3,21 @@
3
3
  *
4
4
  * Why this is one big function and not a class:
5
5
  * initSession runs once per /ws upgrade and walks the session
6
- * through Phase R (rehydrate from SQL), Phase B (build kernel +
7
- * shell + register commands), Phase W (attach terminal), and
8
- * (cold-only) Phase O (MOTD + framework hint). The phases share
9
- * lots of locals (vfs, kernel, registry, shell) and ordering
10
- * matters strictly — there's no interesting reuse boundary that a
11
- * class decomposition would expose.
6
+ * through Phase R (rehydrate from SQL), Phase B (compose the
7
+ * workspace + register the session's commands), Phase W (attach
8
+ * terminal), and (cold-only) Phase O (MOTD + framework hint). The
9
+ * phases share lots of locals (vfs, kernel, registry, shell) and
10
+ * ordering matters strictly — there's no interesting reuse boundary
11
+ * that a class decomposition would expose.
12
+ *
13
+ * What it no longer does is COMPOSE the operating system. The kernel,
14
+ * the provider mounts, the command registry, the coreutils, the exec
15
+ * resolver, the default environment and the shell come from
16
+ * `NimbusWorkspace` (@nimbus-sh/core/workspace), which an embedder off
17
+ * Cloudflare builds the same way. What stays here is everything that
18
+ * only makes sense with a socket, a Durable Object and a product behind
19
+ * it: the terminal and its scrollback, the phase machine, the persisted
20
+ * shell state, npm, git, vite, wrangler and the facet-backed runtimes.
12
21
  *
13
22
  * The function is intentionally written so that a reader sees:
14
23
  * 1. setPhase('rehydrate') ...
@@ -38,6 +47,6 @@ type InitHost = SessionInternal & {
38
47
  readonly ctx: any;
39
48
  readonly env: any;
40
49
  };
41
- export declare function initSession(self: InitHost, ws: WebSocket): void;
50
+ export declare function initSession(self: InitHost, ws: WebSocket): Promise<void>;
42
51
  export {};
43
52
  //# sourceMappingURL=init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/session/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAkFH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;GAQG;AACH,KAAK,QAAQ,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CAAC;AAa3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAivF/D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/session/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AA6EH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;GAQG;AACH,KAAK,QAAQ,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CAAC;AAa3E,wBAAsB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsrF9E"}