@poe-platform/safe-fs 0.1.728 → 0.1.729

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.
@@ -3,17 +3,22 @@ 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 {
19
+ private references;
14
20
  readonly data: Uint8Array;
15
21
  private readonly ledger;
16
- private references;
17
22
  constructor(data: Uint8Array, ledger: MemoryLedger);
18
23
  retain(): void;
19
24
  release(): void;
@@ -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;
@@ -63,3 +88,6 @@ export class MemoryAllocation {
63
88
  this.data.fill(0);
64
89
  }
65
90
  }
91
+ Object.assign(MemoryAllocation.prototype, {
92
+ references: 1,
93
+ });
@@ -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;
@@ -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 ?? 32, "removalLimits.maxRequests", 1),
139
- maxListEntries: validateLimit(options.removalLimits?.maxListEntries ?? 32, "removalLimits.maxListEntries", 1),
140
- maxDeleteObjects: validateLimit(options.removalLimits?.maxDeleteObjects ?? 16, "removalLimits.maxDeleteObjects", 1),
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);
@@ -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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.728",
3
+ "version": "0.1.729",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",