@nimbus-sh/worker 0.2.1 → 0.2.3

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 (45) hide show
  1. package/README.md +5 -2
  2. package/dist/facets/manager.d.ts +24 -0
  3. package/dist/facets/manager.d.ts.map +1 -1
  4. package/dist/facets/manager.js +73 -14
  5. package/dist/loaders/generated-workers.d.ts +2 -2
  6. package/dist/loaders/generated-workers.d.ts.map +1 -1
  7. package/dist/loaders/generated-workers.js +3 -3
  8. package/dist/runtime/facet-loader-host.d.ts +3 -2
  9. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  10. package/dist/runtime/facet-loader-host.js +1 -0
  11. package/dist/runtime/ruby-repl.js +1 -1
  12. package/dist/runtime/ruby-resident.d.ts +20 -0
  13. package/dist/runtime/ruby-resident.d.ts.map +1 -0
  14. package/dist/runtime/ruby-resident.js +278 -0
  15. package/dist/session/init.d.ts +16 -7
  16. package/dist/session/init.d.ts.map +1 -1
  17. package/dist/session/init.js +192 -208
  18. package/dist/session/keys.d.ts +11 -9
  19. package/dist/session/keys.d.ts.map +1 -1
  20. package/dist/session/keys.js +11 -9
  21. package/dist/session/nimbus-session.d.ts +11 -1
  22. package/dist/session/nimbus-session.d.ts.map +1 -1
  23. package/dist/session/nimbus-session.js +13 -57
  24. package/dist/session/programmatic.d.ts +1 -1
  25. package/dist/session/programmatic.d.ts.map +1 -1
  26. package/dist/session/programmatic.js +1 -1
  27. package/dist/session/routes.js +1 -1
  28. package/package.json +2 -2
  29. package/scripts/bundle-facet-workers.mjs +4 -3
  30. package/scripts/bundle-runtime.mjs +279 -44
  31. package/dist/npm/tarball-stream.d.ts +0 -93
  32. package/dist/npm/tarball-stream.d.ts.map +0 -1
  33. package/dist/npm/tarball-stream.js +0 -235
  34. package/dist/npm/tarball.d.ts +0 -34
  35. package/dist/npm/tarball.d.ts.map +0 -1
  36. package/dist/npm/tarball.js +0 -97
  37. package/dist/runtime/clang-runner.d.ts +0 -37
  38. package/dist/runtime/clang-runner.d.ts.map +0 -1
  39. package/dist/runtime/clang-runner.js +0 -865
  40. package/dist/runtime/ruby-gems.d.ts +0 -30
  41. package/dist/runtime/ruby-gems.d.ts.map +0 -1
  42. package/dist/runtime/ruby-gems.js +0 -636
  43. package/dist/runtime/ruby-runner.d.ts +0 -75
  44. package/dist/runtime/ruby-runner.d.ts.map +0 -1
  45. package/dist/runtime/ruby-runner.js +0 -1577
