@poe-platform/safe-fs 0.1.0

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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/dist/safe-fs/bridge/confinement.d.ts +3 -0
  4. package/dist/safe-fs/bridge/confinement.js +171 -0
  5. package/dist/safe-fs/bridge/filesystem.d.ts +88 -0
  6. package/dist/safe-fs/bridge/filesystem.js +411 -0
  7. package/dist/safe-fs/bridge/index.d.ts +6 -0
  8. package/dist/safe-fs/bridge/index.js +18 -0
  9. package/dist/safe-fs/bridge/stats.d.ts +4 -0
  10. package/dist/safe-fs/bridge/stats.js +37 -0
  11. package/dist/safe-fs/bridge/types.d.ts +75 -0
  12. package/dist/safe-fs/bridge/types.js +1 -0
  13. package/dist/safe-fs/bridge/values.d.ts +6 -0
  14. package/dist/safe-fs/bridge/values.js +65 -0
  15. package/dist/safe-fs/config/memory.d.ts +3 -0
  16. package/dist/safe-fs/config/memory.js +9 -0
  17. package/dist/safe-fs/config/real.d.ts +5 -0
  18. package/dist/safe-fs/config/real.js +16 -0
  19. package/dist/safe-fs/config.d.ts +15 -0
  20. package/dist/safe-fs/config.js +46 -0
  21. package/dist/safe-fs/config.node.d.ts +2 -0
  22. package/dist/safe-fs/config.node.js +22 -0
  23. package/dist/safe-fs/contracts/abort.d.ts +5 -0
  24. package/dist/safe-fs/contracts/abort.js +34 -0
  25. package/dist/safe-fs/contracts/cleanup.d.ts +1 -0
  26. package/dist/safe-fs/contracts/cleanup.js +9 -0
  27. package/dist/safe-fs/contracts/errors.d.ts +50 -0
  28. package/dist/safe-fs/contracts/errors.js +77 -0
  29. package/dist/safe-fs/contracts/filesystem.d.ts +96 -0
  30. package/dist/safe-fs/contracts/filesystem.js +1 -0
  31. package/dist/safe-fs/contracts/index.d.ts +4 -0
  32. package/dist/safe-fs/contracts/index.js +4 -0
  33. package/dist/safe-fs/contracts/io.d.ts +8 -0
  34. package/dist/safe-fs/contracts/io.js +92 -0
  35. package/dist/safe-fs/contracts/path.d.ts +8 -0
  36. package/dist/safe-fs/contracts/path.js +8 -0
  37. package/dist/safe-fs/contracts/portable-path.d.ts +13 -0
  38. package/dist/safe-fs/contracts/portable-path.js +57 -0
  39. package/dist/safe-fs/contracts/virtual-path.d.ts +7 -0
  40. package/dist/safe-fs/contracts/virtual-path.js +66 -0
  41. package/dist/safe-fs/core.d.ts +12 -0
  42. package/dist/safe-fs/core.js +12 -0
  43. package/dist/safe-fs/fs/memory/index.d.ts +58 -0
  44. package/dist/safe-fs/fs/memory/index.js +532 -0
  45. package/dist/safe-fs/fs/mount/comparison.d.ts +19 -0
  46. package/dist/safe-fs/fs/mount/comparison.js +118 -0
  47. package/dist/safe-fs/fs/mount/identity.d.ts +2 -0
  48. package/dist/safe-fs/fs/mount/identity.js +12 -0
  49. package/dist/safe-fs/fs/mount/index.d.ts +44 -0
  50. package/dist/safe-fs/fs/mount/index.js +546 -0
  51. package/dist/safe-fs/fs/overlay/index.d.ts +73 -0
  52. package/dist/safe-fs/fs/overlay/index.js +927 -0
  53. package/dist/safe-fs/fs/readonly/index.d.ts +31 -0
  54. package/dist/safe-fs/fs/readonly/index.js +128 -0
  55. package/dist/safe-fs/fs/real/allocation.d.ts +1 -0
  56. package/dist/safe-fs/fs/real/allocation.js +8 -0
  57. package/dist/safe-fs/fs/real/index.d.ts +76 -0
  58. package/dist/safe-fs/fs/real/index.js +543 -0
  59. package/dist/safe-fs/fs/s3/authority.d.ts +7 -0
  60. package/dist/safe-fs/fs/s3/authority.js +31 -0
  61. package/dist/safe-fs/fs/s3/filesystem.d.ts +93 -0
  62. package/dist/safe-fs/fs/s3/filesystem.js +1024 -0
  63. package/dist/safe-fs/fs/s3/http/index.d.ts +2 -0
  64. package/dist/safe-fs/fs/s3/http/index.js +1 -0
  65. package/dist/safe-fs/fs/s3/http/request.d.ts +16 -0
  66. package/dist/safe-fs/fs/s3/http/request.js +196 -0
  67. package/dist/safe-fs/fs/s3/http/signature.d.ts +20 -0
  68. package/dist/safe-fs/fs/s3/http/signature.js +74 -0
  69. package/dist/safe-fs/fs/s3/http/transport.d.ts +3 -0
  70. package/dist/safe-fs/fs/s3/http/transport.js +455 -0
  71. package/dist/safe-fs/fs/s3/http/types.d.ts +30 -0
  72. package/dist/safe-fs/fs/s3/http/types.js +1 -0
  73. package/dist/safe-fs/fs/s3/http/xml.d.ts +11 -0
  74. package/dist/safe-fs/fs/s3/http/xml.js +188 -0
  75. package/dist/safe-fs/fs/s3/index.d.ts +6 -0
  76. package/dist/safe-fs/fs/s3/index.js +3 -0
  77. package/dist/safe-fs/fs/s3/mock.d.ts +48 -0
  78. package/dist/safe-fs/fs/s3/mock.js +233 -0
  79. package/dist/safe-fs/fs/s3/registry.d.ts +10 -0
  80. package/dist/safe-fs/fs/s3/registry.js +71 -0
  81. package/dist/safe-fs/fs/s3/transport.d.ts +103 -0
  82. package/dist/safe-fs/fs/s3/transport.js +28 -0
  83. package/dist/safe-fs/fs/webdav/index.d.ts +2 -0
  84. package/dist/safe-fs/fs/webdav/index.js +1 -0
  85. package/dist/safe-fs/fs/webdav/resource-id.d.ts +18 -0
  86. package/dist/safe-fs/fs/webdav/resource-id.js +109 -0
  87. package/dist/safe-fs/fs/webdav/webdav.d.ts +101 -0
  88. package/dist/safe-fs/fs/webdav/webdav.js +1226 -0
  89. package/dist/safe-fs/fs/webdav/xml.d.ts +15 -0
  90. package/dist/safe-fs/fs/webdav/xml.js +250 -0
  91. package/dist/safe-fs/index.d.ts +14 -0
  92. package/dist/safe-fs/index.js +12 -0
  93. package/dist/safe-fs/node/filesystem.d.ts +11 -0
  94. package/dist/safe-fs/node/filesystem.js +28 -0
  95. package/dist/safe-fs/node/index.d.ts +2 -0
  96. package/dist/safe-fs/node/index.js +1 -0
  97. package/dist/safe-fs/node/stats.d.ts +1 -0
  98. package/dist/safe-fs/node/stats.js +1 -0
  99. package/dist/safe-fs/node/values.d.ts +1 -0
  100. package/dist/safe-fs/node/values.js +1 -0
  101. package/dist/safe-fs/node-host.d.ts +1 -0
  102. package/dist/safe-fs/node-host.js +1 -0
  103. package/dist/safe-fs/node-unavailable.d.ts +1 -0
  104. package/dist/safe-fs/node-unavailable.js +1 -0
  105. package/dist/safe-fs/platform/browser.d.ts +12 -0
  106. package/dist/safe-fs/platform/browser.js +39 -0
  107. package/dist/safe-fs/platform/node.d.ts +13 -0
  108. package/dist/safe-fs/platform/node.js +26 -0
  109. package/package.json +108 -0
