@componentor/fs 3.0.49 → 3.0.51
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/dist/index.js +90 -14
- package/dist/index.js.map +1 -1
- package/dist/workers/opfs-sync.worker.js +74 -2
- package/dist/workers/opfs-sync.worker.js.map +1 -1
- package/dist/workers/repair.worker.js +90 -14
- package/dist/workers/repair.worker.js.map +1 -1
- package/dist/workers/server.worker.js +90 -14
- package/dist/workers/server.worker.js.map +1 -1
- package/dist/workers/sync-relay.worker.js +90 -14
- package/dist/workers/sync-relay.worker.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +12 -0
package/dist/index.js
CHANGED
|
@@ -4257,6 +4257,22 @@ var VFSEngine = class {
|
|
|
4257
4257
|
// generation when implicitDirs was last rebuilt
|
|
4258
4258
|
pathIndexGen = 0;
|
|
4259
4259
|
// bumped on every pathIndex mutation
|
|
4260
|
+
// Incrementally maintained "number of pathIndex entries that have this
|
|
4261
|
+
// path as a strict ancestor" map. Lets `isImplicitDirectory` answer in
|
|
4262
|
+
// O(1) — an implicit dir P is exactly !pathIndex.has(P) && descCount[P] > 0.
|
|
4263
|
+
// Without this, every `isImplicitDirectory` call triggered an O(N×depth)
|
|
4264
|
+
// rebuild of `implicitDirs`, and the 3.0.49 fix put one of those calls on
|
|
4265
|
+
// the hot path of every fresh write/symlink/link/copy — making batch
|
|
4266
|
+
// writes O(N²) on total path count.
|
|
4267
|
+
descCount = /* @__PURE__ */ new Map();
|
|
4268
|
+
// descCount is in sync with pathIndex iff descCountGen >= pathIndexGen.
|
|
4269
|
+
// Helpers `setPathIndex`/`deletePathIndex` keep them in sync. Code that
|
|
4270
|
+
// mutates `pathIndex` directly (only test scaffolding does this in
|
|
4271
|
+
// practice — see the implicit-directory tests in vfs-engine.test.ts)
|
|
4272
|
+
// bumps `pathIndexGen` without going through the helpers, which leaves
|
|
4273
|
+
// descCount stale; `isImplicitDirectory` notices the mismatch and
|
|
4274
|
+
// recomputes descCount on demand.
|
|
4275
|
+
descCountGen = 0;
|
|
4260
4276
|
// Configurable upper bounds
|
|
4261
4277
|
maxInodes = 4e6;
|
|
4262
4278
|
maxBlocks = 4e6;
|
|
@@ -4539,7 +4555,7 @@ var VFSEngine = class {
|
|
|
4539
4555
|
if (!path.startsWith("/") || path.includes("\0")) {
|
|
4540
4556
|
throw new Error(`Corrupt VFS: inode ${i} has invalid path "${path.substring(0, 50)}"`);
|
|
4541
4557
|
}
|
|
4542
|
-
this.
|
|
4558
|
+
this.setPathIndex(path, i);
|
|
4543
4559
|
}
|
|
4544
4560
|
this.pathIndexGen++;
|
|
4545
4561
|
}
|
|
@@ -4861,7 +4877,7 @@ var VFSEngine = class {
|
|
|
4861
4877
|
gid: this.processGid
|
|
4862
4878
|
};
|
|
4863
4879
|
this.writeInode(idx, inode);
|
|
4864
|
-
this.
|
|
4880
|
+
this.setPathIndex(path, idx);
|
|
4865
4881
|
this.pathIndexGen++;
|
|
4866
4882
|
return idx;
|
|
4867
4883
|
}
|
|
@@ -5019,7 +5035,7 @@ var VFSEngine = class {
|
|
|
5019
5035
|
this.freeBlockRange(inode.firstBlock, inode.blockCount);
|
|
5020
5036
|
inode.type = INODE_TYPE.FREE;
|
|
5021
5037
|
this.writeInode(idx, inode);
|
|
5022
|
-
this.
|
|
5038
|
+
this.deletePathIndex(path);
|
|
5023
5039
|
this.pathIndexGen++;
|
|
5024
5040
|
if (idx < this.freeInodeHint) this.freeInodeHint = idx;
|
|
5025
5041
|
this.commitPending();
|
|
@@ -5141,7 +5157,7 @@ var VFSEngine = class {
|
|
|
5141
5157
|
this.freeBlockRange(descInode.firstBlock, descInode.blockCount);
|
|
5142
5158
|
descInode.type = INODE_TYPE.FREE;
|
|
5143
5159
|
this.writeInode(descIdx, descInode);
|
|
5144
|
-
this.
|
|
5160
|
+
this.deletePathIndex(desc);
|
|
5145
5161
|
}
|
|
5146
5162
|
this.pathIndexGen++;
|
|
5147
5163
|
this.commitPending();
|
|
@@ -5161,12 +5177,12 @@ var VFSEngine = class {
|
|
|
5161
5177
|
this.freeBlockRange(childInode.firstBlock, childInode.blockCount);
|
|
5162
5178
|
childInode.type = INODE_TYPE.FREE;
|
|
5163
5179
|
this.writeInode(childIdx, childInode);
|
|
5164
|
-
this.
|
|
5180
|
+
this.deletePathIndex(child);
|
|
5165
5181
|
}
|
|
5166
5182
|
}
|
|
5167
5183
|
inode.type = INODE_TYPE.FREE;
|
|
5168
5184
|
this.writeInode(idx, inode);
|
|
5169
|
-
this.
|
|
5185
|
+
this.deletePathIndex(path);
|
|
5170
5186
|
this.pathIndexGen++;
|
|
5171
5187
|
if (idx < this.freeInodeHint) this.freeInodeHint = idx;
|
|
5172
5188
|
this.commitPending();
|
|
@@ -5257,7 +5273,7 @@ var VFSEngine = class {
|
|
|
5257
5273
|
this.freeBlockRange(existingInode.firstBlock, existingInode.blockCount);
|
|
5258
5274
|
existingInode.type = INODE_TYPE.FREE;
|
|
5259
5275
|
this.writeInode(existingIdx, existingInode);
|
|
5260
|
-
this.
|
|
5276
|
+
this.deletePathIndex(newPath);
|
|
5261
5277
|
if (existingIdx < this.freeInodeHint) this.freeInodeHint = existingIdx;
|
|
5262
5278
|
}
|
|
5263
5279
|
if (cleanDescendants) {
|
|
@@ -5267,7 +5283,7 @@ var VFSEngine = class {
|
|
|
5267
5283
|
this.freeBlockRange(descInode.firstBlock, descInode.blockCount);
|
|
5268
5284
|
descInode.type = INODE_TYPE.FREE;
|
|
5269
5285
|
this.writeInode(descIdx, descInode);
|
|
5270
|
-
this.
|
|
5286
|
+
this.deletePathIndex(desc);
|
|
5271
5287
|
if (descIdx < this.freeInodeHint) this.freeInodeHint = descIdx;
|
|
5272
5288
|
}
|
|
5273
5289
|
}
|
|
@@ -5278,8 +5294,8 @@ var VFSEngine = class {
|
|
|
5278
5294
|
inode.pathLength = pathLen;
|
|
5279
5295
|
inode.mtime = Date.now();
|
|
5280
5296
|
this.writeInode(idx, inode);
|
|
5281
|
-
this.
|
|
5282
|
-
this.
|
|
5297
|
+
this.deletePathIndex(oldPath);
|
|
5298
|
+
this.setPathIndex(newPath, idx);
|
|
5283
5299
|
this.pathIndexGen++;
|
|
5284
5300
|
if (inode.type === INODE_TYPE.DIRECTORY) {
|
|
5285
5301
|
const prefix = oldPath === "/" ? "/" : oldPath + "/";
|
|
@@ -5297,8 +5313,8 @@ var VFSEngine = class {
|
|
|
5297
5313
|
childInode.pathOffset = cpo;
|
|
5298
5314
|
childInode.pathLength = cpl;
|
|
5299
5315
|
this.writeInode(i, childInode);
|
|
5300
|
-
this.
|
|
5301
|
-
this.
|
|
5316
|
+
this.deletePathIndex(p);
|
|
5317
|
+
this.setPathIndex(childNewPath, i);
|
|
5302
5318
|
}
|
|
5303
5319
|
}
|
|
5304
5320
|
this.commitPending();
|
|
@@ -5767,11 +5783,71 @@ var VFSEngine = class {
|
|
|
5767
5783
|
/**
|
|
5768
5784
|
* Check if a path is an implicit directory (exists because files exist under it,
|
|
5769
5785
|
* but no explicit directory inode was created for it).
|
|
5786
|
+
*
|
|
5787
|
+
* O(1) via the incrementally maintained `descCount` map (an implicit dir
|
|
5788
|
+
* is exactly !pathIndex.has(P) && descCount[P] > 0). If `pathIndex` was
|
|
5789
|
+
* mutated directly without going through the helpers (test scaffolding),
|
|
5790
|
+
* descCount is stale and we rebuild it from scratch — once — to resync.
|
|
5770
5791
|
*/
|
|
5771
5792
|
isImplicitDirectory(path) {
|
|
5772
5793
|
if (path === "/") return false;
|
|
5773
|
-
this.
|
|
5774
|
-
|
|
5794
|
+
if (this.pathIndex.has(path)) return false;
|
|
5795
|
+
if (this.descCountGen < this.pathIndexGen) this.rebuildDescCount();
|
|
5796
|
+
return (this.descCount.get(path) ?? 0) > 0;
|
|
5797
|
+
}
|
|
5798
|
+
/**
|
|
5799
|
+
* Recompute `descCount` from scratch by walking every pathIndex entry's
|
|
5800
|
+
* ancestor chain. O(N×depth). Only triggered when something bypassed the
|
|
5801
|
+
* setPathIndex/deletePathIndex helpers — in production code that's
|
|
5802
|
+
* never; the tests exercise this path.
|
|
5803
|
+
*/
|
|
5804
|
+
rebuildDescCount() {
|
|
5805
|
+
this.descCount.clear();
|
|
5806
|
+
for (const path of this.pathIndex.keys()) {
|
|
5807
|
+
this.bumpDescCount(path);
|
|
5808
|
+
}
|
|
5809
|
+
this.descCountGen = this.pathIndexGen;
|
|
5810
|
+
}
|
|
5811
|
+
// ---- pathIndex helpers — keep `descCount` in sync ----
|
|
5812
|
+
// Every pathIndex.set/delete in the engine MUST go through these so the
|
|
5813
|
+
// `descCount` map (used by `isImplicitDirectory`) stays correct. We
|
|
5814
|
+
// anticipate the caller's `pathIndexGen++` by setting `descCountGen` to
|
|
5815
|
+
// `pathIndexGen + 1`; idempotent across multiple helper calls within a
|
|
5816
|
+
// single logical op (e.g. rmdir doing N deletes then one bump). Test
|
|
5817
|
+
// code that mutates `pathIndex` directly leaves descCountGen behind,
|
|
5818
|
+
// which is what triggers the rebuild path in `isImplicitDirectory`.
|
|
5819
|
+
setPathIndex(path, idx) {
|
|
5820
|
+
const had = this.pathIndex.has(path);
|
|
5821
|
+
this.pathIndex.set(path, idx);
|
|
5822
|
+
if (!had) this.bumpDescCount(path);
|
|
5823
|
+
this.descCountGen = this.pathIndexGen + 1;
|
|
5824
|
+
}
|
|
5825
|
+
deletePathIndex(path) {
|
|
5826
|
+
const had = this.pathIndex.delete(path);
|
|
5827
|
+
if (had) this.decDescCount(path);
|
|
5828
|
+
this.descCountGen = this.pathIndexGen + 1;
|
|
5829
|
+
return had;
|
|
5830
|
+
}
|
|
5831
|
+
bumpDescCount(path) {
|
|
5832
|
+
let pos = path.length;
|
|
5833
|
+
while (true) {
|
|
5834
|
+
pos = path.lastIndexOf("/", pos - 1);
|
|
5835
|
+
if (pos <= 0) break;
|
|
5836
|
+
const ancestor = path.substring(0, pos);
|
|
5837
|
+
this.descCount.set(ancestor, (this.descCount.get(ancestor) ?? 0) + 1);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
decDescCount(path) {
|
|
5841
|
+
let pos = path.length;
|
|
5842
|
+
while (true) {
|
|
5843
|
+
pos = path.lastIndexOf("/", pos - 1);
|
|
5844
|
+
if (pos <= 0) break;
|
|
5845
|
+
const ancestor = path.substring(0, pos);
|
|
5846
|
+
const cur = this.descCount.get(ancestor);
|
|
5847
|
+
if (cur === void 0) break;
|
|
5848
|
+
if (cur <= 1) this.descCount.delete(ancestor);
|
|
5849
|
+
else this.descCount.set(ancestor, cur - 1);
|
|
5850
|
+
}
|
|
5775
5851
|
}
|
|
5776
5852
|
/**
|
|
5777
5853
|
* Get direct children of a directory path, including implicit subdirectories.
|