@poe-platform/safe-fs 0.1.720 → 0.1.722

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 (55) hide show
  1. package/README.md +64 -18
  2. package/dist/safe-fs/bridge/filesystem.d.ts +2 -0
  3. package/dist/safe-fs/bridge/filesystem.js +17 -7
  4. package/dist/safe-fs/bridge/types.d.ts +2 -0
  5. package/dist/safe-fs/contracts/descriptor.d.ts +5 -0
  6. package/dist/safe-fs/contracts/filesystem.d.ts +12 -0
  7. package/dist/safe-fs/contracts/io.d.ts +3 -1
  8. package/dist/safe-fs/contracts/io.js +48 -16
  9. package/dist/safe-fs/core.d.ts +2 -1
  10. package/dist/safe-fs/core.js +1 -1
  11. package/dist/safe-fs/fs/capabilities.js +6 -3
  12. package/dist/safe-fs/fs/descriptor.js +9 -2
  13. package/dist/safe-fs/fs/devices/index.d.ts +1 -0
  14. package/dist/safe-fs/fs/devices/index.js +24 -6
  15. package/dist/safe-fs/fs/memory/index.d.ts +1 -0
  16. package/dist/safe-fs/fs/memory/index.js +75 -3
  17. package/dist/safe-fs/fs/memory/limits.js +6 -4
  18. package/dist/safe-fs/fs/mount/index.d.ts +4 -0
  19. package/dist/safe-fs/fs/mount/index.js +74 -11
  20. package/dist/safe-fs/fs/object-publication/index.js +6 -6
  21. package/dist/safe-fs/fs/overlay/index.d.ts +4 -2
  22. package/dist/safe-fs/fs/overlay/index.js +92 -36
  23. package/dist/safe-fs/fs/quota/index.js +18 -16
  24. package/dist/safe-fs/fs/readonly/index.js +5 -3
  25. package/dist/safe-fs/fs/real/index.d.ts +21 -1
  26. package/dist/safe-fs/fs/real/index.js +266 -22
  27. package/dist/safe-fs/fs/s3/filesystem.d.ts +8 -0
  28. package/dist/safe-fs/fs/s3/filesystem.js +58 -12
  29. package/dist/safe-fs/fs/s3/http/request.d.ts +1 -1
  30. package/dist/safe-fs/fs/s3/http/request.js +1 -1
  31. package/dist/safe-fs/fs/s3/http/transport.js +8 -6
  32. package/dist/safe-fs/fs/s3/http/xml.d.ts +4 -1
  33. package/dist/safe-fs/fs/s3/http/xml.js +6 -2
  34. package/dist/safe-fs/fs/s3/namespace.js +12 -10
  35. package/dist/safe-fs/fs/scoped.d.ts +1 -0
  36. package/dist/safe-fs/fs/scoped.js +54 -35
  37. package/dist/safe-fs/fs/webdav/webdav.d.ts +4 -1
  38. package/dist/safe-fs/fs/webdav/webdav.js +30 -22
  39. package/dist/safe-fs/node/filesystem.d.ts +4 -1
  40. package/dist/safe-fs/node/filesystem.js +7 -1
  41. package/dist/safe-fs/node/host.d.ts +3 -0
  42. package/dist/safe-fs/node/host.js +78 -0
  43. package/dist/safe-fs/node/index.d.ts +2 -1
  44. package/dist/safe-fs/node/index.js +2 -1
  45. package/dist/safe-fs/platform/browser.d.ts +1 -0
  46. package/dist/safe-fs/platform/browser.js +1 -0
  47. package/dist/safe-fs/platform/node.d.ts +1 -0
  48. package/dist/safe-fs/platform/node.js +1 -0
  49. package/dist/safe-fs/python/emscripten.d.ts +1 -0
  50. package/dist/safe-fs/python/emscripten.js +5 -5
  51. package/dist/safe-fs/python/filesystem.js +5 -5
  52. package/dist/safe-fs/python/native.d.ts +1 -0
  53. package/dist/safe-fs/python/native.js +3 -2
  54. package/dist/safe-fs/xml.js +32 -21
  55. package/package.json +1 -1
@@ -8,9 +8,10 @@ import { compareIdentity } from "../mount/identity.js";
8
8
  import { compareEntries, registerEntryView } from "../mount/comparison.js";
9
9
  import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
