@zenfs/core 2.6.4 → 2.6.5

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,11 +116,19 @@ export class CopyOnWriteFS extends FileSystem {
116
116
  _nextIno = 1;
117
117
  /** Moves the public inode numbers for a rename, including a directory's descendants */
118
118
  renameInos(oldPath, newPath) {
119
- for (const [path, ino] of [...this.inos]) {
119
+ const changes = [];
120
+ for (const [path, ino] of this.inos) {
120
121
  if (path != oldPath && !path.startsWith(oldPath + '/'))
121
122
  continue;
122
- this.inos.delete(path);
123
- this.inos.set(newPath + path.slice(oldPath.length), ino);
123
+ changes.push({
124
+ path,
125
+ add: newPath + path.slice(oldPath.length),
126
+ ino,
127
+ });
128
+ }
129
+ for (const change of changes) {
130
+ this.inos.delete(change.path);
131
+ this.inos.set(change.add, change.ino);
124
132
  }
125
133
  }
126
134
  /** Replaces the layer's inode number with the public one for `path` */
@@ -17,6 +17,7 @@ export declare const version = 1;
17
17
  * @internal
18
18
  */
19
19
  export declare class Index extends Map<string, Inode> {
20
+ #private;
20
21
  maxSize: number;
21
22
  /**
22
23
  * Converts the index to JSON
@@ -26,6 +27,7 @@ export declare class Index extends Map<string, Inode> {
26
27
  * Converts the index to a string
27
28
  */
28
29
  toString(): string;
30
+ clear(): void;
29
31
  /**
30
32
  * Get the size in bytes of the index (including the size reported for each entry)
31
33
  */
@@ -21,7 +21,8 @@ export class Index extends Map {
21
21
  return {
22
22
  version,
23
23
  maxSize: this.maxSize,
24
- entries: Object.fromEntries([...this].map(([k, v]) => [k, v.toJSON()])),
24
+ // @todo use `Iterator.map` #296
25
+ entries: Object.fromEntries(Array.from(this).map(([k, v]) => [k, v.toJSON()])),
25
26
  };
26
27
  }
27
28
  /**
@@ -30,6 +31,10 @@ export class Index extends Map {
30
31
  toString() {
31
32
  return JSON.stringify(this.toJSON());
32
33
  }
34
+ clear() {
35
+ super.clear();
36
+ this.#lastAlloc = 0;
37
+ }
33
38
  /**
34
39
  * Get the size in bytes of the index (including the size reported for each entry)
35
40
  */
@@ -74,12 +79,15 @@ export class Index extends Map {
74
79
  }
75
80
  return entries;
76
81
  }
82
+ #lastAlloc = 0;
77
83
  /**
78
84
  * Get the next available ID in the index
79
85
  * @internal
80
86
  */
81
87
  _alloc() {
82
- return Math.max(...[...this.values()].flatMap(i => [i.ino, i.data])) + 1;
88
+ // @todo use `Iterator.flatMap` #296
89
+ this.#lastAlloc ||= Array.from(this.values()).reduce((max, inode) => Math.max(max, inode.ino, inode.data), 0);
90
+ return ++this.#lastAlloc;
83
91
  }
84
92
  /**
85
93
  * Gets a list of entries for each directory in the index.
package/dist/polyfills.js CHANGED
@@ -43,7 +43,10 @@ Uint8Array.fromBase64 ??=
43
43
  Uint8Array.prototype.toHex ??=
44
44
  (warn('Using a polyfill of Uint8Array.prototype.toHex'),
45
45
  function toHex() {
46
- return [...this].map(b => b.toString(16).padStart(2, '0')).join('');
46
+ // @todo use `Iterator.map` #296
47
+ return Array.from(this)
48
+ .map(b => b.toString(16).padStart(2, '0'))
49
+ .join('');
47
50
  });
48
51
  Uint8Array.fromHex ??=
49
52
  (warn('Using a polyfill of Uint8Array.fromHex'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.6.4",
3
+ "version": "2.6.5",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -90,12 +90,8 @@
90
90
  "eslint": "^10.1.0",
91
91
  "globals": "^17.3.0",
92
92
  "prettier": "^3.2.5",
93
- "tsx": "^4.19.1",
94
93
  "typedoc": "^0.28.18",
95
94
  "typescript": "^6.0.0",
96
95
  "typescript-eslint": "^8.58.0"
97
- },
98
- "allowScripts": {
99
- "esbuild": true
100
96
  }
101
97
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
- import assert from 'node:assert';
2
+ import assert from 'node:assert/strict';
3
3
  import { randomBytes } from 'node:crypto';
4
4
  import { suite, test } from 'node:test';
5
5
  import { Worker } from 'worker_threads';
@@ -1,4 +1,4 @@
1
- import assert from 'node:assert';
1
+ import assert from 'node:assert/strict';
2
2
  import { writeFileSync as _write } from 'node:fs';
3
3
  import { parentPort, workerData } from 'node:worker_threads';
4
4
  import { configureSingle, fs, SingleBuffer } from '../../dist/index.js';
@@ -0,0 +1,51 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import { Index, Inode } from '@zenfs/core';
3
+ import assert from 'node:assert/strict';
4
+ import { suite, test } from 'node:test';
5
+
6
+ /** Build an index with `n` file entries, using IDs 2..2n+1 */
7
+ function buildIndex(n: number): Index {
8
+ const index = new Index();
9
+ index.set('/', new Inode({ ino: 0, data: 1, mode: 0o40755, nlink: 1 }));
10
+ for (let i = 0; i < n; i++) {
11
+ index.set(`/f${i}`, new Inode({ ino: 2 * i + 2, data: 2 * i + 3, mode: 0o100644, nlink: 1 }));
12
+ }
13
+ return index;
14
+ }
15
+
16
+ /** Calls `fn` with `depth` extra frames on the stack */
17
+ function atDepth<T>(depth: number, fn: () => T): T {
18
+ return depth > 0 ? atDepth(depth - 1, fn) : fn();
19
+ }
20
+
21
+ suite('Index', () => {
22
+ test('_alloc returns an unused ID', () => {
23
+ const index = buildIndex(10);
24
+ assert.equal(index._alloc(), 22);
25
+ });
26
+
27
+ test('_alloc does not re-use IDs', () => {
28
+ const index = buildIndex(10);
29
+ const ids = new Set<number>();
30
+
31
+ for (let i = 0; i < 100; i++) {
32
+ const id = index._alloc();
33
+ assert.equal(index.getByID(id), undefined, `\`_alloc\` returned in-use ID ${id}`);
34
+ assert(!ids.has(id), `\`_alloc\` returned duplicate ID ${id}`);
35
+ ids.add(id);
36
+ }
37
+ });
38
+
39
+ test('_alloc works with a large index #312', () => {
40
+ const index = buildIndex(100_000);
41
+ assert.equal(index._alloc(), 200_002);
42
+ });
43
+
44
+ test('_alloc works with a large index and a deep stack #312', () => {
45
+ const index = buildIndex(60_000);
46
+ assert.equal(
47
+ atDepth(1000, () => index._alloc()),
48
+ 120_002
49
+ );
50
+ });
51
+ });
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import { Inode } from '@zenfs/core';
3
- import assert from 'node:assert';
3
+ import assert from 'node:assert/strict';
4
4
  import { suite, test } from 'node:test';
5
5
 
6
6
  suite('Inode manipulation', () => {