@poe-platform/safe-fs 0.1.630 → 0.1.632
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 +1 -1
- package/dist/safe-fs/contracts/descriptor.d.ts +31 -0
- package/dist/safe-fs/contracts/descriptor.js +1 -0
- package/dist/safe-fs/contracts/errors.d.ts +1 -1
- package/dist/safe-fs/contracts/errors.js +1 -1
- package/dist/safe-fs/contracts/filesystem.d.ts +14 -1
- package/dist/safe-fs/core.d.ts +3 -0
- package/dist/safe-fs/core.js +2 -0
- package/dist/safe-fs/fs/capabilities.d.ts +1 -1
- package/dist/safe-fs/fs/capabilities.js +5 -3
- package/dist/safe-fs/fs/descriptor.d.ts +21 -0
- package/dist/safe-fs/fs/descriptor.js +244 -0
- package/dist/safe-fs/fs/devices/index.d.ts +5 -1
- package/dist/safe-fs/fs/devices/index.js +53 -4
- package/dist/safe-fs/fs/memory/index.d.ts +8 -1
- package/dist/safe-fs/fs/memory/index.js +125 -8
- package/dist/safe-fs/fs/memory/limits.d.ts +1 -0
- package/dist/safe-fs/fs/memory/limits.js +7 -1
- package/dist/safe-fs/fs/mount/index.d.ts +5 -1
- package/dist/safe-fs/fs/mount/index.js +69 -6
- package/dist/safe-fs/fs/overlay/index.d.ts +2 -0
- package/dist/safe-fs/fs/overlay/index.js +9 -1
- package/dist/safe-fs/fs/quota/index.js +5 -1
- package/dist/safe-fs/fs/readonly/index.d.ts +2 -0
- package/dist/safe-fs/fs/readonly/index.js +35 -2
- package/dist/safe-fs/fs/real/index.d.ts +3 -0
- package/dist/safe-fs/fs/real/index.js +73 -3
- package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
- package/dist/safe-fs/fs/s3/filesystem.js +5 -0
- package/dist/safe-fs/fs/scoped.js +68 -17
- package/dist/safe-fs/fs/webdav/webdav.d.ts +3 -0
- package/dist/safe-fs/fs/webdav/webdav.js +5 -0
- package/dist/safe-fs/index.d.ts +3 -0
- package/dist/safe-fs/index.js +2 -0
- package/dist/safe-fs/python/emscripten.d.ts +14 -0
- package/dist/safe-fs/python/emscripten.js +354 -0
- package/dist/safe-fs/python/filesystem.d.ts +51 -0
- package/dist/safe-fs/python/filesystem.js +216 -0
- package/dist/safe-fs/python/flags.d.ts +3 -0
- package/dist/safe-fs/python/flags.js +14 -0
- package/dist/safe-fs/python/index.d.ts +7 -0
- package/dist/safe-fs/python/index.js +4 -0
- package/dist/safe-fs/python/request.d.ts +2 -0
- package/dist/safe-fs/python/request.js +61 -0
- package/dist/safe-fs/python/stat.d.ts +8 -0
- package/dist/safe-fs/python/stat.js +31 -0
- package/dist/safe-fs/xml.d.ts +3 -0
- package/dist/safe-fs/xml.js +41 -19
- package/package.json +5 -1
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import { ACCESS_MODES } from "../../contracts/filesystem.js";
|
|
2
2
|
import { FsError } from "../../contracts/errors.js";
|
|
3
3
|
import { readBytes } from "../../contracts/io.js";
|
|
4
|
+
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
4
5
|
import { compareEntries, registerEntryView } from "../mount/comparison.js";
|
|
5
6
|
import { openRetainedReadFile, readOnlyCapabilities, retainedReadCapabilities } from "../capabilities.js";
|
|
6
7
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
8
|
+
import { forwardFileDescriptor, openFileDescriptor } from "../descriptor.js";
|
|
7
9
|
import { pathNamespace, readOnlyPathNamespace } from "../path-namespace.js";
|
|
8
10
|
function readOnly(syscall, path, dest) {
|
|
9
11
|
throw new FsError("EROFS", { syscall, path, ...(dest === undefined ? {} : { dest }) });
|
|
10
12
|
}
|
|
11
13
|
function snapshotStat(stat) {
|
|
12
|
-
const { type, size, allocatedBytes, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, ino, dev, nlink, uid, gid } = stat;
|
|
14
|
+
const { type, size, allocatedBytes, ioBlockSize, preferredIoBlockSize, mode, mtimeMs, atimeMs, ctimeMs, birthtimeMs, revision, identityScope, ino, dev, rdevMajor, rdevMinor, nlink, uid, gid } = stat;
|
|
13
15
|
return {
|
|
14
16
|
type, size, mode, mtimeMs, atimeMs, ctimeMs,
|
|
15
17
|
...(revision === undefined ? {} : { revision }),
|
|
16
18
|
...(allocatedBytes === undefined ? {} : { allocatedBytes }),
|
|
19
|
+
...(ioBlockSize === undefined ? {} : { ioBlockSize }),
|
|
17
20
|
...(preferredIoBlockSize === undefined ? {} : { preferredIoBlockSize }),
|
|
18
21
|
...(birthtimeMs === undefined ? {} : { birthtimeMs }),
|
|
19
22
|
...(identityScope === undefined ? {} : { identityScope }),
|
|
20
23
|
...(ino === undefined ? {} : { ino }),
|
|
21
24
|
...(dev === undefined ? {} : { dev }),
|
|
25
|
+
...(rdevMajor === undefined ? {} : { rdevMajor }),
|
|
26
|
+
...(rdevMinor === undefined ? {} : { rdevMinor }),
|
|
22
27
|
...(nlink === undefined ? {} : { nlink }),
|
|
23
28
|
...(uid === undefined ? {} : { uid }),
|
|
24
29
|
...(gid === undefined ? {} : { gid }),
|
|
@@ -37,6 +42,7 @@ export class ReadOnlyFileSystem {
|
|
|
37
42
|
const streamingRead = typeof filesystem.readStream === "function" ? filesystem.capabilities.streamingRead : false;
|
|
38
43
|
this.#capabilities = readOnlyCapabilities({
|
|
39
44
|
...retainedReadCapabilities(filesystem),
|
|
45
|
+
...(typeof filesystem.open === "function" ? {} : { open: false }),
|
|
40
46
|
readOnly: true,
|
|
41
47
|
append: false,
|
|
42
48
|
symlinks: filesystem.capabilities.symlinks === true && typeof filesystem.readlink === "function",
|
|
@@ -56,7 +62,34 @@ export class ReadOnlyFileSystem {
|
|
|
56
62
|
if (options?.create !== undefined)
|
|
57
63
|
readOnly("capabilitiesFor", path);
|
|
58
64
|
const capabilities = await this.#filesystem.capabilitiesFor?.(path, options) ?? this.#filesystem.capabilities;
|
|
59
|
-
return readOnlyCapabilities(retainedReadCapabilities(this.#filesystem, capabilities));
|
|
65
|
+
return readOnlyCapabilities({ ...retainedReadCapabilities(this.#filesystem, capabilities), ...(typeof this.#filesystem.open === "function" ? {} : { open: false }) });
|
|
66
|
+
}
|
|
67
|
+
async open(path, options) {
|
|
68
|
+
options?.signal?.throwIfAborted();
|
|
69
|
+
if (options && (options.access === "write" || options.access === "readwrite" || options.truncate === true || options.append === true
|
|
70
|
+
|| options.creation === "ifMissing" || options.creation === "exclusive"))
|
|
71
|
+
readOnly("open", path);
|
|
72
|
+
return openFileDescriptor(path, options, {
|
|
73
|
+
positionedRead: true, positionedWrite: false, truncate: false, synchronization: "none",
|
|
74
|
+
}, async (admitted) => {
|
|
75
|
+
const capabilities = await this.#filesystem.capabilitiesFor?.(path, admitted) ?? this.#filesystem.capabilities;
|
|
76
|
+
if (!this.#filesystem.open || capabilities.open === false)
|
|
77
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
78
|
+
admitted.signal?.throwIfAborted();
|
|
79
|
+
const descriptor = await this.#filesystem.open(path, admitted);
|
|
80
|
+
try {
|
|
81
|
+
return forwardFileDescriptor(descriptor, async (_syscall, forwarded, action) => {
|
|
82
|
+
forwarded.signal?.throwIfAborted();
|
|
83
|
+
return action();
|
|
84
|
+
}, { ...descriptor.capabilities,
|
|
85
|
+
...(descriptor.capabilities.openTruncate === undefined ? {} : { openTruncate: false }),
|
|
86
|
+
positionedWrite: false, truncate: false, synchronization: "none" }, snapshotStat);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
await finishCleanup(() => descriptor.close(), true);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
60
93
|
}
|
|
61
94
|
openReadFile(path, options = {}) {
|
|
62
95
|
return openRetainedReadFile(this.#filesystem, path, options);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
1
2
|
import type { AppendFileOptions, ByteSource, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, OpenReadFileOptions, OpenResizeFileOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/index.js";
|
|
2
3
|
export interface RealFileSystemOptions {
|
|
3
4
|
/** An existing, absolute host directory. It is never created implicitly. */
|
|
@@ -49,6 +50,7 @@ export declare class RealFileSystem implements FileSystem {
|
|
|
49
50
|
private path;
|
|
50
51
|
private operation;
|
|
51
52
|
private protectRoot;
|
|
53
|
+
open(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
|
|
52
54
|
private protectTerminal;
|
|
53
55
|
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
54
56
|
writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
@@ -59,6 +61,7 @@ export declare class RealFileSystem implements FileSystem {
|
|
|
59
61
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
60
62
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
61
63
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
64
|
+
unlink(path: string, options?: FsOptions): Promise<void>;
|
|
62
65
|
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
63
66
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
|
64
67
|
realpath(path: string, options?: FsOptions): Promise<string>;
|
|
@@ -2,6 +2,7 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import * as native from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { nativeAllocatedBytes } from "./allocation.js";
|
|
5
|
+
import { openFileDescriptor } from "../descriptor.js";
|
|
5
6
|
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
6
7
|
import { callNativeSeekEnd, loadNativeSeekBinding } from "../../node/native-seek.js";
|
|
7
8
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
@@ -21,6 +22,7 @@ function fileStat(stats) {
|
|
|
21
22
|
return {
|
|
22
23
|
type: fileType(stats), size: stats.size, mode: stats.mode,
|
|
23
24
|
...(allocatedBytes === undefined ? {} : { allocatedBytes }),
|
|
25
|
+
...(Number.isSafeInteger(stats.blksize) && stats.blksize > 0 ? { ioBlockSize: stats.blksize } : {}),
|
|
24
26
|
...(Number.isSafeInteger(preferredIoBlockSize) && preferredIoBlockSize > 0 ? { preferredIoBlockSize } : {}),
|
|
25
27
|
atimeMs: stats.atimeMs, mtimeMs: stats.mtimeMs, ctimeMs: stats.ctimeMs,
|
|
26
28
|
birthtimeMs: stats.birthtimeMs, ino: stats.ino, dev: stats.dev,
|
|
@@ -84,7 +86,7 @@ function nativeError(error) {
|
|
|
84
86
|
*/
|
|
85
87
|
export class RealFileSystem {
|
|
86
88
|
capabilities = Object.freeze({
|
|
87
|
-
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
89
|
+
read: true, stat: true, readdir: true, realpath: true, access: true, open: true,
|
|
88
90
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
89
91
|
mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
|
|
90
92
|
rename: true, atomicRenameNoReplace: false, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
@@ -251,6 +253,62 @@ export class RealFileSystem {
|
|
|
251
253
|
if (resolve(path) === root)
|
|
252
254
|
throw new FsError("EBUSY", { message: "the filesystem root cannot be removed or replaced" });
|
|
253
255
|
}
|
|
256
|
+
open(path, options) {
|
|
257
|
+
return openFileDescriptor(path, options, {
|
|
258
|
+
positionedRead: true, positionedWrite: true, truncate: true, synchronization: "storage",
|
|
259
|
+
}, async (admitted) => {
|
|
260
|
+
let handle;
|
|
261
|
+
try {
|
|
262
|
+
const target = await this.path(path, {
|
|
263
|
+
...admitted, followFinal: admitted.creation !== "exclusive",
|
|
264
|
+
...(admitted.creation === "never" ? {} : { missing: "final" }),
|
|
265
|
+
});
|
|
266
|
+
let flags = (admitted.access === "read" ? constants.O_RDONLY : admitted.access === "write" ? constants.O_WRONLY : constants.O_RDWR)
|
|
267
|
+
| constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
268
|
+
if (admitted.creation !== "never")
|
|
269
|
+
flags |= constants.O_CREAT;
|
|
270
|
+
if (admitted.creation === "exclusive")
|
|
271
|
+
flags |= constants.O_EXCL;
|
|
272
|
+
if (admitted.append)
|
|
273
|
+
flags |= constants.O_APPEND;
|
|
274
|
+
handle = await native.open(target, flags, admitted.mode);
|
|
275
|
+
admitted.signal?.throwIfAborted();
|
|
276
|
+
const stat = await handle.stat();
|
|
277
|
+
if (stat.isDirectory())
|
|
278
|
+
throw new FsError("EISDIR");
|
|
279
|
+
if (!stat.isFile())
|
|
280
|
+
throw new FsError("ENOTSUP");
|
|
281
|
+
admitted.signal?.throwIfAborted();
|
|
282
|
+
if (admitted.truncate)
|
|
283
|
+
await handle.truncate(0);
|
|
284
|
+
const resource = { handle };
|
|
285
|
+
handle = undefined;
|
|
286
|
+
return {
|
|
287
|
+
resource,
|
|
288
|
+
stat: (retained, forwarded) => this.operation("fstat", path, forwarded, async () => fileStat(await retained.handle.stat())),
|
|
289
|
+
read: (retained, buffer, position, forwarded) => this.operation("read", path, forwarded, async () => (await retained.handle.read(buffer, 0, buffer.byteLength, position)).bytesRead),
|
|
290
|
+
write: (retained, buffer, position, forwarded) => this.operation("write", path, forwarded, async () => (await retained.handle.write(buffer, 0, buffer.byteLength, position)).bytesWritten),
|
|
291
|
+
truncate: (retained, length, forwarded) => this.operation("ftruncate", path, forwarded, () => retained.handle.truncate(length)),
|
|
292
|
+
sync: (retained, dataOnly, forwarded) => this.operation(dataOnly ? "fdatasync" : "fsync", path, forwarded, () => dataOnly ? retained.handle.datasync() : retained.handle.sync()),
|
|
293
|
+
close: async (retained) => {
|
|
294
|
+
try {
|
|
295
|
+
await this.operation("close", path, {}, () => retained.handle.close());
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
retained.handle = undefined;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (handle)
|
|
305
|
+
await finishCleanup(() => handle.close(), true);
|
|
306
|
+
handle = undefined;
|
|
307
|
+
admitted.signal?.throwIfAborted();
|
|
308
|
+
throw new FsError(nativeError(error).code, { syscall: "open", path });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
254
312
|
protectTerminal(path) {
|
|
255
313
|
const terminal = path.split("/").filter(Boolean).at(-1);
|
|
256
314
|
if (terminal === "." || terminal === "..")
|
|
@@ -358,6 +416,15 @@ export class RealFileSystem {
|
|
|
358
416
|
await native.rm(target, { recursive: options.recursive ?? false, force: options.force ?? false });
|
|
359
417
|
});
|
|
360
418
|
}
|
|
419
|
+
async unlink(path, options = {}) {
|
|
420
|
+
return this.operation("unlink", path, options, async () => {
|
|
421
|
+
const target = await this.path(path, { ...options, followFinal: false });
|
|
422
|
+
this.protectTerminal(path);
|
|
423
|
+
this.protectRoot(target, await this.root(options));
|
|
424
|
+
options.signal?.throwIfAborted();
|
|
425
|
+
await native.unlink(target);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
361
428
|
async rename(source, destination, options = {}) {
|
|
362
429
|
return this.operation("rename", source, options, async () => {
|
|
363
430
|
if (options.noReplace)
|
|
@@ -465,11 +532,14 @@ export class RealFileSystem {
|
|
|
465
532
|
}
|
|
466
533
|
async utimes(path, atimeMs, mtimeMs, options = {}) {
|
|
467
534
|
return this.operation("utimes", path, options, async () => {
|
|
468
|
-
if (!Number.isFinite(atimeMs) || !Number.isFinite(mtimeMs)
|
|
535
|
+
if (!Number.isFinite(atimeMs) || !Number.isFinite(mtimeMs)
|
|
536
|
+
|| Math.abs(atimeMs) > 8.64e15 || Math.abs(mtimeMs) > 8.64e15)
|
|
469
537
|
throw new FsError("EINVAL");
|
|
470
538
|
const target = await this.path(path, options);
|
|
471
539
|
options.signal?.throwIfAborted();
|
|
472
|
-
|
|
540
|
+
const atimeSeconds = atimeMs / 1000;
|
|
541
|
+
const mtimeSeconds = mtimeMs / 1000;
|
|
542
|
+
await native.utimes(target, atimeSeconds < 0 ? String(atimeSeconds) : atimeSeconds, mtimeSeconds < 0 ? String(mtimeSeconds) : mtimeSeconds);
|
|
473
543
|
});
|
|
474
544
|
}
|
|
475
545
|
async truncate(path, length = 0, options = {}) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FsError } from "../../contracts/errors.js";
|
|
2
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
|
+
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
4
5
|
import type { S3Transport } from "./transport.js";
|
|
5
6
|
export interface S3FileSystemOptions {
|
|
6
7
|
readonly transport: S3Transport;
|
|
@@ -21,7 +22,9 @@ export declare class S3RenameError extends FsError {
|
|
|
21
22
|
constructor(source: string, destination: string, phase: "copy" | "delete", copied: string[], deleted: string[], cause: FsError);
|
|
22
23
|
}
|
|
23
24
|
export declare class S3FileSystem implements FileSystem {
|
|
25
|
+
open(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
|
|
24
26
|
readonly capabilities: Readonly<{
|
|
27
|
+
open: false;
|
|
25
28
|
read: true;
|
|
26
29
|
stat: true;
|
|
27
30
|
readdir: true;
|
|
@@ -67,6 +67,10 @@ function isWellFormed(value) {
|
|
|
67
67
|
return !/[\uD800-\uDFFF]/u.test(value);
|
|
68
68
|
}
|
|
69
69
|
export class S3FileSystem {
|
|
70
|
+
async open(path, options) {
|
|
71
|
+
options.signal?.throwIfAborted();
|
|
72
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
73
|
+
}
|
|
70
74
|
capabilities;
|
|
71
75
|
readStream;
|
|
72
76
|
writeStream;
|
|
@@ -105,6 +109,7 @@ export class S3FileSystem {
|
|
|
105
109
|
this.maxStreamBytes = validateLimit(options.maxStreamBytes ?? 5_000_000_000, "maxStreamBytes", 0, 5_000_000_000);
|
|
106
110
|
this.maxListEntries = validateLimit(options.maxListEntries ?? 100_000, "maxListEntries", 1);
|
|
107
111
|
this.capabilities = Object.freeze({
|
|
112
|
+
open: false,
|
|
108
113
|
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
109
114
|
write: true, explicitDirectories: true, implicitDirectories: true, mkdir: true, recursiveMkdir: true,
|
|
110
115
|
remove: true, removeDirectory: true, recursiveRemove: true, copy: true, readlink: false,
|
|
@@ -4,10 +4,10 @@ import { registerEntryView } from "./mount/comparison.js";
|
|
|
4
4
|
import { openRetainedResizeFile, retainedResizeCapabilities, ownedMutationCapabilities, requireOwnedMutation } from "./capabilities.js";
|
|
5
5
|
const originals = new WeakMap();
|
|
6
6
|
const operations = new Set([
|
|
7
|
-
"writeFileConditional", "removeFileConditional", "createStagedFile", "publishStagedFile", "removeStagedFile", "prepareDirectory",
|
|
7
|
+
"removeEntryConditional", "writeFileConditional", "removeFileConditional", "createStagedFile", "publishStagedFile", "removeStagedFile", "prepareDirectory",
|
|
8
8
|
"access", "appendFile", "canonicalizeMissingTarget", "capabilitiesFor", "chmod", "compareEntry",
|
|
9
9
|
"copyFile", "link", "lstat", "mkdir", "openReadFile", "openResizeFile", "readFile", "readStream", "readdir",
|
|
10
|
-
"readlink", "realpath", "rename", "resizeFile", "rm", "rmdir", "stat", "symlink", "truncate", "utimes",
|
|
10
|
+
"readlink", "realpath", "rename", "resizeFile", "rm", "rmdir", "unlink", "stat", "symlink", "truncate", "utimes",
|
|
11
11
|
"writeFile", "writeStream",
|
|
12
12
|
]);
|
|
13
13
|
export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = charge) {
|
|
@@ -113,6 +113,40 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
113
113
|
close: () => closing ??= Promise.resolve().then(() => handle.close()),
|
|
114
114
|
};
|
|
115
115
|
};
|
|
116
|
+
const wrapDescriptor = (descriptor) => {
|
|
117
|
+
let closing;
|
|
118
|
+
const invoke = async (options, action) => {
|
|
119
|
+
assertOpen(options);
|
|
120
|
+
if (closing)
|
|
121
|
+
throw new FsError("EBADF");
|
|
122
|
+
admit(options);
|
|
123
|
+
assertOpen(options);
|
|
124
|
+
if (closing)
|
|
125
|
+
throw new FsError("EBADF");
|
|
126
|
+
try {
|
|
127
|
+
const result = await action(resizeOptions(options));
|
|
128
|
+
assertOpen(options);
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
assertOpen(options);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const getPosition = descriptor.getPosition;
|
|
137
|
+
const probeRead = descriptor.probeRead;
|
|
138
|
+
return {
|
|
139
|
+
capabilities: Object.freeze({ ...descriptor.capabilities }),
|
|
140
|
+
...(getPosition === undefined ? {} : { getPosition: (options = {}) => invoke(options, scoped => getPosition.call(descriptor, scoped)) }),
|
|
141
|
+
...(probeRead === undefined ? {} : { probeRead: (options = {}) => invoke(options, scoped => probeRead.call(descriptor, scoped)) }),
|
|
142
|
+
stat: (options = {}) => invoke(options, scoped => descriptor.stat(scoped)),
|
|
143
|
+
read: (buffer, position, options = {}) => invoke(options, scoped => descriptor.read(buffer, position, scoped)),
|
|
144
|
+
write: (buffer, position, options = {}) => invoke(options, scoped => descriptor.write(buffer, position, scoped)),
|
|
145
|
+
truncate: (length, options = {}) => invoke(options, scoped => descriptor.truncate(length, scoped)),
|
|
146
|
+
sync: (dataOnly, options = {}) => invoke(options, scoped => descriptor.sync(dataOnly, scoped)),
|
|
147
|
+
close: () => closing ??= Promise.resolve().then(() => descriptor.close()),
|
|
148
|
+
};
|
|
149
|
+
};
|
|
116
150
|
const wrapStream = (source, options) => ({
|
|
117
151
|
[Symbol.asyncIterator]() {
|
|
118
152
|
assertOpen(options);
|
|
@@ -164,12 +198,12 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
164
198
|
const options = args.at(-1);
|
|
165
199
|
admit(options && typeof options === "object" && "signal" in options ? options : undefined);
|
|
166
200
|
}
|
|
167
|
-
if (["writeFileConditional", "removeFileConditional", "createStagedFile", "publishStagedFile", "removeStagedFile", "prepareDirectory"].includes(String(property)))
|
|
201
|
+
if (["removeEntryConditional", "writeFileConditional", "removeFileConditional", "createStagedFile", "publishStagedFile", "removeStagedFile", "prepareDirectory"].includes(String(property)))
|
|
168
202
|
return (async () => {
|
|
169
203
|
const path = typeof args[0] === "string" ? args[0] : args[0].directory.path;
|
|
170
204
|
const options = args[property === "createStagedFile" ? 3 : property === "publishStagedFile" || property === "writeFileConditional" ? 2 : 1];
|
|
171
205
|
const create = property === "createStagedFile" || (property === "writeFileConditional" || property === "prepareDirectory") && options !== undefined && "expected" in options && options.expected === null;
|
|
172
|
-
await requireOwnedMutation(original, path, property === "prepareDirectory" ? "atomicDirectoryMetadata" : property === "writeFileConditional" || property === "removeFileConditional" ? "atomicFileMutation" : "atomicFileStaging", options ?? {}, create);
|
|
206
|
+
await requireOwnedMutation(original, path, property === "removeEntryConditional" ? "atomicEntryRemoval" : property === "prepareDirectory" ? "atomicDirectoryMetadata" : property === "writeFileConditional" || property === "removeFileConditional" ? "atomicFileMutation" : "atomicFileStaging", options ?? {}, create);
|
|
173
207
|
assertOpen(options);
|
|
174
208
|
return Reflect.apply(method, original, args);
|
|
175
209
|
})();
|
|
@@ -190,21 +224,38 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
190
224
|
})();
|
|
191
225
|
return Reflect.apply(method, original, args);
|
|
192
226
|
};
|
|
193
|
-
const scoped = property === "
|
|
194
|
-
? async (path, options
|
|
227
|
+
const scoped = property === "open"
|
|
228
|
+
? async (path, options) => {
|
|
195
229
|
admit(options);
|
|
196
|
-
|
|
197
|
-
|
|
230
|
+
const selected = resizeOptions(options);
|
|
231
|
+
let descriptor;
|
|
232
|
+
try {
|
|
233
|
+
descriptor = await Reflect.apply(method, original, [path, selected]);
|
|
234
|
+
assertOpen(options);
|
|
235
|
+
return wrapDescriptor(descriptor);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (descriptor)
|
|
239
|
+
await finishCleanup(() => descriptor.close(), true);
|
|
240
|
+
assertOpen(options);
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
198
243
|
}
|
|
199
|
-
: property === "
|
|
200
|
-
? async (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
: property === "openResizeFile"
|
|
245
|
+
? async (path, options = {}) => {
|
|
246
|
+
admit(options);
|
|
247
|
+
assertOpen(options);
|
|
248
|
+
return wrapResizeHandle(await openRetainedResizeFile(original, path, resizeOptions(options)));
|
|
249
|
+
}
|
|
250
|
+
: property === "capabilitiesFor"
|
|
251
|
+
? async (...args) => ownedMutationCapabilities(original, retainedResizeCapabilities(original, await dispatch(...args)))
|
|
252
|
+
: property === "openReadFile"
|
|
253
|
+
? async (...args) => wrapHandle(await dispatch(...args))
|
|
254
|
+
: property === "readStream"
|
|
255
|
+
? (...args) => wrapStream(dispatch(...args), args[1])
|
|
256
|
+
: operations.has(property) && property !== "canonicalizeMissingTarget"
|
|
257
|
+
? async (...args) => dispatch(...args)
|
|
258
|
+
: dispatch;
|
|
208
259
|
methods.set(property, { original: method, scoped });
|
|
209
260
|
return scoped;
|
|
210
261
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PlatformComparisonCallback } from "#safe-fs-platform";
|
|
2
2
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
3
|
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileStat, FileSystem, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions } from "../../contracts/filesystem.js";
|
|
4
|
+
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
4
5
|
export type WebDavFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
5
6
|
export interface WebDavAtomicEmptyDirectoryRequest {
|
|
6
7
|
readonly operation: "atomic-empty-rmdir/v1";
|
|
@@ -33,7 +34,9 @@ export interface WebDavFileSystemOptions {
|
|
|
33
34
|
readonly compareEntry?: PlatformComparisonCallback<(this: FileSystem, ...args: Parameters<NonNullable<FileSystem["compareEntry"]>>) => ReturnType<NonNullable<FileSystem["compareEntry"]>>>;
|
|
34
35
|
}
|
|
35
36
|
export declare class WebDavFileSystem implements FileSystem {
|
|
37
|
+
open(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
|
|
36
38
|
readonly capabilities: Readonly<{
|
|
39
|
+
open: false;
|
|
37
40
|
read: true;
|
|
38
41
|
stat: true;
|
|
39
42
|
readdir: true;
|
|
@@ -140,7 +140,12 @@ function statusCode(element) {
|
|
|
140
140
|
return Number(text.split(" ")[1]);
|
|
141
141
|
}
|
|
142
142
|
export class WebDavFileSystem {
|
|
143
|
+
async open(path, options) {
|
|
144
|
+
options.signal?.throwIfAborted();
|
|
145
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
146
|
+
}
|
|
143
147
|
capabilities = Object.freeze({
|
|
148
|
+
open: false,
|
|
144
149
|
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
145
150
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
146
151
|
mkdir: true, recursiveMkdir: true, remove: true, recursiveRemove: true, rename: true, atomicRenameNoReplace: false, copy: true,
|
package/dist/safe-fs/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from "./contracts/index.js";
|
|
2
|
+
export * from "./python/index.js";
|
|
3
|
+
export { openFileDescriptor } from "./fs/descriptor.js";
|
|
4
|
+
export type { DescriptorBackend, DescriptorOpenOptions } from "./fs/descriptor.js";
|
|
2
5
|
export * from "./fs/memory/index.js";
|
|
3
6
|
export * from "./fs/devices/index.js";
|
|
4
7
|
export * from "./fs/real/index.js";
|
package/dist/safe-fs/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PythonMountOptions {
|
|
2
|
+
request: (operation: string, ...args: any[]) => any;
|
|
3
|
+
cwd: string;
|
|
4
|
+
runtimeMount: string;
|
|
5
|
+
maxTransferBytes: number;
|
|
6
|
+
errno: Record<string, number>;
|
|
7
|
+
synchronizationFlags: number;
|
|
8
|
+
runtimeModule: any;
|
|
9
|
+
}
|
|
10
|
+
export declare function mountPythonFileSystem(FS: any, options: PythonMountOptions): void;
|
|
11
|
+
/** Encode the pinned wasm32 musl stat layout without truncating canonical values. */
|
|
12
|
+
export declare function writePythonStat(heap: Uint8Array, pointer: number, stat: Record<string, any>): number;
|
|
13
|
+
/** Runtime storage and application storage occupy disjoint guest device ranges. */
|
|
14
|
+
export declare function createPythonRuntimeStatMapper(maxDevices?: number): (stat: Record<string | symbol, any>) => Record<string | symbol, any>;
|