@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
|
@@ -164,6 +164,22 @@ var VFSEngine = class {
|
|
|
164
164
|
// generation when implicitDirs was last rebuilt
|
|
165
165
|
pathIndexGen = 0;
|
|
166
166
|
// bumped on every pathIndex mutation
|
|
167
|
+
// Incrementally maintained "number of pathIndex entries that have this
|
|
168
|
+
// path as a strict ancestor" map. Lets `isImplicitDirectory` answer in
|
|
169
|
+
// O(1) — an implicit dir P is exactly !pathIndex.has(P) && descCount[P] > 0.
|
|
170
|
+
// Without this, every `isImplicitDirectory` call triggered an O(N×depth)
|
|
171
|
+
// rebuild of `implicitDirs`, and the 3.0.49 fix put one of those calls on
|
|
172
|
+
// the hot path of every fresh write/symlink/link/copy — making batch
|
|
173
|
+
// writes O(N²) on total path count.
|
|
174
|
+
descCount = /* @__PURE__ */ new Map();
|
|
175
|
+
// descCount is in sync with pathIndex iff descCountGen >= pathIndexGen.
|
|
176
|
+
// Helpers `setPathIndex`/`deletePathIndex` keep them in sync. Code that
|
|
177
|
+
// mutates `pathIndex` directly (only test scaffolding does this in
|
|
178
|
+
// practice — see the implicit-directory tests in vfs-engine.test.ts)
|
|
179
|
+
// bumps `pathIndexGen` without going through the helpers, which leaves
|
|
180
|
+
// descCount stale; `isImplicitDirectory` notices the mismatch and
|
|
181
|
+
// recomputes descCount on demand.
|
|
182
|
+
descCountGen = 0;
|
|
167
183
|
// Configurable upper bounds
|
|
168
184
|
maxInodes = 4e6;
|
|
169
185
|
maxBlocks = 4e6;
|
|
@@ -446,7 +462,7 @@ var VFSEngine = class {
|
|
|
446
462
|
if (!path.startsWith("/") || path.includes("\0")) {
|
|
447
463
|
throw new Error(`Corrupt VFS: inode ${i} has invalid path "${path.substring(0, 50)}"`);
|
|
448
464
|
}
|
|
449
|
-
this.
|
|
465
|
+
this.setPathIndex(path, i);
|
|
450
466
|
}
|
|
451
467
|
this.pathIndexGen++;
|
|
452
468
|
}
|
|
@@ -768,7 +784,7 @@ var VFSEngine = class {
|
|
|
768
784
|
gid: this.processGid
|
|
769
785
|
};
|
|
770
786
|
this.writeInode(idx, inode);
|
|
771
|
-
this.
|
|
787
|
+
this.setPathIndex(path, idx);
|
|
772
788
|
this.pathIndexGen++;
|
|
773
789
|
return idx;
|
|
774
790
|
}
|
|
@@ -926,7 +942,7 @@ var VFSEngine = class {
|
|
|
926
942
|
this.freeBlockRange(inode.firstBlock, inode.blockCount);
|
|
927
943
|
inode.type = INODE_TYPE.FREE;
|
|
928
944
|
this.writeInode(idx, inode);
|
|
929
|
-
this.
|
|
945
|
+
this.deletePathIndex(path);
|
|
930
946
|
this.pathIndexGen++;
|
|
931
947
|
if (idx < this.freeInodeHint) this.freeInodeHint = idx;
|
|
932
948
|
this.commitPending();
|
|
@@ -1048,7 +1064,7 @@ var VFSEngine = class {
|
|
|
1048
1064
|
this.freeBlockRange(descInode.firstBlock, descInode.blockCount);
|
|
1049
1065
|
descInode.type = INODE_TYPE.FREE;
|
|
1050
1066
|
this.writeInode(descIdx, descInode);
|
|
1051
|
-
this.
|
|
1067
|
+
this.deletePathIndex(desc);
|
|
1052
1068
|
}
|
|
1053
1069
|
this.pathIndexGen++;
|
|
1054
1070
|
this.commitPending();
|
|
@@ -1068,12 +1084,12 @@ var VFSEngine = class {
|
|
|
1068
1084
|
this.freeBlockRange(childInode.firstBlock, childInode.blockCount);
|
|
1069
1085
|
childInode.type = INODE_TYPE.FREE;
|
|
1070
1086
|
this.writeInode(childIdx, childInode);
|
|
1071
|
-
this.
|
|
1087
|
+
this.deletePathIndex(child);
|
|
1072
1088
|
}
|
|
1073
1089
|
}
|
|
1074
1090
|
inode.type = INODE_TYPE.FREE;
|
|
1075
1091
|
this.writeInode(idx, inode);
|
|
1076
|
-
this.
|
|
1092
|
+
this.deletePathIndex(path);
|
|
1077
1093
|
this.pathIndexGen++;
|
|
1078
1094
|
if (idx < this.freeInodeHint) this.freeInodeHint = idx;
|
|
1079
1095
|
this.commitPending();
|
|
@@ -1164,7 +1180,7 @@ var VFSEngine = class {
|
|
|
1164
1180
|
this.freeBlockRange(existingInode.firstBlock, existingInode.blockCount);
|
|
1165
1181
|
existingInode.type = INODE_TYPE.FREE;
|
|
1166
1182
|
this.writeInode(existingIdx, existingInode);
|
|
1167
|
-
this.
|
|
1183
|
+
this.deletePathIndex(newPath);
|
|
1168
1184
|
if (existingIdx < this.freeInodeHint) this.freeInodeHint = existingIdx;
|
|
1169
1185
|
}
|
|
1170
1186
|
if (cleanDescendants) {
|
|
@@ -1174,7 +1190,7 @@ var VFSEngine = class {
|
|
|
1174
1190
|
this.freeBlockRange(descInode.firstBlock, descInode.blockCount);
|
|
1175
1191
|
descInode.type = INODE_TYPE.FREE;
|
|
1176
1192
|
this.writeInode(descIdx, descInode);
|
|
1177
|
-
this.
|
|
1193
|
+
this.deletePathIndex(desc);
|
|
1178
1194
|
if (descIdx < this.freeInodeHint) this.freeInodeHint = descIdx;
|
|
1179
1195
|
}
|
|
1180
1196
|
}
|
|
@@ -1185,8 +1201,8 @@ var VFSEngine = class {
|
|
|
1185
1201
|
inode.pathLength = pathLen;
|
|
1186
1202
|
inode.mtime = Date.now();
|
|
1187
1203
|
this.writeInode(idx, inode);
|
|
1188
|
-
this.
|
|
1189
|
-
this.
|
|
1204
|
+
this.deletePathIndex(oldPath);
|
|
1205
|
+
this.setPathIndex(newPath, idx);
|
|
1190
1206
|
this.pathIndexGen++;
|
|
1191
1207
|
if (inode.type === INODE_TYPE.DIRECTORY) {
|
|
1192
1208
|
const prefix = oldPath === "/" ? "/" : oldPath + "/";
|
|
@@ -1204,8 +1220,8 @@ var VFSEngine = class {
|
|
|
1204
1220
|
childInode.pathOffset = cpo;
|
|
1205
1221
|
childInode.pathLength = cpl;
|
|
1206
1222
|
this.writeInode(i, childInode);
|
|
1207
|
-
this.
|
|
1208
|
-
this.
|
|
1223
|
+
this.deletePathIndex(p);
|
|
1224
|
+
this.setPathIndex(childNewPath, i);
|
|
1209
1225
|
}
|
|
1210
1226
|
}
|
|
1211
1227
|
this.commitPending();
|
|
@@ -1674,11 +1690,71 @@ var VFSEngine = class {
|
|
|
1674
1690
|
/**
|
|
1675
1691
|
* Check if a path is an implicit directory (exists because files exist under it,
|
|
1676
1692
|
* but no explicit directory inode was created for it).
|
|
1693
|
+
*
|
|
1694
|
+
* O(1) via the incrementally maintained `descCount` map (an implicit dir
|
|
1695
|
+
* is exactly !pathIndex.has(P) && descCount[P] > 0). If `pathIndex` was
|
|
1696
|
+
* mutated directly without going through the helpers (test scaffolding),
|
|
1697
|
+
* descCount is stale and we rebuild it from scratch — once — to resync.
|
|
1677
1698
|
*/
|
|
1678
1699
|
isImplicitDirectory(path) {
|
|
1679
1700
|
if (path === "/") return false;
|
|
1680
|
-
this.
|
|
1681
|
-
|
|
1701
|
+
if (this.pathIndex.has(path)) return false;
|
|
1702
|
+
if (this.descCountGen < this.pathIndexGen) this.rebuildDescCount();
|
|
1703
|
+
return (this.descCount.get(path) ?? 0) > 0;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Recompute `descCount` from scratch by walking every pathIndex entry's
|
|
1707
|
+
* ancestor chain. O(N×depth). Only triggered when something bypassed the
|
|
1708
|
+
* setPathIndex/deletePathIndex helpers — in production code that's
|
|
1709
|
+
* never; the tests exercise this path.
|
|
1710
|
+
*/
|
|
1711
|
+
rebuildDescCount() {
|
|
1712
|
+
this.descCount.clear();
|
|
1713
|
+
for (const path of this.pathIndex.keys()) {
|
|
1714
|
+
this.bumpDescCount(path);
|
|
1715
|
+
}
|
|
1716
|
+
this.descCountGen = this.pathIndexGen;
|
|
1717
|
+
}
|
|
1718
|
+
// ---- pathIndex helpers — keep `descCount` in sync ----
|
|
1719
|
+
// Every pathIndex.set/delete in the engine MUST go through these so the
|
|
1720
|
+
// `descCount` map (used by `isImplicitDirectory`) stays correct. We
|
|
1721
|
+
// anticipate the caller's `pathIndexGen++` by setting `descCountGen` to
|
|
1722
|
+
// `pathIndexGen + 1`; idempotent across multiple helper calls within a
|
|
1723
|
+
// single logical op (e.g. rmdir doing N deletes then one bump). Test
|
|
1724
|
+
// code that mutates `pathIndex` directly leaves descCountGen behind,
|
|
1725
|
+
// which is what triggers the rebuild path in `isImplicitDirectory`.
|
|
1726
|
+
setPathIndex(path, idx) {
|
|
1727
|
+
const had = this.pathIndex.has(path);
|
|
1728
|
+
this.pathIndex.set(path, idx);
|
|
1729
|
+
if (!had) this.bumpDescCount(path);
|
|
1730
|
+
this.descCountGen = this.pathIndexGen + 1;
|
|
1731
|
+
}
|
|
1732
|
+
deletePathIndex(path) {
|
|
1733
|
+
const had = this.pathIndex.delete(path);
|
|
1734
|
+
if (had) this.decDescCount(path);
|
|
1735
|
+
this.descCountGen = this.pathIndexGen + 1;
|
|
1736
|
+
return had;
|
|
1737
|
+
}
|
|
1738
|
+
bumpDescCount(path) {
|
|
1739
|
+
let pos = path.length;
|
|
1740
|
+
while (true) {
|
|
1741
|
+
pos = path.lastIndexOf("/", pos - 1);
|
|
1742
|
+
if (pos <= 0) break;
|
|
1743
|
+
const ancestor = path.substring(0, pos);
|
|
1744
|
+
this.descCount.set(ancestor, (this.descCount.get(ancestor) ?? 0) + 1);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
decDescCount(path) {
|
|
1748
|
+
let pos = path.length;
|
|
1749
|
+
while (true) {
|
|
1750
|
+
pos = path.lastIndexOf("/", pos - 1);
|
|
1751
|
+
if (pos <= 0) break;
|
|
1752
|
+
const ancestor = path.substring(0, pos);
|
|
1753
|
+
const cur = this.descCount.get(ancestor);
|
|
1754
|
+
if (cur === void 0) break;
|
|
1755
|
+
if (cur <= 1) this.descCount.delete(ancestor);
|
|
1756
|
+
else this.descCount.set(ancestor, cur - 1);
|
|
1757
|
+
}
|
|
1682
1758
|
}
|
|
1683
1759
|
/**
|
|
1684
1760
|
* Get direct children of a directory path, including implicit subdirectories.
|