@poe-platform/safe-fs 0.1.230 → 0.1.232

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.
@@ -54,6 +54,7 @@ export interface FileSystemCapabilities {
54
54
  readonly streamingRead?: boolean;
55
55
  readonly retainedRead?: boolean;
56
56
  readonly streamingWrite?: boolean;
57
+ readonly descriptorWriteStream?: boolean;
57
58
  readonly [capability: string]: boolean | undefined;
58
59
  }
59
60
  export interface FsOptions {
@@ -37,6 +37,7 @@ export function readOnlyCapabilities(capabilities) {
37
37
  mkdir: false, recursiveMkdir: false, remove: false, removeDirectory: false, recursiveRemove: false,
38
38
  rename: false, copy: false, exclusiveCopy: false, truncate: false, streamingAppend: false,
39
39
  randomAccessWrite: false, hardlinks: false, permissions: false, timestamps: false,
40
+ descriptorWriteStream: false,
40
41
  atomicRename: false, streamingWrite: false,
41
42
  });
42
43
  }
@@ -44,7 +45,7 @@ export function quotaCapabilities(capabilities) {
44
45
  const streamingWrite = requireCapabilities(capabilities.write, capabilities.append, !capabilities.readOnly);
45
46
  const streamingAppend = requireCapabilities(capabilities.append, !capabilities.readOnly);
46
47
  const { streamingWrite: ignoredWrite, streamingAppend: ignoredAppend, ...rest } = capabilities;
47
- return Object.freeze({ ...rest,
48
+ return Object.freeze({ ...rest, descriptorWriteStream: false,
48
49
  ...(streamingWrite === undefined ? {} : { streamingWrite }),
49
50
  ...(streamingAppend === undefined ? {} : { streamingAppend }),
50
51
  });
@@ -33,6 +33,7 @@ export declare class MemoryFileSystem implements FileSystem {
33
33
  streamingRead: true;
34
34
  retainedRead: true;
35
35
  streamingWrite: true;
36
+ readonly descriptorWriteStream: boolean;
36
37
  }>;
37
38
  private readonly identityScope;
38
39
  private nextInode;
@@ -56,7 +57,7 @@ export declare class MemoryFileSystem implements FileSystem {
56
57
  private bytes;
57
58
  private allocate;
58
59
  private openWrite;
59
- private append;
60
+ private writeAt;
60
61
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
61
62
  writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
62
63
  appendFile(path: string, data: Uint8Array, options?: AppendFileOptions): Promise<void>;
@@ -49,22 +49,25 @@ const compareOwnedMemory = async (own, peer, options) => {
49
49
  return qualified ? "distinct" : "unknown";
50
50
  };
51
51
  export class MemoryFileSystem {
52
- capabilities = Object.freeze({
53
- read: true, stat: true, readdir: true, realpath: true, access: true,
54
- write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
55
- mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
56
- rename: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
57
- streamingAppend: true, randomAccessWrite: true,
58
- readOnly: false,
59
- symlinks: true,
60
- hardlinks: true,
61
- permissions: true,
62
- timestamps: true,
63
- atomicRename: true,
64
- streamingRead: true,
65
- retainedRead: true,
66
- streamingWrite: true,
67
- });
52
+ capabilities = ((filesystem) => {
53
+ return Object.freeze({
54
+ read: true, stat: true, readdir: true, realpath: true, access: true,
55
+ write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
56
+ mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
57
+ rename: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
58
+ streamingAppend: true, randomAccessWrite: true,
59
+ readOnly: false,
60
+ symlinks: true,
61
+ hardlinks: true,
62
+ permissions: true,
63
+ timestamps: true,
64
+ atomicRename: true,
65
+ streamingRead: true,
66
+ retainedRead: true,
67
+ streamingWrite: true,
68
+ get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
69
+ });
70
+ })(this);
68
71
  identityScope = Symbol();
69
72
  nextInode = 1;
70
73
  root = this.directory(0o755);
@@ -72,6 +75,7 @@ export class MemoryFileSystem {
72
75
  const root = this.root;
73
76
  ownedStores.set(this, {
74
77
  root,
78
+ capabilities: this.capabilities,
75
79
  intact: () => this.root === root,
76
80
  });
77
81
  if (this.compareEntry === memoryImplementation.compareEntry?.value) {
@@ -252,8 +256,15 @@ export class MemoryFileSystem {
252
256
  this.changed(location.parent);
253
257
  return node;
254
258
  }
255
- append(node, data, syscall, path) {
256
- const length = node.data.byteLength + data.byteLength;
259
+ writeAt(node, data, position, syscall, path) {
260
+ if (data.byteLength === 0) {
261
+ this.changed(node);
262
+ return;
263
+ }
264
+ const end = position + data.byteLength;
265
+ if (!Number.isSafeInteger(end))
266
+ this.fail("EFBIG", syscall, path);
267
+ const length = Math.max(node.data.byteLength, end);
257
268
  let storage;
258
269
  if (length <= node.data.buffer.byteLength) {
259
270
  storage = new Uint8Array(node.data.buffer);
@@ -262,7 +273,9 @@ export class MemoryFileSystem {
262
273
  storage = this.allocate(Math.max(length, node.data.byteLength * 2, 64), syscall, path);
263
274
  storage.set(node.data);
264
275
  }
265
- storage.set(data, node.data.byteLength);
276
+ if (position > node.data.byteLength)
277
+ storage.fill(0, node.data.byteLength, position);
278
+ storage.set(data, position);
266
279
  node.data = storage.subarray(0, length);
267
280
  this.changed(node);
268
281
  }
@@ -282,7 +295,7 @@ export class MemoryFileSystem {
282
295
  const copied = this.bytes(data);
283
296
  const node = this.openWrite(path, options, "writeFile");
284
297
  if (options.flag === "a" || options.flag === "ax")
285
- this.append(node, copied, "writeFile", path);
298
+ this.writeAt(node, copied, node.data.byteLength, "writeFile", path);
286
299
  else {
287
300
  node.data = copied;
288
301
  this.changed(node);
@@ -291,7 +304,8 @@ export class MemoryFileSystem {
291
304
  async appendFile(path, data, options = {}) {
292
305
  options.signal?.throwIfAborted();
293
306
  const copied = this.bytes(data);
294
- this.append(this.openWrite(path, { ...options, flag: "a" }, "appendFile"), copied, "appendFile", path);
307
+ const node = this.openWrite(path, { ...options, flag: "a" }, "appendFile");
308
+ this.writeAt(node, copied, node.data.byteLength, "appendFile", path);
295
309
  }
296
310
  async stat(path, options = {}) {
297
311
  options.signal?.throwIfAborted();
@@ -589,18 +603,40 @@ export class MemoryFileSystem {
589
603
  async writeStream(path, source, options = {}) {
590
604
  options.signal?.throwIfAborted();
591
605
  const node = this.openWrite(path, options, "writeStream");
592
- if (options.flag !== "a" && options.flag !== "ax") {
606
+ const append = options.flag === "a" || options.flag === "ax";
607
+ let position = 0;
608
+ if (!append) {
593
609
  node.data = new Uint8Array();
594
610
  this.changed(node);
595
611
  }
596
612
  for await (const chunk of source) {
597
613
  options.signal?.throwIfAborted();
598
- this.append(node, this.bytes(chunk), "writeStream", path);
614
+ if (!(chunk instanceof Uint8Array))
615
+ throw new TypeError("Memory files require Uint8Array data");
616
+ this.writeAt(node, chunk, append ? node.data.byteLength : position, "writeStream", path);
617
+ if (!append)
618
+ position += chunk.byteLength;
599
619
  }
600
620
  options.signal?.throwIfAborted();
601
621
  }
602
622
  }
603
623
  const memoryImplementation = Object.getOwnPropertyDescriptors(MemoryFileSystem.prototype);
624
+ function stockDescriptorWrite(filesystem) {
625
+ if (Object.getPrototypeOf(filesystem) !== MemoryFileSystem.prototype)
626
+ return false;
627
+ const owner = ownedStores.get(filesystem);
628
+ if (!owner || Object.getOwnPropertyDescriptor(filesystem, "root")?.value !== owner.root
629
+ || Object.getOwnPropertyDescriptor(filesystem, "capabilities")?.value !== owner.capabilities)
630
+ return false;
631
+ for (const name of ["writeStream", "writeFile", "appendFile", "access", "stat", "lstat", "realpath",
632
+ "openWrite", "resolve", "permission", "validatePath", "mode", "bytes", "allocate", "changed", "metadata", "integer", "writeAt", "fail"]) {
633
+ const descriptor = Object.getOwnPropertyDescriptor(filesystem, name)
634
+ ?? Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
635
+ if (!descriptor || !("value" in descriptor) || descriptor.value !== memoryImplementation[name]?.value)
636
+ return false;
637
+ }
638
+ return true;
639
+ }
604
640
  export function createMemoryFileSystem() {
605
641
  return new MemoryFileSystem();
606
642
  }
@@ -85,12 +85,15 @@ export class MountFileSystem {
85
85
  const common = (capability) => {
86
86
  const optional = {
87
87
  symlinks: ["symlink", "readlink"], hardlinks: ["link"], permissions: ["chmod"], timestamps: ["utimes"], readlink: ["readlink"],
88
+ descriptorWriteStream: ["writeStream"],
88
89
  };
89
90
  const values = mounts.map(({ backend }) => {
90
91
  if (backend.capabilities.readOnly === true
91
92
  && !["read", "stat", "readdir", "realpath", "access", "readlink", "explicitDirectories", "implicitDirectories"].includes(capability))
92
93
  return false;
93
94
  const declared = backend.capabilities[capability];
95
+ if (capability === "descriptorWriteStream" && backend.capabilities.streamingWrite === false)
96
+ return false;
94
97
  return declared === true && optional[capability]?.some(method => typeof backend[method] !== "function") ? false : declared;
95
98
  });
96
99
  if (["rename", "copy", "exclusiveCopy"].includes(capability) && mounts.length > 1)
@@ -101,7 +104,7 @@ export class MountFileSystem {
101
104
  "read", "stat", "readdir", "realpath", "access",
102
105
  "write", "append", "exclusiveCreate", "explicitDirectories", "implicitDirectories", "mkdir", "recursiveMkdir",
103
106
  "remove", "removeDirectory", "recursiveRemove", "rename", "copy", "exclusiveCopy", "readlink", "truncate",
104
- "streamingAppend", "randomAccessWrite", "symlinks", "hardlinks", "permissions", "timestamps",
107
+ "streamingAppend", "randomAccessWrite", "descriptorWriteStream", "symlinks", "hardlinks", "permissions", "timestamps",
105
108
  ].map(capability => [capability, common(capability)]).filter(([, value]) => value !== undefined));
106
109
  this.capabilities = Object.freeze({
107
110
  get snapshotRmdir() { return mounts.some(({ backend }) => backend.capabilities.snapshotRmdir === true); },
@@ -120,8 +123,11 @@ export class MountFileSystem {
120
123
  async capabilitiesFor(path, options = {}) {
121
124
  return this.operation("capabilitiesFor", path, options, async () => {
122
125
  const location = await this.resolve(path, options, { allowMissing: true });
123
- const declared = await location.mount.backend.capabilitiesFor?.(location.local, options)
126
+ const observed = await location.mount.backend.capabilitiesFor?.(location.local, options)
124
127
  ?? location.mount.backend.capabilities;
128
+ const declared = observed.descriptorWriteStream === true
129
+ && (typeof location.mount.backend.writeStream !== "function" || observed.readOnly === true || observed.streamingWrite === false)
130
+ ? { ...observed, descriptorWriteStream: false } : observed;
125
131
  const capabilities = location.synthetic ? { ...declared, retainedRead: false }
126
132
  : retainedReadCapabilities(location.mount.backend, declared);
127
133
  if (location.synthetic)
@@ -118,6 +118,7 @@ export class OverlayFileSystem {
118
118
  ...(upper.readOnly === undefined ? {} : { readOnly: upper.readOnly }),
119
119
  ...(effectiveAppend === undefined ? {} : { append: effectiveAppend }),
120
120
  atomicRename: false,
121
+ descriptorWriteStream: false,
121
122
  hardlinks: false,
122
123
  symlinks: writable && this.#upper.capabilities.symlinks === true
123
124
  && typeof this.#upper.symlink === "function" && typeof this.#upper.readlink === "function"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.230",
3
+ "version": "0.1.232",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",