@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.
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
@@ -61,12 +61,110 @@ function normalize(p) {
61
61
  * descendants. An operation that cannot satisfy that throws (`writeFile`,
62
62
  * `mkdirp`) or reports a POSIX error through its callback, having changed no
63
63
  * node, byte, or descriptor.
64
+ *
65
+ * @evidence contracts/common.md#principled-implementation
66
+ * The owned Node-shaped fs bridge uses maps for virtual node/descriptor
67
+ * identity and preserves opened nodes independently from paths. The Go js/wasm
68
+ * callback interface is the authority; unsupported link and metadata mutation
69
+ * capabilities are explicit in IWasmExecFS.
70
+ * @evidence contracts/common.md#clear-and-simple-design
71
+ * One closure owns the tree, indexes and descriptor identities. Shared helpers
72
+ * centralize path validation, insertion/removal and byte mutation; separate
73
+ * stream captures and pipe queues reflect non-seekable lifetimes rather than
74
+ * adding filesystem nodes that would blur their operation contracts.
75
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
76
+ * Virtual flag/errno values are bridge constants, not native-host guesses.
77
+ * The implementation owns its maps rather than patching foreign filesystem
78
+ * methods. Directory indexes, geometric file growth and linked pipe queues
79
+ * correct repeated global scans, full-buffer copies and queue shifts at their
80
+ * owners, without size exceptions or benchmark-specific paths.
81
+ * @evidence contracts/common.md#meaningful-documentation
82
+ * Native paragraphs explain installation, copying and tree ownership; helper
83
+ * and interface comments state cost and unsupported capabilities. This follows
84
+ * the documentation skill's paragraph, units and rationale guidance.
85
+ * @evidence contracts/performance.md#efficient-algorithms
86
+ * Directory lookup visits D direct entries and sorts them in O(D log D),
87
+ * emptiness uses the indexed child count, and rename visits only S subtree
88
+ * nodes plus F open descriptors. Growing file writes amortize copying through
89
+ * geometric capacity; pipe enqueue/dequeue change linked endpoints in O(1).
90
+ * Path normalization costs O(P) path characters; ancestor validation builds
91
+ * d prefixes with total length K, costing O(K + d) time and temporary space
92
+ * under expected constant-time map access after hashing each prefix. Output fragments
93
+ * append without flattening prior text; reading a dirty B-byte capture costs
94
+ * O(B), while an unchanged capture read reuses its materialized string.
95
+ * @evidence contracts/performance.md#reuse-equivalent-work
96
+ * Child membership is maintained once by node insertion/deletion rather than
97
+ * reconstructed for every listing. Text decoding is cached on node identity
98
+ * and invalidated by every content mutation. Open descriptors share their
99
+ * actual inode; public byte reads still copy to preserve caller isolation.
100
+ * Each output stream keeps its own incremental UTF-8 decoder, so a split
101
+ * character is decoded once and capture reads reuse the materialized string
102
+ * until another fragment arrives.
103
+ * @evidence contracts/performance.md#bound-retention-and-release-resources
104
+ * Each host owns its tree, indexes, captures and descriptors. Unlink/rmdir
105
+ * remove tree entries, close releases descriptors, and consumed pipe entries
106
+ * are detached. File write capacity stays at most twice logical length; truncate
107
+ * and replacement release spare storage. Total input and captured output have
108
+ * no fixed bound, so the caller owns deletion, resetStdio and host lifetime.
64
109
  */
65
110
  function createMemFS() {
66
111
  const nodes = new Map();
112
+ const children = new Map([["/", new Set()]]);
67
113
  nodes.set("/", { kind: "dir", data: new Uint8Array(), mtimeMs: Date.now() });
68
- const stdout = { buffer: "" };
69
- const stderr = { buffer: "" };
114
+ /** Keep parent membership and directory indexes with the owned node tree. */
115
+ function setNode(p, node) {
116
+ nodes.set(p, node);
117
+ children.get(parentDir(p)).add(p);
118
+ if (node.kind === "dir" && !children.has(p))
119
+ children.set(p, new Set());
120
+ }
121
+ /** Remove a validated node and its index; open descriptors retain the node. */
122
+ function deleteNode(p) {
123
+ nodes.delete(p);
124
+ children.get(parentDir(p)).delete(p);
125
+ children.delete(p);
126
+ }
127
+ /** Decode stream fragments incrementally and materialize capture text on read. */
128
+ function createCapture() {
129
+ let streamDecoder = new TextDecoder();
130
+ let chunks = [];
131
+ let cached = "";
132
+ let dirty = false;
133
+ const append = (text) => {
134
+ if (text.length > 0) {
135
+ chunks.push(text);
136
+ cached = "";
137
+ dirty = true;
138
+ }
139
+ return text;
140
+ };
141
+ return {
142
+ output: {
143
+ get buffer() {
144
+ if (dirty) {
145
+ cached = chunks.join("");
146
+ chunks = cached.length > 0 ? [cached] : [];
147
+ dirty = false;
148
+ }
149
+ return cached;
150
+ },
151
+ set buffer(value) {
152
+ streamDecoder = new TextDecoder();
153
+ chunks = value.length > 0 ? [value] : [];
154
+ cached = value;
155
+ dirty = false;
156
+ },
157
+ },
158
+ write(bytes) {
159
+ return append(streamDecoder.decode(bytes, { stream: true }));
160
+ },
161
+ flush() {
162
+ append(streamDecoder.decode());
163
+ },
164
+ };
165
+ }
166
+ const stdout = createCapture();
167
+ const stderr = createCapture();
70
168
  const fdTable = new Map();
71
169
  let nextFd = 100;
72
170
  // Reserve 1/2 for stdout/stderr writeSync routing.
@@ -86,6 +184,23 @@ function createMemFS() {
86
184
  append: true,
87
185
  isStderr: true,
88
186
  });
187
+ function enqueue(queue, value) {
188
+ const entry = { value };
189
+ if (queue.tail)
190
+ queue.tail.next = entry;
191
+ else
192
+ queue.head = entry;
193
+ queue.tail = entry;
194
+ }
195
+ function dequeue(queue) {
196
+ const entry = queue.head;
197
+ if (!entry)
198
+ return undefined;
199
+ queue.head = entry.next;
200
+ if (!queue.head)
201
+ queue.tail = undefined;
202
+ return entry.value;
203
+ }
89
204
  const pipes = new Map();
90
205
  /**
91
206
  * Drain buffered pipe data into `buffer[offset..offset+length]`. Returns
@@ -93,15 +208,15 @@ function createMemFS() {
93
208
  */
94
209
  function drainPipeInto(state, buffer, offset, length) {
95
210
  let written = 0;
96
- while (state.buffers.length > 0 && written < length) {
97
- const chunk = state.buffers[0];
211
+ while (state.buffers.head && written < length) {
212
+ const chunk = state.buffers.head.value;
98
213
  const copyLen = Math.min(chunk.byteLength, length - written);
99
214
  buffer.set(chunk.subarray(0, copyLen), offset + written);
100
215
  written += copyLen;
101
216
  if (copyLen >= chunk.byteLength)
102
- state.buffers.shift();
217
+ dequeue(state.buffers);
103
218
  else
104
- state.buffers[0] = chunk.subarray(copyLen);
219
+ state.buffers.head.value = chunk.subarray(copyLen);
105
220
  }
106
221
  return written;
107
222
  }
@@ -110,9 +225,9 @@ function createMemFS() {
110
225
  * signal.
111
226
  */
112
227
  function flushPipeReaders(state) {
113
- while (state.pendingReaders.length > 0 &&
114
- (state.buffers.length > 0 || state.writeClosed)) {
115
- const reader = state.pendingReaders.shift();
228
+ while (state.pendingReaders.head &&
229
+ (state.buffers.head || state.writeClosed)) {
230
+ const reader = dequeue(state.pendingReaders);
116
231
  const n = drainPipeInto(state, reader.buffer, reader.offset, reader.length);
117
232
  // Even at EOF (writeClosed && empty buffers) we satisfy with n=0 which
118
233
  // signals EOF to the Go-side caller.
@@ -153,7 +268,7 @@ function createMemFS() {
153
268
  /** Materialize the directories `missingDirs` reported. */
154
269
  function createDirs(paths) {
155
270
  for (const path of paths)
156
- nodes.set(path, {
271
+ setNode(path, {
157
272
  kind: "dir",
158
273
  data: new Uint8Array(),
159
274
  mtimeMs: Date.now(),
@@ -181,22 +296,68 @@ function createMemFS() {
181
296
  }
182
297
  /** True when `dir` has at least one descendant node in the tree. */
183
298
  function hasChildren(dir) {
184
- const prefix = dir === "/" ? "/" : dir + "/";
185
- for (const key of nodes.keys()) {
186
- if (key !== dir && key.startsWith(prefix))
187
- return true;
299
+ return (children.get(dir)?.size ?? 0) > 0;
300
+ }
301
+ /**
302
+ * Allocate zero-filled storage for a file of `length` bytes.
303
+ *
304
+ * The engine refuses a length it cannot allocate with a `RangeError`, which
305
+ * carries no POSIX code and would escape a callback that promises one. That
306
+ * refusal is the virtual filesystem's maximum file size, so it is reported as
307
+ * `EFBIG`; the caller has not yet changed any node when this throws.
308
+ */
309
+ function allocateFileData(length, syscall, path) {
310
+ try {
311
+ return new Uint8Array(length);
312
+ }
313
+ catch (error) {
314
+ if (error instanceof RangeError)
315
+ throw new MemFSError_1.MemFSError("EFBIG", syscall, path);
316
+ throw error;
188
317
  }
189
- return false;
190
318
  }
191
319
  /**
192
320
  * Grow or shrink a file's byte buffer to exactly `length`, zero-filling any
193
321
  * extension. Callers validate `length >= 0` first.
194
322
  */
195
- function resizeFileData(data, length) {
196
- const next = new Uint8Array(length);
323
+ function resizeFileData(data, length, syscall, path) {
324
+ const next = allocateFileData(length, syscall, path);
197
325
  next.set(data.subarray(0, Math.min(length, data.byteLength)));
198
326
  return next;
199
327
  }
328
+ /** Grow writes geometrically; the public view still exposes only file bytes. */
329
+ function growFileData(data, length, syscall, path) {
330
+ const capacity = data.buffer.byteLength - data.byteOffset;
331
+ if (length <= capacity) {
332
+ const next = new Uint8Array(data.buffer, data.byteOffset, length);
333
+ next.fill(0, data.byteLength);
334
+ return next;
335
+ }
336
+ // Spare capacity is only an optimization: when the engine refuses the
337
+ // doubled size, the exact length still satisfies the write.
338
+ let next;
339
+ if (data.byteLength * 2 > length) {
340
+ try {
341
+ next = allocateFileData(data.byteLength * 2, syscall, path);
342
+ }
343
+ catch {
344
+ next = allocateFileData(length, syscall, path);
345
+ }
346
+ }
347
+ else
348
+ next = allocateFileData(length, syscall, path);
349
+ next.set(data);
350
+ return next.subarray(0, length);
351
+ }
352
+ /** Reject invalid byte slices before a write or queued read can mutate state. */
353
+ function validSlice(buffer, offset, length) {
354
+ return (Number.isSafeInteger(offset) &&
355
+ Number.isSafeInteger(length) &&
356
+ offset >= 0 &&
357
+ length >= 0 &&
358
+ offset <= buffer.byteLength &&
359
+ length <= buffer.byteLength - offset);
360
+ }
200
361
  /**
201
362
  * Write `view` through descriptor `entry` and return the bytes stored.
202
363
  *
@@ -220,14 +381,17 @@ function createMemFS() {
220
381
  const start = entry.append
221
382
  ? node.data.byteLength
222
383
  : (position ?? entry.position);
223
- if (!Number.isInteger(start) || start < 0)
384
+ if (!Number.isSafeInteger(start) || start < 0)
224
385
  throw new MemFSError_1.MemFSError("EINVAL", syscall, entry.path);
225
386
  if (view.byteLength === 0)
226
387
  return 0;
227
388
  const end = start + view.byteLength;
389
+ if (!Number.isSafeInteger(end))
390
+ throw new MemFSError_1.MemFSError("EINVAL", syscall, entry.path);
228
391
  if (end > node.data.byteLength)
229
- node.data = resizeFileData(node.data, end);
392
+ node.data = growFileData(node.data, end, syscall, entry.path);
230
393
  node.data.set(view, start);
394
+ node.text = undefined;
231
395
  node.mtimeMs = Date.now();
232
396
  if (position === null || entry.append)
233
397
  entry.position = end;
@@ -242,21 +406,23 @@ function createMemFS() {
242
406
  function moveSubtree(src, dest) {
243
407
  const srcPrefix = src + "/";
244
408
  const moves = [];
245
- for (const [key, node] of nodes) {
246
- if (key === src)
247
- moves.push([dest, node]);
248
- else if (key.startsWith(srcPrefix))
249
- moves.push([dest + "/" + key.slice(srcPrefix.length), node]);
409
+ const pending = [src];
410
+ while (pending.length > 0) {
411
+ const key = pending.pop();
412
+ const target = key === src ? dest : dest + key.slice(src.length);
413
+ moves.push([key, target, nodes.get(key)]);
414
+ for (const child of children.get(key) ?? [])
415
+ pending.push(child);
250
416
  }
251
417
  // Delete the whole source subtree first so a nested overwrite cannot leave
252
418
  // a stale descendant behind, then reinsert at the destination prefix. Any
253
419
  // pre-existing `dest` node is replaced by the reinsert.
254
- for (const key of [...nodes.keys()]) {
255
- if (key === src || key.startsWith(srcPrefix))
256
- nodes.delete(key);
257
- }
258
- for (const [key, node] of moves)
259
- nodes.set(key, node);
420
+ for (let i = moves.length - 1; i >= 0; --i)
421
+ deleteNode(moves[i][0]);
422
+ if (nodes.has(dest))
423
+ deleteNode(dest);
424
+ for (const [, key, node] of moves)
425
+ setNode(key, node);
260
426
  for (const entry of fdTable.values()) {
261
427
  if (entry.path === src)
262
428
  entry.path = dest;
@@ -281,10 +447,11 @@ function createMemFS() {
281
447
  // file observe the replacement instead of a detached predecessor.
282
448
  if (existing) {
283
449
  existing.data = bytes;
450
+ existing.text = undefined;
284
451
  existing.mtimeMs = Date.now();
285
452
  }
286
453
  else
287
- nodes.set(norm, { kind: "file", data: bytes, mtimeMs: Date.now() });
454
+ setNode(norm, { kind: "file", data: bytes, mtimeMs: Date.now() });
288
455
  }
289
456
  function readFile(p) {
290
457
  const node = nodes.get(normalize(p));
@@ -293,8 +460,10 @@ function createMemFS() {
293
460
  return new Uint8Array(node.data);
294
461
  }
295
462
  function readFileText(p) {
296
- const bytes = readFile(p);
297
- return bytes === null ? null : decoder.decode(bytes);
463
+ const node = nodes.get(normalize(p));
464
+ if (!node || node.kind !== "file")
465
+ return null;
466
+ return (node.text ??= decoder.decode(node.data));
298
467
  }
299
468
  function exists(p) {
300
469
  return nodes.has(normalize(p));
@@ -329,10 +498,10 @@ function createMemFS() {
329
498
  };
330
499
  }
331
500
  /**
332
- * Return immediate children of directory `p`, sorted alphabetically.
501
+ * Return immediate children of directory `p`, sorted by UTF-16 code unit.
333
502
  *
334
- * Uses a linear scan over the node map and a `Set` to deduplicate nested
335
- * paths into direct-child names — O(n) in the number of total nodes.
503
+ * The maintained child index limits work to this directory's entries. Sorting
504
+ * provides deterministic output without scanning unrelated project files.
336
505
  */
337
506
  function readdirSync(p) {
338
507
  const norm = normalize(p);
@@ -341,16 +510,9 @@ function createMemFS() {
341
510
  throw new MemFSError_1.MemFSError("ENOENT", "readdir", norm);
342
511
  if (node.kind !== "dir")
343
512
  throw new MemFSError_1.MemFSError("ENOTDIR", "readdir", norm);
344
- const prefix = norm === "/" ? "/" : norm + "/";
345
- const direct = new Set();
346
- for (const key of nodes.keys()) {
347
- if (!key.startsWith(prefix) || key === norm)
348
- continue;
349
- const rest = key.slice(prefix.length);
350
- const cut = rest.indexOf("/");
351
- direct.add(cut === -1 ? rest : rest.slice(0, cut));
352
- }
353
- return [...direct].sort();
513
+ return [...children.get(norm)]
514
+ .map((child) => child.slice(norm === "/" ? 1 : norm.length + 1))
515
+ .sort();
354
516
  }
355
517
  const fs = {
356
518
  constants: {
@@ -364,12 +526,11 @@ function createMemFS() {
364
526
  },
365
527
  writeSync(fd, buf) {
366
528
  if (fd === 1) {
367
- stdout.buffer += decoder.decode(buf);
529
+ stdout.write(buf);
368
530
  return buf.length;
369
531
  }
370
532
  if (fd === 2) {
371
- const text = decoder.decode(buf);
372
- stderr.buffer += text;
533
+ const text = stderr.write(buf);
373
534
  // Surface wasm-side stderr to the host console in real-time so plugin
374
535
  // debug prints / Go panics aren't trapped inside the MemFS buffer.
375
536
  // Stripped at end of message for cleaner display.
@@ -396,6 +557,8 @@ function createMemFS() {
396
557
  },
397
558
  write(fd, buf, offset, length, position, callback) {
398
559
  try {
560
+ if (!validSlice(buf, offset, length))
561
+ throw new MemFSError_1.MemFSError("EINVAL", "write");
399
562
  // Pipe write: snapshot the data (caller may reuse `buf`) and queue.
400
563
  // Synchronously wake any blocked reader so the cooperative wasm
401
564
  // scheduler doesn't deadlock waiting for a future fs roundtrip.
@@ -405,10 +568,14 @@ function createMemFS() {
405
568
  callback(new MemFSError_1.MemFSError("EBADF", "write"), 0);
406
569
  return;
407
570
  }
571
+ if (pipeState.readClosed) {
572
+ callback(new MemFSError_1.MemFSError("EPIPE", "write"), 0);
573
+ return;
574
+ }
408
575
  if (length > 0) {
409
576
  const copy = new Uint8Array(length);
410
577
  copy.set(buf.subarray(offset, offset + length));
411
- pipeState.buffers.push(copy);
578
+ enqueue(pipeState.buffers, copy);
412
579
  flushPipeReaders(pipeState);
413
580
  }
414
581
  callback(null, length);
@@ -463,7 +630,7 @@ function createMemFS() {
463
630
  if (missing.length > 0)
464
631
  throw new MemFSError_1.MemFSError("ENOENT", "open", missing[0]);
465
632
  node = { kind: "file", data: new Uint8Array(), mtimeMs: Date.now() };
466
- nodes.set(norm, node);
633
+ setNode(norm, node);
467
634
  }
468
635
  else {
469
636
  if (creating && exclusive)
@@ -480,6 +647,7 @@ function createMemFS() {
480
647
  }
481
648
  if (truncating) {
482
649
  node.data = new Uint8Array();
650
+ node.text = undefined;
483
651
  node.mtimeMs = Date.now();
484
652
  }
485
653
  const fd = nextFd++;
@@ -500,11 +668,19 @@ function createMemFS() {
500
668
  close(fd, callback) {
501
669
  const pipeState = pipes.get(fd);
502
670
  if (pipeState) {
671
+ pipes.delete(fd);
503
672
  if (fd === pipeState.writeFd) {
504
673
  pipeState.writeClosed = true;
505
674
  flushPipeReaders(pipeState);
506
675
  }
507
- pipes.delete(fd);
676
+ else {
677
+ pipeState.readClosed = true;
678
+ pipeState.buffers = {};
679
+ while (pipeState.pendingReaders.head) {
680
+ const reader = dequeue(pipeState.pendingReaders);
681
+ reader.callback(new MemFSError_1.MemFSError("EBADF", "read"), 0);
682
+ }
683
+ }
508
684
  callback(null);
509
685
  return;
510
686
  }
@@ -512,11 +688,19 @@ function createMemFS() {
512
688
  callback(new MemFSError_1.MemFSError("EBADF", "close"));
513
689
  return;
514
690
  }
691
+ if (fd === 1)
692
+ stdout.flush();
693
+ if (fd === 2)
694
+ stderr.flush();
515
695
  if (fd > 2)
516
696
  fdTable.delete(fd);
517
697
  callback(null);
518
698
  },
519
699
  read(fd, buffer, offset, length, position, callback) {
700
+ if (!validSlice(buffer, offset, length)) {
701
+ callback(new MemFSError_1.MemFSError("EINVAL", "read"), 0);
702
+ return;
703
+ }
520
704
  // Pipe read: drain queued chunks; block (defer callback) on empty.
521
705
  // Empty + writeClosed → return 0 (EOF). Position is ignored for pipes.
522
706
  const pipeState = pipes.get(fd);
@@ -525,7 +709,11 @@ function createMemFS() {
525
709
  callback(new MemFSError_1.MemFSError("EBADF", "read"), 0);
526
710
  return;
527
711
  }
528
- if (pipeState.buffers.length > 0) {
712
+ if (length === 0) {
713
+ callback(null, 0);
714
+ return;
715
+ }
716
+ if (pipeState.buffers.head) {
529
717
  const n = drainPipeInto(pipeState, buffer, offset, length);
530
718
  callback(null, n);
531
719
  return;
@@ -534,7 +722,7 @@ function createMemFS() {
534
722
  callback(null, 0);
535
723
  return;
536
724
  }
537
- pipeState.pendingReaders.push({ buffer, offset, length, callback });
725
+ enqueue(pipeState.pendingReaders, { buffer, offset, length, callback });
538
726
  return;
539
727
  }
540
728
  const entry = fdTable.get(fd);
@@ -554,7 +742,7 @@ function createMemFS() {
554
742
  return;
555
743
  }
556
744
  const start = position ?? entry.position;
557
- if (!Number.isInteger(start) || start < 0) {
745
+ if (!Number.isSafeInteger(start) || start < 0) {
558
746
  callback(new MemFSError_1.MemFSError("EINVAL", "read", entry.path), 0);
559
747
  return;
560
748
  }
@@ -586,7 +774,7 @@ function createMemFS() {
586
774
  throw new MemFSError_1.MemFSError("ENOENT", "mkdir", parentDir(norm));
587
775
  if (parent.kind !== "dir")
588
776
  throw new MemFSError_1.MemFSError("ENOTDIR", "mkdir", parentDir(norm));
589
- nodes.set(norm, {
777
+ setNode(norm, {
590
778
  kind: "dir",
591
779
  data: new Uint8Array(),
592
780
  mtimeMs: Date.now(),
@@ -652,7 +840,7 @@ function createMemFS() {
652
840
  callback(new MemFSError_1.MemFSError("EISDIR", "unlink", norm));
653
841
  return;
654
842
  }
655
- nodes.delete(norm);
843
+ deleteNode(norm);
656
844
  callback(null);
657
845
  },
658
846
  rename(from, to, callback) {
@@ -728,7 +916,7 @@ function createMemFS() {
728
916
  callback(new MemFSError_1.MemFSError("ENOTEMPTY", "rmdir", norm));
729
917
  return;
730
918
  }
731
- nodes.delete(norm);
919
+ deleteNode(norm);
732
920
  callback(null);
733
921
  },
734
922
  chmod(_p, _mode, callback) {
@@ -773,7 +961,14 @@ function createMemFS() {
773
961
  callback(new MemFSError_1.MemFSError("EINVAL", "truncate", norm));
774
962
  return;
775
963
  }
776
- node.data = resizeFileData(node.data, length);
964
+ try {
965
+ node.data = resizeFileData(node.data, length, "truncate", norm);
966
+ }
967
+ catch (err) {
968
+ callback(err);
969
+ return;
970
+ }
971
+ node.text = undefined;
777
972
  node.mtimeMs = Date.now();
778
973
  callback(null);
779
974
  },
@@ -805,7 +1000,14 @@ function createMemFS() {
805
1000
  callback(new MemFSError_1.MemFSError("EINVAL", "ftruncate", entry.path));
806
1001
  return;
807
1002
  }
808
- node.data = resizeFileData(node.data, length);
1003
+ try {
1004
+ node.data = resizeFileData(node.data, length, "ftruncate", entry.path);
1005
+ }
1006
+ catch (err) {
1007
+ callback(err);
1008
+ return;
1009
+ }
1010
+ node.text = undefined;
809
1011
  node.mtimeMs = Date.now();
810
1012
  callback(null);
811
1013
  },
@@ -813,11 +1015,12 @@ function createMemFS() {
813
1015
  const readFd = nextFd++;
814
1016
  const writeFd = nextFd++;
815
1017
  const state = {
816
- buffers: [],
817
- pendingReaders: [],
1018
+ buffers: {},
1019
+ pendingReaders: {},
818
1020
  readFd,
819
1021
  writeFd,
820
1022
  writeClosed: false,
1023
+ readClosed: false,
821
1024
  };
822
1025
  pipes.set(readFd, state);
823
1026
  pipes.set(writeFd, state);
@@ -831,11 +1034,11 @@ function createMemFS() {
831
1034
  readFileText,
832
1035
  exists,
833
1036
  mkdirp,
834
- stdout,
835
- stderr,
1037
+ stdout: stdout.output,
1038
+ stderr: stderr.output,
836
1039
  resetStdio() {
837
- stdout.buffer = "";
838
- stderr.buffer = "";
1040
+ stdout.output.buffer = "";
1041
+ stderr.output.buffer = "";
839
1042
  },
840
1043
  };
841
1044
  }