@@ -0,0 +1,12 @@
1
+ export * from "./contracts/errors.js";
2
+ export * from "./contracts/filesystem.js";
3
+ export * from "./contracts/io.js";
4
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
5
+ export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
6
+ export * from "./fs/memory/index.js";
7
+ export * from "./fs/readonly/index.js";
8
+ export * from "./fs/mount/index.js";
9
+ export * from "./fs/overlay/index.js";
10
+ export * from "./fs/webdav/index.js";
11
+ export * from "./bridge/index.js";
12
+ export { compareEntries } from "./fs/mount/comparison.js";
@@ -0,0 +1,12 @@
1
+ export * from "./contracts/errors.js";
2
+ export * from "./contracts/filesystem.js";
3
+ export * from "./contracts/io.js";
4
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
5
+ export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
6
+ export * from "./fs/memory/index.js";
7
+ export * from "./fs/readonly/index.js";
8
+ export * from "./fs/mount/index.js";
9
+ export * from "./fs/overlay/index.js";
10
+ export * from "./fs/webdav/index.js";
11
+ export * from "./bridge/index.js";
12
+ export { compareEntries } from "./fs/mount/comparison.js";
@@ -0,0 +1,58 @@
1
+ import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, MkdirOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
2
+ import type { ByteSource } from "../../contracts/io.js";
3
+ export declare class MemoryFileSystem implements FileSystem {
4
+ readonly capabilities: Readonly<{
5
+ readOnly: false;
6
+ symlinks: true;
7
+ hardlinks: true;
8
+ permissions: true;
9
+ timestamps: true;
10
+ atomicRename: true;
11
+ streamingRead: true;
12
+ streamingWrite: true;
13
+ }>;
14
+ private readonly identityScope;
15
+ private nextInode;
16
+ private readonly root;
17
+ constructor();
18
+ compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<EntryComparison>;
19
+ private metadata;
20
+ private directory;
21
+ private fail;
22
+ private validatePath;
23
+ private permission;
24
+ private mode;
25
+ private integer;
26
+ private changed;
27
+ private resolve;
28
+ private file;
29
+ private entry;
30
+ private terminalDot;
31
+ private snapshot;
32
+ private bytes;
33
+ private allocate;
34
+ private openWrite;
35
+ private append;
36
+ readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
37
+ writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
38
+ appendFile(path: string, data: Uint8Array, options?: AppendFileOptions): Promise<void>;
39
+ stat(path: string, options?: FsOptions): Promise<FileStat>;
40
+ lstat(path: string, options?: FsOptions): Promise<FileStat>;
41
+ readdir(path: string, options?: FsOptions): Promise<DirectoryEntry[]>;
42
+ mkdir(path: string, options?: MkdirOptions): Promise<void>;
43
+ rmdir(path: string, options?: FsOptions): Promise<void>;
44
+ rm(path: string, options?: RemoveOptions): Promise<void>;
45
+ rename(source: string, destination: string, options?: FsOptions): Promise<void>;
46
+ copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
47
+ realpath(path: string, options?: FsOptions): Promise<string>;
48
+ access(path: string, mode?: number, options?: FsOptions): Promise<void>;
49
+ readlink(path: string, options?: FsOptions): Promise<string>;
50
+ symlink(target: string, path: string, options?: FsOptions): Promise<void>;
51
+ link(existingPath: string, newPath: string, options?: FsOptions): Promise<void>;
52
+ chmod(path: string, mode: number, options?: FsOptions): Promise<void>;
53
+ utimes(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
54
+ truncate(path: string, length?: number, options?: FsOptions): Promise<void>;
55
+ readStream(path: string, options?: ReadStreamOptions): ByteSource;
56
+ writeStream(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
57
+ }
58
+ export declare function createMemoryFileSystem(): MemoryFileSystem;
@@ -0,0 +1,532 @@
1
+ import { FsError } from "../../contracts/errors.js";
2
+ import { assertCallbackAuthorityAllowed, compareEntries, registerEntryAuthority } from "../mount/comparison.js";
3
+ import { getOwnedS3Entry } from "../s3/registry.js";
4
+ import { getOwnedWebDavEntry } from "../webdav/resource-id.js";
5
+ const typeModes = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
6
+ const ownedStats = new WeakMap();
7
+ const ownedStores = new WeakMap();
8
+ const registeredAuthorities = new WeakSet();
9
+ const compareOwnedMemory = async (own, peer, options) => {
10
+ options.signal?.throwIfAborted();
11
+ let explicit = false;
12
+ let answer = "unknown";
13
+ const visited = new Set();
14
+ for (const [left, right] of [[own, peer], [peer, own]]) {
15
+ if (!registeredAuthorities.has(left.filesystem) || visited.has(left.filesystem))
16
+ continue;
17
+ visited.add(left.filesystem);
18
+ const comparison = left.filesystem.compareEntry;
19
+ if (comparison === memoryImplementation.compareEntry?.value)
20
+ continue;
21
+ explicit = true;
22
+ if (!comparison)
23
+ continue;
24
+ options.signal?.throwIfAborted();
25
+ assertCallbackAuthorityAllowed();
26
+ const result = await comparison.call(left.filesystem, left.path, right.filesystem, right.path, options);
27
+ options.signal?.throwIfAborted();
28
+ if (result !== "same" && result !== "distinct" && result !== "unknown") {
29
+ throw new FsError("EIO", { path: own.path, dest: peer.path, message: "invalid explicit Memory comparison" });
30
+ }
31
+ if (result === "unknown")
32
+ continue;
33
+ if (answer !== "unknown" && answer !== result) {
34
+ throw new FsError("EIO", { path: own.path, dest: peer.path, message: "conflicting explicit Memory comparisons" });
35
+ }
36
+ answer = result;
37
+ }
38
+ if (explicit)
39
+ return answer;
40
+ const owner = ownedStores.get(own.filesystem);
41
+ const observation = ownedStats.get(own.stat);
42
+ if (!owner?.intact() || observation?.filesystem !== own.filesystem || observation.path !== own.path
43
+ || observation.root !== owner.root)
44
+ return "unknown";
45
+ const qualified = getOwnedS3Entry(peer) ?? getOwnedWebDavEntry(peer);
46
+ options.signal?.throwIfAborted();
47
+ return qualified ? "distinct" : "unknown";
48
+ };
49
+ export class MemoryFileSystem {
50
+ capabilities = Object.freeze({
51
+ readOnly: false,
52
+ symlinks: true,
53
+ hardlinks: true,
54
+ permissions: true,
55
+ timestamps: true,
56
+ atomicRename: true,
57
+ streamingRead: true,
58
+ streamingWrite: true,
59
+ });
60
+ identityScope = Symbol();
61
+ nextInode = 1;
62
+ root = this.directory(0o755);
63
+ constructor() {
64
+ const root = this.root;
65
+ ownedStores.set(this, {
66
+ root,
67
+ intact: () => this.root === root,
68
+ });
69
+ if (this.compareEntry === memoryImplementation.compareEntry?.value) {
70
+ registeredAuthorities.add(this);
71
+ registerEntryAuthority(this, compareOwnedMemory);
72
+ }
73
+ }
74
+ compareEntry(path, peer, peerPath, options = {}) {
75
+ return compareEntries(this, path, peer, peerPath, options);
76
+ }
77
+ metadata(mode) {
78
+ const now = Date.now();
79
+ return {
80
+ mode, ino: this.nextInode++, nlink: 1,
81
+ atimeMs: now, mtimeMs: now, ctimeMs: now, birthtimeMs: now,
82
+ };
83
+ }
84
+ directory(mode) {
85
+ return { ...this.metadata(typeModes.directory | mode), type: "directory", entries: new Map() };
86
+ }
87
+ fail(code, syscall, path, dest) {
88
+ throw new FsError(code, { syscall, path, ...(dest === undefined ? {} : { dest }) });
89
+ }
90
+ validatePath(path, syscall) {
91
+ if (typeof path !== "string" || path.includes("\0"))
92
+ this.fail("EINVAL", syscall, path);
93
+ if (path.length === 0)
94
+ this.fail("ENOENT", syscall, path);
95
+ }
96
+ permission(node, mask, syscall, path) {
97
+ if (((node.mode >> 6) & mask) !== mask)
98
+ this.fail("EACCES", syscall, path);
99
+ }
100
+ mode(mode, fallback, syscall, path) {
101
+ const value = mode ?? fallback;
102
+ if (!Number.isInteger(value) || value < 0 || value > 0o7777)
103
+ this.fail("EINVAL", syscall, path);
104
+ return value;
105
+ }
106
+ integer(value, syscall, path) {
107
+ if (!Number.isSafeInteger(value) || value < 0)
108
+ this.fail("EINVAL", syscall, path);
109
+ }
110
+ changed(node) {
111
+ node.mtimeMs = node.ctimeMs = Date.now();
112
+ }
113
+ resolve(path, syscall, options = {}) {
114
+ this.validatePath(path, syscall);
115
+ const pending = path.split("/").filter(Boolean);
116
+ if (path.endsWith("/"))
117
+ pending.push(".");
118
+ const stack = [{ node: this.root, name: "" }];
119
+ let links = 0;
120
+ while (pending.length > 0) {
121
+ const component = pending.shift();
122
+ const current = stack[stack.length - 1].node;
123
+ if (current.type !== "directory")
124
+ this.fail("ENOTDIR", syscall, path);
125
+ this.permission(current, 1, syscall, path);
126
+ if (component === ".")
127
+ continue;
128
+ if (component === "..") {
129
+ if (stack.length > 1)
130
+ stack.pop();
131
+ continue;
132
+ }
133
+ if (new TextEncoder().encode(component).byteLength > 255)
134
+ this.fail("ENAMETOOLONG", syscall, path);
135
+ let node = current.entries.get(component);
136
+ if (!node && options.createDirectories !== undefined) {
137
+ this.permission(current, 3, syscall, path);
138
+ node = this.directory(options.createDirectories);
139
+ current.entries.set(component, node);
140
+ this.changed(current);
141
+ }
142
+ if (!node) {
143
+ if (options.allowMissing && pending.length === 0) {
144
+ return { node, parent: current, name: component, path: [...stack.map((entry) => entry.name), component].join("/") };
145
+ }
146
+ this.fail("ENOENT", syscall, path);
147
+ }
148
+ if (node.type === "symlink" && (options.followFinal !== false || pending.length > 0)) {
149
+ if (++links > 40)
150
+ this.fail("ELOOP", syscall, path);
151
+ const target = node.target.split("/").filter(Boolean);
152
+ if (node.target.endsWith("/"))
153
+ target.push(".");
154
+ pending.unshift(...target);
155
+ if (node.target.startsWith("/"))
156
+ stack.splice(1);
157
+ continue;
158
+ }
159
+ stack.push({ node, name: component });
160
+ }
161
+ const entry = stack[stack.length - 1];
162
+ return {
163
+ node: entry.node,
164
+ parent: (stack[stack.length - 2]?.node ?? this.root),
165
+ name: entry.name,
166
+ path: stack.map((part) => part.name).join("/") || "/",
167
+ };
168
+ }
169
+ file(path, syscall) {
170
+ const node = this.resolve(path, syscall).node;
171
+ if (node.type !== "file")
172
+ this.fail("EISDIR", syscall, path);
173
+ return node;
174
+ }
175
+ entry(path, syscall, allowMissing = false) {
176
+ this.validatePath(path, syscall);
177
+ const location = this.resolve(path.replace(/\/+$/, "") || "/", syscall, { followFinal: false, allowMissing });
178
+ if (path.endsWith("/") && location.node && location.node.type !== "directory")
179
+ this.fail("ENOTDIR", syscall, path);
180
+ return location;
181
+ }
182
+ terminalDot(path) {
183
+ return /(?:^|\/)\.{1,2}\/*$/.test(path);
184
+ }
185
+ snapshot(node) {
186
+ return {
187
+ type: node.type,
188
+ size: node.type === "file" ? node.data.byteLength
189
+ : node.type === "symlink" ? new TextEncoder().encode(node.target).byteLength : 0,
190
+ mode: node.mode, ...(ownedStores.get(this)?.intact() ? { identityScope: this.identityScope } : {}),
191
+ ino: node.ino, dev: 0, uid: 0, gid: 0,
192
+ nlink: node.type === "directory"
193
+ ? 2 + [...node.entries.values()].filter((entry) => entry.type === "directory").length : node.nlink,
194
+ atimeMs: node.atimeMs, mtimeMs: node.mtimeMs, ctimeMs: node.ctimeMs, birthtimeMs: node.birthtimeMs,
195
+ };
196
+ }
197
+ bytes(data) {
198
+ if (!(data instanceof Uint8Array))
199
+ throw new TypeError("Memory files require Uint8Array data");
200
+ return new Uint8Array(data);
201
+ }
202
+ allocate(length, syscall, path) {
203
+ try {
204
+ return new Uint8Array(length);
205
+ }
206
+ catch (cause) {
207
+ throw new FsError("EFBIG", { syscall, path, cause });
208
+ }
209
+ }
210
+ openWrite(path, options, syscall) {
211
+ const flag = options.flag ?? "w";
212
+ if (!["w", "wx", "a", "ax"].includes(flag))
213
+ this.fail("EINVAL", syscall, path);
214
+ const mode = this.mode(options.mode, 0o666, syscall, path);
215
+ const exclusive = flag === "wx" || flag === "ax";
216
+ const location = this.resolve(path, syscall, { followFinal: !exclusive, allowMissing: true });
217
+ if (location.node) {
218
+ if (exclusive)
219
+ this.fail("EEXIST", syscall, path);
220
+ if (location.node.type !== "file")
221
+ this.fail("EISDIR", syscall, path);
222
+ this.permission(location.node, 2, syscall, path);
223
+ return location.node;
224
+ }
225
+ this.permission(location.parent, 3, syscall, path);
226
+ const node = { ...this.metadata(typeModes.file | mode), type: "file", data: new Uint8Array() };
227
+ location.parent.entries.set(location.name, node);
228
+ this.changed(location.parent);
229
+ return node;
230
+ }
231
+ append(node, data, syscall, path) {
232
+ const length = node.data.byteLength + data.byteLength;
233
+ let storage;
234
+ if (length <= node.data.buffer.byteLength) {
235
+ storage = new Uint8Array(node.data.buffer);
236
+ }
237
+ else {
238
+ storage = this.allocate(Math.max(length, node.data.byteLength * 2, 64), syscall, path);
239
+ storage.set(node.data);
240
+ }
241
+ storage.set(data, node.data.byteLength);
242
+ node.data = storage.subarray(0, length);
243
+ this.changed(node);
244
+ }
245
+ async readFile(path, options = {}) {
246
+ options.signal?.throwIfAborted();
247
+ if (options.maxBytes !== undefined)
248
+ this.integer(options.maxBytes, "readFile", path);
249
+ const node = this.file(path, "readFile");
250
+ this.permission(node, 4, "readFile", path);
251
+ if (options.maxBytes !== undefined && node.data.byteLength > options.maxBytes)
252
+ this.fail("EFBIG", "readFile", path);
253
+ node.atimeMs = Date.now();
254
+ return new Uint8Array(node.data);
255
+ }
256
+ async writeFile(path, data, options = {}) {
257
+ options.signal?.throwIfAborted();
258
+ const copied = this.bytes(data);
259
+ const node = this.openWrite(path, options, "writeFile");
260
+ if (options.flag === "a" || options.flag === "ax")
261
+ this.append(node, copied, "writeFile", path);
262
+ else {
263
+ node.data = copied;
264
+ this.changed(node);
265
+ }
266
+ }
267
+ async appendFile(path, data, options = {}) {
268
+ options.signal?.throwIfAborted();
269
+ const copied = this.bytes(data);
270
+ this.append(this.openWrite(path, { ...options, flag: "a" }, "appendFile"), copied, "appendFile", path);
271
+ }
272
+ async stat(path, options = {}) {
273
+ options.signal?.throwIfAborted();
274
+ return this.snapshot(this.resolve(path, "stat").node);
275
+ }
276
+ async lstat(path, options = {}) {
277
+ options.signal?.throwIfAborted();
278
+ const stat = this.snapshot(this.resolve(path, "lstat", { followFinal: false }).node);
279
+ ownedStats.set(stat, { filesystem: this, path, root: this.root });
280
+ return stat;
281
+ }
282
+ async readdir(path, options = {}) {
283
+ options.signal?.throwIfAborted();
284
+ const node = this.resolve(path, "readdir").node;
285
+ if (node.type !== "directory")
286
+ this.fail("ENOTDIR", "readdir", path);
287
+ this.permission(node, 4, "readdir", path);
288
+ node.atimeMs = Date.now();
289
+ return [...node.entries].map(([name, entry]) => ({ name, type: entry.type }))
290
+ .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
291
+ }
292
+ async mkdir(path, options = {}) {
293
+ options.signal?.throwIfAborted();
294
+ this.validatePath(path, "mkdir");
295
+ const mode = this.mode(options.mode, 0o777, "mkdir", path);
296
+ if (options.recursive) {
297
+ const node = this.resolve(path, "mkdir", { createDirectories: mode }).node;
298
+ if (node.type !== "directory")
299
+ this.fail("EEXIST", "mkdir", path);
300
+ return;
301
+ }
302
+ const location = this.resolve(path.replace(/\/+$/, "") || "/", "mkdir", { allowMissing: true, followFinal: false });
303
+ if (location.node)
304
+ this.fail("EEXIST", "mkdir", path);
305
+ this.permission(location.parent, 3, "mkdir", path);
306
+ location.parent.entries.set(location.name, this.directory(mode));
307
+ this.changed(location.parent);
308
+ }
309
+ async rmdir(path, options = {}) {
310
+ options.signal?.throwIfAborted();
311
+ const location = this.entry(path, "rmdir");
312
+ const node = location.node;
313
+ if (this.terminalDot(path))
314
+ this.fail("EINVAL", "rmdir", path);
315
+ if (node === this.root)
316
+ this.fail("EBUSY", "rmdir", path);
317
+ if (node.type !== "directory")
318
+ this.fail("ENOTDIR", "rmdir", path);
319
+ this.permission(location.parent, 3, "rmdir", path);
320
+ if (node.entries.size > 0)
321
+ this.fail("ENOTEMPTY", "rmdir", path);
322
+ location.parent.entries.delete(location.name);
323
+ node.nlink = 0;
324
+ node.ctimeMs = Date.now();
325
+ this.changed(location.parent);
326
+ }
327
+ async rm(path, options = {}) {
328
+ options.signal?.throwIfAborted();
329
+ let location;
330
+ try {
331
+ location = this.entry(path, "rm");
332
+ }
333
+ catch (error) {
334
+ if (options.force && error instanceof FsError && error.code === "ENOENT")
335
+ return;
336
+ throw error;
337
+ }
338
+ const node = location.node;
339
+ if (this.terminalDot(path))
340
+ this.fail("EINVAL", "rm", path);
341
+ if (node === this.root)
342
+ this.fail("EBUSY", "rm", path);
343
+ this.permission(location.parent, 3, "rm", path);
344
+ if (node.type === "directory" && !options.recursive)
345
+ this.fail("EISDIR", "rm", path);
346
+ const removed = [node];
347
+ for (let index = 0; index < removed.length; index++) {
348
+ const entry = removed[index];
349
+ if (entry.type === "directory" && entry.entries.size > 0) {
350
+ this.permission(entry, 7, "rm", path);
351
+ for (const child of entry.entries.values())
352
+ removed.push(child);
353
+ }
354
+ }
355
+ location.parent.entries.delete(location.name);
356
+ for (const entry of removed) {
357
+ entry.nlink--;
358
+ entry.ctimeMs = Date.now();
359
+ }
360
+ this.changed(location.parent);
361
+ }
362
+ async rename(source, destination, options = {}) {
363
+ options.signal?.throwIfAborted();
364
+ try {
365
+ const origin = this.entry(source, "rename");
366
+ const target = this.entry(destination, "rename", true);
367
+ const node = origin.node;
368
+ if (this.terminalDot(source) || this.terminalDot(destination))
369
+ this.fail("EINVAL", "rename", source, destination);
370
+ if (node === this.root || target.node === this.root)
371
+ this.fail("EBUSY", "rename", source, destination);
372
+ if (destination.endsWith("/") && node.type !== "directory")
373
+ this.fail("ENOTDIR", "rename", source, destination);
374
+ if (target.node === node)
375
+ return;
376
+ if (node.type === "directory" && target.path.startsWith(`${origin.path}/`))
377
+ this.fail("EINVAL", "rename", source, destination);
378
+ this.permission(origin.parent, 3, "rename", source);
379
+ this.permission(target.parent, 3, "rename", destination);
380
+ if (target.node) {
381
+ if (node.type === "directory" && target.node.type !== "directory")
382
+ this.fail("ENOTDIR", "rename", source, destination);
383
+ if (node.type !== "directory" && target.node.type === "directory")
384
+ this.fail("EISDIR", "rename", source, destination);
385
+ if (target.node.type === "directory" && target.node.entries.size > 0)
386
+ this.fail("ENOTEMPTY", "rename", source, destination);
387
+ target.node.nlink--;
388
+ target.node.ctimeMs = Date.now();
389
+ }
390
+ origin.parent.entries.delete(origin.name);
391
+ target.parent.entries.set(target.name, node);
392
+ this.changed(origin.parent);
393
+ this.changed(target.parent);
394
+ node.ctimeMs = Date.now();
395
+ }
396
+ catch (error) {
397
+ if (error instanceof FsError)
398
+ throw new FsError(error.code, { syscall: "rename", path: source, dest: destination, cause: error });
399
+ throw error;
400
+ }
401
+ }
402
+ async copyFile(source, destination, options = {}) {
403
+ options.signal?.throwIfAborted();
404
+ try {
405
+ const origin = this.file(source, "copyFile");
406
+ this.permission(origin, 4, "copyFile", source);
407
+ const target = this.resolve(destination, "copyFile", { followFinal: !options.exclusive, allowMissing: true });
408
+ if (target.node && options.exclusive)
409
+ this.fail("EEXIST", "copyFile", source, destination);
410
+ if (target.node === origin)
411
+ this.fail("EINVAL", "copyFile", source, destination);
412
+ const node = this.openWrite(destination, { mode: origin.mode & 0o7777, flag: options.exclusive ? "wx" : "w" }, "copyFile");
413
+ node.data = new Uint8Array(origin.data);
414
+ node.mode = origin.mode;
415
+ this.changed(node);
416
+ origin.atimeMs = Date.now();
417
+ }
418
+ catch (error) {
419
+ if (error instanceof FsError)
420
+ throw new FsError(error.code, { syscall: "copyFile", path: source, dest: destination, cause: error });
421
+ throw error;
422
+ }
423
+ }
424
+ async realpath(path, options = {}) {
425
+ options.signal?.throwIfAborted();
426
+ return this.resolve(path, "realpath").path;
427
+ }
428
+ async access(path, mode = 0, options = {}) {
429
+ options.signal?.throwIfAborted();
430
+ if (!Number.isInteger(mode) || mode < 0 || mode > 7)
431
+ this.fail("EINVAL", "access", path);
432
+ this.permission(this.resolve(path, "access").node, mode, "access", path);
433
+ }
434
+ async readlink(path, options = {}) {
435
+ options.signal?.throwIfAborted();
436
+ const node = this.resolve(path, "readlink", { followFinal: false }).node;
437
+ if (node.type !== "symlink")
438
+ this.fail("EINVAL", "readlink", path);
439
+ return node.target;
440
+ }
441
+ async symlink(target, path, options = {}) {
442
+ options.signal?.throwIfAborted();
443
+ this.validatePath(target, "symlink");
444
+ const location = this.resolve(path, "symlink", { followFinal: false, allowMissing: true });
445
+ if (location.node)
446
+ this.fail("EEXIST", "symlink", path);
447
+ this.permission(location.parent, 3, "symlink", path);
448
+ location.parent.entries.set(location.name, { ...this.metadata(typeModes.symlink | 0o777), type: "symlink", target });
449
+ this.changed(location.parent);
450
+ }
451
+ async link(existingPath, newPath, options = {}) {
452
+ options.signal?.throwIfAborted();
453
+ const node = this.resolve(existingPath, "link", { followFinal: false }).node;
454
+ if (node.type === "directory")
455
+ this.fail("EPERM", "link", existingPath, newPath);
456
+ const target = this.resolve(newPath, "link", { followFinal: false, allowMissing: true });
457
+ if (target.node)
458
+ this.fail("EEXIST", "link", existingPath, newPath);
459
+ this.permission(target.parent, 3, "link", newPath);
460
+ target.parent.entries.set(target.name, node);
461
+ node.nlink++;
462
+ node.ctimeMs = Date.now();
463
+ this.changed(target.parent);
464
+ }
465
+ async chmod(path, mode, options = {}) {
466
+ options.signal?.throwIfAborted();
467
+ const permissions = this.mode(mode, 0, "chmod", path);
468
+ const node = this.resolve(path, "chmod").node;
469
+ node.mode = typeModes[node.type] | permissions;
470
+ node.ctimeMs = Date.now();
471
+ }
472
+ async utimes(path, atimeMs, mtimeMs, options = {}) {
473
+ options.signal?.throwIfAborted();
474
+ if (!Number.isFinite(atimeMs) || !Number.isFinite(mtimeMs))
475
+ this.fail("EINVAL", "utimes", path);
476
+ const node = this.resolve(path, "utimes").node;
477
+ node.atimeMs = atimeMs;
478
+ node.mtimeMs = mtimeMs;
479
+ node.ctimeMs = Date.now();
480
+ }
481
+ async truncate(path, length = 0, options = {}) {
482
+ options.signal?.throwIfAborted();
483
+ this.integer(length, "truncate", path);
484
+ const node = this.file(path, "truncate");
485
+ this.permission(node, 2, "truncate", path);
486
+ const data = this.allocate(length, "truncate", path);
487
+ data.set(node.data.subarray(0, length));
488
+ node.data = data;
489
+ this.changed(node);
490
+ }
491
+ async *readStream(path, options = {}) {
492
+ options.signal?.throwIfAborted();
493
+ const start = options.start ?? 0;
494
+ const chunkSize = options.chunkSize ?? 64 * 1024;
495
+ this.integer(start, "readStream", path);
496
+ this.integer(chunkSize, "readStream", path);
497
+ if (chunkSize === 0)
498
+ this.fail("EINVAL", "readStream", path);
499
+ if (options.endExclusive !== undefined) {
500
+ this.integer(options.endExclusive, "readStream", path);
501
+ if (options.endExclusive < start)
502
+ this.fail("EINVAL", "readStream", path);
503
+ }
504
+ const node = this.file(path, "readStream");
505
+ this.permission(node, 4, "readStream", path);
506
+ const data = node.data;
507
+ const end = Math.min(options.endExclusive ?? data.byteLength, data.byteLength);
508
+ node.atimeMs = Date.now();
509
+ for (let offset = start; offset < end; offset += chunkSize) {
510
+ options.signal?.throwIfAborted();
511
+ yield data.slice(offset, Math.min(offset + chunkSize, end));
512
+ }
513
+ options.signal?.throwIfAborted();
514
+ }
515
+ async writeStream(path, source, options = {}) {
516
+ options.signal?.throwIfAborted();
517
+ const node = this.openWrite(path, options, "writeStream");
518
+ if (options.flag !== "a" && options.flag !== "ax") {
519
+ node.data = new Uint8Array();
520
+ this.changed(node);
521
+ }
522
+ for await (const chunk of source) {
523
+ options.signal?.throwIfAborted();
524
+ this.append(node, this.bytes(chunk), "writeStream", path);
525
+ }
526
+ options.signal?.throwIfAborted();
527
+ }
528
+ }
529
+ const memoryImplementation = Object.getOwnPropertyDescriptors(MemoryFileSystem.prototype);
530
+ export function createMemoryFileSystem() {
531
+ return new MemoryFileSystem();
532
+ }
@@ -0,0 +1,19 @@
1
+ import type { EntryComparison, FileStat, FileSystem, FsOptions } from "../../contracts/filesystem.js";
2
+ export interface EntryLocation {
3
+ readonly filesystem: FileSystem;
4
+ readonly path: string;
5
+ readonly readOnly?: boolean;
6
+ readonly stat?: FileStat;
7
+ }
8
+ export interface EntryView extends EntryLocation {
9
+ readonly stat: FileStat;
10
+ readonly readOnly: boolean;
11
+ }
12
+ export type EntryViewResolver = (path: string, options: FsOptions) => Promise<EntryLocation>;
13
+ export type EntryAuthority = (own: EntryView, peer: EntryView, options: FsOptions) => Promise<EntryComparison>;
14
+ export declare function assertCallbackAuthorityAllowed(): void;
15
+ export declare function registerEntryView(filesystem: FileSystem, resolver: EntryViewResolver): void;
16
+ export declare function registerEntryAuthority(filesystem: FileSystem, authority: EntryAuthority): void;
17
+ export declare function resolveEntryView(filesystem: FileSystem, path: string, options?: FsOptions): Promise<EntryView>;
18
+ export declare function compareResolvedEntries(own: EntryView, peer: EntryView, options?: FsOptions): Promise<EntryComparison>;
19
+ export declare function compareEntries(filesystem: FileSystem, path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<EntryComparison>;