@poe-platform/safe-fs 0.1.652 → 0.1.654
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.
|
@@ -116,7 +116,7 @@ export function quotaCapabilities(capabilities) {
|
|
|
116
116
|
const streamingWrite = requireCapabilities(capabilities.write, capabilities.append, !capabilities.readOnly);
|
|
117
117
|
const streamingAppend = requireCapabilities(capabilities.append, !capabilities.readOnly);
|
|
118
118
|
const { streamingWrite: ignoredWrite, streamingAppend: ignoredAppend, ...rest } = capabilities;
|
|
119
|
-
return Object.freeze({ ...rest,
|
|
119
|
+
return Object.freeze({ ...rest, descriptorWriteStream: false, atomicResize: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
120
120
|
...(streamingWrite === undefined ? {} : { streamingWrite }),
|
|
121
121
|
...(streamingAppend === undefined ? {} : { streamingAppend }),
|
|
122
122
|
});
|
|
@@ -6,6 +6,7 @@ export interface FileSystemQuotaOptions {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class FileSystemQuotaError extends Error {
|
|
8
8
|
readonly maxBytes: number;
|
|
9
|
+
readonly code = "ENOSPC";
|
|
9
10
|
constructor(maxBytes: number);
|
|
10
11
|
}
|
|
11
12
|
export declare function withFileSystemQuota(fs: FileSystem, options: FileSystemQuotaOptions): FileSystem;
|
|
@@ -2,8 +2,10 @@ import { FsError } from "../../contracts/errors.js";
|
|
|
2
2
|
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
3
3
|
import { openRetainedReadFile, openRetainedResizeFile, quotaCapabilities, retainedReadCapabilities, retainedResizeCapabilities } from "../capabilities.js";
|
|
4
4
|
import { admitDirectoryEntries } from "../directory-admission.js";
|
|
5
|
+
import { forwardFileDescriptor, openFileDescriptor } from "../descriptor.js";
|
|
5
6
|
export class FileSystemQuotaError extends Error {
|
|
6
7
|
maxBytes;
|
|
8
|
+
code = "ENOSPC";
|
|
7
9
|
constructor(maxBytes) {
|
|
8
10
|
super(`Filesystem quota exceeded (${maxBytes} bytes)`);
|
|
9
11
|
this.maxBytes = maxBytes;
|
|
@@ -183,9 +185,87 @@ export function withFileSystemQuota(fs, options) {
|
|
|
183
185
|
throw new FileSystemQuotaError(options.maxBytes);
|
|
184
186
|
};
|
|
185
187
|
const mutations = {
|
|
186
|
-
|
|
187
|
-
openOptions
|
|
188
|
-
|
|
188
|
+
open(path, openOptions) {
|
|
189
|
+
return openFileDescriptor(path, openOptions, {
|
|
190
|
+
positionedRead: true, positionedWrite: true, truncate: true, synchronization: "storage",
|
|
191
|
+
}, admitted => mutate(async () => {
|
|
192
|
+
admitted.signal?.throwIfAborted();
|
|
193
|
+
const open = fs.open;
|
|
194
|
+
const capabilities = admitted.creation === "exclusive" ? fs.capabilities : await fs.capabilitiesFor?.(path, admitted) ?? fs.capabilities;
|
|
195
|
+
admitted.signal?.throwIfAborted();
|
|
196
|
+
if (!open || capabilities.open === false)
|
|
197
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
198
|
+
admitted.signal?.throwIfAborted();
|
|
199
|
+
const descriptor = await open.call(fs, path, admitted);
|
|
200
|
+
try {
|
|
201
|
+
admitted.signal?.throwIfAborted();
|
|
202
|
+
const pinned = admitted.access === "read" ? undefined : { ...await descriptor.stat(admitted) };
|
|
203
|
+
admitted.signal?.throwIfAborted();
|
|
204
|
+
if (pinned && (pinned.type !== "file" || !completeIdentity(pinned))) {
|
|
205
|
+
throw new FsError("ENOTSUP", { syscall: "open", path, message: "quota requires complete retained file identity" });
|
|
206
|
+
}
|
|
207
|
+
if (pinned && (!Number.isSafeInteger(pinned.size) || pinned.size < 0)) {
|
|
208
|
+
throw new FsError("EIO", { syscall: "fstat", path, message: "invalid retained file size" });
|
|
209
|
+
}
|
|
210
|
+
const current = async (operationOptions) => {
|
|
211
|
+
const stat = { ...await descriptor.stat(operationOptions) };
|
|
212
|
+
operationOptions.signal?.throwIfAborted();
|
|
213
|
+
if (!pinned || stat.type !== "file" || !completeIdentity(stat)
|
|
214
|
+
|| stat.identityScope !== pinned.identityScope || stat.dev !== pinned.dev || stat.ino !== pinned.ino
|
|
215
|
+
|| !Number.isSafeInteger(stat.size) || stat.size < 0) {
|
|
216
|
+
throw new FsError("EIO", { syscall: "fstat", path, message: "invalid retained quota identity or size" });
|
|
217
|
+
}
|
|
218
|
+
return stat;
|
|
219
|
+
};
|
|
220
|
+
const admitSize = async (stat, nextBytes, operationOptions) => {
|
|
221
|
+
if (!Number.isSafeInteger(nextBytes) || nextBytes < 0)
|
|
222
|
+
throw new FsError("EFBIG", { syscall: "write", path });
|
|
223
|
+
const projected = await usedBytes(fs, scanLimits, operationOptions, { path, stat, delta: nextBytes - stat.size, retained: true });
|
|
224
|
+
operationOptions.signal?.throwIfAborted();
|
|
225
|
+
if (projected > options.maxBytes)
|
|
226
|
+
throw new FileSystemQuotaError(options.maxBytes);
|
|
227
|
+
};
|
|
228
|
+
const forwarded = forwardFileDescriptor(descriptor, (syscall, operationOptions, action) => syscall === "close" ? action() : mutate(async () => {
|
|
229
|
+
operationOptions.signal?.throwIfAborted();
|
|
230
|
+
return action();
|
|
231
|
+
}));
|
|
232
|
+
return {
|
|
233
|
+
...forwarded,
|
|
234
|
+
write: (retained, buffer, position, operationOptions) => mutate(async () => {
|
|
235
|
+
operationOptions.signal?.throwIfAborted();
|
|
236
|
+
if (buffer.byteLength === 0)
|
|
237
|
+
return retained.write(buffer, position, operationOptions);
|
|
238
|
+
const stat = await current(operationOptions);
|
|
239
|
+
let offset = position;
|
|
240
|
+
if (offset === null) {
|
|
241
|
+
if (admitted.append)
|
|
242
|
+
offset = stat.size;
|
|
243
|
+
else {
|
|
244
|
+
if (forwarded.capabilities?.position !== true || !forwarded.getPosition) {
|
|
245
|
+
throw new FsError("ENOTSUP", { syscall: "getPosition", path });
|
|
246
|
+
}
|
|
247
|
+
offset = await retained.getPosition(operationOptions);
|
|
248
|
+
operationOptions.signal?.throwIfAborted();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!Number.isSafeInteger(offset) || offset < 0)
|
|
252
|
+
throw new FsError("EIO", { syscall: "getPosition", path });
|
|
253
|
+
await admitSize(stat, Math.max(stat.size, offset + buffer.byteLength), operationOptions);
|
|
254
|
+
return retained.write(buffer, position, operationOptions);
|
|
255
|
+
}),
|
|
256
|
+
truncate: (retained, length, operationOptions) => mutate(async () => {
|
|
257
|
+
operationOptions.signal?.throwIfAborted();
|
|
258
|
+
await admitSize(await current(operationOptions), length, operationOptions);
|
|
259
|
+
await retained.truncate(length, operationOptions);
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
await finishCleanup(() => descriptor.close(), true);
|
|
265
|
+
admitted.signal?.throwIfAborted();
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}));
|
|
189
269
|
},
|
|
190
270
|
openResizeFile(path, resizeOptions = {}) {
|
|
191
271
|
return mutate(async () => {
|
|
@@ -320,14 +400,14 @@ export function withFileSystemQuota(fs, options) {
|
|
|
320
400
|
// capabilities and methods without violating invariants on own properties.
|
|
321
401
|
return new Proxy(Object.create(fs), {
|
|
322
402
|
get(_target, property) {
|
|
323
|
-
if (property === "removeEntryConditional" || property === "
|
|
403
|
+
if (property === "removeEntryConditional" || property === "writeFileConditional" || property === "removeFileConditional" || property === "resizeFile" || property === "canonicalizeMissingTarget" || property === "createStagedFile" || property === "publishStagedFile" || property === "removeStagedFile" || property === "prepareDirectory")
|
|
324
404
|
return undefined;
|
|
325
405
|
if (property === "capabilities")
|
|
326
|
-
return quotaCapabilities(retainedResizeCapabilities(fs, retainedReadCapabilities(fs)));
|
|
406
|
+
return quotaCapabilities({ ...retainedResizeCapabilities(fs, retainedReadCapabilities(fs)), ...(typeof fs.open === "function" ? {} : { open: false }) });
|
|
327
407
|
if (property === "capabilitiesFor")
|
|
328
408
|
return async (path, fsOptions) => {
|
|
329
409
|
const capabilities = await fs.capabilitiesFor?.(path, fsOptions) ?? fs.capabilities;
|
|
330
|
-
return quotaCapabilities(retainedResizeCapabilities(fs, retainedReadCapabilities(fs, capabilities)));
|
|
410
|
+
return quotaCapabilities({ ...retainedResizeCapabilities(fs, retainedReadCapabilities(fs, capabilities)), ...(typeof fs.open === "function" ? {} : { open: false }) });
|
|
331
411
|
};
|
|
332
412
|
if (property === "openReadFile")
|
|
333
413
|
return (path, fsOptions = {}) => openRetainedReadFile(fs, path, fsOptions);
|