@ttsc/wasm 0.30.4 → 0.31.0
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 +3 -3
- package/build/build-wasm.cjs +5 -4
- package/build/go-build-cache.cjs +347 -0
- package/build/pack-prepare.cjs +3 -2
- package/cmd/ttsc-wasm/main.go +22 -10
- package/cmd/ttsc-wasm/main_wasm.go +2 -2
- package/dist/.ttsc-wasm-build.json +138 -58
- package/dist/go.mod +7 -5
- package/dist/ttsc.wasm +2 -2
- package/go.mod +7 -5
- package/go.sum +10 -10
- package/host/api.go +34 -41
- package/host/doc.go +7 -6
- package/host/expose.go +42 -0
- package/host/fountain.go +192 -22
- package/host/host.go +10 -25
- package/host/host_native.go +3 -5
- package/host/invoke.go +133 -0
- package/host/plugin.go +37 -72
- package/host/result.go +108 -0
- package/lib/src/BootTtscWorkerTerminationError.d.ts +20 -0
- package/lib/src/BootTtscWorkerTerminationError.js +20 -0
- package/lib/src/BootTtscWorkerTerminationError.js.map +1 -1
- package/lib/src/MemFSError.d.ts +21 -2
- package/lib/src/MemFSError.js +25 -2
- package/lib/src/MemFSError.js.map +1 -1
- package/lib/src/bootTtsc.d.ts +49 -2
- package/lib/src/bootTtsc.js +89 -23
- package/lib/src/bootTtsc.js.map +1 -1
- package/lib/src/createMemFS.d.ts +45 -0
- package/lib/src/createMemFS.js +269 -66
- package/lib/src/createMemFS.js.map +1 -1
- package/lib/src/parseResult.d.ts +19 -0
- package/lib/src/parseResult.js +19 -0
- package/lib/src/parseResult.js.map +1 -1
- package/lib/src/structures/IBootResult.d.ts +20 -2
- package/lib/src/structures/IBootTtscOptions.d.ts +28 -6
- package/lib/src/structures/IFileStats.d.ts +51 -0
- package/lib/src/structures/IMemFSHost.d.ts +89 -0
- package/lib/src/structures/ITtscApi.d.ts +154 -9
- package/lib/src/structures/ITtscBuildOpts.d.ts +18 -1
- package/lib/src/structures/ITtscCompileResult.d.ts +20 -2
- package/lib/src/structures/ITtscDiagnostic.d.ts +25 -1
- package/lib/src/structures/ITtscFileQuery.d.ts +19 -1
- package/lib/src/structures/ITtscFountainDiagnosticsResult.d.ts +20 -1
- package/lib/src/structures/ITtscNodeAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscNodeInfo.d.ts +23 -2
- package/lib/src/structures/ITtscPluginOpts.d.ts +24 -3
- package/lib/src/structures/ITtscPositionQuery.d.ts +21 -4
- package/lib/src/structures/ITtscReleaseSnapshotResult.d.ts +22 -2
- package/lib/src/structures/ITtscResult.d.ts +31 -5
- package/lib/src/structures/ITtscSnapshotHandle.d.ts +19 -1
- package/lib/src/structures/ITtscSnapshotResult.d.ts +22 -1
- package/lib/src/structures/ITtscSnapshotsResult.d.ts +19 -1
- package/lib/src/structures/ITtscSourceFileTextResult.d.ts +19 -1
- package/lib/src/structures/ITtscSourceFilesResult.d.ts +23 -2
- package/lib/src/structures/ITtscSymbolAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscSymbolDeclaration.d.ts +25 -2
- package/lib/src/structures/ITtscSymbolInfo.d.ts +19 -1
- package/lib/src/structures/ITtscTransformResult.d.ts +21 -3
- package/lib/src/structures/ITtscTypeAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscTypeInfo.d.ts +19 -1
- package/lib/src/structures/ITtscVersion.d.ts +28 -1
- package/lib/src/structures/IWasmExecFS.d.ts +310 -0
- package/package.json +4 -2
- package/shim-vendor/shim/ast/enums_gen.go +4 -2
- package/shim-vendor/shim/ast/go.mod +5 -5
- package/shim-vendor/shim/ast/go.sum +10 -10
- package/shim-vendor/shim/ast/lint.go +1088 -20
- package/shim-vendor/shim/ast/parent.go +38 -22
- package/shim-vendor/shim/ast/safe_text.go +48 -26
- package/shim-vendor/shim/ast/shim.go +1307 -20
- package/shim-vendor/shim/ast/test/next_jsdoc_comment_location_reaches_variable_statement_test.go +51 -0
- package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +24 -9
- package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +9 -5
- package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +13 -5
- package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +12 -5
- package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +35 -5
- package/shim-vendor/shim/astnav/go.mod +5 -5
- package/shim-vendor/shim/astnav/go.sum +12 -10
- package/shim-vendor/shim/astnav/shim.go +9 -0
- package/shim-vendor/shim/bundled/go.mod +7 -1
- package/shim-vendor/shim/bundled/go.sum +12 -0
- package/shim-vendor/shim/checker/enums_gen.go +4 -2
- package/shim-vendor/shim/checker/extra-shim.json +6 -0
- package/shim-vendor/shim/checker/go.mod +5 -5
- package/shim-vendor/shim/checker/go.sum +10 -10
- package/shim-vendor/shim/checker/node_builder_fields_gen.go +19 -0
- package/shim-vendor/shim/checker/shim.go +827 -114
- package/shim-vendor/shim/compiler/emit_sourcemap.go +68 -29
- package/shim-vendor/shim/compiler/emitassemble.go +36 -2
- package/shim-vendor/shim/compiler/emithook.go +13 -3
- package/shim-vendor/shim/compiler/go.mod +5 -5
- package/shim-vendor/shim/compiler/go.sum +10 -10
- package/shim-vendor/shim/compiler/incremental.go +56 -20
- package/shim-vendor/shim/compiler/resolution.go +92 -9
- package/shim-vendor/shim/core/enums_gen.go +2 -2
- package/shim-vendor/shim/core/go.mod +6 -1
- package/shim-vendor/shim/core/go.sum +10 -0
- package/shim-vendor/shim/core/modulekind.go +9 -0
- package/shim-vendor/shim/core/shim.go +157 -6
- package/shim-vendor/shim/diagnosticwriter/go.mod +10 -1
- package/shim-vendor/shim/diagnosticwriter/go.sum +20 -0
- package/shim-vendor/shim/diagnosticwriter/lint.go +188 -10
- package/shim-vendor/shim/diagnosticwriter/shim.go +14 -0
- package/shim-vendor/shim/lsp/go.mod +5 -5
- package/shim-vendor/shim/lsp/go.sum +10 -10
- package/shim-vendor/shim/lsp/shim.go +78 -2
- package/shim-vendor/shim/parser/go.mod +10 -1
- package/shim-vendor/shim/parser/go.sum +22 -0
- package/shim-vendor/shim/printer/emithost.go +46 -1
- package/shim-vendor/shim/printer/go.mod +5 -5
- package/shim-vendor/shim/printer/go.sum +10 -10
- package/shim-vendor/shim/printer/shim.go +114 -7
- package/shim-vendor/shim/printer/sourcemap.go +55 -0
- package/shim-vendor/shim/scanner/go.mod +10 -1
- package/shim-vendor/shim/scanner/go.sum +20 -0
- package/shim-vendor/shim/scanner/regexp.go +19 -4
- package/shim-vendor/shim/scanner/shim.go +3 -0
- package/shim-vendor/shim/stringutil/extra-shim.json +1 -0
- package/shim-vendor/shim/stringutil/go.mod +5 -0
- package/shim-vendor/shim/stringutil/go.sum +2 -0
- package/shim-vendor/shim/stringutil/shim.go +119 -0
- package/shim-vendor/shim/transformers/go.mod +5 -5
- package/shim-vendor/shim/transformers/go.sum +12 -10
- package/shim-vendor/shim/transformers/shim.go +12 -3
- package/shim-vendor/shim/tsoptions/command_line_raw.go +44 -0
- package/shim-vendor/shim/tsoptions/go.mod +10 -1
- package/shim-vendor/shim/tsoptions/go.sum +22 -0
- package/shim-vendor/shim/tsoptions/shim.go +1 -1
- package/shim-vendor/shim/tspath/go.mod +1 -1
- package/shim-vendor/shim/tspath/go.sum +6 -0
- package/shim-vendor/shim/vfs/cachedvfs/go.mod +3 -1
- package/shim-vendor/shim/vfs/cachedvfs/go.sum +12 -0
- package/shim-vendor/shim/vfs/decode.go +26 -0
- package/shim-vendor/shim/vfs/decode_test.go +42 -0
- package/shim-vendor/shim/vfs/go.mod +7 -1
- package/shim-vendor/shim/vfs/go.sum +12 -0
- package/shim-vendor/shim/vfs/osvfs/go.mod +7 -1
- package/shim-vendor/shim/vfs/osvfs/go.sum +12 -0
- package/src/BootTtscWorkerTerminationError.ts +22 -0
- package/src/MemFSError.ts +29 -2
- package/src/bootTtsc.ts +104 -28
- package/src/createMemFS.ts +291 -64
- package/src/parseResult.ts +19 -0
- package/src/structures/IBootResult.ts +21 -2
- package/src/structures/IBootTtscOptions.ts +32 -6
- package/src/structures/IFileStats.ts +65 -0
- package/src/structures/IMemFSHost.ts +97 -0
- package/src/structures/ITtscApi.ts +154 -9
- package/src/structures/ITtscBuildOpts.ts +19 -1
- package/src/structures/ITtscCompileResult.ts +21 -2
- package/src/structures/ITtscDiagnostic.ts +32 -1
- package/src/structures/ITtscFileQuery.ts +19 -1
- package/src/structures/ITtscFountainDiagnosticsResult.ts +20 -1
- package/src/structures/ITtscNodeAtPositionResult.ts +18 -1
- package/src/structures/ITtscNodeInfo.ts +27 -2
- package/src/structures/ITtscPluginOpts.ts +28 -3
- package/src/structures/ITtscPositionQuery.ts +21 -4
- package/src/structures/ITtscReleaseSnapshotResult.ts +22 -2
- package/src/structures/ITtscResult.ts +34 -5
- package/src/structures/ITtscSnapshotHandle.ts +19 -1
- package/src/structures/ITtscSnapshotResult.ts +22 -1
- package/src/structures/ITtscSnapshotsResult.ts +19 -1
- package/src/structures/ITtscSourceFileTextResult.ts +19 -1
- package/src/structures/ITtscSourceFilesResult.ts +23 -2
- package/src/structures/ITtscSymbolAtPositionResult.ts +18 -1
- package/src/structures/ITtscSymbolDeclaration.ts +27 -2
- package/src/structures/ITtscSymbolInfo.ts +23 -1
- package/src/structures/ITtscTransformResult.ts +22 -3
- package/src/structures/ITtscTypeAtPositionResult.ts +18 -1
- package/src/structures/ITtscTypeInfo.ts +20 -1
- package/src/structures/ITtscVersion.ts +33 -1
- package/src/structures/IWasmExecFS.ts +336 -0
package/src/createMemFS.ts
CHANGED
|
@@ -30,6 +30,9 @@ interface INode {
|
|
|
30
30
|
kind: "file" | "dir";
|
|
31
31
|
data: Uint8Array;
|
|
32
32
|
mtimeMs: number;
|
|
33
|
+
|
|
34
|
+
/** UTF-8 decoding of current bytes, cleared by every content mutation. */
|
|
35
|
+
text?: string;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
const encoder = new TextEncoder();
|
|
@@ -70,13 +73,112 @@ function normalize(p: string): string {
|
|
|
70
73
|
* descendants. An operation that cannot satisfy that throws (`writeFile`,
|
|
71
74
|
* `mkdirp`) or reports a POSIX error through its callback, having changed no
|
|
72
75
|
* node, byte, or descriptor.
|
|
76
|
+
*
|
|
77
|
+
* @evidence contracts/common.md#principled-implementation
|
|
78
|
+
* The owned Node-shaped fs bridge uses maps for virtual node/descriptor
|
|
79
|
+
* identity and preserves opened nodes independently from paths. The Go js/wasm
|
|
80
|
+
* callback interface is the authority; unsupported link and metadata mutation
|
|
81
|
+
* capabilities are explicit in IWasmExecFS.
|
|
82
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
83
|
+
* One closure owns the tree, indexes and descriptor identities. Shared helpers
|
|
84
|
+
* centralize path validation, insertion/removal and byte mutation; separate
|
|
85
|
+
* stream captures and pipe queues reflect non-seekable lifetimes rather than
|
|
86
|
+
* adding filesystem nodes that would blur their operation contracts.
|
|
87
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
88
|
+
* Virtual flag/errno values are bridge constants, not native-host guesses.
|
|
89
|
+
* The implementation owns its maps rather than patching foreign filesystem
|
|
90
|
+
* methods. Directory indexes, geometric file growth and linked pipe queues
|
|
91
|
+
* correct repeated global scans, full-buffer copies and queue shifts at their
|
|
92
|
+
* owners, without size exceptions or benchmark-specific paths.
|
|
93
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
94
|
+
* Native paragraphs explain installation, copying and tree ownership; helper
|
|
95
|
+
* and interface comments state cost and unsupported capabilities. This follows
|
|
96
|
+
* the documentation skill's paragraph, units and rationale guidance.
|
|
97
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
98
|
+
* Directory lookup visits D direct entries and sorts them in O(D log D),
|
|
99
|
+
* emptiness uses the indexed child count, and rename visits only S subtree
|
|
100
|
+
* nodes plus F open descriptors. Growing file writes amortize copying through
|
|
101
|
+
* geometric capacity; pipe enqueue/dequeue change linked endpoints in O(1).
|
|
102
|
+
* Path normalization costs O(P) path characters; ancestor validation builds
|
|
103
|
+
* d prefixes with total length K, costing O(K + d) time and temporary space
|
|
104
|
+
* under expected constant-time map access after hashing each prefix. Output fragments
|
|
105
|
+
* append without flattening prior text; reading a dirty B-byte capture costs
|
|
106
|
+
* O(B), while an unchanged capture read reuses its materialized string.
|
|
107
|
+
* @evidence contracts/performance.md#reuse-equivalent-work
|
|
108
|
+
* Child membership is maintained once by node insertion/deletion rather than
|
|
109
|
+
* reconstructed for every listing. Text decoding is cached on node identity
|
|
110
|
+
* and invalidated by every content mutation. Open descriptors share their
|
|
111
|
+
* actual inode; public byte reads still copy to preserve caller isolation.
|
|
112
|
+
* Each output stream keeps its own incremental UTF-8 decoder, so a split
|
|
113
|
+
* character is decoded once and capture reads reuse the materialized string
|
|
114
|
+
* until another fragment arrives.
|
|
115
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources
|
|
116
|
+
* Each host owns its tree, indexes, captures and descriptors. Unlink/rmdir
|
|
117
|
+
* remove tree entries, close releases descriptors, and consumed pipe entries
|
|
118
|
+
* are detached. File write capacity stays at most twice logical length; truncate
|
|
119
|
+
* and replacement release spare storage. Total input and captured output have
|
|
120
|
+
* no fixed bound, so the caller owns deletion, resetStdio and host lifetime.
|
|
73
121
|
*/
|
|
74
122
|
export function createMemFS(): IMemFSHost {
|
|
75
123
|
const nodes = new Map<string, INode>();
|
|
124
|
+
const children = new Map<string, Set<string>>([["/", new Set()]]);
|
|
76
125
|
nodes.set("/", { kind: "dir", data: new Uint8Array(), mtimeMs: Date.now() });
|
|
77
126
|
|
|
78
|
-
|
|
79
|
-
|
|
127
|
+
/** Keep parent membership and directory indexes with the owned node tree. */
|
|
128
|
+
function setNode(p: string, node: INode): void {
|
|
129
|
+
nodes.set(p, node);
|
|
130
|
+
children.get(parentDir(p))!.add(p);
|
|
131
|
+
if (node.kind === "dir" && !children.has(p)) children.set(p, new Set());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Remove a validated node and its index; open descriptors retain the node. */
|
|
135
|
+
function deleteNode(p: string): void {
|
|
136
|
+
nodes.delete(p);
|
|
137
|
+
children.get(parentDir(p))!.delete(p);
|
|
138
|
+
children.delete(p);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Decode stream fragments incrementally and materialize capture text on read. */
|
|
142
|
+
function createCapture() {
|
|
143
|
+
let streamDecoder = new TextDecoder();
|
|
144
|
+
let chunks: string[] = [];
|
|
145
|
+
let cached = "";
|
|
146
|
+
let dirty = false;
|
|
147
|
+
const append = (text: string): string => {
|
|
148
|
+
if (text.length > 0) {
|
|
149
|
+
chunks.push(text);
|
|
150
|
+
cached = "";
|
|
151
|
+
dirty = true;
|
|
152
|
+
}
|
|
153
|
+
return text;
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
output: {
|
|
157
|
+
get buffer(): string {
|
|
158
|
+
if (dirty) {
|
|
159
|
+
cached = chunks.join("");
|
|
160
|
+
chunks = cached.length > 0 ? [cached] : [];
|
|
161
|
+
dirty = false;
|
|
162
|
+
}
|
|
163
|
+
return cached;
|
|
164
|
+
},
|
|
165
|
+
set buffer(value: string) {
|
|
166
|
+
streamDecoder = new TextDecoder();
|
|
167
|
+
chunks = value.length > 0 ? [value] : [];
|
|
168
|
+
cached = value;
|
|
169
|
+
dirty = false;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
write(bytes: Uint8Array): string {
|
|
173
|
+
return append(streamDecoder.decode(bytes, { stream: true }));
|
|
174
|
+
},
|
|
175
|
+
flush(): void {
|
|
176
|
+
append(streamDecoder.decode());
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const stdout = createCapture();
|
|
181
|
+
const stderr = createCapture();
|
|
80
182
|
|
|
81
183
|
/**
|
|
82
184
|
* One open descriptor.
|
|
@@ -125,9 +227,30 @@ export function createMemFS(): IMemFSHost {
|
|
|
125
227
|
// These fds only ever come from a direct JavaScript `fs.pipe2` call. Go's
|
|
126
228
|
// wasm `os.Pipe` returns ENOSYS without crossing the `globalThis.fs` bridge,
|
|
127
229
|
// so the Go runtime never reaches this state — see IWasmExecFS.pipe2.
|
|
230
|
+
interface IQueueEntry<T> {
|
|
231
|
+
value: T;
|
|
232
|
+
next?: IQueueEntry<T>;
|
|
233
|
+
}
|
|
234
|
+
interface IQueue<T> {
|
|
235
|
+
head?: IQueueEntry<T>;
|
|
236
|
+
tail?: IQueueEntry<T>;
|
|
237
|
+
}
|
|
238
|
+
function enqueue<T>(queue: IQueue<T>, value: T): void {
|
|
239
|
+
const entry = { value };
|
|
240
|
+
if (queue.tail) queue.tail.next = entry;
|
|
241
|
+
else queue.head = entry;
|
|
242
|
+
queue.tail = entry;
|
|
243
|
+
}
|
|
244
|
+
function dequeue<T>(queue: IQueue<T>): T | undefined {
|
|
245
|
+
const entry = queue.head;
|
|
246
|
+
if (!entry) return undefined;
|
|
247
|
+
queue.head = entry.next;
|
|
248
|
+
if (!queue.head) queue.tail = undefined;
|
|
249
|
+
return entry.value;
|
|
250
|
+
}
|
|
128
251
|
interface IPipeState {
|
|
129
|
-
buffers: Uint8Array
|
|
130
|
-
pendingReaders:
|
|
252
|
+
buffers: IQueue<Uint8Array>;
|
|
253
|
+
pendingReaders: IQueue<{
|
|
131
254
|
buffer: Uint8Array;
|
|
132
255
|
offset: number;
|
|
133
256
|
length: number;
|
|
@@ -136,6 +259,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
136
259
|
readFd: number;
|
|
137
260
|
writeFd: number;
|
|
138
261
|
writeClosed: boolean;
|
|
262
|
+
readClosed: boolean;
|
|
139
263
|
}
|
|
140
264
|
const pipes = new Map<number, IPipeState>();
|
|
141
265
|
|
|
@@ -150,13 +274,13 @@ export function createMemFS(): IMemFSHost {
|
|
|
150
274
|
length: number,
|
|
151
275
|
): number {
|
|
152
276
|
let written = 0;
|
|
153
|
-
while (state.buffers.
|
|
154
|
-
const chunk = state.buffers
|
|
277
|
+
while (state.buffers.head && written < length) {
|
|
278
|
+
const chunk = state.buffers.head.value;
|
|
155
279
|
const copyLen = Math.min(chunk.byteLength, length - written);
|
|
156
280
|
buffer.set(chunk.subarray(0, copyLen), offset + written);
|
|
157
281
|
written += copyLen;
|
|
158
|
-
if (copyLen >= chunk.byteLength) state.buffers
|
|
159
|
-
else state.buffers
|
|
282
|
+
if (copyLen >= chunk.byteLength) dequeue(state.buffers);
|
|
283
|
+
else state.buffers.head.value = chunk.subarray(copyLen);
|
|
160
284
|
}
|
|
161
285
|
return written;
|
|
162
286
|
}
|
|
@@ -167,10 +291,10 @@ export function createMemFS(): IMemFSHost {
|
|
|
167
291
|
*/
|
|
168
292
|
function flushPipeReaders(state: IPipeState): void {
|
|
169
293
|
while (
|
|
170
|
-
state.pendingReaders.
|
|
171
|
-
(state.buffers.
|
|
294
|
+
state.pendingReaders.head &&
|
|
295
|
+
(state.buffers.head || state.writeClosed)
|
|
172
296
|
) {
|
|
173
|
-
const reader = state.pendingReaders
|
|
297
|
+
const reader = dequeue(state.pendingReaders)!;
|
|
174
298
|
const n = drainPipeInto(
|
|
175
299
|
state,
|
|
176
300
|
reader.buffer,
|
|
@@ -218,7 +342,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
218
342
|
/** Materialize the directories `missingDirs` reported. */
|
|
219
343
|
function createDirs(paths: string[]): void {
|
|
220
344
|
for (const path of paths)
|
|
221
|
-
|
|
345
|
+
setNode(path, {
|
|
222
346
|
kind: "dir",
|
|
223
347
|
data: new Uint8Array(),
|
|
224
348
|
mtimeMs: Date.now(),
|
|
@@ -249,23 +373,89 @@ export function createMemFS(): IMemFSHost {
|
|
|
249
373
|
|
|
250
374
|
/** True when `dir` has at least one descendant node in the tree. */
|
|
251
375
|
function hasChildren(dir: string): boolean {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
376
|
+
return (children.get(dir)?.size ?? 0) > 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Allocate zero-filled storage for a file of `length` bytes.
|
|
381
|
+
*
|
|
382
|
+
* The engine refuses a length it cannot allocate with a `RangeError`, which
|
|
383
|
+
* carries no POSIX code and would escape a callback that promises one. That
|
|
384
|
+
* refusal is the virtual filesystem's maximum file size, so it is reported as
|
|
385
|
+
* `EFBIG`; the caller has not yet changed any node when this throws.
|
|
386
|
+
*/
|
|
387
|
+
function allocateFileData(
|
|
388
|
+
length: number,
|
|
389
|
+
syscall: string,
|
|
390
|
+
path: string,
|
|
391
|
+
): Uint8Array {
|
|
392
|
+
try {
|
|
393
|
+
return new Uint8Array(length);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof RangeError)
|
|
396
|
+
throw new MemFSError("EFBIG", syscall, path);
|
|
397
|
+
throw error;
|
|
255
398
|
}
|
|
256
|
-
return false;
|
|
257
399
|
}
|
|
258
400
|
|
|
259
401
|
/**
|
|
260
402
|
* Grow or shrink a file's byte buffer to exactly `length`, zero-filling any
|
|
261
403
|
* extension. Callers validate `length >= 0` first.
|
|
262
404
|
*/
|
|
263
|
-
function resizeFileData(
|
|
264
|
-
|
|
405
|
+
function resizeFileData(
|
|
406
|
+
data: Uint8Array,
|
|
407
|
+
length: number,
|
|
408
|
+
syscall: string,
|
|
409
|
+
path: string,
|
|
410
|
+
): Uint8Array {
|
|
411
|
+
const next = allocateFileData(length, syscall, path);
|
|
265
412
|
next.set(data.subarray(0, Math.min(length, data.byteLength)));
|
|
266
413
|
return next;
|
|
267
414
|
}
|
|
268
415
|
|
|
416
|
+
/** Grow writes geometrically; the public view still exposes only file bytes. */
|
|
417
|
+
function growFileData(
|
|
418
|
+
data: Uint8Array,
|
|
419
|
+
length: number,
|
|
420
|
+
syscall: string,
|
|
421
|
+
path: string,
|
|
422
|
+
): Uint8Array {
|
|
423
|
+
const capacity = data.buffer.byteLength - data.byteOffset;
|
|
424
|
+
if (length <= capacity) {
|
|
425
|
+
const next = new Uint8Array(data.buffer, data.byteOffset, length);
|
|
426
|
+
next.fill(0, data.byteLength);
|
|
427
|
+
return next;
|
|
428
|
+
}
|
|
429
|
+
// Spare capacity is only an optimization: when the engine refuses the
|
|
430
|
+
// doubled size, the exact length still satisfies the write.
|
|
431
|
+
let next: Uint8Array;
|
|
432
|
+
if (data.byteLength * 2 > length) {
|
|
433
|
+
try {
|
|
434
|
+
next = allocateFileData(data.byteLength * 2, syscall, path);
|
|
435
|
+
} catch {
|
|
436
|
+
next = allocateFileData(length, syscall, path);
|
|
437
|
+
}
|
|
438
|
+
} else next = allocateFileData(length, syscall, path);
|
|
439
|
+
next.set(data);
|
|
440
|
+
return next.subarray(0, length);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Reject invalid byte slices before a write or queued read can mutate state. */
|
|
444
|
+
function validSlice(
|
|
445
|
+
buffer: Uint8Array,
|
|
446
|
+
offset: number,
|
|
447
|
+
length: number,
|
|
448
|
+
): boolean {
|
|
449
|
+
return (
|
|
450
|
+
Number.isSafeInteger(offset) &&
|
|
451
|
+
Number.isSafeInteger(length) &&
|
|
452
|
+
offset >= 0 &&
|
|
453
|
+
length >= 0 &&
|
|
454
|
+
offset <= buffer.byteLength &&
|
|
455
|
+
length <= buffer.byteLength - offset
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
269
459
|
/**
|
|
270
460
|
* Write `view` through descriptor `entry` and return the bytes stored.
|
|
271
461
|
*
|
|
@@ -292,12 +482,16 @@ export function createMemFS(): IMemFSHost {
|
|
|
292
482
|
const start = entry.append
|
|
293
483
|
? node.data.byteLength
|
|
294
484
|
: (position ?? entry.position);
|
|
295
|
-
if (!Number.
|
|
485
|
+
if (!Number.isSafeInteger(start) || start < 0)
|
|
296
486
|
throw new MemFSError("EINVAL", syscall, entry.path);
|
|
297
487
|
if (view.byteLength === 0) return 0;
|
|
298
488
|
const end = start + view.byteLength;
|
|
299
|
-
if (
|
|
489
|
+
if (!Number.isSafeInteger(end))
|
|
490
|
+
throw new MemFSError("EINVAL", syscall, entry.path);
|
|
491
|
+
if (end > node.data.byteLength)
|
|
492
|
+
node.data = growFileData(node.data, end, syscall, entry.path);
|
|
300
493
|
node.data.set(view, start);
|
|
494
|
+
node.text = undefined;
|
|
301
495
|
node.mtimeMs = Date.now();
|
|
302
496
|
if (position === null || entry.append) entry.position = end;
|
|
303
497
|
return view.byteLength;
|
|
@@ -311,19 +505,20 @@ export function createMemFS(): IMemFSHost {
|
|
|
311
505
|
*/
|
|
312
506
|
function moveSubtree(src: string, dest: string): void {
|
|
313
507
|
const srcPrefix = src + "/";
|
|
314
|
-
const moves: Array<[string, INode]> = [];
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
508
|
+
const moves: Array<[string, string, INode]> = [];
|
|
509
|
+
const pending = [src];
|
|
510
|
+
while (pending.length > 0) {
|
|
511
|
+
const key = pending.pop()!;
|
|
512
|
+
const target = key === src ? dest : dest + key.slice(src.length);
|
|
513
|
+
moves.push([key, target, nodes.get(key)!]);
|
|
514
|
+
for (const child of children.get(key) ?? []) pending.push(child);
|
|
319
515
|
}
|
|
320
516
|
// Delete the whole source subtree first so a nested overwrite cannot leave
|
|
321
517
|
// a stale descendant behind, then reinsert at the destination prefix. Any
|
|
322
518
|
// pre-existing `dest` node is replaced by the reinsert.
|
|
323
|
-
for (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
for (const [key, node] of moves) nodes.set(key, node);
|
|
519
|
+
for (let i = moves.length - 1; i >= 0; --i) deleteNode(moves[i]![0]);
|
|
520
|
+
if (nodes.has(dest)) deleteNode(dest);
|
|
521
|
+
for (const [, key, node] of moves) setNode(key, node);
|
|
327
522
|
for (const entry of fdTable.values()) {
|
|
328
523
|
if (entry.path === src) entry.path = dest;
|
|
329
524
|
else if (entry.path.startsWith(srcPrefix))
|
|
@@ -350,8 +545,9 @@ export function createMemFS(): IMemFSHost {
|
|
|
350
545
|
// file observe the replacement instead of a detached predecessor.
|
|
351
546
|
if (existing) {
|
|
352
547
|
existing.data = bytes;
|
|
548
|
+
existing.text = undefined;
|
|
353
549
|
existing.mtimeMs = Date.now();
|
|
354
|
-
} else
|
|
550
|
+
} else setNode(norm, { kind: "file", data: bytes, mtimeMs: Date.now() });
|
|
355
551
|
}
|
|
356
552
|
|
|
357
553
|
function readFile(p: string): Uint8Array | null {
|
|
@@ -361,8 +557,9 @@ export function createMemFS(): IMemFSHost {
|
|
|
361
557
|
}
|
|
362
558
|
|
|
363
559
|
function readFileText(p: string): string | null {
|
|
364
|
-
const
|
|
365
|
-
|
|
560
|
+
const node = nodes.get(normalize(p));
|
|
561
|
+
if (!node || node.kind !== "file") return null;
|
|
562
|
+
return (node.text ??= decoder.decode(node.data));
|
|
366
563
|
}
|
|
367
564
|
|
|
368
565
|
function exists(p: string): boolean {
|
|
@@ -400,25 +597,19 @@ export function createMemFS(): IMemFSHost {
|
|
|
400
597
|
}
|
|
401
598
|
|
|
402
599
|
/**
|
|
403
|
-
* Return immediate children of directory `p`, sorted
|
|
600
|
+
* Return immediate children of directory `p`, sorted by UTF-16 code unit.
|
|
404
601
|
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
602
|
+
* The maintained child index limits work to this directory's entries. Sorting
|
|
603
|
+
* provides deterministic output without scanning unrelated project files.
|
|
407
604
|
*/
|
|
408
605
|
function readdirSync(p: string): string[] {
|
|
409
606
|
const norm = normalize(p);
|
|
410
607
|
const node = nodes.get(norm);
|
|
411
608
|
if (!node) throw new MemFSError("ENOENT", "readdir", norm);
|
|
412
609
|
if (node.kind !== "dir") throw new MemFSError("ENOTDIR", "readdir", norm);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (!key.startsWith(prefix) || key === norm) continue;
|
|
417
|
-
const rest = key.slice(prefix.length);
|
|
418
|
-
const cut = rest.indexOf("/");
|
|
419
|
-
direct.add(cut === -1 ? rest : rest.slice(0, cut));
|
|
420
|
-
}
|
|
421
|
-
return [...direct].sort();
|
|
610
|
+
return [...children.get(norm)!]
|
|
611
|
+
.map((child) => child.slice(norm === "/" ? 1 : norm.length + 1))
|
|
612
|
+
.sort();
|
|
422
613
|
}
|
|
423
614
|
|
|
424
615
|
const fs: IWasmExecFS = {
|
|
@@ -434,12 +625,11 @@ export function createMemFS(): IMemFSHost {
|
|
|
434
625
|
|
|
435
626
|
writeSync(fd, buf) {
|
|
436
627
|
if (fd === 1) {
|
|
437
|
-
stdout.
|
|
628
|
+
stdout.write(buf);
|
|
438
629
|
return buf.length;
|
|
439
630
|
}
|
|
440
631
|
if (fd === 2) {
|
|
441
|
-
const text =
|
|
442
|
-
stderr.buffer += text;
|
|
632
|
+
const text = stderr.write(buf);
|
|
443
633
|
// Surface wasm-side stderr to the host console in real-time so plugin
|
|
444
634
|
// debug prints / Go panics aren't trapped inside the MemFS buffer.
|
|
445
635
|
// Stripped at end of message for cleaner display.
|
|
@@ -466,6 +656,8 @@ export function createMemFS(): IMemFSHost {
|
|
|
466
656
|
|
|
467
657
|
write(fd, buf, offset, length, position, callback) {
|
|
468
658
|
try {
|
|
659
|
+
if (!validSlice(buf, offset, length))
|
|
660
|
+
throw new MemFSError("EINVAL", "write");
|
|
469
661
|
// Pipe write: snapshot the data (caller may reuse `buf`) and queue.
|
|
470
662
|
// Synchronously wake any blocked reader so the cooperative wasm
|
|
471
663
|
// scheduler doesn't deadlock waiting for a future fs roundtrip.
|
|
@@ -475,10 +667,14 @@ export function createMemFS(): IMemFSHost {
|
|
|
475
667
|
callback(new MemFSError("EBADF", "write"), 0);
|
|
476
668
|
return;
|
|
477
669
|
}
|
|
670
|
+
if (pipeState.readClosed) {
|
|
671
|
+
callback(new MemFSError("EPIPE", "write"), 0);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
478
674
|
if (length > 0) {
|
|
479
675
|
const copy = new Uint8Array(length);
|
|
480
676
|
copy.set(buf.subarray(offset, offset + length));
|
|
481
|
-
pipeState.buffers
|
|
677
|
+
enqueue(pipeState.buffers, copy);
|
|
482
678
|
flushPipeReaders(pipeState);
|
|
483
679
|
}
|
|
484
680
|
callback(null, length);
|
|
@@ -531,7 +727,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
531
727
|
if (missing.length > 0)
|
|
532
728
|
throw new MemFSError("ENOENT", "open", missing[0]!);
|
|
533
729
|
node = { kind: "file", data: new Uint8Array(), mtimeMs: Date.now() };
|
|
534
|
-
|
|
730
|
+
setNode(norm, node);
|
|
535
731
|
} else {
|
|
536
732
|
if (creating && exclusive)
|
|
537
733
|
throw new MemFSError("EEXIST", "open", norm);
|
|
@@ -546,6 +742,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
546
742
|
}
|
|
547
743
|
if (truncating) {
|
|
548
744
|
node.data = new Uint8Array();
|
|
745
|
+
node.text = undefined;
|
|
549
746
|
node.mtimeMs = Date.now();
|
|
550
747
|
}
|
|
551
748
|
const fd = nextFd++;
|
|
@@ -566,11 +763,18 @@ export function createMemFS(): IMemFSHost {
|
|
|
566
763
|
close(fd, callback) {
|
|
567
764
|
const pipeState = pipes.get(fd);
|
|
568
765
|
if (pipeState) {
|
|
766
|
+
pipes.delete(fd);
|
|
569
767
|
if (fd === pipeState.writeFd) {
|
|
570
768
|
pipeState.writeClosed = true;
|
|
571
769
|
flushPipeReaders(pipeState);
|
|
770
|
+
} else {
|
|
771
|
+
pipeState.readClosed = true;
|
|
772
|
+
pipeState.buffers = {};
|
|
773
|
+
while (pipeState.pendingReaders.head) {
|
|
774
|
+
const reader = dequeue(pipeState.pendingReaders)!;
|
|
775
|
+
reader.callback(new MemFSError("EBADF", "read"), 0);
|
|
776
|
+
}
|
|
572
777
|
}
|
|
573
|
-
pipes.delete(fd);
|
|
574
778
|
callback(null);
|
|
575
779
|
return;
|
|
576
780
|
}
|
|
@@ -578,11 +782,17 @@ export function createMemFS(): IMemFSHost {
|
|
|
578
782
|
callback(new MemFSError("EBADF", "close"));
|
|
579
783
|
return;
|
|
580
784
|
}
|
|
785
|
+
if (fd === 1) stdout.flush();
|
|
786
|
+
if (fd === 2) stderr.flush();
|
|
581
787
|
if (fd > 2) fdTable.delete(fd);
|
|
582
788
|
callback(null);
|
|
583
789
|
},
|
|
584
790
|
|
|
585
791
|
read(fd, buffer, offset, length, position, callback) {
|
|
792
|
+
if (!validSlice(buffer, offset, length)) {
|
|
793
|
+
callback(new MemFSError("EINVAL", "read"), 0);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
586
796
|
// Pipe read: drain queued chunks; block (defer callback) on empty.
|
|
587
797
|
// Empty + writeClosed → return 0 (EOF). Position is ignored for pipes.
|
|
588
798
|
const pipeState = pipes.get(fd);
|
|
@@ -591,7 +801,11 @@ export function createMemFS(): IMemFSHost {
|
|
|
591
801
|
callback(new MemFSError("EBADF", "read"), 0);
|
|
592
802
|
return;
|
|
593
803
|
}
|
|
594
|
-
if (
|
|
804
|
+
if (length === 0) {
|
|
805
|
+
callback(null, 0);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (pipeState.buffers.head) {
|
|
595
809
|
const n = drainPipeInto(pipeState, buffer, offset, length);
|
|
596
810
|
callback(null, n);
|
|
597
811
|
return;
|
|
@@ -600,7 +814,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
600
814
|
callback(null, 0);
|
|
601
815
|
return;
|
|
602
816
|
}
|
|
603
|
-
pipeState.pendingReaders
|
|
817
|
+
enqueue(pipeState.pendingReaders, { buffer, offset, length, callback });
|
|
604
818
|
return;
|
|
605
819
|
}
|
|
606
820
|
const entry = fdTable.get(fd);
|
|
@@ -620,7 +834,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
620
834
|
return;
|
|
621
835
|
}
|
|
622
836
|
const start = position ?? entry.position;
|
|
623
|
-
if (!Number.
|
|
837
|
+
if (!Number.isSafeInteger(start) || start < 0) {
|
|
624
838
|
callback(new MemFSError("EINVAL", "read", entry.path), 0);
|
|
625
839
|
return;
|
|
626
840
|
}
|
|
@@ -650,7 +864,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
650
864
|
if (!parent) throw new MemFSError("ENOENT", "mkdir", parentDir(norm));
|
|
651
865
|
if (parent.kind !== "dir")
|
|
652
866
|
throw new MemFSError("ENOTDIR", "mkdir", parentDir(norm));
|
|
653
|
-
|
|
867
|
+
setNode(norm, {
|
|
654
868
|
kind: "dir",
|
|
655
869
|
data: new Uint8Array(),
|
|
656
870
|
mtimeMs: Date.now(),
|
|
@@ -729,7 +943,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
729
943
|
callback(new MemFSError("EISDIR", "unlink", norm));
|
|
730
944
|
return;
|
|
731
945
|
}
|
|
732
|
-
|
|
946
|
+
deleteNode(norm);
|
|
733
947
|
callback(null);
|
|
734
948
|
},
|
|
735
949
|
|
|
@@ -805,7 +1019,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
805
1019
|
callback(new MemFSError("ENOTEMPTY", "rmdir", norm));
|
|
806
1020
|
return;
|
|
807
1021
|
}
|
|
808
|
-
|
|
1022
|
+
deleteNode(norm);
|
|
809
1023
|
callback(null);
|
|
810
1024
|
},
|
|
811
1025
|
|
|
@@ -851,7 +1065,13 @@ export function createMemFS(): IMemFSHost {
|
|
|
851
1065
|
callback(new MemFSError("EINVAL", "truncate", norm));
|
|
852
1066
|
return;
|
|
853
1067
|
}
|
|
854
|
-
|
|
1068
|
+
try {
|
|
1069
|
+
node.data = resizeFileData(node.data, length, "truncate", norm);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
callback(err as NodeJS.ErrnoException);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
node.text = undefined;
|
|
855
1075
|
node.mtimeMs = Date.now();
|
|
856
1076
|
callback(null);
|
|
857
1077
|
},
|
|
@@ -883,7 +1103,13 @@ export function createMemFS(): IMemFSHost {
|
|
|
883
1103
|
callback(new MemFSError("EINVAL", "ftruncate", entry.path));
|
|
884
1104
|
return;
|
|
885
1105
|
}
|
|
886
|
-
|
|
1106
|
+
try {
|
|
1107
|
+
node.data = resizeFileData(node.data, length, "ftruncate", entry.path);
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
callback(err as NodeJS.ErrnoException);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
node.text = undefined;
|
|
887
1113
|
node.mtimeMs = Date.now();
|
|
888
1114
|
callback(null);
|
|
889
1115
|
},
|
|
@@ -891,11 +1117,12 @@ export function createMemFS(): IMemFSHost {
|
|
|
891
1117
|
const readFd = nextFd++;
|
|
892
1118
|
const writeFd = nextFd++;
|
|
893
1119
|
const state: IPipeState = {
|
|
894
|
-
buffers:
|
|
895
|
-
pendingReaders:
|
|
1120
|
+
buffers: {},
|
|
1121
|
+
pendingReaders: {},
|
|
896
1122
|
readFd,
|
|
897
1123
|
writeFd,
|
|
898
1124
|
writeClosed: false,
|
|
1125
|
+
readClosed: false,
|
|
899
1126
|
};
|
|
900
1127
|
pipes.set(readFd, state);
|
|
901
1128
|
pipes.set(writeFd, state);
|
|
@@ -910,11 +1137,11 @@ export function createMemFS(): IMemFSHost {
|
|
|
910
1137
|
readFileText,
|
|
911
1138
|
exists,
|
|
912
1139
|
mkdirp,
|
|
913
|
-
stdout,
|
|
914
|
-
stderr,
|
|
1140
|
+
stdout: stdout.output,
|
|
1141
|
+
stderr: stderr.output,
|
|
915
1142
|
resetStdio() {
|
|
916
|
-
stdout.buffer = "";
|
|
917
|
-
stderr.buffer = "";
|
|
1143
|
+
stdout.output.buffer = "";
|
|
1144
|
+
stderr.output.buffer = "";
|
|
918
1145
|
},
|
|
919
1146
|
};
|
|
920
1147
|
}
|
package/src/parseResult.ts
CHANGED
|
@@ -5,6 +5,25 @@ import type { ITtscResult } from "./structures/ITtscResult";
|
|
|
5
5
|
*
|
|
6
6
|
* The wasm returns JSON as a string because `js.ValueOf` does not handle large
|
|
7
7
|
* nested maps efficiently. Callers JSON.parse exactly once at the boundary.
|
|
8
|
+
*
|
|
9
|
+
* Returns null for empty or invalid JSON. The generic parameter describes the
|
|
10
|
+
* expected payload; this function does not validate its schema or exit code.
|
|
11
|
+
*
|
|
12
|
+
* @evidence contracts/common.md#principled-implementation
|
|
13
|
+
* JSON.parse handles the native string envelope using the standard parser;
|
|
14
|
+
* the generic cast expresses the caller's expected endpoint payload.
|
|
15
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
16
|
+
* One parser owns envelope decoding; endpoint selection and exit handling
|
|
17
|
+
* remain with the caller instead of introducing another result schema.
|
|
18
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
19
|
+
* Parser failure returns explicit null rather than synthesizing an expected
|
|
20
|
+
* payload. The cast is documented as an expectation, not runtime validation.
|
|
21
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
22
|
+
* Separate JSDoc paragraphs explain the boundary and failure/schema limits,
|
|
23
|
+
* following the documentation skill's guidance to give each paragraph one role.
|
|
24
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources parseResult retains nothing; the decoded value transfers to the caller.
|
|
25
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms parseResult delegates to JSON.parse, linear in the string length, and chooses no algorithm of its own.
|
|
26
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work parseResult decodes one string per call and coordinates no repeated or concurrent requests.
|
|
8
27
|
*/
|
|
9
28
|
export function parseResult<T>(result: ITtscResult): T | null {
|
|
10
29
|
if (!result.result) return null;
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import type { IMemFSHost } from "./IMemFSHost";
|
|
2
2
|
import type { ITtscApi } from "./ITtscApi";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Handle returned by `bootTtsc` once the wasm is ready.
|
|
6
|
+
*
|
|
7
|
+
* @evidence contracts/common.md#principled-implementation
|
|
8
|
+
* Pairing the typed bridge and the exact MemFS host preserves the runtime's
|
|
9
|
+
* filesystem identity instead of exposing the API separately from its inputs.
|
|
10
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
11
|
+
* The result pairs the two objects needed to seed and invoke this runtime;
|
|
12
|
+
* loading options and mutable boot coordination remain outside the handle.
|
|
13
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
14
|
+
* The host is the booted runtime's actual backing object, not an unrelated
|
|
15
|
+
* replacement presented to a consumer after readiness.
|
|
16
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
17
|
+
* Native member JSDoc explains bridge and shared filesystem ownership, following
|
|
18
|
+
* the documentation skill's requirement for nonobvious usage context.
|
|
19
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources IBootResult is a data interface and acquires no handle, task or retained state.
|
|
20
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms IBootResult is a data interface and chooses no algorithm or processing strategy.
|
|
21
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work IBootResult is a data interface and coordinates no shared or repeated computation.
|
|
22
|
+
*/
|
|
5
23
|
export interface IBootResult {
|
|
6
|
-
/** The typed API
|
|
24
|
+
/** The typed API object bound by the wasm to `globalThis[apiName]`. */
|
|
7
25
|
api: ITtscApi;
|
|
26
|
+
|
|
8
27
|
/** The MemFS instance shared with the wasm's virtual filesystem. */
|
|
9
28
|
host: IMemFSHost;
|
|
10
29
|
}
|