xit-wasm 0.1.0

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.
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Node-fs-backed Host implementation.
3
+ *
4
+ * Strategy:
5
+ * - Dir handles are i32s mapped to absolute filesystem paths. Handle 0 is
6
+ * reserved for the "root" directory provided to NodeHost's constructor;
7
+ * all sub-path operations resolve relative to it.
8
+ * - File handles are i32s mapped to Node file descriptors (fs.openSync), but
9
+ * wrapped with a small extra state object so we can track current position
10
+ * (for seek-by) and locked state without re-querying the fs.
11
+ * - All ops use Node sync APIs. Plenty fast for spike-grade testing; can
12
+ * swap for a Worker-driven async model later if needed.
13
+ */
14
+
15
+ import * as fs from "node:fs";
16
+ import * as path from "node:path";
17
+ import * as crypto from "node:crypto";
18
+
19
+ import {
20
+ KIND_FILE,
21
+ KIND_DIR,
22
+ KIND_SYMLINK,
23
+ KIND_OTHER,
24
+ OK,
25
+ ERR_GENERIC,
26
+ ERR_NOT_FOUND,
27
+ } from "./host.ts";
28
+ import type { Host, StatResult } from "./host.ts";
29
+
30
+ interface FileEntry {
31
+ fd: number;
32
+ position: bigint;
33
+ }
34
+
35
+ export class NodeHost implements Host {
36
+ private dirs = new Map<number, string>();
37
+ private files = new Map<number, FileEntry>();
38
+ private nextHandle = 100;
39
+ trace = false;
40
+
41
+ constructor(rootPath: string) {
42
+ this.dirs.set(0, rootPath);
43
+ }
44
+
45
+ private log(...args: unknown[]) {
46
+ if (this.trace) console.error("[host]", ...args);
47
+ }
48
+
49
+ private resolve(dirHandle: number, sub: string): string {
50
+ const base = this.dirs.get(dirHandle);
51
+ if (base === undefined) throw new Error(`unknown dir handle ${dirHandle}`);
52
+ return path.resolve(base, sub);
53
+ }
54
+
55
+ private allocHandle(): number {
56
+ return this.nextHandle++;
57
+ }
58
+
59
+ private statToResult(s: fs.Stats): StatResult {
60
+ let kind = KIND_OTHER;
61
+ if (s.isFile()) kind = KIND_FILE;
62
+ else if (s.isDirectory()) kind = KIND_DIR;
63
+ else if (s.isSymbolicLink()) kind = KIND_SYMLINK;
64
+ // Node's plain Stats has *Ms (number); promote to ns BigInt by hand. The
65
+ // bigint variant of Stats has *Ns directly but it forces every access to
66
+ // BigInt and is hairier to thread through.
67
+ const msToNs = (ms: number) => BigInt(Math.floor(ms)) * 1_000_000n;
68
+ return {
69
+ size: BigInt(s.size),
70
+ mtimeNs: msToNs(s.mtimeMs),
71
+ atimeNs: msToNs(s.atimeMs),
72
+ ctimeNs: msToNs(s.ctimeMs),
73
+ inode: BigInt(s.ino),
74
+ kind,
75
+ modeBits: s.mode & 0o777,
76
+ };
77
+ }
78
+
79
+ // ----- dir -----
80
+
81
+ dirCreateFile(dirHandle: number, sub: string) {
82
+ const abs = this.resolve(dirHandle, sub);
83
+ try {
84
+ const fd = fs.openSync(abs, "w+");
85
+ const handle = this.allocHandle();
86
+ this.files.set(handle, { fd, position: 0n });
87
+ this.log("dirCreateFile", abs, "->", handle);
88
+ return { code: OK, handle };
89
+ } catch (err: any) {
90
+ this.log("dirCreateFile FAIL", abs, err.code);
91
+ return { code: ERR_GENERIC, handle: -1 };
92
+ }
93
+ }
94
+
95
+ dirOpenFile(dirHandle: number, sub: string) {
96
+ const abs = this.resolve(dirHandle, sub);
97
+ try {
98
+ const fd = fs.openSync(abs, "r+");
99
+ const handle = this.allocHandle();
100
+ this.files.set(handle, { fd, position: 0n });
101
+ this.log("dirOpenFile", abs, "->", handle);
102
+ return { code: OK, handle };
103
+ } catch (err: any) {
104
+ this.log("dirOpenFile FAIL", abs, err.code);
105
+ return { code: ERR_NOT_FOUND, handle: -1 };
106
+ }
107
+ }
108
+
109
+ dirCreateDir(dirHandle: number, sub: string) {
110
+ try {
111
+ fs.mkdirSync(this.resolve(dirHandle, sub));
112
+ return OK;
113
+ } catch (err: any) {
114
+ if (err.code === "EEXIST") return ERR_GENERIC; // xit treats this as already-exists
115
+ return ERR_GENERIC;
116
+ }
117
+ }
118
+
119
+ dirCreateDirPath(dirHandle: number, sub: string) {
120
+ const abs = this.resolve(dirHandle, sub);
121
+ let existed = false;
122
+ try {
123
+ fs.statSync(abs);
124
+ existed = true;
125
+ } catch {}
126
+ try {
127
+ fs.mkdirSync(abs, { recursive: true });
128
+ return { code: OK, existed };
129
+ } catch {
130
+ return { code: ERR_GENERIC, existed: false };
131
+ }
132
+ }
133
+
134
+ dirCreateDirPathOpen(dirHandle: number, sub: string) {
135
+ const abs = this.resolve(dirHandle, sub);
136
+ try {
137
+ fs.mkdirSync(abs, { recursive: true });
138
+ const handle = this.allocHandle();
139
+ this.dirs.set(handle, abs);
140
+ this.log("dirCreateDirPathOpen", abs, "->", handle);
141
+ return { code: OK, handle };
142
+ } catch (err: any) {
143
+ this.log("dirCreateDirPathOpen FAIL", abs, err.code);
144
+ return { code: ERR_GENERIC, handle: -1 };
145
+ }
146
+ }
147
+
148
+ dirOpenDir(dirHandle: number, sub: string) {
149
+ const abs = this.resolve(dirHandle, sub);
150
+ try {
151
+ const s = fs.statSync(abs);
152
+ if (!s.isDirectory()) return { code: ERR_NOT_FOUND, handle: -1 };
153
+ const handle = this.allocHandle();
154
+ this.dirs.set(handle, abs);
155
+ return { code: OK, handle };
156
+ } catch {
157
+ return { code: ERR_NOT_FOUND, handle: -1 };
158
+ }
159
+ }
160
+
161
+ dirClose(handle: number) {
162
+ if (handle === 0) return; // never close the root
163
+ this.dirs.delete(handle);
164
+ }
165
+
166
+ dirDeleteFile(dirHandle: number, sub: string) {
167
+ const abs = this.resolve(dirHandle, sub);
168
+ try {
169
+ fs.unlinkSync(abs);
170
+ this.log("dirDeleteFile", abs);
171
+ return OK;
172
+ } catch (err: any) {
173
+ this.log("dirDeleteFile FAIL", abs, err.code);
174
+ return ERR_NOT_FOUND;
175
+ }
176
+ }
177
+
178
+ dirRename(oldDir: number, oldSub: string, newDir: number, newSub: string) {
179
+ const oldAbs = this.resolve(oldDir, oldSub);
180
+ const newAbs = this.resolve(newDir, newSub);
181
+ try {
182
+ fs.renameSync(oldAbs, newAbs);
183
+ this.log("dirRename", oldAbs, "->", newAbs);
184
+ return OK;
185
+ } catch (err: any) {
186
+ this.log("dirRename FAIL", oldAbs, "->", newAbs, err.code);
187
+ return ERR_GENERIC;
188
+ }
189
+ }
190
+
191
+ dirStatFile(dirHandle: number, sub: string) {
192
+ const abs = this.resolve(dirHandle, sub);
193
+ try {
194
+ const s = fs.lstatSync(abs);
195
+ this.log("dirStatFile", abs, "size", s.size);
196
+ return { code: OK, stat: this.statToResult(s) };
197
+ } catch (err: any) {
198
+ this.log("dirStatFile FAIL", abs, err.code);
199
+ return { code: ERR_NOT_FOUND };
200
+ }
201
+ }
202
+
203
+ dirReadLink(dirHandle: number, sub: string) {
204
+ const abs = this.resolve(dirHandle, sub);
205
+ try {
206
+ const target = fs.readlinkSync(abs);
207
+ this.log("dirReadLink", abs, "->", target);
208
+ return { code: 1, target };
209
+ } catch (err: any) {
210
+ if (err.code === "EINVAL") {
211
+ // not a symlink — xit needs to know this distinction
212
+ this.log("dirReadLink", abs, "(not a symlink)");
213
+ return { code: 0 };
214
+ }
215
+ this.log("dirReadLink FAIL", abs, err.code);
216
+ return { code: -1 };
217
+ }
218
+ }
219
+
220
+ dirAccess(dirHandle: number, sub: string) {
221
+ const abs = this.resolve(dirHandle, sub);
222
+ try {
223
+ fs.accessSync(abs);
224
+ this.log("dirAccess OK", abs);
225
+ return OK;
226
+ } catch {
227
+ this.log("dirAccess MISSING", abs);
228
+ return ERR_NOT_FOUND;
229
+ }
230
+ }
231
+
232
+ // ----- file -----
233
+
234
+ fileClose(handle: number) {
235
+ const entry = this.files.get(handle);
236
+ if (!entry) return;
237
+ try {
238
+ fs.closeSync(entry.fd);
239
+ } catch {}
240
+ this.files.delete(handle);
241
+ }
242
+
243
+ fileRead(handle: number, offset: bigint, len: number) {
244
+ const entry = this.files.get(handle);
245
+ if (!entry) return { code: ERR_GENERIC, data: new Uint8Array(0) };
246
+ const buf = Buffer.alloc(len);
247
+ try {
248
+ const n = fs.readSync(entry.fd, buf, 0, len, Number(offset));
249
+ this.log("fileRead", handle, "offset", offset, "len", len, "got", n);
250
+ return { code: OK, data: new Uint8Array(buf.buffer, buf.byteOffset, n) };
251
+ } catch (err: any) {
252
+ this.log("fileRead FAIL", handle, err.code);
253
+ return { code: ERR_GENERIC, data: new Uint8Array(0) };
254
+ }
255
+ }
256
+
257
+ fileWriteStream(handle: number, data: Uint8Array) {
258
+ const entry = this.files.get(handle);
259
+ if (!entry) return { code: ERR_GENERIC, written: 0 };
260
+ const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
261
+ try {
262
+ const n = fs.writeSync(entry.fd, buf, 0, buf.length, Number(entry.position));
263
+ entry.position += BigInt(n);
264
+ this.log("fileWriteStream", handle, "wrote", n, "newPos", entry.position);
265
+ return { code: OK, written: n };
266
+ } catch (err: any) {
267
+ this.log("fileWriteStream FAIL", handle, err.code);
268
+ return { code: ERR_GENERIC, written: 0 };
269
+ }
270
+ }
271
+
272
+ fileReadStream(handle: number, len: number) {
273
+ const entry = this.files.get(handle);
274
+ if (!entry) return { code: ERR_GENERIC, data: new Uint8Array(0) };
275
+ const buf = Buffer.alloc(len);
276
+ try {
277
+ const n = fs.readSync(entry.fd, buf, 0, len, Number(entry.position));
278
+ entry.position += BigInt(n);
279
+ this.log("fileReadStream", handle, "got", n, "newPos", entry.position);
280
+ return { code: OK, data: new Uint8Array(buf.buffer, buf.byteOffset, n) };
281
+ } catch (err: any) {
282
+ this.log("fileReadStream FAIL", handle, err.code);
283
+ return { code: ERR_GENERIC, data: new Uint8Array(0) };
284
+ }
285
+ }
286
+
287
+ fileWrite(handle: number, offset: bigint, data: Uint8Array) {
288
+ const entry = this.files.get(handle);
289
+ if (!entry) return { code: ERR_GENERIC, written: 0 };
290
+ const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
291
+ try {
292
+ const n = fs.writeSync(entry.fd, buf, 0, buf.length, Number(offset));
293
+ this.log("fileWrite", handle, "offset", offset, "len", buf.length, "wrote", n);
294
+ return { code: OK, written: n };
295
+ } catch (err: any) {
296
+ this.log("fileWrite FAIL", handle, err.code);
297
+ return { code: ERR_GENERIC, written: 0 };
298
+ }
299
+ }
300
+
301
+ fileStat(handle: number) {
302
+ const entry = this.files.get(handle);
303
+ if (!entry) return { code: ERR_GENERIC };
304
+ try {
305
+ const s = fs.fstatSync(entry.fd);
306
+ this.log("fileStat", handle, "size", s.size, "kind", this.statToResult(s).kind);
307
+ return { code: OK, stat: this.statToResult(s) };
308
+ } catch (err: any) {
309
+ this.log("fileStat FAIL", handle, err.code);
310
+ return { code: ERR_GENERIC };
311
+ }
312
+ }
313
+
314
+ fileLength(handle: number) {
315
+ const entry = this.files.get(handle);
316
+ if (!entry) return { code: ERR_GENERIC, length: 0n };
317
+ try {
318
+ const s = fs.fstatSync(entry.fd);
319
+ this.log("fileLength", handle, "->", s.size);
320
+ return { code: OK, length: BigInt(s.size) };
321
+ } catch (err: any) {
322
+ this.log("fileLength FAIL", handle, err.code);
323
+ return { code: ERR_GENERIC, length: 0n };
324
+ }
325
+ }
326
+
327
+ fileSeekTo(handle: number, offset: bigint) {
328
+ const entry = this.files.get(handle);
329
+ if (!entry) return ERR_GENERIC;
330
+ entry.position = offset;
331
+ return OK;
332
+ }
333
+
334
+ fileSeekBy(handle: number, relative: bigint) {
335
+ const entry = this.files.get(handle);
336
+ if (!entry) return ERR_GENERIC;
337
+ entry.position += relative;
338
+ return OK;
339
+ }
340
+
341
+ fileLock(_handle: number, _kind: number) {
342
+ // Spike: no real flock. We're single-process, single-threaded — index/db
343
+ // mutexes inside one xit run are guaranteed serial via JS event loop.
344
+ return OK;
345
+ }
346
+
347
+ fileTryLock(_handle: number, _kind: number) {
348
+ return 1; // always acquired in spike mode
349
+ }
350
+
351
+ fileUnlock(_handle: number) {
352
+ /* no-op */
353
+ }
354
+
355
+ fileSync(handle: number) {
356
+ const entry = this.files.get(handle);
357
+ if (!entry) return ERR_GENERIC;
358
+ try {
359
+ fs.fsyncSync(entry.fd);
360
+ return OK;
361
+ } catch {
362
+ return ERR_GENERIC;
363
+ }
364
+ }
365
+
366
+ fileSetLength(handle: number, length: bigint) {
367
+ const entry = this.files.get(handle);
368
+ if (!entry) return ERR_GENERIC;
369
+ try {
370
+ fs.ftruncateSync(entry.fd, Number(length));
371
+ this.log("fileSetLength", handle, "->", length);
372
+ return OK;
373
+ } catch (err: any) {
374
+ this.log("fileSetLength FAIL", handle, err.code);
375
+ return ERR_GENERIC;
376
+ }
377
+ }
378
+
379
+ // ----- misc -----
380
+
381
+ nowNanos() {
382
+ return BigInt(Date.now()) * 1_000_000n;
383
+ }
384
+
385
+ random(buf: Uint8Array) {
386
+ crypto.randomFillSync(buf);
387
+ }
388
+ }
package/src/host.ts ADDED
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Host import surface. The wasm module declares 24 `host.*` extern functions;
3
+ * a Host implementation provides their JS-side bodies. This module defines:
4
+ *
5
+ * - The Host interface (signatures matching src/host_io.zig).
6
+ * - HostStat: byte-layout helper that mirrors the Zig HostStat extern struct.
7
+ * - hostImports(): converts a Host into the WebAssembly imports object the
8
+ * wasm module expects.
9
+ *
10
+ * The wasm passes pointers + lengths into linear memory; Host implementations
11
+ * take a `WebAssembly.Memory` reference at construction time so they can read
12
+ * and write into wasm memory directly.
13
+ */
14
+
15
+ /** Wire layout matches `HostStat` in src/host_io.zig — 48 bytes, 8-aligned. */
16
+ export const HOST_STAT_BYTES = 48;
17
+
18
+ export type Kind = "file" | "directory" | "sym_link" | "other";
19
+
20
+ export const KIND_FILE = 0;
21
+ export const KIND_DIR = 1;
22
+ export const KIND_SYMLINK = 2;
23
+ export const KIND_OTHER = 3;
24
+
25
+ export const LOCK_NONE = 0;
26
+ export const LOCK_SHARED = 1;
27
+ export const LOCK_EXCLUSIVE = 2;
28
+
29
+ /** Status codes the Host returns. 0 = success; non-zero values are passed
30
+ * through to xit and surface as errors there. Keeping this enum loose for
31
+ * now — finer-grained error mapping is a follow-up. */
32
+ export const OK = 0;
33
+ export const ERR_GENERIC = 1;
34
+ export const ERR_NOT_FOUND = 2;
35
+
36
+ export interface StatResult {
37
+ size: bigint;
38
+ mtimeNs: bigint;
39
+ atimeNs: bigint;
40
+ ctimeNs: bigint;
41
+ inode: bigint;
42
+ kind: number;
43
+ modeBits: number;
44
+ }
45
+
46
+ /**
47
+ * The behavior surface a Host has to provide. Each method takes already-decoded
48
+ * arguments (paths as strings, handles as numbers) — the wasm-import shims in
49
+ * hostImports() handle pointer-decoding before calling into the host.
50
+ *
51
+ * Handles are opaque i32s; the host owns the mapping (e.g. dir handle 0 is the
52
+ * "root" the host chose to expose to xit). xit itself only ever passes them
53
+ * back through the same set of host calls.
54
+ */
55
+ export interface Host {
56
+ // dir
57
+ dirCreateFile(dirHandle: number, path: string): { code: number; handle: number };
58
+ dirOpenFile(dirHandle: number, path: string): { code: number; handle: number };
59
+ dirCreateDir(dirHandle: number, path: string): number;
60
+ dirCreateDirPath(dirHandle: number, path: string): { code: number; existed: boolean };
61
+ dirCreateDirPathOpen(dirHandle: number, path: string): { code: number; handle: number };
62
+ dirOpenDir(dirHandle: number, path: string): { code: number; handle: number };
63
+ dirClose(handle: number): void;
64
+ dirDeleteFile(dirHandle: number, path: string): number;
65
+ dirRename(oldDir: number, oldPath: string, newDir: number, newPath: string): number;
66
+ dirStatFile(dirHandle: number, path: string): { code: number; stat?: StatResult };
67
+ dirAccess(dirHandle: number, path: string): number;
68
+ /** Returns:
69
+ * - { code: 1+, target } — symlink with target string
70
+ * - { code: 0 } — path exists but is not a symlink (xit needs the distinction)
71
+ * - { code: -1 } — path does not exist or unreadable
72
+ */
73
+ dirReadLink(dirHandle: number, path: string): { code: number; target?: string };
74
+ // file
75
+ fileClose(handle: number): void;
76
+ fileRead(handle: number, offset: bigint, len: number): { code: number; data: Uint8Array };
77
+ fileWrite(handle: number, offset: bigint, data: Uint8Array): { code: number; written: number };
78
+ fileWriteStream(handle: number, data: Uint8Array): { code: number; written: number };
79
+ fileReadStream(handle: number, len: number): { code: number; data: Uint8Array };
80
+ fileStat(handle: number): { code: number; stat?: StatResult };
81
+ fileLength(handle: number): { code: number; length: bigint };
82
+ fileSeekTo(handle: number, offset: bigint): number;
83
+ fileSeekBy(handle: number, relative: bigint): number;
84
+ fileLock(handle: number, kind: number): number;
85
+ /** Returns 1 if acquired, 0 if would-block, negative on error. */
86
+ fileTryLock(handle: number, kind: number): number;
87
+ fileUnlock(handle: number): void;
88
+ fileSync(handle: number): number;
89
+ fileSetLength(handle: number, length: bigint): number;
90
+ // misc
91
+ nowNanos(): bigint;
92
+ random(buf: Uint8Array): void;
93
+ }
94
+
95
+ /**
96
+ * Build the WebAssembly imports object expected by the wasm module. Pointer-
97
+ * decoding lives here (centralized) so individual Host implementations can stay
98
+ * focused on their own backing store.
99
+ */
100
+ export function hostImports(host: Host, getMemory: () => WebAssembly.Memory) {
101
+ const dec = new TextDecoder();
102
+ const memU8 = () => new Uint8Array(getMemory().buffer);
103
+ const memDV = () => new DataView(getMemory().buffer);
104
+
105
+ const readStr = (ptr: number, len: number) => dec.decode(memU8().slice(ptr, ptr + len));
106
+
107
+ const writeStat = (ptr: number, s: StatResult) => {
108
+ const dv = memDV();
109
+ dv.setBigUint64(ptr + 0, s.size, true);
110
+ dv.setBigInt64(ptr + 8, s.mtimeNs, true);
111
+ dv.setBigInt64(ptr + 16, s.atimeNs, true);
112
+ dv.setBigInt64(ptr + 24, s.ctimeNs, true);
113
+ dv.setBigUint64(ptr + 32, s.inode, true);
114
+ dv.setUint32(ptr + 40, s.kind, true);
115
+ dv.setUint32(ptr + 44, s.modeBits, true);
116
+ };
117
+
118
+ return {
119
+ host: {
120
+ host_dir_create_file: (dirH: number, p: number, plen: number, outH: number) => {
121
+ const r = host.dirCreateFile(dirH, readStr(p, plen));
122
+ if (r.code === OK) memDV().setInt32(outH, r.handle, true);
123
+ return r.code;
124
+ },
125
+ host_dir_open_file: (dirH: number, p: number, plen: number, outH: number) => {
126
+ const r = host.dirOpenFile(dirH, readStr(p, plen));
127
+ if (r.code === OK) memDV().setInt32(outH, r.handle, true);
128
+ return r.code;
129
+ },
130
+ host_dir_create_dir: (dirH: number, p: number, plen: number) =>
131
+ host.dirCreateDir(dirH, readStr(p, plen)),
132
+ host_dir_create_dir_path: (dirH: number, p: number, plen: number, outExisted: number) => {
133
+ const r = host.dirCreateDirPath(dirH, readStr(p, plen));
134
+ if (r.code === OK) memU8()[outExisted] = r.existed ? 1 : 0;
135
+ return r.code;
136
+ },
137
+ host_dir_create_dir_path_open: (dirH: number, p: number, plen: number, outH: number) => {
138
+ const r = host.dirCreateDirPathOpen(dirH, readStr(p, plen));
139
+ if (r.code === OK) memDV().setInt32(outH, r.handle, true);
140
+ return r.code;
141
+ },
142
+ host_dir_open_dir: (dirH: number, p: number, plen: number, outH: number) => {
143
+ const r = host.dirOpenDir(dirH, readStr(p, plen));
144
+ if (r.code === OK) memDV().setInt32(outH, r.handle, true);
145
+ return r.code;
146
+ },
147
+ host_dir_close: (h: number) => host.dirClose(h),
148
+ host_dir_delete_file: (dirH: number, p: number, plen: number) =>
149
+ host.dirDeleteFile(dirH, readStr(p, plen)),
150
+ host_dir_rename: (
151
+ oldDir: number,
152
+ op: number,
153
+ olen: number,
154
+ newDir: number,
155
+ np: number,
156
+ nlen: number,
157
+ ) => host.dirRename(oldDir, readStr(op, olen), newDir, readStr(np, nlen)),
158
+ host_dir_stat_file: (dirH: number, p: number, plen: number, outStat: number) => {
159
+ const r = host.dirStatFile(dirH, readStr(p, plen));
160
+ if (r.code === OK && r.stat) writeStat(outStat, r.stat);
161
+ return r.code;
162
+ },
163
+ host_dir_access: (dirH: number, p: number, plen: number) =>
164
+ host.dirAccess(dirH, readStr(p, plen)),
165
+ host_dir_read_link: (
166
+ dirH: number,
167
+ p: number,
168
+ plen: number,
169
+ bufPtr: number,
170
+ bufLen: number,
171
+ outSize: number,
172
+ ) => {
173
+ const r = host.dirReadLink(dirH, readStr(p, plen));
174
+ if (r.code > 0 && r.target !== undefined) {
175
+ const bytes = new TextEncoder().encode(r.target);
176
+ if (bytes.length > bufLen) return -1;
177
+ memU8().set(bytes, bufPtr);
178
+ memDV().setUint32(outSize, bytes.length, true);
179
+ return bytes.length;
180
+ }
181
+ return r.code;
182
+ },
183
+
184
+ host_file_close: (h: number) => host.fileClose(h),
185
+ host_file_read: (
186
+ h: number,
187
+ offset: bigint,
188
+ bufPtr: number,
189
+ bufLen: number,
190
+ outRead: number,
191
+ ) => {
192
+ const r = host.fileRead(h, offset, bufLen);
193
+ if (r.code === OK) {
194
+ memU8().set(r.data, bufPtr);
195
+ // out_read is `*usize` which on wasm32 is 4 bytes
196
+ memDV().setUint32(outRead, r.data.length, true);
197
+ }
198
+ return r.code;
199
+ },
200
+ host_file_write: (
201
+ h: number,
202
+ offset: bigint,
203
+ bufPtr: number,
204
+ bufLen: number,
205
+ outWritten: number,
206
+ ) => {
207
+ const data = memU8().slice(bufPtr, bufPtr + bufLen);
208
+ const r = host.fileWrite(h, offset, data);
209
+ if (r.code === OK) memDV().setUint32(outWritten, r.written, true);
210
+ return r.code;
211
+ },
212
+ host_file_stat: (h: number, outStat: number) => {
213
+ const r = host.fileStat(h);
214
+ if (r.code === OK && r.stat) writeStat(outStat, r.stat);
215
+ return r.code;
216
+ },
217
+ host_file_length: (h: number, outLen: number) => {
218
+ const r = host.fileLength(h);
219
+ if (r.code === OK) memDV().setBigUint64(outLen, r.length, true);
220
+ return r.code;
221
+ },
222
+ host_file_write_stream: (
223
+ h: number,
224
+ bufPtr: number,
225
+ bufLen: number,
226
+ outWritten: number,
227
+ ) => {
228
+ const data = memU8().slice(bufPtr, bufPtr + bufLen);
229
+ const r = host.fileWriteStream(h, data);
230
+ if (r.code === OK) memDV().setUint32(outWritten, r.written, true);
231
+ return r.code;
232
+ },
233
+ host_file_read_stream: (
234
+ h: number,
235
+ bufPtr: number,
236
+ bufLen: number,
237
+ outRead: number,
238
+ ) => {
239
+ const r = host.fileReadStream(h, bufLen);
240
+ if (r.code === OK) {
241
+ memU8().set(r.data, bufPtr);
242
+ memDV().setUint32(outRead, r.data.length, true);
243
+ }
244
+ return r.code;
245
+ },
246
+ host_file_seek_to: (h: number, offset: bigint) => host.fileSeekTo(h, offset),
247
+ host_file_seek_by: (h: number, relative: bigint) => host.fileSeekBy(h, relative),
248
+ host_file_lock: (h: number, kind: number) => host.fileLock(h, kind),
249
+ host_file_try_lock: (h: number, kind: number) => host.fileTryLock(h, kind),
250
+ host_file_unlock: (h: number) => host.fileUnlock(h),
251
+ host_file_sync: (h: number) => host.fileSync(h),
252
+ host_file_set_length: (h: number, len: bigint) => host.fileSetLength(h, len),
253
+
254
+ host_now_nanos: () => host.nowNanos(),
255
+ host_random: (bufPtr: number, bufLen: number) => {
256
+ const buf = new Uint8Array(bufLen);
257
+ host.random(buf);
258
+ memU8().set(buf, bufPtr);
259
+ },
260
+ },
261
+ };
262
+ }