@poe-platform/safe-fs 0.1.728 → 0.1.730
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/README.md +16 -8
- package/dist/safe-fs/contracts/filesystem.d.ts +8 -0
- package/dist/safe-fs/contracts/io.js +140 -114
- package/dist/safe-fs/core.d.ts +1 -1
- package/dist/safe-fs/core.js +1 -1
- package/dist/safe-fs/fs/capabilities.js +4 -2
- package/dist/safe-fs/fs/memory/index.d.ts +36 -0
- package/dist/safe-fs/fs/memory/index.js +704 -156
- package/dist/safe-fs/fs/memory/ledger.d.ts +9 -3
- package/dist/safe-fs/fs/memory/ledger.js +37 -4
- package/dist/safe-fs/fs/mount/index.js +1 -1
- package/dist/safe-fs/fs/overlay/index.js +5 -3
- package/dist/safe-fs/fs/quota/index.js +4 -4
- package/dist/safe-fs/fs/real/index.js +28 -9
- package/dist/safe-fs/fs/s3/filesystem.js +10 -10
- package/dist/safe-fs/fs/scoped.d.ts +1 -0
- package/dist/safe-fs/fs/scoped.js +34 -3
- package/dist/safe-fs/fs/webdav/webdav.js +7 -5
- package/dist/safe-fs/node/native-seek-unavailable.d.ts +1 -0
- package/dist/safe-fs/node/native-seek-unavailable.js +5 -0
- package/dist/safe-fs/platform/browser.d.ts +8 -0
- package/dist/safe-fs/platform/browser.js +1 -0
- package/dist/safe-fs/platform/node.d.ts +8 -0
- package/dist/safe-fs/platform/node.js +1 -0
- package/package.json +6 -2
|
@@ -3,20 +3,26 @@ export declare class MemoryLedger {
|
|
|
3
3
|
readonly limits: Readonly<MemoryFileSystemLimits>;
|
|
4
4
|
private retainedBytes;
|
|
5
5
|
private metadataUnits;
|
|
6
|
+
readonly maxFileBytesSmi: number;
|
|
7
|
+
readonly maxRetainedBytesSmi: number;
|
|
8
|
+
readonly maxMetadataUnitsSmi: number;
|
|
9
|
+
readonly hasInfiniteRetained: boolean;
|
|
6
10
|
constructor(limits: Readonly<MemoryFileSystemLimits>);
|
|
7
11
|
get availableBytes(): number;
|
|
12
|
+
canPreallocate64(nameBytes: number): boolean;
|
|
8
13
|
fileSize(length: number, syscall: string, path: string): void;
|
|
9
14
|
check(bytes: number, units: number, syscall: string, path: string): void;
|
|
10
15
|
reserve(bytes: number, units: number, syscall: string, path: string): void;
|
|
11
16
|
release(bytes: number, units: number): void;
|
|
12
17
|
}
|
|
13
18
|
export declare class MemoryAllocation {
|
|
14
|
-
readonly data: Uint8Array;
|
|
15
|
-
private readonly ledger;
|
|
16
19
|
private references;
|
|
20
|
+
readonly data: Uint8Array;
|
|
21
|
+
private ledger;
|
|
17
22
|
constructor(data: Uint8Array, ledger: MemoryLedger);
|
|
18
23
|
retain(): void;
|
|
19
24
|
release(): void;
|
|
20
25
|
isReleased64(): boolean;
|
|
21
|
-
|
|
26
|
+
detachLedger(dummyLedger: MemoryLedger): void;
|
|
27
|
+
reuse(ledger?: MemoryLedger): void;
|
|
22
28
|
}
|
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
import { FsError } from "../../contracts/errors.js";
|
|
2
|
+
const MAX_SMI = 1073741823;
|
|
2
3
|
export class MemoryLedger {
|
|
3
4
|
limits;
|
|
4
5
|
retainedBytes = 0;
|
|
5
6
|
metadataUnits = 0;
|
|
7
|
+
maxFileBytesSmi;
|
|
8
|
+
maxRetainedBytesSmi;
|
|
9
|
+
maxMetadataUnitsSmi;
|
|
10
|
+
hasInfiniteRetained;
|
|
6
11
|
constructor(limits) {
|
|
7
12
|
this.limits = limits;
|
|
13
|
+
this.maxFileBytesSmi = limits.maxFileBytes >= MAX_SMI ? MAX_SMI : (limits.maxFileBytes | 0);
|
|
14
|
+
this.maxRetainedBytesSmi = limits.maxRetainedBytes >= MAX_SMI ? MAX_SMI : (limits.maxRetainedBytes | 0);
|
|
15
|
+
this.maxMetadataUnitsSmi = limits.maxMetadataUnits >= MAX_SMI ? MAX_SMI : (limits.maxMetadataUnits | 0);
|
|
16
|
+
this.hasInfiniteRetained = limits.maxRetainedBytes === Infinity;
|
|
8
17
|
}
|
|
9
18
|
get availableBytes() {
|
|
10
19
|
const max = this.limits.maxRetainedBytes;
|
|
11
20
|
return max === Infinity ? Infinity : max - this.retainedBytes;
|
|
12
21
|
}
|
|
22
|
+
canPreallocate64(nameBytes) {
|
|
23
|
+
return this.maxFileBytesSmi >= 64 && (this.hasInfiniteRetained || this.maxRetainedBytesSmi - this.retainedBytes > 65536 + nameBytes);
|
|
24
|
+
}
|
|
13
25
|
fileSize(length, syscall, path) {
|
|
26
|
+
if ((length | 0) === length && length >= 0 && length <= this.maxFileBytesSmi)
|
|
27
|
+
return;
|
|
14
28
|
if (!Number.isSafeInteger(length) || length < 0 || length > this.limits.maxFileBytes) {
|
|
15
29
|
throw new FsError("EFBIG", { syscall, path });
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
check(bytes, units, syscall, path) {
|
|
33
|
+
if ((bytes | 0) === bytes && bytes >= 0 &&
|
|
34
|
+
(units | 0) === units && units >= 0 &&
|
|
35
|
+
this.retainedBytes <= this.maxRetainedBytesSmi - bytes &&
|
|
36
|
+
this.metadataUnits <= this.maxMetadataUnitsSmi - units) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
19
39
|
const maxRetained = this.limits.maxRetainedBytes;
|
|
20
40
|
const maxUnits = this.limits.maxMetadataUnits;
|
|
21
41
|
if (!Number.isSafeInteger(bytes) || bytes < 0 ||
|
|
@@ -26,6 +46,14 @@ export class MemoryLedger {
|
|
|
26
46
|
}
|
|
27
47
|
}
|
|
28
48
|
reserve(bytes, units, syscall, path) {
|
|
49
|
+
if ((bytes | 0) === bytes && bytes >= 0 &&
|
|
50
|
+
(units | 0) === units && units >= 0 &&
|
|
51
|
+
this.retainedBytes <= this.maxRetainedBytesSmi - bytes &&
|
|
52
|
+
this.metadataUnits <= this.maxMetadataUnitsSmi - units) {
|
|
53
|
+
this.retainedBytes = (this.retainedBytes + bytes) | 0;
|
|
54
|
+
this.metadataUnits = (this.metadataUnits + units) | 0;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
29
57
|
this.check(bytes, units, syscall, path);
|
|
30
58
|
this.retainedBytes += bytes;
|
|
31
59
|
this.metadataUnits += units;
|
|
@@ -39,9 +67,6 @@ export class MemoryLedger {
|
|
|
39
67
|
}
|
|
40
68
|
}
|
|
41
69
|
export class MemoryAllocation {
|
|
42
|
-
data;
|
|
43
|
-
ledger;
|
|
44
|
-
references = 1;
|
|
45
70
|
constructor(data, ledger) {
|
|
46
71
|
this.data = data;
|
|
47
72
|
this.ledger = ledger;
|
|
@@ -58,8 +83,16 @@ export class MemoryAllocation {
|
|
|
58
83
|
isReleased64() {
|
|
59
84
|
return this.references === 0 && this.data.byteLength === 64;
|
|
60
85
|
}
|
|
61
|
-
|
|
86
|
+
detachLedger(dummyLedger) {
|
|
87
|
+
this.ledger = dummyLedger;
|
|
88
|
+
}
|
|
89
|
+
reuse(ledger) {
|
|
62
90
|
this.references = 1;
|
|
91
|
+
if (ledger !== undefined)
|
|
92
|
+
this.ledger = ledger;
|
|
63
93
|
this.data.fill(0);
|
|
64
94
|
}
|
|
65
95
|
}
|
|
96
|
+
Object.assign(MemoryAllocation.prototype, {
|
|
97
|
+
references: 1,
|
|
98
|
+
});
|
|
@@ -188,7 +188,7 @@ export class MountFileSystem {
|
|
|
188
188
|
&& await this.supportsDirectoryValidation(location.path.slice(0, location.path.lastIndexOf("/")) || "/", options, true)
|
|
189
189
|
: this.capabilities.conditionalChmod;
|
|
190
190
|
const { synchronousDirectoryValidation: ignoredValidation, synchronousStagingResolution: ignoredResolution, ...ordinary } = resize;
|
|
191
|
-
const withOpen = { ...ordinary, ...(conditionalChmod === undefined ? {} : { conditionalChmod }), ...(resolution === undefined ? {} : { synchronousStagingResolution: resolution }), ...(validation === undefined ? {} : { synchronousDirectoryValidation: validation }), atomicStagingAncestry: ancestry, ...(typeof location.mount.backend.open === "function" ? {} : { open: false }) };
|
|
191
|
+
const withOpen = { ...ordinary, retainedStagingWrite: false, ...(conditionalChmod === undefined ? {} : { conditionalChmod }), ...(resolution === undefined ? {} : { synchronousStagingResolution: resolution }), ...(validation === undefined ? {} : { synchronousDirectoryValidation: validation }), atomicStagingAncestry: ancestry, ...(typeof location.mount.backend.open === "function" ? {} : { open: false }) };
|
|
192
192
|
const capabilities = location.synthetic ? { ...withOpen, open: false, retainedRead: false }
|
|
193
193
|
: retainedResizeCapabilities(location.mount.backend, retainedReadCapabilities(location.mount.backend, withOpen));
|
|
194
194
|
if (location.synthetic)
|
|
@@ -36,7 +36,9 @@ function hasCode(error, code) {
|
|
|
36
36
|
function fail(code, path, message) {
|
|
37
37
|
throw new FsError(code, { syscall: "overlay", path, ...(message === undefined ? {} : { message }) });
|
|
38
38
|
}
|
|
39
|
-
function integer(value, path) {
|
|
39
|
+
function integer(value, path, unlimited = false) {
|
|
40
|
+
if (unlimited && value === Infinity)
|
|
41
|
+
return;
|
|
40
42
|
if (!Number.isSafeInteger(value) || value < 0)
|
|
41
43
|
fail("EINVAL", path);
|
|
42
44
|
}
|
|
@@ -86,7 +88,7 @@ export class OverlayFileSystem {
|
|
|
86
88
|
}, false));
|
|
87
89
|
this.maxBufferBytes = options.maxBufferBytes ?? Infinity;
|
|
88
90
|
if (options.maxBufferBytes !== undefined)
|
|
89
|
-
integer(options.maxBufferBytes, "/");
|
|
91
|
+
integer(options.maxBufferBytes, "/", true);
|
|
90
92
|
const writable = !this.#upper.capabilities.readOnly && this.#upper.capabilities.atomicRename === true;
|
|
91
93
|
const readable = [this.#upper, this.#lower].map((backend) => retainedReadCapabilities(backend).retainedRead);
|
|
92
94
|
const streamingRead = readable.every((capability) => capability === true) ? true
|
|
@@ -520,7 +522,7 @@ export class OverlayFileSystem {
|
|
|
520
522
|
fail("EISDIR", entry.path);
|
|
521
523
|
this.permission(entry, 4);
|
|
522
524
|
if (options.maxBytes !== undefined)
|
|
523
|
-
integer(options.maxBytes, entry.path);
|
|
525
|
+
integer(options.maxBytes, entry.path, true);
|
|
524
526
|
const limit = Math.min(options.maxBytes ?? this.maxBufferBytes, this.maxBufferBytes);
|
|
525
527
|
if (entry.stat.size > limit)
|
|
526
528
|
fail("EFBIG", entry.path);
|
|
@@ -153,15 +153,15 @@ async function existingBytes(fs, path, options) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
export function withFileSystemQuota(fs, options) {
|
|
156
|
-
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)
|
|
157
|
-
throw new RangeError("maxBytes must be a nonnegative safe integer");
|
|
156
|
+
if (options.maxBytes !== Infinity && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0))
|
|
157
|
+
throw new RangeError("maxBytes must be a nonnegative safe integer or Infinity");
|
|
158
158
|
const scanLimits = {
|
|
159
159
|
maxScanEntries: options.maxScanEntries ?? Infinity,
|
|
160
160
|
maxScanDepth: options.maxScanDepth ?? Infinity,
|
|
161
161
|
};
|
|
162
162
|
for (const [name, value] of Object.entries({ maxScanEntries: options.maxScanEntries, maxScanDepth: options.maxScanDepth })) {
|
|
163
|
-
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
|
|
164
|
-
throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
163
|
+
if (value !== undefined && value !== Infinity && (!Number.isSafeInteger(value) || value < 0))
|
|
164
|
+
throw new RangeError(`${name} must be a nonnegative safe integer or Infinity`);
|
|
165
165
|
}
|
|
166
166
|
let queue = Promise.resolve();
|
|
167
167
|
let reservedBytes = 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { platform } from "#safe-fs-platform";
|
|
1
2
|
import { snapshotStagingCreation } from "../staging-cleanup.js";
|
|
2
3
|
import { snapshotConditionalChmod } from "../conditional-chmod.js";
|
|
3
4
|
import { runStagingGuard, snapshotDirectoryAncestry } from "../staging-ancestry.js";
|
|
@@ -7,6 +8,7 @@ import * as native from "node:fs/promises";
|
|
|
7
8
|
import { dirname, basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
9
|
import { nativeAllocatedBytes } from "./allocation.js";
|
|
9
10
|
import { openFileDescriptor } from "../descriptor.js";
|
|
11
|
+
import { compareIdentity } from "../mount/identity.js";
|
|
10
12
|
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
11
13
|
import { callNativeSeekEnd, loadNativeSeekBinding } from "../../node/native-seek.js";
|
|
12
14
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
@@ -32,11 +34,11 @@ function fileStat(stats) {
|
|
|
32
34
|
mtimeMs: "mtimeNs" in stats ? Number(stats.mtimeNs / 1000000n) + Number(stats.mtimeNs % 1000000n) / 1e6 : stats.mtimeMs,
|
|
33
35
|
ctimeMs: "ctimeNs" in stats ? Number(stats.ctimeNs / 1000000n) + Number(stats.ctimeNs % 1000000n) / 1e6 : stats.ctimeMs,
|
|
34
36
|
birthtimeMs: "birthtimeNs" in stats ? Number(stats.birthtimeNs / 1000000n) + Number(stats.birthtimeNs % 1000000n) / 1e6 : stats.birthtimeMs, ino, dev,
|
|
35
|
-
...(Number.isSafeInteger(dev) && dev >= 0 && Number.isSafeInteger(ino) && ino
|
|
37
|
+
...(Number.isSafeInteger(dev) && dev >= 0 && Number.isSafeInteger(ino) && ino > 0
|
|
36
38
|
? { identityScope: Symbol.for("virtual-bash.fs.native") } : {}),
|
|
37
39
|
nlink: Number(stats.nlink), uid: Number(stats.uid), gid: Number(stats.gid),
|
|
38
40
|
};
|
|
39
|
-
if ("ctimeNs" in stats && !stats.isDirectory())
|
|
41
|
+
if (snapshot.identityScope !== undefined && "ctimeNs" in stats && (stats.ctimeNs !== 0n || stats.mtimeNs !== 0n) && !stats.isDirectory())
|
|
40
42
|
Object.defineProperty(snapshot, "opaqueVersion", { value: `${stats.ctimeNs}:${stats.mtimeNs}`, enumerable: true });
|
|
41
43
|
return snapshot;
|
|
42
44
|
}
|
|
@@ -98,14 +100,13 @@ function nativeError(error) {
|
|
|
98
100
|
*/
|
|
99
101
|
export class RealFileSystem {
|
|
100
102
|
capabilities = Object.freeze({
|
|
101
|
-
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
103
|
+
read: true, stat: true, readdir: true, realpath: true, access: true,
|
|
102
104
|
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
103
105
|
mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
|
|
104
106
|
rename: true, atomicRenameNoReplace: false, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
105
107
|
streamingAppend: true, randomAccessWrite: true,
|
|
106
|
-
readOnly: false, symlinks: true, hardlinks: true,
|
|
107
|
-
|
|
108
|
-
timestamps: true, atomicRename: true, streamingRead: true, streamingWrite: true, retainedRead: true, retainedResize: true, trustedOwnedStaging: true,
|
|
108
|
+
readOnly: false, symlinks: true, hardlinks: true, streamingRead: true, streamingWrite: true, retainedRead: true, retainedResize: true,
|
|
109
|
+
...platform.nativeFileSystem,
|
|
109
110
|
});
|
|
110
111
|
configuredRoot;
|
|
111
112
|
renameNoReplace;
|
|
@@ -122,6 +123,8 @@ export class RealFileSystem {
|
|
|
122
123
|
this.configuredRoot = root;
|
|
123
124
|
this.renameNoReplace = typeof options === "string" ? undefined : options.renameNoReplace;
|
|
124
125
|
if (this.renameNoReplace !== undefined) {
|
|
126
|
+
if (!platform.nativeFileSystem.atomicRename)
|
|
127
|
+
throw new FsError("ENOTSUP", { syscall: "root", message: "atomic rename is unavailable on this host" });
|
|
125
128
|
if (typeof this.renameNoReplace !== "function")
|
|
126
129
|
throw new FsError("EINVAL", { syscall: "root", message: "renameNoReplace must be a function" });
|
|
127
130
|
this.capabilities = Object.freeze({ ...this.capabilities, atomicRenameNoReplace: true });
|
|
@@ -291,10 +294,15 @@ export class RealFileSystem {
|
|
|
291
294
|
}
|
|
292
295
|
}
|
|
293
296
|
expectStaging(path, expected, identityOnly = false) {
|
|
297
|
+
if (!platform.nativeFileSystem.trustedOwnedStaging)
|
|
298
|
+
throw new FsError("ENOTSUP");
|
|
294
299
|
if (expected && (expected.identityScope !== Symbol.for("virtual-bash.fs.native")
|
|
295
300
|
|| expected.dev === undefined || expected.ino === undefined || !identityOnly && expected.opaqueVersion === undefined))
|
|
296
301
|
throw new FsError("ENOTSUP");
|
|
297
302
|
const current = this.stagingSnapshot(path);
|
|
303
|
+
if (expected && current && (current.identityScope !== Symbol.for("virtual-bash.fs.native")
|
|
304
|
+
|| current.dev === undefined || current.ino === undefined || !identityOnly && current.opaqueVersion === undefined))
|
|
305
|
+
throw new FsError("ENOTSUP");
|
|
298
306
|
if (expected === null ? current !== null : !current || current.dev !== expected.dev || current.ino !== expected.ino || current.type !== expected.type
|
|
299
307
|
|| !identityOnly && (current.opaqueVersion !== expected.opaqueVersion || current.size !== expected.size || current.mode !== expected.mode || current.nlink !== expected.nlink))
|
|
300
308
|
throw new FsError("EAGAIN");
|
|
@@ -521,6 +529,8 @@ export class RealFileSystem {
|
|
|
521
529
|
}
|
|
522
530
|
}
|
|
523
531
|
open(path, options) {
|
|
532
|
+
if (!platform.nativeFileSystem.open)
|
|
533
|
+
return this.operation("open", path, options, async () => { throw new FsError("ENOTSUP"); });
|
|
524
534
|
return openFileDescriptor(path, options, {
|
|
525
535
|
noFollow: true, positionedRead: true, positionedWrite: true, truncate: true, synchronization: "storage",
|
|
526
536
|
}, async (admitted) => {
|
|
@@ -663,7 +673,7 @@ export class RealFileSystem {
|
|
|
663
673
|
return this.operation("mkdir", path, options, async () => {
|
|
664
674
|
if (options.mode !== undefined)
|
|
665
675
|
integer(options.mode);
|
|
666
|
-
if (options.exactMode && options.recursive)
|
|
676
|
+
if (options.exactMode && (!platform.nativeFileSystem.permissions || options.recursive))
|
|
667
677
|
throw new FsError("ENOTSUP");
|
|
668
678
|
const target = await this.path(path, {
|
|
669
679
|
...options,
|
|
@@ -746,8 +756,13 @@ export class RealFileSystem {
|
|
|
746
756
|
}
|
|
747
757
|
if (target && options.exclusive)
|
|
748
758
|
throw new FsError("EEXIST");
|
|
749
|
-
if (target && origin.isFile()
|
|
750
|
-
|
|
759
|
+
if (target && origin.isFile()) {
|
|
760
|
+
const identity = compareIdentity(fileStat(origin), fileStat(target));
|
|
761
|
+
if (identity === "same")
|
|
762
|
+
throw new FsError("EINVAL");
|
|
763
|
+
if (identity === "unknown")
|
|
764
|
+
throw new FsError("ENOTSUP");
|
|
765
|
+
}
|
|
751
766
|
if (!target)
|
|
752
767
|
flags |= constants.COPYFILE_EXCL;
|
|
753
768
|
}
|
|
@@ -813,6 +828,8 @@ export class RealFileSystem {
|
|
|
813
828
|
}
|
|
814
829
|
async chmod(path, mode, options = {}) {
|
|
815
830
|
return this.operation("chmod", path, options, async () => {
|
|
831
|
+
if (!platform.nativeFileSystem.permissions)
|
|
832
|
+
throw new FsError("ENOTSUP");
|
|
816
833
|
integer(mode);
|
|
817
834
|
const conditional = snapshotConditionalChmod(path, options);
|
|
818
835
|
if (conditional) {
|
|
@@ -846,6 +863,8 @@ export class RealFileSystem {
|
|
|
846
863
|
}
|
|
847
864
|
async utimes(path, atimeMs, mtimeMs, options = {}) {
|
|
848
865
|
return this.operation("utimes", path, options, async () => {
|
|
866
|
+
if (!platform.nativeFileSystem.timestamps)
|
|
867
|
+
throw new FsError("ENOTSUP");
|
|
849
868
|
if (!Number.isFinite(atimeMs) || !Number.isFinite(mtimeMs)
|
|
850
869
|
|| Math.abs(atimeMs) > 8.64e15 || Math.abs(mtimeMs) > 8.64e15)
|
|
851
870
|
throw new FsError("EINVAL");
|
|
@@ -59,7 +59,7 @@ function fail(code, syscall, path, message) {
|
|
|
59
59
|
throw new FsError(code, { syscall, path, ...(message === undefined ? {} : { message }) });
|
|
60
60
|
}
|
|
61
61
|
function validateLimit(value, name, minimum, maximum = Number.MAX_SAFE_INTEGER) {
|
|
62
|
-
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
62
|
+
if ((value !== Infinity || maximum !== Infinity) && (!Number.isSafeInteger(value) || value < minimum || value > maximum)) {
|
|
63
63
|
throw new FsError("EINVAL", { message: `${name} must be an integer from ${minimum} to ${maximum}` });
|
|
64
64
|
}
|
|
65
65
|
return value;
|
|
@@ -130,14 +130,14 @@ export class S3FileSystem {
|
|
|
130
130
|
this.bucket = options.bucket;
|
|
131
131
|
this.allowRename = options.allowNonAtomicRename ?? true;
|
|
132
132
|
this.pageSize = validateLimit(options.pageSize ?? 1000, "pageSize", 1, 1000);
|
|
133
|
-
this.maxReadBytes = options.maxReadBytes === undefined ? Infinity : validateLimit(options.maxReadBytes, "maxReadBytes", 0);
|
|
134
|
-
this.maxStreamBytes = options.maxStreamBytes === undefined ? Infinity : validateLimit(options.maxStreamBytes, "maxStreamBytes", 0);
|
|
135
|
-
this.maxListEntries = options.maxListEntries === undefined ? Infinity : validateLimit(options.maxListEntries, "maxListEntries", 1);
|
|
136
|
-
this.maxRequests = options.maxRequests === undefined ? Infinity : validateLimit(options.maxRequests, "maxRequests", 1);
|
|
133
|
+
this.maxReadBytes = options.maxReadBytes === undefined ? Infinity : validateLimit(options.maxReadBytes, "maxReadBytes", 0, Infinity);
|
|
134
|
+
this.maxStreamBytes = options.maxStreamBytes === undefined ? Infinity : validateLimit(options.maxStreamBytes, "maxStreamBytes", 0, Infinity);
|
|
135
|
+
this.maxListEntries = options.maxListEntries === undefined ? Infinity : validateLimit(options.maxListEntries, "maxListEntries", 1, Infinity);
|
|
136
|
+
this.maxRequests = options.maxRequests === undefined ? Infinity : validateLimit(options.maxRequests, "maxRequests", 1, Infinity);
|
|
137
137
|
this.removalLimits = {
|
|
138
|
-
maxRequests: validateLimit(options.removalLimits?.maxRequests ??
|
|
139
|
-
maxListEntries: validateLimit(options.removalLimits?.maxListEntries ??
|
|
140
|
-
maxDeleteObjects: validateLimit(options.removalLimits?.maxDeleteObjects ??
|
|
138
|
+
maxRequests: validateLimit(options.removalLimits?.maxRequests ?? Infinity, "removalLimits.maxRequests", 1, Infinity),
|
|
139
|
+
maxListEntries: validateLimit(options.removalLimits?.maxListEntries ?? Infinity, "removalLimits.maxListEntries", 1, Infinity),
|
|
140
|
+
maxDeleteObjects: validateLimit(options.removalLimits?.maxDeleteObjects ?? Infinity, "removalLimits.maxDeleteObjects", 1, Infinity),
|
|
141
141
|
};
|
|
142
142
|
this.capabilities = Object.freeze({
|
|
143
143
|
open: false,
|
|
@@ -427,7 +427,7 @@ export class S3FileSystem {
|
|
|
427
427
|
return this.stat(path, options);
|
|
428
428
|
}
|
|
429
429
|
async body(output, path, options) {
|
|
430
|
-
const limit = Math.min(this.maxReadBytes, options.maxBytes === undefined ? this.maxReadBytes : validateLimit(options.maxBytes, "maxBytes", 0));
|
|
430
|
+
const limit = Math.min(this.maxReadBytes, options.maxBytes === undefined ? this.maxReadBytes : validateLimit(options.maxBytes, "maxBytes", 0, Infinity));
|
|
431
431
|
let iterator;
|
|
432
432
|
return this.call("readFile", path, options, async () => {
|
|
433
433
|
if (output.Body && Symbol.asyncIterator in output.Body)
|
|
@@ -473,7 +473,7 @@ export class S3FileSystem {
|
|
|
473
473
|
async readFile(input, options = {}) {
|
|
474
474
|
const path = this.path(input);
|
|
475
475
|
if (options.maxBytes !== undefined)
|
|
476
|
-
validateLimit(options.maxBytes, "maxBytes", 0);
|
|
476
|
+
validateLimit(options.maxBytes, "maxBytes", 0, Infinity);
|
|
477
477
|
const info = await this.stat(input, options);
|
|
478
478
|
if (info.type === "directory")
|
|
479
479
|
fail("EISDIR", "readFile", path);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FileSystem, FsOptions } from "../contracts/filesystem.js";
|
|
2
|
+
export declare function retargetScopedFileSystem(scoped: FileSystem, charge: () => void, signal: AbortSignal, cleanupCharge?: () => void, maxPathComponents?: number): boolean;
|
|
2
3
|
export declare function scopeFileSystem(filesystem: FileSystem, charge: () => void, signal: AbortSignal, cleanupCharge?: () => void, options?: {
|
|
3
4
|
readonly preserveDescriptorWriteReceipt?: boolean;
|
|
4
5
|
readonly creationMask?: number;
|
|
@@ -8,6 +8,14 @@ import { openRetainedResizeFile, retainedResizeCapabilities, ownedMutationCapabi
|
|
|
8
8
|
import { createStagingCleanup, snapshotStagingCreation } from "./staging-cleanup.js";
|
|
9
9
|
import { inspectStagingBindings, runStagingGuard, snapshotDirectoryAncestry, snapshotStagingResolution } from "./staging-ancestry.js";
|
|
10
10
|
const originals = new WeakMap();
|
|
11
|
+
const retargeters = new WeakMap();
|
|
12
|
+
export function retargetScopedFileSystem(scoped, charge, signal, cleanupCharge = charge, maxPathComponents) {
|
|
13
|
+
const fn = retargeters.get(scoped);
|
|
14
|
+
if (!fn)
|
|
15
|
+
return false;
|
|
16
|
+
fn(charge, signal, cleanupCharge, maxPathComponents);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
11
19
|
const operations = new Set([
|
|
12
20
|
"prepareDirectoryAncestry", "prepareStagingResolution",
|
|
13
21
|
"publishFileConditional", "removeEntryConditional", "removeTreeConditional", "writeFileConditional", "removeFileConditional", "createStagedFile", "publishStagedFile", "removeStagedFile", "prepareDirectory",
|
|
@@ -19,7 +27,7 @@ const operations = new Set([
|
|
|
19
27
|
export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = charge, options = {}) {
|
|
20
28
|
const original = originals.get(filesystem)?.filesystem ?? filesystem;
|
|
21
29
|
const creationMask = options.creationMask ?? originals.get(filesystem)?.creationMask;
|
|
22
|
-
|
|
30
|
+
let maxPathComponents = options.maxPathComponents ?? originals.get(filesystem)?.maxPathComponents;
|
|
23
31
|
if (creationMask !== undefined && (!Number.isInteger(creationMask) || creationMask < 0 || creationMask > 0o777))
|
|
24
32
|
throw new RangeError("creationMask must be a permission mask between 0 and 0777");
|
|
25
33
|
const creationOptions = (settings, directory = false) => {
|
|
@@ -343,7 +351,20 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
343
351
|
controls.signal?.throwIfAborted();
|
|
344
352
|
return backendCleanup.remove(controls);
|
|
345
353
|
}, () => backendCleanup.close());
|
|
346
|
-
|
|
354
|
+
const backendWriter = staging.writer;
|
|
355
|
+
const writer = backendWriter && Object.freeze({
|
|
356
|
+
write: async (bytes, settings = {}) => {
|
|
357
|
+
const controls = resizeOptions(settings);
|
|
358
|
+
admit(controls);
|
|
359
|
+
await backendWriter.write(bytes, controls);
|
|
360
|
+
},
|
|
361
|
+
finish: async (settings = {}) => {
|
|
362
|
+
const controls = resizeOptions(settings);
|
|
363
|
+
admit(controls);
|
|
364
|
+
return backendWriter.finish(controls);
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
return Object.freeze({ ...staging, cleanup, ...(writer ? { writer } : {}) });
|
|
347
368
|
}
|
|
348
369
|
catch (error) {
|
|
349
370
|
await finishCleanup(() => backendCleanup.close(), true);
|
|
@@ -471,7 +492,17 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
471
492
|
return scoped;
|
|
472
493
|
},
|
|
473
494
|
});
|
|
474
|
-
|
|
495
|
+
const originalRecord = { filesystem: original, signal, cleanupCharge, creationMask, maxPathComponents };
|
|
496
|
+
originals.set(view, originalRecord);
|
|
497
|
+
retargeters.set(view, (nextCharge, nextSignal, nextCleanupCharge, nextMaxPathComponents) => {
|
|
498
|
+
charge = nextCharge;
|
|
499
|
+
signal = nextSignal;
|
|
500
|
+
cleanupCharge = nextCleanupCharge;
|
|
501
|
+
maxPathComponents = nextMaxPathComponents ?? originals.get(filesystem)?.maxPathComponents;
|
|
502
|
+
originalRecord.signal = nextSignal;
|
|
503
|
+
originalRecord.cleanupCharge = nextCleanupCharge;
|
|
504
|
+
originalRecord.maxPathComponents = maxPathComponents;
|
|
505
|
+
});
|
|
475
506
|
registerEntryView(view, async (path, options) => {
|
|
476
507
|
assertOpen(options);
|
|
477
508
|
return { filesystem: original, path };
|
|
@@ -86,7 +86,9 @@ const resourceIdBody = '<?xml version="1.0" encoding="utf-8"?>'
|
|
|
86
86
|
function fail(code, syscall, path, message) {
|
|
87
87
|
throw new FsError(code, { syscall, path, ...(message === undefined ? {} : { message }) });
|
|
88
88
|
}
|
|
89
|
-
function positive(value, name, zero = false) {
|
|
89
|
+
function positive(value, name, zero = false, unlimited = false) {
|
|
90
|
+
if (unlimited && value === Infinity)
|
|
91
|
+
return value;
|
|
90
92
|
if (!Number.isSafeInteger(value) || value < (zero ? 0 : 1)) {
|
|
91
93
|
throw new FsError("EINVAL", { message: `${name} must be a ${zero ? "nonnegative" : "positive"} safe integer` });
|
|
92
94
|
}
|
|
@@ -286,9 +288,9 @@ export class WebDavFileSystem {
|
|
|
286
288
|
removeDirectory: this.atomicEmptyDirectory !== undefined,
|
|
287
289
|
streamingAppend: this.requestStreamSupport !== false,
|
|
288
290
|
});
|
|
289
|
-
this.maxResponseBytes = options.maxResponseBytes === undefined ? 16 * 1024 * 1024 : positive(options.maxResponseBytes, "maxResponseBytes");
|
|
290
|
-
this.maxXmlBytes = options.maxXmlBytes === undefined ? 1024 * 1024 : positive(options.maxXmlBytes, "maxXmlBytes");
|
|
291
|
-
this.maxEntries = options.maxEntries === undefined ? Infinity : positive(options.maxEntries, "maxEntries");
|
|
291
|
+
this.maxResponseBytes = options.maxResponseBytes === undefined ? 16 * 1024 * 1024 : positive(options.maxResponseBytes, "maxResponseBytes", false, true);
|
|
292
|
+
this.maxXmlBytes = options.maxXmlBytes === undefined ? 1024 * 1024 : positive(options.maxXmlBytes, "maxXmlBytes", false, true);
|
|
293
|
+
this.maxEntries = options.maxEntries === undefined ? Infinity : positive(options.maxEntries, "maxEntries", false, true);
|
|
292
294
|
this.timeoutMs = options.timeoutMs === undefined ? undefined : positive(options.timeoutMs, "timeoutMs");
|
|
293
295
|
this.overwritePolicy = options.overwritePolicy ?? "lock";
|
|
294
296
|
this.configuredComparison = options.compareEntry !== undefined;
|
|
@@ -867,7 +869,7 @@ export class WebDavFileSystem {
|
|
|
867
869
|
}
|
|
868
870
|
async readFile(path, options = {}) {
|
|
869
871
|
const normalized = normalize(path);
|
|
870
|
-
const limit = Math.min(this.maxResponseBytes, options.maxBytes === undefined ? this.maxResponseBytes : positive(options.maxBytes, "maxBytes", true));
|
|
872
|
+
const limit = Math.min(this.maxResponseBytes, options.maxBytes === undefined ? this.maxResponseBytes : positive(options.maxBytes, "maxBytes", true, true));
|
|
871
873
|
if ((await this.stat(path, options)).type === "directory")
|
|
872
874
|
fail("EISDIR", "readFile", path);
|
|
873
875
|
return this.request("GET", normalized, options, { headers: { "Accept-Encoding": "identity" } }, async (response, signal) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadBinding(): Promise<never>;
|
|
@@ -3,6 +3,14 @@ import type { ScopedTransportBudgetFrame } from "./transport-budget.js";
|
|
|
3
3
|
export type PlatformErrno = number | undefined;
|
|
4
4
|
export type PlatformComparisonCallback<Callback> = Callback & never;
|
|
5
5
|
export declare const platform: Readonly<{
|
|
6
|
+
nativeFileSystem: Readonly<{
|
|
7
|
+
open: false;
|
|
8
|
+
permissions: false;
|
|
9
|
+
timestamps: false;
|
|
10
|
+
conditionalChmod: false;
|
|
11
|
+
trustedOwnedStaging: false;
|
|
12
|
+
atomicRename: false;
|
|
13
|
+
}>;
|
|
6
14
|
maxCollectionBytes: number;
|
|
7
15
|
errno(_code: string): PlatformErrno;
|
|
8
16
|
callbackAuthorities: false;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FsError } from "../contracts/errors.js";
|
|
2
2
|
export const platform = Object.freeze({
|
|
3
|
+
nativeFileSystem: Object.freeze({ open: false, permissions: false, timestamps: false, conditionalChmod: false, trustedOwnedStaging: false, atomicRename: false }),
|
|
3
4
|
maxCollectionBytes: 32 * 1024 * 1024,
|
|
4
5
|
errno(_code) {
|
|
5
6
|
return undefined;
|
|
@@ -4,6 +4,14 @@ import { type ScopedTransportBudgetFrame } from "./transport-budget.js";
|
|
|
4
4
|
export type PlatformErrno = number;
|
|
5
5
|
export type PlatformComparisonCallback<Callback> = Callback;
|
|
6
6
|
export declare const platform: Readonly<{
|
|
7
|
+
nativeFileSystem: Readonly<{
|
|
8
|
+
open: true;
|
|
9
|
+
permissions: true;
|
|
10
|
+
timestamps: true;
|
|
11
|
+
conditionalChmod: true;
|
|
12
|
+
trustedOwnedStaging: true;
|
|
13
|
+
atomicRename: true;
|
|
14
|
+
}>;
|
|
7
15
|
maxCollectionBytes: number;
|
|
8
16
|
errno(code: string): PlatformErrno;
|
|
9
17
|
callbackAuthorities: true;
|
|
@@ -16,6 +16,7 @@ const systemErrnos = (() => {
|
|
|
16
16
|
})();
|
|
17
17
|
const negotiating = new AsyncLocalStorage();
|
|
18
18
|
export const platform = Object.freeze({
|
|
19
|
+
nativeFileSystem: Object.freeze({ open: true, permissions: true, timestamps: true, conditionalChmod: true, trustedOwnedStaging: true, atomicRename: true }),
|
|
19
20
|
maxCollectionBytes: Infinity,
|
|
20
21
|
errno(code) {
|
|
21
22
|
const errno = systemErrnos.get(code === "EOPNOTSUPP" ? "ENOTSUP" : code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poe-platform/safe-fs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.730",
|
|
4
4
|
"description": "Composable filesystem with a portable core and explicit Node adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,9 +69,11 @@
|
|
|
69
69
|
},
|
|
70
70
|
"./fs/real": {
|
|
71
71
|
"types": {
|
|
72
|
+
"workerd": "./dist/safe-fs/fs/real/index.d.ts",
|
|
72
73
|
"browser": "./dist/safe-fs/node-unavailable.d.ts",
|
|
73
74
|
"default": "./dist/safe-fs/fs/real/index.d.ts"
|
|
74
75
|
},
|
|
76
|
+
"workerd": "./dist/safe-fs/fs/real/index.js",
|
|
75
77
|
"browser": null,
|
|
76
78
|
"import": "./dist/safe-fs/fs/real/index.js"
|
|
77
79
|
},
|
|
@@ -134,15 +136,17 @@
|
|
|
134
136
|
"imports": {
|
|
135
137
|
"#safe-fs-platform": {
|
|
136
138
|
"types": {
|
|
139
|
+
"workerd": "./dist/safe-fs/platform/browser.d.ts",
|
|
137
140
|
"browser": "./dist/safe-fs/platform/browser.d.ts",
|
|
138
141
|
"default": "./dist/safe-fs/platform/node.d.ts"
|
|
139
142
|
},
|
|
143
|
+
"workerd": "./dist/safe-fs/platform/browser.js",
|
|
140
144
|
"browser": "./dist/safe-fs/platform/browser.js",
|
|
141
145
|
"default": "./dist/safe-fs/platform/node.js"
|
|
142
146
|
},
|
|
143
147
|
"#safe-fs-native-seek": {
|
|
144
148
|
"types": "./dist/safe-fs/native/fs-seek/loader.d.ts",
|
|
145
|
-
"workerd":
|
|
149
|
+
"workerd": "./dist/safe-fs/node/native-seek-unavailable.js",
|
|
146
150
|
"browser": null,
|
|
147
151
|
"default": "./dist/safe-fs/native/fs-seek/loader.mjs"
|
|
148
152
|
}
|