@rljson/fs-agent 0.0.2 → 0.0.4
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.architecture.md +47 -0
- package/README.contributors.md +4 -0
- package/README.public.md +59 -2
- package/dist/README.architecture.md +47 -0
- package/dist/README.contributors.md +4 -0
- package/dist/README.public.md +59 -2
- package/dist/client-server/example-from-client.d.ts +1 -0
- package/dist/client-server/live-client-server-socketio.d.ts +1 -0
- package/dist/fs-agent.d.ts +137 -1
- package/dist/fs-agent.js +289 -3150
- package/dist/fs-db-adapter.d.ts +10 -3
- package/dist/index.d.ts +1 -1
- package/package.json +19 -17
package/README.architecture.md
CHANGED
|
@@ -447,3 +447,50 @@ This peer-to-peer pattern with server coordination enables:
|
|
|
447
447
|
✅ **Real-world deployment**: Works across networks, not just in-memory mocks
|
|
448
448
|
|
|
449
449
|
**This is why clients must NEVER access server Io/Bs directly** - it would bypass the entire peer-to-peer mechanism and make the system only work in single-process scenarios.
|
|
450
|
+
|
|
451
|
+
## Bounce-Back Prevention
|
|
452
|
+
|
|
453
|
+
Bidirectional sync creates a potential infinite loop:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
Client A writes file → syncToDb stores tree → broadcasts ref →
|
|
457
|
+
Client B receives ref → syncFromDb restores file → fs watcher fires →
|
|
458
|
+
syncToDb stores tree → broadcasts ref → Client A receives ref → ...
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
FsAgent uses three layers of deduplication to break the loop:
|
|
462
|
+
|
|
463
|
+
1. **Ref-level dedup**: After `syncToDb` stores a tree, it compares the
|
|
464
|
+
resulting ref against `_lastSentRef`. If identical, no broadcast occurs.
|
|
465
|
+
|
|
466
|
+
2. **Content-key dedup**: Even when refs differ (e.g. different mtimes produce
|
|
467
|
+
different hashes), `syncToDb` computes a content key from file paths +
|
|
468
|
+
blobIds. If the content key matches `_lastSentContentKey`, the broadcast
|
|
469
|
+
is skipped.
|
|
470
|
+
|
|
471
|
+
3. **Content comparison before restore**: In `syncFromDb`, before restoring
|
|
472
|
+
an incoming tree, FsAgent compares its file content map against the current
|
|
473
|
+
filesystem. If they are equivalent, the restore is skipped entirely,
|
|
474
|
+
preventing the filesystem watcher from firing.
|
|
475
|
+
|
|
476
|
+
Additionally, all sync callbacks are **debounced** (default 300ms) to coalesce
|
|
477
|
+
rapid filesystem events (e.g. multi-file saves, editor autosave) into a single
|
|
478
|
+
sync operation.
|
|
479
|
+
|
|
480
|
+
## Known Constraints
|
|
481
|
+
|
|
482
|
+
### macOS Finder Paste and Rename
|
|
483
|
+
|
|
484
|
+
Bidirectional sync relies on Node.js `fs.watch` (FSEvents on macOS). This works
|
|
485
|
+
reliably for programmatic file operations (Node.js `writeFile`/`copyFile`,
|
|
486
|
+
terminal commands, editor saves) but **not** for macOS Finder's paste-and-rename
|
|
487
|
+
workflow.
|
|
488
|
+
|
|
489
|
+
Finder generates rapid, non-atomic, multi-step event sequences (create temp →
|
|
490
|
+
write → rename → delete temp) that FSEvents may coalesce, reorder, or split
|
|
491
|
+
unpredictably. This causes intermediate filesystem states to be scanned and
|
|
492
|
+
broadcast before the operation completes, leading to sync conflicts.
|
|
493
|
+
|
|
494
|
+
**This is a known limitation and is not supported.** Use programmatic operations
|
|
495
|
+
or terminal commands instead of Finder drag-and-drop or paste-and-rename for
|
|
496
|
+
files in synced folders.
|
package/README.contributors.md
CHANGED
|
@@ -13,6 +13,10 @@ found in the LICENSE file in the root of this package.
|
|
|
13
13
|
- [Administrate](#administrate)
|
|
14
14
|
- [Fast Coding](#fast-coding)
|
|
15
15
|
|
|
16
|
+
## ⚠️ Important: ESLint Version
|
|
17
|
+
|
|
18
|
+
**Do NOT update ESLint to v10.x** - The package is pinned to `eslint ~9.39.2` because `eslint-plugin-tsdoc@0.5.0` is incompatible with ESLint v10's API changes (specifically `context.getSourceCode()` was removed). Updating to v10 will break the build.
|
|
19
|
+
|
|
16
20
|
## Prepare
|
|
17
21
|
|
|
18
22
|
Read [prepare.md](doc/prepare.md)
|
package/README.public.md
CHANGED
|
@@ -21,9 +21,11 @@ found in the LICENSE file in the root of this package.
|
|
|
21
21
|
- 💾 **Smart Storage**: Content-addressed blob storage eliminates duplicate file content
|
|
22
22
|
- 📜 **Version History**: Every change is tracked with complete insert history
|
|
23
23
|
- 🔁 **Bidirectional Sync**: Changes in either direction are automatically propagated
|
|
24
|
-
- 🛡️ **Loop Prevention**:
|
|
24
|
+
- 🛡️ **Loop Prevention**: Content-based dedup, ref tracking, and debounce prevent infinite sync loops
|
|
25
|
+
- ⏱️ **Timeout Guards**: Every async operation is bounded to prevent silent hangs
|
|
26
|
+
- 🎯 **Debounce**: Rapid filesystem events are coalesced into a single sync cycle
|
|
25
27
|
- ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
26
|
-
- 🧪 **Battle-Tested**: 100% test coverage with
|
|
28
|
+
- 🧪 **Battle-Tested**: 100% test coverage with 204 tests (SocketMock + Socket.IO)
|
|
27
29
|
- 🧹 **Target Cleanup**: Optional `cleanTarget` restore prunes stale files and directories
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
@@ -700,6 +702,36 @@ try {
|
|
|
700
702
|
}
|
|
701
703
|
```
|
|
702
704
|
|
|
705
|
+
## Timeout & Debounce Configuration
|
|
706
|
+
|
|
707
|
+
Every async operation in FsAgent is guarded by a timeout to prevent silent hangs.
|
|
708
|
+
Rapid filesystem events are debounced to coalesce into a single sync cycle.
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
const agent = new FsAgent('./my-project', bs, {
|
|
712
|
+
timeouts: {
|
|
713
|
+
dbQuery: 10_000, // Single db.get() query (default: 10s)
|
|
714
|
+
fetchTree: 20_000, // Fetching an entire tree from the DB (default: 20s)
|
|
715
|
+
extract: 15_000, // Filesystem extract/scan (default: 15s)
|
|
716
|
+
restore: 15_000, // Filesystem restore (default: 15s)
|
|
717
|
+
syncCallback: 25_000, // Overall syncFromDb callback (default: 25s)
|
|
718
|
+
debounceMs: 300, // Coalesce rapid FS events (default: 300ms)
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
## Bounce-Back Prevention
|
|
724
|
+
|
|
725
|
+
Bidirectional sync can cause infinite loops when both clients detect each
|
|
726
|
+
other's restores as changes. FsAgent prevents this with three layers:
|
|
727
|
+
|
|
728
|
+
1. **Ref tracking** (`_lastSentRef`): Skips re-broadcast if the stored tree
|
|
729
|
+
produces the same ref as the last one we sent.
|
|
730
|
+
2. **Content-key dedup** (`_lastSentContentKey`): Compares file paths + blobIds
|
|
731
|
+
(ignoring mtimes) to detect identical content with different tree hashes.
|
|
732
|
+
3. **Content comparison before restore**: `syncFromDb` compares the incoming
|
|
733
|
+
tree's content with the current filesystem and skips restore if identical.
|
|
734
|
+
|
|
703
735
|
## Performance
|
|
704
736
|
|
|
705
737
|
- **Efficient Scanning**: Only scans changed directories
|
|
@@ -707,6 +739,8 @@ try {
|
|
|
707
739
|
- **Content-Addressed**: Fast lookups via hashes
|
|
708
740
|
- **Optimized Watching**: Uses native filesystem events
|
|
709
741
|
- **Smart Sync**: Only syncs when changes detected
|
|
742
|
+
- **Debounced Events**: Rapid changes coalesced into single operations
|
|
743
|
+
- **Bounded Operations**: All async operations time out to prevent hangs
|
|
710
744
|
|
|
711
745
|
## Troubleshooting
|
|
712
746
|
|
|
@@ -763,6 +797,29 @@ ignore: ['*'];
|
|
|
763
797
|
ignore: ['node_modules', '*.log'];
|
|
764
798
|
```
|
|
765
799
|
|
|
800
|
+
## Known Constraints
|
|
801
|
+
|
|
802
|
+
### macOS Finder paste and rename
|
|
803
|
+
|
|
804
|
+
Bidirectional sync relies on Node.js `fs.watch` (FSEvents on macOS) to detect
|
|
805
|
+
filesystem changes. This works reliably for **programmatic** file operations:
|
|
806
|
+
|
|
807
|
+
- ✅ `writeFile`, `copyFile`, `rename` from Node.js or shell commands
|
|
808
|
+
- ✅ Editor saves (VS Code, Vim, etc.)
|
|
809
|
+
- ✅ Terminal commands (`echo`, `cp`, `mv`, `touch`, etc.)
|
|
810
|
+
- ✅ Application-generated file changes
|
|
811
|
+
|
|
812
|
+
However, **macOS Finder's paste-and-rename workflow** generates rapid,
|
|
813
|
+
non-atomic, multi-step event sequences (create temporary file → write → rename →
|
|
814
|
+
delete temporary) that FSEvents may coalesce, reorder, or split unpredictably.
|
|
815
|
+
This can cause intermediate states to be scanned and broadcast before the
|
|
816
|
+
operation completes, leading to sync conflicts.
|
|
817
|
+
|
|
818
|
+
**This is a known limitation of filesystem watching on macOS and is not
|
|
819
|
+
supported.** If you need to add or rename files in synced folders, use
|
|
820
|
+
programmatic operations or terminal commands instead of Finder drag-and-drop or
|
|
821
|
+
paste-and-rename.
|
|
822
|
+
|
|
766
823
|
## Related Packages
|
|
767
824
|
|
|
768
825
|
- `@rljson/db` - RLJSON database
|
|
@@ -447,3 +447,50 @@ This peer-to-peer pattern with server coordination enables:
|
|
|
447
447
|
✅ **Real-world deployment**: Works across networks, not just in-memory mocks
|
|
448
448
|
|
|
449
449
|
**This is why clients must NEVER access server Io/Bs directly** - it would bypass the entire peer-to-peer mechanism and make the system only work in single-process scenarios.
|
|
450
|
+
|
|
451
|
+
## Bounce-Back Prevention
|
|
452
|
+
|
|
453
|
+
Bidirectional sync creates a potential infinite loop:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
Client A writes file → syncToDb stores tree → broadcasts ref →
|
|
457
|
+
Client B receives ref → syncFromDb restores file → fs watcher fires →
|
|
458
|
+
syncToDb stores tree → broadcasts ref → Client A receives ref → ...
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
FsAgent uses three layers of deduplication to break the loop:
|
|
462
|
+
|
|
463
|
+
1. **Ref-level dedup**: After `syncToDb` stores a tree, it compares the
|
|
464
|
+
resulting ref against `_lastSentRef`. If identical, no broadcast occurs.
|
|
465
|
+
|
|
466
|
+
2. **Content-key dedup**: Even when refs differ (e.g. different mtimes produce
|
|
467
|
+
different hashes), `syncToDb` computes a content key from file paths +
|
|
468
|
+
blobIds. If the content key matches `_lastSentContentKey`, the broadcast
|
|
469
|
+
is skipped.
|
|
470
|
+
|
|
471
|
+
3. **Content comparison before restore**: In `syncFromDb`, before restoring
|
|
472
|
+
an incoming tree, FsAgent compares its file content map against the current
|
|
473
|
+
filesystem. If they are equivalent, the restore is skipped entirely,
|
|
474
|
+
preventing the filesystem watcher from firing.
|
|
475
|
+
|
|
476
|
+
Additionally, all sync callbacks are **debounced** (default 300ms) to coalesce
|
|
477
|
+
rapid filesystem events (e.g. multi-file saves, editor autosave) into a single
|
|
478
|
+
sync operation.
|
|
479
|
+
|
|
480
|
+
## Known Constraints
|
|
481
|
+
|
|
482
|
+
### macOS Finder Paste and Rename
|
|
483
|
+
|
|
484
|
+
Bidirectional sync relies on Node.js `fs.watch` (FSEvents on macOS). This works
|
|
485
|
+
reliably for programmatic file operations (Node.js `writeFile`/`copyFile`,
|
|
486
|
+
terminal commands, editor saves) but **not** for macOS Finder's paste-and-rename
|
|
487
|
+
workflow.
|
|
488
|
+
|
|
489
|
+
Finder generates rapid, non-atomic, multi-step event sequences (create temp →
|
|
490
|
+
write → rename → delete temp) that FSEvents may coalesce, reorder, or split
|
|
491
|
+
unpredictably. This causes intermediate filesystem states to be scanned and
|
|
492
|
+
broadcast before the operation completes, leading to sync conflicts.
|
|
493
|
+
|
|
494
|
+
**This is a known limitation and is not supported.** Use programmatic operations
|
|
495
|
+
or terminal commands instead of Finder drag-and-drop or paste-and-rename for
|
|
496
|
+
files in synced folders.
|
|
@@ -13,6 +13,10 @@ found in the LICENSE file in the root of this package.
|
|
|
13
13
|
- [Administrate](#administrate)
|
|
14
14
|
- [Fast Coding](#fast-coding)
|
|
15
15
|
|
|
16
|
+
## ⚠️ Important: ESLint Version
|
|
17
|
+
|
|
18
|
+
**Do NOT update ESLint to v10.x** - The package is pinned to `eslint ~9.39.2` because `eslint-plugin-tsdoc@0.5.0` is incompatible with ESLint v10's API changes (specifically `context.getSourceCode()` was removed). Updating to v10 will break the build.
|
|
19
|
+
|
|
16
20
|
## Prepare
|
|
17
21
|
|
|
18
22
|
Read [prepare.md](doc/prepare.md)
|
package/dist/README.public.md
CHANGED
|
@@ -21,9 +21,11 @@ found in the LICENSE file in the root of this package.
|
|
|
21
21
|
- 💾 **Smart Storage**: Content-addressed blob storage eliminates duplicate file content
|
|
22
22
|
- 📜 **Version History**: Every change is tracked with complete insert history
|
|
23
23
|
- 🔁 **Bidirectional Sync**: Changes in either direction are automatically propagated
|
|
24
|
-
- 🛡️ **Loop Prevention**:
|
|
24
|
+
- 🛡️ **Loop Prevention**: Content-based dedup, ref tracking, and debounce prevent infinite sync loops
|
|
25
|
+
- ⏱️ **Timeout Guards**: Every async operation is bounded to prevent silent hangs
|
|
26
|
+
- 🎯 **Debounce**: Rapid filesystem events are coalesced into a single sync cycle
|
|
25
27
|
- ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
26
|
-
- 🧪 **Battle-Tested**: 100% test coverage with
|
|
28
|
+
- 🧪 **Battle-Tested**: 100% test coverage with 204 tests (SocketMock + Socket.IO)
|
|
27
29
|
- 🧹 **Target Cleanup**: Optional `cleanTarget` restore prunes stale files and directories
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
@@ -700,6 +702,36 @@ try {
|
|
|
700
702
|
}
|
|
701
703
|
```
|
|
702
704
|
|
|
705
|
+
## Timeout & Debounce Configuration
|
|
706
|
+
|
|
707
|
+
Every async operation in FsAgent is guarded by a timeout to prevent silent hangs.
|
|
708
|
+
Rapid filesystem events are debounced to coalesce into a single sync cycle.
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
const agent = new FsAgent('./my-project', bs, {
|
|
712
|
+
timeouts: {
|
|
713
|
+
dbQuery: 10_000, // Single db.get() query (default: 10s)
|
|
714
|
+
fetchTree: 20_000, // Fetching an entire tree from the DB (default: 20s)
|
|
715
|
+
extract: 15_000, // Filesystem extract/scan (default: 15s)
|
|
716
|
+
restore: 15_000, // Filesystem restore (default: 15s)
|
|
717
|
+
syncCallback: 25_000, // Overall syncFromDb callback (default: 25s)
|
|
718
|
+
debounceMs: 300, // Coalesce rapid FS events (default: 300ms)
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
## Bounce-Back Prevention
|
|
724
|
+
|
|
725
|
+
Bidirectional sync can cause infinite loops when both clients detect each
|
|
726
|
+
other's restores as changes. FsAgent prevents this with three layers:
|
|
727
|
+
|
|
728
|
+
1. **Ref tracking** (`_lastSentRef`): Skips re-broadcast if the stored tree
|
|
729
|
+
produces the same ref as the last one we sent.
|
|
730
|
+
2. **Content-key dedup** (`_lastSentContentKey`): Compares file paths + blobIds
|
|
731
|
+
(ignoring mtimes) to detect identical content with different tree hashes.
|
|
732
|
+
3. **Content comparison before restore**: `syncFromDb` compares the incoming
|
|
733
|
+
tree's content with the current filesystem and skips restore if identical.
|
|
734
|
+
|
|
703
735
|
## Performance
|
|
704
736
|
|
|
705
737
|
- **Efficient Scanning**: Only scans changed directories
|
|
@@ -707,6 +739,8 @@ try {
|
|
|
707
739
|
- **Content-Addressed**: Fast lookups via hashes
|
|
708
740
|
- **Optimized Watching**: Uses native filesystem events
|
|
709
741
|
- **Smart Sync**: Only syncs when changes detected
|
|
742
|
+
- **Debounced Events**: Rapid changes coalesced into single operations
|
|
743
|
+
- **Bounded Operations**: All async operations time out to prevent hangs
|
|
710
744
|
|
|
711
745
|
## Troubleshooting
|
|
712
746
|
|
|
@@ -763,6 +797,29 @@ ignore: ['*'];
|
|
|
763
797
|
ignore: ['node_modules', '*.log'];
|
|
764
798
|
```
|
|
765
799
|
|
|
800
|
+
## Known Constraints
|
|
801
|
+
|
|
802
|
+
### macOS Finder paste and rename
|
|
803
|
+
|
|
804
|
+
Bidirectional sync relies on Node.js `fs.watch` (FSEvents on macOS) to detect
|
|
805
|
+
filesystem changes. This works reliably for **programmatic** file operations:
|
|
806
|
+
|
|
807
|
+
- ✅ `writeFile`, `copyFile`, `rename` from Node.js or shell commands
|
|
808
|
+
- ✅ Editor saves (VS Code, Vim, etc.)
|
|
809
|
+
- ✅ Terminal commands (`echo`, `cp`, `mv`, `touch`, etc.)
|
|
810
|
+
- ✅ Application-generated file changes
|
|
811
|
+
|
|
812
|
+
However, **macOS Finder's paste-and-rename workflow** generates rapid,
|
|
813
|
+
non-atomic, multi-step event sequences (create temporary file → write → rename →
|
|
814
|
+
delete temporary) that FSEvents may coalesce, reorder, or split unpredictably.
|
|
815
|
+
This can cause intermediate states to be scanned and broadcast before the
|
|
816
|
+
operation completes, leading to sync conflicts.
|
|
817
|
+
|
|
818
|
+
**This is a known limitation of filesystem watching on macOS and is not
|
|
819
|
+
supported.** If you need to add or rename files in synced folders, use
|
|
820
|
+
programmatic operations or terminal commands instead of Finder drag-and-drop or
|
|
821
|
+
paste-and-rename.
|
|
822
|
+
|
|
766
823
|
## Related Packages
|
|
767
824
|
|
|
768
825
|
- `@rljson/db` - RLJSON database
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/fs-agent.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Bs } from '@rljson/bs';
|
|
2
|
+
import { ClientId, SyncConfig } from '@rljson/rljson';
|
|
2
3
|
import { FsBlobAdapter } from './fs-blob-adapter.ts';
|
|
3
4
|
import { StoreFsTreeOptions } from './fs-db-adapter.ts';
|
|
4
5
|
import { FsScanner, FsTree } from './fs-scanner.ts';
|
|
@@ -23,12 +24,56 @@ export interface FsAgentOptions {
|
|
|
23
24
|
bidirectional?: boolean;
|
|
24
25
|
/** Restore options applied when syncing from DB */
|
|
25
26
|
restoreOptions?: RestoreOptions;
|
|
27
|
+
/** Timeout configuration for async operations */
|
|
28
|
+
timeouts?: TimeoutConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Centralized sync protocol configuration.
|
|
31
|
+
* When provided, this SyncConfig is forwarded to every Connector
|
|
32
|
+
* created by {@link FsAgent.fromClient}, and governs whether
|
|
33
|
+
* `sendWithAck()` (when `requireAck` is true) or `send()` is used
|
|
34
|
+
* in {@link FsAgent.syncToDb}.
|
|
35
|
+
*
|
|
36
|
+
* The same SyncConfig should also be passed to the Server and Client
|
|
37
|
+
* constructors so that every layer uses the same protocol settings.
|
|
38
|
+
*/
|
|
39
|
+
syncConfig?: SyncConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Stable client identity. When provided, it is forwarded to every
|
|
42
|
+
* Connector created by {@link FsAgent.fromClient}. When omitted but
|
|
43
|
+
* `syncConfig.includeClientIdentity` is true, each Connector
|
|
44
|
+
* auto-generates its own identity.
|
|
45
|
+
*/
|
|
46
|
+
clientIdentity?: ClientId;
|
|
26
47
|
}
|
|
27
48
|
/** Restore options */
|
|
28
49
|
export interface RestoreOptions {
|
|
29
50
|
/** Remove files/dirs on target that are not present in the tree */
|
|
30
51
|
cleanTarget?: boolean;
|
|
31
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Timeout configuration for async operations (milliseconds).
|
|
55
|
+
* Every async operation in FsAgent is guarded by a timeout to prevent
|
|
56
|
+
* silent hangs in socket communication, filesystem I/O, or database queries.
|
|
57
|
+
*/
|
|
58
|
+
export interface TimeoutConfig {
|
|
59
|
+
/** Timeout for a single db.get() query. Default: 10 000 ms */
|
|
60
|
+
dbQuery?: number;
|
|
61
|
+
/** Timeout for fetching an entire tree from the DB. Default: 20 000 ms */
|
|
62
|
+
fetchTree?: number;
|
|
63
|
+
/** Timeout for a filesystem extract / scan. Default: 15 000 ms */
|
|
64
|
+
extract?: number;
|
|
65
|
+
/** Timeout for a filesystem restore. Default: 15 000 ms */
|
|
66
|
+
restore?: number;
|
|
67
|
+
/** Timeout for the overall syncFromDb callback. Default: 25 000 ms */
|
|
68
|
+
syncCallback?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Debounce delay for sync callbacks (milliseconds). Default: 300 ms.
|
|
71
|
+
* Rapid filesystem events (e.g. macOS Finder "Keep Both" copy+rename)
|
|
72
|
+
* are coalesced into a single sync operation after this quiet period.
|
|
73
|
+
* Also applies to incoming database refs in syncFromDb.
|
|
74
|
+
*/
|
|
75
|
+
debounceMs?: number;
|
|
76
|
+
}
|
|
32
77
|
/**
|
|
33
78
|
* Orchestrates filesystem operations with tree structures and blob storage
|
|
34
79
|
*/
|
|
@@ -42,6 +87,9 @@ export declare class FsAgent {
|
|
|
42
87
|
private _stopSync?;
|
|
43
88
|
private _stopSyncFromDb?;
|
|
44
89
|
private _lastSentRef?;
|
|
90
|
+
/** Content fingerprint of the last tree we broadcasted (paths+blobIds) */
|
|
91
|
+
private _lastSentContentKey?;
|
|
92
|
+
private _timeouts;
|
|
45
93
|
constructor(rootPath: string, bs?: Bs, options?: FsAgentOptions);
|
|
46
94
|
/**
|
|
47
95
|
* Gets the root path
|
|
@@ -59,6 +107,27 @@ export declare class FsAgent {
|
|
|
59
107
|
* Gets the adapter instance
|
|
60
108
|
*/
|
|
61
109
|
get adapter(): FsBlobAdapter;
|
|
110
|
+
/**
|
|
111
|
+
* Gets the current timeout configuration
|
|
112
|
+
*/
|
|
113
|
+
get timeouts(): Required<TimeoutConfig>;
|
|
114
|
+
/**
|
|
115
|
+
* Wraps a promise with a timeout.
|
|
116
|
+
* Rejects with a descriptive error if the promise does not settle
|
|
117
|
+
* within the given number of milliseconds.
|
|
118
|
+
* @param promise - The promise to guard
|
|
119
|
+
* @param ms - Maximum allowed time in milliseconds
|
|
120
|
+
* @param label - Human-readable label included in the error message
|
|
121
|
+
*/
|
|
122
|
+
private static _withTimeout;
|
|
123
|
+
/**
|
|
124
|
+
* Sends a ref through the connector.
|
|
125
|
+
* Uses `sendWithAck()` when the connector has `requireAck` enabled,
|
|
126
|
+
* otherwise falls back to fire-and-forget `send()`.
|
|
127
|
+
* @param connector - The Connector to send through
|
|
128
|
+
* @param ref - The ref to broadcast
|
|
129
|
+
*/
|
|
130
|
+
private _sendRef;
|
|
62
131
|
/**
|
|
63
132
|
* Starts automatic syncing to database
|
|
64
133
|
* Note: Auto-sync requires Connector which is not available in constructor.
|
|
@@ -130,6 +199,15 @@ export declare class FsAgent {
|
|
|
130
199
|
* @returns Array of all tree nodes in the tree
|
|
131
200
|
*/
|
|
132
201
|
private _fetchTreeRecursively;
|
|
202
|
+
/**
|
|
203
|
+
* Fetches tree from database without restoring to filesystem.
|
|
204
|
+
* Separated from loadFromDb to allow content comparison before restore.
|
|
205
|
+
* @param db - Database instance
|
|
206
|
+
* @param treeKey - Tree table key
|
|
207
|
+
* @param rootRef - Root tree reference (hash)
|
|
208
|
+
* @returns FsTree structure ready for restore
|
|
209
|
+
*/
|
|
210
|
+
private _fetchTreeFromDb;
|
|
133
211
|
/**
|
|
134
212
|
* Loads tree from database and restores to filesystem
|
|
135
213
|
* Writes to filesystem from DB trees and Bs blobs
|
|
@@ -159,10 +237,37 @@ export declare class FsAgent {
|
|
|
159
237
|
* @param db - Database instance
|
|
160
238
|
* @param connector - Connector instance for socket-based sync
|
|
161
239
|
* @param treeKey - Tree table key
|
|
162
|
-
* @param options - Storage options (e.g.,
|
|
240
|
+
* @param options - Storage options (e.g., skipNotification)
|
|
163
241
|
* @returns Function to stop watching
|
|
164
242
|
*/
|
|
165
243
|
syncToDb(db: Db, connector: Connector, treeKey: string, options?: StoreFsTreeOptions): Promise<() => void>;
|
|
244
|
+
/**
|
|
245
|
+
* Builds a map of relativePath → blobId for all files in a tree.
|
|
246
|
+
* Used to compare trees by content rather than by hash (which includes mtime).
|
|
247
|
+
* @param tree - Tree structure to extract file content map from
|
|
248
|
+
*/
|
|
249
|
+
private _getFileContentMap;
|
|
250
|
+
/**
|
|
251
|
+
* Derives a deterministic string key from a content map so that two trees
|
|
252
|
+
* with identical file paths + blobIds produce the same key regardless of
|
|
253
|
+
* mtime differences.
|
|
254
|
+
* @param map - Content map (relativePath → blobId)
|
|
255
|
+
*/
|
|
256
|
+
private _contentKeyFromMap;
|
|
257
|
+
/**
|
|
258
|
+
* Derives a deterministic content key from an FsTree.
|
|
259
|
+
* @param tree - Tree structure to derive content key from
|
|
260
|
+
*/
|
|
261
|
+
private _contentKeyFromTree;
|
|
262
|
+
/**
|
|
263
|
+
* Compares two trees by file content (relativePath + blobId).
|
|
264
|
+
* Ignores mtime differences — trees are equivalent if they have the same
|
|
265
|
+
* files with the same content. This prevents bounce-back restores from
|
|
266
|
+
* destroying locally-created files during bidirectional sync.
|
|
267
|
+
* @param a - First tree to compare
|
|
268
|
+
* @param b - Second tree to compare
|
|
269
|
+
*/
|
|
270
|
+
private _treesHaveEquivalentContent;
|
|
166
271
|
/**
|
|
167
272
|
* Watches database for tree changes and syncs to filesystem
|
|
168
273
|
* Uses Connector for socket-based notifications
|
|
@@ -173,6 +278,37 @@ export declare class FsAgent {
|
|
|
173
278
|
* @returns Function to stop watching
|
|
174
279
|
*/
|
|
175
280
|
syncFromDb(db: Db, connector: Connector, treeKey: string, restoreOptions?: RestoreOptions): Promise<() => void>;
|
|
281
|
+
/**
|
|
282
|
+
* Creates a fully configured FsAgent from a Client instance.
|
|
283
|
+
* This factory method provides a simplified API where sync methods don't require
|
|
284
|
+
* db, connector, and treeKey parameters - they are stored internally.
|
|
285
|
+
* @param filePath - Directory path to sync
|
|
286
|
+
* @param treeKey - Tree table key (route will be `/${treeKey}`)
|
|
287
|
+
* @param client - Client instance with io and bs properties
|
|
288
|
+
* @param socket - Socket instance for connector communication
|
|
289
|
+
* @param options - Optional FsAgent options (db and treeKey are set automatically).
|
|
290
|
+
* `syncConfig` and `clientIdentity` from these options are forwarded to
|
|
291
|
+
* the Connector so that a single config origin governs all layers.
|
|
292
|
+
* @returns Configured FsAgent instance with simplified sync API
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* const syncConfig: SyncConfig = { requireAck: true, maxDedupSetSize: 5000 };
|
|
296
|
+
* const agent = await FsAgent.fromClient(
|
|
297
|
+
* './my-folder', 'sharedTree', client, socket, { syncConfig },
|
|
298
|
+
* );
|
|
299
|
+
* // Simplified sync methods - no db/connector/treeKey needed
|
|
300
|
+
* await agent.syncToDbSimple();
|
|
301
|
+
* await agent.syncFromDbSimple({ cleanTarget: true });
|
|
302
|
+
* // Original methods still work
|
|
303
|
+
* await agent.syncToDb(db, connector, treeKey);
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
static fromClient(filePath: string, treeKey: string, client: any, // Client type from \@rljson/server
|
|
307
|
+
socket: any, // Socket type from \@rljson/io
|
|
308
|
+
options?: Omit<FsAgentOptions, 'db' | 'treeKey'>): Promise<FsAgent & {
|
|
309
|
+
syncToDbSimple: (options?: StoreFsTreeOptions) => Promise<() => void>;
|
|
310
|
+
syncFromDbSimple: (options?: RestoreOptions) => Promise<() => void>;
|
|
311
|
+
}>;
|
|
176
312
|
/** Example instance for test purposes */
|
|
177
313
|
static get example(): FsAgent;
|
|
178
314
|
}
|