@poe-platform/safe-fs 0.1.804 → 0.1.806

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +5 -5
  2. package/dist/safe-fs/bridge/filesystem.d.ts +1 -0
  3. package/dist/safe-fs/bridge/filesystem.js +3 -0
  4. package/dist/safe-fs/bridge/index.d.ts +1 -0
  5. package/dist/safe-fs/bridge/index.js +1 -0
  6. package/dist/safe-fs/config/registry.d.ts +2 -0
  7. package/dist/safe-fs/config/registry.js +15 -0
  8. package/dist/safe-fs/config.node.js +3 -14
  9. package/dist/safe-fs/config.portable.d.ts +3 -0
  10. package/dist/safe-fs/config.portable.js +9 -0
  11. package/dist/safe-fs/contracts/errors.d.ts +1 -0
  12. package/dist/safe-fs/contracts/errors.js +12 -0
  13. package/dist/safe-fs/contracts/filesystem.d.ts +12 -0
  14. package/dist/safe-fs/contracts/index.d.ts +1 -0
  15. package/dist/safe-fs/contracts/index.js +1 -0
  16. package/dist/safe-fs/contracts/io.js +1 -1
  17. package/dist/safe-fs/core.d.ts +5 -14
  18. package/dist/safe-fs/core.js +4 -11
  19. package/dist/safe-fs/fs/devices/index.d.ts +8 -2
  20. package/dist/safe-fs/fs/devices/index.js +79 -23
  21. package/dist/safe-fs/fs/devices/path.js +1 -1
  22. package/dist/safe-fs/fs/memory/atomic-view.d.ts +3 -0
  23. package/dist/safe-fs/fs/memory/atomic-view.js +5 -0
  24. package/dist/safe-fs/fs/memory/index.d.ts +7 -4
  25. package/dist/safe-fs/fs/memory/index.js +211 -160
  26. package/dist/safe-fs/fs/memory/ledger.d.ts +0 -4
  27. package/dist/safe-fs/fs/memory/ledger.js +0 -15
  28. package/dist/safe-fs/fs/memory/limits.js +3 -0
  29. package/dist/safe-fs/fs/mount/comparison.js +3 -3
  30. package/dist/safe-fs/fs/mount/identity.d.ts +4 -0
  31. package/dist/safe-fs/fs/mount/identity.js +8 -0
  32. package/dist/safe-fs/fs/mount/index.d.ts +1 -0
  33. package/dist/safe-fs/fs/mount/index.js +31 -0
  34. package/dist/safe-fs/fs/object-publication/index.d.ts +3 -0
  35. package/dist/safe-fs/fs/object-publication/index.js +3 -2
  36. package/dist/safe-fs/fs/quota/index.js +1 -1
  37. package/dist/safe-fs/fs/readonly/index.js +1 -1
  38. package/dist/safe-fs/fs/s3/filesystem.js +1 -1
  39. package/dist/safe-fs/fs/s3/http/transport.js +1 -1
  40. package/dist/safe-fs/fs/s3/http/xml.js +2 -2
  41. package/dist/safe-fs/fs/scoped.js +32 -2
  42. package/dist/safe-fs/fs/webdav/webdav.js +1 -1
  43. package/dist/safe-fs/index.d.ts +2 -0
  44. package/dist/safe-fs/index.js +2 -0
  45. package/dist/safe-fs/node/filesystem.d.ts +2 -1
  46. package/dist/safe-fs/node/filesystem.js +1 -5
  47. package/dist/safe-fs/runtime-core.d.ts +27 -0
  48. package/dist/safe-fs/runtime-core.js +57 -0
  49. package/package.json +9 -1
