@scelar/nodepod 1.0.2 → 1.0.4

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 (94) hide show
  1. package/dist/__sw__.js +642 -642
  2. package/dist/__tests__/bench/integration.bench.d.ts +1 -0
  3. package/dist/__tests__/bench/memory-volume.bench.d.ts +1 -0
  4. package/dist/__tests__/bench/polyfills.bench.d.ts +1 -0
  5. package/dist/__tests__/bench/script-engine.bench.d.ts +1 -0
  6. package/dist/__tests__/bench/shell.bench.d.ts +1 -0
  7. package/dist/__tests__/bench/syntax-transforms.bench.d.ts +1 -0
  8. package/dist/__tests__/bench/version-resolver.bench.d.ts +1 -0
  9. package/dist/__tests__/buffer.test.d.ts +1 -0
  10. package/dist/__tests__/byte-encoding.test.d.ts +1 -0
  11. package/dist/__tests__/digest.test.d.ts +1 -0
  12. package/dist/__tests__/events.test.d.ts +1 -0
  13. package/dist/__tests__/memory-volume.test.d.ts +1 -0
  14. package/dist/__tests__/path.test.d.ts +1 -0
  15. package/dist/__tests__/process.test.d.ts +1 -0
  16. package/dist/__tests__/script-engine.test.d.ts +1 -0
  17. package/dist/__tests__/shell-builtins.test.d.ts +1 -0
  18. package/dist/__tests__/shell-interpreter.test.d.ts +1 -0
  19. package/dist/__tests__/shell-parser.test.d.ts +1 -0
  20. package/dist/__tests__/stream.test.d.ts +1 -0
  21. package/dist/__tests__/syntax-transforms.test.d.ts +1 -0
  22. package/dist/__tests__/version-resolver.test.d.ts +1 -0
  23. package/dist/{child_process-Dopvyd-E.js → child_process-53fMkug_.js} +4 -4
  24. package/dist/child_process-53fMkug_.js.map +1 -0
  25. package/dist/{child_process-B38qoN6R.cjs → child_process-lxSKECHq.cjs} +5 -5
  26. package/dist/child_process-lxSKECHq.cjs.map +1 -0
  27. package/dist/{index--Qr8LVpQ.js → index-B8lyh_ti.js} +1316 -559
  28. package/dist/index-B8lyh_ti.js.map +1 -0
  29. package/dist/{index-cnitc68U.cjs → index-C-TQIrdG.cjs} +1422 -612
  30. package/dist/index-C-TQIrdG.cjs.map +1 -0
  31. package/dist/index.cjs +1 -1
  32. package/dist/index.mjs +1 -1
  33. package/dist/memory-volume.d.ts +1 -1
  34. package/dist/polyfills/wasi.d.ts +45 -4
  35. package/dist/script-engine.d.ts +2 -0
  36. package/dist/sdk/nodepod.d.ts +4 -3
  37. package/dist/sdk/types.d.ts +6 -0
  38. package/dist/syntax-transforms.d.ts +1 -0
  39. package/dist/threading/process-manager.d.ts +1 -1
  40. package/dist/threading/worker-protocol.d.ts +1 -1
  41. package/package.json +5 -3
  42. package/src/__tests__/bench/integration.bench.ts +117 -0
  43. package/src/__tests__/bench/memory-volume.bench.ts +115 -0
  44. package/src/__tests__/bench/polyfills.bench.ts +147 -0
  45. package/src/__tests__/bench/script-engine.bench.ts +104 -0
  46. package/src/__tests__/bench/shell.bench.ts +101 -0
  47. package/src/__tests__/bench/syntax-transforms.bench.ts +82 -0
  48. package/src/__tests__/bench/version-resolver.bench.ts +95 -0
  49. package/src/__tests__/buffer.test.ts +273 -0
  50. package/src/__tests__/byte-encoding.test.ts +98 -0
  51. package/src/__tests__/digest.test.ts +44 -0
  52. package/src/__tests__/events.test.ts +245 -0
  53. package/src/__tests__/memory-volume.test.ts +443 -0
  54. package/src/__tests__/path.test.ts +181 -0
  55. package/src/__tests__/process.test.ts +129 -0
  56. package/src/__tests__/script-engine.test.ts +229 -0
  57. package/src/__tests__/shell-builtins.test.ts +357 -0
  58. package/src/__tests__/shell-interpreter.test.ts +157 -0
  59. package/src/__tests__/shell-parser.test.ts +204 -0
  60. package/src/__tests__/stream.test.ts +142 -0
  61. package/src/__tests__/syntax-transforms.test.ts +158 -0
  62. package/src/__tests__/version-resolver.test.ts +184 -0
  63. package/src/constants/cdn-urls.ts +18 -18
  64. package/src/helpers/byte-encoding.ts +51 -39
  65. package/src/index.ts +192 -192
  66. package/src/memory-volume.ts +968 -941
  67. package/src/module-transformer.ts +368 -368
  68. package/src/packages/installer.ts +396 -396
  69. package/src/packages/version-resolver.ts +12 -2
  70. package/src/polyfills/buffer.ts +633 -628
  71. package/src/polyfills/child_process.ts +2288 -2288
  72. package/src/polyfills/esbuild.ts +854 -854
  73. package/src/polyfills/events.ts +282 -276
  74. package/src/polyfills/fs.ts +2888 -2888
  75. package/src/polyfills/http.ts +1450 -1449
  76. package/src/polyfills/process.ts +721 -690
  77. package/src/polyfills/readline.ts +692 -692
  78. package/src/polyfills/stream.ts +1620 -1620
  79. package/src/polyfills/tty.ts +71 -71
  80. package/src/polyfills/wasi.ts +1284 -22
  81. package/src/request-proxy.ts +716 -716
  82. package/src/script-engine.ts +465 -146
  83. package/src/sdk/nodepod.ts +525 -509
  84. package/src/sdk/types.ts +7 -0
  85. package/src/syntax-transforms.ts +543 -561
  86. package/src/threading/offload-worker.ts +383 -383
  87. package/src/threading/offload.ts +271 -271
  88. package/src/threading/process-manager.ts +956 -956
  89. package/src/threading/process-worker-entry.ts +858 -854
  90. package/src/threading/worker-protocol.ts +1 -1
  91. package/dist/child_process-B38qoN6R.cjs.map +0 -1
  92. package/dist/child_process-Dopvyd-E.js.map +0 -1
  93. package/dist/index--Qr8LVpQ.js.map +0 -1
  94. package/dist/index-cnitc68U.cjs.map +0 -1
@@ -1,44 +1,1306 @@
1
- // stub - not available in browser
1
+ const ERRNO_SUCCESS = 0;
2
+ const ERRNO_2BIG = 1;
3
+ const ERRNO_ACCES = 2;
4
+ const ERRNO_BADF = 8;
5
+ const ERRNO_EXIST = 20;
6
+ const ERRNO_FAULT = 21;
7
+ const ERRNO_INVAL = 28;
8
+ const ERRNO_IO = 29;
9
+ const ERRNO_ISDIR = 31;
10
+ const ERRNO_NOENT = 44;
11
+ const ERRNO_NOSYS = 52;
12
+ const ERRNO_NOTDIR = 54;
13
+ const ERRNO_NOTEMPTY = 55;
14
+ const ERRNO_PERM = 63;
15
+ const ERRNO_PIPE = 64;
16
+ const ERRNO_SPIPE = 70;
2
17
 
