@capsule-run/sdk 0.7.2 → 0.8.1

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 (61) hide show
  1. package/README.md +5 -1
  2. package/dist/app.d.ts +7 -0
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +7 -0
  5. package/dist/app.js.map +1 -1
  6. package/dist/index.d.ts +1 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/polyfills/fs.d.ts +81 -11
  11. package/dist/polyfills/fs.d.ts.map +1 -1
  12. package/dist/polyfills/fs.js +290 -11
  13. package/dist/polyfills/fs.js.map +1 -1
  14. package/dist/polyfills/process.d.ts +17 -4
  15. package/dist/polyfills/process.d.ts.map +1 -1
  16. package/dist/polyfills/process.js +118 -19
  17. package/dist/polyfills/process.js.map +1 -1
  18. package/dist/run.d.ts +3 -0
  19. package/dist/run.d.ts.map +1 -1
  20. package/dist/run.js.map +1 -1
  21. package/dist/task.d.ts +11 -2
  22. package/dist/task.d.ts.map +1 -1
  23. package/dist/task.js +6 -3
  24. package/dist/task.js.map +1 -1
  25. package/package.json +4 -10
  26. package/src/app.ts +8 -0
  27. package/src/index.ts +1 -2
  28. package/src/polyfills/fs.ts +317 -12
  29. package/src/polyfills/process.ts +119 -20
  30. package/src/run.ts +3 -0
  31. package/src/task.ts +32 -14
  32. package/dist/polyfills/buffer.d.ts +0 -8
  33. package/dist/polyfills/buffer.d.ts.map +0 -1
  34. package/dist/polyfills/buffer.js +0 -9
  35. package/dist/polyfills/buffer.js.map +0 -1
  36. package/dist/polyfills/events.d.ts +0 -8
  37. package/dist/polyfills/events.d.ts.map +0 -1
  38. package/dist/polyfills/events.js +0 -9
  39. package/dist/polyfills/events.js.map +0 -1
  40. package/dist/polyfills/path.d.ts +0 -8
  41. package/dist/polyfills/path.d.ts.map +0 -1
  42. package/dist/polyfills/path.js +0 -8
  43. package/dist/polyfills/path.js.map +0 -1
  44. package/dist/polyfills/stream-web.d.ts +0 -74
  45. package/dist/polyfills/stream-web.d.ts.map +0 -1
  46. package/dist/polyfills/stream-web.js +0 -23
  47. package/dist/polyfills/stream-web.js.map +0 -1
  48. package/dist/polyfills/stream.d.ts +0 -16
  49. package/dist/polyfills/stream.d.ts.map +0 -1
  50. package/dist/polyfills/stream.js +0 -16
  51. package/dist/polyfills/stream.js.map +0 -1
  52. package/dist/polyfills/url.d.ts +0 -47
  53. package/dist/polyfills/url.d.ts.map +0 -1
  54. package/dist/polyfills/url.js +0 -68
  55. package/dist/polyfills/url.js.map +0 -1
  56. package/src/polyfills/buffer.ts +0 -11
  57. package/src/polyfills/events.ts +0 -11
  58. package/src/polyfills/path.ts +0 -21
  59. package/src/polyfills/stream-web.ts +0 -24
  60. package/src/polyfills/stream.ts +0 -28
  61. package/src/polyfills/url.ts +0 -73
@@ -3,6 +3,8 @@
3
3
  * Provides both Node.js fs API
4
4
  */
5
5
 
