@capsule-run/sdk 0.4.2 → 0.5.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 (66) hide show
  1. package/README.md +13 -24
  2. package/dist/index.d.ts +4 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/polyfills/buffer.d.ts +8 -0
  7. package/dist/polyfills/buffer.d.ts.map +1 -0
  8. package/dist/polyfills/buffer.js +9 -0
  9. package/dist/polyfills/buffer.js.map +1 -0
  10. package/dist/polyfills/events.d.ts +8 -0
  11. package/dist/polyfills/events.d.ts.map +1 -0
  12. package/dist/polyfills/events.js +9 -0
  13. package/dist/polyfills/events.js.map +1 -0
  14. package/dist/polyfills/fs-promises.d.ts +7 -0
  15. package/dist/polyfills/fs-promises.d.ts.map +1 -0
  16. package/dist/polyfills/fs-promises.js +7 -0
  17. package/dist/polyfills/fs-promises.js.map +1 -0
  18. package/dist/polyfills/fs.d.ts +82 -0
  19. package/dist/polyfills/fs.d.ts.map +1 -0
  20. package/dist/{files.js → polyfills/fs.js} +100 -20
  21. package/dist/polyfills/fs.js.map +1 -0
  22. package/dist/polyfills/os.d.ts +150 -0
  23. package/dist/polyfills/os.d.ts.map +1 -0
  24. package/dist/polyfills/os.js +151 -0
  25. package/dist/polyfills/os.js.map +1 -0
  26. package/dist/polyfills/path.d.ts +8 -0
  27. package/dist/polyfills/path.d.ts.map +1 -0
  28. package/dist/polyfills/path.js +8 -0
  29. package/dist/polyfills/path.js.map +1 -0
  30. package/dist/polyfills/process.d.ts +109 -0
  31. package/dist/polyfills/process.d.ts.map +1 -0
  32. package/dist/polyfills/process.js +224 -0
  33. package/dist/polyfills/process.js.map +1 -0
  34. package/dist/polyfills/stream-web.d.ts +74 -0
  35. package/dist/polyfills/stream-web.d.ts.map +1 -0
  36. package/dist/polyfills/stream-web.js +23 -0
  37. package/dist/polyfills/stream-web.js.map +1 -0
  38. package/dist/polyfills/stream.d.ts +16 -0
  39. package/dist/polyfills/stream.d.ts.map +1 -0
  40. package/dist/polyfills/stream.js +16 -0
  41. package/dist/polyfills/stream.js.map +1 -0
  42. package/dist/polyfills/url.d.ts +47 -0
  43. package/dist/polyfills/url.d.ts.map +1 -0
  44. package/dist/polyfills/url.js +68 -0
  45. package/dist/polyfills/url.js.map +1 -0
  46. package/package.json +18 -1
  47. package/src/index.ts +4 -2
  48. package/src/polyfills/buffer.ts +11 -0
  49. package/src/polyfills/events.ts +11 -0
  50. package/src/polyfills/fs-promises.ts +7 -0
  51. package/src/polyfills/fs.ts +336 -0
  52. package/src/polyfills/os.ts +222 -0
  53. package/src/polyfills/path.ts +21 -0
  54. package/src/polyfills/process.ts +286 -0
  55. package/src/polyfills/stream-web.ts +24 -0
  56. package/src/polyfills/stream.ts +28 -0
  57. package/src/polyfills/url.ts +73 -0
  58. package/dist/env.d.ts +0 -22
  59. package/dist/env.d.ts.map +0 -1
  60. package/dist/env.js +0 -61
  61. package/dist/env.js.map +0 -1
  62. package/dist/files.d.ts +0 -46
  63. package/dist/files.d.ts.map +0 -1
  64. package/dist/files.js.map +0 -1
  65. package/src/env.ts +0 -67
  66. package/src/files.ts +0 -217