@@ -1,235 +0,0 @@
1
- /**
2
- * npm-tarball-stream.ts — pure streaming tar primitives.
3
- *
4
- * Extracted from npm-tarball.ts so these helpers can be esbuild-bundled
5
- * WITHOUT dragging in NpmCache / NpmResolver / SqliteVFS. Consumed by
6
- * scripts/bundle-facet-workers.mjs which emits a string constant the
7
- * NimbusLoaderPool uses to inject the tar parser into dynamic workers.
8
- *
9
- * Zero dependencies. Works identically on the supervisor and inside a
10
- * facet isolate. Never buffers the full decompressed tarball — peak
11
- * transient heap is one file's bytes plus a 512-byte carry.
12
- */
13
- /**
14
- * Maximum size of a single file inside a tarball. Larger entries are skipped.
15
- *
16
- * History: 5 MB was too low — it silently dropped `esbuild-wasm/esbuild.wasm`
17
- * (11.35 MB on v0.24.2), which made Nimbus-in-Nimbus `npm run dev` fail with
18
- * `No such module "esbuild-wasm/esbuild.wasm"` since the missing file caused
19
- * esbuild's VFS plugin to mark the import `external`, and workerd's LOADER
20
- * has no entry for that specifier. 20 MB covers esbuild-wasm with headroom
21
- * while keeping per-facet peak heap bounded for the streaming extractor.
22
- */
23
- export const MAX_FILE_BYTES = 20_000_000;
24
- /**
25
- * Read one tar header (USTAR) out of `block`. Returns parsed fields or
26
- * `null` for an end-of-archive block (all zeros).
27
- */
28
- /**
29
- * Collapse "."/".." segments in a tar entry's package-relative path.
30
- * Returns the canonical relative path, or '' when the entry escapes its
31
- * package root (a leading ".." that pops above the root) — the caller
32
- * treats '' as a no-name entry and skips it. Mirrors the segment logic in
33
- * w7-frame's canonicalPath so joined write paths are always accepted.
34
- */
35
- export function canonicalTarName(name) {
36
- const out = [];
37
- for (const seg of name.split('/')) {
38
- if (seg === '..') {
39
- if (out.length === 0)
40
- return '';
41
- out.pop();
42
- }
43
- else if (seg !== '' && seg !== '.') {
44
- out.push(seg);
45
- }
46
- }
47
- return out.join('/');
48
- }
49
- export function parseTarHeader(block) {
50
- if (block[0] === 0)
51
- return null;
52
- let name = '';
53
- for (let i = 0; i < 100 && block[i] !== 0; i++) {
54
- name += String.fromCharCode(block[i]);
55
- }
56
- let prefix = '';
57
- for (let i = 345; i < 500 && block[i] !== 0; i++) {
58
- prefix += String.fromCharCode(block[i]);
59
- }
60
- if (prefix)
61
- name = prefix + '/' + name;
62
- // Strip the npm `package/` convention.
63
- name = name.replace(/^package\//, '');
64
- // Canonicalize the entry-relative path. npm tarballs legitimately carry
65
- // entries like "./dist/index.js" (agent-base, http-proxy-agent,
66
- // protobufjs, ...); left as-is the "./" survives into the VFS write path
67
- // and the w7-frame writer rejects the noncanonical path, failing the
68
- // whole shared install wave and dropping shard-mates' completion markers.
69
- // Collapsing here (the one place entry names are assembled) keeps every
70
- // downstream join canonical. An entry that escapes its package root via
71
- // ".." is dropped to '' → skipped as a no-name entry.
72
- name = canonicalTarName(name);
73
- let sizeStr = '';
74
- for (let i = 124; i < 136 && block[i] !== 0; i++) {
75
- sizeStr += String.fromCharCode(block[i]);
76
- }
77
- const size = parseInt(sizeStr.trim(), 8) || 0;
78
- const typeFlag = block[156];
79
- return { name, size, typeFlag };
80
- }
81
- /**
82
- * Wrap a `ReadableStream<Uint8Array>` as an async iterable. Workerd and
83
- * Node both support `Symbol.asyncIterator` on ReadableStream, but we
84
- * spell the reader loop out so we don't depend on ambient lib typings.
85
- */
86
- export async function* readableStreamToAsyncIterable(rs) {
87
- const reader = rs.getReader();
88
- try {
89
- while (true) {
90
- const { value, done } = await reader.read();
91
- if (done)
92
- return;
93
- if (value && value.length > 0)
94
- yield value;
95
- }
96
- }
97
- finally {
98
- try {
99
- reader.releaseLock();
100
- }
101
- catch { /* ignore */ }
102
- }
103
- }
104
- /**
105
- * Streaming tar extractor.
106
- *
107
- * Consumes an async iterable of Uint8Array chunks (the decompressed tar
108
- * byte stream) and yields one `{ name, data }` entry per regular file,
109
- * as each file completes.
110
- *
111
- * Memory invariant: holds at most one pending file's bytes (≤ MAX_FILE_BYTES)
112
- * plus a small carry buffer for the tar header being assembled.
113
- *
114
- * Skips: symlinks, directories, hardlinks, long-name extensions (PaxHeader),
115
- * and any file whose declared size exceeds MAX_FILE_BYTES.
116
- *
117
- * If `onSkip` is provided, it is invoked for each skipped entry with the
118
- * name, declared size, and reason code. Callers that need to surface
119
- * dropped-file warnings to users should pass one; legacy callers that
120
- * omit the arg still behave exactly as before (silent skip).
121
- */
122
- export async function* streamTarEntries(source, onSkip) {
123
- let carry = new Uint8Array(0);
124
- let state = { kind: 'header' };
125
- function concat(a, b) {
126
- if (a.length === 0)
127
- return b;
128
- if (b.length === 0)
129
- return a;
130
- const out = new Uint8Array(a.length + b.length);
131
- out.set(a, 0);
132
- out.set(b, a.length);
133
- return out;
134
- }
135
- for await (const chunkRaw of source) {
136
- let buf = concat(carry, chunkRaw);
137
- let cursor = 0;
138
- while (true) {
139
- if (state.kind === 'header') {
140
- if (buf.length - cursor < 512)
141
- break;
142
- const header = buf.subarray(cursor, cursor + 512);
143
- const parsed = parseTarHeader(header);
144
- cursor += 512;
145
- if (!parsed)
146
- return; // end-of-archive
147
- const { name, size, typeFlag } = parsed;
148
- const pad = size === 0 ? 0 : (512 - (size % 512)) % 512;
149
- const isRegularFile = (typeFlag === 48 /* '0' */ || typeFlag === 0);
150
- if (size === 0) {
151
- if (isRegularFile && name) {
152
- yield { name, data: new Uint8Array(0) };
153
- }
154
- state = { kind: 'header' };
155
- continue;
156
- }
157
- if (!isRegularFile || !name || size > MAX_FILE_BYTES) {
158
- if (onSkip) {
159
- // Classify before entering the skip state so the reason is
160
- // exact. Precedence matches the condition order above:
161
- // non-regular first (directories/symlinks/PaxHeaders are
162
- // skipped regardless of size), then no-name, then too-large.
163
- const reason = !isRegularFile
164
- ? 'non-regular'
165
- : !name
166
- ? 'no-name'
167
- : 'too-large';
168
- try {
169
- onSkip(name, size, reason);
170
- }
171
- catch { /* best-effort */ }
172
- }
173
- state = { kind: 'skip', remaining: size + pad };
174
- continue;
175
- }
176
- state = {
177
- kind: 'file',
178
- name,
179
- remaining: size,
180
- fileBuf: new Uint8Array(size),
181
- fileOffset: 0,
182
- pad,
183
- skip: false,
184
- };
185
- continue;
186
- }
187
- if (state.kind === 'file') {
188
- const avail = buf.length - cursor;
189
- if (avail === 0)
190
- break;
191
- if (state.remaining > 0) {
192
- const take = Math.min(state.remaining, avail);
193
- state.fileBuf.set(buf.subarray(cursor, cursor + take), state.fileOffset);
194
- state.fileOffset += take;
195
- state.remaining -= take;
196
- cursor += take;
197
- if (state.remaining > 0)
198
- break;
199
- }
200
- if (state.pad > 0) {
201
- const avail2 = buf.length - cursor;
202
- if (avail2 === 0)
203
- break;
204
- const take = Math.min(state.pad, avail2);
205
- state.pad -= take;
206
- cursor += take;
207
- if (state.pad > 0)
208
- break;
209
- }
210
- yield { name: state.name, data: state.fileBuf };
211
- state = { kind: 'header' };
212
- continue;
213
- }
214
- // state.kind === 'skip'
215
- const avail = buf.length - cursor;
216
- if (avail === 0)
217
- break;
218
- const take = Math.min(state.remaining, avail);
219
- cursor += take;
220
- state.remaining -= take;
221
- if (state.remaining > 0)
222
- break;
223
- state = { kind: 'header' };
224
- }
225
- if (cursor >= buf.length) {
226
- carry = new Uint8Array(0);
227
- }
228
- else if (cursor === 0) {
229
- carry = buf;
230
- }
231
- else {
232
- carry = buf.slice(cursor);
233
- }
234
- }
235
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * npm-tarball.ts — streaming tarball extraction (gunzip + tar).
3
- *
4
- * `extractTarball` / `extractTarballFromResponse` walk a gzipped tar stream
5
- * entry-by-entry, holding at most one file's bytes at a time. Used by the
6
- * install-batch facet (whose own 128 MiB envelope, not the supervisor's,
7
- * holds the bytes) and the ruby-gems runtime.
8
- *
9
- * The legacy supervisor-resident `fetchWaves` / `buildBatchPayload` and the
10
- * `buildCacheRestorePayload` fast path were removed — they ran in supervisor
11
- * heap and held tarball bytes long enough to OOM the DO on large installs.
12
- * The single batch-facet path (install-batch-facet.ts) supersedes them and
13
- * consults the shared L2/L3 tarball cache directly.
14
- */
15
- /**
16
- * Streaming extractor driven by a `Response` body.
17
- *
18
- * Pipes `resp.body` through `DecompressionStream('gzip')` (npm tarballs are
19
- * always gzipped) and walks the tar stream entry-by-entry. Never buffers the
20
- * full decompressed tarball — peak transient heap is one file's bytes plus a
21
- * small carry buffer.
22
- *
23
- * The returned Map is per-file Uint8Arrays. If the response has no body
24
- * (unusual but possible with some proxies), we fall back to `arrayBuffer()`
25
- * + `extractTarball` so we still make progress.
26
- */
27
- export declare function extractTarballFromResponse(resp: Response): Promise<Map<string, Uint8Array>>;
28
- /**
29
- * Legacy buffered extractor. Kept for code paths that receive a fully-buffered
30
- * tarball (e.g. the tarball cache restore path, which already stores bytes in
31
- * SQLite as Uint8Arrays). New install paths use `streamTarEntries` instead.
32
- */
33
- export declare function extractTarball(tarball: ArrayBuffer): Promise<Map<string, Uint8Array>>;
34
- //# sourceMappingURL=tarball.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tarball.d.ts","sourceRoot":"","sources":["../../src/npm/tarball.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH;;;;;;;;;;;GAWG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAmClC;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAsBlC"}
@@ -1,97 +0,0 @@
1
- /**
2
- * npm-tarball.ts — streaming tarball extraction (gunzip + tar).
3
- *
4
- * `extractTarball` / `extractTarballFromResponse` walk a gzipped tar stream
5
- * entry-by-entry, holding at most one file's bytes at a time. Used by the
6
- * install-batch facet (whose own 128 MiB envelope, not the supervisor's,
7
- * holds the bytes) and the ruby-gems runtime.
8
- *
9
- * The legacy supervisor-resident `fetchWaves` / `buildBatchPayload` and the
10
- * `buildCacheRestorePayload` fast path were removed — they ran in supervisor
11
- * heap and held tarball bytes long enough to OOM the DO on large installs.
12
- * The single batch-facet path (install-batch-facet.ts) supersedes them and
13
- * consults the shared L2/L3 tarball cache directly.
14
- */
15
- import { streamTarEntries, readableStreamToAsyncIterable, } from './tarball-stream.js';
16
- // ── Tarball extraction ──────────────────────────────────────────────────
17
- //
18
- // The streaming primitives (parseTarHeader, streamTarEntries,
19
- // readableStreamToAsyncIterable, MAX_FILE_BYTES) live in
20
- // ./npm-tarball-stream.ts — a dependency-free leaf so
21
- // scripts/bundle-facet-workers.mjs can esbuild it into a string the facet
22
- // pool injects into dynamic workers. Import them directly from that module.
23
- /**
24
- * Streaming extractor driven by a `Response` body.
25
- *
26
- * Pipes `resp.body` through `DecompressionStream('gzip')` (npm tarballs are
27
- * always gzipped) and walks the tar stream entry-by-entry. Never buffers the
28
- * full decompressed tarball — peak transient heap is one file's bytes plus a
29
- * small carry buffer.
30
- *
31
- * The returned Map is per-file Uint8Arrays. If the response has no body
32
- * (unusual but possible with some proxies), we fall back to `arrayBuffer()`
33
- * + `extractTarball` so we still make progress.
34
- */
35
- export async function extractTarballFromResponse(resp) {
36
- const files = new Map();
37
- const body = resp.body;
38
- if (!body) {
39
- // Fallback: no streamable body (shouldn't happen, but handle it).
40
- const buf = await resp.arrayBuffer();
41
- return extractTarball(buf);
42
- }
43
- // Time-bound the whole extraction to detect hung gunzip streams.
44
- const controller = new AbortController();
45
- const timer = setTimeout(() => controller.abort(), 60_000);
46
- try {
47
- const decompressed = body.pipeThrough(new DecompressionStream('gzip'));
48
- const source = readableStreamToAsyncIterable(decompressed);
49
- for await (const entry of streamTarEntries(source)) {
50
- if (controller.signal.aborted)
51
- throw new Error('tarball extract timeout');
52
- files.set(entry.name, entry.data);
53
- }
54
- }
55
- catch (e) {
56
- // On any error, just return whatever we managed to extract. Callers
57
- // treat `files.size === 0` as a failed fetch and mark the package failed.
58
- // If partial data is present (some files succeeded before the stream
59
- // broke), we propagate it; the installer will re-fetch on mismatch.
60
- if (files.size === 0) {
61
- clearTimeout(timer);
62
- throw e;
63
- }
64
- }
65
- finally {
66
- clearTimeout(timer);
67
- }
68
- return files;
69
- }
70
- /**
71
- * Legacy buffered extractor. Kept for code paths that receive a fully-buffered
72
- * tarball (e.g. the tarball cache restore path, which already stores bytes in
73
- * SQLite as Uint8Arrays). New install paths use `streamTarEntries` instead.
74
- */
75
- export async function extractTarball(tarball) {
76
- const files = new Map();
77
- const raw = new Uint8Array(tarball);
78
- // Adapter: wrap the single buffer as an async iterable. If gzipped, pipe
79
- // through DecompressionStream so the streaming parser still sees tar bytes.
80
- let source;
81
- if (raw[0] === 0x1f && raw[1] === 0x8b) {
82
- const rs = new Blob([tarball]).stream().pipeThrough(new DecompressionStream('gzip'));
83
- source = readableStreamToAsyncIterable(rs);
84
- }
85
- else {
86
- source = (async function* () { yield raw; })();
87
- }
88
- try {
89
- for await (const entry of streamTarEntries(source)) {
90
- files.set(entry.name, entry.data);
91
- }
92
- }
93
- catch {
94
- return files;
95
- }
96
- return files;
97
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * clang-runner.ts — compile, link, and execute C programs for Nimbus WASI.
3
- *
4
- * Architecture (compile-link-run, two facet calls):
5
- *
6
- * compile : clang.wasm + sysroot subset for C includes + user .c
7
- * → produces .o bytes (returned to supervisor).
8
- * link : lld.wasm + sysroot subset for link (crt1.o + libc.a)
9
- * + .o from compile → produces .wasm executable.
10
- * write : final .wasm flushed to user VFS at the requested path.
11
- *
12
- * The filesystem both halves see is the one WASI layer every other
13
- * non-node runtime uses (wasi-instance.ts), seeded and sealed.
14
- *
15
- * Splitting compile and link into separate LOADER calls keeps each
16
- * call under the empirical loader payload ceiling. Each ships:
17
- *
18
- * - compile: 31 MiB clang.wasm + ~1.3 MiB sysroot subset (C includes).
19
- * - link : 19 MiB lld.wasm + ~0.75 MiB libs + tiny .o.
20
- *
21
- * Sysroot subset extraction happens supervisor-side via a small ustar
22
- * parser. The full sysroot.tar is parsed once when the clang runtime
23
- * warms for a session; compile/link calls reuse the filtered subsets.
24
- *
25
- * Dispatch stays direct: no sleeps, no caller-side retries, and no
26
- * catch-and-continue around loader failures.
27
- */
28
- import type { RuntimeManifest } from '@nimbus-sh/core/runtime/runtime-manifest.js';
29
- import type { SqliteVFS } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
30
- import type { FacetManager } from '../facets/manager.js';
31
- /** Build the runner factory. Closes over facetMgr + vfs. */
32
- export declare function makeClangRunnerFactory(deps: {
33
- facetMgr: FacetManager;
34
- vfs: SqliteVFS;
35
- }): (manifest: RuntimeManifest, installRoot: string, binName: string, binKind: string | undefined) => (ctx: any) => Promise<number>;
36
- export declare const CLANG_RUNNER_PREAMBLE: string;
37
- //# sourceMappingURL=clang-runner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clang-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/clang-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AASzD,4DAA4D;AAC5D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,QAAQ,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,SAAS,CAAC;CAChB,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,KAC7F,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,CAkThC;AAyjBD,eAAO,MAAM,qBAAqB,QAAiE,CAAC"}