@rljson/fs-agent 0.0.2

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.
@@ -0,0 +1,781 @@
1
+ <!--
2
+ @license
3
+ Copyright (c) 2025 Rljson
4
+
5
+ Use of this source code is governed by terms that can be
6
+ found in the LICENSE file in the root of this package.
7
+ -->
8
+
9
+ # @rljson/fs-agent
10
+
11
+ > **A powerful filesystem agent that seamlessly synchronizes your file system with RLJSON databases, providing automatic bidirectional sync, tree structures, and content-addressed blob storage.**
12
+
13
+ ## Overview
14
+
15
+ `@rljson/fs-agent` bridges the gap between your filesystem and RLJSON databases. It watches for changes, extracts hierarchical tree structures, stores file content efficiently in blob storage with automatic deduplication, and maintains complete version history. With built-in bidirectional synchronization, your filesystem and database stay perfectly in sync—automatically.
16
+
17
+ ### Why Use fs-agent?
18
+
19
+ - 🔄 **Automatic Synchronization**: Changes flow seamlessly between filesystem and database
20
+ - 🌳 **Tree Structures**: Represents directories as RLJSON tree structures with parent-child relationships
21
+ - 💾 **Smart Storage**: Content-addressed blob storage eliminates duplicate file content
22
+ - 📜 **Version History**: Every change is tracked with complete insert history
23
+ - 🔁 **Bidirectional Sync**: Changes in either direction are automatically propagated
24
+ - 🛡️ **Loop Prevention**: Intelligent pause/resume mechanism prevents infinite sync loops
25
+ - ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions
26
+ - 🧪 **Battle-Tested**: 100% test coverage with 139 tests
27
+ - 🧹 **Target Cleanup**: Optional `cleanTarget` restore prunes stale files and directories
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @rljson/fs-agent
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Basic Usage - Automatic Sync
38
+
39
+ The simplest way to get started is with automatic synchronization:
40
+
41
+ ```typescript
42
+ import { FsAgent } from '@rljson/fs-agent';
43
+ import { Connector, Db } from '@rljson/db';
44
+ import { IoMem, SocketMock } from '@rljson/io';
45
+ import { BsMem } from '@rljson/bs';
46
+ import { Route, createTreesTableCfg } from '@rljson/rljson';
47
+
48
+ // Setup database
49
+ const io = new IoMem();
50
+ await io.init();
51
+ const db = new Db(io);
52
+
53
+ // Create tree table (must end with "Tree")
54
+ const treeKey = 'projectFilesTree';
55
+ const treeTableCfg = createTreesTableCfg(treeKey);
56
+ await db.core.createTableWithInsertHistory(treeTableCfg);
57
+
58
+ // Create FsAgent
59
+ const agent = new FsAgent('./my-project', new BsMem(), {
60
+ ignore: ['node_modules', '.git', 'dist'],
61
+ maxDepth: 10,
62
+ });
63
+
64
+ // Create Connector for socket-based synchronization
65
+ const socket = new SocketMock();
66
+ const route = Route.fromFlat(`/${treeKey}`);
67
+ const connector = new Connector(db, route, socket);
68
+
69
+ // Start syncing
70
+ const stopSync = await agent.syncToDb(db, connector, treeKey);
71
+
72
+ // The agent now:
73
+ // ✓ Watches your filesystem for changes
74
+ // ✓ Extracts tree structures from directories
75
+ // ✓ Stores file content in blob storage
76
+ // ✓ Broadcasts changes via Connector
77
+
78
+ // When you're done:
79
+ stopSync();
80
+ agent.dispose();
81
+ ```
82
+
83
+ ### Bidirectional Sync
84
+
85
+ Enable two-way synchronization so changes in the database also update the filesystem:
86
+
87
+ ```typescript
88
+ import { Connector } from '@rljson/db';
89
+ import { Route } from '@rljson/rljson';
90
+ import { SocketMock } from '@rljson/io';
91
+
92
+ const agent = new FsAgent('./my-project', new BsMem(), {
93
+ ignore: ['node_modules', '.git', 'dist'],
94
+ });
95
+
96
+ // Create Connector for bidirectional communication
97
+ const socket = new SocketMock();
98
+ const route = Route.fromFlat(`/${treeKey}`);
99
+ const connector = new Connector(db, route, socket);
100
+
101
+ // Start filesystem → database sync
102
+ const stopToDb = await agent.syncToDb(db, connector, treeKey);
103
+
104
+ // Start database → filesystem sync (same Connector)
105
+ const stopFromDb = await agent.syncFromDb(db, connector, treeKey);
106
+
107
+ // Now the agent handles changes in BOTH directions:
108
+ // 1. Filesystem changes → automatically synced to database
109
+ // 2. Database changes → automatically synced to filesystem
110
+ // 3. Loop prevention ensures no infinite sync cycles
111
+ // 4. Both sides stay perfectly synchronized
112
+
113
+ // Stop syncing
114
+ stopToDb();
115
+ stopFromDb();
116
+ ```
117
+
118
+ ## Core Concepts
119
+
120
+ ### Tree Structures
121
+
122
+ `fs-agent` represents your filesystem as RLJSON tree structures:
123
+
124
+ ```typescript
125
+ // Directory hierarchy:
126
+ // my-project/
127
+ // ├── src/
128
+ // │ ├── index.ts
129
+ // │ └── utils.ts
130
+ // └── package.json
131
+
132
+ // Becomes a tree structure where:
133
+ // - Each file/directory is a tree node
134
+ // - Nodes are content-addressed (identified by hash)
135
+ // - Parent-child relationships are preserved
136
+ // - Changes are tracked via insert history
137
+ ```
138
+
139
+ ### Blob Storage
140
+
141
+ Files are stored efficiently using content-addressed blob storage:
142
+
143
+ - **Deduplication**: Identical files are stored only once
144
+ - **Content-Addressed**: Files are identified by their content hash
145
+ - **Efficient**: Only changed files are re-stored
146
+ - **Flexible**: Works with any `Bs` (Blob Storage) implementation
147
+
148
+ ### Automatic Watching
149
+
150
+ ### Automatic Synchronization (Constructor-based)
151
+
152
+ **Note:** Constructor-based automatic synchronization using `db`, `treeKey`, and `bidirectional` options is deprecated and will throw an error. Use the explicit `syncToDb()` and `syncFromDb()` methods with a `Connector` instance instead (see examples above).
153
+
154
+ The new approach provides better control and uses the Connector pattern for socket-based synchronization:
155
+
156
+ ```typescript
157
+ // ❌ Deprecated (will throw error)
158
+ const agent = new FsAgent('./my-project', new BsMem(), {
159
+ db,
160
+ treeKey,
161
+ bidirectional: true,
162
+ });
163
+
164
+ // ✅ Use this instead
165
+ const agent = new FsAgent('./my-project', new BsMem());
166
+ const connector = new Connector(db, route, socket);
167
+ const stopToDb = await agent.syncToDb(db, connector, treeKey);
168
+ const stopFromDb = await agent.syncFromDb(db, connector, treeKey);
169
+ ```
170
+
171
+ ### Live Client-Server Demo
172
+
173
+ Run the live in-process demo that mirrors changes between two folders using the same approach as our sync tests (SocketMock, IoMem, BsMem):
174
+
175
+ ```bash
176
+ pnpm exec vite-node src/live-client-server.ts
177
+ ```
178
+
179
+ It wipes and recreates `demo/live-client-server/folder-a` and `demo/live-client-server/folder-b`, seeds sample files, and keeps them in sync until you press Ctrl+C. Pass `--keep-existing` to skip the reset.
180
+
181
+ ## API Reference
182
+
183
+ ### FsAgent
184
+
185
+ Main class that orchestrates filesystem operations.
186
+
187
+ #### Constructor
188
+
189
+ ```typescript
190
+ new FsAgent(rootPath: string, bs?: Bs, options?: FsAgentOptions)
191
+ ```
192
+
193
+ **Parameters:**
194
+
195
+ - `rootPath` - Root directory to monitor
196
+ - `bs` - Blob storage instance (defaults to `BsMem`)
197
+ - `options` - Configuration options
198
+
199
+ **Options:**
200
+
201
+ ```typescript
202
+ interface FsAgentOptions {
203
+ // Scanning options
204
+ ignore?: string[]; // Patterns to ignore (e.g., ['node_modules', '*.log'])
205
+ maxDepth?: number; // Maximum directory depth to scan
206
+ followSymlinks?: boolean; // Whether to follow symbolic links (default: false)
207
+
208
+ // Deprecated options (will throw error if used)
209
+ // Use syncToDb()/syncFromDb() methods with Connector instead
210
+ db?: Db; // DEPRECATED
211
+ treeKey?: string; // DEPRECATED
212
+ bidirectional?: boolean; // DEPRECATED
213
+ storageOptions?: StoreFsTreeOptions; // DEPRECATED
214
+ restoreOptions?: RestoreOptions; // DEPRECATED
215
+ }
216
+ maxDepth?: number; // Maximum directory depth (default: 10)
217
+ followSymlinks?: boolean; // Follow symbolic links (default: false)
218
+
219
+ // Automatic sync options
220
+ db?: Db; // Database instance for auto-sync
221
+ treeKey?: string; // Tree table key for storage
222
+ storageOptions?: {
223
+ // Options for database storage
224
+ includeBlobs?: boolean; // Include blob data in tree (default: true)
225
+ };
226
+
227
+ // Bidirectional sync
228
+ bidirectional?: boolean; // Enable database → filesystem sync (default: false)
229
+
230
+ // Restore options applied when syncing from DB (e.g., cleanTarget)
231
+ restoreOptions?: RestoreOptions;
232
+ }
233
+ ```
234
+
235
+ #### Properties
236
+
237
+ ```typescript
238
+ agent.rootPath: string // Root directory path
239
+ agent.bs: Bs // Blob storage instance
240
+ agent.scanner: FsScanner // File scanner instance
241
+ agent.adapter: FsBlobAdapter // Blob adapter instance
242
+ ```
243
+
244
+ #### Methods
245
+
246
+ ##### `extract(): Promise<FsTree>`
247
+
248
+ Extracts the current filesystem as a tree structure.
249
+
250
+ ```typescript
251
+ const tree = await agent.extract();
252
+ // Returns: { rootHash: string, trees: Map<string, Tree> }
253
+ ```
254
+
255
+ ##### `restore(tree: FsTree, targetPath?: string, options?: RestoreOptions): Promise<void>`
256
+
257
+ Restores a tree structure to the filesystem. Use `options.cleanTarget` to remove files and directories that are not part of the tree (helpful for propagating renames or deletions):
258
+
259
+ ```typescript
260
+ await agent.restore(tree, './restore-location', { cleanTarget: true });
261
+ ```
262
+
263
+ ##### `storeInDb(db: Db, treeKey: string, options?: StoreFsTreeOptions): Promise<void>`
264
+
265
+ Manually stores the current filesystem state in the database.
266
+
267
+ ```typescript
268
+ await agent.storeInDb(db, 'myFilesTree', { includeBlobs: true });
269
+ ```
270
+
271
+ ##### `syncToDb(db: Db, connector: Connector, treeKey: string, options?: StoreFsTreeOptions): Promise<() => void>`
272
+
273
+ Starts watching filesystem and syncing to database using Connector for socket-based broadcasts.
274
+
275
+ ```typescript
276
+ import { Connector } from '@rljson/db';
277
+ import { Route } from '@rljson/rljson';
278
+ import { SocketMock } from '@rljson/io';
279
+
280
+ const socket = new SocketMock();
281
+ const route = Route.fromFlat(`/${treeKey}+`);
282
+ const connector = new Connector(db, route, socket);
283
+
284
+ const stopSync = await agent.syncToDb(db, connector, 'myFilesTree');
285
+ // Later: stopSync();
286
+ ```
287
+
288
+ ##### `syncFromDb(db: Db, connector: Connector, treeKey: string, restoreOptions?: RestoreOptions): Promise<() => void>`
289
+
290
+ Starts listening to database changes via Connector and syncing to filesystem.
291
+
292
+ ```typescript
293
+ import { Connector } from '@rljson/db';
294
+ import { Route } from '@rljson/rljson';
295
+ import { SocketMock } from '@rljson/io';
296
+
297
+ const socket = new SocketMock();
298
+ const route = Route.fromFlat(`/${treeKey}+`);
299
+ const connector = new Connector(db, route, socket);
300
+
301
+ const stopSync = await agent.syncFromDb(db, connector, 'myFilesTree', {
302
+ cleanTarget: true // Optional: remove files not in tree
303
+ });
304
+ // Later: stopSync();
305
+ ```
306
+
307
+ ##### `dispose(): void`
308
+
309
+ Stops all syncing and cleans up resources.
310
+
311
+ ```typescript
312
+ agent.dispose();
313
+ ```
314
+
315
+ ### FsScanner
316
+
317
+ Low-level filesystem scanner (usually accessed via `agent.scanner`).
318
+
319
+ #### Methods
320
+
321
+ ```typescript
322
+ // Scan filesystem once
323
+ const tree = await scanner.scan();
324
+
325
+ // Start watching for changes
326
+ await scanner.watch();
327
+
328
+ // Register change callback
329
+ scanner.onChange(async (change) => {
330
+ console.log(change.type, change.path);
331
+ });
332
+
333
+ // Pause/resume watching (for loop prevention)
334
+ scanner.pauseWatch();
335
+ scanner.resumeWatch();
336
+
337
+ // Get root tree
338
+ const rootTree = scanner.getRootTree();
339
+ ```
340
+
341
+ ## Advanced Usage
342
+
343
+ ### Manual Sync Control
344
+
345
+ If you need fine-grained control over synchronization:
346
+
347
+ ```typescript
348
+ import { Connector } from '@rljson/db';
349
+ import { Route } from '@rljson/rljson';
350
+ import { SocketMock } from '@rljson/io';
351
+
352
+ const agent = new FsAgent('./my-project', new BsMem());
353
+
354
+ // Create Connector for synchronization
355
+ const treeKey = 'myFilesTree';
356
+ const socket = new SocketMock();
357
+ const route = Route.fromFlat(`/${treeKey}`);
358
+ const connector = new Connector(db, route, socket);
359
+
360
+ // Start filesystem → database sync
361
+ const stopToDb = await agent.syncToDb(db, connector, treeKey);
362
+
363
+ // Start database → filesystem sync
364
+ const stopFromDb = await agent.syncFromDb(db, connector, treeKey);
365
+
366
+ // Stop when needed
367
+ stopToDb();
368
+ stopFromDb();
369
+ ```
370
+
371
+ ### Custom Blob Storage
372
+
373
+ Use any blob storage implementation:
374
+
375
+ ```typescript
376
+ import { BsSql } from '@rljson/bs-sql';
377
+
378
+ // SQL-backed blob storage
379
+ const sqlBs = new BsSql(myDatabase);
380
+ const agent = new FsAgent('./my-project', sqlBs, {
381
+ db,
382
+ treeKey: 'filesTree',
383
+ });
384
+ ```
385
+
386
+ ### Ignore Patterns
387
+
388
+ Control what gets scanned and synced:
389
+
390
+ ```typescript
391
+ const agent = new FsAgent('./my-project', new BsMem(), {
392
+ db,
393
+ treeKey: 'filesTree',
394
+ ignore: [
395
+ 'node_modules',
396
+ '.git',
397
+ 'dist',
398
+ 'coverage',
399
+ '*.log',
400
+ '.DS_Store',
401
+ 'tmp/**',
402
+ ],
403
+ });
404
+ ```
405
+
406
+ ### Limited Depth Scanning
407
+
408
+ Control how deep to traverse directories:
409
+
410
+ ```typescript
411
+ const agent = new FsAgent('./my-project', new BsMem(), {
412
+ db,
413
+ treeKey: 'filesTree',
414
+ maxDepth: 3, // Only scan 3 levels deep
415
+ });
416
+ ```
417
+
418
+ ### Extract and Restore
419
+
420
+ Work with tree structures directly:
421
+
422
+ ```typescript
423
+ // Extract current state
424
+ const tree = await agent.extract();
425
+
426
+ // Trees are content-addressed
427
+ console.log('Root hash:', tree.rootHash);
428
+ console.log('Total nodes:', tree.trees.size);
429
+
430
+ // Restore to a different location
431
+ await agent.restore(tree, './backup-location');
432
+
433
+ // Or restore from database
434
+ const dbAdapter = new FsDbAdapter(db, 'myFilesTree');
435
+ const treeFromDb = await dbAdapter.loadFsTree('abc123'); // tree hash
436
+ await agent.restore(treeFromDb, './restore-here');
437
+ ```
438
+
439
+ ### Version History
440
+
441
+ Access historical versions via insert history:
442
+
443
+ ```typescript
444
+ import { Route } from '@rljson/rljson';
445
+
446
+ // Get insert history for the tree
447
+ const route = Route.fromFlat(`${treeKey}/`);
448
+ const historyResult = await db.getInsertHistory(route, {});
449
+
450
+ // historyResult contains all versions with timestamps
451
+ for (const entry of historyResult.history) {
452
+ const treeRef = entry.treeRef;
453
+ const timestamp = entry.insertedAt;
454
+
455
+ // Load and restore specific version
456
+ const tree = await agent.loadFromDb(db, treeKey, treeRef);
457
+ await agent.restore(tree, `./version-${timestamp}`);
458
+ }
459
+ ```
460
+
461
+ ### Change Detection
462
+
463
+ React to specific filesystem changes:
464
+
465
+ ```typescript
466
+ agent.scanner.onChange(async (change) => {
467
+ switch (change.type) {
468
+ case 'add':
469
+ console.log('File added:', change.path);
470
+ break;
471
+ case 'change':
472
+ console.log('File modified:', change.path);
473
+ break;
474
+ case 'unlink':
475
+ console.log('File deleted:', change.path);
476
+ break;
477
+ case 'addDir':
478
+ console.log('Directory created:', change.path);
479
+ break;
480
+ case 'unlinkDir':
481
+ console.log('Directory deleted:', change.path);
482
+ break;
483
+ }
484
+ });
485
+ ```
486
+
487
+ ## Error Handling
488
+
489
+ `fs-agent` provides robust error handling with clear error messages:
490
+
491
+ ```typescript
492
+ try {
493
+ const agent = new FsAgent('./nonexistent', new BsMem(), {
494
+ db,
495
+ treeKey: 'filesTree',
496
+ });
497
+ } catch (error) {
498
+ // Error: Root path "./nonexistent" does not exist. Cannot scan non-existent directory.
499
+ }
500
+ ```
501
+
502
+ ### Common Error Scenarios
503
+
504
+ 1. **Missing Root Path**: Clear error if directory doesn't exist
505
+ 2. **Database Failures**: Errors include context about what operation failed
506
+ 3. **Invalid Tree Data**: Validation errors explain what's wrong
507
+ 4. **Sync Failures**: Non-fatal errors logged, syncing continues
508
+ 5. **Loop Detection**: Automatic prevention via pause/resume
509
+
510
+ ## Examples
511
+
512
+ ### Example 1: Simple Project Sync
513
+
514
+ ```typescript
515
+ import { FsAgent } from '@rljson/fs-agent';
516
+ import { Connector, Db } from '@rljson/db';
517
+ import { IoMem, SocketMock } from '@rljson/io';
518
+ import { BsMem } from '@rljson/bs';
519
+ import { Route, createTreesTableCfg } from '@rljson/rljson';
520
+
521
+ async function syncProject() {
522
+ // Setup
523
+ const io = new IoMem();
524
+ await io.init();
525
+ const db = new Db(io);
526
+
527
+ const treeKey = 'projectTree';
528
+ const treeTableCfg = createTreesTableCfg(treeKey);
529
+ await db.core.createTableWithInsertHistory(treeTableCfg);
530
+
531
+ // Create agent
532
+ const agent = new FsAgent('./src', new BsMem(), {
533
+ ignore: ['*.tmp'],
534
+ });
535
+
536
+ // Create Connector and start syncing
537
+ const socket = new SocketMock();
538
+ const route = Route.fromFlat(`/${treeKey}+`);
539
+ const connector = new Connector(db, route, socket);
540
+ const stopSync = await agent.syncToDb(db, connector, treeKey);
541
+
542
+ // Clean up when done
543
+ process.on('SIGINT', () => {
544
+ stopSync();
545
+ agent.dispose();
546
+ process.exit();
547
+ });
548
+ }
549
+ ```
550
+
551
+ ### Example 2: Backup and Restore
552
+
553
+ ```typescript
554
+ async function backupAndRestore() {
555
+ // Create agent
556
+ const agent = new FsAgent('./my-data', new BsMem());
557
+
558
+ // Extract current state
559
+ const backup = await agent.extract();
560
+ console.log('Backed up', backup.trees.size, 'nodes');
561
+
562
+ // ... later, restore from backup
563
+ await agent.restore(backup, './my-data-restored');
564
+ }
565
+ ```
566
+
567
+ ### Example 2b: Clean Restore (remove stale files)
568
+
569
+ ```typescript
570
+ async function cleanRestore() {
571
+ const agent = new FsAgent('./source', new BsMem());
572
+ const snapshot = await agent.extract();
573
+
574
+ // Restore to target and prune anything not in the snapshot
575
+ await agent.restore(snapshot, './target', { cleanTarget: true });
576
+ }
577
+ ```
578
+
579
+ ### Example 3: Bidirectional Sync
580
+
581
+ ```typescript
582
+ async function bidirectionalSync() {
583
+ // Setup database
584
+ const io = new IoMem();
585
+ await io.init();
586
+ const db = new Db(io);
587
+
588
+ const treeTableCfg = createTreesTableCfg('sharedTree');
589
+ await db.core.createTableWithInsertHistory(treeTableCfg);
590
+
591
+ // Agent 1: Watches ./alice and syncs to DB
592
+ const alice = new FsAgent('./alice', new BsMem(), {
593
+ db,
594
+ treeKey: 'sharedTree',
595
+ bidirectional: true, // ← Bidirectional
596
+ });
597
+
598
+ // Agent 2: Watches ./bob and syncs to DB
599
+ const bob = new FsAgent('./bob', new BsMem(), {
600
+ db,
601
+ treeKey: 'sharedTree',
602
+ bidirectional: true, // ← Bidirectional
603
+ });
604
+
605
+ // Now:
606
+ // - Changes in ./alice → sync to DB → appear in ./bob
607
+ // - Changes in ./bob → sync to DB → appear in ./alice
608
+ // - Loop prevention ensures stability
609
+ }
610
+ ```
611
+
612
+ ### Example 4: Custom Change Handling
613
+
614
+ ```typescript
615
+ async function customHandling() {
616
+ const agent = new FsAgent('./watched', new BsMem(), {
617
+ db,
618
+ treeKey: 'watchedTree',
619
+ });
620
+
621
+ let changeCount = 0;
622
+
623
+ agent.scanner.onChange(async (change) => {
624
+ changeCount++;
625
+
626
+ if (change.type === 'add' && change.path.endsWith('.ts')) {
627
+ console.log(`New TypeScript file: ${change.path}`);
628
+ // Could trigger build, linting, etc.
629
+ }
630
+
631
+ if (changeCount % 10 === 0) {
632
+ console.log(`Processed ${changeCount} changes`);
633
+ }
634
+ });
635
+ }
636
+ ```
637
+
638
+ ## Best Practices
639
+
640
+ ### 1. Use Ignore Patterns
641
+
642
+ Always ignore build artifacts, dependencies, and temporary files:
643
+
644
+ ```typescript
645
+ {
646
+ ignore: [
647
+ 'node_modules',
648
+ '.git',
649
+ 'dist',
650
+ 'build',
651
+ 'coverage',
652
+ '*.log',
653
+ '.DS_Store',
654
+ 'tmp',
655
+ ];
656
+ }
657
+ ```
658
+
659
+ ### 2. Dispose When Done
660
+
661
+ Always clean up resources:
662
+
663
+ ```typescript
664
+ const agent = new FsAgent(path, bs, options);
665
+
666
+ try {
667
+ // Use agent...
668
+ } finally {
669
+ agent.dispose();
670
+ }
671
+ ```
672
+
673
+ ### 3. Use Bidirectional Sync Carefully
674
+
675
+ Bidirectional sync is powerful but consider:
676
+
677
+ - Multiple agents sharing the same `treeKey` will sync to each other
678
+ - Loop prevention is automatic but adds slight latency
679
+ - Best for collaborative scenarios or distributed systems
680
+
681
+ ### 4. Monitor Depth
682
+
683
+ Use `maxDepth` for deep directory structures:
684
+
685
+ ```typescript
686
+ {
687
+ maxDepth: 5, // Prevents extremely deep recursion
688
+ }
689
+ ```
690
+
691
+ ### 5. Handle Errors
692
+
693
+ Wrap agent creation in try-catch for better error handling:
694
+
695
+ ```typescript
696
+ try {
697
+ const agent = new FsAgent(userProvidedPath, bs, options);
698
+ } catch (error) {
699
+ console.error('Failed to create agent:', error.message);
700
+ }
701
+ ```
702
+
703
+ ## Performance
704
+
705
+ - **Efficient Scanning**: Only scans changed directories
706
+ - **Deduplication**: Identical files stored once
707
+ - **Content-Addressed**: Fast lookups via hashes
708
+ - **Optimized Watching**: Uses native filesystem events
709
+ - **Smart Sync**: Only syncs when changes detected
710
+
711
+ ## Troubleshooting
712
+
713
+ ### Agent not syncing changes
714
+
715
+ Make sure you've called `syncToDb()` with a Connector:
716
+
717
+ ```typescript
718
+ import { Connector } from '@rljson/db';
719
+ import { Route } from '@rljson/rljson';
720
+ import { SocketMock } from '@rljson/io';
721
+
722
+ const agent = new FsAgent(path, bs);
723
+
724
+ // Create Connector and start syncing
725
+ const socket = new SocketMock();
726
+ const route = Route.fromFlat(`/${treeKey}+`);
727
+ const connector = new Connector(db, route, socket);
728
+ const stopSync = await agent.syncToDb(db, connector, treeKey);
729
+ ```
730
+
731
+ ### Bidirectional sync not working
732
+
733
+ Ensure you've started both `syncToDb()` and `syncFromDb()` with the same Connector:
734
+
735
+ ```typescript
736
+ const connector = new Connector(db, route, socket);
737
+
738
+ // Start both directions
739
+ const stopToDb = await agent.syncToDb(db, connector, treeKey);
740
+ const stopFromDb = await agent.syncFromDb(db, connector, treeKey);
741
+ ```
742
+
743
+ ### High memory usage
744
+
745
+ Reduce scan depth or add more ignore patterns:
746
+
747
+ ```typescript
748
+ {
749
+ maxDepth: 3,
750
+ ignore: ['large-directory/**'],
751
+ }
752
+ ```
753
+
754
+ ### Files not appearing
755
+
756
+ Check ignore patterns aren't too broad:
757
+
758
+ ```typescript
759
+ // Bad: ignores everything
760
+ ignore: ['*'];
761
+
762
+ // Good: specific patterns
763
+ ignore: ['node_modules', '*.log'];
764
+ ```
765
+
766
+ ## Related Packages
767
+
768
+ - `@rljson/db` - RLJSON database
769
+ - `@rljson/bs` - Blob storage interface
770
+ - `@rljson/rljson` - Core RLJSON library
771
+ - `@rljson/io` - I/O abstractions
772
+
773
+ ## License
774
+
775
+ See [LICENSE](LICENSE) file.
776
+
777
+ ## More Information
778
+
779
+ - [Example Code](src/example.ts)
780
+ - [Architecture](README.architecture.md)
781
+ - [Contributors](README.contributors.md)