@volter/tabnode 0.5.33 → 0.5.35

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/dist/__sw__.js CHANGED
@@ -289,7 +289,7 @@ function handleMainMessage(host, event) {
289
289
  /**
290
290
  * Send request to main thread and wait for response
291
291
  */
292
- async function sendRequest(host, port, method, url, headers, body) {
292
+ async function sendRequest(host, port, method, url, headers, body, destination = '') {
293
293
  DEBUG && console.log('[SW] sendRequest called, host:', !!host, 'url:', url);
294
294
 
295
295
  if (!host) {
@@ -312,7 +312,7 @@ async function sendRequest(host, port, method, url, headers, body) {
312
312
  host.port.postMessage({
313
313
  type: 'request',
314
314
  id,
315
- data: { port, method, url, headers, body },
315
+ data: { port, method, url, headers, body, destination },
316
316
  });
317
317
  });
318
318
  }
@@ -321,7 +321,7 @@ async function sendRequest(host, port, method, url, headers, body) {
321
321
  * Send streaming request to main thread
322
322
  * Returns a ReadableStream that receives chunks from main thread
323
323
  */
324
- async function sendStreamingRequest(host, port, method, url, headers, body) {
324
+ async function sendStreamingRequest(host, port, method, url, headers, body, destination = '') {
325
325
  DEBUG && console.log('[SW] sendStreamingRequest called, url:', url);
326
326
 
327
327
  if (!host) {
@@ -351,7 +351,7 @@ async function sendStreamingRequest(host, port, method, url, headers, body) {
351
351
  host.port.postMessage({
352
352
  type: 'request',
353
353
  id,
354
- data: { port, method, url, headers, body, streaming: true },
354
+ data: { port, method, url, headers, body, streaming: true, destination },
355
355
  });
356
356
  },
357
357
  cancel() {
@@ -365,7 +365,7 @@ async function sendStreamingRequest(host, port, method, url, headers, body) {
365
365
  // A Node HTTP producer must stop when its consumer stops reading and must
366
366
  // observe disconnect. One pull credit bounds the service-worker response queue;
367
367
  // this protocol requires a paired host bridge and is negotiated per port.
368
- async function sendControlledRequest(host, port, method, url, headers, body, signal) {
368
+ async function sendControlledRequest(host, port, method, url, headers, body, signal, destination = '') {
369
369
  if (!host) throw new Error('Service Worker not initialized');
370
370
  if (signal.aborted) throw signal.reason;
371
371
  const channel = host.port;
@@ -450,7 +450,7 @@ async function sendControlledRequest(host, port, method, url, headers, body, sig
450
450
  signal.addEventListener('abort', abort, { once: true });
451
451
  try {
452
452
  channel.postMessage({ type: 'request', id,
453
- data: { port, method, url, headers, body, streaming: true, flowControl: 1 } });
453
+ data: { port, method, url, headers, body, streaming: true, flowControl: 1, destination } });
454
454
  if (signal.aborted) abort();
455
455
  const head = await headersPromise;
456
456
  const noBody = method === 'HEAD' || [204, 205, 304].includes(head.statusCode);
@@ -775,7 +775,7 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
775
775
  if (registration) {
776
776
  if (host.flowControlledPorts.get(port) !== registration || host.port !== registration.channel || hosts.get(host.id) !== host)
777
777
  throw new Error('Virtual server registration changed during upload');
778
- return await sendControlledRequest(host, port, request.method, path, headers, body, request.signal);
778
+ return await sendControlledRequest(host, port, request.method, path, headers, body, request.signal, request.destination);
779
779
  }
780
780
 
781
781
  // Legacy peers retain their old selection until they negotiate flow control.
@@ -783,12 +783,12 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
783
783
 
784
784
  if (isStreamingCandidate) {
785
785
  DEBUG && console.log('[SW] Using streaming mode for:', path);
786
- return handleStreamingRequest(host, port, request.method, path, headers, body);
786
+ return handleStreamingRequest(host, port, request.method, path, headers, body, request.destination);
787
787
  }
788
788
  DEBUG && console.log('[SW] Using non-streaming mode for:', request.method, path);
789
789
 
790
790
  // Send to main thread
791
- const response = await sendRequest(host, port, request.method, path, headers, body);
791
+ const response = await sendRequest(host, port, request.method, path, headers, body, request.destination);
792
792
 
793
793
  DEBUG && console.log('[SW] Got response from main thread:', {
794
794
  statusCode: response.statusCode,
@@ -853,8 +853,8 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
853
853
  /**
854
854
  * Handle a streaming request
855
855
  */
856
- async function handleStreamingRequest(host, port, method, path, headers, body) {
857
- const { stream, headersPromise, id } = await sendStreamingRequest(host, port, method, path, headers, body);
856
+ async function handleStreamingRequest(host, port, method, path, headers, body, destination = '') {
857
+ const { stream, headersPromise, id } = await sendStreamingRequest(host, port, method, path, headers, body, destination);
858
858
 
859
859
  // Wait for headers to arrive
860
860
  const responseData = await headersPromise;
@@ -8639,6 +8639,7 @@ function createFsBindings(Bytes = () => Buffer$9) {
8639
8639
  // ---- writing ------------------------------------------------------------
8640
8640
  writeBuffer(fd2, buffer2, offset2, length, position, req) {
8641
8641
  return answer(req, () => {
8642
+ var _a3;
8642
8643
  const stream = handleForFd(fd2);
8643
8644
  if (stream instanceof LibuvStreamWrap) {
8644
8645
  if (position !== null && position !== void 0 && position >= 0) throw createNodeError$1("ESPIPE", "write", String(fd2));
@@ -8651,6 +8652,23 @@ function createFsBindings(Bytes = () => Buffer$9) {
8651
8652
  if ((file.flags & 3) === 0) throw createNodeError$1("EBADF", "write", file.path);
8652
8653
  const tree2 = file.tree;
8653
8654
  const slice = buffer2.subarray(offset2, offset2 + length);
8655
+ const held = ((_a3 = file.cached) == null ? void 0 : _a3.length) ?? (tree2.existsSync(file.path) ? tree2.statSync(file.path).size : 0);
8656
+ const end = (file.flags & flagBits().append) !== 0 ? held : position === null || position === void 0 || position < 0 ? file.position : position;
8657
+ if (end === held && typeof tree2.appendFileSync === "function") {
8658
+ tree2.appendFileSync(file.path, slice);
8659
+ if (file.cached !== void 0) {
8660
+ let room = file.room;
8661
+ if (!room || file.cached.buffer !== room.buffer || file.cached.byteOffset !== 0 || room.byteLength < held + slice.length) {
8662
+ room = new Uint8Array(Math.max(held + slice.length, held * 2, 4096));
8663
+ room.set(file.cached, 0);
8664
+ }
8665
+ room.set(slice, held);
8666
+ file.cached = room.subarray(0, held + slice.length);
8667
+ file.room = room;
8668
+ }
8669
+ if (position === null || position === void 0 || position < 0) file.position = end + slice.length;
8670
+ return slice.length;
8671
+ }
8654
8672
  const existing = file.cached ?? (tree2.existsSync(file.path) ? tree2.readFileSync(file.path) : new Uint8Array(0));
8655
8673
  const at3 = (file.flags & flagBits().append) !== 0 ? existing.length : position === null || position === void 0 || position < 0 ? file.position : position;
8656
8674
  const size = Math.max(existing.length, at3 + slice.length);
@@ -31697,6 +31715,47 @@ class VirtualFS {
31697
31715
  writeFileSync(path2, data2) {
31698
31716
  this.writeFileSyncInternal(path2, data2, true);
31699
31717
  }
31718
+ /**
31719
+ * Adds bytes at a file's end, creating it. A file grown a piece at a time
31720
+ * (a write stream's chunks) is kept in a buffer with room to grow, so an
31721
+ * append copies only what it adds: writing the whole file again for each
31722
+ * 1 KB chunk of a 50 MB file copied about a terabyte. A tree that keeps its
31723
+ * files elsewhere (its own writeFileSync) is appended to through its own
31724
+ * read and write unless it answers appends itself.
31725
+ */
31726
+ appendFileSync(path2, data2) {
31727
+ var _a3;
31728
+ const bytes2 = typeof data2 === "string" ? this.encoder.encode(data2) : data2;
31729
+ if (this.writeFileSync !== VirtualFS.prototype.writeFileSync) {
31730
+ const existing = this.existsSync(path2) ? this.readFileSync(path2) : new Uint8Array(0);
31731
+ const whole = new Uint8Array(existing.byteLength + bytes2.byteLength);
31732
+ whole.set(existing, 0);
31733
+ whole.set(bytes2, existing.byteLength);
31734
+ this.writeFileSync(path2, whole);
31735
+ return;
31736
+ }
31737
+ const normalized = this.normalizePath(path2);
31738
+ const node = this.getNode(normalized);
31739
+ if (!node) {
31740
+ this.writeFileSyncInternal(normalized, bytes2.slice(), true);
31741
+ return;
31742
+ }
31743
+ if (node.type !== "file") throw createNodeError("EISDIR", "open", path2);
31744
+ this.assertWritable(normalized, "open");
31745
+ const current3 = node.content ?? new Uint8Array(0);
31746
+ const length = current3.byteLength + bytes2.byteLength;
31747
+ let room = node.room;
31748
+ if (!room || current3.buffer !== room.buffer || current3.byteOffset !== 0 || room.byteLength < length) {
31749
+ room = new Uint8Array(Math.max(length, current3.byteLength * 2, 4096));
31750
+ room.set(current3, 0);
31751
+ }
31752
+ room.set(bytes2, current3.byteLength);
31753
+ node.content = room.subarray(0, length);
31754
+ node.room = room;
31755
+ node.mtime = Date.now();
31756
+ this.notifyWatchers(normalized, "change");
31757
+ if ((_a3 = this.eventListeners.get("change")) == null ? void 0 : _a3.size) this.emit("change", normalized, this.decoder.decode(node.content));
31758
+ }
31700
31759
  /**
31701
31760
  * Create directory, optionally with recursive parent creation
31702
31761
  */
package/dist/index.mjs CHANGED
@@ -8767,6 +8767,7 @@ function createFsBindings(Bytes = () => Buffer$9) {
8767
8767
  // ---- writing ------------------------------------------------------------
8768
8768
  writeBuffer(fd, buffer2, offset2, length, position, req) {
8769
8769
  return answer(req, () => {
8770
+ var _a2;
8770
8771
  const stream = handleForFd(fd);
8771
8772
  if (stream instanceof LibuvStreamWrap) {
8772
8773
  if (position !== null && position !== void 0 && position >= 0) throw createNodeError$1("ESPIPE", "write", String(fd));
@@ -8779,6 +8780,23 @@ function createFsBindings(Bytes = () => Buffer$9) {
8779
8780
  if ((file.flags & 3) === 0) throw createNodeError$1("EBADF", "write", file.path);
8780
8781
  const tree2 = file.tree;
8781
8782
  const slice = buffer2.subarray(offset2, offset2 + length);
8783
+ const held = ((_a2 = file.cached) == null ? void 0 : _a2.length) ?? (tree2.existsSync(file.path) ? tree2.statSync(file.path).size : 0);
8784
+ const end = (file.flags & flagBits().append) !== 0 ? held : position === null || position === void 0 || position < 0 ? file.position : position;
8785
+ if (end === held && typeof tree2.appendFileSync === "function") {
8786
+ tree2.appendFileSync(file.path, slice);
8787
+ if (file.cached !== void 0) {
8788
+ let room = file.room;
8789
+ if (!room || file.cached.buffer !== room.buffer || file.cached.byteOffset !== 0 || room.byteLength < held + slice.length) {
8790
+ room = new Uint8Array(Math.max(held + slice.length, held * 2, 4096));
8791
+ room.set(file.cached, 0);
8792
+ }
8793
+ room.set(slice, held);
8794
+ file.cached = room.subarray(0, held + slice.length);
8795
+ file.room = room;
8796
+ }
8797
+ if (position === null || position === void 0 || position < 0) file.position = end + slice.length;
8798
+ return slice.length;
8799
+ }
8782
8800
  const existing = file.cached ?? (tree2.existsSync(file.path) ? tree2.readFileSync(file.path) : new Uint8Array(0));
8783
8801
  const at2 = (file.flags & flagBits().append) !== 0 ? existing.length : position === null || position === void 0 || position < 0 ? file.position : position;
8784
8802
  const size = Math.max(existing.length, at2 + slice.length);
@@ -21284,10 +21302,17 @@ var implementation$1 = function bind2(that) {
21284
21302
  var implementation = implementation$1;
21285
21303
  var functionBind = Function.prototype.bind || implementation;
21286
21304
  var functionCall = Function.prototype.call;
21287
- var functionApply = Function.prototype.apply;
21305
+ var functionApply;
21306
+ var hasRequiredFunctionApply;
21307
+ function requireFunctionApply() {
21308
+ if (hasRequiredFunctionApply) return functionApply;
21309
+ hasRequiredFunctionApply = 1;
21310
+ functionApply = Function.prototype.apply;
21311
+ return functionApply;
21312
+ }
21288
21313
  var reflectApply = typeof Reflect !== "undefined" && Reflect && Reflect.apply;
21289
21314
  var bind$2 = functionBind;
21290
- var $apply$1 = functionApply;
21315
+ var $apply$1 = requireFunctionApply();
21291
21316
  var $call$2 = functionCall;
21292
21317
  var $reflectApply = reflectApply;
21293
21318
  var actualApply = $reflectApply || bind$2.call($call$2, $apply$1);
@@ -21407,7 +21432,7 @@ var hasSymbols = requireHasSymbols()();
21407
21432
  var getProto = requireGetProto();
21408
21433
  var $ObjectGPO = requireObject_getPrototypeOf();
21409
21434
  var $ReflectGPO = requireReflect_getPrototypeOf();
21410
- var $apply = functionApply;
21435
+ var $apply = requireFunctionApply();
21411
21436
  var $call = functionCall;
21412
21437
  var needsEval = {};
21413
21438
  var TypedArray = typeof Uint8Array === "undefined" || !getProto ? undefined$1 : getProto(Uint8Array);
@@ -22048,7 +22073,7 @@ function requireApplyBind() {
22048
22073
  if (hasRequiredApplyBind) return applyBind;
22049
22074
  hasRequiredApplyBind = 1;
22050
22075
  var bind3 = functionBind;
22051
- var $apply2 = functionApply;
22076
+ var $apply2 = requireFunctionApply();
22052
22077
  var actualApply$1 = actualApply;
22053
22078
  applyBind = function applyBind2() {
22054
22079
  return actualApply$1(bind3, $apply2, arguments);
@@ -31844,6 +31869,47 @@ class VirtualFS {
31844
31869
  writeFileSync(path2, data2) {
31845
31870
  this.writeFileSyncInternal(path2, data2, true);
31846
31871
  }
31872
+ /**
31873
+ * Adds bytes at a file's end, creating it. A file grown a piece at a time
31874
+ * (a write stream's chunks) is kept in a buffer with room to grow, so an
31875
+ * append copies only what it adds: writing the whole file again for each
31876
+ * 1 KB chunk of a 50 MB file copied about a terabyte. A tree that keeps its
31877
+ * files elsewhere (its own writeFileSync) is appended to through its own
31878
+ * read and write unless it answers appends itself.
31879
+ */
31880
+ appendFileSync(path2, data2) {
31881
+ var _a2;
31882
+ const bytes2 = typeof data2 === "string" ? this.encoder.encode(data2) : data2;
31883
+ if (this.writeFileSync !== VirtualFS.prototype.writeFileSync) {
31884
+ const existing = this.existsSync(path2) ? this.readFileSync(path2) : new Uint8Array(0);
31885
+ const whole = new Uint8Array(existing.byteLength + bytes2.byteLength);
31886
+ whole.set(existing, 0);
31887
+ whole.set(bytes2, existing.byteLength);
31888
+ this.writeFileSync(path2, whole);
31889
+ return;
31890
+ }
31891
+ const normalized = this.normalizePath(path2);
31892
+ const node = this.getNode(normalized);
31893
+ if (!node) {
31894
+ this.writeFileSyncInternal(normalized, bytes2.slice(), true);
31895
+ return;
31896
+ }
31897
+ if (node.type !== "file") throw createNodeError("EISDIR", "open", path2);
31898
+ this.assertWritable(normalized, "open");
31899
+ const current3 = node.content ?? new Uint8Array(0);
31900
+ const length = current3.byteLength + bytes2.byteLength;
31901
+ let room = node.room;
31902
+ if (!room || current3.buffer !== room.buffer || current3.byteOffset !== 0 || room.byteLength < length) {
31903
+ room = new Uint8Array(Math.max(length, current3.byteLength * 2, 4096));
31904
+ room.set(current3, 0);
31905
+ }
31906
+ room.set(bytes2, current3.byteLength);
31907
+ node.content = room.subarray(0, length);
31908
+ node.room = room;
31909
+ node.mtime = Date.now();
31910
+ this.notifyWatchers(normalized, "change");
31911
+ if ((_a2 = this.eventListeners.get("change")) == null ? void 0 : _a2.size) this.emit("change", normalized, this.decoder.decode(node.content));
31912
+ }
31847
31913
  /**
31848
31914
  * Create directory, optionally with recursive parent creation
31849
31915
  */
@@ -40227,7 +40293,7 @@ class WorkerRuntime {
40227
40293
  this.worker = new Worker(
40228
40294
  new URL(
40229
40295
  /* @vite-ignore */
40230
- "/assets/runtime-worker-b9-COQEP.js",
40296
+ "/assets/runtime-worker-DEgP1CYS.js",
40231
40297
  import.meta.url
40232
40298
  ),
40233
40299
  { type: "module" }
package/dist/runtime.d.ts CHANGED
@@ -14,7 +14,8 @@ export { pendingGuestTimers, stopGuestTimers } from './guest-timers';
14
14
  * playwright-core's, measured in a tab, against 0.9 s under Node on the same
15
15
  * machine. The image carries each body under the hash of the file it was made
16
16
  * from, in this directory of the tab's filesystem, and the loader takes it in
17
- * place of the passes when the file it read hashes to one.
17
+ * place of the passes when the file it read hashes to one. A body the loader
18
+ * prepares for a file the image has none for is kept there the same way.
18
19
  */
19
20
  export declare const PREPARED_MODULES_DIR = "/.tabnode/prepared";
20
21
  /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
@@ -195,6 +195,15 @@ export declare class VirtualFS {
195
195
  * Write data to file, creating parent directories as needed
196
196
  */
197
197
  writeFileSync(path: string, data: string | Uint8Array): void;
198
+ /**
199
+ * Adds bytes at a file's end, creating it. A file grown a piece at a time
200
+ * (a write stream's chunks) is kept in a buffer with room to grow, so an
201
+ * append copies only what it adds: writing the whole file again for each
202
+ * 1 KB chunk of a 50 MB file copied about a terabyte. A tree that keeps its
203
+ * files elsewhere (its own writeFileSync) is appended to through its own
204
+ * read and write unless it answers appends itself.
205
+ */
206
+ appendFileSync(path: string, data: string | Uint8Array): void;
198
207
  /**
199
208
  * Create directory, optionally with recursive parent creation
200
209
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.33",
3
+ "version": "0.5.35",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -157,6 +157,8 @@ interface OpenFile {
157
157
  * it could send ready.
158
158
  */
159
159
  cached?: Uint8Array;
160
+ /** The buffer `cached` views when writes have grown it a piece at a time. */
161
+ room?: Uint8Array;
160
162
  }
161
163
 
162
164
  /** Every descriptor this engine has open, and the next number to hand out. */
@@ -606,6 +608,28 @@ const fsBinding = {
606
608
  if ((file.flags & 3) === 0) throw createNodeError('EBADF', 'write', file.path);
607
609
  const tree = file.tree;
608
610
  const slice = buffer.subarray(offset, offset + length);
611
+ // A write at the file's end is an append: the tree is given only the
612
+ // bytes it adds, and this descriptor's copy grows in a buffer with room.
613
+ // Writing the whole file again for each chunk made a 50 MB stream of
614
+ // 1 KB writes copy about a terabyte.
615
+ const held = file.cached?.length ?? (tree.existsSync(file.path) ? tree.statSync(file.path).size : 0);
616
+ const end = (file.flags & flagBits().append) !== 0 ? held
617
+ : position === null || position === undefined || position < 0 ? file.position : position;
618
+ if (end === held && typeof (tree as { appendFileSync?: unknown }).appendFileSync === 'function') {
619
+ (tree as { appendFileSync(path: string, data: Uint8Array): void }).appendFileSync(file.path, slice);
620
+ if (file.cached !== undefined) {
621
+ let room = file.room;
622
+ if (!room || file.cached.buffer !== room.buffer || file.cached.byteOffset !== 0 || room.byteLength < held + slice.length) {
623
+ room = new Uint8Array(Math.max(held + slice.length, held * 2, 4096));
624
+ room.set(file.cached, 0);
625
+ }
626
+ room.set(slice, held);
627
+ file.cached = room.subarray(0, held + slice.length);
628
+ file.room = room;
629
+ }
630
+ if (position === null || position === undefined || position < 0) file.position = end + slice.length;
631
+ return slice.length;
632
+ }
609
633
  const existing = file.cached ?? (tree.existsSync(file.path) ? tree.readFileSync(file.path) as Uint8Array : new Uint8Array(0));
610
634
  const at = (file.flags & flagBits().append) !== 0 ? existing.length
611
635
  : position === null || position === undefined || position < 0 ? file.position : position;
package/src/runtime.ts CHANGED
@@ -587,7 +587,8 @@ function __substratePrepareBody(rawCode: string, resolvedPath: string, format: s
587
587
  * playwright-core's, measured in a tab, against 0.9 s under Node on the same
588
588
  * machine. The image carries each body under the hash of the file it was made
589
589
  * from, in this directory of the tab's filesystem, and the loader takes it in
590
- * place of the passes when the file it read hashes to one.
590
+ * place of the passes when the file it read hashes to one. A body the loader
591
+ * prepares for a file the image has none for is kept there the same way.
591
592
  */
592
593
  export const PREPARED_MODULES_DIR = '/.tabnode/prepared';
593
594
  const PREPARED_MODULES_FORMAT = 'tabnode-prepared-2';
package/src/virtual-fs.ts CHANGED
@@ -656,6 +656,48 @@ export class VirtualFS {
656
656
  this.writeFileSyncInternal(path, data, true);
657
657
  }
658
658
 
659
+ /**
660
+ * Adds bytes at a file's end, creating it. A file grown a piece at a time
661
+ * (a write stream's chunks) is kept in a buffer with room to grow, so an
662
+ * append copies only what it adds: writing the whole file again for each
663
+ * 1 KB chunk of a 50 MB file copied about a terabyte. A tree that keeps its
664
+ * files elsewhere (its own writeFileSync) is appended to through its own
665
+ * read and write unless it answers appends itself.
666
+ */
667
+ appendFileSync(path: string, data: string | Uint8Array): void {
668
+ const bytes = typeof data === 'string' ? this.encoder.encode(data) : data;
669
+ if (this.writeFileSync !== VirtualFS.prototype.writeFileSync) {
670
+ const existing = this.existsSync(path) ? this.readFileSync(path) as Uint8Array : new Uint8Array(0);
671
+ const whole = new Uint8Array(existing.byteLength + bytes.byteLength);
672
+ whole.set(existing, 0);
673
+ whole.set(bytes, existing.byteLength);
674
+ this.writeFileSync(path, whole);
675
+ return;
676
+ }
677
+ const normalized = this.normalizePath(path);
678
+ const node = this.getNode(normalized);
679
+ if (!node) {
680
+ this.writeFileSyncInternal(normalized, bytes.slice(), true);
681
+ return;
682
+ }
683
+ if (node.type !== 'file') throw createNodeError('EISDIR', 'open', path);
684
+ this.assertWritable(normalized, 'open');
685
+ const current = node.content ?? new Uint8Array(0);
686
+ const length = current.byteLength + bytes.byteLength;
687
+ let room = (node as FSNode & { room?: Uint8Array }).room;
688
+ if (!room || current.buffer !== room.buffer || current.byteOffset !== 0 || room.byteLength < length) {
689
+ room = new Uint8Array(Math.max(length, current.byteLength * 2, 4096));
690
+ room.set(current, 0);
691
+ }
692
+ room.set(bytes, current.byteLength);
693
+ node.content = room.subarray(0, length);
694
+ (node as FSNode & { room?: Uint8Array }).room = room;
695
+ node.mtime = Date.now();
696
+ this.notifyWatchers(normalized, 'change');
697
+ // The change event carries the file's text; it is made only for a listener.
698
+ if (this.eventListeners.get('change')?.size) this.emit('change', normalized, this.decoder.decode(node.content));
699
+ }
700
+
659
701
  /**
660
702
  * Create directory, optionally with recursive parent creation
661
703
  */