@ttsc/wasm 0.30.4 → 0.31.1

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.
Files changed (174) hide show
  1. package/README.md +3 -3
  2. package/build/build-wasm.cjs +5 -4
  3. package/build/go-build-cache.cjs +347 -0
  4. package/build/pack-prepare.cjs +3 -2
  5. package/cmd/ttsc-wasm/main.go +22 -10
  6. package/cmd/ttsc-wasm/main_wasm.go +2 -2
  7. package/dist/.ttsc-wasm-build.json +138 -58
  8. package/dist/go.mod +7 -5
  9. package/dist/ttsc.wasm +2 -2
  10. package/go.mod +7 -5
  11. package/go.sum +10 -10
  12. package/host/api.go +34 -41
  13. package/host/doc.go +7 -6
  14. package/host/expose.go +42 -0
  15. package/host/fountain.go +192 -22
  16. package/host/host.go +10 -25
  17. package/host/host_native.go +3 -5
  18. package/host/invoke.go +133 -0
  19. package/host/plugin.go +37 -72
  20. package/host/result.go +108 -0
  21. package/lib/src/BootTtscWorkerTerminationError.d.ts +20 -0
  22. package/lib/src/BootTtscWorkerTerminationError.js +20 -0
  23. package/lib/src/BootTtscWorkerTerminationError.js.map +1 -1
  24. package/lib/src/MemFSError.d.ts +21 -2
  25. package/lib/src/MemFSError.js +25 -2
  26. package/lib/src/MemFSError.js.map +1 -1
  27. package/lib/src/bootTtsc.d.ts +49 -2
  28. package/lib/src/bootTtsc.js +89 -23
  29. package/lib/src/bootTtsc.js.map +1 -1
  30. package/lib/src/createMemFS.d.ts +45 -0
  31. package/lib/src/createMemFS.js +269 -66
  32. package/lib/src/createMemFS.js.map +1 -1
  33. package/lib/src/parseResult.d.ts +19 -0
  34. package/lib/src/parseResult.js +19 -0
  35. package/lib/src/parseResult.js.map +1 -1
  36. package/lib/src/structures/IBootResult.d.ts +20 -2
  37. package/lib/src/structures/IBootTtscOptions.d.ts +28 -6
  38. package/lib/src/structures/IFileStats.d.ts +51 -0
  39. package/lib/src/structures/IMemFSHost.d.ts +89 -0
  40. package/lib/src/structures/ITtscApi.d.ts +154 -9
  41. package/lib/src/structures/ITtscBuildOpts.d.ts +18 -1
  42. package/lib/src/structures/ITtscCompileResult.d.ts +20 -2
  43. package/lib/src/structures/ITtscDiagnostic.d.ts +25 -1
  44. package/lib/src/structures/ITtscFileQuery.d.ts +19 -1
  45. package/lib/src/structures/ITtscFountainDiagnosticsResult.d.ts +20 -1
  46. package/lib/src/structures/ITtscNodeAtPositionResult.d.ts +18 -1
  47. package/lib/src/structures/ITtscNodeInfo.d.ts +23 -2
  48. package/lib/src/structures/ITtscPluginOpts.d.ts +24 -3
  49. package/lib/src/structures/ITtscPositionQuery.d.ts +21 -4
  50. package/lib/src/structures/ITtscReleaseSnapshotResult.d.ts +22 -2
  51. package/lib/src/structures/ITtscResult.d.ts +31 -5
  52. package/lib/src/structures/ITtscSnapshotHandle.d.ts +19 -1
  53. package/lib/src/structures/ITtscSnapshotResult.d.ts +22 -1
  54. package/lib/src/structures/ITtscSnapshotsResult.d.ts +19 -1
  55. package/lib/src/structures/ITtscSourceFileTextResult.d.ts +19 -1
  56. package/lib/src/structures/ITtscSourceFilesResult.d.ts +23 -2
  57. package/lib/src/structures/ITtscSymbolAtPositionResult.d.ts +18 -1
  58. package/lib/src/structures/ITtscSymbolDeclaration.d.ts +25 -2
  59. package/lib/src/structures/ITtscSymbolInfo.d.ts +19 -1
  60. package/lib/src/structures/ITtscTransformResult.d.ts +21 -3
  61. package/lib/src/structures/ITtscTypeAtPositionResult.d.ts +18 -1
  62. package/lib/src/structures/ITtscTypeInfo.d.ts +19 -1
  63. package/lib/src/structures/ITtscVersion.d.ts +28 -1
  64. package/lib/src/structures/IWasmExecFS.d.ts +310 -0
  65. package/package.json +4 -2
  66. package/shim-vendor/shim/ast/enums_gen.go +4 -2
  67. package/shim-vendor/shim/ast/go.mod +5 -5
  68. package/shim-vendor/shim/ast/go.sum +10 -10
  69. package/shim-vendor/shim/ast/lint.go +1088 -20
  70. package/shim-vendor/shim/ast/parent.go +38 -22
  71. package/shim-vendor/shim/ast/safe_text.go +48 -26
  72. package/shim-vendor/shim/ast/shim.go +1307 -20
  73. package/shim-vendor/shim/ast/test/next_jsdoc_comment_location_reaches_variable_statement_test.go +51 -0
  74. package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +24 -9
  75. package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +9 -5
  76. package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +13 -5
  77. package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +12 -5
  78. package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +35 -5
  79. package/shim-vendor/shim/astnav/go.mod +5 -5
  80. package/shim-vendor/shim/astnav/go.sum +12 -10
  81. package/shim-vendor/shim/astnav/shim.go +9 -0
  82. package/shim-vendor/shim/bundled/go.mod +7 -1
  83. package/shim-vendor/shim/bundled/go.sum +12 -0
  84. package/shim-vendor/shim/checker/enums_gen.go +4 -2
  85. package/shim-vendor/shim/checker/extra-shim.json +6 -0
  86. package/shim-vendor/shim/checker/go.mod +5 -5
  87. package/shim-vendor/shim/checker/go.sum +10 -10
  88. package/shim-vendor/shim/checker/node_builder_fields_gen.go +19 -0
  89. package/shim-vendor/shim/checker/shim.go +827 -114
  90. package/shim-vendor/shim/compiler/emit_sourcemap.go +68 -29
  91. package/shim-vendor/shim/compiler/emitassemble.go +36 -2
  92. package/shim-vendor/shim/compiler/emithook.go +13 -3
  93. package/shim-vendor/shim/compiler/go.mod +5 -5
  94. package/shim-vendor/shim/compiler/go.sum +10 -10
  95. package/shim-vendor/shim/compiler/incremental.go +56 -20
  96. package/shim-vendor/shim/compiler/resolution.go +92 -9
  97. package/shim-vendor/shim/core/enums_gen.go +2 -2
  98. package/shim-vendor/shim/core/go.mod +6 -1
  99. package/shim-vendor/shim/core/go.sum +10 -0
  100. package/shim-vendor/shim/core/modulekind.go +9 -0
  101. package/shim-vendor/shim/core/shim.go +157 -6
  102. package/shim-vendor/shim/diagnosticwriter/go.mod +10 -1
  103. package/shim-vendor/shim/diagnosticwriter/go.sum +20 -0
  104. package/shim-vendor/shim/diagnosticwriter/lint.go +188 -10
  105. package/shim-vendor/shim/diagnosticwriter/shim.go +14 -0
  106. package/shim-vendor/shim/lsp/go.mod +5 -5
  107. package/shim-vendor/shim/lsp/go.sum +10 -10
  108. package/shim-vendor/shim/lsp/shim.go +78 -2
  109. package/shim-vendor/shim/parser/go.mod +10 -1
  110. package/shim-vendor/shim/parser/go.sum +22 -0
  111. package/shim-vendor/shim/printer/emithost.go +46 -1
  112. package/shim-vendor/shim/printer/go.mod +5 -5
  113. package/shim-vendor/shim/printer/go.sum +10 -10
  114. package/shim-vendor/shim/printer/shim.go +114 -7
  115. package/shim-vendor/shim/printer/sourcemap.go +55 -0
  116. package/shim-vendor/shim/scanner/go.mod +10 -1
  117. package/shim-vendor/shim/scanner/go.sum +20 -0
  118. package/shim-vendor/shim/scanner/regexp.go +19 -4
  119. package/shim-vendor/shim/scanner/shim.go +3 -0
  120. package/shim-vendor/shim/stringutil/extra-shim.json +1 -0
  121. package/shim-vendor/shim/stringutil/go.mod +5 -0
  122. package/shim-vendor/shim/stringutil/go.sum +2 -0
  123. package/shim-vendor/shim/stringutil/shim.go +119 -0
  124. package/shim-vendor/shim/transformers/go.mod +5 -5
  125. package/shim-vendor/shim/transformers/go.sum +12 -10
  126. package/shim-vendor/shim/transformers/shim.go +12 -3
  127. package/shim-vendor/shim/tsoptions/command_line_raw.go +44 -0
  128. package/shim-vendor/shim/tsoptions/go.mod +10 -1
  129. package/shim-vendor/shim/tsoptions/go.sum +22 -0
  130. package/shim-vendor/shim/tsoptions/shim.go +1 -1
  131. package/shim-vendor/shim/tspath/go.mod +1 -1
  132. package/shim-vendor/shim/tspath/go.sum +6 -0
  133. package/shim-vendor/shim/vfs/cachedvfs/go.mod +3 -1
  134. package/shim-vendor/shim/vfs/cachedvfs/go.sum +12 -0
  135. package/shim-vendor/shim/vfs/decode.go +26 -0
  136. package/shim-vendor/shim/vfs/decode_test.go +42 -0
  137. package/shim-vendor/shim/vfs/go.mod +7 -1
  138. package/shim-vendor/shim/vfs/go.sum +12 -0
  139. package/shim-vendor/shim/vfs/osvfs/go.mod +7 -1
  140. package/shim-vendor/shim/vfs/osvfs/go.sum +12 -0
  141. package/src/BootTtscWorkerTerminationError.ts +22 -0
  142. package/src/MemFSError.ts +29 -2
  143. package/src/bootTtsc.ts +104 -28
  144. package/src/createMemFS.ts +291 -64
  145. package/src/parseResult.ts +19 -0
  146. package/src/structures/IBootResult.ts +21 -2
  147. package/src/structures/IBootTtscOptions.ts +32 -6
  148. package/src/structures/IFileStats.ts +65 -0
  149. package/src/structures/IMemFSHost.ts +97 -0
  150. package/src/structures/ITtscApi.ts +154 -9
  151. package/src/structures/ITtscBuildOpts.ts +19 -1
  152. package/src/structures/ITtscCompileResult.ts +21 -2
  153. package/src/structures/ITtscDiagnostic.ts +32 -1
  154. package/src/structures/ITtscFileQuery.ts +19 -1
  155. package/src/structures/ITtscFountainDiagnosticsResult.ts +20 -1
  156. package/src/structures/ITtscNodeAtPositionResult.ts +18 -1
  157. package/src/structures/ITtscNodeInfo.ts +27 -2
  158. package/src/structures/ITtscPluginOpts.ts +28 -3
  159. package/src/structures/ITtscPositionQuery.ts +21 -4
  160. package/src/structures/ITtscReleaseSnapshotResult.ts +22 -2
  161. package/src/structures/ITtscResult.ts +34 -5
  162. package/src/structures/ITtscSnapshotHandle.ts +19 -1
  163. package/src/structures/ITtscSnapshotResult.ts +22 -1
  164. package/src/structures/ITtscSnapshotsResult.ts +19 -1
  165. package/src/structures/ITtscSourceFileTextResult.ts +19 -1
  166. package/src/structures/ITtscSourceFilesResult.ts +23 -2
  167. package/src/structures/ITtscSymbolAtPositionResult.ts +18 -1
  168. package/src/structures/ITtscSymbolDeclaration.ts +27 -2
  169. package/src/structures/ITtscSymbolInfo.ts +23 -1
  170. package/src/structures/ITtscTransformResult.ts +22 -3
  171. package/src/structures/ITtscTypeAtPositionResult.ts +18 -1
  172. package/src/structures/ITtscTypeInfo.ts +20 -1
  173. package/src/structures/ITtscVersion.ts +33 -1
  174. package/src/structures/IWasmExecFS.ts +336 -0
