space-data-module-sdk 0.8.7 → 0.8.8
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/docs/browser-wasmedge-isomorphic.md +1 -1
- package/docs/language-runtime-matrix.md +2 -2
- package/package.json +13 -5
- package/src/browser.js +14 -4
- package/src/bundle/artifactBytes.js +44 -0
- package/src/bundle/index.js +1 -0
- package/src/flow/isomorphicFlowHost.js +1 -1
- package/src/{testing → host}/browserModuleHarness.js +21 -34
- package/src/host/isomorphicLoader.js +17 -45
- package/src/host/isomorphicLoaderBrowser.js +47 -0
- package/src/host/isomorphicLoaderCore.js +58 -0
- package/src/host/nodeBuiltinSpecifier.js +43 -0
- package/src/host/sabHostcallChannel.js +1 -1
- package/src/host/wasiThreadHost.js +2 -3
- package/src/{testing → host}/workerModuleHarness.js +9 -5
- package/src/{testing → host}/workerModuleHarnessWorker.js +1 -1
- package/src/standards/browser.js +95 -0
- package/src/standards/catalogCore.js +290 -0
- package/src/standards/index.js +27 -251
- package/src/testing/AGENTS.md +25 -2
- package/src/testing/browser.js +32 -0
- package/src/testing/index.js +3 -3
- package/src/testing/parityBrowserRunner.js +1 -1
- package/src/testing/parityHarness.js +1 -1
- /package/src/{testing → host}/moduleFlatbufferStreamPump.js +0 -0
|
@@ -146,7 +146,7 @@ The supported browser/WasmEdge entry points are:
|
|
|
146
146
|
|
|
147
147
|
- browser entry bundle: [`src/browser.js`](/Users/tj/software/space-data-module-sdk/src/browser.js)
|
|
148
148
|
- isomorphic loader: [`src/host/isomorphicLoader.js`](/Users/tj/software/space-data-module-sdk/src/host/isomorphicLoader.js)
|
|
149
|
-
- browser harness: [`src/
|
|
149
|
+
- browser harness: [`src/host/browserModuleHarness.js`](../src/host/browserModuleHarness.js)
|
|
150
150
|
- browser WASI shim: [`src/host/wasiShim.js`](/Users/tj/software/space-data-module-sdk/src/host/wasiShim.js)
|
|
151
151
|
- browser edge shims: [`src/host/browserEdgeShims.js`](/Users/tj/software/space-data-module-sdk/src/host/browserEdgeShims.js)
|
|
152
152
|
- browser host adapter: [`src/host/browserHost.js`](/Users/tj/software/space-data-module-sdk/src/host/browserHost.js)
|
|
@@ -17,9 +17,9 @@ Framing: **hosts** embed a runtime to execute SDN `.wasm` modules; **guests** ar
|
|
|
17
17
|
| Host environment / language | Module runtime used | WasmEdge native? | Status in this codebase | Evidence |
|
|
18
18
|
|---|---|---|---|---|
|
|
19
19
|
| **Go node (`sdn-server`)** — Go | WasmEdge embedded in-process via CGO (`second-state/WasmEdge-go v0.14.0`) | **YES** (in-process, production) | **In use** — the production module runtime | `sdn-server/go.mod:27`; `internal/wasmrt/runtime.go:12,117-130` (`wasmedge.NewVMWithConfig`, THREADS/EXCEPTION_HANDLING/WASI); `internal/modulert/module.go:167-175` |
|
|
20
|
-
| **Browser** — JS/TS (sdn-js UI, OrbPro) | Plain `WebAssembly.instantiate` + pure-JS WASI shim + the `space_data_module_host` hostcall bridge (**the JS harness**) | **NO** — WasmEdge does not exist in browsers | **In use** — `createBrowserModuleHarness`, `createWorkerModuleHarness`, `createFlowRuntimeHost` | `sdn-js/src/ui/runtime/live-delivery.ts:2,204`; SDK `src/host/wasiShim.js:33`, `src/host/abi.js:10` (`DEFAULT_HOSTCALL_IMPORT_MODULE = "space_data_module_host"`, matching Go `internal/modulert/hostbridge.go:28`), `src/
|
|
20
|
+
| **Browser** — JS/TS (sdn-js UI, OrbPro) | Plain `WebAssembly.instantiate` + pure-JS WASI shim + the `space_data_module_host` hostcall bridge (**the JS harness**) | **NO** — WasmEdge does not exist in browsers | **In use** — `createBrowserModuleHarness`, `createWorkerModuleHarness`, `createFlowRuntimeHost` | `sdn-js/src/ui/runtime/live-delivery.ts:2,204`; SDK `src/host/wasiShim.js:33`, `src/host/abi.js:10` (`DEFAULT_HOSTCALL_IMPORT_MODULE = "space_data_module_host"`, matching Go `internal/modulert/hostbridge.go:28`), `src/host/browserModuleHarness.js`, `src/flow/flowRuntimeHost.js:36` |
|
|
21
21
|
| **Node.js — JS/TS, subprocess path** | WasmEdge **out-of-process**: `spawn("wasmedge", ...)` CLI, invoke request over stdin/stdout | Yes, but only as an external binary | **In use** (Node harness default for standalone artifacts) | SDK `src/host/isomorphicLoader.js:89,112`; `src/testing/processInvoke.js:111,131` |
|
|
22
|
-
| **Node.js — JS/TS, pure-JS path** | Plain V8 `WebAssembly` + the same browser harness (wasiShim + hostcall bridge) | NO | **In use** (vitest, isomorphic artifacts, no-wasmedge-binary environments) | `wasiShim.js:1-12` ("same .wasm that runs in WasmEdge … instantiated directly"); `browserModuleHarness.js
|
|
22
|
+
| **Node.js — JS/TS, pure-JS path** | Plain V8 `WebAssembly` + the same browser harness (wasiShim + hostcall bridge) | NO | **In use** (vitest, isomorphic artifacts, no-wasmedge-binary environments) | `wasiShim.js:1-12` ("same .wasm that runs in WasmEdge … instantiated directly"); `src/host/browserModuleHarness.js` |
|
|
23
23
|
| **C** — native test runner | WasmEdge embedded via the C API (`wasmedge/wasmedge.h`) | **YES** (in-process, test-only) | **In use** (pthread module test runner only) | SDK `src/testing/native/wasmedge_emscripten_pthread_runner.c:9`; `src/testing/buildWasmEdgeRunner.js` |
|
|
24
24
|
| **C/C++** — as guest language | n/a (compiles to the wasm modules themselves) | n/a | **In use** — the *only* guest language | All module families (`propagator/*`, `foundation/*`, `comms/*`, closed-modules `*.cpp`); compiler supports exactly `.cpp → em++` / else `emcc` |
|
|
25
25
|
| **Rust** | WasmEdge Rust SDK (`wasmedge-sdk`) exists upstream | Would be native | **N/A — not used** (only vendored emsdk test fixtures contain `.rs`) | Zero `Cargo.toml` outside `deps/emsdk/.../test/rust/` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
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",
|
|
@@ -50,14 +50,22 @@
|
|
|
50
50
|
"./http": "./src/http/index.js",
|
|
51
51
|
"./host/browser-edge-shims": "./src/host/browserEdgeShims.js",
|
|
52
52
|
"./host/wasi-shim": "./src/host/wasiShim.js",
|
|
53
|
-
"./host/isomorphic":
|
|
54
|
-
|
|
53
|
+
"./host/isomorphic": {
|
|
54
|
+
"browser": "./src/host/isomorphicLoaderBrowser.js",
|
|
55
|
+
"default": "./src/host/isomorphicLoader.js"
|
|
56
|
+
},
|
|
57
|
+
"./host/browser-module": "./src/host/browserModuleHarness.js",
|
|
58
|
+
"./host/worker-module": "./src/host/workerModuleHarness.js",
|
|
59
|
+
"./testing/browser": "./src/testing/browser.js",
|
|
55
60
|
"./testing/module-flatbuffer-stream-pump": {
|
|
56
61
|
"types": "./src/index.d.ts",
|
|
57
|
-
"default": "./src/
|
|
62
|
+
"default": "./src/host/moduleFlatbufferStreamPump.js"
|
|
58
63
|
},
|
|
59
64
|
"./testing": "./src/testing/index.js",
|
|
60
|
-
"./standards":
|
|
65
|
+
"./standards": {
|
|
66
|
+
"browser": "./src/standards/browser.js",
|
|
67
|
+
"default": "./src/standards/index.js"
|
|
68
|
+
},
|
|
61
69
|
"./utils/wasm-crypto": "./src/utils/wasmCrypto.js",
|
|
62
70
|
"./schemas/*": "./schemas/*",
|
|
63
71
|
"./compat": "./src/compat/index.js"
|
package/src/browser.js
CHANGED
|
@@ -16,7 +16,17 @@ export * from "./host/sabHostcallChannel.js";
|
|
|
16
16
|
export * from "./host/timerDriver.js";
|
|
17
17
|
export * from "./flow/index.js";
|
|
18
18
|
export * from "./compat/index.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
19
|
+
// The browser leg of `./host/isomorphic`. NEVER isomorphicLoader.js: that is
|
|
20
|
+
// the Node leg, and a bundler resolves its node:* branches even though a
|
|
21
|
+
// browser can never take them.
|
|
22
|
+
export * from "./host/isomorphicLoaderBrowser.js";
|
|
23
|
+
// Browser module hosts. These are runtime surface and live in src/host/;
|
|
24
|
+
// nothing under src/testing/ is reachable from this entry.
|
|
25
|
+
export {
|
|
26
|
+
createBrowserModuleHarness,
|
|
27
|
+
detectArtifactProfile,
|
|
28
|
+
zeroWasmBytes,
|
|
29
|
+
isSharedArrayBufferLike,
|
|
30
|
+
} from "./host/browserModuleHarness.js";
|
|
31
|
+
export { createWorkerModuleHarness } from "./host/workerModuleHarness.js";
|
|
32
|
+
export { createModuleFlatBufferStreamPump } from "./host/moduleFlatbufferStreamPump.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact byte reduction — runtime surface, not test-harness surface.
|
|
3
|
+
*
|
|
4
|
+
* A published module artifact is `module.wasm` plus an appended publication
|
|
5
|
+
* record collection (MBL bundle: the sds.signature entry, PNM/REC trailers).
|
|
6
|
+
* EVERY runtime that loads a module — browser, native WasmEdge, Docker
|
|
7
|
+
* WasmEdge — must reduce the artifact to the same canonical payload before it
|
|
8
|
+
* reaches a wasm engine, because every engine rejects the trailing bytes
|
|
9
|
+
* ("unknown section code" / "malformed section id"). Same input bytes in,
|
|
10
|
+
* byte-identical payload out, in all three runtimes.
|
|
11
|
+
*
|
|
12
|
+
* That makes this an ARTIFACT concern and it belongs beside signing and
|
|
13
|
+
* verification, on `space-data-module-sdk/bundle`. It previously lived in
|
|
14
|
+
* `src/testing/browserModuleHarness.js`, which forced production delivery code
|
|
15
|
+
* (OrbPro's SDN module decryptor) to import a TEST-harness subpath to get it —
|
|
16
|
+
* the import that dragged the Node harnesses into a browser bundle and blanked
|
|
17
|
+
* all 275 gallery demos (`orbpro-engine-bundle-ships-node-builtins`).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { extractPublicationRecordCollection } from "../transport/records.js";
|
|
21
|
+
import { ModuleSignatureError } from "./signing.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reduce a module artifact to the bytes a wasm engine can compile.
|
|
25
|
+
*
|
|
26
|
+
* ENC-protected payloads cannot be reduced here — decryption is a host
|
|
27
|
+
* concern, and the caller must decrypt before loading.
|
|
28
|
+
*
|
|
29
|
+
* @param {Uint8Array} bytes - raw artifact bytes
|
|
30
|
+
* @returns {Uint8Array} compilable wasm bytes
|
|
31
|
+
*/
|
|
32
|
+
export function toLoadableWasmBytes(bytes) {
|
|
33
|
+
const publication = extractPublicationRecordCollection(bytes);
|
|
34
|
+
if (!publication) {
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
37
|
+
if (publication.enc) {
|
|
38
|
+
throw new ModuleSignatureError(
|
|
39
|
+
"encrypted_artifact",
|
|
40
|
+
"Module artifact payload is ENC-protected; decrypt it before loading.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return publication.payloadBytes;
|
|
44
|
+
}
|
package/src/bundle/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./constants.js";
|
|
2
2
|
export * from "./codec.js";
|
|
3
3
|
export * from "./wasm.js";
|
|
4
|
+
export * from "./artifactBytes.js";
|
|
4
5
|
export * from "./signing.js";
|
|
5
6
|
// Re-exported under explicit names rather than `export *`: sigdomain's own
|
|
6
7
|
// names (`statement`, `domains`, `describe`, `registered`) are deliberately
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "../bundle/signing.js";
|
|
6
6
|
import { getWasmCustomSections } from "../bundle/wasm.js";
|
|
7
7
|
import { decodePlgManifest } from "../manifest/plgCodec.js";
|
|
8
|
-
import { createBrowserModuleHarness } from "../
|
|
8
|
+
import { createBrowserModuleHarness } from "../host/browserModuleHarness.js";
|
|
9
9
|
import { extractPublicationRecordCollection } from "../transport/records.js";
|
|
10
10
|
import { sha256Bytes } from "../utils/crypto.js";
|
|
11
11
|
import { bytesToHex } from "../utils/encoding.js";
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser
|
|
2
|
+
* Browser module host — the browser leg of the tri-runtime contract.
|
|
3
3
|
*
|
|
4
|
-
* Loads the same standalone WASI .wasm artifact that WasmEdge
|
|
5
|
-
* instantiating it in the browser with the WASI
|
|
6
|
-
* bridge. Matches the
|
|
4
|
+
* Loads the same standalone WASI .wasm artifact that native WasmEdge and the
|
|
5
|
+
* Docker WasmEdge container run, instantiating it in the browser with the WASI
|
|
6
|
+
* shim + optional space_data_module_host bridge. Matches the
|
|
7
|
+
* createModuleHarness() API surface.
|
|
8
|
+
*
|
|
9
|
+
* This is PRODUCTION runtime surface, published as
|
|
10
|
+
* `space-data-module-sdk/host/browser-module`. It lived under `src/testing/`
|
|
11
|
+
* until 2026-08-07, which made every browser consumer import a test-harness
|
|
12
|
+
* subpath to load a module and let the Node harnesses next door reach a browser
|
|
13
|
+
* bundle (`orbpro-engine-bundle-ships-node-builtins`). Nothing browser-facing
|
|
14
|
+
* lives under `src/testing/` any more.
|
|
7
15
|
*
|
|
8
16
|
* Supports two invoke paths:
|
|
9
17
|
* 1. "direct" — call plugin_invoke_stream(ptr, len, &outLen) and read
|
|
@@ -12,19 +20,19 @@
|
|
|
12
20
|
* stdout for the response bytes.
|
|
13
21
|
*/
|
|
14
22
|
|
|
15
|
-
import { createBrowserWasiShim, WasiExitError } from "
|
|
23
|
+
import { createBrowserWasiShim, WasiExitError } from "./wasiShim.js";
|
|
16
24
|
import {
|
|
17
25
|
createWasiThreadSpawn,
|
|
18
26
|
isWasiThreadsModule,
|
|
19
|
-
} from "
|
|
20
|
-
import { createBrowserHost } from "
|
|
27
|
+
} from "./wasiThreadHost.js";
|
|
28
|
+
import { createBrowserHost } from "./browserHost.js";
|
|
21
29
|
import { getWasmWallet } from "../utils/wasmCrypto.js";
|
|
22
30
|
import {
|
|
23
31
|
createHostcallBridge,
|
|
24
32
|
createAsyncHostDispatcher,
|
|
25
33
|
createHostSyncDispatcher,
|
|
26
34
|
DEFAULT_HOSTCALL_IMPORT_MODULE,
|
|
27
|
-
} from "
|
|
35
|
+
} from "./abi.js";
|
|
28
36
|
import {
|
|
29
37
|
DefaultInvokeExports,
|
|
30
38
|
DefaultManifestExports,
|
|
@@ -41,33 +49,12 @@ import {
|
|
|
41
49
|
resolveModuleSignaturePolicy,
|
|
42
50
|
verifyModuleArtifact,
|
|
43
51
|
} from "../bundle/signing.js";
|
|
44
|
-
|
|
52
|
+
// Artifact byte reduction is an ARTIFACT concern shared by all three runtimes,
|
|
53
|
+
// so it lives on `space-data-module-sdk/bundle`. Re-exported here because a
|
|
54
|
+
// caller loading a module in the browser needs it in the same breath.
|
|
55
|
+
import { toLoadableWasmBytes } from "../bundle/artifactBytes.js";
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
* Reduce a module artifact to the bytes a wasm engine can compile.
|
|
48
|
-
*
|
|
49
|
-
* Signed/published artifacts carry an appended publication record collection
|
|
50
|
-
* (MBL bundle with the sds.signature entry, PNM/REC trailers). Wasm engines
|
|
51
|
-
* reject those trailing bytes ("unknown section code"), so the canonical
|
|
52
|
-
* module payload must be extracted before compile. ENC-protected payloads
|
|
53
|
-
* cannot be loaded here — decryption is a host concern.
|
|
54
|
-
*
|
|
55
|
-
* @param {Uint8Array} bytes - raw artifact bytes
|
|
56
|
-
* @returns {Uint8Array} compilable wasm bytes
|
|
57
|
-
*/
|
|
58
|
-
export function toLoadableWasmBytes(bytes) {
|
|
59
|
-
const publication = extractPublicationRecordCollection(bytes);
|
|
60
|
-
if (!publication) {
|
|
61
|
-
return bytes;
|
|
62
|
-
}
|
|
63
|
-
if (publication.enc) {
|
|
64
|
-
throw new ModuleSignatureError(
|
|
65
|
-
"encrypted_artifact",
|
|
66
|
-
"Module artifact payload is ENC-protected; decrypt it before loading.",
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
return publication.payloadBytes;
|
|
70
|
-
}
|
|
57
|
+
export { toLoadableWasmBytes };
|
|
71
58
|
|
|
72
59
|
const STANDALONE_SHARED_MEMORY_ENV_STUBS = Object.freeze({
|
|
73
60
|
_emscripten_init_main_thread_js: () => 0,
|
|
@@ -1,43 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Isomorphic module loader.
|
|
2
|
+
* Isomorphic module loader — NODE/WasmEdge leg (the `default` condition).
|
|
3
3
|
*
|
|
4
4
|
* Unified entry point that detects the runtime environment and artifact
|
|
5
5
|
* profile, then loads the module through the appropriate path:
|
|
6
|
-
* - Browser
|
|
7
|
-
* - Node/WasmEdge: createModuleHarness (subprocess)
|
|
6
|
+
* - Browser (jsdom/embedded window under Node): createBrowserModuleHarness
|
|
7
|
+
* - Node/WasmEdge: createModuleHarness (subprocess) or the WasmEdge command
|
|
8
|
+
* harness below
|
|
8
9
|
*
|
|
9
|
-
* The same compiled .wasm artifact works in
|
|
10
|
+
* The same compiled .wasm artifact works in every environment; only HOW it is
|
|
11
|
+
* launched differs, and that difference is absorbed here — never in a guest.
|
|
12
|
+
*
|
|
13
|
+
* A browser resolves `./host/isomorphic` to `isomorphicLoaderBrowser.js`
|
|
14
|
+
* instead, via the package's `browser` export condition. The two legs share
|
|
15
|
+
* `isomorphicLoaderCore.js` so the runtime-independent half cannot drift. The
|
|
16
|
+
* split is what keeps a browser bundle from statically resolving the
|
|
17
|
+
* node:child_process/os/path/fs imports below
|
|
18
|
+
* (`module-sdk-browser-entry-node-builtins`).
|
|
10
19
|
*/
|
|
11
20
|
|
|
12
21
|
import {
|
|
13
22
|
createBrowserModuleHarness,
|
|
14
|
-
detectArtifactProfile,
|
|
15
23
|
toLoadableWasmBytes,
|
|
16
|
-
} from "
|
|
17
|
-
import { createAsyncHostDispatcher } from "./abi.js";
|
|
24
|
+
} from "./browserModuleHarness.js";
|
|
18
25
|
import {
|
|
19
26
|
resolveModuleSignaturePolicy,
|
|
20
27
|
verifyModuleArtifact,
|
|
21
28
|
} from "../bundle/signing.js";
|
|
29
|
+
import { attachHostDispatch, inspectModule } from "./isomorphicLoaderCore.js";
|
|
30
|
+
|
|
31
|
+
export { inspectModule, attachHostDispatch };
|
|
22
32
|
|
|
23
33
|
const isBrowser =
|
|
24
34
|
typeof globalThis.window !== "undefined" &&
|
|
25
35
|
typeof globalThis.document !== "undefined";
|
|
26
36
|
|
|
27
|
-
function attachHostDispatch(harness, host) {
|
|
28
|
-
if (!host || typeof host !== "object") {
|
|
29
|
-
return harness;
|
|
30
|
-
}
|
|
31
|
-
const dispatchHost = createAsyncHostDispatcher(host);
|
|
32
|
-
return {
|
|
33
|
-
...harness,
|
|
34
|
-
host,
|
|
35
|
-
async callHost(operation, params = {}) {
|
|
36
|
-
return dispatchHost(operation, params);
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
37
|
async function createWasmEdgeCommandHarness(options = {}) {
|
|
42
38
|
const [
|
|
43
39
|
{ spawn },
|
|
@@ -269,27 +265,3 @@ export async function loadModule(options = {}) {
|
|
|
269
265
|
options.host ?? null,
|
|
270
266
|
);
|
|
271
267
|
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Inspect a WASM module's artifact profile without instantiating it.
|
|
275
|
-
*
|
|
276
|
-
* @param {Uint8Array|ArrayBuffer|WebAssembly.Module} source
|
|
277
|
-
* @returns {Promise<{profile: string, exports: string[], imports: Array}>}
|
|
278
|
-
*/
|
|
279
|
-
export async function inspectModule(source) {
|
|
280
|
-
let wasmModule;
|
|
281
|
-
if (source instanceof WebAssembly.Module) {
|
|
282
|
-
wasmModule = source;
|
|
283
|
-
} else {
|
|
284
|
-
const bytes =
|
|
285
|
-
source instanceof ArrayBuffer ? new Uint8Array(source) : source;
|
|
286
|
-
// Tolerate signed/published artifacts (appended publication records).
|
|
287
|
-
wasmModule = await WebAssembly.compile(toLoadableWasmBytes(bytes));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const profile = detectArtifactProfile(wasmModule);
|
|
291
|
-
const exports = WebAssembly.Module.exports(wasmModule).map((e) => e.name);
|
|
292
|
-
const imports = WebAssembly.Module.imports(wasmModule);
|
|
293
|
-
|
|
294
|
-
return { profile, exports, imports };
|
|
295
|
-
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isomorphic module loader — BROWSER leg.
|
|
3
|
+
*
|
|
4
|
+
* The target of the `browser` export condition on
|
|
5
|
+
* `space-data-module-sdk/host/isomorphic`, and the loader re-exported by the
|
|
6
|
+
* package's own `browser` entry (`src/browser.js`).
|
|
7
|
+
*
|
|
8
|
+
* Same public surface as the Node leg — `loadModule` and `inspectModule` — and
|
|
9
|
+
* for a browser it does EXACTLY what the Node leg's `isBrowser` branch did
|
|
10
|
+
* before the split: hand the artifact to `createBrowserModuleHarness`. What it
|
|
11
|
+
* does not carry is the WasmEdge subprocess path, because a browser can never
|
|
12
|
+
* take it and a bundler resolves it anyway.
|
|
13
|
+
*
|
|
14
|
+
* Asking this leg for a server runtime is a defect, not a fallback: it throws.
|
|
15
|
+
* A caller that legitimately needs both is running under Node and resolves the
|
|
16
|
+
* Node leg through the same subpath.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createBrowserModuleHarness } from "./browserModuleHarness.js";
|
|
20
|
+
import { resolveModuleSignaturePolicy } from "../bundle/signing.js";
|
|
21
|
+
import { attachHostDispatch, inspectModule } from "./isomorphicLoaderCore.js";
|
|
22
|
+
|
|
23
|
+
export { inspectModule, attachHostDispatch };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load a WASM module in the browser.
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} options - see the Node leg for the full option list; the
|
|
29
|
+
* browser leg accepts the same object and honours the browser-relevant keys.
|
|
30
|
+
* @returns {Promise<Object>} Harness with invoke(), readManifest(), destroy().
|
|
31
|
+
*/
|
|
32
|
+
export async function loadModule(options = {}) {
|
|
33
|
+
// Resolved for the same reason the Node leg resolves it: an invalid policy is
|
|
34
|
+
// a caller error that must surface identically in every runtime, before any
|
|
35
|
+
// artifact work happens.
|
|
36
|
+
resolveModuleSignaturePolicy(options);
|
|
37
|
+
|
|
38
|
+
const runtimeKind = options.runtimeKind;
|
|
39
|
+
if (typeof runtimeKind === "string" && runtimeKind !== "browser") {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`The browser isomorphic loader cannot run runtimeKind "${runtimeKind}"; ` +
|
|
42
|
+
"that artifact must be loaded from a Node/WasmEdge host.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return createBrowserModuleHarness(options);
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isomorphic loader — the runtime-independent half.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is byte-identical work that every runtime does: reduce the
|
|
5
|
+
* artifact, compile it, read its profile/exports/imports, attach the host
|
|
6
|
+
* dispatcher. `isomorphicLoader.js` (Node/WasmEdge) and
|
|
7
|
+
* `isomorphicLoaderBrowser.js` (browser) both build on this, so the two legs of
|
|
8
|
+
* `./host/isomorphic` cannot drift apart on the parts that must not differ.
|
|
9
|
+
*
|
|
10
|
+
* The split exists because a browser BUNDLER resolves every branch statically:
|
|
11
|
+
* one file holding both legs put node:child_process/os/path/fs into every
|
|
12
|
+
* browser bundle of the SDK's own `browser` export condition
|
|
13
|
+
* (`module-sdk-browser-entry-node-builtins`). The runtime difference is now
|
|
14
|
+
* absorbed by the package's export conditions — a host-shim concern — instead
|
|
15
|
+
* of by a runtime check inside one file.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { detectArtifactProfile } from "./browserModuleHarness.js";
|
|
19
|
+
import { createAsyncHostDispatcher } from "./abi.js";
|
|
20
|
+
import { toLoadableWasmBytes } from "../bundle/artifactBytes.js";
|
|
21
|
+
|
|
22
|
+
export function attachHostDispatch(harness, host) {
|
|
23
|
+
if (!host || typeof host !== "object") {
|
|
24
|
+
return harness;
|
|
25
|
+
}
|
|
26
|
+
const dispatchHost = createAsyncHostDispatcher(host);
|
|
27
|
+
return {
|
|
28
|
+
...harness,
|
|
29
|
+
host,
|
|
30
|
+
async callHost(operation, params = {}) {
|
|
31
|
+
return dispatchHost(operation, params);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Inspect a WASM module's artifact profile without instantiating it.
|
|
38
|
+
*
|
|
39
|
+
* @param {Uint8Array|ArrayBuffer|WebAssembly.Module} source
|
|
40
|
+
* @returns {Promise<{profile: string, exports: string[], imports: Array}>}
|
|
41
|
+
*/
|
|
42
|
+
export async function inspectModule(source) {
|
|
43
|
+
let wasmModule;
|
|
44
|
+
if (source instanceof WebAssembly.Module) {
|
|
45
|
+
wasmModule = source;
|
|
46
|
+
} else {
|
|
47
|
+
const bytes =
|
|
48
|
+
source instanceof ArrayBuffer ? new Uint8Array(source) : source;
|
|
49
|
+
// Tolerate signed/published artifacts (appended publication records).
|
|
50
|
+
wasmModule = await WebAssembly.compile(toLoadableWasmBytes(bytes));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const profile = detectArtifactProfile(wasmModule);
|
|
54
|
+
const exports = WebAssembly.Module.exports(wasmModule).map((e) => e.name);
|
|
55
|
+
const imports = WebAssembly.Module.imports(wasmModule);
|
|
56
|
+
|
|
57
|
+
return { profile, exports, imports };
|
|
58
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque Node builtin specifiers for isomorphic host shims.
|
|
3
|
+
*
|
|
4
|
+
* A host shim that serves BOTH runtimes needs a Node-only branch that is dead
|
|
5
|
+
* in a browser. Writing that branch as a literal `import("node:worker_threads")`
|
|
6
|
+
* does not work: esbuild/vite/rollup resolve dynamic imports STATICALLY, so a
|
|
7
|
+
* branch that can never run still fails — or, worse, is emitted as an external
|
|
8
|
+
* `node:` specifier that the browser then tries to FETCH, which is how the
|
|
9
|
+
* whole OrbPro gallery went dark (`orbpro-engine-bundle-ships-node-builtins`).
|
|
10
|
+
*
|
|
11
|
+
* Assembling the specifier at runtime keeps it opaque to the bundler. This is
|
|
12
|
+
* host-shim code absorbing a runtime difference, which is the only place the
|
|
13
|
+
* tri-runtime contract allows a difference to be absorbed; no guest module ever
|
|
14
|
+
* gains a runtime check.
|
|
15
|
+
*
|
|
16
|
+
* Use it ONLY behind a runtime check that is false in a browser, and only in
|
|
17
|
+
* `src/host/**` — a module that needs a Node builtin unconditionally belongs on
|
|
18
|
+
* a Node-only subpath instead.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Split so no bundler's `node:`-prefix scanner sees a literal here either.
|
|
22
|
+
const NODE_BUILTIN_PREFIX = "no" + "de:";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} name - builtin name without the `node:` prefix
|
|
26
|
+
* @returns {string} the specifier, assembled at runtime
|
|
27
|
+
*/
|
|
28
|
+
export function nodeBuiltinSpecifier(name) {
|
|
29
|
+
return NODE_BUILTIN_PREFIX + name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Import a Node builtin from an isomorphic host shim.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} name - builtin name without the `node:` prefix
|
|
36
|
+
* @returns {Promise<any>} the builtin's module namespace
|
|
37
|
+
*/
|
|
38
|
+
export async function importNodeBuiltin(name) {
|
|
39
|
+
const specifier = nodeBuiltinSpecifier(name);
|
|
40
|
+
return import(/* @vite-ignore */ /* webpackIgnore: true */ specifier);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { NODE_BUILTIN_PREFIX };
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `createHostcallBridge({ dispatch })` (src/host/abi.js) — the existing
|
|
22
22
|
* envelope/error machinery is reused unchanged. `Atomics.wait` is illegal on
|
|
23
23
|
* the main thread, so the guest MUST run in a Worker (see
|
|
24
|
-
* src/
|
|
24
|
+
* src/host/workerModuleHarness.js).
|
|
25
25
|
*
|
|
26
26
|
* Payloads cross the channel as hostcall value envelopes (hostcallWire.js),
|
|
27
27
|
* so binary leaves (Uint8Array) survive without base64/JSON round-trips.
|
|
@@ -32,14 +32,13 @@
|
|
|
32
32
|
//
|
|
33
33
|
// See docs/isomorphic-pthreads.md and docs/browser-wasmedge-isomorphic.md.
|
|
34
34
|
|
|
35
|
+
import { NODE_BUILTIN_PREFIX } from "./nodeBuiltinSpecifier.js";
|
|
36
|
+
|
|
35
37
|
const IS_NODE =
|
|
36
38
|
typeof process !== "undefined" &&
|
|
37
39
|
!!process.release &&
|
|
38
40
|
process.release.name === "node";
|
|
39
41
|
|
|
40
|
-
// Split so no bundler can constant-fold it back into a literal `node:` import.
|
|
41
|
-
const NODE_BUILTIN_PREFIX = "no" + "de:";
|
|
42
|
-
|
|
43
42
|
const BROWSER_WORKER_URL = new URL(
|
|
44
43
|
"./wasiThreadBrowserWorker.mjs",
|
|
45
44
|
import.meta.url,
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
* cross-origin isolated (COOP+COEP). In Node, worker_threads is used.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { createBrowserHost } from "
|
|
22
|
-
import { createAsyncHostDispatcher } from "
|
|
21
|
+
import { createBrowserHost } from "./browserHost.js";
|
|
22
|
+
import { createAsyncHostDispatcher } from "./abi.js";
|
|
23
23
|
import {
|
|
24
24
|
createSabHostcallBuffer,
|
|
25
25
|
createSabHostcallServer,
|
|
26
26
|
DEFAULT_SAB_HOSTCALL_RESPONSE_BYTES,
|
|
27
|
-
} from "
|
|
28
|
-
import { WASI_THREAD_HOSTCALL_MESSAGE } from "
|
|
27
|
+
} from "./sabHostcallChannel.js";
|
|
28
|
+
import { WASI_THREAD_HOSTCALL_MESSAGE } from "./wasiThreadWorkerRuntime.js";
|
|
29
|
+
import { importNodeBuiltin } from "./nodeBuiltinSpecifier.js";
|
|
29
30
|
|
|
30
31
|
const WORKER_URL = new URL("./workerModuleHarnessWorker.js", import.meta.url);
|
|
31
32
|
|
|
@@ -51,7 +52,10 @@ function isNodeRuntime() {
|
|
|
51
52
|
|
|
52
53
|
async function spawnWorker(workerUrl) {
|
|
53
54
|
if (isNodeRuntime()) {
|
|
54
|
-
|
|
55
|
+
// Specifier assembled at runtime on purpose — see nodeBuiltinSpecifier.js.
|
|
56
|
+
// A literal here is statically resolved by every browser bundler even
|
|
57
|
+
// though this branch is dead in a browser.
|
|
58
|
+
const { Worker: NodeWorker } = await importNodeBuiltin("worker_threads");
|
|
55
59
|
const worker = new NodeWorker(workerUrl);
|
|
56
60
|
return {
|
|
57
61
|
post: (message) => worker.postMessage(message),
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createBrowserModuleHarness } from "./browserModuleHarness.js";
|
|
13
|
-
import { createSabHostcallClientDispatch } from "
|
|
13
|
+
import { createSabHostcallClientDispatch } from "./sabHostcallChannel.js";
|
|
14
14
|
|
|
15
15
|
async function resolvePort() {
|
|
16
16
|
if (
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards catalog — BROWSER leg (the `browser` condition of `./standards`).
|
|
3
|
+
*
|
|
4
|
+
* Same catalog, same validation verdicts as the Node leg; only acquisition
|
|
5
|
+
* differs, and that difference is absorbed here rather than by a runtime check
|
|
6
|
+
* inside shared code. A browser has no `require.resolve` and no readFile, so
|
|
7
|
+
* the manifest must be handed in or fetched:
|
|
8
|
+
*
|
|
9
|
+
* loadStandardsCatalog({ manifest }) // already-parsed manifest.json
|
|
10
|
+
* loadStandardsCatalog({ manifestUrl }) // fetched, then parsed
|
|
11
|
+
*
|
|
12
|
+
* Neither one supplied is a caller defect, not a fallback: it throws. Silently
|
|
13
|
+
* returning an empty catalog would let a browser "validate" a manifest against
|
|
14
|
+
* nothing and agree with a Node run that actually checked it — exactly the kind
|
|
15
|
+
* of quiet divergence the tri-runtime contract exists to forbid.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
buildStandardsCatalog,
|
|
20
|
+
validateManifestAgainstCatalog,
|
|
21
|
+
withSharedModuleCatalog,
|
|
22
|
+
} from "./catalogCore.js";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
buildStandardsCatalog,
|
|
26
|
+
resolveStandardsTypeRef,
|
|
27
|
+
validateManifestAgainstCatalog,
|
|
28
|
+
withSharedModuleCatalog,
|
|
29
|
+
} from "./catalogCore.js";
|
|
30
|
+
|
|
31
|
+
const standardsCatalogPromises = new Map();
|
|
32
|
+
const knownTypeCatalogPromises = new Map();
|
|
33
|
+
|
|
34
|
+
function catalogCacheKey(options) {
|
|
35
|
+
if (typeof options?.manifestUrl === "string" && options.manifestUrl) {
|
|
36
|
+
return `url:${options.manifestUrl}`;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readManifest(options) {
|
|
42
|
+
if (options?.manifest && typeof options.manifest === "object") {
|
|
43
|
+
return options.manifest;
|
|
44
|
+
}
|
|
45
|
+
if (typeof options?.manifestUrl === "string" && options.manifestUrl) {
|
|
46
|
+
const response = await fetch(options.manifestUrl);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Failed to fetch the spacedatastandards.org manifest from ${options.manifestUrl}: ${response.status}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return response.json();
|
|
53
|
+
}
|
|
54
|
+
throw new Error(
|
|
55
|
+
"The browser standards catalog needs the spacedatastandards.org manifest: " +
|
|
56
|
+
"pass { manifest } (already parsed) or { manifestUrl } (fetched). " +
|
|
57
|
+
"There is no on-disk manifest to read in a browser.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadStandardsCatalog(options = {}) {
|
|
62
|
+
const key = catalogCacheKey(options);
|
|
63
|
+
if (key === null) {
|
|
64
|
+
return buildStandardsCatalog(await readManifest(options));
|
|
65
|
+
}
|
|
66
|
+
if (!standardsCatalogPromises.has(key)) {
|
|
67
|
+
standardsCatalogPromises.set(
|
|
68
|
+
key,
|
|
69
|
+
(async () => buildStandardsCatalog(await readManifest(options)))(),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return standardsCatalogPromises.get(key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function loadKnownTypeCatalog(options = {}) {
|
|
76
|
+
const key = catalogCacheKey(options);
|
|
77
|
+
if (key === null) {
|
|
78
|
+
return withSharedModuleCatalog(await loadStandardsCatalog(options));
|
|
79
|
+
}
|
|
80
|
+
if (!knownTypeCatalogPromises.has(key)) {
|
|
81
|
+
knownTypeCatalogPromises.set(
|
|
82
|
+
key,
|
|
83
|
+
loadStandardsCatalog(options).then(withSharedModuleCatalog),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return knownTypeCatalogPromises.get(key);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function validateManifestAgainstStandardsCatalog(
|
|
90
|
+
manifest,
|
|
91
|
+
options = {},
|
|
92
|
+
) {
|
|
93
|
+
const catalog = options.catalog ?? (await loadKnownTypeCatalog(options));
|
|
94
|
+
return validateManifestAgainstCatalog(catalog, manifest, options);
|
|
95
|
+
}
|