space-data-module-sdk 0.5.18 → 0.5.19

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,275 @@
1
+ /**
2
+ * Browser-compatible WASI preview1 shim.
3
+ *
4
+ * Provides the wasi_snapshot_preview1 import namespace so that standalone
5
+ * WASI modules (the same .wasm that runs in WasmEdge) can be instantiated
6
+ * directly in the browser via WebAssembly.instantiate().
7
+ *
8
+ * Covers the 11 imports observed across all SDN plugin standalone builds:
9
+ * clock_time_get, fd_write, fd_read, fd_close, fd_seek, fd_fdstat_get,
10
+ * environ_sizes_get, environ_get, proc_exit, args_get, args_sizes_get
11
+ */
12
+
13
+ const ERRNO_SUCCESS = 0;
14
+ const ERRNO_BADF = 8;
15
+ const ERRNO_INVAL = 28;
16
+ const ERRNO_NOSYS = 52;
17
+ const ERRNO_SPIPE = 70;
18
+
19
+ const CLOCKID_REALTIME = 0;
20
+ const CLOCKID_MONOTONIC = 1;
21
+
22
+ const FILETYPE_CHARACTER_DEVICE = 2;
23
+
24
+ export class WasiExitError extends Error {
25
+ constructor(code) {
26
+ super(`WASI exit with code ${code}`);
27
+ this.name = "WasiExitError";
28
+ this.code = code;
29
+ }
30
+ }
31
+
32
+ export function createBrowserWasiShim(options = {}) {
33
+ const args = options.args ?? [];
34
+ const env = options.env ?? {};
35
+ const stdinBytes = new Uint8Array(options.stdinBytes ?? []);
36
+ const logOutput = options.logOutput === true;
37
+ const performanceApi = options.performance ?? globalThis.performance ?? {
38
+ now: () => Date.now(),
39
+ timeOrigin: 0,
40
+ };
41
+ const stdoutChunks = [];
42
+ const stderrChunks = [];
43
+ let stdinOffset = 0;
44
+
45
+ let memory = null;
46
+
47
+ function setMemory(mem) {
48
+ memory = mem;
49
+ }
50
+
51
+ function getMemory() {
52
+ return memory;
53
+ }
54
+
55
+ function mem8() {
56
+ return new Uint8Array(memory.buffer);
57
+ }
58
+ function view() {
59
+ return new DataView(memory.buffer);
60
+ }
61
+
62
+ // --- Environment encoding helpers ---
63
+
64
+ const envEntries = Object.entries(env).map(([k, v]) => `${k}=${v}`);
65
+ const encodedEnvEntries = envEntries.map((e) => new TextEncoder().encode(e));
66
+ const encodedArgs = args.map((a) => new TextEncoder().encode(a));
67
+
68
+ // --- WASI functions ---
69
+
70
+ function clock_time_get(clockId, _precisionBigInt, resultPtr) {
71
+ let nanos;
72
+ if (clockId === CLOCKID_REALTIME) {
73
+ nanos = BigInt(
74
+ Math.round((performanceApi.timeOrigin + performanceApi.now()) * 1e6),
75
+ );
76
+ } else if (clockId === CLOCKID_MONOTONIC) {
77
+ nanos = BigInt(Math.round(performanceApi.now() * 1e6));
78
+ } else {
79
+ return ERRNO_INVAL;
80
+ }
81
+ view().setBigUint64(resultPtr, nanos, true);
82
+ return ERRNO_SUCCESS;
83
+ }
84
+
85
+ function fd_write(fd, iovsPtr, iovsLen, nwrittenPtr) {
86
+ if (fd !== 1 && fd !== 2) return ERRNO_BADF;
87
+
88
+ const target = fd === 1 ? stdoutChunks : stderrChunks;
89
+ let totalWritten = 0;
90
+ const dv = view();
91
+ const bytes = mem8();
92
+
93
+ for (let i = 0; i < iovsLen; i++) {
94
+ const base = iovsPtr + i * 8;
95
+ const ptr = dv.getUint32(base, true);
96
+ const len = dv.getUint32(base + 4, true);
97
+ target.push(bytes.slice(ptr, ptr + len));
98
+ totalWritten += len;
99
+ }
100
+
101
+ dv.setUint32(nwrittenPtr, totalWritten, true);
102
+ return ERRNO_SUCCESS;
103
+ }
104
+
105
+ function fd_read(fd, iovsPtr, iovsLen, nreadPtr) {
106
+ if (fd !== 0) {
107
+ view().setUint32(nreadPtr, 0, true);
108
+ return ERRNO_BADF;
109
+ }
110
+
111
+ const dv = view();
112
+ const bytes = mem8();
113
+ let totalRead = 0;
114
+
115
+ for (let i = 0; i < iovsLen; i += 1) {
116
+ if (stdinOffset >= stdinBytes.length) {
117
+ break;
118
+ }
119
+ const base = iovsPtr + i * 8;
120
+ const ptr = dv.getUint32(base, true);
121
+ const len = dv.getUint32(base + 4, true);
122
+ const remaining = stdinBytes.length - stdinOffset;
123
+ const count = Math.min(len, remaining);
124
+ bytes.set(stdinBytes.subarray(stdinOffset, stdinOffset + count), ptr);
125
+ stdinOffset += count;
126
+ totalRead += count;
127
+ }
128
+
129
+ dv.setUint32(nreadPtr, totalRead, true);
130
+ return ERRNO_SUCCESS;
131
+ }
132
+
133
+ function fd_close(fd) {
134
+ if (fd <= 2) return ERRNO_SUCCESS;
135
+ return ERRNO_BADF;
136
+ }
137
+
138
+ function fd_seek(fd, _offsetLo, _whence, _resultPtr) {
139
+ if (fd <= 2) return ERRNO_SPIPE;
140
+ return ERRNO_BADF;
141
+ }
142
+
143
+ function fd_fdstat_get(fd, bufPtr) {
144
+ if (fd > 2) return ERRNO_BADF;
145
+ const dv = view();
146
+ // filetype (u8) at offset 0 — CHARACTER_DEVICE
147
+ dv.setUint8(bufPtr, FILETYPE_CHARACTER_DEVICE);
148
+ // fdflags (u16) at offset 2
149
+ dv.setUint16(bufPtr + 2, 0, true);
150
+ // rights_base (u64) at offset 8
151
+ dv.setBigUint64(bufPtr + 8, 0n, true);
152
+ // rights_inheriting (u64) at offset 16
153
+ dv.setBigUint64(bufPtr + 16, 0n, true);
154
+ return ERRNO_SUCCESS;
155
+ }
156
+
157
+ function environ_sizes_get(countPtr, bufSizePtr) {
158
+ const dv = view();
159
+ dv.setUint32(countPtr, encodedEnvEntries.length, true);
160
+ let totalSize = 0;
161
+ for (const entry of encodedEnvEntries) {
162
+ totalSize += entry.length + 1; // null terminator
163
+ }
164
+ dv.setUint32(bufSizePtr, totalSize, true);
165
+ return ERRNO_SUCCESS;
166
+ }
167
+
168
+ function environ_get(environPtr, environBufPtr) {
169
+ const dv = view();
170
+ const bytes = mem8();
171
+ let bufOffset = environBufPtr;
172
+
173
+ for (let i = 0; i < encodedEnvEntries.length; i++) {
174
+ dv.setUint32(environPtr + i * 4, bufOffset, true);
175
+ bytes.set(encodedEnvEntries[i], bufOffset);
176
+ bufOffset += encodedEnvEntries[i].length;
177
+ bytes[bufOffset] = 0; // null terminator
178
+ bufOffset += 1;
179
+ }
180
+ return ERRNO_SUCCESS;
181
+ }
182
+
183
+ function args_sizes_get(argcPtr, argvBufSizePtr) {
184
+ const dv = view();
185
+ dv.setUint32(argcPtr, encodedArgs.length, true);
186
+ let totalSize = 0;
187
+ for (const arg of encodedArgs) {
188
+ totalSize += arg.length + 1;
189
+ }
190
+ dv.setUint32(argvBufSizePtr, totalSize, true);
191
+ return ERRNO_SUCCESS;
192
+ }
193
+
194
+ function args_get(argvPtr, argvBufPtr) {
195
+ const dv = view();
196
+ const bytes = mem8();
197
+ let bufOffset = argvBufPtr;
198
+
199
+ for (let i = 0; i < encodedArgs.length; i++) {
200
+ dv.setUint32(argvPtr + i * 4, bufOffset, true);
201
+ bytes.set(encodedArgs[i], bufOffset);
202
+ bufOffset += encodedArgs[i].length;
203
+ bytes[bufOffset] = 0;
204
+ bufOffset += 1;
205
+ }
206
+ return ERRNO_SUCCESS;
207
+ }
208
+
209
+ function proc_exit(code) {
210
+ if (logOutput) {
211
+ flushOutput();
212
+ }
213
+ throw new WasiExitError(code);
214
+ }
215
+
216
+ // --- Output helpers ---
217
+
218
+ function flushOutput() {
219
+ if (stdoutChunks.length > 0) {
220
+ const combined = concatChunks(stdoutChunks);
221
+ if (logOutput) {
222
+ const text = new TextDecoder().decode(combined);
223
+ if (text) console.log(text);
224
+ }
225
+ stdoutChunks.length = 0;
226
+ }
227
+ if (stderrChunks.length > 0) {
228
+ const combined = concatChunks(stderrChunks);
229
+ if (logOutput) {
230
+ const text = new TextDecoder().decode(combined);
231
+ if (text) console.warn(text);
232
+ }
233
+ stderrChunks.length = 0;
234
+ }
235
+ }
236
+
237
+ function concatChunks(chunks) {
238
+ let totalLen = 0;
239
+ for (const chunk of chunks) totalLen += chunk.length;
240
+ const result = new Uint8Array(totalLen);
241
+ let offset = 0;
242
+ for (const chunk of chunks) {
243
+ result.set(chunk, offset);
244
+ offset += chunk.length;
245
+ }
246
+ return result;
247
+ }
248
+
249
+ return {
250
+ imports: {
251
+ wasi_snapshot_preview1: {
252
+ clock_time_get,
253
+ fd_write,
254
+ fd_read,
255
+ fd_close,
256
+ fd_seek,
257
+ fd_fdstat_get,
258
+ environ_sizes_get,
259
+ environ_get,
260
+ args_sizes_get,
261
+ args_get,
262
+ proc_exit,
263
+ },
264
+ },
265
+ setMemory,
266
+ getMemory,
267
+ flushOutput,
268
+ get stdout() {
269
+ return concatChunks(stdoutChunks);
270
+ },
271
+ get stderr() {
272
+ return concatChunks(stderrChunks);
273
+ },
274
+ };
275
+ }
package/src/index.d.ts CHANGED
@@ -1037,6 +1037,62 @@ export interface HostcallBridge {
1037
1037
  getLastResponseJson(): unknown;
1038
1038
  }