@@ -0,0 +1,47 @@
1
+ /**
2
+ * URL polyfill for WASI environment
3
+ * Aliases Node.js 'url' module to native Web URL APIs
4
+ */
5
+ export declare const URL: {
6
+ new (url: string | URL, base?: string | URL): URL;
7
+ prototype: URL;
8
+ canParse(url: string | URL, base?: string | URL): boolean;
9
+ createObjectURL(obj: Blob | MediaSource): string;
10
+ parse(url: string | URL, base?: string | URL): URL | null;
11
+ revokeObjectURL(url: string): void;
12
+ };
13
+ export declare const URLSearchParams: {
14
+ new (init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
15
+ prototype: URLSearchParams;
16
+ };
17
+ /**
18
+ * Legacy Node.js url.parse()
19
+ */
20
+ export declare function parse(urlString: string, parseQueryString?: boolean): any;
21
+ /**
22
+ * Legacy Node.js url.format()
23
+ */
24
+ export declare function format(urlObject: any): string;
25
+ /**
26
+ * Legacy Node.js url.resolve()
27
+ */
28
+ export declare function resolve(from: string, to: string): string;
29
+ declare const url: {
30
+ URL: {
31
+ new (url: string | URL, base?: string | URL): URL;
32
+ prototype: URL;
33
+ canParse(url: string | URL, base?: string | URL): boolean;
34
+ createObjectURL(obj: Blob | MediaSource): string;
35
+ parse(url: string | URL, base?: string | URL): URL | null;
36
+ revokeObjectURL(url: string): void;
37
+ };
38
+ URLSearchParams: {
39
+ new (init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
40
+ prototype: URLSearchParams;
41
+ };
42
+ parse: typeof parse;
43
+ format: typeof format;
44
+ resolve: typeof resolve;
45
+ };
46
+ export default url;
47
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/polyfills/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,GAAG;;;;;;;CAAiB,CAAC;AAElC,eAAO,MAAM,eAAe;;;CAA6B,CAAC;AAE1D;;GAEG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,OAAO,GAAG,GAAG,CAiBxE;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,CAiB7C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;CAMR,CAAC;AAEF,eAAe,GAAG,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * URL polyfill for WASI environment
3
+ * Aliases Node.js 'url' module to native Web URL APIs
4
+ */
5
+ export const URL = globalThis.URL;
6
+ export const URLSearchParams = globalThis.URLSearchParams;
7
+ /**
8
+ * Legacy Node.js url.parse()
9
+ */
10
+ export function parse(urlString, parseQueryString) {
11
+ try {
12
+ const url = new URL(urlString);
13
+ return {
14
+ href: url.href,
15
+ protocol: url.protocol,
16
+ host: url.host,
17
+ hostname: url.hostname,
18
+ port: url.port,
19
+ pathname: url.pathname,
20
+ search: url.search,
21
+ hash: url.hash,
22
+ query: parseQueryString ? Object.fromEntries(url.searchParams) : url.search.slice(1),
23
+ };
24
+ }
25
+ catch (e) {
26
+ return null;
27
+ }
28
+ }
29
+ /**
30
+ * Legacy Node.js url.format()
31
+ */
32
+ export function format(urlObject) {
33
+ if (typeof urlObject === 'string') {
34
+ return urlObject;
35
+ }
36
+ try {
37
+ const protocol = urlObject.protocol || 'http:';
38
+ const hostname = urlObject.hostname || urlObject.host || 'localhost';
39
+ const port = urlObject.port ? `:${urlObject.port}` : '';
40
+ const pathname = urlObject.pathname || '/';
41
+ const search = urlObject.search || '';
42
+ const hash = urlObject.hash || '';
43
+ return `${protocol}//${hostname}${port}${pathname}${search}${hash}`;
44
+ }
45
+ catch (e) {
46
+ return '';
47
+ }
48
+ }
49
+ /**
50
+ * Legacy Node.js url.resolve()
51
+ */
52
+ export function resolve(from, to) {
53
+ try {
54
+ return new URL(to, from).href;
55
+ }
56
+ catch (e) {
57
+ return to;
58
+ }
59
+ }
60
+ const url = {
61
+ URL,
62
+ URLSearchParams,
63
+ parse,
64
+ format,
65
+ resolve,
66
+ };
67
+ export default url;
68
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/polyfills/url.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC,eAAe,CAAC;AAE1D;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,SAAiB,EAAE,gBAA0B;IAC/D,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/B,OAAO;YACH,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;SACvF,CAAC;IACN,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,SAAc;IACjC,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,IAAI,OAAO,CAAC;QAC/C,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,IAAI,IAAI,WAAW,CAAC;QACrE,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,IAAI,GAAG,CAAC;QAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC;QAElC,OAAO,GAAG,QAAQ,KAAK,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC;IACxE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,EAAU;IAC5C,IAAI,CAAC;QACD,OAAO,IAAI,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;IAClC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED,MAAM,GAAG,GAAG;IACR,GAAG;IACH,eAAe;IACf,KAAK;IACL,MAAM;IACN,OAAO;CACV,CAAC;AAEF,eAAe,GAAG,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capsule-run/sdk",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Capsule JavaScript SDK - run AI agent tasks in secure WASM sandboxes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,24 @@
39
39
  "license": "Apache-2.0",
40
40
  "dependencies": {
41
41
  "@bytecodealliance/jco": "^1.0.0",
42
+ "buffer": "^6.0.3",
42
43
  "esbuild": "^0.27.2",
44
+ "events": "^3.3.0",
45
+ "path-browserify": "^1.0.1",
46
+ "readable-stream": "^4.7.0",
43
47
  "typescript": "^5.9.3"
48
+ },
49
+ "devDependencies": {
50
+ "@types/events": "^3.0.3",
51
+ "@types/path-browserify": "^1.0.3",
52
+ "@types/readable-stream": "^4.0.18"
53
+ },
54
+ "peerDependencies": {
55
+ "@types/node": ">=18"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@types/node": {
59
+ "optional": true
60
+ }
44
61
  }
45
62
  }
