@rybosome/tspice 0.0.3 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -84
- package/backend-contract/dist/.tsbuildinfo +1 -1
- package/backend-contract/dist/domains/cells-windows.d.ts +94 -0
- package/backend-contract/dist/domains/cells-windows.js +10 -0
- package/backend-contract/dist/domains/coords-vectors.d.ts +53 -3
- package/backend-contract/dist/domains/dsk.d.ts +49 -0
- package/backend-contract/dist/domains/dsk.js +2 -0
- package/backend-contract/dist/domains/ek.d.ts +186 -0
- package/backend-contract/dist/domains/ek.js +8 -0
- package/backend-contract/dist/domains/ephemeris.d.ts +141 -3
- package/backend-contract/dist/domains/error.d.ts +42 -0
- package/backend-contract/dist/domains/error.js +33 -0
- package/backend-contract/dist/domains/file-io.d.ts +114 -0
- package/backend-contract/dist/domains/file-io.js +8 -0
- package/backend-contract/dist/domains/frames.d.ts +44 -4
- package/backend-contract/dist/domains/geometry-gf.d.ts +44 -0
- package/backend-contract/dist/domains/geometry-gf.js +14 -0
- package/backend-contract/dist/domains/geometry.d.ts +21 -1
- package/backend-contract/dist/domains/ids-names-normalize.d.ts +3 -0
- package/backend-contract/dist/domains/ids-names-normalize.js +74 -0
- package/backend-contract/dist/domains/ids-names.d.ts +37 -0
- package/backend-contract/dist/domains/kernel-pool.d.ts +134 -0
- package/backend-contract/dist/domains/kernel-pool.js +2 -0
- package/backend-contract/dist/domains/kernels-utils.d.ts +44 -0
- package/backend-contract/dist/domains/kernels-utils.js +265 -0
- package/backend-contract/dist/domains/kernels.d.ts +39 -3
- package/backend-contract/dist/domains/time.d.ts +102 -0
- package/backend-contract/dist/index.d.ts +34 -15
- package/backend-contract/dist/index.js +15 -1
- package/backend-contract/dist/shared/errors.d.ts +6 -0
- package/backend-contract/dist/shared/errors.js +8 -0
- package/backend-contract/dist/shared/mat3.d.ts +52 -0
- package/backend-contract/dist/shared/mat3.js +150 -0
- package/backend-contract/dist/shared/mat6.d.ts +34 -0
- package/backend-contract/dist/shared/mat6.js +116 -0
- package/backend-contract/dist/shared/spice-handles.d.ts +20 -0
- package/backend-contract/dist/shared/spice-handles.js +82 -0
- package/backend-contract/dist/shared/spice-int.d.ts +32 -0
- package/backend-contract/dist/shared/spice-int.js +41 -0
- package/backend-contract/dist/shared/types.d.ts +136 -5
- package/backend-contract/dist/shared/types.js +1 -1
- package/backend-contract/dist/shared/vec.d.ts +54 -0
- package/backend-contract/dist/shared/vec.js +162 -0
- package/backend-fake/dist/.tsbuildinfo +1 -1
- package/backend-fake/dist/index.d.ts +21 -1
- package/backend-fake/dist/index.js +1112 -33
- package/backend-node/dist/.tsbuildinfo +1 -1
- package/backend-node/dist/codec/arrays.d.ts +9 -0
- package/backend-node/dist/codec/arrays.js +36 -0
- package/backend-node/dist/codec/errors.d.ts +6 -6
- package/backend-node/dist/codec/errors.js +6 -6
- package/backend-node/dist/domains/cells-windows.d.ts +5 -0
- package/backend-node/dist/domains/cells-windows.js +112 -0
- package/backend-node/dist/domains/coords-vectors.d.ts +1 -0
- package/backend-node/dist/domains/coords-vectors.js +66 -0
- package/backend-node/dist/domains/dsk.d.ts +6 -0
- package/backend-node/dist/domains/dsk.js +108 -0
- package/backend-node/dist/domains/ek.d.ts +10 -0
- package/backend-node/dist/domains/ek.js +100 -0
- package/backend-node/dist/domains/ephemeris.d.ts +5 -1
- package/backend-node/dist/domains/ephemeris.js +150 -1
- package/backend-node/dist/domains/error.d.ts +5 -0
- package/backend-node/dist/domains/error.js +34 -0
- package/backend-node/dist/domains/file-io.d.ts +7 -0
- package/backend-node/dist/domains/file-io.js +105 -0
- package/backend-node/dist/domains/frames.d.ts +1 -0
- package/backend-node/dist/domains/frames.js +58 -6
- package/backend-node/dist/domains/geometry-gf.d.ts +5 -0
- package/backend-node/dist/domains/geometry-gf.js +74 -0
- package/backend-node/dist/domains/geometry.d.ts +1 -0
- package/backend-node/dist/domains/geometry.js +62 -0
- package/backend-node/dist/domains/ids-names.d.ts +2 -1
- package/backend-node/dist/domains/ids-names.js +30 -0
- package/backend-node/dist/domains/kernel-pool.d.ts +5 -0
- package/backend-node/dist/domains/kernel-pool.js +74 -0
- package/backend-node/dist/domains/kernels.d.ts +1 -0
- package/backend-node/dist/domains/kernels.js +100 -13
- package/backend-node/dist/domains/time.d.ts +1 -0
- package/backend-node/dist/domains/time.js +75 -1
- package/backend-node/dist/index.d.ts +5 -1
- package/backend-node/dist/index.js +62 -1
- package/backend-node/dist/lowlevel/binding.d.ts +3 -0
- package/backend-node/dist/lowlevel/binding.js +115 -0
- package/backend-node/dist/runtime/addon.d.ts +273 -2
- package/backend-node/dist/runtime/addon.js +3 -0
- package/backend-node/dist/runtime/kernel-staging.d.ts +17 -0
- package/backend-node/dist/runtime/kernel-staging.js +80 -7
- package/backend-node/dist/runtime/spice-handles.d.ts +3 -0
- package/backend-node/dist/runtime/spice-handles.js +2 -0
- package/backend-node/dist/runtime/virtual-output-staging.d.ts +16 -0
- package/backend-node/dist/runtime/virtual-output-staging.js +148 -0
- package/backend-wasm/dist/.tsbuildinfo +1 -1
- package/backend-wasm/dist/codec/alloc.d.ts +19 -0
- package/backend-wasm/dist/codec/alloc.js +64 -0
- package/backend-wasm/dist/codec/calls.d.ts +2 -0
- package/backend-wasm/dist/codec/calls.js +13 -24
- package/backend-wasm/dist/codec/errors.d.ts +6 -0
- package/backend-wasm/dist/codec/errors.js +34 -2
- package/backend-wasm/dist/codec/found.d.ts +2 -0
- package/backend-wasm/dist/codec/found.js +20 -43
- package/backend-wasm/dist/codec/strings.d.ts +31 -1
- package/backend-wasm/dist/codec/strings.js +93 -6
- package/backend-wasm/dist/domains/cells-windows.d.ts +9 -0
- package/backend-wasm/dist/domains/cells-windows.js +392 -0
- package/backend-wasm/dist/domains/coords-vectors.d.ts +1 -0
- package/backend-wasm/dist/domains/coords-vectors.js +377 -184
- package/backend-wasm/dist/domains/dsk.d.ts +6 -0
- package/backend-wasm/dist/domains/dsk.js +179 -0
- package/backend-wasm/dist/domains/ek.d.ts +6 -0
- package/backend-wasm/dist/domains/ek.js +543 -0
- package/backend-wasm/dist/domains/ephemeris.d.ts +4 -1
- package/backend-wasm/dist/domains/ephemeris.js +405 -46
- package/backend-wasm/dist/domains/error.d.ts +5 -0
- package/backend-wasm/dist/domains/error.js +109 -0
- package/backend-wasm/dist/domains/file-io.d.ts +7 -0
- package/backend-wasm/dist/domains/file-io.js +462 -0
- package/backend-wasm/dist/domains/frames.d.ts +1 -0
- package/backend-wasm/dist/domains/frames.js +139 -6
- package/backend-wasm/dist/domains/geometry-gf.d.ts +5 -0
- package/backend-wasm/dist/domains/geometry-gf.js +178 -0
- package/backend-wasm/dist/domains/geometry.d.ts +1 -0
- package/backend-wasm/dist/domains/geometry.js +210 -0
- package/backend-wasm/dist/domains/ids-names.d.ts +2 -1
- package/backend-wasm/dist/domains/ids-names.js +89 -0
- package/backend-wasm/dist/domains/kernel-pool.d.ts +5 -0
- package/backend-wasm/dist/domains/kernel-pool.js +357 -0
- package/backend-wasm/dist/domains/kernels.d.ts +1 -0
- package/backend-wasm/dist/domains/kernels.js +108 -4
- package/backend-wasm/dist/domains/time.d.ts +2 -0
- package/backend-wasm/dist/domains/time.js +235 -133
- package/backend-wasm/dist/index.d.ts +4 -2
- package/backend-wasm/dist/lowlevel/exports.d.ts +215 -1
- package/backend-wasm/dist/lowlevel/exports.js +217 -38
- package/backend-wasm/dist/runtime/create-backend-options.d.ts +21 -0
- package/backend-wasm/dist/runtime/create-backend.node.d.ts +11 -2
- package/backend-wasm/dist/runtime/create-backend.node.js +283 -14
- package/backend-wasm/dist/runtime/create-backend.web.d.ts +5 -2
- package/backend-wasm/dist/runtime/create-backend.web.js +40 -6
- package/backend-wasm/dist/runtime/fs.d.ts +6 -0
- package/backend-wasm/dist/runtime/fs.js +29 -3
- package/backend-wasm/dist/runtime/spice-handles.d.ts +3 -0
- package/backend-wasm/dist/runtime/spice-handles.js +2 -0
- package/backend-wasm/dist/runtime/virtual-outputs.d.ts +16 -0
- package/backend-wasm/dist/runtime/virtual-outputs.js +35 -0
- package/backend-wasm/dist/tspice_backend_wasm.node.js +3 -3
- package/backend-wasm/dist/tspice_backend_wasm.wasm +0 -0
- package/backend-wasm/dist/tspice_backend_wasm.web.js +1 -1
- package/core/dist/.tsbuildinfo +1 -1
- package/core/dist/index.d.ts +21 -0
- package/core/dist/index.js +57 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/backend.d.ts +15 -6
- package/dist/backend.js +3 -6
- package/dist/clients/createSpiceAsyncFromTransport.d.ts +5 -0
- package/dist/clients/createSpiceAsyncFromTransport.js +90 -0
- package/dist/clients/createSpiceSyncFromTransport.d.ts +5 -0
- package/dist/clients/createSpiceSyncFromTransport.js +88 -0
- package/dist/clients/spiceClients.d.ts +59 -0
- package/dist/clients/spiceClients.js +292 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +4 -0
- package/dist/index.d.ts +12 -7
- package/dist/index.js +5 -2
- package/dist/kernels/defaultKernelPathFromUrl.d.ts +8 -0
- package/dist/kernels/defaultKernelPathFromUrl.js +32 -0
- package/dist/kernels/kernelPack.d.ts +88 -0
- package/dist/kernels/kernelPack.js +122 -0
- package/dist/kernels/kernels.d.ts +98 -0
- package/dist/kernels/kernels.js +217 -0
- package/dist/kernels/naifKernelId.d.ts +2 -0
- package/dist/kernels/naifKernelId.js +2 -0
- package/dist/kit/index.d.ts +4 -0
- package/dist/kit/index.js +3 -0
- package/dist/kit/math/mat3.d.ts +31 -0
- package/dist/kit/math/mat3.js +82 -0
- package/dist/kit/spice/create-kit.d.ts +12 -0
- package/dist/kit/spice/create-kit.js +23 -0
- package/dist/kit/spice/frames.d.ts +8 -0
- package/dist/kit/spice/frames.js +16 -0
- package/dist/kit/spice/kernels.d.ts +14 -0
- package/dist/kit/spice/kernels.js +39 -0
- package/dist/kit/spice/state.d.ts +7 -0
- package/dist/kit/spice/state.js +36 -0
- package/dist/kit/spice/time.d.ts +9 -0
- package/dist/kit/spice/time.js +31 -0
- package/dist/kit/types/spice-types.d.ts +51 -0
- package/dist/spice.d.ts +10 -1
- package/dist/spice.js +84 -72
- package/dist/transport/caching/policy.d.ts +16 -0
- package/dist/transport/caching/policy.js +77 -0
- package/dist/transport/caching/withCaching.d.ts +125 -0
- package/dist/transport/caching/withCaching.js +335 -0
- package/dist/transport/caching/withCachingSync.d.ts +24 -0
- package/dist/transport/caching/withCachingSync.js +161 -0
- package/dist/transport/rpc/protocol.d.ts +35 -0
- package/dist/transport/rpc/protocol.js +56 -0
- package/dist/transport/rpc/taskScheduling.d.ts +20 -0
- package/dist/transport/rpc/taskScheduling.js +98 -0
- package/dist/transport/rpc/valueCodec.d.ts +5 -0
- package/dist/transport/rpc/valueCodec.js +106 -0
- package/dist/transport/types.d.ts +7 -0
- package/dist/transport/types.js +2 -0
- package/dist/types.d.ts +8 -17
- package/dist/types.js +2 -1
- package/dist/worker/browser/createSpiceWorker.d.ts +22 -0
- package/dist/worker/browser/createSpiceWorker.js +41 -0
- package/dist/worker/browser/createSpiceWorkerClient.d.ts +40 -0
- package/dist/worker/browser/createSpiceWorkerClient.js +99 -0
- package/dist/worker/browser/spiceWorkerEntry.d.ts +2 -0
- package/dist/worker/browser/spiceWorkerEntry.js +129 -0
- package/dist/worker/browser/spiceWorkerInlineSource.d.ts +2 -0
- package/dist/worker/browser/spiceWorkerInlineSource.js +4 -0
- package/dist/worker/index.d.ts +10 -0
- package/dist/worker/index.js +7 -0
- package/dist/worker/transport/createWorkerTransport.d.ts +69 -0
- package/dist/worker/transport/createWorkerTransport.js +398 -0
- package/dist/worker/transport/exposeTransportToWorker.d.ts +51 -0
- package/dist/worker/transport/exposeTransportToWorker.js +196 -0
- package/package.json +4 -4
- package/dist/spice-types.d.ts +0 -36
- /package/dist/{spice-types.js → kit/types/spice-types.js} +0 -0
|
@@ -1,20 +1,157 @@
|
|
|
1
1
|
import { assertEmscriptenModule } from "../lowlevel/exports.js";
|
|
2
2
|
import { createCoordsVectorsApi } from "../domains/coords-vectors.js";
|
|
3
|
+
import { createCellsWindowsApi } from "../domains/cells-windows.js";
|
|
3
4
|
import { createEphemerisApi } from "../domains/ephemeris.js";
|
|
4
5
|
import { createFramesApi } from "../domains/frames.js";
|
|
5
6
|
import { createGeometryApi } from "../domains/geometry.js";
|
|
7
|
+
import { createGeometryGfApi } from "../domains/geometry-gf.js";
|
|
6
8
|
import { createIdsNamesApi } from "../domains/ids-names.js";
|
|
7
9
|
import { createKernelsApi } from "../domains/kernels.js";
|
|
10
|
+
import { createKernelPoolApi } from "../domains/kernel-pool.js";
|
|
8
11
|
import { createTimeApi, getToolkitVersion } from "../domains/time.js";
|
|
12
|
+
import { createFileIoApi } from "../domains/file-io.js";
|
|
13
|
+
import { createErrorApi } from "../domains/error.js";
|
|
14
|
+
import { createDskApi } from "../domains/dsk.js";
|
|
15
|
+
import { createEkApi } from "../domains/ek.js";
|
|
9
16
|
import { createWasmFs } from "./fs.js";
|
|
17
|
+
import { createSpiceHandleRegistry } from "./spice-handles.js";
|
|
18
|
+
import { createVirtualOutputRegistry } from "./virtual-outputs.js";
|
|
10
19
|
export const WASM_JS_FILENAME = "tspice_backend_wasm.node.js";
|
|
11
20
|
export const WASM_BINARY_FILENAME = "tspice_backend_wasm.wasm";
|
|
21
|
+
// Cache wasm binaries by URL to avoid repeated (sometimes flaky) disk reads.
|
|
22
|
+
//
|
|
23
|
+
// This cache MUST be bounded: in long-lived processes that construct backends
|
|
24
|
+
// dynamically (or accept user-provided URLs), an unbounded cache would retain
|
|
25
|
+
// bytes indefinitely.
|
|
26
|
+
const WASM_BINARY_CACHE_MAX_ENTRIES = 2;
|
|
27
|
+
const wasmBinaryCache = new Map();
|
|
28
|
+
function boundedCacheGet(key) {
|
|
29
|
+
const hit = wasmBinaryCache.get(key);
|
|
30
|
+
if (!hit)
|
|
31
|
+
return undefined;
|
|
32
|
+
// Refresh recency (Map preserves insertion order).
|
|
33
|
+
wasmBinaryCache.delete(key);
|
|
34
|
+
wasmBinaryCache.set(key, hit);
|
|
35
|
+
return hit;
|
|
36
|
+
}
|
|
37
|
+
function boundedCacheSet(key, value) {
|
|
38
|
+
// Refresh recency on overwrite (Map preserves insertion order).
|
|
39
|
+
wasmBinaryCache.delete(key);
|
|
40
|
+
wasmBinaryCache.set(key, value);
|
|
41
|
+
while (wasmBinaryCache.size > WASM_BINARY_CACHE_MAX_ENTRIES) {
|
|
42
|
+
const lruKey = wasmBinaryCache.keys().next().value;
|
|
43
|
+
if (!lruKey)
|
|
44
|
+
break;
|
|
45
|
+
wasmBinaryCache.delete(lruKey);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert a Uint8Array view into an exact-length ArrayBuffer (copying only when needed).
|
|
50
|
+
*/
|
|
51
|
+
export function toExactArrayBuffer(bytes) {
|
|
52
|
+
// Fast-path: if this view covers the whole underlying buffer, return it
|
|
53
|
+
// directly (no copy).
|
|
54
|
+
// NOTE: `Uint8Array#buffer` is typed as `ArrayBufferLike` (can be
|
|
55
|
+
// `SharedArrayBuffer`). We only want to return an actual `ArrayBuffer`.
|
|
56
|
+
if (bytes.buffer instanceof ArrayBuffer &&
|
|
57
|
+
bytes.byteOffset === 0 &&
|
|
58
|
+
bytes.byteLength === bytes.buffer.byteLength) {
|
|
59
|
+
return bytes.buffer;
|
|
60
|
+
}
|
|
61
|
+
// Node Buffers can be views into a larger ArrayBuffer (and can be offset).
|
|
62
|
+
// Passing `bytes.buffer` directly can include unrelated trailing bytes, and
|
|
63
|
+
// getting `ArrayBuffer#slice` bounds wrong can truncate the module.
|
|
64
|
+
//
|
|
65
|
+
// Copy into a fresh, exact-length ArrayBuffer starting at 0.
|
|
66
|
+
const uint8 = new Uint8Array(bytes.byteLength);
|
|
67
|
+
uint8.set(bytes);
|
|
68
|
+
return uint8.buffer;
|
|
69
|
+
}
|
|
70
|
+
/** Read the tspice backend WASM binary from disk (Node-only). */
|
|
71
|
+
export async function readWasmBinaryForNode(wasmUrl) {
|
|
72
|
+
// Allow http(s) URLs to be fetched by Emscripten.
|
|
73
|
+
if (wasmUrl.startsWith("http://") || wasmUrl.startsWith("https://")) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const WINDOWS_DRIVE_PATH_RE = /^[A-Za-z]:[\\/]/;
|
|
77
|
+
const URL_SCHEME_WITH_AUTHORITY_RE = /^[A-Za-z][A-Za-z\d+.-]*:\/\//;
|
|
78
|
+
const SINGLE_LETTER_SCHEME_RE = /^[A-Za-z]:/;
|
|
79
|
+
const [{ readFile }, { fileURLToPath }] = await Promise.all([
|
|
80
|
+
import("node:fs/promises"),
|
|
81
|
+
import("node:url"),
|
|
82
|
+
]);
|
|
83
|
+
// In Node, treat values as filesystem paths unless they are unambiguously a
|
|
84
|
+
// URL (file://, or any other scheme://...) or a known non-fs scheme like
|
|
85
|
+
// data: or blob:.
|
|
86
|
+
const isWindowsDrivePath = WINDOWS_DRIVE_PATH_RE.test(wasmUrl);
|
|
87
|
+
const isFileUrl = wasmUrl.startsWith("file://");
|
|
88
|
+
if (!isWindowsDrivePath && !isFileUrl) {
|
|
89
|
+
if (wasmUrl.startsWith("node:")) {
|
|
90
|
+
const u = new URL(wasmUrl);
|
|
91
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, or a filesystem path.`);
|
|
92
|
+
}
|
|
93
|
+
if (wasmUrl.startsWith("blob:") || wasmUrl.startsWith("data:")) {
|
|
94
|
+
const u = new URL(wasmUrl);
|
|
95
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, or a filesystem path.`);
|
|
96
|
+
}
|
|
97
|
+
if (URL_SCHEME_WITH_AUTHORITY_RE.test(wasmUrl)) {
|
|
98
|
+
const u = new URL(wasmUrl);
|
|
99
|
+
if (u.protocol !== "http:" && u.protocol !== "https:" && u.protocol !== "file:") {
|
|
100
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, or a filesystem path.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Avoid treating `c:foo` as a Windows drive path; it's ambiguous and often a typo.
|
|
104
|
+
// But allow longer values like `foo:bar/...` to be treated as filesystem paths.
|
|
105
|
+
if (SINGLE_LETTER_SCHEME_RE.test(wasmUrl)) {
|
|
106
|
+
const u = new URL(wasmUrl);
|
|
107
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, or a filesystem path.`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const wasmPath = wasmUrl.startsWith("file://") ? fileURLToPath(wasmUrl) : wasmUrl;
|
|
111
|
+
const cached = boundedCacheGet(wasmPath);
|
|
112
|
+
if (cached) {
|
|
113
|
+
return cached;
|
|
114
|
+
}
|
|
115
|
+
const bytes = await readFile(wasmPath);
|
|
116
|
+
const buffer = toExactArrayBuffer(bytes);
|
|
117
|
+
boundedCacheSet(wasmPath, buffer);
|
|
118
|
+
return buffer;
|
|
119
|
+
}
|
|
120
|
+
/** Create a {@link SpiceBackend} implementation backed by WASM (Node runtime). */
|
|
12
121
|
export async function createWasmBackend(options = {}) {
|
|
13
122
|
// NOTE: Keep this as a literal string so bundlers (Vite) don't generate a
|
|
14
123
|
// runtime glob map for *every* file in this directory (including *.d.ts.map),
|
|
15
124
|
// which can lead to JSON being imported as an ESM module.
|
|
16
|
-
const
|
|
17
|
-
const
|
|
125
|
+
const wasmUrl = options.wasmUrl?.toString() ?? new URL("../tspice_backend_wasm.wasm", import.meta.url).href;
|
|
126
|
+
const WINDOWS_DRIVE_PATH_RE = /^[A-Za-z]:[\\/]/;
|
|
127
|
+
const URL_SCHEME_WITH_AUTHORITY_RE = /^[A-Za-z][A-Za-z\d+.-]*:\/\//;
|
|
128
|
+
const SINGLE_LETTER_SCHEME_RE = /^[A-Za-z]:/;
|
|
129
|
+
const isWindowsDrivePath = (value) => WINDOWS_DRIVE_PATH_RE.test(value);
|
|
130
|
+
const isAllowedNodeUrl = (value) => value.startsWith("http://") || value.startsWith("https://") || value.startsWith("file://");
|
|
131
|
+
const throwUnsupportedScheme = (u) => {
|
|
132
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, or a filesystem path.`);
|
|
133
|
+
};
|
|
134
|
+
// In Node, treat values as filesystem paths unless they are unambiguously a
|
|
135
|
+
// URL (http(s)://, file://, or any other scheme://...) or a known non-fs
|
|
136
|
+
// scheme like data: or blob:.
|
|
137
|
+
if (!isWindowsDrivePath(wasmUrl) && !isAllowedNodeUrl(wasmUrl)) {
|
|
138
|
+
if (wasmUrl.startsWith("node:")) {
|
|
139
|
+
throwUnsupportedScheme(new URL(wasmUrl));
|
|
140
|
+
}
|
|
141
|
+
if (wasmUrl.startsWith("blob:") || wasmUrl.startsWith("data:")) {
|
|
142
|
+
throwUnsupportedScheme(new URL(wasmUrl));
|
|
143
|
+
}
|
|
144
|
+
if (URL_SCHEME_WITH_AUTHORITY_RE.test(wasmUrl)) {
|
|
145
|
+
const u = new URL(wasmUrl);
|
|
146
|
+
if (u.protocol !== "http:" && u.protocol !== "https:" && u.protocol !== "file:") {
|
|
147
|
+
throwUnsupportedScheme(u);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Avoid treating `c:foo` as a Windows drive path; it's ambiguous and often a typo.
|
|
151
|
+
if (SINGLE_LETTER_SCHEME_RE.test(wasmUrl)) {
|
|
152
|
+
throwUnsupportedScheme(new URL(wasmUrl));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
18
155
|
let createEmscriptenModule;
|
|
19
156
|
try {
|
|
20
157
|
// NOTE: This must be a literal import path so bundlers like Vite don't
|
|
@@ -26,18 +163,138 @@ export async function createWasmBackend(options = {}) {
|
|
|
26
163
|
throw new Error(`Failed to load tspice WASM glue (../${WASM_JS_FILENAME}): ${String(error)}`);
|
|
27
164
|
}
|
|
28
165
|
const wasmLocator = wasmUrl;
|
|
29
|
-
// Node's
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
166
|
+
// In Node, avoid Emscripten's fetch/instantiateStreaming path for:
|
|
167
|
+
// - `file://...` URLs (Node's built-in `fetch` can't load them)
|
|
168
|
+
// - plain filesystem paths when `fetch` exists
|
|
169
|
+
// Instead, feed the bytes directly to Emscripten via `wasmBinary`.
|
|
170
|
+
let wasmBinary;
|
|
171
|
+
if (wasmUrl.startsWith("file://")) {
|
|
172
|
+
wasmBinary = await (async () => {
|
|
173
|
+
const [{ readFileSync, statSync }, { writeFile, rename }, { fileURLToPath }] = await Promise.all([
|
|
174
|
+
import("node:fs"),
|
|
34
175
|
import("node:fs/promises"),
|
|
35
176
|
import("node:url"),
|
|
36
177
|
]);
|
|
178
|
+
const usingDefaultWasmUrl = options.wasmUrl == null;
|
|
37
179
|
const wasmPath = fileURLToPath(wasmUrl);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
180
|
+
// `WebAssembly` is available in Node, but TypeScript only types it when the DOM
|
|
181
|
+
// lib is enabled. Define a minimal type so we can use `validate()` without pulling
|
|
182
|
+
// in browser-only lib typings.
|
|
183
|
+
const wasmApi = globalThis.WebAssembly;
|
|
184
|
+
const assertMagicHeader = (bytes, path, urlForMessage) => {
|
|
185
|
+
// Validate WASM magic header: 0x00 0x61 0x73 0x6d ("\0asm")
|
|
186
|
+
if (bytes.length < 4 ||
|
|
187
|
+
bytes[0] !== 0x00 ||
|
|
188
|
+
bytes[1] !== 0x61 ||
|
|
189
|
+
bytes[2] !== 0x73 ||
|
|
190
|
+
bytes[3] !== 0x6d) {
|
|
191
|
+
const prefixBytes = bytes.slice(0, Math.min(8, bytes.length));
|
|
192
|
+
const prefix = Array.from(prefixBytes)
|
|
193
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
194
|
+
.join(" ");
|
|
195
|
+
throw new Error(`Invalid WASM magic header for ${path} (url=${urlForMessage}). ` +
|
|
196
|
+
`Expected 00 61 73 6d ("\\0asm") but got ${prefix}${bytes.length > 8 ? " ..." : ""}.`);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const isValidWasm = (bytes) => {
|
|
200
|
+
// Fail fast on truncated/corrupt cache restores.
|
|
201
|
+
//
|
|
202
|
+
// If `validate()` is unavailable (or stubbed), assume valid and let
|
|
203
|
+
// instantiation fail with a real error.
|
|
204
|
+
if (!wasmApi?.validate) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return wasmApi.validate(bytes);
|
|
208
|
+
};
|
|
209
|
+
const readBytesWithSizeCheck = (path, urlForMessage) => {
|
|
210
|
+
const readWithSizeCheck = () => {
|
|
211
|
+
const bytes = readFileSync(path);
|
|
212
|
+
const statSize = statSync(path).size;
|
|
213
|
+
return { bytes, statSize };
|
|
214
|
+
};
|
|
215
|
+
// On macOS + Node 22 we've occasionally observed truncated reads leading to
|
|
216
|
+
// `WebAssembly.instantiate(): section ... extends past end of the module`.
|
|
217
|
+
// Sync reads + a size sanity-check seems to avoid the issue.
|
|
218
|
+
let { bytes, statSize } = readWithSizeCheck();
|
|
219
|
+
if (bytes.length !== statSize) {
|
|
220
|
+
const firstReadSize = bytes.length;
|
|
221
|
+
const firstStatSize = statSize;
|
|
222
|
+
({ bytes, statSize } = readWithSizeCheck());
|
|
223
|
+
if (bytes.length !== statSize) {
|
|
224
|
+
throw new Error(`WASM binary read size mismatch for ${path} (url=${urlForMessage}): ` +
|
|
225
|
+
`readFileSync().length=${bytes.length} (previous=${firstReadSize}) ` +
|
|
226
|
+
`statSync().size=${statSize} (previous=${firstStatSize}). ` +
|
|
227
|
+
`This may indicate a transient/inconsistent filesystem state.`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return bytes;
|
|
231
|
+
};
|
|
232
|
+
let lastValidationError;
|
|
233
|
+
async function readValidatedOrNull(path, urlForMessage) {
|
|
234
|
+
try {
|
|
235
|
+
const bytes = readBytesWithSizeCheck(path, urlForMessage);
|
|
236
|
+
assertMagicHeader(bytes, path, urlForMessage);
|
|
237
|
+
if (!isValidWasm(bytes)) {
|
|
238
|
+
throw new Error(`WebAssembly.validate() returned false for ${path} (url=${urlForMessage}).`);
|
|
239
|
+
}
|
|
240
|
+
return bytes;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
lastValidationError = error;
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// If turbo restores `dist/**` from cache, downstream tasks can sometimes observe
|
|
248
|
+
// partially-written outputs. Validate the wasm bytes before loading.
|
|
249
|
+
const initial = await readValidatedOrNull(wasmPath, wasmUrl);
|
|
250
|
+
if (initial) {
|
|
251
|
+
return initial;
|
|
252
|
+
}
|
|
253
|
+
// Retry briefly in case another process is still writing/restoring the file.
|
|
254
|
+
for (const delayMs of [10, 25, 50, 100, 250]) {
|
|
255
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
256
|
+
const retry = await readValidatedOrNull(wasmPath, wasmUrl);
|
|
257
|
+
if (retry) {
|
|
258
|
+
return retry;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// If the dist wasm is still invalid, fall back to the checked-in Emscripten artifact
|
|
262
|
+
// when running from a workspace checkout.
|
|
263
|
+
if (usingDefaultWasmUrl) {
|
|
264
|
+
const fallbackUrl = new URL(`../../emscripten/${WASM_BINARY_FILENAME}`, import.meta.url);
|
|
265
|
+
const fallbackPath = fileURLToPath(fallbackUrl);
|
|
266
|
+
const fallback = await readValidatedOrNull(fallbackPath, fallbackUrl.href);
|
|
267
|
+
if (fallback) {
|
|
268
|
+
if (options.repairInvalidDistWasm === true) {
|
|
269
|
+
// Best-effort repair so subsequent loads see a valid file.
|
|
270
|
+
try {
|
|
271
|
+
const tmpPath = `${wasmPath}.tmp.${process.pid}.${Date.now()}`;
|
|
272
|
+
await writeFile(tmpPath, fallback);
|
|
273
|
+
await rename(tmpPath, wasmPath);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// ignore
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return fallback;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const message = `Invalid/partial WASM binary at ${wasmPath}. ` +
|
|
283
|
+
`Try rerunning backend-wasm build (pnpm -C packages/backend-wasm build) ` +
|
|
284
|
+
`or ensure turbo cache outputs are fully restored before tests run.`;
|
|
285
|
+
throw lastValidationError != null
|
|
286
|
+
? new Error(message, { cause: lastValidationError })
|
|
287
|
+
: new Error(message);
|
|
288
|
+
})();
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
try {
|
|
292
|
+
wasmBinary = await readWasmBinaryForNode(wasmUrl);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
throw new Error(`Failed to read tspice WASM binary (wasmUrl=${wasmUrl}): ${String(error)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
41
298
|
let module;
|
|
42
299
|
try {
|
|
43
300
|
module = (await createEmscriptenModule({
|
|
@@ -53,21 +310,33 @@ export async function createWasmBackend(options = {}) {
|
|
|
53
310
|
catch (error) {
|
|
54
311
|
throw new Error(`Failed to initialize tspice WASM module (wasmUrl=${wasmUrl}): ${String(error)}`);
|
|
55
312
|
}
|
|
56
|
-
|
|
313
|
+
const skipAssertViaEnv = process.env.TSPICE_WASM_SKIP_EMSCRIPTEN_ASSERT === "1" ||
|
|
314
|
+
process.env.TSPICE_WASM_SKIP_EMSCRIPTEN_ASSERT === "true";
|
|
315
|
+
const validateEmscriptenModule = options.validateEmscriptenModule ?? !skipAssertViaEnv;
|
|
316
|
+
if (validateEmscriptenModule) {
|
|
317
|
+
assertEmscriptenModule(module);
|
|
318
|
+
}
|
|
57
319
|
// The toolkit version is constant for the lifetime of a loaded module.
|
|
58
320
|
const toolkitVersion = getToolkitVersion(module);
|
|
59
321
|
const fsApi = createWasmFs(module);
|
|
322
|
+
const spiceHandles = createSpiceHandleRegistry();
|
|
323
|
+
const virtualOutputs = createVirtualOutputRegistry();
|
|
60
324
|
const backend = {
|
|
61
325
|
kind: "wasm",
|
|
62
326
|
...createTimeApi(module, toolkitVersion),
|
|
63
327
|
...createKernelsApi(module, fsApi),
|
|
328
|
+
...createKernelPoolApi(module),
|
|
64
329
|
...createIdsNamesApi(module),
|
|
65
330
|
...createFramesApi(module),
|
|
66
|
-
...createEphemerisApi(module),
|
|
331
|
+
...createEphemerisApi(module, spiceHandles, virtualOutputs),
|
|
67
332
|
...createGeometryApi(module),
|
|
333
|
+
...createGeometryGfApi(module),
|
|
68
334
|
...createCoordsVectorsApi(module),
|
|
69
|
-
|
|
70
|
-
...
|
|
335
|
+
...createFileIoApi(module, spiceHandles, virtualOutputs),
|
|
336
|
+
...createErrorApi(module),
|
|
337
|
+
...createCellsWindowsApi(module),
|
|
338
|
+
...createEkApi(module, spiceHandles),
|
|
339
|
+
...createDskApi(module, spiceHandles),
|
|
71
340
|
};
|
|
72
341
|
return backend;
|
|
73
342
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SpiceBackend } from "#backend-contract";
|
|
2
2
|
export type { CreateWasmBackendOptions } from "./create-backend-options.js";
|
|
3
3
|
import type { CreateWasmBackendOptions } from "./create-backend-options.js";
|
|
4
4
|
export declare const WASM_JS_FILENAME: "tspice_backend_wasm.web.js";
|
|
5
5
|
export declare const WASM_BINARY_FILENAME: "tspice_backend_wasm.wasm";
|
|
6
|
-
|
|
6
|
+
/** Create a {@link SpiceBackend} implementation backed by WASM (web/runtime loader). */
|
|
7
|
+
export declare function createWasmBackend(options?: CreateWasmBackendOptions): Promise<SpiceBackend & {
|
|
8
|
+
kind: "wasm";
|
|
9
|
+
}>;
|
|
7
10
|
//# sourceMappingURL=create-backend.web.d.ts.map
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { assertEmscriptenModule } from "../lowlevel/exports.js";
|
|
2
2
|
import { createCoordsVectorsApi } from "../domains/coords-vectors.js";
|
|
3
|
+
import { createCellsWindowsApi } from "../domains/cells-windows.js";
|
|
3
4
|
import { createEphemerisApi } from "../domains/ephemeris.js";
|
|
4
5
|
import { createFramesApi } from "../domains/frames.js";
|
|
5
6
|
import { createGeometryApi } from "../domains/geometry.js";
|
|
7
|
+
import { createGeometryGfApi } from "../domains/geometry-gf.js";
|
|
6
8
|
import { createIdsNamesApi } from "../domains/ids-names.js";
|
|
7
9
|
import { createKernelsApi } from "../domains/kernels.js";
|
|
10
|
+
import { createKernelPoolApi } from "../domains/kernel-pool.js";
|
|
8
11
|
import { createTimeApi, getToolkitVersion } from "../domains/time.js";
|
|
12
|
+
import { createFileIoApi } from "../domains/file-io.js";
|
|
13
|
+
import { createErrorApi } from "../domains/error.js";
|
|
14
|
+
import { createDskApi } from "../domains/dsk.js";
|
|
15
|
+
import { createEkApi } from "../domains/ek.js";
|
|
9
16
|
import { createWasmFs } from "./fs.js";
|
|
17
|
+
import { createSpiceHandleRegistry } from "./spice-handles.js";
|
|
18
|
+
import { createVirtualOutputRegistry } from "./virtual-outputs.js";
|
|
10
19
|
export const WASM_JS_FILENAME = "tspice_backend_wasm.web.js";
|
|
11
20
|
export const WASM_BINARY_FILENAME = "tspice_backend_wasm.wasm";
|
|
21
|
+
/** Create a {@link SpiceBackend} implementation backed by WASM (web/runtime loader). */
|
|
12
22
|
export async function createWasmBackend(options = {}) {
|
|
13
23
|
// NOTE: Keep this as a literal string so bundlers (Vite) don't generate a
|
|
14
24
|
// runtime glob map for *every* file in this directory (including *.d.ts.map),
|
|
15
25
|
// which can lead to JSON being imported as an ESM module.
|
|
16
|
-
const
|
|
17
|
-
|
|
26
|
+
const wasmUrl = options.wasmUrl?.toString() ??
|
|
27
|
+
// NOTE: Inline blob workers set `options.wasmUrl` explicitly, since
|
|
28
|
+
// `import.meta.url` will be `blob:` for the worker entry module.
|
|
29
|
+
new URL("../tspice_backend_wasm.wasm", import.meta.url).href;
|
|
30
|
+
const URL_SCHEME_RE = /^[A-Za-z][A-Za-z\d+.-]*:/;
|
|
31
|
+
const WINDOWS_DRIVE_PATH_RE = /^[A-Za-z]:[\\/]/;
|
|
32
|
+
const hasUrlScheme = (value) => URL_SCHEME_RE.test(value) && !WINDOWS_DRIVE_PATH_RE.test(value);
|
|
33
|
+
if (hasUrlScheme(wasmUrl)) {
|
|
34
|
+
const u = new URL(wasmUrl);
|
|
35
|
+
// In web builds, `blob:` URLs are a real-world possibility (some bundlers and
|
|
36
|
+
// runtime loaders produce them). `data:` is also generally fetchable.
|
|
37
|
+
const allowedProtocols = new Set(["http:", "https:", "file:", "blob:", "data:"]);
|
|
38
|
+
if (!allowedProtocols.has(u.protocol)) {
|
|
39
|
+
throw new Error(`Unsupported wasmUrl scheme '${u.protocol}'. Expected http(s) URL, file:// URL, blob: URL, data: URL, or a filesystem path.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
18
42
|
let createEmscriptenModule;
|
|
19
43
|
try {
|
|
20
44
|
// NOTE: This must be a literal import path so bundlers like Vite don't
|
|
@@ -40,21 +64,31 @@ export async function createWasmBackend(options = {}) {
|
|
|
40
64
|
catch (error) {
|
|
41
65
|
throw new Error(`Failed to initialize tspice WASM module (wasmUrl=${wasmUrl}): ${String(error)}`);
|
|
42
66
|
}
|
|
43
|
-
|
|
67
|
+
const validateEmscriptenModule = options.validateEmscriptenModule ?? true;
|
|
68
|
+
if (validateEmscriptenModule) {
|
|
69
|
+
assertEmscriptenModule(module);
|
|
70
|
+
}
|
|
44
71
|
// The toolkit version is constant for the lifetime of a loaded module.
|
|
45
72
|
const toolkitVersion = getToolkitVersion(module);
|
|
46
73
|
const fsApi = createWasmFs(module);
|
|
74
|
+
const spiceHandles = createSpiceHandleRegistry();
|
|
75
|
+
const virtualOutputs = createVirtualOutputRegistry();
|
|
47
76
|
const backend = {
|
|
48
77
|
kind: "wasm",
|
|
49
78
|
...createTimeApi(module, toolkitVersion),
|
|
50
79
|
...createKernelsApi(module, fsApi),
|
|
80
|
+
...createKernelPoolApi(module),
|
|
51
81
|
...createIdsNamesApi(module),
|
|
52
82
|
...createFramesApi(module),
|
|
53
|
-
...createEphemerisApi(module),
|
|
83
|
+
...createEphemerisApi(module, spiceHandles, virtualOutputs),
|
|
54
84
|
...createGeometryApi(module),
|
|
85
|
+
...createGeometryGfApi(module),
|
|
55
86
|
...createCoordsVectorsApi(module),
|
|
56
|
-
|
|
57
|
-
...
|
|
87
|
+
...createFileIoApi(module, spiceHandles, virtualOutputs),
|
|
88
|
+
...createErrorApi(module),
|
|
89
|
+
...createCellsWindowsApi(module),
|
|
90
|
+
...createEkApi(module, spiceHandles),
|
|
91
|
+
...createDskApi(module, spiceHandles),
|
|
58
92
|
};
|
|
59
93
|
return backend;
|
|
60
94
|
}
|
|
@@ -4,6 +4,12 @@ export type WasmFsApi = {
|
|
|
4
4
|
writeFile(path: string, data: Uint8Array): void;
|
|
5
5
|
loadKernel(path: string, data: Uint8Array): void;
|
|
6
6
|
};
|
|
7
|
+
/** Normalize and validate a virtual kernel path for the WASM FS (under `/kernels`). */
|
|
8
|
+
export declare function resolveKernelPath(path: string): string;
|
|
9
|
+
/** Create a minimal WASM-FS facade for writing files and loading kernels via `furnsh`. */
|
|
7
10
|
export declare function createWasmFs(module: EmscriptenModule): WasmFsApi;
|
|
11
|
+
/**
|
|
12
|
+
* Write a {@link KernelSource} into the WASM FS (if needed) and return the path to load.
|
|
13
|
+
*/
|
|
8
14
|
export declare function writeKernelSource(module: EmscriptenModule, fs: WasmFsApi, kernel: KernelSource): string;
|
|
9
15
|
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
import { normalizeVirtualKernelPath } from "#core";
|
|
1
2
|
import { tspiceCall1Path } from "../codec/calls.js";
|
|
3
|
+
/** Normalize and validate a virtual kernel path for the WASM FS (under `/kernels`). */
|
|
4
|
+
export function resolveKernelPath(path) {
|
|
5
|
+
const raw = path.trim();
|
|
6
|
+
if (!raw) {
|
|
7
|
+
throw new Error("Kernel path must be non-empty");
|
|
8
|
+
}
|
|
9
|
+
// Fail fast for common non-virtual path forms. This improves debuggability for
|
|
10
|
+
// consumers who accidentally pass OS paths/URLs to the WASM backend.
|
|
11
|
+
//
|
|
12
|
+
// Note: `/kernels/...` is an allowed virtual path form.
|
|
13
|
+
if (/^[a-zA-Z]+:/.test(raw) || // urls, `file:`, Windows drive letters, etc.
|
|
14
|
+
raw.startsWith("//") ||
|
|
15
|
+
raw.startsWith("\\\\") ||
|
|
16
|
+
(raw.startsWith("/") && !raw.startsWith("/kernels/"))) {
|
|
17
|
+
throw new Error(`WASM kernel paths must be virtual ids (e.g. "naif0012.tls"), not OS paths/URLs: ${path}`);
|
|
18
|
+
}
|
|
19
|
+
// We treat kernel paths as *virtual* WASM-FS paths under `/kernels`.
|
|
20
|
+
// Normalize to a canonical absolute path.
|
|
21
|
+
return `/kernels/${normalizeVirtualKernelPath(raw)}`;
|
|
22
|
+
}
|
|
23
|
+
/** Create a minimal WASM-FS facade for writing files and loading kernels via `furnsh`. */
|
|
2
24
|
export function createWasmFs(module) {
|
|
3
25
|
function writeFile(path, data) {
|
|
4
26
|
const dir = path.split("/").slice(0, -1).join("/") || "/";
|
|
@@ -14,17 +36,21 @@ export function createWasmFs(module) {
|
|
|
14
36
|
return {
|
|
15
37
|
writeFile,
|
|
16
38
|
loadKernel: (path, data) => {
|
|
17
|
-
const resolvedPath = path
|
|
39
|
+
const resolvedPath = resolveKernelPath(path);
|
|
18
40
|
writeFile(resolvedPath, data);
|
|
19
41
|
tspiceCall1Path(module, module._tspice_furnsh, resolvedPath);
|
|
20
42
|
},
|
|
21
43
|
};
|
|
22
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Write a {@link KernelSource} into the WASM FS (if needed) and return the path to load.
|
|
47
|
+
*/
|
|
23
48
|
export function writeKernelSource(module, fs, kernel) {
|
|
24
49
|
if (typeof kernel === "string") {
|
|
25
50
|
return kernel;
|
|
26
51
|
}
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
const resolved = resolveKernelPath(kernel.path);
|
|
53
|
+
fs.writeFile(resolved, kernel.bytes);
|
|
54
|
+
return resolved;
|
|
29
55
|
}
|
|
30
56
|
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type VirtualOutputRegistry = {
|
|
2
|
+
/** Mark an output path as opened for write by a native handle. */
|
|
3
|
+
markOpen(resolvedPath: string): void;
|
|
4
|
+
/** Mark an output path as closed (writer handle closed). */
|
|
5
|
+
markClosed(resolvedPath: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Assert that a VirtualOutput is readable.
|
|
8
|
+
*
|
|
9
|
+
* This is used to ensure `readVirtualOutput()` does not become a generic
|
|
10
|
+
* filesystem read primitive.
|
|
11
|
+
*/
|
|
12
|
+
assertReadable(resolvedPath: string, virtualPath: string): void;
|
|
13
|
+
};
|
|
14
|
+
/** Create an in-memory registry tracking open/closed virtual outputs for the WASM backend. */
|
|
15
|
+
export declare function createVirtualOutputRegistry(): VirtualOutputRegistry;
|
|
16
|
+
//# sourceMappingURL=virtual-outputs.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Create an in-memory registry tracking open/closed virtual outputs for the WASM backend. */
|
|
2
|
+
export function createVirtualOutputRegistry() {
|
|
3
|
+
const known = new Set();
|
|
4
|
+
const openRefCount = new Map();
|
|
5
|
+
return {
|
|
6
|
+
markOpen: (resolvedPath) => {
|
|
7
|
+
known.add(resolvedPath);
|
|
8
|
+
openRefCount.set(resolvedPath, (openRefCount.get(resolvedPath) ?? 0) + 1);
|
|
9
|
+
},
|
|
10
|
+
markClosed: (resolvedPath) => {
|
|
11
|
+
const next = (openRefCount.get(resolvedPath) ?? 0) - 1;
|
|
12
|
+
if (next <= 0) {
|
|
13
|
+
openRefCount.delete(resolvedPath);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
openRefCount.set(resolvedPath, next);
|
|
17
|
+
}
|
|
18
|
+
// Even if the counts get out of sync, preserve `known` so reads remain
|
|
19
|
+
// namespace-restricted to outputs created via writer APIs.
|
|
20
|
+
known.add(resolvedPath);
|
|
21
|
+
},
|
|
22
|
+
assertReadable: (resolvedPath, virtualPath) => {
|
|
23
|
+
if (!known.has(resolvedPath)) {
|
|
24
|
+
throw new Error(`readVirtualOutput(): VirtualOutput ${JSON.stringify(virtualPath)} is not a known virtual output for this backend instance. ` +
|
|
25
|
+
"Only outputs created via writer APIs (e.g. spkopn(...VirtualOutput...)) can be read back.");
|
|
26
|
+
}
|
|
27
|
+
const open = openRefCount.get(resolvedPath) ?? 0;
|
|
28
|
+
if (open > 0) {
|
|
29
|
+
throw new Error(`readVirtualOutput(): VirtualOutput ${JSON.stringify(virtualPath)} is still open. ` +
|
|
30
|
+
"Close the writer handle first (e.g. spkcls(handle)) before reading bytes.");
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=virtual-outputs.js.map
|