space-data-module-sdk 0.8.7 → 0.8.9

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.
@@ -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/testing/browserModuleHarness.js`](/Users/tj/software/space-data-module-sdk/src/testing/browserModuleHarness.js)
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/testing/browserModuleHarness.js:263,310,353`, `src/flow/flowRuntimeHost.js:36` |
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:186,310` |
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.7",
3
+ "version": "0.8.9",
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,23 @@
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": "./src/host/isomorphicLoader.js",
54
- "./testing/browser": "./src/testing/browserModuleHarness.js",
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/node-builtin": "./src/host/nodeBuiltinSpecifier.js",
59
+ "./host/worker-module": "./src/host/workerModuleHarness.js",
60
+ "./testing/browser": "./src/testing/browser.js",
55
61
  "./testing/module-flatbuffer-stream-pump": {
56
62
  "types": "./src/index.d.ts",
57
- "default": "./src/testing/moduleFlatbufferStreamPump.js"
63
+ "default": "./src/host/moduleFlatbufferStreamPump.js"
58
64
  },
59
65
  "./testing": "./src/testing/index.js",
60
- "./standards": "./src/standards/index.js",
66
+ "./standards": {
67
+ "browser": "./src/standards/browser.js",
68
+ "default": "./src/standards/index.js"
69
+ },
61
70
  "./utils/wasm-crypto": "./src/utils/wasmCrypto.js",
62
71
  "./schemas/*": "./schemas/*",
63
72
  "./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
- export * from "./host/isomorphicLoader.js";
20
- export { createBrowserModuleHarness, detectArtifactProfile } from "./testing/browserModuleHarness.js";
21
- export { createWorkerModuleHarness } from "./testing/workerModuleHarness.js";
22
- export { createModuleFlatBufferStreamPump } from "./testing/moduleFlatbufferStreamPump.js";
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
+ }
@@ -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 "../testing/browserModuleHarness.js";
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-side module harness.
2
+ * Browser module host — the browser leg of the tri-runtime contract.
3
3
  *
4
- * Loads the same standalone WASI .wasm artifact that WasmEdge runs,
5
- * instantiating it in the browser with the WASI shim + optional space_data_module_host
6
- * bridge. Matches the createModuleHarness() API surface.
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 "../host/wasiShim.js";
23
+ import { createBrowserWasiShim, WasiExitError } from "./wasiShim.js";
16
24
  import {
17
25
  createWasiThreadSpawn,
18
26
  isWasiThreadsModule,
19
- } from "../host/wasiThreadHost.js";
20
- import { createBrowserHost } from "../host/browserHost.js";
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 "../host/abi.js";
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
- import { extractPublicationRecordCollection } from "../transport/records.js";
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: createBrowserModuleHarness (WASI shim + optional space_data_module_host)
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 both environments.
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 "../testing/browserModuleHarness.js";
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/testing/workerModuleHarness.js).
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 "../host/browserHost.js";
22
- import { createAsyncHostDispatcher } from "../host/abi.js";
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 "../host/sabHostcallChannel.js";
28
- import { WASI_THREAD_HOSTCALL_MESSAGE } from "../host/wasiThreadWorkerRuntime.js";
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
- const { Worker: NodeWorker } = await import("node:worker_threads");
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 "../host/sabHostcallChannel.js";
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
+ }