18
+ const CLOCKID_REALTIME = 0;
19
+ const CLOCKID_MONOTONIC = 1;
20
+ const CLOCKID_PROCESS_CPUTIME_ID = 2;
21
+ const CLOCKID_THREAD_CPUTIME_ID = 3;
22
+
23
+ const FILETYPE_UNKNOWN = 0;
24
+ const FILETYPE_DIRECTORY = 3;
25
+ const FILETYPE_REGULAR_FILE = 4;
26
+ const FILETYPE_SYMBOLIC_LINK = 7;
27
+
28
+ const FDFLAGS_APPEND = 0x0001;
29
+
30
+ const FSTFLAGS_ATIM = 0x0001;
31
+ const FSTFLAGS_ATIM_NOW = 0x0002;
32
+ const FSTFLAGS_MTIM = 0x0004;
33
+ const FSTFLAGS_MTIM_NOW = 0x0008;
34
+
35
+ const OFLAGS_CREAT = 0x0001;
36
+ const OFLAGS_DIRECTORY = 0x0002;
37
+ const OFLAGS_EXCL = 0x0004;
38
+ const OFLAGS_TRUNC = 0x0008;
39
+
40
+ const WHENCE_SET = 0;
41
+ const WHENCE_CUR = 1;
42
+ const WHENCE_END = 2;
43
+
44
+ const PREOPENTYPE_DIR = 0;
45
+
46
+ const RIGHTS_FD_READ = 0x0000000000000002n;
47
+ const RIGHTS_FD_WRITE = 0x0000000000000040n;
48
+ const RIGHTS_FD_SEEK = 0x0000000000000004n;
49
+ const RIGHTS_FD_TELL = 0x0000000000000020n;
50
+ const RIGHTS_FD_READDIR = 0x0000000000004000n;
51
+ const RIGHTS_PATH_OPEN = 0x0000000000002000n;
52
+ const RIGHTS_PATH_CREATE_DIRECTORY = 0x0000000000000200n;
53
+ const RIGHTS_PATH_CREATE_FILE = 0x0000000000000400n;
54
+ const RIGHTS_PATH_UNLINK_FILE = 0x0000000004000000n;
55
+ const RIGHTS_PATH_REMOVE_DIRECTORY = 0x0000000002000000n;
56
+ const RIGHTS_PATH_RENAME_SOURCE = 0x0000000000010000n;
57
+ const RIGHTS_PATH_RENAME_TARGET = 0x0000000000020000n;
58
+ const RIGHTS_PATH_FILESTAT_GET = 0x0000000000040000n;
59
+ const RIGHTS_PATH_SYMLINK = 0x0000000001000000n;
60
+ const RIGHTS_PATH_READLINK = 0x0000000000008000n;
61
+ const RIGHTS_FD_FILESTAT_GET = 0x0000000000200000n;
62
+ const RIGHTS_FD_FILESTAT_SET_SIZE = 0x0000000000400000n;
63
+
64
+ const RIGHTS_ALL = 0x1fffffffn;
65
+ const RIGHTS_DIR_BASE =
66
+ RIGHTS_FD_READ |
67
+ RIGHTS_FD_READDIR |
68
+ RIGHTS_PATH_OPEN |
69
+ RIGHTS_PATH_CREATE_DIRECTORY |
70
+ RIGHTS_PATH_CREATE_FILE |
71
+ RIGHTS_PATH_UNLINK_FILE |
72
+ RIGHTS_PATH_REMOVE_DIRECTORY |
73
+ RIGHTS_PATH_RENAME_SOURCE |
74
+ RIGHTS_PATH_RENAME_TARGET |
75
+ RIGHTS_PATH_FILESTAT_GET |
76
+ RIGHTS_PATH_SYMLINK |
77
+ RIGHTS_PATH_READLINK |
78
+ RIGHTS_FD_FILESTAT_GET;
79
+ const RIGHTS_FILE_BASE =
80
+ RIGHTS_FD_READ |
81
+ RIGHTS_FD_WRITE |
82
+ RIGHTS_FD_SEEK |
83
+ RIGHTS_FD_TELL |
84
+ RIGHTS_FD_FILESTAT_GET |
85
+ RIGHTS_FD_FILESTAT_SET_SIZE;
86
+
87
+ const EVENTTYPE_CLOCK = 0;
88
+ const EVENTTYPE_FD_READ = 1;
89
+ const EVENTTYPE_FD_WRITE = 2;
90
+
91
+ /* ------------------------------------------------------------------ */
92
+ /* Filesystem interface (subset of our MemoryVolume / fs bridge) */
93
+ /* ------------------------------------------------------------------ */
94
+
95
+ interface WasiFileStat {
96
+ isFile(): boolean;
97
+ isDirectory(): boolean;
98
+ isSymbolicLink(): boolean;
99
+ size: number;
100
+ mtimeMs: number;
101
+ atimeMs: number;
102
+ ctimeMs: number;
103
+ ino?: number;
104
+ nlink?: number;
105
+ }
106
+
107
+ interface WasiFS {
108
+ readFileSync(p: string): Uint8Array;
109
+ writeFileSync(p: string, data: string | Uint8Array): void;
110
+ mkdirSync(p: string, options?: { recursive?: boolean }): void;
111
+ statSync(p: string): WasiFileStat;
112
+ readdirSync(p: string): string[];
113
+ unlinkSync(p: string): void;
114
+ rmdirSync(p: string): void;
115
+ renameSync(from: string, to: string): void;
116
+ existsSync(p: string): boolean;
117
+ symlinkSync?(target: string, linkPath: string): void;
118
+ readlinkSync?(p: string): string;
119
+ }
3
120
 
4
121
  /* ------------------------------------------------------------------ */
5
- /* WASI class */
122
+ /* File descriptor table */
6
123
  /* ------------------------------------------------------------------ */
7
124
 
125
+ const enum FdKind {
126
+ Stdin,
127
+ Stdout,
128
+ Stderr,
129
+ PreopenDir,
130
+ File,
131
+ }
132
+
133
+ interface FdEntry {
134
+ kind: FdKind;
135
+ path: string; // real path (for dirs and files)
136
+ rights: bigint;
137
+ // file-specific
138
+ data?: Uint8Array;
139
+ offset?: number;
140
+ dirty?: boolean; // needs flush on close
141
+ flags?: number; // O_APPEND etc
142
+ }
143
+
144
+ /* ------------------------------------------------------------------ */
145
+ /* ExitStatus */
146
+ /* ---------------------------------------------------------------- */
147
+ export class ExitStatus extends Error {
148
+ readonly code: number;
149
+ constructor(code: number) {
150
+ super(`WASI exit(${code})`);
151
+ this.code = code;
152
+ }
153
+ }
154
+
155
+ /* ------------------------------------------------------------------ */
156
+ /* syscall wrapper */
157
+ /* ---------------------------------------------------------------- */
158
+ function syscall(target: Function): Function {
159
+ return function (this: unknown, ...args: unknown[]): number {
160
+ try {
161
+ return target.apply(this, args);
162
+ } catch (err: any) {
163
+ if (err instanceof ExitStatus) throw err;
164
+ // map common fs errors to WASI errno
165
+ const code = err?.code;
166
+ if (code === "ENOENT") return ERRNO_NOENT;
167
+ if (code === "EEXIST") return ERRNO_EXIST;
168
+ if (code === "EISDIR") return ERRNO_ISDIR;
169
+ if (code === "ENOTDIR") return ERRNO_NOTDIR;
170
+ if (code === "ENOTEMPTY") return ERRNO_NOTEMPTY;
171
+ if (code === "EACCES" || code === "EPERM") return ERRNO_ACCES;
172
+ return ERRNO_IO;
173
+ }
174
+ };
175
+ }
176
+
177
+ /* Path helpers */
178
+
179
+ function joinPath(base: string, rel: string): string {
180
+ if (rel.startsWith("/")) return normalizePath(rel);
181
+ const combined = base.endsWith("/") ? base + rel : base + "/" + rel;
182
+ return normalizePath(combined);
183
+ }
184
+
185
+ function normalizePath(p: string): string {
186
+ const parts = p.split("/");
187
+ const out: string[] = [];
188
+ for (const seg of parts) {
189
+ if (seg === "." || seg === "") continue;
190
+ if (seg === "..") {
191
+ out.pop();
192
+ continue;
193
+ }
194
+ out.push(seg);
195
+ }
196
+ return "/" + out.join("/");
197
+ }
198
+
199
+ /* Text encoder / decoder (cached)*/
200
+ const encoder = new TextEncoder();
201
+ const decoder = new TextDecoder();
202
+
203
+ /* WASI class (matches Nodejs `wasi` module API) */
204
+
205
+ export interface WASIOptions {
206
+ version?: "preview1" | "unstable";
207
+ args?: string[];
208
+ env?: Record<string, string>;
209
+ preopens?: Record<string, string>;
210
+ returnOnExit?: boolean;
211
+ stdin?: number;
212
+ stdout?: number;
213
+ stderr?: number;
214
+ // extensions for our environment
215
+ fs?: WasiFS;
216
+ }
217
+
8
218
  export interface WASI {
9
219
  readonly wasiImport: Record<string, Function>;
10
- start(_instance: object): number;
11
- initialize(_instance: object): void;
220
+ start(instance: object): number;
221
+ initialize(instance: object): void;
12
222
  getImportObject(): Record<string, Record<string, Function>>;
13
223
  }
