@rljson/fs-agent 0.0.17 → 0.0.19
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 +43 -0
- package/dist/fs-agent.js +121 -9
- package/dist/fs-scanner.d.ts +37 -3
- package/package.json +3 -3
package/dist/fs-agent.d.ts
CHANGED
|
@@ -112,6 +112,14 @@ export interface TimeoutConfig {
|
|
|
112
112
|
*/
|
|
113
113
|
recoveryRetries?: number;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Longest a disconnect may keep the watcher paused.
|
|
117
|
+
*
|
|
118
|
+
* Generous enough for an ordinary reconnect, short enough that a reconnect
|
|
119
|
+
* which never arrives costs a few seconds of missed notifications rather than
|
|
120
|
+
* every write from then on.
|
|
121
|
+
*/
|
|
122
|
+
export declare const DISCONNECT_PAUSE_MAX_MS = 30000;
|
|
115
123
|
/** Filename for sync error log written to the sync folder */
|
|
116
124
|
export declare const SYNC_ERROR_FILE = ".sync-errors.log";
|
|
117
125
|
/**
|
|
@@ -133,8 +141,24 @@ export declare class FsAgent {
|
|
|
133
141
|
private _stopSync?;
|
|
134
142
|
private _stopSyncFromDb?;
|
|
135
143
|
private _lastSentRef?;
|
|
144
|
+
/**
|
|
145
|
+
* The incoming ref most recently applied. Retired from the connector's dedup
|
|
146
|
+
* sets when the next one supersedes it, so a peer returning the tree to that
|
|
147
|
+
* state can still reach this agent.
|
|
148
|
+
*/
|
|
149
|
+
private _lastAppliedRef?;
|
|
136
150
|
/** Content fingerprint of the last tree we broadcasted (paths+blobIds) */
|
|
137
151
|
private _lastSentContentKey?;
|
|
152
|
+
/**
|
|
153
|
+
* True while a ref received from a peer is being applied to disk.
|
|
154
|
+
*
|
|
155
|
+
* The safety rescan cannot tell a local change the watcher missed (which it
|
|
156
|
+
* must broadcast) from a remote change not yet applied here (which it must
|
|
157
|
+
* not). The agent can: while this is set, the disk is mid-way through
|
|
158
|
+
* someone else's revision, so a rescan-driven push would re-assert our stale
|
|
159
|
+
* view — and undo a deletion the peer just made.
|
|
160
|
+
*/
|
|
161
|
+
private _remoteApplyInFlight;
|
|
138
162
|
private _timeouts;
|
|
139
163
|
/** Client-only: resolve DAG-branch conflicts into merge revisions. */
|
|
140
164
|
private _resolveConflicts;
|
|
@@ -400,6 +424,25 @@ export declare class FsAgent {
|
|
|
400
424
|
* @param map - Content map (relativePath → blobId)
|
|
401
425
|
*/
|
|
402
426
|
private _contentKeyFromMap;
|
|
427
|
+
/**
|
|
428
|
+
* Records `treeRef` as the ref describing this folder's current state, and
|
|
429
|
+
* retires the one it supersedes from the connector's dedup sets.
|
|
430
|
+
*
|
|
431
|
+
* A tree ref is a pure content hash, so a folder that returns to an earlier
|
|
432
|
+
* state re-derives that state's exact ref. The connector drops an incoming
|
|
433
|
+
* ref it has already received, which assumes a state is reached once and
|
|
434
|
+
* never returned to — false for content-addressed state, and false in the
|
|
435
|
+
* most ordinary way possible: create a file, then delete it again.
|
|
436
|
+
*
|
|
437
|
+
* Retiring the SUPERSEDED ref is what keeps the return trip deliverable.
|
|
438
|
+
* The ref just adopted stays deduped, so a peer re-advertising the state
|
|
439
|
+
* this folder is actually in is still suppressed as the echo it is. That
|
|
440
|
+
* only works if every adopted state passes through here — a state adopted
|
|
441
|
+
* silently is never retired and blocks its own return for good.
|
|
442
|
+
* @param connector - Connector whose dedup sets to retire from.
|
|
443
|
+
* @param treeRef - The ref that now describes this folder.
|
|
444
|
+
*/
|
|
445
|
+
private _adoptAppliedRef;
|
|
403
446
|
/**
|
|
404
447
|
* Derives a deterministic content key from an FsTree.
|
|
405
448
|
* @param tree - Tree structure to derive content key from
|
package/dist/fs-agent.js
CHANGED
|
@@ -413,6 +413,9 @@ class FsDbAdapter {
|
|
|
413
413
|
return this.db;
|
|
414
414
|
}
|
|
415
415
|
}
|
|
416
|
+
const _envRescan = Number(process.env["RLJSON_FS_RESCAN_MS"]);
|
|
417
|
+
const SAFETY_RESCAN_INTERVAL_MS = Number.isFinite(_envRescan) && _envRescan > 0 ? _envRescan : 5e3;
|
|
418
|
+
const STUCK_PAUSE_MS = 15e3;
|
|
416
419
|
class FsScanner {
|
|
417
420
|
_rootPath;
|
|
418
421
|
_tree = null;
|
|
@@ -424,6 +427,10 @@ class FsScanner {
|
|
|
424
427
|
_missedChangesDuringPause = false;
|
|
425
428
|
/** Periodic full-rescan timer that catches events the native watcher drops. */
|
|
426
429
|
_safetyTimer = null;
|
|
430
|
+
/** When the current pause began, or null when not paused. */
|
|
431
|
+
_pausedAt = null;
|
|
432
|
+
/** Releases a pause whose resume never arrived (see {@link pauseWatch}). */
|
|
433
|
+
_autoResumeTimer = null;
|
|
427
434
|
/** Set by stopWatch() so a pending watcher reinstall / rescan bails out. */
|
|
428
435
|
_stopRequested = false;
|
|
429
436
|
/** Path→content cache backing {@link FsScanOptions.scanCachePath} (unused when unset). */
|
|
@@ -697,7 +704,7 @@ class FsScanner {
|
|
|
697
704
|
if (!this._safetyTimer) {
|
|
698
705
|
this._safetyTimer = setInterval(() => {
|
|
699
706
|
void this._runSafetyRescan();
|
|
700
|
-
},
|
|
707
|
+
}, SAFETY_RESCAN_INTERVAL_MS);
|
|
701
708
|
this._safetyTimer.unref?.();
|
|
702
709
|
}
|
|
703
710
|
}
|
|
@@ -708,7 +715,10 @@ class FsScanner {
|
|
|
708
715
|
* scan failures are no-ops.
|
|
709
716
|
*/
|
|
710
717
|
async _runSafetyRescan() {
|
|
711
|
-
if (this.
|
|
718
|
+
if (this._stopRequested) return;
|
|
719
|
+
if (this._paused && !this._pauseLooksStuck({ type: "safety-rescan", path: "." })) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
712
722
|
const prevKey = this._tree ? this._safetyContentKey(this._tree) : null;
|
|
713
723
|
try {
|
|
714
724
|
await this.scan();
|
|
@@ -718,7 +728,7 @@ class FsScanner {
|
|
|
718
728
|
);
|
|
719
729
|
return;
|
|
720
730
|
}
|
|
721
|
-
if (this.
|
|
731
|
+
if (this._stopRequested) return;
|
|
722
732
|
const nextKey = this._tree ? this._safetyContentKey(this._tree) : null;
|
|
723
733
|
if (prevKey !== nextKey) {
|
|
724
734
|
console.warn(
|
|
@@ -819,8 +829,24 @@ class FsScanner {
|
|
|
819
829
|
}
|
|
820
830
|
return void 0;
|
|
821
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Whether this notification should escape the pause.
|
|
834
|
+
*
|
|
835
|
+
* Only the safety rescan may, and only once the pause has outlasted any
|
|
836
|
+
* plausible restore. Letting it through a SHORT pause reintroduces exactly
|
|
837
|
+
* the echo the pause prevents — a rescan firing mid-restore notifies, the
|
|
838
|
+
* agent stores a tree built from half-restored files, and delete propagation
|
|
839
|
+
* breaks (caught by the client-server suite, not by unit tests).
|
|
840
|
+
* @param change - The pending notification.
|
|
841
|
+
* @returns `true` when the pause has lasted long enough to look stuck.
|
|
842
|
+
*/
|
|
843
|
+
_pauseLooksStuck(change) {
|
|
844
|
+
if (change.type !== "safety-rescan") return false;
|
|
845
|
+
if (this._pausedAt === null) return false;
|
|
846
|
+
return Date.now() - this._pausedAt >= STUCK_PAUSE_MS;
|
|
847
|
+
}
|
|
822
848
|
async _notifyChange(change) {
|
|
823
|
-
if (this._paused) {
|
|
849
|
+
if (this._paused && !this._pauseLooksStuck(change)) {
|
|
824
850
|
return;
|
|
825
851
|
}
|
|
826
852
|
for (const callback of this._changeCallbacks) {
|
|
@@ -837,6 +863,10 @@ class FsScanner {
|
|
|
837
863
|
}
|
|
838
864
|
stopWatch() {
|
|
839
865
|
this._stopRequested = true;
|
|
866
|
+
if (this._autoResumeTimer) {
|
|
867
|
+
clearTimeout(this._autoResumeTimer);
|
|
868
|
+
this._autoResumeTimer = null;
|
|
869
|
+
}
|
|
840
870
|
if (this._safetyTimer) {
|
|
841
871
|
clearInterval(this._safetyTimer);
|
|
842
872
|
this._safetyTimer = null;
|
|
@@ -847,12 +877,37 @@ class FsScanner {
|
|
|
847
877
|
}
|
|
848
878
|
}
|
|
849
879
|
/**
|
|
850
|
-
* Temporarily pause file change notifications
|
|
851
|
-
*
|
|
880
|
+
* Temporarily pause file change notifications, so an external restore does
|
|
881
|
+
* not loop back as a local change.
|
|
882
|
+
*
|
|
883
|
+
* `autoResumeMs` bounds the pause. A pause taken on socket disconnect is
|
|
884
|
+
* released by the matching reconnect — and when that reconnect never fires,
|
|
885
|
+
* an unbounded pause silences the node permanently. A bounded one degrades
|
|
886
|
+
* to a little duplicate work instead, which is the right way round: the
|
|
887
|
+
* loop-suppression this exists for is an optimisation, staying alive is not.
|
|
888
|
+
* @param autoResumeMs - Release the pause after this many milliseconds.
|
|
889
|
+
* Omit to pause until an explicit `resumeWatch()`.
|
|
852
890
|
*/
|
|
853
|
-
pauseWatch() {
|
|
891
|
+
pauseWatch(autoResumeMs) {
|
|
892
|
+
if (!this._paused) this._pausedAt = Date.now();
|
|
854
893
|
this._paused = true;
|
|
855
894
|
this._missedChangesDuringPause = false;
|
|
895
|
+
if (this._autoResumeTimer) {
|
|
896
|
+
clearTimeout(this._autoResumeTimer);
|
|
897
|
+
this._autoResumeTimer = null;
|
|
898
|
+
}
|
|
899
|
+
if (autoResumeMs !== void 0) {
|
|
900
|
+
this._autoResumeTimer = setTimeout(() => {
|
|
901
|
+
this._autoResumeTimer = null;
|
|
902
|
+
if (this._paused) {
|
|
903
|
+
console.warn(
|
|
904
|
+
`[fs-scanner] pause exceeded ${autoResumeMs}ms without a resume — releasing it so ${this._rootPath} keeps syncing`
|
|
905
|
+
);
|
|
906
|
+
this.resumeWatch();
|
|
907
|
+
}
|
|
908
|
+
}, autoResumeMs);
|
|
909
|
+
this._autoResumeTimer.unref?.();
|
|
910
|
+
}
|
|
856
911
|
}
|
|
857
912
|
/**
|
|
858
913
|
* Resume file change notifications.
|
|
@@ -860,8 +915,13 @@ class FsScanner {
|
|
|
860
915
|
* an asynchronous rescan so that syncToDb can detect and push the changes.
|
|
861
916
|
*/
|
|
862
917
|
resumeWatch() {
|
|
918
|
+
if (this._autoResumeTimer) {
|
|
919
|
+
clearTimeout(this._autoResumeTimer);
|
|
920
|
+
this._autoResumeTimer = null;
|
|
921
|
+
}
|
|
863
922
|
const missedChanges = this._missedChangesDuringPause;
|
|
864
923
|
this._paused = false;
|
|
924
|
+
this._pausedAt = null;
|
|
865
925
|
this._missedChangesDuringPause = false;
|
|
866
926
|
if (missedChanges) {
|
|
867
927
|
void this._rescanAfterPause();
|
|
@@ -921,6 +981,7 @@ const DEFAULT_TIMEOUTS = {
|
|
|
921
981
|
processRefRetryDelayMs: 5e3,
|
|
922
982
|
recoveryRetries: 10
|
|
923
983
|
};
|
|
984
|
+
const DISCONNECT_PAUSE_MAX_MS = 3e4;
|
|
924
985
|
const SYNC_ERROR_FILE = ".sync-errors.log";
|
|
925
986
|
const ATOMIC_TMP_PREFIX = ".fsagent-tmp-";
|
|
926
987
|
class FsAgent {
|
|
@@ -933,8 +994,24 @@ class FsAgent {
|
|
|
933
994
|
_stopSync;
|
|
934
995
|
_stopSyncFromDb;
|
|
935
996
|
_lastSentRef;
|
|
997
|
+
/**
|
|
998
|
+
* The incoming ref most recently applied. Retired from the connector's dedup
|
|
999
|
+
* sets when the next one supersedes it, so a peer returning the tree to that
|
|
1000
|
+
* state can still reach this agent.
|
|
1001
|
+
*/
|
|
1002
|
+
_lastAppliedRef;
|
|
936
1003
|
/** Content fingerprint of the last tree we broadcasted (paths+blobIds) */
|
|
937
1004
|
_lastSentContentKey;
|
|
1005
|
+
/**
|
|
1006
|
+
* True while a ref received from a peer is being applied to disk.
|
|
1007
|
+
*
|
|
1008
|
+
* The safety rescan cannot tell a local change the watcher missed (which it
|
|
1009
|
+
* must broadcast) from a remote change not yet applied here (which it must
|
|
1010
|
+
* not). The agent can: while this is set, the disk is mid-way through
|
|
1011
|
+
* someone else's revision, so a rescan-driven push would re-assert our stale
|
|
1012
|
+
* view — and undo a deletion the peer just made.
|
|
1013
|
+
*/
|
|
1014
|
+
_remoteApplyInFlight = false;
|
|
938
1015
|
_timeouts;
|
|
939
1016
|
/** Client-only: resolve DAG-branch conflicts into merge revisions. */
|
|
940
1017
|
_resolveConflicts;
|
|
@@ -1119,6 +1196,7 @@ ${err.stack}` : String(err);
|
|
|
1119
1196
|
* via an explicit send, which pre-empts the Connector's db-observer path.
|
|
1120
1197
|
*/
|
|
1121
1198
|
async _sendRef(connector, ref, predecessorRefs) {
|
|
1199
|
+
connector.invalidateSent?.(ref);
|
|
1122
1200
|
if (this._resolveConflicts) {
|
|
1123
1201
|
connector.setPredecessors(predecessorRefs ?? []);
|
|
1124
1202
|
}
|
|
@@ -1530,7 +1608,10 @@ ${err.stack}` : String(err);
|
|
|
1530
1608
|
);
|
|
1531
1609
|
}
|
|
1532
1610
|
let debounceTimer = null;
|
|
1533
|
-
const debouncedSync = () => {
|
|
1611
|
+
const debouncedSync = (change) => {
|
|
1612
|
+
if (change?.type === "safety-rescan" && this._remoteApplyInFlight) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1534
1615
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1535
1616
|
debounceTimer = setTimeout(async () => {
|
|
1536
1617
|
debounceTimer = null;
|
|
@@ -1719,6 +1800,30 @@ ${err.stack}` : String(err);
|
|
|
1719
1800
|
);
|
|
1720
1801
|
return sorted.map(([p, b]) => `${p}:${b}`).join("\n");
|
|
1721
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Records `treeRef` as the ref describing this folder's current state, and
|
|
1805
|
+
* retires the one it supersedes from the connector's dedup sets.
|
|
1806
|
+
*
|
|
1807
|
+
* A tree ref is a pure content hash, so a folder that returns to an earlier
|
|
1808
|
+
* state re-derives that state's exact ref. The connector drops an incoming
|
|
1809
|
+
* ref it has already received, which assumes a state is reached once and
|
|
1810
|
+
* never returned to — false for content-addressed state, and false in the
|
|
1811
|
+
* most ordinary way possible: create a file, then delete it again.
|
|
1812
|
+
*
|
|
1813
|
+
* Retiring the SUPERSEDED ref is what keeps the return trip deliverable.
|
|
1814
|
+
* The ref just adopted stays deduped, so a peer re-advertising the state
|
|
1815
|
+
* this folder is actually in is still suppressed as the echo it is. That
|
|
1816
|
+
* only works if every adopted state passes through here — a state adopted
|
|
1817
|
+
* silently is never retired and blocks its own return for good.
|
|
1818
|
+
* @param connector - Connector whose dedup sets to retire from.
|
|
1819
|
+
* @param treeRef - The ref that now describes this folder.
|
|
1820
|
+
*/
|
|
1821
|
+
_adoptAppliedRef(connector, treeRef) {
|
|
1822
|
+
if (this._lastAppliedRef && this._lastAppliedRef !== treeRef) {
|
|
1823
|
+
connector.invalidateSent?.(this._lastAppliedRef);
|
|
1824
|
+
}
|
|
1825
|
+
this._lastAppliedRef = treeRef;
|
|
1826
|
+
}
|
|
1722
1827
|
/**
|
|
1723
1828
|
* Derives a deterministic content key from an FsTree.
|
|
1724
1829
|
* @param tree - Tree structure to derive content key from
|
|
@@ -1809,6 +1914,7 @@ ${err.stack}` : String(err);
|
|
|
1809
1914
|
const maxAttempts = this._timeouts.processRefRetries + 1;
|
|
1810
1915
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1811
1916
|
this._scanner.pauseWatch();
|
|
1917
|
+
this._remoteApplyInFlight = true;
|
|
1812
1918
|
try {
|
|
1813
1919
|
const incomingTree = await FsAgent._withTimeout(
|
|
1814
1920
|
this._fetchTreeFromDb(db, treeKey, treeRef),
|
|
@@ -1830,6 +1936,7 @@ ${err.stack}` : String(err);
|
|
|
1830
1936
|
console.log(
|
|
1831
1937
|
`[FsAgent] syncFromDb: equivalent content, skipping restore (incoming=${incomingFiles.size} entries, current=${currentFiles.size} entries, ref=${treeRef.slice(0, 8)}…)`
|
|
1832
1938
|
);
|
|
1939
|
+
this._adoptAppliedRef(connector, treeRef);
|
|
1833
1940
|
return;
|
|
1834
1941
|
}
|
|
1835
1942
|
if (this._resolveConflicts && this._currentRef && predecessorRefs && predecessorRefs.length > 0) {
|
|
@@ -1870,6 +1977,7 @@ ${err.stack}` : String(err);
|
|
|
1870
1977
|
skipNotification: true,
|
|
1871
1978
|
previous
|
|
1872
1979
|
});
|
|
1980
|
+
this._adoptAppliedRef(connector, treeRef);
|
|
1873
1981
|
this._lastSentRef = postRestoreRef;
|
|
1874
1982
|
this._lastSentContentKey = this._contentKeyFromTree(postRestoreTree);
|
|
1875
1983
|
this._currentRef = postRestoreRef;
|
|
@@ -1903,6 +2011,7 @@ ${err.stack}` : String(err);
|
|
|
1903
2011
|
);
|
|
1904
2012
|
}
|
|
1905
2013
|
} finally {
|
|
2014
|
+
this._remoteApplyInFlight = false;
|
|
1906
2015
|
this._scanner.resumeWatch();
|
|
1907
2016
|
}
|
|
1908
2017
|
await new Promise(
|
|
@@ -1911,6 +2020,9 @@ ${err.stack}` : String(err);
|
|
|
1911
2020
|
}
|
|
1912
2021
|
};
|
|
1913
2022
|
const scheduleProcess = (ref, delayMs, recoveryAttempt, predecessorRefs) => {
|
|
2023
|
+
if (pendingRef && pendingRef !== ref) {
|
|
2024
|
+
connector.invalidateReceived(pendingRef);
|
|
2025
|
+
}
|
|
1914
2026
|
pendingRef = ref;
|
|
1915
2027
|
pendingRecoveryAttempt = recoveryAttempt;
|
|
1916
2028
|
pendingPredecessorRefs = predecessorRefs;
|
|
@@ -1991,7 +2103,7 @@ ${err.stack}` : String(err);
|
|
|
1991
2103
|
};
|
|
1992
2104
|
if (typeof client.onDisconnect === "function") {
|
|
1993
2105
|
client.onDisconnect(() => {
|
|
1994
|
-
agent.scanner.pauseWatch();
|
|
2106
|
+
agent.scanner.pauseWatch(DISCONNECT_PAUSE_MAX_MS);
|
|
1995
2107
|
});
|
|
1996
2108
|
}
|
|
1997
2109
|
if (typeof client.onReconnect === "function") {
|
package/dist/fs-scanner.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Bs } from '@rljson/bs';
|
|
2
2
|
import { Json } from '@rljson/json';
|
|
3
3
|
import { Tree, TreeRef } from '@rljson/rljson';
|
|
4
|
+
export declare const SAFETY_RESCAN_INTERVAL_MS: number;
|
|
5
|
+
/**
|
|
6
|
+
* How long a pause must last before the safety rescan treats it as stuck and
|
|
7
|
+
* reports through it.
|
|
8
|
+
*
|
|
9
|
+
* Longer than any restore takes, so ordinary loop-suppression is untouched;
|
|
10
|
+
* short enough that a pause whose resume never arrives costs seconds, not
|
|
11
|
+
* every write from then on.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STUCK_PAUSE_MS = 15000;
|
|
4
14
|
/**
|
|
5
15
|
* Metadata stored in Tree.meta for file system nodes
|
|
6
16
|
*/
|
|
@@ -93,6 +103,10 @@ export declare class FsScanner {
|
|
|
93
103
|
private _missedChangesDuringPause;
|
|
94
104
|
/** Periodic full-rescan timer that catches events the native watcher drops. */
|
|
95
105
|
private _safetyTimer;
|
|
106
|
+
/** When the current pause began, or null when not paused. */
|
|
107
|
+
private _pausedAt;
|
|
108
|
+
/** Releases a pause whose resume never arrived (see {@link pauseWatch}). */
|
|
109
|
+
private _autoResumeTimer;
|
|
96
110
|
/** Set by stopWatch() so a pending watcher reinstall / rescan bails out. */
|
|
97
111
|
private _stopRequested;
|
|
98
112
|
/** Path→content cache backing {@link FsScanOptions.scanCachePath} (unused when unset). */
|
|
@@ -146,15 +160,35 @@ export declare class FsScanner {
|
|
|
146
160
|
private static get _isWindows();
|
|
147
161
|
private _handleFileChange;
|
|
148
162
|
private _findTreeByPath;
|
|
163
|
+
/**
|
|
164
|
+
* Whether this notification should escape the pause.
|
|
165
|
+
*
|
|
166
|
+
* Only the safety rescan may, and only once the pause has outlasted any
|
|
167
|
+
* plausible restore. Letting it through a SHORT pause reintroduces exactly
|
|
168
|
+
* the echo the pause prevents — a rescan firing mid-restore notifies, the
|
|
169
|
+
* agent stores a tree built from half-restored files, and delete propagation
|
|
170
|
+
* breaks (caught by the client-server suite, not by unit tests).
|
|
171
|
+
* @param change - The pending notification.
|
|
172
|
+
* @returns `true` when the pause has lasted long enough to look stuck.
|
|
173
|
+
*/
|
|
174
|
+
private _pauseLooksStuck;
|
|
149
175
|
private _notifyChange;
|
|
150
176
|
onChange(callback: FsChangeCallback): void;
|
|
151
177
|
offChange(callback: FsChangeCallback): void;
|
|
152
178
|
stopWatch(): void;
|
|
153
179
|
/**
|
|
154
|
-
* Temporarily pause file change notifications
|
|
155
|
-
*
|
|
180
|
+
* Temporarily pause file change notifications, so an external restore does
|
|
181
|
+
* not loop back as a local change.
|
|
182
|
+
*
|
|
183
|
+
* `autoResumeMs` bounds the pause. A pause taken on socket disconnect is
|
|
184
|
+
* released by the matching reconnect — and when that reconnect never fires,
|
|
185
|
+
* an unbounded pause silences the node permanently. A bounded one degrades
|
|
186
|
+
* to a little duplicate work instead, which is the right way round: the
|
|
187
|
+
* loop-suppression this exists for is an optimisation, staying alive is not.
|
|
188
|
+
* @param autoResumeMs - Release the pause after this many milliseconds.
|
|
189
|
+
* Omit to pause until an explicit `resumeWatch()`.
|
|
156
190
|
*/
|
|
157
|
-
pauseWatch(): void;
|
|
191
|
+
pauseWatch(autoResumeMs?: number): void;
|
|
158
192
|
/**
|
|
159
193
|
* Resume file change notifications.
|
|
160
194
|
* If any filesystem events were missed during the pause, triggers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rljson/fs-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"updateGoldens": "cross-env UPDATE_GOLDENS=true pnpm test"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@rljson/server": "^0.0.
|
|
31
|
+
"@rljson/server": "^0.0.43",
|
|
32
32
|
"@types/node": "^25.3.1",
|
|
33
33
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
34
34
|
"@typescript-eslint/parser": "^8.56.1",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@rljson/bs": "^0.0.21",
|
|
54
|
-
"@rljson/db": "^0.0.
|
|
54
|
+
"@rljson/db": "^0.0.31",
|
|
55
55
|
"@rljson/hash": "^0.0.18",
|
|
56
56
|
"@rljson/io": "^0.0.66",
|
|
57
57
|
"@rljson/json": "^0.0.23",
|