@rljson/fs-agent 0.0.34 → 0.0.36
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 +21 -0
- package/dist/fs-agent.js +70 -42
- 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
|
|
@@ -622,6 +631,18 @@ export declare class FsAgent {
|
|
|
622
631
|
* @param connector - Connector to broadcast on.
|
|
623
632
|
*/
|
|
624
633
|
private _readvertiseAfterRefusal;
|
|
634
|
+
/**
|
|
635
|
+
* Whether a tree describes a folder holding nothing whatsoever.
|
|
636
|
+
*
|
|
637
|
+
* Deliberately conservative: an empty DIRECTORY counts as content. A user who
|
|
638
|
+
* creates a folder and puts a directory in it has made a statement about what
|
|
639
|
+
* should be there, and the silence this gates is only correct for an agent
|
|
640
|
+
* that has established nothing at all. Being wrong in that direction would
|
|
641
|
+
* mean a folder that never advertises until something else happens to change.
|
|
642
|
+
* @param tree - The tree to inspect.
|
|
643
|
+
* @returns `true` when the tree carries no entries at all.
|
|
644
|
+
*/
|
|
645
|
+
private _treeIsEmpty;
|
|
625
646
|
private _adoptAppliedRef;
|
|
626
647
|
/**
|
|
627
648
|
* Derives a deterministic content key from an FsTree.
|
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,56 @@ ${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
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1694
|
+
const seen = /* @__PURE__ */ new Set([rootHash]);
|
|
1695
|
+
let frontier = [rootHash];
|
|
1696
|
+
while (frontier.length > 0) {
|
|
1697
|
+
const next = [];
|
|
1698
|
+
for (let i = 0; i < frontier.length; i += TREE_FETCH_CONCURRENCY) {
|
|
1699
|
+
const batch = frontier.slice(i, i + TREE_FETCH_CONCURRENCY);
|
|
1700
|
+
const results = await Promise.all(
|
|
1701
|
+
batch.map(async (hash) => {
|
|
1702
|
+
try {
|
|
1703
|
+
return await FsAgent._withTimeout(
|
|
1704
|
+
db.get(route, { _hash: hash }),
|
|
1705
|
+
this._timeouts.dbQuery,
|
|
1706
|
+
`db.get(${treeKey}, _hash=${hash.slice(0, 8)}…)`
|
|
1707
|
+
);
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
if (error instanceof Error && error.message.startsWith("Timeout")) {
|
|
1710
|
+
throw error;
|
|
1711
|
+
}
|
|
1712
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1713
|
+
console.warn(
|
|
1714
|
+
`[FsAgent] _fetchTreeRecursively: db.get failed for hash=${hash.slice(0, 8)}…: ${errMsg}`
|
|
1715
|
+
);
|
|
1716
|
+
this._writeSyncError(
|
|
1717
|
+
`fetchTree/db.get(${hash.slice(0, 8)}…)`,
|
|
1718
|
+
error
|
|
1719
|
+
);
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
})
|
|
1720
1723
|
);
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1724
|
+
for (const result of results) {
|
|
1725
|
+
const treeData = result?.rljson?.[treeKey];
|
|
1726
|
+
if (!treeData || !treeData._data) continue;
|
|
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" || seen.has(childHash)) {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
seen.add(childHash);
|
|
1737
|
+
next.push(childHash);
|
|
1738
|
+
}
|
|
1735
1739
|
}
|
|
1736
1740
|
}
|
|
1737
1741
|
}
|
|
1738
1742
|
}
|
|
1743
|
+
frontier = next;
|
|
1739
1744
|
}
|
|
1740
1745
|
return Array.from(fetchedNodes.values());
|
|
1741
1746
|
}
|
|
@@ -1883,7 +1888,16 @@ ${err.stack}` : String(err);
|
|
|
1883
1888
|
this._timeouts.fetchTree,
|
|
1884
1889
|
`syncToDb → initial storeFsTree(${treeKey})`
|
|
1885
1890
|
);
|
|
1886
|
-
|
|
1891
|
+
const isSilentJoiner = initialParentRef === void 0 && this._treeIsEmpty(initialTree);
|
|
1892
|
+
if (isSilentJoiner) {
|
|
1893
|
+
console.warn(
|
|
1894
|
+
`[FsAgent] ${this._rootPath} is empty and has no remembered state — joining quietly rather than announcing emptiness.`
|
|
1895
|
+
);
|
|
1896
|
+
this._currentRef = initialRef;
|
|
1897
|
+
this._persistCurrentRef(initialRef);
|
|
1898
|
+
this._lastSentContentKey = this._contentKeyFromTree(initialTree);
|
|
1899
|
+
}
|
|
1900
|
+
if (initialRef && !isSilentJoiner) {
|
|
1887
1901
|
this._lastSentRef = initialRef;
|
|
1888
1902
|
this._currentRef = initialRef;
|
|
1889
1903
|
this._persistCurrentRef(initialRef);
|
|
@@ -2136,6 +2150,20 @@ ${err.stack}` : String(err);
|
|
|
2136
2150
|
console.warn(`[FsAgent] re-announcement failed: ${String(err)}`);
|
|
2137
2151
|
}
|
|
2138
2152
|
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Whether a tree describes a folder holding nothing whatsoever.
|
|
2155
|
+
*
|
|
2156
|
+
* Deliberately conservative: an empty DIRECTORY counts as content. A user who
|
|
2157
|
+
* creates a folder and puts a directory in it has made a statement about what
|
|
2158
|
+
* should be there, and the silence this gates is only correct for an agent
|
|
2159
|
+
* that has established nothing at all. Being wrong in that direction would
|
|
2160
|
+
* mean a folder that never advertises until something else happens to change.
|
|
2161
|
+
* @param tree - The tree to inspect.
|
|
2162
|
+
* @returns `true` when the tree carries no entries at all.
|
|
2163
|
+
*/
|
|
2164
|
+
_treeIsEmpty(tree) {
|
|
2165
|
+
return this._getFileContentMap(tree).size === 0;
|
|
2166
|
+
}
|
|
2139
2167
|
_adoptAppliedRef(connector, treeRef) {
|
|
2140
2168
|
if (this._lastAppliedRef && this._lastAppliedRef !== treeRef) {
|
|
2141
2169
|
connector.invalidateSent?.(this._lastAppliedRef);
|