@rljson/fs-agent 0.0.9 → 0.0.10
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/README.public.md +10 -0
- package/dist/README.public.md +10 -0
- package/dist/fs-agent.d.ts +12 -0
- package/dist/fs-agent.js +56 -37
- package/package.json +1 -1
package/README.public.md
CHANGED
|
@@ -747,10 +747,20 @@ const agent = new FsAgent('./my-project', bs, {
|
|
|
747
747
|
restore: 15_000, // Filesystem restore (default: 15s)
|
|
748
748
|
syncCallback: 25_000, // Overall syncFromDb callback (default: 25s)
|
|
749
749
|
debounceMs: 300, // Coalesce rapid FS events (default: 300ms)
|
|
750
|
+
processRefRetries: 3, // Retry failed refs before dropping (default: 3)
|
|
751
|
+
processRefRetryDelayMs: 5_000, // Base delay between retries (default: 5s)
|
|
750
752
|
},
|
|
751
753
|
});
|
|
752
754
|
```
|
|
753
755
|
|
|
756
|
+
### processRef Retry Behavior
|
|
757
|
+
|
|
758
|
+
When `syncFromDb` receives a tree ref and fails to process it (e.g. `db.get`
|
|
759
|
+
times out because the IoPeer transport hasn't connected yet), the ref is
|
|
760
|
+
retried up to `processRefRetries` times with increasing delay
|
|
761
|
+
(`attempt * processRefRetryDelayMs`). This prevents a single transient
|
|
762
|
+
timeout from permanently breaking the sync pipeline.
|
|
763
|
+
|
|
754
764
|
## Bounce-Back Prevention
|
|
755
765
|
|
|
756
766
|
Bidirectional sync can cause infinite loops when both clients detect each
|
package/dist/README.public.md
CHANGED
|
@@ -747,10 +747,20 @@ const agent = new FsAgent('./my-project', bs, {
|
|
|
747
747
|
restore: 15_000, // Filesystem restore (default: 15s)
|
|
748
748
|
syncCallback: 25_000, // Overall syncFromDb callback (default: 25s)
|
|
749
749
|
debounceMs: 300, // Coalesce rapid FS events (default: 300ms)
|
|
750
|
+
processRefRetries: 3, // Retry failed refs before dropping (default: 3)
|
|
751
|
+
processRefRetryDelayMs: 5_000, // Base delay between retries (default: 5s)
|
|
750
752
|
},
|
|
751
753
|
});
|
|
752
754
|
```
|
|
753
755
|
|
|
756
|
+
### processRef Retry Behavior
|
|
757
|
+
|
|
758
|
+
When `syncFromDb` receives a tree ref and fails to process it (e.g. `db.get`
|
|
759
|
+
times out because the IoPeer transport hasn't connected yet), the ref is
|
|
760
|
+
retried up to `processRefRetries` times with increasing delay
|
|
761
|
+
(`attempt * processRefRetryDelayMs`). This prevents a single transient
|
|
762
|
+
timeout from permanently breaking the sync pipeline.
|
|
763
|
+
|
|
754
764
|
## Bounce-Back Prevention
|
|
755
765
|
|
|
756
766
|
Bidirectional sync can cause infinite loops when both clients detect each
|
package/dist/fs-agent.d.ts
CHANGED
|
@@ -73,6 +73,18 @@ export interface TimeoutConfig {
|
|
|
73
73
|
* Also applies to incoming database refs in syncFromDb.
|
|
74
74
|
*/
|
|
75
75
|
debounceMs?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Number of retries for processRef in syncFromDb. Default: 3.
|
|
78
|
+
* When a ref fails to process (e.g. db.get timeout because the IoPeer
|
|
79
|
+
* transport hasn't connected yet), the ref is retried this many times
|
|
80
|
+
* with increasing delay before being dropped.
|
|
81
|
+
*/
|
|
82
|
+
processRefRetries?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Base delay between processRef retries (milliseconds). Default: 5 000 ms.
|
|
85
|
+
* Each retry waits `attempt * processRefRetryDelayMs` (i.e. 5s, 10s, 15s).
|
|
86
|
+
*/
|
|
87
|
+
processRefRetryDelayMs?: number;
|
|
76
88
|
}
|
|
77
89
|
/** Filename for sync error log written to the sync folder */
|
|
78
90
|
export declare const SYNC_ERROR_FILE = ".sync-errors.log";
|
package/dist/fs-agent.js
CHANGED
|
@@ -484,7 +484,9 @@ const DEFAULT_TIMEOUTS = {
|
|
|
484
484
|
extract: 15e3,
|
|
485
485
|
restore: 15e3,
|
|
486
486
|
syncCallback: 25e3,
|
|
487
|
-
debounceMs: 300
|
|
487
|
+
debounceMs: 300,
|
|
488
|
+
processRefRetries: 3,
|
|
489
|
+
processRefRetryDelayMs: 5e3
|
|
488
490
|
};
|
|
489
491
|
const SYNC_ERROR_FILE = ".sync-errors.log";
|
|
490
492
|
class FsAgent {
|
|
@@ -1052,47 +1054,64 @@ ${err.stack}` : String(err);
|
|
|
1052
1054
|
let pendingRef = null;
|
|
1053
1055
|
let fromDbTimer = null;
|
|
1054
1056
|
const processRef = async (treeRef) => {
|
|
1055
|
-
this.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
);
|
|
1066
|
-
const currentTree = await FsAgent._withTimeout(
|
|
1067
|
-
this.extract(),
|
|
1068
|
-
this._timeouts.extract,
|
|
1069
|
-
`syncFromDb → extract(${this._rootPath})`
|
|
1070
|
-
);
|
|
1071
|
-
if (this._treesHaveEquivalentContent(currentTree, incomingTree)) {
|
|
1072
|
-
const incomingFiles = this._getFileContentMap(incomingTree);
|
|
1073
|
-
const currentFiles = this._getFileContentMap(currentTree);
|
|
1057
|
+
const maxAttempts = this._timeouts.processRefRetries + 1;
|
|
1058
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1059
|
+
this._scanner.pauseWatch();
|
|
1060
|
+
try {
|
|
1061
|
+
const incomingTree = await FsAgent._withTimeout(
|
|
1062
|
+
this._fetchTreeFromDb(db, treeKey, treeRef),
|
|
1063
|
+
this._timeouts.fetchTree,
|
|
1064
|
+
`syncFromDb → fetchTree(${treeKey}@${treeRef.slice(0, 8)}…)`
|
|
1065
|
+
);
|
|
1066
|
+
const incomingNodeCount = incomingTree.trees.size;
|
|
1074
1067
|
console.log(
|
|
1075
|
-
`[FsAgent] syncFromDb:
|
|
1068
|
+
`[FsAgent] syncFromDb: fetched tree with ${incomingNodeCount} nodes for ref=${treeRef.slice(0, 8)}…`
|
|
1076
1069
|
);
|
|
1070
|
+
const currentTree = await FsAgent._withTimeout(
|
|
1071
|
+
this.extract(),
|
|
1072
|
+
this._timeouts.extract,
|
|
1073
|
+
`syncFromDb → extract(${this._rootPath})`
|
|
1074
|
+
);
|
|
1075
|
+
if (this._treesHaveEquivalentContent(currentTree, incomingTree)) {
|
|
1076
|
+
const incomingFiles = this._getFileContentMap(incomingTree);
|
|
1077
|
+
const currentFiles = this._getFileContentMap(currentTree);
|
|
1078
|
+
console.log(
|
|
1079
|
+
`[FsAgent] syncFromDb: equivalent content, skipping restore (incoming=${incomingFiles.size} entries, current=${currentFiles.size} entries, ref=${treeRef.slice(0, 8)}…)`
|
|
1080
|
+
);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
await FsAgent._withTimeout(
|
|
1084
|
+
this.restore(incomingTree, void 0, restoreOptions),
|
|
1085
|
+
this._timeouts.restore,
|
|
1086
|
+
`syncFromDb → restore(${treeKey})`
|
|
1087
|
+
);
|
|
1088
|
+
const postRestoreTree = await this._scanner.scan();
|
|
1089
|
+
const dbAdapter = new FsDbAdapter(db, treeKey);
|
|
1090
|
+
const postRestoreRef = await dbAdapter.storeFsTree(postRestoreTree, {
|
|
1091
|
+
skipNotification: true
|
|
1092
|
+
});
|
|
1093
|
+
this._lastSentRef = postRestoreRef;
|
|
1094
|
+
this._lastSentContentKey = this._contentKeyFromTree(postRestoreTree);
|
|
1077
1095
|
return;
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
if (attempt === maxAttempts) {
|
|
1098
|
+
console.error(
|
|
1099
|
+
`[FsAgent] syncFromDb processRef failed after ${maxAttempts} attempts:`,
|
|
1100
|
+
err
|
|
1101
|
+
);
|
|
1102
|
+
this._writeSyncError("syncFromDb/processRef", err);
|
|
1103
|
+
} else {
|
|
1104
|
+
const delaySec = attempt * this._timeouts.processRefRetryDelayMs / 1e3;
|
|
1105
|
+
console.warn(
|
|
1106
|
+
`[FsAgent] syncFromDb: attempt ${attempt}/${maxAttempts} failed for ref=${treeRef.slice(0, 8)}…, retrying in ${delaySec}s: ${err instanceof Error ? err.message : String(err)}`
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
} finally {
|
|
1110
|
+
this._scanner.resumeWatch();
|
|
1078
1111
|
}
|
|
1079
|
-
await
|
|
1080
|
-
|
|
1081
|
-
this._timeouts.restore,
|
|
1082
|
-
`syncFromDb → restore(${treeKey})`
|
|
1112
|
+
await new Promise(
|
|
1113
|
+
(r) => setTimeout(r, attempt * this._timeouts.processRefRetryDelayMs)
|
|
1083
1114
|
);
|
|
1084
|
-
const postRestoreTree = await this._scanner.scan();
|
|
1085
|
-
const dbAdapter = new FsDbAdapter(db, treeKey);
|
|
1086
|
-
const postRestoreRef = await dbAdapter.storeFsTree(postRestoreTree, {
|
|
1087
|
-
skipNotification: true
|
|
1088
|
-
});
|
|
1089
|
-
this._lastSentRef = postRestoreRef;
|
|
1090
|
-
this._lastSentContentKey = this._contentKeyFromTree(postRestoreTree);
|
|
1091
|
-
} catch (err) {
|
|
1092
|
-
console.error("[FsAgent] syncFromDb processRef failed:", err);
|
|
1093
|
-
this._writeSyncError("syncFromDb/processRef", err);
|
|
1094
|
-
} finally {
|
|
1095
|
-
this._scanner.resumeWatch();
|
|
1096
1115
|
}
|
|
1097
1116
|
};
|
|
1098
1117
|
const syncCallback = async (treeRef) => {
|