@@ -9,7 +9,7 @@ import { directoryAncestryPaths, runStagingGuard, snapshotDirectoryAncestry, sna
9
9
  import { compareIdentity } from "../mount/identity.js";
10
10
  import { createStagingCleanup, snapshotStagingCreation } from "../staging-cleanup.js";
11
11
  import { resolveMissingTarget } from "./missing-target.js";
12
- import { registerMemoryAtomicView } from "./atomic-view.js";
12
+ import { registerLazyMemoryAtomicView } from "./atomic-view.js";
13
13
  import { snapshotConditionalChmod } from "../conditional-chmod.js";
14
14
  import { MemoryAllocation, MemoryLedger } from "./ledger.js";
15
15
  import { normalizeMemoryFileSystemLimits } from "./limits.js";
@@ -50,14 +50,17 @@ const DUMMY_POOL_LEDGER = new MemoryLedger(normalizeMemoryFileSystemLimits({}));
50
50
  const DUMMY_POOL_ALLOCATION = new MemoryAllocation(EMPTY_ALLOC_BYTES, DUMMY_POOL_LEDGER);
51
51
  const DUMMY_POOL_FILE_NODE = new MemoryFileNode(0, 0, null, 0, DUMMY_POOL_ALLOCATION, DUMMY_POOL_ALLOCATION.data);
52
52
  DUMMY_POOL_FILE_NODE.atimeMs = DUMMY_POOL_FILE_NODE.mtimeMs = DUMMY_POOL_FILE_NODE.ctimeMs = DUMMY_POOL_FILE_NODE.birthtimeMs = 1700000000000;
53
+ const EMPTY_DIR_TABLE = new Int16Array(0);
54
+ const EMPTY_DIR_KEYS = [];
55
+ const EMPTY_DIR_VALS = [];
53
56
  class FastDirectoryEntriesMap {
54
57
  // Keep speculative slots local so a failed write cannot pin a tenant globally.
55
58
  _missKey = "";
56
59
  _missSlot = -1;
57
- _table = new Int16Array(128).fill(-1);
58
- _mask = 127;
59
- _keys = new Array(64).fill("");
60
- _vals = new Array(64).fill(DUMMY_POOL_FILE_NODE);
60
+ _table = EMPTY_DIR_TABLE;
61
+ _mask = 0;
62
+ _keys = EMPTY_DIR_KEYS;
63
+ _vals = EMPTY_DIR_VALS;
61
64
  _next = 0;
62
65
  size = 0;
63
66
  [Symbol.toStringTag] = "Map";
@@ -90,6 +93,11 @@ class FastDirectoryEntriesMap {
90
93
  getForWriteWithHash(k, h) {
91
94
  let slot = h & this._mask;
92
95
  if (this.size === 0) {
96
+ if (this._keys.length === 0) {
97
+ this._missKey = "";
98
+ this._missSlot = -1;
99
+ return undefined;
100
+ }
93
101
  this._missKey = k;
94
102
  this._missSlot = slot;
95
103
  return undefined;
@@ -205,7 +213,7 @@ class FastDirectoryEntriesMap {
205
213
  return this;
206
214
  }
207
215
  if (this._next >= this._keys.length) {
208
- this._rebuild(this.size * 2 <= this._keys.length ? this._keys.length : this._keys.length * 2);
216
+ this._rebuild(this._keys.length === 0 ? 8 : (this.size * 2 <= this._keys.length ? this._keys.length : this._keys.length * 2));
209
217
  }
210
218
  let slot = this._hash(k);
211
219
  const mask = this._mask;
@@ -316,13 +324,6 @@ class FastDirectoryEntriesMap {
316
324
  }
317
325
  const SHARED_POOL_CAPACITY = 256;
318
326
  const SHARED_DIR_POOL_CAPACITY = 64;
319
- const sharedAllocationPool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
320
- let sharedAllocationPoolLen = 0;
321
- const SHARED_LARGE_POOL_CAPACITY = 8;
322
- const sharedLargeAllocationPool = new Array(SHARED_LARGE_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
323
- let sharedLargeAllocationPoolLen = 0;
324
- const sharedMediumAllocationPool = new Array(SHARED_LARGE_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
325
- let sharedMediumAllocationPoolLen = 0;
326
327
  const sharedFileNodePool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_FILE_NODE);
327
328
  let sharedFileNodePoolLen = 0;
328
329
  const sharedDirectoryNodePool = new Array(SHARED_DIR_POOL_CAPACITY);
@@ -343,27 +344,9 @@ function collectRemovedChild(child, name) {
343
344
  sharedRemovedKeyEntries++;
344
345
  }
345
346
  function replenishSharedMemoryPools() {
346
- while (sharedAllocationPoolLen < 112) {
347
- DUMMY_POOL_LEDGER.reserve(64, 0, "init", "/");
348
- const alloc = new MemoryAllocation(new Uint8Array(64), DUMMY_POOL_LEDGER);
349
- alloc.release();
350
- sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
351
- }
352
347
  while (sharedFileNodePoolLen < 112) {
353
348
  sharedFileNodePool[sharedFileNodePoolLen++] = new MemoryFileNode(0, 0, 0, 0, DUMMY_POOL_ALLOCATION, undefined);
354
349
  }
355
- while (sharedLargeAllocationPoolLen < 4) {
356
- DUMMY_POOL_LEDGER.reserve(65536, 0, "init", "/");
357
- const alloc = new MemoryAllocation(new Uint8Array(65536), DUMMY_POOL_LEDGER);
358
- alloc.release();
359
- sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = alloc;
360
- }
361
- while (sharedMediumAllocationPoolLen < 4) {
362
- DUMMY_POOL_LEDGER.reserve(16384, 0, "init", "/");
363
- const alloc = new MemoryAllocation(new Uint8Array(16384), DUMMY_POOL_LEDGER);
364
- alloc.release();
365
- sharedMediumAllocationPool[sharedMediumAllocationPoolLen++] = alloc;
366
- }
367
350
  while (sharedDirectoryNodePoolLen < 16) {
368
351
  sharedDirectoryNodePool[sharedDirectoryNodePoolLen++] = new MemoryDirectoryNode(0, 0, 0);
369
352
  }
@@ -395,7 +378,6 @@ Object.assign(MemoryDirectoryNode.prototype, {
395
378
  const DUMMY_POOL_DIR_NODE = new MemoryDirectoryNode(0, 0, null);
396
379
  DUMMY_POOL_DIR_NODE.atimeMs = DUMMY_POOL_DIR_NODE.mtimeMs = DUMMY_POOL_DIR_NODE.ctimeMs = DUMMY_POOL_DIR_NODE.birthtimeMs = 1700000000000;
397
380
  class MemoryCache {
398
- allocations = [];
399
381
  files = [];
400
382
  directories = [];
401
383
  removedScratch = [];
@@ -415,7 +397,6 @@ class MemoryCache {
415
397
  const typeModes = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
416
398
  const emptyResolveOptions = Object.freeze({});
417
399
  const noFollowResolveOptions = Object.freeze({ followFinal: false });
418
- const sharedMkdirResolveOptions = { createDirectories: 0o777 };
419
400
  const sharedAllowMissingNoFollowOptions = Object.freeze({ allowMissing: true, followFinal: false });
420
401
  const sharedFastLocation = { node: undefined, parent: DUMMY_POOL_DIR_NODE, name: "", path: "" };
421
402
  const resolvedVoid = Promise.resolve();
@@ -590,32 +571,48 @@ class MemoryReadStream {
590
571
  return STREAM_RESOLVED_DONE;
591
572
  }
592
573
  }
574
+ const SHARED_STOCK_MEMORY_CAPABILITIES = Object.freeze({
575
+ read: true, stat: true, readdir: true, realpath: true, access: true, open: true,
576
+ write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
577
+ mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
578
+ rename: true, atomicRenameNoReplace: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
579
+ streamingAppend: true, randomAccessWrite: true,
580
+ readOnly: false,
581
+ symlinks: true, conditionalChmod: true,
582
+ hardlinks: true,
583
+ permissions: true,
584
+ timestamps: true,
585
+ atomicRename: true,
586
+ atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
587
+ synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
588
+ atomicDirectoryMetadata: true,
589
+ streamingRead: true,
590
+ retainedRead: true,
591
+ retainedResize: true,
592
+ streamingWrite: true,
593
+ descriptorWriteStream: true,
594
+ });
595
+ function customMemoryCapabilities(filesystem) {
596
+ return Object.freeze({
597
+ ...SHARED_STOCK_MEMORY_CAPABILITIES,
598
+ get retainedResize() { return stockRetainedResize(filesystem); },
599
+ get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
600
+ });
601
+ }
593
602
  export class MemoryFileSystem {
594
603
  capabilitiesFor;
595
- capabilities = ((filesystem) => {
596
- return Object.freeze({
597
- read: true, stat: true, readdir: true, realpath: true, access: true, open: true,
598
- write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
599
- mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
600
- rename: true, atomicRenameNoReplace: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
601
- streamingAppend: true, randomAccessWrite: true,
602
- readOnly: false,
603
- symlinks: true, conditionalChmod: true,
604
- hardlinks: true,
605
- permissions: true,
606
- timestamps: true,
607
- atomicRename: true,
608
- atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
609
- synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
610
- atomicDirectoryMetadata: true,
611
- streamingRead: true,
612
- retainedRead: true,
613
- get retainedResize() { return stockRetainedResize(filesystem); },
614
- streamingWrite: true,
615
- get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
616
- });
617
- })(this);
618
- identityScope = Symbol();
604
+ #customCapabilities;
605
+ #identityScopeSym;
606
+ get identityScope() {
607
+ return (this.#identityScopeSym ??= Symbol());
608
+ }
609
+ get capabilities() {
610
+ if (!this.#customCapabilities) {
611
+ this.#customCapabilities = customMemoryCapabilities(this);
612
+ this._owner.capabilities = this.#customCapabilities;
613
+ }
614
+ return this.#customCapabilities;
615
+ }
619
616
  nextInode = 1;
620
617
  ledger;
621
618
  _cache;
@@ -624,26 +621,34 @@ export class MemoryFileSystem {
624
621
  root;
625
622
  totalBytes = 0;
626
623
  symlinkCount = 0;
627
- constructor(options = {}) {
628
- this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
629
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes) {
630
- replenishSharedMemoryPools();
624
+ #atomicView;
625
+ _getAtomicView() {
626
+ const owner = ownedStores.get(this);
627
+ if (!owner ||
628
+ Object.getPrototypeOf(this) !== MemoryFileSystem.prototype ||
629
+ this.root !== owner.root ||
630
+ this.ledger !== owner.ledger ||
631
+ this.capabilities !== owner.capabilities) {
632
+ return undefined;
631
633
  }
632
- const cache = new MemoryCache();
633
- this._cache = cache;
634
- memoryCaches.set(this.ledger, cache);
635
- this.ledger.reserve(0, 1, "mkdir", "/");
636
- this.root = this.directory(0o755);
637
- const root = this.root;
638
- const owner = {
639
- root,
640
- ledger: this.ledger,
641
- capabilities: this.capabilities,
642
- intact: () => this.root === root,
643
- };
644
- this._owner = owner;
645
- ownedStores.set(this, owner);
646
- registerMemoryAtomicView(this, {
634
+ const proto = MemoryFileSystem.prototype;
635
+ for (let i = 0; i < memoryImplementationKeys.length; i++) {
636
+ const name = memoryImplementationKeys[i];
637
+ const expected = memoryImplementationDescriptors[i];
638
+ if (name === "capabilities")
639
+ continue;
640
+ if (Object.prototype.hasOwnProperty.call(this, name)) {
641
+ const actual = Object.getOwnPropertyDescriptor(this, name);
642
+ if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
643
+ return undefined;
644
+ }
645
+ else if (expected.get !== undefined || expected.set !== undefined || proto[name] !== expected.value) {
646
+ const actual = Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
647
+ if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
648
+ return undefined;
649
+ }
650
+ }
651
+ return this.#atomicView ??= {
647
652
  stat: (path) => {
648
653
  this.validatePath(path, "overlayAtomicView");
649
654
  let node = this.root;
@@ -665,30 +670,28 @@ export class MemoryFileSystem {
665
670
  this.fail("ENOTDIR", "overlayAtomicView", path);
666
671
  return [...node.entries.keys()];
667
672
  },
668
- }, () => {
669
- if (Object.getPrototypeOf(this) !== MemoryFileSystem.prototype ||
670
- this.root !== root ||
671
- this.ledger !== ownedStores.get(this)?.ledger ||
672
- this.capabilities !== ownedStores.get(this)?.capabilities) {
673
- return false;
674
- }
675
- const proto = MemoryFileSystem.prototype;
676
- for (let i = 0; i < memoryImplementationKeys.length; i++) {
677
- const name = memoryImplementationKeys[i];
678
- const expected = memoryImplementationDescriptors[i];
679
- if (Object.prototype.hasOwnProperty.call(this, name)) {
680
- const actual = Object.getOwnPropertyDescriptor(this, name);
681
- if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
682
- return false;
683
- }
684
- else if (expected.get !== undefined || expected.set !== undefined || proto[name] !== expected.value) {
685
- const actual = Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
686
- if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
687
- return false;
688
- }
689
- }
690
- return true;
691
- });
673
+ };
674
+ }
675
+ constructor(options = {}) {
676
+ this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
677
+ if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes) {
678
+ replenishSharedMemoryPools();
679
+ }
680
+ const cache = new MemoryCache();
681
+ this._cache = cache;
682
+ memoryCaches.set(this.ledger, cache);
683
+ this.ledger.reserve(0, 1, "mkdir", "/");
684
+ this.root = this.directory(0o755);
685
+ const root = this.root;
686
+ const owner = {
687
+ root,
688
+ ledger: this.ledger,
689
+ capabilities: SHARED_STOCK_MEMORY_CAPABILITIES,
690
+ intact: () => this.root === root,
691
+ };
692
+ this._owner = owner;
693
+ ownedStores.set(this, owner);
694
+ registerLazyMemoryAtomicView(this, memoryImplementation._getAtomicView.value);
692
695
  if (this.compareEntry === memoryImplementation.compareEntry?.value) {
693
696
  registeredAuthorities.add(this);
694
697
  registerEntryAuthority(this, compareOwnedMemory);
@@ -882,21 +885,6 @@ export class MemoryFileSystem {
882
885
  node.sourceRef = undefined;
883
886
  node.allocation = DUMMY_POOL_ALLOCATION;
884
887
  alloc.release();
885
- if (alloc.isReleased64()) {
886
- alloc.detachLedger(DUMMY_POOL_LEDGER);
887
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen < SHARED_POOL_CAPACITY) {
888
- sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
889
- }
890
- else if (cache.allocations.length < 128) {
891
- cache.allocations.push(alloc);
892
- }
893
- }
894
- else if (alloc.isReleased65536()) {
895
- alloc.detachLedger(DUMMY_POOL_LEDGER);
896
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen < SHARED_LARGE_POOL_CAPACITY) {
897
- sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = alloc;
898
- }
899
- }
900
888
  if (sharedFileNodePoolLen < SHARED_POOL_CAPACITY) {
901
889
  sharedFileNodePool[sharedFileNodePoolLen++] = node;
902
890
  }
@@ -936,18 +924,6 @@ export class MemoryFileSystem {
936
924
  node.view = length === allocation.data.byteLength ? allocation.data : undefined;
937
925
  node.allocation = allocation;
938
926
  previous.release();
939
- if (previous.isReleased64()) {
940
- previous.detachLedger(DUMMY_POOL_LEDGER);
941
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen < SHARED_POOL_CAPACITY) {
942
- sharedAllocationPool[sharedAllocationPoolLen++] = previous;
943
- }
944
- }
945
- else if (previous.isReleased65536()) {
946
- previous.detachLedger(DUMMY_POOL_LEDGER);
947
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen < SHARED_LARGE_POOL_CAPACITY) {
948
- sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = previous;
949
- }
950
- }
951
927
  }
952
928
  admitSize(node, length, syscall, path) {
953
929
  this.ledger.fileSize(length, syscall, path);
@@ -1325,36 +1301,7 @@ export class MemoryFileSystem {
1325
1301
  if (length === 0) {
1326
1302
  return DUMMY_POOL_ALLOCATION;
1327
1303
  }
1328
- if (length === 16384) {
1329
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedMediumAllocationPoolLen > 0) {
1330
- const pooled = sharedMediumAllocationPool[--sharedMediumAllocationPoolLen];
1331
- sharedMediumAllocationPool[sharedMediumAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
1332
- pooled.reuse(this.ledger);
1333
- return pooled;
1334
- }
1335
- }
1336
- if (length === 65536) {
1337
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen > 0) {
1338
- const pooled = sharedLargeAllocationPool[--sharedLargeAllocationPoolLen];
1339
- sharedLargeAllocationPool[sharedLargeAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
1340
- pooled.reuse(this.ledger);
1341
- return pooled;
1342
- }
1343
- }
1344
- if (length === 64) {
1345
- let pooled;
1346
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen > 0) {
1347
- pooled = sharedAllocationPool[--sharedAllocationPoolLen];
1348
- sharedAllocationPool[sharedAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
1349
- }
1350
- else {
1351
- pooled = memoryCaches.get(this.ledger).allocations.pop();
1352
- }
1353
- if (pooled) {
1354
- pooled.reuse(this.ledger);
1355
- return pooled;
1356
- }
1357
- }
1304
+ // Views can outlive files: never recycle their backing buffers across owners.
1358
1305
  try {
1359
1306
  return new MemoryAllocation(new Uint8Array(length), this.ledger);
1360
1307
  }
@@ -2300,6 +2247,93 @@ export class MemoryFileSystem {
2300
2247
  this.fail("EAGAIN", "publishStagedFile", destination);
2301
2248
  return MemoryFileSystem.prototype.rename.call(this, staging.file.path, destination, { ...controls, noReplace: expected === null });
2302
2249
  }
2250
+ async publishStagedFileSet(receipt, destination, options) {
2251
+ const signal = options.signal;
2252
+ const guard = options.commitGuard;
2253
+ const ancestors = options.ancestors === undefined ? undefined : snapshotDirectoryAncestry(options.ancestors);
2254
+ const snapshot = (entry) => ({ path: entry.path, stat: { ...entry.stat } });
2255
+ const staging = { parent: snapshot(receipt.parent), directory: snapshot(receipt.directory), file: snapshot(receipt.file) };
2256
+ const parent = { ...options.parent };
2257
+ const expected = options.destination === null ? null : { ...options.destination };
2258
+ const companions = options.companions.map(entry => ({ path: entry.path, expected: entry.expected === null ? null : { ...entry.expected }, remove: entry.remove }));
2259
+ const syscall = "publishStagedFileSet";
2260
+ const parentPath = destination.slice(0, destination.lastIndexOf("/")) || "/";
2261
+ const paths = new Set([destination]);
2262
+ const canonical = (path) => path.startsWith("/") && path.slice(1).split("/").every(part => part !== "" && part !== "." && part !== "..");
2263
+ if (!canonical(destination) || ancestors && ancestors.at(-1)?.path !== parentPath)
2264
+ this.fail("EINVAL", syscall, destination);
2265
+ for (const entry of companions) {
2266
+ if (!canonical(entry.path) || paths.has(entry.path) || (entry.path.slice(0, entry.path.lastIndexOf("/")) || "/") !== parentPath)
2267
+ this.fail("EINVAL", syscall, entry.path);
2268
+ paths.add(entry.path);
2269
+ }
2270
+ signal?.throwIfAborted();
2271
+ if (guard !== undefined)
2272
+ runStagingGuard(guard);
2273
+ signal?.throwIfAborted();
2274
+ if (ancestors)
2275
+ this.verifyDirectoryAncestry(ancestors, signal === undefined ? {} : { signal });
2276
+ const { directory, file } = this.stagingLocations(staging);
2277
+ this.expectEntry(file.node, staging.file.stat, staging.file.path);
2278
+ const target = this.entry(destination, syscall, true);
2279
+ this.expectEntry(target.parent, parent, destination, false);
2280
+ this.expectEntry(target.node, expected, destination);
2281
+ const node = file.node;
2282
+ if (node.type !== "file" || target.parent === directory.node || target.node === node)
2283
+ this.fail("EINVAL", syscall, destination);
2284
+ if (target.node && target.node.type !== "file")
2285
+ this.fail("EAGAIN", syscall, destination);
2286
+ this.permission(file.parent, 3, syscall, staging.file.path);
2287
+ this.permission(target.parent, 3, syscall, destination);
2288
+ const retired = [];
2289
+ for (const companion of companions) {
2290
+ const location = this.entry(companion.path, syscall, true);
2291
+ if (location.parent !== target.parent || location.node === node)
2292
+ this.fail("EINVAL", syscall, companion.path);
2293
+ this.expectEntry(location.node, companion.expected, companion.path);
2294
+ if (location.node && location.node.type !== "file")
2295
+ this.fail("EAGAIN", syscall, companion.path);
2296
+ if (companion.remove && location.node)
2297
+ retired.push(location);
2298
+ }
2299
+ const now = Date.now();
2300
+ const published = { ...this.snapshot(node), ctimeMs: now, revision: Math.min(Number.MAX_SAFE_INTEGER + 1, node.revision + 1) };
2301
+ // Admission and all source-set checks precede the single synchronous commit.
2302
+ // No calls to overridable async mutations can interleave a partial result.
2303
+ const nameGrowth = target.node ? 0 : (target.name.length - file.name.length) * 2;
2304
+ this.ledger.reserve(Math.max(0, nameGrowth), 0, syscall, destination);
2305
+ try {
2306
+ target.parent.entries.set(target.name, node);
2307
+ }
2308
+ catch (error) {
2309
+ this.ledger.release(Math.max(0, nameGrowth), 0);
2310
+ throw error;
2311
+ }
2312
+ memoryCaches.get(this.ledger).clearWrites();
2313
+ file.parent.entries.delete(file.name);
2314
+ const unlink = (entry) => {
2315
+ entry.node.nlink--;
2316
+ entry.node.ctimeMs = now;
2317
+ entry.node.revision = Math.min(Number.MAX_SAFE_INTEGER + 1, entry.node.revision + 1);
2318
+ this.releaseNode(entry.node);
2319
+ };
2320
+ if (target.node) {
2321
+ this.ledger.release(file.name.length * 2, 1);
2322
+ unlink(target);
2323
+ }
2324
+ else if (nameGrowth < 0)
2325
+ this.ledger.release(-nameGrowth, 0);
2326
+ for (const entry of retired) {
2327
+ entry.parent.entries.delete(entry.name);
2328
+ this.ledger.release(entry.name.length * 2, 1);
2329
+ unlink(entry);
2330
+ }
2331
+ this.changed(file.parent, now);
2332
+ this.changed(target.parent, now);
2333
+ node.ctimeMs = now;
2334
+ node.revision = published.revision;
2335
+ return published;
2336
+ }
2303
2337
  async removeStagedFile(staging, options = {}) {
2304
2338
  options.signal?.throwIfAborted();
2305
2339
  const { directory, file } = this.stagingLocations(staging);
@@ -2896,7 +2930,10 @@ export class MemoryFileSystem {
2896
2930
  if (!descriptor || !("value" in descriptor) || descriptor.value !== memoryImplementation[name]?.value)
2897
2931
  unsupported();
2898
2932
  }
2899
- const retainedRead = Object.getOwnPropertyDescriptor(this, "capabilities")?.value?.retainedRead;
2933
+ const capabilities = Object.getOwnPropertyDescriptor(this, "capabilities");
2934
+ const retainedRead = capabilities === undefined
2935
+ ? Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get
2936
+ : capabilities.value?.retainedRead;
2900
2937
  signal?.throwIfAborted();
2901
2938
  if (retainedRead !== true)
2902
2939
  unsupported();
@@ -3036,7 +3073,7 @@ function stockDescriptorWrite(filesystem) {
3036
3073
  return false;
3037
3074
  const owner = ownedStores.get(filesystem);
3038
3075
  const capDesc = Object.getOwnPropertyDescriptor(filesystem, "capabilities");
3039
- return !!capDesc && "value" in capDesc && capDesc.value === owner.capabilities;
3076
+ return (capDesc === undefined && Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get) || (!!capDesc && "value" in capDesc && capDesc.value === owner.capabilities);
3040
3077
  }
3041
3078
  function stockRetainedResize(filesystem) {
3042
3079
  if (!stockDescriptorWrite(filesystem) || !isStockMemoryMethods(filesystem, stockRetainedResizeMethodNames, false))
@@ -3395,7 +3432,7 @@ export function tryResolveMemoryDevicePath(filesystem, path, resizeCreate) {
3395
3432
  start = slash + 1;
3396
3433
  }
3397
3434
  }
3398
- const cachedMethodValuesMap = new WeakMap();
3435
+ const cachedMethodValuesMap = /* @__PURE__ */ new WeakMap();
3399
3436
  function getExpectedMethodValues(names) {
3400
3437
  let vals = cachedMethodValuesMap.get(names);
3401
3438
  if (!vals) {
@@ -3718,3 +3755,17 @@ export function tryWriteMemoryFileInDirSync(filesystem, dirPrefix, name, data, a
3718
3755
  mem.writeMemoryFileInDirFast(dirPrefix, name, data, append, mode);
3719
3756
  return true;
3720
3757
  }
3758
+ const _memHooks = (globalThis.__safeBashMemHooks ??= { protos: new Set(), byProto: new Map() });
3759
+ _memHooks.protos.add(MemoryFileSystem.prototype);
3760
+ _memHooks.byProto.set(MemoryFileSystem.prototype, {
3761
+ tryGetMemoryDirectoryEntryNamesSync,
3762
+ tryReadMemoryFileViewSync,
3763
+ tryResolveMemoryDevicePath,
3764
+ tryOpenMemoryRedirectHandleSync,
3765
+ tryWriteMemoryFileSync,
3766
+ tryMkdirMemorySync,
3767
+ tryRmRfMemorySync,
3768
+ tryWriteMemoryFileInDirSync,
3769
+ });
3770
+ _memHooks.isCleanAbsolutePath = isCleanAbsolutePath;
3771
+ _memHooks.bindConditionalMutation = bindConditionalMutation;
@@ -23,8 +23,4 @@ export declare class MemoryAllocation {
23
23
  constructor(data: Uint8Array, ledger: MemoryLedger);
24
24
  retain(): void;
25
25
  release(): void;
26
- isReleased64(): boolean;
27
- isReleased65536(): boolean;
28
- detachLedger(dummyLedger: MemoryLedger): void;
29
- reuse(ledger?: MemoryLedger): void;
30
26
  }
@@ -89,21 +89,6 @@ export class MemoryAllocation {
89
89
  this.ledger.release(this.data.byteLength, 0);
90
90
  }
91
91
  }
92
- isReleased64() {
93
- return this.references === 0 && this.data.byteLength === 64;
94
- }
95
- isReleased65536() {
96
- return this.references === 0 && this.data.byteLength === 65536;
97
- }
98
- detachLedger(dummyLedger) {
99
- this.ledger = dummyLedger;
100
- }
101
- reuse(ledger) {
102
- this.references = 1;
103
- if (ledger !== undefined)
104
- this.ledger = ledger;
105
- this.data.fill(0);
106
- }
107
92
  }
108
93
  Object.assign(MemoryAllocation.prototype, {
109
94
  references: 1,
@@ -7,6 +7,9 @@ export const defaultMemoryFileSystemLimits = Object.freeze({
7
7
  maxMetadataUnits: Infinity,
8
8
  });
9
9
  export function normalizeMemoryFileSystemLimits(options) {
10
+ if (options === undefined || (typeof options === "object" && options !== null && Object.getPrototypeOf(options) === Object.prototype && Reflect.ownKeys(options).length === 0)) {
11
+ return defaultMemoryFileSystemLimits;
12
+ }
10
13
  const keys = ["maxPathBytes", "maxPathComponents", "maxFileBytes", "maxRetainedBytes", "maxMetadataUnits"];
11
14
  const record = readConfigRecord(options, "memory option", [...keys, "maxBytes"]);
12
15
  const limits = { ...defaultMemoryFileSystemLimits };
@@ -29,7 +29,7 @@ export async function resolveEntryView(filesystem, path, options = {}) {
29
29
  readOnly ||= location.readOnly === true || location.filesystem.capabilities.readOnly === true;
30
30
  if (location.stat)
31
31
  return { filesystem: location.filesystem, path: location.path, stat: location.stat, readOnly };
32
- const resolve = resolvers.get(location.filesystem);
32
+ const resolve = resolvers.get(location.filesystem) ?? location.filesystem._resolveEntryView;
33
33
  if (!resolve) {
34
34
  let stat;
35
35
  let followedPath;
@@ -49,7 +49,7 @@ export async function resolveEntryView(filesystem, path, options = {}) {
49
49
  }
50
50
  let next;
51
51
  try {
52
- next = await resolve(location.path, options);
52
+ next = await resolve.call(location.filesystem, location.path, options);
53
53
  }
54
54
  catch (error) {
55
55
  options.signal?.throwIfAborted();
@@ -74,7 +74,7 @@ export async function compareResolvedEntries(own, peer, options = {}) {
74
74
  let result = "unknown";
75
75
  for (const [left, right] of [[own, peer], [peer, own]]) {
76
76
  options.signal?.throwIfAborted();
77
- const authority = authorities.get(left.filesystem);
77
+ const authority = authorities.get(left.filesystem) ?? left.filesystem._entryAuthority;
78
78
  const key = authority ?? left.filesystem;
79
79
  if (queried.has(key))
80
80
  continue;
@@ -1,2 +1,6 @@
1
1
  import type { FileStat } from "../../contracts/filesystem.js";
2
2
  export declare function compareIdentity(left: FileStat | undefined, right: FileStat | undefined): "same" | "distinct" | "unknown";
3
+ /** Compare content snapshots, including adapter generations when supplied.
4
+ * Identity is checked separately so identityless streaming adapters remain usable.
5
+ */
6
+ export declare function compareFileVersion(left: FileStat, right: FileStat): boolean;
@@ -19,3 +19,11 @@ export function compareIdentity(left, right) {
19
19
  return "distinct";
20
20
  return "unknown";
21
21
  }
22
+ /** Compare content snapshots, including adapter generations when supplied.
23
+ * Identity is checked separately so identityless streaming adapters remain usable.
24
+ */
25
+ export function compareFileVersion(left, right) {
26
+ return left.type === right.type && left.size === right.size
27
+ && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs
28
+ && left.revision === right.revision && left.opaqueVersion === right.opaqueVersion;
29
+ }
@@ -12,6 +12,7 @@ export declare class MountFileSystem implements FileSystem {
12
12
  private namespaceMutation;
13
13
  private readonly namespaceWaiters;
14
14
  constructor(options: MountFileSystemOptions);
15
+ confineExtraction(roots: readonly string[], options?: FsOptions): Promise<FileSystem>;
15
16
  private select;
16
17
  capabilitiesFor(path: string, options?: CapabilityQueryOptions): Promise<FileSystemCapabilities>;
17
18
  private supportsStagingAncestry;
@@ -9,6 +9,7 @@ import { pathNamespace } from "../path-namespace.js";
9
9
  import { createStagingCleanup, snapshotStagingCreation } from "../staging-cleanup.js";
10
10
  import { directoryAncestryPaths, inspectStagingBindings, runStagingGuard, snapshotDirectoryAncestry, snapshotStagingResolution } from "../staging-ancestry.js";
11
11
  import { compareIdentity } from "./identity.js";
12
+ import { createReadOnlyFileSystem } from "../readonly/index.js";
12
13
  import { snapshotConditionalChmod } from "../conditional-chmod.js";
13
14
  import { compareEntries, registerEntryAuthority, registerEntryView } from "./comparison.js";
14
15
  const syntheticStat = Object.freeze({
@@ -150,6 +151,36 @@ export class MountFileSystem {
150
151
  ...(retainedRead === undefined ? {} : { retainedRead }),
151
152
  });
152
153
  }
154
+ async confineExtraction(roots, options = {}) {
155
+ options.signal?.throwIfAborted();
156
+ const root = this.select("/").backend;
157
+ const captured = [...roots];
158
+ for (const path of captured) {
159
+ validatePath(path);
160
+ if (normalizePath(globalPath(path)) !== path)
161
+ fail("EINVAL");
162
+ // A backing confinement retains its own root-to-destination ancestry.
163
+ // It cannot retain directories belonging to another mounted backend.
164
+ if (this.select(path).path !== "/")
165
+ fail("ENOTSUP");
166
+ }
167
+ if (!root.confineExtraction)
168
+ fail("ENOTSUP");
169
+ const confined = await root.confineExtraction(captured, options);
170
+ options.signal?.throwIfAborted();
171
+ const observations = new Set(["prepareDirectoryAncestry", "prepareStagingResolution"]);
172
+ const backing = new Proxy(confined, {
173
+ get(target, key) {
174
+ // These only capture/validate bindings. Mutations must always use the
175
+ // backend's confined view, including creation, metadata and hardlinks.
176
+ const owner = observations.has(key) ? root : target;
177
+ const value = Reflect.get(owner, key);
178
+ return typeof value === "function" ? value.bind(owner) : value;
179
+ },
180
+ });
181
+ return new MountFileSystem({ root: backing, mounts: Object.fromEntries(this.mounts.filter(mount => mount.path !== "/").map(mount => [mount.path,
182
+ mount.backend.capabilities.readOnly === true ? mount.backend : createReadOnlyFileSystem(mount.backend)])) });
183
+ }
153
184
  select(path) {
154
185
  return this.mounts.find((mount) => within(mount.path, path));
155
186
  }