@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 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
@@ -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
@@ -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._scanner.pauseWatch();
1056
- try {
1057
- const incomingTree = await FsAgent._withTimeout(
1058
- this._fetchTreeFromDb(db, treeKey, treeRef),
1059
- this._timeouts.fetchTree,
1060
- `syncFromDb → fetchTree(${treeKey}@${treeRef.slice(0, 8)}…)`
1061
- );
1062
- const incomingNodeCount = incomingTree.trees.size;
1063
- console.log(
1064
- `[FsAgent] syncFromDb: fetched tree with ${incomingNodeCount} nodes for ref=${treeRef.slice(0, 8)}…`
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: equivalent content, skipping restore (incoming=${incomingFiles.size} entries, current=${currentFiles.size} entries, ref=${treeRef.slice(0, 8)}…)`
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 FsAgent._withTimeout(
1080
- this.restore(incomingTree, void 0, restoreOptions),
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/fs-agent",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
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",