space-data-module-sdk 0.8.14 → 0.8.16

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 (125) hide show
  1. package/bin/space-data-module.js +91 -0
  2. package/docs/_shell/consumer-assets.html +5 -0
  3. package/docs/browser-wasmedge-isomorphic.html +261 -0
  4. package/docs/byo-wasm-quickstart.html +226 -0
  5. package/docs/byo-wasm-quickstart.md +204 -0
  6. package/docs/conformance.html +156 -0
  7. package/docs/conformance.md +92 -0
  8. package/docs/emception-shared-memory-verdict.md +209 -0
  9. package/docs/events-abi.html +313 -0
  10. package/docs/events-abi.md +530 -0
  11. package/docs/families/analytics.html +178 -0
  12. package/docs/families/analytics.md +104 -0
  13. package/docs/families/attitude.html +157 -0
  14. package/docs/families/attitude.md +36 -0
  15. package/docs/families/behavior.html +157 -0
  16. package/docs/families/behavior.md +36 -0
  17. package/docs/families/breakup.html +157 -0
  18. package/docs/families/breakup.md +36 -0
  19. package/docs/families/conjunction.html +186 -0
  20. package/docs/families/conjunction.md +121 -0
  21. package/docs/families/data-source.html +318 -0
  22. package/docs/families/effects.html +157 -0
  23. package/docs/families/effects.md +36 -0
  24. package/docs/families/environment.html +170 -0
  25. package/docs/families/environment.md +73 -0
  26. package/docs/families/estimation.html +190 -0
  27. package/docs/families/estimation.md +128 -0
  28. package/docs/families/gnc.html +157 -0
  29. package/docs/families/gnc.md +36 -0
  30. package/docs/families/maneuver.html +209 -0
  31. package/docs/families/maneuver.md +206 -0
  32. package/docs/families/obstruction.html +191 -0
  33. package/docs/families/obstruction.md +96 -0
  34. package/docs/families/propagator.html +333 -0
  35. package/docs/families/propulsion.html +157 -0
  36. package/docs/families/propulsion.md +36 -0
  37. package/docs/families/reentry.html +157 -0
  38. package/docs/families/reentry.md +36 -0
  39. package/docs/families/rf.html +193 -0
  40. package/docs/families/rf.md +136 -0
  41. package/docs/families/scheduler.html +157 -0
  42. package/docs/families/scheduler.md +36 -0
  43. package/docs/families/sensor.html +157 -0
  44. package/docs/families/sensor.md +36 -0
  45. package/docs/families/signature.html +157 -0
  46. package/docs/families/signature.md +36 -0
  47. package/docs/flatsql-host-contract.html +176 -0
  48. package/docs/flatsql-streaming-standard.html +375 -0
  49. package/docs/gpu-module-abi.html +233 -0
  50. package/docs/harness-family-matrix.md +135 -0
  51. package/docs/index.html +216 -85
  52. package/docs/isomorphic-pthreads.html +228 -0
  53. package/docs/isomorphic-pthreads.md +84 -0
  54. package/docs/isomorphic-sdn-runtime-plan.html +274 -0
  55. package/docs/language-runtime-matrix.html +182 -0
  56. package/docs/llms.txt +86 -0
  57. package/docs/module-bundle-runtime-plan.html +146 -0
  58. package/docs/module-publication-standard.html +503 -0
  59. package/docs/node-red-default-node-parity.html +201 -0
  60. package/docs/propagator-abi.md +73 -21
  61. package/docs/protect-and-sign.html +181 -0
  62. package/docs/protect-and-sign.md +120 -0
  63. package/docs/protocol-installation.html +296 -0
  64. package/docs/publication-submission.html +158 -0
  65. package/docs/publication-submission.md +78 -0
  66. package/docs/secrets-capability.html +206 -0
  67. package/docs/styles.css +860 -167
  68. package/docs/testing-harness.html +408 -0
  69. package/docs/tri-runtime-parity-gate.html +182 -0
  70. package/docs/tri-runtime-parity.html +203 -0
  71. package/include/orbpro/orbpro_event_runner.h +951 -0
  72. package/include/orbpro/orbpro_events_abi.h +565 -0
  73. package/include/orbpro/orbpro_propagator_abi.h +17 -0
  74. package/package.json +15 -5
  75. package/schemas/orbpro/Events.fbs +501 -0
  76. package/schemas/orbpro/Propagator.fbs +72 -9
  77. package/schemas/orbpro/reference-frame-crosswalk.json +108 -0
  78. package/schemas/orbpro/reference-frame.lock.json +98 -0
  79. package/src/browser.js +13 -0
  80. package/src/conformance/abiDriver.js +225 -0
  81. package/src/conformance/index.js +151 -0
  82. package/src/conformance/propagatorSuite.js +526 -0
  83. package/src/conformance/selfTest.js +275 -0
  84. package/src/conformance/selfTestCorpus.js +68 -0
  85. package/src/conformance/twoBodyReference.js +132 -0
  86. package/src/generated/orbpro/events-abi.js +206 -0
  87. package/src/generated/orbpro/events-abi.ts +319 -0
  88. package/src/generated/orbpro/propagator/reference-frame.js +17 -0
  89. package/src/generated/orbpro/propagator/reference-frame.ts +91 -1
  90. package/src/generated/orbpro/propagator-abi.js +17 -0
  91. package/src/generated/orbpro/propagator-abi.ts +17 -0
  92. package/src/generated/spacedatastandards/plg/CCT.d.ts +94 -0
  93. package/src/generated/spacedatastandards/plg/CCT.d.ts.map +1 -0
  94. package/src/generated/spacedatastandards/plg/CCT.js +208 -0
  95. package/src/generated/spacedatastandards/plg/CCT.ts +281 -0
  96. package/src/generated/spacedatastandards/plg/CCTCategory.d.ts +98 -0
  97. package/src/generated/spacedatastandards/plg/CCTCategory.d.ts.map +1 -0
  98. package/src/generated/spacedatastandards/plg/CCTCategory.js +177 -0
  99. package/src/generated/spacedatastandards/plg/CCTCategory.ts +269 -0
  100. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts +54 -0
  101. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts.map +1 -0
  102. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.js +106 -0
  103. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.ts +144 -0
  104. package/src/generated/spacedatastandards/plg/PLG.d.ts +46 -2
  105. package/src/generated/spacedatastandards/plg/PLG.d.ts.map +1 -1
  106. package/src/generated/spacedatastandards/plg/PLG.js +78 -5
  107. package/src/generated/spacedatastandards/plg/PLG.ts +90 -5
  108. package/src/generated/spacedatastandards/plg/capabilityClass.d.ts +181 -0
  109. package/src/generated/spacedatastandards/plg/capabilityClass.d.ts.map +1 -0
  110. package/src/generated/spacedatastandards/plg/capabilityClass.js +183 -0
  111. package/src/generated/spacedatastandards/plg/capabilityClass.ts +208 -0
  112. package/src/generated/spacedatastandards/plg/main.d.ts +4 -0
  113. package/src/generated/spacedatastandards/plg/main.d.ts.map +1 -1
  114. package/src/generated/spacedatastandards/plg/main.js +4 -0
  115. package/src/generated/spacedatastandards/plg/main.ts +4 -0
  116. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +40 -2
  117. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
  118. package/src/generated/spacedatastandards/plg/pluginCategory.js +40 -2
  119. package/src/generated/spacedatastandards/plg/pluginCategory.ts +40 -2
  120. package/src/host/browserModuleHarness.js +8 -0
  121. package/src/host/wasiThreadHost.js +189 -14
  122. package/src/index.d.ts +18 -1
  123. package/src/testing/isomorphicHarness.js +233 -0
  124. package/src/transport/index.js +16 -0
  125. package/src/transport/records.js +1015 -3
