@poe-platform/safe-fs 0.1.804 → 0.1.805

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.
@@ -40,6 +40,7 @@ export interface FsErrorOptions extends ErrorOptions {
40
40
  readonly message?: string;
41
41
  }
42
42
  export declare class FsError extends Error {
43
+ static [Symbol.hasInstance](value: unknown): boolean;
43
44
  readonly code: ErrnoCode;
44
45
  readonly errno: PlatformErrno;
45
46
  readonly syscall?: string;
@@ -31,7 +31,18 @@ const descriptions = {
31
31
  ETIMEDOUT: "operation timed out",
32
32
  EXDEV: "cross-device link not permitted",
33
33
  };
34
+ const FS_ERROR_BRAND = Symbol.for("@poe-code/safe-fs.FsError");
34
35
  export class FsError extends Error {
36
+ static [Symbol.hasInstance](value) {
37
+ if (typeof value !== "object" || value === null)
38
+ return false;
39
+ if (this !== FsError)
40
+ return Function.prototype[Symbol.hasInstance].call(this, value);
41
+ if (Function.prototype[Symbol.hasInstance].call(FsError, value))
42
+ return true;
43
+ const record = value;
44
+ return record[FS_ERROR_BRAND] === true && isErrnoCode(record.code);
45
+ }
35
46
  code;
36
47
  errno;
37
48
  syscall;
@@ -53,6 +64,7 @@ export class FsError extends Error {
53
64
  finally {
54
65
  errorConstructor.stackTraceLimit = prevStackLimit;
55
66
  }
67
+ Object.defineProperty(this, FS_ERROR_BRAND, { value: true, enumerable: false, configurable: false, writable: false });
56
68
  this.name = "FsError";
57
69
  this.code = code;
58
70
  this.errno = errno;
@@ -1,28 +1,16 @@
1
- export * from "./contracts/errors.js";
2
- export * from "./contracts/filesystem.js";
3
- export * from "./contracts/io.js";
4
- export { openFileDescriptor } from "./fs/descriptor.js";
5
- export type { DescriptorBackend, DescriptorOpenOptions } from "./fs/descriptor.js";
6
- export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
7
- export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
8
- export * from "./fs/memory/index.js";
9
- export * from "./fs/devices/index.js";
1
+ export * from "./runtime-core.js";
2
+ export { MemoryFileSystem, MemoryRedirectHandle, createMemoryFileSystem, defaultMemoryFileSystemLimits, bindConditionalMutation, getLastReadMemoryFileSourceRef, isCleanAbsolutePath, tryGetMemoryDirectoryEntryNamesSync, tryMkdirMemorySync, tryOpenMemoryRedirectHandleSync, tryReadMemoryFileViewSync, tryResolveMemoryDevicePath, tryRmRfMemorySync, tryWriteMemoryFileInDirSync, tryWriteMemoryFileSync, utf8ByteLength } from "./fs/memory/index.js";
10
3
  export * from "./fs/readonly/index.js";
11
4
  export * from "./fs/mount/index.js";
12
5
  export * from "./fs/overlay/index.js";
13
6
  export * from "./fs/quota/index.js";
14
7
  export * from "./fs/object-publication/index.js";
15
- export { retargetScopedFileSystem, scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
16
- export type { RetainedFileSystemCleanupView, RetainedFileSystemCleanupOptions } from "./fs/scoped.js";
17
8
  export * from "./fs/webdav/index.js";
18
9
  export * from "./bridge/index.js";
19
10
  export * from "./python/index.js";
20
- export { compareEntries, registerEntryView } from "./fs/mount/comparison.js";
21
- export type { EntryViewResolver } from "./fs/mount/comparison.js";
22
11
  export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
23
12
  export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
24
13
  export * from "./contracts/object.js";
25
14
  export { ObjectAuthority } from "./fs/object-authority.js";
26
- export { bindConditionalMutation, type ConditionalMutationBinding } from "./fs/memory/index.js";
27
15
  export * from "./fs/s3/index.js";
28
16
  export * from "./fs/s3/http/index.js";
@@ -1,24 +1,15 @@
1
- export * from "./contracts/errors.js";
2
- export * from "./contracts/filesystem.js";
3
- export * from "./contracts/io.js";
4
- export { openFileDescriptor } from "./fs/descriptor.js";
5
- export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
6
- export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
7
- export * from "./fs/memory/index.js";
8
- export * from "./fs/devices/index.js";
1
+ export * from "./runtime-core.js";
2
+ export { MemoryFileSystem, MemoryRedirectHandle, createMemoryFileSystem, defaultMemoryFileSystemLimits, bindConditionalMutation, getLastReadMemoryFileSourceRef, isCleanAbsolutePath, tryGetMemoryDirectoryEntryNamesSync, tryMkdirMemorySync, tryOpenMemoryRedirectHandleSync, tryReadMemoryFileViewSync, tryResolveMemoryDevicePath, tryRmRfMemorySync, tryWriteMemoryFileInDirSync, tryWriteMemoryFileSync, utf8ByteLength } from "./fs/memory/index.js";
9
3
  export * from "./fs/readonly/index.js";
10
4
  export * from "./fs/mount/index.js";
11
5
  export * from "./fs/overlay/index.js";
12
6
  export * from "./fs/quota/index.js";
13
7
  export * from "./fs/object-publication/index.js";
14
- export { retargetScopedFileSystem, scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
15
8
  export * from "./fs/webdav/index.js";
16
9
  export * from "./bridge/index.js";
17
10
  export * from "./python/index.js";
18
- export { compareEntries, registerEntryView } from "./fs/mount/comparison.js";
19
11
  export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
20
12
  export * from "./contracts/object.js";
21
13
  export { ObjectAuthority } from "./fs/object-authority.js";
22
- export { bindConditionalMutation } from "./fs/memory/index.js";
23
14
  export * from "./fs/s3/index.js";
24
15
  export * from "./fs/s3/http/index.js";
@@ -1,15 +1,20 @@
1
1
  import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
2
2
  import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalFilePublicationOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PublishStagedFileOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
3
3
  import type { ByteSource } from "../../contracts/io.js";
4
+ import { type EntryLocation } from "../mount/comparison.js";
5
+ import { pathNamespace } from "../path-namespace.js";
4
6
  export declare class DeviceFileSystem implements FileSystem {
5
7
  #private;
6
8
  readonly capabilities: FileSystemCapabilities;
9
+ get [pathNamespace](): any;
10
+ _resolveEntryView: (path: string, options: FsOptions) => Promise<EntryLocation>;
11
+ _entryAuthority: () => Promise<"unknown">;
7
12
  constructor(filesystem: FileSystem);
8
13
  canonicalizeMissingTarget(path: string, options?: FsOptions): string | undefined;
9
14
  capabilitiesFor(path: string, options?: CapabilityQueryOptions): Promise<FileSystemCapabilities>;
10
15
  stat(path: string, options?: FsOptions): Promise<FileStat>;
11
16
  lstat(path: string, options?: FsOptions): Promise<FileStat>;
12
- compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<import("../../contracts/filesystem.js").EntryComparison>;
17
+ compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<import("../../runtime-core.js").EntryComparison>;
13
18
  realpath(path: string, options?: FsOptions): Promise<string>;
14
19
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
15
20
  readStream(path: string, options?: ReadStreamOptions): ByteSource;
@@ -2,9 +2,9 @@ import { snapshotStagingCreation } from "../staging-cleanup.js";
2
2
  import { FsError, isFsError } from "../../contracts/errors.js";
3
3
  import { finishCleanup } from "../../contracts/cleanup.js";
4
4
  import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
5
- import { compareEntries, registerEntryAuthority, registerEntryView } from "../mount/comparison.js";
5
+ import { compareEntries } from "../mount/comparison.js";
6
6
  import { deviceDirectory, lexicalDevicePath, nullPath, resolveDevicePath } from "./path.js";
7
- import { tryResolveMemoryDevicePath } from "../memory/index.js";
7
+ import { tryResolveMemoryDevicePath } from "../../runtime-core.js";
8
8
  import { createDeviceYield, deviceReadStream, drainDeviceFile, drainDeviceInput } from "./stream.js";
9
9
  import { openRetainedReadFile, openRetainedResizeFile, retainedResizeCapabilities, ownedMutationCapabilities, requireOwnedMutation } from "../capabilities.js";
10
10
  import { pathNamespace } from "../path-namespace.js";
@@ -71,21 +71,46 @@ function nonnegative(value, path) {
71
71
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
72
72
  throw new FsError("EINVAL", { path });
73
73
  }
74
+ const sharedUnknownEntryAuthority = async () => "unknown";
74
75
  export class DeviceFileSystem {
75
76
  #filesystem;
76
77
  capabilities;
77
- #nullStat;
78
- #directoryStat;
78
+ #identityScopeObj;
79
+ #nullStatObj;
80
+ #directoryStatObj;
81
+ get [pathNamespace]() {
82
+ return Reflect.get(this.#filesystem, pathNamespace);
83
+ }
84
+ get #nullStat() {
85
+ if (this.#nullStatObj)
86
+ return this.#nullStatObj;
87
+ const identityScope = (this.#identityScopeObj ??= Object.freeze({}));
88
+ return (this.#nullStatObj = Object.freeze({
89
+ type: "character", mode: 0o020666, size: 0, allocatedBytes: 0, uid: 0, gid: 0,
90
+ mtimeMs: 0, atimeMs: 0, ctimeMs: 0, birthtimeMs: 0, identityScope, ino: 1, dev: 0, nlink: 1, preferredIoBlockSize: 4096,
91
+ }));
92
+ }
93
+ get #directoryStat() {
94
+ if (this.#directoryStatObj)
95
+ return this.#directoryStatObj;
96
+ const identityScope = (this.#identityScopeObj ??= Object.freeze({}));
97
+ return (this.#directoryStatObj = Object.freeze({
98
+ type: "directory", mode: 0o040755, size: 0, allocatedBytes: 0, uid: 0, gid: 0,
99
+ mtimeMs: 0, atimeMs: 0, ctimeMs: 0, birthtimeMs: 0, identityScope, ino: 2, dev: 0, nlink: 1,
100
+ }));
101
+ }
102
+ _resolveEntryView = async (path, options) => {
103
+ const resolved = await this.#resolve(path, options);
104
+ if (resolved === nullPath || resolved === deviceDirectory) {
105
+ return { filesystem: this, path: resolved, stat: resolved === nullPath ? this.#nullStat : this.#directoryStat, readOnly: false };
106
+ }
107
+ return { filesystem: this.#filesystem, path };
108
+ };
109
+ _entryAuthority = sharedUnknownEntryAuthority;
79
110
  constructor(filesystem) {
80
111
  this.#filesystem = filesystem;
81
- Object.defineProperty(this, pathNamespace, { get: () => Reflect.get(filesystem, pathNamespace) });
82
112
  this.capabilities = globalCapabilities(filesystem);
83
- const identityScope = Object.freeze({});
84
- const stat = { mode: 0o020666, size: 0, allocatedBytes: 0, uid: 0, gid: 0,
85
- mtimeMs: 0, atimeMs: 0, ctimeMs: 0, birthtimeMs: 0, identityScope, ino: 1, dev: 0, nlink: 1 };
86
- this.#nullStat = Object.freeze({ ...stat, type: "character", preferredIoBlockSize: 4096 });
87
- this.#directoryStat = Object.freeze({ ...stat, type: "directory", mode: 0o040755, ino: 2 });
88
- const methods = new Map();
113
+ let methods;
89
114
  const view = new Proxy(this, {
90
115
  set: (_target, property, value) => Reflect.set(filesystem, property, value, filesystem),
91
116
  get(target, property) {
@@ -93,24 +118,14 @@ export class DeviceFileSystem {
93
118
  if (typeof value !== "function" || property === "constructor")
94
119
  return value;
95
120
  const backing = Reflect.get(filesystem, property, filesystem);
96
- const cached = methods.get(property);
121
+ const cached = methods?.get(property);
97
122
  if (cached && cached.backing === backing)
98
123
  return cached.bound;
99
124
  const bound = value.bind(target);
100
- methods.set(property, { backing, bound });
125
+ (methods ??= new Map()).set(property, { backing, bound });
101
126
  return bound;
102
127
  },
103
128
  });
104
- for (const identity of [this, view])
105
- registerEntryView(identity, async (path, options) => {
106
- const resolved = await this.#resolve(path, options);
107
- if (resolved === nullPath || resolved === deviceDirectory)
108
- return { filesystem: this, path: resolved,
109
- stat: resolved === nullPath ? this.#nullStat : this.#directoryStat, readOnly: false };
110
- return { filesystem, path };
111
- });
112
- // This internal view has no identity proof beyond its synthetic stats.
113
- registerEntryAuthority(this, async () => "unknown");
114
129
  return view;
115
130
  }
116
131
  #resolve(path, options, followFinal = true, resizeCreate, traversal) {
@@ -1,7 +1,7 @@
1
1
  import { FsError, isFsError } from "../../contracts/errors.js";
2
2
  import { pathByteLength, validatePath } from "../../contracts/virtual-path.js";
3
3
  import { capturePathNamespace, pathNamespace } from "../path-namespace.js";
4
- import { isCleanAbsolutePath, tryResolveMemoryDevicePath } from "../memory/index.js";
4
+ import { isCleanAbsolutePath, tryResolveMemoryDevicePath } from "../../runtime-core.js";
5
5
  export const nullPath = "/dev/null";
6
6
  export const deviceDirectory = "/dev";
7
7
  function isNullParts(parts) {
@@ -4,5 +4,8 @@ export interface MemoryAtomicView {
4
4
  stat(path: string): FileStat | undefined;
5
5
  names(path: string): readonly string[];
6
6
  }
7
+ type LazyMemoryAtomicView = (this: FileSystem) => MemoryAtomicView | undefined;
7
8
  export declare function registerMemoryAtomicView(fs: FileSystem, view: MemoryAtomicView, intact: () => boolean): void;
9
+ export declare function registerLazyMemoryAtomicView(fs: FileSystem, create: LazyMemoryAtomicView): void;
8
10
  export declare function memoryAtomicView(fs: FileSystem): MemoryAtomicView | undefined;
11
+ export {};
@@ -2,7 +2,12 @@ const views = new WeakMap();
2
2
  export function registerMemoryAtomicView(fs, view, intact) {
3
3
  views.set(fs, { view, intact });
4
4
  }
5
+ export function registerLazyMemoryAtomicView(fs, create) {
6
+ views.set(fs, create);
7
+ }
5
8
  export function memoryAtomicView(fs) {
6
9
  const registered = views.get(fs);
10
+ if (typeof registered === "function")
11
+ return registered.call(fs);
7
12
  return registered?.intact() ? registered.view : undefined;
8
13
  }
@@ -1,5 +1,6 @@
1
1
  import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, ChmodOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, FileDescriptor, OpenFileOptions, OpenReadFileOptions, OpenResizeFileOptions, WriteFileOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PublishStagedFileOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
2
2
  import type { ByteSource } from "../../contracts/io.js";
3
+ import { type MemoryAtomicView } from "./atomic-view.js";
3
4
  import { MemoryAllocation, MemoryLedger } from "./ledger.js";
4
5
  import { type MemoryFileSystemOptions } from "./limits.js";
5
6
  export { defaultMemoryFileSystemLimits, type MemoryFileSystemLimits, type MemoryFileSystemOptions } from "./limits.js";
@@ -59,9 +60,10 @@ export interface ConditionalMutationBinding {
59
60
  }
60
61
  export declare function bindConditionalMutation<Result>(identityScope: object | symbol | undefined, binding: ConditionalMutationBinding, action: () => Promise<Result>): Promise<Result>;
61
62
  export declare class MemoryFileSystem implements FileSystem {
63
+ #private;
62
64
  capabilitiesFor?: NonNullable<FileSystem["capabilitiesFor"]>;
63
- readonly capabilities: FileSystemCapabilities;
64
- private readonly identityScope;
65
+ private get identityScope();
66
+ get capabilities(): FileSystemCapabilities;
65
67
  private nextInode;
66
68
  private readonly ledger;
67
69
  readonly _cache: MemoryCache;
@@ -70,6 +72,7 @@ export declare class MemoryFileSystem implements FileSystem {
70
72
  private readonly root;
71
73
  private totalBytes;
72
74
  symlinkCount: number;
75
+ _getAtomicView(): MemoryAtomicView | undefined;
73
76
  constructor(options?: MemoryFileSystemOptions);
74
77
  compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<EntryComparison>;
75
78
  canonicalizeMissingTarget(path: string, options?: FsOptions): string | undefined;
@@ -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;
@@ -415,7 +423,6 @@ class MemoryCache {
415
423
  const typeModes = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
416
424
  const emptyResolveOptions = Object.freeze({});
417
425
  const noFollowResolveOptions = Object.freeze({ followFinal: false });
418
- const sharedMkdirResolveOptions = { createDirectories: 0o777 };
419
426
  const sharedAllowMissingNoFollowOptions = Object.freeze({ allowMissing: true, followFinal: false });
420
427
  const sharedFastLocation = { node: undefined, parent: DUMMY_POOL_DIR_NODE, name: "", path: "" };
421
428
  const resolvedVoid = Promise.resolve();
@@ -441,7 +448,7 @@ const compareOwnedMemory = async (own, peer, options) => {
441
448
  let answer = "unknown";
442
449
  const visited = new Set();
443
450
  for (const [left, right] of [[own, peer], [peer, own]]) {
444
- if (!registeredAuthorities.has(left.filesystem) || visited.has(left.filesystem))
451
+ if ((!registeredAuthorities.has(left.filesystem) && !ownedStores.has(left.filesystem)) || visited.has(left.filesystem))
445
452
  continue;
446
453
  visited.add(left.filesystem);
447
454
  const comparison = left.filesystem.compareEntry;
@@ -590,32 +597,48 @@ class MemoryReadStream {
590
597
  return STREAM_RESOLVED_DONE;
591
598
  }
592
599
  }
600
+ const SHARED_STOCK_MEMORY_CAPABILITIES = Object.freeze({
601
+ read: true, stat: true, readdir: true, realpath: true, access: true, open: true,
602
+ write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
603
+ mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
604
+ rename: true, atomicRenameNoReplace: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
605
+ streamingAppend: true, randomAccessWrite: true,
606
+ readOnly: false,
607
+ symlinks: true, conditionalChmod: true,
608
+ hardlinks: true,
609
+ permissions: true,
610
+ timestamps: true,
611
+ atomicRename: true,
612
+ atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
613
+ synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
614
+ atomicDirectoryMetadata: true,
615
+ streamingRead: true,
616
+ retainedRead: true,
617
+ retainedResize: true,
618
+ streamingWrite: true,
619
+ descriptorWriteStream: true,
620
+ });
621
+ function customMemoryCapabilities(filesystem) {
622
+ return Object.freeze({
623
+ ...SHARED_STOCK_MEMORY_CAPABILITIES,
624
+ get retainedResize() { return stockRetainedResize(filesystem); },
625
+ get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
626
+ });
627
+ }
593
628
  export class MemoryFileSystem {
594
629
  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();
630
+ #customCapabilities;
631
+ #identityScopeSym;
632
+ get identityScope() {
633
+ return (this.#identityScopeSym ??= Symbol());
634
+ }
635
+ get capabilities() {
636
+ if (!this.#customCapabilities) {
637
+ this.#customCapabilities = customMemoryCapabilities(this);
638
+ this._owner.capabilities = this.#customCapabilities;
639
+ }
640
+ return this.#customCapabilities;
641
+ }
619
642
  nextInode = 1;
620
643
  ledger;
621
644
  _cache;
@@ -624,26 +647,34 @@ export class MemoryFileSystem {
624
647
  root;
625
648
  totalBytes = 0;
626
649
  symlinkCount = 0;
627
- constructor(options = {}) {
628
- this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
629
- if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes) {
630
- replenishSharedMemoryPools();
650
+ #atomicView;
651
+ _getAtomicView() {
652
+ const owner = ownedStores.get(this);
653
+ if (!owner ||
654
+ Object.getPrototypeOf(this) !== MemoryFileSystem.prototype ||
655
+ this.root !== owner.root ||
656
+ this.ledger !== owner.ledger ||
657
+ this.capabilities !== owner.capabilities) {
658
+ return undefined;
631
659
  }
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, {
660
+ const proto = MemoryFileSystem.prototype;
661
+ for (let i = 0; i < memoryImplementationKeys.length; i++) {
662
+ const name = memoryImplementationKeys[i];
663
+ const expected = memoryImplementationDescriptors[i];
664
+ if (name === "capabilities")
665
+ continue;
666
+ if (Object.prototype.hasOwnProperty.call(this, name)) {
667
+ const actual = Object.getOwnPropertyDescriptor(this, name);
668
+ if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
669
+ return undefined;
670
+ }
671
+ else if (expected.get !== undefined || expected.set !== undefined || proto[name] !== expected.value) {
672
+ const actual = Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
673
+ if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
674
+ return undefined;
675
+ }
676
+ }
677
+ return this.#atomicView ??= {
647
678
  stat: (path) => {
648
679
  this.validatePath(path, "overlayAtomicView");
649
680
  let node = this.root;
@@ -665,30 +696,28 @@ export class MemoryFileSystem {
665
696
  this.fail("ENOTDIR", "overlayAtomicView", path);
666
697
  return [...node.entries.keys()];
667
698
  },
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
- });
699
+ };
700
+ }
701
+ constructor(options = {}) {
702
+ this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
703
+ if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes) {
704
+ replenishSharedMemoryPools();
705
+ }
706
+ const cache = new MemoryCache();
707
+ this._cache = cache;
708
+ memoryCaches.set(this.ledger, cache);
709
+ this.ledger.reserve(0, 1, "mkdir", "/");
710
+ this.root = this.directory(0o755);
711
+ const root = this.root;
712
+ const owner = {
713
+ root,
714
+ ledger: this.ledger,
715
+ capabilities: SHARED_STOCK_MEMORY_CAPABILITIES,
716
+ intact: () => this.root === root,
717
+ };
718
+ this._owner = owner;
719
+ ownedStores.set(this, owner);
720
+ registerLazyMemoryAtomicView(this, memoryImplementation._getAtomicView.value);
692
721
  if (this.compareEntry === memoryImplementation.compareEntry?.value) {
693
722
  registeredAuthorities.add(this);
694
723
  registerEntryAuthority(this, compareOwnedMemory);
@@ -2896,7 +2925,10 @@ export class MemoryFileSystem {
2896
2925
  if (!descriptor || !("value" in descriptor) || descriptor.value !== memoryImplementation[name]?.value)
2897
2926
  unsupported();
2898
2927
  }
2899
- const retainedRead = Object.getOwnPropertyDescriptor(this, "capabilities")?.value?.retainedRead;
2928
+ const capabilities = Object.getOwnPropertyDescriptor(this, "capabilities");
2929
+ const retainedRead = capabilities === undefined
2930
+ ? Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get
2931
+ : capabilities.value?.retainedRead;
2900
2932
  signal?.throwIfAborted();
2901
2933
  if (retainedRead !== true)
2902
2934
  unsupported();
@@ -3036,7 +3068,7 @@ function stockDescriptorWrite(filesystem) {
3036
3068
  return false;
3037
3069
  const owner = ownedStores.get(filesystem);
3038
3070
  const capDesc = Object.getOwnPropertyDescriptor(filesystem, "capabilities");
3039
- return !!capDesc && "value" in capDesc && capDesc.value === owner.capabilities;
3071
+ return (capDesc === undefined && Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get) || (!!capDesc && "value" in capDesc && capDesc.value === owner.capabilities);
3040
3072
  }
3041
3073
  function stockRetainedResize(filesystem) {
3042
3074
  if (!stockDescriptorWrite(filesystem) || !isStockMemoryMethods(filesystem, stockRetainedResizeMethodNames, false))
@@ -3395,7 +3427,7 @@ export function tryResolveMemoryDevicePath(filesystem, path, resizeCreate) {
3395
3427
  start = slash + 1;
3396
3428
  }
3397
3429
  }
3398
- const cachedMethodValuesMap = new WeakMap();
3430
+ const cachedMethodValuesMap = /* @__PURE__ */ new WeakMap();
3399
3431
  function getExpectedMethodValues(names) {
3400
3432
  let vals = cachedMethodValuesMap.get(names);
3401
3433
  if (!vals) {
@@ -3718,3 +3750,17 @@ export function tryWriteMemoryFileInDirSync(filesystem, dirPrefix, name, data, a
3718
3750
  mem.writeMemoryFileInDirFast(dirPrefix, name, data, append, mode);
3719
3751
  return true;
3720
3752
  }
3753
+ const _memHooks = (globalThis.__safeBashMemHooks ??= { protos: new Set(), byProto: new Map() });
3754
+ _memHooks.protos.add(MemoryFileSystem.prototype);
3755
+ _memHooks.byProto.set(MemoryFileSystem.prototype, {
3756
+ tryGetMemoryDirectoryEntryNamesSync,
3757
+ tryReadMemoryFileViewSync,
3758
+ tryResolveMemoryDevicePath,
3759
+ tryOpenMemoryRedirectHandleSync,
3760
+ tryWriteMemoryFileSync,
3761
+ tryMkdirMemorySync,
3762
+ tryRmRfMemorySync,
3763
+ tryWriteMemoryFileInDirSync,
3764
+ });
3765
+ _memHooks.isCleanAbsolutePath = isCleanAbsolutePath;
3766
+ _memHooks.bindConditionalMutation = bindConditionalMutation;
@@ -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;
@@ -202,7 +202,7 @@ export class ReadOnlyFileSystem {
202
202
  readOnly("openResizeFile", path);
203
203
  }
204
204
  }
205
- const readOnlyImplementation = Object.getOwnPropertyDescriptors(ReadOnlyFileSystem.prototype);
205
+ const readOnlyImplementation = /* @__PURE__ */ Object.getOwnPropertyDescriptors(ReadOnlyFileSystem.prototype);
206
206
  export function createReadOnlyFileSystem(filesystem) {
207
207
  return new ReadOnlyFileSystem(filesystem);
208
208
  }
@@ -1134,4 +1134,4 @@ export class S3FileSystem {
1134
1134
  }
1135
1135
  }
1136
1136
  }
1137
- const s3Comparison = S3FileSystem.prototype.compareEntry;
1137
+ const s3Comparison = /* @__PURE__ */ (() => S3FileSystem.prototype.compareEntry)();
@@ -1374,4 +1374,4 @@ export class WebDavFileSystem {
1374
1374
  }
1375
1375
  async truncate(path, _length, _options = {}) { this.unsupported("truncate", path); }
1376
1376
  }
1377
- const originalWebDavComparison = WebDavFileSystem.prototype.compareEntry;
1377
+ const originalWebDavComparison = /* @__PURE__ */ (() => WebDavFileSystem.prototype.compareEntry)();
@@ -0,0 +1,26 @@
1
+ export * from "./contracts/errors.js";
2
+ export * from "./contracts/filesystem.js";
3
+ export * from "./contracts/io.js";
4
+ export { openFileDescriptor } from "./fs/descriptor.js";
5
+ export type { DescriptorBackend, DescriptorOpenOptions } from "./fs/descriptor.js";
6
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
7
+ export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
8
+ import type { FileSystem } from "./contracts/filesystem.js";
9
+ export type { MemoryFileSystem, MemoryFileSystemLimits, MemoryFileSystemOptions, MemoryRedirectHandle } from "./fs/memory/index.js";
10
+ export declare function isUnmodifiedMemoryFileSystem(fs: FileSystem): boolean;
11
+ export declare function isCleanAbsolutePath(p: string): boolean;
12
+ export declare function tryGetMemoryDirectoryEntryNamesSync(fs: FileSystem, path: string): any;
13
+ export declare function tryReadMemoryFileViewSync(fs: FileSystem, path: string, maxBytes?: number, signal?: AbortSignal, captureSourceRef?: boolean, beforeRead?: () => void): Uint8Array | undefined;
14
+ export declare function tryResolveMemoryDevicePath(fs: FileSystem, path: string, followFinal?: boolean): string | undefined;
15
+ export * from "./fs/devices/index.js";
16
+ export { retargetScopedFileSystem, scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
17
+ export type { RetainedFileSystemCleanupView, RetainedFileSystemCleanupOptions } from "./fs/scoped.js";
18
+ export { compareEntries, registerEntryView } from "./fs/mount/comparison.js";
19
+ export type { EntryViewResolver } from "./fs/mount/comparison.js";
20
+ export type { ConditionalMutationBinding } from "./fs/memory/index.js";
21
+ export declare function bindConditionalMutation<Result>(identityScope: object | symbol | undefined, binding: any, action: () => Promise<Result>): Promise<Result>;
22
+ export declare function tryOpenMemoryRedirectHandleSync(fs: FileSystem, path: string, append: boolean, mode: number, signal?: AbortSignal): any;
23
+ export declare function tryWriteMemoryFileSync(fs: FileSystem, path: string, data: Uint8Array, append: boolean, mode: number, signal?: AbortSignal): boolean;
24
+ export declare function tryMkdirMemorySync(fs: FileSystem, path: string, recursive: boolean, mode: number, signal?: AbortSignal, dirPrefixHint?: string): boolean;
25
+ export declare function tryRmRfMemorySync(fs: FileSystem, path: string, signal?: AbortSignal): boolean;
26
+ export declare function tryWriteMemoryFileInDirSync(fs: FileSystem, dirPrefix: string, name: string, data: Uint8Array, append: boolean, mode: number, signal?: AbortSignal): boolean;
@@ -0,0 +1,56 @@
1
+ export * from "./contracts/errors.js";
2
+ export * from "./contracts/filesystem.js";
3
+ export * from "./contracts/io.js";
4
+ export { openFileDescriptor } from "./fs/descriptor.js";
5
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
6
+ export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
7
+ const _getMemHooks = () => globalThis.__safeBashMemHooks;
8
+ export function isUnmodifiedMemoryFileSystem(fs) {
9
+ const proto = Object.getPrototypeOf(fs);
10
+ if (!proto || !_getMemHooks()?.protos?.has(proto))
11
+ return false;
12
+ return Object.getOwnPropertyNames(fs).every(name => {
13
+ const method = Object.getOwnPropertyDescriptor(proto, name)?.value;
14
+ return typeof method !== "function" || Object.getOwnPropertyDescriptor(fs, name)?.value === method;
15
+ }) && Object.getOwnPropertyNames(proto).every(name => {
16
+ const method = Object.getOwnPropertyDescriptor(proto, name)?.value;
17
+ return typeof method !== "function" || Reflect.get(fs, name) === method;
18
+ });
19
+ }
20
+ export function isCleanAbsolutePath(p) {
21
+ const fn = _getMemHooks()?.isCleanAbsolutePath;
22
+ if (fn)
23
+ return fn(p);
24
+ return p.length > 0 && p.charCodeAt(0) === 47 && !p.includes("//") && !p.includes("/.") && (p.length === 1 || p.charCodeAt(p.length - 1) !== 47);
25
+ }
26
+ export function tryGetMemoryDirectoryEntryNamesSync(fs, path) {
27
+ return (fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryGetMemoryDirectoryEntryNamesSync)?.(fs, path);
28
+ }
29
+ export function tryReadMemoryFileViewSync(fs, path, maxBytes, signal, captureSourceRef, beforeRead) {
30
+ return (fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryReadMemoryFileViewSync)?.(fs, path, maxBytes, signal, captureSourceRef, beforeRead);
31
+ }
32
+ export function tryResolveMemoryDevicePath(fs, path, followFinal) {
33
+ return (fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryResolveMemoryDevicePath)?.(fs, path, followFinal);
34
+ }
35
+ export * from "./fs/devices/index.js";
36
+ export { retargetScopedFileSystem, scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
37
+ export { compareEntries, registerEntryView } from "./fs/mount/comparison.js";
38
+ export async function bindConditionalMutation(identityScope, binding, action) {
39
+ const fn = _getMemHooks()?.bindConditionalMutation;
40
+ return fn ? fn(identityScope, binding, action) : action();
41
+ }
42
+ export function tryOpenMemoryRedirectHandleSync(fs, path, append, mode, signal) {
43
+ return (fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryOpenMemoryRedirectHandleSync)?.(fs, path, append, mode, signal);
44
+ }
45
+ export function tryWriteMemoryFileSync(fs, path, data, append, mode, signal) {
46
+ return Boolean((fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryWriteMemoryFileSync)?.(fs, path, data, append, mode, signal));
47
+ }
48
+ export function tryMkdirMemorySync(fs, path, recursive, mode, signal, dirPrefixHint) {
49
+ return Boolean((fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryMkdirMemorySync)?.(fs, path, recursive, mode, signal, dirPrefixHint));
50
+ }
51
+ export function tryRmRfMemorySync(fs, path, signal) {
52
+ return Boolean((fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryRmRfMemorySync)?.(fs, path, signal));
53
+ }
54
+ export function tryWriteMemoryFileInDirSync(fs, dirPrefix, name, data, append, mode, signal) {
55
+ return Boolean((fs && _getMemHooks()?.byProto?.get(Object.getPrototypeOf(fs))?.tryWriteMemoryFileInDirSync)?.(fs, dirPrefix, name, data, append, mode, signal));
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.804",
3
+ "version": "0.1.805",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -126,6 +126,10 @@
126
126
  "./contracts/errors": {
127
127
  "types": "./dist/safe-fs/contracts/errors.d.ts",
128
128
  "import": "./dist/safe-fs/contracts/errors.js"
129
+ },
130
+ "./runtime-core": {
131
+ "types": "./dist/safe-fs/runtime-core.d.ts",
132
+ "import": "./dist/safe-fs/runtime-core.js"
129
133
  }
130
134
  },
131
135
  "repository": {