space-data-module-sdk 0.5.22 → 0.6.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.
- package/README.md +9 -2
- package/package.json +2 -2
- package/src/host/abi.js +31 -3
- package/src/host/browserEdgeShims.js +4 -0
- package/src/host/browserHost.js +236 -2
- package/src/host/isomorphicLoader.js +43 -20
- package/src/host/nodeHost.js +179 -2
- package/src/runtime-host/index.js +56 -0
- package/src/testing/browserModuleHarness.js +8 -2
- package/src/testing/index.js +38 -1
package/README.md
CHANGED
|
@@ -176,8 +176,15 @@ This repo currently includes:
|
|
|
176
176
|
- non-JS bundle reference clients in
|
|
177
177
|
[`examples/single-file-bundle/go`](./examples/single-file-bundle/go) and
|
|
178
178
|
[`examples/single-file-bundle/python`](./examples/single-file-bundle/python)
|
|
179
|
-
-
|
|
180
|
-
|
|
179
|
+
- reference Node and browser hosts plus the legacy sync `sdn_host` bridge for
|
|
180
|
+
sync-safe guest hostcalls
|
|
181
|
+
|
|
182
|
+
The current host/runtime contract is two-tiered:
|
|
183
|
+
|
|
184
|
+
- raw guest `sdn_host` imports stay sync-only
|
|
185
|
+
- generic async capabilities such as filesystem, network, IPFS, and protocol
|
|
186
|
+
adapters are exposed through `NodeHost`, `BrowserHost`, and the browser/module
|
|
187
|
+
harness APIs
|
|
181
188
|
|
|
182
189
|
## Runtime Targets
|
|
183
190
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
],
|
|
52
52
|
"scripts": {
|
|
53
53
|
"test": "node --test",
|
|
54
|
-
"test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js",
|
|
54
|
+
"test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js test/browser-host.test.js test/browser-module-harness.test.js test/testing-harness.test.js",
|
|
55
55
|
"test:runtime-matrix": "SPACE_DATA_MODULE_SDK_ENABLE_RUNTIME_MATRIX=1 node --test test/runtime-matrix.test.js",
|
|
56
56
|
"test:stream-ingest": "node --test test/runtime-host-stream-ingest.test.js",
|
|
57
57
|
"test:module-stream": "node --test test/module-flatbuffer-stream-pump.test.js",
|
package/src/host/abi.js
CHANGED
|
@@ -7,7 +7,7 @@ export const DEFAULT_HOSTCALL_IMPORT_MODULE = "sdn_host";
|
|
|
7
7
|
export const HOSTCALL_STATUS_OK = 0;
|
|
8
8
|
export const HOSTCALL_STATUS_ERROR = 1;
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const SyncHostcallOperations = Object.freeze([
|
|
11
11
|
"host.runtimeTarget",
|
|
12
12
|
"host.listCapabilities",
|
|
13
13
|
"host.listSupportedCapabilities",
|
|
@@ -22,6 +22,7 @@ export const NodeHostSyncHostcallOperations = Object.freeze([
|
|
|
22
22
|
"schedule.next",
|
|
23
23
|
"filesystem.resolvePath",
|
|
24
24
|
]);
|
|
25
|
+
export const NodeHostSyncHostcallOperations = SyncHostcallOperations;
|
|
25
26
|
|
|
26
27
|
function assertNonEmptyString(value, label) {
|
|
27
28
|
const normalized = String(value ?? "").trim();
|
|
@@ -149,7 +150,7 @@ function encodeHostcallValue(value) {
|
|
|
149
150
|
return value;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
export function
|
|
153
|
+
export function dispatchHostSyncOperation(host, operation, params = null) {
|
|
153
154
|
const normalized = assertNonEmptyString(operation, "Hostcall operation");
|
|
154
155
|
switch (normalized) {
|
|
155
156
|
case "host.runtimeTarget":
|
|
@@ -185,12 +186,39 @@ export function dispatchNodeHostSyncOperation(host, operation, params = null) {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
export function dispatchNodeHostSyncOperation(host, operation, params = null) {
|
|
190
|
+
return dispatchHostSyncOperation(host, operation, params);
|
|
191
|
+
}
|
|
192
|
+
|
|
188
193
|
export function createNodeHostSyncDispatcher(host) {
|
|
189
194
|
if (!host || typeof host !== "object") {
|
|
190
195
|
throw new TypeError("createNodeHostSyncDispatcher requires a host object.");
|
|
191
196
|
}
|
|
192
197
|
return (operation, params = null) =>
|
|
193
|
-
|
|
198
|
+
dispatchHostSyncOperation(host, operation, params);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createHostSyncDispatcher(host) {
|
|
202
|
+
return createNodeHostSyncDispatcher(host);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function dispatchHostOperation(host, operation, params = null) {
|
|
206
|
+
if (!host || typeof host !== "object") {
|
|
207
|
+
throw new TypeError("dispatchHostOperation requires a host object.");
|
|
208
|
+
}
|
|
209
|
+
const normalized = assertNonEmptyString(operation, "Host operation");
|
|
210
|
+
if (typeof host.invoke === "function") {
|
|
211
|
+
return host.invoke(normalized, params);
|
|
212
|
+
}
|
|
213
|
+
return dispatchHostSyncOperation(host, normalized, params);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function createAsyncHostDispatcher(host) {
|
|
217
|
+
if (!host || typeof host !== "object") {
|
|
218
|
+
throw new TypeError("createAsyncHostDispatcher requires a host object.");
|
|
219
|
+
}
|
|
220
|
+
return (operation, params = null) =>
|
|
221
|
+
dispatchHostOperation(host, operation, params);
|
|
194
222
|
}
|
|
195
223
|
|
|
196
224
|
export function createJsonHostcallBridge(options = {}) {
|
|
@@ -346,6 +346,10 @@ export function createBrowserEdgeShims(options = {}) {
|
|
|
346
346
|
WebSocket: options.WebSocket ?? globalThis.WebSocket,
|
|
347
347
|
crypto: options.crypto ?? globalThis.crypto,
|
|
348
348
|
performance: options.performance ?? globalThis.performance,
|
|
349
|
+
network: options.network ?? null,
|
|
350
|
+
ipfs: options.ipfs ?? null,
|
|
351
|
+
protocolHandle: options.protocolHandle ?? null,
|
|
352
|
+
protocolDial: options.protocolDial ?? null,
|
|
349
353
|
filesystem:
|
|
350
354
|
options.filesystem ??
|
|
351
355
|
createMemoryFilesystemEdgeShim({
|
package/src/host/browserHost.js
CHANGED
|
@@ -20,7 +20,11 @@ export const BrowserHostSupportedCapabilities = Object.freeze([
|
|
|
20
20
|
"schedule_cron",
|
|
21
21
|
"http",
|
|
22
22
|
"websocket",
|
|
23
|
+
"network",
|
|
23
24
|
"filesystem",
|
|
25
|
+
"ipfs",
|
|
26
|
+
"protocol_handle",
|
|
27
|
+
"protocol_dial",
|
|
24
28
|
"context_read",
|
|
25
29
|
"context_write",
|
|
26
30
|
"crypto_hash",
|
|
@@ -39,11 +43,13 @@ export const BrowserHostSupportedOperations = Object.freeze([
|
|
|
39
43
|
"clock.monotonicNow",
|
|
40
44
|
"clock.nowIso",
|
|
41
45
|
"random.bytes",
|
|
46
|
+
"timers.delay",
|
|
42
47
|
"schedule.parse",
|
|
43
48
|
"schedule.matches",
|
|
44
49
|
"schedule.next",
|
|
45
50
|
"http.request",
|
|
46
51
|
"websocket.exchange",
|
|
52
|
+
"network.request",
|
|
47
53
|
"filesystem.resolvePath",
|
|
48
54
|
"filesystem.readFile",
|
|
49
55
|
"filesystem.writeFile",
|
|
@@ -53,6 +59,10 @@ export const BrowserHostSupportedOperations = Object.freeze([
|
|
|
53
59
|
"filesystem.readdir",
|
|
54
60
|
"filesystem.stat",
|
|
55
61
|
"filesystem.rename",
|
|
62
|
+
"ipfs.invoke",
|
|
63
|
+
"protocol_handle.register",
|
|
64
|
+
"protocol_handle.unregister",
|
|
65
|
+
"protocol_dial.dial",
|
|
56
66
|
"context.get",
|
|
57
67
|
"context.set",
|
|
58
68
|
"context.delete",
|
|
@@ -73,6 +83,46 @@ export class BrowserHostCapabilityError extends Error {
|
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
function normalizeBrowserNetworkTransport(params = {}) {
|
|
87
|
+
const explicit = String(
|
|
88
|
+
params.transport ?? params.kind ?? params.request?.transport ?? "",
|
|
89
|
+
)
|
|
90
|
+
.trim()
|
|
91
|
+
.toLowerCase();
|
|
92
|
+
if (explicit) {
|
|
93
|
+
return explicit;
|
|
94
|
+
}
|
|
95
|
+
const candidateUrl = params.url ?? params.request?.url ?? null;
|
|
96
|
+
if (candidateUrl) {
|
|
97
|
+
const protocol = new URL(candidateUrl, "https://browser-host.invalid")
|
|
98
|
+
.protocol.toLowerCase();
|
|
99
|
+
if (protocol === "http:" || protocol === "https:") {
|
|
100
|
+
return "http";
|
|
101
|
+
}
|
|
102
|
+
if (protocol === "ws:" || protocol === "wss:") {
|
|
103
|
+
return "websocket";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw new Error(
|
|
107
|
+
'network.request requires a transport value such as "http" or "websocket".',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function invokeAdapterMethod(adapter, methodName, params, label) {
|
|
112
|
+
if (!adapter || typeof adapter !== "object") {
|
|
113
|
+
throw new Error(`${label} adapter is not configured for this host.`);
|
|
114
|
+
}
|
|
115
|
+
if (typeof adapter[methodName] === "function") {
|
|
116
|
+
return adapter[methodName](params);
|
|
117
|
+
}
|
|
118
|
+
if (typeof adapter.invoke === "function") {
|
|
119
|
+
return adapter.invoke(methodName, params);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(
|
|
122
|
+
`${label} adapter does not implement "${methodName}" or invoke().`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
76
126
|
export class BrowserHost {
|
|
77
127
|
constructor(options = {}) {
|
|
78
128
|
this.runtimeTarget = RuntimeTarget.BROWSER;
|
|
@@ -86,6 +136,11 @@ export class BrowserHost {
|
|
|
86
136
|
WebSocket: options.WebSocket ?? options.edgeShims?.WebSocket,
|
|
87
137
|
crypto: options.crypto ?? options.edgeShims?.crypto,
|
|
88
138
|
performance: options.performance ?? options.edgeShims?.performance,
|
|
139
|
+
network: options.network ?? options.edgeShims?.network,
|
|
140
|
+
ipfs: options.ipfs ?? options.edgeShims?.ipfs,
|
|
141
|
+
protocolHandle:
|
|
142
|
+
options.protocolHandle ?? options.edgeShims?.protocolHandle,
|
|
143
|
+
protocolDial: options.protocolDial ?? options.edgeShims?.protocolDial,
|
|
89
144
|
filesystem: options.filesystem ?? options.edgeShims?.filesystem,
|
|
90
145
|
filesystemRoot: options.filesystemRoot ?? options.edgeShims?.filesystemRoot,
|
|
91
146
|
});
|
|
@@ -97,6 +152,10 @@ export class BrowserHost {
|
|
|
97
152
|
const fetchImpl = edgeShims.fetch;
|
|
98
153
|
const WebSocketImpl = edgeShims.WebSocket;
|
|
99
154
|
const filesystem = edgeShims.filesystem;
|
|
155
|
+
const networkAdapter = edgeShims.network;
|
|
156
|
+
const ipfsAdapter = edgeShims.ipfs;
|
|
157
|
+
const protocolHandleAdapter = edgeShims.protocolHandle;
|
|
158
|
+
const protocolDialAdapter = edgeShims.protocolDial;
|
|
100
159
|
|
|
101
160
|
this._grantedCapabilities = granted;
|
|
102
161
|
this._contextStore = options.contextStore ?? new Map();
|
|
@@ -239,6 +298,73 @@ export class BrowserHost {
|
|
|
239
298
|
},
|
|
240
299
|
});
|
|
241
300
|
|
|
301
|
+
this.network = Object.freeze({
|
|
302
|
+
request: async (params = {}) => {
|
|
303
|
+
this.#assertCapability("network", "network.request");
|
|
304
|
+
const transport = normalizeBrowserNetworkTransport(params);
|
|
305
|
+
const request = params.request ?? params;
|
|
306
|
+
if (transport === "http") {
|
|
307
|
+
return this.http.request(request);
|
|
308
|
+
}
|
|
309
|
+
if (transport === "websocket") {
|
|
310
|
+
return this.websocket.exchange(request);
|
|
311
|
+
}
|
|
312
|
+
if (networkAdapter) {
|
|
313
|
+
return invokeAdapterMethod(networkAdapter, "request", {
|
|
314
|
+
...request,
|
|
315
|
+
transport,
|
|
316
|
+
}, "network");
|
|
317
|
+
}
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Browser host does not support network transport "${transport}".`,
|
|
320
|
+
);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.ipfs = Object.freeze({
|
|
325
|
+
invoke: async (params = {}) => {
|
|
326
|
+
this.#assertCapability("ipfs", "ipfs.invoke");
|
|
327
|
+
const operation = String(params.operation ?? "invoke").trim();
|
|
328
|
+
if (!operation) {
|
|
329
|
+
throw new Error("ipfs.invoke requires a non-empty operation.");
|
|
330
|
+
}
|
|
331
|
+
return invokeAdapterMethod(ipfsAdapter, operation, params, "ipfs");
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
this.protocolHandle = Object.freeze({
|
|
336
|
+
register: async (params = {}) => {
|
|
337
|
+
this.#assertCapability("protocol_handle", "protocol_handle.register");
|
|
338
|
+
return invokeAdapterMethod(
|
|
339
|
+
protocolHandleAdapter,
|
|
340
|
+
"register",
|
|
341
|
+
params,
|
|
342
|
+
"protocol_handle",
|
|
343
|
+
);
|
|
344
|
+
},
|
|
345
|
+
unregister: async (params = {}) => {
|
|
346
|
+
this.#assertCapability("protocol_handle", "protocol_handle.unregister");
|
|
347
|
+
return invokeAdapterMethod(
|
|
348
|
+
protocolHandleAdapter,
|
|
349
|
+
"unregister",
|
|
350
|
+
params,
|
|
351
|
+
"protocol_handle",
|
|
352
|
+
);
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
this.protocolDial = Object.freeze({
|
|
357
|
+
dial: async (params = {}) => {
|
|
358
|
+
this.#assertCapability("protocol_dial", "protocol_dial.dial");
|
|
359
|
+
return invokeAdapterMethod(
|
|
360
|
+
protocolDialAdapter,
|
|
361
|
+
"dial",
|
|
362
|
+
params,
|
|
363
|
+
"protocol_dial",
|
|
364
|
+
);
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
242
368
|
this.context = Object.freeze({
|
|
243
369
|
get: (scope, key) => {
|
|
244
370
|
this.#assertCapability("context_read", "context.get");
|
|
@@ -378,17 +504,125 @@ export class BrowserHost {
|
|
|
378
504
|
}
|
|
379
505
|
|
|
380
506
|
hasCapability(capability) {
|
|
381
|
-
|
|
507
|
+
const normalized = String(capability ?? "").trim();
|
|
508
|
+
return (
|
|
509
|
+
this._grantedCapabilities.has(normalized) ||
|
|
510
|
+
(this._grantedCapabilities.has("network") &&
|
|
511
|
+
["http", "websocket"].includes(normalized))
|
|
512
|
+
);
|
|
382
513
|
}
|
|
383
514
|
|
|
384
515
|
assertCapability(capability, operation) {
|
|
385
516
|
this.#assertCapability(capability, operation);
|
|
386
517
|
}
|
|
387
518
|
|
|
519
|
+
async invoke(operation, params = {}) {
|
|
520
|
+
const normalized = String(operation ?? "").trim();
|
|
521
|
+
switch (normalized) {
|
|
522
|
+
case "host.runtimeTarget":
|
|
523
|
+
return this.runtimeTarget;
|
|
524
|
+
case "host.listCapabilities":
|
|
525
|
+
return this.listCapabilities();
|
|
526
|
+
case "host.listSupportedCapabilities":
|
|
527
|
+
return this.listSupportedCapabilities();
|
|
528
|
+
case "host.listOperations":
|
|
529
|
+
return this.listOperations();
|
|
530
|
+
case "host.hasCapability":
|
|
531
|
+
return this.hasCapability(params.capability);
|
|
532
|
+
case "clock.now":
|
|
533
|
+
return this.clock.now();
|
|
534
|
+
case "clock.monotonicNow":
|
|
535
|
+
return this.clock.monotonicNow();
|
|
536
|
+
case "clock.nowIso":
|
|
537
|
+
return this.clock.nowIso();
|
|
538
|
+
case "random.bytes":
|
|
539
|
+
return this.random.bytes(params.length);
|
|
540
|
+
case "timers.delay":
|
|
541
|
+
return this.timers.delay(params.ms ?? params.delayMs ?? 0);
|
|
542
|
+
case "schedule.parse":
|
|
543
|
+
return this.schedule.parse(params.expression);
|
|
544
|
+
case "schedule.matches":
|
|
545
|
+
return this.schedule.matches(params.expression, params.date);
|
|
546
|
+
case "schedule.next":
|
|
547
|
+
return this.schedule.next(params.expression, params.from);
|
|
548
|
+
case "http.request":
|
|
549
|
+
return this.http.request(params);
|
|
550
|
+
case "websocket.exchange":
|
|
551
|
+
return this.websocket.exchange(params);
|
|
552
|
+
case "network.request":
|
|
553
|
+
return this.network.request(params);
|
|
554
|
+
case "filesystem.resolvePath":
|
|
555
|
+
return this.filesystem.resolvePath(params.path);
|
|
556
|
+
case "filesystem.readFile":
|
|
557
|
+
return this.filesystem.readFile(params.path, {
|
|
558
|
+
encoding: params.encoding,
|
|
559
|
+
});
|
|
560
|
+
case "filesystem.writeFile":
|
|
561
|
+
return this.filesystem.writeFile(params.path, params.value, {
|
|
562
|
+
encoding: params.encoding,
|
|
563
|
+
});
|
|
564
|
+
case "filesystem.appendFile":
|
|
565
|
+
return this.filesystem.appendFile(params.path, params.value, {
|
|
566
|
+
encoding: params.encoding,
|
|
567
|
+
});
|
|
568
|
+
case "filesystem.deleteFile":
|
|
569
|
+
return this.filesystem.deleteFile(params.path);
|
|
570
|
+
case "filesystem.mkdir":
|
|
571
|
+
return this.filesystem.mkdir(params.path, {
|
|
572
|
+
recursive: params.recursive,
|
|
573
|
+
});
|
|
574
|
+
case "filesystem.readdir":
|
|
575
|
+
return this.filesystem.readdir(params.path);
|
|
576
|
+
case "filesystem.stat":
|
|
577
|
+
return this.filesystem.stat(params.path);
|
|
578
|
+
case "filesystem.rename":
|
|
579
|
+
return this.filesystem.rename(params.fromPath, params.toPath);
|
|
580
|
+
case "ipfs.invoke":
|
|
581
|
+
return this.ipfs.invoke(params);
|
|
582
|
+
case "protocol_handle.register":
|
|
583
|
+
return this.protocolHandle.register(params);
|
|
584
|
+
case "protocol_handle.unregister":
|
|
585
|
+
return this.protocolHandle.unregister(params);
|
|
586
|
+
case "protocol_dial.dial":
|
|
587
|
+
return this.protocolDial.dial(params);
|
|
588
|
+
case "context.get":
|
|
589
|
+
return this.context.get(params.scope, params.key);
|
|
590
|
+
case "context.set":
|
|
591
|
+
return this.context.set(params.scope, params.key, params.value);
|
|
592
|
+
case "context.delete":
|
|
593
|
+
return this.context.delete(params.scope, params.key);
|
|
594
|
+
case "context.listKeys":
|
|
595
|
+
return this.context.listKeys(params.scope);
|
|
596
|
+
case "context.listScopes":
|
|
597
|
+
return this.context.listScopes();
|
|
598
|
+
case "crypto.sha256":
|
|
599
|
+
return this.crypto.sha256(params.value ?? params.bytes);
|
|
600
|
+
case "crypto.sha512":
|
|
601
|
+
return this.crypto.sha512(params.value ?? params.bytes);
|
|
602
|
+
case "crypto.aesGcmEncrypt":
|
|
603
|
+
return this.crypto.aesGcmEncrypt(params);
|
|
604
|
+
case "crypto.aesGcmDecrypt":
|
|
605
|
+
return this.crypto.aesGcmDecrypt(params);
|
|
606
|
+
default:
|
|
607
|
+
throw new Error(`Unknown browser host operation "${normalized}".`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
388
611
|
// --- Private ---
|
|
389
612
|
|
|
390
613
|
#assertCapability(capability, operation) {
|
|
391
|
-
|
|
614
|
+
const normalized = String(capability ?? "").trim();
|
|
615
|
+
const networkBackedCapabilities = new Set([
|
|
616
|
+
"http",
|
|
617
|
+
"websocket",
|
|
618
|
+
]);
|
|
619
|
+
if (
|
|
620
|
+
!this._grantedCapabilities.has(normalized) &&
|
|
621
|
+
!(
|
|
622
|
+
this._grantedCapabilities.has("network") &&
|
|
623
|
+
networkBackedCapabilities.has(normalized)
|
|
624
|
+
)
|
|
625
|
+
) {
|
|
392
626
|
throw new BrowserHostCapabilityError(capability, operation);
|
|
393
627
|
}
|
|
394
628
|
}
|
|
@@ -13,11 +13,25 @@ import {
|
|
|
13
13
|
createBrowserModuleHarness,
|
|
14
14
|
detectArtifactProfile,
|
|
15
15
|
} from "../testing/browserModuleHarness.js";
|
|
16
|
+
import { dispatchHostOperation } from "./abi.js";
|
|
16
17
|
|
|
17
18
|
const isBrowser =
|
|
18
19
|
typeof globalThis.window !== "undefined" &&
|
|
19
20
|
typeof globalThis.document !== "undefined";
|
|
20
21
|
|
|
22
|
+
function attachHostDispatch(harness, host) {
|
|
23
|
+
if (!host || typeof host !== "object") {
|
|
24
|
+
return harness;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...harness,
|
|
28
|
+
host,
|
|
29
|
+
async callHost(operation, params = {}) {
|
|
30
|
+
return dispatchHostOperation(host, operation, params);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
async function createWasmEdgeCommandHarness(options = {}) {
|
|
22
36
|
const [
|
|
23
37
|
{ spawn },
|
|
@@ -175,14 +189,18 @@ export async function loadModule(options = {}) {
|
|
|
175
189
|
(inspection.profile === "standalone" || inspection.profile === "sdn-abi") &&
|
|
176
190
|
inspection.exports.includes("_start")
|
|
177
191
|
) {
|
|
178
|
-
return
|
|
192
|
+
return attachHostDispatch(
|
|
193
|
+
await createWasmEdgeCommandHarness(options),
|
|
194
|
+
options.host ?? null,
|
|
195
|
+
);
|
|
179
196
|
}
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
199
|
+
return attachHostDispatch(
|
|
200
|
+
await createModuleHarness({
|
|
201
|
+
runtime: {
|
|
202
|
+
kind: "wasmedge",
|
|
203
|
+
wasmPath: source,
|
|
186
204
|
wasmEdgeBinary: options.wasmEdgeBinary,
|
|
187
205
|
wasmEdgeRunnerBinary: options.wasmEdgeRunnerBinary,
|
|
188
206
|
enableThreads: options.enableThreads,
|
|
@@ -191,23 +209,28 @@ export async function loadModule(options = {}) {
|
|
|
191
209
|
hostProfile: options.hostProfile,
|
|
192
210
|
modules: options.modules,
|
|
193
211
|
defaultModuleId: options.defaultModuleId,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
metadata: options.metadata,
|
|
213
|
+
},
|
|
214
|
+
}),
|
|
215
|
+
options.host ?? null,
|
|
216
|
+
);
|
|
197
217
|
}
|
|
198
218
|
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
219
|
+
return attachHostDispatch(
|
|
220
|
+
await createModuleHarness({
|
|
221
|
+
runtime: {
|
|
222
|
+
kind: runtimeKind,
|
|
223
|
+
command: options.command ?? runtimeKind,
|
|
224
|
+
args: options.args ?? [source],
|
|
225
|
+
env: options.env,
|
|
226
|
+
cwd: options.cwd,
|
|
227
|
+
hostProfile: options.hostProfile,
|
|
228
|
+
modules: options.modules,
|
|
229
|
+
defaultModuleId: options.defaultModuleId,
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
options.host ?? null,
|
|
233
|
+
);
|
|
211
234
|
}
|
|
212
235
|
|
|
213
236
|
/**
|
package/src/host/nodeHost.js
CHANGED
|
@@ -46,10 +46,14 @@ export const NodeHostSupportedCapabilities = Object.freeze([
|
|
|
46
46
|
"http",
|
|
47
47
|
"websocket",
|
|
48
48
|
"mqtt",
|
|
49
|
+
"network",
|
|
49
50
|
"filesystem",
|
|
50
51
|
"tcp",
|
|
51
52
|
"udp",
|
|
52
53
|
"tls",
|
|
54
|
+
"ipfs",
|
|
55
|
+
"protocol_handle",
|
|
56
|
+
"protocol_dial",
|
|
53
57
|
"context_read",
|
|
54
58
|
"context_write",
|
|
55
59
|
"crypto_hash",
|
|
@@ -75,6 +79,7 @@ export const NodeHostSupportedOperations = Object.freeze([
|
|
|
75
79
|
"websocket.exchange",
|
|
76
80
|
"mqtt.publish",
|
|
77
81
|
"mqtt.subscribeOnce",
|
|
82
|
+
"network.request",
|
|
78
83
|
"filesystem.resolvePath",
|
|
79
84
|
"filesystem.readFile",
|
|
80
85
|
"filesystem.writeFile",
|
|
@@ -87,6 +92,10 @@ export const NodeHostSupportedOperations = Object.freeze([
|
|
|
87
92
|
"tcp.request",
|
|
88
93
|
"udp.request",
|
|
89
94
|
"tls.request",
|
|
95
|
+
"ipfs.invoke",
|
|
96
|
+
"protocol_handle.register",
|
|
97
|
+
"protocol_handle.unregister",
|
|
98
|
+
"protocol_dial.dial",
|
|
90
99
|
"exec.execFile",
|
|
91
100
|
"context.get",
|
|
92
101
|
"context.set",
|
|
@@ -229,6 +238,45 @@ function normalizeAllowedPorts(ports, label, { allowZero = false } = {}) {
|
|
|
229
238
|
return normalized;
|
|
230
239
|
}
|
|
231
240
|
|
|
241
|
+
function normalizeNetworkTransport(params = {}) {
|
|
242
|
+
const explicit = String(
|
|
243
|
+
params.transport ?? params.kind ?? params.request?.transport ?? "",
|
|
244
|
+
)
|
|
245
|
+
.trim()
|
|
246
|
+
.toLowerCase();
|
|
247
|
+
if (explicit) {
|
|
248
|
+
return explicit;
|
|
249
|
+
}
|
|
250
|
+
const candidateUrl = params.url ?? params.request?.url ?? null;
|
|
251
|
+
if (candidateUrl) {
|
|
252
|
+
const protocol = new URL(candidateUrl).protocol.toLowerCase();
|
|
253
|
+
if (protocol === "http:" || protocol === "https:") {
|
|
254
|
+
return "http";
|
|
255
|
+
}
|
|
256
|
+
if (protocol === "ws:" || protocol === "wss:") {
|
|
257
|
+
return "websocket";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
throw new Error(
|
|
261
|
+
'network.request requires a transport value such as "http", "tcp", or "websocket".',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function invokeAdapterMethod(adapter, methodName, params, label) {
|
|
266
|
+
if (!adapter || typeof adapter !== "object") {
|
|
267
|
+
throw new Error(`${label} adapter is not configured for this host.`);
|
|
268
|
+
}
|
|
269
|
+
if (typeof adapter[methodName] === "function") {
|
|
270
|
+
return adapter[methodName](params);
|
|
271
|
+
}
|
|
272
|
+
if (typeof adapter.invoke === "function") {
|
|
273
|
+
return adapter.invoke(methodName, params);
|
|
274
|
+
}
|
|
275
|
+
throw new Error(
|
|
276
|
+
`${label} adapter does not implement "${methodName}" or invoke().`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
232
280
|
function normalizeFilesystemRoot(rootPath) {
|
|
233
281
|
return path.resolve(String(rootPath ?? process.cwd()));
|
|
234
282
|
}
|
|
@@ -1397,6 +1445,10 @@ export class NodeHost {
|
|
|
1397
1445
|
);
|
|
1398
1446
|
this.fetch = options.fetch ?? globalThis.fetch;
|
|
1399
1447
|
this.WebSocket = options.WebSocket ?? globalThis.WebSocket;
|
|
1448
|
+
this._networkAdapter = options.network ?? null;
|
|
1449
|
+
this._ipfsAdapter = options.ipfs ?? null;
|
|
1450
|
+
this._protocolHandleAdapter = options.protocolHandle ?? null;
|
|
1451
|
+
this._protocolDialAdapter = options.protocolDial ?? null;
|
|
1400
1452
|
if (typeof this.fetch !== "function") {
|
|
1401
1453
|
throw new TypeError(
|
|
1402
1454
|
"A fetch implementation is required to create a Node host.",
|
|
@@ -1550,6 +1602,40 @@ export class NodeHost {
|
|
|
1550
1602
|
}),
|
|
1551
1603
|
});
|
|
1552
1604
|
|
|
1605
|
+
this.network = Object.freeze({
|
|
1606
|
+
request: async (params = {}) =>
|
|
1607
|
+
this.#withCapability("network", "network.request", async () => {
|
|
1608
|
+
const transport = normalizeNetworkTransport(params);
|
|
1609
|
+
const request = params.request ?? params;
|
|
1610
|
+
switch (transport) {
|
|
1611
|
+
case "http":
|
|
1612
|
+
return this.http.request(request);
|
|
1613
|
+
case "websocket":
|
|
1614
|
+
return this.websocket.exchange(request);
|
|
1615
|
+
case "mqtt":
|
|
1616
|
+
return request.subscribe === true
|
|
1617
|
+
? this.mqtt.subscribeOnce(request)
|
|
1618
|
+
: this.mqtt.publish(request);
|
|
1619
|
+
case "tcp":
|
|
1620
|
+
return this.tcp.request(request);
|
|
1621
|
+
case "udp":
|
|
1622
|
+
return this.udp.request(request);
|
|
1623
|
+
case "tls":
|
|
1624
|
+
return this.tls.request(request);
|
|
1625
|
+
default:
|
|
1626
|
+
if (this._networkAdapter) {
|
|
1627
|
+
return invokeAdapterMethod(this._networkAdapter, "request", {
|
|
1628
|
+
...request,
|
|
1629
|
+
transport,
|
|
1630
|
+
}, "network");
|
|
1631
|
+
}
|
|
1632
|
+
throw new Error(
|
|
1633
|
+
`Node host does not support network transport "${transport}".`,
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
}),
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1553
1639
|
this.mqtt = Object.freeze({
|
|
1554
1640
|
publish: async (mqttOptions = {}) =>
|
|
1555
1641
|
this.#withCapability("mqtt", "mqtt.publish", async () => {
|
|
@@ -1825,6 +1911,56 @@ export class NodeHost {
|
|
|
1825
1911
|
this._contextStore.listScopes(),
|
|
1826
1912
|
),
|
|
1827
1913
|
});
|
|
1914
|
+
|
|
1915
|
+
this.ipfs = Object.freeze({
|
|
1916
|
+
invoke: async (params = {}) =>
|
|
1917
|
+
this.#withCapability("ipfs", "ipfs.invoke", async () => {
|
|
1918
|
+
const operation = String(params.operation ?? "invoke").trim();
|
|
1919
|
+
if (!operation) {
|
|
1920
|
+
throw new Error("ipfs.invoke requires a non-empty operation.");
|
|
1921
|
+
}
|
|
1922
|
+
return invokeAdapterMethod(this._ipfsAdapter, operation, params, "ipfs");
|
|
1923
|
+
}),
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
this.protocolHandle = Object.freeze({
|
|
1927
|
+
register: async (params = {}) =>
|
|
1928
|
+
this.#withCapability(
|
|
1929
|
+
"protocol_handle",
|
|
1930
|
+
"protocol_handle.register",
|
|
1931
|
+
async () =>
|
|
1932
|
+
invokeAdapterMethod(
|
|
1933
|
+
this._protocolHandleAdapter,
|
|
1934
|
+
"register",
|
|
1935
|
+
params,
|
|
1936
|
+
"protocol_handle",
|
|
1937
|
+
),
|
|
1938
|
+
),
|
|
1939
|
+
unregister: async (params = {}) =>
|
|
1940
|
+
this.#withCapability(
|
|
1941
|
+
"protocol_handle",
|
|
1942
|
+
"protocol_handle.unregister",
|
|
1943
|
+
async () =>
|
|
1944
|
+
invokeAdapterMethod(
|
|
1945
|
+
this._protocolHandleAdapter,
|
|
1946
|
+
"unregister",
|
|
1947
|
+
params,
|
|
1948
|
+
"protocol_handle",
|
|
1949
|
+
),
|
|
1950
|
+
),
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
this.protocolDial = Object.freeze({
|
|
1954
|
+
dial: async (params = {}) =>
|
|
1955
|
+
this.#withCapability("protocol_dial", "protocol_dial.dial", async () =>
|
|
1956
|
+
invokeAdapterMethod(
|
|
1957
|
+
this._protocolDialAdapter,
|
|
1958
|
+
"dial",
|
|
1959
|
+
params,
|
|
1960
|
+
"protocol_dial",
|
|
1961
|
+
),
|
|
1962
|
+
),
|
|
1963
|
+
});
|
|
1828
1964
|
}
|
|
1829
1965
|
|
|
1830
1966
|
listCapabilities() {
|
|
@@ -1840,11 +1976,26 @@ export class NodeHost {
|
|
|
1840
1976
|
}
|
|
1841
1977
|
|
|
1842
1978
|
hasCapability(capability) {
|
|
1843
|
-
|
|
1979
|
+
const normalized = String(capability ?? "").trim();
|
|
1980
|
+
return (
|
|
1981
|
+
this._grantedCapabilities.has(normalized) ||
|
|
1982
|
+
(this._grantedCapabilities.has("network") &&
|
|
1983
|
+
["http", "websocket", "mqtt", "tcp", "udp", "tls"].includes(
|
|
1984
|
+
normalized,
|
|
1985
|
+
))
|
|
1986
|
+
);
|
|
1844
1987
|
}
|
|
1845
1988
|
|
|
1846
1989
|
assertCapability(capability, operation = null) {
|
|
1847
1990
|
const normalized = assertNonEmptyString(capability, "Capability id");
|
|
1991
|
+
const networkBackedCapabilities = new Set([
|
|
1992
|
+
"http",
|
|
1993
|
+
"websocket",
|
|
1994
|
+
"mqtt",
|
|
1995
|
+
"tcp",
|
|
1996
|
+
"udp",
|
|
1997
|
+
"tls",
|
|
1998
|
+
]);
|
|
1848
1999
|
if (!this._supportedCapabilities.has(normalized)) {
|
|
1849
2000
|
throw new HostCapabilityError(
|
|
1850
2001
|
`Capability "${normalized}" is not supported by the reference Node host.`,
|
|
@@ -1855,7 +2006,13 @@ export class NodeHost {
|
|
|
1855
2006
|
},
|
|
1856
2007
|
);
|
|
1857
2008
|
}
|
|
1858
|
-
if (
|
|
2009
|
+
if (
|
|
2010
|
+
!this._grantedCapabilities.has(normalized) &&
|
|
2011
|
+
!(
|
|
2012
|
+
this._grantedCapabilities.has("network") &&
|
|
2013
|
+
networkBackedCapabilities.has(normalized)
|
|
2014
|
+
)
|
|
2015
|
+
) {
|
|
1859
2016
|
throw new HostCapabilityError(
|
|
1860
2017
|
`Capability "${normalized}" is not granted for this Node host.`,
|
|
1861
2018
|
{
|
|
@@ -1871,6 +2028,16 @@ export class NodeHost {
|
|
|
1871
2028
|
async invoke(operation, params = {}) {
|
|
1872
2029
|
const normalized = assertNonEmptyString(operation, "Host operation");
|
|
1873
2030
|
switch (normalized) {
|
|
2031
|
+
case "host.runtimeTarget":
|
|
2032
|
+
return this.runtimeTarget;
|
|
2033
|
+
case "host.listCapabilities":
|
|
2034
|
+
return this.listCapabilities();
|
|
2035
|
+
case "host.listSupportedCapabilities":
|
|
2036
|
+
return this.listSupportedCapabilities();
|
|
2037
|
+
case "host.listOperations":
|
|
2038
|
+
return this.listOperations();
|
|
2039
|
+
case "host.hasCapability":
|
|
2040
|
+
return this.hasCapability(params.capability);
|
|
1874
2041
|
case "clock.now":
|
|
1875
2042
|
return this.clock.now();
|
|
1876
2043
|
case "clock.monotonicNow":
|
|
@@ -1897,6 +2064,8 @@ export class NodeHost {
|
|
|
1897
2064
|
return this.mqtt.publish(params);
|
|
1898
2065
|
case "mqtt.subscribeOnce":
|
|
1899
2066
|
return this.mqtt.subscribeOnce(params);
|
|
2067
|
+
case "network.request":
|
|
2068
|
+
return this.network.request(params);
|
|
1900
2069
|
case "filesystem.resolvePath":
|
|
1901
2070
|
return this.filesystem.resolvePath(params.path);
|
|
1902
2071
|
case "filesystem.readFile":
|
|
@@ -1929,6 +2098,14 @@ export class NodeHost {
|
|
|
1929
2098
|
return this.udp.request(params);
|
|
1930
2099
|
case "tls.request":
|
|
1931
2100
|
return this.tls.request(params);
|
|
2101
|
+
case "ipfs.invoke":
|
|
2102
|
+
return this.ipfs.invoke(params);
|
|
2103
|
+
case "protocol_handle.register":
|
|
2104
|
+
return this.protocolHandle.register(params);
|
|
2105
|
+
case "protocol_handle.unregister":
|
|
2106
|
+
return this.protocolHandle.unregister(params);
|
|
2107
|
+
case "protocol_dial.dial":
|
|
2108
|
+
return this.protocolDial.dial(params);
|
|
1932
2109
|
case "exec.execFile":
|
|
1933
2110
|
return this.exec.execFile(params);
|
|
1934
2111
|
case "context.get":
|
|
@@ -3,15 +3,71 @@ import { createFlatBufferStreamIngestor } from "./flatbufferStreamIngestor.js";
|
|
|
3
3
|
import { createRuntimeRegionStore } from "./runtimeRegionStore.js";
|
|
4
4
|
import { createModuleRegistry } from "./moduleRegistry.js";
|
|
5
5
|
|
|
6
|
+
function normalizeCapabilityId(capability) {
|
|
7
|
+
const normalized = String(capability ?? "").trim();
|
|
8
|
+
if (!normalized) {
|
|
9
|
+
throw new TypeError("Capability id is required.");
|
|
10
|
+
}
|
|
11
|
+
return normalized;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createCapabilityRegistry(capabilities = {}) {
|
|
15
|
+
const entries =
|
|
16
|
+
capabilities instanceof Map
|
|
17
|
+
? capabilities.entries()
|
|
18
|
+
: Object.entries(capabilities ?? {});
|
|
19
|
+
const registry = new Map();
|
|
20
|
+
for (const [capability, adapter] of entries) {
|
|
21
|
+
registry.set(normalizeCapabilityId(capability), adapter);
|
|
22
|
+
}
|
|
23
|
+
return registry;
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
export function createRuntimeHost(options = {}) {
|
|
7
27
|
const rows = options.rows ?? createFlatSqlRuntimeStore();
|
|
8
28
|
const regions = options.regions ?? createRuntimeRegionStore();
|
|
9
29
|
const moduleRegistry = options.moduleRegistry ?? createModuleRegistry();
|
|
30
|
+
const capabilities = createCapabilityRegistry(options.capabilities);
|
|
10
31
|
|
|
11
32
|
return {
|
|
12
33
|
rows,
|
|
13
34
|
regions,
|
|
14
35
|
moduleRegistry,
|
|
36
|
+
listCapabilities() {
|
|
37
|
+
return Array.from(capabilities.keys()).sort();
|
|
38
|
+
},
|
|
39
|
+
getCapability(capability) {
|
|
40
|
+
return capabilities.get(normalizeCapabilityId(capability)) ?? null;
|
|
41
|
+
},
|
|
42
|
+
registerCapability(capability, adapter) {
|
|
43
|
+
const normalized = normalizeCapabilityId(capability);
|
|
44
|
+
capabilities.set(normalized, adapter);
|
|
45
|
+
return adapter;
|
|
46
|
+
},
|
|
47
|
+
unregisterCapability(capability) {
|
|
48
|
+
return capabilities.delete(normalizeCapabilityId(capability));
|
|
49
|
+
},
|
|
50
|
+
async invokeCapability(operation, params = {}) {
|
|
51
|
+
const normalized = normalizeCapabilityId(operation);
|
|
52
|
+
const separator = normalized.indexOf(".");
|
|
53
|
+
const capabilityId =
|
|
54
|
+
separator >= 0 ? normalized.slice(0, separator) : normalized;
|
|
55
|
+
const methodId =
|
|
56
|
+
separator >= 0 ? normalized.slice(separator + 1) : "invoke";
|
|
57
|
+
const adapter = capabilities.get(capabilityId);
|
|
58
|
+
if (!adapter || typeof adapter !== "object") {
|
|
59
|
+
throw new Error(`Runtime host capability "${capabilityId}" is not registered.`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof adapter[methodId] === "function") {
|
|
62
|
+
return adapter[methodId](params);
|
|
63
|
+
}
|
|
64
|
+
if (typeof adapter.invoke === "function") {
|
|
65
|
+
return adapter.invoke(methodId, params);
|
|
66
|
+
}
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Runtime host capability "${capabilityId}" does not implement "${methodId}" or invoke().`,
|
|
69
|
+
);
|
|
70
|
+
},
|
|
15
71
|
};
|
|
16
72
|
}
|
|
17
73
|
|
|
@@ -16,7 +16,8 @@ import { createBrowserWasiShim, WasiExitError } from "../host/wasiShim.js";
|
|
|
16
16
|
import { createBrowserHost } from "../host/browserHost.js";
|
|
17
17
|
import {
|
|
18
18
|
createJsonHostcallBridge,
|
|
19
|
-
|
|
19
|
+
createHostSyncDispatcher,
|
|
20
|
+
dispatchHostOperation,
|
|
20
21
|
DEFAULT_HOSTCALL_IMPORT_MODULE,
|
|
21
22
|
} from "../host/abi.js";
|
|
22
23
|
import {
|
|
@@ -90,7 +91,7 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
90
91
|
let instance = null;
|
|
91
92
|
let bridge = null;
|
|
92
93
|
if (needsHostBridge) {
|
|
93
|
-
const dispatch =
|
|
94
|
+
const dispatch = createHostSyncDispatcher(options.host);
|
|
94
95
|
bridge = createJsonHostcallBridge({
|
|
95
96
|
dispatch,
|
|
96
97
|
getMemory: () => instance.exports.memory,
|
|
@@ -238,6 +239,10 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
238
239
|
return decodePluginInvokeResponse(responseBytes);
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
async function callHost(operation, params = {}) {
|
|
243
|
+
return dispatchHostOperation(host, operation, params);
|
|
244
|
+
}
|
|
245
|
+
|
|
241
246
|
function readManifest() {
|
|
242
247
|
const getBytesExport =
|
|
243
248
|
instance.exports[DefaultManifestExports.pluginBytesSymbol];
|
|
@@ -267,6 +272,7 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
267
272
|
host,
|
|
268
273
|
bridge,
|
|
269
274
|
wasi,
|
|
275
|
+
callHost,
|
|
270
276
|
invoke,
|
|
271
277
|
invokeRaw,
|
|
272
278
|
readManifest,
|
package/src/testing/index.js
CHANGED
|
@@ -120,7 +120,8 @@ const CapabilitySurfaceMatrix = Object.freeze({
|
|
|
120
120
|
syncHostcall: false,
|
|
121
121
|
nodeHostApi: true,
|
|
122
122
|
notes: [
|
|
123
|
-
"The coarse network capability maps to host-side HTTP/TCP/UDP/TLS/WebSocket services
|
|
123
|
+
"The coarse network capability maps to async host-side HTTP/TCP/UDP/TLS/WebSocket services.",
|
|
124
|
+
"Browser harnesses can await the same capability surface through BrowserHost.invoke().",
|
|
124
125
|
"Pure WASI guests cannot reach that surface through the current sync hostcall ABI.",
|
|
125
126
|
"WasmEdge provides non-blocking socket-oriented extensions that can serve as the standard server-side max-WASI target.",
|
|
126
127
|
],
|
|
@@ -198,6 +199,42 @@ const CapabilitySurfaceMatrix = Object.freeze({
|
|
|
198
199
|
"WasmEdge TLS support provides a practical no-wrapper server-side target for guest HTTPS/TLS logic.",
|
|
199
200
|
],
|
|
200
201
|
}),
|
|
202
|
+
ipfs: Object.freeze({
|
|
203
|
+
capability: "ipfs",
|
|
204
|
+
wasi: false,
|
|
205
|
+
standaloneWasi: false,
|
|
206
|
+
wasmedge: false,
|
|
207
|
+
syncHostcall: false,
|
|
208
|
+
nodeHostApi: true,
|
|
209
|
+
notes: [
|
|
210
|
+
"IPFS access is available through async host adapters and browser/module harness dispatch.",
|
|
211
|
+
"Pure WASM guests cannot reach that surface through the current sync hostcall ABI.",
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
protocol_handle: Object.freeze({
|
|
215
|
+
capability: "protocol_handle",
|
|
216
|
+
wasi: false,
|
|
217
|
+
standaloneWasi: false,
|
|
218
|
+
wasmedge: false,
|
|
219
|
+
syncHostcall: false,
|
|
220
|
+
nodeHostApi: true,
|
|
221
|
+
notes: [
|
|
222
|
+
"Protocol registration is available through async host adapters and browser/module harness dispatch.",
|
|
223
|
+
"Pure WASM guests cannot reach that surface through the current sync hostcall ABI.",
|
|
224
|
+
],
|
|
225
|
+
}),
|
|
226
|
+
protocol_dial: Object.freeze({
|
|
227
|
+
capability: "protocol_dial",
|
|
228
|
+
wasi: false,
|
|
229
|
+
standaloneWasi: false,
|
|
230
|
+
wasmedge: false,
|
|
231
|
+
syncHostcall: false,
|
|
232
|
+
nodeHostApi: true,
|
|
233
|
+
notes: [
|
|
234
|
+
"Protocol dialing is available through async host adapters and browser/module harness dispatch.",
|
|
235
|
+
"Pure WASM guests cannot reach that surface through the current sync hostcall ABI.",
|
|
236
|
+
],
|
|
237
|
+
}),
|
|
201
238
|
context_read: Object.freeze({
|
|
202
239
|
capability: "context_read",
|
|
203
240
|
wasi: false,
|