@@ -39,11 +39,128 @@ const IS_NODE =
39
39
  !!process.release &&
40
40
  process.release.name === "node";
41
41
 
42
- const BROWSER_WORKER_URL = new URL(
43
- "./wasiThreadBrowserWorker.mjs",
42
+ // ---------------------------------------------------------------------------
43
+ // Browser worker asset resolution (the BUNDLED-CONSUMER anchor).
44
+ //
45
+ // `import.meta.url` is only a correct anchor when this file is served in its
46
+ // package layout (unbundled Node/dev). Any bundler that inlines this source
47
+ // rewrites `import.meta.url` to the BUNDLE's URL, and the sibling
48
+ // `wasiThreadBrowserWorker.mjs` was never emitted next to the bundle -> the
49
+ // Worker 404s. That is a real defect that shipped: five OrbPro/sdn-js bundles
50
+ // re-emitted this literal and the published sandcastle bucket requested
51
+ // `Build/CesiumUnminified/wasiThreadBrowserWorker.mjs`.
52
+ //
53
+ // So the anchor is now an EXPLICIT, documented parameter:
54
+ // - `createWasiThreadSpawn({ browserWorkerUrl })` — the worker file itself, or
55
+ // - `createWasiThreadSpawn({ browserWorkerBaseUrl })` — the DIRECTORY that
56
+ // holds the worker chain, or
57
+ // - `setBrowserWasiThreadWorkerBase(baseUrl)` — a process-wide default a host
58
+ // shim installs once (OrbPro points it at the served
59
+ // `js/vendor/space-data-module-sdk/src/host/`).
60
+ // The default stays today's `import.meta.url` sibling, so unbundled behavior is
61
+ // byte-for-byte unchanged.
62
+ //
63
+ // IMPORTANT: the anchor names a DIRECTORY that must contain the WHOLE chain —
64
+ // `wasiThreadBrowserWorker.mjs` imports `./wasiThreadWorkerRuntime.js`. Staging
65
+ // only the single `.mjs` next to a bundle does NOT work.
66
+ //
67
+ // There is deliberately NO consumer-side `location` sniffing and NO
68
+ // fetch-and-retry probe: resolution must be deterministic (Node never fetches;
69
+ // browser thread count may not become a function of network timing), and an
70
+ // unreachable worker asset fails LOUD (see WasiThreadWorkerUnreachableError).
71
+ // ---------------------------------------------------------------------------
72
+
73
+ const BROWSER_WORKER_FILENAME = "wasiThreadBrowserWorker.mjs";
74
+
75
+ /** Default anchor: the sibling asset in this file's own package layout. */
76
+ export const DEFAULT_BROWSER_WORKER_URL = new URL(
77
+ `./${BROWSER_WORKER_FILENAME}`,
44
78
  import.meta.url,
45
79
  );
46
80
 
81
+ let browserWorkerBaseOverride = null;
82
+
83
+ /**
84
+ * Install the process-wide browser worker base URL. Intended for a host shim
85
+ * (e.g. an engine bundle) that knows where its build published the SDK host
86
+ * directory. Pass `null` to restore the packaged default.
87
+ *
88
+ * @param {string|URL|null} baseUrl directory URL containing
89
+ * `wasiThreadBrowserWorker.mjs` AND `wasiThreadWorkerRuntime.js`. A trailing
90
+ * slash is added when missing.
91
+ */
92
+ export function setBrowserWasiThreadWorkerBase(baseUrl) {
93
+ browserWorkerBaseOverride = baseUrl == null ? null : normalizeBase(baseUrl);
94
+ }
95
+
96
+ /** Current process-wide base override, or null when unset. */
97
+ export function getBrowserWasiThreadWorkerBase() {
98
+ return browserWorkerBaseOverride;
99
+ }
100
+
101
+ function normalizeBase(baseUrl) {
102
+ const asString = String(baseUrl);
103
+ return asString.endsWith("/") ? asString : `${asString}/`;
104
+ }
105
+
106
+ /**
107
+ * Resolve the browser worker URL for one host. Precedence (highest first):
108
+ * 1. `browserWorkerUrl` option (the worker file itself)
109
+ * 2. `browserWorkerBaseUrl` option (directory)
110
+ * 3. `setBrowserWasiThreadWorkerBase()` process-wide base
111
+ * 4. packaged `import.meta.url` sibling (default)
112
+ */
113
+ export function resolveBrowserWorkerUrl({
114
+ browserWorkerUrl,
115
+ browserWorkerBaseUrl,
116
+ } = {}) {
117
+ if (browserWorkerUrl) {
118
+ return String(browserWorkerUrl);
119
+ }
120
+ const base = browserWorkerBaseUrl
121
+ ? normalizeBase(browserWorkerBaseUrl)
122
+ : browserWorkerBaseOverride;
123
+ if (base) {
124
+ // Resolve relative bases (e.g. "js/vendor/space-data-module-sdk/src/host/")
125
+ // against the document when one exists; absolute URLs pass through.
126
+ const documentBase =
127
+ typeof globalThis !== "undefined" &&
128
+ globalThis.location &&
129
+ typeof globalThis.location.href === "string"
130
+ ? globalThis.location.href
131
+ : undefined;
132
+ return documentBase
133
+ ? new URL(`${base}${BROWSER_WORKER_FILENAME}`, documentBase).href
134
+ : `${base}${BROWSER_WORKER_FILENAME}`;
135
+ }
136
+ return String(DEFAULT_BROWSER_WORKER_URL);
137
+ }
138
+
139
+ /**
140
+ * Thrown when the pooled browser path was REQUESTED (threads enabled,
141
+ * cross-origin isolated, shared memory) but the worker asset at the resolved
142
+ * anchor could not be loaded at all. This is the fail-loud replacement for the
143
+ * old silent sequential fallback: a 404'd worker is a deployment defect, not a
144
+ * capability negotiation, and it must never degrade quietly to one thread.
145
+ */
146
+ export class WasiThreadWorkerUnreachableError extends Error {
147
+ constructor(workerUrl, cause) {
148
+ super(
149
+ `[wasi-thread] pooled browser worker asset is unreachable at ${workerUrl}. ` +
150
+ `The anchor must name a directory that serves BOTH ${BROWSER_WORKER_FILENAME} ` +
151
+ `and wasiThreadWorkerRuntime.js. When this host source is bundled, pass ` +
152
+ `browserWorkerBaseUrl / browserWorkerUrl to createWasiThreadSpawn (or call ` +
153
+ `setBrowserWasiThreadWorkerBase) — import.meta.url anchors to the bundle, not ` +
154
+ `to the package layout.`,
155
+ );
156
+ this.name = "WasiThreadWorkerUnreachableError";
157
+ this.workerUrl = String(workerUrl);
158
+ if (cause !== undefined) {
159
+ this.cause = cause;
160
+ }
161
+ }
162
+ }
163
+
47
164
  /**
48
165
  * Does the compiled module require the wasi-threads host (i.e. does it import
49
166
  * `wasi.thread-spawn`)? Single-thread artifacts do not, so the host is only
@@ -68,12 +185,21 @@ export function isWasiThreadsModule(wasmModule) {
68
185
  const BROWSER_POOL_PROBE_TIMEOUT_MS = 1500;
69
186
 
70
187
  // Arm the browser warm pool: probe every created worker and resolve a single
71
- // all-or-nothing decision. Resolves true ONLY when every worker confirms
72
- // {t:"ready", ok:true}; resolves false the instant ANY worker reports not-ready,
73
- // errors, or misses the probe deadline — a committed, fast sequential fallback,
74
- // never a wait for the slowest loser. The per-worker onmessage handler installed
75
- // here is PERSISTENT: after arming it keeps dispatching {t:"exit"} (idle return)
76
- // and {t:"error"} (guest fault surfacing) for the life of the pool.
188
+ // all-or-nothing decision, as `{ ok, unreachable, error }`.
189
+ // ok:true -> every worker confirmed {t:"ready", ok:true}
190
+ // ok:false -> a worker reported not-ready or missed the
191
+ // probe deadline: a committed, fast SEQUENTIAL
192
+ // fallback (capability negotiation).
193
+ // ok:false, unreachable:true -> a worker raised a worker-level `onerror`
194
+ // without ever speaking the pool protocol,
195
+ // i.e. the worker ASSET did not load (404 /
196
+ // wrong anchor / broken import chain). That is
197
+ // a deployment defect, not a capability, and
198
+ // the caller must fail LOUD.
199
+ // It resolves the instant any worker loses — never a wait for the slowest loser.
200
+ // The per-worker onmessage handler installed here is PERSISTENT: after arming it
201
+ // keeps dispatching {t:"exit"} (idle return) and {t:"error"} (guest fault
202
+ // surfacing) for the life of the pool.
77
203
  function armBrowserPool(
78
204
  created,
79
205
  { wasmModule, memory, hostcallChannel, timeoutMs, onExit },
@@ -82,7 +208,8 @@ function armBrowserPool(
82
208
  let remaining = created.length;
83
209
  let settled = false;
84
210
  const timers = [];
85
- const finish = (ok) => {
211
+ const spoke = new WeakSet();
212
+ const finish = (ok, extra = {}) => {
86
213
  if (settled) {
87
214
  return;
88
215
  }
@@ -90,13 +217,16 @@ function armBrowserPool(
90
217
  for (const timer of timers) {
91
218
  clearTimeout(timer);
92
219
  }
93
- resolve(ok);
220
+ resolve({ ok, unreachable: false, error: null, ...extra });
94
221
  };
95
222
  for (const worker of created) {
96
223
  const timer = setTimeout(() => finish(false), timeoutMs);
97
224
  timers.push(timer);
98
225
  worker.onmessage = (event) => {
99
226
  const message = event.data || {};
227
+ // Any protocol message proves the worker script LOADED; a later onerror
228
+ // is then a guest fault, not an unreachable asset.
229
+ spoke.add(worker);
100
230
  if (message.t === "ready") {
101
231
  clearTimeout(timer);
102
232
  if (message.ok === true) {
@@ -127,7 +257,9 @@ function armBrowserPool(
127
257
  "[wasi-thread] pooled worker error:",
128
258
  error?.message ?? error,
129
259
  );
130
- finish(false);
260
+ // A worker that errored without ever answering the pool protocol never
261
+ // ran our script: the asset at the resolved anchor is unreachable.
262
+ finish(false, { unreachable: !spoke.has(worker), error });
131
263
  };
132
264
  worker.postMessage({
133
265
  t: "probe",
@@ -181,7 +313,20 @@ function detectHardwareConcurrency() {
181
313
  * the generic module-host ABI.
182
314
  * @param {boolean} [options.enableBrowserThreads] explicit successful host
183
315
  * capability negotiation for an owning cross-origin-isolated worker harness.
316
+ * @param {string|URL} [options.browserWorkerBaseUrl] BROWSER ANCHOR — the
317
+ * directory URL under which this build serves the SDK host worker chain
318
+ * (`wasiThreadBrowserWorker.mjs` + `wasiThreadWorkerRuntime.js`). REQUIRED
319
+ * whenever this host source is BUNDLED: `import.meta.url` then resolves to the
320
+ * bundle, not to the package layout, and the sibling asset 404s. Defaults to
321
+ * the process-wide `setBrowserWasiThreadWorkerBase()` value, then to the
322
+ * packaged sibling.
323
+ * @param {string|URL} [options.browserWorkerUrl] the worker file itself; wins
324
+ * over `browserWorkerBaseUrl`. Use only when the file is not named
325
+ * `wasiThreadBrowserWorker.mjs` in its served directory.
184
326
  * @returns {Promise<{ threadSpawn: Function, activeThreadCount: () => number, spawnCount: () => number, distinctOsThreadCount: () => number, terminateAll: () => Promise<void> }>}
327
+ * @throws {WasiThreadWorkerUnreachableError} in the browser, when the pooled
328
+ * path was requested but the worker asset at the resolved anchor never loaded.
329
+ * Deliberately fatal: a silent drop to one thread is the defect this replaces.
185
330
  */
186
331
  export async function createWasiThreadSpawn({
187
332
  wasmModule,
@@ -190,6 +335,8 @@ export async function createWasiThreadSpawn({
190
335
  hostcallChannel,
191
336
  requiresHostcalls = false,
192
337
  enableBrowserThreads,
338
+ browserWorkerBaseUrl,
339
+ browserWorkerUrl,
193
340
  } = {}) {
194
341
  let nextTid = 0;
195
342
  let spawnCount = 0;
@@ -319,17 +466,45 @@ export async function createWasiThreadSpawn({
319
466
  };
320
467
 
321
468
  if (poolSize > 0) {
469
+ const workerUrl = resolveBrowserWorkerUrl({
470
+ browserWorkerUrl,
471
+ browserWorkerBaseUrl,
472
+ });
322
473
  const created = [];
323
- for (let i = 0; i < poolSize; i += 1) {
324
- created.push(new Worker(BROWSER_WORKER_URL, { type: "module" }));
474
+ try {
475
+ for (let i = 0; i < poolSize; i += 1) {
476
+ created.push(new Worker(workerUrl, { type: "module" }));
477
+ }
478
+ } catch (error) {
479
+ // Constructing a module Worker throws synchronously for a malformed or
480
+ // cross-origin-forbidden URL. Same class of defect as a 404: fail LOUD.
481
+ for (const worker of created) {
482
+ try {
483
+ worker.terminate();
484
+ } catch {
485
+ // best effort
486
+ }
487
+ }
488
+ throw new WasiThreadWorkerUnreachableError(workerUrl, error);
325
489
  }
326
- const armed = await armBrowserPool(created, {
490
+ const armResult = await armBrowserPool(created, {
327
491
  wasmModule,
328
492
  memory,
329
493
  hostcallChannel,
330
494
  timeoutMs: BROWSER_POOL_PROBE_TIMEOUT_MS,
331
495
  onExit: returnWorkerToIdle,
332
496
  });
497
+ const armed = armResult.ok;
498
+ if (armResult.unreachable) {
499
+ for (const worker of created) {
500
+ try {
501
+ worker.terminate();
502
+ } catch {
503
+ // best effort
504
+ }
505
+ }
506
+ throw new WasiThreadWorkerUnreachableError(workerUrl, armResult.error);
507
+ }
333
508
  if (armed) {
334
509
  poolDisabled = false;
335
510
  for (const worker of created) {
package/src/index.d.ts CHANGED
@@ -996,13 +996,30 @@ export interface GuestLinkArtifact {
996
996
  objectBytes: Uint8Array;
997
997
  }
998
998
 
999
+ /**
1000
+ * The three legitimate thread models.
1001
+ *
1002
+ * `"wasi-sequential"` was missing here while `src/compiler/compileModule.js`
1003
+ * has accepted it since the model was introduced, so every TypeScript consumer
1004
+ * that declared the model the propagator and event-locator families call for
1005
+ * failed to typecheck against a compiler that would have accepted it. Adding
1006
+ * it is a declaration repair, not a new capability.
1007
+ *
1008
+ * `"single-thread"` is the LEGACY EMSCRIPTEN profile and routes to `em++`. It
1009
+ * is not a synonym for "this guest does not thread" — a guest that provably
1010
+ * never spawns declares `"wasi-sequential"`, which is built by the sanctioned
1011
+ * clang `wasm32-wasip1-threads` toolchain and requires
1012
+ * `manifest.sequentialJustification`.
1013
+ */
999
1014
  export type ModuleThreadModelName =
1000
1015
  | "single-thread"
1001
- | "emscripten-pthreads";
1016
+ | "emscripten-pthreads"
1017
+ | "wasi-sequential";
1002
1018
 
1003
1019
  export const ModuleThreadModel: {
1004
1020
  readonly SINGLE_THREAD: "single-thread";
1005
1021
  readonly EMSCRIPTEN_PTHREADS: "emscripten-pthreads";
1022
+ readonly WASI_SEQUENTIAL: "wasi-sequential";
1006
1023
  };
1007
1024
 
1008
1025
  export interface ProtectedArtifact {
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The isomorphic module-test harness — ONE dist/isomorphic/module.wasm driven
3
+ * through the browser harness (SAB + Workers) and native WasmEdge with one
4
+ * API, so a module's behavior tests are written once and run per lane.
5
+ *
6
+ * MOVED INTO THE SDK by W1.4 of the official-harness-shapes program (it began
7
+ * life as space-data-network-modules/tests/lib/isomorphicHarness.mjs, which
8
+ * now re-exports this file). Living here makes it part of the SDK's testing
9
+ * contract: module repos stop copying it, and the conformance runner and the
10
+ * module suites drive artifacts through the same loader.
11
+ *
12
+ * Node-only (child_process, fs): import via `space-data-module-sdk/testing/isomorphic`.
13
+ */
14
+
15
+ import assert from "node:assert/strict";
16
+ import { spawnSync } from "node:child_process";
17
+ import fs from "node:fs";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ import { createBrowserModuleHarness } from "../host/browserModuleHarness.js";
21
+ import { loadModule } from "../host/isomorphicLoader.js";
22
+
23
+ export const STANDALONE_RUNTIME_KINDS = Object.freeze(["browser", "wasmedge"]);
24
+
25
+ function resolveWasmPath(wasmPath) {
26
+ if (wasmPath instanceof URL) {
27
+ return fileURLToPath(wasmPath);
28
+ }
29
+ return String(wasmPath);
30
+ }
31
+
32
+ export function isMissingWasmEdgeError(error) {
33
+ return /spawn wasmedge ENOENT|command not found|Failed to launch/i.test(
34
+ String(error),
35
+ );
36
+ }
37
+
38
+ export function isWasmEdgeAvailable() {
39
+ const result = spawnSync("wasmedge", ["--version"], { stdio: "ignore" });
40
+ return result.status === 0;
41
+ }
42
+
43
+ export async function createStandaloneHarness(runtimeKind, wasmPath, options = {}) {
44
+ const resolvedWasmPath = resolveWasmPath(wasmPath);
45
+ if (runtimeKind === "browser") {
46
+ return createBrowserModuleHarness({
47
+ wasmSource: fs.readFileSync(resolvedWasmPath),
48
+ surface: options.surface ?? "command",
49
+ host: options.host,
50
+ hostOptions: options.hostOptions,
51
+ args: options.args,
52
+ env: options.env,
53
+ logOutput: options.logOutput,
54
+ performance: options.performance,
55
+ wasmMemory: options.wasmMemory,
56
+ memory: options.memory,
57
+ sharedMemory: options.sharedMemory,
58
+ allowRawInvoke: options.allowRawInvoke,
59
+ initialMemoryBytes: options.initialMemoryBytes,
60
+ maximumMemoryBytes: options.maximumMemoryBytes,
61
+ });
62
+ }
63
+
64
+ if (runtimeKind === "wasmedge") {
65
+ return loadModule({
66
+ wasmSource: resolvedWasmPath,
67
+ runtimeKind: "wasmedge",
68
+ enableThreads: options.enableThreads ?? false,
69
+ wasmEdgeBinary: options.wasmEdgeBinary,
70
+ wasmEdgeRunnerBinary: options.wasmEdgeRunnerBinary,
71
+ args: options.args,
72
+ env: options.env,
73
+ cwd: options.cwd,
74
+ });
75
+ }
76
+
77
+ throw new Error(`Unsupported runtime kind: ${runtimeKind}`);
78
+ }
79
+
80
+ export async function createStandaloneHarnessOrSkip(
81
+ runtimeKind,
82
+ wasmPath,
83
+ t,
84
+ options = {},
85
+ ) {
86
+ if (runtimeKind === "wasmedge" && !isWasmEdgeAvailable()) {
87
+ t.skip("Install wasmedge to verify the server-path harness.");
88
+ return null;
89
+ }
90
+
91
+ try {
92
+ return await createStandaloneHarness(runtimeKind, wasmPath, options);
93
+ } catch (error) {
94
+ if (runtimeKind === "wasmedge" && isMissingWasmEdgeError(error)) {
95
+ t.skip("Install wasmedge to verify the server-path harness.");
96
+ return null;
97
+ }
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ export function assertSuccessfulResponse(
103
+ response,
104
+ { outputPortId = "response" } = {},
105
+ ) {
106
+ assert.equal(response.statusCode, 0);
107
+ assert.ok(response.errorCode === "" || response.errorCode === null);
108
+ const frame = response.outputs.find((entry) => entry.portId === outputPortId);
109
+ assert.ok(frame, `missing ${outputPortId} output frame`);
110
+ return frame.payload;
111
+ }
112
+
113
+ function toPayloadBytes(payload) {
114
+ if (payload instanceof Uint8Array) {
115
+ return payload;
116
+ }
117
+ if (ArrayBuffer.isView(payload)) {
118
+ return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
119
+ }
120
+ if (payload instanceof ArrayBuffer) {
121
+ return new Uint8Array(payload);
122
+ }
123
+ throw new TypeError("Binary module requests require Uint8Array-compatible payload bytes.");
124
+ }
125
+
126
+ function isBrowserDirectHarness(harness) {
127
+ return harness?.runtime?.kind === "browser" && harness?.runtime?.surface === "direct";
128
+ }
129
+
130
+ export async function invokeBinaryRequest(
131
+ harness,
132
+ payload,
133
+ {
134
+ methodId = "invoke",
135
+ inputPortId = "request",
136
+ inputTypeRef = null,
137
+ alignment = 8,
138
+ } = {},
139
+ ) {
140
+ const payloadBytes = toPayloadBytes(payload);
141
+
142
+ if (!isBrowserDirectHarness(harness)) {
143
+ const response = await harness.invoke({
144
+ methodId,
145
+ inputs: [
146
+ {
147
+ portId: inputPortId,
148
+ typeRef: inputTypeRef,
149
+ payload: payloadBytes,
150
+ },
151
+ ],
152
+ });
153
+ return response;
154
+ }
155
+
156
+ if (
157
+ typeof SharedArrayBuffer !== "function" ||
158
+ !(harness.memory?.buffer instanceof SharedArrayBuffer)
159
+ ) {
160
+ throw new Error(
161
+ "Browser direct binary requests require SharedArrayBuffer-backed module memory.",
162
+ );
163
+ }
164
+ const alloc = harness.instance?.exports?.plugin_alloc;
165
+ const free = harness.instance?.exports?.plugin_free;
166
+ if (typeof alloc !== "function" || typeof free !== "function") {
167
+ throw new Error(
168
+ "Browser direct binary requests require plugin_alloc and plugin_free exports.",
169
+ );
170
+ }
171
+
172
+ const payloadSize = payloadBytes.byteLength;
173
+ const payloadPtr = payloadSize > 0 ? alloc(payloadSize) : 0;
174
+ if (payloadSize > 0 && !payloadPtr) {
175
+ throw new Error("plugin_alloc returned null for binary request payload.");
176
+ }
177
+ if (payloadSize > 0 && payloadPtr % alignment !== 0) {
178
+ if (payloadPtr > 0) {
179
+ free(payloadPtr, payloadSize);
180
+ }
181
+ throw new Error(
182
+ `plugin_alloc returned a payload pointer (${payloadPtr}) that is not ${alignment}-byte aligned.`,
183
+ );
184
+ }
185
+
186
+ try {
187
+ if (payloadSize > 0) {
188
+ new Uint8Array(harness.memory.buffer, payloadPtr, payloadSize).set(payloadBytes);
189
+ }
190
+ const response = await harness.invoke({
191
+ methodId,
192
+ externalArena: new Uint8Array(harness.memory.buffer),
193
+ inputs: [
194
+ {
195
+ portId: inputPortId,
196
+ typeRef: inputTypeRef,
197
+ offset: payloadPtr,
198
+ size: payloadSize,
199
+ alignment,
200
+ },
201
+ ],
202
+ });
203
+ return response;
204
+ } finally {
205
+ if (payloadSize > 0) {
206
+ free(payloadPtr, payloadSize);
207
+ }
208
+ }
209
+ }
210
+
211
+ export async function invokeJsonRequest(
212
+ harness,
213
+ request,
214
+ {
215
+ methodId = "invoke",
216
+ inputPortId = "request",
217
+ outputPortId = "response",
218
+ inputTypeRef = null,
219
+ } = {},
220
+ ) {
221
+ const response = await harness.invoke({
222
+ methodId,
223
+ inputs: [
224
+ {
225
+ portId: inputPortId,
226
+ typeRef: inputTypeRef,
227
+ payload: Buffer.from(JSON.stringify(request), "utf8"),
228
+ },
229
+ ],
230
+ });
231
+ const payload = assertSuccessfulResponse(response, { outputPortId });
232
+ return JSON.parse(new TextDecoder().decode(payload));
233
+ }
@@ -13,18 +13,34 @@ export {
13
13
 
14
14
  export {
15
15
  appendPublicationRecordCollection,
16
+ BUILD_PROFILE_FILE_IDENTIFIER,
17
+ BUILD_PROFILE_SIGNATURE_LENGTH,
18
+ canonicalBuildProfileJson,
16
19
  createCidV1Raw,
17
20
  createEncryptedEnvelopePayload,
18
21
  createPublicationNotice,
19
22
  decodeEncRecord,
20
23
  decodePnmRecord,
24
+ decodeBuildProfile,
25
+ decodeBuildProfileRecordCollection,
21
26
  decodeProtectedBlobBase64,
22
27
  decodePublicationRecordCollection,
28
+ DEFAULT_RUNTIME_LOCK_TTL_DAYS,
29
+ encodeBuildProfile,
30
+ encodeBuildProfileRecordCollection,
23
31
  encodeEncRecord,
24
32
  encodePnmRecord,
25
33
  encodePublicationRecordCollection,
26
34
  extractPublicationRecordCollection,
35
+ MAX_RUNTIME_LOCK_TTL_DAYS,
36
+ MIN_RUNTIME_LOCK_TTL_DAYS,
37
+ signBuildProfile,
27
38
  stripPublicationRecordCollection,
28
39
  TRAILER_FOOTER_LENGTH,
29
40
  TRAILER_MAGIC_TEXT,
41
+ validateBuildProfile,
42
+ verifyBuildProfile,
43
+ verifyBuildProfileCanonicalJsonSignature,
44
+ verifyBuildProfileFlatbufferSignature,
45
+ zeroBuildProfileSignaturePayloads,
30
46
  } from "./records.js";