@poe-platform/safe-fs 0.1.501 → 0.1.503
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/bridge/stats.js +2 -2
- package/dist/safe-fs/contracts/filesystem.d.ts +6 -2
- package/dist/safe-fs/core.d.ts +1 -0
- package/dist/safe-fs/core.js +1 -0
- package/dist/safe-fs/fs/capabilities.js +1 -1
- package/dist/safe-fs/fs/devices/index.d.ts +33 -0
- package/dist/safe-fs/fs/devices/index.js +379 -0
- package/dist/safe-fs/fs/devices/path.d.ts +5 -0
- package/dist/safe-fs/fs/devices/path.js +87 -0
- package/dist/safe-fs/fs/devices/stream.d.ts +5 -0
- package/dist/safe-fs/fs/devices/stream.js +116 -0
- package/dist/safe-fs/fs/memory/index.d.ts +3 -2
- package/dist/safe-fs/fs/memory/index.js +3 -1
- package/dist/safe-fs/fs/mount/index.d.ts +2 -2
- package/dist/safe-fs/fs/mount/index.js +8 -2
- package/dist/safe-fs/fs/overlay/index.d.ts +2 -2
- package/dist/safe-fs/fs/overlay/index.js +3 -1
- package/dist/safe-fs/fs/quota/index.js +10 -0
- package/dist/safe-fs/fs/readonly/index.d.ts +2 -2
- package/dist/safe-fs/fs/real/index.d.ts +2 -2
- package/dist/safe-fs/fs/real/index.js +3 -1
- package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -2
- package/dist/safe-fs/fs/s3/filesystem.js +5 -0
- package/dist/safe-fs/fs/scoped.js +12 -0
- package/dist/safe-fs/fs/webdav/webdav.d.ts +3 -2
- package/dist/safe-fs/fs/webdav/webdav.js +5 -1
- package/dist/safe-fs/index.d.ts +1 -0
- package/dist/safe-fs/index.js +1 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ function predicates(type) {
|
|
|
4
4
|
isDirectory: () => type === "directory",
|
|
5
5
|
isSymbolicLink: () => type === "symlink",
|
|
6
6
|
isBlockDevice: () => false,
|
|
7
|
-
isCharacterDevice: () =>
|
|
7
|
+
isCharacterDevice: () => type === "character",
|
|
8
8
|
isFIFO: () => false,
|
|
9
9
|
isSocket: () => false,
|
|
10
10
|
};
|
|
@@ -13,7 +13,7 @@ export function bridgeStats(stat) {
|
|
|
13
13
|
return {
|
|
14
14
|
dev: stat.dev ?? 0,
|
|
15
15
|
ino: stat.ino ?? 0,
|
|
16
|
-
mode: (stat.mode & 0o7777) | (stat.type === "directory" ? 0o040000 : stat.type === "symlink" ? 0o120000 : 0o100000),
|
|
16
|
+
mode: (stat.mode & 0o7777) | (stat.type === "directory" ? 0o040000 : stat.type === "symlink" ? 0o120000 : stat.type === "character" ? 0o020000 : 0o100000),
|
|
17
17
|
nlink: stat.nlink ?? 1,
|
|
18
18
|
uid: stat.uid ?? 0,
|
|
19
19
|
gid: stat.gid ?? 0,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ByteSource } from "./io.js";
|
|
2
|
-
export type FileType = "file" | "directory" | "symlink";
|
|
2
|
+
export type FileType = "file" | "directory" | "symlink" | "character";
|
|
3
3
|
export type EntryComparison = "same" | "distinct" | "unknown";
|
|
4
4
|
export interface FileStat {
|
|
5
5
|
readonly type: FileType;
|
|
@@ -50,6 +50,7 @@ export interface FileSystemCapabilities {
|
|
|
50
50
|
readonly permissions?: boolean;
|
|
51
51
|
readonly timestamps?: boolean;
|
|
52
52
|
readonly atomicRename?: boolean;
|
|
53
|
+
readonly atomicRenameNoReplace?: boolean;
|
|
53
54
|
readonly snapshotRmdir?: boolean;
|
|
54
55
|
readonly streamingRead?: boolean;
|
|
55
56
|
readonly retainedRead?: boolean;
|
|
@@ -65,6 +66,9 @@ export interface FileReadHandle {
|
|
|
65
66
|
read(position: number, maxBytes: number, options?: FsOptions): Promise<Uint8Array>;
|
|
66
67
|
close(): Promise<void>;
|
|
67
68
|
}
|
|
69
|
+
export interface RenameOptions extends FsOptions {
|
|
70
|
+
readonly noReplace?: boolean;
|
|
71
|
+
}
|
|
68
72
|
export interface ReadFileOptions extends FsOptions {
|
|
69
73
|
readonly maxBytes?: number;
|
|
70
74
|
}
|
|
@@ -116,7 +120,7 @@ export interface FileSystem {
|
|
|
116
120
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
117
121
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
118
122
|
rmdir?(path: string, options?: FsOptions): Promise<void>;
|
|
119
|
-
rename(source: string, destination: string, options?:
|
|
123
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
120
124
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
121
125
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
122
126
|
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
package/dist/safe-fs/core.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./contracts/io.js";
|
|
|
4
4
|
export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
|
|
5
5
|
export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
|
|
6
6
|
export * from "./fs/memory/index.js";
|
|
7
|
+
export * from "./fs/devices/index.js";
|
|
7
8
|
export * from "./fs/readonly/index.js";
|
|
8
9
|
export * from "./fs/mount/index.js";
|
|
9
10
|
export * from "./fs/overlay/index.js";
|
package/dist/safe-fs/core.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./contracts/io.js";
|
|
|
4
4
|
export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
|
|
5
5
|
export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
|
|
6
6
|
export * from "./fs/memory/index.js";
|
|
7
|
+
export * from "./fs/devices/index.js";
|
|
7
8
|
export * from "./fs/readonly/index.js";
|
|
8
9
|
export * from "./fs/mount/index.js";
|
|
9
10
|
export * from "./fs/overlay/index.js";
|
|
@@ -38,7 +38,7 @@ export function readOnlyCapabilities(capabilities) {
|
|
|
38
38
|
rename: false, copy: false, exclusiveCopy: false, truncate: false, streamingAppend: false,
|
|
39
39
|
randomAccessWrite: false, hardlinks: false, permissions: false, timestamps: false,
|
|
40
40
|
descriptorWriteStream: false,
|
|
41
|
-
atomicRename: false, streamingWrite: false,
|
|
41
|
+
atomicRename: false, atomicRenameNoReplace: false, streamingWrite: false,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
export function quotaCapabilities(capabilities) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
2
|
+
import type { ByteSource } from "../../contracts/io.js";
|
|
3
|
+
export declare class DeviceFileSystem implements FileSystem {
|
|
4
|
+
#private;
|
|
5
|
+
readonly capabilities: FileSystemCapabilities;
|
|
6
|
+
constructor(filesystem: FileSystem);
|
|
7
|
+
canonicalizeMissingTarget(path: string, options?: FsOptions): string | undefined;
|
|
8
|
+
capabilitiesFor(path: string, options?: FsOptions): Promise<FileSystemCapabilities>;
|
|
9
|
+
stat(path: string, options?: FsOptions): Promise<FileStat>;
|
|
10
|
+
lstat(path: string, options?: FsOptions): Promise<FileStat>;
|
|
11
|
+
compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<import("../../contracts/filesystem.js").EntryComparison>;
|
|
12
|
+
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
13
|
+
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
14
|
+
readStream(path: string, options?: ReadStreamOptions): ByteSource;
|
|
15
|
+
openReadFile(path: string, options?: FsOptions): Promise<FileReadHandle>;
|
|
16
|
+
writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
17
|
+
appendFile(path: string, data: Uint8Array, options?: AppendFileOptions): Promise<void>;
|
|
18
|
+
writeStream(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
|
|
19
|
+
readdir(path: string, options?: ReadDirectoryOptions): Promise<DirectoryEntry[]>;
|
|
20
|
+
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
21
|
+
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
22
|
+
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
23
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
24
|
+
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
25
|
+
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
|
26
|
+
readlink(path: string, options?: FsOptions): Promise<string>;
|
|
27
|
+
symlink(target: string, path: string, options?: FsOptions): Promise<void>;
|
|
28
|
+
link(existingPath: string, newPath: string, options?: FsOptions): Promise<void>;
|
|
29
|
+
chmod(path: string, mode: number, options?: FsOptions): Promise<void>;
|
|
30
|
+
utimes(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
|
|
31
|
+
truncate(path: string, length?: number, options?: FsOptions): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function createDeviceFileSystem(filesystem: FileSystem): DeviceFileSystem;
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { FsError, isFsError } from "../../contracts/errors.js";
|
|
2
|
+
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
3
|
+
import { compareEntries, registerEntryView } from "../mount/comparison.js";
|
|
4
|
+
import { deviceDirectory, lexicalDevicePath, nullPath, resolveDevicePath } from "./path.js";
|
|
5
|
+
import { deviceReadStream, drainDeviceFile, drainDeviceInput } from "./stream.js";
|
|
6
|
+
const views = new WeakMap();
|
|
7
|
+
const deviceCapabilities = Object.freeze({
|
|
8
|
+
readOnly: false, read: true, stat: true, realpath: true, access: true, readdir: false,
|
|
9
|
+
write: true, append: true, exclusiveCreate: true, streamingRead: true, retainedRead: true,
|
|
10
|
+
streamingWrite: true, streamingAppend: true, copy: true, exclusiveCopy: true,
|
|
11
|
+
remove: false, removeDirectory: false, recursiveRemove: false, rename: false,
|
|
12
|
+
mkdir: false, recursiveMkdir: false, symlinks: false, hardlinks: false, readlink: false,
|
|
13
|
+
permissions: false, timestamps: false, truncate: false, randomAccessWrite: false,
|
|
14
|
+
atomicRename: false, atomicRenameNoReplace: false, descriptorWriteStream: true,
|
|
15
|
+
});
|
|
16
|
+
function globalCapabilities(filesystem) {
|
|
17
|
+
const capabilities = { readOnly: false };
|
|
18
|
+
const optional = {
|
|
19
|
+
streamingRead: ["readStream"], streamingWrite: ["writeStream"], retainedRead: ["openReadFile"],
|
|
20
|
+
streamingAppend: ["writeStream"], descriptorWriteStream: ["writeStream"],
|
|
21
|
+
symlinks: ["symlink", "readlink"], hardlinks: ["link"], permissions: ["chmod"],
|
|
22
|
+
timestamps: ["utimes"], readlink: ["readlink"], truncate: ["truncate"], removeDirectory: ["rmdir"],
|
|
23
|
+
};
|
|
24
|
+
for (const key of new Set([...Object.keys(filesystem.capabilities), ...Object.keys(deviceCapabilities)])) {
|
|
25
|
+
if (key === "readOnly")
|
|
26
|
+
continue;
|
|
27
|
+
Object.defineProperty(capabilities, key, { enumerable: true, get() {
|
|
28
|
+
if (key === "copy" || key === "exclusiveCopy")
|
|
29
|
+
return undefined;
|
|
30
|
+
let declared = filesystem.capabilities[key];
|
|
31
|
+
if (declared === true && optional[key]?.some(method => typeof filesystem[method] !== "function"))
|
|
32
|
+
declared = false;
|
|
33
|
+
if (key === "descriptorWriteStream" && filesystem.capabilities.streamingWrite === false)
|
|
34
|
+
declared = false;
|
|
35
|
+
return declared === deviceCapabilities[key] ? declared : undefined;
|
|
36
|
+
} });
|
|
37
|
+
}
|
|
38
|
+
return Object.freeze(capabilities);
|
|
39
|
+
}
|
|
40
|
+
function reserved(path) { return path === "/" || path === deviceDirectory || path === nullPath; }
|
|
41
|
+
function nonnegative(value, path) {
|
|
42
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
|
|
43
|
+
throw new FsError("EINVAL", { path });
|
|
44
|
+
}
|
|
45
|
+
export class DeviceFileSystem {
|
|
46
|
+
#filesystem;
|
|
47
|
+
capabilities;
|
|
48
|
+
#nullStat;
|
|
49
|
+
#directoryStat;
|
|
50
|
+
constructor(filesystem) {
|
|
51
|
+
this.#filesystem = filesystem;
|
|
52
|
+
this.capabilities = globalCapabilities(filesystem);
|
|
53
|
+
const identityScope = Object.freeze({});
|
|
54
|
+
this.#nullStat = Object.freeze({ type: "character", mode: 0o020666, size: 0, allocatedBytes: 0,
|
|
55
|
+
mtimeMs: 0, atimeMs: 0, ctimeMs: 0, birthtimeMs: 0, identityScope, ino: 1, dev: 0, nlink: 1 });
|
|
56
|
+
this.#directoryStat = Object.freeze({ ...this.#nullStat, type: "directory", mode: 0o040755, ino: 2 });
|
|
57
|
+
const methods = new Map();
|
|
58
|
+
const view = new Proxy(this, {
|
|
59
|
+
set: (_target, property, value) => Reflect.set(filesystem, property, value, filesystem),
|
|
60
|
+
get(target, property) {
|
|
61
|
+
const value = Reflect.get(target, property, target);
|
|
62
|
+
if (typeof value !== "function" || property === "constructor")
|
|
63
|
+
return value;
|
|
64
|
+
const backing = Reflect.get(filesystem, property, filesystem);
|
|
65
|
+
const cached = methods.get(property);
|
|
66
|
+
if (cached && cached.backing === backing)
|
|
67
|
+
return cached.bound;
|
|
68
|
+
const bound = value.bind(target);
|
|
69
|
+
methods.set(property, { backing, bound });
|
|
70
|
+
return bound;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
for (const identity of [this, view])
|
|
74
|
+
registerEntryView(identity, async (path, options) => {
|
|
75
|
+
const resolved = await this.#resolve(path, options);
|
|
76
|
+
if (resolved === nullPath || resolved === deviceDirectory)
|
|
77
|
+
return { filesystem: this, path: resolved,
|
|
78
|
+
stat: resolved === nullPath ? this.#nullStat : this.#directoryStat, readOnly: false };
|
|
79
|
+
return { filesystem, path };
|
|
80
|
+
});
|
|
81
|
+
return view;
|
|
82
|
+
}
|
|
83
|
+
#resolve(path, options, followFinal = true) {
|
|
84
|
+
return resolveDevicePath(this.#filesystem, path, options, followFinal);
|
|
85
|
+
}
|
|
86
|
+
async #mutable(path, options, followFinal = true) {
|
|
87
|
+
if (reserved(await this.#resolve(path, options, followFinal)))
|
|
88
|
+
throw new FsError("EBUSY", { path });
|
|
89
|
+
}
|
|
90
|
+
async #writeTarget(path, options) {
|
|
91
|
+
const resolved = await this.#resolve(path, options);
|
|
92
|
+
if (resolved === nullPath) {
|
|
93
|
+
if (options.flag === "wx" || options.flag === "ax")
|
|
94
|
+
throw new FsError("EEXIST", { path });
|
|
95
|
+
if (options.flag !== undefined && options.flag !== "w" && options.flag !== "a")
|
|
96
|
+
throw new FsError("EINVAL", { path });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (resolved === "/" || resolved === deviceDirectory)
|
|
100
|
+
throw new FsError("EISDIR", { path });
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
canonicalizeMissingTarget(path, options = {}) {
|
|
104
|
+
options.signal?.throwIfAborted();
|
|
105
|
+
const resolved = lexicalDevicePath(path);
|
|
106
|
+
return reserved(resolved) ? resolved : this.#filesystem.canonicalizeMissingTarget?.(path, options);
|
|
107
|
+
}
|
|
108
|
+
async capabilitiesFor(path, options = {}) {
|
|
109
|
+
const resolved = await this.#resolve(path, options);
|
|
110
|
+
if (resolved === nullPath)
|
|
111
|
+
return deviceCapabilities;
|
|
112
|
+
if (resolved === deviceDirectory)
|
|
113
|
+
return { ...deviceCapabilities, readdir: true, write: false, append: false,
|
|
114
|
+
exclusiveCreate: false, streamingWrite: false, streamingAppend: false, descriptorWriteStream: false,
|
|
115
|
+
retainedRead: false, streamingRead: false, copy: false, exclusiveCopy: false };
|
|
116
|
+
const capabilities = await this.#filesystem.capabilitiesFor?.(path, options) ?? this.#filesystem.capabilities;
|
|
117
|
+
const unavailable = {};
|
|
118
|
+
if (typeof this.#filesystem.readStream !== "function")
|
|
119
|
+
unavailable.streamingRead = false;
|
|
120
|
+
if (typeof this.#filesystem.writeStream !== "function") {
|
|
121
|
+
unavailable.streamingWrite = false;
|
|
122
|
+
unavailable.streamingAppend = false;
|
|
123
|
+
unavailable.descriptorWriteStream = false;
|
|
124
|
+
}
|
|
125
|
+
if (typeof this.#filesystem.openReadFile !== "function")
|
|
126
|
+
unavailable.retainedRead = false;
|
|
127
|
+
return Object.keys(unavailable).some(key => capabilities[key] !== false)
|
|
128
|
+
? { ...capabilities, ...unavailable } : capabilities;
|
|
129
|
+
}
|
|
130
|
+
async stat(path, options = {}) {
|
|
131
|
+
const resolved = await this.#resolve(path, options);
|
|
132
|
+
if (resolved === nullPath)
|
|
133
|
+
return { ...this.#nullStat };
|
|
134
|
+
if (resolved === deviceDirectory)
|
|
135
|
+
return { ...this.#directoryStat };
|
|
136
|
+
return this.#filesystem.stat(path, options);
|
|
137
|
+
}
|
|
138
|
+
async lstat(path, options = {}) {
|
|
139
|
+
const resolved = await this.#resolve(path, options, false);
|
|
140
|
+
if (resolved === nullPath)
|
|
141
|
+
return { ...this.#nullStat };
|
|
142
|
+
if (resolved === deviceDirectory)
|
|
143
|
+
return { ...this.#directoryStat };
|
|
144
|
+
return this.#filesystem.lstat(path, options);
|
|
145
|
+
}
|
|
146
|
+
compareEntry(path, peer, peerPath, options = {}) {
|
|
147
|
+
return compareEntries(this, path, peer, peerPath, options);
|
|
148
|
+
}
|
|
149
|
+
async realpath(path, options = {}) {
|
|
150
|
+
const resolved = await this.#resolve(path, options);
|
|
151
|
+
return reserved(resolved) ? resolved : this.#filesystem.realpath(path, options);
|
|
152
|
+
}
|
|
153
|
+
async readFile(path, options = {}) {
|
|
154
|
+
const resolved = await this.#resolve(path, options);
|
|
155
|
+
if (resolved === nullPath) {
|
|
156
|
+
nonnegative(options.maxBytes, path);
|
|
157
|
+
return new Uint8Array();
|
|
158
|
+
}
|
|
159
|
+
if (resolved === deviceDirectory)
|
|
160
|
+
throw new FsError("EISDIR", { path });
|
|
161
|
+
return this.#filesystem.readFile(path, options);
|
|
162
|
+
}
|
|
163
|
+
readStream(path, options = {}) {
|
|
164
|
+
return deviceReadStream(async () => {
|
|
165
|
+
const resolved = await this.#resolve(path, options);
|
|
166
|
+
if (resolved === nullPath) {
|
|
167
|
+
nonnegative(options.start, path);
|
|
168
|
+
nonnegative(options.endExclusive, path);
|
|
169
|
+
nonnegative(options.chunkSize, path);
|
|
170
|
+
if (options.chunkSize === 0 || (options.endExclusive !== undefined && options.endExclusive < (options.start ?? 0)))
|
|
171
|
+
throw new FsError("EINVAL", { path });
|
|
172
|
+
return { [Symbol.asyncIterator]: () => ({ next: async () => ({ done: true, value: undefined }) }) };
|
|
173
|
+
}
|
|
174
|
+
if (resolved === deviceDirectory)
|
|
175
|
+
throw new FsError("EISDIR", { path });
|
|
176
|
+
if (!this.#filesystem.readStream)
|
|
177
|
+
throw new FsError("ENOTSUP", { path });
|
|
178
|
+
return this.#filesystem.readStream(path, options);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async openReadFile(path, options = {}) {
|
|
182
|
+
const resolved = await this.#resolve(path, options);
|
|
183
|
+
if (resolved !== nullPath) {
|
|
184
|
+
if (resolved === deviceDirectory)
|
|
185
|
+
throw new FsError("EISDIR", { path });
|
|
186
|
+
if (!this.#filesystem.openReadFile)
|
|
187
|
+
throw new FsError("ENOTSUP", { path });
|
|
188
|
+
return this.#filesystem.openReadFile(path, options);
|
|
189
|
+
}
|
|
190
|
+
const stat = this.#nullStat;
|
|
191
|
+
let closed = false;
|
|
192
|
+
const check = (settings) => { settings.signal?.throwIfAborted(); if (closed)
|
|
193
|
+
throw new FsError("EBADF", { path }); };
|
|
194
|
+
return {
|
|
195
|
+
async stat(settings = {}) { check(settings); return { ...stat }; },
|
|
196
|
+
async read(position, maxBytes, settings = {}) { check(settings); nonnegative(position, path); nonnegative(maxBytes, path); return new Uint8Array(); },
|
|
197
|
+
async close() { closed = true; },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async writeFile(path, data, options = {}) {
|
|
201
|
+
if (await this.#writeTarget(path, options)) {
|
|
202
|
+
if (!(data instanceof Uint8Array))
|
|
203
|
+
throw new TypeError("File data must be Uint8Array");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
await this.#filesystem.writeFile(path, data, options);
|
|
207
|
+
}
|
|
208
|
+
async appendFile(path, data, options = {}) {
|
|
209
|
+
if (await this.#writeTarget(path, options)) {
|
|
210
|
+
if (!(data instanceof Uint8Array))
|
|
211
|
+
throw new TypeError("File data must be Uint8Array");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await this.#filesystem.appendFile(path, data, options);
|
|
215
|
+
}
|
|
216
|
+
async writeStream(path, source, options = {}) {
|
|
217
|
+
if (await this.#writeTarget(path, options))
|
|
218
|
+
return drainDeviceInput(source, options.signal);
|
|
219
|
+
if (!this.#filesystem.writeStream)
|
|
220
|
+
throw new FsError("ENOTSUP", { path });
|
|
221
|
+
await this.#filesystem.writeStream(path, source, options);
|
|
222
|
+
}
|
|
223
|
+
async readdir(path, options = {}) {
|
|
224
|
+
const resolved = await this.#resolve(path, options);
|
|
225
|
+
if (resolved === nullPath)
|
|
226
|
+
throw new FsError("ENOTDIR", { path });
|
|
227
|
+
if (resolved !== "/" && resolved !== deviceDirectory)
|
|
228
|
+
return this.#filesystem.readdir(path, options);
|
|
229
|
+
const limit = directoryEntryLimit(options, path);
|
|
230
|
+
let entries;
|
|
231
|
+
try {
|
|
232
|
+
entries = await this.#filesystem.readdir(resolved, options);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
options.signal?.throwIfAborted();
|
|
236
|
+
if (!isFsError(error, "ENOENT") && !isFsError(error, "ENOTDIR"))
|
|
237
|
+
throw error;
|
|
238
|
+
entries = [];
|
|
239
|
+
}
|
|
240
|
+
options.signal?.throwIfAborted();
|
|
241
|
+
const merged = new Map();
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
if (entry.name === "." || entry.name === "..")
|
|
244
|
+
continue;
|
|
245
|
+
if (!entry.name || entry.name.includes("/") || entry.name.includes("\0"))
|
|
246
|
+
throw new FsError("EIO", { path, message: "invalid directory entry" });
|
|
247
|
+
merged.set(entry.name, { name: entry.name, type: entry.type });
|
|
248
|
+
admitDirectoryEntries(merged.size, limit, path);
|
|
249
|
+
}
|
|
250
|
+
const name = resolved === "/" ? "dev" : "null";
|
|
251
|
+
merged.set(name, { name, type: resolved === "/" ? "directory" : "character" });
|
|
252
|
+
admitDirectoryEntries(merged.size, limit, path);
|
|
253
|
+
return [...merged.values()];
|
|
254
|
+
}
|
|
255
|
+
async mkdir(path, options = {}) {
|
|
256
|
+
const resolved = await this.#resolve(path, options);
|
|
257
|
+
if (reserved(resolved)) {
|
|
258
|
+
if (resolved !== nullPath && options.recursive)
|
|
259
|
+
return;
|
|
260
|
+
throw new FsError("EEXIST", { path });
|
|
261
|
+
}
|
|
262
|
+
await this.#filesystem.mkdir(path, options);
|
|
263
|
+
}
|
|
264
|
+
async rm(path, options = {}) {
|
|
265
|
+
await this.#mutable(path, options, false);
|
|
266
|
+
await this.#filesystem.rm(path, options);
|
|
267
|
+
}
|
|
268
|
+
async rmdir(path, options = {}) {
|
|
269
|
+
const resolved = await this.#resolve(path, options, false);
|
|
270
|
+
if (resolved === nullPath)
|
|
271
|
+
throw new FsError("ENOTDIR", { path });
|
|
272
|
+
if (reserved(resolved))
|
|
273
|
+
throw new FsError("EBUSY", { path });
|
|
274
|
+
if (!this.#filesystem.rmdir)
|
|
275
|
+
throw new FsError("ENOTSUP", { path });
|
|
276
|
+
await this.#filesystem.rmdir(path, options);
|
|
277
|
+
}
|
|
278
|
+
async rename(source, destination, options = {}) {
|
|
279
|
+
await this.#mutable(source, options, false);
|
|
280
|
+
await this.#mutable(destination, options, false);
|
|
281
|
+
if (options.noReplace) {
|
|
282
|
+
const capabilities = await this.#filesystem.capabilitiesFor?.(destination, options) ?? this.#filesystem.capabilities;
|
|
283
|
+
options.signal?.throwIfAborted();
|
|
284
|
+
if (capabilities.atomicRenameNoReplace !== true)
|
|
285
|
+
throw new FsError("ENOTSUP", { syscall: "rename", path: source, dest: destination });
|
|
286
|
+
}
|
|
287
|
+
await this.#filesystem.rename(source, destination, options);
|
|
288
|
+
}
|
|
289
|
+
async copyFile(source, destination, options = {}) {
|
|
290
|
+
const target = await this.#resolve(destination, options);
|
|
291
|
+
if (target === nullPath && options.exclusive)
|
|
292
|
+
throw new FsError("EEXIST", { path: destination });
|
|
293
|
+
const origin = await this.#resolve(source, options);
|
|
294
|
+
if (target !== nullPath && origin !== nullPath) {
|
|
295
|
+
if (target === "/" || target === deviceDirectory)
|
|
296
|
+
throw new FsError("EISDIR", { path: destination });
|
|
297
|
+
await this.#filesystem.copyFile(source, destination, options);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (target === nullPath) {
|
|
301
|
+
if (origin === nullPath)
|
|
302
|
+
return;
|
|
303
|
+
if (origin === "/" || origin === deviceDirectory)
|
|
304
|
+
throw new FsError("EISDIR", { path: source });
|
|
305
|
+
const capabilities = await this.capabilitiesFor(source, options);
|
|
306
|
+
if (this.#filesystem.readStream && capabilities.streamingRead !== false) {
|
|
307
|
+
await drainDeviceInput(this.#filesystem.readStream(source, options), options.signal);
|
|
308
|
+
}
|
|
309
|
+
else if (this.#filesystem.openReadFile && capabilities.retainedRead !== false) {
|
|
310
|
+
await drainDeviceFile(await this.#filesystem.openReadFile(source, options), options.signal);
|
|
311
|
+
}
|
|
312
|
+
else
|
|
313
|
+
throw new FsError("ENOTSUP", { path: source });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (reserved(target))
|
|
317
|
+
throw new FsError("EISDIR", { path: destination });
|
|
318
|
+
await this.#filesystem.writeFile(destination, new Uint8Array(), { ...options, flag: options.exclusive ? "wx" : "w" });
|
|
319
|
+
}
|
|
320
|
+
async access(path, mode = 0, options = {}) {
|
|
321
|
+
const resolved = await this.#resolve(path, options);
|
|
322
|
+
if (resolved === nullPath || resolved === deviceDirectory) {
|
|
323
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 7)
|
|
324
|
+
throw new FsError("EINVAL", { path });
|
|
325
|
+
if ((resolved === nullPath && (mode & 1)) || (resolved === deviceDirectory && (mode & 2)))
|
|
326
|
+
throw new FsError("EACCES", { path });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
await this.#filesystem.access(path, mode, options);
|
|
330
|
+
}
|
|
331
|
+
async readlink(path, options = {}) {
|
|
332
|
+
if (reserved(await this.#resolve(path, options, false)))
|
|
333
|
+
throw new FsError("EINVAL", { path });
|
|
334
|
+
if (!this.#filesystem.readlink)
|
|
335
|
+
throw new FsError("ENOTSUP", { path });
|
|
336
|
+
return this.#filesystem.readlink(path, options);
|
|
337
|
+
}
|
|
338
|
+
async symlink(target, path, options = {}) {
|
|
339
|
+
await this.#mutable(path, options, false);
|
|
340
|
+
if (!this.#filesystem.symlink)
|
|
341
|
+
throw new FsError("ENOTSUP", { path });
|
|
342
|
+
await this.#filesystem.symlink(target, path, options);
|
|
343
|
+
}
|
|
344
|
+
async link(existingPath, newPath, options = {}) {
|
|
345
|
+
await this.#mutable(existingPath, options, false);
|
|
346
|
+
await this.#mutable(newPath, options, false);
|
|
347
|
+
if (!this.#filesystem.link)
|
|
348
|
+
throw new FsError("ENOTSUP", { path: newPath });
|
|
349
|
+
await this.#filesystem.link(existingPath, newPath, options);
|
|
350
|
+
}
|
|
351
|
+
async chmod(path, mode, options = {}) {
|
|
352
|
+
await this.#mutable(path, options);
|
|
353
|
+
if (!this.#filesystem.chmod)
|
|
354
|
+
throw new FsError("ENOTSUP", { path });
|
|
355
|
+
await this.#filesystem.chmod(path, mode, options);
|
|
356
|
+
}
|
|
357
|
+
async utimes(path, atimeMs, mtimeMs, options = {}) {
|
|
358
|
+
await this.#mutable(path, options);
|
|
359
|
+
if (!this.#filesystem.utimes)
|
|
360
|
+
throw new FsError("ENOTSUP", { path });
|
|
361
|
+
await this.#filesystem.utimes(path, atimeMs, mtimeMs, options);
|
|
362
|
+
}
|
|
363
|
+
async truncate(path, length, options = {}) {
|
|
364
|
+
await this.#mutable(path, options);
|
|
365
|
+
if (!this.#filesystem.truncate)
|
|
366
|
+
throw new FsError("ENOTSUP", { path });
|
|
367
|
+
await this.#filesystem.truncate(path, length, options);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export function createDeviceFileSystem(filesystem) {
|
|
371
|
+
if (filesystem instanceof DeviceFileSystem)
|
|
372
|
+
return filesystem;
|
|
373
|
+
let view = views.get(filesystem);
|
|
374
|
+
if (!view) {
|
|
375
|
+
view = new DeviceFileSystem(filesystem);
|
|
376
|
+
views.set(filesystem, view);
|
|
377
|
+
}
|
|
378
|
+
return view;
|
|
379
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FileSystem, FsOptions } from "../../contracts/filesystem.js";
|
|
2
|
+
export declare const nullPath = "/dev/null";
|
|
3
|
+
export declare const deviceDirectory = "/dev";
|
|
4
|
+
export declare function lexicalDevicePath(path: string): string;
|
|
5
|
+
export declare function resolveDevicePath(filesystem: FileSystem, path: string, options: FsOptions, followFinal?: boolean): Promise<string>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FsError, isFsError } from "../../contracts/errors.js";
|
|
2
|
+
import { validatePath } from "../../contracts/virtual-path.js";
|
|
3
|
+
export const nullPath = "/dev/null";
|
|
4
|
+
export const deviceDirectory = "/dev";
|
|
5
|
+
export function lexicalDevicePath(path) {
|
|
6
|
+
validatePath(path);
|
|
7
|
+
const parts = [];
|
|
8
|
+
for (const component of path.split("/")) {
|
|
9
|
+
if (`/${parts.join("/")}` === nullPath)
|
|
10
|
+
throw new FsError("ENOTDIR", { path });
|
|
11
|
+
if (component === "..")
|
|
12
|
+
parts.pop();
|
|
13
|
+
else if (component && component !== ".")
|
|
14
|
+
parts.push(component);
|
|
15
|
+
}
|
|
16
|
+
return `/${parts.join("/")}`;
|
|
17
|
+
}
|
|
18
|
+
export async function resolveDevicePath(filesystem, path, options, followFinal = true) {
|
|
19
|
+
options.signal?.throwIfAborted();
|
|
20
|
+
const lexical = lexicalDevicePath(path);
|
|
21
|
+
const aliases = typeof filesystem.lstat === "function";
|
|
22
|
+
if (!aliases && lexical !== nullPath && lexical !== deviceDirectory && lexical !== "/")
|
|
23
|
+
return lexical;
|
|
24
|
+
const pending = path.split("/");
|
|
25
|
+
const parts = [];
|
|
26
|
+
let links = 0;
|
|
27
|
+
let expanded = path.length;
|
|
28
|
+
let absolute = path.startsWith("/");
|
|
29
|
+
let traversalFailure;
|
|
30
|
+
while (pending.length) {
|
|
31
|
+
options.signal?.throwIfAborted();
|
|
32
|
+
if (`/${parts.join("/")}` === nullPath)
|
|
33
|
+
throw new FsError("ENOTDIR", { path });
|
|
34
|
+
const component = pending.shift();
|
|
35
|
+
if (!component || component === ".")
|
|
36
|
+
continue;
|
|
37
|
+
if (component === "..") {
|
|
38
|
+
parts.pop();
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const candidate = `/${[...parts, component].join("/")}`;
|
|
42
|
+
if (candidate !== deviceDirectory && candidate !== nullPath && ((followFinal && aliases) || pending.length)) {
|
|
43
|
+
const lookup = absolute ? candidate : candidate.slice(1);
|
|
44
|
+
let stat;
|
|
45
|
+
try {
|
|
46
|
+
if (typeof filesystem.lstat !== "function")
|
|
47
|
+
throw new FsError("ENOTSUP", { path });
|
|
48
|
+
stat = await filesystem.lstat(lookup, options);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
options.signal?.throwIfAborted();
|
|
52
|
+
if (isFsError(error, "ENOTSUP") && links === 0 && lexical !== nullPath && lexical !== deviceDirectory && lexical !== "/")
|
|
53
|
+
return lexical;
|
|
54
|
+
if (!isFsError(error, "ENOENT"))
|
|
55
|
+
throw error;
|
|
56
|
+
if (pending.length)
|
|
57
|
+
traversalFailure ??= error;
|
|
58
|
+
}
|
|
59
|
+
options.signal?.throwIfAborted();
|
|
60
|
+
if (stat?.type === "symlink") {
|
|
61
|
+
if (typeof filesystem.readlink !== "function")
|
|
62
|
+
throw new FsError("ENOTSUP", { path });
|
|
63
|
+
if (++links > 40)
|
|
64
|
+
throw new FsError("ELOOP", { path });
|
|
65
|
+
const target = await filesystem.readlink(lookup, options);
|
|
66
|
+
options.signal?.throwIfAborted();
|
|
67
|
+
validatePath(target);
|
|
68
|
+
expanded += target.length;
|
|
69
|
+
if (expanded > 65536)
|
|
70
|
+
throw new FsError("ENAMETOOLONG", { path });
|
|
71
|
+
if (target.startsWith("/")) {
|
|
72
|
+
parts.length = 0;
|
|
73
|
+
absolute = true;
|
|
74
|
+
}
|
|
75
|
+
pending.unshift(...target.split("/"));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (stat && pending.length && stat.type !== "directory")
|
|
79
|
+
traversalFailure ??= new FsError("ENOTDIR", { path });
|
|
80
|
+
}
|
|
81
|
+
parts.push(component);
|
|
82
|
+
}
|
|
83
|
+
const resolved = `/${parts.join("/")}`;
|
|
84
|
+
if (traversalFailure && (resolved === nullPath || resolved === deviceDirectory || resolved === "/"))
|
|
85
|
+
throw traversalFailure;
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ByteSource } from "../../contracts/io.js";
|
|
2
|
+
import type { FileReadHandle } from "../../contracts/filesystem.js";
|
|
3
|
+
export declare function deviceReadStream(open: () => Promise<ByteSource>): ByteSource;
|
|
4
|
+
export declare function drainDeviceFile(handle: FileReadHandle, signal?: AbortSignal): Promise<void>;
|
|
5
|
+
export declare function drainDeviceInput(source: ByteSource, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
2
|
+
export function deviceReadStream(open) {
|
|
3
|
+
return { [Symbol.asyncIterator]() {
|
|
4
|
+
let opened;
|
|
5
|
+
let closing;
|
|
6
|
+
const iterator = () => opened ??= open().then(source => source[Symbol.asyncIterator]());
|
|
7
|
+
const close = () => closing ??= iterator().then(async (source) => source.return ? source.return() : { done: true, value: undefined });
|
|
8
|
+
return {
|
|
9
|
+
async next() {
|
|
10
|
+
if (closing) {
|
|
11
|
+
await closing;
|
|
12
|
+
return { done: true, value: undefined };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return await (await iterator()).next();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
await finishCleanup(close, true);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
return: close,
|
|
23
|
+
};
|
|
24
|
+
} };
|
|
25
|
+
}
|
|
26
|
+
export async function drainDeviceFile(handle, signal) {
|
|
27
|
+
let failed = false;
|
|
28
|
+
let position = 0;
|
|
29
|
+
try {
|
|
30
|
+
await drainDeviceInput({ [Symbol.asyncIterator]: () => ({ async next() {
|
|
31
|
+
const value = await handle.read(position, 65536, signal ? { signal } : {});
|
|
32
|
+
if (!(value instanceof Uint8Array) || value.byteLength > 65536)
|
|
33
|
+
throw new TypeError("Retained reads must return bounded Uint8Array chunks");
|
|
34
|
+
position += value.byteLength;
|
|
35
|
+
if (!Number.isSafeInteger(position))
|
|
36
|
+
throw new RangeError("Retained read position exceeds safe integer range");
|
|
37
|
+
return { done: value.byteLength === 0, value };
|
|
38
|
+
} }) }, signal);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
failed = true;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await finishCleanup(async () => {
|
|
46
|
+
try {
|
|
47
|
+
await handle.close();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
signal?.throwIfAborted();
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}, failed);
|
|
54
|
+
}
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
}
|
|
57
|
+
function nextChunk(iterator, signal) {
|
|
58
|
+
signal?.throwIfAborted();
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const aborted = () => { signal?.removeEventListener("abort", aborted); reject(signal?.reason); };
|
|
61
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
62
|
+
try {
|
|
63
|
+
Promise.resolve(iterator.next()).then(result => {
|
|
64
|
+
signal?.removeEventListener("abort", aborted);
|
|
65
|
+
if (signal?.aborted)
|
|
66
|
+
reject(signal.reason);
|
|
67
|
+
else
|
|
68
|
+
resolve(result);
|
|
69
|
+
}, error => { signal?.removeEventListener("abort", aborted); reject(signal?.aborted ? signal.reason : error); });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
signal?.removeEventListener("abort", aborted);
|
|
73
|
+
reject(signal?.aborted ? signal.reason : error);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function drainDeviceInput(source, signal) {
|
|
78
|
+
signal?.throwIfAborted();
|
|
79
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
80
|
+
let failed = false;
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
let pulls = 0;
|
|
83
|
+
try {
|
|
84
|
+
for (;;) {
|
|
85
|
+
const result = await nextChunk(iterator, signal);
|
|
86
|
+
signal?.throwIfAborted();
|
|
87
|
+
if (result.done)
|
|
88
|
+
break;
|
|
89
|
+
if (!(result.value instanceof Uint8Array))
|
|
90
|
+
throw new TypeError("Byte sources must yield Uint8Array chunks");
|
|
91
|
+
bytes += result.value.byteLength;
|
|
92
|
+
if (++pulls >= 64 || bytes >= 65536) {
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
94
|
+
signal?.throwIfAborted();
|
|
95
|
+
bytes = 0;
|
|
96
|
+
pulls = 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
failed = true;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await finishCleanup(async () => {
|
|
106
|
+
try {
|
|
107
|
+
await iterator.return?.();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
signal?.throwIfAborted();
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}, failed);
|
|
114
|
+
}
|
|
115
|
+
signal?.throwIfAborted();
|
|
116
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileStat, FileSystem, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileStat, FileSystem, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } 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";
|
|
@@ -20,6 +20,7 @@ export declare class MemoryFileSystem implements FileSystem {
|
|
|
20
20
|
removeDirectory: true;
|
|
21
21
|
recursiveRemove: true;
|
|
22
22
|
rename: true;
|
|
23
|
+
atomicRenameNoReplace: true;
|
|
23
24
|
copy: true;
|
|
24
25
|
exclusiveCopy: true;
|
|
25
26
|
readlink: true;
|
|
@@ -76,7 +77,7 @@ export declare class MemoryFileSystem implements FileSystem {
|
|
|
76
77
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
77
78
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
78
79
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
79
|
-
rename(source: string, destination: string, options?:
|
|
80
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
80
81
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
81
82
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
82
83
|
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
|
@@ -57,7 +57,7 @@ export class MemoryFileSystem {
|
|
|
57
57
|
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
58
58
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
59
59
|
mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
|
|
60
|
-
rename: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
60
|
+
rename: true, atomicRenameNoReplace: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
61
61
|
streamingAppend: true, randomAccessWrite: true,
|
|
62
62
|
readOnly: false,
|
|
63
63
|
symlinks: true,
|
|
@@ -515,6 +515,8 @@ export class MemoryFileSystem {
|
|
|
515
515
|
this.fail("EBUSY", "rename", source, destination);
|
|
516
516
|
if (destination.endsWith("/") && node.type !== "directory")
|
|
517
517
|
this.fail("ENOTDIR", "rename", source, destination);
|
|
518
|
+
if (options.noReplace && target.node)
|
|
519
|
+
this.fail("EEXIST", "rename", source, destination);
|
|
518
520
|
if (target.node === node)
|
|
519
521
|
return;
|
|
520
522
|
if (node.type === "directory" && target.path.startsWith(`${origin.path}/`))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, 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;
|
|
@@ -29,7 +29,7 @@ export declare class MountFileSystem implements FileSystem {
|
|
|
29
29
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
30
30
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
31
31
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
32
|
-
rename(source: string, destination: string, options?:
|
|
32
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
33
33
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
34
34
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
35
35
|
compareEntry(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<import("../../contracts/filesystem.js").EntryComparison>;
|
|
@@ -96,14 +96,14 @@ export class MountFileSystem {
|
|
|
96
96
|
return false;
|
|
97
97
|
return declared === true && optional[capability]?.some(method => typeof backend[method] !== "function") ? false : declared;
|
|
98
98
|
});
|
|
99
|
-
if (["rename", "copy", "exclusiveCopy"].includes(capability) && mounts.length > 1)
|
|
99
|
+
if (["rename", "atomicRenameNoReplace", "copy", "exclusiveCopy"].includes(capability) && mounts.length > 1)
|
|
100
100
|
return undefined;
|
|
101
101
|
return values.every(value => value === true) ? true : values.every(value => value === false) ? false : undefined;
|
|
102
102
|
};
|
|
103
103
|
const semantics = Object.fromEntries([
|
|
104
104
|
"read", "stat", "readdir", "realpath", "access",
|
|
105
105
|
"write", "append", "exclusiveCreate", "explicitDirectories", "implicitDirectories", "mkdir", "recursiveMkdir",
|
|
106
|
-
"remove", "removeDirectory", "recursiveRemove", "rename", "copy", "exclusiveCopy", "readlink", "truncate",
|
|
106
|
+
"remove", "removeDirectory", "recursiveRemove", "rename", "atomicRenameNoReplace", "copy", "exclusiveCopy", "readlink", "truncate",
|
|
107
107
|
"streamingAppend", "randomAccessWrite", "descriptorWriteStream", "symlinks", "hardlinks", "permissions", "timestamps",
|
|
108
108
|
].map(capability => [capability, common(capability)]).filter(([, value]) => value !== undefined));
|
|
109
109
|
this.capabilities = Object.freeze({
|
|
@@ -455,6 +455,12 @@ export class MountFileSystem {
|
|
|
455
455
|
this.mutable(target);
|
|
456
456
|
this.entryPath(source);
|
|
457
457
|
this.entryPath(destination);
|
|
458
|
+
if (options.noReplace) {
|
|
459
|
+
const capabilities = await target.mount.backend.capabilitiesFor?.(target.local, options) ?? target.mount.backend.capabilities;
|
|
460
|
+
options.signal?.throwIfAborted();
|
|
461
|
+
if (capabilities.atomicRenameNoReplace !== true)
|
|
462
|
+
fail("ENOTSUP");
|
|
463
|
+
}
|
|
458
464
|
await origin.mount.backend.rename(origin.local, target.local, options);
|
|
459
465
|
}, destination);
|
|
460
466
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ByteSource } from "../../contracts/io.js";
|
|
2
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
2
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
3
3
|
export interface OverlayFileSystemOptions {
|
|
4
4
|
readonly upper: FileSystem;
|
|
5
5
|
readonly lower: FileSystem;
|
|
@@ -58,7 +58,7 @@ export declare class OverlayFileSystem implements FileSystem {
|
|
|
58
58
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
59
59
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
60
60
|
private materialize;
|
|
61
|
-
rename(source: string, destination: string, options?:
|
|
61
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
62
62
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
63
63
|
readlink(path: string, options?: FsOptions): Promise<string>;
|
|
64
64
|
symlink(target: string, path: string, options?: FsOptions): Promise<void>;
|
|
@@ -117,7 +117,7 @@ export class OverlayFileSystem {
|
|
|
117
117
|
: upper.readlink === false && this.#lower.capabilities.readlink === false ? false : undefined,
|
|
118
118
|
...(upper.readOnly === undefined ? {} : { readOnly: upper.readOnly }),
|
|
119
119
|
...(effectiveAppend === undefined ? {} : { append: effectiveAppend }),
|
|
120
|
-
atomicRename: false,
|
|
120
|
+
atomicRename: false, atomicRenameNoReplace: false,
|
|
121
121
|
descriptorWriteStream: false,
|
|
122
122
|
hardlinks: false,
|
|
123
123
|
symlinks: writable && this.#upper.capabilities.symlinks === true
|
|
@@ -737,6 +737,8 @@ export class OverlayFileSystem {
|
|
|
737
737
|
}
|
|
738
738
|
async rename(source, destination, options = {}) {
|
|
739
739
|
return this.run(options, async () => {
|
|
740
|
+
if (options.noReplace)
|
|
741
|
+
fail("ENOTSUP", source, "atomic no-replace rename is unsupported");
|
|
740
742
|
this.writable(source);
|
|
741
743
|
const original = await this.required(source, options, false);
|
|
742
744
|
const target = await this.resolve(destination, options, false, true);
|
|
@@ -130,6 +130,16 @@ export function withFileSystemQuota(fs, options) {
|
|
|
130
130
|
await fs.appendFile(path, data, appendOptions);
|
|
131
131
|
});
|
|
132
132
|
},
|
|
133
|
+
async rename(source, destination, renameOptions) {
|
|
134
|
+
if (renameOptions?.noReplace) {
|
|
135
|
+
renameOptions.signal?.throwIfAborted();
|
|
136
|
+
const capabilities = await fs.capabilitiesFor?.(destination, renameOptions) ?? fs.capabilities;
|
|
137
|
+
renameOptions?.signal?.throwIfAborted();
|
|
138
|
+
if (capabilities.atomicRenameNoReplace !== true)
|
|
139
|
+
throw new FsError("ENOTSUP", { syscall: "rename", path: source, dest: destination });
|
|
140
|
+
}
|
|
141
|
+
await fs.rename(source, destination, renameOptions);
|
|
142
|
+
},
|
|
133
143
|
copyFile(source, destination, copyOptions) {
|
|
134
144
|
return mutate(async () => {
|
|
135
145
|
await assertDelta(destination, (await fs.stat(source, copyOptions)).size, copyOptions);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ByteSource } from "../../contracts/io.js";
|
|
2
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
2
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
3
3
|
export declare class ReadOnlyFileSystem implements FileSystem {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(filesystem: FileSystem);
|
|
@@ -22,7 +22,7 @@ export declare class ReadOnlyFileSystem implements FileSystem {
|
|
|
22
22
|
rm(path: string, _options?: RemoveOptions): Promise<void>;
|
|
23
23
|
unlink(path: string, _options?: FsOptions): Promise<void>;
|
|
24
24
|
rmdir(path: string, _options?: FsOptions): Promise<void>;
|
|
25
|
-
rename(source: string, destination: string, _options?:
|
|
25
|
+
rename(source: string, destination: string, _options?: RenameOptions): Promise<void>;
|
|
26
26
|
copyFile(source: string, destination: string, _options?: CopyFileOptions): Promise<void>;
|
|
27
27
|
symlink(target: string, path: string, _options?: FsOptions): Promise<void>;
|
|
28
28
|
link(existingPath: string, newPath: string, _options?: FsOptions): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
|
|
1
|
+
import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
|
|
2
2
|
export interface RealFileSystemOptions {
|
|
3
3
|
/** An existing, absolute host directory. It is never created implicitly. */
|
|
4
4
|
readonly root: string;
|
|
@@ -59,7 +59,7 @@ export declare class RealFileSystem implements FileSystem {
|
|
|
59
59
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
60
60
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
61
61
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
62
|
-
rename(source: string, destination: string, options?:
|
|
62
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
63
63
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
64
64
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
65
65
|
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
|
@@ -84,7 +84,7 @@ export class RealFileSystem {
|
|
|
84
84
|
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
85
85
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
86
86
|
mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
|
|
87
|
-
rename: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
87
|
+
rename: true, atomicRenameNoReplace: false, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
88
88
|
streamingAppend: true, randomAccessWrite: true,
|
|
89
89
|
readOnly: false, symlinks: true, hardlinks: true, permissions: true,
|
|
90
90
|
timestamps: true, atomicRename: true, streamingRead: true, streamingWrite: true, retainedRead: true,
|
|
@@ -346,6 +346,8 @@ export class RealFileSystem {
|
|
|
346
346
|
}
|
|
347
347
|
async rename(source, destination, options = {}) {
|
|
348
348
|
return this.operation("rename", source, options, async () => {
|
|
349
|
+
if (options.noReplace)
|
|
350
|
+
throw new FsError("ENOTSUP", { syscall: "rename", path: source, dest: destination });
|
|
349
351
|
const from = await this.path(source, { ...options, followFinal: false });
|
|
350
352
|
const to = await this.path(destination, { ...options, followFinal: false, missing: "final" });
|
|
351
353
|
this.protectTerminal(source);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FsError } from "../../contracts/errors.js";
|
|
2
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
2
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
3
3
|
import type { ByteSource } from "../../contracts/io.js";
|
|
4
4
|
import type { S3Transport } from "./transport.js";
|
|
5
5
|
export interface S3FileSystemOptions {
|
|
@@ -41,6 +41,7 @@ export declare class S3FileSystem implements FileSystem {
|
|
|
41
41
|
append: boolean;
|
|
42
42
|
exclusiveCreate: boolean;
|
|
43
43
|
truncate: boolean;
|
|
44
|
+
atomicRenameNoReplace: false;
|
|
44
45
|
rename: boolean;
|
|
45
46
|
streamingAppend: boolean;
|
|
46
47
|
randomAccessWrite: false;
|
|
@@ -99,7 +100,7 @@ export declare class S3FileSystem implements FileSystem {
|
|
|
99
100
|
rm(input: string, options?: RemoveOptions): Promise<void>;
|
|
100
101
|
private copy;
|
|
101
102
|
copyFile(sourceInput: string, destinationInput: string, options?: CopyFileOptions): Promise<void>;
|
|
102
|
-
rename(sourceInput: string, destinationInput: string, options?:
|
|
103
|
+
rename(sourceInput: string, destinationInput: string, options?: RenameOptions): Promise<void>;
|
|
103
104
|
private copyWithPut;
|
|
104
105
|
realpath(input: string, options?: FsOptions): Promise<string>;
|
|
105
106
|
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
|
@@ -112,6 +112,7 @@ export class S3FileSystem {
|
|
|
112
112
|
append: options.transport.capabilities?.conditionalPut === true,
|
|
113
113
|
exclusiveCreate: options.transport.capabilities?.conditionalPut === true,
|
|
114
114
|
truncate: options.transport.capabilities?.conditionalPut === true,
|
|
115
|
+
atomicRenameNoReplace: false,
|
|
115
116
|
rename: this.allowRename && options.transport.capabilities?.conditionalDelete === true
|
|
116
117
|
&& (options.transport.capabilities?.conditionalCopy === true || options.transport.capabilities?.conditionalPut === true),
|
|
117
118
|
streamingAppend: options.transport.capabilities?.streamingWrite === true
|
|
@@ -674,6 +675,10 @@ export class S3FileSystem {
|
|
|
674
675
|
}
|
|
675
676
|
}
|
|
676
677
|
async rename(sourceInput, destinationInput, options = {}) {
|
|
678
|
+
if (options.noReplace) {
|
|
679
|
+
options.signal?.throwIfAborted();
|
|
680
|
+
this.unsupported("atomic no-replace rename", sourceInput);
|
|
681
|
+
}
|
|
677
682
|
const source = this.path(sourceInput);
|
|
678
683
|
const destination = this.path(destinationInput);
|
|
679
684
|
this.writable(destination);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FsError } from "../contracts/errors.js";
|
|
1
2
|
import { finishCleanup } from "../contracts/cleanup.js";
|
|
2
3
|
import { registerEntryView } from "./mount/comparison.js";
|
|
3
4
|
const originals = new WeakMap();
|
|
@@ -76,6 +77,17 @@ export function scopeFileSystem(filesystem, charge, signal) {
|
|
|
76
77
|
const peer = args[1];
|
|
77
78
|
args[1] = originals.get(peer) ?? peer;
|
|
78
79
|
}
|
|
80
|
+
if (property === "rename" && args[2]?.noReplace)
|
|
81
|
+
return (async () => {
|
|
82
|
+
const options = args[2];
|
|
83
|
+
const capabilities = await original.capabilitiesFor?.(args[1], options) ?? original.capabilities;
|
|
84
|
+
assertOpen(options);
|
|
85
|
+
if (capabilities.atomicRenameNoReplace !== true)
|
|
86
|
+
throw new FsError("ENOTSUP", {
|
|
87
|
+
syscall: "rename", path: args[0], dest: args[1],
|
|
88
|
+
});
|
|
89
|
+
return Reflect.apply(method, original, args);
|
|
90
|
+
})();
|
|
79
91
|
return Reflect.apply(method, original, args);
|
|
80
92
|
};
|
|
81
93
|
const scoped = property === "openReadFile"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PlatformComparisonCallback } from "#safe-fs-platform";
|
|
2
2
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
3
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
4
4
|
export type WebDavFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
5
5
|
export interface WebDavAtomicEmptyDirectoryRequest {
|
|
6
6
|
readonly operation: "atomic-empty-rmdir/v1";
|
|
@@ -49,6 +49,7 @@ export declare class WebDavFileSystem implements FileSystem {
|
|
|
49
49
|
remove: true;
|
|
50
50
|
recursiveRemove: true;
|
|
51
51
|
rename: true;
|
|
52
|
+
atomicRenameNoReplace: false;
|
|
52
53
|
copy: true;
|
|
53
54
|
exclusiveCopy: true;
|
|
54
55
|
readlink: false;
|
|
@@ -114,7 +115,7 @@ export declare class WebDavFileSystem implements FileSystem {
|
|
|
114
115
|
private guardTransferIdentity;
|
|
115
116
|
private transfer;
|
|
116
117
|
private withDestinationLock;
|
|
117
|
-
rename(source: string, destination: string, options?:
|
|
118
|
+
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
118
119
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
119
120
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
120
121
|
access(path: string, mode?: number, options?: FsOptions): Promise<void>;
|
|
@@ -143,7 +143,7 @@ export class WebDavFileSystem {
|
|
|
143
143
|
capabilities = Object.freeze({
|
|
144
144
|
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
145
145
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
146
|
-
mkdir: true, recursiveMkdir: true, remove: true, recursiveRemove: true, rename: true, copy: true,
|
|
146
|
+
mkdir: true, recursiveMkdir: true, remove: true, recursiveRemove: true, rename: true, atomicRenameNoReplace: false, copy: true,
|
|
147
147
|
exclusiveCopy: true, readlink: false, truncate: false, randomAccessWrite: false,
|
|
148
148
|
removeDirectory: false, streamingAppend: false,
|
|
149
149
|
symlinks: false, hardlinks: false, permissions: false, timestamps: true,
|
|
@@ -1192,6 +1192,10 @@ export class WebDavFileSystem {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
1194
|
async rename(source, destination, options = {}) {
|
|
1195
|
+
if (options.noReplace) {
|
|
1196
|
+
options.signal?.throwIfAborted();
|
|
1197
|
+
fail("ENOTSUP", "rename", source, "atomic no-replace rename is unsupported");
|
|
1198
|
+
}
|
|
1195
1199
|
await this.transfer("MOVE", source, destination, options);
|
|
1196
1200
|
}
|
|
1197
1201
|
async copyFile(source, destination, options = {}) {
|
package/dist/safe-fs/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./contracts/index.js";
|
|
2
2
|
export * from "./fs/memory/index.js";
|
|
3
|
+
export * from "./fs/devices/index.js";
|
|
3
4
|
export * from "./fs/real/index.js";
|
|
4
5
|
export * from "./fs/s3/index.js";
|
|
5
6
|
export type { S3StreamGetInput, S3StreamGetOutput, S3StreamPutInput } from "./fs/s3/transport.js";
|
package/dist/safe-fs/index.js
CHANGED