@poe-platform/safe-fs 0.1.538 → 0.1.540
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.
- package/dist/safe-fs/contracts/filesystem.d.ts +19 -0
- package/dist/safe-fs/fs/capabilities.js +2 -2
- package/dist/safe-fs/fs/devices/index.d.ts +2 -1
- package/dist/safe-fs/fs/devices/index.js +20 -3
- package/dist/safe-fs/fs/mount/index.d.ts +2 -1
- package/dist/safe-fs/fs/mount/index.js +23 -4
- package/dist/safe-fs/fs/overlay/index.js +1 -1
- package/dist/safe-fs/fs/quota/index.js +1 -1
- package/dist/safe-fs/fs/scoped.js +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,7 @@ export interface FileSystemCapabilities {
|
|
|
56
56
|
readonly streamingRead?: boolean;
|
|
57
57
|
readonly retainedRead?: boolean;
|
|
58
58
|
readonly retainedResize?: boolean;
|
|
59
|
+
readonly atomicResize?: boolean;
|
|
59
60
|
readonly streamingWrite?: boolean;
|
|
60
61
|
readonly descriptorWriteStream?: boolean;
|
|
61
62
|
readonly [capability: string]: boolean | undefined;
|
|
@@ -79,6 +80,22 @@ export interface OpenResizeFileOptions extends FsOptions {
|
|
|
79
80
|
readonly create?: boolean;
|
|
80
81
|
readonly mode?: number;
|
|
81
82
|
}
|
|
83
|
+
/** A single atomic read/compute/resize operation, never a caller-side stat/write pair.
|
|
84
|
+
* size and referenceSize use signed 64-bit byte integers (referenceSize is nonnegative).
|
|
85
|
+
* ioBlocks scales size by the target's preferred I/O block size before applying the
|
|
86
|
+
* modifier. Relative/min/max/round operations use referenceSize or the current size.
|
|
87
|
+
* Signed overflow rejects; negative final sizes clamp to zero. Providers enforce
|
|
88
|
+
* their size/quota/permission limits before allocation or publication. */
|
|
89
|
+
export interface FileResizeOperation {
|
|
90
|
+
readonly size: bigint;
|
|
91
|
+
readonly modifier: "absolute" | "relative" | "minimum" | "maximum" | "down" | "up";
|
|
92
|
+
readonly referenceSize?: bigint;
|
|
93
|
+
readonly ioBlocks?: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface FileResizeOptions extends FsOptions {
|
|
96
|
+
readonly create?: boolean;
|
|
97
|
+
readonly mode?: number;
|
|
98
|
+
}
|
|
82
99
|
export interface FileResizeHandle {
|
|
83
100
|
stat(options?: FsOptions): Promise<FileStat>;
|
|
84
101
|
truncate(length: number, options?: FsOptions): Promise<void>;
|
|
@@ -128,6 +145,8 @@ export interface FileSystem {
|
|
|
128
145
|
readonly capabilities: FileSystemCapabilities;
|
|
129
146
|
openReadFile?(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
130
147
|
openResizeFile?(path: string, options?: OpenResizeFileOptions): Promise<FileResizeHandle>;
|
|
148
|
+
/** Missing targets reject ENOENT unless create is true; existing bytes survive refusals. */
|
|
149
|
+
resizeFile?(path: string, operation: FileResizeOperation, options?: FileResizeOptions): Promise<void>;
|
|
131
150
|
canonicalizeMissingTarget?(path: string, options?: FsOptions): string | undefined;
|
|
132
151
|
capabilitiesFor?(path: string, options?: CapabilityQueryOptions): Promise<FileSystemCapabilities>;
|
|
133
152
|
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
@@ -108,7 +108,7 @@ export function readOnlyCapabilities(capabilities) {
|
|
|
108
108
|
mkdir: false, recursiveMkdir: false, remove: false, removeDirectory: false, recursiveRemove: false,
|
|
109
109
|
rename: false, copy: false, exclusiveCopy: false, truncate: false, streamingAppend: false,
|
|
110
110
|
randomAccessWrite: false, hardlinks: false, permissions: false, timestamps: false,
|
|
111
|
-
descriptorWriteStream: false, retainedResize: false,
|
|
111
|
+
descriptorWriteStream: false, retainedResize: false, atomicResize: false,
|
|
112
112
|
atomicRename: false, atomicRenameNoReplace: false, streamingWrite: false,
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -116,7 +116,7 @@ export function quotaCapabilities(capabilities) {
|
|
|
116
116
|
const streamingWrite = requireCapabilities(capabilities.write, capabilities.append, !capabilities.readOnly);
|
|
117
117
|
const streamingAppend = requireCapabilities(capabilities.append, !capabilities.readOnly);
|
|
118
118
|
const { streamingWrite: ignoredWrite, streamingAppend: ignoredAppend, ...rest } = capabilities;
|
|
119
|
-
return Object.freeze({ ...rest, descriptorWriteStream: false,
|
|
119
|
+
return Object.freeze({ ...rest, descriptorWriteStream: false, atomicResize: false,
|
|
120
120
|
...(streamingWrite === undefined ? {} : { streamingWrite }),
|
|
121
121
|
...(streamingAppend === undefined ? {} : { streamingAppend }),
|
|
122
122
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
2
2
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
3
|
export declare class DeviceFileSystem implements FileSystem {
|
|
4
4
|
#private;
|
|
@@ -13,6 +13,7 @@ export declare class DeviceFileSystem implements FileSystem {
|
|
|
13
13
|
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
14
14
|
readStream(path: string, options?: ReadStreamOptions): ByteSource;
|
|
15
15
|
openReadFile(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
16
|
+
resizeFile(path: string, operation: FileResizeOperation, options?: FileResizeOptions): Promise<void>;
|
|
16
17
|
openResizeFile(path: string, options?: OpenResizeFileOptions): Promise<FileResizeHandle>;
|
|
17
18
|
writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
18
19
|
appendFile(path: string, data: Uint8Array, options?: AppendFileOptions): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FsError, isFsError } from "../../contracts/errors.js";
|
|
2
2
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
3
|
-
import { compareEntries, registerEntryView } from "../mount/comparison.js";
|
|
3
|
+
import { compareEntries, registerEntryAuthority, registerEntryView } from "../mount/comparison.js";
|
|
4
4
|
import { deviceDirectory, lexicalDevicePath, nullPath, resolveDevicePath } from "./path.js";
|
|
5
5
|
import { deviceReadStream, drainDeviceFile, drainDeviceInput } from "./stream.js";
|
|
6
6
|
import { openRetainedReadFile, openRetainedResizeFile, retainedResizeCapabilities } from "../capabilities.js";
|
|
@@ -13,13 +13,13 @@ const deviceCapabilities = Object.freeze({
|
|
|
13
13
|
remove: false, removeDirectory: false, recursiveRemove: false, rename: false,
|
|
14
14
|
mkdir: false, recursiveMkdir: false, symlinks: false, hardlinks: false, readlink: false,
|
|
15
15
|
permissions: false, timestamps: false, truncate: false, randomAccessWrite: false,
|
|
16
|
-
atomicRename: false, atomicRenameNoReplace: false, descriptorWriteStream: true, retainedResize: true,
|
|
16
|
+
atomicRename: false, atomicRenameNoReplace: false, descriptorWriteStream: true, retainedResize: true, atomicResize: false,
|
|
17
17
|
});
|
|
18
18
|
function globalCapabilities(filesystem) {
|
|
19
19
|
const capabilities = { readOnly: false };
|
|
20
20
|
const optional = {
|
|
21
21
|
streamingRead: ["readStream"], streamingWrite: ["writeStream"], retainedRead: ["openReadFile"],
|
|
22
|
-
streamingAppend: ["writeStream"], descriptorWriteStream: ["writeStream"], retainedResize: ["openResizeFile"],
|
|
22
|
+
streamingAppend: ["writeStream"], descriptorWriteStream: ["writeStream"], retainedResize: ["openResizeFile"], atomicResize: ["resizeFile"],
|
|
23
23
|
symlinks: ["symlink", "readlink"], hardlinks: ["link"], permissions: ["chmod"],
|
|
24
24
|
timestamps: ["utimes"], readlink: ["readlink"], truncate: ["truncate"], removeDirectory: ["rmdir"],
|
|
25
25
|
};
|
|
@@ -84,6 +84,8 @@ export class DeviceFileSystem {
|
|
|
84
84
|
stat: resolved === nullPath ? this.#nullStat : this.#directoryStat, readOnly: false };
|
|
85
85
|
return { filesystem, path };
|
|
86
86
|
});
|
|
87
|
+
// This internal view has no identity proof beyond its synthetic stats.
|
|
88
|
+
registerEntryAuthority(this, async () => "unknown");
|
|
87
89
|
return view;
|
|
88
90
|
}
|
|
89
91
|
#resolve(path, options, followFinal = true, resizeCreate) {
|
|
@@ -139,6 +141,8 @@ export class DeviceFileSystem {
|
|
|
139
141
|
}
|
|
140
142
|
if (typeof this.#filesystem.openReadFile !== "function")
|
|
141
143
|
unavailable.retainedRead = false;
|
|
144
|
+
if (observed.atomicResize === true && (typeof this.#filesystem.resizeFile !== "function" || observed.readOnly === true))
|
|
145
|
+
unavailable.atomicResize = false;
|
|
142
146
|
const result = Object.keys(unavailable).some(key => capabilities[key] !== false)
|
|
143
147
|
? { ...capabilities, ...unavailable } : capabilities;
|
|
144
148
|
options.signal?.throwIfAborted();
|
|
@@ -223,6 +227,19 @@ export class DeviceFileSystem {
|
|
|
223
227
|
async close() { closed = true; },
|
|
224
228
|
};
|
|
225
229
|
}
|
|
230
|
+
async resizeFile(path, operation, options = {}) {
|
|
231
|
+
await this.#mutable(path, options);
|
|
232
|
+
const capabilities = await this.capabilitiesFor(path, options);
|
|
233
|
+
options.signal?.throwIfAborted();
|
|
234
|
+
if (capabilities.readOnly === true)
|
|
235
|
+
throw new FsError("EROFS", { syscall: "resizeFile", path });
|
|
236
|
+
const resize = this.#filesystem.resizeFile;
|
|
237
|
+
options.signal?.throwIfAborted();
|
|
238
|
+
if (capabilities.atomicResize !== true || typeof resize !== "function")
|
|
239
|
+
throw new FsError("ENOTSUP", { syscall: "resizeFile", path });
|
|
240
|
+
await Reflect.apply(resize, this.#filesystem, [path, operation, options]);
|
|
241
|
+
options.signal?.throwIfAborted();
|
|
242
|
+
}
|
|
226
243
|
async openResizeFile(path, options = {}) {
|
|
227
244
|
options.signal?.throwIfAborted();
|
|
228
245
|
const resolution = this.#resolve(path, options, true, options.create ?? false);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, 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;
|
|
@@ -12,6 +12,7 @@ export declare class MountFileSystem implements FileSystem {
|
|
|
12
12
|
capabilitiesFor(path: string, options?: CapabilityQueryOptions): Promise<FileSystemCapabilities>;
|
|
13
13
|
private protected;
|
|
14
14
|
openReadFile(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
15
|
+
resizeFile(path: string, operation: FileResizeOperation, options?: FileResizeOptions): Promise<void>;
|
|
15
16
|
openResizeFile(path: string, options?: OpenResizeFileOptions): Promise<FileResizeHandle>;
|
|
16
17
|
private error;
|
|
17
18
|
private operation;
|
|
@@ -91,7 +91,7 @@ export class MountFileSystem {
|
|
|
91
91
|
const common = (capability) => {
|
|
92
92
|
const optional = {
|
|
93
93
|
symlinks: ["symlink", "readlink"], hardlinks: ["link"], permissions: ["chmod"], timestamps: ["utimes"], readlink: ["readlink"],
|
|
94
|
-
descriptorWriteStream: ["writeStream"], retainedResize: ["openResizeFile"],
|
|
94
|
+
descriptorWriteStream: ["writeStream"], retainedResize: ["openResizeFile"], atomicResize: ["resizeFile"],
|
|
95
95
|
};
|
|
96
96
|
const values = mounts.map(({ backend }) => {
|
|
97
97
|
if (backend.capabilities.readOnly === true
|
|
@@ -110,7 +110,7 @@ export class MountFileSystem {
|
|
|
110
110
|
"read", "stat", "readdir", "realpath", "access",
|
|
111
111
|
"write", "append", "exclusiveCreate", "explicitDirectories", "implicitDirectories", "mkdir", "recursiveMkdir",
|
|
112
112
|
"remove", "removeDirectory", "recursiveRemove", "rename", "atomicRenameNoReplace", "copy", "exclusiveCopy", "readlink", "truncate",
|
|
113
|
-
"streamingAppend", "randomAccessWrite", "descriptorWriteStream", "retainedResize", "symlinks", "hardlinks", "permissions", "timestamps",
|
|
113
|
+
"streamingAppend", "randomAccessWrite", "descriptorWriteStream", "retainedResize", "atomicResize", "symlinks", "hardlinks", "permissions", "timestamps",
|
|
114
114
|
].map(capability => [capability, common(capability)]).filter(([, value]) => value !== undefined));
|
|
115
115
|
this.capabilities = Object.freeze({
|
|
116
116
|
get snapshotRmdir() { return mounts.some(({ backend }) => backend.capabilities.snapshotRmdir === true); },
|
|
@@ -137,8 +137,10 @@ export class MountFileSystem {
|
|
|
137
137
|
const declared = observed.descriptorWriteStream === true
|
|
138
138
|
&& (typeof location.mount.backend.writeStream !== "function" || observed.readOnly === true || observed.streamingWrite === false)
|
|
139
139
|
? { ...observed, descriptorWriteStream: false } : observed;
|
|
140
|
-
const
|
|
141
|
-
|
|
140
|
+
const resize = declared.atomicResize === true && (typeof location.mount.backend.resizeFile !== "function" || declared.readOnly === true)
|
|
141
|
+
? { ...declared, atomicResize: false } : declared;
|
|
142
|
+
const capabilities = location.synthetic ? { ...resize, retainedRead: false }
|
|
143
|
+
: retainedResizeCapabilities(location.mount.backend, retainedReadCapabilities(location.mount.backend, resize));
|
|
142
144
|
if (location.synthetic)
|
|
143
145
|
return readOnlyCapabilities(capabilities);
|
|
144
146
|
if (this.mounts.length === 1 || capabilities.readOnly === true)
|
|
@@ -167,6 +169,23 @@ export class MountFileSystem {
|
|
|
167
169
|
throw error ? this.error(error, "openReadFile", path, options) : error;
|
|
168
170
|
}
|
|
169
171
|
}
|
|
172
|
+
async resizeFile(path, operation, options = {}) {
|
|
173
|
+
return this.operation("resizeFile", path, options, async () => {
|
|
174
|
+
const location = await this.resolve(path, options, { allowMissing: options.create === true, resizeCreate: options.create ?? false });
|
|
175
|
+
if (location.synthetic)
|
|
176
|
+
fail("EROFS");
|
|
177
|
+
const backend = location.mount.backend;
|
|
178
|
+
const capabilities = await backend.capabilitiesFor?.(location.local, options) ?? backend.capabilities;
|
|
179
|
+
options.signal?.throwIfAborted();
|
|
180
|
+
if (capabilities.readOnly === true)
|
|
181
|
+
fail("EROFS");
|
|
182
|
+
const resize = backend.resizeFile;
|
|
183
|
+
options.signal?.throwIfAborted();
|
|
184
|
+
if (capabilities.atomicResize !== true || typeof resize !== "function")
|
|
185
|
+
fail("ENOTSUP");
|
|
186
|
+
await Reflect.apply(resize, backend, [location.local, operation, options]);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
170
189
|
async openResizeFile(path, options = {}) {
|
|
171
190
|
try {
|
|
172
191
|
options.signal?.throwIfAborted();
|
|
@@ -120,7 +120,7 @@ export class OverlayFileSystem {
|
|
|
120
120
|
...(effectiveAppend === undefined ? {} : { append: effectiveAppend }),
|
|
121
121
|
atomicRename: false, atomicRenameNoReplace: false,
|
|
122
122
|
descriptorWriteStream: false,
|
|
123
|
-
retainedResize: false,
|
|
123
|
+
retainedResize: false, atomicResize: false,
|
|
124
124
|
hardlinks: false,
|
|
125
125
|
symlinks: writable && this.#upper.capabilities.symlinks === true
|
|
126
126
|
&& typeof this.#upper.symlink === "function" && typeof this.#upper.readlink === "function"
|
|
@@ -316,7 +316,7 @@ export function withFileSystemQuota(fs, options) {
|
|
|
316
316
|
// capabilities and methods without violating invariants on own properties.
|
|
317
317
|
return new Proxy(Object.create(fs), {
|
|
318
318
|
get(_target, property) {
|
|
319
|
-
if (property === "canonicalizeMissingTarget")
|
|
319
|
+
if (property === "canonicalizeMissingTarget" || property === "resizeFile")
|
|
320
320
|
return undefined;
|
|
321
321
|
if (property === "capabilities")
|
|
322
322
|
return quotaCapabilities(retainedResizeCapabilities(fs, retainedReadCapabilities(fs)));
|
|
@@ -6,7 +6,7 @@ const originals = new WeakMap();
|
|
|
6
6
|
const operations = new Set([
|
|
7
7
|
"access", "appendFile", "canonicalizeMissingTarget", "capabilitiesFor", "chmod", "compareEntry",
|
|
8
8
|
"copyFile", "link", "lstat", "mkdir", "openReadFile", "openResizeFile", "readFile", "readStream", "readdir",
|
|
9
|
-
"readlink", "realpath", "rename", "rm", "rmdir", "stat", "symlink", "truncate", "utimes",
|
|
9
|
+
"readlink", "realpath", "rename", "resizeFile", "rm", "rmdir", "stat", "symlink", "truncate", "utimes",
|
|
10
10
|
"writeFile", "writeStream",
|
|
11
11
|
]);
|
|
12
12
|
export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = charge) {
|