@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.
@@ -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 nodesToFetch = /* @__PURE__ */ new Set([rootHash]);
1694
- const processed = /* @__PURE__ */ new Set();
1695
- while (nodesToFetch.size > 0) {
1696
- const currentHash = Array.from(nodesToFetch)[0];
1697
- nodesToFetch.delete(currentHash);
1698
- if (processed.has(currentHash)) {
1699
- continue;
1700
- }
1701
- processed.add(currentHash);
1702
- let result;
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
- result = await FsAgent._withTimeout(
1705
- db.get(route, { _hash: currentHash }),
1715
+ const rowsByHash = await FsAgent._withTimeout(
1716
+ db.core.readRowsByHashes(treeKey, frontier),
1706
1717
  this._timeouts.dbQuery,
1707
- `db.get(${treeKey}, _hash=${currentHash.slice(0, 8)})`
1718
+ `readRowsByHashes(${treeKey}, ${frontier.length})`
1708
1719
  );
1709
- } catch (error) {
1710
- if (error instanceof Error && error.message.startsWith("Timeout")) {
1711
- throw error;
1720
+ if (rowsByHash.size > 0) {
1721
+ collect(Array.from(rowsByHash.values()));
1722
+ unresolved = frontier.filter((h) => !rowsByHash.has(h));
1712
1723
  }
1713
- const errMsg = error instanceof Error ? error.message : String(error);
1714
- console.warn(
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
- const dataArray = Array.isArray(treeData._data) ? treeData._data : Object.values(treeData._data);
1728
- for (const node of dataArray) {
1729
- if (!node._hash) continue;
1730
- fetchedNodes.set(node._hash, node);
1731
- if (node.children && Array.isArray(node.children)) {
1732
- for (const childHash of node.children) {
1733
- if (typeof childHash === "string" && !processed.has(childHash)) {
1734
- nodesToFetch.add(childHash);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/fs-agent",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "description": "Rljson fs-agent description",
5
5
  "homepage": "https://github.com/rljson/fs-agent",
6
6
  "bugs": "https://github.com/rljson/fs-agent/issues",