@ai-ecoverse/slicc-kernel 1.35.1 → 1.36.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.
@@ -448,11 +448,41 @@ var OpfsFs = class {
448
448
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
449
449
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
450
450
  }
451
+ dirTimes = /* @__PURE__ */ new Map();
452
+ dirDirty = /* @__PURE__ */ new Set();
453
+ dirFlush;
454
+ touch(...dirs) {
455
+ const now = Date.now();
456
+ for (const dir of dirs) {
457
+ this.dirTimes.set(dir, now);
458
+ this.dirDirty.add(dir);
459
+ }
460
+ this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
461
+ this.dirFlush = void 0;
462
+ });
463
+ }
464
+ async flushDirTimes() {
465
+ for (const path of this.dirDirty) {
466
+ this.dirDirty.delete(path);
467
+ const ms = this.dirTimes.get(path);
468
+ if (ms === void 0) continue;
469
+ await this.meta.update(path, (entry) => ({
470
+ ...entry,
471
+ path,
472
+ mtimeMs: ms,
473
+ mtimeFor: 0,
474
+ ctimeMs: ms
475
+ }));
476
+ if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
477
+ }
478
+ }
451
479
  async statOf(path, handle, meta) {
452
480
  const directory = handle.kind === "directory";
453
481
  const file = directory ? void 0 : await handle.getFile();
454
482
  const modified = file?.lastModified ?? 0;
455
- const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
483
+ const touched = directory ? this.dirTimes.get(path) : void 0;
484
+ const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
485
+ const mtime = touched ?? kept;
456
486
  return {
457
487
  isFile: !directory,
458
488
  isDirectory: directory,
@@ -461,7 +491,7 @@ var OpfsFs = class {
461
491
  mode: this.mode(path, directory, meta),
462
492
  mtime: new Date(mtime),
463
493
  atime: new Date(meta?.atimeMs ?? mtime),
464
- ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
494
+ ctime: new Date(Math.max(touched ?? meta?.ctimeMs ?? mtime, modified)),
465
495
  ino: meta?.ino ?? inodeOf(path),
466
496
  ...this.ranged && !directory ? { ranged: true } : {}
467
497
  };
@@ -538,6 +568,19 @@ var OpfsFs = class {
538
568
  const handle = await this.existing(path);
539
569
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
540
570
  }
571
+ async writeSession(path, truncate) {
572
+ const handle = (await this.fileFor(path))[0];
573
+ const writable = await this.retrying(
574
+ path,
575
+ () => handle.createWritable({ keepExistingData: !truncate }),
576
+ true
577
+ );
578
+ return {
579
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
580
+ truncate: (size) => writable.truncate(size),
581
+ close: () => writable.close()
582
+ };
583
+ }
541
584
  async readFile(path) {
542
585
  return new TextDecoder().decode(await this.readFileBuffer(path));
543
586
  }
@@ -555,10 +598,12 @@ var OpfsFs = class {
555
598
  const [target, existed] = await this.writeTarget(path);
556
599
  const [dir, name2] = await this.parent(target);
557
600
  try {
558
- return [
559
- await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
560
- existed
561
- ];
601
+ const handle = await this.inDir(
602
+ dir,
603
+ (parent) => parent.getFileHandle(name2, { create: true })
604
+ );
605
+ if (!existed) this.touch(parentOf2(target));
606
+ return [handle, existed];
562
607
  } catch (err) {
563
608
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
564
609
  throw code ? fsError(code, path) : translate(err, path);
@@ -616,6 +661,7 @@ var OpfsFs = class {
616
661
  const parent = await this.locate(parentOf2(path), true);
617
662
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
618
663
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
664
+ this.touch(parent.path);
619
665
  }
620
666
  async rm(path, options = {}) {
621
667
  let found;
@@ -639,6 +685,7 @@ var OpfsFs = class {
639
685
  this.changed(found.path);
640
686
  }
641
687
  await this.meta.remove([found.path]);
688
+ this.touch(parentOf2(found.path));
642
689
  }
643
690
  async rename(from, to) {
644
691
  if (from === to) return;
@@ -670,6 +717,7 @@ var OpfsFs = class {
670
717
  if (source.link) {
671
718
  if (target?.handle) await this.rm(destination);
672
719
  await this.meta.move(source.path, destination, carry);
720
+ this.touch(parentOf2(source.path), parent.path);
673
721
  return;
674
722
  }
675
723
  const dir = parent.handle;
@@ -691,6 +739,7 @@ var OpfsFs = class {
691
739
  }
692
740
  if (copied) await this.restamp(destination, timed);
693
741
  this.changed(source.path, destination);
742
+ this.touch(parentOf2(source.path), parent.path);
694
743
  }
695
744
  async modified(path) {
696
745
  const handle = await this.handle(path);
@@ -754,6 +803,7 @@ var OpfsFs = class {
754
803
  mtimeMs: now,
755
804
  ctimeMs: now
756
805
  }));
806
+ this.touch(parent.path);
757
807
  }
758
808
  async realpath(path, follow) {
759
809
  return (await this.locate(path, follow)).path;
@@ -794,6 +844,7 @@ var OpfsFs = class {
794
844
  }
795
845
  const handle = found.handle;
796
846
  const file = handle.kind === "file" ? await handle.getFile() : void 0;
847
+ this.dirTimes.delete(found.path);
797
848
  const ctimeMs = Date.now();
798
849
  await this.meta.update(found.path, (entry) => ({
799
850
  ...entry,
@@ -5187,18 +5238,36 @@ function parseSize(text) {
5187
5238
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
5188
5239
  }
5189
5240
  var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5241
+ var HeldSet = class extends Set {
5242
+ idle;
5243
+ constructor(idle) {
5244
+ super();
5245
+ this.idle = idle;
5246
+ }
5247
+ add(holds) {
5248
+ holds.onIdle = this.idle;
5249
+ return super.add(holds);
5250
+ }
5251
+ delete(holds) {
5252
+ const removed = super.delete(holds);
5253
+ for (const path of holds.keys()) this.idle(path);
5254
+ return removed;
5255
+ }
5256
+ };
5190
5257
  function heldUnder(held, target) {
5191
5258
  for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
5192
5259
  return false;
5193
5260
  }
5194
5261
  var HeldPaths = class extends Map {
5195
5262
  revoked = /* @__PURE__ */ new Set();
5263
+ onIdle;
5196
5264
  hold(path, on) {
5197
5265
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
5198
5266
  if (count > 0) this.set(path, count);
5199
5267
  else {
5200
5268
  this.delete(path);
5201
5269
  this.revoked.delete(path);
5270
+ this.onIdle?.(path);
5202
5271
  }
5203
5272
  }
5204
5273
  revoke(prefix) {
@@ -5266,6 +5335,8 @@ function spliced(bytes, offset, part) {
5266
5335
  }
5267
5336
  var MountTable = class {
5268
5337
  mounts = /* @__PURE__ */ new Map();
5338
+ sessions = /* @__PURE__ */ new Map();
5339
+ committing = /* @__PURE__ */ new Map();
5269
5340
  devices = 0;
5270
5341
  pending = /* @__PURE__ */ new Set();
5271
5342
  notes = /* @__PURE__ */ new Map();
@@ -5390,8 +5461,69 @@ var MountTable = class {
5390
5461
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
5391
5462
  this.notes.delete(at);
5392
5463
  this.mounts.delete(at);
5393
- mount.conn.close();
5394
- mount.dispose();
5464
+ const waits = [...this.sessions].filter(([, s]) => s.mount === mount).map(([real]) => this.commit(real));
5465
+ for (const c of this.committing.values()) if (c.mount === mount) waits.push(c.done);
5466
+ const close = () => {
5467
+ mount.conn.close();
5468
+ mount.dispose();
5469
+ };
5470
+ if (waits.length === 0) close();
5471
+ else void Promise.allSettled(waits).then(close);
5472
+ }
5473
+ session(mount, real, rel2) {
5474
+ let session = this.sessions.get(real);
5475
+ if (!session) {
5476
+ const open = () => mount.conn.call({
5477
+ op: "open",
5478
+ path: rel2,
5479
+ write: true,
5480
+ create: true,
5481
+ truncate: false,
5482
+ exclusive: false
5483
+ });
5484
+ const before = this.committing.get(real)?.done ?? Promise.resolve();
5485
+ const fh = before.then(open, open);
5486
+ session = { mount, fh, extents: [], truncated: false };
5487
+ this.sessions.set(real, session);
5488
+ fh.catch(() => this.sessions.delete(real));
5489
+ }
5490
+ return session;
5491
+ }
5492
+ writing(real) {
5493
+ return this.sessions.get(real);
5494
+ }
5495
+ async commit(real) {
5496
+ const session = this.sessions.get(real);
5497
+ if (!session) return this.committing.get(real)?.done;
5498
+ this.sessions.delete(real);
5499
+ const done4 = (async () => {
5500
+ try {
5501
+ await session.mount.conn.call({ op: "release", fh: await session.fh });
5502
+ } finally {
5503
+ this.forget(session.mount, real);
5504
+ }
5505
+ })();
5506
+ const entry = { mount: session.mount, done: done4 };
5507
+ this.committing.set(real, entry);
5508
+ const settle3 = () => {
5509
+ if (this.committing.get(real) === entry) this.committing.delete(real);
5510
+ };
5511
+ done4.then(settle3, settle3);
5512
+ return done4;
5513
+ }
5514
+ async commitUnder(prefix) {
5515
+ const at = normalizePath(prefix);
5516
+ await Promise.all(
5517
+ [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
5518
+ );
5519
+ }
5520
+ async commitOverlapping(real, offset, length2) {
5521
+ const session = this.sessions.get(real);
5522
+ if (!session) return;
5523
+ const end = offset + length2;
5524
+ if (session.truncated || session.extents.some(([a, b]) => a < end && offset < b)) {
5525
+ await this.commit(real);
5526
+ }
5395
5527
  }
5396
5528
  crashed(mount, error) {
5397
5529
  mount.state = "failed";
@@ -5450,6 +5582,13 @@ var MountTable = class {
5450
5582
  return new MountFs(this, base);
5451
5583
  }
5452
5584
  };
5585
+ function noteExtent(extents, from, to) {
5586
+ const last = extents.at(-1);
5587
+ if (last && from <= last[1] && to >= last[0]) {
5588
+ last[0] = Math.min(last[0], from);
5589
+ last[1] = Math.max(last[1], to);
5590
+ } else extents.push([from, to]);
5591
+ }
5453
5592
  var MountFs = class {
5454
5593
  table;
5455
5594
  base;
@@ -5509,6 +5648,7 @@ var MountFs = class {
5509
5648
  }
5510
5649
  }
5511
5650
  async readWhole(mount, real, rel2) {
5651
+ await this.table.commit(real);
5512
5652
  const attr = await this.table.getattr(mount, real, rel2);
5513
5653
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
5514
5654
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
@@ -5558,6 +5698,7 @@ var MountFs = class {
5558
5698
  }
5559
5699
  }
5560
5700
  async writeWhole(mount, real, rel2, bytes) {
5701
+ await this.table.commit(real);
5561
5702
  this.table.forget(mount, real);
5562
5703
  const fh = await this.open(mount, rel2, true);
5563
5704
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
@@ -5580,6 +5721,7 @@ var MountFs = class {
5580
5721
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
5581
5722
  throw errnoError("EISDIR", real);
5582
5723
  }
5724
+ await this.table.commitOverlapping(real, offset, length2);
5583
5725
  const fh = await mount.conn.call({
5584
5726
  op: "open",
5585
5727
  path: rel2,
@@ -5633,6 +5775,14 @@ var MountFs = class {
5633
5775
  this.writable(mount, real);
5634
5776
  this.fits(mount, real, offset + bytes.length);
5635
5777
  this.table.forget(mount, real);
5778
+ if (mount.caps.sessions) {
5779
+ const session = this.table.session(mount, real, rel2);
5780
+ const end = offset + bytes.length;
5781
+ await this.writeRuns(mount, await session.fh, offset, bytes, transfer);
5782
+ noteExtent(session.extents, offset, end);
5783
+ this.table.forget(mount, real);
5784
+ return;
5785
+ }
5636
5786
  const fh = await mount.conn.call({
5637
5787
  op: "open",
5638
5788
  path: rel2,
@@ -5641,17 +5791,20 @@ var MountFs = class {
5641
5791
  truncate: false,
5642
5792
  exclusive: false
5643
5793
  });
5644
- const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
5645
5794
  try {
5646
- for (let at = 0; at < bytes.length; at += chunk2) {
5647
- const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
5648
- await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
5649
- }
5795
+ await this.writeRuns(mount, fh, offset, bytes, transfer);
5650
5796
  } finally {
5651
5797
  await mount.conn.call({ op: "release", fh });
5652
5798
  this.table.forget(mount, real);
5653
5799
  }
5654
5800
  }
5801
+ async writeRuns(mount, fh, offset, bytes, transfer) {
5802
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
5803
+ for (let at = 0; at < bytes.length; at += chunk2) {
5804
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
5805
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
5806
+ }
5807
+ }
5655
5808
  async truncate(path, size) {
5656
5809
  const real = await this.follow(path);
5657
5810
  const found = this.at(real);
@@ -5663,6 +5816,8 @@ var MountFs = class {
5663
5816
  this.writable(mount, real);
5664
5817
  this.fits(mount, real, size);
5665
5818
  this.table.forget(mount, real);
5819
+ const session = this.table.writing(real);
5820
+ if (session) session.truncated = true;
5666
5821
  try {
5667
5822
  await mount.conn.call({ op: "setattr", path: rel2, size });
5668
5823
  } finally {
@@ -5684,7 +5839,16 @@ var MountFs = class {
5684
5839
  const found = this.at(path);
5685
5840
  if (!found) return this.base.lstat(path);
5686
5841
  const real = normalizePath(path);
5687
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
5842
+ if (this.table.writing(real)?.truncated) await this.table.commit(real);
5843
+ return this.statWriting(
5844
+ await this.table.getattr(found.mount, real, found.rel),
5845
+ real,
5846
+ found.mount
5847
+ );
5848
+ }
5849
+ statWriting(attr, real, mount) {
5850
+ const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
5851
+ return statOf({ ...attr, size: Math.max(attr.size, ...ends) }, real, mount);
5688
5852
  }
5689
5853
  async stat(path) {
5690
5854
  const real = await this.follow(path);
@@ -5711,7 +5875,7 @@ var MountFs = class {
5711
5875
  list.map(async (entry) => {
5712
5876
  const child = normalizePath(`${real}/${entry.name}`);
5713
5877
  if (entry.attr && mount.caps.listingStats) {
5714
- return [entry.name, statOf(entry.attr, child, mount)];
5878
+ return [entry.name, this.statWriting(entry.attr, child, mount)];
5715
5879
  }
5716
5880
  return [entry.name, await this.lstat(child).catch(() => null)];
5717
5881
  })
@@ -5747,6 +5911,7 @@ var MountFs = class {
5747
5911
  const real = normalizePath(path);
5748
5912
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
5749
5913
  this.writable(mount, real);
5914
+ await this.table.commitUnder(real);
5750
5915
  let attr;
5751
5916
  try {
5752
5917
  attr = await this.table.getattr(mount, real, rel2);
@@ -5770,6 +5935,8 @@ var MountFs = class {
5770
5935
  if (!a && !b) return this.base.rename(from, to);
5771
5936
  if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
5772
5937
  this.writable(a.mount, normalizePath(to));
5938
+ await this.table.commitUnder(from);
5939
+ await this.table.commitUnder(to);
5773
5940
  this.table.forget(a.mount, normalizePath(from));
5774
5941
  this.table.forget(a.mount, normalizePath(to));
5775
5942
  try {
@@ -7376,6 +7543,7 @@ var VfsNode = class {
7376
7543
  writeBack;
7377
7544
  writeBackCost = 0;
7378
7545
  opens = 0;
7546
+ writers = 0;
7379
7547
  missing = false;
7380
7548
  revoked = false;
7381
7549
  fs;
@@ -7551,8 +7719,10 @@ var VfsNodes = class {
7551
7719
  byPath = /* @__PURE__ */ new Map();
7552
7720
  fs;
7553
7721
  entries = /* @__PURE__ */ new Map();
7554
- constructor(fs) {
7722
+ onClosed;
7723
+ constructor(fs, onClosed) {
7555
7724
  this.fs = fs;
7725
+ this.onClosed = onClosed;
7556
7726
  }
7557
7727
  async onEntry(path, op) {
7558
7728
  const key = await this.entryKey(path);
@@ -7590,16 +7760,17 @@ var VfsNodes = class {
7590
7760
  await this.fs.writeFile(path, new Uint8Array(0));
7591
7761
  });
7592
7762
  }
7593
- open(path, pin) {
7763
+ open(path, pin, writable = false) {
7594
7764
  let node = this.byPath.get(path);
7595
7765
  if (!node) {
7596
7766
  node = new VfsNode(this.fs, path, void 0, false, false, pin);
7597
7767
  this.byPath.set(path, node);
7598
7768
  }
7599
7769
  node.opens++;
7770
+ if (writable) node.writers++;
7600
7771
  return node;
7601
7772
  }
7602
- adopt(path, contents2, dirty) {
7773
+ adopt(path, contents2, dirty, writable = false) {
7603
7774
  let node = this.byPath.get(path);
7604
7775
  if (!node) {
7605
7776
  node = new VfsNode(this.fs, path, contents2, false, dirty);
@@ -7608,15 +7779,27 @@ var VfsNodes = class {
7608
7779
  node.replace(contents2);
7609
7780
  }
7610
7781
  node.opens++;
7782
+ if (writable) node.writers++;
7611
7783
  return node;
7612
7784
  }
7613
7785
  holds(prefix) {
7614
7786
  for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7615
7787
  return false;
7616
7788
  }
7617
- closed(node) {
7789
+ writes(prefix) {
7790
+ for (const [path, node] of this.byPath)
7791
+ if (node.writers > 0 && within4(path, prefix)) return true;
7792
+ return false;
7793
+ }
7794
+ closed(node, writable = false) {
7618
7795
  node.opens--;
7619
- if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7796
+ if (writable) node.writers--;
7797
+ if (node.opens > 0) {
7798
+ if (writable && node.writers === 0) this.onClosed?.(node.path);
7799
+ return;
7800
+ }
7801
+ if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7802
+ this.onClosed?.(node.path);
7620
7803
  }
7621
7804
  async flush(path) {
7622
7805
  for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
@@ -7762,7 +7945,7 @@ function vfsFile(fs, opts, nodes) {
7762
7945
  const access = opts.flags & O_ACCMODE;
7763
7946
  const readable = access !== O_WRONLY;
7764
7947
  const writable = access === O_WRONLY || access === O_RDWR;
7765
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true) : nodes.open(opts.path, opts.pin);
7948
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable) : nodes.open(opts.path, opts.pin, writable);
7766
7949
  let openError;
7767
7950
  const atOpen = (op) => {
7768
7951
  void node.serial(op).catch((err) => {
@@ -7825,7 +8008,7 @@ function vfsFile(fs, opts, nodes) {
7825
8008
  takeOpenError();
7826
8009
  await node.flush();
7827
8010
  } finally {
7828
- nodes?.closed(node);
8011
+ nodes?.closed(node, writable);
7829
8012
  }
7830
8013
  })
7831
8014
  });
@@ -9172,7 +9355,11 @@ function mediumSlot(lost, onLost) {
9172
9355
  }
9173
9356
 
9174
9357
  // src/mount/fsa.ts
9175
- var FSA_CAPABILITIES = { listingStats: true };
9358
+ var FSA_CAPABILITIES = {
9359
+ listingStats: true,
9360
+ ranges: true,
9361
+ sessions: true
9362
+ };
9176
9363
  var codeOf2 = (err) => err?.code;
9177
9364
  var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
9178
9365
  function attrOf(st) {
@@ -9212,30 +9399,20 @@ function kernelFsHandlers(fs) {
9212
9399
  if (st && flags.exclusive) throw fsError2("EEXIST", path);
9213
9400
  if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
9214
9401
  if (!st && !flags.create) throw fsError2("ENOENT", path);
9215
- const data = st && !flags.truncate ? await fs.readFileBuffer(path) : new Uint8Array(0);
9216
- handles.set(++nextFh, { path, data, length: data.length, dirty: !st || flags.truncate });
9402
+ if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
9403
+ handles.set(++nextFh, { path });
9217
9404
  return nextFh;
9218
9405
  },
9219
- async read(fh, offset, size) {
9220
- const h = handle(fh);
9221
- return h.data.slice(offset, Math.min(h.length, offset + size));
9222
- },
9406
+ read: async (fh, offset, size) => fs.pread(handle(fh).path, offset, size),
9223
9407
  async write(fh, offset, bytes) {
9224
9408
  const h = handle(fh);
9225
- const end = offset + bytes.length;
9226
- if (end > h.data.length) {
9227
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
9228
- grown.set(h.data.subarray(0, h.length));
9229
- h.data = grown;
9230
- }
9231
- h.data.set(bytes, offset);
9232
- h.length = Math.max(h.length, end);
9233
- h.dirty = true;
9409
+ h.session ??= fs.writeSession(h.path, false);
9410
+ await (await h.session).write(offset, bytes);
9234
9411
  },
9235
9412
  async release(fh) {
9236
9413
  const h = handle(fh);
9237
9414
  handles.delete(fh);
9238
- if (h.dirty) await fs.writeFile(h.path, h.data.slice(0, h.length));
9415
+ if (h.session !== void 0) await (await h.session).close();
9239
9416
  },
9240
9417
  mkdir: (path) => fs.mkdir(path),
9241
9418
  async rmdir(path) {
@@ -9251,6 +9428,13 @@ function kernelFsHandlers(fs) {
9251
9428
  symlink: (target, path) => fs.symlink(target, path),
9252
9429
  readlink: (path) => fs.readlink(path),
9253
9430
  async setattr(path, change) {
9431
+ if (change.size !== void 0) {
9432
+ const open = [...handles.values()].find(
9433
+ (h) => h.path === path && h.session !== void 0
9434
+ )?.session;
9435
+ if (open !== void 0) await (await open).truncate(change.size);
9436
+ else await fs.truncate?.(path, change.size);
9437
+ }
9254
9438
  if (change.mode !== void 0) await fs.chmod(path, change.mode);
9255
9439
  if (change.mtime !== void 0) {
9256
9440
  await fs.utimes(path, new Date(change.mtime), new Date(change.mtime));
@@ -9966,6 +10150,7 @@ function tmpfs() {
9966
10150
  const { dir, name: name2 } = parent(path);
9967
10151
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
9968
10152
  dir.children.set(name2, make("directory", 16877));
10153
+ dir.mtime = Date.now();
9969
10154
  },
9970
10155
  async rmdir(path) {
9971
10156
  const { dir, name: name2 } = parent(path);
@@ -9974,6 +10159,7 @@ function tmpfs() {
9974
10159
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
9975
10160
  if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
9976
10161
  dir.children.delete(name2);
10162
+ dir.mtime = Date.now();
9977
10163
  },
9978
10164
  async unlink(path) {
9979
10165
  const { dir, name: name2 } = parent(path);
@@ -9981,6 +10167,7 @@ function tmpfs() {
9981
10167
  if (!node) throw fsError2("ENOENT", path);
9982
10168
  if (node.kind === "directory") throw fsError2("EISDIR", path);
9983
10169
  dir.children.delete(name2);
10170
+ dir.mtime = Date.now();
9984
10171
  },
9985
10172
  async rename(from, to) {
9986
10173
  const a = `/${parts(from).join("/")}`;
@@ -9997,6 +10184,8 @@ function tmpfs() {
9997
10184
  }
9998
10185
  source.dir.children.delete(source.name);
9999
10186
  target.dir.children.set(target.name, node);
10187
+ source.dir.mtime = Date.now();
10188
+ target.dir.mtime = source.dir.mtime;
10000
10189
  },
10001
10190
  async symlink(target, path) {
10002
10191
  const { dir, name: name2 } = parent(path);
@@ -10004,6 +10193,7 @@ function tmpfs() {
10004
10193
  const node = make("symlink", 41471);
10005
10194
  node.target = target;
10006
10195
  dir.children.set(name2, node);
10196
+ dir.mtime = Date.now();
10007
10197
  },
10008
10198
  async readlink(path) {
10009
10199
  const node = find(path);
@@ -10302,7 +10492,7 @@ var Launcher = class {
10302
10492
  deciding = /* @__PURE__ */ new Map();
10303
10493
  identities = { asked: 0, timedOut: 0 };
10304
10494
  nodes;
10305
- held = /* @__PURE__ */ new Set();
10495
+ held = new HeldSet((path) => this.released(path));
10306
10496
  processMounts;
10307
10497
  booting = new AbortController();
10308
10498
  fstab = Promise.resolve([]);
@@ -10344,7 +10534,7 @@ var Launcher = class {
10344
10534
  });
10345
10535
  enableCdp(this.net, this.cdp);
10346
10536
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10347
- this.nodes = new VfsNodes(fs);
10537
+ this.nodes = new VfsNodes(fs, (path) => this.released(path));
10348
10538
  this.openFiles.add(this.nodes);
10349
10539
  this.fs = keepingOpen(fs, this.nodes);
10350
10540
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
@@ -10706,6 +10896,11 @@ var Launcher = class {
10706
10896
  umaskOf(pid) {
10707
10897
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
10708
10898
  }
10899
+ released(path) {
10900
+ const at = normalizePath(path);
10901
+ const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10902
+ if (!open) void this.mounts.commit(at).catch(() => void 0);
10903
+ }
10709
10904
  ignoredBy(pid, fork = false) {
10710
10905
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
10711
10906
  }