@ttsc/wasm 0.22.0 → 0.24.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 CHANGED
@@ -100,9 +100,12 @@ The `version`, `commit`, and `date` variables in `host/host.go` are all overrida
100
100
  ```ts
101
101
  import { bootTtsc } from "@ttsc/wasm";
102
102
 
103
+ const controller = new AbortController();
103
104
  const { api, host } = await bootTtsc({
104
105
  wasmUrl: "/your.wasm",
105
106
  apiName: "yourApi",
107
+ signal: controller.signal,
108
+ timeoutMs: 60_000,
106
109
  });
107
110
 
108
111
  host.writeFile("/work/tsconfig.json", '{"compilerOptions":{"strict":true}}');
@@ -112,6 +115,10 @@ const result = await api.build({ cwd: "/work" });
112
115
  console.log(result.result); // JSON: { diagnostics, output }
113
116
  ```
114
117
 
118
+ `timeoutMs` defaults to 60 seconds and covers serialization behind an earlier boot, fetch, instantiation, and the Go readiness signal. A caller abort or deadline cancels the shared in-flight boot. Failures before `go.run` clear cache and callback state so a later call can retry. `importScripts` is synchronous, so cancellation is observed immediately before and after that call but cannot interrupt the script while it evaluates.
119
+
120
+ Once `go.run` starts, JavaScript cannot terminate the Go wasm runtime. A boot failure from that point throws `BootTtscWorkerTerminationError` with code `TTSC_WASM_WORKER_TERMINATION_REQUIRED`, and later boots for the same `apiName` reject without installing another readiness bridge. Terminate and replace that Worker before retrying.
121
+
115
122
  Booting two wasms with the same `apiName` overwrites the previous global binding; pick a unique `apiName` per binary. `bootTtsc` also installs shared `fs` and `process` globals in its Worker, so use separate Workers when binaries need independent filesystems.
116
123
 
