libfx 0.0.7-dev.632.g9fe8a30c19a7 → 0.0.8-dev.820.g1d9d3b63d6ea

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/node.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
+ import { closeSync } from "node:fs";
2
3
  import { createRequire } from "node:module";
4
+ import { Socket } from "node:net";
3
5
  import { homedir } from "node:os";
4
6
  import { isAbsolute, resolve } from "node:path";
5
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import { CoreOutput } from "./core-output.js";
9
+ import { loadModule, withModuleFailure } from "./wasm-module.js";
6
10
  import {
7
11
  createFxAgent as createWasmAgent,
8
12
  createFxTerminal as createWasmTerminal,
@@ -15,21 +19,29 @@ import {
15
19
 
16
20
  export { encodeXtermKeyEvent, fxSdkApiVersion, listModels, supportsJspi, xtermAdapter };
17
21
  export const libfxApiVersion = 2;
22
+ const nativeCoreApiVersion = 3;
18
23
 
19
24
  const fetchOperationStale = 0;
20
25
  const fetchOperationApplied = 1;
21
26
  const fetchOperationBackpressure = 2;
22
27
 
23
- const require = createRequire(import.meta.url);
28
+ const nodeRequire = createRequire(import.meta.url);
24
29
  const defaultCoreWasm = new URL("./fx-core.wasm", import.meta.url);
25
30
  const defaultTermWasm = new URL("./fx-term.wasm", import.meta.url);
26
- const defaultNativeCandidates = [
27
- "./libfx.node",
28
- `./libfx.${process.platform}-${process.arch}.node`,
29
- ];
30
31
  let nativeBackendPromise;
31
32
  const wasmFilePromises = new Map();
32
33
 
34
+ const backendReasonCodes = {
35
+ unsupportedPlatform: "LIBFX_UNSUPPORTED_PLATFORM",
36
+ missingArtifact: "LIBFX_NATIVE_ARTIFACT_MISSING",
37
+ nativeLoad: "LIBFX_NATIVE_LOAD_FAILED",
38
+ nativeApi: "LIBFX_NATIVE_API_MISMATCH",
39
+ missingSurface: "LIBFX_NATIVE_SURFACE_MISSING",
40
+ disabledNative: "LIBFX_NATIVE_DISABLED",
41
+ jspiUnavailable: "LIBFX_JSPI_UNAVAILABLE",
42
+ wasmLoad: "LIBFX_WASM_LOAD_FAILED",
43
+ };
44
+
33
45
  function jspiFallbackError(surface, nativeError) {
34
46
  const nativeDetail = nativeError ? ` Native loading failed: ${nativeError.message}.` : " No compatible native addon was found.";
35
47
  const error = new Error(
@@ -46,7 +58,8 @@ async function loadNativeCandidate(candidate) {
46
58
  if (candidate == null) return null;
47
59
  if (candidate instanceof URL) {
48
60
  if (candidate.protocol === "file:" && candidate.pathname.endsWith(".node")) {
49
- return require(fileURLToPath(candidate));
61
+ // Bundlers trace the asset URL; Node must load the native file at runtime.
62
+ return Reflect.apply(nodeRequire, undefined, [fileURLToPath(candidate)]);
50
63
  }
51
64
  const imported = await import(candidate.href);
52
65
  return imported.default ?? imported;
@@ -55,18 +68,36 @@ async function loadNativeCandidate(candidate) {
55
68
  if (typeof candidate !== "string") {
56
69
  throw new TypeError("nativeAddon must be a module, path, URL, false, or undefined");
57
70
  }
58
- if (candidate.endsWith(".node")) return require(isAbsolute(candidate) ? candidate : resolve(candidate));
71
+ if (candidate.endsWith(".node")) {
72
+ return Reflect.apply(nodeRequire, undefined, [isAbsolute(candidate) ? candidate : resolve(candidate)]);
73
+ }
59
74
  const imported = await import(candidate.startsWith("file:") ? candidate : pathToFileURL(candidate).href);
60
75
  return imported.default ?? imported;
61
76
  }
62
77
 
78
+ function defaultNativeCandidate() {
79
+ if (process.platform === "linux" && process.arch === "x64") {
80
+ return new URL("./libfx.linux-x64.node", import.meta.url);
81
+ }
82
+ if (process.platform === "linux" && process.arch === "arm64") {
83
+ return new URL("./libfx.linux-arm64.node", import.meta.url);
84
+ }
85
+ if (process.platform === "darwin" && process.arch === "x64") {
86
+ return new URL("./libfx.darwin-x64.node", import.meta.url);
87
+ }
88
+ if (process.platform === "darwin" && process.arch === "arm64") {
89
+ return new URL("./libfx.darwin-arm64.node", import.meta.url);
90
+ }
91
+ return null;
92
+ }
93
+
63
94
  function validateNativeBackend(backend) {
64
95
  if (!backend) return null;
65
96
  const hasLowLevelCore = typeof backend.createCore === "function";
66
- if ((hasLowLevelCore && backend.libfxApiVersion !== libfxApiVersion) ||
67
- (!hasLowLevelCore && backend.libfxApiVersion !== undefined && backend.libfxApiVersion !== libfxApiVersion)) {
97
+ const expectedVersion = hasLowLevelCore ? nativeCoreApiVersion : libfxApiVersion;
98
+ if ((hasLowLevelCore || backend.libfxApiVersion !== undefined) && backend.libfxApiVersion !== expectedVersion) {
68
99
  const actualVersion = backend.libfxApiVersion ?? "missing";
69
- throw new Error(`native addon API version ${actualVersion} is incompatible with libfx API version ${libfxApiVersion}`);
100
+ throw new Error(`native addon API version ${actualVersion} is incompatible with expected API version ${expectedVersion}`);
70
101
  }
71
102
  if (typeof backend.createCore !== "function" && typeof backend.createFxTerminal !== "function") {
72
103
  throw new Error("native addon must export createCore() or createFxTerminal()");
@@ -74,52 +105,217 @@ function validateNativeBackend(backend) {
74
105
  return backend;
75
106
  }
76
107
 
108
+ function missingArtifact(error) {
109
+ return error?.code === "ENOENT" || error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
110
+ }
111
+
112
+ function nativeCandidateFilePath(candidate) {
113
+ if (candidate instanceof URL) return candidate.protocol === "file:" ? fileURLToPath(candidate) : null;
114
+ if (typeof candidate !== "string") return null;
115
+ if (candidate.startsWith("file:")) return fileURLToPath(new URL(candidate));
116
+ return URL.canParse(candidate) ? null : resolve(candidate);
117
+ }
118
+
119
+ async function nativeArtifactMissing(candidate) {
120
+ try {
121
+ const path = nativeCandidateFilePath(candidate);
122
+ if (path === null) return false;
123
+ await access(path);
124
+ return false;
125
+ } catch (error) {
126
+ return missingArtifact(error);
127
+ }
128
+ }
129
+
130
+ function validationFailure(error) {
131
+ return error?.message?.startsWith("native addon API version ") ? "api" : "surface";
132
+ }
133
+
134
+ async function loadAndValidateNativeCandidate(candidate, artifactMissing = false) {
135
+ let backend;
136
+ try {
137
+ backend = await loadNativeCandidate(candidate);
138
+ } catch (error) {
139
+ return { backend: null, error, failure: artifactMissing ? "missing" : "load" };
140
+ }
141
+ try {
142
+ return { backend: validateNativeBackend(backend), error: null, failure: null };
143
+ } catch (error) {
144
+ return { backend: null, error, failure: validationFailure(error) };
145
+ }
146
+ }
147
+
77
148
  async function discoverNativeBackend() {
78
- for (const relativePath of defaultNativeCandidates) {
79
- const url = new URL(relativePath, import.meta.url);
80
- try {
81
- await access(fileURLToPath(url));
82
- } catch (error) {
83
- if (error?.code === "ENOENT") continue;
84
- return { backend: null, error };
85
- }
86
- try {
87
- return { backend: validateNativeBackend(await loadNativeCandidate(url)), error: null };
88
- } catch (error) {
89
- return { backend: null, error };
149
+ const candidate = defaultNativeCandidate();
150
+ if (!candidate) {
151
+ return { backend: null, error: null, failure: "unsupported" };
152
+ }
153
+ try {
154
+ await access(fileURLToPath(candidate));
155
+ } catch (error) {
156
+ if (missingArtifact(error)) {
157
+ return { backend: null, error: null, probeError: error, failure: "missing" };
90
158
  }
159
+ return { backend: null, error, failure: "load" };
91
160
  }
92
- return { backend: null, error: null };
161
+ return loadAndValidateNativeCandidate(candidate);
93
162
  }
94
163
 
95
164
  async function resolveNativeBackend(nativeAddon) {
96
- if (nativeAddon === false) return { backend: null, error: null };
165
+ if (nativeAddon === false) return { backend: null, error: null, failure: "disabled" };
97
166
  if (nativeAddon !== undefined) {
98
- try {
99
- return { backend: validateNativeBackend(await loadNativeCandidate(nativeAddon)), error: null };
100
- } catch (error) {
101
- return { backend: null, error };
102
- }
167
+ return loadAndValidateNativeCandidate(nativeAddon, await nativeArtifactMissing(nativeAddon));
103
168
  }
104
169
  nativeBackendPromise ??= discoverNativeBackend();
105
170
  return nativeBackendPromise;
106
171
  }
107
172
 
108
- function wasmBytes(input) {
109
- let path;
110
- if (input instanceof URL && input.protocol === "file:") path = fileURLToPath(input);
111
- else if (typeof input === "string" && !URL.canParse(input)) path = resolve(input);
112
- else return input;
173
+ function wasmInput(input) {
174
+ const path = wasmFilePath(input);
175
+ if (path === null) {
176
+ if (input instanceof URL) return input.href;
177
+ return input;
178
+ }
113
179
  const cached = wasmFilePromises.get(path);
114
180
  if (cached) return cached;
115
- const pending = readFile(path);
181
+ const pendingRead = readFile(path);
182
+ let pending;
183
+ pending = withModuleFailure(pendingRead, () => {
184
+ if (wasmFilePromises.get(path) === pending) wasmFilePromises.delete(path);
185
+ });
116
186
  wasmFilePromises.set(path, pending);
117
- pending.catch(() => {
187
+ pendingRead.catch(() => {
118
188
  if (wasmFilePromises.get(path) === pending) wasmFilePromises.delete(path);
119
189
  });
120
190
  return pending;
121
191
  }
122
192
 
193
+ function wasmFilePath(input) {
194
+ if (input instanceof URL && input.protocol === "file:") return fileURLToPath(input);
195
+ if (typeof input === "string" && !URL.canParse(input)) return resolve(input);
196
+ return null;
197
+ }
198
+
199
+ function reason(code, message, error) {
200
+ const causeCode = error?.code;
201
+ return {
202
+ code,
203
+ message,
204
+ ...(typeof causeCode === "string" || typeof causeCode === "number" ? { causeCode } : {}),
205
+ };
206
+ }
207
+
208
+ function nativeFailureReason(result) {
209
+ const detailError = result.probeError ?? result.error;
210
+ switch (result.failure) {
211
+ case "unsupported":
212
+ return reason(
213
+ backendReasonCodes.unsupportedPlatform,
214
+ `native addon is not available for ${process.platform}-${process.arch}`,
215
+ );
216
+ case "missing":
217
+ return reason(
218
+ backendReasonCodes.missingArtifact,
219
+ `native addon artifact was not found${detailError?.message ? `: ${detailError.message}` : ""}`,
220
+ detailError,
221
+ );
222
+ case "load":
223
+ return reason(
224
+ backendReasonCodes.nativeLoad,
225
+ `native addon failed to load${result.error?.message ? `: ${result.error.message}` : ""}`,
226
+ result.error,
227
+ );
228
+ case "api":
229
+ return reason(backendReasonCodes.nativeApi, result.error.message, result.error);
230
+ case "surface":
231
+ return reason(backendReasonCodes.missingSurface, result.error.message, result.error);
232
+ case "disabled":
233
+ return reason(backendReasonCodes.disabledNative, "native addon loading is disabled");
234
+ default:
235
+ return reason(backendReasonCodes.nativeLoad, "native addon is unavailable", result.error);
236
+ }
237
+ }
238
+
239
+ function validateBackendInfoOptions(value) {
240
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
241
+ throw new TypeError("getBackendInfo() options must be an object");
242
+ }
243
+ const options = { ...value };
244
+ for (const key of Object.keys(options)) {
245
+ if (!new Set(["surface", "backend", "nativeAddon", "wasm"]).has(key)) {
246
+ throw new TypeError(`getBackendInfo() does not accept ${key}`);
247
+ }
248
+ }
249
+ const surface = options.surface ?? "agent";
250
+ if (!new Set(["agent", "terminal"]).has(surface)) {
251
+ throw new TypeError('surface must be "agent" or "terminal"');
252
+ }
253
+ const backend = options.backend ?? "auto";
254
+ if (!new Set(["auto", "native", "wasm"]).has(backend)) {
255
+ throw new TypeError('backend must be "auto", "native", or "wasm"');
256
+ }
257
+ if (Object.hasOwn(options, "nativeAddon") && options.nativeAddon !== undefined && options.nativeAddon !== false &&
258
+ typeof options.nativeAddon !== "string" && !(options.nativeAddon instanceof URL) &&
259
+ (typeof options.nativeAddon !== "object" || options.nativeAddon === null)) {
260
+ throw new TypeError("nativeAddon must be a module, path, URL, false, or undefined");
261
+ }
262
+ const validWasm = options.wasm === undefined || typeof options.wasm === "string" || options.wasm instanceof URL ||
263
+ options.wasm instanceof Promise || options.wasm instanceof WebAssembly.Module || options.wasm instanceof Response ||
264
+ options.wasm instanceof ArrayBuffer || ArrayBuffer.isView(options.wasm);
265
+ if (Object.hasOwn(options, "wasm") && !validWasm) {
266
+ throw new TypeError("wasm must be a URL, Response, ArrayBuffer, typed array, or WebAssembly.Module");
267
+ }
268
+ return { ...options, surface, backend };
269
+ }
270
+
271
+ export async function getBackendInfo(value = {}) {
272
+ const { surface, backend, nativeAddon, wasm } = validateBackendInfoOptions(value);
273
+ const attempts = [];
274
+ if (backend !== "wasm") {
275
+ const native = await resolveNativeBackend(nativeAddon);
276
+ const nativeMethod = surface === "agent" ? "createCore" : "createFxTerminal";
277
+ if (typeof native.backend?.[nativeMethod] === "function") {
278
+ attempts.push({ backend: "native", available: true, reason: null });
279
+ return { surface, backend: "native", attempts };
280
+ }
281
+ const failureReason = native.backend
282
+ ? reason(backendReasonCodes.missingSurface, `native addon does not provide ${nativeMethod}()`)
283
+ : nativeFailureReason(native);
284
+ attempts.push({ backend: "native", available: false, reason: failureReason });
285
+ if (backend === "native") return { surface, backend: "unavailable", attempts };
286
+ }
287
+
288
+ if (!supportsJspi()) {
289
+ attempts.push({
290
+ backend: "wasm-jspi",
291
+ available: false,
292
+ reason: reason(
293
+ backendReasonCodes.jspiUnavailable,
294
+ "WebAssembly backend requires JavaScript Promise Integration (JSPI)",
295
+ ),
296
+ });
297
+ return { surface, backend: "unavailable", attempts };
298
+ }
299
+ const defaultWasm = surface === "agent" ? defaultCoreWasm : defaultTermWasm;
300
+ const wasmSource = wasm ?? defaultWasm;
301
+ try {
302
+ await loadModule(wasmInput(wasmSource));
303
+ attempts.push({ backend: "wasm-jspi", available: true, reason: null });
304
+ return { surface, backend: "wasm-jspi", attempts };
305
+ } catch (error) {
306
+ attempts.push({
307
+ backend: "wasm-jspi",
308
+ available: false,
309
+ reason: reason(
310
+ backendReasonCodes.wasmLoad,
311
+ `WebAssembly asset failed to load or compile: ${error?.message ?? String(error)}`,
312
+ error,
313
+ ),
314
+ });
315
+ return { surface, backend: "unavailable", attempts };
316
+ }
317
+ }
318
+
123
319
  function createNativeCoreRuntime(addon, options) {
124
320
  const { apiKey, model, gatewayChatUrl } = options;
125
321
  const core = addon.createCore({
@@ -129,9 +325,24 @@ function createNativeCoreRuntime(addon, options) {
129
325
  ...(model === undefined ? {} : { model }),
130
326
  ...(gatewayChatUrl === undefined ? {} : { gatewayChatUrl }),
131
327
  });
328
+ let readyFd;
329
+ let readySocket;
330
+ try {
331
+ readyFd = addon.takeCoreReadyFd(core);
332
+ readySocket = new Socket({ fd: readyFd, readable: true, writable: false });
333
+ } catch (error) {
334
+ if (readyFd !== undefined) {
335
+ try { closeSync(readyFd); } catch {}
336
+ }
337
+ addon.destroyCore(core);
338
+ throw error;
339
+ }
340
+ const readyClosed = new Promise((resolve) => readySocket.once("close", resolve));
132
341
  let exitedResolve;
133
342
  let lineHandler = null;
134
- let lineBuffer = "";
343
+ const output = new CoreOutput((message, size) => lineHandler(message, size));
344
+ let draining = false;
345
+ let outputError;
135
346
  let settled = false;
136
347
  let fetchState = null;
137
348
  const exited = new Promise((resolve) => { exitedResolve = resolve; });
@@ -139,13 +350,15 @@ function createNativeCoreRuntime(addon, options) {
139
350
  fetchState?.controller.abort();
140
351
  try { addon.abortCoreFetch(core); } catch {}
141
352
  };
142
- const finish = (code) => {
353
+ const finish = (code, error) => {
143
354
  if (settled) return;
144
355
  settled = true;
145
- clearInterval(timer);
356
+ outputError = error;
357
+ output.close();
146
358
  abortHostEffects();
147
359
  try { addon.destroyCore(core); } catch {}
148
- exitedResolve(code);
360
+ readySocket.destroy();
361
+ void readyClosed.then(() => exitedResolve(code));
149
362
  };
150
363
  const pumpFetch = async (request) => {
151
364
  const controller = new AbortController();
@@ -189,10 +402,14 @@ function createNativeCoreRuntime(addon, options) {
189
402
  } catch {}
190
403
  }
191
404
  } finally {
192
- if (fetchState === state) fetchState = null;
405
+ if (fetchState === state) {
406
+ fetchState = null;
407
+ queueMicrotask(drainReady);
408
+ }
193
409
  }
194
410
  };
195
- const timer = setInterval(() => {
411
+ function drainReady() {
412
+ if (settled) return;
196
413
  try {
197
414
  if (fetchState) {
198
415
  if (!fetchState.controller.signal.aborted && !addon.coreFetchActive(core, fetchState.handle)) {
@@ -202,29 +419,55 @@ function createNativeCoreRuntime(addon, options) {
202
419
  const fetchRequest = addon.takeCoreFetch(core);
203
420
  if (fetchRequest) void pumpFetch(JSON.parse(fetchRequest.toString("utf8")));
204
421
  }
205
- const chunk = addon.drainCore(core);
206
- if (chunk.length && lineHandler) {
207
- lineBuffer += chunk.toString("utf8");
208
- for (;;) {
209
- const newline = lineBuffer.indexOf("\n");
210
- if (newline < 0) break;
211
- const line = lineBuffer.slice(0, newline);
212
- lineBuffer = lineBuffer.slice(newline + 1);
213
- if (line) void Promise.resolve(lineHandler(JSON.parse(line))).catch(() => finish(1));
214
- }
422
+ if (addon.coreExitCode(core) !== 0) {
423
+ finish(1, new Error("native output delivery failed"));
424
+ return;
425
+ }
426
+ void drainOutput();
427
+ } catch (error) {
428
+ finish(1, error);
429
+ }
430
+ }
431
+ async function drainOutput() {
432
+ if (draining || settled) return;
433
+ draining = true;
434
+ try {
435
+ while (!settled) {
436
+ const chunk = addon.drainCore(core);
437
+ if (!chunk.length) break;
438
+ const pending = output.write(chunk);
439
+ if (pending) await pending;
440
+ }
441
+ if (!settled && addon.coreExited(core)) {
442
+ output.finish();
443
+ finish(addon.coreExitCode(core));
215
444
  }
216
- if (addon.coreExited(core)) finish(addon.coreExitCode(core));
217
- } catch {
218
- finish(1);
445
+ } catch (error) {
446
+ finish(1, error);
447
+ } finally {
448
+ draining = false;
219
449
  }
220
- }, 2);
450
+ }
451
+
452
+ readySocket.on("data", drainReady);
453
+ readySocket.on("end", () => { drainReady(); if (!settled) finish(1); });
454
+ readySocket.on("error", () => finish(1));
455
+ readySocket.on("close", () => { if (!settled) finish(1); });
456
+ // Some runtimes defer descriptor adoption until connect().
457
+ if (readySocket.pending) {
458
+ try { readySocket.connect({ fd: readyFd }); } catch (error) { finish(1); throw error; }
459
+ }
221
460
 
222
461
  return {
223
462
  exited,
463
+ get error() { return outputError; },
224
464
  write(data) { addon.writeCore(core, Buffer.from(data)); },
225
465
  closeStdin() { addon.closeCore(core); },
226
466
  abortHostEffects,
227
- abort() { abortHostEffects(); addon.closeCore(core); },
467
+ abort(error) {
468
+ if (error) finish(1, error);
469
+ else { abortHostEffects(); addon.closeCore(core); }
470
+ },
228
471
  setLineHandler(handler) { lineHandler = handler; },
229
472
  };
230
473
  }
@@ -270,10 +513,8 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
270
513
  if (nativeAttempted) throw nativeError;
271
514
  throw jspiFallbackError(surface, nativeError);
272
515
  }
273
- return wasmFactory({
274
- ...runtimeOptions,
275
- wasm: await wasmBytes(runtimeOptions.wasm ?? defaultWasm),
276
- });
516
+ const wasmSource = runtimeOptions.wasm ?? defaultWasm;
517
+ return wasmFactory({ ...runtimeOptions, wasm: wasmInput(wasmSource) });
277
518
  }
278
519
 
279
520
  export async function createFxAgent(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libfx",
3
- "version": "0.0.7-dev.632.g9fe8a30c19a7",
3
+ "version": "0.0.8-dev.820.g1d9d3b63d6ea",
4
4
  "description": "Embed fx agents and terminals in JavaScript hosts",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,11 +17,17 @@
17
17
  },
18
18
  "exports": {
19
19
  ".": {
20
- "node": "./node.js",
20
+ "node": {
21
+ "import": "./node.js",
22
+ "require": "./node.cjs"
23
+ },
21
24
  "browser": "./browser.js",
22
25
  "default": "./browser.js"
23
26
  },
24
- "./node": "./node.js",
27
+ "./node": {
28
+ "import": "./node.js",
29
+ "require": "./node.cjs"
30
+ },
25
31
  "./browser": "./browser.js",
26
32
  "./wasm": "./fx-sdk.js",
27
33
  "./mcp": "./mcp.js",
package/wasm-module.js ADDED
@@ -0,0 +1,50 @@
1
+ const modulePromisesBySource = new Map();
2
+ const modulePromisesByObject = new WeakMap();
3
+ const moduleFailureSource = Symbol("libfx.moduleFailureSource");
4
+
5
+ export function withModuleFailure(input, onFailure) {
6
+ return { [moduleFailureSource]: { input, onFailure } };
7
+ }
8
+
9
+ async function compileModule(input) {
10
+ const failureSource = input?.[moduleFailureSource];
11
+ if (failureSource) {
12
+ try {
13
+ return await compileModule(failureSource.input);
14
+ } catch (error) {
15
+ failureSource.onFailure();
16
+ throw error;
17
+ }
18
+ }
19
+ if (input instanceof WebAssembly.Module) return input;
20
+ if (typeof input === "string") input = fetch(input);
21
+ if (input instanceof Promise) input = await input;
22
+ if (input instanceof WebAssembly.Module) return input;
23
+ if (input instanceof Response) {
24
+ const contentType = input.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
25
+ if (contentType === "application/wasm" && typeof WebAssembly.compileStreaming === "function") {
26
+ return WebAssembly.compileStreaming(input);
27
+ }
28
+ const bytes = await input.arrayBuffer();
29
+ return WebAssembly.compile(bytes);
30
+ }
31
+ if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
32
+ return WebAssembly.compile(input);
33
+ }
34
+ throw new TypeError("wasm must be a URL, Response, ArrayBuffer, typed array, or WebAssembly.Module");
35
+ }
36
+
37
+ export function loadModule(input) {
38
+ if (input instanceof WebAssembly.Module) return Promise.resolve(input);
39
+ const isString = typeof input === "string";
40
+ if (!isString && (typeof input !== "object" || input === null)) return compileModule(input);
41
+ const cache = isString ? modulePromisesBySource : modulePromisesByObject;
42
+ const cached = cache.get(input);
43
+ if (cached) return cached;
44
+ const pending = compileModule(input);
45
+ cache.set(input, pending);
46
+ pending.catch(() => {
47
+ if (cache.get(input) === pending) cache.delete(input);
48
+ });
49
+ return pending;
50
+ }