package/src/index.ts CHANGED
@@ -16,7 +16,9 @@
16
16
 
17
17
  export { task, type TaskOptions } from "./task.js";
18
18
  export { TaskRunner, exports, type TaskConfig } from "./app.js";
19
- export * as files from "./files.js";
20
- export * as env from "./env.js";
19
+ export { default as fs } from "./polyfills/fs.js";
21
20
  export { isWasmMode } from "./hostApi.js";
21
+ export { default as path } from "./polyfills/path.js";
22
+ export { default as os } from "./polyfills/os.js";
23
+ export { default as process } from "./polyfills/process.js";
22
24
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Buffer polyfill for WASI environment
3
+ * Provides Node.js-compatible Buffer class using the buffer package
4
+ */
5
+
6
+ import { Buffer as BufferPolyfill } from 'buffer';
7
+
8
+ (globalThis as any).Buffer = BufferPolyfill;
9
+
10
+ export const Buffer = BufferPolyfill;
11
+ export default BufferPolyfill;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Events polyfill for WASI environment
3
+ * Provides Node.js-compatible EventEmitter using the events package
4
+ */
5
+
6
+ import EventEmitter from 'events';
7
+
8
+ (globalThis as any).EventEmitter = EventEmitter;
9
+
10
+ export { EventEmitter };
11
+ export default EventEmitter;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * fs/promises polyfill for WASI environment
3
+ * Allows: import fs from 'fs/promises'
4
+ */
5
+
6
+ export { promises as default } from './fs.js';
7
+ export * from './fs.js';
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Node.js fs polyfill for WASI environment
3
+ * Provides both Node.js fs API
4
+ */
5
+
6
+ declare const globalThis: {
7
+ 'wasi:filesystem/types': any;
8
+ 'wasi:filesystem/preopens': any;
9
+ };
10
+
11
+ interface Descriptor {
12
+ read(length: bigint, offset: bigint): [Uint8Array, boolean];
13
+ write(buffer: Uint8Array, offset: bigint): bigint;
14
+ stat(): { size: bigint };
15
+ readDirectory(): any;
16
+ openAt(
17
+ pathFlags: { symlinkFollow?: boolean },
18
+ path: string,
19
+ openFlags: { create?: boolean; directory?: boolean; exclusive?: boolean; truncate?: boolean },
20
+ descriptorFlags: { read?: boolean; write?: boolean; mutateDirectory?: boolean }
21
+ ): Descriptor;
22
+ }
23
+
24
+ interface PreopenedDir {
25
+ descriptor: Descriptor;
26
+ guestPath: string;
27
+ }
28
+
29
+ function getFsBindings(): { types: any; preopens: any } | null {
30
+ try {
31
+ const types = globalThis['wasi:filesystem/types'];
32
+ const preopens = globalThis['wasi:filesystem/preopens'];
33
+ if (types && preopens) {
34
+ return { types, preopens };
35
+ }
36
+ } catch {}
37
+ return null;
38
+ }
39
+
40
+ function getPreopenedDirs(): PreopenedDir[] {
41
+ const fs = getFsBindings();
42
+ if (!fs) return [];
43
+
44
+ try {
45
+ const dirs = fs.preopens.getDirectories();
46
+ return (dirs || []).map((entry: [Descriptor, string]) => ({
47
+ descriptor: entry[0],
48
+ guestPath: entry[1]
49
+ }));
50
+ } catch {
51
+ return [];
52
+ }
53
+ }
54
+
55
+ function normalizePath(path: string): string {
56
+ if (path.startsWith('./')) {
57
+ return path.slice(2);
58
+ }
59
+ return path;
60
+ }
61
+
62
+ function resolvePath(path: string): { dir: Descriptor; relativePath: string } | null {
63
+ const preopens = getPreopenedDirs();
64
+ if (preopens.length === 0) return null;
65
+
66
+ const normalizedPath = normalizePath(path);
67
+
68
+ for (const { descriptor, guestPath } of preopens) {
69
+ const normalizedGuest = normalizePath(guestPath);
70
+
71
+ if (normalizedGuest === '.' || normalizedGuest === '') {
72
+ return { dir: descriptor, relativePath: normalizedPath };
73
+ }
74
+
75
+ if (normalizedPath.startsWith(normalizedGuest + '/')) {
76
+ const relativePath = normalizedPath.slice(normalizedGuest.length + 1);
77
+ return { dir: descriptor, relativePath };
78
+ }
79
+
80
+ if (normalizedPath === normalizedGuest) {
81
+ return { dir: descriptor, relativePath: '.' };
82
+ }
83
+ }
84
+
85
+ return { dir: preopens[0].descriptor, relativePath: normalizedPath };
86
+ }
87
+
88
+ /**
89
+ * Read a file as text.
90
+ */
91
+ export async function readText(path: string): Promise<string> {
92
+ const bytes = await readBytes(path);
93
+ return new TextDecoder().decode(bytes);
94
+ }
95
+
96
+ /**
97
+ * Read a file as bytes.
98
+ */
99
+ export async function readBytes(path: string): Promise<Uint8Array> {
100
+ const resolved = resolvePath(path);
101
+ if (!resolved) {
102
+ throw new Error("Filesystem not available.");
103
+ }
104
+
105
+ try {
106
+ const pathFlags = { symlinkFollow: false };
107
+ const openFlags = {};
108
+ const descriptorFlags = { read: true };
109
+
110
+ const fd = resolved.dir.openAt(pathFlags, resolved.relativePath, openFlags, descriptorFlags);
111
+ const stat = fd.stat();
112
+ const [data] = fd.read(stat.size, BigInt(0));
113
+ return data;
114
+ } catch (e) {
115
+ throw new Error(`Failed to read file '${path}': ${e}`);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Write text content to a file.
121
+ */
122
+ export async function writeText(path: string, content: string): Promise<void> {
123
+ const bytes = new TextEncoder().encode(content);
124
+ await writeBytes(path, bytes);
125
+ }
126
+
127
+ /**
128
+ * Write bytes to a file.
129
+ */
130
+ export async function writeBytes(path: string, data: Uint8Array): Promise<void> {
131
+ const resolved = resolvePath(path);
132
+ if (!resolved) {
133
+ throw new Error("Filesystem not available.");
134
+ }
135
+
136
+ try {
137
+ const pathFlags = { symlinkFollow: false };
138
+ const openFlags = { create: true, truncate: true };
139
+ const descriptorFlags = { write: true };
140
+
141
+ const fd = resolved.dir.openAt(pathFlags, resolved.relativePath, openFlags, descriptorFlags);
142
+ fd.write(data, BigInt(0));
143
+ } catch (e) {
144
+ throw new Error(`Failed to write file '${path}': ${e}`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * List files/directories at a path.
150
+ */
151
+ export async function list(path: string = "."): Promise<string[]> {
152
+ const resolved = resolvePath(path);
153
+ if (!resolved) {
154
+ throw new Error("Filesystem not available.");
155
+ }
156
+
157
+ try {
158
+ let targetDir = resolved.dir;
159
+ if (resolved.relativePath !== ".") {
160
+ const pathFlags = { symlinkFollow: false };
161
+ const openFlags = { directory: true };
162
+ const descriptorFlags = { read: true };
163
+ targetDir = resolved.dir.openAt(pathFlags, resolved.relativePath, openFlags, descriptorFlags);
164
+ }
165
+
166
+ const stream = targetDir.readDirectory();
167
+ const entries: string[] = [];
168
+
169
+ let entry;
170
+ while ((entry = stream.readDirectoryEntry()) && entry) {
171
+ if (entry.name) {
172
+ entries.push(entry.name);
173
+ }
174
+ }
175
+
176
+ return entries;
177
+ } catch (e) {
178
+ throw new Error(`Failed to list directory '${path}': ${e}`);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Check if a file or directory exists.
184
+ */
185
+ export async function exists(path: string): Promise<boolean> {
186
+ const resolved = resolvePath(path);
187
+ if (!resolved) {
188
+ return false;
189
+ }
190
+
191
+ try {
192
+ const pathFlags = { symlinkFollow: false };
193
+ const openFlags = {};
194
+ const descriptorFlags = { read: true };
195
+ resolved.dir.openAt(pathFlags, resolved.relativePath, openFlags, descriptorFlags);
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+
202
+ type Encoding = 'utf8' | 'utf-8' | 'buffer' | null | undefined;
203
+
204
+ interface ReadFileOptions {
205
+ encoding?: Encoding;
206
+ }
207
+
208
+ interface WriteFileOptions {
209
+ encoding?: Encoding;
210
+ }
211
+
212
+ /**
213
+ * Read file contents (async/callback style)
214
+ */
215
+ export function readFile(
216
+ path: string,
217
+ optionsOrCallback: ReadFileOptions | Encoding | ((err: Error | null, data?: string | Uint8Array) => void),
218
+ callback?: (err: Error | null, data?: string | Uint8Array) => void
219
+ ): void {
220
+ let options: ReadFileOptions = {};
221
+ let cb: ((err: Error | null, data?: string | Uint8Array) => void) | undefined;
222
+
223
+ if (typeof optionsOrCallback === 'function') {
224
+ cb = optionsOrCallback;
225
+ } else if (typeof optionsOrCallback === 'string') {
226
+ options = { encoding: optionsOrCallback as Encoding };
227
+ cb = callback;
228
+ } else if (optionsOrCallback) {
229
+ options = optionsOrCallback;
230
+ cb = callback;
231
+ }
232
+
233
+ const encoding = options.encoding;
234
+ const isText = encoding === 'utf8' || encoding === 'utf-8';
235
+
236
+ const promise = isText ? readText(path) : readBytes(path);
237
+
238
+ promise
239
+ .then((data) => cb?.(null, data))
240
+ .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
241
+ }
242
+
243
+ /**
244
+ * Write file contents (async/callback style)
245
+ */
246
+ export function writeFile(
247
+ path: string,
248
+ data: string | Uint8Array,
249
+ optionsOrCallback: WriteFileOptions | Encoding | ((err: Error | null) => void),
250
+ callback?: (err: Error | null) => void
251
+ ): void {
252
+ const cb: ((err: Error | null) => void) | undefined =
253
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
254
+
255
+ const promise = typeof data === 'string'
256
+ ? writeText(path, data)
257
+ : writeBytes(path, data);
258
+
259
+ promise
260
+ .then(() => cb?.(null))
261
+ .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
262
+ }
263
+
264
+ /**
265
+ * Read directory contents (async/callback style)
266
+ */
267
+ export function readdir(
268
+ path: string,
269
+ optionsOrCallback: any | ((err: Error | null, files?: string[]) => void),
270
+ callback?: (err: Error | null, files?: string[]) => void
271
+ ): void {
272
+ const cb = typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
273
+
274
+ list(path)
275
+ .then((files) => cb?.(null, files))
276
+ .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
277
+ }
278
+
279
+ /**
280
+ * Check if file/directory exists (sync-style, limited in WASM)
281
+ */
282
+ export function existsSync(_path: string): boolean {
283
+ console.warn('fs.existsSync: Cannot implement true sync in WASM. Use fs.access instead.');
284
+ return false;
285
+ }
286
+
287
+ /**
288
+ * Promises API
289
+ */
290
+ export const promises = {
291
+ async readFile(path: string, options?: ReadFileOptions | Encoding): Promise<string | Uint8Array> {
292
+ const encoding = typeof options === 'string' ? options : options?.encoding;
293
+ const isText = encoding === 'utf8' || encoding === 'utf-8';
294
+ return isText ? readText(path) : readBytes(path);
295
+ },
296
+
297
+ async writeFile(path: string, data: string | Uint8Array): Promise<void> {
298
+ if (typeof data === 'string') {
299
+ await writeText(path, data);
300
+ } else {
301
+ await writeBytes(path, data);
302
+ }
303
+ },
304
+
305
+ async readdir(path: string): Promise<string[]> {
306
+ return list(path);
307
+ },
308
+
309
+ async access(path: string): Promise<void> {
310
+ const fileExists = await exists(path);
311
+ if (!fileExists) {
312
+ throw new Error(`ENOENT: no such file or directory, access '${path}'`);
313
+ }
314
+ },
315
+
316
+ async stat(path: string): Promise<{ isFile: () => boolean; isDirectory: () => boolean }> {
317
+ const fileExists = await exists(path);
318
+ if (!fileExists) {
319
+ throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
320
+ }
321
+ return {
322
+ isFile: () => true,
323
+ isDirectory: () => false,
324
+ };
325
+ },
326
+ };
327
+
328
+ const fs = {
329
+ readFile,
330
+ writeFile,
331
+ readdir,
332
+ existsSync,
333
+ promises,
334
+ };
335
+
336
+ export default fs;