@ttsc/wasm 0.12.2 → 0.12.4
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/cmd/ttsc-wasm/main.go +101 -89
- package/cmd/ttsc-wasm/main_wasm.go +6 -4
- package/dist/ttsc.wasm +0 -0
- package/host/api.go +154 -145
- package/host/doc.go +12 -12
- package/host/host.go +236 -225
- package/host/host_native.go +5 -2
- package/host/plugin.go +31 -31
- package/lib/src/MemFS.d.ts +35 -0
- package/lib/src/MemFS.js +57 -4
- package/lib/src/MemFS.js.map +1 -1
- package/lib/src/api.d.ts +46 -13
- package/lib/src/api.js +7 -4
- package/lib/src/api.js.map +1 -1
- package/lib/src/index.d.ts +1 -1
- package/lib/src/index.js +10 -3
- package/lib/src/index.js.map +1 -1
- package/lib/src/instantiate.d.ts +10 -7
- package/lib/src/instantiate.js +20 -3
- package/lib/src/instantiate.js.map +1 -1
- package/package.json +2 -2
- package/shim-vendor/shim/ast/go.mod +9 -0
- package/shim-vendor/shim/ast/go.sum +20 -0
- package/shim-vendor/shim/ast/safe_text.go +86 -0
- package/shim-vendor/shim/ast/shim.go +24 -0
- package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +32 -0
- package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +31 -0
- package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +23 -0
- package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +27 -0
- package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +30 -0
- package/shim-vendor/shim/checker/shim.go +35 -0
- package/shim-vendor/shim/core/shim.go +17 -0
- package/shim-vendor/shim/diagnosticwriter/shim.go +4 -0
- package/shim-vendor/shim/lsp/shim.go +3 -3
- package/shim-vendor/shim/printer/shim.go +20 -0
- package/src/MemFS.ts +92 -7
- package/src/api.ts +46 -13
- package/src/index.ts +1 -5
- package/src/instantiate.ts +37 -14
package/src/MemFS.ts
CHANGED
|
@@ -20,6 +20,15 @@ const S_IFREG = 0o100000;
|
|
|
20
20
|
const DEFAULT_FILE_MODE = S_IFREG | 0o644;
|
|
21
21
|
const DEFAULT_DIR_MODE = S_IFDIR | 0o755;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Subset of the Node.js `fs` module that `wasm_exec.js` calls into.
|
|
25
|
+
*
|
|
26
|
+
* Go's js/wasm runtime routes all `syscall/js` filesystem operations through
|
|
27
|
+
* `globalThis.fs`. In a browser there is no real `fs`, so a MemFS
|
|
28
|
+
* implementation fulfils this interface. Only the operations that
|
|
29
|
+
* typescript-go's compiler exercises are required; the rest are no-ops or
|
|
30
|
+
* return `EPERM`/`EINVAL`.
|
|
31
|
+
*/
|
|
23
32
|
export interface IWasmExecFS {
|
|
24
33
|
constants: Record<string, number>;
|
|
25
34
|
writeSync(fd: number, buf: Uint8Array): number;
|
|
@@ -37,7 +46,10 @@ export interface IWasmExecFS {
|
|
|
37
46
|
mode: number,
|
|
38
47
|
callback: (err: NodeJS.ErrnoException | null, fd: number) => void,
|
|
39
48
|
): void;
|
|
40
|
-
close(
|
|
49
|
+
close(
|
|
50
|
+
fd: number,
|
|
51
|
+
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
52
|
+
): void;
|
|
41
53
|
read(
|
|
42
54
|
fd: number,
|
|
43
55
|
buffer: Uint8Array,
|
|
@@ -67,7 +79,10 @@ export interface IWasmExecFS {
|
|
|
67
79
|
fd: number,
|
|
68
80
|
callback: (err: NodeJS.ErrnoException | null, stats: IFileStats) => void,
|
|
69
81
|
): void;
|
|
70
|
-
fsync(
|
|
82
|
+
fsync(
|
|
83
|
+
fd: number,
|
|
84
|
+
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
85
|
+
): void;
|
|
71
86
|
unlink(
|
|
72
87
|
path: string,
|
|
73
88
|
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
@@ -152,6 +167,12 @@ export interface IWasmExecFS {
|
|
|
152
167
|
): void;
|
|
153
168
|
}
|
|
154
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Stat-like object returned by `stat`, `lstat`, and `fstat`.
|
|
172
|
+
*
|
|
173
|
+
* Mirrors the subset of `fs.Stats` that `wasm_exec.js` reads. Fields not
|
|
174
|
+
* relevant to Go's `os.FileInfo` (e.g. ownership) are zeroed.
|
|
175
|
+
*/
|
|
155
176
|
export interface IFileStats {
|
|
156
177
|
isDirectory(): boolean;
|
|
157
178
|
isFile(): boolean;
|
|
@@ -170,12 +191,19 @@ export interface IFileStats {
|
|
|
170
191
|
blocks: number;
|
|
171
192
|
}
|
|
172
193
|
|
|
194
|
+
/** Internal filesystem tree node. Directories carry an empty `data` buffer. */
|
|
173
195
|
interface INode {
|
|
174
196
|
kind: "file" | "dir";
|
|
175
197
|
data: Uint8Array;
|
|
176
198
|
mtimeMs: number;
|
|
177
199
|
}
|
|
178
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Filesystem error with a POSIX error code and numeric `errno`.
|
|
203
|
+
*
|
|
204
|
+
* Matches the shape of `NodeJS.ErrnoException` so Go's os package interprets it
|
|
205
|
+
* as a proper `os.PathError` with a numeric error code.
|
|
206
|
+
*/
|
|
179
207
|
export class MemFSError extends Error {
|
|
180
208
|
public code: string;
|
|
181
209
|
public errno: number;
|
|
@@ -190,6 +218,7 @@ export class MemFSError extends Error {
|
|
|
190
218
|
}
|
|
191
219
|
}
|
|
192
220
|
|
|
221
|
+
/** Map a POSIX error name to its Linux numeric errno (negative by convention). */
|
|
193
222
|
function errnoForCode(code: string): number {
|
|
194
223
|
switch (code) {
|
|
195
224
|
case "ENOENT":
|
|
@@ -207,6 +236,11 @@ function errnoForCode(code: string): number {
|
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Handle returned by `createMemFS`. Provides the `fs` shim to install on
|
|
241
|
+
* `globalThis` plus convenience methods for seeding the virtual filesystem
|
|
242
|
+
* before booting the wasm.
|
|
243
|
+
*/
|
|
210
244
|
export interface IMemFSHost {
|
|
211
245
|
fs: IWasmExecFS;
|
|
212
246
|
writeFile(path: string, data: string | Uint8Array): void;
|
|
@@ -222,6 +256,12 @@ export interface IMemFSHost {
|
|
|
222
256
|
const encoder = new TextEncoder();
|
|
223
257
|
const decoder = new TextDecoder();
|
|
224
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Resolve a path to an absolute, normalized POSIX path.
|
|
261
|
+
*
|
|
262
|
+
* Collapses `.` and `..` segments and converts backslashes. Returns `"/"` for
|
|
263
|
+
* empty input.
|
|
264
|
+
*/
|
|
225
265
|
function normalize(p: string): string {
|
|
226
266
|
if (!p) return "/";
|
|
227
267
|
const parts = p.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
@@ -237,6 +277,15 @@ function normalize(p: string): string {
|
|
|
237
277
|
return "/" + stack.join("/");
|
|
238
278
|
}
|
|
239
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Create an in-memory filesystem suitable for use as `globalThis.fs` inside a
|
|
282
|
+
* Go/wasm runtime.
|
|
283
|
+
*
|
|
284
|
+
* The returned host exposes the low-level `fs` object (install it on
|
|
285
|
+
* `globalThis.fs` before loading `wasm_exec.js`) and convenience helpers
|
|
286
|
+
* (`writeFile`, `readFile`, `mkdirp`, …) for seeding source files and reading
|
|
287
|
+
* compiler output without touching the real filesystem.
|
|
288
|
+
*/
|
|
240
289
|
export function createMemFS(): IMemFSHost {
|
|
241
290
|
const nodes = new Map<string, INode>();
|
|
242
291
|
nodes.set("/", { kind: "dir", data: new Uint8Array(), mtimeMs: Date.now() });
|
|
@@ -271,6 +320,10 @@ export function createMemFS(): IMemFSHost {
|
|
|
271
320
|
}
|
|
272
321
|
const pipes = new Map<number, IPipeState>();
|
|
273
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Drain buffered pipe data into `buffer[offset..offset+length]`. Returns
|
|
325
|
+
* bytes copied.
|
|
326
|
+
*/
|
|
274
327
|
function drainPipeInto(
|
|
275
328
|
state: IPipeState,
|
|
276
329
|
buffer: Uint8Array,
|
|
@@ -289,19 +342,29 @@ export function createMemFS(): IMemFSHost {
|
|
|
289
342
|
return written;
|
|
290
343
|
}
|
|
291
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Satisfy any pending blocked readers using available pipe data or EOF
|
|
347
|
+
* signal.
|
|
348
|
+
*/
|
|
292
349
|
function flushPipeReaders(state: IPipeState): void {
|
|
293
350
|
while (
|
|
294
351
|
state.pendingReaders.length > 0 &&
|
|
295
352
|
(state.buffers.length > 0 || state.writeClosed)
|
|
296
353
|
) {
|
|
297
354
|
const reader = state.pendingReaders.shift()!;
|
|
298
|
-
const n = drainPipeInto(
|
|
355
|
+
const n = drainPipeInto(
|
|
356
|
+
state,
|
|
357
|
+
reader.buffer,
|
|
358
|
+
reader.offset,
|
|
359
|
+
reader.length,
|
|
360
|
+
);
|
|
299
361
|
// Even at EOF (writeClosed && empty buffers) we satisfy with n=0 which
|
|
300
362
|
// signals EOF to the Go-side caller.
|
|
301
363
|
reader.callback(null, n);
|
|
302
364
|
}
|
|
303
365
|
}
|
|
304
366
|
|
|
367
|
+
/** Silently create any missing ancestor directories for path `p`. */
|
|
305
368
|
function ensureParentDirs(p: string): void {
|
|
306
369
|
const segments = normalize(p).split("/").filter(Boolean);
|
|
307
370
|
segments.pop();
|
|
@@ -358,6 +421,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
358
421
|
return nodes.has(normalize(p));
|
|
359
422
|
}
|
|
360
423
|
|
|
424
|
+
/** Synchronously stat `p`; throws `MemFSError("ENOENT")` if not found. */
|
|
361
425
|
function statSync(p: string): IFileStats {
|
|
362
426
|
const norm = normalize(p);
|
|
363
427
|
const node = nodes.get(norm);
|
|
@@ -365,6 +429,7 @@ export function createMemFS(): IMemFSHost {
|
|
|
365
429
|
return makeStats(node);
|
|
366
430
|
}
|
|
367
431
|
|
|
432
|
+
/** Build an `IFileStats` object from a filesystem node. */
|
|
368
433
|
function makeStats(node: INode): IFileStats {
|
|
369
434
|
const isDir = node.kind === "dir";
|
|
370
435
|
return {
|
|
@@ -386,6 +451,12 @@ export function createMemFS(): IMemFSHost {
|
|
|
386
451
|
};
|
|
387
452
|
}
|
|
388
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Return immediate children of directory `p`, sorted alphabetically.
|
|
456
|
+
*
|
|
457
|
+
* Uses a linear scan over the node map and a `Set` to deduplicate nested
|
|
458
|
+
* paths into direct-child names — O(n) in the number of total nodes.
|
|
459
|
+
*/
|
|
389
460
|
function readdirSync(p: string): string[] {
|
|
390
461
|
const norm = normalize(p);
|
|
391
462
|
const node = nodes.get(norm);
|
|
@@ -438,9 +509,12 @@ export function createMemFS(): IMemFSHost {
|
|
|
438
509
|
if (entry) {
|
|
439
510
|
const node = nodes.get(entry.path);
|
|
440
511
|
if (node && node.kind === "file") {
|
|
512
|
+
// subarray(0) is a zero-copy view over the full incoming buffer.
|
|
441
513
|
const incoming = buf.subarray(0);
|
|
442
514
|
const existing = node.data;
|
|
443
|
-
const next = new Uint8Array(
|
|
515
|
+
const next = new Uint8Array(
|
|
516
|
+
existing.byteLength + incoming.byteLength,
|
|
517
|
+
);
|
|
444
518
|
next.set(existing, 0);
|
|
445
519
|
next.set(incoming, existing.byteLength);
|
|
446
520
|
node.data = next;
|
|
@@ -455,7 +529,11 @@ export function createMemFS(): IMemFSHost {
|
|
|
455
529
|
stderr.buffer += decoder.decode(buf);
|
|
456
530
|
// eslint-disable-next-line no-console
|
|
457
531
|
console.error(
|
|
458
|
-
"[wasm] writeSync to unknown fd " +
|
|
532
|
+
"[wasm] writeSync to unknown fd " +
|
|
533
|
+
fd +
|
|
534
|
+
" (" +
|
|
535
|
+
buf.byteLength +
|
|
536
|
+
" bytes); routed to stderr buffer",
|
|
459
537
|
);
|
|
460
538
|
return buf.length;
|
|
461
539
|
},
|
|
@@ -599,7 +677,10 @@ export function createMemFS(): IMemFSHost {
|
|
|
599
677
|
try {
|
|
600
678
|
callback(null, statSync(p));
|
|
601
679
|
} catch (err) {
|
|
602
|
-
callback(
|
|
680
|
+
callback(
|
|
681
|
+
err as NodeJS.ErrnoException,
|
|
682
|
+
undefined as unknown as IFileStats,
|
|
683
|
+
);
|
|
603
684
|
}
|
|
604
685
|
},
|
|
605
686
|
|
|
@@ -614,7 +695,11 @@ export function createMemFS(): IMemFSHost {
|
|
|
614
695
|
if (pipes.has(fd)) {
|
|
615
696
|
callback(
|
|
616
697
|
null,
|
|
617
|
-
makeStats({
|
|
698
|
+
makeStats({
|
|
699
|
+
kind: "file",
|
|
700
|
+
data: new Uint8Array(),
|
|
701
|
+
mtimeMs: Date.now(),
|
|
702
|
+
}),
|
|
618
703
|
);
|
|
619
704
|
return;
|
|
620
705
|
}
|
package/src/api.ts
CHANGED
|
@@ -3,28 +3,40 @@
|
|
|
3
3
|
// `playground.wasm`, typia's `ttsc-typia.wasm`) exposes the same surface, so
|
|
4
4
|
// one set of types covers them all.
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* API object that every host-built wasm binds to `globalThis[apiName]`.
|
|
8
|
+
*
|
|
9
|
+
* Obtain an instance via `bootTtsc`, which waits for the wasm to signal
|
|
10
|
+
* readiness and returns the typed handle together with its `IMemFSHost`.
|
|
11
|
+
*/
|
|
6
12
|
export interface ITtscApi {
|
|
7
13
|
/** Build metadata reported by the wasm. Useful for diagnostics. */
|
|
8
14
|
version(): ITtscVersion;
|
|
9
15
|
|
|
10
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Compile a project: typecheck + emit. `result` is JSON;
|
|
18
|
+
* `parseResult<ITtscCompileResult>` deserializes it.
|
|
19
|
+
*/
|
|
11
20
|
build(opts: ITtscBuildOpts): Promise<ITtscResult>;
|
|
12
21
|
|
|
13
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Typecheck without emit. `result` is JSON; `parseResult<ITtscCompileResult>`
|
|
24
|
+
* deserializes it.
|
|
25
|
+
*/
|
|
14
26
|
check(opts: ITtscBuildOpts): Promise<ITtscResult>;
|
|
15
27
|
|
|
16
28
|
/**
|
|
17
|
-
* Return every source file the program saw, keyed by project-relative
|
|
18
|
-
*
|
|
19
|
-
*
|
|
29
|
+
* Return every source file the program saw, keyed by project-relative path.
|
|
30
|
+
* Used by playgrounds that want to render the TypeScript view after a source
|
|
31
|
+
* rewriter (e.g. paths) has run. `result` is JSON; use
|
|
20
32
|
* `parseResult<ITtscTransformResult>` to deserialize.
|
|
21
33
|
*/
|
|
22
34
|
transform(opts: ITtscBuildOpts): Promise<ITtscResult>;
|
|
23
35
|
|
|
24
36
|
/**
|
|
25
37
|
* Dispatch a registered plugin's subcommand. Returns the captured stdout /
|
|
26
|
-
* stderr (the same streams the native sidecar binary would write to)
|
|
27
|
-
*
|
|
38
|
+
* stderr (the same streams the native sidecar binary would write to) together
|
|
39
|
+
* with the exit code.
|
|
28
40
|
*/
|
|
29
41
|
plugin(opts: ITtscPluginOpts): Promise<ITtscResult>;
|
|
30
42
|
|
|
@@ -32,6 +44,7 @@ export interface ITtscApi {
|
|
|
32
44
|
plugins(): string[];
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
/** Build metadata embedded in the wasm by the Go linker at compile time. */
|
|
35
48
|
export interface ITtscVersion {
|
|
36
49
|
version: string;
|
|
37
50
|
commit: string;
|
|
@@ -41,13 +54,15 @@ export interface ITtscVersion {
|
|
|
41
54
|
goarch: string;
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
/** Options shared by `build`, `check`, and `transform`. */
|
|
44
58
|
export interface ITtscBuildOpts {
|
|
45
59
|
/** Absolute virtual path the project lives at inside the MemFS. */
|
|
46
60
|
cwd: string;
|
|
47
|
-
/**
|
|
61
|
+
/** Tsconfig path, relative to `cwd`. Defaults to `tsconfig.json`. */
|
|
48
62
|
tsconfig?: string;
|
|
49
63
|
}
|
|
50
64
|
|
|
65
|
+
/** Options for dispatching a named plugin subcommand via `api.plugin`. */
|
|
51
66
|
export interface ITtscPluginOpts {
|
|
52
67
|
/** Plugin id registered with `host.Expose` (e.g. `@ttsc/banner`). */
|
|
53
68
|
name: string;
|
|
@@ -61,6 +76,7 @@ export interface ITtscPluginOpts {
|
|
|
61
76
|
[key: string]: string | boolean | number | undefined;
|
|
62
77
|
}
|
|
63
78
|
|
|
79
|
+
/** Envelope returned by every `ITtscApi` method. */
|
|
64
80
|
export interface ITtscResult {
|
|
65
81
|
/** Exit code. 0 = success, 2 = usage error, 3 = runtime error. */
|
|
66
82
|
code: number;
|
|
@@ -69,23 +85,40 @@ export interface ITtscResult {
|
|
|
69
85
|
/** Anything the wasm wrote to its stderr stream. */
|
|
70
86
|
stderr: string;
|
|
71
87
|
/**
|
|
72
|
-
* For the base endpoints, the JSON-encoded compile/transform result. For
|
|
73
|
-
*
|
|
88
|
+
* For the base endpoints, the JSON-encoded compile/transform result. For the
|
|
89
|
+
* plugin endpoint, this is empty — the plugin's own output sits in
|
|
74
90
|
* stdout/stderr. Use `parseResult<T>` to deserialize.
|
|
75
91
|
*/
|
|
76
92
|
result: string;
|
|
77
93
|
}
|
|
78
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Structured payload inside `ITtscResult.result` for `build` and `check`.
|
|
97
|
+
*
|
|
98
|
+
* `output` maps emit-destination paths (relative to `outDir`) to file contents.
|
|
99
|
+
* It is empty when `check` is called without emit.
|
|
100
|
+
*/
|
|
79
101
|
export interface ITtscCompileResult {
|
|
80
102
|
diagnostics?: ITtscDiagnostic[];
|
|
81
103
|
output: Record<string, string>;
|
|
82
104
|
}
|
|
83
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Structured payload inside `ITtscResult.result` for `transform`.
|
|
108
|
+
*
|
|
109
|
+
* `typescript` maps source file paths (relative to `cwd`) to their
|
|
110
|
+
* post-transform TypeScript text — useful for playgrounds that want to show the
|
|
111
|
+
* rewritten source before it is emitted.
|
|
112
|
+
*/
|
|
84
113
|
export interface ITtscTransformResult {
|
|
85
114
|
diagnostics?: ITtscDiagnostic[];
|
|
86
115
|
typescript: Record<string, string>;
|
|
87
116
|
}
|
|
88
117
|
|
|
118
|
+
/**
|
|
119
|
+
* A single TypeScript compiler diagnostic emitted during `build`, `check`, or
|
|
120
|
+
* `transform`. `line` and `character` are 0-based.
|
|
121
|
+
*/
|
|
89
122
|
export interface ITtscDiagnostic {
|
|
90
123
|
file: string | null;
|
|
91
124
|
category: "error" | "warning";
|
|
@@ -98,9 +131,9 @@ export interface ITtscDiagnostic {
|
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
/**
|
|
101
|
-
* Parse the `result` field of an ITtscResult into the structured payload.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
134
|
+
* Parse the `result` field of an ITtscResult into the structured payload. The
|
|
135
|
+
* wasm returns JSON as a string because js.ValueOf does not handle large nested
|
|
136
|
+
* maps efficiently. Callers JSON.parse exactly once at the boundary.
|
|
104
137
|
*/
|
|
105
138
|
export function parseResult<T>(result: ITtscResult): T | null {
|
|
106
139
|
if (!result.result) return null;
|
package/src/index.ts
CHANGED
|
@@ -9,11 +9,7 @@ export { bootTtsc } from "./instantiate";
|
|
|
9
9
|
export type { IBootTtscOptions, IBootResult } from "./instantiate";
|
|
10
10
|
|
|
11
11
|
export { createMemFS, MemFSError } from "./MemFS";
|
|
12
|
-
export type {
|
|
13
|
-
IMemFSHost,
|
|
14
|
-
IWasmExecFS,
|
|
15
|
-
IFileStats,
|
|
16
|
-
} from "./MemFS";
|
|
12
|
+
export type { IMemFSHost, IWasmExecFS, IFileStats } from "./MemFS";
|
|
17
13
|
|
|
18
14
|
export type {
|
|
19
15
|
ITtscApi,
|
package/src/instantiate.ts
CHANGED
|
@@ -7,39 +7,43 @@
|
|
|
7
7
|
// The boot helper is parameterized by `apiName` so any wasm built with
|
|
8
8
|
// `host.Expose(...)` can be loaded the same way. The base wasm uses "ttsc";
|
|
9
9
|
// downstream consumers pick their own (e.g. "ttscPlayground", "ttscTypia").
|
|
10
|
-
|
|
11
|
-
import { createMemFS, type IMemFSHost } from "./MemFS";
|
|
10
|
+
import { type IMemFSHost, createMemFS } from "./MemFS";
|
|
12
11
|
import type { ITtscApi } from "./api";
|
|
13
12
|
|
|
14
13
|
declare const importScripts: (...urls: string[]) => void;
|
|
15
14
|
|
|
15
|
+
/** Options for `bootTtsc`. All fields except `wasmUrl` have sensible defaults. */
|
|
16
16
|
export interface IBootTtscOptions {
|
|
17
17
|
/** URL of the .wasm to fetch. */
|
|
18
18
|
wasmUrl: string;
|
|
19
19
|
/** URL of wasm_exec.js. Defaults to the same directory as wasmUrl. */
|
|
20
20
|
wasmExecUrl?: string;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* "ttsc".
|
|
22
|
+
* GlobalThis property name the wasm binds. Must match the value the wasm was
|
|
23
|
+
* built with (the `apiName` passed to `host.Expose`). Defaults to "ttsc".
|
|
25
24
|
*/
|
|
26
25
|
apiName?: string;
|
|
27
26
|
/**
|
|
28
|
-
* Optional pre-existing MemFS host. When omitted, a fresh one is created
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Optional pre-existing MemFS host. When omitted, a fresh one is created and
|
|
28
|
+
* stored on the returned BootResult. Pass an existing host when you want to
|
|
29
|
+
* boot multiple wasms over the same filesystem (e.g. base ttsc + a typia
|
|
30
|
+
* wasm) so they share project sources.
|
|
32
31
|
*/
|
|
33
32
|
host?: IMemFSHost;
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
/** Handle returned by `bootTtsc` once the wasm is ready. */
|
|
36
36
|
export interface IBootResult {
|
|
37
|
+
/** The typed API proxy bound by the wasm to `globalThis[apiName]`. */
|
|
37
38
|
api: ITtscApi;
|
|
39
|
+
/** The MemFS instance shared with the wasm's virtual filesystem. */
|
|
38
40
|
host: IMemFSHost;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/** Boot a host-built wasm. Re-entrant only if you reuse the same `host`. */
|
|
42
|
-
export async function bootTtsc(
|
|
44
|
+
export async function bootTtsc(
|
|
45
|
+
options: IBootTtscOptions,
|
|
46
|
+
): Promise<IBootResult> {
|
|
43
47
|
const wasmUrl = options.wasmUrl;
|
|
44
48
|
const wasmExecUrl = options.wasmExecUrl ?? defaultWasmExecUrl(wasmUrl);
|
|
45
49
|
const apiName = options.apiName ?? "ttsc";
|
|
@@ -64,11 +68,12 @@ export async function bootTtsc(options: IBootTtscOptions): Promise<IBootResult>
|
|
|
64
68
|
|
|
65
69
|
const response = await fetch(wasmUrl);
|
|
66
70
|
if (!response.ok) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`,
|
|
69
|
-
);
|
|
71
|
+
throw new Error(`bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`);
|
|
70
72
|
}
|
|
71
|
-
const wasm = await WebAssembly.instantiateStreaming(
|
|
73
|
+
const wasm = await WebAssembly.instantiateStreaming(
|
|
74
|
+
response,
|
|
75
|
+
go.importObject,
|
|
76
|
+
);
|
|
72
77
|
// go.run never resolves until the wasm exits; we don't await it.
|
|
73
78
|
void go.run(wasm.instance);
|
|
74
79
|
await ready;
|
|
@@ -81,17 +86,35 @@ export async function bootTtsc(options: IBootTtscOptions): Promise<IBootResult>
|
|
|
81
86
|
return { api, host };
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Derive the `wasm_exec.js` URL from the wasm URL by replacing the filename.
|
|
91
|
+
*
|
|
92
|
+
* If `wasmUrl` has no directory component, returns `"wasm_exec.js"` (same
|
|
93
|
+
* directory as the caller's base URL).
|
|
94
|
+
*/
|
|
84
95
|
function defaultWasmExecUrl(wasmUrl: string): string {
|
|
85
96
|
const slash = wasmUrl.lastIndexOf("/");
|
|
86
97
|
if (slash < 0) return "wasm_exec.js";
|
|
87
98
|
return wasmUrl.slice(0, slash + 1) + "wasm_exec.js";
|
|
88
99
|
}
|
|
89
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Minimal shape of the `Go` constructor that `wasm_exec.js` exports on
|
|
103
|
+
* `globalThis`. Only the members we actually use are typed here.
|
|
104
|
+
*/
|
|
90
105
|
interface IGoInstance {
|
|
91
106
|
importObject: WebAssembly.Imports;
|
|
92
107
|
run(instance: WebAssembly.Instance): Promise<void>;
|
|
93
108
|
}
|
|
94
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Minimal `process` shim required by `wasm_exec.js` in non-Node environments.
|
|
112
|
+
*
|
|
113
|
+
* Go's js/wasm bridge reads `process.pid`, `process.ppid`, and calls
|
|
114
|
+
* `process.cwd()`. `getuid`/`getgid` and friends return `-1` (root-less).
|
|
115
|
+
* `umask` and `getgroups` are never exercised by the compiler but are included
|
|
116
|
+
* for completeness so unexpected calls surface as clear errors.
|
|
117
|
+
*/
|
|
95
118
|
function createProcessShim(): Record<string, unknown> {
|
|
96
119
|
return {
|
|
97
120
|
getuid: () => -1,
|