@ai-ecoverse/slicc-kernel 1.37.0 → 1.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -3394,13 +3394,193 @@ function withCommandDirs(fs, names) {
3394
3394
  };
3395
3395
  }
3396
3396
 
3397
+ // src/fs/unlinked.ts
3398
+ var UNLINKED_DIR = ".slicc-unlinked";
3399
+ function webLocks() {
3400
+ return globalThis.navigator?.locks;
3401
+ }
3402
+ var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3403
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3404
+ var lockName = (instance) => `slicc-unlinked:${instance}`;
3405
+ var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
3406
+ var PathGate = class {
3407
+ tails = /* @__PURE__ */ new Map();
3408
+ async acquire(path) {
3409
+ const prev = this.tails.get(path) ?? Promise.resolve();
3410
+ const { promise, resolve } = Promise.withResolvers();
3411
+ const tail = prev.then(() => promise);
3412
+ this.tails.set(path, tail);
3413
+ await prev;
3414
+ return () => {
3415
+ resolve();
3416
+ if (this.tails.get(path) === tail) this.tails.delete(path);
3417
+ };
3418
+ }
3419
+ async run(path, op) {
3420
+ const release = await this.acquire(path);
3421
+ try {
3422
+ return await op();
3423
+ } finally {
3424
+ release();
3425
+ }
3426
+ }
3427
+ };
3428
+ var UnlinkedKeeper = class {
3429
+ fs;
3430
+ holders;
3431
+ rootOf;
3432
+ locks;
3433
+ busy;
3434
+ waiting = /* @__PURE__ */ new Set();
3435
+ instance = crypto.randomUUID();
3436
+ gate = new PathGate();
3437
+ queue = Promise.resolve();
3438
+ count = 0;
3439
+ constructor(fs, holders, rootOf, locks, busy = () => false) {
3440
+ this.fs = fs;
3441
+ this.holders = holders;
3442
+ this.rootOf = rootOf;
3443
+ this.locks = locks;
3444
+ this.busy = busy;
3445
+ void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
3446
+ }
3447
+ serial(op) {
3448
+ const next = this.queue.then(op);
3449
+ this.queue = next.catch(() => void 0);
3450
+ return next;
3451
+ }
3452
+ wrap() {
3453
+ const fs = this.fs;
3454
+ const keeper = this;
3455
+ const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
3456
+ return {
3457
+ ...fs,
3458
+ async rm(path, options) {
3459
+ const target = fs.resolvePath("/", path);
3460
+ const pinned = await keeper.pin(target);
3461
+ if (pinned.moved.has(target)) pinned.commit();
3462
+ else await keeper.settle(pinned, () => fs.rm(path, options));
3463
+ },
3464
+ async rename(from, to) {
3465
+ const pinned = await keeper.pin(fs.resolvePath("/", to));
3466
+ await keeper.settle(pinned, () => fs.rename(from, to));
3467
+ },
3468
+ readdir: async (path) => visible(await fs.readdir(path)),
3469
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
3470
+ };
3471
+ }
3472
+ async settle(pinned, op) {
3473
+ try {
3474
+ await op();
3475
+ } catch (err) {
3476
+ await pinned.rollback();
3477
+ throw err;
3478
+ }
3479
+ pinned.commit();
3480
+ }
3481
+ async pin(target) {
3482
+ const held = /* @__PURE__ */ new Map();
3483
+ for (const holder of this.holders) {
3484
+ if (holder.owns(target)) continue;
3485
+ for (const path of holder.opens.keys()) {
3486
+ if (!within2(path, target) || holder.unlinked.has(path)) continue;
3487
+ held.set(path, [...held.get(path) ?? [], holder]);
3488
+ }
3489
+ }
3490
+ const paths = [...held.keys()].sort();
3491
+ const releases = [];
3492
+ for (const path of paths) releases.push(await this.gate.acquire(path));
3493
+ const hidden = /* @__PURE__ */ new Map();
3494
+ for (const path of paths) {
3495
+ const at = await this.hide(path);
3496
+ if (at !== void 0) hidden.set(path, at);
3497
+ }
3498
+ const done4 = () => {
3499
+ for (const release of releases) release();
3500
+ };
3501
+ return {
3502
+ moved: new Set(hidden.keys()),
3503
+ commit: () => {
3504
+ for (const [path, at] of hidden) {
3505
+ const holders = held.get(path);
3506
+ const file = this.keptFile(at, { refs: holders.length });
3507
+ for (const holder of holders) holder.unlinked.set(path, file);
3508
+ }
3509
+ done4();
3510
+ },
3511
+ rollback: async () => {
3512
+ for (const [path, at] of hidden) {
3513
+ await this.serial(async () => {
3514
+ await Promise.allSettled([this.fs.rename(at, path)]);
3515
+ await this.tidy(parentOf3(at));
3516
+ });
3517
+ }
3518
+ done4();
3519
+ }
3520
+ };
3521
+ }
3522
+ keptFile(hidden, entry) {
3523
+ return {
3524
+ hidden,
3525
+ release: () => {
3526
+ if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
3527
+ }
3528
+ };
3529
+ }
3530
+ hide(path) {
3531
+ return this.serial(async () => {
3532
+ const dir = dirOf(this.rootOf(path));
3533
+ const hidden = `${dir}/${this.instance}.${++this.count}`;
3534
+ try {
3535
+ await this.fs.mkdir(dir, { recursive: true });
3536
+ await this.fs.rename(path, hidden);
3537
+ return hidden;
3538
+ } catch {
3539
+ await this.tidy(dir);
3540
+ return void 0;
3541
+ }
3542
+ });
3543
+ }
3544
+ closed(path) {
3545
+ if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
3546
+ }
3547
+ async drop(hidden) {
3548
+ if (this.busy(hidden)) {
3549
+ this.waiting.add(hidden);
3550
+ return;
3551
+ }
3552
+ await this.fs.rm(hidden).catch(() => void 0);
3553
+ await this.tidy(parentOf3(hidden));
3554
+ }
3555
+ async tidy(dir) {
3556
+ const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
3557
+ if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
3558
+ }
3559
+ sweep(root) {
3560
+ const query = this.locks?.query?.bind(this.locks);
3561
+ if (!query) return Promise.resolve();
3562
+ return this.serial(async () => {
3563
+ const dir = dirOf(root);
3564
+ const names = await this.fs.readdir(dir).catch(() => void 0);
3565
+ if (!names) return;
3566
+ const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
3567
+ for (const name2 of names) {
3568
+ const owner = name2.slice(0, name2.lastIndexOf("."));
3569
+ if (owner === this.instance || held.has(lockName(owner))) continue;
3570
+ await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
3571
+ }
3572
+ await this.tidy(dir);
3573
+ });
3574
+ }
3575
+ };
3576
+
3397
3577
  // src/fs/watch.ts