6
+ import { getCwd } from './process.js';
7
+
6
8
  declare const globalThis: {
7
9
  'wasi:filesystem/types': any;
8
10
  'wasi:filesystem/preopens': any;
@@ -76,11 +78,24 @@ function normalizePath(path: string): string {
76
78
  return path;
77
79
  }
78
80
 
81
+ /**
82
+ * Absolute paths pass through unchanged.
83
+ */
84
+ function getEffectivePath(path: string): string {
85
+ if (path.startsWith('/')) return path;
86
+
87
+ const cwd = getCwd();
88
+ if (cwd === '.' || cwd === '') return path;
89
+ if (path === '.') return cwd;
90
+ if (path.startsWith('./')) return cwd.replace(/\/+$/, '') + '/' + path.slice(2);
91
+ return cwd.replace(/\/+$/, '') + '/' + path;
92
+ }
93
+
79
94
  function resolvePath(path: string): { dir: Descriptor; relativePath: string } | null {
80
95
  const preopens = getPreopenedDirs();
81
96
  if (preopens.length === 0) return null;
82
97
 
83
- const normalizedPath = normalizePath(path);
98
+ const normalizedPath = normalizePath(getEffectivePath(path));
84
99
 
85
100
  let catchAll: { dir: Descriptor; relativePath: string } | null = null;
86
101
 
@@ -296,12 +311,287 @@ export function readdir(
296
311
  .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
297
312
  }
298
313
 
314
+ // ---------------------------------------------------------------------------
315
+ // Sync implementations
316
+ // ---------------------------------------------------------------------------
317
+
318
+ function enoent(path: string): Error {
319
+ return Object.assign(
320
+ new Error(`ENOENT: no such file or directory, open '${path}'`),
321
+ { code: 'ENOENT' }
322
+ );
323
+ }
324
+
325
+ /**
326
+ * Read file contents synchronously.
327
+ */
328
+ export function readFileSync(path: string, options?: ReadFileOptions | Encoding): string | Uint8Array {
329
+ const resolved = resolvePath(path);
330
+ if (!resolved) throw enoent(path);
331
+
332
+ try {
333
+ const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
334
+ const stat = fd.stat();
335
+ const [data] = fd.read(stat.size, BigInt(0));
336
+ const encoding = typeof options === 'string' ? options : options?.encoding;
337
+ return (encoding === 'utf8' || encoding === 'utf-8') ? new TextDecoder().decode(data) : data;
338
+ } catch (e) {
339
+ if (e instanceof Error && (e as any).code) throw e;
340
+ throw Object.assign(new Error(`ENOENT: no such file or directory, open '${path}'`), { code: 'ENOENT' });
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Write data to a file synchronously.
346
+ */
347
+ export function writeFileSync(path: string, data: string | Uint8Array, _options?: WriteFileOptions | Encoding): void {
348
+ const resolved = resolvePath(path);
349
+ if (!resolved) throw enoent(path);
350
+
351
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
352
+ try {
353
+ const fd = resolved.dir.openAt(
354
+ { symlinkFollow: false },
355
+ resolved.relativePath,
356
+ { create: true, truncate: true },
357
+ { write: true, mutateDirectory: true }
358
+ );
359
+ fd.write(bytes, BigInt(0));
360
+ } catch (e) {
361
+ throw new Error(`ENOENT: no such file or directory, open '${path}'`);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Append data to a file synchronously, creating it if it doesn't exist.
367
+ */
368
+ export function appendFileSync(path: string, data: string | Uint8Array, _options?: WriteFileOptions | Encoding): void {
369
+ const resolved = resolvePath(path);
370
+ if (!resolved) throw enoent(path);
371
+
372
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
373
+ try {
374
+ const fd = resolved.dir.openAt(
375
+ { symlinkFollow: false },
376
+ resolved.relativePath,
377
+ { create: true },
378
+ { write: true, mutateDirectory: true }
379
+ );
380
+ const stat = fd.stat();
381
+ fd.write(bytes, stat.size);
382
+ } catch (e) {
383
+ throw new Error(`Failed to append to file '${path}': ${e}`);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Read directory contents synchronously.
389
+ */
390
+ export function readdirSync(path: string, _options?: any): string[] {
391
+ const resolved = resolvePath(path);
392
+ if (!resolved) throw enoent(path);
393
+
394
+ try {
395
+ let targetDir = resolved.dir;
396
+ if (resolved.relativePath !== '.') {
397
+ targetDir = resolved.dir.openAt(
398
+ { symlinkFollow: false },
399
+ resolved.relativePath,
400
+ { directory: true },
401
+ { read: true }
402
+ );
403
+ }
404
+ const stream = targetDir.readDirectory();
405
+ const entries: string[] = [];
406
+ let entry;
407
+ while ((entry = stream.readDirectoryEntry()) && entry) {
408
+ if (entry.name) entries.push(entry.name);
409
+ }
410
+ return entries;
411
+ } catch (e) {
412
+ throw Object.assign(new Error(`ENOENT: no such file or directory, scandir '${path}'`), { code: 'ENOENT' });
413
+ }
414
+ }
415
+
416
+ export interface StatResult {
417
+ isFile: () => boolean;
418
+ isDirectory: () => boolean;
419
+ isSymbolicLink: () => boolean;
420
+ size: number;
421
+ mtimeMs: number;
422
+ atimeMs: number;
423
+ ctimeMs: number;
424
+ mode: number;
425
+ }
426
+
427
+ function makeStatResult(type: 'file' | 'directory' | 'notfound', size: bigint = BigInt(0)): StatResult {
428
+ return {
429
+ isFile: () => type === 'file',
430
+ isDirectory: () => type === 'directory',
431
+ isSymbolicLink: () => false,
432
+ size: Number(size),
433
+ mtimeMs: 0,
434
+ atimeMs: 0,
435
+ ctimeMs: 0,
436
+ mode: type === 'directory' ? 0o40755 : 0o100644,
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Get file stats synchronously.
442
+ */
443
+ export function statSync(path: string): StatResult {
444
+ const resolved = resolvePath(path);
445
+ if (!resolved) throw enoent(path);
446
+
447
+ try {
448
+ const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
449
+ const s = fd.stat();
450
+ const type = s.type === 'directory' ? 'directory' : 'file';
451
+ return makeStatResult(type, s.size);
452
+ } catch (e) {
453
+ throw Object.assign(new Error(`ENOENT: no such file or directory, stat '${path}'`), { code: 'ENOENT' });
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Get file stats synchronously (no symlink follow — same as stat in WASI 0.2).
459
+ */
460
+ export function lstatSync(path: string): StatResult {
461
+ return statSync(path);
462
+ }
463
+
464
+ /**
465
+ * Create a directory synchronously.
466
+ */
467
+ export function mkdirSync(path: string, options?: MkdirOptions): void {
468
+ if (options?.recursive) {
469
+ const normalized = normalizePath(path);
470
+ const parts = normalized.split('/').filter(Boolean);
471
+ for (let i = 1; i <= parts.length; i++) {
472
+ const partial = parts.slice(0, i).join('/');
473
+ const resolved = resolvePath(partial);
474
+ if (!resolved) continue;
475
+ try { resolved.dir.createDirectoryAt(resolved.relativePath); } catch { /* already exists */ }
476
+ }
477
+ } else {
478
+ const resolved = resolvePath(path);
479
+ if (!resolved) throw enoent(path);
480
+ try {
481
+ resolved.dir.createDirectoryAt(resolved.relativePath);
482
+ } catch (e) {
483
+ throw Object.assign(new Error(`EEXIST: file already exists, mkdir '${path}'`), { code: 'EEXIST' });
484
+ }
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Remove a directory synchronously.
490
+ */
491
+ export function rmdirSync(path: string, _options?: RmdirOptions): void {
492
+ const resolved = resolvePath(path);
493
+ if (!resolved) throw enoent(path);
494
+ try {
495
+ resolved.dir.removeDirectoryAt(resolved.relativePath);
496
+ } catch (e) {
497
+ throw Object.assign(new Error(`ENOENT: no such file or directory, rmdir '${path}'`), { code: 'ENOENT' });
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Remove a file or directory synchronously.
503
+ */
504
+ export function rmSync(path: string, options?: RmOptions): void {
505
+ const resolved = resolvePath(path);
506
+ if (!resolved) {
507
+ if (options?.force) return;
508
+ throw enoent(path);
509
+ }
510
+
511
+ try {
512
+ const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
513
+ const s = fd.stat();
514
+ if (s.type === 'directory') {
515
+ if (!options?.recursive) {
516
+ throw Object.assign(
517
+ new Error(`EISDIR: illegal operation on a directory, rm '${path}'`),
518
+ { code: 'EISDIR' }
519
+ );
520
+ }
521
+ resolved.dir.removeDirectoryAt(resolved.relativePath);
522
+ } else {
523
+ resolved.dir.unlinkFileAt(resolved.relativePath);
524
+ }
525
+ } catch (e) {
526
+ if (options?.force) return;
527
+ if (e instanceof Error && (e as any).code) throw e;
528
+ throw enoent(path);
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Remove a file synchronously.
534
+ */
535
+ export function unlinkSync(path: string): void {
536
+ const resolved = resolvePath(path);
537
+ if (!resolved) throw enoent(path);
538
+ try {
539
+ resolved.dir.unlinkFileAt(resolved.relativePath);
540
+ } catch (e) {
541
+ throw Object.assign(new Error(`ENOENT: no such file or directory, unlink '${path}'`), { code: 'ENOENT' });
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Copy a file synchronously.
547
+ */
548
+ export function copyFileSync(src: string, dest: string): void {
549
+ const data = readFileSync(src) as Uint8Array;
550
+ writeFileSync(dest, data);
551
+ }
552
+
553
+ /**
554
+ * Rename a file or directory synchronously.
555
+ */
556
+ export function renameSync(oldPath: string, newPath: string): void {
557
+ try {
558
+ const data = readFileSync(oldPath);
559
+ writeFileSync(newPath, data as Uint8Array);
560
+ unlinkSync(oldPath);
561
+ } catch (e) {
562
+ throw Object.assign(
563
+ new Error(`ENOENT: no such file or directory, rename '${oldPath}' -> '${newPath}'`),
564
+ { code: 'ENOENT' }
565
+ );
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Check file accessibility synchronously.
571
+ * Throws if the path does not exist.
572
+ */
573
+ export function accessSync(path: string, _mode?: number): void {
574
+ const resolved = resolvePath(path);
575
+ if (!resolved) throw enoent(path);
576
+ try {
577
+ resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
578
+ } catch (e) {
579
+ throw Object.assign(new Error(`ENOENT: no such file or directory, access '${path}'`), { code: 'ENOENT' });
580
+ }
581
+ }
582
+
299
583
  /**
300
- * Check if file/directory exists (sync-style, limited in WASM)
584
+ * Check if a path exists synchronously.
301
585
  */
302
- export function existsSync(_path: string): boolean {
303
- console.warn('fs.existsSync: Cannot implement true sync in WASM. Use fs.access instead.');
304
- return false;
586
+ export function existsSync(path: string): boolean {
587
+ const resolved = resolvePath(path);
588
+ if (!resolved) return false;
589
+ try {
590
+ resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
591
+ return true;
592
+ } catch {
593
+ return false;
594
+ }
305
595
  }
306
596
 
307
597
  /**
@@ -533,15 +823,15 @@ export const promises = {
533
823
  await unlink(path);
534
824
  },
535
825
 
536
- async stat(path: string): Promise<{ isFile: () => boolean; isDirectory: () => boolean }> {
826
+ async stat(path: string): Promise<StatResult> {
537
827
  const kind = await statPath(path);
538
828
  if (kind === 'notfound') {
539
- throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
829
+ throw Object.assign(
830
+ new Error(`ENOENT: no such file or directory, stat '${path}'`),
831
+ { code: 'ENOENT' }
832
+ );
540
833
  }
541
- return {
542
- isFile: () => kind === 'file',
543
- isDirectory: () => kind === 'directory',
544
- };
834
+ return makeStatResult(kind);
545
835
  },
546
836
 
547
837
  async rmdir(path: string, options?: RmdirOptions): Promise<void> {
@@ -566,16 +856,31 @@ export const promises = {
566
856
  };
567
857
 
568
858
  const fs = {
859
+ // Async / callback
569
860
  readFile,
570
861
  writeFile,
571
862
  readdir,
572
- existsSync,
573
863
  unlink,
574
864
  rmdir,
575
865
  rm,
576
866
  mkdir,
577
867
  copyFile,
578
868
  cp,
869
+ readFileSync,
870
+ writeFileSync,
871
+ appendFileSync,
872
+ readdirSync,
873
+ statSync,
874
+ lstatSync,
875
+ mkdirSync,
876
+ rmdirSync,
877
+ rmSync,
878
+ unlinkSync,
879
+ copyFileSync,
880
+ renameSync,
881
+ accessSync,
882
+ existsSync,
883
+ // Promises API
579
884
  promises,
580
885
  };
581
886
 
@@ -9,6 +9,16 @@ declare const globalThis: {
9
9
  getArguments(): string[];
10
10
  initialCwd(): string | null;
11
11
  };
12
+ 'wasi:cli/stdin': {
13
+ getStdin(): {
14
+ blockingRead(len: bigint): [Uint8Array, boolean];
15
+ };
16
+ };
17
+ 'wasi:cli/stdout': {
18
+ getStdout(): {
19
+ blockingWriteAndFlush(buf: Uint8Array): void;
20
+ };
21
+ };
12
22
  };
13
23
 
14
24
  /**
@@ -60,22 +70,44 @@ function getArgv(): string[] {
60
70
  }
61
71
 
62
72
  /**
63
- * Get current working directory from WASI
73
+ * Initialized from wasi:cli/environment initialCwd(), falls back to '.'.
64
74
  */
65
- function getCwd(): string {
66
- const bindings = getEnvBindings();
67
- if (!bindings || typeof bindings.initialCwd !== 'function') {
68
- return '/';
69
- }
70
-
75
+ let _virtualCwd: string = (() => {
71
76
  try {
72
- const cwd = bindings.initialCwd();
73
- return cwd || '/';
74
- } catch {
75
- return '/';
77
+ const env = (globalThis as any)['wasi:cli/environment'];
78
+ if (env && typeof env.initialCwd === 'function') {
79
+ return env.initialCwd() ?? '.';
80
+ }
81
+ } catch {}
82
+ return '.';
83
+ })();
84
+
85
+ /**
86
+ * Internal setter for virtual CWD — handles relative and absolute paths.
87
+ */
88
+ function setCwd(directory: string): void {
89
+ if (!directory) return;
90
+ if (directory.startsWith('/')) {
91
+ _virtualCwd = directory.replace(/\/+$/, '') || '/';
92
+ } else if (directory === '..') {
93
+ const parts = _virtualCwd.split('/').filter(Boolean);
94
+ parts.pop();
95
+ _virtualCwd = parts.join('/') || '.';
96
+ } else {
97
+ _virtualCwd = (_virtualCwd === '.' || _virtualCwd === '')
98
+ ? directory.replace(/\/+$/, '')
99
+ : _virtualCwd.replace(/\/+$/, '') + '/' + directory.replace(/\/+$/, '');
76
100
  }
77
101
  }
78
102
 
103
+ /**
104
+ * Returns the current virtual CWD.
105
+ * Exported so the fs polyfill can resolve relative paths against it.
106
+ */
107
+ export function getCwd(): string {
108
+ return _virtualCwd;
109
+ }
110
+
79
111
  const process = {
80
112
  /**
81
113
  * Environment variables
@@ -92,17 +124,20 @@ const process = {
92
124
  },
93
125
 
94
126
  /**
95
- * Returns the current working directory
127
+ * Returns the current virtual working directory.
128
+ * Backed by in-process state, same as CPython's WASI libc.
96
129
  */
97
130
  cwd(): string {
98
131
  return getCwd();
99
132
  },
100
133
 
101
134
  /**
102
- * Change directory (not supported in WASI)
135
+ * Change the virtual working directory.
136
+ * Affects all relative path resolution in the fs polyfill.
137
+ * Does not perform any real syscall — mirrors CPython WASI behaviour.
103
138
  */
104
139
  chdir(directory: string): void {
105
- throw new Error('process.chdir() is not supported in WASI environment');
140
+ setCwd(directory);
106
141
  },
107
142
 
108
143
  /**
@@ -178,24 +213,88 @@ const process = {
178
213
  },
179
214
 
180
215
  /**
181
- * Standard streams (not fully implemented)
216
+ * Standard input backed by wasi:cli/stdin@0.2.0.
217
+ * Exposes a minimal EventEmitter-compatible Readable so that
218
+ * MCP's StdioServerTransport can call stdin.on('data', ...).
182
219
  */
183
- get stdout(): any {
220
+ get stdin(): any {
221
+ const listeners: Array<(chunk: Uint8Array | string) => void> = [];
222
+ let reading = false;
223
+
224
+ function startReading() {
225
+ if (reading) return;
226
+ reading = true;
227
+ (async () => {
228
+ try {
229
+ const api = globalThis['wasi:cli/stdin'];
230
+ if (!api) return;
231
+ const stream = api.getStdin();
232
+ while (true) {
233
+ const [chunk, done] = stream.blockingRead(BigInt(4096));
234
+ if (chunk && chunk.length > 0) {
235
+ const buf = chunk;
236
+ listeners.forEach(fn => fn(buf));
237
+ }
238
+ if (done) {
239
+ endListeners.forEach(fn => fn());
240
+ break;
241
+ }
242
+ }
243
+ } catch { /* stdin not available */ }
244
+ })();
245
+ }
246
+
247
+ const endListeners: Array<() => void> = [];
248
+
184
249
  return {
185
- write: (data: string) => console.log(data),
186
250
  isTTY: false,
251
+ readable: true,
252
+ on(event: string, fn: (...args: any[]) => void) {
253
+ if (event === 'data') { listeners.push(fn); startReading(); }
254
+ if (event === 'end') { endListeners.push(fn); }
255
+ return this;
256
+ },
257
+ once(event: string, fn: (...args: any[]) => void) {
258
+ return this.on(event, fn);
259
+ },
260
+ pause() { return this; },
261
+ resume() { startReading(); return this; },
262
+ pipe(dest: any) { return dest; },
263
+ setEncoding(_enc: string) { return this; },
187
264
  };
188
265
  },
189
266
 
190
- get stderr(): any {
267
+ /**
268
+ * Standard output — backed by wasi:cli/stdout@0.2.0.
269
+ */
270
+ get stdout(): any {
191
271
  return {
192
- write: (data: string) => console.error(data),
193
272
  isTTY: false,
273
+ write(data: string | Uint8Array, _enc?: string, cb?: () => void): boolean {
274
+ try {
275
+ const api = globalThis['wasi:cli/stdout'];
276
+ if (api) {
277
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
278
+ api.getStdout().blockingWriteAndFlush(bytes);
279
+ } else {
280
+ console.log(typeof data === 'string' ? data : new TextDecoder().decode(data));
281
+ }
282
+ } catch {
283
+ console.log(typeof data === 'string' ? data : new TextDecoder().decode(data));
284
+ }
285
+ cb?.();
286
+ return true;
287
+ },
288
+ end(data?: string | Uint8Array, cb?: () => void): void {
289
+ if (data) this.write(data);
290
+ cb?.();
291
+ },
194
292
  };
195
293
  },
196
294
 
197
- get stdin(): any {
295
+ get stderr(): any {
198
296
  return {
297
+ write: (data: string) => console.error(data),
199
298
  isTTY: false,
200
299
  };
201
300
  },
package/src/run.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { execFile } from 'child_process';
9
9
  import { resolve, extname } from 'path';
10
10
  import { existsSync } from 'fs';
11
+ import { HostRequest } from './task';
11
12
 
12
13
  export interface RunnerOptions {
13
14
  file: string;
@@ -26,6 +27,8 @@ export interface RunnerResult {
26
27
  duration_ms: number;
27
28
  retries: number;
28
29
  fuel_consumed: number;
30
+ ram_used: number;
31
+ host_requests: HostRequest[];
29
32
  };
30
33
  }
31
34
 
package/src/task.ts CHANGED
@@ -50,13 +50,18 @@ interface TaskExecution {
50
50
  duration_ms: number;
51
51
  retries: number;
52
52
  fuel_consumed: number;
53
+ ram_used: number;
54
+ host_requests: HostRequest[];
53
55
  }
54
56
 
55
- type Awaited<T> = T extends Promise<infer U> ? U : T;
57
+ export interface HostRequest {
58
+ method: string;
59
+ url: string;
60
+ headers?: string[] | null;
61
+ body?: string | null;
62
+ status: number;
63
+ }
56
64
 
57
- type TaskReturnType<T> = T extends Promise<infer U>
58
- ? Promise<TaskResult<U>>
59
- : TaskResult<T>;
60
65
 
61
66
  /**
62
67
  * Define a Capsule task with configuration.
@@ -121,10 +126,20 @@ function normalizeAllowedFile(entry: string | AllowedFile): string {
121
126
  }
122
127
  }
123
128
 
124
- export function task<TArgs extends any[], TReturn>(
129
+ export function task<TArgs extends unknown[], TReturn>(
130
+ options: TaskOptions,
131
+ fn: (...args: TArgs) => Promise<TReturn>
132
+ ): (...args: TArgs) => Promise<TaskResult<TReturn>>;
133
+
134
+ export function task<TArgs extends unknown[], TReturn>(
125
135
  options: TaskOptions,
126
136
  fn: (...args: TArgs) => TReturn
127
- ): (...args: TArgs) => TaskReturnType<TReturn> {
137
+ ): (...args: TArgs) => TaskResult<TReturn>;
138
+
139
+ export function task<TArgs extends unknown[], TReturn>(
140
+ options: TaskOptions,
141
+ fn: (...args: TArgs) => TReturn | Promise<TReturn>
142
+ ): (...args: TArgs) => TaskResult<TReturn> | Promise<TaskResult<TReturn>> {
128
143
  const taskName = options.name;
129
144
  let compute = options.compute?.toString().toUpperCase() ?? "MEDIUM";
130
145
  let allowedHosts = options.allowedHosts ?? [];
@@ -140,45 +155,48 @@ export function task<TArgs extends any[], TReturn>(
140
155
  envVariables: options.envVariables,
141
156
  };
142
157
 
143
- const wrapper = (...args: TArgs): TaskReturnType<TReturn> => {
158
+ const wrapper = (...args: TArgs): TaskResult<TReturn> | Promise<TaskResult<TReturn>> => {
144
159
  if (!isWasmMode()) {
145
160
  const result = fn(...args);
146
161
 
147
162
  if (result instanceof Promise) {
148
163
  return result.then((value) => ({
149
164
  success: true,
150
- result: value,
165
+ result: value as TReturn,
151
166
  error: null,
152
167
  execution: {
153
168
  task_name: taskName,
154
169
  duration_ms: 0,
155
170
  retries: 0,
156
171
  fuel_consumed: 0,
172
+ ram_used: 0,
173
+ host_requests: [],
157
174
  },
158
- })) as TaskReturnType<TReturn>;
175
+ }));
159
176
  }
160
177
 
161
178
  return {
162
179
  success: true,
163
- result,
180
+ result: result as TReturn,
164
181
  error: null,
165
182
  execution: {
166
183
  task_name: taskName,
167
184
  duration_ms: 0,
168
185
  retries: 0,
169
186
  fuel_consumed: 0,
187
+ ram_used: 0,
188
+ host_requests: [],
170
189
  },
171
- } as TaskReturnType<TReturn>;
190
+ };
172
191
  }
173
192
 
174
193
  const resultJson = callHost(taskName, args, taskConfig);
175
194
 
176
195
  try {
177
- const result: TaskResult<Awaited<TReturn>> = JSON.parse(resultJson);
178
- return result as TaskReturnType<TReturn>;
196
+ return JSON.parse(resultJson) as TaskResult<TReturn>;
179
197
  } catch (e) {
180
198
  if (e instanceof SyntaxError) {
181
- return resultJson as unknown as TaskReturnType<TReturn>;
199
+ return resultJson as unknown as TaskResult<TReturn>;
182
200
  }
183
201
  throw e;
184
202
  }
@@ -1,8 +0,0 @@
1
- /**
2
- * Buffer polyfill for WASI environment
3
- * Provides Node.js-compatible Buffer class using the buffer package
4
- */
5
- import { Buffer as BufferPolyfill } from 'buffer';
6
- export declare const Buffer: BufferConstructor;
7
- export default BufferPolyfill;
8
- //# sourceMappingURL=buffer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/polyfills/buffer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAIlD,eAAO,MAAM,MAAM,mBAAiB,CAAC;AACrC,eAAe,cAAc,CAAC"}