10
10
  function snapshotStat(stat) {
11
- const { type, size, allocatedBytes, ioBlockSize, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, opaqueIdentity, opaqueVersion, ino, dev, rdevMajor, rdevMinor, nlink, uid, gid } = stat;
11
+ const { type, filesystemType, size, allocatedBytes, ioBlockSize, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, opaqueIdentity, opaqueVersion, ino, dev, rdevMajor, rdevMinor, nlink, uid, gid } = stat;
12
12
  return {
13
13
  type, size, mode, mtimeMs, atimeMs, ctimeMs,
14
+ ...(filesystemType === undefined ? {} : { filesystemType }),
14
15
  ...(allocatedBytes === undefined ? {} : { allocatedBytes }),
15
16
  ...(ioBlockSize === undefined ? {} : { ioBlockSize }),
16
17
  ...(preferredIoBlockSize === undefined ? {} : { preferredIoBlockSize }),
@@ -80,15 +81,14 @@ export class OverlayFileSystem {
80
81
  const entry = await this.required(path, options);
81
82
  return { filesystem: entry.backend, path: entry.path, readOnly: this.capabilities.readOnly === true };
82
83
  }, false));
83
- this.maxBufferBytes = options.maxBufferBytes ?? 64 * 1024 * 1024;
84
- integer(this.maxBufferBytes, "/");
84
+ this.maxBufferBytes = options.maxBufferBytes ?? Infinity;
85
+ if (options.maxBufferBytes !== undefined)
86
+ integer(options.maxBufferBytes, "/");
85
87
  const writable = !this.#upper.capabilities.readOnly && this.#upper.capabilities.atomicRename === true;