package/src/MemFSError.ts CHANGED
@@ -3,14 +3,37 @@
3
3
  *
4
4
  * Matches the shape of `NodeJS.ErrnoException` so Go's `os` package interprets
5
5
  * it as a proper `os.PathError` with a numeric error code. Thrown by the MemFS
6
- * implementation when a callback would otherwise pass `null` for an impossible
7
- * filesystem operation (missing path, type mismatch, …).
6
+ * implementation for rejected virtual filesystem operations.
7
+ *
8
+ * @evidence contracts/common.md#principled-implementation
9
+ * Error fields follow NodeJS.ErrnoException and Go's js/wasm filesystem bridge;
10
+ * numeric errno values describe that virtual ABI, not the browser host OS.
11
+ * @evidence contracts/common.md#clear-and-simple-design
12
+ * The Error subclass groups the bridge's machine fields with its message;
13
+ * one private mapping owns errno translation for every virtual operation.
14
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
15
+ * POSIX error identifiers are protocol constants; failed operations remain
16
+ * errors instead of returning a fake successful callback value.
17
+ * @evidence contracts/common.md#meaningful-documentation
18
+ * Native JSDoc explains the bridge and members' machine-readable roles,
19
+ * following the documentation skill's concrete context guidance.
20
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources MemFSError retains only its own fields and is released with the thrown error.
21
+ * @evidenceExclude contracts/performance.md#efficient-algorithms MemFSError maps a code with one constant switch and chooses no processing strategy.
22
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work MemFSError is an error value and coordinates no shared or repeated computation.
8
23
  */