3398
3578
  var WATCH_BATCH_LIMIT = 1e3;
3399
3579
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
3400
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3580
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3401
3581
  function touches(watcher, changed) {
3402
3582
  return watcher.paths.some(
3403
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
3583
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
3404
3584
  );
3405
3585
  }
3406
3586
  var FsWatchers = class {
@@ -3782,7 +3962,7 @@ function parseSize(text) {
3782
3962
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
3783
3963
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
3784
3964
  }
3785
- var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3965
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3786
3966
  var HeldSet = class extends Set {
3787
3967
  idle;
3788
3968
  constructor(idle) {
@@ -3795,18 +3975,47 @@ var HeldSet = class extends Set {
3795
3975
  }
3796
3976
  delete(holds) {
3797
3977
  const removed = super.delete(holds);
3798
- for (const path of holds.keys()) this.idle(path);
3978
+ for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
3979
+ for (const kept of holds.unlinked.values()) kept.release();
3980
+ holds.unlinked.clear();
3799
3981
  return removed;
3800
3982
  }
3801
3983
  };
3802
3984
  function heldUnder(held, target) {
3803
- for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
3985
+ for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
3804
3986
  return false;
3805
3987
  }
3806
3988
  var HeldPaths = class extends Map {
3807
3989
  revoked = /* @__PURE__ */ new Set();
3990
+ opens = /* @__PURE__ */ new Map();
3991
+ unlinked = /* @__PURE__ */ new Map();
3992
+ aliases = /* @__PURE__ */ new Map();
3993
+ owning;
3808
3994
  onIdle;
3809
- hold(path, on) {
3995
+ keyOf(path) {
3996
+ return this.aliases.get(path) ?? path;
3997
+ }
3998
+ keptAt(path) {
3999
+ return this.unlinked.get(this.keyOf(path));
4000
+ }
4001
+ holding(path) {
4002
+ return this.opens.has(this.keyOf(path));
4003
+ }
4004
+ hold(path, on, open = false, real) {
4005
+ if (open) {
4006
+ const key = real ?? this.keyOf(path);
4007
+ if (key !== path) this.aliases.set(path, key);
4008
+ const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
4009
+ if (count2 > 0) this.opens.set(key, count2);
4010
+ else {
4011
+ this.opens.delete(key);
4012
+ this.unlinked.get(key)?.release();
4013
+ this.unlinked.delete(key);
4014
+ for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
4015
+ this.onIdle?.(key);
4016
+ }
4017
+ return;
4018
+ }
3810
4019
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
3811
4020
  if (count > 0) this.set(path, count);
3812
4021
  else {
@@ -3815,23 +4024,40 @@ var HeldPaths = class extends Map {
3815
4024
  this.onIdle?.(path);
3816
4025
  }
3817
4026
  }
4027
+ async own(path, op) {
4028
+ this.owning = path;
4029
+ try {
4030
+ return await op();
4031
+ } finally {
4032
+ this.owning = void 0;
4033
+ }
4034
+ }
4035
+ owns(path) {
4036
+ return this.owning === path;
4037
+ }
3818
4038
  revoke(prefix) {
3819
- for (const path of this.keys()) if (within2(path, prefix)) this.revoked.add(path);
4039
+ for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
3820
4040
  }
3821
4041
  renamed(from, to) {
3822
4042
  for (const [path, count] of [...this]) {
3823
- if (!within2(path, from)) continue;
4043
+ if (!within3(path, from)) continue;
3824
4044
  const moved2 = to + path.slice(from.length);
3825
4045
  this.delete(path);
3826
4046
  this.set(moved2, (this.get(moved2) ?? 0) + count);
3827
4047
  if (this.revoked.delete(path)) this.revoked.add(moved2);
3828
4048
  }
4049
+ for (const [path, count] of [...this.opens]) {
4050
+ if (!within3(path, from)) continue;
4051
+ const moved2 = to + path.slice(from.length);
4052
+ this.opens.delete(path);
4053
+ this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
4054
+ }
3829
4055
  }
3830
4056
  isRevoked(path) {
3831
4057
  return this.revoked.has(path);
3832
4058
  }
3833
4059
  };
3834
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4060
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3835
4061
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
3836
4062
  function statOf(attr, path, mount) {
3837
4063
  const mtime = new Date(attr.mtime);
@@ -3920,7 +4146,7 @@ var MountTable = class {
3920
4146
  locate(path) {
3921
4147
  let best;
3922
4148
  for (const mount of this.mounts.values()) {
3923
- if (within2(path, mount.target) && (!best || mount.target.length > best.target.length))
4149
+ if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
3924
4150
  best = mount;
3925
4151
  }
3926
4152
  if (!best) return void 0;
@@ -4059,7 +4285,7 @@ var MountTable = class {
4059
4285
  async commitUnder(prefix) {
4060
4286
  const at = normalizePath(prefix);
4061
4287
  await Promise.all(
4062
- [...this.sessions.keys()].filter((p) => within2(p, at)).map((p) => this.commit(p))
4288
+ [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
4063
4289
  );
4064
4290
  }
4065
4291
  async commitOverlapping(real, offset, length2) {
@@ -4093,10 +4319,10 @@ var MountTable = class {
4093
4319
  this.deps.changed?.(...full);
4094
4320
  }
4095
4321
  forget(mount, path) {
4096
- for (const key of [...mount.attrs.keys()]) if (within2(key, path)) mount.attrs.delete(key);
4322
+ for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
4097
4323
  mount.lists.delete(path);
4098
- mount.lists.delete(parentOf4(path));
4099
- for (const key of [...mount.lists.keys()]) if (within2(key, path)) mount.lists.delete(key);
4324
+ mount.lists.delete(parentOf5(path));
4325
+ for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
4100
4326
  }
4101
4327
  async getattr(mount, path, rel2) {
4102
4328
  if (mount.conn.failure) throw mount.conn.failure;
@@ -4161,7 +4387,7 @@ var MountFs = class {
4161
4387
  if (attr.kind !== "symlink") return real;
4162
4388
  if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
4163
4389
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
4164
- return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
4390
+ return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
4165
4391
  }
4166
4392
  async open(mount, rel2, write) {
4167
4393
  return await mount.conn.call({
@@ -4675,6 +4901,8 @@ async function ranged(fs, path, req) {
4675
4901
  else await ops.truncate(path, req.length ?? 0);
4676
4902
  return done;
4677
4903
  }
4904
+ var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
4905
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
4678
4906
  async function dispatchSyncFs(req) {
4679
4907
  const entry = resolveSyncFsToken(req.token);
4680
4908
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -4683,11 +4911,22 @@ async function dispatchSyncFs(req) {
4683
4911
  const path = fs.resolvePath(cwd, req.path);
4684
4912
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
4685
4913
  if (req.op === "hold") {
4686
- entry.hold?.(path, req.mode === 1);
4914
+ const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
4915
+ const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
4916
+ entry.hold?.(path, on, open, real);
4687
4917
  return done;
4688
4918
  }
4689
4919
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
4690
- return await run(entry, path, req);
4920
+ if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
4921
+ const serve = async () => {
4922
+ const kept = entry.unlinked?.(path);
4923
+ return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
4924
+ };
4925
+ return await (entry.gate ? entry.gate(path, serve) : serve());
4926
+ }
4927
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
4928
+ const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
4929
+ return await entry.own(target, () => run(entry, path, req));
4691
4930
  } catch (err) {
4692
4931
  return toErrno(err);
4693
4932
  }
@@ -6266,7 +6505,7 @@ function parsePrefix(text) {
6266
6505
  if (length2 < 0 || length2 > max) return void 0;
6267
6506
  return { ...parsed, length: length2 };
6268
6507
  }
6269
- function within3(prefix, addr) {
6508
+ function within4(prefix, addr) {
6270
6509
  if (prefix.v6 !== addr.v6) return false;
6271
6510
  const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
6272
6511
  return addr.bits >> shift === prefix.bits >> shift;
@@ -6306,9 +6545,9 @@ var Routes = class {
6306
6545
  if (!addr) return "unreachable";
6307
6546
  if (addr.v6 && addr.bits === 1n) return "loopback";
6308
6547
  if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
6309
- if (RESERVED.some((p) => within3(p, addr))) return "reserved";
6548
+ if (RESERVED.some((p) => within4(p, addr))) return "reserved";
6310
6549
  if (addr.v6 && !this.ipv6) return "unreachable";
6311
- if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
6550
+ if (this.exit || this.table.some((p) => within4(p, addr))) return "uplink";
6312
6551
  return "unreachable";
6313
6552
  }
6314
6553
  };
@@ -7277,7 +7516,7 @@ function isMissing(err) {
7277
7516
  if (err?.code === "ENOENT") return true;
7278
7517
  return err instanceof Error && err.message.startsWith("ENOENT");
7279
7518
  }
7280
- function within4(path, root) {
7519
+ function within5(path, root) {
7281
7520
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
7282
7521
  }
7283
7522
  var VfsNode = class {
@@ -7482,7 +7721,7 @@ var VfsNodes = class {
7482
7721
  return run2;
7483
7722
  }
7484
7723
  async entryKey(path) {
7485
- const parent = parentOf5(path);
7724
+ const parent = parentOf6(path);
7486
7725
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
7487
7726
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
7488
7727
  }
@@ -7530,12 +7769,12 @@ var VfsNodes = class {
7530
7769
  return node;
7531
7770
  }
7532
7771
  holds(prefix) {
7533
- for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7772
+ for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
7534
7773
  return false;
7535
7774
  }
7536
7775
  writes(prefix) {
7537
7776
  for (const [path, node] of this.byPath)
7538
- if (node.writers > 0 && within4(path, prefix)) return true;
7777
+ if (node.writers > 0 && within5(path, prefix)) return true;
7539
7778
  return false;
7540
7779
  }
7541
7780
  closed(node, writable = false) {
@@ -7549,24 +7788,24 @@ var VfsNodes = class {
7549
7788
  this.onClosed?.(node.path);
7550
7789
  }
7551
7790
  async flush(path) {
7552
- for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
7791
+ for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
7553
7792
  }
7554
7793
  async unlinking(path) {
7555
7794
  for (const [held, node] of [...this.byPath]) {
7556
- if (!within4(held, path)) continue;
7795
+ if (!within5(held, path)) continue;
7557
7796
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
7558
7797
  }
7559
7798
  }
7560
7799
  revoke(prefix) {
7561
7800
  for (const [path, node] of this.byPath) {
7562
- if (!within4(path, prefix)) continue;
7801
+ if (!within5(path, prefix)) continue;
7563
7802
  node.revoke();
7564
7803
  this.byPath.delete(path);
7565
7804
  }
7566
7805
  }
7567
7806
  unlinked(path) {
7568
7807
  for (const [held, node] of [...this.byPath]) {
7569
- if (!within4(held, path)) continue;
7808
+ if (!within5(held, path)) continue;
7570
7809
  node.orphaned = true;
7571
7810
  this.byPath.delete(held);
7572
7811
  }
@@ -7575,8 +7814,8 @@ var VfsNodes = class {
7575
7814
  if (from === to) return;
7576
7815
  const moved2 = [];
7577
7816
  for (const [p, node] of this.byPath) {
7578
- if (within4(p, from)) moved2.push(node);
7579
- else if (within4(p, to)) {
7817
+ if (within5(p, from)) moved2.push(node);
7818
+ else if (within5(p, to)) {
7580
7819
  node.orphaned = true;
7581
7820
  this.byPath.delete(p);
7582
7821
  }
@@ -7599,7 +7838,7 @@ async function present(fs, path, follow = false) {
7599
7838
  }
7600
7839
  }
7601
7840
  var MAX_LINKS5 = 40;
7602
- function parentOf5(path) {
7841
+ function parentOf6(path) {
7603
7842
  return path.slice(0, path.lastIndexOf("/")) || "/";
7604
7843
  }
7605
7844
  async function linkOf(fs, path) {
@@ -7612,7 +7851,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
7612
7851
  const part = queue.shift();
7613
7852
  if (part === ".") continue;
7614
7853
  if (part === "..") {
7615
- at = parentOf5(at);
7854
+ at = parentOf6(at);
7616
7855
  continue;
7617
7856
  }
7618
7857
  const next = at === "/" ? `/${part}` : `${at}/${part}`;
@@ -7636,7 +7875,7 @@ function joinName(dir, name2) {
7636
7875
  }
7637
7876
  async function realTarget(fs, path) {
7638
7877
  const budget = { hops: 0 };
7639
- let dir = await realDir(fs, parentOf5(path), budget);
7878
+ let dir = await realDir(fs, parentOf6(path), budget);
7640
7879
  let name2 = path.slice(path.lastIndexOf("/") + 1);
7641
7880
  for (; ; ) {
7642
7881
  const link2 = await linkOf(fs, joinName(dir, name2));
@@ -7644,13 +7883,13 @@ async function realTarget(fs, path) {
7644
7883
  if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
7645
7884
  budget.hops++;
7646
7885
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
7647
- dir = await realDir(fs, parentOf5(target), budget);
7886
+ dir = await realDir(fs, parentOf6(target), budget);
7648
7887
  name2 = target.slice(target.lastIndexOf("/") + 1);
7649
7888
  }
7650
7889
  return joinName(dir, name2);
7651
7890
  }
7652
7891
  async function targetDir(fs, path) {
7653
- return parentOf5(await realTarget(fs, path).catch(() => path));
7892
+ return parentOf6(await realTarget(fs, path).catch(() => path));
7654
7893
  }
7655
7894
  async function refuseReadonly(fs, path, flags, opts = {}) {
7656
7895
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -8039,7 +8278,7 @@ var WasmProcess = class {
8039
8278
  const fd = this.fds.install(heldFile(), 3);
8040
8279
  try {
8041
8280
  const nodes = this.nodes;
8042
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
8281
+ const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path));
8043
8282
  const open = { ...req, path };
8044
8283
  if (req.exclusive) {
8045
8284
  await nodes.createExclusive(path);
@@ -8195,6 +8434,11 @@ var WasmProcess = class {
8195
8434
  return { ok: true, kind: "json", json: await file.stat() };
8196
8435
  }
8197
8436
  }
8437
+ keptPath;
8438
+ async keptOr(path) {
8439
+ const kept = this.keptPath?.(path);
8440
+ return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
8441
+ }
8198
8442
  async promote(req) {
8199
8443
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8200
8444
  if (req.share !== void 0) {
@@ -8202,7 +8446,7 @@ var WasmProcess = class {
8202
8446
  return;
8203
8447
  }
8204
8448
  if (req.path === void 0) throw new KernelError("EINVAL");
8205
- const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
8449
+ const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
8206
8450
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8207
8451
  const file = vfsFile(
8208
8452
  this.options.fs,
@@ -8561,6 +8805,19 @@ function inherited(opts) {
8561
8805
  ...opts.umask !== void 0 ? { umask: opts.umask } : {}
8562
8806
  };
8563
8807
  }
8808
+ function holdingToken(opts, holds) {
8809
+ return mintSyncFsToken({
8810
+ fs: opts.fs,
8811
+ cwd: opts.cwd,
8812
+ ...opts.statfs ? { statfs: opts.statfs } : {},
8813
+ hold: (path, on, open, real) => holds.hold(path, on, open, real),
8814
+ unlinked: (path) => holds.keptAt(path),
8815
+ gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
8816
+ own: (path, op) => holds.own(path, op),
8817
+ revoked: (path) => holds.isRevoked(path),
8818
+ renamed: (from, to) => holds.renamed(from, to)
8819
+ });
8820
+ }
8564
8821
  function spawnWasmProcess(opts) {
8565
8822
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
8566
8823
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -8605,14 +8862,8 @@ function spawnWasmProcess(opts) {
8605
8862
  });
8606
8863
  const holds = new HeldPaths();
8607
8864
  opts.held?.add(holds);
8608
- const token = mintSyncFsToken({
8609
- fs: opts.fs,
8610
- cwd: opts.cwd,
8611
- ...opts.statfs ? { statfs: opts.statfs } : {},
8612
- hold: (path, on) => holds.hold(path, on),
8613
- revoked: (path) => holds.isRevoked(path),
8614
- renamed: (from, to) => holds.renamed(from, to)
8615
- });
8865
+ const token = holdingToken(opts, holds);
8866
+ process.keptPath = (path) => holds.keptAt(path)?.hidden;
8616
8867
  const worker = opts.createWorker();
8617
8868
  const dying = new Dying();
8618
8869
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
@@ -10261,6 +10512,7 @@ var Launcher = class {
10261
10512
  deciding = /* @__PURE__ */ new Map();
10262
10513
  identities = { asked: 0, timedOut: 0 };
10263
10514
  nodes;
10515
+ unlinked;
10264
10516
  held = new HeldSet((path) => this.released(path));
10265
10517
  processMounts;
10266
10518
  booting = new AbortController();
@@ -10305,7 +10557,15 @@ var Launcher = class {
10305
10557
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10306
10558
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
10307
10559
  this.openFiles.add(this.nodes);
10308
- this.fs = keepingOpen(fs, this.nodes);
10560
+ this.unlinked = new UnlinkedKeeper(
10561
+ fs,
10562
+ this.held,
10563
+ (path) => this.mountRoot(path),
10564
+ webLocks(),
10565
+ (path) => [...this.openFiles].some((nodes) => nodes.holds(path))
10566
+ );
10567
+ this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
10568
+ void this.unlinked.sweep("/");
10309
10569
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10310
10570
  this.catalog = void 0;
10311
10571
  this.binfmts = void 0;
@@ -10515,6 +10775,7 @@ var Launcher = class {
10515
10775
  openFiles: this.openFiles,
10516
10776
  nodes: this.nodes,
10517
10777
  held: this.held,
10778
+ unlinkGate: this.unlinked.gate,
10518
10779
  statfs: (path) => this.mounts.statfs(path),
10519
10780
  mounts: () => this.mounts.list(),
10520
10781
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -10672,6 +10933,7 @@ var Launcher = class {
10672
10933
  }
10673
10934
  released(path) {
10674
10935
  const at = normalizePath(path);
10936
+ this.unlinked.closed(at);
10675
10937
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10676
10938
  if (!open) void this.mounts.commit(at).catch(() => void 0);
10677
10939
  }
@@ -10767,10 +11029,23 @@ var Launcher = class {
10767
11029
  }
10768
11030
  };
10769
11031
  }
10770
- mount(spec) {
10771
- if (spec.type !== "fsa" || spec.source.startsWith("fsa:"))
10772
- return this.mounts.mount(spec, this.fs);
10773
- return this.mounts.mount({ ...spec, source: `fsa:${crypto.randomUUID()}` }, this.fs);
11032
+ async mount(spec) {
11033
+ const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
11034
+ const entry = await this.mounts.mount(
11035
+ fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
11036
+ this.fs
11037
+ );
11038
+ void this.unlinked.sweep(entry.target);
11039
+ return entry;
11040
+ }
11041
+ mountRoot(path) {
11042
+ let root = "/";
11043
+ for (const { target } of this.mounts.list()) {
11044
+ if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
11045
+ root = target;
11046
+ }
11047
+ }
11048
+ return root;
10774
11049
  }
10775
11050
  async insert(target, handle, source) {
10776
11051
  const at = normalizePath(target);
@@ -10784,6 +11059,7 @@ var Launcher = class {
10784
11059
  await this.media.put(slot.id, handle);
10785
11060
  slot.medium.insert(handle);
10786
11061
  slot.served.invalidate(true);
11062
+ void this.unlinked.sweep(at);
10787
11063
  }
10788
11064
  umount(target, detach = false) {
10789
11065
  const at = normalizePath(target);