space-data-module-sdk 0.8.6 → 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.
Files changed (51) hide show
  1. package/README.md +13 -0
  2. package/docs/AGENTS.md +5 -0
  3. package/docs/browser-wasmedge-isomorphic.md +65 -1
  4. package/docs/language-runtime-matrix.md +2 -2
  5. package/docs/provider-access-abi.md +600 -0
  6. package/package.json +15 -6
  7. package/schemas/orbpro/Propagator.fbs +19 -3
  8. package/src/browser.js +14 -4
  9. package/src/bundle/artifactBytes.js +44 -0
  10. package/src/bundle/index.js +1 -0
  11. package/src/compiler/compileModule.js +24 -1
  12. package/src/flow/flowCompiler.js +141 -2
  13. package/src/flow/flowRuntimeHost.js +7 -1
  14. package/src/flow/isomorphicFlowHost.js +1 -1
  15. package/src/flow/vendor/sdn-flow/MethodRegistry.js +6 -1
  16. package/src/generated/orbpro/propagator/propagator-source-description.js +2 -2
  17. package/src/generated/orbpro/propagator/propagator-source-description.ts +2 -2
  18. package/src/generated/orbpro/propagator/propagator-source-kind.js +13 -2
  19. package/src/generated/orbpro/propagator/propagator-source-kind.ts +13 -2
  20. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +18 -1
  21. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
  22. package/src/generated/spacedatastandards/plg/pluginCategory.js +17 -0
  23. package/src/generated/spacedatastandards/plg/pluginCategory.ts +21 -1
  24. package/src/{testing → host}/browserModuleHarness.js +69 -34
  25. package/src/host/index.js +6 -0
  26. package/src/host/isomorphicLoader.js +17 -45
  27. package/src/host/isomorphicLoaderBrowser.js +47 -0
  28. package/src/host/isomorphicLoaderCore.js +58 -0
  29. package/src/host/nodeBuiltinSpecifier.js +43 -0
  30. package/src/host/providerAccess.js +727 -0
  31. package/src/host/providerAccessAbi.js +403 -0
  32. package/src/host/providerAccessEngineAdapter.js +338 -0
  33. package/src/host/providerAccessFixtureAdapter.js +444 -0
  34. package/src/host/providerAccessTileStoreAdapter.js +366 -0
  35. package/src/host/sabHostcallChannel.js +1 -1
  36. package/src/host/terrainSourceSeam.js +205 -0
  37. package/src/host/wasiThreadHost.js +11 -1
  38. package/src/{testing → host}/workerModuleHarness.js +45 -12
  39. package/src/{testing → host}/workerModuleHarnessWorker.js +6 -5
  40. package/src/index.d.ts +29 -2
  41. package/src/standards/browser.js +95 -0
  42. package/src/standards/catalogCore.js +290 -0
  43. package/src/standards/index.js +27 -251
  44. package/src/testing/AGENTS.md +25 -2
  45. package/src/testing/browser.js +32 -0
  46. package/src/testing/index.d.ts +12 -1
  47. package/src/testing/index.js +3 -3
  48. package/src/testing/parityBrowserRunner.js +9 -2
  49. package/src/testing/parityHarness.js +1 -1
  50. package/templates/provider-access-module/include/space_data_provider_abi.h +227 -0
  51. /package/src/{testing → host}/moduleFlatbufferStreamPump.js +0 -0
@@ -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,
@@ -196,6 +183,37 @@ const DEFAULT_IMPORTED_MEMORY_MAXIMUM_BYTES = 2 * 1024 * 1024 * 1024;
196
183
  const DEFAULT_DIRECT_INVOKE_REQUEST_ARENA_BYTES = 64 * 1024;
197
184
  const SHARED_ARRAY_BUFFER_TAG = "[object SharedArrayBuffer]";
198
185
 
186
+ /**
187
+ * Overwrite a plaintext wasm buffer in place. Best-effort by contract: a
188
+ * detached (transferred) buffer, a frozen typed array, or a runtime without
189
+ * writable views must not turn a successful module load into a thrown error.
190
+ *
191
+ * @param {Uint8Array|ArrayBuffer|ArrayBufferView|null|undefined} bytes
192
+ * @returns {number} bytes actually overwritten
193
+ */
194
+ export function zeroWasmBytes(bytes) {
195
+ if (!bytes) {
196
+ return 0;
197
+ }
198
+ try {
199
+ const view =
200
+ bytes instanceof Uint8Array
201
+ ? bytes
202
+ : bytes instanceof ArrayBuffer
203
+ ? new Uint8Array(bytes)
204
+ : ArrayBuffer.isView(bytes)
205
+ ? new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
206
+ : null;
207
+ if (!view || view.byteLength === 0) {
208
+ return 0;
209
+ }
210
+ view.fill(0);
211
+ return view.byteLength;
212
+ } catch {
213
+ return 0;
214
+ }
215
+ }
216
+
199
217
  export function isSharedArrayBufferLike(value) {
200
218
  return (
201
219
  value !== null &&
@@ -408,6 +426,11 @@ async function instantiateBrowserModule(options = {}) {
408
426
  export async function createBrowserModuleHarness(options = {}) {
409
427
  let wasmSource = options.wasmSource;
410
428
  const signaturePolicy = resolveModuleSignaturePolicy(options);
429
+ // Bytes THIS function materializes itself (Response/URL fetch, or the
430
+ // ArrayBuffer/array copy taken for signature verification) — never a
431
+ // caller-owned buffer, which this function neither created nor may safely
432
+ // scrub. Zeroed below once the module is compiled.
433
+ let ownedArtifactBytes = null;
411
434
  if (signaturePolicy) {
412
435
  if (wasmSource instanceof WebAssembly.Module) {
413
436
  throw new ModuleSignatureError(
@@ -434,8 +457,20 @@ export async function createBrowserModuleHarness(options = {}) {
434
457
  }
435
458
  await verifyModuleArtifact(artifactBytes, signaturePolicy);
436
459
  wasmSource = artifactBytes;
460
+ ownedArtifactBytes = artifactBytes;
437
461
  }
438
462
  const wasmModule = await compileWasmModule(wasmSource);
463
+ // Plaintext hygiene (wasm-plaintext-memory-hygiene): nothing needs the
464
+ // source bytes past this point. Scrub whatever THIS function materialized
465
+ // itself, then drop every local/options reference to the source — a
466
+ // caller-supplied Uint8Array/ArrayBuffer must not outlive this call by
467
+ // being pinned via closure over `options` for the harness's entire
468
+ // lifetime (the returned harness closes over `options` for many other,
469
+ // unrelated fields).
470
+ zeroWasmBytes(ownedArtifactBytes);
471
+ ownedArtifactBytes = null;
472
+ wasmSource = null;
473
+ options.wasmSource = null;
439
474
  const moduleImports = WebAssembly.Module.imports(wasmModule);
440
475
  const needsHostBridge = moduleImports.some(
441
476
  (entry) => entry.module === DEFAULT_HOSTCALL_IMPORT_MODULE,
package/src/host/index.js CHANGED
@@ -7,3 +7,9 @@ export * from "./isomorphicLoader.js";
7
7
  export * from "./abi.js";
8
8
  export * from "./sabHostcallChannel.js";
9
9
  export * from "./timerDriver.js";
10
+ export * from "./providerAccessAbi.js";
11
+ export * from "./providerAccess.js";
12
+ export * from "./providerAccessFixtureAdapter.js";
13
+ export * from "./providerAccessEngineAdapter.js";
14
+ export * from "./providerAccessTileStoreAdapter.js";
15
+ export * from "./terrainSourceSeam.js";
@@ -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 };