86
- const readable = [this.#upper, this.#lower].map((backend) => typeof backend.readStream === "function" ? backend.capabilities.streamingRead : false);
88
+ const readable = [this.#upper, this.#lower].map((backend) => retainedReadCapabilities(backend).retainedRead);
87
89
  const streamingRead = readable.every((capability) => capability === true) ? true
88
90
  : readable.every((capability) => capability === false) ? false : undefined;
89
- const retained = [this.#upper, this.#lower].map((backend) => retainedReadCapabilities(backend).retainedRead);
90
- const retainedRead = retained.every((capability) => capability === true) ? true
91
- : retained.every((capability) => capability === false) ? false : undefined;
91
+ const retainedRead = streamingRead;
92
92
  const streamingWrite = writable && this.#upper.capabilities.streamingWrite === true
93
93
  && this.#upper.capabilities.streamingRead === true
94
94
  && typeof this.#upper.writeStream === "function" && typeof this.#upper.readStream === "function"
@@ -101,7 +101,7 @@ export class OverlayFileSystem {
101
101
  const effectiveStreamingWrite = requireCapabilities(streamingWrite, mutation);
102
102
  const semantics = Object.fromEntries([
103
103
  ...["read", "stat", "readdir", "realpath", "access"].map(capability => [capability,
104
- requireCapabilities(upper[capability], this.#lower.capabilities[capability])]),
104
+ requireCapabilities(upper[capability], this.#lower.capabilities[capability], ...(capability === "read" ? readable : []))]),
105
105
  ["write", requireCapabilities(mutation, upper.write)],
106
106
  ["exclusiveCreate", requireCapabilities(mutation, upper.exclusiveCreate)],
107
107
  ["mkdir", requireCapabilities(mutation, upper.mkdir)],
@@ -120,7 +120,7 @@ export class OverlayFileSystem {
120
120
  this.capabilities = Object.freeze({
121
121
  ...semantics,
122
122
  open: false,
123
- atomicFilePublication: false, atomicFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
123
+ atomicFilePublication: false, atomicFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false, trustedOwnedStaging: false,
124
124
  implicitDirectories: false,
125
125
  readlink: upper.readlink === true && this.#lower.capabilities.readlink === true ? true
126
126
  : upper.readlink === false && this.#lower.capabilities.readlink === false ? false : undefined,
@@ -169,7 +169,7 @@ export class OverlayFileSystem {
169
169
  }
170
170
  capabilitiesFor(path, options = {}) {
171
171
  return this.run(options, async () => {
172
- const location = await this.resolve(path, options, true, true);
172
+ const location = await this.resolve(path, options, options.create !== undefined || options.creation !== "exclusive", true);
173
173
  const backend = location.entry?.backend ?? this.#upper;
174
174
  const capabilities = await backend.capabilitiesFor?.(location.path, options) ?? backend.capabilities;
175
175
  options.signal?.throwIfAborted();
@@ -188,9 +188,60 @@ export class OverlayFileSystem {
188
188
  fail("EISDIR", path);
189
189
  }
190
190
  this.permission(entry, 4);
191
- return openRetainedReadFile(entry.backend, entry.path, options);
191
+ return this.pinRead(entry, options);
192
192
  }, false);
193
193
  }
194
+ async pinRead(entry, options) {
195
+ // A retained object, not the resolved pathname, carries read authority.
196
+ if (compareIdentity(entry.stat, entry.stat) !== "same")
197
+ fail("ENOTSUP", entry.path, "overlay reads require stable backend identity");
198
+ const ancestors = [];
199
+ let parent = dirname(entry.path);
200
+ while (true) {
201
+ const stat = await entry.backend.lstat(parent, options);
202
+ if (stat.type !== "directory" || compareIdentity(stat, stat) !== "same")
203
+ fail("ENOTSUP", entry.path, "overlay reads require stable directory ancestry");
204
+ ancestors.push({ backend: entry.backend, path: parent, stat });
205
+ if (parent === "/")
206
+ break;
207
+ parent = dirname(parent);
208
+ }
209
+ const handle = await openRetainedReadFile(entry.backend, entry.path, options);
210
+ try {
211
+ if (compareIdentity(entry.stat, await handle.stat(options)) !== "same")
212
+ fail("ENOTSUP", entry.path, "backend read object changed during admission");
213
+ for (const ancestor of ancestors) {
214
+ const stat = await entry.backend.lstat(ancestor.path, options);
215
+ if (stat.type !== "directory" || compareIdentity(ancestor.stat, stat) !== "same")
216
+ fail("ENOTSUP", entry.path, "backend read ancestry changed during admission");
217
+ }
218
+ const current = await this.required(entry.path, options);
219
+ if (current.backend !== entry.backend || compareIdentity(entry.stat, current.stat) !== "same")
220
+ fail("ENOTSUP", entry.path, "overlay read layer changed during admission");
221
+ options.signal?.throwIfAborted();
222
+ return handle;
223
+ }
224
+ catch (error) {
225
+ await finishCleanup(() => handle.close(), true);
226
+ throw error;
227
+ }
228
+ }
229
+ async *handleBytes(handle, options) {
230
+ let position = options.start ?? 0;
231
+ const end = options.endExclusive ?? Number.MAX_SAFE_INTEGER;
232
+ const chunkSize = options.chunkSize ?? 64 * 1024;
233
+ while (position < end) {
234
+ const size = Math.min(chunkSize, end - position);
235
+ const chunk = await handle.read(position, size, options);
236
+ options.signal?.throwIfAborted();
237
+ if (!(chunk instanceof Uint8Array) || chunk.byteLength > size)
238
+ fail("EIO", "", "backend returned invalid retained bytes");
239
+ if (!chunk.byteLength)
240
+ break;
241
+ position += chunk.byteLength;
242
+ yield new Uint8Array(chunk);
243
+ }
244
+ }
194
245
  writable(path) {
195
246
  if (this.#upper.capabilities.readOnly)
196
247
  fail("EROFS", path);
@@ -435,13 +486,16 @@ export class OverlayFileSystem {
435
486
  const limit = Math.min(options.maxBytes ?? this.maxBufferBytes, this.maxBufferBytes);
436
487
  if (entry.stat.size > limit)
437
488
  fail("EFBIG", entry.path);
438
- const result = await entry.backend.readFile(entry.path, { ...options, maxBytes: limit });
439
- options.signal?.throwIfAborted();
440
- if (!(result instanceof Uint8Array))
441
- fail("EIO", entry.path, "backend returned non-byte data");
442
- if (result.byteLength > limit)
443
- fail("EFBIG", entry.path);
444
- return new Uint8Array(result);
489
+ const handle = await this.pinRead(entry, options);
490
+ let failed = true;
491
+ try {
492
+ const result = await collectBytes(this.handleBytes(handle, options), { ...options, ...(limit === Infinity ? {} : { maxBytes: limit }) });
493
+ failed = false;
494
+ return result;
495
+ }
496
+ finally {
497
+ await finishCleanup(() => handle.close(), failed);
498
+ }
445
499
  }
446
500
  async cleanGarbage(strict) {
447
501
  const failures = [];
@@ -501,8 +555,16 @@ export class OverlayFileSystem {
501
555
  this.permission(entry, 4);
502
556
  if (entry.stat.size > this.maxBufferBytes)
503
557
  fail("EFBIG", entry.path);
504
- const source = this.bounded(entry.backend.readStream(entry.path, options), entry.path, options);
505
- await this.streamToUpper(destination, source, { ...options, mode: 0o600, flag: "wx" });
558
+ const handle = await this.pinRead(entry, options);
559
+ let failed = true;
560
+ try {
561
+ const source = this.bounded(this.handleBytes(handle, options), entry.path, options);
562
+ await this.streamToUpper(destination, source, { ...options, mode: 0o600, flag: "wx" });
563
+ failed = false;
564
+ }
565
+ finally {
566
+ await finishCleanup(() => handle.close(), failed);
567
+ }
506
568
  }
507
569
  else
508
570
  await this.#upper.writeFile(destination, await this.bytes(entry, options), { ...options, mode: 0o600, flag: "wx" });
@@ -918,7 +980,7 @@ export class OverlayFileSystem {
918
980
  if (this.#upper.truncate)
919
981
  await this.#upper.truncate(temporary, length, options);
920
982
  else {
921
- const previous = await this.#upper.readFile(temporary, { ...options, maxBytes: this.maxBufferBytes });
983
+ const previous = await this.#upper.readFile(temporary, { ...options, ...(this.maxBufferBytes === Infinity ? {} : { maxBytes: this.maxBufferBytes }) });
922
984
  const bytes = new Uint8Array(length);
923
985
  bytes.set(previous.subarray(0, length));
924
986
  await this.#upper.writeFile(temporary, bytes, options);
@@ -939,27 +1001,21 @@ export class OverlayFileSystem {
939
1001
  if (options.endExclusive < start)
940
1002
  fail("EINVAL", path);
941
1003
  }
942
- const entry = await this.run(options, async () => {
1004
+ const handle = await this.run(options, async () => {
943
1005
  const entry = await this.required(path, options);
944
1006
  if (entry.stat.type !== "file")
945
1007
  fail("EISDIR", path);
946
1008
  this.permission(entry, 4);
947
- return entry;
1009
+ return this.pinRead(entry, options);
948
1010
  });
949
- if (entry.backend.readStream && entry.backend.capabilities.streamingRead !== false) {
950
- for await (const chunk of readBytes(entry.backend.readStream(entry.path, options), options.signal)) {
951
- yield new Uint8Array(chunk);
952
- }
953
- options.signal?.throwIfAborted();
954
- return;
1011
+ let failed = true;
1012
+ try {
1013
+ yield* readBytes(this.handleBytes(handle, options), options.signal);
1014
+ failed = false;
955
1015
  }
956
- const bytes = await this.bytes(entry, options);
957
- const end = Math.min(bytes.byteLength, options.endExclusive ?? bytes.byteLength);
958
- for (let offset = start; offset < end; offset += chunkSize) {
959
- options.signal?.throwIfAborted();
960
- yield bytes.slice(offset, Math.min(end, offset + chunkSize));
1016
+ finally {
1017
+ await finishCleanup(() => handle.close(), failed);
961
1018
  }
962
- options.signal?.throwIfAborted();
963
1019
  }
964
1020
  async *bounded(source, path, options) {
965
1021
  let size = 0;
@@ -1007,7 +1063,7 @@ export class OverlayFileSystem {
1007
1063
  });
1008
1064
  return;
1009
1065
  }
1010
- const bytes = await collectBytes(source, { maxBytes: this.maxBufferBytes, ...(options.signal ? { signal: options.signal } : {}) });
1066
+ const bytes = await collectBytes(source, { ...(this.maxBufferBytes === Infinity ? {} : { maxBytes: this.maxBufferBytes }), ...(options.signal ? { signal: options.signal } : {}) });
1011
1067
  await this.writeFile(path, bytes, options);
1012
1068
  }
1013
1069
  }
@@ -40,7 +40,7 @@ async function scanUsedBytes(fs, limits, options, change, retained = change?.ret
40
40
  if (!directory)
41
41
  break;
42
42
  const signal = options?.signal;
43
- const listing = fs.readdir(directory.path, { ...options, ...(signal ? { signal } : {}), maxEntries: remaining });
43
+ const listing = fs.readdir(directory.path, { ...options, ...(signal ? { signal } : {}), ...(remaining === Infinity ? {} : { maxEntries: remaining }) });
44
44
  const entries = await (retained ? namespaceMetadata(listing, signal) : listing);
45
45
  options?.signal?.throwIfAborted();
46
46
  const count = entries.length;
@@ -136,11 +136,11 @@ export function withFileSystemQuota(fs, options) {
136
136
  if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)
137
137
  throw new RangeError("maxBytes must be a nonnegative safe integer");
138
138
  const scanLimits = {
139
- maxScanEntries: options.maxScanEntries === undefined ? 4096 : options.maxScanEntries,
140
- maxScanDepth: options.maxScanDepth === undefined ? 64 : options.maxScanDepth,
139
+ maxScanEntries: options.maxScanEntries ?? Infinity,
140
+ maxScanDepth: options.maxScanDepth ?? Infinity,
141
141
  };
142
- for (const [name, value] of Object.entries(scanLimits)) {
143
- if (!Number.isSafeInteger(value) || value < 0)
142
+ for (const [name, value] of Object.entries({ maxScanEntries: options.maxScanEntries, maxScanDepth: options.maxScanDepth })) {
143
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
144
144
  throw new RangeError(`${name} must be a nonnegative safe integer`);
145
145
  }
146
146
  let queue = Promise.resolve();
@@ -189,11 +189,11 @@ export function withFileSystemQuota(fs, options) {
189
189
  const mutations = {
190
190
  open(path, openOptions) {
191
191
  return openFileDescriptor(path, openOptions, {
192
- positionedRead: true, positionedWrite: true, truncate: true, synchronization: "storage",
192
+ noFollow: true, positionedRead: true, positionedWrite: true, truncate: true, synchronization: "storage",
193
193
  }, admitted => mutate(async () => {
194
194
  admitted.signal?.throwIfAborted();
195
195
  const open = fs.open;
196
- const capabilities = admitted.creation === "exclusive" ? fs.capabilities : await fs.capabilitiesFor?.(path, admitted) ?? fs.capabilities;
196
+ const capabilities = admitted.noFollow || admitted.creation === "exclusive" ? fs.capabilities : await fs.capabilitiesFor?.(path, admitted) ?? fs.capabilities;
197
197
  admitted.signal?.throwIfAborted();
198
198
  if (!open || capabilities.open === false)
199
199
  throw new FsError("ENOTSUP", { syscall: "open", path });
@@ -389,15 +389,17 @@ export function withFileSystemQuota(fs, options) {
389
389
  await fs.appendFile(path, data, appendOptions);
390
390
  });
391
391
  },
392
- async rename(source, destination, renameOptions) {
393
- if (renameOptions?.noReplace) {
394
- renameOptions.signal?.throwIfAborted();
395
- const capabilities = await fs.capabilitiesFor?.(destination, renameOptions) ?? fs.capabilities;
396
- renameOptions?.signal?.throwIfAborted();
397
- if (capabilities.atomicRenameNoReplace !== true)
398
- throw new FsError("ENOTSUP", { syscall: "rename", path: source, dest: destination });
399
- }
400
- await fs.rename(source, destination, renameOptions);
392
+ rename(source, destination, renameOptions) {
393
+ return mutate(async () => {
394
+ if (renameOptions?.noReplace) {
395
+ renameOptions.signal?.throwIfAborted();
396
+ const capabilities = await fs.capabilitiesFor?.(destination, renameOptions) ?? fs.capabilities;
397
+ renameOptions.signal?.throwIfAborted();
398
+ if (capabilities.atomicRenameNoReplace !== true)
399
+ throw new FsError("ENOTSUP", { syscall: "rename", path: source, dest: destination });
400
+ }
401
+ await fs.rename(source, destination, renameOptions);
402
+ });
401
403
  },
402
404
  copyFile(source, destination, copyOptions) {
403
405
  return mutate(async () => {
@@ -11,9 +11,10 @@ function readOnly(syscall, path, dest) {
11
11
  throw new FsError("EROFS", { syscall, path, ...(dest === undefined ? {} : { dest }) });
12
12
  }
13
13
  function snapshotStat(stat) {
14
- const { type, size, allocatedBytes, ioBlockSize, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, opaqueIdentity, opaqueVersion, ino, dev, rdevMajor, rdevMinor, nlink, uid, gid } = stat;
14
+ const { type, filesystemType, size, allocatedBytes, ioBlockSize, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, opaqueIdentity, opaqueVersion, ino, dev, rdevMajor, rdevMinor, nlink, uid, gid } = stat;
15
15
  return {
16
16
  type, size, mode, mtimeMs, atimeMs, ctimeMs,
17
+ ...(filesystemType === undefined ? {} : { filesystemType }),
17
18
  ...(revision === undefined ? {} : { revision }),
18
19
  ...(allocatedBytes === undefined ? {} : { allocatedBytes }),
19
20
  ...(ioBlockSize === undefined ? {} : { ioBlockSize }),
@@ -72,9 +73,10 @@ export class ReadOnlyFileSystem {
72
73
  || options.creation === "ifMissing" || options.creation === "exclusive"))
73
74
  readOnly("open", path);
74
75
  return openFileDescriptor(path, options, {
75
- positionedRead: true, positionedWrite: false, truncate: false, synchronization: "none",
76
+ noFollow: true, positionedRead: true, positionedWrite: false, truncate: false, synchronization: "none",
76
77
  }, async (admitted) => {
77
- const capabilities = await this.#filesystem.capabilitiesFor?.(path, admitted) ?? this.#filesystem.capabilities;
78
+ const capabilities = admitted.noFollow ? this.#filesystem.capabilities
79
+ : await this.#filesystem.capabilitiesFor?.(path, admitted) ?? this.#filesystem.capabilities;
78
80
  if (!this.#filesystem.open || capabilities.open === false)
79
81
  throw new FsError("ENOTSUP", { syscall: "open", path });
80
82
  admitted.signal?.throwIfAborted();
@@ -1,8 +1,16 @@
1
1
  import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
2
- import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, OpenReadFileOptions, OpenResizeFileOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
2
+ import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, CreateStagedFileOptions, FileStaging, PublishStagedFileOptions, PrepareDirectoryOptions, StagedFileContent, OpenReadFileOptions, OpenResizeFileOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
3
3
  export interface RealFileSystemOptions {
4
4
  /** An existing, absolute host directory. It is never created implicitly. */
5
5
  readonly root: string;
6
+ /**
7
+ * Trusted host primitive for an atomic no-replace move (e.g. renameat2 with
8
+ * RENAME_NOREPLACE). Receives resolved absolute host paths. Must reject EEXIST
9
+ * for any existing entry, including dangling symlinks, without mutation.
10
+ * Never implement with an existence check, ordinary rename, or copy/delete.
11
+ * Unsupported filesystems must reject ENOTSUP; cross-device moves reject EXDEV.
12
+ */
13
+ readonly renameNoReplace?: (source: string, destination: string, options: FsOptions) => Promise<void>;
6
14
  }
7
15
  /**
8
16
  * Async byte filesystem for trusted POSIX hosts (Windows is unsupported).
@@ -42,6 +50,7 @@ export interface RealFileSystemOptions {
42
50
  export declare class RealFileSystem implements FileSystem {
43
51
  readonly capabilities: FileSystemCapabilities;
44
52
  private readonly configuredRoot;
53
+ private readonly renameNoReplace;
45
54
  private rootPromise;
46
55
  constructor(options: RealFileSystemOptions | string);
47
56
  private root;
@@ -50,6 +59,17 @@ export declare class RealFileSystem implements FileSystem {
50
59
  private path;
51
60
  private operation;
52
61
  private protectRoot;
62
+ private stagingSnapshot;
63
+ private expectStaging;
64
+ private stagingMetadata;
65
+ createStagedFile(directoryPath: string, name: string, content: StagedFileContent, options: CreateStagedFileOptions): Promise<FileStaging>;
66
+ private stagingPaths;
67
+ private checkStaging;
68
+ publishStagedFile(staging: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
69
+ removeStagedFile(staging: FileStaging, options?: FsOptions): Promise<void>;
70
+ writeFileConditional(path: string, data: Uint8Array, options: ConditionalWriteFileOptions): Promise<FileStat>;
71
+ removeFileConditional(path: string, options: ConditionalRemoveFileOptions): Promise<void>;
72
+ prepareDirectory(path: string, options: PrepareDirectoryOptions): Promise<FileStat>;
53
73
  open(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
54
74
  private protectTerminal;
55
75
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;