@poe-platform/safe-fs 0.1.153 → 0.1.155

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.
@@ -52,12 +52,18 @@ export interface FileSystemCapabilities {
52
52
  readonly atomicRename?: boolean;
53
53
  readonly snapshotRmdir?: boolean;
54
54
  readonly streamingRead?: boolean;
55
+ readonly retainedRead?: boolean;
55
56
  readonly streamingWrite?: boolean;
56
57
  readonly [capability: string]: boolean | undefined;
57
58
  }
58
59
  export interface FsOptions {
59
60
  readonly signal?: AbortSignal;
60
61
  }
62
+ export interface FileReadHandle {
63
+ stat(options?: FsOptions): Promise<FileStat>;
64
+ read(position: number, maxBytes: number, options?: FsOptions): Promise<Uint8Array>;
65
+ close(): Promise<void>;
66
+ }
61
67
  export interface ReadFileOptions extends FsOptions {
62
68
  readonly maxBytes?: number;
63
69
  }
@@ -96,6 +102,7 @@ export interface ReadStreamOptions extends FsOptions {
96
102
  }
97
103
  export interface FileSystem {
98
104
  readonly capabilities: FileSystemCapabilities;
105
+ openReadFile?(path: string, options?: FsOptions): Promise<FileReadHandle>;
99
106
  canonicalizeMissingTarget?(path: string, options?: FsOptions): string | undefined;
100
107
  capabilitiesFor?(path: string, options?: FsOptions): Promise<FileSystemCapabilities>;
101
108
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
@@ -1,4 +1,6 @@
1
- import type { FileSystemCapabilities } from "../contracts/filesystem.js";
1
+ import type { FileReadHandle, FileSystem, FileSystemCapabilities, FsOptions } from "../contracts/filesystem.js";
2
+ export declare function retainedReadCapabilities(filesystem: FileSystem, capabilities?: FileSystemCapabilities): FileSystemCapabilities;
3
+ export declare function openRetainedReadFile(filesystem: FileSystem, path: string, options: FsOptions): Promise<FileReadHandle>;
2
4
  export declare function requireCapabilities(...values: readonly (boolean | undefined)[]): boolean | undefined;
3
5
  export declare function readOnlyCapabilities(capabilities: FileSystemCapabilities): FileSystemCapabilities;
4
6
  export declare function quotaCapabilities(capabilities: FileSystemCapabilities): FileSystemCapabilities;
@@ -1,10 +1,36 @@
1
+ import { FsError } from "../contracts/errors.js";
2
+ import { finishCleanup } from "../contracts/cleanup.js";
3
+ export function retainedReadCapabilities(filesystem, capabilities = filesystem.capabilities) {
4
+ return typeof filesystem.openReadFile === "function" ? capabilities : { ...capabilities, retainedRead: false };
5
+ }
6
+ export async function openRetainedReadFile(filesystem, path, options) {
7
+ let handle;
8
+ try {
9
+ options.signal?.throwIfAborted();
10
+ if (typeof filesystem.openReadFile !== "function")
11
+ throw new FsError("ENOTSUP", { syscall: "openReadFile", path });
12
+ const capabilities = await filesystem.capabilitiesFor?.(path, options) ?? filesystem.capabilities;
13
+ options.signal?.throwIfAborted();
14
+ if (retainedReadCapabilities(filesystem, capabilities).retainedRead !== true)
15
+ throw new FsError("ENOTSUP", { syscall: "openReadFile", path });
16
+ handle = await filesystem.openReadFile(path, options);
17
+ options.signal?.throwIfAborted();
18
+ return handle;
19
+ }
20
+ catch (error) {
21
+ if (handle)
22
+ await finishCleanup(() => handle.close(), true);
23
+ options.signal?.throwIfAborted();
24
+ throw error;
25
+ }
26
+ }
1
27
  export function requireCapabilities(...values) {
2
28
  return values.some(value => value === false) ? false : values.every(value => value === true) ? true : undefined;
3
29
  }
4
30
  export function readOnlyCapabilities(capabilities) {
5
31
  const inspection = Object.fromEntries([
6
32
  "read", "stat", "readdir", "realpath", "access", "readlink", "explicitDirectories", "implicitDirectories",
7
- "symlinks", "streamingRead",
33
+ "symlinks", "streamingRead", "retainedRead",
8
34
  ].filter(name => capabilities[name] !== undefined).map(name => [name, capabilities[name]]));
9
35
  return Object.freeze({
10
36
  ...inspection, readOnly: true, write: false, append: false, exclusiveCreate: false,
@@ -1,4 +1,4 @@
1
- import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
1
+ import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileStat, FileSystem, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
2
2
  import type { ByteSource } from "../../contracts/io.js";
3
3
  export declare class MemoryFileSystem implements FileSystem {
4
4
  readonly capabilities: Readonly<{
@@ -31,6 +31,7 @@ export declare class MemoryFileSystem implements FileSystem {
31
31
  timestamps: true;
32
32
  atomicRename: true;
33
33
  streamingRead: true;
34
+ retainedRead: true;
34
35
  streamingWrite: true;
35
36
  }>;
36
37
  private readonly identityScope;
@@ -75,6 +76,7 @@ export declare class MemoryFileSystem implements FileSystem {
75
76
  chmod(path: string, mode: number, options?: FsOptions): Promise<void>;
76
77
  utimes(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
77
78
  truncate(path: string, length?: number, options?: FsOptions): Promise<void>;
79
+ openReadFile(path: string, options?: FsOptions): Promise<FileReadHandle>;
78
80
  readStream(path: string, options?: ReadStreamOptions): ByteSource;
79
81
  writeStream(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
80
82
  }
@@ -62,6 +62,7 @@ export class MemoryFileSystem {
62
62
  timestamps: true,
63
63
  atomicRename: true,
64
64
  streamingRead: true,
65
+ retainedRead: true,
65
66
  streamingWrite: true,
66
67
  });
67
68
  identityScope = Symbol();
@@ -512,6 +513,55 @@ export class MemoryFileSystem {
512
513
  node.data = data;
513
514
  this.changed(node);
514
515
  }
516
+ async openReadFile(path, options = {}) {
517
+ options.signal?.throwIfAborted();
518
+ const unsupported = () => { throw new FsError("ENOTSUP", { syscall: "openReadFile", path }); };
519
+ const owner = ownedStores.get(this);
520
+ if (!owner || Object.getPrototypeOf(this) !== MemoryFileSystem.prototype
521
+ || Object.getOwnPropertyDescriptor(this, "root")?.value !== owner.root)
522
+ unsupported();
523
+ for (const name of ["openReadFile", "readFile", "readStream", "stat", "lstat", "realpath", "access",
524
+ "file", "resolve", "permission", "validatePath", "fail", "snapshot", "integer"]) {
525
+ const descriptor = Object.getOwnPropertyDescriptor(this, name)
526
+ ?? Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
527
+ if (!descriptor || !("value" in descriptor) || descriptor.value !== memoryImplementation[name]?.value)
528
+ unsupported();
529
+ }
530
+ if (Object.getOwnPropertyDescriptor(this, "capabilities")?.value?.retainedRead !== true)
531
+ unsupported();
532
+ let node = this.file(path, "openReadFile");
533
+ this.permission(node, 4, "openReadFile", path);
534
+ const snapshot = this.snapshot.bind(this);
535
+ const integer = this.integer.bind(this);
536
+ let closing;
537
+ const current = (signal, syscall) => {
538
+ signal?.throwIfAborted();
539
+ if (!node)
540
+ throw new FsError("EBADF", { syscall, path });
541
+ return node;
542
+ };
543
+ return {
544
+ async stat(options = {}) {
545
+ return snapshot(current(options.signal, "fstat"));
546
+ },
547
+ async read(position, maxBytes, options = {}) {
548
+ const file = current(options.signal, "read");
549
+ integer(position, "read", path);
550
+ integer(maxBytes, "read", path);
551
+ if (maxBytes === 0 || maxBytes > Number.MAX_SAFE_INTEGER - position) {
552
+ throw new FsError("EINVAL", { syscall: "read", path });
553
+ }
554
+ const bytes = file.data.slice(position, position + maxBytes);
555
+ file.atimeMs = Date.now();
556
+ return bytes;
557
+ },
558
+ close() {
559
+ node = undefined;
560
+ closing ??= Promise.resolve();
561
+ return closing;
562
+ },
563
+ };
564
+ }
515
565
  async *readStream(path, options = {}) {
516
566
  options.signal?.throwIfAborted();
517
567
  const start = options.start ?? 0;
@@ -1,4 +1,4 @@
1
- import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
1
+ import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
2
2
  import type { ByteSource } from "../../contracts/io.js";
3
3
  export interface MountFileSystemOptions {
4
4
  readonly root: FileSystem;
@@ -11,6 +11,7 @@ export declare class MountFileSystem implements FileSystem {
11
11
  private select;
12
12
  capabilitiesFor(path: string, options?: FsOptions): Promise<FileSystemCapabilities>;
13
13
  private protected;
14
+ openReadFile(path: string, options?: FsOptions): Promise<FileReadHandle>;
14
15
  private error;
15
16
  private operation;
16
17
  private components;
@@ -1,7 +1,7 @@
1
1
  import { FsError, isFsError, toFsError } from "../../contracts/errors.js";
2
2
  import { readBytes } from "../../contracts/io.js";
3
3
  import { finishCleanup } from "../../contracts/cleanup.js";
4
- import { readOnlyCapabilities } from "../capabilities.js";
4
+ import { openRetainedReadFile, readOnlyCapabilities, retainedReadCapabilities } from "../capabilities.js";
5
5
  import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
6
6
  import { normalizePath, validatePath } from "../../contracts/virtual-path.js";
7
7
  import { compareIdentity } from "./identity.js";
@@ -79,6 +79,7 @@ export class MountFileSystem {
79
79
  const streaming = (capability, method) => all(capability, [method]) ? true : mounts.some(({ backend }) => backend.capabilities[capability] !== false && typeof backend[method] === "function") ? undefined : false;
80
80
  const streamingRead = streaming("streamingRead", "readStream");
81
81
  const streamingWrite = streaming("streamingWrite", "writeStream");
82
+ const retainedRead = streaming("retainedRead", "openReadFile");
82
83
  const append = all("append") ? true
83
84
  : mounts.every(({ backend }) => backend.capabilities.append === false) ? false : undefined;
84
85
  const common = (capability) => {
@@ -110,6 +111,7 @@ export class MountFileSystem {
110
111
  atomicRename: mounts.length === 1 && all("atomicRename"),
111
112
  ...(streamingRead === undefined ? {} : { streamingRead }),
112
113
  ...(streamingWrite === undefined ? {} : { streamingWrite }),
114
+ ...(retainedRead === undefined ? {} : { retainedRead }),
113
115
  });
114
116
  }
115
117
  select(path) {
@@ -118,8 +120,10 @@ export class MountFileSystem {
118
120
  async capabilitiesFor(path, options = {}) {
119
121
  return this.operation("capabilitiesFor", path, options, async () => {
120
122
  const location = await this.resolve(path, options, { allowMissing: true });
121
- const capabilities = await location.mount.backend.capabilitiesFor?.(location.local, options)
123
+ const declared = await location.mount.backend.capabilitiesFor?.(location.local, options)
122
124
  ?? location.mount.backend.capabilities;
125
+ const capabilities = location.synthetic ? { ...declared, retainedRead: false }
126
+ : retainedReadCapabilities(location.mount.backend, declared);
123
127
  if (location.synthetic)
124
128
  return readOnlyCapabilities(capabilities);
125
129
  if (this.mounts.length === 1 || capabilities.readOnly === true)
@@ -136,6 +140,18 @@ export class MountFileSystem {
136
140
  protected(path) {
137
141
  return path === "/" || this.mounts.some((mount) => within(path, mount.path));
138
142
  }
143
+ async openReadFile(path, options = {}) {
144
+ try {
145
+ options.signal?.throwIfAborted();
146
+ const location = await this.resolve(path, options);
147
+ if (location.synthetic)
148
+ fail("EISDIR");
149
+ return await openRetainedReadFile(location.mount.backend, location.local, options);
150
+ }
151
+ catch (error) {
152
+ throw error ? this.error(error, "openReadFile", path, options) : error;
153
+ }
154
+ }
139
155
  error(error, syscall, path, options, dest) {
140
156
  if (options.signal?.aborted && error === options.signal.reason)
141
157
  return error;
@@ -18,6 +18,8 @@ export declare class OverlayFileSystem implements FileSystem {
18
18
  private queue;
19
19
  constructor(options: OverlayFileSystemOptions);
20
20
  private run;
21
+ capabilitiesFor(path: string, options?: FsOptions): Promise<FileSystemCapabilities>;
22
+ openReadFile(path: string, options?: FsOptions): Promise<import("../../contracts/filesystem.js").FileReadHandle>;
21
23
  private writable;
22
24
  private permission;
23
25
  private maybeStat;
@@ -1,5 +1,5 @@
1
1
  import { platform } from "#safe-fs-platform";
2
- import { requireCapabilities } from "../capabilities.js";
2
+ import { openRetainedReadFile, requireCapabilities, retainedReadCapabilities } from "../capabilities.js";
3
3
  import { finishCleanup } from "../../contracts/cleanup.js";
4
4
  import { collectBytes, readBytes } from "../../contracts/io.js";
5
5
  import { FsError, toFsError } from "../../contracts/errors.js";
@@ -79,6 +79,9 @@ export class OverlayFileSystem {
79
79
  const readable = [this.#upper, this.#lower].map((backend) => typeof backend.readStream === "function" ? backend.capabilities.streamingRead : false);
80
80
  const streamingRead = readable.every((capability) => capability === true) ? true
81
81
  : readable.every((capability) => capability === false) ? false : undefined;
82
+ const retained = [this.#upper, this.#lower].map((backend) => retainedReadCapabilities(backend).retainedRead);
83
+ const retainedRead = retained.every((capability) => capability === true) ? true
84
+ : retained.every((capability) => capability === false) ? false : undefined;
82
85
  const streamingWrite = writable && this.#upper.capabilities.streamingWrite === true
83
86
  && this.#upper.capabilities.streamingRead === true
84
87
  && typeof this.#upper.writeStream === "function" && typeof this.#upper.readStream === "function"
@@ -122,6 +125,7 @@ export class OverlayFileSystem {
122
125
  permissions: writable && this.#upper.capabilities.permissions === true && typeof this.#upper.chmod === "function",
123
126
  timestamps: writable && this.#upper.capabilities.timestamps === true && typeof this.#upper.utimes === "function",
124
127
  ...(streamingRead === undefined ? {} : { streamingRead }),
128
+ ...(retainedRead === undefined ? {} : { retainedRead }),
125
129
  ...(effectiveStreamingWrite === undefined ? {} : { streamingWrite: effectiveStreamingWrite }),
126
130
  });
127
131
  Object.defineProperty(this, "capabilities", { writable: false, configurable: false });
@@ -148,6 +152,26 @@ export class OverlayFileSystem {
148
152
  release();
149
153
  }
150
154
  }
155
+ capabilitiesFor(path, options = {}) {
156
+ return this.run(options, async () => {
157
+ const location = await this.resolve(path, options, true, true);
158
+ const backend = location.entry?.backend ?? this.#upper;
159
+ const capabilities = await backend.capabilitiesFor?.(location.path, options) ?? backend.capabilities;
160
+ options.signal?.throwIfAborted();
161
+ const { retainedRead: ignoredRetainedRead, ...composed } = this.capabilities;
162
+ const retainedRead = retainedReadCapabilities(backend, capabilities).retainedRead;
163
+ return Object.freeze({ ...composed, ...(retainedRead === undefined ? {} : { retainedRead }) });
164
+ }, false);
165
+ }
166
+ openReadFile(path, options = {}) {
167
+ return this.run(options, async () => {
168
+ const entry = await this.required(path, options);
169
+ if (entry.stat.type !== "file")
170
+ fail("EISDIR", path);
171
+ this.permission(entry, 4);
172
+ return openRetainedReadFile(entry.backend, entry.path, options);
173
+ }, false);
174
+ }
151
175
  writable(path) {
152
176
  if (this.#upper.capabilities.readOnly)
153
177
  fail("EROFS", path);
@@ -1,5 +1,5 @@
1
1
  import { FsError } from "../../contracts/errors.js";
2
- import { quotaCapabilities } from "../capabilities.js";
2
+ import { openRetainedReadFile, quotaCapabilities, retainedReadCapabilities } from "../capabilities.js";
3
3
  import { admitDirectoryEntries } from "../directory-admission.js";
4
4
  export class FileSystemQuotaError extends Error {
5
5
  maxBytes;
@@ -173,9 +173,14 @@ export function withFileSystemQuota(fs, options) {
173
173
  if (property === "canonicalizeMissingTarget")
174
174
  return undefined;
175
175
  if (property === "capabilities")
176
- return quotaCapabilities(fs.capabilities);
176
+ return quotaCapabilities(retainedReadCapabilities(fs));
177
177
  if (property === "capabilitiesFor")
178
- return async (path, fsOptions) => quotaCapabilities(await fs.capabilitiesFor?.(path, fsOptions) ?? fs.capabilities);
178
+ return async (path, fsOptions) => {
179
+ const capabilities = await fs.capabilitiesFor?.(path, fsOptions) ?? fs.capabilities;
180
+ return quotaCapabilities(retainedReadCapabilities(fs, capabilities));
181
+ };
182
+ if (property === "openReadFile")
183
+ return (path, fsOptions = {}) => openRetainedReadFile(fs, path, fsOptions);
179
184
  const replacement = Reflect.get(mutations, property);
180
185
  if (typeof replacement === "function")
181
186
  return replacement;
@@ -5,6 +5,7 @@ export declare class ReadOnlyFileSystem implements FileSystem {
5
5
  constructor(filesystem: FileSystem);
6
6
  get capabilities(): FileSystemCapabilities;
7
7
  capabilitiesFor(path: string, options?: FsOptions): Promise<FileSystemCapabilities>;
8
+ openReadFile(path: string, options?: FsOptions): Promise<import("../../contracts/filesystem.js").FileReadHandle>;
8
9
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
9
10
  stat(path: string, options?: FsOptions): Promise<FileStat>;
10
11
  lstat(path: string, options?: FsOptions): Promise<FileStat>;
@@ -2,7 +2,7 @@ import { ACCESS_MODES } from "../../contracts/filesystem.js";
2
2
  import { FsError } from "../../contracts/errors.js";
3
3
  import { readBytes } from "../../contracts/io.js";
4
4
  import { compareEntries, registerEntryView } from "../mount/comparison.js";
5
- import { readOnlyCapabilities } from "../capabilities.js";
5
+ import { openRetainedReadFile, readOnlyCapabilities, retainedReadCapabilities } from "../capabilities.js";
6
6
  import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
7
7
  function readOnly(syscall, path, dest) {
8
8
  throw new FsError("EROFS", { syscall, path, ...(dest === undefined ? {} : { dest }) });
@@ -29,7 +29,7 @@ export class ReadOnlyFileSystem {
29
29
  registerEntryView(this, async (path) => ({ filesystem: this.#filesystem, path, readOnly: true }));
30
30
  const streamingRead = typeof filesystem.readStream === "function" ? filesystem.capabilities.streamingRead : false;
31
31
  this.#capabilities = readOnlyCapabilities({
32
- ...filesystem.capabilities,
32
+ ...retainedReadCapabilities(filesystem),
33
33
  readOnly: true,
34
34
  append: false,
35
35
  symlinks: filesystem.capabilities.symlinks === true && typeof filesystem.readlink === "function",
@@ -46,7 +46,10 @@ export class ReadOnlyFileSystem {
46
46
  }
47
47
  async capabilitiesFor(path, options) {
48
48
  const capabilities = await this.#filesystem.capabilitiesFor?.(path, options) ?? this.#filesystem.capabilities;
49
- return readOnlyCapabilities(capabilities);
49
+ return readOnlyCapabilities(retainedReadCapabilities(this.#filesystem, capabilities));
50
+ }
51
+ openReadFile(path, options = {}) {
52
+ return openRetainedReadFile(this.#filesystem, path, options);
50
53
  }
51
54
  async readFile(path, options) {
52
55
  return new Uint8Array(await this.#filesystem.readFile(path, options));
@@ -1,4 +1,4 @@
1
- import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
1
+ import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
2
2
  export interface RealFileSystemOptions {
3
3
  /** An existing, absolute host directory. It is never created implicitly. */
4
4
  readonly root: string;
@@ -69,6 +69,7 @@ export declare class RealFileSystem implements FileSystem {
69
69
  chmod(path: string, mode: number, options?: FsOptions): Promise<void>;
70
70
  utimes(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
71
71
  truncate(path: string, length?: number, options?: FsOptions): Promise<void>;
72
+ openReadFile(path: string, options?: FsOptions): Promise<FileReadHandle>;
72
73
  readStream(path: string, options?: ReadStreamOptions): ByteSource;
73
74
  writeStream(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
74
75
  }
@@ -87,7 +87,7 @@ export class RealFileSystem {
87
87
  rename: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
88
88
  streamingAppend: true, randomAccessWrite: true,
89
89
  readOnly: false, symlinks: true, hardlinks: true, permissions: true,
90
- timestamps: true, atomicRename: true, streamingRead: true, streamingWrite: true,
90
+ timestamps: true, atomicRename: true, streamingRead: true, streamingWrite: true, retainedRead: true,
91
91
  });
92
92
  configuredRoot;
93
93
  rootPromise;
@@ -474,6 +474,116 @@ export class RealFileSystem {
474
474
  }
475
475
  });
476
476
  }
477
+ async openReadFile(path, options = {}) {
478
+ options.signal?.throwIfAborted();
479
+ const assertStock = () => {
480
+ const unsupported = () => { throw new FsError("ENOTSUP", { syscall: "openReadFile", path }); };
481
+ if (Object.getPrototypeOf(this) !== RealFileSystem.prototype)
482
+ unsupported();
483
+ for (const name of ["openReadFile", "readFile", "readStream", "stat", "lstat", "realpath", "access",
484
+ "root", "absoluteTarget", "walk", "path"]) {
485
+ const descriptor = Object.getOwnPropertyDescriptor(this, name)
486
+ ?? Object.getOwnPropertyDescriptor(RealFileSystem.prototype, name);
487
+ if (!descriptor || !("value" in descriptor) || descriptor.value !== realImplementation[name]?.value)
488
+ unsupported();
489
+ }
490
+ if (Object.getOwnPropertyDescriptor(this, "capabilities")?.value?.retainedRead !== true)
491
+ unsupported();
492
+ };
493
+ assertStock();
494
+ const failure = (error, syscall) => error
495
+ ? new FsError(nativeError(error).code, { syscall, path }) : error;
496
+ let resource;
497
+ try {
498
+ const target = await this.path(path, options);
499
+ options.signal?.throwIfAborted();
500
+ assertStock();
501
+ resource = await native.open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
502
+ options.signal?.throwIfAborted();
503
+ const stats = await resource.stat();
504
+ options.signal?.throwIfAborted();
505
+ assertStock();
506
+ if (stats.isDirectory())
507
+ throw new FsError("EISDIR");
508
+ if (!stats.isFile())
509
+ throw new FsError("ENOTSUP");
510
+ }
511
+ catch (error) {
512
+ await finishCleanup(async () => { await resource?.close(); }, true);
513
+ options.signal?.throwIfAborted();
514
+ throw failure(error, "openReadFile");
515
+ }
516
+ const handle = resource;
517
+ const pending = new Set();
518
+ let accepting = true;
519
+ let closing;
520
+ const assertOpen = (options, syscall) => {
521
+ options.signal?.throwIfAborted();
522
+ if (!accepting)
523
+ throw new FsError("EBADF", { syscall, path });
524
+ };
525
+ const perform = async (options, syscall, action) => {
526
+ assertOpen(options, syscall);
527
+ let settled;
528
+ const admitted = new Promise(resolve => { settled = resolve; });
529
+ pending.add(admitted);
530
+ try {
531
+ const value = await action();
532
+ options.signal?.throwIfAborted();
533
+ return value;
534
+ }
535
+ catch (error) {
536
+ options.signal?.throwIfAborted();
537
+ throw failure(error, syscall);
538
+ }
539
+ finally {
540
+ pending.delete(admitted);
541
+ settled();
542
+ }
543
+ };
544
+ return {
545
+ stat(options = {}) {
546
+ return perform(options, "fstat", async () => fileStat(await handle.stat()));
547
+ },
548
+ async read(position, maxBytes, options = {}) {
549
+ assertOpen(options, "read");
550
+ try {
551
+ integer(position);
552
+ integer(maxBytes, 1);
553
+ }
554
+ catch (error) {
555
+ throw failure(error, "read");
556
+ }
557
+ if (maxBytes > Number.MAX_SAFE_INTEGER - position)
558
+ throw new FsError("EINVAL", { syscall: "read", path });
559
+ return perform(options, "read", async () => {
560
+ let bytes;
561
+ try {
562
+ bytes = new Uint8Array(maxBytes);
563
+ }
564
+ catch {
565
+ throw new FsError("EFBIG");
566
+ }
567
+ const { bytesRead } = await handle.read(bytes, 0, bytes.byteLength, position);
568
+ if (!Number.isSafeInteger(bytesRead) || bytesRead < 0 || bytesRead > maxBytes)
569
+ throw new FsError("EIO");
570
+ return bytes.slice(0, bytesRead);
571
+ });
572
+ },
573
+ close() {
574
+ accepting = false;
575
+ closing ??= Promise.all([...pending]).then(async () => {
576
+ try {
577
+ await handle.close();
578
+ }
579
+ catch (error) {
580
+ throw failure(error, "close");
581
+ }
582
+ });
583
+ return closing;
584
+ },
585
+ };
586
+ }
477
587
  async *readStream(path, options = {}) {
478
588
  const syscall = "readStream";
479
589
  let handle;
@@ -565,6 +675,7 @@ export class RealFileSystem {
565
675
  });
566
676
  }
567
677
  }
678
+ const realImplementation = Object.getOwnPropertyDescriptors(RealFileSystem.prototype);
568
679
  /** Construct and validate an existing root before returning the backend. */
569
680
  export async function createRealFileSystem(options) {
570
681
  const filesystem = new RealFileSystem(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.153",
3
+ "version": "0.1.155",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",