14
224
 
15
225
  interface WASIConstructor {
16
- new (_options?: object): WASI;
17
- (this: any, _options?: object): void;
226
+ new (options?: WASIOptions): WASI;
227
+ (this: any, options?: WASIOptions): void;
18
228
  prototype: any;
19
229
  }
20
230
 
21
- export const WASI = function WASI(this: any, _options?: object) {
22
- if (!this) return;
23
- this.wasiImport = {};
24
- } as unknown as WASIConstructor;
231
+ export const WASI = function WASI(this: any, options?: WASIOptions) {
232
+ if (!(this instanceof WASI)) {
233
+ throw new TypeError(
234
+ "Class constructor WASI cannot be invoked without 'new'",
235
+ );
236
+ }
25
237
 
26
- WASI.prototype.start = function start(_instance: object): number {
27
- throw new Error("WASI is not supported in the browser environment");
28
- };
238
+ const opts = options ?? {};
239
+ const args = opts.args ?? [];
240
+ const envVars = opts.env ?? {};
241
+ const preopens = opts.preopens ?? {};
242
+ const returnOnExit = opts.returnOnExit ?? false;
243
+ const fs = opts.fs ?? null;
29
244
 
30
- WASI.prototype.initialize = function initialize(_instance: object): void {
31
- throw new Error("WASI is not supported in the browser environment");
32
- };
245
+ /* file descriptor table */
33
246
 
34
- WASI.prototype.getImportObject = function getImportObject(this: any): Record<string, Record<string, Function>> {
35
- return { wasi_snapshot_preview1: this.wasiImport };
36
- };
247
+ const fds = new Map<number, FdEntry>();
248
+ let nextFd = 3;
37
249
 
38
- /* ------------------------------------------------------------------ */
39
- /* Default export */
40
- /* ------------------------------------------------------------------ */
250
+ // fd 0 = stdin, fd 1 = stdout, fd 2 = stderr
251
+ fds.set(0, { kind: FdKind.Stdin, path: "", rights: RIGHTS_FD_READ });
252
+ fds.set(1, { kind: FdKind.Stdout, path: "", rights: RIGHTS_FD_WRITE });
253
+ fds.set(2, { kind: FdKind.Stderr, path: "", rights: RIGHTS_FD_WRITE });
254
+
255
+ // preopened directories
256
+ const preopenEntries: Array<{
257
+ fd: number;
258
+ virtualPath: string;
259
+ realPath: string;
260
+ }> = [];
261
+ for (const [virtualPath, realPath] of Object.entries(preopens)) {
262
+ const fd = nextFd++;
263
+ fds.set(fd, {
264
+ kind: FdKind.PreopenDir,
265
+ path: realPath,
266
+ rights: RIGHTS_DIR_BASE,
267
+ });
268
+ preopenEntries.push({ fd, virtualPath, realPath });
269
+ }
270
+
271
+ /* wasm memory ref */
272
+
273
+ let memory: WebAssembly.Memory | null = null;
274
+ let instance: WebAssembly.Instance | null = null;
275
+
276
+ const getMemory = (): WebAssembly.Memory => {
277
+ if (memory) return memory;
278
+ if (instance) {
279
+ memory = instance.exports.memory as WebAssembly.Memory;
280
+ if (memory) return memory;
281
+ }
282
+ throw new Error("WASI: WebAssembly.Memory not available");
283
+ };
284
+
285
+ const view = () => new DataView(getMemory().buffer);
286
+ const bytes = () => new Uint8Array(getMemory().buffer);
287
+
288
+ /* stdout / stderr text buffers */
289
+
290
+ let stdoutBuf = "";
291
+ let stderrBuf = "";
292
+
293
+ const flushLine = (fd: number, buf: string): string => {
294
+ const nl = buf.lastIndexOf("\n");
295
+ if (nl < 0) return buf;
296
+ const lines = buf.substring(0, nl);
297
+ if (fd === 1) console.log(lines);
298
+ else console.error(lines);
299
+ return buf.substring(nl + 1);
300
+ };
301
+
302
+ /* helpers */
303
+
304
+ const readString = (ptr: number, len: number): string => {
305
+ return decoder.decode(new Uint8Array(getMemory().buffer, ptr, len));
306
+ };
307
+
308
+ const writeString = (ptr: number, str: string): number => {
309
+ const encoded = encoder.encode(str + "\0");
310
+ bytes().set(encoded, ptr);
311
+ return encoded.length;
312
+ };
313
+
314
+ const flushFile = (entry: FdEntry): void => {
315
+ if (entry.dirty && fs && entry.path && entry.data) {
316
+ fs.writeFileSync(entry.path, entry.data);
317
+ entry.dirty = false;
318
+ }
319
+ };
320
+
321
+ /* Build the wasi_snapshot_preview1 import object */
322
+ const wasiImport: Record<string, Function> = {
323
+ /* args */
324
+ args_get: syscall((argv_ptr: number, argv_buf_ptr: number): number => {
325
+ const dv = view();
326
+ const mem = bytes();
327
+ for (const arg of args) {
328
+ dv.setUint32(argv_ptr, argv_buf_ptr, true);
329
+ argv_ptr += 4;
330
+ const encoded = encoder.encode(arg + "\0");
331
+ mem.set(encoded, argv_buf_ptr);
332
+ argv_buf_ptr += encoded.length;
333
+ }
334
+ return ERRNO_SUCCESS;
335
+ }),
336
+
337
+ args_sizes_get: syscall(
338
+ (argc_out: number, argv_buf_size_out: number): number => {
339
+ const dv = view();
340
+ dv.setUint32(argc_out, args.length, true);
341
+ let bufSize = 0;
342
+ for (const arg of args) bufSize += encoder.encode(arg + "\0").length;
343
+ dv.setUint32(argv_buf_size_out, bufSize, true);
344
+ return ERRNO_SUCCESS;
345
+ },
346
+ ),
347
+
348
+ /* environ */
349
+
350
+ environ_get: syscall(
351
+ (environ_ptr: number, environ_buf_ptr: number): number => {
352
+ const entries = Object.entries(envVars);
353
+ const dv = view();
354
+ const mem = bytes();
355
+ for (const [key, value] of entries) {
356
+ dv.setUint32(environ_ptr, environ_buf_ptr, true);
357
+ environ_ptr += 4;
358
+ const encoded = encoder.encode(`${key}=${value}\0`);
359
+ mem.set(encoded, environ_buf_ptr);
360
+ environ_buf_ptr += encoded.length;
361
+ }
362
+ return ERRNO_SUCCESS;
363
+ },
364
+ ),
365
+
366
+ environ_sizes_get: syscall(
367
+ (environc_out: number, environ_buf_size_out: number): number => {
368
+ const entries = Object.entries(envVars);
369
+ const dv = view();
370
+ dv.setUint32(environc_out, entries.length, true);
371
+ let bufSize = 0;
372
+ for (const [key, value] of entries)
373
+ bufSize += encoder.encode(`${key}=${value}\0`).length;
374
+ dv.setUint32(environ_buf_size_out, bufSize, true);
375
+ return ERRNO_SUCCESS;
376
+ },
377
+ ),
378
+
379
+ /* clock */
380
+
381
+ clock_res_get: syscall((id: number, resolution_out: number): number => {
382
+ const dv = view();
383
+ switch (id) {
384
+ case CLOCKID_REALTIME:
385
+ dv.setBigUint64(resolution_out, BigInt(1e6), true);
386
+ break;
387
+ case CLOCKID_MONOTONIC:
388
+ case CLOCKID_PROCESS_CPUTIME_ID:
389
+ case CLOCKID_THREAD_CPUTIME_ID:
390
+ dv.setBigUint64(resolution_out, BigInt(1e3), true);
391
+ break;
392
+ default:
393
+ return ERRNO_INVAL;
394
+ }
395
+ return ERRNO_SUCCESS;
396
+ }),
397
+
398
+ clock_time_get: syscall(
399
+ (id: number, _precision: bigint, time_out: number): number => {
400
+ const dv = view();
401
+ switch (id) {
402
+ case CLOCKID_REALTIME: {
403
+ const time = BigInt(Date.now()) * BigInt(1e6);
404
+ dv.setBigUint64(time_out, time, true);
405
+ break;
406
+ }
407
+ case CLOCKID_MONOTONIC:
408
+ case CLOCKID_PROCESS_CPUTIME_ID:
409
+ case CLOCKID_THREAD_CPUTIME_ID: {
410
+ const t = performance.now();
411
+ const s = Math.trunc(t);
412
+ const ms = Math.floor((t - s) * 1e3);
413
+ const time = BigInt(s) * BigInt(1e9) + BigInt(ms) * BigInt(1e6);
414
+ dv.setBigUint64(time_out, time, true);
415
+ break;
416
+ }
417
+ default:
418
+ return ERRNO_INVAL;
419
+ }
420
+ return ERRNO_SUCCESS;
421
+ },
422
+ ),
423
+
424
+ /* ---- fd operations ------------------------------------------- */
425
+
426
+ fd_advise: syscall(
427
+ (_fd: number, _offset: bigint, _len: bigint, _advice: number): number => {
428
+ return ERRNO_SUCCESS; // advisory, can be a no-op
429
+ },
430
+ ),
431
+
432
+ fd_allocate: syscall(
433
+ (_fd: number, _offset: bigint, _len: bigint): number => {
434
+ return ERRNO_NOSYS;
435
+ },
436
+ ),
437
+
438
+ fd_close: syscall((fd: number): number => {
439
+ const entry = fds.get(fd);
440
+ if (!entry) return ERRNO_BADF;
441
+ if (entry.kind === FdKind.File) flushFile(entry);
442
+ fds.delete(fd);
443
+ return ERRNO_SUCCESS;
444
+ }),
445
+
446
+ fd_datasync: syscall((fd: number): number => {
447
+ const entry = fds.get(fd);
448
+ if (!entry) return ERRNO_BADF;
449
+ if (entry.kind === FdKind.File) flushFile(entry);
450
+ return ERRNO_SUCCESS;
451
+ }),
452
+
453
+ fd_fdstat_get: syscall((fd: number, stat_out: number): number => {
454
+ const entry = fds.get(fd);
455
+ if (!entry) return ERRNO_BADF;
456
+ const dv = view();
457
+
458
+ let filetype = FILETYPE_UNKNOWN;
459
+ let fdflags = 0;
460
+ let rightsBase = entry.rights;
461
+ let rightsInheriting = 0n;
462
+
463
+ switch (entry.kind) {
464
+ case FdKind.Stdin:
465
+ case FdKind.Stdout:
466
+ case FdKind.Stderr:
467
+ filetype = FILETYPE_UNKNOWN; // character device, but WASI doesnt have that in snapshot1 for this
468
+ break;
469
+ case FdKind.PreopenDir:
470
+ filetype = FILETYPE_DIRECTORY;
471
+ rightsInheriting = RIGHTS_ALL;
472
+ break;
473
+ case FdKind.File:
474
+ filetype = FILETYPE_REGULAR_FILE;
475
+ if (entry.flags && entry.flags & FDFLAGS_APPEND)
476
+ fdflags |= FDFLAGS_APPEND;
477
+ break;
478
+ }
479
+
480
+ // fdstat layout: u8 filetype, u16 fdflags, u64 rights_base, u64 rights_inheriting
481
+ dv.setUint8(stat_out, filetype);
482
+ dv.setUint16(stat_out + 2, fdflags, true);
483
+ dv.setBigUint64(stat_out + 8, rightsBase, true);
484
+ dv.setBigUint64(stat_out + 16, rightsInheriting, true);
485
+ return ERRNO_SUCCESS;
486
+ }),
487
+
488
+ fd_fdstat_set_flags: syscall((_fd: number, _flags: number): number => {
489
+ return ERRNO_NOSYS;
490
+ }),
491
+
492
+ fd_fdstat_set_rights: syscall(
493
+ (
494
+ _fd: number,
495
+ _rights_base: bigint,
496
+ _rights_inheriting: bigint,
497
+ ): number => {
498
+ return ERRNO_NOSYS;
499
+ },
500
+ ),
501
+
502
+ fd_filestat_get: syscall((fd: number, buf_out: number): number => {
503
+ const entry = fds.get(fd);
504
+ if (!entry) return ERRNO_BADF;
505
+
506
+ const dv = view();
507
+ let size = 0n;
508
+ let filetype = FILETYPE_UNKNOWN;
509
+ let mtimeNs = 0n;
510
+ let atimeNs = 0n;
511
+ let ctimeNs = 0n;
512
+ let ino = 0n;
513
+ let nlink = 1n;
514
+
515
+ if (entry.kind === FdKind.File) {
516
+ size = BigInt(entry.data ? entry.data.length : 0);
517
+ filetype = FILETYPE_REGULAR_FILE;
518
+ if (fs && entry.path) {
519
+ try {
520
+ const stat = fs.statSync(entry.path);
521
+ mtimeNs = BigInt(Math.floor(stat.mtimeMs)) * BigInt(1e6);
522
+ atimeNs = BigInt(Math.floor(stat.atimeMs)) * BigInt(1e6);
523
+ ctimeNs = BigInt(Math.floor(stat.ctimeMs)) * BigInt(1e6);
524
+ if (stat.ino) ino = BigInt(stat.ino);
525
+ if (stat.nlink) nlink = BigInt(stat.nlink);
526
+ } catch {
527
+ /* ignore */
528
+ }
529
+ }
530
+ } else if (entry.kind === FdKind.PreopenDir) {
531
+ filetype = FILETYPE_DIRECTORY;
532
+ }
533
+
534
+ // filestat layout: u64 dev, u64 ino, u8 filetype (at +16), u64 nlink, u64 size,
535
+ // u64 atim, u64 mtim, u64 ctim
536
+ dv.setBigUint64(buf_out, 0n, true); // dev
537
+ dv.setBigUint64(buf_out + 8, ino, true); // ino
538
+ dv.setUint8(buf_out + 16, filetype); // filetype
539
+ dv.setBigUint64(buf_out + 24, nlink, true); // nlink
540
+ dv.setBigUint64(buf_out + 32, size, true); // size
541
+ dv.setBigUint64(buf_out + 40, atimeNs, true);
542
+ dv.setBigUint64(buf_out + 48, mtimeNs, true);
543
+ dv.setBigUint64(buf_out + 56, ctimeNs, true);
544
+ return ERRNO_SUCCESS;
545
+ }),
546
+
547
+ fd_filestat_set_size: syscall((fd: number, size: bigint): number => {
548
+ const entry = fds.get(fd);
549
+ if (!entry || entry.kind !== FdKind.File) return ERRNO_BADF;
550
+ const newSize = Number(size);
551
+ if (!entry.data) {
552
+ entry.data = new Uint8Array(newSize);
553
+ } else if (entry.data.length !== newSize) {
554
+ const newData = new Uint8Array(newSize);
555
+ newData.set(
556
+ entry.data.subarray(0, Math.min(entry.data.length, newSize)),
557
+ );
558
+ entry.data = newData;
559
+ }
560
+ entry.dirty = true;
561
+ return ERRNO_SUCCESS;
562
+ }),
563
+
564
+ fd_filestat_set_times: syscall(
565
+ (
566
+ _fd: number,
567
+ _atim: bigint,
568
+ _mtim: bigint,
569
+ _fst_flags: number,
570
+ ): number => {
571
+ return ERRNO_SUCCESS; // no-op, our MemoryVolume auto timestamps
572
+ },
573
+ ),
574
+
575
+ fd_pread: syscall(
576
+ (
577
+ fd: number,
578
+ iovs_ptr: number,
579
+ iovs_len: number,
580
+ offset: bigint,
581
+ nread_out: number,
582
+ ): number => {
583
+ const entry = fds.get(fd);
584
+ if (!entry || entry.kind !== FdKind.File) return ERRNO_BADF;
585
+ if (!entry.data) {
586
+ view().setUint32(nread_out, 0, true);
587
+ return ERRNO_SUCCESS;
588
+ }
589
+
590
+ const dv = view();
591
+ let pos = Number(offset);
592
+ let totalRead = 0;
593
+
594
+ for (let i = 0; i < iovs_len; i++) {
595
+ const bufPtr = dv.getUint32(iovs_ptr + i * 8, true);
596
+ const bufLen = dv.getUint32(iovs_ptr + i * 8 + 4, true);
597
+ const toRead = Math.min(bufLen, entry.data.length - pos);
598
+ if (toRead <= 0) break;
599
+ bytes().set(entry.data.subarray(pos, pos + toRead), bufPtr);
600
+ pos += toRead;
601
+ totalRead += toRead;
602
+ }
603
+
604
+ dv.setUint32(nread_out, totalRead, true);
605
+ return ERRNO_SUCCESS;
606
+ },
607
+ ),
608
+
609
+ fd_prestat_get: syscall((fd: number, buf_out: number): number => {
610
+ const entry = fds.get(fd);
611
+ if (!entry || entry.kind !== FdKind.PreopenDir) return ERRNO_BADF;
612
+
613
+ const preopen = preopenEntries.find((p) => p.fd === fd);
614
+ if (!preopen) return ERRNO_BADF;
615
+
616
+ const dv = view();
617
+ const nameLen = encoder.encode(preopen.virtualPath).length;
618
+ // prestat: u8 type (0 = dir), then u32 name_len at +4
619
+ dv.setUint8(buf_out, PREOPENTYPE_DIR);
620
+ dv.setUint32(buf_out + 4, nameLen, true);
621
+ return ERRNO_SUCCESS;
622
+ }),
623
+
624
+ fd_prestat_dir_name: syscall(
625
+ (fd: number, path_ptr: number, path_len: number): number => {
626
+ const entry = fds.get(fd);
627
+ if (!entry || entry.kind !== FdKind.PreopenDir) return ERRNO_BADF;
628
+
629
+ const preopen = preopenEntries.find((p) => p.fd === fd);
630
+ if (!preopen) return ERRNO_BADF;
631
+
632
+ const encoded = encoder.encode(preopen.virtualPath);
633
+ const toCopy = Math.min(encoded.length, path_len);
634
+ bytes().set(encoded.subarray(0, toCopy), path_ptr);
635
+ return ERRNO_SUCCESS;
636
+ },
637
+ ),
638
+
639
+ fd_pwrite: syscall(
640
+ (
641
+ fd: number,
642
+ iovs_ptr: number,
643
+ iovs_len: number,
644
+ offset: bigint,
645
+ nwritten_out: number,
646
+ ): number => {
647
+ const entry = fds.get(fd);
648
+ if (!entry || entry.kind !== FdKind.File) return ERRNO_BADF;
649
+
650
+ const dv = view();
651
+ let pos = Number(offset);
652
+ let totalWritten = 0;
653
+
654
+ for (let i = 0; i < iovs_len; i++) {
655
+ const bufPtr = dv.getUint32(iovs_ptr + i * 8, true);
656
+ const bufLen = dv.getUint32(iovs_ptr + i * 8 + 4, true);
657
+ const chunk = new Uint8Array(getMemory().buffer, bufPtr, bufLen);
658
+
659
+ // grow data if needed
660
+ const needed = pos + bufLen;
661
+ if (!entry.data || entry.data.length < needed) {
662
+ const newData = new Uint8Array(needed);
663
+ if (entry.data) newData.set(entry.data);
664
+ entry.data = newData;
665
+ }
666
+ entry.data.set(chunk, pos);
667
+ pos += bufLen;
668
+ totalWritten += bufLen;
669
+ }
670
+
671
+ entry.dirty = true;
672
+ dv.setUint32(nwritten_out, totalWritten, true);
673
+ return ERRNO_SUCCESS;
674
+ },
675
+ ),
676
+
677
+ fd_read: syscall(
678
+ (
679
+ fd: number,
680
+ iovs_ptr: number,
681
+ iovs_len: number,
682
+ nread_out: number,
683
+ ): number => {
684
+ const entry = fds.get(fd);
685
+ if (!entry) return ERRNO_BADF;
686
+
687
+ const dv = view();
688
+
689
+ if (entry.kind === FdKind.Stdin) {
690
+ // stdin - no interactive input in browser, return 0 bytes (EOF)
691
+ dv.setUint32(nread_out, 0, true);
692
+ return ERRNO_SUCCESS;
693
+ }
694
+
695
+ if (entry.kind !== FdKind.File) return ERRNO_BADF;
696
+ if (!entry.data) {
697
+ dv.setUint32(nread_out, 0, true);
698
+ return ERRNO_SUCCESS;
699
+ }
700
+
701
+ let totalRead = 0;
702
+ let pos = entry.offset ?? 0;
703
+
704
+ for (let i = 0; i < iovs_len; i++) {
705
+ const bufPtr = dv.getUint32(iovs_ptr + i * 8, true);
706
+ const bufLen = dv.getUint32(iovs_ptr + i * 8 + 4, true);
707
+ const toRead = Math.min(bufLen, entry.data.length - pos);
708
+ if (toRead <= 0) break;
709
+ bytes().set(entry.data.subarray(pos, pos + toRead), bufPtr);
710
+ pos += toRead;
711
+ totalRead += toRead;
712
+ }
713
+
714
+ entry.offset = pos;
715
+ dv.setUint32(nread_out, totalRead, true);
716
+ return ERRNO_SUCCESS;
717
+ },
718
+ ),
719
+
720
+ fd_readdir: syscall(
721
+ (
722
+ fd: number,
723
+ buf_ptr: number,
724
+ buf_len: number,
725
+ cookie: bigint,
726
+ bufused_out: number,
727
+ ): number => {
728
+ const entry = fds.get(fd);
729
+ if (
730
+ !entry ||
731
+ (entry.kind !== FdKind.PreopenDir && entry.kind !== FdKind.File)
732
+ )
733
+ return ERRNO_BADF;
734
+ if (!fs) return ERRNO_NOSYS;
735
+
736
+ let entries: string[];
737
+ try {
738
+ entries = fs.readdirSync(entry.path);
739
+ } catch {
740
+ return ERRNO_IO;
741
+ }
742
+
743
+ const dv = view();
744
+ const mem = bytes();
745
+ let offset = buf_ptr;
746
+ const end = buf_ptr + buf_len;
747
+ let idx = 0;
748
+ const start = Number(cookie);
749
+
750
+ for (let i = start; i < entries.length; i++) {
751
+ const name = entries[i];
752
+ const nameBytes = encoder.encode(name);
753
+
754
+ // dirent: u64 d_next, u64 d_ino, u32 d_namlen, u8 d_type, then name
755
+ const direntSize = 24 + nameBytes.length;
756
+ if (offset + 24 > end) break; // not enough space for header
757
+
758
+ dv.setBigUint64(offset, BigInt(i + 1), true); // d_next
759
+ dv.setBigUint64(offset + 8, BigInt(i + 1), true); // d_ino (fake)
760
+
761
+ dv.setUint32(offset + 16, nameBytes.length, true); // d_namlen
762
+
763
+ // determine type
764
+ let dtype = FILETYPE_REGULAR_FILE;
765
+ try {
766
+ const st = fs.statSync(joinPath(entry.path, name));
767
+ if (st.isDirectory()) dtype = FILETYPE_DIRECTORY;
768
+ else if (st.isSymbolicLink()) dtype = FILETYPE_SYMBOLIC_LINK;
769
+ } catch {
770
+ /* default to regular */
771
+ }
772
+ dv.setUint8(offset + 20, dtype); // d_type
773
+
774
+ // write name (may be partial if buf too small)
775
+ const nameCopy = Math.min(nameBytes.length, end - (offset + 24));
776
+ if (nameCopy > 0)
777
+ mem.set(nameBytes.subarray(0, nameCopy), offset + 24);
778
+
779
+ offset += 24 + nameCopy;
780
+ idx++;
781
+ }
782
+
783
+ dv.setUint32(bufused_out, offset - buf_ptr, true);
784
+ return ERRNO_SUCCESS;
785
+ },
786
+ ),
787
+
788
+ fd_renumber: syscall((fd: number, to: number): number => {
789
+ const entry = fds.get(fd);
790
+ if (!entry) return ERRNO_BADF;
791
+ if (fds.has(to)) {
792
+ const toEntry = fds.get(to)!;
793
+ if (toEntry.kind === FdKind.File) flushFile(toEntry);
794
+ }
795
+ fds.set(to, entry);
796
+ fds.delete(fd);
797
+ return ERRNO_SUCCESS;
798
+ }),
799
+
800
+ fd_seek: syscall(
801
+ (
802
+ fd: number,
803
+ offset: bigint,
804
+ whence: number,
805
+ newoffset_out: number,
806
+ ): number => {
807
+ const entry = fds.get(fd);
808
+ if (!entry) return ERRNO_BADF;
809
+
810
+ if (
811
+ entry.kind === FdKind.Stdin ||
812
+ entry.kind === FdKind.Stdout ||
813
+ entry.kind === FdKind.Stderr
814
+ ) {
815
+ return ERRNO_SPIPE;
816
+ }
817
+ if (entry.kind === FdKind.PreopenDir) return ERRNO_BADF;
818
+
819
+ const dataLen = entry.data ? entry.data.length : 0;
820
+ let pos = entry.offset ?? 0;
821
+ const off = Number(offset);
822
+
823
+ switch (whence) {
824
+ case WHENCE_SET:
825
+ pos = off;
826
+ break;
827
+ case WHENCE_CUR:
828
+ pos += off;
829
+ break;
830
+ case WHENCE_END:
831
+ pos = dataLen + off;
832
+ break;
833
+ default:
834
+ return ERRNO_INVAL;
835
+ }
836
+
837
+ if (pos < 0) pos = 0;
838
+ entry.offset = pos;
839
+ view().setBigUint64(newoffset_out, BigInt(pos), true);
840
+ return ERRNO_SUCCESS;
841
+ },
842
+ ),
843
+
844
+ fd_sync: syscall((fd: number): number => {
845
+ const entry = fds.get(fd);
846
+ if (!entry) return ERRNO_BADF;
847
+ if (entry.kind === FdKind.File) flushFile(entry);
848
+ return ERRNO_SUCCESS;
849
+ }),
850
+
851
+ fd_tell: syscall((fd: number, offset_out: number): number => {
852
+ const entry = fds.get(fd);
853
+ if (!entry || entry.kind !== FdKind.File) return ERRNO_BADF;
854
+ view().setBigUint64(offset_out, BigInt(entry.offset ?? 0), true);
855
+ return ERRNO_SUCCESS;
856
+ }),
857
+
858
+ fd_write: syscall(
859
+ (
860
+ fd: number,
861
+ iovs_ptr: number,
862
+ iovs_len: number,
863
+ nwritten_out: number,
864
+ ): number => {
865
+ const entry = fds.get(fd);
866
+ if (!entry) return ERRNO_BADF;
867
+
868
+ const dv = view();
869
+
870
+ // stdout / stderr
871
+ if (entry.kind === FdKind.Stdout || entry.kind === FdKind.Stderr) {
872
+ let totalWritten = 0;
873
+ for (let i = 0; i < iovs_len; i++) {
874
+ const bufPtr = dv.getUint32(iovs_ptr + i * 8, true);
875
+ const bufLen = dv.getUint32(iovs_ptr + i * 8 + 4, true);
876
+ const data = new Uint8Array(getMemory().buffer, bufPtr, bufLen);
877
+ const text = decoder.decode(data);
878
+ if (entry.kind === FdKind.Stdout) {
879
+ stdoutBuf += text;
880
+ stdoutBuf = flushLine(1, stdoutBuf);
881
+ } else {
882
+ stderrBuf += text;
883
+ stderrBuf = flushLine(2, stderrBuf);
884
+ }
885
+ totalWritten += bufLen;
886
+ }
887
+ dv.setUint32(nwritten_out, totalWritten, true);
888
+ return ERRNO_SUCCESS;
889
+ }
890
+
891
+ if (entry.kind !== FdKind.File) return ERRNO_BADF;
892
+
893
+ let totalWritten = 0;
894
+ let pos =
895
+ entry.flags && entry.flags & FDFLAGS_APPEND
896
+ ? entry.data
897
+ ? entry.data.length
898
+ : 0
899
+ : (entry.offset ?? 0);
900
+
901
+ for (let i = 0; i < iovs_len; i++) {
902
+ const bufPtr = dv.getUint32(iovs_ptr + i * 8, true);
903
+ const bufLen = dv.getUint32(iovs_ptr + i * 8 + 4, true);
904
+ const chunk = new Uint8Array(getMemory().buffer, bufPtr, bufLen);
905
+
906
+ const needed = pos + bufLen;
907
+ if (!entry.data || entry.data.length < needed) {
908
+ const newData = new Uint8Array(needed);
909
+ if (entry.data) newData.set(entry.data);
910
+ entry.data = newData;
911
+ }
912
+ entry.data.set(chunk, pos);
913
+ pos += bufLen;
914
+ totalWritten += bufLen;
915
+ }
916
+
917
+ entry.offset = pos;
918
+ entry.dirty = true;
919
+ dv.setUint32(nwritten_out, totalWritten, true);
920
+ return ERRNO_SUCCESS;
921
+ },
922
+ ),
923
+
924
+ /* path operations */
925
+
926
+ path_create_directory: syscall(
927
+ (fd: number, path_ptr: number, path_len: number): number => {
928
+ if (!fs) return ERRNO_NOSYS;
929
+ const entry = fds.get(fd);
930
+ if (!entry) return ERRNO_BADF;
931
+ const rel = readString(path_ptr, path_len);
932
+ const fullPath = joinPath(entry.path, rel);
933
+ fs.mkdirSync(fullPath, { recursive: true });
934
+ return ERRNO_SUCCESS;
935
+ },
936
+ ),
937
+
938
+ path_filestat_get: syscall(
939
+ (
940
+ fd: number,
941
+ _flags: number,
942
+ path_ptr: number,
943
+ path_len: number,
944
+ buf_out: number,
945
+ ): number => {
946
+ if (!fs) return ERRNO_NOSYS;
947
+ const entry = fds.get(fd);
948
+ if (!entry) return ERRNO_BADF;
949
+ const rel = readString(path_ptr, path_len);
950
+ const fullPath = joinPath(entry.path, rel);
951
+
952
+ const stat = fs.statSync(fullPath);
953
+ const dv = view();
954
+
955
+ let filetype = FILETYPE_REGULAR_FILE;
956
+ if (stat.isDirectory()) filetype = FILETYPE_DIRECTORY;
957
+ else if (stat.isSymbolicLink()) filetype = FILETYPE_SYMBOLIC_LINK;
958
+
959
+ const mtimeNs = BigInt(Math.floor(stat.mtimeMs)) * BigInt(1e6);
960
+ const atimeNs = BigInt(Math.floor(stat.atimeMs)) * BigInt(1e6);
961
+ const ctimeNs = BigInt(Math.floor(stat.ctimeMs)) * BigInt(1e6);
962
+
963
+ dv.setBigUint64(buf_out, 0n, true); // dev
964
+ dv.setBigUint64(buf_out + 8, BigInt(stat.ino ?? 0), true); // ino
965
+ dv.setUint8(buf_out + 16, filetype); // filetype
966
+ dv.setBigUint64(buf_out + 24, BigInt(stat.nlink ?? 1), true);
967
+ dv.setBigUint64(buf_out + 32, BigInt(stat.size), true);
968
+ dv.setBigUint64(buf_out + 40, atimeNs, true);
969
+ dv.setBigUint64(buf_out + 48, mtimeNs, true);
970
+ dv.setBigUint64(buf_out + 56, ctimeNs, true);
971
+ return ERRNO_SUCCESS;
972
+ },
973
+ ),
974
+
975
+ path_filestat_set_times: syscall(
976
+ (
977
+ _fd: number,
978
+ _flags: number,
979
+ _path_ptr: number,
980
+ _path_len: number,
981
+ _atim: bigint,
982
+ _mtim: bigint,
983
+ _fst_flags: number,
984
+ ): number => {
985
+ return ERRNO_SUCCESS; // no-op
986
+ },
987
+ ),
988
+
989
+ path_link: syscall(
990
+ (
991
+ _old_fd: number,
992
+ _old_flags: number,
993
+ _old_path_ptr: number,
994
+ _old_path_len: number,
995
+ _new_fd: number,
996
+ _new_path_ptr: number,
997
+ _new_path_len: number,
998
+ ): number => {
999
+ return ERRNO_NOSYS;
1000
+ },
1001
+ ),
1002
+
1003
+ path_open: syscall(
1004
+ (
1005
+ fd: number,
1006
+ _dirflags: number,
1007
+ path_ptr: number,
1008
+ path_len: number,
1009
+ oflags: number,
1010
+ _fs_rights_base: bigint,
1011
+ _fs_rights_inheriting: bigint,
1012
+ fdflags: number,
1013
+ opened_fd_out: number,
1014
+ ): number => {
1015
+ if (!fs) return ERRNO_NOSYS;
1016
+ const dirEntry = fds.get(fd);
1017
+ if (!dirEntry) return ERRNO_BADF;
1018
+
1019
+ const rel = readString(path_ptr, path_len);
1020
+ const fullPath = joinPath(dirEntry.path, rel);
1021
+
1022
+ const wantDir = (oflags & OFLAGS_DIRECTORY) !== 0;
1023
+ const wantCreate = (oflags & OFLAGS_CREAT) !== 0;
1024
+ const wantExcl = (oflags & OFLAGS_EXCL) !== 0;
1025
+ const wantTrunc = (oflags & OFLAGS_TRUNC) !== 0;
1026
+
1027
+ let exists = fs.existsSync(fullPath);
1028
+
1029
+ if (wantExcl && exists) return ERRNO_EXIST;
1030
+
1031
+ if (wantDir) {
1032
+ if (!exists) {
1033
+ if (wantCreate) {
1034
+ fs.mkdirSync(fullPath, { recursive: true });
1035
+ } else {
1036
+ return ERRNO_NOENT;
1037
+ }
1038
+ }
1039
+ // open directory fd
1040
+ const newFd = nextFd++;
1041
+ fds.set(newFd, {
1042
+ kind: FdKind.PreopenDir,
1043
+ path: fullPath,
1044
+ rights: RIGHTS_DIR_BASE,
1045
+ });
1046
+ view().setUint32(opened_fd_out, newFd, true);
1047
+ return ERRNO_SUCCESS;
1048
+ }
1049
+
1050
+ // regular file
1051
+ let data: Uint8Array;
1052
+ if (exists && !wantTrunc) {
1053
+ data = fs.readFileSync(fullPath);
1054
+ // make a copy so mutations dont affect the volume until flush
1055
+ const copy = new Uint8Array(data.length);
1056
+ copy.set(data);
1057
+ data = copy;
1058
+ } else if (wantCreate || wantTrunc) {
1059
+ if (!exists) {
1060
+ fs.writeFileSync(fullPath, new Uint8Array(0));
1061
+ }
1062
+ data = new Uint8Array(0);
1063
+ } else {
1064
+ if (!exists) return ERRNO_NOENT;
1065
+ data = fs.readFileSync(fullPath);
1066
+ const copy = new Uint8Array(data.length);
1067
+ copy.set(data);
1068
+ data = copy;
1069
+ }
1070
+
1071
+ const newFd = nextFd++;
1072
+ fds.set(newFd, {
1073
+ kind: FdKind.File,
1074
+ path: fullPath,
1075
+ rights: RIGHTS_FILE_BASE,
1076
+ data,
1077
+ offset: 0,
1078
+ dirty: false,
1079
+ flags: fdflags,
1080
+ });
1081
+ view().setUint32(opened_fd_out, newFd, true);
1082
+ return ERRNO_SUCCESS;
1083
+ },
1084
+ ),
1085
+
1086
+ path_readlink: syscall(
1087
+ (
1088
+ fd: number,
1089
+ path_ptr: number,
1090
+ path_len: number,
1091
+ buf_ptr: number,
1092
+ buf_len: number,
1093
+ bufused_out: number,
1094
+ ): number => {
1095
+ if (!fs || !fs.readlinkSync) return ERRNO_NOSYS;
1096
+ const entry = fds.get(fd);
1097
+ if (!entry) return ERRNO_BADF;
1098
+ const rel = readString(path_ptr, path_len);
1099
+ const fullPath = joinPath(entry.path, rel);
1100
+ const target = fs.readlinkSync(fullPath);
1101
+ const encoded = encoder.encode(target);
1102
+ const toCopy = Math.min(encoded.length, buf_len);
1103
+ bytes().set(encoded.subarray(0, toCopy), buf_ptr);
1104
+ view().setUint32(bufused_out, toCopy, true);
1105
+ return ERRNO_SUCCESS;
1106
+ },
1107
+ ),
1108
+
1109
+ path_remove_directory: syscall(
1110
+ (fd: number, path_ptr: number, path_len: number): number => {
1111
+ if (!fs) return ERRNO_NOSYS;
1112
+ const entry = fds.get(fd);
1113
+ if (!entry) return ERRNO_BADF;
1114
+ const rel = readString(path_ptr, path_len);
1115
+ const fullPath = joinPath(entry.path, rel);
1116
+ fs.rmdirSync(fullPath);
1117
+ return ERRNO_SUCCESS;
1118
+ },
1119
+ ),
1120
+
1121
+ path_rename: syscall(
1122
+ (
1123
+ fd: number,
1124
+ old_path_ptr: number,
1125
+ old_path_len: number,
1126
+ new_fd: number,
1127
+ new_path_ptr: number,
1128
+ new_path_len: number,
1129
+ ): number => {
1130
+ if (!fs) return ERRNO_NOSYS;
1131
+ const oldEntry = fds.get(fd);
1132
+ const newEntry = fds.get(new_fd);
1133
+ if (!oldEntry || !newEntry) return ERRNO_BADF;
1134
+ const oldRel = readString(old_path_ptr, old_path_len);
1135
+ const newRel = readString(new_path_ptr, new_path_len);
1136
+ const oldPath = joinPath(oldEntry.path, oldRel);
1137
+ const newPath = joinPath(newEntry.path, newRel);
1138
+ fs.renameSync(oldPath, newPath);
1139
+ return ERRNO_SUCCESS;
1140
+ },
1141
+ ),
1142
+
1143
+ path_symlink: syscall(
1144
+ (
1145
+ old_path_ptr: number,
1146
+ old_path_len: number,
1147
+ fd: number,
1148
+ new_path_ptr: number,
1149
+ new_path_len: number,
1150
+ ): number => {
1151
+ if (!fs || !fs.symlinkSync) return ERRNO_NOSYS;
1152
+ const entry = fds.get(fd);
1153
+ if (!entry) return ERRNO_BADF;
1154
+ const target = readString(old_path_ptr, old_path_len);
1155
+ const linkRel = readString(new_path_ptr, new_path_len);
1156
+ const linkPath = joinPath(entry.path, linkRel);
1157
+ fs.symlinkSync(target, linkPath);
1158
+ return ERRNO_SUCCESS;
1159
+ },
1160
+ ),
1161
+
1162
+ path_unlink_file: syscall(
1163
+ (fd: number, path_ptr: number, path_len: number): number => {
1164
+ if (!fs) return ERRNO_NOSYS;
1165
+ const entry = fds.get(fd);
1166
+ if (!entry) return ERRNO_BADF;
1167
+ const rel = readString(path_ptr, path_len);
1168
+ const fullPath = joinPath(entry.path, rel);
1169
+ fs.unlinkSync(fullPath);
1170
+ return ERRNO_SUCCESS;
1171
+ },
1172
+ ),
1173
+
1174
+ /* ---- misc ---------------------------------------------------- */
1175
+
1176
+ poll_oneoff: syscall(
1177
+ (
1178
+ in_ptr: number,
1179
+ out_ptr: number,
1180
+ nsubscriptions: number,
1181
+ nevents_out: number,
1182
+ ): number => {
1183
+ // uhm this is a very minimal implementation: handle clock subscriptions as immediate,
1184
+ // report all fd subscriptions as ready
1185
+ const dv = view();
1186
+ let nevents = 0;
1187
+
1188
+ for (let i = 0; i < nsubscriptions; i++) {
1189
+ const subPtr = in_ptr + i * 48;
1190
+ const eventPtr = out_ptr + nevents * 32;
1191
+
1192
+ const userdata = dv.getBigUint64(subPtr, true);
1193
+ const eventType = dv.getUint8(subPtr + 8);
1194
+
1195
+ dv.setBigUint64(eventPtr, userdata, true); // userdata
1196
+ dv.setUint16(eventPtr + 8, ERRNO_SUCCESS, true); // error
1197
+ dv.setUint8(eventPtr + 10, eventType); // type
1198
+
1199
+ if (
1200
+ eventType === EVENTTYPE_FD_READ ||
1201
+ eventType === EVENTTYPE_FD_WRITE
1202
+ ) {
1203
+ // report some available bytes
1204
+ dv.setBigUint64(eventPtr + 16, BigInt(1), true); // nbytes
1205
+ dv.setUint16(eventPtr + 24, 0, true); // flags
1206
+ }
1207
+
1208
+ nevents++;
1209
+ }
1210
+
1211
+ dv.setUint32(nevents_out, nevents, true);
1212
+ return ERRNO_SUCCESS;
1213
+ },
1214
+ ),
1215
+
1216
+ proc_exit: syscall((rval: number): never => {
1217
+ // flush remaining output
1218
+ if (stdoutBuf) {
1219
+ console.log(stdoutBuf);
1220
+ stdoutBuf = "";
1221
+ }
1222
+ if (stderrBuf) {
1223
+ console.error(stderrBuf);
1224
+ stderrBuf = "";
1225
+ }
1226
+ throw new ExitStatus(rval);
1227
+ }),
1228
+
1229
+ proc_raise: syscall((_sig: number): number => {
1230
+ return ERRNO_NOSYS;
1231
+ }),
1232
+
1233
+ sched_yield: syscall((): number => {
1234
+ return ERRNO_SUCCESS;
1235
+ }),
1236
+
1237
+ random_get: syscall((buf_ptr: number, buf_len: number): number => {
1238
+ const slice = new Uint8Array(getMemory().buffer, buf_ptr, buf_len);
1239
+ crypto.getRandomValues(slice);
1240
+ return ERRNO_SUCCESS;
1241
+ }),
1242
+
1243
+ sock_recv: syscall((): number => ERRNO_NOSYS),
1244
+ sock_send: syscall((): number => ERRNO_NOSYS),
1245
+ sock_shutdown: syscall((): number => ERRNO_NOSYS),
1246
+ sock_accept: syscall((): number => ERRNO_NOSYS),
1247
+ };
1248
+
1249
+ /* public api */
1250
+
1251
+ const self: any = this;
1252
+ self.wasiImport = wasiImport;
1253
+
1254
+ self.start = function start(wasmInstance: any): number {
1255
+ instance = wasmInstance;
1256
+ memory = wasmInstance.exports.memory as WebAssembly.Memory;
1257
+
1258
+ const _start = wasmInstance.exports._start;
1259
+ if (typeof _start !== "function") {
1260
+ throw new Error("WASI: instance has no _start export");
1261
+ }
1262
+
1263
+ try {
1264
+ _start();
1265
+ } catch (err) {
1266
+ if (err instanceof ExitStatus) {
1267
+ if (returnOnExit) return err.code;
1268
+ throw err;
1269
+ }
1270
+ throw err;
1271
+ } finally {
1272
+ // flush remaining output
1273
+ if (stdoutBuf) {
1274
+ console.log(stdoutBuf);
1275
+ stdoutBuf = "";
1276
+ }
1277
+ if (stderrBuf) {
1278
+ console.error(stderrBuf);
1279
+ stderrBuf = "";
1280
+ }
1281
+ }
1282
+ return 0;
1283
+ };
1284
+
1285
+ self.initialize = function initialize(wasmInstance: any): void {
1286
+ instance = wasmInstance;
1287
+ memory = wasmInstance.exports.memory as WebAssembly.Memory;
1288
+
1289
+ const _initialize = wasmInstance.exports._initialize;
1290
+ if (typeof _initialize === "function") {
1291
+ _initialize();
1292
+ }
1293
+ };
1294
+
1295
+ self.getImportObject = function getImportObject(): Record<
1296
+ string,
1297
+ Record<string, Function>
1298
+ > {
1299
+ return { wasi_snapshot_preview1: wasiImport };
1300
+ };
1301
+ } as unknown as WASIConstructor;
41
1302
 
1303
+ /* matches Node.js `require('wasi')` */
42
1304
  export default {
43
1305
  WASI,
44
1306
  };