@poe-platform/safe-fs 0.1.727 → 0.1.728

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 (57) hide show
  1. package/README.md +8 -1
  2. package/dist/safe-fs/contracts/errors.js +8 -1
  3. package/dist/safe-fs/contracts/filesystem.d.ts +68 -2
  4. package/dist/safe-fs/contracts/io.js +183 -27
  5. package/dist/safe-fs/contracts/virtual-path.d.ts +3 -1
  6. package/dist/safe-fs/contracts/virtual-path.js +20 -1
  7. package/dist/safe-fs/core.d.ts +1 -0
  8. package/dist/safe-fs/core.js +1 -0
  9. package/dist/safe-fs/fs/capabilities.d.ts +1 -1
  10. package/dist/safe-fs/fs/capabilities.js +59 -21
  11. package/dist/safe-fs/fs/conditional-chmod.d.ts +7 -0
  12. package/dist/safe-fs/fs/conditional-chmod.js +19 -0
  13. package/dist/safe-fs/fs/descriptor.js +51 -25
  14. package/dist/safe-fs/fs/devices/index.d.ts +7 -2
  15. package/dist/safe-fs/fs/devices/index.js +210 -29
  16. package/dist/safe-fs/fs/devices/path.d.ts +3 -1
  17. package/dist/safe-fs/fs/devices/path.js +70 -12
  18. package/dist/safe-fs/fs/memory/index.d.ts +34 -48
  19. package/dist/safe-fs/fs/memory/index.js +1150 -235
  20. package/dist/safe-fs/fs/memory/ledger.d.ts +2 -0
  21. package/dist/safe-fs/fs/memory/ledger.js +15 -3
  22. package/dist/safe-fs/fs/memory/missing-target.js +21 -1
  23. package/dist/safe-fs/fs/mount/index.d.ts +11 -3
  24. package/dist/safe-fs/fs/mount/index.js +326 -15
  25. package/dist/safe-fs/fs/object-publication/index.js +79 -3
  26. package/dist/safe-fs/fs/overlay/index.d.ts +2 -2
  27. package/dist/safe-fs/fs/overlay/index.js +22 -2
  28. package/dist/safe-fs/fs/overlay/memory-publication.d.ts +2 -1
  29. package/dist/safe-fs/fs/overlay/memory-publication.js +39 -2
  30. package/dist/safe-fs/fs/quota/index.js +1 -1
  31. package/dist/safe-fs/fs/readonly/index.d.ts +1 -0
  32. package/dist/safe-fs/fs/readonly/index.js +13 -0
  33. package/dist/safe-fs/fs/real/index.d.ts +6 -2
  34. package/dist/safe-fs/fs/real/index.js +78 -8
  35. package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
  36. package/dist/safe-fs/fs/s3/filesystem.js +48 -16
  37. package/dist/safe-fs/fs/s3/http/transport.js +20 -3
  38. package/dist/safe-fs/fs/s3/mock.js +19 -2
  39. package/dist/safe-fs/fs/s3/namespace.js +8 -2
  40. package/dist/safe-fs/fs/s3/registry.js +2 -0
  41. package/dist/safe-fs/fs/scoped.d.ts +1 -0
  42. package/dist/safe-fs/fs/scoped.js +261 -74
  43. package/dist/safe-fs/fs/staging-ancestry.d.ts +8 -0
  44. package/dist/safe-fs/fs/staging-ancestry.js +115 -0
  45. package/dist/safe-fs/fs/staging-cleanup.d.ts +5 -0
  46. package/dist/safe-fs/fs/staging-cleanup.js +63 -0
  47. package/dist/safe-fs/platform/browser.d.ts +5 -0
  48. package/dist/safe-fs/platform/browser.js +17 -0
  49. package/dist/safe-fs/platform/node.d.ts +5 -0
  50. package/dist/safe-fs/platform/node.js +28 -0
  51. package/dist/safe-fs/platform/transport-budget.d.ts +7 -0
  52. package/dist/safe-fs/platform/transport-budget.js +4 -0
  53. package/dist/safe-fs/testing/object-io-metrics.d.ts +3 -0
  54. package/dist/safe-fs/testing/object-io-metrics.js +29 -0
  55. package/dist/safe-fs/testing/object-publication.d.ts +3 -1
  56. package/dist/safe-fs/testing/object-publication.js +2 -2
  57. package/package.json +1 -1
@@ -1,13 +1,19 @@
1
1
  import { FsError, isFsError } from "../../contracts/errors.js";