117
124
  ## Fountain API (snapshot, AST, type checker)
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schema": 1,
3
- "identity": "6f7894fab98969ad707bca087ef102735838475dab17f07554d00a2c9772b9c7",
3
+ "identity": "43f55c14dfcfa3b29de25ab986c544d115fc6e9aaef6a297d1869c6e303a0653",
4
4
  "artifacts": [
5
5
  {
6
6
  "path": "/home/runner/work/ttsc/ttsc/packages/wasm/dist/ttsc.wasm",
7
7
  "type": "file",
8
- "sha256": "db5fb453c335d95e128c3c94f0255549c998aa3999332bb910e07016f8204ad5"
8
+ "sha256": "8d8e5608f22bc13a9dbff056da11040cf02b7de7468c23c1dd00276c9c150895"
9
9
  },
10
10
  {
11
11
  "path": "/home/runner/work/ttsc/ttsc/packages/wasm/dist/wasm_exec.js",
package/dist/ttsc.wasm CHANGED
Binary file
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A boot failure that requires replacing the current Web Worker.
3
+ *
4
+ * Once `go.run` starts, JavaScript cannot stop that Go runtime. Retrying the
5
+ * same API name in the same Worker would install a new readiness bridge that
6
+ * the stale runtime could invoke, so `bootTtsc` terminally rejects later boots
7
+ * until the caller terminates the Worker.
8
+ */
9
+ export declare class BootTtscWorkerTerminationError extends Error {
10
+ static readonly CODE = "TTSC_WASM_WORKER_TERMINATION_REQUIRED";
11
+ readonly apiName: string;
12
+ readonly code = "TTSC_WASM_WORKER_TERMINATION_REQUIRED";
13
+ readonly cause: unknown;
14
+ constructor(apiName: string, cause: unknown);
15
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BootTtscWorkerTerminationError = void 0;
4
+ /**
5
+ * A boot failure that requires replacing the current Web Worker.
6
+ *
7
+ * Once `go.run` starts, JavaScript cannot stop that Go runtime. Retrying the
8
+ * same API name in the same Worker would install a new readiness bridge that
9
+ * the stale runtime could invoke, so `bootTtsc` terminally rejects later boots
10
+ * until the caller terminates the Worker.
11
+ */
12
+ class BootTtscWorkerTerminationError extends Error {
13
+ static CODE = "TTSC_WASM_WORKER_TERMINATION_REQUIRED";
14
+ apiName;
15
+ code = BootTtscWorkerTerminationError.CODE;
16
+ cause;
17
+ constructor(apiName, cause) {
18
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? "boot failed");
19
+ super(`[${BootTtscWorkerTerminationError.CODE}] ${causeMessage} bootTtsc: the ${apiName} Go runtime already started; terminate and replace this Worker before retrying.`);
20
+ this.name = "BootTtscWorkerTerminationError";
21
+ this.apiName = apiName;
22
+ this.cause = cause;
23
+ }
24
+ }
25
+ exports.BootTtscWorkerTerminationError = BootTtscWorkerTerminationError;
26
+ //# sourceMappingURL=BootTtscWorkerTerminationError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BootTtscWorkerTerminationError.js","sourceRoot":"","sources":["../../src/BootTtscWorkerTerminationError.ts"],"names":[],"mappings":";;;AAAA;;;;;;;GAOG;AACH,oCAA4C,SAAQ,KAAK;IAChD,MAAM,CAAU,IAAI,GAAG,uCAAuC,CAAC;IAEtD,OAAO,CAAS;IAChB,IAAI,GAAG,8BAA8B,CAAC,IAAI,CAAC;IAClC,KAAK,CAAU;IAExC,YAAmB,OAAe,EAAE,KAAc;QAChD,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC,CAAC;QAC1E,KAAK,CACH,IAAI,8BAA8B,CAAC,IAAI,KAAK,YAAY,kBAAkB,OAAO,iFAAiF,CACnK,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF"}
@@ -1,13 +1,17 @@
1
1
  import type { IBootResult } from "./structures/IBootResult";
2
2
  import type { IBootTtscOptions } from "./structures/IBootTtscOptions";
3
+ /** Finite default for queueing, fetch, instantiation, and Go readiness. */
4
+ export declare const DEFAULT_BOOT_TIMEOUT_MS = 60000;
3
5
  /**
4
6
  * Boot a host-built wasm. Re-entrant only if you reuse the same `host`.
5
7
  *
6
8
  * Concurrent calls with the same `(apiName, wasmUrl)` pair share the same
7
9
  * in-flight boot. Calls with the same `apiName` but different `wasmUrl` are
8
10
  * serialized via `bootChainByApiName` so they don't race on the shared
9
- * `globalThis[apiName+"Ready"]` resolver slot. On rejection the cache entries
10
- * are cleared so the next call retries from scratch.
11
+ * `globalThis[apiName+"Ready"]` resolver slot. A rejection before `go.run`
12
+ * clears the cache entries so the next call can retry from scratch. A rejection
13
+ * after `go.run` terminally poisons that API name because JavaScript cannot
14
+ * stop the old runtime; replace the Worker before retrying.
11
15
  *
12
16
  * **Single-Worker caveat.** Even with the chain, a second boot loaded into the
13
17
  * SAME Worker after a first boot completes will overlay its Go runtime on top
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_BOOT_TIMEOUT_MS = void 0;
3
4
  exports.bootTtsc = bootTtsc;
4
5
  // Boots a host-built wasm and returns the JS-side handle.
5
6
  //
@@ -10,16 +11,15 @@ exports.bootTtsc = bootTtsc;
10
11
  // The boot helper is parameterized by `apiName` so any wasm built with
11
12
  // `host.Expose(...)` can be loaded the same way. The base wasm uses "ttsc";
12
13
  // downstream consumers pick their own (e.g. "ttscPlayground", "ttscTypia").
14
+ const BootTtscWorkerTerminationError_1 = require("./BootTtscWorkerTerminationError");
13
15
  const createMemFS_1 = require("./createMemFS");
16
+ const bootsInFlight = new Map();
14
17
  /**
15
- * Per-(apiName, wasmUrl) single-flight cache for boots. Keying on apiName alone
16
- * would let a second call with the same apiName but a different wasmUrl
17
- * silently return the cached IBootResult of the first wasm — the caller would
18
- * think they booted a fresh binary while the cached one stayed in place. The
19
- * composite key lets HMR / cache-busting query strings get a fresh boot while
20
- * still single-flighting genuine concurrent duplicate calls.
18
+ * Terminal failures keyed by the global API bridge they own. A failed runtime
19
+ * cannot be stopped after `go.run` starts, so no later boot may install another
20
+ * readiness bridge for that API name inside the same Worker.
21
21
  */
22
- const bootsInFlight = new Map();
22
+ const terminalBootFailuresByApiName = new Map();
23
23
  /**
24
24
  * Per-apiName serialization chain. Two concurrent boots with the same apiName
25
25
  * but different wasmUrls each install their own `globalThis[apiName
@@ -29,6 +29,8 @@ const bootsInFlight = new Map();
29
29
  * Go-side`Ready.Invoke()` always lands on the resolver that boot installed.
30
30
  */
31
31
  const bootChainByApiName = new Map();
32
+ /** Finite default for queueing, fetch, instantiation, and Go readiness. */
33
+ exports.DEFAULT_BOOT_TIMEOUT_MS = 60_000;
32
34
  function bootKey(apiName, wasmUrl) {
33
35
  return `${apiName}|${resolveWasmUrl(wasmUrl)}`;
34
36
  }
@@ -54,8 +56,10 @@ function resolveWasmUrl(wasmUrl) {
54
56
  * Concurrent calls with the same `(apiName, wasmUrl)` pair share the same
55
57
  * in-flight boot. Calls with the same `apiName` but different `wasmUrl` are
56
58
  * serialized via `bootChainByApiName` so they don't race on the shared
57
- * `globalThis[apiName+"Ready"]` resolver slot. On rejection the cache entries
58
- * are cleared so the next call retries from scratch.
59
+ * `globalThis[apiName+"Ready"]` resolver slot. A rejection before `go.run`
60
+ * clears the cache entries so the next call can retry from scratch. A rejection
61
+ * after `go.run` terminally poisons that API name because JavaScript cannot
62
+ * stop the old runtime; replace the Worker before retrying.
59
63
  *
60
64
  * **Single-Worker caveat.** Even with the chain, a second boot loaded into the
61
65
  * SAME Worker after a first boot completes will overlay its Go runtime on top
@@ -69,30 +73,54 @@ function resolveWasmUrl(wasmUrl) {
69
73
  function bootTtsc(options) {
70
74
  const apiName = options.apiName ?? "ttsc";
71
75
  const key = bootKey(apiName, options.wasmUrl);
76
+ const timeoutMs = resolveBootTimeout(options.timeoutMs);
77
+ const terminalFailure = terminalBootFailuresByApiName.get(apiName);
78
+ if (terminalFailure)
79
+ return Promise.reject(terminalFailure);
72
80
  const inflight = bootsInFlight.get(key);
73
- if (inflight)
74
- return inflight;
81
+ if (inflight) {
82
+ attachBootCancellation(inflight, options.signal, timeoutMs);
83
+ return inflight.promise;
84
+ }
85
+ const controller = new AbortController();
75
86
  const prior = bootChainByApiName.get(apiName) ?? Promise.resolve();
76
- const promise = prior
87
+ const queuedCancellation = createBootCancellationPromise(controller.signal, apiName, () => "waiting for an earlier boot");
88
+ const queued = prior
77
89
  .catch(() => {
78
90
  /* don't propagate a prior boot's rejection — let this one run */
79
91
  })
80
- .then(() => bootTtscOnce(options, apiName))
92
+ .then(() => {
93
+ throwIfBootWorkerTerminated(apiName);
94
+ throwIfBootCanceled(controller.signal, apiName, "waiting for an earlier boot");
95
+ queuedCancellation.dispose();
96
+ return bootTtscOnce(options, apiName, controller.signal);
97
+ });
98
+ let entry;
99
+ const promise = Promise.race([queued, queuedCancellation.promise])
81
100
  .catch((err) => {
82
- bootsInFlight.delete(key);
101
+ if (bootsInFlight.get(key) === entry)
102
+ bootsInFlight.delete(key);
83
103
  throw err;
84
- });
85
- bootsInFlight.set(key, promise);
104
+ })
105
+ .finally(queuedCancellation.dispose);
106
+ entry = { controller, promise };
107
+ bootsInFlight.set(key, entry);
108
+ attachBootCancellation(entry, options.signal, timeoutMs);
86
109
  // Track the chain head for this apiName so the next boot waits on it.
87
110
  // Swallow on the chain head specifically: we already throw to the
88
111
  // immediate caller; surfacing it through the chain would reject every
89
112
  // subsequent boot just because this one failed.
90
- bootChainByApiName.set(apiName, promise.catch(() => { }));
113
+ bootChainByApiName.set(apiName, queued.catch(() => { }));
91
114
  return promise;
92
115
  }
93
- async function bootTtscOnce(options, apiName) {
116
+ async function bootTtscOnce(options, apiName, signal) {
94
117
  const wasmUrl = options.wasmUrl;
95
118
  const wasmExecUrl = options.wasmExecUrl ?? defaultWasmExecUrl(wasmUrl);
119
+ let phase = "preparing the wasm host";
120
+ const cancellation = createBootCancellationPromise(signal, apiName, () => phase);
121
+ let readyCb;
122
+ let failedCb;
123
+ let runtimeStarted = false;
96
124
  const host = options.host ?? (0, createMemFS_1.createMemFS)();
97
125
  const globalAny = globalThis;
98
126
  // Install fs / process only if they aren't already in place, and remember
@@ -123,14 +151,18 @@ async function bootTtscOnce(options, apiName) {
123
151
  delete globalAny.process;
124
152
  };
125
153
  try {
154
+ throwIfBootCanceled(signal, apiName, phase);
126
155
  // wasm_exec.js installs `globalThis.Go`. It also reads globalThis.fs at
127
156
  // module-eval time, so this import must follow the assignment above.
157
+ phase = `loading ${wasmExecUrl}`;
128
158
  importScripts(wasmExecUrl);
159
+ // importScripts is synchronous and therefore cannot be interrupted while
160
+ // the script evaluates. Observe a cancellation that arrived around that
161
+ // boundary before starting any asynchronous work.
162
+ throwIfBootCanceled(signal, apiName, phase);
129
163
  // Race the Ready resolver against a Failed signal so a wasm-side fault
130
164
  // (e.g. `host.Expose` refusing a duplicate call) surfaces here instead of
131
165
  // hanging on `await ready` forever.
132
- let readyCb;
133
- let failedCb;
134
166
  const ready = new Promise((resolve, reject) => {
135
167
  readyCb = () => {
136
168
  delete globalAny[apiName + "Failed"];
@@ -148,11 +180,13 @@ async function bootTtscOnce(options, apiName) {
148
180
  throw new Error(`bootTtsc: globalThis.Go was not installed by ${wasmExecUrl} — the file may not have loaded (CSP block, wrong content type, 404), or it is not the wasm_exec.js shipped with the Go toolchain.`);
149
181
  }
150
182
  const go = new goCtor();
151
- const response = await fetch(wasmUrl);
183
+ phase = `fetching ${wasmUrl}`;
184
+ const response = await raceBootCancellation(fetch(wasmUrl, { signal }), cancellation.promise, signal, apiName, () => phase);
152
185
  if (!response.ok) {
153
186
  throw new Error(`bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`);
154
187
  }
155
- const wasm = await WebAssembly.instantiateStreaming(response, go.importObject);
188
+ phase = `instantiating ${wasmUrl}`;
189
+ const wasm = await raceBootCancellation(WebAssembly.instantiateStreaming(response, go.importObject), cancellation.promise, signal, apiName, () => phase);
156
190
  // A normal host keeps `go.run` pending forever after signaling Ready, so a
157
191
  // settlement (fulfil OR reject) BEFORE Ready means the Go runtime exited or
158
192
  // panicked before it could register — e.g. an early `host.Expose` panic
@@ -161,6 +195,7 @@ async function bootTtscOnce(options, apiName) {
161
195
  // The standard Go runner discards the exit code, so an unknown early exit
162
196
  // can only synthesize a generic message; known host validation failures
163
197
  // reject through Failed above and keep their original cause.
198
+ runtimeStarted = true;
164
199
  const runPromise = Promise.resolve(go.run(wasm.instance));
165
200
  const earlyExit = runPromise.then(() => {
166
201
  throw new Error(`bootTtsc: the ${apiName} wasm runtime exited before signaling readiness (the host may have panicked; check the wasm stderr).`);
@@ -171,17 +206,8 @@ async function bootTtscOnce(options, apiName) {
171
206
  // must not surface as an unhandled rejection. Attach a terminal handler to
172
207
  // the losing branch.
173
208
  earlyExit.catch(() => { });
174
- try {
175
- await Promise.race([ready, earlyExit]);
176
- }
177
- finally {
178
- // Drop this attempt's readiness bridge so a later boot for the same
179
- // apiName installs a clean pair and no stale resolver survives.
180
- if (globalAny[apiName + "Ready"] === readyCb)
181
- delete globalAny[apiName + "Ready"];
182
- if (globalAny[apiName + "Failed"] === failedCb)
183
- delete globalAny[apiName + "Failed"];
184
- }
209
+ phase = `waiting for ${apiName} readiness`;
210
+ await raceBootCancellation(Promise.race([ready, earlyExit]), cancellation.promise, signal, apiName, () => phase);
185
211
  const api = globalAny[apiName];
186
212
  if (!api)
187
213
  throw new Error(`bootTtsc: ${apiName} global was not set — was the wasm built with host.Expose(${JSON.stringify(apiName)}, ...)?`);
@@ -189,8 +215,111 @@ async function bootTtscOnce(options, apiName) {
189
215
  }
190
216
  catch (err) {
191
217
  restoreGlobals();
218
+ if (runtimeStarted)
219
+ throw poisonBootWorker(apiName, err);
192
220
  throw err;
193
221
  }
222
+ finally {
223
+ cancellation.dispose();
224
+ // Drop this attempt's readiness bridge on every path so failed fetches,
225
+ // instantiation failures, and cancellations cannot poison the next boot.
226
+ if (globalAny[apiName + "Ready"] === readyCb)
227
+ delete globalAny[apiName + "Ready"];
228
+ if (globalAny[apiName + "Failed"] === failedCb)
229
+ delete globalAny[apiName + "Failed"];
230
+ }
231
+ }
232
+ function throwIfBootWorkerTerminated(apiName) {
233
+ const failure = terminalBootFailuresByApiName.get(apiName);
234
+ if (failure)
235
+ throw failure;
236
+ }
237
+ function poisonBootWorker(apiName, cause) {
238
+ const existing = terminalBootFailuresByApiName.get(apiName);
239
+ if (existing)
240
+ return existing;
241
+ const failure = new BootTtscWorkerTerminationError_1.BootTtscWorkerTerminationError(apiName, cause);
242
+ terminalBootFailuresByApiName.set(apiName, failure);
243
+ return failure;
244
+ }
245
+ function resolveBootTimeout(timeoutMs) {
246
+ const value = timeoutMs ?? exports.DEFAULT_BOOT_TIMEOUT_MS;
247
+ if (!Number.isSafeInteger(value) || value <= 0 || value > 2_147_483_647)
248
+ throw new RangeError("bootTtsc: timeoutMs must be a positive integer no greater than 2147483647.");
249
+ return value;
250
+ }
251
+ /**
252
+ * Couple every caller's cancellation policy to the shared single-flight. A
253
+ * duplicate caller is joining the same mutable boot, so its cancellation
254
+ * cancels that shared attempt rather than pretending only one waiter left.
255
+ */
256
+ function attachBootCancellation(entry, callerSignal, timeoutMs) {
257
+ const abortFromCaller = () => {
258
+ if (!entry.controller.signal.aborted)
259
+ entry.controller.abort({
260
+ kind: "abort",
261
+ reason: callerSignal?.reason,
262
+ });
263
+ };
264
+ if (callerSignal?.aborted)
265
+ abortFromCaller();
266
+ else
267
+ callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
268
+ const timer = setTimeout(() => {
269
+ if (!entry.controller.signal.aborted)
270
+ entry.controller.abort({
271
+ kind: "timeout",
272
+ timeoutMs,
273
+ });
274
+ }, timeoutMs);
275
+ const cleanup = () => {
276
+ clearTimeout(timer);
277
+ callerSignal?.removeEventListener("abort", abortFromCaller);
278
+ };
279
+ void entry.promise.then(cleanup, cleanup);
280
+ }
281
+ function createBootCancellationPromise(signal, apiName, getPhase) {
282
+ let rejectCancellation;
283
+ const promise = new Promise((_resolve, reject) => {
284
+ rejectCancellation = reject;
285
+ });
286
+ // Some synchronous boundaries observe cancellation with throwIfBootCanceled
287
+ // before a Promise.race consumes this branch. Keep that rejection owned.
288
+ void promise.catch(() => { });
289
+ const onAbort = () => {
290
+ rejectCancellation(bootCancellationError(signal, apiName, getPhase()));
291
+ };
292
+ signal.addEventListener("abort", onAbort, { once: true });
293
+ if (signal.aborted)
294
+ onAbort();
295
+ return {
296
+ promise,
297
+ dispose: () => signal.removeEventListener("abort", onAbort),
298
+ };
299
+ }
300
+ async function raceBootCancellation(work, cancellation, signal, apiName, getPhase) {
301
+ try {
302
+ return await Promise.race([work, cancellation]);
303
+ }
304
+ catch (error) {
305
+ if (signal.aborted)
306
+ throw bootCancellationError(signal, apiName, getPhase());
307
+ throw error;
308
+ }
309
+ }
310
+ function throwIfBootCanceled(signal, apiName, phase) {
311
+ if (signal.aborted)
312
+ throw bootCancellationError(signal, apiName, phase);
313
+ }
314
+ function bootCancellationError(signal, apiName, phase) {
315
+ const reason = signal.reason;
316
+ if (reason?.kind === "timeout")
317
+ return new Error(`bootTtsc: timed out after ${reason.timeoutMs}ms while ${phase} for ${apiName}.`);
318
+ const error = new Error(`bootTtsc: aborted while ${phase} for ${apiName}.`);
319
+ const cause = reason?.kind === "abort" ? reason.reason : signal.reason;
320
+ if (cause !== undefined)
321
+ error.cause = cause;
322
+ return error;
194
323
  }
195
324
  /**
196
325
  * Derive the `wasm_exec.js` URL from the wasm URL by replacing the filename.
@@ -1 +1 @@
1
- {"version":3,"file":"bootTtsc.js","sourceRoot":"","sources":["../../src/bootTtsc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bootTtsc.js","sourceRoot":"","sources":["../../src/bootTtsc.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export * from "./bootTtsc";
2
+ export * from "./BootTtscWorkerTerminationError";
2
3
  export * from "./createMemFS";
3
4
  export * from "./MemFSError";
4
5
  export * from "./parseResult";
package/lib/src/index.js CHANGED
@@ -21,6 +21,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
21
21
  };
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  __exportStar(require("./bootTtsc"), exports);
24
+ __exportStar(require("./BootTtscWorkerTerminationError"), exports);
24
25
  __exportStar(require("./createMemFS"), exports);
25
26
  __exportStar(require("./MemFSError"), exports);
26
27
  __exportStar(require("./parseResult"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,uCAAuC;AACvC,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,oBAAoB;;;;;;;;;;;;;;;;AAEpB,6CAA2B;AAC3B,gDAA8B;AAC9B,+CAA6B;AAC7B,gDAA8B;AAC9B,qDAAmC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,uCAAuC;AACvC,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,oBAAoB;;;;;;;;;;;;;;;;AAEpB,6CAA2B;AAC3B,mEAAiD;AACjD,gDAA8B;AAC9B,+CAA6B;AAC7B,gDAA8B;AAC9B,qDAAmC"}
@@ -3,6 +3,17 @@ import type { IMemFSHost } from "./IMemFSHost";
3
3
  export interface IBootTtscOptions {
4
4
  /** URL of the .wasm to fetch. */
5
5
  wasmUrl: string;
6
+ /** Cancel this boot attempt, including a shared in-flight attempt. */
7
+ signal?: AbortSignal;
8
+ /**
9
+ * Maximum initialization time in milliseconds. Defaults to 60 seconds.
10
+ *
11
+ * The deadline covers queued, fetch, instantiate, and readiness phases.
12
+ * `importScripts` is synchronous and can only observe cancellation before or
13
+ * after it returns. A failure after `go.run` starts requires terminating the
14
+ * Worker; later boots of the same API name reject terminally.
15
+ */
16
+ timeoutMs?: number;
6
17
  /** URL of wasm_exec.js. Defaults to the same directory as wasmUrl. */
7
18
  wasmExecUrl?: string;
8
19
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/wasm",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Build in-browser ttsc playgrounds. Compose ttsc + typescript-go with your own plugins via the Go host helper.",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A boot failure that requires replacing the current Web Worker.
3
+ *
4
+ * Once `go.run` starts, JavaScript cannot stop that Go runtime. Retrying the
5
+ * same API name in the same Worker would install a new readiness bridge that
6
+ * the stale runtime could invoke, so `bootTtsc` terminally rejects later boots
7
+ * until the caller terminates the Worker.
8
+ */
9
+ export class BootTtscWorkerTerminationError extends Error {
10
+ public static readonly CODE = "TTSC_WASM_WORKER_TERMINATION_REQUIRED";
11
+
12
+ public readonly apiName: string;
13
+ public readonly code = BootTtscWorkerTerminationError.CODE;
14
+ public override readonly cause: unknown;
15
+
16
+ public constructor(apiName: string, cause: unknown) {
17
+ const causeMessage =
18
+ cause instanceof Error ? cause.message : String(cause ?? "boot failed");
19
+ super(
20
+ `[${BootTtscWorkerTerminationError.CODE}] ${causeMessage} bootTtsc: the ${apiName} Go runtime already started; terminate and replace this Worker before retrying.`,
21
+ );
22
+ this.name = "BootTtscWorkerTerminationError";
23
+ this.apiName = apiName;
24
+ this.cause = cause;
25
+ }
26
+ }
package/src/bootTtsc.ts CHANGED
@@ -7,6 +7,7 @@
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
+ import { BootTtscWorkerTerminationError } from "./BootTtscWorkerTerminationError";
10
11
  import { createMemFS } from "./createMemFS";
11
12
  import type { IBootResult } from "./structures/IBootResult";
12
13
  import type { IBootTtscOptions } from "./structures/IBootTtscOptions";
@@ -22,7 +23,28 @@ declare const importScripts: (...urls: string[]) => void;
22
23
  * composite key lets HMR / cache-busting query strings get a fresh boot while
23
24
  * still single-flighting genuine concurrent duplicate calls.
24
25
  */
25
- const bootsInFlight = new Map<string, Promise<IBootResult>>();
26
+ interface BootInFlight {
27
+ controller: AbortController;
28
+ promise: Promise<IBootResult>;
29
+ }
30
+
31
+ interface BootCancellationReason {
32
+ kind: "abort" | "timeout";
33
+ reason?: unknown;
34
+ timeoutMs?: number;
35
+ }
36
+
37
+ const bootsInFlight = new Map<string, BootInFlight>();
38
+
39
+ /**
40
+ * Terminal failures keyed by the global API bridge they own. A failed runtime
41
+ * cannot be stopped after `go.run` starts, so no later boot may install another
42
+ * readiness bridge for that API name inside the same Worker.
43
+ */
44
+ const terminalBootFailuresByApiName = new Map<
45
+ string,
46
+ BootTtscWorkerTerminationError
47
+ >();
26
48
 
27
49
  /**
28
50
  * Per-apiName serialization chain. Two concurrent boots with the same apiName
@@ -34,6 +56,9 @@ const bootsInFlight = new Map<string, Promise<IBootResult>>();
34
56
  */
35
57
  const bootChainByApiName = new Map<string, Promise<unknown>>();
36
58
 
59
+ /** Finite default for queueing, fetch, instantiation, and Go readiness. */
60
+ export const DEFAULT_BOOT_TIMEOUT_MS = 60_000;
61
+
37
62
  function bootKey(apiName: string, wasmUrl: string): string {
38
63
  return `${apiName}|${resolveWasmUrl(wasmUrl)}`;
39
64
  }
@@ -61,8 +86,10 @@ function resolveWasmUrl(wasmUrl: string): string {
61
86
  * Concurrent calls with the same `(apiName, wasmUrl)` pair share the same
62
87
  * in-flight boot. Calls with the same `apiName` but different `wasmUrl` are
63
88
  * serialized via `bootChainByApiName` so they don't race on the shared
64
- * `globalThis[apiName+"Ready"]` resolver slot. On rejection the cache entries
65
- * are cleared so the next call retries from scratch.
89
+ * `globalThis[apiName+"Ready"]` resolver slot. A rejection before `go.run`
90
+ * clears the cache entries so the next call can retry from scratch. A rejection
91
+ * after `go.run` terminally poisons that API name because JavaScript cannot
92
+ * stop the old runtime; replace the Worker before retrying.
66
93
  *
67
94
  * **Single-Worker caveat.** Even with the chain, a second boot loaded into the
68
95
  * SAME Worker after a first boot completes will overlay its Go runtime on top
@@ -76,26 +103,52 @@ function resolveWasmUrl(wasmUrl: string): string {
76
103
  export function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
77
104
  const apiName = options.apiName ?? "ttsc";
78
105
  const key = bootKey(apiName, options.wasmUrl);
106
+ const timeoutMs = resolveBootTimeout(options.timeoutMs);
107
+ const terminalFailure = terminalBootFailuresByApiName.get(apiName);
108
+ if (terminalFailure) return Promise.reject(terminalFailure);
79
109
  const inflight = bootsInFlight.get(key);
80
- if (inflight) return inflight;
110
+ if (inflight) {
111
+ attachBootCancellation(inflight, options.signal, timeoutMs);
112
+ return inflight.promise;
113
+ }
114
+ const controller = new AbortController();
81
115
  const prior = bootChainByApiName.get(apiName) ?? Promise.resolve();
82
- const promise = prior
116
+ const queuedCancellation = createBootCancellationPromise(
117
+ controller.signal,
118
+ apiName,
119
+ () => "waiting for an earlier boot",
120
+ );
121
+ const queued = prior
83
122
  .catch(() => {
84
123
  /* don't propagate a prior boot's rejection — let this one run */
85
124
  })
86
- .then(() => bootTtscOnce(options, apiName))
125
+ .then(() => {
126
+ throwIfBootWorkerTerminated(apiName);
127
+ throwIfBootCanceled(
128
+ controller.signal,
129
+ apiName,
130
+ "waiting for an earlier boot",
131
+ );
132
+ queuedCancellation.dispose();
133
+ return bootTtscOnce(options, apiName, controller.signal);
134
+ });
135
+ let entry!: BootInFlight;
136
+ const promise = Promise.race([queued, queuedCancellation.promise])
87
137
  .catch((err) => {
88
- bootsInFlight.delete(key);
138
+ if (bootsInFlight.get(key) === entry) bootsInFlight.delete(key);
89
139
  throw err;
90
- });
91
- bootsInFlight.set(key, promise);
140
+ })
141
+ .finally(queuedCancellation.dispose);
142
+ entry = { controller, promise };
143
+ bootsInFlight.set(key, entry);
144
+ attachBootCancellation(entry, options.signal, timeoutMs);
92
145
  // Track the chain head for this apiName so the next boot waits on it.
93
146
  // Swallow on the chain head specifically: we already throw to the
94
147
  // immediate caller; surfacing it through the chain would reject every
95
148
  // subsequent boot just because this one failed.
96
149
  bootChainByApiName.set(
97
150
  apiName,
98
- promise.catch(() => {}),
151
+ queued.catch(() => {}),
99
152
  );
100
153
  return promise;
101
154
  }
@@ -103,9 +156,19 @@ export function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
103
156
  async function bootTtscOnce(
104
157
  options: IBootTtscOptions,
105
158
  apiName: string,
159
+ signal: AbortSignal,
106
160
  ): Promise<IBootResult> {
107
161
  const wasmUrl = options.wasmUrl;
108
162
  const wasmExecUrl = options.wasmExecUrl ?? defaultWasmExecUrl(wasmUrl);
163
+ let phase = "preparing the wasm host";
164
+ const cancellation = createBootCancellationPromise(
165
+ signal,
166
+ apiName,
167
+ () => phase,
168
+ );
169
+ let readyCb: (() => void) | undefined;
170
+ let failedCb: ((err: unknown) => void) | undefined;
171
+ let runtimeStarted = false;
109
172
 
110
173
  const host = options.host ?? createMemFS();
111
174
  const globalAny = globalThis as Record<string, unknown>;
@@ -137,15 +200,19 @@ async function bootTtscOnce(
137
200
  };
138
201
 
139
202
  try {
203
+ throwIfBootCanceled(signal, apiName, phase);
140
204
  // wasm_exec.js installs `globalThis.Go`. It also reads globalThis.fs at
141
205
  // module-eval time, so this import must follow the assignment above.
206
+ phase = `loading ${wasmExecUrl}`;
142
207
  importScripts(wasmExecUrl);
208
+ // importScripts is synchronous and therefore cannot be interrupted while
209
+ // the script evaluates. Observe a cancellation that arrived around that
210
+ // boundary before starting any asynchronous work.
211
+ throwIfBootCanceled(signal, apiName, phase);
143
212
 
144
213
  // Race the Ready resolver against a Failed signal so a wasm-side fault
145
214
  // (e.g. `host.Expose` refusing a duplicate call) surfaces here instead of
146
215
  // hanging on `await ready` forever.
147
- let readyCb!: () => void;
148
- let failedCb!: (err: unknown) => void;
149
216
  const ready = new Promise<void>((resolve, reject) => {
150
217
  readyCb = () => {
151
218
  delete globalAny[apiName + "Failed"];
@@ -167,15 +234,26 @@ async function bootTtscOnce(
167
234
  }
168
235
  const go = new goCtor();
169
236
 
170
- const response = await fetch(wasmUrl);
237
+ phase = `fetching ${wasmUrl}`;
238
+ const response = await raceBootCancellation(
239
+ fetch(wasmUrl, { signal }),
240
+ cancellation.promise,
241
+ signal,
242
+ apiName,
243
+ () => phase,
244
+ );
171
245
  if (!response.ok) {
172
246
  throw new Error(
173
247
  `bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`,
174
248
  );
175
249
  }
176
- const wasm = await WebAssembly.instantiateStreaming(
177
- response,
178
- go.importObject,
250
+ phase = `instantiating ${wasmUrl}`;
251
+ const wasm = await raceBootCancellation(
252
+ WebAssembly.instantiateStreaming(response, go.importObject),
253
+ cancellation.promise,
254
+ signal,
255
+ apiName,
256
+ () => phase,
179
257
  );
180
258
 
181
259
  // A normal host keeps `go.run` pending forever after signaling Ready, so a
@@ -186,6 +264,7 @@ async function bootTtscOnce(
186
264
  // The standard Go runner discards the exit code, so an unknown early exit
187
265
  // can only synthesize a generic message; known host validation failures
188
266
  // reject through Failed above and keep their original cause.
267
+ runtimeStarted = true;
189
268
  const runPromise = Promise.resolve(go.run(wasm.instance));
190
269
  const earlyExit = runPromise.then(
191
270
  () => {
@@ -206,16 +285,14 @@ async function bootTtscOnce(
206
285
  // the losing branch.
207
286
  earlyExit.catch(() => {});
208
287
 
209
- try {
210
- await Promise.race([ready, earlyExit]);
211
- } finally {
212
- // Drop this attempt's readiness bridge so a later boot for the same
213
- // apiName installs a clean pair and no stale resolver survives.
214
- if (globalAny[apiName + "Ready"] === readyCb)
215
- delete globalAny[apiName + "Ready"];
216
- if (globalAny[apiName + "Failed"] === failedCb)
217
- delete globalAny[apiName + "Failed"];
218
- }
288
+ phase = `waiting for ${apiName} readiness`;
289
+ await raceBootCancellation(
290
+ Promise.race([ready, earlyExit]),
291
+ cancellation.promise,
292
+ signal,
293
+ apiName,
294
+ () => phase,
295
+ );
219
296
 
220
297
  const api = (globalAny as Record<string, ITtscApi | undefined>)[apiName];
221
298
  if (!api)
@@ -225,10 +302,147 @@ async function bootTtscOnce(
225
302
  return { api, host };
226
303
  } catch (err) {
227
304
  restoreGlobals();
305
+ if (runtimeStarted) throw poisonBootWorker(apiName, err);
228
306
  throw err;
307
+ } finally {
308
+ cancellation.dispose();
309
+ // Drop this attempt's readiness bridge on every path so failed fetches,
310
+ // instantiation failures, and cancellations cannot poison the next boot.
311
+ if (globalAny[apiName + "Ready"] === readyCb)
312
+ delete globalAny[apiName + "Ready"];
313
+ if (globalAny[apiName + "Failed"] === failedCb)
314
+ delete globalAny[apiName + "Failed"];
315
+ }
316
+ }
317
+
318
+ function throwIfBootWorkerTerminated(apiName: string): void {
319
+ const failure = terminalBootFailuresByApiName.get(apiName);
320
+ if (failure) throw failure;
321
+ }
322
+
323
+ function poisonBootWorker(
324
+ apiName: string,
325
+ cause: unknown,
326
+ ): BootTtscWorkerTerminationError {
327
+ const existing = terminalBootFailuresByApiName.get(apiName);
328
+ if (existing) return existing;
329
+ const failure = new BootTtscWorkerTerminationError(apiName, cause);
330
+ terminalBootFailuresByApiName.set(apiName, failure);
331
+ return failure;
332
+ }
333
+
334
+ interface BootCancellation {
335
+ promise: Promise<never>;
336
+ dispose: () => void;
337
+ }
338
+
339
+ function resolveBootTimeout(timeoutMs: number | undefined): number {
340
+ const value = timeoutMs ?? DEFAULT_BOOT_TIMEOUT_MS;
341
+ if (!Number.isSafeInteger(value) || value <= 0 || value > 2_147_483_647)
342
+ throw new RangeError(
343
+ "bootTtsc: timeoutMs must be a positive integer no greater than 2147483647.",
344
+ );
345
+ return value;
346
+ }
347
+
348
+ /**
349
+ * Couple every caller's cancellation policy to the shared single-flight. A
350
+ * duplicate caller is joining the same mutable boot, so its cancellation
351
+ * cancels that shared attempt rather than pretending only one waiter left.
352
+ */
353
+ function attachBootCancellation(
354
+ entry: BootInFlight,
355
+ callerSignal: AbortSignal | undefined,
356
+ timeoutMs: number,
357
+ ): void {
358
+ const abortFromCaller = (): void => {
359
+ if (!entry.controller.signal.aborted)
360
+ entry.controller.abort({
361
+ kind: "abort",
362
+ reason: callerSignal?.reason,
363
+ } satisfies BootCancellationReason);
364
+ };
365
+ if (callerSignal?.aborted) abortFromCaller();
366
+ else callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
367
+
368
+ const timer = setTimeout(() => {
369
+ if (!entry.controller.signal.aborted)
370
+ entry.controller.abort({
371
+ kind: "timeout",
372
+ timeoutMs,
373
+ } satisfies BootCancellationReason);
374
+ }, timeoutMs);
375
+ const cleanup = (): void => {
376
+ clearTimeout(timer);
377
+ callerSignal?.removeEventListener("abort", abortFromCaller);
378
+ };
379
+ void entry.promise.then(cleanup, cleanup);
380
+ }
381
+
382
+ function createBootCancellationPromise(
383
+ signal: AbortSignal,
384
+ apiName: string,
385
+ getPhase: () => string,
386
+ ): BootCancellation {
387
+ let rejectCancellation!: (error: Error) => void;
388
+ const promise = new Promise<never>((_resolve, reject) => {
389
+ rejectCancellation = reject;
390
+ });
391
+ // Some synchronous boundaries observe cancellation with throwIfBootCanceled
392
+ // before a Promise.race consumes this branch. Keep that rejection owned.
393
+ void promise.catch(() => {});
394
+ const onAbort = (): void => {
395
+ rejectCancellation(bootCancellationError(signal, apiName, getPhase()));
396
+ };
397
+ signal.addEventListener("abort", onAbort, { once: true });
398
+ if (signal.aborted) onAbort();
399
+ return {
400
+ promise,
401
+ dispose: () => signal.removeEventListener("abort", onAbort),
402
+ };
403
+ }
404
+
405
+ async function raceBootCancellation<T>(
406
+ work: Promise<T>,
407
+ cancellation: Promise<never>,
408
+ signal: AbortSignal,
409
+ apiName: string,
410
+ getPhase: () => string,
411
+ ): Promise<T> {
412
+ try {
413
+ return await Promise.race([work, cancellation]);
414
+ } catch (error) {
415
+ if (signal.aborted)
416
+ throw bootCancellationError(signal, apiName, getPhase());
417
+ throw error;
229
418
  }
230
419
  }
231
420
 
421
+ function throwIfBootCanceled(
422
+ signal: AbortSignal,
423
+ apiName: string,
424
+ phase: string,
425
+ ): void {
426
+ if (signal.aborted) throw bootCancellationError(signal, apiName, phase);
427
+ }
428
+
429
+ function bootCancellationError(
430
+ signal: AbortSignal,
431
+ apiName: string,
432
+ phase: string,
433
+ ): Error {
434
+ const reason = signal.reason as BootCancellationReason | undefined;
435
+ if (reason?.kind === "timeout")
436
+ return new Error(
437
+ `bootTtsc: timed out after ${reason.timeoutMs}ms while ${phase} for ${apiName}.`,
438
+ );
439
+
440
+ const error = new Error(`bootTtsc: aborted while ${phase} for ${apiName}.`);
441
+ const cause = reason?.kind === "abort" ? reason.reason : signal.reason;
442
+ if (cause !== undefined) (error as Error & { cause?: unknown }).cause = cause;
443
+ return error;
444
+ }
445
+
232
446
  /**
233
447
  * Derive the `wasm_exec.js` URL from the wasm URL by replacing the filename.
234
448
  *
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  // imported from JS.
7
7
 
8
8
  export * from "./bootTtsc";
9
+ export * from "./BootTtscWorkerTerminationError";
9
10
  export * from "./createMemFS";
10
11
  export * from "./MemFSError";
11
12
  export * from "./parseResult";
@@ -4,6 +4,17 @@ import type { IMemFSHost } from "./IMemFSHost";
4
4
  export interface IBootTtscOptions {
5
5
  /** URL of the .wasm to fetch. */
6
6
  wasmUrl: string;
7
+ /** Cancel this boot attempt, including a shared in-flight attempt. */
8
+ signal?: AbortSignal;
9
+ /**
10
+ * Maximum initialization time in milliseconds. Defaults to 60 seconds.
11
+ *
12
+ * The deadline covers queued, fetch, instantiate, and readiness phases.
13
+ * `importScripts` is synchronous and can only observe cancellation before or
14
+ * after it returns. A failure after `go.run` starts requires terminating the
15
+ * Worker; later boots of the same API name reject terminally.
16
+ */
17
+ timeoutMs?: number;
7
18
  /** URL of wasm_exec.js. Defaults to the same directory as wasmUrl. */
8
19
  wasmExecUrl?: string;
9
20
  /**