@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.
package/dist/node.js CHANGED
@@ -1938,11 +1938,41 @@ var OpfsFs = class {
1938
1938
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
1939
1939
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
1940
1940
  }
1941
+ dirTimes = /* @__PURE__ */ new Map();
1942
+ dirDirty = /* @__PURE__ */ new Set();
1943
+ dirFlush;
1944
+ touch(...dirs) {
1945
+ const now = Date.now();
1946
+ for (const dir of dirs) {
1947
+ this.dirTimes.set(dir, now);
1948
+ this.dirDirty.add(dir);
1949
+ }
1950
+ this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
1951
+ this.dirFlush = void 0;
1952
+ });
1953
+ }
1954
+ async flushDirTimes() {
1955
+ for (const path of this.dirDirty) {
1956
+ this.dirDirty.delete(path);
1957
+ const ms = this.dirTimes.get(path);
1958
+ if (ms === void 0) continue;
1959
+ await this.meta.update(path, (entry) => ({
1960
+ ...entry,
1961
+ path,
1962
+ mtimeMs: ms,
1963
+ mtimeFor: 0,
1964
+ ctimeMs: ms
1965
+ }));
1966
+ if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
1967
+ }
1968
+ }
1941
1969
  async statOf(path, handle, meta) {
1942
1970
  const directory = handle.kind === "directory";
1943
1971
  const file = directory ? void 0 : await handle.getFile();
1944
1972
  const modified = file?.lastModified ?? 0;
1945
- const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
1973
+ const touched = directory ? this.dirTimes.get(path) : void 0;
1974
+ const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
1975
+ const mtime = touched ?? kept;
1946
1976
  return {
1947
1977
  isFile: !directory,
1948
1978
  isDirectory: directory,
@@ -1951,7 +1981,7 @@ var OpfsFs = class {
1951
1981
  mode: this.mode(path, directory, meta),
1952
1982
  mtime: new Date(mtime),
1953
1983
  atime: new Date(meta?.atimeMs ?? mtime),
1954
- ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
1984
+ ctime: new Date(Math.max(touched ?? meta?.ctimeMs ?? mtime, modified)),
1955
1985
  ino: meta?.ino ?? inodeOf(path),
1956
1986
  ...this.ranged && !directory ? { ranged: true } : {}
1957
1987
  };
@@ -2028,6 +2058,19 @@ var OpfsFs = class {
2028
2058
  const handle = await this.existing(path);
2029
2059
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
2030
2060
  }
2061
+ async writeSession(path, truncate) {
2062
+ const handle = (await this.fileFor(path))[0];
2063
+ const writable = await this.retrying(
2064
+ path,
2065
+ () => handle.createWritable({ keepExistingData: !truncate }),
2066
+ true
2067
+ );
2068
+ return {
2069
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
2070
+ truncate: (size) => writable.truncate(size),
2071
+ close: () => writable.close()
2072
+ };
2073
+ }
2031
2074
  async readFile(path) {
2032
2075
  return new TextDecoder().decode(await this.readFileBuffer(path));
2033
2076
  }
@@ -2045,10 +2088,12 @@ var OpfsFs = class {
2045
2088
  const [target, existed] = await this.writeTarget(path);
2046
2089
  const [dir, name2] = await this.parent(target);
2047
2090
  try {
2048
- return [
2049
- await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
2050
- existed
2051
- ];
2091
+ const handle = await this.inDir(
2092
+ dir,
2093
+ (parent) => parent.getFileHandle(name2, { create: true })
2094
+ );
2095
+ if (!existed) this.touch(parentOf2(target));
2096
+ return [handle, existed];
2052
2097
  } catch (err) {
2053
2098
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
2054
2099
  throw code ? fsError(code, path) : translate(err, path);
@@ -2106,6 +2151,7 @@ var OpfsFs = class {
2106
2151
  const parent = await this.locate(parentOf2(path), true);
2107
2152
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
2108
2153
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
2154
+ this.touch(parent.path);
2109
2155
  }
2110
2156
  async rm(path, options = {}) {
2111
2157
  let found;
@@ -2129,6 +2175,7 @@ var OpfsFs = class {
2129
2175
  this.changed(found.path);
2130
2176
  }
2131
2177
  await this.meta.remove([found.path]);
2178
+ this.touch(parentOf2(found.path));
2132
2179
  }
2133
2180
  async rename(from, to) {
2134
2181
  if (from === to) return;
@@ -2160,6 +2207,7 @@ var OpfsFs = class {
2160
2207
  if (source.link) {
2161
2208
  if (target?.handle) await this.rm(destination);
2162
2209
  await this.meta.move(source.path, destination, carry);
2210
+ this.touch(parentOf2(source.path), parent.path);
2163
2211
  return;
2164
2212
  }
2165
2213
  const dir = parent.handle;
@@ -2181,6 +2229,7 @@ var OpfsFs = class {
2181
2229
  }
2182
2230
  if (copied) await this.restamp(destination, timed);
2183
2231
  this.changed(source.path, destination);
2232
+ this.touch(parentOf2(source.path), parent.path);
2184
2233
  }
2185
2234
  async modified(path) {
2186
2235
  const handle = await this.handle(path);
@@ -2244,6 +2293,7 @@ var OpfsFs = class {
2244
2293
  mtimeMs: now,
2245
2294
  ctimeMs: now
2246
2295
  }));
2296
+ this.touch(parent.path);
2247
2297
  }
2248
2298
  async realpath(path, follow) {
2249
2299
  return (await this.locate(path, follow)).path;
@@ -2284,6 +2334,7 @@ var OpfsFs = class {
2284
2334
  }
2285
2335
  const handle = found.handle;
2286
2336
  const file = handle.kind === "file" ? await handle.getFile() : void 0;
2337
+ this.dirTimes.delete(found.path);
2287
2338
  const ctimeMs = Date.now();
2288
2339
  await this.meta.update(found.path, (entry) => ({
2289
2340
  ...entry,
@@ -3703,18 +3754,36 @@ function parseSize(text) {
3703
3754
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
3704
3755
  }
3705
3756
  var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3757
+ var HeldSet = class extends Set {
3758
+ idle;
3759
+ constructor(idle) {
3760
+ super();
3761
+ this.idle = idle;
3762
+ }
3763
+ add(holds) {
3764
+ holds.onIdle = this.idle;
3765
+ return super.add(holds);
3766
+ }
3767
+ delete(holds) {
3768
+ const removed = super.delete(holds);
3769
+ for (const path of holds.keys()) this.idle(path);
3770
+ return removed;
3771
+ }
3772
+ };
3706
3773
  function heldUnder(held, target) {
3707
3774
  for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
3708
3775
  return false;
3709
3776
  }
3710
3777
  var HeldPaths = class extends Map {
3711
3778
  revoked = /* @__PURE__ */ new Set();
3779
+ onIdle;
3712
3780
  hold(path, on) {
3713
3781
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
3714
3782
  if (count > 0) this.set(path, count);
3715
3783
  else {
3716
3784
  this.delete(path);
3717
3785
  this.revoked.delete(path);
3786
+ this.onIdle?.(path);
3718
3787
  }
3719
3788
  }
3720
3789
  revoke(prefix) {
@@ -3782,6 +3851,8 @@ function spliced(bytes, offset, part) {
3782
3851
  }
3783
3852
  var MountTable = class {
3784
3853
  mounts = /* @__PURE__ */ new Map();
3854
+ sessions = /* @__PURE__ */ new Map();
3855
+ committing = /* @__PURE__ */ new Map();
3785
3856
  devices = 0;
3786
3857
  pending = /* @__PURE__ */ new Set();
3787
3858
  notes = /* @__PURE__ */ new Map();
@@ -3906,8 +3977,69 @@ var MountTable = class {
3906
3977
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
3907
3978
  this.notes.delete(at);
3908
3979
  this.mounts.delete(at);
3909
- mount.conn.close();
3910
- mount.dispose();
3980
+ const waits = [...this.sessions].filter(([, s]) => s.mount === mount).map(([real]) => this.commit(real));
3981
+ for (const c of this.committing.values()) if (c.mount === mount) waits.push(c.done);
3982
+ const close = () => {
3983
+ mount.conn.close();
3984
+ mount.dispose();
3985
+ };
3986
+ if (waits.length === 0) close();
3987
+ else void Promise.allSettled(waits).then(close);
3988
+ }
3989
+ session(mount, real, rel2) {
3990
+ let session = this.sessions.get(real);
3991
+ if (!session) {
3992
+ const open = () => mount.conn.call({
3993
+ op: "open",
3994
+ path: rel2,
3995
+ write: true,
3996
+ create: true,
3997
+ truncate: false,
3998
+ exclusive: false
3999
+ });
4000
+ const before = this.committing.get(real)?.done ?? Promise.resolve();
4001
+ const fh = before.then(open, open);
4002
+ session = { mount, fh, extents: [], truncated: false };
4003
+ this.sessions.set(real, session);
4004
+ fh.catch(() => this.sessions.delete(real));
4005
+ }
4006
+ return session;
4007
+ }
4008
+ writing(real) {
4009
+ return this.sessions.get(real);
4010
+ }
4011
+ async commit(real) {
4012
+ const session = this.sessions.get(real);
4013
+ if (!session) return this.committing.get(real)?.done;
4014
+ this.sessions.delete(real);
4015
+ const done4 = (async () => {
4016
+ try {
4017
+ await session.mount.conn.call({ op: "release", fh: await session.fh });
4018
+ } finally {
4019
+ this.forget(session.mount, real);
4020
+ }
4021
+ })();
4022
+ const entry = { mount: session.mount, done: done4 };
4023
+ this.committing.set(real, entry);
4024
+ const settle = () => {
4025
+ if (this.committing.get(real) === entry) this.committing.delete(real);
4026
+ };
4027
+ done4.then(settle, settle);
4028
+ return done4;
4029
+ }
4030
+ async commitUnder(prefix) {
4031
+ const at = normalizePath(prefix);
4032
+ await Promise.all(
4033
+ [...this.sessions.keys()].filter((p) => within2(p, at)).map((p) => this.commit(p))
4034
+ );
4035
+ }
4036
+ async commitOverlapping(real, offset, length2) {
4037
+ const session = this.sessions.get(real);
4038
+ if (!session) return;
4039
+ const end = offset + length2;
4040
+ if (session.truncated || session.extents.some(([a, b]) => a < end && offset < b)) {
4041
+ await this.commit(real);
4042
+ }
3911
4043
  }
3912
4044
  crashed(mount, error) {
3913
4045
  mount.state = "failed";
@@ -3966,6 +4098,13 @@ var MountTable = class {
3966
4098
  return new MountFs(this, base);
3967
4099
  }
3968
4100
  };
4101
+ function noteExtent(extents, from, to) {
4102
+ const last = extents.at(-1);
4103
+ if (last && from <= last[1] && to >= last[0]) {
4104
+ last[0] = Math.min(last[0], from);
4105
+ last[1] = Math.max(last[1], to);
4106
+ } else extents.push([from, to]);
4107
+ }
3969
4108
  var MountFs = class {
3970
4109
  table;
3971
4110
  base;
@@ -4025,6 +4164,7 @@ var MountFs = class {
4025
4164
  }
4026
4165
  }
4027
4166
  async readWhole(mount, real, rel2) {
4167
+ await this.table.commit(real);
4028
4168
  const attr = await this.table.getattr(mount, real, rel2);
4029
4169
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
4030
4170
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
@@ -4074,6 +4214,7 @@ var MountFs = class {
4074
4214
  }
4075
4215
  }
4076
4216
  async writeWhole(mount, real, rel2, bytes) {
4217
+ await this.table.commit(real);
4077
4218
  this.table.forget(mount, real);
4078
4219
  const fh = await this.open(mount, rel2, true);
4079
4220
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
@@ -4096,6 +4237,7 @@ var MountFs = class {
4096
4237
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
4097
4238
  throw errnoError("EISDIR", real);
4098
4239
  }
4240
+ await this.table.commitOverlapping(real, offset, length2);
4099
4241
  const fh = await mount.conn.call({
4100
4242
  op: "open",
4101
4243
  path: rel2,
@@ -4149,6 +4291,14 @@ var MountFs = class {
4149
4291
  this.writable(mount, real);
4150
4292
  this.fits(mount, real, offset + bytes.length);
4151
4293
  this.table.forget(mount, real);
4294
+ if (mount.caps.sessions) {
4295
+ const session = this.table.session(mount, real, rel2);
4296
+ const end = offset + bytes.length;
4297
+ await this.writeRuns(mount, await session.fh, offset, bytes, transfer);
4298
+ noteExtent(session.extents, offset, end);
4299
+ this.table.forget(mount, real);
4300
+ return;
4301
+ }
4152
4302
  const fh = await mount.conn.call({
4153
4303
  op: "open",
4154
4304
  path: rel2,
@@ -4157,17 +4307,20 @@ var MountFs = class {
4157
4307
  truncate: false,
4158
4308
  exclusive: false
4159
4309
  });
4160
- const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4161
4310
  try {
4162
- for (let at = 0; at < bytes.length; at += chunk2) {
4163
- const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
4164
- await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
4165
- }
4311
+ await this.writeRuns(mount, fh, offset, bytes, transfer);
4166
4312
  } finally {
4167
4313
  await mount.conn.call({ op: "release", fh });
4168
4314
  this.table.forget(mount, real);
4169
4315
  }
4170
4316
  }
4317
+ async writeRuns(mount, fh, offset, bytes, transfer) {
4318
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4319
+ for (let at = 0; at < bytes.length; at += chunk2) {
4320
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
4321
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
4322
+ }
4323
+ }
4171
4324
  async truncate(path, size) {
4172
4325
  const real = await this.follow(path);
4173
4326
  const found = this.at(real);
@@ -4179,6 +4332,8 @@ var MountFs = class {
4179
4332
  this.writable(mount, real);
4180
4333
  this.fits(mount, real, size);
4181
4334
  this.table.forget(mount, real);
4335
+ const session = this.table.writing(real);
4336
+ if (session) session.truncated = true;
4182
4337
  try {
4183
4338
  await mount.conn.call({ op: "setattr", path: rel2, size });
4184
4339
  } finally {
@@ -4200,7 +4355,16 @@ var MountFs = class {
4200
4355
  const found = this.at(path);
4201
4356
  if (!found) return this.base.lstat(path);
4202
4357
  const real = normalizePath(path);
4203
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
4358
+ if (this.table.writing(real)?.truncated) await this.table.commit(real);
4359
+ return this.statWriting(
4360
+ await this.table.getattr(found.mount, real, found.rel),
4361
+ real,
4362
+ found.mount
4363
+ );
4364
+ }
4365
+ statWriting(attr, real, mount) {
4366
+ const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
4367
+ return statOf({ ...attr, size: Math.max(attr.size, ...ends) }, real, mount);
4204
4368
  }
4205
4369
  async stat(path) {
4206
4370
  const real = await this.follow(path);
@@ -4227,7 +4391,7 @@ var MountFs = class {
4227
4391
  list.map(async (entry) => {
4228
4392
  const child = normalizePath(`${real}/${entry.name}`);
4229
4393
  if (entry.attr && mount.caps.listingStats) {
4230
- return [entry.name, statOf(entry.attr, child, mount)];
4394
+ return [entry.name, this.statWriting(entry.attr, child, mount)];
4231
4395
  }
4232
4396
  return [entry.name, await this.lstat(child).catch(() => null)];
4233
4397
  })
@@ -4263,6 +4427,7 @@ var MountFs = class {
4263
4427
  const real = normalizePath(path);
4264
4428
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
4265
4429
  this.writable(mount, real);
4430
+ await this.table.commitUnder(real);
4266
4431
  let attr;
4267
4432
  try {
4268
4433
  attr = await this.table.getattr(mount, real, rel2);
@@ -4286,6 +4451,8 @@ var MountFs = class {
4286
4451
  if (!a && !b) return this.base.rename(from, to);
4287
4452
  if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
4288
4453
  this.writable(a.mount, normalizePath(to));
4454
+ await this.table.commitUnder(from);
4455
+ await this.table.commitUnder(to);
4289
4456
  this.table.forget(a.mount, normalizePath(from));
4290
4457
  this.table.forget(a.mount, normalizePath(to));
4291
4458
  try {
@@ -7094,6 +7261,7 @@ var VfsNode = class {
7094
7261
  writeBack;
7095
7262
  writeBackCost = 0;
7096
7263
  opens = 0;
7264
+ writers = 0;
7097
7265
  missing = false;
7098
7266
  revoked = false;
7099
7267
  fs;
@@ -7269,8 +7437,10 @@ var VfsNodes = class {
7269
7437
  byPath = /* @__PURE__ */ new Map();
7270
7438
  fs;
7271
7439
  entries = /* @__PURE__ */ new Map();
7272
- constructor(fs) {
7440
+ onClosed;
7441
+ constructor(fs, onClosed) {
7273
7442
  this.fs = fs;
7443
+ this.onClosed = onClosed;
7274
7444
  }
7275
7445
  async onEntry(path, op) {
7276
7446
  const key = await this.entryKey(path);
@@ -7308,16 +7478,17 @@ var VfsNodes = class {
7308
7478
  await this.fs.writeFile(path, new Uint8Array(0));
7309
7479
  });
7310
7480
  }
7311
- open(path, pin) {
7481
+ open(path, pin, writable = false) {
7312
7482
  let node = this.byPath.get(path);
7313
7483
  if (!node) {
7314
7484
  node = new VfsNode(this.fs, path, void 0, false, false, pin);
7315
7485
  this.byPath.set(path, node);
7316
7486
  }
7317
7487
  node.opens++;
7488
+ if (writable) node.writers++;
7318
7489
  return node;
7319
7490
  }
7320
- adopt(path, contents2, dirty) {
7491
+ adopt(path, contents2, dirty, writable = false) {
7321
7492
  let node = this.byPath.get(path);
7322
7493
  if (!node) {
7323
7494
  node = new VfsNode(this.fs, path, contents2, false, dirty);
@@ -7326,15 +7497,27 @@ var VfsNodes = class {
7326
7497
  node.replace(contents2);
7327
7498
  }
7328
7499
  node.opens++;
7500
+ if (writable) node.writers++;
7329
7501
  return node;
7330
7502
  }
7331
7503
  holds(prefix) {
7332
7504
  for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7333
7505
  return false;
7334
7506
  }
7335
- closed(node) {
7507
+ writes(prefix) {
7508
+ for (const [path, node] of this.byPath)
7509
+ if (node.writers > 0 && within4(path, prefix)) return true;
7510
+ return false;
7511
+ }
7512
+ closed(node, writable = false) {
7336
7513
  node.opens--;
7337
- if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7514
+ if (writable) node.writers--;
7515
+ if (node.opens > 0) {
7516
+ if (writable && node.writers === 0) this.onClosed?.(node.path);
7517
+ return;
7518
+ }
7519
+ if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7520
+ this.onClosed?.(node.path);
7338
7521
  }
7339
7522
  async flush(path) {
7340
7523
  for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
@@ -7480,7 +7663,7 @@ function vfsFile(fs, opts, nodes) {
7480
7663
  const access = opts.flags & O_ACCMODE;
7481
7664
  const readable = access !== O_WRONLY;
7482
7665
  const writable = access === O_WRONLY || access === O_RDWR;
7483
- 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);
7666
+ 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);
7484
7667
  let openError;
7485
7668
  const atOpen = (op) => {
7486
7669
  void node.serial(op).catch((err) => {
@@ -7543,7 +7726,7 @@ function vfsFile(fs, opts, nodes) {
7543
7726
  takeOpenError();
7544
7727
  await node.flush();
7545
7728
  } finally {
7546
- nodes?.closed(node);
7729
+ nodes?.closed(node, writable);
7547
7730
  }
7548
7731
  })
7549
7732
  });
@@ -8890,7 +9073,11 @@ function mediumSlot(lost, onLost) {
8890
9073
  }
8891
9074
 
8892
9075
  // src/mount/fsa.ts
8893
- var FSA_CAPABILITIES = { listingStats: true };
9076
+ var FSA_CAPABILITIES = {
9077
+ listingStats: true,
9078
+ ranges: true,
9079
+ sessions: true
9080
+ };
8894
9081
  var codeOf2 = (err) => err?.code;
8895
9082
  var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
8896
9083
  function attrOf(st) {
@@ -8930,30 +9117,20 @@ function kernelFsHandlers(fs) {
8930
9117
  if (st && flags.exclusive) throw fsError2("EEXIST", path);
8931
9118
  if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
8932
9119
  if (!st && !flags.create) throw fsError2("ENOENT", path);
8933
- const data = st && !flags.truncate ? await fs.readFileBuffer(path) : new Uint8Array(0);
8934
- handles.set(++nextFh, { path, data, length: data.length, dirty: !st || flags.truncate });
9120
+ if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
9121
+ handles.set(++nextFh, { path });
8935
9122
  return nextFh;
8936
9123
  },
8937
- async read(fh, offset, size) {
8938
- const h = handle(fh);
8939
- return h.data.slice(offset, Math.min(h.length, offset + size));
8940
- },
9124
+ read: async (fh, offset, size) => fs.pread(handle(fh).path, offset, size),
8941
9125
  async write(fh, offset, bytes) {
8942
9126
  const h = handle(fh);
8943
- const end = offset + bytes.length;
8944
- if (end > h.data.length) {
8945
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
8946
- grown.set(h.data.subarray(0, h.length));
8947
- h.data = grown;
8948
- }
8949
- h.data.set(bytes, offset);
8950
- h.length = Math.max(h.length, end);
8951
- h.dirty = true;
9127
+ h.session ??= fs.writeSession(h.path, false);
9128
+ await (await h.session).write(offset, bytes);
8952
9129
  },
8953
9130
  async release(fh) {
8954
9131
  const h = handle(fh);
8955
9132
  handles.delete(fh);
8956
- if (h.dirty) await fs.writeFile(h.path, h.data.slice(0, h.length));
9133
+ if (h.session !== void 0) await (await h.session).close();
8957
9134
  },
8958
9135
  mkdir: (path) => fs.mkdir(path),
8959
9136
  async rmdir(path) {
@@ -8969,6 +9146,13 @@ function kernelFsHandlers(fs) {
8969
9146
  symlink: (target, path) => fs.symlink(target, path),
8970
9147
  readlink: (path) => fs.readlink(path),
8971
9148
  async setattr(path, change) {
9149
+ if (change.size !== void 0) {
9150
+ const open = [...handles.values()].find(
9151
+ (h) => h.path === path && h.session !== void 0
9152
+ )?.session;
9153
+ if (open !== void 0) await (await open).truncate(change.size);
9154
+ else await fs.truncate?.(path, change.size);
9155
+ }
8972
9156
  if (change.mode !== void 0) await fs.chmod(path, change.mode);
8973
9157
  if (change.mtime !== void 0) {
8974
9158
  await fs.utimes(path, new Date(change.mtime), new Date(change.mtime));
@@ -9693,6 +9877,7 @@ function tmpfs() {
9693
9877
  const { dir, name: name2 } = parent(path);
9694
9878
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
9695
9879
  dir.children.set(name2, make("directory", 16877));
9880
+ dir.mtime = Date.now();
9696
9881
  },
9697
9882
  async rmdir(path) {
9698
9883
  const { dir, name: name2 } = parent(path);
@@ -9701,6 +9886,7 @@ function tmpfs() {
9701
9886
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
9702
9887
  if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
9703
9888
  dir.children.delete(name2);
9889
+ dir.mtime = Date.now();
9704
9890
  },
9705
9891
  async unlink(path) {
9706
9892
  const { dir, name: name2 } = parent(path);
@@ -9708,6 +9894,7 @@ function tmpfs() {
9708
9894
  if (!node) throw fsError2("ENOENT", path);
9709
9895
  if (node.kind === "directory") throw fsError2("EISDIR", path);
9710
9896
  dir.children.delete(name2);
9897
+ dir.mtime = Date.now();
9711
9898
  },
9712
9899
  async rename(from, to) {
9713
9900
  const a = `/${parts(from).join("/")}`;
@@ -9724,6 +9911,8 @@ function tmpfs() {
9724
9911
  }
9725
9912
  source.dir.children.delete(source.name);
9726
9913
  target.dir.children.set(target.name, node);
9914
+ source.dir.mtime = Date.now();
9915
+ target.dir.mtime = source.dir.mtime;
9727
9916
  },
9728
9917
  async symlink(target, path) {
9729
9918
  const { dir, name: name2 } = parent(path);
@@ -9731,6 +9920,7 @@ function tmpfs() {
9731
9920
  const node = make("symlink", 41471);
9732
9921
  node.target = target;
9733
9922
  dir.children.set(name2, node);
9923
+ dir.mtime = Date.now();
9734
9924
  },
9735
9925
  async readlink(path) {
9736
9926
  const node = find(path);
@@ -10029,7 +10219,7 @@ var Launcher = class {
10029
10219
  deciding = /* @__PURE__ */ new Map();
10030
10220
  identities = { asked: 0, timedOut: 0 };
10031
10221
  nodes;
10032
- held = /* @__PURE__ */ new Set();
10222
+ held = new HeldSet((path) => this.released(path));
10033
10223
  processMounts;
10034
10224
  booting = new AbortController();
10035
10225
  fstab = Promise.resolve([]);
@@ -10071,7 +10261,7 @@ var Launcher = class {
10071
10261
  });
10072
10262
  enableCdp(this.net, this.cdp);
10073
10263
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10074
- this.nodes = new VfsNodes(fs);
10264
+ this.nodes = new VfsNodes(fs, (path) => this.released(path));
10075
10265
  this.openFiles.add(this.nodes);
10076
10266
  this.fs = keepingOpen(fs, this.nodes);
10077
10267
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
@@ -10433,6 +10623,11 @@ var Launcher = class {
10433
10623
  umaskOf(pid) {
10434
10624
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
10435
10625
  }
10626
+ released(path) {
10627
+ const at = normalizePath(path);
10628
+ const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10629
+ if (!open) void this.mounts.commit(at).catch(() => void 0);
10630
+ }
10436
10631
  ignoredBy(pid, fork = false) {
10437
10632
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
10438
10633
  }