2
- import { validatePath } from "../../contracts/virtual-path.js";
3
- import { capturePathNamespace } from "../path-namespace.js";
2
+ import { MAX_PATH_COMPONENTS, validatePath } from "../../contracts/virtual-path.js";
3
+ import { capturePathNamespace, pathNamespace } from "../path-namespace.js";
4
+ import { isCleanAbsolutePath, tryResolveMemoryDevicePath } from "../memory/index.js";
4
5
  export const nullPath = "/dev/null";
5
6
  export const deviceDirectory = "/dev";
7
+ function isNullParts(parts) {
8
+ return parts.length === 2 && parts[0] === "dev" && parts[1] === "null";
9
+ }
6
10
  export function lexicalDevicePath(path) {
7
11
  validatePath(path);
12
+ if (isCleanAbsolutePath(path) && !path.startsWith("/dev/null/"))
13
+ return path;
8
14
  const parts = [];
9
15
  for (const component of path.split("/")) {
10
- if (`/${parts.join("/")}` === nullPath)
16
+ if (isNullParts(parts))
11
17
  throw new FsError("ENOTDIR", { path });
12
18
  if (component === "..")
13
19
  parts.pop();
@@ -16,7 +22,7 @@ export function lexicalDevicePath(path) {
16
22
  }
17
23
  return `/${parts.join("/")}`;
18
24
  }
19
- async function resolveResizeDevicePath(filesystem, path, options, create, namespace) {
25
+ async function resolveResizeDevicePath(filesystem, path, options, create, namespace, traversal) {
20
26
  validatePath(path);
21
27
  const components = (value) => {
22
28
  const result = value.split("/").filter(Boolean);
@@ -25,6 +31,9 @@ async function resolveResizeDevicePath(filesystem, path, options, create, namesp
25
31
  return result;
26
32
  };
27
33
  const pending = components(path);
34
+ let remainingComponents = MAX_PATH_COMPONENTS - pending.length;
35
+ if (remainingComponents < 0)
36
+ throw new FsError("ENAMETOOLONG", { path });
28
37
  const parts = [];
29
38
  let links = 0;
30
39
  let expanded = path.length;
@@ -57,12 +66,14 @@ async function resolveResizeDevicePath(filesystem, path, options, create, namesp
57
66
  parts.pop();
58
67
  continue;
59
68
  }
60
- if (new TextEncoder().encode(component).byteLength > 255)
69
+ if (exceedsComponentByteLimit(component))
61
70
  throw new FsError("ENAMETOOLONG", { path });
62
71
  const candidate = `/${[...parts, component].join("/")}`;
63
72
  const selected = namespace === undefined ? undefined : candidate === deviceDirectory || candidate === nullPath ? "/" : namespace(candidate);
64
73
  if (boundary !== undefined && selected !== boundary)
65
74
  throw new FsError("EACCES", { path });
75
+ if (traversal && (candidate === deviceDirectory || candidate === nullPath))
76
+ traversal.virtual = true;
66
77
  if (create && pending.length === 1 && pending[0] === "")
67
78
  throw new FsError("EISDIR", { path });
68
79
  if (candidate !== deviceDirectory && candidate !== nullPath) {
@@ -97,6 +108,9 @@ async function resolveResizeDevicePath(filesystem, path, options, create, namesp
97
108
  const targetParts = components(target);
98
109
  if (targetParts.at(-1) === "" && pending[0] === "")
99
110
  targetParts.pop();
111
+ if (targetParts.length > remainingComponents)
112
+ throw new FsError("ENAMETOOLONG", { path });
113
+ remainingComponents -= targetParts.length;
100
114
  pending.unshift(...targetParts);
101
115
  continue;
102
116
  }
@@ -105,11 +119,36 @@ async function resolveResizeDevicePath(filesystem, path, options, create, namesp
105
119
  }
106
120
  return `/${parts.join("/")}`;
107
121
  }
108
- export async function resolveDevicePath(filesystem, path, options, followFinal = true, resizeCreate) {
122
+ function utf8ByteLength(value) {
123
+ let bytes = 0;
124
+ for (let i = 0; i < value.length; i++) {
125
+ const code = value.charCodeAt(i);
126
+ if (code < 0x80)
127
+ bytes += 1;
128
+ else if (code < 0x800)
129
+ bytes += 2;
130
+ else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length && value.charCodeAt(i + 1) >= 0xdc00 && value.charCodeAt(i + 1) <= 0xdfff) {
131
+ bytes += 4;
132
+ i++;
133
+ }
134
+ else
135
+ bytes += 3;
136
+ }
137
+ return bytes;
138
+ }
139
+ function exceedsComponentByteLimit(value) {
140
+ return value.length > 85 && (value.length > 255 || utf8ByteLength(value) > 255);
141
+ }
142
+ export async function resolveDevicePath(filesystem, path, options, followFinal = true, resizeCreate, traversal) {
109
143
  options.signal?.throwIfAborted();
144
+ if (Reflect.get(filesystem, pathNamespace) === undefined) {
145
+ const fast = tryResolveMemoryDevicePath(filesystem, path, resizeCreate);
146
+ if (fast !== undefined)
147
+ return fast;
148
+ }
110
149
  const namespace = capturePathNamespace(filesystem, options);
111
150
  if (resizeCreate !== undefined)
112
- return resolveResizeDevicePath(filesystem, path, options, resizeCreate, namespace);
151
+ return resolveResizeDevicePath(filesystem, path, options, resizeCreate, namespace, traversal);
113
152
  const lexical = lexicalDevicePath(path);
114
153
  const lstat = filesystem.lstat;
115
154
  options.signal?.throwIfAborted();
@@ -117,15 +156,19 @@ export async function resolveDevicePath(filesystem, path, options, followFinal =
117
156
  if (!aliases && lexical !== nullPath && lexical !== deviceDirectory && lexical !== "/")
118
157
  return lexical;
119
158
  const pending = path.split("/");
159
+ let remainingComponents = MAX_PATH_COMPONENTS - pending.filter(Boolean).length;
160
+ if (remainingComponents < 0)
161
+ throw new FsError("ENAMETOOLONG", { path });
120
162
  const parts = [];
121
163
  let links = 0;
122
164
  let expanded = path.length;
123
165
  let absolute = path.startsWith("/");
124
166
  let traversalFailure;
167
+ let failedDepth = Infinity;
125
168
  let boundary;
126
169
  while (pending.length) {
127
170
  options.signal?.throwIfAborted();
128
- if (`/${parts.join("/")}` === nullPath)
171
+ if (isNullParts(parts))
129
172
  throw new FsError("ENOTDIR", { path });
130
173
  const component = pending.shift();
131
174
  if (!component || component === ".")
@@ -134,13 +177,18 @@ export async function resolveDevicePath(filesystem, path, options, followFinal =
134
177
  if (boundary !== undefined && `/${parts.join("/")}` === boundary)
135
178
  throw new FsError("EACCES", { path });
136
179
  parts.pop();
180
+ if (parts.length < failedDepth) {
181
+ failedDepth = Infinity;
182
+ }
137
183
  continue;
138
184
  }
139
185
  const candidate = `/${[...parts, component].join("/")}`;
140
186
  const selected = namespace === undefined ? undefined : candidate === deviceDirectory || candidate === nullPath ? "/" : namespace(candidate);
141
187
  if (boundary !== undefined && selected !== boundary)
142
188
  throw new FsError("EACCES", { path });
143
- if (candidate !== deviceDirectory && candidate !== nullPath && ((followFinal && aliases) || pending.length)) {
189
+ if (traversal && (candidate === deviceDirectory || candidate === nullPath))
190
+ traversal.virtual = true;
191
+ if (parts.length < failedDepth && candidate !== deviceDirectory && candidate !== nullPath && ((followFinal && aliases) || pending.length)) {
144
192
  const lookup = absolute ? candidate : candidate.slice(1);
145
193
  let stat;
146
194
  try {
@@ -154,8 +202,10 @@ export async function resolveDevicePath(filesystem, path, options, followFinal =
154
202
  return lexical;
155
203
  if (!isFsError(error, "ENOENT"))
156
204
  throw error;
157
- if (pending.length)
205
+ if (pending.length) {
158
206
  traversalFailure ??= error;
207
+ failedDepth = Math.min(failedDepth, parts.length + 1);
208
+ }
159
209
  }
160
210
  options.signal?.throwIfAborted();
161
211
  if (stat?.type === "symlink") {
@@ -172,15 +222,23 @@ export async function resolveDevicePath(filesystem, path, options, followFinal =
172
222
  expanded += target.length;
173
223
  if (expanded > 65536)
174
224
  throw new FsError("ENAMETOOLONG", { path });
225
+ const targetSplit = target.split("/");
226
+ const nonEmptyCount = targetSplit.filter(Boolean).length;
227
+ if (nonEmptyCount > remainingComponents)
228
+ throw new FsError("ENAMETOOLONG", { path });
229
+ remainingComponents -= nonEmptyCount;
175
230
  if (target.startsWith("/")) {
176
231
  parts.splice(0, parts.length, ...(boundary ?? "/").split("/").filter(Boolean));
177
232
  absolute = true;
233
+ failedDepth = Infinity;
178
234
  }
179
- pending.unshift(...target.split("/"));
235
+ pending.unshift(...targetSplit);
180
236
  continue;
181
237
  }
182
- if (stat && pending.length && stat.type !== "directory")
238
+ if (stat && pending.length && stat.type !== "directory") {
183
239
  traversalFailure ??= new FsError("ENOTDIR", { path });
240
+ failedDepth = Math.min(failedDepth, parts.length + 1);
241
+ }
184
242
  }
185
243
  parts.push(component);
186
244
  }
@@ -1,56 +1,23 @@
1
- import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, FileDescriptor, OpenFileOptions, OpenReadFileOptions, OpenResizeFileOptions, WriteFileOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, CreateStagedFileOptions, FileStaging, PublishStagedFileOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
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
3
  import { type MemoryFileSystemOptions } from "./limits.js";
4
4
  export { defaultMemoryFileSystemLimits, type MemoryFileSystemLimits, type MemoryFileSystemOptions } from "./limits.js";
5
+ export interface ConditionalMutationBinding {
6
+ readonly path: string;
7
+ readonly parent: FileStat;
8
+ readonly expected: FileStat;
9
+ readonly ancestors: readonly FileStagingEntry[];
10
+ }
11
+ export declare function bindConditionalMutation<Result>(identityScope: object | symbol | undefined, binding: ConditionalMutationBinding, action: () => Promise<Result>): Promise<Result>;
5
12
  export declare class MemoryFileSystem implements FileSystem {
6
- readonly capabilities: Readonly<{
7
- read: true;
8
- stat: true;
9
- readdir: true;
10
- realpath: true;
11
- access: true;
12
- open: true;
13
- write: true;
14
- append: true;
15
- exclusiveCreate: true;
16
- explicitDirectories: true;
17
- implicitDirectories: false;
18
- mkdir: true;
19
- recursiveMkdir: true;
20
- remove: true;
21
- removeDirectory: true;
22
- recursiveRemove: true;
23
- rename: true;
24
- atomicRenameNoReplace: true;
25
- copy: true;
26
- exclusiveCopy: true;
27
- readlink: true;
28
- truncate: true;
29
- streamingAppend: true;
30
- randomAccessWrite: true;
31
- readOnly: false;
32
- symlinks: true;
33
- hardlinks: true;
34
- permissions: true;
35
- timestamps: true;
36
- atomicRename: true;
37
- atomicFileStaging: true;
38
- atomicStagingAncestry: true;
39
- atomicFileMutation: true;
40
- atomicEntryRemoval: true;
41
- atomicTreeRemoval: true;
42
- atomicDirectoryMetadata: true;
43
- streamingRead: true;
44
- retainedRead: true;
45
- readonly retainedResize: boolean;
46
- streamingWrite: true;
47
- readonly descriptorWriteStream: boolean;
48
- }>;
13
+ capabilitiesFor?: NonNullable<FileSystem["capabilitiesFor"]>;
14
+ readonly capabilities: FileSystemCapabilities;
49
15
  private readonly identityScope;
50
16
  private nextInode;
51
17
  private readonly ledger;
52
18
  private readonly root;
53
19
  private totalBytes;
20
+ symlinkCount: number;
54
21
  constructor(options?: MemoryFileSystemOptions);
55
22
  compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<EntryComparison>;
56
23
  canonicalizeMissingTarget(path: string, options?: FsOptions): string | undefined;
@@ -68,10 +35,12 @@ export declare class MemoryFileSystem implements FileSystem {
68
35
  private mode;
69
36
  private integer;
70
37
  private changed;
38
+ private resolveNode;
71
39
  private resolve;
72
40
  private file;
73
41
  private entry;
74
42
  private terminalDot;
43
+ private directoryNlink;
75
44
  private snapshot;
76
45
  private bytes;
77
46
  private allocate;
@@ -80,15 +49,24 @@ export declare class MemoryFileSystem implements FileSystem {
80
49
  private openWrite;
81
50
  private writeData;
82
51
  private writeAt;
52
+ writeMemoryFileFast(path: string, data: Uint8Array, append: boolean, mode: number): void;
83
53
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
84
54
  private expectEntry;
85
55
  private stagingLocations;
86
56
  createStagedFile(directoryPath: string, name: string, content: StagedFileContent, options: CreateStagedFileOptions): Promise<FileStaging>;
87
- publishStagedFile(staging: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
57
+ private retainStagingCleanup;
58
+ prepareDirectoryAncestry(ancestors: readonly FileStagingEntry[], options?: FsOptions): Promise<() => true>;
59
+ prepareStagingResolution(path: string, options?: FsOptions): Promise<FileStagingResolution>;
60
+ private verifyDirectoryAncestry;
61
+ publishStagedFile(receipt: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
88
62
  removeStagedFile(staging: FileStaging, options?: FsOptions): Promise<void>;
63
+ private removeStagingLocations;
89
64
  prepareDirectory(path: string, options: PrepareDirectoryOptions): Promise<FileStat>;
90
65
  writeFileConditional(path: string, data: Uint8Array, options: ConditionalWriteFileOptions): Promise<FileStat>;
91
- removeEntryConditional(path: string, options: ConditionalRemoveEntryOptions): Promise<void>;
66
+ removeEntryConditional(path: string, options: ConditionalRemoveEntryOptions & {
67
+ readonly returnRemainingStat?: false | undefined;
68
+ }): Promise<void>;
69
+ removeEntryConditional(path: string, options: ConditionalRemoveEntryReceiptOptions): Promise<void | FileStat>;
92
70
  removeFileConditional(path: string, options: ConditionalRemoveFileOptions): Promise<void>;
93
71
  removeTreeConditional(path: string, options: ConditionalRemoveEntryOptions): Promise<void>;
94
72
  writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
@@ -98,7 +76,7 @@ export declare class MemoryFileSystem implements FileSystem {
98
76
  readdir(path: string, options?: ReadDirectoryOptions): Promise<DirectoryEntry[]>;
99
77
  mkdir(path: string, options?: MkdirOptions): Promise<void>;
100
78
  rmdir(path: string, options?: FsOptions): Promise<void>;
101
- rm(path: string, options?: RemoveOptions): Promise<void>;
79
+ rm(path: string, options?: RemoveOptions & Partial<ConditionalMutationBinding>): Promise<void>;
102
80
  private removeLocation;
103
81
  unlink(path: string, options?: FsOptions): Promise<void>;
104
82
  rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
@@ -108,7 +86,7 @@ export declare class MemoryFileSystem implements FileSystem {
108
86
  readlink(path: string, options?: FsOptions): Promise<string>;
109
87
  symlink(target: string, path: string, options?: FsOptions): Promise<void>;
110
88
  link(existingPath: string, newPath: string, options?: FsOptions): Promise<void>;
111
- chmod(path: string, mode: number, options?: FsOptions): Promise<void>;
89
+ chmod(path: string, mode: number, options?: ChmodOptions): Promise<void>;
112
90
  utimes(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
113
91
  truncate(path: string, length?: number, options?: FsOptions): Promise<void>;
114
92
  private resizeNode;
@@ -118,3 +96,11 @@ export declare class MemoryFileSystem implements FileSystem {
118
96
  writeStream(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
119
97
  }
120
98
  export declare function createMemoryFileSystem(options?: MemoryFileSystemOptions | Readonly<Record<string, unknown>>): MemoryFileSystem;
99
+ export declare function utf8ByteLength(value: string): number;
100
+ export declare function isCleanAbsolutePath(path: string): boolean;
101
+ export declare function tryResolveMemoryDevicePath(filesystem: FileSystem, path: string, resizeCreate?: boolean): string | undefined;
102
+ export declare function tryReadMemoryFileViewSync(filesystem: FileSystem, path: string, maxBytes?: number, signal?: AbortSignal): Uint8Array | undefined;
103
+ export declare function tryGetMemoryDirectoryEntryNamesSync(filesystem: FileSystem, path: string): ReadonlyMap<string, {
104
+ readonly type: "file" | "directory" | "symlink";
105
+ }> | undefined;
106
+ export declare function tryWriteMemoryFileSync(filesystem: FileSystem, path: string, data: Uint8Array, append: boolean, mode: number, signal?: AbortSignal): boolean;