9
24
  export class MemFSError extends Error {
25
+ /** POSIX symbolic error, consumed by the Go bridge. */
10
26
  public code: string;
27
+
28
+ /** Negative Linux-style errno for the virtual filesystem ABI. */
11
29
  public errno: number;
30
+
31
+ /** Normalized virtual path, when the failing operation identifies one. */
12
32
  public path?: string;
33
+
34
+ /** Filesystem operation that produced the error. */
13
35
  public syscall?: string;
36
+
14
37
  constructor(code: string, syscall: string, path?: string) {
15
38
  super(`${code}: ${syscall} ${path ?? ""}`.trim());
16
39
  this.code = code;
@@ -37,8 +60,12 @@ function errnoForCode(code: string): number {
37
60
  return -21;
38
61
  case "EINVAL":
39
62
  return -22;
63
+ case "EFBIG":
64
+ return -27;
40
65
  case "ESPIPE":
41
66
  return -29;
67
+ case "EPIPE":
68
+ return -32;
42
69
  case "ENOTEMPTY":
43
70
  return -39;
44
71
  default:
package/src/bootTtsc.ts CHANGED
@@ -16,16 +16,16 @@ import type { ITtscApi } from "./structures/ITtscApi";
16
16
  declare const importScripts: (...urls: string[]) => void;
17
17
 
18
18
  /**
19
- * Per-(apiName, wasmUrl) single-flight cache for boots. Keying on apiName alone
20
- * would let a second call with the same apiName but a different wasmUrl
21
- * silently return the cached IBootResult of the first wasm — the caller would
22
- * think they booted a fresh binary while the cached one stayed in place. The
23
- * composite key lets HMR / cache-busting query strings get a fresh boot while
24
- * still single-flighting genuine concurrent duplicate calls.
19
+ * One boot attempt shared by every caller that names the same API and binary.
20
+ * `controller` cancels the shared attempt, `host` is the filesystem its runtime
21
+ * binds, and `wasmExecUrl` is the runtime script it loaded; a joining caller
22
+ * must not name a different host or script.
25
23
  */
26
24
  interface BootInFlight {
27
25
  controller: AbortController;
28
26
  promise: Promise<IBootResult>;
27
+ host: IBootResult["host"];
28
+ wasmExecUrl: string;
29
29
  }
30
30
 
31
31
  interface BootCancellationReason {
@@ -33,6 +33,14 @@ interface BootCancellationReason {
33
33
  reason?: unknown;
34
34
  }
35
35
 
36
+ /**
37
+ * Per-(apiName, wasmUrl) single-flight cache for boots. Keying on apiName alone
38
+ * would let a second call with the same apiName but a different wasmUrl
39
+ * silently return the cached IBootResult of the first wasm. The caller would
40
+ * think they booted a fresh binary while the cached one stayed in place. The
41
+ * composite key lets HMR / cache-busting query strings get a fresh boot while
42
+ * still single-flighting genuine concurrent duplicate calls.
43
+ */
36
44
  const bootsInFlight = new Map<string, BootInFlight>();
37
45
 
38
46
  /**
@@ -46,25 +54,23 @@ const terminalBootFailuresByApiName = new Map<
46
54
  >();
47
55
 
48
56
  /**
49
- * Per-apiName serialization chain. Two concurrent boots with the same apiName
50
- * but different wasmUrls each install their own `globalThis[apiName
51
- *
52
- * - "Ready"]`resolver — they would race and the second would overwrite the first,
53
- * stranding the first boot's await. The chain serializes them so one boot's
54
- * Go-side`Ready.Invoke()` always lands on the resolver that boot installed.
57
+ * Serialize boots sharing an API name because each installs the same
58
+ * `globalThis[apiName + "Ready"]` resolver. Serialization prevents a later
59
+ * attempt from overwriting the resolver before the earlier attempt settles.
55
60
  */
56
61
  const bootChainByApiName = new Map<string, Promise<unknown>>();
57
62
 
58
63
  function bootKey(apiName: string, wasmUrl: string): string {
59
- return `${apiName}|${resolveWasmUrl(wasmUrl)}`;
64
+ return JSON.stringify([apiName, resolveWasmUrl(wasmUrl)]);
60
65
  }
61
66
 
62
67
  /**
63
- * Resolve `wasmUrl` against the current document base before keying so that
68
+ * Resolve `wasmUrl` against the current location before keying so that
64
69
  * `./playground.wasm`, `/compiler/playground.wasm`, and the fully qualified
65
70
  * absolute href all collapse to the same cache entry instead of spawning
66
- * duplicate boots. Falls back to the raw string when no base is available
67
- * (Node-side tests, non-DOM workers).
71
+ * duplicate boots. In environments without a location, such as Node-side tests,
72
+ * relative URLs use the synthetic `http://local/` base. If parsing fails, the
73
+ * input retains its original spelling.
68
74
  */
69
75
  function resolveWasmUrl(wasmUrl: string): string {
70
76
  try {
@@ -88,25 +94,94 @@ function resolveWasmUrl(wasmUrl: string): string {
88
94
  * stop the old runtime; replace the Worker before retrying.
89
95
  *
90
96
  * **Single-Worker caveat.** Even with the chain, a second boot loaded into the
91
- * SAME Worker after a first boot completes will overlay its Go runtime on top
92
- * of the first — `importScripts(wasmExecUrl)` rebinds `globalThis.Go`, and the
97
+ * same Worker after a first boot completes will overlay its Go runtime on top
98
+ * of the first. `importScripts(wasmExecUrl)` rebinds `globalThis.Go`, and the
93
99
  * first wasm's keepalive goroutine keeps running through the new runtime's
94
100
  * js-bridge tables. The serialization is sufficient for the typical use case
95
101
  * (one boot per Worker over the page's lifetime) but DOES NOT make a Worker
96
102
  * safe to host two wasm instances at once. Create a fresh Worker per concurrent
97
103
  * wasm.
104
+ *
105
+ * @evidence contracts/common.md#principled-implementation
106
+ * The documented Go wasm_exec bridge is installed before streaming
107
+ * instantiation, and Promise sharing coordinates calls that use its global
108
+ * readiness slot. This follows the host.Expose integration rather than
109
+ * inventing a second runtime protocol.
110
+ *
111
+ * @evidence contracts/common.md#clear-and-simple-design
112
+ * The entry owns one attempt's host and cancellation, the per-API chain owns
113
+ * readiness-slot serialization, and bootTtscOnce owns setup and cleanup.
114
+ * These separate identities are needed because duplicate calls may join,
115
+ * distinct binary URLs may queue, and a started runtime cannot be retried.
116
+ *
117
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
118
+ * The ttsc default API name and Ready/Failed slots are the host protocol,
119
+ * not consumer or fixture matches. The attempt installs missing Go host
120
+ * shims and removes only its own failed installation; it does not replace an
121
+ * existing foreign filesystem. The documented one-runtime-per-Worker
122
+ * limitation is retained rather than hidden beneath retries after go.run.
123
+ *
124
+ * @evidence contracts/common.md#meaningful-documentation
125
+ * JSDoc explains why same-URL calls share a boot, why API-slot serialization
126
+ * is needed and why post-start failure requires Worker replacement. Separate
127
+ * paragraphs state purpose and the shared-runtime caveat under the
128
+ * documentation skill; option and result interfaces document cancellation
129
+ * and host ownership.
130
+ *
131
+ * @evidence contracts/performance.md#efficient-algorithms
132
+ * Cache lookup uses normalized URL length U and expected constant-time Map
133
+ * access; fetch/streaming compilation process the B binary bytes without a
134
+ * second JavaScript binary buffer. C joined callers add O(C) abort listeners,
135
+ * and cancellation uses events rather than a polling scan.
136
+ *
137
+ * @evidence contracts/performance.md#reuse-equivalent-work
138
+ * One API/normalized-URL pair shares fetch and instantiation only with the same
139
+ * runtime-script URL and an identical supplied host. Omitted hosts reuse the
140
+ * entry's actual host. Per-API Promise
141
+ * chains serialize access to the global readiness slot; distinct URLs are
142
+ * distinct boot identities. The URL must identify immutable binary content
143
+ * for successful-result reuse to remain valid within the Worker lifetime.
144
+ *
145
+ * @evidence contracts/performance.md#bound-retention-and-release-resources
146
+ * Each entry owns its controller, host and result; settlement removes joined
147
+ * caller listeners and internal cancellation listeners. Successful boot keys,
148
+ * API chain heads and terminal failures remain until Worker termination, with
149
+ * no fixed key or retained-byte bound. Failed pre-start entries are removed;
150
+ * started runtimes cannot be stopped here and require Worker replacement.
98
151
  */
99
152
  export function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
100
153
  const apiName = options.apiName ?? "ttsc";
101
- const key = bootKey(apiName, options.wasmUrl);
154
+ let wasmUrl: string;
155
+ let wasmExecUrl: string;
156
+ try {
157
+ wasmUrl = resolveWasmUrl(options.wasmUrl);
158
+ wasmExecUrl = resolveWasmUrl(
159
+ options.wasmExecUrl ?? defaultWasmExecUrl(wasmUrl),
160
+ );
161
+ } catch (error) {
162
+ return Promise.reject(error);
163
+ }
164
+ const key = bootKey(apiName, wasmUrl);
102
165
  const terminalFailure = terminalBootFailuresByApiName.get(apiName);
103
166
  if (terminalFailure) return Promise.reject(terminalFailure);
104
167
  const inflight = bootsInFlight.get(key);
105
168
  if (inflight) {
169
+ if (
170
+ (options.host !== undefined && options.host !== inflight.host) ||
171
+ wasmExecUrl !== inflight.wasmExecUrl
172
+ ) {
173
+ return Promise.reject(
174
+ new Error(
175
+ `bootTtsc: ${apiName} is already bound to a different host or wasm_exec.js URL; use the original boot options or replace the Worker.`,
176
+ ),
177
+ );
178
+ }
106
179
  attachBootCancellation(inflight, options.signal);
107
180
  return inflight.promise;
108
181
  }
109
182
  const controller = new AbortController();
183
+ const host = options.host ?? createMemFS();
184
+ const bootOptions = { ...options, host, wasmUrl, wasmExecUrl };
110
185
  const prior = bootChainByApiName.get(apiName) ?? Promise.resolve();
111
186
  const queuedCancellation = createBootCancellationPromise(
112
187
  controller.signal,
@@ -125,7 +200,7 @@ export function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
125
200
  "waiting for an earlier boot",
126
201
  );
127
202
  queuedCancellation.dispose();
128
- return bootTtscOnce(options, apiName, controller.signal);
203
+ return bootTtscOnce(bootOptions, apiName, controller.signal);
129
204
  });
130
205
  let entry!: BootInFlight;
131
206
  const promise = Promise.race([queued, queuedCancellation.promise])
@@ -134,7 +209,7 @@ export function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
134
209
  throw err;
135
210
  })
136
211
  .finally(queuedCancellation.dispose);
137
- entry = { controller, promise };
212
+ entry = { controller, promise, host, wasmExecUrl };
138
213
  bootsInFlight.set(key, entry);
139
214
  attachBootCancellation(entry, options.signal);
140
215
  // Track the chain head for this apiName so the next boot waits on it.
@@ -195,6 +270,11 @@ async function bootTtscOnce(
195
270
  };
196
271
 
197
272
  try {
273
+ if (globalAny.fs !== host.fs) {
274
+ throw new Error(
275
+ "bootTtsc: globalThis.fs belongs to a different host; supply its original MemFS host or boot in a fresh Worker.",
276
+ );
277
+ }
198
278
  throwIfBootCanceled(signal, apiName, phase);
199
279
  // wasm_exec.js installs `globalThis.Go`. It also reads globalThis.fs at
200
280
  // module-eval time, so this import must follow the assignment above.
@@ -416,15 +496,11 @@ function bootCancellationError(
416
496
  }
417
497
 
418
498
  /**
419
- * Derive the `wasm_exec.js` URL from the wasm URL by replacing the filename.
420
- *
421
- * If `wasmUrl` has no directory component, returns `"wasm_exec.js"` (same
422
- * directory as the caller's base URL).
499
+ * Resolve the sibling runtime script using URL path semantics, dropping the
500
+ * binary's query and fragment so slashes in either cannot become directories.
423
501
  */
424
502
  function defaultWasmExecUrl(wasmUrl: string): string {
425
- const slash = wasmUrl.lastIndexOf("/");
426
- if (slash < 0) return "wasm_exec.js";
427
- return wasmUrl.slice(0, slash + 1) + "wasm_exec.js";
503
+ return new URL("wasm_exec.js", resolveWasmUrl(wasmUrl)).href;
428
504
  }
429
505
 
430
506
  /**