1039
1039
 
1040
+ export interface BrowserFilesystemShim {
1041
+ filesystemRoot?: string;
1042
+ resolvePath(path?: string): string;
1043
+ readFile(
1044
+ path: string,
1045
+ options?: { encoding?: string | null },
1046
+ ): Promise<string | Uint8Array>;
1047
+ writeFile(
1048
+ path: string,
1049
+ value: Uint8Array | ArrayBuffer | ArrayBufferView | string,
1050
+ options?: { encoding?: string | null },
1051
+ ): Promise<{ path: string }>;
1052
+ appendFile(
1053
+ path: string,
1054
+ value: Uint8Array | ArrayBuffer | ArrayBufferView | string,
1055
+ options?: { encoding?: string | null },
1056
+ ): Promise<{ path: string }>;
1057
+ deleteFile(path: string): Promise<{ path: string }>;
1058
+ mkdir(
1059
+ path: string,
1060
+ options?: { recursive?: boolean },
1061
+ ): Promise<{ path: string }>;
1062
+ readdir(path?: string): Promise<NodeHostFilesystemEntry[]>;
1063
+ stat(path: string): Promise<NodeHostFilesystemStat>;
1064
+ rename(
1065
+ fromPath: string,
1066
+ toPath: string,
1067
+ ): Promise<{ from: string; to: string }>;
1068
+ }
1069
+
1070
+ export interface BrowserEdgeShims {
1071
+ fetch?: (...args: any[]) => Promise<any>;
1072
+ WebSocket?: any;
1073
+ crypto?: any;
1074
+ performance?: {
1075
+ now(): number;
1076
+ timeOrigin: number;
1077
+ };
1078
+ filesystem?: BrowserFilesystemShim;
1079
+ }
1080
+
1081
+ export interface BrowserHostOptions {
1082
+ capabilities?: string[];
1083
+ edgeShims?: BrowserEdgeShims;
1084
+ contextStore?: Map<string, Map<string, unknown>>;
1085
+ fetch?: (...args: any[]) => Promise<any>;
1086
+ WebSocket?: any;
1087
+ crypto?: any;
1088
+ performance?: {
1089
+ now(): number;
1090
+ timeOrigin: number;
1091
+ };
1092
+ filesystem?: BrowserFilesystemShim;
1093
+ filesystemRoot?: string;
1094
+ }
1095
+
1040
1096
  export interface NodeHostOptions {
1041
1097
  manifest?: PluginManifest;
1042
1098
  grantedCapabilities?: string[];
@@ -1072,6 +1128,12 @@ export class HostFilesystemScopeError extends Error {
1072
1128
  filesystemRoot: string | null;
1073
1129
  }
1074
1130
 
1131
+ export class BrowserFilesystemScopeError extends Error {
1132
+ code: string;
1133
+ requestedPath: string | null;
1134
+ filesystemRoot: string | null;
1135
+ }
1136
+
1075
1137
  export class NodeHost {
1076
1138
  runtimeTarget: string;
1077
1139
  filesystemRoot: string;
@@ -1322,14 +1384,93 @@ export class NodeHost {
1322
1384
  invoke(operation: string, params?: Record<string, any>): Promise<any>;
1323
1385
  }
1324
1386
 
1387
+ export class BrowserHost {
1388
+ runtimeTarget: string;
1389
+ filesystemRoot: string;
1390
+ clock: {
1391
+ now(): number;
1392
+ monotonicNow(): number;
1393
+ nowIso(): string;
1394
+ };
1395
+ random: {
1396
+ bytes(length: number): Uint8Array;
1397
+ };
1398
+ timers: {
1399
+ delay(ms: number): Promise<void>;
1400
+ };
1401
+ schedule: {
1402
+ parse(expression: string): CronSchedule;
1403
+ matches(expression: string | CronSchedule, date?: Date | number | string): boolean;
1404
+ next(expression: string | CronSchedule, from?: Date | number | string): Date;
1405
+ };
1406
+ http: {
1407
+ request<TBody = unknown>(options: NodeHostHttpRequest): Promise<NodeHostHttpResponse<TBody>>;
1408
+ };
1409
+ websocket: {
1410
+ exchange<TBody = unknown>(options: {
1411
+ url: string;
1412
+ protocols?: string | string[];
1413
+ message?: Uint8Array | ArrayBuffer | ArrayBufferView | string | null;
1414
+ responseType?: "bytes" | "utf8" | "json";
1415
+ timeoutMs?: number;
1416
+ expectResponse?: boolean;
1417
+ }): Promise<NodeHostWebSocketResponse<TBody>>;
1418
+ };
1419
+ context: {
1420
+ get(scope: string, key: string): unknown;
1421
+ set(scope: string, key: string, value: unknown): void;
1422
+ delete(scope: string, key: string): void;
1423
+ listKeys(scope: string): string[];
1424
+ listScopes(): string[];
1425
+ };
1426
+ crypto: {
1427
+ sha256(data: Uint8Array | ArrayBuffer | ArrayBufferView | string): Promise<Uint8Array>;
1428
+ sha512(data: Uint8Array | ArrayBuffer | ArrayBufferView | string): Promise<Uint8Array>;
1429
+ aesGcmEncrypt(options: {
1430
+ key: Uint8Array | ArrayBuffer | ArrayBufferView;
1431
+ plaintext: Uint8Array | ArrayBuffer | ArrayBufferView;
1432
+ iv?: Uint8Array | ArrayBuffer | ArrayBufferView;
1433
+ }): Promise<{ ciphertext: Uint8Array; iv: Uint8Array | ArrayBuffer | ArrayBufferView }>;
1434
+ aesGcmDecrypt(options: {
1435
+ key: Uint8Array | ArrayBuffer | ArrayBufferView;
1436
+ ciphertext: Uint8Array | ArrayBuffer | ArrayBufferView;
1437
+ iv: Uint8Array | ArrayBuffer | ArrayBufferView;
1438
+ }): Promise<Uint8Array>;
1439
+ };
1440
+ filesystem: BrowserFilesystemShim;
1441
+ constructor(options?: BrowserHostOptions);
1442
+ listCapabilities(): string[];
1443
+ listSupportedCapabilities(): string[];
1444
+ listOperations(): string[];
1445
+ hasCapability(capability: string): boolean;
1446
+ assertCapability(capability: string, operation?: string | null): void;
1447
+ }
1448
+
1325
1449
  export const NodeHostSupportedCapabilities: readonly string[];
1326
1450
  export const NodeHostSupportedOperations: readonly string[];
1451
+ export const BrowserHostSupportedCapabilities: readonly string[];
1452
+ export const BrowserHostSupportedOperations: readonly string[];
1327
1453
  export const DEFAULT_HOSTCALL_IMPORT_MODULE: string;
1328
1454
  export const HOSTCALL_STATUS_OK: number;
1329
1455
  export const HOSTCALL_STATUS_ERROR: number;
1330
1456
  export const NodeHostSyncHostcallOperations: readonly string[];
1331
1457
 
1332
1458
  export function createNodeHost(options?: NodeHostOptions): NodeHost;
1459
+ export function createBrowserHost(options?: BrowserHostOptions): BrowserHost;
1460
+ export function createMemoryFilesystemEdgeShim(options?: {
1461
+ filesystemRoot?: string;
1462
+ }): BrowserFilesystemShim;
1463
+ export function createBrowserEdgeShims(options?: {
1464
+ fetch?: (...args: any[]) => Promise<any>;
1465
+ WebSocket?: any;
1466
+ crypto?: any;
1467
+ performance?: {
1468
+ now(): number;
1469
+ timeOrigin: number;
1470
+ };
1471
+ filesystem?: BrowserFilesystemShim;
1472
+ filesystemRoot?: string;
1473
+ }): BrowserEdgeShims;
1333
1474
  export function parseCronExpression(expression: string): CronSchedule;
1334
1475
  export function matchesCronExpression(
1335
1476
  expression: string | CronSchedule,
@@ -1361,6 +1502,90 @@ export function createNodeHostSyncHostcallBridge(options: {
1361
1502
  maxRequestBytes?: number;
1362
1503
  maxResponseBytes?: number;
1363
1504
  }): HostcallBridge;
1505
+ export class WasiExitError extends Error {
1506
+ code: number;
1507
+ }
1508
+ export interface BrowserWasiShim {
1509
+ imports: Record<string, Record<string, (...args: number[]) => number>>;
1510
+ setMemory(mem: { buffer: ArrayBuffer | SharedArrayBuffer }): void;
1511
+ getMemory(): { buffer: ArrayBuffer | SharedArrayBuffer } | null;
1512
+ flushOutput(): void;
1513
+ stdout: Uint8Array;
1514
+ stderr: Uint8Array;
1515
+ }
1516
+ export function createBrowserWasiShim(options?: {
1517
+ args?: string[];
1518
+ env?: Record<string, string>;
1519
+ stdinBytes?: Uint8Array | ArrayBuffer | ArrayBufferView;
1520
+ logOutput?: boolean;
1521
+ performance?: {
1522
+ now(): number;
1523
+ timeOrigin: number;
1524
+ };
1525
+ }): BrowserWasiShim;
1526
+ export interface BrowserModuleHarness {
1527
+ runtime: {
1528
+ kind: "browser";
1529
+ profile: string;
1530
+ surface: string;
1531
+ };
1532
+ instance: WebAssembly.Instance;
1533
+ module: WebAssembly.Module;
1534
+ host: BrowserHost;
1535
+ bridge: HostcallBridge | null;
1536
+ wasi: BrowserWasiShim;
1537
+ invokeRaw(
1538
+ requestBytes: Uint8Array | ArrayBuffer | ArrayBufferView,
1539
+ ): Promise<Uint8Array>;
1540
+ invoke(request: {
1541
+ methodId?: string | null;
1542
+ inputs?: HarnessInputFrame[];
1543
+ }): Promise<{
1544
+ statusCode: number;
1545
+ errorCode?: string | null;
1546
+ errorMessage?: string | null;
1547
+ outputs: HarnessInputFrame[];
1548
+ }>;
1549
+ readManifest(): Uint8Array | null;
1550
+ destroy(): void;
1551
+ }
1552
+ export function detectArtifactProfile(wasmModule: WebAssembly.Module): string;
1553
+ export function createBrowserModuleHarness(options?: {
1554
+ wasmSource: Uint8Array | ArrayBuffer | string | WebAssembly.Module | unknown;
1555
+ host?: BrowserHost;
1556
+ hostOptions?: BrowserHostOptions;
1557
+ args?: string[];
1558
+ env?: Record<string, string>;
1559
+ surface?: "direct" | "command";
1560
+ performance?: {
1561
+ now(): number;
1562
+ timeOrigin: number;
1563
+ };
1564
+ logOutput?: boolean;
1565
+ }): Promise<BrowserModuleHarness>;
1566
+ export function loadModule(options?: {
1567
+ wasmSource: Uint8Array | ArrayBuffer | string | WebAssembly.Module | unknown;
1568
+ host?: BrowserHost;
1569
+ hostOptions?: BrowserHostOptions;
1570
+ args?: string[];
1571
+ env?: Record<string, string | undefined>;
1572
+ surface?: "direct" | "command";
1573
+ runtimeKind?: "wasmedge" | "process";
1574
+ wasmEdgeBinary?: string;
1575
+ wasmEdgeRunnerBinary?: string;
1576
+ enableThreads?: boolean;
1577
+ hostProfile?: "runtime-host";
1578
+ modules?: RuntimeHostTestModuleDefinition[];
1579
+ defaultModuleId?: string;
1580
+ metadata?: unknown;
1581
+ command?: string;
1582
+ cwd?: string;
1583
+ }): Promise<BrowserModuleHarness | ModuleHarness>;
1584
+ export function inspectModule(source: Uint8Array | ArrayBuffer | WebAssembly.Module): Promise<{
1585
+ profile: string;
1586
+ exports: string[];
1587
+ imports: WebAssembly.ModuleImportDescriptor[];
1588
+ }>;
1364
1589
 
1365
1590
  // --- Runtime constants ---
1366
1591