@rljson/fs-agent 0.0.35 → 0.0.37
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/fs-agent.d.ts +9 -0
- package/dist/fs-agent.js +61 -38
- package/package.json +1 -1
package/dist/fs-agent.d.ts
CHANGED
|
@@ -157,6 +157,15 @@ export declare const AGENT_STATE_FILE = ".fsagent-state.json";
|
|
|
157
157
|
* that a node joining an idle network is not left waiting.
|
|
158
158
|
*/
|
|
159
159
|
export declare const REFUSAL_ANSWER_COOLDOWN_MS = 5000;
|
|
160
|
+
/**
|
|
161
|
+
* How many tree nodes are fetched at once while walking a tree.
|
|
162
|
+
*
|
|
163
|
+
* The walk is latency-bound, so the whole point is to stop waiting for one node
|
|
164
|
+
* before asking for the next. Bounded because "the whole level at once" on a
|
|
165
|
+
* 184 000-file catalogue would be tens of thousands of simultaneous requests,
|
|
166
|
+
* which trades a latency problem for a queueing one.
|
|
167
|
+
*/
|
|
168
|
+
export declare const TREE_FETCH_CONCURRENCY = 64;
|
|
160
169
|
export declare const MASS_DELETE_MIN_FILES = 100;
|
|
161
170
|
/**
|
|
162
171
|
* Above this share of the folder, a prune is treated as suspicious rather than
|
package/dist/fs-agent.js
CHANGED
|
@@ -1028,6 +1028,7 @@ const SYNC_ERROR_FILE = ".sync-errors.log";
|
|
|
1028
1028
|
const ATOMIC_TMP_PREFIX = ".fsagent-tmp-";
|
|
1029
1029
|
const AGENT_STATE_FILE = ".fsagent-state.json";
|
|
1030
1030
|
const REFUSAL_ANSWER_COOLDOWN_MS = 5e3;
|
|
1031
|
+
const TREE_FETCH_CONCURRENCY = 64;
|
|
1031
1032
|
const MASS_DELETE_MIN_FILES = 100;
|
|
1032
1033
|
const MASS_DELETE_MAX_RATIO = 0.3;
|
|
1033
1034
|
class RestoreIncompleteError extends Error {
|
|
@@ -1690,52 +1691,74 @@ ${err.stack}` : String(err);
|
|
|
1690
1691
|
*/
|
|
1691
1692
|
async _fetchTreeRecursively(db, route, treeKey, rootHash) {
|
|
1692
1693
|
const fetchedNodes = /* @__PURE__ */ new Map();
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
while (
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1694
|
+
const seen = /* @__PURE__ */ new Set([rootHash]);
|
|
1695
|
+
let frontier = [rootHash];
|
|
1696
|
+
while (frontier.length > 0) {
|
|
1697
|
+
const next = [];
|
|
1698
|
+
const collect = (dataArray) => {
|
|
1699
|
+
for (const node of dataArray) {
|
|
1700
|
+
if (!node?._hash) continue;
|
|
1701
|
+
fetchedNodes.set(node._hash, node);
|
|
1702
|
+
if (node.children && Array.isArray(node.children)) {
|
|
1703
|
+
for (const childHash of node.children) {
|
|
1704
|
+
if (typeof childHash !== "string" || seen.has(childHash)) {
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
seen.add(childHash);
|
|
1708
|
+
next.push(childHash);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
let unresolved = frontier;
|
|
1703
1714
|
try {
|
|
1704
|
-
|
|
1705
|
-
db.
|
|
1715
|
+
const rowsByHash = await FsAgent._withTimeout(
|
|
1716
|
+
db.core.readRowsByHashes(treeKey, frontier),
|
|
1706
1717
|
this._timeouts.dbQuery,
|
|
1707
|
-
`
|
|
1718
|
+
`readRowsByHashes(${treeKey}, ${frontier.length})`
|
|
1708
1719
|
);
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1720
|
+
if (rowsByHash.size > 0) {
|
|
1721
|
+
collect(Array.from(rowsByHash.values()));
|
|
1722
|
+
unresolved = frontier.filter((h) => !rowsByHash.has(h));
|
|
1712
1723
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
`[FsAgent] _fetchTreeRecursively: db.get failed for hash=${currentHash.slice(0, 8)}…: ${errMsg}`
|
|
1716
|
-
);
|
|
1717
|
-
this._writeSyncError(
|
|
1718
|
-
`fetchTree/db.get(${currentHash.slice(0, 8)}…)`,
|
|
1719
|
-
error
|
|
1720
|
-
);
|
|
1721
|
-
continue;
|
|
1722
|
-
}
|
|
1723
|
-
const treeData = result?.rljson?.[treeKey];
|
|
1724
|
-
if (!treeData || !treeData._data) {
|
|
1725
|
-
continue;
|
|
1724
|
+
} catch {
|
|
1725
|
+
unresolved = frontier;
|
|
1726
1726
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1727
|
+
for (let i = 0; i < unresolved.length; i += TREE_FETCH_CONCURRENCY) {
|
|
1728
|
+
const batch = unresolved.slice(i, i + TREE_FETCH_CONCURRENCY);
|
|
1729
|
+
const results = await Promise.all(
|
|
1730
|
+
batch.map(async (hash) => {
|
|
1731
|
+
try {
|
|
1732
|
+
return await FsAgent._withTimeout(
|
|
1733
|
+
db.get(route, { _hash: hash }),
|
|
1734
|
+
this._timeouts.dbQuery,
|
|
1735
|
+
`db.get(${treeKey}, _hash=${hash.slice(0, 8)}…)`
|
|
1736
|
+
);
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
if (error instanceof Error && error.message.startsWith("Timeout")) {
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1742
|
+
console.warn(
|
|
1743
|
+
`[FsAgent] _fetchTreeRecursively: db.get failed for hash=${hash.slice(0, 8)}…: ${errMsg}`
|
|
1744
|
+
);
|
|
1745
|
+
this._writeSyncError(
|
|
1746
|
+
`fetchTree/db.get(${hash.slice(0, 8)}…)`,
|
|
1747
|
+
error
|
|
1748
|
+
);
|
|
1749
|
+
return null;
|
|
1735
1750
|
}
|
|
1736
|
-
}
|
|
1751
|
+
})
|
|
1752
|
+
);
|
|
1753
|
+
for (const result of results) {
|
|
1754
|
+
const treeData = result?.rljson?.[treeKey];
|
|
1755
|
+
if (!treeData || !treeData._data) continue;
|
|
1756
|
+
collect(
|
|
1757
|
+
Array.isArray(treeData._data) ? treeData._data : Object.values(treeData._data)
|
|
1758
|
+
);
|
|
1737
1759
|
}
|
|
1738
1760
|
}
|
|
1761
|
+
frontier = next;
|
|
1739
1762
|
}
|
|
1740
1763
|
return Array.from(fetchedNodes.values());
|
|
1741
1764
|
}
|