s3db.js 9.2.2 → 10.0.0

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/PLUGINS.md DELETED
@@ -1,5036 +0,0 @@
1
- # 🔌 s3db.js Plugins Documentation
2
-
3
- <p align="center">
4
- <strong>Comprehensive guide to all s3db.js plugins</strong><br>
5
- <em>Extend your database with powerful features</em>
6
- </p>
7
-
8
- ---
9
-
10
- ## 📋 Table of Contents
11
-
12
- - [🚀 Getting Started](#-getting-started-with-plugins)
13
- - [🧩 Available Plugins](#-available-plugins)
14
- - [💾 Cache Plugin](#-cache-plugin)
15
- - [💰 Costs Plugin](#-costs-plugin)
16
- - [📝 Audit Plugin](#-audit-plugin)
17
- - [🔍 FullText Plugin](#-fulltext-plugin)
18
- - [📊 Metrics Plugin](#-metrics-plugin)
19
- - [🔄 Replicator Plugin](#-replicator-plugin)
20
- - [📬 Queue Consumer Plugin](#-queue-consumer-plugin)
21
- - [🤖 State Machine Plugin](#-state-machine-plugin)
22
- - [💾 Backup Plugin](#-backup-plugin)
23
- - [⏰ Scheduler Plugin](#-scheduler-plugin)
24
- - [🔧 Plugin Development](#-plugin-development)
25
- - [💡 Plugin Combinations](#-plugin-combinations)
26
- - [🎯 Best Practices](#-best-practices)
27
-
28
- ---
29
-
30
- ## 🚀 Getting Started with Plugins
31
-
32
- Plugins extend s3db.js with additional functionality using a **driver-based architecture**. They can be used individually or combined for powerful workflows.
33
-
34
- ### Plugin Architecture
35
-
36
- Most s3db.js plugins follow a **driver pattern** where you specify:
37
- - **`driver`**: The storage/connection type (`filesystem`, `s3`, `multi`, etc.)
38
- - **`config`**: Driver-specific configuration options
39
- - **Plugin options**: Global settings that apply across drivers
40
-
41
- ### Basic Plugin Usage
42
-
43
- ```javascript
44
- import { S3db, CachePlugin, BackupPlugin, CostsPlugin } from 's3db.js';
45
-
46
- const s3db = new S3db({
47
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
48
- });
49
-
50
- await s3db.connect();
51
-
52
- // Driver-based plugins (most common)
53
- await s3db.usePlugin(new CachePlugin({
54
- driver: 'memory',
55
- config: { maxSize: 1000 }
56
- }));
57
-
58
- await s3db.usePlugin(new BackupPlugin({
59
- driver: 'filesystem',
60
- config: { path: './backups/{date}/' }
61
- }));
62
-
63
- // Static utility plugins
64
- await s3db.usePlugin(CostsPlugin);
65
- ```
66
-
67
- ### Driver-Based Configuration Pattern
68
-
69
- ```javascript
70
- // Single driver example
71
- new SomePlugin({
72
- driver: 'driverType',
73
- config: {
74
- // Driver-specific options
75
- option1: 'value1',
76
- option2: 'value2'
77
- },
78
- // Global plugin options
79
- verbose: true,
80
- timeout: 30000
81
- });
82
-
83
- // Multi-driver example
84
- new SomePlugin({
85
- driver: 'multi',
86
- config: {
87
- strategy: 'all',
88
- destinations: [
89
- { driver: 'driver1', config: {...} },
90
- { driver: 'driver2', config: {...} }
91
- ]
92
- }
93
- });
94
- ```
95
-
96
- ### Plugin Types
97
-
98
- - **Instance Plugins**: Require `new` - `new CachePlugin(config)`
99
- - **Static Plugins**: Used directly - `CostsPlugin`
100
- - **Configurable**: Accept options for customization
101
- - **Event-Driven**: Emit events for monitoring and integration
102
-
103
- ---
104
-
105
- ## 🧩 Available Plugins
106
-
107
- ## 💾 Cache Plugin
108
-
109
- **Driver-Based Caching System** - Intelligent caching that reduces S3 API calls and improves performance using configurable storage drivers.
110
-
111
- > 🏎️ **Performance**: Dramatically reduces S3 costs and latency by caching frequently accessed data.
112
-
113
- ### 🚀 Quick Start
114
-
115
- #### Memory Driver (Fast & Temporary)
116
- ```javascript
117
- import { S3db, CachePlugin } from 's3db.js';
118
-
119
- const s3db = new S3db({
120
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
121
- plugins: [
122
- new CachePlugin({
123
- driver: 'memory',
124
- ttl: 300000, // 5 minutes
125
- maxSize: 1000, // Max 1000 items
126
- config: {
127
- evictionPolicy: 'lru',
128
- enableStats: true
129
- }
130
- })
131
- ]
132
- });
133
-
134
- await s3db.connect();
135
-
136
- // Cache automatically intercepts read operations
137
- const users = s3db.resource('users');
138
- await users.count(); // ⚡ Cached for 5 minutes
139
- await users.list(); // ⚡ Cached result
140
- ```
141
-
142
- #### S3 Driver (Persistent & Shared)
143
- ```javascript
144
- const s3db = new S3db({
145
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
146
- plugins: [
147
- new CachePlugin({
148
- driver: 's3',
149
- ttl: 1800000, // 30 minutes
150
- config: {
151
- bucket: 'my-cache-bucket', // Optional: separate bucket
152
- keyPrefix: 'cache/', // Cache key prefix
153
- storageClass: 'STANDARD' // S3 storage class
154
- }
155
- })
156
- ]
157
- });
158
- ```
159
-
160
- #### Filesystem Driver (Local & Fast)
161
- ```javascript
162
- const s3db = new S3db({
163
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
164
- plugins: [
165
- new CachePlugin({
166
- driver: 'filesystem',
167
- config: {
168
- path: './cache',
169
- partitionAware: true,
170
- partitionStrategy: 'hierarchical'
171
- }
172
- })
173
- ]
174
- });
175
- ```
176
-
177
- ### ⚙️ Configuration Parameters
178
-
179
- | Parameter | Type | Default | Description |
180
- |-----------|------|---------|-------------|
181
- | `driver` | string | `'s3'` | Cache driver: `'memory'`, `'s3'`, or `'filesystem'` |
182
- | `ttl` | number | `300000` | Time-to-live in milliseconds (5 minutes) - global setting |
183
- | `maxSize` | number | `1000` | Maximum number of items in cache - global setting |
184
- | `config` | object | `{}` | Driver-specific configuration options (can override global settings) |
185
- | `includePartitions` | boolean | `true` | Include partition values in cache keys |
186
- | `partitionAware` | boolean | `false` | Use partition-aware filesystem cache |
187
- | `partitionStrategy` | string | `'hierarchical'` | Partition strategy: `'hierarchical'`, `'flat'`, `'temporal'` |
188
- | `trackUsage` | boolean | `true` | Track partition usage statistics |
189
- | `preloadRelated` | boolean | `false` | Preload related partition data |
190
-
191
- **Configuration Priority:** Driver-specific `config` options override global plugin settings. For example, if you set `ttl: 600000` at the plugin level and `ttl: 1800000` in the driver config, the driver will use 1800000 (30 minutes) while the global setting serves as the default for any drivers that don't specify their own TTL.
192
-
193
- ### Driver Configuration Options
194
-
195
- The `config` object contains driver-specific options. Note that `ttl` and `maxSize` can be configured at the plugin level (applies to all operations) or in the driver config (driver-specific override).
196
-
197
- #### Memory Driver (`driver: 'memory'`)
198
-
199
- | Parameter | Type | Default | Description |
200
- |-----------|------|---------|-------------|
201
- | `ttl` | number | inherited | TTL override for memory cache (inherits from plugin level) |
202
- | `maxSize` | number | inherited | Max items override for memory cache (inherits from plugin level) |
203
- | `enableStats` | boolean | `false` | Whether to track cache statistics (hits, misses, etc.) |
204
- | `evictionPolicy` | string | `'lru'` | Cache eviction policy: `'lru'` (Least Recently Used) or `'fifo'` (First In First Out) |
205
- | `logEvictions` | boolean | `false` | Whether to log when items are evicted from cache |
206
- | `cleanupInterval` | number | `60000` | Interval in milliseconds to run cleanup of expired items (1 minute default) |
207
- | `caseSensitive` | boolean | `true` | Whether cache keys are case sensitive |
208
- | `enableCompression` | boolean | `false` | Whether to compress values using gzip (requires zlib) |
209
- | `compressionThreshold` | number | `1024` | Minimum size in bytes to trigger compression |
210
- | `tags` | object | `{}` | Default tags to apply to all cached items |
211
- | `persistent` | boolean | `false` | Whether to persist cache to disk (experimental) |
212
- | `persistencePath` | string | `'./cache'` | Directory path for persistent cache storage |
213
- | `persistenceInterval` | number | `300000` | Interval in milliseconds to save cache to disk (5 minutes default) |
214
-
215
- #### S3 Driver (`driver: 's3'`)
216
-
217
- | Parameter | Type | Default | Description |
218
- |-----------|------|---------|-------------|
219
- | `ttl` | number | inherited | TTL override for S3 cache (inherits from plugin level) |
220
- | `keyPrefix` | string | `'cache'` | S3 key prefix for cache objects |
221
- | `client` | object | Database client | Custom S3 client instance |
222
-
223
- **Note:** S3 cache automatically uses gzip compression for all cached values.
224
-
225
- #### Filesystem Driver (`driver: 'filesystem'`)
226
-
227
- | Parameter | Type | Default | Description |
228
- |-----------|------|---------|-------------|
229
- | `directory` | string | required | Directory path to store cache files |
230
- | `ttl` | number | inherited | TTL override for filesystem cache (inherits from plugin level) |
231
- | `prefix` | string | `'cache'` | Prefix for cache filenames |
232
- | `enableCompression` | boolean | `true` | Whether to compress cache values using gzip |
233
- | `compressionThreshold` | number | `1024` | Minimum size in bytes to trigger compression |
234
- | `createDirectory` | boolean | `true` | Whether to create the directory if it doesn't exist |
235
- | `fileExtension` | string | `'.cache'` | File extension for cache files |
236
- | `enableMetadata` | boolean | `true` | Whether to store metadata alongside cache data |
237
- | `maxFileSize` | number | `10485760` | Maximum file size in bytes (10MB) |
238
- | `enableStats` | boolean | `false` | Whether to track cache statistics |
239
- | `enableCleanup` | boolean | `true` | Whether to automatically clean up expired files |
240
- | `cleanupInterval` | number | `300000` | Interval in milliseconds to run cleanup (5 minutes) |
241
- | `encoding` | string | `'utf8'` | File encoding to use |
242
- | `fileMode` | number | `0o644` | File permissions in octal notation |
243
- | `enableBackup` | boolean | `false` | Whether to create backup files before overwriting |
244
- | `backupSuffix` | string | `'.bak'` | Suffix for backup files |
245
- | `enableLocking` | boolean | `false` | Whether to use file locking to prevent concurrent access |
246
- | `lockTimeout` | number | `5000` | Lock timeout in milliseconds |
247
- | `enableJournal` | boolean | `false` | Whether to maintain a journal of operations |
248
- | `journalFile` | string | `'cache.journal'` | Journal filename |
249
-
250
- #### Partition-Aware Filesystem Driver (when `partitionAware: true`)
251
-
252
- | Parameter | Type | Default | Description |
253
- |-----------|------|---------|-------------|
254
- | `partitionStrategy` | string | `'hierarchical'` | Partition strategy: `'hierarchical'`, `'flat'`, `'temporal'` |
255
- | `trackUsage` | boolean | `true` | Track partition usage statistics |
256
- | `preloadRelated` | boolean | `false` | Preload related partition data |
257
- | `preloadThreshold` | number | `10` | Minimum usage count to trigger preloading |
258
- | `maxCacheSize` | string/null | `null` | Maximum cache size (e.g., `'1GB'`, `'500MB'`) |
259
- | `usageStatsFile` | string | `'partition-usage.json'` | File to store usage statistics |
260
-
261
- **Note:** All Filesystem Driver parameters also apply to Partition-Aware Filesystem Driver.
262
-
263
- ### 🔧 Easy Example
264
-
265
- ```javascript
266
- import { S3db, CachePlugin } from 's3db.js';
267
-
268
- // Memory cache example with global settings
269
- const s3db = new S3db({
270
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
271
- plugins: [new CachePlugin({
272
- driver: 'memory',
273
- ttl: 600000, // 10 minutes - applies to all cache operations
274
- maxSize: 500, // 500 items max - applies to all cache operations
275
- config: {
276
- enableStats: true,
277
- evictionPolicy: 'lru'
278
- }
279
- })]
280
- });
281
-
282
- // Filesystem cache example with driver-specific override
283
- const s3dbWithFileCache = new S3db({
284
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
285
- plugins: [new CachePlugin({
286
- driver: 'filesystem',
287
- ttl: 900000, // 15 minutes - global default
288
- maxSize: 2000, // 2000 items max - global default
289
- config: {
290
- directory: './cache',
291
- ttl: 1800000, // 30 minutes - overrides global for filesystem only
292
- enableCompression: true,
293
- enableCleanup: true,
294
- enableMetadata: true
295
- }
296
- })]
297
- });
298
-
299
- // S3 cache example
300
- const s3dbWithS3Cache = new S3db({
301
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
302
- plugins: [new CachePlugin({
303
- driver: 's3',
304
- ttl: 3600000, // 1 hour
305
- config: {
306
- keyPrefix: 'app-cache'
307
- }
308
- })]
309
- });
310
-
311
- await s3db.connect();
312
-
313
- const products = s3db.resource('products');
314
-
315
- // First call hits the database
316
- console.time('First call');
317
- const result1 = await products.count();
318
- console.timeEnd('First call'); // ~200ms
319
-
320
- // Second call uses cache
321
- console.time('Cached call');
322
- const result2 = await products.count();
323
- console.timeEnd('Cached call'); // ~2ms
324
-
325
- // Cache is automatically cleared on write operations
326
- await products.insert({ name: 'New Product', price: 29.99 });
327
-
328
- // Next call will hit database again (cache cleared)
329
- const result3 = await products.count(); // Fresh data
330
- ```
331
-
332
- ### 🚀 Advanced Configuration Example
333
-
334
- ```javascript
335
- import { S3db, CachePlugin } from 's3db.js';
336
-
337
- // Advanced cache configuration with partition-aware filesystem cache
338
- const s3dbWithAdvancedCache = new S3db({
339
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
340
- plugins: [new CachePlugin({
341
- driver: 'filesystem',
342
-
343
- // Global cache settings (apply to all operations)
344
- ttl: 3600000, // 1 hour default
345
- maxSize: 5000, // 5000 items max
346
- includePartitions: true,
347
- partitionAware: true, // Enable partition-aware caching
348
- partitionStrategy: 'hierarchical',
349
- trackUsage: true,
350
- preloadRelated: true,
351
-
352
- // Driver-specific configuration
353
- config: {
354
- directory: './data/cache',
355
- prefix: 'app-cache',
356
- ttl: 7200000, // 2 hours - overrides global TTL for filesystem
357
- enableCompression: true,
358
- compressionThreshold: 512, // Compress files > 512 bytes
359
- enableCleanup: true,
360
- cleanupInterval: 600000, // 10 minutes
361
- enableMetadata: true,
362
- maxFileSize: 5242880, // 5MB per file
363
- enableStats: true,
364
- fileMode: 0o644,
365
- encoding: 'utf8',
366
- enableBackup: true,
367
- enableLocking: true,
368
- lockTimeout: 3000
369
- }
370
- })]
371
- });
372
-
373
- // Memory cache with advanced features
374
- const s3dbWithMemoryCache = new S3db({
375
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
376
- plugins: [new CachePlugin({
377
- driver: 'memory',
378
- ttl: 600000, // 10 minutes - global TTL
379
- maxSize: 5000, // 5000 items max - global limit
380
- includePartitions: true,
381
- config: {
382
- enableStats: true,
383
- evictionPolicy: 'lru',
384
- logEvictions: true,
385
- enableCompression: true,
386
- compressionThreshold: 1024,
387
- tags: { environment: 'production' }
388
- }
389
- })]
390
- });
391
-
392
- // S3 cache with custom prefix
393
- const s3dbWithS3Cache = new S3db({
394
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
395
- plugins: [new CachePlugin({
396
- driver: 's3',
397
- ttl: 3600000, // 1 hour - global TTL
398
- includePartitions: true,
399
- config: {
400
- keyPrefix: 'app-cache'
401
- }
402
- })]
403
- });
404
-
405
- await s3dbWithAdvancedCache.connect();
406
-
407
- // Access cache methods on resources
408
- const users = s3dbWithAdvancedCache.resource('users');
409
-
410
- // Generate custom cache keys
411
- const cacheKey = await users.cacheKeyFor({
412
- action: 'list',
413
- params: { limit: 10 },
414
- partition: 'byStatus',
415
- partitionValues: { status: 'active' }
416
- });
417
-
418
- // Manual cache operations
419
- await users.cache.set(cacheKey, data);
420
- const cached = await users.cache.get(cacheKey);
421
- await users.cache.delete(cacheKey);
422
- await users.cache.clear(); // Clear all cache
423
-
424
- // Partition-aware cache operations (if using partition-aware cache)
425
- if (users.cache.clearPartition) {
426
- await users.cache.clearPartition('byStatus', { status: 'active' });
427
- const stats = await users.cache.getPartitionStats('byStatus');
428
- console.log('Partition stats:', stats);
429
- }
430
-
431
- // Cache statistics (if enabled)
432
- if (users.cache.stats) {
433
- const stats = users.cache.stats();
434
- console.log('Cache hit rate:', stats.hitRate);
435
- console.log('Total hits:', stats.hits);
436
- console.log('Total misses:', stats.misses);
437
- }
438
- ```
439
-
440
- ---
441
-
442
- ## 💰 Costs Plugin
443
-
444
- Track and monitor AWS S3 costs in real-time by calculating expenses for each API operation. Essential for cost optimization and budget management.
445
-
446
- ### ⚡ Quick Start
447
-
448
- ```javascript
449
- import { S3db, CostsPlugin } from 's3db.js';
450
-
451
- const s3db = new S3db({
452
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
453
- plugins: [CostsPlugin] // Static plugin - no 'new' required
454
- });
455
-
456
- await s3db.connect();
457
-
458
- // Use your database normally
459
- const users = s3db.resource('users');
460
- await users.insert({ name: 'John', email: 'john@example.com' });
461
- await users.list();
462
-
463
- // Check costs
464
- console.log('Total cost:', s3db.client.costs.total);
465
- console.log('Request breakdown:', s3db.client.costs.requests);
466
- ```
467
-
468
- ### ⚙️ Configuration Parameters
469
-
470
- **Note**: CostsPlugin is a static plugin with no configuration options. It automatically tracks all S3 operations.
471
-
472
- ### Cost Tracking Details
473
-
474
- | Operation | Cost per 1000 requests | Tracked Commands |
475
- |-----------|------------------------|------------------|
476
- | PUT operations | $0.005 | PutObjectCommand |
477
- | GET operations | $0.0004 | GetObjectCommand |
478
- | HEAD operations | $0.0004 | HeadObjectCommand |
479
- | DELETE operations | $0.0004 | DeleteObjectCommand, DeleteObjectsCommand |
480
- | LIST operations | $0.005 | ListObjectsV2Command |
481
-
482
- ### Cost Data Structure
483
-
484
- ```javascript
485
- {
486
- total: 0.000123, // Total cost in USD
487
- prices: { // Cost per 1000 requests
488
- put: 0.000005,
489
- get: 0.0000004,
490
- head: 0.0000004,
491
- delete: 0.0000004,
492
- list: 0.000005
493
- },
494
- requests: { // Request counters
495
- total: 15,
496
- put: 3,
497
- get: 8,
498
- head: 2,
499
- delete: 1,
500
- list: 1
501
- },
502
- events: { // Command-specific counters
503
- total: 15,
504
- PutObjectCommand: 3,
505
- GetObjectCommand: 8,
506
- HeadObjectCommand: 2,
507
- DeleteObjectCommand: 1,
508
- ListObjectsV2Command: 1
509
- }
510
- }
511
- ```
512
-
513
- ### 🔧 Easy Example
514
-
515
- ```javascript
516
- import { S3db, CostsPlugin } from 's3db.js';
517
-
518
- const s3db = new S3db({
519
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
520
- plugins: [CostsPlugin]
521
- });
522
-
523
- await s3db.connect();
524
-
525
- const products = s3db.resource('products');
526
-
527
- // Perform operations and track costs
528
- await products.insert({ name: 'Widget A', price: 19.99 });
529
- await products.insert({ name: 'Widget B', price: 29.99 });
530
- await products.list();
531
- await products.count();
532
-
533
- // Analyze costs
534
- const costs = s3db.client.costs;
535
- console.log(`Operations performed: ${costs.requests.total}`);
536
- console.log(`Total cost: $${costs.total.toFixed(6)}`);
537
- console.log(`Most expensive operation: PUT (${costs.requests.put} requests)`);
538
-
539
- // Cost breakdown
540
- console.log('\nCost breakdown:');
541
- Object.entries(costs.requests).forEach(([operation, count]) => {
542
- if (operation !== 'total' && count > 0) {
543
- const operationCost = count * costs.prices[operation];
544
- console.log(` ${operation.toUpperCase()}: ${count} requests = $${operationCost.toFixed(6)}`);
545
- }
546
- });
547
- ```
548
-
549
- ### 🚀 Advanced Monitoring Example
550
-
551
- ```javascript
552
- import { S3db, CostsPlugin } from 's3db.js';
553
-
554
- class CostMonitor {
555
- constructor(s3db) {
556
- this.s3db = s3db;
557
- this.startTime = Date.now();
558
- this.checkpoints = [];
559
- }
560
-
561
- checkpoint(label) {
562
- const costs = { ...this.s3db.client.costs };
563
- const timestamp = Date.now();
564
-
565
- this.checkpoints.push({
566
- label,
567
- timestamp,
568
- costs,
569
- duration: timestamp - this.startTime
570
- });
571
-
572
- return costs;
573
- }
574
-
575
- report() {
576
- console.log('\n=== Cost Analysis Report ===');
577
-
578
- for (let i = 0; i < this.checkpoints.length; i++) {
579
- const checkpoint = this.checkpoints[i];
580
- const prevCheckpoint = i > 0 ? this.checkpoints[i - 1] : null;
581
-
582
- console.log(`\n${checkpoint.label}:`);
583
- console.log(` Time: ${checkpoint.duration}ms`);
584
- console.log(` Total cost: $${checkpoint.costs.total.toFixed(6)}`);
585
-
586
- if (prevCheckpoint) {
587
- const costDiff = checkpoint.costs.total - prevCheckpoint.costs.total;
588
- const requestDiff = checkpoint.costs.requests.total - prevCheckpoint.costs.requests.total;
589
- console.log(` Cost increase: $${costDiff.toFixed(6)}`);
590
- console.log(` New requests: ${requestDiff}`);
591
- }
592
- }
593
-
594
- // Efficiency metrics
595
- const finalCosts = this.checkpoints[this.checkpoints.length - 1].costs;
596
- const totalTime = this.checkpoints[this.checkpoints.length - 1].duration;
597
-
598
- console.log('\n=== Efficiency Metrics ===');
599
- console.log(`Total execution time: ${totalTime}ms`);
600
- console.log(`Total requests: ${finalCosts.requests.total}`);
601
- console.log(`Requests per second: ${(finalCosts.requests.total / (totalTime / 1000)).toFixed(2)}`);
602
- console.log(`Cost per request: $${(finalCosts.total / finalCosts.requests.total).toFixed(8)}`);
603
- console.log(`Monthly projection (1M ops): $${(finalCosts.total * 1000000).toFixed(2)}`);
604
- }
605
- }
606
-
607
- // Usage
608
- const s3db = new S3db({
609
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
610
- plugins: [CostsPlugin]
611
- });
612
-
613
- await s3db.connect();
614
-
615
- const monitor = new CostMonitor(s3db);
616
- const users = s3db.resource('users');
617
-
618
- // Bulk operations with cost tracking
619
- monitor.checkpoint('Initial state');
620
-
621
- // Bulk insert
622
- const userData = Array.from({ length: 100 }, (_, i) => ({
623
- name: `User ${i}`,
624
- email: `user${i}@example.com`,
625
- role: i % 3 === 0 ? 'admin' : 'user'
626
- }));
627
-
628
- await users.insertMany(userData);
629
- monitor.checkpoint('After bulk insert');
630
-
631
- // Query operations
632
- await users.count();
633
- await users.list({ limit: 50 });
634
- await users.list({ limit: 25, offset: 25 });
635
- monitor.checkpoint('After queries');
636
-
637
- // Update operations
638
- const userList = await users.list({ limit: 10 });
639
- for (const user of userList) {
640
- await users.update(user.id, { lastLogin: new Date().toISOString() });
641
- }
642
- monitor.checkpoint('After updates');
643
-
644
- // Generate detailed report
645
- monitor.report();
646
-
647
- // Set cost alerts
648
- const currentCost = s3db.client.costs.total;
649
- if (currentCost > 0.01) { // $0.01 threshold
650
- console.warn(`⚠️ Cost threshold exceeded: $${currentCost.toFixed(6)}`);
651
- }
652
-
653
- // Export cost data for external analysis
654
- const costData = {
655
- timestamp: new Date().toISOString(),
656
- sessionCosts: s3db.client.costs,
657
- checkpoints: monitor.checkpoints,
658
- summary: {
659
- totalCost: s3db.client.costs.total,
660
- totalRequests: s3db.client.costs.requests.total,
661
- avgCostPerRequest: s3db.client.costs.total / s3db.client.costs.requests.total,
662
- mostExpensiveOperation: Object.entries(s3db.client.costs.requests)
663
- .filter(([key]) => key !== 'total')
664
- .sort(([,a], [,b]) => b - a)[0]
665
- }
666
- };
667
-
668
- console.log('\nExportable cost data:', JSON.stringify(costData, null, 2));
669
- ```
670
-
671
- ---
672
-
673
- ## 📝 Audit Plugin
674
-
675
- Comprehensive audit logging system that tracks all database operations for compliance, security monitoring, and debugging purposes.
676
-
677
- ### ⚡ Quick Start
678
-
679
- ```javascript
680
- import { S3db, AuditPlugin } from 's3db.js';
681
-
682
- const s3db = new S3db({
683
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
684
- plugins: [new AuditPlugin({ enabled: true })]
685
- });
686
-
687
- await s3db.connect();
688
-
689
- // All operations are automatically logged
690
- const users = s3db.resource('users');
691
- await users.insert({ name: 'John', email: 'john@example.com' });
692
- await users.update(userId, { name: 'John Doe' });
693
-
694
- // Access audit logs
695
- const auditResource = s3db.resource('audits');
696
- const logs = await auditResource.list();
697
- console.log('Audit trail:', logs);
698
- ```
699
-
700
- ### ⚙️ Configuration Parameters
701
-
702
- | Parameter | Type | Default | Description |
703
- |-----------|------|---------|-------------|
704
- | `enabled` | boolean | `true` | Enable/disable audit logging |
705
- | `includeData` | boolean | `true` | Include data payloads in audit logs |
706
- | `includePartitions` | boolean | `true` | Include partition information in logs |
707
- | `maxDataSize` | number | `10000` | Maximum data size to log (bytes) |
708
- | `trackOperations` | array | `['insert', 'update', 'delete']` | Operations to audit |
709
- | `excludeResources` | array | `[]` | Resources to exclude from auditing |
710
- | `userId` | function | `null` | Function to extract user ID from context |
711
- | `metadata` | function | `null` | Function to add custom metadata |
712
-
713
- ### Audit Log Structure
714
-
715
- ```javascript
716
- {
717
- id: 'audit-abc123',
718
- resourceName: 'users',
719
- operation: 'insert',
720
- recordId: 'user-123',
721
- userId: 'admin-456',
722
- timestamp: '2024-01-15T10:30:00.000Z',
723
- oldData: '{"name":"John"}', // For updates
724
- newData: '{"name":"John Doe"}', // JSON string of data
725
- partition: 'byStatus', // If using partitions
726
- partitionValues: '{"status":"active"}',
727
- metadata: '{"ip":"192.168.1.1"}', // Custom metadata
728
- _v: 0 // Audit record version
729
- }
730
- ```
731
-
732
- ### 🔧 Easy Example
733
-
734
- ```javascript
735
- import { S3db, AuditPlugin } from 's3db.js';
736
-
737
- const s3db = new S3db({
738
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
739
- plugins: [new AuditPlugin({
740
- enabled: true,
741
- includeData: true,
742
- trackOperations: ['insert', 'update', 'delete', 'get'],
743
- maxDataSize: 5000
744
- })]
745
- });
746
-
747
- await s3db.connect();
748
-
749
- const products = s3db.resource('products');
750
- const audits = s3db.resource('audits');
751
-
752
- // Perform operations (automatically audited)
753
- const product = await products.insert({
754
- name: 'Gaming Laptop',
755
- price: 1299.99,
756
- category: 'electronics'
757
- });
758
-
759
- await products.update(product.id, { price: 1199.99 });
760
- await products.get(product.id);
761
- await products.delete(product.id);
762
-
763
- // Review audit trail
764
- const auditLogs = await audits.list();
765
-
766
- console.log('\n=== Audit Trail ===');
767
- auditLogs.forEach(log => {
768
- console.log(`${log.timestamp} | ${log.operation.toUpperCase()} | ${log.resourceName} | ${log.recordId}`);
769
-
770
- if (log.operation === 'update') {
771
- const oldData = JSON.parse(log.oldData);
772
- const newData = JSON.parse(log.newData);
773
- console.log(` Price changed: $${oldData.price} → $${newData.price}`);
774
- }
775
- });
776
-
777
- // Query specific audit logs
778
- const updateLogs = await audits.list({
779
- filter: log => log.operation === 'update'
780
- });
781
-
782
- console.log(`\nFound ${updateLogs.length} update operations`);
783
- ```
784
-
785
- ### 🚀 Advanced Configuration Example
786
-
787
- ```javascript
788
- import { S3db, AuditPlugin } from 's3db.js';
789
-
790
- const s3db = new S3db({
791
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
792
- plugins: [new AuditPlugin({
793
- enabled: true,
794
- includeData: true,
795
- includePartitions: true,
796
- maxDataSize: 20000, // 20KB limit
797
-
798
- // Track all operations including reads
799
- trackOperations: ['insert', 'update', 'delete', 'get', 'list'],
800
-
801
- // Exclude sensitive resources from auditing
802
- excludeResources: ['sessions', 'temp_data'],
803
-
804
- // Extract user ID from request context
805
- userId: (context) => {
806
- return context?.user?.id ||
807
- context?.headers?.['x-user-id'] ||
808
- 'anonymous';
809
- },
810
-
811
- // Add custom metadata to audit logs
812
- metadata: (operation, resourceName, data, context) => {
813
- return {
814
- ip: context?.ip,
815
- userAgent: context?.userAgent,
816
- sessionId: context?.sessionId,
817
- apiVersion: '1.0',
818
- environment: process.env.NODE_ENV,
819
- requestId: context?.requestId,
820
-
821
- // Operation-specific metadata
822
- ...(operation === 'insert' && {
823
- createdVia: 'api',
824
- validationPassed: true
825
- }),
826
-
827
- ...(operation === 'update' && {
828
- fieldsChanged: Object.keys(data || {}),
829
- automaticUpdate: false
830
- }),
831
-
832
- ...(operation === 'delete' && {
833
- softDelete: false,
834
- cascadeDelete: false
835
- })
836
- };
837
- }
838
- })]
839
- });
840
-
841
- await s3db.connect();
842
-
843
- // Custom audit query functions
844
- class AuditAnalyzer {
845
- constructor(auditResource) {
846
- this.audits = auditResource;
847
- }
848
-
849
- async getUserActivity(userId, timeRange = 24) {
850
- const since = new Date(Date.now() - timeRange * 60 * 60 * 1000);
851
- const logs = await this.audits.list();
852
-
853
- return logs.filter(log =>
854
- log.userId === userId &&
855
- new Date(log.timestamp) > since
856
- );
857
- }
858
-
859
- async getResourceActivity(resourceName, operation = null) {
860
- const logs = await this.audits.list();
861
-
862
- return logs.filter(log =>
863
- log.resourceName === resourceName &&
864
- (!operation || log.operation === operation)
865
- );
866
- }
867
-
868
- async getDataChanges(resourceName, recordId) {
869
- const logs = await this.audits.list();
870
-
871
- return logs
872
- .filter(log =>
873
- log.resourceName === resourceName &&
874
- log.recordId === recordId &&
875
- log.operation === 'update'
876
- )
877
- .sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
878
- .map(log => ({
879
- timestamp: log.timestamp,
880
- oldData: JSON.parse(log.oldData || '{}'),
881
- newData: JSON.parse(log.newData || '{}'),
882
- userId: log.userId,
883
- metadata: JSON.parse(log.metadata || '{}')
884
- }));
885
- }
886
-
887
- async generateComplianceReport(startDate, endDate) {
888
- const logs = await this.audits.list();
889
-
890
- const filteredLogs = logs.filter(log => {
891
- const logDate = new Date(log.timestamp);
892
- return logDate >= startDate && logDate <= endDate;
893
- });
894
-
895
- const summary = {
896
- totalOperations: filteredLogs.length,
897
- operationBreakdown: {},
898
- resourceActivity: {},
899
- userActivity: {},
900
- timeRange: { startDate, endDate }
901
- };
902
-
903
- filteredLogs.forEach(log => {
904
- // Operation breakdown
905
- summary.operationBreakdown[log.operation] =
906
- (summary.operationBreakdown[log.operation] || 0) + 1;
907
-
908
- // Resource activity
909
- summary.resourceActivity[log.resourceName] =
910
- (summary.resourceActivity[log.resourceName] || 0) + 1;
911
-
912
- // User activity
913
- summary.userActivity[log.userId] =
914
- (summary.userActivity[log.userId] || 0) + 1;
915
- });
916
-
917
- return summary;
918
- }
919
- }
920
-
921
- // Usage with context
922
- const users = s3db.resource('users');
923
- const audits = s3db.resource('audits');
924
- const analyzer = new AuditAnalyzer(audits);
925
-
926
- // Simulate operations with user context
927
- const userContext = {
928
- user: { id: 'admin-123', role: 'admin' },
929
- ip: '192.168.1.100',
930
- userAgent: 'Mozilla/5.0...',
931
- sessionId: 'sess-789',
932
- requestId: 'req-456'
933
- };
934
-
935
- // Operations with context (would be passed through middleware in real app)
936
- await users.insert({
937
- name: 'Alice Johnson',
938
- email: 'alice@example.com'
939
- }, userContext);
940
-
941
- // Analyze audit data
942
- const userActivity = await analyzer.getUserActivity('admin-123');
943
- console.log('Recent user activity:', userActivity);
944
-
945
- const complianceReport = await analyzer.generateComplianceReport(
946
- new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
947
- new Date()
948
- );
949
-
950
- console.log('\n=== Compliance Report ===');
951
- console.log(`Total operations: ${complianceReport.totalOperations}`);
952
- console.log('Operation breakdown:', complianceReport.operationBreakdown);
953
- console.log('Most active resource:',
954
- Object.entries(complianceReport.resourceActivity)
955
- .sort(([,a], [,b]) => b - a)[0]
956
- );
957
-
958
- // Real-time audit monitoring
959
- audits.on('insert', (auditLog) => {
960
- console.log(`🔍 New audit log: ${auditLog.operation} on ${auditLog.resourceName}`);
961
-
962
- // Security alerts
963
- if (auditLog.operation === 'delete' && auditLog.userId === 'anonymous') {
964
- console.warn('🚨 SECURITY ALERT: Anonymous user performed delete operation');
965
- }
966
-
967
- if (auditLog.operation === 'get' && auditLog.resourceName === 'sensitive_data') {
968
- console.warn('🔒 PRIVACY ALERT: Sensitive data accessed');
969
- }
970
- });
971
-
972
- // Audit log retention and cleanup
973
- setInterval(async () => {
974
- const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
975
- const oldLogs = await audits.list({
976
- filter: log => new Date(log.timestamp) < thirtyDaysAgo
977
- });
978
-
979
- console.log(`Cleaning up ${oldLogs.length} old audit logs`);
980
-
981
- for (const log of oldLogs) {
982
- await audits.delete(log.id);
983
- }
984
- }, 24 * 60 * 60 * 1000); // Daily cleanup
985
- ```
986
-
987
- ---
988
-
989
- ## 🔍 FullText Plugin
990
-
991
- Powerful full-text search engine with automatic indexing, scoring, and advanced search capabilities for your s3db resources.
992
-
993
- ### ⚡ Quick Start
994
-
995
- ```javascript
996
- import { S3db, FullTextPlugin } from 's3db.js';
997
-
998
- const s3db = new S3db({
999
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1000
- plugins: [new FullTextPlugin({
1001
- enabled: true,
1002
- fields: ['title', 'description', 'content']
1003
- })]
1004
- });
1005
-
1006
- await s3db.connect();
1007
-
1008
- const articles = s3db.resource('articles');
1009
-
1010
- // Insert data (automatically indexed)
1011
- await articles.insert({
1012
- title: 'Introduction to Machine Learning',
1013
- description: 'A comprehensive guide to ML basics',
1014
- content: 'Machine learning is a subset of artificial intelligence...'
1015
- });
1016
-
1017
- // Search across indexed fields
1018
- const results = await s3db.plugins.fulltext.searchRecords('articles', 'machine learning');
1019
- console.log('Search results:', results);
1020
- ```
1021
-
1022
- ### ⚙️ Configuration Parameters
1023
-
1024
- | Parameter | Type | Default | Description |
1025
- |-----------|------|---------|-------------|
1026
- | `enabled` | boolean | `true` | Enable/disable full-text search |
1027
- | `fields` | array | `[]` | Fields to index for search |
1028
- | `minWordLength` | number | `3` | Minimum word length for indexing |
1029
- | `maxResults` | number | `100` | Maximum search results to return |
1030
- | `language` | string | `'en-US'` | Language for text processing |
1031
- | `stopWords` | array | `['the', 'a', 'an', ...]` | Words to exclude from indexing |
1032
- | `stemming` | boolean | `false` | Enable word stemming |
1033
- | `caseSensitive` | boolean | `false` | Case-sensitive search |
1034
- | `fuzzySearch` | boolean | `false` | Enable fuzzy matching |
1035
- | `indexName` | string | `'fulltext_indexes'` | Name of index resource |
1036
-
1037
- ### Search Result Structure
1038
-
1039
- ```javascript
1040
- {
1041
- id: 'article-123',
1042
- title: 'Introduction to Machine Learning',
1043
- description: 'A comprehensive guide to ML basics',
1044
- content: 'Machine learning is a subset...',
1045
- _searchScore: 0.85, // Relevance score (0-1)
1046
- _matchedFields: ['title', 'content'], // Fields with matches
1047
- _matchedWords: ['machine', 'learning'], // Matched search terms
1048
- _highlights: { // Highlighted snippets
1049
- title: 'Introduction to <mark>Machine Learning</mark>',
1050
- content: '<mark>Machine learning</mark> is a subset...'
1051
- }
1052
- }
1053
- ```
1054
-
1055
- ### 🔧 Easy Example
1056
-
1057
- ```javascript
1058
- import { S3db, FullTextPlugin } from 's3db.js';
1059
-
1060
- const s3db = new S3db({
1061
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1062
- plugins: [new FullTextPlugin({
1063
- enabled: true,
1064
- fields: ['name', 'description', 'tags'],
1065
- minWordLength: 2,
1066
- maxResults: 50
1067
- })]
1068
- });
1069
-
1070
- await s3db.connect();
1071
-
1072
- const products = s3db.resource('products');
1073
-
1074
- // Add products with searchable content
1075
- await products.insertMany([
1076
- {
1077
- name: 'Gaming Laptop Pro',
1078
- description: 'High-performance laptop for gaming and productivity',
1079
- tags: ['gaming', 'laptop', 'computer', 'electronics']
1080
- },
1081
- {
1082
- name: 'Wireless Gaming Mouse',
1083
- description: 'Precision wireless mouse designed for gamers',
1084
- tags: ['gaming', 'mouse', 'wireless', 'electronics']
1085
- },
1086
- {
1087
- name: 'Mechanical Keyboard',
1088
- description: 'Professional mechanical keyboard with RGB lighting',
1089
- tags: ['keyboard', 'mechanical', 'typing', 'electronics']
1090
- }
1091
- ]);
1092
-
1093
- // Search for gaming products
1094
- const gamingProducts = await s3db.plugins.fulltext.searchRecords('products', 'gaming');
1095
-
1096
- console.log('\n=== Gaming Products ===');
1097
- gamingProducts.forEach(product => {
1098
- console.log(`${product.name} (Score: ${product._searchScore.toFixed(2)})`);
1099
- console.log(` Matched fields: ${product._matchedFields.join(', ')}`);
1100
- console.log(` Description: ${product.description}`);
1101
- });
1102
-
1103
- // Search for wireless devices
1104
- const wirelessProducts = await s3db.plugins.fulltext.searchRecords('products', 'wireless');
1105
-
1106
- console.log('\n=== Wireless Products ===');
1107
- wirelessProducts.forEach(product => {
1108
- console.log(`${product.name} - ${product.description}`);
1109
- });
1110
-
1111
- // Multi-word search
1112
- const laptopGaming = await s3db.plugins.fulltext.searchRecords('products', 'laptop gaming');
1113
-
1114
- console.log('\n=== Laptop Gaming Search ===');
1115
- console.log(`Found ${laptopGaming.length} products matching "laptop gaming"`);
1116
- ```
1117
-
1118
- ### 🚀 Advanced Configuration Example
1119
-
1120
- ```javascript
1121
- import { S3db, FullTextPlugin } from 's3db.js';
1122
-
1123
- const s3db = new S3db({
1124
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1125
- plugins: [new FullTextPlugin({
1126
- enabled: true,
1127
-
1128
- // Comprehensive field indexing
1129
- fields: ['title', 'description', 'content', 'tags', 'category', 'author'],
1130
-
1131
- // Advanced text processing
1132
- minWordLength: 2,
1133
- maxResults: 200,
1134
- language: 'en-US',
1135
- stemming: true, // Enable word stemming (run/running/ran)
1136
- caseSensitive: false,
1137
- fuzzySearch: true, // Enable typo tolerance
1138
-
1139
- // Custom stop words (words to ignore)
1140
- stopWords: [
1141
- 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
1142
- 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
1143
- 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
1144
- 'should', 'may', 'might', 'must', 'can', 'this', 'that', 'these', 'those'
1145
- ],
1146
-
1147
- // Advanced search options
1148
- highlightTags: {
1149
- start: '<mark class="highlight">',
1150
- end: '</mark>'
1151
- },
1152
-
1153
- // Custom scoring weights per field
1154
- fieldWeights: {
1155
- title: 3.0, // Title matches score higher
1156
- description: 2.0, // Description is important
1157
- content: 1.0, // Content has normal weight
1158
- tags: 2.5, // Tags are highly relevant
1159
- category: 1.5, // Category is moderately important
1160
- author: 1.0 // Author has normal weight
1161
- },
1162
-
1163
- // Indexing behavior
1164
- indexName: 'search_indexes',
1165
- autoReindex: true, // Automatically reindex on data changes
1166
- batchSize: 100, // Index batch size
1167
- maxIndexSize: 10000 // Maximum index entries
1168
- })]
1169
- });
1170
-
1171
- await s3db.connect();
1172
-
1173
- // Advanced search class with custom methods
1174
- class AdvancedSearch {
1175
- constructor(fulltextPlugin) {
1176
- this.plugin = fulltextPlugin;
1177
- }
1178
-
1179
- async searchWithFilters(resourceName, query, filters = {}) {
1180
- let results = await this.plugin.searchRecords(resourceName, query);
1181
-
1182
- // Apply additional filters
1183
- if (filters.category) {
1184
- results = results.filter(item => item.category === filters.category);
1185
- }
1186
-
1187
- if (filters.minScore) {
1188
- results = results.filter(item => item._searchScore >= filters.minScore);
1189
- }
1190
-
1191
- if (filters.dateRange) {
1192
- const { start, end } = filters.dateRange;
1193
- results = results.filter(item => {
1194
- const itemDate = new Date(item.createdAt);
1195
- return itemDate >= start && itemDate <= end;
1196
- });
1197
- }
1198
-
1199
- return results;
1200
- }
1201
-
1202
- async searchMultipleResources(resourceNames, query) {
1203
- const allResults = [];
1204
-
1205
- for (const resourceName of resourceNames) {
1206
- const results = await this.plugin.searchRecords(resourceName, query);
1207
- allResults.push(...results.map(item => ({
1208
- ...item,
1209
- _resourceType: resourceName
1210
- })));
1211
- }
1212
-
1213
- // Sort by relevance across all resources
1214
- return allResults.sort((a, b) => b._searchScore - a._searchScore);
1215
- }
1216
-
1217
- async suggestWords(resourceName, partial) {
1218
- // Get all indexed words that start with partial
1219
- const allIndexes = await this.plugin.indexResource.list();
1220
-
1221
- const suggestions = allIndexes
1222
- .filter(index =>
1223
- index.resourceName === resourceName &&
1224
- index.word.toLowerCase().startsWith(partial.toLowerCase())
1225
- )
1226
- .sort((a, b) => b.count - a.count) // Sort by frequency
1227
- .slice(0, 10)
1228
- .map(index => index.word);
1229
-
1230
- return [...new Set(suggestions)]; // Remove duplicates
1231
- }
1232
-
1233
- async getSearchAnalytics(resourceName) {
1234
- const indexes = await this.plugin.indexResource.list();
1235
- const resourceIndexes = indexes.filter(i => i.resourceName === resourceName);
1236
-
1237
- const analytics = {
1238
- totalWords: resourceIndexes.length,
1239
- totalOccurrences: resourceIndexes.reduce((sum, i) => sum + i.count, 0),
1240
- avgWordsPerDocument: 0,
1241
- topWords: resourceIndexes
1242
- .sort((a, b) => b.count - a.count)
1243
- .slice(0, 20)
1244
- .map(i => ({ word: i.word, count: i.count })),
1245
- wordDistribution: {},
1246
- lastIndexed: Math.max(...resourceIndexes.map(i => new Date(i.lastUpdated)))
1247
- };
1248
-
1249
- // Calculate word distribution by frequency ranges
1250
- resourceIndexes.forEach(index => {
1251
- const range = index.count < 5 ? 'rare' :
1252
- index.count < 20 ? 'common' : 'frequent';
1253
- analytics.wordDistribution[range] = (analytics.wordDistribution[range] || 0) + 1;
1254
- });
1255
-
1256
- return analytics;
1257
- }
1258
- }
1259
-
1260
- // Setup sample data
1261
- const articles = s3db.resource('articles');
1262
- const products = s3db.resource('products');
1263
-
1264
- await articles.insertMany([
1265
- {
1266
- title: 'Advanced JavaScript Techniques',
1267
- description: 'Deep dive into modern JavaScript features',
1268
- content: 'JavaScript has evolved significantly with ES6+ features...',
1269
- tags: ['javascript', 'programming', 'web-development'],
1270
- category: 'technology',
1271
- author: 'John Smith'
1272
- },
1273
- {
1274
- title: 'Machine Learning Fundamentals',
1275
- description: 'Introduction to ML concepts and algorithms',
1276
- content: 'Machine learning is revolutionizing how we process data...',
1277
- tags: ['machine-learning', 'ai', 'data-science'],
1278
- category: 'technology',
1279
- author: 'Jane Doe'
1280
- },
1281
- {
1282
- title: 'Sustainable Cooking Tips',
1283
- description: 'Eco-friendly approaches to home cooking',
1284
- content: 'Sustainable cooking practices can reduce your environmental impact...',
1285
- tags: ['cooking', 'sustainability', 'environment'],
1286
- category: 'lifestyle',
1287
- author: 'Chef Maria'
1288
- }
1289
- ]);
1290
-
1291
- // Initialize advanced search
1292
- const search = new AdvancedSearch(s3db.plugins.fulltext);
1293
-
1294
- // Complex search with filters
1295
- const techArticles = await search.searchWithFilters('articles', 'javascript programming', {
1296
- category: 'technology',
1297
- minScore: 0.5
1298
- });
1299
-
1300
- console.log('\n=== Technology Articles ===');
1301
- techArticles.forEach(article => {
1302
- console.log(`${article.title} by ${article.author}`);
1303
- console.log(` Score: ${article._searchScore.toFixed(3)}`);
1304
- console.log(` Matches: ${article._matchedWords.join(', ')}`);
1305
- console.log(` Highlighted: ${article._highlights?.title || article.title}`);
1306
- });
1307
-
1308
- // Multi-resource search
1309
- const allContent = await search.searchMultipleResources(['articles', 'products'], 'technology');
1310
-
1311
- console.log('\n=== Cross-Resource Search ===');
1312
- allContent.forEach(item => {
1313
- console.log(`[${item._resourceType.toUpperCase()}] ${item.title || item.name}`);
1314
- console.log(` Score: ${item._searchScore.toFixed(3)}`);
1315
- });
1316
-
1317
- // Auto-complete suggestions
1318
- const suggestions = await search.suggestWords('articles', 'java');
1319
- console.log('\nSuggestions for "java":', suggestions);
1320
-
1321
- // Search analytics
1322
- const analytics = await search.getSearchAnalytics('articles');
1323
- console.log('\n=== Search Analytics ===');
1324
- console.log(`Total indexed words: ${analytics.totalWords}`);
1325
- console.log(`Total word occurrences: ${analytics.totalOccurrences}`);
1326
- console.log('Top words:', analytics.topWords.slice(0, 5));
1327
- console.log('Word distribution:', analytics.wordDistribution);
1328
-
1329
- // Real-time search monitoring
1330
- s3db.plugins.fulltext.on('indexed', (data) => {
1331
- console.log(`🔍 Indexed: ${data.resourceName} - ${data.recordId}`);
1332
- });
1333
-
1334
- s3db.plugins.fulltext.on('searched', (data) => {
1335
- console.log(`🔎 Search: "${data.query}" in ${data.resourceName} (${data.results} results)`);
1336
- });
1337
-
1338
- // Performance monitoring
1339
- console.time('Search Performance');
1340
- const perfResults = await s3db.plugins.fulltext.searchRecords('articles', 'machine learning javascript');
1341
- console.timeEnd('Search Performance');
1342
- console.log(`Search returned ${perfResults.length} results`);
1343
- ```
1344
-
1345
- ---
1346
-
1347
- ## 📊 Metrics Plugin
1348
-
1349
- Comprehensive performance monitoring and usage analytics system that tracks operation timing, resource usage, errors, and provides detailed insights.
1350
-
1351
- ### ⚡ Quick Start
1352
-
1353
- ```javascript
1354
- import { S3db, MetricsPlugin } from 's3db.js';
1355
-
1356
- const s3db = new S3db({
1357
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1358
- plugins: [new MetricsPlugin({ enabled: true })]
1359
- });
1360
-
1361
- await s3db.connect();
1362
-
1363
- // Use your database normally - metrics are collected automatically
1364
- const users = s3db.resource('users');
1365
- await users.insert({ name: 'John', email: 'john@example.com' });
1366
- await users.list();
1367
- await users.count();
1368
-
1369
- // Get comprehensive metrics
1370
- const metrics = await s3db.plugins.metrics.getMetrics();
1371
- console.log('Performance metrics:', metrics);
1372
- ```
1373
-
1374
- ### ⚙️ Configuration Parameters
1375
-
1376
- | Parameter | Type | Default | Description |
1377
- |-----------|------|---------|-------------|
1378
- | `enabled` | boolean | `true` | Enable/disable metrics collection |
1379
- | `collectPerformance` | boolean | `true` | Track operation timing and performance |
1380
- | `collectErrors` | boolean | `true` | Track errors and failures |
1381
- | `collectUsage` | boolean | `true` | Track resource usage patterns |
1382
- | `retentionDays` | number | `30` | Days to retain metric data |
1383
- | `flushInterval` | number | `60000` | Interval to flush metrics (ms) |
1384
- | `sampleRate` | number | `1.0` | Sampling rate for metrics (0.0-1.0) |
1385
- | `trackSlowQueries` | boolean | `true` | Track slow operations |
1386
- | `slowQueryThreshold` | number | `1000` | Threshold for slow queries (ms) |
1387
- | `batchSize` | number | `100` | Batch size for metric storage |
1388
-
1389
- ### Metrics Data Structure
1390
-
1391
- ```javascript
1392
- {
1393
- performance: {
1394
- averageResponseTime: 245, // milliseconds
1395
- totalRequests: 1250,
1396
- requestsPerSecond: 12.5,
1397
- slowestOperations: [
1398
- { operation: "list", resource: "users", avgTime: 450, count: 50 },
1399
- { operation: "get", resource: "products", avgTime: 320, count: 200 }
1400
- ],
1401
- operationTiming: {
1402
- insert: { avg: 180, min: 120, max: 350, total: 50 },
1403
- update: { avg: 160, min: 90, max: 280, total: 30 },
1404
- get: { avg: 95, min: 45, max: 180, total: 200 }
1405
- }
1406
- },
1407
- usage: {
1408
- resources: {
1409
- users: { inserts: 150, updates: 75, deletes: 10, reads: 800 },
1410
- products: { inserts: 300, updates: 120, deletes: 25, reads: 1200 }
1411
- },
1412
- totalOperations: 2680,
1413
- mostActiveResource: "products",
1414
- peakUsageHour: "14:00",
1415
- dailyPatterns: { /* hourly usage data */ }
1416
- },
1417
- errors: {
1418
- total: 15,
1419
- byType: {
1420
- "ValidationError": 8,
1421
- "NotFoundError": 5,
1422
- "PermissionError": 2
1423
- },
1424
- byResource: { users: 10, products: 5 },
1425
- errorRate: 0.0056 // 0.56%
1426
- },
1427
- cache: {
1428
- hitRate: 0.78,
1429
- totalHits: 980,
1430
- totalMisses: 270
1431
- }
1432
- }
1433
- ```
1434
-
1435
- ### 🔧 Easy Example
1436
-
1437
- ```javascript
1438
- import { S3db, MetricsPlugin } from 's3db.js';
1439
-
1440
- const s3db = new S3db({
1441
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1442
- plugins: [new MetricsPlugin({
1443
- enabled: true,
1444
- collectPerformance: true,
1445
- collectErrors: true,
1446
- flushInterval: 30000 // 30 seconds
1447
- })]
1448
- });
1449
-
1450
- await s3db.connect();
1451
-
1452
- const orders = s3db.resource('orders');
1453
-
1454
- // Simulate various operations
1455
- console.log('Performing operations...');
1456
-
1457
- // Fast operations
1458
- for (let i = 0; i < 10; i++) {
1459
- await orders.insert({
1460
- customerId: `customer-${i}`,
1461
- amount: Math.random() * 1000,
1462
- status: 'pending'
1463
- });
1464
- }
1465
-
1466
- // Query operations
1467
- await orders.count();
1468
- await orders.list({ limit: 5 });
1469
-
1470
- // Some updates
1471
- const orderList = await orders.list({ limit: 3 });
1472
- for (const order of orderList) {
1473
- await orders.update(order.id, { status: 'processing' });
1474
- }
1475
-
1476
- // Get performance metrics
1477
- const metrics = await s3db.plugins.metrics.getMetrics();
1478
-
1479
- console.log('\n=== Performance Report ===');
1480
- console.log(`Average response time: ${metrics.performance.averageResponseTime}ms`);
1481
- console.log(`Total operations: ${metrics.usage.totalOperations}`);
1482
- console.log(`Error rate: ${(metrics.errors.errorRate * 100).toFixed(2)}%`);
1483
-
1484
- console.log('\n=== Operation Breakdown ===');
1485
- Object.entries(metrics.performance.operationTiming).forEach(([op, timing]) => {
1486
- console.log(`${op.toUpperCase()}: avg ${timing.avg}ms (${timing.total} operations)`);
1487
- });
1488
-
1489
- console.log('\n=== Resource Usage ===');
1490
- Object.entries(metrics.usage.resources).forEach(([resource, usage]) => {
1491
- const total = Object.values(usage).reduce((sum, count) => sum + count, 0);
1492
- console.log(`${resource}: ${total} total operations`);
1493
- });
1494
- ```
1495
-
1496
- ### 🚀 Advanced Configuration Example
1497
-
1498
- ```javascript
1499
- import { S3db, MetricsPlugin } from 's3db.js';
1500
-
1501
- const s3db = new S3db({
1502
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1503
- plugins: [new MetricsPlugin({
1504
- enabled: true,
1505
-
1506
- // Comprehensive monitoring
1507
- collectPerformance: true,
1508
- collectErrors: true,
1509
- collectUsage: true,
1510
-
1511
- // Advanced settings
1512
- retentionDays: 90, // 3 months of data
1513
- flushInterval: 10000, // 10 seconds
1514
- sampleRate: 1.0, // 100% sampling
1515
-
1516
- // Performance thresholds
1517
- trackSlowQueries: true,
1518
- slowQueryThreshold: 500, // 500ms threshold
1519
-
1520
- // Storage optimization
1521
- batchSize: 50,
1522
- compressionEnabled: true,
1523
-
1524
- // Custom alerting thresholds
1525
- alertThresholds: {
1526
- errorRate: 0.05, // 5% error rate
1527
- avgResponseTime: 1000, // 1 second average
1528
- memoryUsage: 0.9 // 90% memory usage
1529
- },
1530
-
1531
- // Event hooks
1532
- onSlowQuery: (operation, resource, duration) => {
1533
- console.warn(`🐌 Slow query: ${operation} on ${resource} took ${duration}ms`);
1534
- },
1535
-
1536
- onHighErrorRate: (resource, errorRate) => {
1537
- console.error(`🚨 High error rate: ${resource} has ${(errorRate * 100).toFixed(1)}% errors`);
1538
- },
1539
-
1540
- onThresholdExceeded: (metric, value, threshold) => {
1541
- console.warn(`⚠️ Threshold exceeded: ${metric} = ${value} (threshold: ${threshold})`);
1542
- }
1543
- })]
1544
- });
1545
-
1546
- await s3db.connect();
1547
-
1548
- // Advanced metrics analysis class
1549
- class MetricsAnalyzer {
1550
- constructor(metricsPlugin) {
1551
- this.plugin = metricsPlugin;
1552
- this.alertHandlers = new Map();
1553
- }
1554
-
1555
- addAlertHandler(condition, handler) {
1556
- this.alertHandlers.set(condition, handler);
1557
- }
1558
-
1559
- async analyzePerformance(timeRange = 3600000) { // 1 hour
1560
- const metrics = await this.plugin.getMetrics();
1561
- const analysis = {
1562
- summary: {
1563
- totalOperations: metrics.usage.totalOperations,
1564
- avgResponseTime: metrics.performance.averageResponseTime,
1565
- errorRate: metrics.errors.errorRate,
1566
- slowQueries: metrics.performance.slowestOperations.length
1567
- },
1568
- recommendations: [],
1569
- alerts: []
1570
- };
1571
-
1572
- // Performance analysis
1573
- if (metrics.performance.averageResponseTime > 500) {
1574
- analysis.recommendations.push({
1575
- type: 'performance',
1576
- message: 'Average response time is high. Consider adding caching or optimizing queries.',
1577
- priority: 'high'
1578
- });
1579
- }
1580
-
1581
- // Error rate analysis
1582
- if (metrics.errors.errorRate > 0.02) { // 2%
1583
- analysis.alerts.push({
1584
- type: 'error_rate',
1585
- message: `Error rate (${(metrics.errors.errorRate * 100).toFixed(2)}%) exceeds threshold`,
1586
- severity: 'warning'
1587
- });
1588
- }
1589
-
1590
- // Resource usage patterns
1591
- const resourceUsage = Object.entries(metrics.usage.resources);
1592
- const imbalancedResources = resourceUsage.filter(([name, usage]) => {
1593
- const writes = usage.inserts + usage.updates + usage.deletes;
1594
- const reads = usage.reads;
1595
- return writes > 0 && (reads / writes) < 0.1; // Very low read/write ratio
1596
- });
1597
-
1598
- if (imbalancedResources.length > 0) {
1599
- analysis.recommendations.push({
1600
- type: 'usage_pattern',
1601
- message: `Resources with low read/write ratio: ${imbalancedResources.map(([name]) => name).join(', ')}`,
1602
- priority: 'medium'
1603
- });
1604
- }
1605
-
1606
- return analysis;
1607
- }
1608
-
1609
- async generateReport(format = 'console') {
1610
- const metrics = await this.plugin.getMetrics();
1611
- const analysis = await this.analyzePerformance();
1612
-
1613
- if (format === 'console') {
1614
- console.log('\n=== 📊 COMPREHENSIVE METRICS REPORT ===');
1615
-
1616
- // Performance Summary
1617
- console.log('\n🚀 Performance Summary:');
1618
- console.log(` Total Operations: ${analysis.summary.totalOperations.toLocaleString()}`);
1619
- console.log(` Average Response Time: ${analysis.summary.avgResponseTime}ms`);
1620
- console.log(` Error Rate: ${(analysis.summary.errorRate * 100).toFixed(2)}%`);
1621
- console.log(` Slow Queries: ${analysis.summary.slowQueries}`);
1622
-
1623
- // Operation Breakdown
1624
- console.log('\n⏱️ Operation Timing:');
1625
- Object.entries(metrics.performance.operationTiming).forEach(([op, timing]) => {
1626
- console.log(` ${op.toUpperCase()}:`);
1627
- console.log(` Average: ${timing.avg}ms`);
1628
- console.log(` Range: ${timing.min}ms - ${timing.max}ms`);
1629
- console.log(` Count: ${timing.total}`);
1630
- });
1631
-
1632
- // Resource Activity
1633
- console.log('\n📈 Resource Activity:');
1634
- Object.entries(metrics.usage.resources)
1635
- .sort(([,a], [,b]) => {
1636
- const totalA = Object.values(a).reduce((sum, val) => sum + val, 0);
1637
- const totalB = Object.values(b).reduce((sum, val) => sum + val, 0);
1638
- return totalB - totalA;
1639
- })
1640
- .forEach(([resource, usage]) => {
1641
- const total = Object.values(usage).reduce((sum, val) => sum + val, 0);
1642
- console.log(` ${resource}: ${total} operations`);
1643
- console.log(` Reads: ${usage.reads}, Writes: ${usage.inserts + usage.updates + usage.deletes}`);
1644
- });
1645
-
1646
- // Error Analysis
1647
- if (metrics.errors.total > 0) {
1648
- console.log('\n🚨 Error Analysis:');
1649
- console.log(` Total Errors: ${metrics.errors.total}`);
1650
- console.log(' By Type:');
1651
- Object.entries(metrics.errors.byType).forEach(([type, count]) => {
1652
- console.log(` ${type}: ${count}`);
1653
- });
1654
- }
1655
-
1656
- // Recommendations
1657
- if (analysis.recommendations.length > 0) {
1658
- console.log('\n💡 Recommendations:');
1659
- analysis.recommendations.forEach(rec => {
1660
- const emoji = rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟡' : '🟢';
1661
- console.log(` ${emoji} [${rec.priority.toUpperCase()}] ${rec.message}`);
1662
- });
1663
- }
1664
-
1665
- // Alerts
1666
- if (analysis.alerts.length > 0) {
1667
- console.log('\n⚠️ Active Alerts:');
1668
- analysis.alerts.forEach(alert => {
1669
- console.log(` 🚨 ${alert.message}`);
1670
- });
1671
- }
1672
- }
1673
-
1674
- return { metrics, analysis };
1675
- }
1676
-
1677
- async exportMetrics(filename) {
1678
- const metrics = await this.plugin.getMetrics();
1679
- const data = {
1680
- timestamp: new Date().toISOString(),
1681
- metrics,
1682
- analysis: await this.analyzePerformance()
1683
- };
1684
-
1685
- // In real implementation, save to file
1686
- console.log(`📁 Metrics exported to ${filename}`);
1687
- return data;
1688
- }
1689
-
1690
- startRealTimeMonitoring(interval = 5000) {
1691
- const monitor = setInterval(async () => {
1692
- const metrics = await this.plugin.getMetrics();
1693
-
1694
- // Check alert conditions
1695
- this.alertHandlers.forEach((handler, condition) => {
1696
- if (condition(metrics)) {
1697
- handler(metrics);
1698
- }
1699
- });
1700
-
1701
- // Auto-optimization suggestions
1702
- if (metrics.performance.averageResponseTime > 1000) {
1703
- console.log('💡 Suggestion: Consider implementing caching for frequently accessed data');
1704
- }
1705
-
1706
- if (metrics.errors.errorRate > 0.05) {
1707
- console.log('🚨 Alert: Error rate is above 5% - investigate immediately');
1708
- }
1709
-
1710
- }, interval);
1711
-
1712
- return monitor;
1713
- }
1714
- }
1715
-
1716
- // Simulate complex workload
1717
- const users = s3db.resource('users');
1718
- const products = s3db.resource('products');
1719
- const orders = s3db.resource('orders');
1720
-
1721
- // Setup metrics analyzer
1722
- const analyzer = new MetricsAnalyzer(s3db.plugins.metrics);
1723
-
1724
- // Add custom alert handlers
1725
- analyzer.addAlertHandler(
1726
- (metrics) => metrics.errors.errorRate > 0.03,
1727
- (metrics) => console.log('🚨 Error rate alert triggered!')
1728
- );
1729
-
1730
- analyzer.addAlertHandler(
1731
- (metrics) => metrics.performance.averageResponseTime > 800,
1732
- (metrics) => console.log('⏰ Performance degradation detected!')
1733
- );
1734
-
1735
- // Simulate workload
1736
- console.log('🔄 Simulating complex workload...');
1737
-
1738
- // Bulk operations
1739
- const userData = Array.from({ length: 50 }, (_, i) => ({
1740
- name: `User ${i}`,
1741
- email: `user${i}@example.com`,
1742
- role: i % 3 === 0 ? 'admin' : 'user'
1743
- }));
1744
-
1745
- await users.insertMany(userData);
1746
-
1747
- // Mixed operations with some errors
1748
- for (let i = 0; i < 20; i++) {
1749
- try {
1750
- await products.insert({
1751
- name: `Product ${i}`,
1752
- price: Math.random() * 100,
1753
- category: ['electronics', 'books', 'clothing'][i % 3]
1754
- });
1755
-
1756
- if (i % 5 === 0) {
1757
- // Simulate some slow operations
1758
- await new Promise(resolve => setTimeout(resolve, 600));
1759
- await products.list({ limit: 20 });
1760
- }
1761
-
1762
- if (i % 10 === 0) {
1763
- // Simulate some errors
1764
- try {
1765
- await products.get('non-existent-id');
1766
- } catch (error) {
1767
- // Expected error for testing
1768
- }
1769
- }
1770
-
1771
- } catch (error) {
1772
- // Handle errors
1773
- }
1774
- }
1775
-
1776
- // Generate comprehensive report
1777
- await analyzer.generateReport();
1778
-
1779
- // Start real-time monitoring
1780
- const monitor = analyzer.startRealTimeMonitoring(3000);
1781
-
1782
- // Export metrics for external analysis
1783
- await analyzer.exportMetrics('metrics-export.json');
1784
-
1785
- // Stop monitoring after demo
1786
- setTimeout(() => {
1787
- clearInterval(monitor);
1788
- console.log('\n✅ Metrics demonstration completed');
1789
- }, 15000);
1790
- ```
1791
-
1792
- ---
1793
-
1794
- ## 🔄 Replicator Plugin
1795
-
1796
- **Enterprise-grade data replication system** that synchronizes your s3db data in real-time to multiple targets including other S3DB instances, SQS queues, BigQuery, PostgreSQL databases, and more. Features robust error handling, advanced transformation capabilities, and comprehensive monitoring.
1797
-
1798
- ### 🎯 Key Features
1799
-
1800
- - **Real-time Replication**: Automatic data synchronization on insert, update, and delete operations
1801
- - **Multi-Target Support**: Replicate to S3DB, BigQuery, PostgreSQL, SQS, and custom targets
1802
- - **Advanced Transformations**: Transform data with custom functions before replication
1803
- - **Error Resilience**: Automatic retries, detailed error reporting, and dead letter queue support
1804
- - **Performance Monitoring**: Built-in metrics, performance tracking, and health monitoring
1805
- - **Flexible Configuration**: Support for multiple resource mapping syntaxes and selective replication
1806
-
1807
- ### ⚡ Quick Start
1808
-
1809
- ```javascript
1810
- import { S3db, ReplicatorPlugin } from 's3db.js';
1811
-
1812
- const s3db = new S3db({
1813
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1814
- plugins: [new ReplicatorPlugin({
1815
- verbose: true, // Enable detailed logging for debugging
1816
- replicators: [
1817
- {
1818
- driver: 's3db',
1819
- resources: ['users'],
1820
- config: {
1821
- connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
1822
- }
1823
- }
1824
- ]
1825
- })]
1826
- });
1827
-
1828
- await s3db.connect();
1829
-
1830
- // Data is automatically replicated with detailed error reporting
1831
- const users = s3db.resource('users');
1832
- await users.insert({ name: 'John', email: 'john@example.com' });
1833
- // This insert is automatically replicated to the backup database
1834
- ```
1835
-
1836
- ### ⚙️ Configuration Parameters
1837
-
1838
- | Parameter | Type | Default | Description |
1839
- |-----------|------|---------|-------------|
1840
- | `enabled` | boolean | `true` | Enable/disable replication globally |
1841
- | `replicators` | array | `[]` | Array of replicator configurations (required) |
1842
- | `verbose` | boolean | `false` | Enable detailed console logging for debugging |
1843
- | `persistReplicatorLog` | boolean | `false` | Store replication logs in database resource |
1844
- | `replicatorLogResource` | string | `'replicator_log'` | Name of log resource for persistence |
1845
- | `logErrors` | boolean | `true` | Log errors to replication log resource |
1846
- | `batchSize` | number | `100` | Batch size for bulk replication operations |
1847
- | `maxRetries` | number | `3` | Maximum retry attempts for failed replications |
1848
- | `timeout` | number | `30000` | Timeout for replication operations (ms) |
1849
-
1850
- ### 🗂️ Event System & Debugging
1851
-
1852
- The Replicator Plugin emits comprehensive events for monitoring and debugging:
1853
-
1854
- ```javascript
1855
- const replicatorPlugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
1856
-
1857
- // Success events
1858
- replicatorPlugin.on('replicated', (data) => {
1859
- console.log(`✅ Replicated: ${data.operation} on ${data.resourceName} to ${data.replicator}`);
1860
- });
1861
-
1862
- // Error events
1863
- replicatorPlugin.on('replicator_error', (data) => {
1864
- console.error(`❌ Replication failed: ${data.error} (${data.resourceName})`);
1865
- });
1866
-
1867
- // Log resource errors
1868
- replicatorPlugin.on('replicator_log_error', (data) => {
1869
- console.warn(`⚠️ Failed to log replication: ${data.logError}`);
1870
- });
1871
-
1872
- // Setup errors
1873
- replicatorPlugin.on('replicator_log_resource_creation_error', (data) => {
1874
- console.error(`🚨 Log resource creation failed: ${data.error}`);
1875
- });
1876
-
1877
- // Cleanup errors
1878
- replicatorPlugin.on('replicator_cleanup_error', (data) => {
1879
- console.warn(`🧹 Cleanup failed for ${data.replicator}: ${data.error}`);
1880
- });
1881
- ```
1882
-
1883
- ### 🔧 Replicator Drivers
1884
-
1885
- #### 🗃️ S3DB Replicator
1886
-
1887
- Replicate to another S3DB instance with **advanced resource mapping and transformation capabilities**. Supports multiple configuration syntaxes for maximum flexibility.
1888
-
1889
- **Basic Configuration:**
1890
- ```javascript
1891
- {
1892
- driver: 's3db',
1893
- config: {
1894
- connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
1895
- },
1896
- resources: {
1897
- // Simple resource mapping (replicate to same name)
1898
- users: 'users',
1899
-
1900
- // Map source → destination resource name
1901
- products: 'backup_products',
1902
-
1903
- // Advanced mapping with transform function
1904
- orders: {
1905
- resource: 'order_backup',
1906
- transform: (data) => ({
1907
- ...data,
1908
- backup_timestamp: new Date().toISOString(),
1909
- original_source: 'production',
1910
- migrated_at: new Date().toISOString()
1911
- }),
1912
- actions: ['insert', 'update', 'delete']
1913
- }
1914
- }
1915
- }
1916
- ```
1917
-
1918
- ### 📋 Resource Configuration Syntaxes
1919
-
1920
- The S3DB replicator supports **multiple configuration syntaxes** for maximum flexibility. You can mix and match these formats as needed:
1921
-
1922
- #### 1. Array of Resource Names
1923
- **Use case**: Simple backup/clone scenarios
1924
- ```javascript
1925
- resources: ['users', 'products', 'orders']
1926
- // Replicates each resource to itself in the destination database
1927
- ```
1928
-
1929
- #### 2. Simple Object Mapping
1930
- **Use case**: Rename resources during replication
1931
- ```javascript
1932
- resources: {
1933
- users: 'people', // users → people
1934
- products: 'items', // products → items
1935
- orders: 'order_history' // orders → order_history
1936
- }
1937
- ```
1938
-
1939
- #### 3. Object with Transform Function
1940
- **Use case**: Data transformation during replication ⭐ **RECOMMENDED**
1941
- ```javascript
1942
- resources: {
1943
- users: {
1944
- resource: 'people', // Destination resource name
1945
- transform: (data) => ({ // Data transformation function
1946
- ...data,
1947
- fullName: `${data.firstName} ${data.lastName}`,
1948
- migrated_at: new Date().toISOString(),
1949
- source_system: 'production'
1950
- }),
1951
- actions: ['insert', 'update', 'delete'] // Optional: which operations to replicate
1952
- }
1953
- }
1954
- ```
1955
-
1956
- #### 4. Function-Only Transformation
1957
- **Use case**: Transform data without changing resource name
1958
- ```javascript
1959
- resources: {
1960
- users: (data) => ({
1961
- ...data,
1962
- processed: true,
1963
- backup_date: new Date().toISOString(),
1964
- hash: crypto.createHash('md5').update(JSON.stringify(data)).digest('hex')
1965
- })
1966
- }
1967
- ```
1968
-
1969
- #### 5. Multi-Destination Replication
1970
- **Use case**: Send data to multiple targets with different transformations
1971
- ```javascript
1972
- resources: {
1973
- users: [
1974
- 'people', // Simple copy to 'people'
1975
- {
1976
- resource: 'user_analytics',
1977
- transform: (data) => ({ // Transformed copy to 'user_analytics'
1978
- id: data.id,
1979
- signup_date: data.createdAt,
1980
- user_type: data.role || 'standard',
1981
- last_activity: new Date().toISOString()
1982
- })
1983
- },
1984
- {
1985
- resource: 'audit_trail',
1986
- transform: (data) => ({ // Audit copy to 'audit_trail'
1987
- user_id: data.id,
1988
- action: 'user_replicated',
1989
- timestamp: new Date().toISOString(),
1990
- data_hash: crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex')
1991
- })
1992
- }
1993
- ]
1994
- }
1995
- ```
1996
-
1997
- #### 6. Advanced Mixed Configuration
1998
- **Use case**: Complex enterprise scenarios
1999
- ```javascript
2000
- resources: {
2001
- // Simple mappings
2002
- sessions: 'user_sessions',
2003
-
2004
- // Transform without renaming
2005
- products: (data) => ({
2006
- ...data,
2007
- search_keywords: data.name?.toLowerCase().split(' ') || [],
2008
- price_category: data.price > 100 ? 'premium' : 'standard'
2009
- }),
2010
-
2011
- // Complex multi-destination with conditions
2012
- orders: [
2013
- 'order_backup', // Simple backup
2014
- {
2015
- resource: 'order_analytics',
2016
- transform: (data) => ({
2017
- order_id: data.id,
2018
- customer_id: data.userId,
2019
- amount: data.amount,
2020
- order_date: data.createdAt?.split('T')[0],
2021
- status: data.status || 'pending',
2022
- item_count: data.items?.length || 0,
2023
- is_large_order: data.amount > 1000
2024
- }),
2025
- actions: ['insert', 'update'] // Only replicate inserts and updates, not deletes
2026
- },
2027
- {
2028
- resource: 'financial_records',
2029
- transform: (data) => ({
2030
- transaction_id: data.id,
2031
- amount: data.amount,
2032
- currency: data.currency || 'USD',
2033
- type: 'order_payment',
2034
- timestamp: new Date().toISOString(),
2035
- metadata: {
2036
- customer_id: data.userId,
2037
- order_items: data.items?.length || 0
2038
- }
2039
- }),
2040
- actions: ['insert'] // Only replicate new orders for financial records
2041
- }
2042
- ],
2043
-
2044
- // Conditional replication
2045
- users: {
2046
- resource: 'customer_profiles',
2047
- transform: (data) => {
2048
- // Only replicate active users
2049
- if (data.status !== 'active') return null;
2050
-
2051
- return {
2052
- ...data,
2053
- fullName: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
2054
- account_age_days: data.createdAt ?
2055
- Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0,
2056
- preferences: data.preferences || {},
2057
- last_updated: new Date().toISOString()
2058
- };
2059
- },
2060
- actions: ['insert', 'update']
2061
- }
2062
- }
2063
- ```
2064
-
2065
- ### 🔧 S3DB Replicator Configuration Options
2066
-
2067
- | Parameter | Type | Required | Description |
2068
- |-----------|------|----------|-------------|
2069
- | `connectionString` | string | Yes* | S3DB connection string for destination database |
2070
- | `client` | S3db | Yes* | Pre-configured S3DB client instance |
2071
- | `resources` | object/array | Yes | Resource mapping configuration (see syntaxes above) |
2072
- | `timeout` | number | No | Operation timeout in milliseconds (default: 30000) |
2073
- | `retryAttempts` | number | No | Number of retry attempts for failed operations (default: 3) |
2074
-
2075
- *Either `connectionString` or `client` must be provided.
2076
-
2077
- ### 🎯 Transform Function Features
2078
-
2079
- Transform functions provide powerful data manipulation capabilities:
2080
-
2081
- #### Data Transformation Examples:
2082
- ```javascript
2083
- // 1. Field mapping and enrichment
2084
- transform: (data) => ({
2085
- id: data.id,
2086
- customer_name: `${data.firstName} ${data.lastName}`,
2087
- email_domain: data.email?.split('@')[1] || 'unknown',
2088
- created_timestamp: Date.now(),
2089
- source: 'production-db'
2090
- })
2091
-
2092
- // 2. Conditional logic
2093
- transform: (data) => {
2094
- if (data.type === 'premium') {
2095
- return { ...data, priority: 'high', sla: '4hours' };
2096
- }
2097
- return { ...data, priority: 'normal', sla: '24hours' };
2098
- }
2099
-
2100
- // 3. Data validation and filtering
2101
- transform: (data) => {
2102
- // Skip replication for invalid data
2103
- if (!data.email || !data.name) return null;
2104
-
2105
- return {
2106
- ...data,
2107
- email: data.email.toLowerCase(),
2108
- name: data.name.trim(),
2109
- validated: true
2110
- };
2111
- }
2112
-
2113
- // 4. Computed fields
2114
- transform: (data) => ({
2115
- ...data,
2116
- age: data.birthDate ?
2117
- Math.floor((Date.now() - new Date(data.birthDate)) / (1000 * 60 * 60 * 24 * 365)) : null,
2118
- account_value: (data.orders || []).reduce((sum, order) => sum + order.amount, 0),
2119
- last_activity: new Date().toISOString()
2120
- })
2121
-
2122
- // 5. Data aggregation
2123
- transform: (data) => ({
2124
- user_id: data.id,
2125
- total_orders: data.orderHistory?.length || 0,
2126
- total_spent: data.orderHistory?.reduce((sum, order) => sum + order.amount, 0) || 0,
2127
- favorite_category: data.orderHistory?.map(o => o.category)
2128
- .sort((a,b) => a.localeCompare(b))
2129
- .reduce((prev, curr, i, arr) =>
2130
- arr.filter(v => v === prev).length >= arr.filter(v => v === curr).length ? prev : curr
2131
- ) || null,
2132
- analysis_date: new Date().toISOString()
2133
- })
2134
- ```
2135
-
2136
- **Transform Function Best Practices:**
2137
- - **Return `null`** to skip replication for specific records
2138
- - **Preserve the `id` field** unless specifically mapping to a different field
2139
- - **Handle edge cases** like missing fields or null values
2140
- - **Use immutable operations** to avoid modifying the original data
2141
- - **Keep transforms lightweight** to maintain replication performance
2142
- - **Add metadata fields** like timestamps for tracking purposes
2143
-
2144
- #### 📬 SQS Replicator
2145
-
2146
- **Real-time event streaming** to AWS SQS queues for microservices integration and event-driven architectures.
2147
-
2148
- **⚠️ Required Dependency:**
2149
- ```bash
2150
- pnpm add @aws-sdk/client-sqs
2151
- ```
2152
-
2153
- **Basic Configuration:**
2154
- ```javascript
2155
- {
2156
- driver: 'sqs',
2157
- resources: ['orders', 'users'],
2158
- config: {
2159
- region: 'us-east-1',
2160
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/events.fifo',
2161
- messageGroupId: 's3db-events',
2162
- deduplicationId: true
2163
- }
2164
- }
2165
- ```
2166
-
2167
- **Advanced Configuration with Resource-Specific Queues:**
2168
- ```javascript
2169
- {
2170
- driver: 'sqs',
2171
- config: {
2172
- region: 'us-east-1',
2173
- credentials: {
2174
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
2175
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
2176
- },
2177
-
2178
- // Resource-specific queue URLs
2179
- queues: {
2180
- orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/order-events.fifo',
2181
- users: 'https://sqs.us-east-1.amazonaws.com/123456789012/user-events.fifo',
2182
- payments: 'https://sqs.us-east-1.amazonaws.com/123456789012/payment-events.fifo'
2183
- },
2184
-
2185
- // Default queue for resources not specifically mapped
2186
- defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/general-events.fifo',
2187
-
2188
- // FIFO queue settings
2189
- messageGroupId: 's3db-replicator',
2190
- deduplicationId: true,
2191
-
2192
- // Message attributes (applied to all messages)
2193
- messageAttributes: {
2194
- source: { StringValue: 'production-db', DataType: 'String' },
2195
- version: { StringValue: '1.0', DataType: 'String' },
2196
- environment: { StringValue: process.env.NODE_ENV || 'development', DataType: 'String' }
2197
- }
2198
- },
2199
- resources: {
2200
- // Simple resource mapping
2201
- orders: true,
2202
- users: true,
2203
-
2204
- // Resource with transformation
2205
- payments: {
2206
- transform: (data) => ({
2207
- payment_id: data.id,
2208
- amount: data.amount,
2209
- currency: data.currency || 'USD',
2210
- customer_id: data.userId,
2211
- payment_method: data.method,
2212
- status: data.status,
2213
- timestamp: new Date().toISOString(),
2214
-
2215
- // Add computed fields
2216
- amount_usd: data.currency === 'USD' ? data.amount : data.amount * (data.exchange_rate || 1),
2217
- is_large_payment: data.amount > 1000,
2218
- risk_score: data.amount > 5000 ? 'high' : data.amount > 1000 ? 'medium' : 'low'
2219
- })
2220
- }
2221
- }
2222
- }
2223
- ```
2224
-
2225
- **SQS Message Format:**
2226
- ```javascript
2227
- {
2228
- // Message Body (JSON string)
2229
- MessageBody: JSON.stringify({
2230
- resource: 'orders', // Source resource name
2231
- operation: 'insert', // Operation: insert, update, delete
2232
- id: 'order-123', // Record ID
2233
- data: { // Transformed data payload
2234
- id: 'order-123',
2235
- userId: 'user-456',
2236
- amount: 99.99,
2237
- status: 'pending',
2238
- timestamp: '2024-01-15T10:30:00.000Z'
2239
- },
2240
- beforeData: null, // Previous data for updates
2241
- metadata: {
2242
- source: 'production-db',
2243
- replicator: 'sqs-replicator',
2244
- timestamp: '2024-01-15T10:30:00.000Z'
2245
- }
2246
- }),
2247
-
2248
- // Message Attributes
2249
- MessageAttributes: {
2250
- source: { StringValue: 'production-db', DataType: 'String' },
2251
- resource: { StringValue: 'orders', DataType: 'String' },
2252
- operation: { StringValue: 'insert', DataType: 'String' }
2253
- },
2254
-
2255
- // FIFO Queue Settings
2256
- MessageGroupId: 's3db-events',
2257
- MessageDeduplicationId: 'orders:insert:order-123'
2258
- }
2259
- ```
2260
-
2261
- **Configuration Options:**
2262
-
2263
- | Parameter | Type | Required | Description |
2264
- |-----------|------|----------|-------------|
2265
- | `region` | string | Yes | AWS region for SQS service |
2266
- | `queueUrl` | string | Yes* | Single queue URL for all resources |
2267
- | `queues` | object | Yes* | Resource-specific queue mapping |
2268
- | `defaultQueueUrl` | string | No | Fallback queue for unmapped resources |
2269
- | `credentials` | object | No | AWS credentials (uses default chain if omitted) |
2270
- | `messageGroupId` | string | No | Message group ID for FIFO queues |
2271
- | `deduplicationId` | boolean | No | Enable message deduplication for FIFO queues |
2272
- | `messageAttributes` | object | No | Custom message attributes applied to all messages |
2273
-
2274
- *Either `queueUrl` or `queues` must be provided.
2275
-
2276
- #### 📊 BigQuery Replicator
2277
-
2278
- **Data warehouse integration** for Google BigQuery with advanced transformation capabilities, multi-table support, and automatic retry logic for streaming buffer limitations.
2279
-
2280
- **⚠️ Required Dependency:**
2281
- ```bash
2282
- pnpm add @google-cloud/bigquery
2283
- ```
2284
-
2285
- **Basic Configuration:**
2286
- ```javascript
2287
- {
2288
- driver: 'bigquery',
2289
- config: {
2290
- projectId: 'my-analytics-project',
2291
- datasetId: 'production_data',
2292
- location: 'US',
2293
- credentials: {
2294
- client_email: 'service-account@project.iam.gserviceaccount.com',
2295
- private_key: process.env.BIGQUERY_PRIVATE_KEY,
2296
- project_id: 'my-analytics-project'
2297
- }
2298
- },
2299
- resources: {
2300
- // Simple table mapping
2301
- users: 'users_table',
2302
- orders: 'orders_table',
2303
- products: 'products_table'
2304
- }
2305
- }
2306
- ```
2307
-
2308
- **Advanced Multi-Table Configuration:**
2309
- ```javascript
2310
- {
2311
- driver: 'bigquery',
2312
- config: {
2313
- projectId: 'my-analytics-project',
2314
- datasetId: 'analytics_warehouse',
2315
- location: 'US',
2316
- logTable: 'replication_audit_log', // Optional: audit all operations
2317
- credentials: JSON.parse(Buffer.from(process.env.GOOGLE_CREDENTIALS, 'base64').toString())
2318
- },
2319
- resources: {
2320
- // Simple string mapping
2321
- sessions: 'user_sessions',
2322
- clicks: 'click_events',
2323
-
2324
- // Single table with actions and transforms
2325
- users: {
2326
- table: 'dim_users',
2327
- actions: ['insert', 'update'],
2328
- transform: (data) => ({
2329
- ...data,
2330
- // Data cleaning and enrichment
2331
- email: data.email?.toLowerCase(),
2332
- full_name: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
2333
- registration_date: data.createdAt?.split('T')[0],
2334
- account_age_days: data.createdAt ?
2335
- Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0,
2336
-
2337
- // Computed fields for analytics
2338
- user_tier: data.totalSpent > 1000 ? 'premium' :
2339
- data.totalSpent > 100 ? 'standard' : 'basic',
2340
- is_active: data.lastLoginAt &&
2341
- (Date.now() - new Date(data.lastLoginAt)) < (30 * 24 * 60 * 60 * 1000),
2342
-
2343
- // BigQuery-specific fields
2344
- processed_at: new Date().toISOString(),
2345
- data_source: 'production_s3db'
2346
- })
2347
- },
2348
-
2349
- // Multiple destinations for comprehensive analytics
2350
- orders: [
2351
- // Fact table for detailed order data
2352
- {
2353
- table: 'fact_orders',
2354
- actions: ['insert', 'update'],
2355
- transform: (data) => ({
2356
- order_id: data.id,
2357
- customer_id: data.userId,
2358
- order_date: data.createdAt?.split('T')[0],
2359
- order_timestamp: data.createdAt,
2360
- amount: parseFloat(data.amount) || 0,
2361
- currency: data.currency || 'USD',
2362
- status: data.status,
2363
- item_count: data.items?.length || 0,
2364
-
2365
- // Derived analytics fields
2366
- is_weekend_order: new Date(data.createdAt).getDay() % 6 === 0,
2367
- order_hour: new Date(data.createdAt).getHours(),
2368
- is_large_order: data.amount > 500,
2369
-
2370
- // Metadata
2371
- ingested_at: new Date().toISOString(),
2372
- source_system: 'production'
2373
- })
2374
- },
2375
-
2376
- // Daily aggregation table
2377
- {
2378
- table: 'daily_revenue_summary',
2379
- actions: ['insert'], // Only for new orders
2380
- transform: (data) => ({
2381
- date: data.createdAt?.split('T')[0],
2382
- revenue: parseFloat(data.amount) || 0,
2383
- currency: data.currency || 'USD',
2384
- order_count: 1,
2385
- customer_id: data.userId,
2386
-
2387
- // Additional dimensions
2388
- order_source: data.source || 'web',
2389
- payment_method: data.paymentMethod,
2390
- is_first_order: data.isFirstOrder || false,
2391
-
2392
- created_at: new Date().toISOString()
2393
- })
2394
- },
2395
-
2396
- // Customer analytics (updates only)
2397
- {
2398
- table: 'customer_order_analytics',
2399
- actions: ['insert', 'update'],
2400
- transform: (data) => ({
2401
- customer_id: data.userId,
2402
- latest_order_id: data.id,
2403
- latest_order_date: data.createdAt?.split('T')[0],
2404
- latest_order_amount: parseFloat(data.amount) || 0,
2405
-
2406
- // Will need to be aggregated in BigQuery
2407
- lifetime_value_increment: parseFloat(data.amount) || 0,
2408
-
2409
- updated_at: new Date().toISOString()
2410
- })
2411
- }
2412
- ],
2413
-
2414
- // Event tracking with conditional replication
2415
- events: {
2416
- table: 'user_events',
2417
- actions: ['insert'],
2418
- transform: (data) => {
2419
- // Only replicate certain event types
2420
- const allowedEventTypes = ['page_view', 'button_click', 'form_submit', 'purchase'];
2421
- if (!allowedEventTypes.includes(data.event_type)) {
2422
- return null; // Skip replication
2423
- }
2424
-
2425
- return {
2426
- event_id: data.id,
2427
- user_id: data.userId,
2428
- event_type: data.event_type,
2429
- event_timestamp: data.timestamp,
2430
- event_date: data.timestamp?.split('T')[0],
2431
-
2432
- // Parse and clean event properties
2433
- properties: JSON.stringify(data.properties || {}),
2434
- page_url: data.properties?.page_url,
2435
- referrer: data.properties?.referrer,
2436
-
2437
- // Session information
2438
- session_id: data.sessionId,
2439
-
2440
- // Technical metadata
2441
- user_agent: data.userAgent,
2442
- ip_address: data.ipAddress ? 'masked' : null, // Privacy compliance
2443
-
2444
- // Processing metadata
2445
- processed_at: new Date().toISOString(),
2446
- schema_version: '1.0'
2447
- };
2448
- }
2449
- }
2450
- }
2451
- }
2452
- ```
2453
-
2454
- **Transform Function Features:**
2455
-
2456
- 1. **Data Cleaning & Validation:**
2457
- ```javascript
2458
- transform: (data) => {
2459
- // Skip invalid records
2460
- if (!data.email || !data.id) return null;
2461
-
2462
- return {
2463
- ...data,
2464
- email: data.email.toLowerCase().trim(),
2465
- phone: data.phone?.replace(/\D/g, '') || null, // Remove non-digits
2466
- validated_at: new Date().toISOString()
2467
- };
2468
- }
2469
- ```
2470
-
2471
- 2. **Type Conversion for BigQuery:**
2472
- ```javascript
2473
- transform: (data) => ({
2474
- ...data,
2475
- amount: parseFloat(data.amount) || 0,
2476
- quantity: parseInt(data.quantity) || 0,
2477
- is_active: Boolean(data.isActive),
2478
- tags: Array.isArray(data.tags) ? data.tags : [],
2479
- metadata: JSON.stringify(data.metadata || {})
2480
- })
2481
- ```
2482
-
2483
- 3. **Computed Analytics Fields:**
2484
- ```javascript
2485
- transform: (data) => ({
2486
- ...data,
2487
- // Date dimensions
2488
- order_date: data.createdAt?.split('T')[0],
2489
- order_year: new Date(data.createdAt).getFullYear(),
2490
- order_month: new Date(data.createdAt).getMonth() + 1,
2491
- order_quarter: Math.ceil((new Date(data.createdAt).getMonth() + 1) / 3),
2492
-
2493
- // Business logic
2494
- customer_segment: data.totalSpent > 1000 ? 'VIP' :
2495
- data.totalSpent > 500 ? 'Premium' : 'Standard',
2496
-
2497
- // Geospatial (if you have coordinates)
2498
- location_string: data.lat && data.lng ? `${data.lat},${data.lng}` : null
2499
- })
2500
- ```
2501
-
2502
- **Configuration Options:**
2503
-
2504
- | Parameter | Type | Required | Description |
2505
- |-----------|------|----------|-------------|
2506
- | `projectId` | string | Yes | Google Cloud project ID |
2507
- | `datasetId` | string | Yes | BigQuery dataset ID |
2508
- | `credentials` | object | No | Service account credentials (uses ADC if omitted) |
2509
- | `location` | string | No | Dataset location/region (default: 'US') |
2510
- | `logTable` | string | No | Table name for operation audit logging |
2511
-
2512
- **Resource Configuration Formats:**
2513
-
2514
- 1. **String**: Simple table mapping
2515
- ```javascript
2516
- resources: { users: 'users_table' }
2517
- ```
2518
-
2519
- 2. **Object**: Single table with actions and transform
2520
- ```javascript
2521
- resources: {
2522
- users: {
2523
- table: 'users_table',
2524
- actions: ['insert', 'update'],
2525
- transform: (data) => ({ ...data, processed: true })
2526
- }
2527
- }
2528
- ```
2529
-
2530
- 3. **Array**: Multiple destination tables
2531
- ```javascript
2532
- resources: {
2533
- orders: [
2534
- { table: 'fact_orders', actions: ['insert'] },
2535
- { table: 'daily_revenue', actions: ['insert'], transform: aggregationFn }
2536
- ]
2537
- }
2538
- ```
2539
-
2540
- **Automatic Features:**
2541
-
2542
- - **🔄 Retry Logic**: Handles BigQuery streaming buffer limitations with 30-second delays
2543
- - **🛡️ Error Handling**: Graceful handling of schema mismatches and quota limits
2544
- - **📋 Operation Logging**: Optional audit trail in specified log table
2545
- - **🔧 Schema Compatibility**: Automatic handling of missing fields and type coercion
2546
- - **⚡ Streaming Inserts**: Uses BigQuery streaming API for real-time data ingestion
2547
- - **🎯 Selective Replication**: Transform functions can return `null` to skip records
2548
-
2549
- #### 🐘 PostgreSQL Replicator
2550
-
2551
- **Operational database integration** for PostgreSQL with support for complex SQL operations, connection pooling, and custom transformations.
2552
-
2553
- **⚠️ Required Dependency:**
2554
- ```bash
2555
- pnpm add pg
2556
- ```
2557
-
2558
- **Basic Configuration:**
2559
- ```javascript
2560
- {
2561
- driver: 'postgres',
2562
- config: {
2563
- connectionString: 'postgresql://user:pass@localhost:5432/analytics',
2564
- ssl: { rejectUnauthorized: false },
2565
- pool: {
2566
- max: 10,
2567
- idleTimeoutMillis: 30000,
2568
- connectionTimeoutMillis: 2000
2569
- }
2570
- },
2571
- resources: {
2572
- users: [{
2573
- table: 'users_table',
2574
- actions: ['insert', 'update', 'delete']
2575
- }]
2576
- }
2577
- }
2578
- ```
2579
-
2580
- **Advanced Configuration with Custom SQL:**
2581
- ```javascript
2582
- {
2583
- driver: 'postgres',
2584
- config: {
2585
- connectionString: 'postgresql://analytics:password@localhost:5432/operations',
2586
- ssl: { rejectUnauthorized: false },
2587
- logTable: 'replication_audit',
2588
- pool: {
2589
- max: 20,
2590
- min: 5,
2591
- idleTimeoutMillis: 30000,
2592
- connectionTimeoutMillis: 2000,
2593
- acquireTimeoutMillis: 60000
2594
- }
2595
- },
2596
- resources: {
2597
- // Multi-table replication with different strategies
2598
- users: [
2599
- {
2600
- table: 'operational_users',
2601
- actions: ['insert', 'update', 'delete'],
2602
- transform: (data, operation) => {
2603
- if (operation === 'delete') {
2604
- return {
2605
- id: data.id,
2606
- deleted_at: new Date(),
2607
- deletion_reason: 'user_requested'
2608
- };
2609
- }
2610
-
2611
- return {
2612
- ...data,
2613
- sync_timestamp: new Date(),
2614
- source_system: 's3db',
2615
- data_version: '1.0',
2616
-
2617
- // Computed fields
2618
- full_name: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
2619
- email_domain: data.email?.split('@')[1] || 'unknown',
2620
- account_age_days: data.createdAt ?
2621
- Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0
2622
- };
2623
- }
2624
- },
2625
-
2626
- // Separate audit trail table
2627
- {
2628
- table: 'user_audit_trail',
2629
- actions: ['insert', 'update', 'delete'],
2630
- transform: (data, operation) => ({
2631
- user_id: data.id,
2632
- operation_type: operation,
2633
- operation_timestamp: new Date(),
2634
- data_snapshot: JSON.stringify(data),
2635
- source_database: 's3db',
2636
- replication_id: crypto.randomUUID()
2637
- })
2638
- }
2639
- ],
2640
-
2641
- // Orders with complex business logic
2642
- orders: [
2643
- {
2644
- table: 'order_events',
2645
- actions: ['insert'],
2646
- transform: (data) => ({
2647
- event_id: crypto.randomUUID(),
2648
- order_id: data.id,
2649
- customer_id: data.userId,
2650
- event_type: 'order_created',
2651
- event_timestamp: new Date(),
2652
- order_amount: parseFloat(data.amount) || 0,
2653
- order_status: data.status || 'pending',
2654
-
2655
- // Business metrics
2656
- is_large_order: data.amount > 500,
2657
- order_priority: data.amount > 1000 ? 'high' :
2658
- data.amount > 100 ? 'medium' : 'low',
2659
- estimated_fulfillment: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days
2660
-
2661
- // Metadata
2662
- created_at: new Date(),
2663
- source_system: 'production_s3db'
2664
- })
2665
- },
2666
-
2667
- {
2668
- table: 'order_updates',
2669
- actions: ['update'],
2670
- transform: (data, operation, beforeData) => ({
2671
- update_id: crypto.randomUUID(),
2672
- order_id: data.id,
2673
- updated_at: new Date(),
2674
-
2675
- // Track what changed
2676
- changed_fields: Object.keys(data).filter(key =>
2677
- beforeData && data[key] !== beforeData[key]
2678
- ),
2679
- previous_status: beforeData?.status,
2680
- new_status: data.status,
2681
-
2682
- // Change metadata
2683
- status_progression: `${beforeData?.status || 'unknown'} -> ${data.status}`,
2684
- update_source: 'automated_replication'
2685
- })
2686
- }
2687
- ],
2688
-
2689
- // Conditional replication based on data
2690
- sensitive_data: {
2691
- table: 'masked_sensitive_data',
2692
- actions: ['insert', 'update'],
2693
- transform: (data) => {
2694
- // Skip replication for certain sensitive records
2695
- if (data.privacy_level === 'restricted') {
2696
- return null;
2697
- }
2698
-
2699
- return {
2700
- id: data.id,
2701
- // Mask sensitive fields
2702
- email: data.email ? `${data.email.split('@')[0].slice(0, 3)}***@${data.email.split('@')[1]}` : null,
2703
- phone: data.phone ? `***-***-${data.phone.slice(-4)}` : null,
2704
- name: data.name ? `${data.name.charAt(0)}${'*'.repeat(data.name.length - 1)}` : null,
2705
-
2706
- // Keep non-sensitive data
2707
- created_at: data.createdAt,
2708
- status: data.status,
2709
- category: data.category,
2710
-
2711
- // Add masking metadata
2712
- masked_at: new Date(),
2713
- masking_version: '1.0'
2714
- };
2715
- }
2716
- }
2717
- }
2718
- }
2719
- ```
2720
-
2721
- **Configuration Options:**
2722
-
2723
- | Parameter | Type | Required | Description |
2724
- |-----------|------|----------|-------------|
2725
- | `connectionString` | string | Yes | PostgreSQL connection string |
2726
- | `ssl` | object | No | SSL configuration options |
2727
- | `pool` | object | No | Connection pool configuration |
2728
- | `logTable` | string | No | Table name for operation audit logging |
2729
-
2730
- **Pool Configuration Options:**
2731
-
2732
- | Parameter | Type | Default | Description |
2733
- |-----------|------|---------|-------------|
2734
- | `max` | number | `10` | Maximum number of connections in pool |
2735
- | `min` | number | `0` | Minimum number of connections in pool |
2736
- | `idleTimeoutMillis` | number | `10000` | How long a client is allowed to remain idle |
2737
- | `connectionTimeoutMillis` | number | `0` | Return an error after timeout |
2738
- | `acquireTimeoutMillis` | number | `0` | Return an error if no connection available |
2739
-
2740
- **Resource Configuration:**
2741
-
2742
- Resources must be configured as arrays of table configurations:
2743
-
2744
- ```javascript
2745
- resources: {
2746
- resourceName: [
2747
- {
2748
- table: 'destination_table',
2749
- actions: ['insert', 'update', 'delete'],
2750
- transform: (data, operation, beforeData) => ({
2751
- // Return transformed data or null to skip
2752
- })
2753
- }
2754
- ]
2755
- }
2756
- ```
2757
-
2758
- **Automatic Features:**
2759
-
2760
- - **🔄 Connection Pooling**: Efficient database connection management
2761
- - **📋 Audit Logging**: Optional audit trail in specified log table
2762
- - **🛡️ Error Handling**: Graceful handling of connection failures and SQL errors
2763
- - **⚡ Bulk Operations**: Optimized for high-throughput replication
2764
- - **🎯 Flexible Actions**: Support for insert, update, delete operations
2765
- - **🔧 Custom SQL**: Transform functions receive operation context for complex logic
2766
-
2767
- ### 🔍 Monitoring & Health Checks
2768
-
2769
- Monitor replication health and performance with built-in tools:
2770
-
2771
- ```javascript
2772
- // Get replication status for all replicators
2773
- const replicationStatus = await replicatorPlugin.getReplicationStats();
2774
-
2775
- console.log('Replication Health:', {
2776
- totalReplicators: replicationStatus.replicators.length,
2777
- activeReplicators: replicationStatus.replicators.filter(r => r.status.enabled).length,
2778
- lastSync: replicationStatus.lastSync,
2779
- errorRate: replicationStatus.stats.errorRate
2780
- });
2781
-
2782
- // Check individual replicator health
2783
- for (const replicator of replicationStatus.replicators) {
2784
- console.log(`${replicator.driver} replicator:`, {
2785
- enabled: replicator.status.enabled,
2786
- connected: replicator.status.connected,
2787
- lastActivity: replicator.status.lastActivity,
2788
- totalOperations: replicator.status.totalOperations,
2789
- errorCount: replicator.status.errorCount
2790
- });
2791
- }
2792
-
2793
- // Get detailed replication logs
2794
- const replicationLogs = await replicatorPlugin.getReplicationLogs({
2795
- resourceName: 'users', // Filter by resource
2796
- status: 'failed', // Show only failed replications
2797
- limit: 50, // Limit results
2798
- offset: 0 // Pagination
2799
- });
2800
-
2801
- console.log('Recent failures:', replicationLogs.map(log => ({
2802
- timestamp: log.timestamp,
2803
- resource: log.resourceName,
2804
- operation: log.operation,
2805
- error: log.error,
2806
- replicator: log.replicator
2807
- })));
2808
-
2809
- // Retry failed replications
2810
- const retryResult = await replicatorPlugin.retryFailedReplications();
2811
- console.log(`Retried ${retryResult.retried} failed replications`);
2812
- ```
2813
-
2814
- ### 🚨 Troubleshooting Guide
2815
-
2816
- #### Common Issues and Solutions
2817
-
2818
- **1. Replication Not Happening**
2819
- ```javascript
2820
- // Check if replicator is enabled
2821
- const plugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
2822
- console.log('Plugin enabled:', plugin.config.enabled);
2823
-
2824
- // Check resource configuration
2825
- console.log('Resources configured:', Object.keys(plugin.config.replicators[0].resources));
2826
-
2827
- // Listen for debug events
2828
- plugin.on('replicator_error', (error) => {
2829
- console.error('Replication error:', error);
2830
- });
2831
- ```
2832
-
2833
- **2. Transform Function Errors**
2834
- ```javascript
2835
- // Add error handling in transform functions
2836
- transform: (data) => {
2837
- try {
2838
- return {
2839
- ...data,
2840
- fullName: `${data.firstName} ${data.lastName}`,
2841
- processedAt: new Date().toISOString()
2842
- };
2843
- } catch (error) {
2844
- console.error('Transform error:', error, 'Data:', data);
2845
- return data; // Fallback to original data
2846
- }
2847
- }
2848
- ```
2849
-
2850
- **3. Connection Issues**
2851
- ```javascript
2852
- // Test replicator connections
2853
- const testResults = await Promise.allSettled(
2854
- replicatorPlugin.replicators.map(async (replicator) => {
2855
- try {
2856
- const result = await replicator.testConnection();
2857
- return { replicator: replicator.id, success: result, error: null };
2858
- } catch (error) {
2859
- return { replicator: replicator.id, success: false, error: error.message };
2860
- }
2861
- })
2862
- );
2863
-
2864
- testResults.forEach(result => {
2865
- if (result.status === 'fulfilled') {
2866
- console.log(`${result.value.replicator}: ${result.value.success ? '✅' : '❌'} ${result.value.error || ''}`);
2867
- }
2868
- });
2869
- ```
2870
-
2871
- **4. Performance Issues**
2872
- ```javascript
2873
- // Monitor replication performance
2874
- const performanceMonitor = setInterval(async () => {
2875
- const stats = await replicatorPlugin.getReplicationStats();
2876
-
2877
- console.log('Performance Metrics:', {
2878
- operationsPerSecond: stats.operationsPerSecond,
2879
- averageLatency: stats.averageLatency,
2880
- queueSize: stats.queueSize,
2881
- memoryUsage: process.memoryUsage()
2882
- });
2883
-
2884
- // Alert on performance degradation
2885
- if (stats.averageLatency > 5000) { // 5 seconds
2886
- console.warn('⚠️ High replication latency detected:', stats.averageLatency + 'ms');
2887
- }
2888
- }, 30000); // Check every 30 seconds
2889
- ```
2890
-
2891
- ### 🎯 Advanced Use Cases
2892
-
2893
- #### Multi-Environment Replication Pipeline
2894
-
2895
- ```javascript
2896
- const replicatorPlugin = new ReplicatorPlugin({
2897
- verbose: true,
2898
- persistReplicatorLog: true,
2899
- replicators: [
2900
- // Production backup
2901
- {
2902
- driver: 's3db',
2903
- config: { connectionString: process.env.BACKUP_DB_URL },
2904
- resources: ['users', 'orders', 'products']
2905
- },
2906
-
2907
- // Staging environment sync
2908
- {
2909
- driver: 's3db',
2910
- config: { connectionString: process.env.STAGING_DB_URL },
2911
- resources: {
2912
- users: {
2913
- resource: 'users',
2914
- transform: (data) => ({
2915
- ...data,
2916
- // Remove PII for staging
2917
- email: data.email?.replace(/(.{2})(.*)(@.*)/, '$1***$3'),
2918
- phone: '***-***-' + (data.phone?.slice(-4) || '0000')
2919
- })
2920
- }
2921
- }
2922
- },
2923
-
2924
- // Analytics warehouse
2925
- {
2926
- driver: 'bigquery',
2927
- config: {
2928
- projectId: 'analytics-project',
2929
- datasetId: 'production_data'
2930
- },
2931
- resources: {
2932
- orders: [
2933
- { table: 'fact_orders', actions: ['insert'] },
2934
- { table: 'daily_revenue', actions: ['insert'], transform: aggregateDaily }
2935
- ]
2936
- }
2937
- },
2938
-
2939
- // Real-time events
2940
- {
2941
- driver: 'sqs',
2942
- config: {
2943
- region: 'us-east-1',
2944
- queues: {
2945
- users: process.env.USER_EVENTS_QUEUE,
2946
- orders: process.env.ORDER_EVENTS_QUEUE
2947
- }
2948
- },
2949
- resources: ['users', 'orders']
2950
- }
2951
- ]
2952
- });
2953
- ```
2954
-
2955
- #### Conditional Replication Based on Business Rules
2956
-
2957
- ```javascript
2958
- const businessRulesReplicator = new ReplicatorPlugin({
2959
- replicators: [
2960
- {
2961
- driver: 's3db',
2962
- config: { connectionString: process.env.COMPLIANCE_DB_URL },
2963
- resources: {
2964
- users: {
2965
- resource: 'compliant_users',
2966
- transform: (data) => {
2967
- // Only replicate users from certain regions
2968
- const allowedRegions = ['US', 'EU', 'CA'];
2969
- if (!allowedRegions.includes(data.region)) {
2970
- return null; // Skip replication
2971
- }
2972
-
2973
- // Apply data retention rules
2974
- const accountAge = Date.now() - new Date(data.createdAt);
2975
- const maxAge = 7 * 365 * 24 * 60 * 60 * 1000; // 7 years
2976
-
2977
- if (accountAge > maxAge && data.status === 'inactive') {
2978
- return null; // Skip old inactive accounts
2979
- }
2980
-
2981
- return {
2982
- ...data,
2983
- complianceVersion: '2.0',
2984
- lastAudit: new Date().toISOString(),
2985
- retentionCategory: accountAge > maxAge * 0.8 ? 'pending_review' : 'active'
2986
- };
2987
- }
2988
- },
2989
-
2990
- orders: {
2991
- resource: 'audit_orders',
2992
- transform: (data) => {
2993
- // Only replicate high-value orders for compliance
2994
- if (data.amount < 10000) return null;
2995
-
2996
- return {
2997
- order_id: data.id,
2998
- customer_id: data.userId,
2999
- amount: data.amount,
3000
- currency: data.currency,
3001
- order_date: data.createdAt,
3002
- compliance_flag: 'high_value',
3003
- audit_required: true,
3004
- retention_years: 10
3005
- };
3006
- }
3007
- }
3008
- }
3009
- }
3010
- ]
3011
- });
3012
- ```
3013
-
3014
- #### Event-Driven Architecture Integration
3015
-
3016
- ```javascript
3017
- // Custom event handlers for complex workflows
3018
- replicatorPlugin.on('replicated', async (event) => {
3019
- const { resourceName, operation, recordId, replicator } = event;
3020
-
3021
- // Trigger downstream processes
3022
- if (resourceName === 'orders' && operation === 'insert') {
3023
- // Trigger inventory update
3024
- await inventoryService.updateStock(event.data);
3025
-
3026
- // Send confirmation email
3027
- await emailService.sendOrderConfirmation(event.data);
3028
-
3029
- // Update analytics dashboard
3030
- await analyticsService.recordSale(event.data);
3031
- }
3032
-
3033
- if (resourceName === 'users' && operation === 'update') {
3034
- // Check for important profile changes
3035
- const criticalFields = ['email', 'phone', 'address'];
3036
- const changedFields = Object.keys(event.data);
3037
-
3038
- if (criticalFields.some(field => changedFields.includes(field))) {
3039
- await auditService.logCriticalChange({
3040
- userId: recordId,
3041
- changedFields: changedFields.filter(f => criticalFields.includes(f)),
3042
- timestamp: new Date()
3043
- });
3044
- }
3045
- }
3046
- });
3047
-
3048
- // Handle replication failures with custom logic
3049
- replicatorPlugin.on('replicator_error', async (error) => {
3050
- const { resourceName, operation, recordId, replicator, error: errorMessage } = error;
3051
-
3052
- // Log to external monitoring system
3053
- await monitoringService.logError({
3054
- service: 'replication',
3055
- error: errorMessage,
3056
- context: { resourceName, operation, recordId, replicator }
3057
- });
3058
-
3059
- // Send alerts for critical resources
3060
- const criticalResources = ['users', 'orders', 'payments'];
3061
- if (criticalResources.includes(resourceName)) {
3062
- await alertingService.sendAlert({
3063
- severity: 'high',
3064
- message: `Critical replication failure: ${resourceName}`,
3065
- details: error
3066
- });
3067
- }
3068
-
3069
- // Implement circuit breaker pattern
3070
- const errorCount = await redis.incr(`replication_errors:${replicator}`);
3071
- await redis.expire(`replication_errors:${replicator}`, 3600); // 1 hour window
3072
-
3073
- if (errorCount > 10) {
3074
- console.warn(`⚠️ Circuit breaker: Disabling ${replicator} due to high error rate`);
3075
- // Temporarily disable problematic replicator
3076
- const problematicReplicator = replicatorPlugin.replicators.find(r => r.id === replicator);
3077
- if (problematicReplicator) {
3078
- problematicReplicator.enabled = false;
3079
- }
3080
- }
3081
- });
3082
- ```
3083
-
3084
- ### 🎛️ Performance Tuning
3085
-
3086
- #### Optimize Replication Performance
3087
-
3088
- ```javascript
3089
- // Configure for high-throughput scenarios
3090
- const highPerformanceReplicator = new ReplicatorPlugin({
3091
- batchSize: 500, // Larger batches for better throughput
3092
- maxRetries: 5, // More retries for reliability
3093
- timeout: 60000, // Longer timeout for large operations
3094
-
3095
- replicators: [
3096
- {
3097
- driver: 'bigquery',
3098
- config: {
3099
- projectId: 'analytics',
3100
- datasetId: 'high_volume_data',
3101
- // Use streaming inserts for real-time data
3102
- streamingInserts: true,
3103
- insertAllTimeout: 30000
3104
- },
3105
- resources: {
3106
- events: {
3107
- table: 'raw_events',
3108
- actions: ['insert'],
3109
- // Optimize transform for performance
3110
- transform: (data) => {
3111
- // Pre-compute expensive operations
3112
- const eventDate = data.timestamp?.split('T')[0];
3113
-
3114
- return {
3115
- event_id: data.id,
3116
- user_id: data.userId,
3117
- event_type: data.type,
3118
- event_date: eventDate,
3119
- properties: JSON.stringify(data.properties || {}),
3120
-
3121
- // Batch-friendly fields
3122
- partition_date: eventDate,
3123
- ingestion_timestamp: Math.floor(Date.now() / 1000) // Unix timestamp
3124
- };
3125
- }
3126
- }
3127
- }
3128
- },
3129
-
3130
- {
3131
- driver: 'postgres',
3132
- config: {
3133
- connectionString: process.env.POSTGRES_URL,
3134
- pool: {
3135
- max: 50, // Larger connection pool
3136
- min: 10,
3137
- idleTimeoutMillis: 60000,
3138
- acquireTimeoutMillis: 10000
3139
- }
3140
- },
3141
- resources: {
3142
- users: [{
3143
- table: 'users_cache',
3144
- actions: ['insert', 'update'],
3145
- // Use upsert pattern for better performance
3146
- upsertMode: true,
3147
- transform: (data) => ({
3148
- id: data.id,
3149
- email: data.email,
3150
- name: data.name,
3151
- updated_at: new Date(),
3152
-
3153
- // Optimized for query performance
3154
- search_vector: `${data.name} ${data.email}`.toLowerCase()
3155
- })
3156
- }]
3157
- }
3158
- }
3159
- ]
3160
- });
3161
- ```
3162
-
3163
- ### 🔐 Security Best Practices
3164
-
3165
- #### Secure Replication Configuration
3166
-
3167
- ```javascript
3168
- const secureReplicator = new ReplicatorPlugin({
3169
- replicators: [
3170
- {
3171
- driver: 'bigquery',
3172
- config: {
3173
- projectId: 'secure-analytics',
3174
- datasetId: 'encrypted_data',
3175
- // Use service account with minimal permissions
3176
- credentials: {
3177
- type: 'service_account',
3178
- project_id: 'secure-analytics',
3179
- private_key: process.env.BIGQUERY_PRIVATE_KEY,
3180
- client_email: 'replication-service@secure-analytics.iam.gserviceaccount.com'
3181
- }
3182
- },
3183
- resources: {
3184
- users: {
3185
- table: 'encrypted_users',
3186
- actions: ['insert', 'update'],
3187
- transform: (data) => ({
3188
- // Hash sensitive identifiers
3189
- user_hash: crypto.createHash('sha256').update(data.id + process.env.SALT).digest('hex'),
3190
-
3191
- // Encrypt PII fields
3192
- encrypted_email: encrypt(data.email),
3193
- encrypted_phone: data.phone ? encrypt(data.phone) : null,
3194
-
3195
- // Keep non-sensitive analytics data
3196
- registration_date: data.createdAt?.split('T')[0],
3197
- account_type: data.accountType,
3198
- region: data.region,
3199
-
3200
- // Add encryption metadata
3201
- encryption_version: '1.0',
3202
- processed_at: new Date().toISOString()
3203
- })
3204
- }
3205
- }
3206
- }
3207
- ]
3208
- });
3209
-
3210
- // Encryption utility functions
3211
- function encrypt(text) {
3212
- if (!text) return null;
3213
- const cipher = crypto.createCipher('aes-256-cbc', process.env.ENCRYPTION_KEY);
3214
- let encrypted = cipher.update(text, 'utf8', 'hex');
3215
- encrypted += cipher.final('hex');
3216
- return encrypted;
3217
- }
3218
-
3219
- function decrypt(encryptedText) {
3220
- if (!encryptedText) return null;
3221
- const decipher = crypto.createDecipher('aes-256-cbc', process.env.ENCRYPTION_KEY);
3222
- let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
3223
- decrypted += decipher.final('utf8');
3224
- return decrypted;
3225
- }
3226
- ```
3227
-
3228
- ### ✅ Best Practices Summary
3229
-
3230
- 1. **Configuration Management**
3231
- - Use environment variables for connection strings and credentials
3232
- - Implement proper error handling in transform functions
3233
- - Test replicator connections during application startup
3234
-
3235
- 2. **Performance Optimization**
3236
- - Use appropriate batch sizes for your data volume
3237
- - Configure connection pools for database replicators
3238
- - Monitor replication lag and throughput
3239
-
3240
- 3. **Security & Compliance**
3241
- - Encrypt sensitive data before replication
3242
- - Implement data masking for non-production environments
3243
- - Use minimal privilege service accounts
3244
-
3245
- 4. **Monitoring & Alerting**
3246
- - Set up alerts for replication failures
3247
- - Monitor replication lag and error rates
3248
- - Implement health checks for all replicators
3249
-
3250
- 5. **Error Handling**
3251
- - Implement circuit breakers for unreliable targets
3252
- - Use dead letter queues for failed messages
3253
- - Log detailed error information for debugging
3254
-
3255
- ### 🔧 Easy Example
3256
-
3257
- ```javascript
3258
- import { S3db, ReplicatorPlugin } from 's3db.js';
3259
-
3260
- const s3db = new S3db({
3261
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
3262
- plugins: [new ReplicatorPlugin({
3263
- persistReplicatorLog: true,
3264
- replicators: [
3265
- {
3266
- driver: 's3db',
3267
- resources: ['users', 'products'],
3268
- config: {
3269
- connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
3270
- }
3271
- },
3272
- {
3273
- driver: 'sqs',
3274
- resources: ['orders'],
3275
- config: {
3276
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/orders-queue.fifo',
3277
- region: 'us-east-1',
3278
- messageGroupId: 'order-updates'
3279
- }
3280
- }
3281
- ]
3282
- })]
3283
- });
3284
-
3285
- await s3db.connect();
3286
-
3287
- const users = s3db.resource('users');
3288
- const orders = s3db.resource('orders');
3289
-
3290
- // Monitor replication events
3291
- const replicatorPlugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
3292
-
3293
- replicatorPlugin.on('replicator.success', (data) => {
3294
- console.log(`✅ Replicated: ${data.action} on ${data.resource} to ${data.replicator}`);
3295
- });
3296
-
3297
- replicatorPlugin.on('replicator.failed', (data) => {
3298
- console.error(`❌ Replication failed: ${data.error}`);
3299
- });
3300
-
3301
- // Insert data (automatically replicated)
3302
- const user = await users.insert({
3303
- name: 'Alice Johnson',
3304
- email: 'alice@example.com',
3305
- role: 'customer'
3306
- });
3307
-
3308
- const order = await orders.insert({
3309
- userId: user.id,
3310
- amount: 99.99,
3311
- items: ['item1', 'item2']
3312
- });
3313
-
3314
- // Check replication logs
3315
- const replicatorLogs = s3db.resource('replicator_logs');
3316
- const logs = await replicatorLogs.list();
3317
-
3318
- console.log('\n=== Replication History ===');
3319
- logs.forEach(log => {
3320
- console.log(`${log.timestamp}: ${log.action} ${log.resource} → ${log.replicator}`);
3321
- });
3322
- ```
3323
-
3324
- ### 🚀 Advanced Multi-Driver Example
3325
-
3326
- ```javascript
3327
- import { S3db, ReplicatorPlugin } from 's3db.js';
3328
-
3329
- const s3db = new S3db({
3330
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
3331
- plugins: [new ReplicatorPlugin({
3332
- enabled: true,
3333
- persistReplicatorLog: true,
3334
- replicatorLogResource: 'replication_audit',
3335
- batchSize: 25,
3336
- retryAttempts: 5,
3337
- retryDelay: 2000,
3338
-
3339
- replicators: [
3340
- // Backup to another S3DB instance
3341
- {
3342
- driver: 's3db',
3343
- resources: ['users', 'products', 'orders'],
3344
- config: {
3345
- connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup",
3346
- enabled: true,
3347
- timeout: 30000
3348
- }
3349
- },
3350
-
3351
- // Real-time events to SQS
3352
- {
3353
- driver: 'sqs',
3354
- resources: ['orders', 'users'],
3355
- config: {
3356
- region: 'us-east-1',
3357
- credentials: {
3358
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
3359
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
3360
- },
3361
- // Resource-specific queues
3362
- queues: {
3363
- orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/order-events.fifo',
3364
- users: 'https://sqs.us-east-1.amazonaws.com/123456789012/user-events.fifo'
3365
- },
3366
- // Default queue for unspecified resources
3367
- defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/default-events.fifo',
3368
- messageGroupId: 's3db-replicator',
3369
- deduplicationId: true,
3370
- messageAttributes: {
3371
- source: { StringValue: 'production-db', DataType: 'String' },
3372
- version: { StringValue: '1.0', DataType: 'String' }
3373
- }
3374
- }
3375
- },
3376
-
3377
- // Analytics to BigQuery
3378
- {
3379
- driver: 'bigquery',
3380
- config: {
3381
- projectId: 'my-analytics-project',
3382
- datasetId: 's3db_analytics',
3383
- location: 'US',
3384
- logTable: 'replication_log',
3385
- credentials: {
3386
- client_email: 'service-account@project.iam.gserviceaccount.com',
3387
- private_key: process.env.BIGQUERY_PRIVATE_KEY,
3388
- project_id: 'my-analytics-project'
3389
- }
3390
- },
3391
- resources: {
3392
- // Multiple destinations for users
3393
- users: [
3394
- { actions: ['insert', 'update'], table: 'dim_users' },
3395
- { actions: ['insert'], table: 'fact_user_activity' }
3396
- ],
3397
-
3398
- // Orders to analytics tables
3399
- orders: [
3400
- { actions: ['insert'], table: 'fact_orders' },
3401
- { actions: ['insert'], table: 'daily_revenue',
3402
- transformer: (data) => ({
3403
- date: data.createdAt?.split('T')[0],
3404
- revenue: data.amount,
3405
- customer_id: data.userId,
3406
- order_count: 1
3407
- })
3408
- }
3409
- ],
3410
-
3411
- // Products with transformation
3412
- products: {
3413
- table: 'dim_products',
3414
- actions: ['insert', 'update'],
3415
- transformer: (data) => ({
3416
- ...data,
3417
- price_category: data.price > 100 ? 'premium' : 'standard',
3418
- last_updated: new Date().toISOString()
3419
- })
3420
- }
3421
- }
3422
- },
3423
-
3424
- // Operational database (PostgreSQL)
3425
- {
3426
- driver: 'postgres',
3427
- config: {
3428
- connectionString: 'postgresql://analytics:password@localhost:5432/operations',
3429
- ssl: { rejectUnauthorized: false },
3430
- logTable: 'replication_log',
3431
- pool: {
3432
- max: 20,
3433
- idleTimeoutMillis: 30000,
3434
- connectionTimeoutMillis: 2000
3435
- }
3436
- },
3437
- resources: {
3438
- users: [
3439
- {
3440
- actions: ['insert', 'update', 'delete'],
3441
- table: 'operational_users',
3442
- transformer: (data, action) => {
3443
- if (action === 'delete') return { id: data.id, deleted_at: new Date() };
3444
- return {
3445
- ...data,
3446
- sync_timestamp: new Date(),
3447
- source_system: 's3db'
3448
- };
3449
- }
3450
- }
3451
- ],
3452
-
3453
- orders: [
3454
- { actions: ['insert'], table: 'order_events' },
3455
- {
3456
- actions: ['update'],
3457
- table: 'order_updates',
3458
- transformer: (data) => ({
3459
- order_id: data.id,
3460
- updated_fields: Object.keys(data),
3461
- update_timestamp: new Date()
3462
- })
3463
- }
3464
- ]
3465
- }
3466
- }
3467
- ]
3468
- })]
3469
- });
3470
-
3471
- await s3db.connect();
3472
-
3473
- // Advanced replicator management
3474
- class ReplicatorManager {
3475
- constructor(replicatorPlugin) {
3476
- this.plugin = replicatorPlugin;
3477
- this.stats = {
3478
- totalReplications: 0,
3479
- successfulReplications: 0,
3480
- failedReplications: 0,
3481
- byReplicator: {},
3482
- byResource: {}
3483
- };
3484
-
3485
- this.setupEventListeners();
3486
- }
3487
-
3488
- setupEventListeners() {
3489
- this.plugin.on('replicator.queued', (data) => {
3490
- this.stats.totalReplications++;
3491
- this.updateResourceStats(data.resource, 'queued');
3492
- });
3493
-
3494
- this.plugin.on('replicator.success', (data) => {
3495
- this.stats.successfulReplications++;
3496
- this.updateReplicatorStats(data.replicator, 'success');
3497
- this.updateResourceStats(data.resource, 'success');
3498
- });
3499
-
3500
- this.plugin.on('replicator.failed', (data) => {
3501
- this.stats.failedReplications++;
3502
- this.updateReplicatorStats(data.replicator, 'failed');
3503
- this.updateResourceStats(data.resource, 'failed');
3504
-
3505
- // Advanced error handling
3506
- if (data.error.includes('BigQuery')) {
3507
- console.log('🔧 BigQuery error detected - checking schema compatibility...');
3508
- } else if (data.error.includes('SQS')) {
3509
- console.log('📮 SQS error detected - checking queue permissions...');
3510
- }
3511
- });
3512
- }
3513
-
3514
- updateReplicatorStats(replicator, status) {
3515
- if (!this.stats.byReplicator[replicator]) {
3516
- this.stats.byReplicator[replicator] = { success: 0, failed: 0 };
3517
- }
3518
- this.stats.byReplicator[replicator][status]++;
3519
- }
3520
-
3521
- updateResourceStats(resource, status) {
3522
- if (!this.stats.byResource[resource]) {
3523
- this.stats.byResource[resource] = { queued: 0, success: 0, failed: 0 };
3524
- }
3525
- this.stats.byResource[resource][status]++;
3526
- }
3527
-
3528
- async getReplicationHealth() {
3529
- const totalAttempts = this.stats.successfulReplications + this.stats.failedReplications;
3530
- const successRate = totalAttempts > 0 ? this.stats.successfulReplications / totalAttempts : 1;
3531
-
3532
- return {
3533
- overall: {
3534
- successRate: successRate,
3535
- totalReplications: this.stats.totalReplications,
3536
- pending: this.stats.totalReplications - totalAttempts,
3537
- health: successRate > 0.95 ? 'excellent' :
3538
- successRate > 0.85 ? 'good' :
3539
- successRate > 0.7 ? 'warning' : 'critical'
3540
- },
3541
- byReplicator: this.stats.byReplicator,
3542
- byResource: this.stats.byResource
3543
- };
3544
- }
3545
-
3546
- async pauseReplicator(replicatorId) {
3547
- const replicator = this.plugin.replicators.find(r => r.id === replicatorId);
3548
- if (replicator) {
3549
- replicator.enabled = false;
3550
- console.log(`⏸️ Paused replicator: ${replicatorId}`);
3551
- }
3552
- }
3553
-
3554
- async resumeReplicator(replicatorId) {
3555
- const replicator = this.plugin.replicators.find(r => r.id === replicatorId);
3556
- if (replicator) {
3557
- replicator.enabled = true;
3558
- console.log(`▶️ Resumed replicator: ${replicatorId}`);
3559
- }
3560
- }
3561
-
3562
- async testReplicatorConnections() {
3563
- console.log('🔍 Testing replicator connections...');
3564
-
3565
- for (const replicator of this.plugin.replicators) {
3566
- try {
3567
- const result = await replicator.testConnection();
3568
- console.log(`✅ ${replicator.driver}: ${result.status}`);
3569
- } catch (error) {
3570
- console.log(`❌ ${replicator.driver}: ${error.message}`);
3571
- }
3572
- }
3573
- }
3574
- }
3575
-
3576
- // Setup sample data and test all replicators
3577
- const users = s3db.resource('users');
3578
- const products = s3db.resource('products');
3579
- const orders = s3db.resource('orders');
3580
-
3581
- const replicatorPlugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
3582
- const manager = new ReplicatorManager(replicatorPlugin);
3583
-
3584
- // Test connections
3585
- await manager.testReplicatorConnections();
3586
-
3587
- // Create sample data
3588
- console.log('🔄 Creating sample data with multi-driver replication...');
3589
-
3590
- const sampleUsers = await users.insertMany([
3591
- { name: 'John Smith', email: 'john@example.com', role: 'admin' },
3592
- { name: 'Jane Doe', email: 'jane@example.com', role: 'user' },
3593
- { name: 'Bob Wilson', email: 'bob@example.com', role: 'user' }
3594
- ]);
3595
-
3596
- const sampleProducts = await products.insertMany([
3597
- { name: 'Laptop Pro', price: 1299.99, category: 'electronics' },
3598
- { name: 'Wireless Mouse', price: 29.99, category: 'electronics' },
3599
- { name: 'Coffee Mug', price: 12.99, category: 'home' }
3600
- ]);
3601
-
3602
- const sampleOrders = await orders.insertMany([
3603
- { userId: sampleUsers[0].id, amount: 1329.98, items: [sampleProducts[0].id, sampleProducts[1].id] },
3604
- { userId: sampleUsers[1].id, amount: 29.99, items: [sampleProducts[1].id] },
3605
- { userId: sampleUsers[2].id, amount: 12.99, items: [sampleProducts[2].id] }
3606
- ]);
3607
-
3608
- // Wait for replications to complete
3609
- await new Promise(resolve => setTimeout(resolve, 3000));
3610
-
3611
- // Get replication statistics
3612
- const health = await manager.getReplicationHealth();
3613
- console.log('\n=== Replication Health Report ===');
3614
- console.log(`Overall success rate: ${(health.overall.successRate * 100).toFixed(1)}%`);
3615
- console.log(`Health status: ${health.overall.health.toUpperCase()}`);
3616
- console.log(`Total replications: ${health.overall.totalReplications}`);
3617
- console.log(`Pending: ${health.overall.pending}`);
3618
-
3619
- console.log('\n=== By Replicator ===');
3620
- Object.entries(health.byReplicator).forEach(([replicator, stats]) => {
3621
- const total = stats.success + stats.failed;
3622
- const rate = total > 0 ? (stats.success / total * 100).toFixed(1) : 0;
3623
- console.log(`${replicator}: ${rate}% success (${stats.success}/${total})`);
3624
- });
3625
-
3626
- console.log('\n=== By Resource ===');
3627
- Object.entries(health.byResource).forEach(([resource, stats]) => {
3628
- console.log(`${resource}: queued ${stats.queued}, success ${stats.success}, failed ${stats.failed}`);
3629
- });
3630
-
3631
- // Get detailed replication logs
3632
- const replicationLogs = await replicatorPlugin.getReplicatorLogs({ limit: 10 });
3633
- console.log('\n=== Recent Replication Logs ===');
3634
- replicationLogs.forEach(log => {
3635
- const status = log.success ? '✅' : '❌';
3636
- console.log(`${status} ${log.timestamp} | ${log.action} ${log.resource} → ${log.replicator}`);
3637
- if (!log.success && log.error) {
3638
- console.log(` Error: ${log.error}`);
3639
- }
3640
- });
3641
-
3642
- console.log('\n✅ Multi-driver replication demonstration completed');
3643
- ```
3644
-
3645
- ---
3646
-
3647
- ## 📬 Queue Consumer Plugin
3648
-
3649
- Consume messages from external queues (SQS, RabbitMQ) and automatically process them into your s3db resources.
3650
-
3651
- ### ⚡ Quick Start
3652
-
3653
- ```javascript
3654
- import { S3db, QueueConsumerPlugin } from 's3db.js';
3655
-
3656
- const s3db = new S3db({
3657
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
3658
- plugins: [new QueueConsumerPlugin({
3659
- consumers: [
3660
- {
3661
- driver: 'sqs',
3662
- config: {
3663
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
3664
- region: 'us-east-1'
3665
- },
3666
- consumers: [
3667
- { resources: 'users' }
3668
- ]
3669
- }
3670
- ]
3671
- })]
3672
- });
3673
-
3674
- await s3db.connect();
3675
- // Queue messages are automatically processed into your resources
3676
- ```
3677
-
3678
- ### ⚙️ Configuration Parameters
3679
-
3680
- | Parameter | Type | Default | Description |
3681
- |-----------|------|---------|-------------|
3682
- | `enabled` | boolean | `true` | Enable/disable queue consumption |
3683
- | `consumers` | array | `[]` | Array of consumer configurations |
3684
- | `batchSize` | number | `10` | Messages to process per batch |
3685
- | `concurrency` | number | `5` | Concurrent message processing |
3686
- | `retryAttempts` | number | `3` | Retry failed message processing |
3687
- | `retryDelay` | number | `1000` | Delay between retries (ms) |
3688
- | `deadLetterQueue` | string | `null` | DLQ for failed messages |
3689
-
3690
- ### Supported Drivers
3691
-
3692
- #### SQS Consumer
3693
-
3694
- Consume from AWS SQS queues:
3695
-
3696
- ```javascript
3697
- {
3698
- driver: 'sqs',
3699
- config: {
3700
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
3701
- region: 'us-east-1',
3702
- credentials: { accessKeyId: '...', secretAccessKey: '...' },
3703
- pollingInterval: 1000,
3704
- maxMessages: 10,
3705
- visibilityTimeout: 300
3706
- },
3707
- consumers: [
3708
- { resources: ['users'], queueUrl: 'specific-queue-url' }
3709
- ]
3710
- }
3711
- ```
3712
-
3713
- #### RabbitMQ Consumer
3714
-
3715
- Consume from RabbitMQ queues:
3716
-
3717
- ```javascript
3718
- {
3719
- driver: 'rabbitmq',
3720
- config: {
3721
- amqpUrl: 'amqp://user:pass@localhost:5672',
3722
- exchange: 'my-exchange',
3723
- prefetch: 10,
3724
- reconnectInterval: 2000
3725
- },
3726
- consumers: [
3727
- { resources: ['orders'], queue: 'orders-queue' }
3728
- ]
3729
- }
3730
- ```
3731
-
3732
- ### Message Format
3733
-
3734
- Expected message structure:
3735
-
3736
- ```javascript
3737
- {
3738
- resource: 'users', // Target resource name
3739
- action: 'insert', // Operation: insert, update, delete
3740
- data: { // Data payload
3741
- name: 'John Doe',
3742
- email: 'john@example.com'
3743
- }
3744
- }
3745
- ```
3746
-
3747
- ### 🔧 Easy Example
3748
-
3749
- ```javascript
3750
- import { S3db, QueueConsumerPlugin } from 's3db.js';
3751
-
3752
- const s3db = new S3db({
3753
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
3754
- plugins: [new QueueConsumerPlugin({
3755
- enabled: true,
3756
- consumers: [
3757
- {
3758
- driver: 'sqs',
3759
- config: {
3760
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/user-updates.fifo',
3761
- region: 'us-east-1',
3762
- pollingInterval: 2000,
3763
- maxMessages: 5
3764
- },
3765
- consumers: [
3766
- { resources: ['users', 'profiles'] }
3767
- ]
3768
- }
3769
- ]
3770
- })]
3771
- });
3772
-
3773
- await s3db.connect();
3774
-
3775
- // Messages are automatically consumed and processed
3776
- console.log('Queue consumer started - listening for messages...');
3777
-
3778
- // Simulate sending a message (in real use, external systems send these)
3779
- const testMessage = {
3780
- resource: 'users',
3781
- action: 'insert',
3782
- data: {
3783
- name: 'Queue User',
3784
- email: 'queue@example.com',
3785
- source: 'external-system'
3786
- }
3787
- };
3788
-
3789
- console.log('Processing message:', testMessage);
3790
- ```
3791
-
3792
- ### 🚀 Advanced Multi-Driver Example
3793
-
3794
- ```javascript
3795
- import { S3db, QueueConsumerPlugin } from 's3db.js';
3796
-
3797
- const s3db = new S3db({
3798
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
3799
- plugins: [new QueueConsumerPlugin({
3800
- enabled: true,
3801
- batchSize: 20,
3802
- concurrency: 10,
3803
- retryAttempts: 5,
3804
- retryDelay: 2000,
3805
-
3806
- consumers: [
3807
- // SQS Consumer for user events
3808
- {
3809
- driver: 'sqs',
3810
- config: {
3811
- region: 'us-east-1',
3812
- credentials: {
3813
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
3814
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
3815
- },
3816
- pollingInterval: 1000,
3817
- maxMessages: 10,
3818
- visibilityTimeout: 300,
3819
- waitTimeSeconds: 20 // Long polling
3820
- },
3821
- consumers: [
3822
- {
3823
- resources: ['users'],
3824
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/user-events.fifo',
3825
- messageGroupId: 'user-processing'
3826
- },
3827
- {
3828
- resources: ['orders'],
3829
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/order-events.fifo',
3830
- messageGroupId: 'order-processing'
3831
- }
3832
- ]
3833
- },
3834
-
3835
- // RabbitMQ Consumer for analytics events
3836
- {
3837
- driver: 'rabbitmq',
3838
- config: {
3839
- amqpUrl: 'amqp://analytics:password@localhost:5672',
3840
- exchange: 'analytics-events',
3841
- exchangeType: 'topic',
3842
- prefetch: 15,
3843
- reconnectInterval: 3000,
3844
- heartbeat: 60
3845
- },
3846
- consumers: [
3847
- {
3848
- resources: ['analytics', 'metrics'],
3849
- queue: 'analytics-queue',
3850
- routingKey: 'analytics.*',
3851
- durable: true
3852
- },
3853
- {
3854
- resources: ['logs'],
3855
- queue: 'logs-queue',
3856
- routingKey: 'logs.*',
3857
- durable: true
3858
- }
3859
- ]
3860
- }
3861
- ]
3862
- })]
3863
- });
3864
-
3865
- await s3db.connect();
3866
-
3867
- // Advanced message processing with custom handlers
3868
- class QueueMessageProcessor {
3869
- constructor(queuePlugin) {
3870
- this.plugin = queuePlugin;
3871
- this.stats = {
3872
- processed: 0,
3873
- errors: 0,
3874
- byResource: {},
3875
- byAction: {}
3876
- };
3877
-
3878
- this.setupEventListeners();
3879
- }
3880
-
3881
- setupEventListeners() {
3882
- // Listen for message processing events
3883
- this.plugin.on('message.received', (data) => {
3884
- console.log(`📨 Received message: ${data.action} on ${data.resource}`);
3885
- });
3886
-
3887
- this.plugin.on('message.processed', (data) => {
3888
- this.stats.processed++;
3889
- this.updateStats(data.resource, data.action, 'success');
3890
- console.log(`✅ Processed: ${data.action} on ${data.resource}`);
3891
- });
3892
-
3893
- this.plugin.on('message.failed', (data) => {
3894
- this.stats.errors++;
3895
- this.updateStats(data.resource, data.action, 'error');
3896
- console.error(`❌ Failed: ${data.error}`);
3897
-
3898
- // Custom error handling
3899
- this.handleProcessingError(data);
3900
- });
3901
- }
3902
-
3903
- updateStats(resource, action, status) {
3904
- if (!this.stats.byResource[resource]) {
3905
- this.stats.byResource[resource] = { success: 0, error: 0 };
3906
- }
3907
- if (!this.stats.byAction[action]) {
3908
- this.stats.byAction[action] = { success: 0, error: 0 };
3909
- }
3910
-
3911
- this.stats.byResource[resource][status]++;
3912
- this.stats.byAction[action][status]++;
3913
- }
3914
-
3915
- handleProcessingError(errorData) {
3916
- const { resource, action, error, attempts } = errorData;
3917
-
3918
- // Log to external monitoring system
3919
- console.log(`🚨 Error processing ${action} on ${resource}: ${error}`);
3920
-
3921
- // Custom retry logic
3922
- if (attempts >= 3) {
3923
- console.log(`💀 Moving to dead letter queue after ${attempts} attempts`);
3924
- // In real implementation, move to DLQ
3925
- }
3926
-
3927
- // Resource-specific error handling
3928
- if (resource === 'users' && error.includes('validation')) {
3929
- console.log('👤 User validation error - checking schema compatibility');
3930
- } else if (resource === 'orders' && error.includes('duplicate')) {
3931
- console.log('🛒 Duplicate order detected - implementing idempotency check');
3932
- }
3933
- }
3934
-
3935
- getProcessingStats() {
3936
- const totalMessages = this.stats.processed + this.stats.errors;
3937
- const successRate = totalMessages > 0 ? this.stats.processed / totalMessages : 1;
3938
-
3939
- return {
3940
- summary: {
3941
- totalProcessed: this.stats.processed,
3942
- totalErrors: this.stats.errors,
3943
- successRate: successRate,
3944
- health: successRate > 0.95 ? 'excellent' :
3945
- successRate > 0.85 ? 'good' :
3946
- successRate > 0.7 ? 'warning' : 'critical'
3947
- },
3948
- byResource: this.stats.byResource,
3949
- byAction: this.stats.byAction
3950
- };
3951
- }
3952
-
3953
- async pauseConsumption() {
3954
- console.log('⏸️ Pausing queue consumption...');
3955
- await this.plugin.pause();
3956
- }
3957
-
3958
- async resumeConsumption() {
3959
- console.log('▶️ Resuming queue consumption...');
3960
- await this.plugin.resume();
3961
- }
3962
- }
3963
-
3964
- // Setup message processing
3965
- const queuePlugin = s3db.plugins.find(p => p.constructor.name === 'QueueConsumerPlugin');
3966
- const processor = new QueueMessageProcessor(queuePlugin);
3967
-
3968
- // Simulate processing for demonstration
3969
- console.log('🔄 Queue consumers started - processing messages...');
3970
-
3971
- // In real scenario, messages come from external systems
3972
- // Here we simulate the processing results
3973
- setTimeout(async () => {
3974
- const stats = processor.getProcessingStats();
3975
-
3976
- console.log('\n=== Queue Processing Stats ===');
3977
- console.log(`Total processed: ${stats.summary.totalProcessed}`);
3978
- console.log(`Total errors: ${stats.summary.totalErrors}`);
3979
- console.log(`Success rate: ${(stats.summary.successRate * 100).toFixed(1)}%`);
3980
- console.log(`Health: ${stats.summary.health.toUpperCase()}`);
3981
-
3982
- console.log('\n=== By Resource ===');
3983
- Object.entries(stats.byResource).forEach(([resource, counts]) => {
3984
- const total = counts.success + counts.error;
3985
- console.log(`${resource}: ${counts.success}/${total} successful`);
3986
- });
3987
-
3988
- console.log('\n=== By Action ===');
3989
- Object.entries(stats.byAction).forEach(([action, counts]) => {
3990
- const total = counts.success + counts.error;
3991
- console.log(`${action}: ${counts.success}/${total} successful`);
3992
- });
3993
-
3994
- }, 5000);
3995
-
3996
- console.log('\n✅ Queue consumer demonstration completed');
3997
- ```
3998
-
3999
- ---
4000
-
4001
- ## 🔧 Plugin Development
4002
-
4003
- Create custom plugins to extend s3db.js with your specific requirements.
4004
-
4005
- ### Plugin Base Class
4006
-
4007
- ```javascript
4008
- import { Plugin } from 's3db.js';
4009
-
4010
- class MyCustomPlugin extends Plugin {
4011
- constructor(options = {}) {
4012
- super(options);
4013
- this.config = {
4014
- enabled: options.enabled !== false,
4015
- ...options
4016
- };
4017
- }
4018
-
4019
- async onSetup() {
4020
- // Initialize plugin after database connection
4021
- console.log('Setting up MyCustomPlugin');
4022
- }
4023
-
4024
- async onStart() {
4025
- // Plugin is ready to operate
4026
- console.log('MyCustomPlugin started');
4027
- }
4028
-
4029
- async onStop() {
4030
- // Cleanup before shutdown
4031
- console.log('MyCustomPlugin stopped');
4032
- }
4033
- }
4034
- ```
4035
-
4036
- ### Plugin Lifecycle
4037
-
4038
- 1. **Constructor**: Configure plugin options
4039
- 2. **setup()**: Called when database connects
4040
- 3. **onSetup()**: Initialize plugin resources
4041
- 4. **start()**: Called when database is ready
4042
- 5. **onStart()**: Begin plugin operations
4043
- 6. **stop()**: Called during shutdown
4044
- 7. **onStop()**: Cleanup plugin resources
4045
-
4046
- ### Custom Plugin Example
4047
-
4048
- ```javascript
4049
- class NotificationPlugin extends Plugin {
4050
- constructor(options = {}) {
4051
- super(options);
4052
- this.config = {
4053
- enabled: options.enabled !== false,
4054
- webhookUrl: options.webhookUrl,
4055
- events: options.events || ['insert', 'update', 'delete'],
4056
- ...options
4057
- };
4058
- }
4059
-
4060
- async onSetup() {
4061
- // Install hooks for all resources
4062
- for (const resource of Object.values(this.database.resources)) {
4063
- this.installResourceHooks(resource);
4064
- }
4065
- }
4066
-
4067
- installResourceHooks(resource) {
4068
- this.config.events.forEach(event => {
4069
- resource.on(event, async (data) => {
4070
- await this.sendNotification(event, resource.name, data);
4071
- });
4072
- });
4073
- }
4074
-
4075
- async sendNotification(event, resourceName, data) {
4076
- if (!this.config.webhookUrl) return;
4077
-
4078
- const payload = {
4079
- event,
4080
- resource: resourceName,
4081
- data,
4082
- timestamp: new Date().toISOString()
4083
- };
4084
-
4085
- try {
4086
- await fetch(this.config.webhookUrl, {
4087
- method: 'POST',
4088
- headers: { 'Content-Type': 'application/json' },
4089
- body: JSON.stringify(payload)
4090
- });
4091
- } catch (error) {
4092
- console.error('Notification failed:', error);
4093
- }
4094
- }
4095
- }
4096
- ```
4097
-
4098
- ---
4099
-
4100
- ## 💡 Plugin Combinations
4101
-
4102
- Powerful workflows using multiple plugins together.
4103
-
4104
- ### Complete Monitoring Stack
4105
-
4106
- ```javascript
4107
- import {
4108
- S3db,
4109
- CachePlugin,
4110
- CostsPlugin,
4111
- AuditPlugin,
4112
- FullTextPlugin,
4113
- MetricsPlugin,
4114
- ReplicatorPlugin
4115
- } from 's3db.js';
4116
-
4117
- const s3db = new S3db({
4118
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
4119
- plugins: [
4120
- // Performance optimization
4121
- new CachePlugin({
4122
- driver: 'memory',
4123
- ttl: 600000, // 10 minutes
4124
- maxSize: 1000
4125
- }),
4126
-
4127
- // Cost tracking
4128
- CostsPlugin,
4129
-
4130
- // Compliance and security
4131
- new AuditPlugin({
4132
- enabled: true,
4133
- includeData: true,
4134
- trackOperations: ['insert', 'update', 'delete', 'get']
4135
- }),
4136
-
4137
- // Search capabilities
4138
- new FullTextPlugin({
4139
- enabled: true,
4140
- fields: ['name', 'description', 'content', 'tags']
4141
- }),
4142
-
4143
- // Performance monitoring
4144
- new MetricsPlugin({
4145
- enabled: true,
4146
- collectPerformance: true,
4147
- collectErrors: true,
4148
- flushInterval: 30000
4149
- }),
4150
-
4151
- // Data replication
4152
- new ReplicatorPlugin({
4153
- replicators: [
4154
- {
4155
- driver: 's3db',
4156
- resources: ['users', 'products', 'orders'],
4157
- config: {
4158
- connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
4159
- }
4160
- }
4161
- ]
4162
- })
4163
- ]
4164
- });
4165
-
4166
- await s3db.connect();
4167
-
4168
- // All plugins work seamlessly together
4169
- const products = s3db.resource('products');
4170
-
4171
- // This single operation triggers:
4172
- // - Audit logging
4173
- // - Cost tracking
4174
- // - Performance metrics
4175
- // - Cache invalidation
4176
- // - Data replication
4177
- // - Search indexing
4178
- await products.insert({
4179
- name: 'New Product',
4180
- description: 'Amazing new product with great features',
4181
- price: 99.99,
4182
- tags: ['new', 'featured', 'electronics']
4183
- });
4184
- ```
4185
-
4186
- ### E-commerce Analytics Pipeline
4187
-
4188
- ```javascript
4189
- const s3db = new S3db({
4190
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/ecommerce",
4191
- plugins: [
4192
- // Real-time search
4193
- new FullTextPlugin({
4194
- fields: ['name', 'description', 'brand', 'category'],
4195
- language: 'en-US',
4196
- stemming: true
4197
- }),
4198
-
4199
- // Performance monitoring
4200
- new MetricsPlugin({
4201
- collectPerformance: true,
4202
- slowQueryThreshold: 500
4203
- }),
4204
-
4205
- // Multi-destination replication
4206
- new ReplicatorPlugin({
4207
- replicators: [
4208
- // Backup
4209
- { driver: 's3db', resources: '*', config: { connectionString: 'backup-db' } },
4210
-
4211
- // Analytics warehouse
4212
- {
4213
- driver: 'bigquery',
4214
- resources: {
4215
- orders: 'fact_orders',
4216
- products: 'dim_products',
4217
- users: 'dim_customers'
4218
- },
4219
- config: { projectId: 'analytics', datasetId: 'ecommerce' }
4220
- },
4221
-
4222
- // Real-time events
4223
- {
4224
- driver: 'sqs',
4225
- resources: ['orders', 'cart_events'],
4226
- config: { queueUrl: 'order-events-queue' }
4227
- }
4228
- ]
4229
- }),
4230
-
4231
- // Comprehensive auditing
4232
- new AuditPlugin({
4233
- trackOperations: ['insert', 'update', 'delete'],
4234
- includeData: true,
4235
- excludeResources: ['sessions', 'temp_data']
4236
- })
4237
- ]
4238
- });
4239
- ```
4240
-
4241
- ---
4242
-
4243
- ## 🤖 State Machine Plugin
4244
-
4245
- Finite state machine capabilities for managing complex workflows and business processes with well-defined states and transitions.
4246
-
4247
- ### ⚡ Quick Start
4248
-
4249
- ```javascript
4250
- import { S3db, StateMachinePlugin } from 's3db.js';
4251
-
4252
- const s3db = new S3db({
4253
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
4254
- plugins: [
4255
- new StateMachinePlugin({
4256
- stateMachines: {
4257
- order_processing: {
4258
- initialState: 'pending',
4259
- states: {
4260
- pending: {
4261
- on: { CONFIRM: 'confirmed', CANCEL: 'cancelled' }
4262
- },
4263
- confirmed: {
4264
- on: { PREPARE: 'preparing', CANCEL: 'cancelled' },
4265
- entry: 'onConfirmed'
4266
- },
4267
- preparing: {
4268
- on: { SHIP: 'shipped', CANCEL: 'cancelled' },
4269
- guards: { SHIP: 'canShip' }
4270
- },
4271
- shipped: {
4272
- on: { DELIVER: 'delivered', RETURN: 'returned' }
4273
- },
4274
- delivered: { type: 'final' },
4275
- cancelled: { type: 'final' },
4276
- returned: { type: 'final' }
4277
- }
4278
- }
4279
- },
4280
- actions: {
4281
- onConfirmed: async (context, event, machine) => {
4282
- console.log(`Order ${context.id} confirmed!`);
4283
- return { action: 'confirmed', timestamp: new Date() };
4284
- }
4285
- },
4286
- guards: {
4287
- canShip: async (context, event, machine) => {
4288
- const inventory = await machine.database.resource('inventory').get(context.productId);
4289
- return inventory && inventory.quantity >= context.quantity;
4290
- }
4291
- }
4292
- })
4293
- ]
4294
- });
4295
-
4296
- await s3db.connect();
4297
-
4298
- // Initialize entity with state machine
4299
- await s3db.plugins.stateMachine.initializeEntity('order_processing', 'order123');
4300
-
4301
- // Send events to trigger transitions
4302
- await s3db.plugins.stateMachine.send('order_processing', 'order123', 'CONFIRM', {
4303
- id: 'order123',
4304
- productId: 'prod1',
4305
- quantity: 2
4306
- });
4307
-
4308
- // Get current state
4309
- const state = await s3db.plugins.stateMachine.getState('order_processing', 'order123');
4310
- console.log('Current state:', state); // 'confirmed'
4311
- ```
4312
-
4313
- ### ⚙️ Configuration Parameters
4314
-
4315
- | Parameter | Type | Default | Description |
4316
- |-----------|------|---------|-------------|
4317
- | `stateMachines` | object | `{}` | State machine definitions |
4318
- | `actions` | object | `{}` | Action functions for state entry/exit |
4319
- | `guards` | object | `{}` | Guard functions for transition validation |
4320
- | `persistTransitions` | boolean | `true` | Store transition history in database |
4321
- | `transitionLogResource` | string | `'transitions'` | Resource name for transition log |
4322
- | `stateResource` | string | `'entity_states'` | Resource name for current states |
4323
- | `verbose` | boolean | `false` | Enable detailed logging |
4324
-
4325
- ### State Machine Definition Structure
4326
-
4327
- ```javascript
4328
- stateMachines: {
4329
- machine_name: {
4330
- initialState: 'start_state',
4331
- states: {
4332
- state_name: {
4333
- on: { EVENT_NAME: 'target_state' }, // Transitions
4334
- entry: 'action_name', // Action on state entry
4335
- exit: 'action_name', // Action on state exit
4336
- guards: { EVENT_NAME: 'guard_name' }, // Transition guards
4337
- meta: { custom: 'metadata' }, // Additional metadata
4338
- type: 'final' // Mark as final state
4339
- }
4340
- }
4341
- }
4342
- }
4343
- ```
4344
-
4345
- ### API Methods
4346
-
4347
- ```javascript
4348
- // Entity management
4349
- await stateMachine.initializeEntity(machineId, entityId, context);
4350
- const state = await stateMachine.getState(machineId, entityId);
4351
- const result = await stateMachine.send(machineId, entityId, event, context);
4352
-
4353
- // State information
4354
- const events = stateMachine.getValidEvents(machineId, entityId);
4355
- const definition = stateMachine.getMachineDefinition(machineId);
4356
- const machines = stateMachine.getMachines();
4357
-
4358
- // History and visualization
4359
- const history = await stateMachine.getTransitionHistory(machineId, entityId);
4360
- const dot = stateMachine.visualize(machineId); // Graphviz DOT format
4361
- ```
4362
-
4363
- ### Events
4364
-
4365
- ```javascript
4366
- stateMachine.on('initialized', ({ machines }) => {
4367
- console.log('Initialized machines:', machines);
4368
- });
4369
-
4370
- stateMachine.on('transition', ({ machineId, entityId, from, to, event, context }) => {
4371
- console.log(`${entityId}: ${from} → ${to} via ${event}`);
4372
- });
4373
-
4374
- stateMachine.on('action_error', ({ actionName, error, machineId, entityId }) => {
4375
- console.error(`Action ${actionName} failed:`, error);
4376
- });
4377
- ```
4378
-
4379
- ---
4380
-
4381
- ## 💾 Backup Plugin
4382
-
4383
- **Driver-Based Backup System** - Comprehensive database backup and restore capabilities with configurable drivers, compression, encryption, and retention policies.
4384
-
4385
- > ⚡ **NEW**: Driver-based architecture supports filesystem, S3, and multi-destination backups with flexible strategies.
4386
-
4387
- ### 🚀 Quick Start
4388
-
4389
- #### Single Driver (Filesystem)
4390
- ```javascript
4391
- import { S3db, BackupPlugin } from 's3db.js';
4392
-
4393
- const s3db = new S3db({
4394
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
4395
- });
4396
-
4397
- await s3db.connect();
4398
-
4399
- // Install backup plugin with filesystem driver
4400
- const backupPlugin = new BackupPlugin({
4401
- driver: 'filesystem',
4402
- config: {
4403
- path: './backups/{date}/',
4404
- compression: 'gzip'
4405
- },
4406
- retention: {
4407
- daily: 7,
4408
- weekly: 4,
4409
- monthly: 12
4410
- }
4411
- });
4412
-
4413
- await s3db.usePlugin(backupPlugin);
4414
-
4415
- // Create backups
4416
- const fullBackup = await backupPlugin.backup('full');
4417
- console.log('Backup ID:', fullBackup.id);
4418
-
4419
- // List and restore
4420
- const backups = await backupPlugin.listBackups();
4421
- await backupPlugin.restore(fullBackup.id);
4422
- ```
4423
-
4424
- #### Single Driver (S3)
4425
- ```javascript
4426
- const backupPlugin = new BackupPlugin({
4427
- driver: 's3',
4428
- config: {
4429
- bucket: 'my-backup-bucket',
4430
- path: 'database/{date}/',
4431
- storageClass: 'STANDARD_IA',
4432
- serverSideEncryption: 'AES256'
4433
- },
4434
- compression: 'gzip',
4435
- verification: true
4436
- });
4437
- ```
4438
-
4439
- #### Multi-Driver (Multiple Destinations)
4440
- ```javascript
4441
- const backupPlugin = new BackupPlugin({
4442
- driver: 'multi',
4443
- config: {
4444
- strategy: 'all', // 'all', 'any', 'priority'
4445
- destinations: [
4446
- {
4447
- driver: 'filesystem',
4448
- config: { path: '/local/backups/{date}/' }
4449
- },
4450
- {
4451
- driver: 's3',
4452
- config: {
4453
- bucket: 'remote-backups',
4454
- storageClass: 'GLACIER'
4455
- }
4456
- }
4457
- ]
4458
- }
4459
- });
4460
- ```
4461
-
4462
- ### 🎯 Driver Types
4463
-
4464
- #### 📁 Filesystem Driver
4465
- **Perfect for**: Local backups, network storage, development
4466
-
4467
- ```javascript
4468
- {
4469
- driver: 'filesystem',
4470
- config: {
4471
- path: '/backups/{date}/', // Template path with variables
4472
- permissions: 0o644, // File permissions
4473
- directoryPermissions: 0o755 // Directory permissions
4474
- }
4475
- }
4476
- ```
4477
-
4478
- **Path Templates:**
4479
- - `{date}` → `2024-03-15`
4480
- - `{time}` → `14-30-45`
4481
- - `{year}` → `2024`
4482
- - `{month}` → `03`
4483
- - `{day}` → `15`
4484
- - `{backupId}` → `full-2024-03-15T14-30-45-abc123`
4485
- - `{type}` → `full` | `incremental`
4486
-
4487
- #### ☁️ S3 Driver
4488
- **Perfect for**: Cloud backups, long-term storage, disaster recovery
4489
-
4490
- ```javascript
4491
- {
4492
- driver: 's3',
4493
- config: {
4494
- bucket: 'my-backup-bucket', // S3 bucket (optional, uses database bucket)
4495
- path: 'backups/{date}/', // S3 key prefix with templates
4496
- storageClass: 'STANDARD_IA', // S3 storage class
4497
- serverSideEncryption: 'AES256', // Server-side encryption
4498
- client: customS3Client // Custom S3 client (optional)
4499
- }
4500
- }
4501
- ```
4502
-
4503
- **Storage Classes:** `STANDARD`, `STANDARD_IA`, `ONEZONE_IA`, `REDUCED_REDUNDANCY`, `GLACIER`, `DEEP_ARCHIVE`
4504
-
4505
- #### 🔄 Multi Driver
4506
- **Perfect for**: Redundancy, hybrid storage, complex backup strategies
4507
-
4508
- ```javascript
4509
- {
4510
- driver: 'multi',
4511
- config: {
4512
- strategy: 'all', // Backup strategy
4513
- concurrency: 3, // Max concurrent uploads
4514
- destinations: [
4515
- { driver: 'filesystem', config: {...} },
4516
- { driver: 's3', config: {...} }
4517
- ]
4518
- }
4519
- }
4520
- ```
4521
-
4522
- **Strategies:**
4523
- - **`all`**: Upload to all destinations (fail if any fails)
4524
- - **`any`**: Upload to all, succeed if at least one succeeds
4525
- - **`priority`**: Try destinations in order, stop on first success
4526
-
4527
- ### 🔧 Configuration Parameters
4528
-
4529
- | Parameter | Type | Default | Description |
4530
- |-----------|------|---------|-------------|
4531
- | **`driver`** | `string` | `'filesystem'` | Driver type: `filesystem`, `s3`, `multi` |
4532
- | **`config`** | `object` | `{}` | Driver-specific configuration |
4533
- | `retention` | `object` | `{}` | Retention policy (GFS rotation) |
4534
- | `include` | `array` | `null` | Resources to include (null = all) |
4535
- | `exclude` | `array` | `[]` | Resources to exclude |
4536
- | `compression` | `string` | `'gzip'` | `'none'`, `'gzip'`, `'brotli'`, `'deflate'` |
4537
- | `encryption` | `object` | `null` | Encryption configuration |
4538
- | `verification` | `boolean` | `true` | Verify backup integrity |
4539
- | `tempDir` | `string` | `'./tmp/backups'` | Temporary working directory |
4540
- | `verbose` | `boolean` | `false` | Enable detailed logging |
4541
-
4542
- ### 🎛️ Backup Types & Operations
4543
-
4544
- ```javascript
4545
- // Full backup - complete database snapshot
4546
- const fullBackup = await backupPlugin.backup('full');
4547
- console.log(`✓ Full backup: ${fullBackup.id} (${fullBackup.size} bytes)`);
4548
-
4549
- // Incremental backup - changes since last backup
4550
- const incrementalBackup = await backupPlugin.backup('incremental');
4551
-
4552
- // Selective backup - specific resources only
4553
- const selectiveBackup = await backupPlugin.backup('full', {
4554
- resources: ['users', 'posts']
4555
- });
4556
-
4557
- // Custom backup type
4558
- const customBackup = await backupPlugin.backup('weekly-snapshot');
4559
- ```
4560
-
4561
- ### 📋 Backup Management
4562
-
4563
- ```javascript
4564
- // List all backups
4565
- const allBackups = await backupPlugin.listBackups();
4566
-
4567
- // List with filters
4568
- const recentBackups = await backupPlugin.listBackups({
4569
- limit: 10,
4570
- prefix: 'full-2024'
4571
- });
4572
-
4573
- // Get backup status
4574
- const status = await backupPlugin.getBackupStatus(backupId);
4575
- console.log(`Status: ${status.status}, Size: ${status.size}`);
4576
-
4577
- // Restore operations
4578
- await backupPlugin.restore(backupId); // Full restore
4579
- await backupPlugin.restore(backupId, { overwrite: true }); // Overwrite existing
4580
- await backupPlugin.restore(backupId, {
4581
- resources: ['users']
4582
- }); // Selective restore
4583
- ```
4584
-
4585
- ### 🔄 Legacy Format Support
4586
-
4587
- The plugin automatically converts legacy `destinations` format:
4588
-
4589
- ```javascript
4590
- // ❌ Old format (still works)
4591
- new BackupPlugin({
4592
- destinations: [
4593
- { type: 'filesystem', path: '/backups/' }
4594
- ]
4595
- });
4596
-
4597
- // ✅ Automatically converted to:
4598
- // driver: 'multi'
4599
- // config: { destinations: [{ driver: 'filesystem', config: { path: '/backups/' } }] }
4600
- ```
4601
-
4602
- ### 📊 Retention Policies (GFS)
4603
-
4604
- Grandfather-Father-Son rotation keeps backups efficiently:
4605
-
4606
- ```javascript
4607
- retention: {
4608
- daily: 7, // Keep 7 daily backups
4609
- weekly: 4, // Keep 4 weekly backups
4610
- monthly: 12, // Keep 12 monthly backups
4611
- yearly: 3 // Keep 3 yearly backups
4612
- }
4613
- ```
4614
-
4615
- ### 🎣 Hooks & Events
4616
-
4617
- ```javascript
4618
- const backupPlugin = new BackupPlugin({
4619
- driver: 'filesystem',
4620
- config: { path: './backups/' },
4621
-
4622
- // Lifecycle hooks
4623
- onBackupStart: async (type, { backupId }) => {
4624
- console.log(`🚀 Starting ${type} backup: ${backupId}`);
4625
- await notifySlack(`Backup ${backupId} started`);
4626
- },
4627
-
4628
- onBackupComplete: async (type, stats) => {
4629
- console.log(`✅ ${type} backup completed:`, {
4630
- id: stats.backupId,
4631
- size: `${Math.round(stats.size / 1024)}KB`,
4632
- duration: `${stats.duration}ms`,
4633
- destinations: stats.driverInfo
4634
- });
4635
- },
4636
-
4637
- onBackupError: async (type, { backupId, error }) => {
4638
- console.error(`❌ Backup ${backupId} failed:`, error.message);
4639
- await alertOps(error);
4640
- }
4641
- });
4642
-
4643
- // Event listeners
4644
- backupPlugin.on('backup_start', ({ id, type }) => {
4645
- updateDashboard(`Backup ${id} started`);
4646
- });
4647
-
4648
- backupPlugin.on('backup_complete', ({ id, type, size, duration }) => {
4649
- metrics.record('backup.completed', { type, size, duration });
4650
- });
4651
-
4652
- backupPlugin.on('restore_complete', ({ id, restored }) => {
4653
- console.log(`Restored ${restored.length} resources from ${id}`);
4654
- });
4655
- ```
4656
-
4657
- ### 🔒 Advanced Security
4658
-
4659
- ```javascript
4660
- const secureBackupPlugin = new BackupPlugin({
4661
- driver: 's3',
4662
- config: {
4663
- bucket: 'secure-backups',
4664
- storageClass: 'STANDARD_IA',
4665
- serverSideEncryption: 'aws:kms',
4666
- kmsKeyId: 'arn:aws:kms:region:account:key/key-id'
4667
- },
4668
-
4669
- // Client-side encryption (before upload)
4670
- encryption: {
4671
- algorithm: 'AES-256-GCM',
4672
- key: process.env.BACKUP_ENCRYPTION_KEY,
4673
- keyDerivation: {
4674
- algorithm: 'PBKDF2',
4675
- iterations: 100000,
4676
- salt: 'backup-salt-2024'
4677
- }
4678
- },
4679
-
4680
- // Integrity verification
4681
- verification: true,
4682
-
4683
- // Compression for efficiency
4684
- compression: 'gzip'
4685
- });
4686
- ```
4687
-
4688
- ### 🚀 Production Examples
4689
-
4690
- #### Enterprise Multi-Region Setup
4691
- ```javascript
4692
- const enterpriseBackup = new BackupPlugin({
4693
- driver: 'multi',
4694
- config: {
4695
- strategy: 'all',
4696
- destinations: [
4697
- {
4698
- driver: 's3',
4699
- config: {
4700
- bucket: 'backups-us-east-1',
4701
- path: 'production/{date}/',
4702
- storageClass: 'STANDARD_IA'
4703
- }
4704
- },
4705
- {
4706
- driver: 's3',
4707
- config: {
4708
- bucket: 'backups-eu-west-1',
4709
- path: 'production/{date}/',
4710
- storageClass: 'STANDARD_IA'
4711
- }
4712
- },
4713
- {
4714
- driver: 'filesystem',
4715
- config: {
4716
- path: '/mnt/backup-nas/s3db/{date}/'
4717
- }
4718
- }
4719
- ]
4720
- },
4721
- retention: {
4722
- daily: 30,
4723
- weekly: 12,
4724
- monthly: 24,
4725
- yearly: 7
4726
- },
4727
- verification: true,
4728
- compression: 'gzip'
4729
- });
4730
- ```
4731
-
4732
- #### Development Quick Backup
4733
- ```javascript
4734
- const devBackup = new BackupPlugin({
4735
- driver: 'filesystem',
4736
- config: {
4737
- path: './dev-backups/{date}/'
4738
- },
4739
- compression: 'none',
4740
- verification: false,
4741
- verbose: true,
4742
- retention: { daily: 3 }
4743
- });
4744
- ```
4745
-
4746
- ### 🎯 CLI Integration
4747
-
4748
- The BackupPlugin works with s3db CLI commands:
4749
-
4750
- ```bash
4751
- # Create backups
4752
- s3db backup full --connection "s3://key:secret@bucket"
4753
- s3db backup incremental --connection "s3://key:secret@bucket"
4754
-
4755
- # List and status
4756
- s3db backup --list --connection "s3://key:secret@bucket"
4757
- s3db backup --status backup-id --connection "s3://key:secret@bucket"
4758
-
4759
- # Restore operations
4760
- s3db restore backup-id --connection "s3://key:secret@bucket"
4761
- s3db restore backup-id --overwrite --connection "s3://key:secret@bucket"
4762
- ```
4763
-
4764
- > **Note**: CLI requires the BackupPlugin to be installed in the database instance.
4765
-
4766
- ### 🔍 Driver Information
4767
-
4768
- ```javascript
4769
- // Get driver details
4770
- const driverInfo = backupPlugin.driver.getStorageInfo();
4771
- console.log('Driver type:', driverInfo.type);
4772
- console.log('Configuration:', driverInfo.config);
4773
-
4774
- // Multi-driver details
4775
- if (driverInfo.type === 'multi') {
4776
- console.log('Strategy:', driverInfo.strategy);
4777
- driverInfo.destinations.forEach((dest, i) => {
4778
- console.log(`Destination ${i}:`, dest.driver, dest.info);
4779
- });
4780
- }
4781
- ```
4782
-
4783
- ---
4784
-
4785
- ## ⏰ Scheduler Plugin
4786
-
4787
- Robust job scheduling capabilities with cron expressions, retry logic, and comprehensive monitoring for automated tasks.
4788
-
4789
- ### ⚡ Quick Start
4790
-
4791
- ```javascript
4792
- import { S3db, SchedulerPlugin } from 's3db.js';
4793
-
4794
- const s3db = new S3db({
4795
- connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
4796
- plugins: [
4797
- new SchedulerPlugin({
4798
- timezone: 'America/Sao_Paulo',
4799
- jobs: {
4800
- daily_cleanup: {
4801
- schedule: '0 3 * * *', // 3 AM daily
4802
- description: 'Clean up expired sessions',
4803
- action: async (database, context) => {
4804
- const expired = await database.resource('sessions').list({
4805
- where: { expiresAt: { $lt: new Date() } }
4806
- });
4807
-
4808
- for (const session of expired) {
4809
- await database.resource('sessions').delete(session.id);
4810
- }
4811
-
4812
- return { deleted: expired.length };
4813
- },
4814
- enabled: true,
4815
- retries: 2,
4816
- timeout: 30000
4817
- },
4818
-
4819
- hourly_metrics: {
4820
- schedule: '@hourly',
4821
- description: 'Collect system metrics',
4822
- action: async (database) => {
4823
- const metrics = {
4824
- timestamp: new Date().toISOString(),
4825
- memory: process.memoryUsage(),
4826
- uptime: process.uptime()
4827
- };
4828
-
4829
- await database.resource('metrics').insert({
4830
- id: `metrics_${Date.now()}`,
4831
- ...metrics
4832
- });
4833
-
4834
- return metrics;
4835
- }
4836
- }
4837
- },
4838
- onJobComplete: (jobName, result, duration) => {
4839
- console.log(`Job ${jobName} completed in ${duration}ms`);
4840
- }
4841
- })
4842
- ]
4843
- });
4844
-
4845
- await s3db.connect();
4846
-
4847
- // Jobs run automatically based on schedule
4848
- // Manual execution
4849
- await s3db.plugins.scheduler.runJob('daily_cleanup');
4850
-
4851
- // Get job status
4852
- const allJobs = s3db.plugins.scheduler.getAllJobsStatus();
4853
- console.log('Scheduled jobs:', allJobs.length);
4854
- ```
4855
-
4856
- ### ⚙️ Configuration Parameters
4857
-
4858
- | Parameter | Type | Default | Description |
4859
- |-----------|------|---------|-------------|
4860
- | `timezone` | string | `'UTC'` | IANA timezone identifier |
4861
- | `jobs` | object | `{}` | Job definitions |
4862
- | `defaultTimeout` | number | `60000` | Default job timeout (ms) |
4863
- | `defaultRetries` | number | `1` | Default retry attempts |
4864
- | `persistJobs` | boolean | `true` | Store job execution history |
4865
- | `jobHistoryResource` | string | `'job_history'` | Resource for job history |
4866
- | `onJobStart` | function | `null` | Callback when job starts |
4867
- | `onJobComplete` | function | `null` | Callback when job completes |
4868
- | `onJobError` | function | `null` | Callback when job fails |
4869
- | `verbose` | boolean | `false` | Enable detailed logging |
4870
-
4871
- ### Job Configuration
4872
-
4873
- ```javascript
4874
- jobs: {
4875
- job_name: {
4876
- schedule: '0 0 * * *', // Cron expression (required)
4877
- description: 'Job description', // Human-readable description
4878
- action: async (database, context, schedulerPlugin) => {
4879
- // Job implementation (required)
4880
- return { success: true };
4881
- },
4882
- enabled: true, // Enable/disable job
4883
- retries: 2, // Retry attempts on failure
4884
- timeout: 30000, // Timeout in milliseconds
4885
- meta: { priority: 'high' } // Custom metadata
4886
- }
4887
- }
4888
- ```
4889
-
4890
- ### Cron Expressions
4891
-
4892
- #### Standard Format
4893
- ```
4894
- * * * * *
4895
- │ │ │ │ │
4896
- │ │ │ │ └─── Day of week (0-7, Sunday = 0 or 7)
4897
- │ │ │ └───── Month (1-12)
4898
- │ │ └─────── Day of month (1-31)
4899
- │ └───────── Hour (0-23)
4900
- └─────────── Minute (0-59)
4901
- ```
4902
-
4903
- #### Examples
4904
- ```javascript
4905
- '0 0 * * *' // Daily at midnight
4906
- '0 9 * * MON' // Every Monday at 9 AM
4907
- '*/15 * * * *' // Every 15 minutes
4908
- '0 2 1 * *' // First day of month at 2 AM
4909
- ```
4910
-
4911
- #### Shorthand Expressions
4912
- ```javascript
4913
- '@yearly' // Once a year at midnight on January 1st
4914
- '@monthly' // Once a month at midnight on the 1st
4915
- '@weekly' // Once a week at midnight on Sunday
4916
- '@daily' // Once a day at midnight
4917
- '@hourly' // Once an hour at the beginning of the hour
4918
- ```
4919
-
4920
- ### API Methods
4921
-
4922
- ```javascript
4923
- // Job execution
4924
- const result = await scheduler.runJob(jobName, context);
4925
- const stats = scheduler.getJobStatus(jobName);
4926
- const allJobs = scheduler.getAllJobsStatus();
4927
-
4928
- // Job management
4929
- scheduler.enableJob(jobName);
4930
- scheduler.disableJob(jobName);
4931
- scheduler.addJob(jobName, jobConfig);
4932
- scheduler.removeJob(jobName);
4933
-
4934
- // History and monitoring
4935
- const history = await scheduler.getJobHistory(jobName, { status: 'success', limit: 10 });
4936
- const stats = scheduler.getJobStatistics(jobName);
4937
- ```
4938
-
4939
- ### Events
4940
-
4941
- ```javascript
4942
- scheduler.on('job_start', ({ jobName, context }) => {
4943
- console.log(`Job ${jobName} started`);
4944
- });
4945
-
4946
- scheduler.on('job_complete', ({ jobName, result, duration }) => {
4947
- console.log(`Job ${jobName} completed in ${duration}ms`);
4948
- });
4949
-
4950
- scheduler.on('job_error', ({ jobName, error, retryCount }) => {
4951
- console.error(`Job ${jobName} failed (attempt ${retryCount}):`, error);
4952
- });
4953
-
4954
- scheduler.on('job_enabled', ({ jobName }) => {
4955
- console.log(`Job ${jobName} enabled`);
4956
- });
4957
- ```
4958
-
4959
- ### Integration with Other Plugins
4960
-
4961
- ```javascript
4962
- // Scheduled backups
4963
- jobs: {
4964
- daily_backup: {
4965
- schedule: '0 1 * * *',
4966
- action: async (database) => {
4967
- const backup = database.getPlugin('BackupPlugin');
4968
- return await backup.backup('full');
4969
- }
4970
- }
4971
- }
4972
-
4973
- // Process state machine entities
4974
- jobs: {
4975
- process_pending_orders: {
4976
- schedule: '*/10 * * * *',
4977
- action: async (database) => {
4978
- const stateMachine = database.getPlugin('StateMachinePlugin');
4979
- const orders = await database.resource('orders').list({
4980
- where: { status: 'pending' }
4981
- });
4982
-
4983
- for (const order of orders) {
4984
- await stateMachine.send('order_processing', order.id, 'AUTO_PROCESS');
4985
- }
4986
-
4987
- return { processed: orders.length };
4988
- }
4989
- }
4990
- }
4991
- ```
4992
-
4993
- ---
4994
-
4995
- ## 🎯 Best Practices
4996
-
4997
- ### Plugin Performance
4998
-
4999
- 1. **Enable caching** for read-heavy workloads
5000
- 2. **Monitor costs** in production environments
5001
- 3. **Use appropriate sampling** for metrics collection
5002
- 4. **Configure retention policies** for audit logs
5003
- 5. **Test replicator connections** before deployment
5004
- 6. **Design efficient state machines** with minimal guards and actions
5005
- 7. **Schedule backups during low-traffic periods** to minimize impact
5006
- 8. **Use appropriate job timeouts** to prevent resource exhaustion
5007
-
5008
- ### Plugin Security
5009
-
5010
- 1. **Exclude sensitive resources** from full-text indexing
5011
- 2. **Limit audit data size** to prevent information leakage
5012
- 3. **Use IAM roles** instead of access keys when possible
5013
- 4. **Encrypt replication data** in transit and at rest
5014
- 5. **Validate message sources** in queue consumers
5015
- 6. **Secure state machine actions** to prevent unauthorized transitions
5016
- 7. **Encrypt backup data** for sensitive databases
5017
- 8. **Restrict job execution permissions** to necessary resources only
5018
-
5019
- ### Plugin Monitoring
5020
-
5021
- 1. **Set up alerting** for replication failures
5022
- 2. **Monitor plugin health** with metrics
5023
- 3. **Track error rates** across all plugins
5024
- 4. **Use structured logging** for debugging
5025
- 5. **Implement circuit breakers** for external services
5026
- 6. **Monitor state machine transition rates** and error patterns
5027
- 7. **Track backup success rates** and storage usage
5028
- 8. **Alert on job failures** and execution delays
5029
-
5030
- ---
5031
-
5032
- **🎉 That's a wrap!** You now have comprehensive documentation for all s3db.js plugins. Each plugin is designed to work independently or in combination with others, providing a powerful and flexible foundation for your database needs.
5033
-
5034
- For more examples and advanced use cases, check out the `/examples` directory in the s3db.js repository.
5035
-
5036
- **Happy coding with s3db.js! 🚀**