@rljson/fs-agent 0.0.60 → 0.0.62
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 +20 -0
- package/dist/fs-agent.js +61 -2
- package/dist/fs-scanner.d.ts +21 -0
- package/package.json +1 -1
package/dist/fs-agent.d.ts
CHANGED
|
@@ -300,6 +300,16 @@ export declare class FsAgent {
|
|
|
300
300
|
/** Files written vs left alone by the current {@link restore}. */
|
|
301
301
|
private _restoreWritten;
|
|
302
302
|
private _restoreSkipped;
|
|
303
|
+
/**
|
|
304
|
+
* Absolute paths deleted here since the last announcement.
|
|
305
|
+
*
|
|
306
|
+
* The mirror of {@link _announcedFiles}. Between a local unlink and the push
|
|
307
|
+
* that announces it, this node's advertised state still CONTAINS the file —
|
|
308
|
+
* so a peer pushing in that window descends from a state the file is in, and
|
|
309
|
+
* an apply re-creates it. The local scan then sees the file present, and the
|
|
310
|
+
* deletion is never announced at all: silently undone, everywhere.
|
|
311
|
+
*/
|
|
312
|
+
private readonly _pendingDeletes;
|
|
303
313
|
/**
|
|
304
314
|
* The states this node has held, oldest first.
|
|
305
315
|
*
|
|
@@ -783,6 +793,16 @@ export declare class FsAgent {
|
|
|
783
793
|
* @returns True when every declared parent is a state this node has left.
|
|
784
794
|
*/
|
|
785
795
|
private _senderIsBehind;
|
|
796
|
+
/**
|
|
797
|
+
* Marks an incoming tree's files as known to the network.
|
|
798
|
+
*
|
|
799
|
+
* A union, not a replacement: {@link _rememberAnnounced} answers "what this
|
|
800
|
+
* node has told the network", and this answers "what the network has told
|
|
801
|
+
* this node". Both make a file prunable; neither alone is the whole set, and
|
|
802
|
+
* treating the first as the whole set is what made deletions vanish.
|
|
803
|
+
* @param tree - The tree just applied.
|
|
804
|
+
*/
|
|
805
|
+
private _rememberReceived;
|
|
786
806
|
private _rememberAnnounced;
|
|
787
807
|
private _adoptAppliedRef;
|
|
788
808
|
/**
|
package/dist/fs-agent.js
CHANGED
|
@@ -684,6 +684,32 @@ class FsScanner {
|
|
|
684
684
|
} catch {
|
|
685
685
|
}
|
|
686
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Whether a watcher event's path should be ignored.
|
|
689
|
+
*
|
|
690
|
+
* `fs.watch` in recursive mode reports a path RELATIVE TO THE ROOT
|
|
691
|
+
* (`sub/dir/.fsagent-tmp-abc`), while the ignore patterns are basenames.
|
|
692
|
+
* {@link _shouldIgnore} tests `startsWith`, so a nested match never fired:
|
|
693
|
+
* every atomic write the agent itself makes during a restore came back as a
|
|
694
|
+
* change event, each event triggered a scan, and the debounce that batches a
|
|
695
|
+
* push was reset before it could fire.
|
|
696
|
+
*
|
|
697
|
+
* Measured on the customer's folder: after a 3 642-file restore the watcher
|
|
698
|
+
* reported the SAME newly added file nine times and the agent never emitted a
|
|
699
|
+
* ref for it — `[fs] added: …/probe-….txt` nine times, no `sync:out`. The
|
|
700
|
+
* file did not fail to arrive; it was never sent.
|
|
701
|
+
*
|
|
702
|
+
* Every segment is tested, because the pattern may match a directory as
|
|
703
|
+
* easily as a file.
|
|
704
|
+
* @param relativePath - Path as the watcher reports it.
|
|
705
|
+
* @returns True when any segment matches an ignore pattern.
|
|
706
|
+
*/
|
|
707
|
+
_shouldIgnorePath(relativePath) {
|
|
708
|
+
for (const segment of relativePath.split(/[\\/]/)) {
|
|
709
|
+
if (segment && this._shouldIgnore(segment)) return true;
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
687
713
|
_shouldIgnore(name) {
|
|
688
714
|
if (!this._options.ignore) {
|
|
689
715
|
return false;
|
|
@@ -704,7 +730,7 @@ class FsScanner {
|
|
|
704
730
|
}
|
|
705
731
|
const onEvent = async (eventType, filename) => {
|
|
706
732
|
if (!filename) return;
|
|
707
|
-
if (this.
|
|
733
|
+
if (this._shouldIgnorePath(filename)) {
|
|
708
734
|
return;
|
|
709
735
|
}
|
|
710
736
|
await this._handleFileChange(eventType, filename);
|
|
@@ -1154,6 +1180,16 @@ class FsAgent {
|
|
|
1154
1180
|
/** Files written vs left alone by the current {@link restore}. */
|
|
1155
1181
|
_restoreWritten = 0;
|
|
1156
1182
|
_restoreSkipped = 0;
|
|
1183
|
+
/**
|
|
1184
|
+
* Absolute paths deleted here since the last announcement.
|
|
1185
|
+
*
|
|
1186
|
+
* The mirror of {@link _announcedFiles}. Between a local unlink and the push
|
|
1187
|
+
* that announces it, this node's advertised state still CONTAINS the file —
|
|
1188
|
+
* so a peer pushing in that window descends from a state the file is in, and
|
|
1189
|
+
* an apply re-creates it. The local scan then sees the file present, and the
|
|
1190
|
+
* deletion is never announced at all: silently undone, everywhere.
|
|
1191
|
+
*/
|
|
1192
|
+
_pendingDeletes = /* @__PURE__ */ new Set();
|
|
1157
1193
|
/**
|
|
1158
1194
|
* The states this node has held, oldest first.
|
|
1159
1195
|
*
|
|
@@ -1605,6 +1641,10 @@ ${err.stack}` : String(err);
|
|
|
1605
1641
|
this._restoreSkipped++;
|
|
1606
1642
|
return;
|
|
1607
1643
|
}
|
|
1644
|
+
if (this._pendingDeletes.has(filePath)) {
|
|
1645
|
+
this._restoreSkipped++;
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1608
1648
|
let fileBlob;
|
|
1609
1649
|
try {
|
|
1610
1650
|
fileBlob = await this._bs.getBlob(meta.blobId);
|
|
@@ -2046,9 +2086,12 @@ ${err.stack}` : String(err);
|
|
|
2046
2086
|
this._flushDeferredRescan = () => {
|
|
2047
2087
|
if (!this._rescanDeferred) return;
|
|
2048
2088
|
this._rescanDeferred = false;
|
|
2049
|
-
debouncedSync({ type: "safety-rescan" });
|
|
2089
|
+
debouncedSync({ type: "safety-rescan", path: "." });
|
|
2050
2090
|
};
|
|
2051
2091
|
const debouncedSync = (change) => {
|
|
2092
|
+
if (change?.type === "deleted" && change.path !== ".") {
|
|
2093
|
+
this._pendingDeletes.add(join(this._rootPath, change.path));
|
|
2094
|
+
}
|
|
2052
2095
|
if (change?.type === "safety-rescan" && this._remoteApplyInFlight) {
|
|
2053
2096
|
this._rescanDeferred = true;
|
|
2054
2097
|
return;
|
|
@@ -2411,11 +2454,26 @@ ${err.stack}` : String(err);
|
|
|
2411
2454
|
(ref) => ref !== this._currentRef && ref !== this._lastAppliedRef && this._stateHistory.includes(ref)
|
|
2412
2455
|
);
|
|
2413
2456
|
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Marks an incoming tree's files as known to the network.
|
|
2459
|
+
*
|
|
2460
|
+
* A union, not a replacement: {@link _rememberAnnounced} answers "what this
|
|
2461
|
+
* node has told the network", and this answers "what the network has told
|
|
2462
|
+
* this node". Both make a file prunable; neither alone is the whole set, and
|
|
2463
|
+
* treating the first as the whole set is what made deletions vanish.
|
|
2464
|
+
* @param tree - The tree just applied.
|
|
2465
|
+
*/
|
|
2466
|
+
_rememberReceived(tree) {
|
|
2467
|
+
for (const path of this._getFileContentMap(tree).keys()) {
|
|
2468
|
+
this._announcedFiles.add(join(this._rootPath, path));
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2414
2471
|
_rememberAnnounced(tree) {
|
|
2415
2472
|
this._announcedFiles.clear();
|
|
2416
2473
|
for (const path of this._getFileContentMap(tree).keys()) {
|
|
2417
2474
|
this._announcedFiles.add(join(this._rootPath, path));
|
|
2418
2475
|
}
|
|
2476
|
+
this._pendingDeletes.clear();
|
|
2419
2477
|
}
|
|
2420
2478
|
_adoptAppliedRef(connector, treeRef) {
|
|
2421
2479
|
if (this._lastAppliedRef && this._lastAppliedRef !== treeRef) {
|
|
@@ -2619,6 +2677,7 @@ ${err.stack}` : String(err);
|
|
|
2619
2677
|
this._adoptAppliedRef(connector, treeRef);
|
|
2620
2678
|
this._currentRef = postRestoreRef;
|
|
2621
2679
|
this._persistCurrentRef(postRestoreRef);
|
|
2680
|
+
this._rememberReceived(incomingTree);
|
|
2622
2681
|
const postRestoreFiles = this._getFileContentMap(postRestoreTree);
|
|
2623
2682
|
const incomingFiles = this._getFileContentMap(incomingTree);
|
|
2624
2683
|
let hasNewsOfOurOwn = false;
|
package/dist/fs-scanner.d.ts
CHANGED
|
@@ -141,6 +141,27 @@ export declare class FsScanner {
|
|
|
141
141
|
* (temp + rename). Best-effort: a failed cache write never fails the scan.
|
|
142
142
|
*/
|
|
143
143
|
private _persistCache;
|
|
144
|
+
/**
|
|
145
|
+
* Whether a watcher event's path should be ignored.
|
|
146
|
+
*
|
|
147
|
+
* `fs.watch` in recursive mode reports a path RELATIVE TO THE ROOT
|
|
148
|
+
* (`sub/dir/.fsagent-tmp-abc`), while the ignore patterns are basenames.
|
|
149
|
+
* {@link _shouldIgnore} tests `startsWith`, so a nested match never fired:
|
|
150
|
+
* every atomic write the agent itself makes during a restore came back as a
|
|
151
|
+
* change event, each event triggered a scan, and the debounce that batches a
|
|
152
|
+
* push was reset before it could fire.
|
|
153
|
+
*
|
|
154
|
+
* Measured on the customer's folder: after a 3 642-file restore the watcher
|
|
155
|
+
* reported the SAME newly added file nine times and the agent never emitted a
|
|
156
|
+
* ref for it — `[fs] added: …/probe-….txt` nine times, no `sync:out`. The
|
|
157
|
+
* file did not fail to arrive; it was never sent.
|
|
158
|
+
*
|
|
159
|
+
* Every segment is tested, because the pattern may match a directory as
|
|
160
|
+
* easily as a file.
|
|
161
|
+
* @param relativePath - Path as the watcher reports it.
|
|
162
|
+
* @returns True when any segment matches an ignore pattern.
|
|
163
|
+
*/
|
|
164
|
+
private _shouldIgnorePath;
|
|
144
165
|
private _shouldIgnore;
|
|
145
166
|
watch(): Promise<void>;
|
|
146
167
|
/**
|