s3db.js 7.3.4 โ†’ 7.3.6

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 CHANGED
@@ -1561,7 +1561,16 @@ setTimeout(() => {
1561
1561
 
1562
1562
  ## ๐Ÿ”„ Replicator Plugin
1563
1563
 
1564
- Powerful data replication system that synchronizes your s3db data to multiple targets including other S3DB instances, SQS queues, BigQuery, and PostgreSQL databases.
1564
+ **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.
1565
+
1566
+ ### ๐ŸŽฏ Key Features
1567
+
1568
+ - **Real-time Replication**: Automatic data synchronization on insert, update, and delete operations
1569
+ - **Multi-Target Support**: Replicate to S3DB, BigQuery, PostgreSQL, SQS, and custom targets
1570
+ - **Advanced Transformations**: Transform data with custom functions before replication
1571
+ - **Error Resilience**: Automatic retries, detailed error reporting, and dead letter queue support
1572
+ - **Performance Monitoring**: Built-in metrics, performance tracking, and health monitoring
1573
+ - **Flexible Configuration**: Support for multiple resource mapping syntaxes and selective replication
1565
1574
 
1566
1575
  ### โšก Quick Start
1567
1576
 
@@ -1571,6 +1580,7 @@ import { S3db, ReplicatorPlugin } from 's3db.js';
1571
1580
  const s3db = new S3db({
1572
1581
  connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
1573
1582
  plugins: [new ReplicatorPlugin({
1583
+ verbose: true, // Enable detailed logging for debugging
1574
1584
  replicators: [
1575
1585
  {
1576
1586
  driver: 's3db',
@@ -1585,7 +1595,7 @@ const s3db = new S3db({
1585
1595
 
1586
1596
  await s3db.connect();
1587
1597
 
1588
- // Data is automatically replicated
1598
+ // Data is automatically replicated with detailed error reporting
1589
1599
  const users = s3db.resource('users');
1590
1600
  await users.insert({ name: 'John', email: 'john@example.com' });
1591
1601
  // This insert is automatically replicated to the backup database
@@ -1595,21 +1605,56 @@ await users.insert({ name: 'John', email: 'john@example.com' });
1595
1605
 
1596
1606
  | Parameter | Type | Default | Description |
1597
1607
  |-----------|------|---------|-------------|
1598
- | `enabled` | boolean | `true` | Enable/disable replication |
1599
- | `replicators` | array | `[]` | Array of replicator configurations |
1600
- | `persistReplicatorLog` | boolean | `false` | Store replication logs in database |
1601
- | `replicatorLogResource` | string | `'replicator_logs'` | Name of log resource |
1602
- | `batchSize` | number | `10` | Batch size for bulk operations |
1603
- | `retryAttempts` | number | `3` | Retry failed replications |
1604
- | `retryDelay` | number | `1000` | Delay between retries (ms) |
1605
- | `syncInterval` | number | `0` | Auto-sync interval (0 = disabled) |
1608
+ | `enabled` | boolean | `true` | Enable/disable replication globally |
1609
+ | `replicators` | array | `[]` | Array of replicator configurations (required) |
1610
+ | `verbose` | boolean | `false` | Enable detailed console logging for debugging |
1611
+ | `persistReplicatorLog` | boolean | `false` | Store replication logs in database resource |
1612
+ | `replicatorLogResource` | string | `'replicator_log'` | Name of log resource for persistence |
1613
+ | `logErrors` | boolean | `true` | Log errors to replication log resource |
1614
+ | `batchSize` | number | `100` | Batch size for bulk replication operations |
1615
+ | `maxRetries` | number | `3` | Maximum retry attempts for failed replications |
1616
+ | `timeout` | number | `30000` | Timeout for replication operations (ms) |
1617
+
1618
+ ### ๐Ÿ—‚๏ธ Event System & Debugging
1619
+
1620
+ The Replicator Plugin emits comprehensive events for monitoring and debugging:
1621
+
1622
+ ```javascript
1623
+ const replicatorPlugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
1606
1624
 
1607
- ### Replicator Drivers
1625
+ // Success events
1626
+ replicatorPlugin.on('replicated', (data) => {
1627
+ console.log(`โœ… Replicated: ${data.operation} on ${data.resourceName} to ${data.replicator}`);
1628
+ });
1608
1629
 
1609
- #### S3DB Replicator
1630
+ // Error events
1631
+ replicatorPlugin.on('replicator_error', (data) => {
1632
+ console.error(`โŒ Replication failed: ${data.error} (${data.resourceName})`);
1633
+ });
1610
1634
 
1611
- Replicate to another S3DB instance with flexible resource mapping and transformation capabilities:
1635
+ // Log resource errors
1636
+ replicatorPlugin.on('replicator_log_error', (data) => {
1637
+ console.warn(`โš ๏ธ Failed to log replication: ${data.logError}`);
1638
+ });
1612
1639
 
1640
+ // Setup errors
1641
+ replicatorPlugin.on('replicator_log_resource_creation_error', (data) => {
1642
+ console.error(`๐Ÿšจ Log resource creation failed: ${data.error}`);
1643
+ });
1644
+
1645
+ // Cleanup errors
1646
+ replicatorPlugin.on('replicator_cleanup_error', (data) => {
1647
+ console.warn(`๐Ÿงน Cleanup failed for ${data.replicator}: ${data.error}`);
1648
+ });
1649
+ ```
1650
+
1651
+ ### ๐Ÿ”ง Replicator Drivers
1652
+
1653
+ #### ๐Ÿ—ƒ๏ธ S3DB Replicator
1654
+
1655
+ Replicate to another S3DB instance with **advanced resource mapping and transformation capabilities**. Supports multiple configuration syntaxes for maximum flexibility.
1656
+
1657
+ **Basic Configuration:**
1613
1658
  ```javascript
1614
1659
  {
1615
1660
  driver: 's3db',
@@ -1623,43 +1668,34 @@ Replicate to another S3DB instance with flexible resource mapping and transforma
1623
1668
  // Map source โ†’ destination resource name
1624
1669
  products: 'backup_products',
1625
1670
 
1626
- // Advanced mapping with transformer
1671
+ // Advanced mapping with transform function
1627
1672
  orders: {
1628
1673
  resource: 'order_backup',
1629
- transformer: (data) => ({
1674
+ transform: (data) => ({
1630
1675
  ...data,
1631
1676
  backup_timestamp: new Date().toISOString(),
1632
- original_source: 'production'
1633
- })
1634
- },
1635
-
1636
- // Multi-destination replication
1637
- analytics: [
1638
- 'analytics_backup',
1639
- {
1640
- resource: 'analytics_processed',
1641
- transformer: (data) => ({
1642
- ...data,
1643
- processed_date: data.createdAt?.split('T')[0],
1644
- data_source: 'analytics'
1645
- })
1646
- }
1647
- ]
1677
+ original_source: 'production',
1678
+ migrated_at: new Date().toISOString()
1679
+ }),
1680
+ actions: ['insert', 'update', 'delete']
1681
+ }
1648
1682
  }
1649
1683
  }
1650
1684
  ```
1651
1685
 
1652
- **Resource Configuration Syntaxes:**
1686
+ ### ๐Ÿ“‹ Resource Configuration Syntaxes
1653
1687
 
1654
- The S3DB replicator supports highly flexible resource mapping configurations:
1688
+ The S3DB replicator supports **multiple configuration syntaxes** for maximum flexibility. You can mix and match these formats as needed:
1655
1689
 
1656
- **1. Array of resource names** (replicate to same name):
1690
+ #### 1. Array of Resource Names
1691
+ **Use case**: Simple backup/clone scenarios
1657
1692
  ```javascript
1658
1693
  resources: ['users', 'products', 'orders']
1659
1694
  // Replicates each resource to itself in the destination database
1660
1695
  ```
1661
1696
 
1662
- **2. Object mapping** (source โ†’ destination):
1697
+ #### 2. Simple Object Mapping
1698
+ **Use case**: Rename resources during replication
1663
1699
  ```javascript
1664
1700
  resources: {
1665
1701
  users: 'people', // users โ†’ people
@@ -1668,230 +1704,1322 @@ resources: {
1668
1704
  }
1669
1705
  ```
1670
1706
 
1671
- **3. Array with transformer** (resource + transformation):
1707
+ #### 3. Object with Transform Function
1708
+ **Use case**: Data transformation during replication โญ **RECOMMENDED**
1672
1709
  ```javascript
1673
1710
  resources: {
1674
- users: ['people', (data) => ({ ...data, fullName: `${data.firstName} ${data.lastName}` })]
1675
- // Replicates 'users' to 'people' with transformation
1711
+ users: {
1712
+ resource: 'people', // Destination resource name
1713
+ transform: (data) => ({ // Data transformation function
1714
+ ...data,
1715
+ fullName: `${data.firstName} ${data.lastName}`,
1716
+ migrated_at: new Date().toISOString(),
1717
+ source_system: 'production'
1718
+ }),
1719
+ actions: ['insert', 'update', 'delete'] // Optional: which operations to replicate
1720
+ }
1676
1721
  }
1677
1722
  ```
1678
1723
 
1679
- **4. Object with resource and transformer**:
1724
+ #### 4. Function-Only Transformation
1725
+ **Use case**: Transform data without changing resource name
1680
1726
  ```javascript
1681
1727
  resources: {
1682
- users: {
1683
- resource: 'people',
1684
- transformer: (data) => ({
1685
- ...data,
1686
- fullName: `${data.firstName} ${data.lastName}`,
1687
- migrated_at: new Date().toISOString()
1688
- })
1689
- }
1728
+ users: (data) => ({
1729
+ ...data,
1730
+ processed: true,
1731
+ backup_date: new Date().toISOString(),
1732
+ hash: crypto.createHash('md5').update(JSON.stringify(data)).digest('hex')
1733
+ })
1690
1734
  }
1691
1735
  ```
1692
1736
 
1693
- **5. Multi-destination arrays**:
1737
+ #### 5. Multi-Destination Replication
1738
+ **Use case**: Send data to multiple targets with different transformations
1694
1739
  ```javascript
1695
1740
  resources: {
1696
1741
  users: [
1697
- 'people', // Simple copy
1742
+ 'people', // Simple copy to 'people'
1698
1743
  {
1699
1744
  resource: 'user_analytics',
1700
- transformer: (data) => ({
1745
+ transform: (data) => ({ // Transformed copy to 'user_analytics'
1701
1746
  id: data.id,
1702
1747
  signup_date: data.createdAt,
1703
- user_type: data.role || 'standard'
1748
+ user_type: data.role || 'standard',
1749
+ last_activity: new Date().toISOString()
1750
+ })
1751
+ },
1752
+ {
1753
+ resource: 'audit_trail',
1754
+ transform: (data) => ({ // Audit copy to 'audit_trail'
1755
+ user_id: data.id,
1756
+ action: 'user_replicated',
1757
+ timestamp: new Date().toISOString(),
1758
+ data_hash: crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex')
1704
1759
  })
1705
1760
  }
1706
1761
  ]
1707
1762
  }
1708
1763
  ```
1709
1764
 
1710
- **6. Function-only transformation** (transform to same resource):
1765
+ #### 6. Advanced Mixed Configuration
1766
+ **Use case**: Complex enterprise scenarios
1711
1767
  ```javascript
1712
- resources: {
1713
- users: (data) => ({
1768
+ resources: {
1769
+ // Simple mappings
1770
+ sessions: 'user_sessions',
1771
+
1772
+ // Transform without renaming
1773
+ products: (data) => ({
1714
1774
  ...data,
1715
- processed: true,
1716
- backup_date: new Date().toISOString()
1717
- })
1775
+ search_keywords: data.name?.toLowerCase().split(' ') || [],
1776
+ price_category: data.price > 100 ? 'premium' : 'standard'
1777
+ }),
1778
+
1779
+ // Complex multi-destination with conditions
1780
+ orders: [
1781
+ 'order_backup', // Simple backup
1782
+ {
1783
+ resource: 'order_analytics',
1784
+ transform: (data) => ({
1785
+ order_id: data.id,
1786
+ customer_id: data.userId,
1787
+ amount: data.amount,
1788
+ order_date: data.createdAt?.split('T')[0],
1789
+ status: data.status || 'pending',
1790
+ item_count: data.items?.length || 0,
1791
+ is_large_order: data.amount > 1000
1792
+ }),
1793
+ actions: ['insert', 'update'] // Only replicate inserts and updates, not deletes
1794
+ },
1795
+ {
1796
+ resource: 'financial_records',
1797
+ transform: (data) => ({
1798
+ transaction_id: data.id,
1799
+ amount: data.amount,
1800
+ currency: data.currency || 'USD',
1801
+ type: 'order_payment',
1802
+ timestamp: new Date().toISOString(),
1803
+ metadata: {
1804
+ customer_id: data.userId,
1805
+ order_items: data.items?.length || 0
1806
+ }
1807
+ }),
1808
+ actions: ['insert'] // Only replicate new orders for financial records
1809
+ }
1810
+ ],
1811
+
1812
+ // Conditional replication
1813
+ users: {
1814
+ resource: 'customer_profiles',
1815
+ transform: (data) => {
1816
+ // Only replicate active users
1817
+ if (data.status !== 'active') return null;
1818
+
1819
+ return {
1820
+ ...data,
1821
+ fullName: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
1822
+ account_age_days: data.createdAt ?
1823
+ Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0,
1824
+ preferences: data.preferences || {},
1825
+ last_updated: new Date().toISOString()
1826
+ };
1827
+ },
1828
+ actions: ['insert', 'update']
1829
+ }
1718
1830
  }
1719
1831
  ```
1720
1832
 
1721
- **Mixed Configuration Example:**
1833
+ ### ๐Ÿ”ง S3DB Replicator Configuration Options
1834
+
1835
+ | Parameter | Type | Required | Description |
1836
+ |-----------|------|----------|-------------|
1837
+ | `connectionString` | string | Yes* | S3DB connection string for destination database |
1838
+ | `client` | S3db | Yes* | Pre-configured S3DB client instance |
1839
+ | `resources` | object/array | Yes | Resource mapping configuration (see syntaxes above) |
1840
+ | `timeout` | number | No | Operation timeout in milliseconds (default: 30000) |
1841
+ | `retryAttempts` | number | No | Number of retry attempts for failed operations (default: 3) |
1842
+
1843
+ *Either `connectionString` or `client` must be provided.
1844
+
1845
+ ### ๐ŸŽฏ Transform Function Features
1846
+
1847
+ Transform functions provide powerful data manipulation capabilities:
1848
+
1849
+ #### Data Transformation Examples:
1722
1850
  ```javascript
1723
- resources: {
1724
- users: [
1725
- 'people', // Simple copy
1726
- {
1727
- resource: 'user_profiles',
1728
- transformer: (data) => ({
1729
- ...data,
1730
- profile_complete: !!(data.name && data.email)
1731
- })
1732
- }
1733
- ],
1734
- orders: 'order_backup', // Rename only
1735
- products: { resource: 'product_catalog' }, // Object form
1736
- analytics: (data) => ({ ...data, processed: true }) // Transform only
1851
+ // 1. Field mapping and enrichment
1852
+ transform: (data) => ({
1853
+ id: data.id,
1854
+ customer_name: `${data.firstName} ${data.lastName}`,
1855
+ email_domain: data.email?.split('@')[1] || 'unknown',
1856
+ created_timestamp: Date.now(),
1857
+ source: 'production-db'
1858
+ })
1859
+
1860
+ // 2. Conditional logic
1861
+ transform: (data) => {
1862
+ if (data.type === 'premium') {
1863
+ return { ...data, priority: 'high', sla: '4hours' };
1864
+ }
1865
+ return { ...data, priority: 'normal', sla: '24hours' };
1866
+ }
1867
+
1868
+ // 3. Data validation and filtering
1869
+ transform: (data) => {
1870
+ // Skip replication for invalid data
1871
+ if (!data.email || !data.name) return null;
1872
+
1873
+ return {
1874
+ ...data,
1875
+ email: data.email.toLowerCase(),
1876
+ name: data.name.trim(),
1877
+ validated: true
1878
+ };
1737
1879
  }
1880
+
1881
+ // 4. Computed fields
1882
+ transform: (data) => ({
1883
+ ...data,
1884
+ age: data.birthDate ?
1885
+ Math.floor((Date.now() - new Date(data.birthDate)) / (1000 * 60 * 60 * 24 * 365)) : null,
1886
+ account_value: (data.orders || []).reduce((sum, order) => sum + order.amount, 0),
1887
+ last_activity: new Date().toISOString()
1888
+ })
1889
+
1890
+ // 5. Data aggregation
1891
+ transform: (data) => ({
1892
+ user_id: data.id,
1893
+ total_orders: data.orderHistory?.length || 0,
1894
+ total_spent: data.orderHistory?.reduce((sum, order) => sum + order.amount, 0) || 0,
1895
+ favorite_category: data.orderHistory?.map(o => o.category)
1896
+ .sort((a,b) => a.localeCompare(b))
1897
+ .reduce((prev, curr, i, arr) =>
1898
+ arr.filter(v => v === prev).length >= arr.filter(v => v === curr).length ? prev : curr
1899
+ ) || null,
1900
+ analysis_date: new Date().toISOString()
1901
+ })
1738
1902
  ```
1739
1903
 
1740
- **Configuration Options:**
1741
- - `connectionString`: S3DB connection string for destination database (required)
1742
- - `client`: Pre-configured S3DB client instance (alternative to connectionString)
1743
- - `resources`: Resource mapping configuration (see syntaxes above)
1904
+ **Transform Function Best Practices:**
1905
+ - **Return `null`** to skip replication for specific records
1906
+ - **Preserve the `id` field** unless specifically mapping to a different field
1907
+ - **Handle edge cases** like missing fields or null values
1908
+ - **Use immutable operations** to avoid modifying the original data
1909
+ - **Keep transforms lightweight** to maintain replication performance
1910
+ - **Add metadata fields** like timestamps for tracking purposes
1744
1911
 
1745
- **Transformer Features:**
1746
- - **Data Transformation**: Apply custom logic before replication
1747
- - **Field Mapping**: Rename, combine, or derive new fields
1748
- - **Data Enrichment**: Add metadata, timestamps, or computed values
1749
- - **Conditional Logic**: Apply transformations based on data content
1750
- - **Multi-destination**: Send different transformed versions to multiple targets
1912
+ #### ๐Ÿ“ฌ SQS Replicator
1751
1913
 
1752
- #### SQS Replicator
1914
+ **Real-time event streaming** to AWS SQS queues for microservices integration and event-driven architectures.
1753
1915
 
1754
- Send changes to AWS SQS queues:
1916
+ **โš ๏ธ Required Dependency:**
1917
+ ```bash
1918
+ pnpm add @aws-sdk/client-sqs
1919
+ ```
1755
1920
 
1921
+ **Basic Configuration:**
1756
1922
  ```javascript
1757
1923
  {
1758
1924
  driver: 'sqs',
1759
- resources: ['orders'],
1925
+ resources: ['orders', 'users'],
1760
1926
  config: {
1761
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
1762
1927
  region: 'us-east-1',
1763
- messageGroupId: 's3db-replicator',
1928
+ queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/events.fifo',
1929
+ messageGroupId: 's3db-events',
1764
1930
  deduplicationId: true
1765
1931
  }
1766
1932
  }
1767
1933
  ```
1768
1934
 
1769
- #### BigQuery Replicator
1935
+ **Advanced Configuration with Resource-Specific Queues:**
1936
+ ```javascript
1937
+ {
1938
+ driver: 'sqs',
1939
+ config: {
1940
+ region: 'us-east-1',
1941
+ credentials: {
1942
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1943
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
1944
+ },
1945
+
1946
+ // Resource-specific queue URLs
1947
+ queues: {
1948
+ orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/order-events.fifo',
1949
+ users: 'https://sqs.us-east-1.amazonaws.com/123456789012/user-events.fifo',
1950
+ payments: 'https://sqs.us-east-1.amazonaws.com/123456789012/payment-events.fifo'
1951
+ },
1952
+
1953
+ // Default queue for resources not specifically mapped
1954
+ defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/general-events.fifo',
1955
+
1956
+ // FIFO queue settings
1957
+ messageGroupId: 's3db-replicator',
1958
+ deduplicationId: true,
1959
+
1960
+ // Message attributes (applied to all messages)
1961
+ messageAttributes: {
1962
+ source: { StringValue: 'production-db', DataType: 'String' },
1963
+ version: { StringValue: '1.0', DataType: 'String' },
1964
+ environment: { StringValue: process.env.NODE_ENV || 'development', DataType: 'String' }
1965
+ }
1966
+ },
1967
+ resources: {
1968
+ // Simple resource mapping
1969
+ orders: true,
1970
+ users: true,
1971
+
1972
+ // Resource with transformation
1973
+ payments: {
1974
+ transform: (data) => ({
1975
+ payment_id: data.id,
1976
+ amount: data.amount,
1977
+ currency: data.currency || 'USD',
1978
+ customer_id: data.userId,
1979
+ payment_method: data.method,
1980
+ status: data.status,
1981
+ timestamp: new Date().toISOString(),
1982
+
1983
+ // Add computed fields
1984
+ amount_usd: data.currency === 'USD' ? data.amount : data.amount * (data.exchange_rate || 1),
1985
+ is_large_payment: data.amount > 1000,
1986
+ risk_score: data.amount > 5000 ? 'high' : data.amount > 1000 ? 'medium' : 'low'
1987
+ })
1988
+ }
1989
+ }
1990
+ }
1991
+ ```
1770
1992
 
1771
- Replicate to Google BigQuery with advanced data transformation capabilities:
1993
+ **SQS Message Format:**
1994
+ ```javascript
1995
+ {
1996
+ // Message Body (JSON string)
1997
+ MessageBody: JSON.stringify({
1998
+ resource: 'orders', // Source resource name
1999
+ operation: 'insert', // Operation: insert, update, delete
2000
+ id: 'order-123', // Record ID
2001
+ data: { // Transformed data payload
2002
+ id: 'order-123',
2003
+ userId: 'user-456',
2004
+ amount: 99.99,
2005
+ status: 'pending',
2006
+ timestamp: '2024-01-15T10:30:00.000Z'
2007
+ },
2008
+ beforeData: null, // Previous data for updates
2009
+ metadata: {
2010
+ source: 'production-db',
2011
+ replicator: 'sqs-replicator',
2012
+ timestamp: '2024-01-15T10:30:00.000Z'
2013
+ }
2014
+ }),
2015
+
2016
+ // Message Attributes
2017
+ MessageAttributes: {
2018
+ source: { StringValue: 'production-db', DataType: 'String' },
2019
+ resource: { StringValue: 'orders', DataType: 'String' },
2020
+ operation: { StringValue: 'insert', DataType: 'String' }
2021
+ },
2022
+
2023
+ // FIFO Queue Settings
2024
+ MessageGroupId: 's3db-events',
2025
+ MessageDeduplicationId: 'orders:insert:order-123'
2026
+ }
2027
+ ```
1772
2028
 
2029
+ **Configuration Options:**
2030
+
2031
+ | Parameter | Type | Required | Description |
2032
+ |-----------|------|----------|-------------|
2033
+ | `region` | string | Yes | AWS region for SQS service |
2034
+ | `queueUrl` | string | Yes* | Single queue URL for all resources |
2035
+ | `queues` | object | Yes* | Resource-specific queue mapping |
2036
+ | `defaultQueueUrl` | string | No | Fallback queue for unmapped resources |
2037
+ | `credentials` | object | No | AWS credentials (uses default chain if omitted) |
2038
+ | `messageGroupId` | string | No | Message group ID for FIFO queues |
2039
+ | `deduplicationId` | boolean | No | Enable message deduplication for FIFO queues |
2040
+ | `messageAttributes` | object | No | Custom message attributes applied to all messages |
2041
+
2042
+ *Either `queueUrl` or `queues` must be provided.
2043
+
2044
+ #### ๐Ÿ“Š BigQuery Replicator
2045
+
2046
+ **Data warehouse integration** for Google BigQuery with advanced transformation capabilities, multi-table support, and automatic retry logic for streaming buffer limitations.
2047
+
2048
+ **โš ๏ธ Required Dependency:**
2049
+ ```bash
2050
+ pnpm add @google-cloud/bigquery
2051
+ ```
2052
+
2053
+ **Basic Configuration:**
1773
2054
  ```javascript
1774
2055
  {
1775
2056
  driver: 'bigquery',
1776
2057
  config: {
2058
+ projectId: 'my-analytics-project',
2059
+ datasetId: 'production_data',
1777
2060
  location: 'US',
1778
- projectId: 'my-gcp-project',
1779
- datasetId: 'analytics',
1780
- credentials: JSON.parse(Buffer.from(GOOGLE_CREDENTIALS, 'base64').toString()),
1781
- logTable: 'replication_log' // Optional: table for operation logging
2061
+ credentials: {
2062
+ client_email: 'service-account@project.iam.gserviceaccount.com',
2063
+ private_key: process.env.BIGQUERY_PRIVATE_KEY,
2064
+ project_id: 'my-analytics-project'
2065
+ }
1782
2066
  },
1783
2067
  resources: {
1784
2068
  // Simple table mapping
1785
- clicks: 'mrt-shortner__clicks',
1786
- fingerprints: 'mrt-shortner__fingerprints',
1787
- scans: 'mrt-shortner__scans',
1788
- sessions: 'mrt-shortner__sessions',
1789
- shares: 'mrt-shortner__shares',
1790
- urls: 'mrt-shortner__urls',
1791
- views: 'mrt-shortner__views',
1792
-
1793
- // Advanced configuration with transform functions
2069
+ users: 'users_table',
2070
+ orders: 'orders_table',
2071
+ products: 'products_table'
2072
+ }
2073
+ }
2074
+ ```
2075
+
2076
+ **Advanced Multi-Table Configuration:**
2077
+ ```javascript
2078
+ {
2079
+ driver: 'bigquery',
2080
+ config: {
2081
+ projectId: 'my-analytics-project',
2082
+ datasetId: 'analytics_warehouse',
2083
+ location: 'US',
2084
+ logTable: 'replication_audit_log', // Optional: audit all operations
2085
+ credentials: JSON.parse(Buffer.from(process.env.GOOGLE_CREDENTIALS, 'base64').toString())
2086
+ },
2087
+ resources: {
2088
+ // Simple string mapping
2089
+ sessions: 'user_sessions',
2090
+ clicks: 'click_events',
2091
+
2092
+ // Single table with actions and transforms
1794
2093
  users: {
1795
- table: 'mrt-shortner__users',
2094
+ table: 'dim_users',
1796
2095
  actions: ['insert', 'update'],
1797
- transform: (data) => {
1798
- return {
1799
- ...data,
1800
- ip: data.ip || 'unknown',
1801
- userIp: data.userIp || 'unknown',
1802
- }
1803
- }
2096
+ transform: (data) => ({
2097
+ ...data,
2098
+ // Data cleaning and enrichment
2099
+ email: data.email?.toLowerCase(),
2100
+ full_name: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
2101
+ registration_date: data.createdAt?.split('T')[0],
2102
+ account_age_days: data.createdAt ?
2103
+ Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0,
2104
+
2105
+ // Computed fields for analytics
2106
+ user_tier: data.totalSpent > 1000 ? 'premium' :
2107
+ data.totalSpent > 100 ? 'standard' : 'basic',
2108
+ is_active: data.lastLoginAt &&
2109
+ (Date.now() - new Date(data.lastLoginAt)) < (30 * 24 * 60 * 60 * 1000),
2110
+
2111
+ // BigQuery-specific fields
2112
+ processed_at: new Date().toISOString(),
2113
+ data_source: 'production_s3db'
2114
+ })
1804
2115
  },
1805
2116
 
1806
- // Multiple destinations for a single resource
2117
+ // Multiple destinations for comprehensive analytics
1807
2118
  orders: [
1808
- { actions: ['insert'], table: 'fact_orders' },
1809
- {
1810
- actions: ['insert'],
1811
- table: 'daily_revenue',
2119
+ // Fact table for detailed order data
2120
+ {
2121
+ table: 'fact_orders',
2122
+ actions: ['insert', 'update'],
2123
+ transform: (data) => ({
2124
+ order_id: data.id,
2125
+ customer_id: data.userId,
2126
+ order_date: data.createdAt?.split('T')[0],
2127
+ order_timestamp: data.createdAt,
2128
+ amount: parseFloat(data.amount) || 0,
2129
+ currency: data.currency || 'USD',
2130
+ status: data.status,
2131
+ item_count: data.items?.length || 0,
2132
+
2133
+ // Derived analytics fields
2134
+ is_weekend_order: new Date(data.createdAt).getDay() % 6 === 0,
2135
+ order_hour: new Date(data.createdAt).getHours(),
2136
+ is_large_order: data.amount > 500,
2137
+
2138
+ // Metadata
2139
+ ingested_at: new Date().toISOString(),
2140
+ source_system: 'production'
2141
+ })
2142
+ },
2143
+
2144
+ // Daily aggregation table
2145
+ {
2146
+ table: 'daily_revenue_summary',
2147
+ actions: ['insert'], // Only for new orders
1812
2148
  transform: (data) => ({
1813
2149
  date: data.createdAt?.split('T')[0],
1814
- revenue: data.amount,
2150
+ revenue: parseFloat(data.amount) || 0,
2151
+ currency: data.currency || 'USD',
2152
+ order_count: 1,
1815
2153
  customer_id: data.userId,
1816
- order_count: 1
2154
+
2155
+ // Additional dimensions
2156
+ order_source: data.source || 'web',
2157
+ payment_method: data.paymentMethod,
2158
+ is_first_order: data.isFirstOrder || false,
2159
+
2160
+ created_at: new Date().toISOString()
2161
+ })
2162
+ },
2163
+
2164
+ // Customer analytics (updates only)
2165
+ {
2166
+ table: 'customer_order_analytics',
2167
+ actions: ['insert', 'update'],
2168
+ transform: (data) => ({
2169
+ customer_id: data.userId,
2170
+ latest_order_id: data.id,
2171
+ latest_order_date: data.createdAt?.split('T')[0],
2172
+ latest_order_amount: parseFloat(data.amount) || 0,
2173
+
2174
+ // Will need to be aggregated in BigQuery
2175
+ lifetime_value_increment: parseFloat(data.amount) || 0,
2176
+
2177
+ updated_at: new Date().toISOString()
1817
2178
  })
1818
2179
  }
1819
- ]
2180
+ ],
2181
+
2182
+ // Event tracking with conditional replication
2183
+ events: {
2184
+ table: 'user_events',
2185
+ actions: ['insert'],
2186
+ transform: (data) => {
2187
+ // Only replicate certain event types
2188
+ const allowedEventTypes = ['page_view', 'button_click', 'form_submit', 'purchase'];
2189
+ if (!allowedEventTypes.includes(data.event_type)) {
2190
+ return null; // Skip replication
2191
+ }
2192
+
2193
+ return {
2194
+ event_id: data.id,
2195
+ user_id: data.userId,
2196
+ event_type: data.event_type,
2197
+ event_timestamp: data.timestamp,
2198
+ event_date: data.timestamp?.split('T')[0],
2199
+
2200
+ // Parse and clean event properties
2201
+ properties: JSON.stringify(data.properties || {}),
2202
+ page_url: data.properties?.page_url,
2203
+ referrer: data.properties?.referrer,
2204
+
2205
+ // Session information
2206
+ session_id: data.sessionId,
2207
+
2208
+ // Technical metadata
2209
+ user_agent: data.userAgent,
2210
+ ip_address: data.ipAddress ? 'masked' : null, // Privacy compliance
2211
+
2212
+ // Processing metadata
2213
+ processed_at: new Date().toISOString(),
2214
+ schema_version: '1.0'
2215
+ };
2216
+ }
2217
+ }
1820
2218
  }
1821
2219
  }
1822
2220
  ```
1823
2221
 
1824
2222
  **Transform Function Features:**
1825
- - **Data Transformation**: Apply custom logic before sending to BigQuery
1826
- - **Field Mapping**: Rename, combine, or derive new fields
1827
- - **Data Enrichment**: Add computed fields, defaults, or metadata
1828
- - **Format Conversion**: Convert data types or formats for BigQuery compatibility
1829
- - **Multiple Destinations**: Send transformed data to different tables
2223
+
2224
+ 1. **Data Cleaning & Validation:**
2225
+ ```javascript
2226
+ transform: (data) => {
2227
+ // Skip invalid records
2228
+ if (!data.email || !data.id) return null;
2229
+
2230
+ return {
2231
+ ...data,
2232
+ email: data.email.toLowerCase().trim(),
2233
+ phone: data.phone?.replace(/\D/g, '') || null, // Remove non-digits
2234
+ validated_at: new Date().toISOString()
2235
+ };
2236
+ }
2237
+ ```
2238
+
2239
+ 2. **Type Conversion for BigQuery:**
2240
+ ```javascript
2241
+ transform: (data) => ({
2242
+ ...data,
2243
+ amount: parseFloat(data.amount) || 0,
2244
+ quantity: parseInt(data.quantity) || 0,
2245
+ is_active: Boolean(data.isActive),
2246
+ tags: Array.isArray(data.tags) ? data.tags : [],
2247
+ metadata: JSON.stringify(data.metadata || {})
2248
+ })
2249
+ ```
2250
+
2251
+ 3. **Computed Analytics Fields:**
2252
+ ```javascript
2253
+ transform: (data) => ({
2254
+ ...data,
2255
+ // Date dimensions
2256
+ order_date: data.createdAt?.split('T')[0],
2257
+ order_year: new Date(data.createdAt).getFullYear(),
2258
+ order_month: new Date(data.createdAt).getMonth() + 1,
2259
+ order_quarter: Math.ceil((new Date(data.createdAt).getMonth() + 1) / 3),
2260
+
2261
+ // Business logic
2262
+ customer_segment: data.totalSpent > 1000 ? 'VIP' :
2263
+ data.totalSpent > 500 ? 'Premium' : 'Standard',
2264
+
2265
+ // Geospatial (if you have coordinates)
2266
+ location_string: data.lat && data.lng ? `${data.lat},${data.lng}` : null
2267
+ })
2268
+ ```
1830
2269
 
1831
2270
  **Configuration Options:**
1832
- - `projectId`: Google Cloud project ID (required)
1833
- - `datasetId`: BigQuery dataset ID (required)
1834
- - `credentials`: Service account credentials object (optional, uses default if omitted)
1835
- - `location`: BigQuery dataset location/region (default: 'US')
1836
- - `logTable`: Table name for operation logging (optional)
1837
2271
 
1838
- **Resource Configuration:**
1839
- - **String**: Simple table mapping (e.g., `'table_name'`)
1840
- - **Object**: Advanced configuration with actions and transforms
1841
- - **Array**: Multiple destination tables with different configurations
2272
+ | Parameter | Type | Required | Description |
2273
+ |-----------|------|----------|-------------|
2274
+ | `projectId` | string | Yes | Google Cloud project ID |
2275
+ | `datasetId` | string | Yes | BigQuery dataset ID |
2276
+ | `credentials` | object | No | Service account credentials (uses ADC if omitted) |
2277
+ | `location` | string | No | Dataset location/region (default: 'US') |
2278
+ | `logTable` | string | No | Table name for operation audit logging |
2279
+
2280
+ **Resource Configuration Formats:**
2281
+
2282
+ 1. **String**: Simple table mapping
2283
+ ```javascript
2284
+ resources: { users: 'users_table' }
2285
+ ```
2286
+
2287
+ 2. **Object**: Single table with actions and transform
2288
+ ```javascript
2289
+ resources: {
2290
+ users: {
2291
+ table: 'users_table',
2292
+ actions: ['insert', 'update'],
2293
+ transform: (data) => ({ ...data, processed: true })
2294
+ }
2295
+ }
2296
+ ```
2297
+
2298
+ 3. **Array**: Multiple destination tables
2299
+ ```javascript
2300
+ resources: {
2301
+ orders: [
2302
+ { table: 'fact_orders', actions: ['insert'] },
2303
+ { table: 'daily_revenue', actions: ['insert'], transform: aggregationFn }
2304
+ ]
2305
+ }
2306
+ ```
1842
2307
 
1843
2308
  **Automatic Features:**
1844
- - **Retry Logic**: Handles BigQuery streaming buffer limitations with 30-second retry delays
1845
- - **Error Handling**: Graceful handling of schema mismatches and quota limits
1846
- - **Operation Logging**: Optional audit trail of all replication operations
1847
- - **Schema Compatibility**: Automatic handling of missing fields
1848
2309
 
1849
- #### PostgreSQL Replicator
2310
+ - **๐Ÿ”„ Retry Logic**: Handles BigQuery streaming buffer limitations with 30-second delays
2311
+ - **๐Ÿ›ก๏ธ Error Handling**: Graceful handling of schema mismatches and quota limits
2312
+ - **๐Ÿ“‹ Operation Logging**: Optional audit trail in specified log table
2313
+ - **๐Ÿ”ง Schema Compatibility**: Automatic handling of missing fields and type coercion
2314
+ - **โšก Streaming Inserts**: Uses BigQuery streaming API for real-time data ingestion
2315
+ - **๐ŸŽฏ Selective Replication**: Transform functions can return `null` to skip records
1850
2316
 
1851
- Replicate to PostgreSQL database:
2317
+ #### ๐Ÿ˜ PostgreSQL Replicator
2318
+
2319
+ **Operational database integration** for PostgreSQL with support for complex SQL operations, connection pooling, and custom transformations.
2320
+
2321
+ **โš ๏ธ Required Dependency:**
2322
+ ```bash
2323
+ pnpm add pg
2324
+ ```
1852
2325
 
2326
+ **Basic Configuration:**
1853
2327
  ```javascript
1854
2328
  {
1855
2329
  driver: 'postgres',
1856
- resources: {
1857
- users: [{ actions: ['insert', 'update', 'delete'], table: 'users_table' }]
2330
+ config: {
2331
+ connectionString: 'postgresql://user:pass@localhost:5432/analytics',
2332
+ ssl: { rejectUnauthorized: false },
2333
+ pool: {
2334
+ max: 10,
2335
+ idleTimeoutMillis: 30000,
2336
+ connectionTimeoutMillis: 2000
2337
+ }
1858
2338
  },
2339
+ resources: {
2340
+ users: [{
2341
+ table: 'users_table',
2342
+ actions: ['insert', 'update', 'delete']
2343
+ }]
2344
+ }
2345
+ }
2346
+ ```
2347
+
2348
+ **Advanced Configuration with Custom SQL:**
2349
+ ```javascript
2350
+ {
2351
+ driver: 'postgres',
1859
2352
  config: {
1860
- connectionString: 'postgresql://user:pass@localhost:5432/analytics'
2353
+ connectionString: 'postgresql://analytics:password@localhost:5432/operations',
2354
+ ssl: { rejectUnauthorized: false },
2355
+ logTable: 'replication_audit',
2356
+ pool: {
2357
+ max: 20,
2358
+ min: 5,
2359
+ idleTimeoutMillis: 30000,
2360
+ connectionTimeoutMillis: 2000,
2361
+ acquireTimeoutMillis: 60000
2362
+ }
2363
+ },
2364
+ resources: {
2365
+ // Multi-table replication with different strategies
2366
+ users: [
2367
+ {
2368
+ table: 'operational_users',
2369
+ actions: ['insert', 'update', 'delete'],
2370
+ transform: (data, operation) => {
2371
+ if (operation === 'delete') {
2372
+ return {
2373
+ id: data.id,
2374
+ deleted_at: new Date(),
2375
+ deletion_reason: 'user_requested'
2376
+ };
2377
+ }
2378
+
2379
+ return {
2380
+ ...data,
2381
+ sync_timestamp: new Date(),
2382
+ source_system: 's3db',
2383
+ data_version: '1.0',
2384
+
2385
+ // Computed fields
2386
+ full_name: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
2387
+ email_domain: data.email?.split('@')[1] || 'unknown',
2388
+ account_age_days: data.createdAt ?
2389
+ Math.floor((Date.now() - new Date(data.createdAt)) / (1000 * 60 * 60 * 24)) : 0
2390
+ };
2391
+ }
2392
+ },
2393
+
2394
+ // Separate audit trail table
2395
+ {
2396
+ table: 'user_audit_trail',
2397
+ actions: ['insert', 'update', 'delete'],
2398
+ transform: (data, operation) => ({
2399
+ user_id: data.id,
2400
+ operation_type: operation,
2401
+ operation_timestamp: new Date(),
2402
+ data_snapshot: JSON.stringify(data),
2403
+ source_database: 's3db',
2404
+ replication_id: crypto.randomUUID()
2405
+ })
2406
+ }
2407
+ ],
2408
+
2409
+ // Orders with complex business logic
2410
+ orders: [
2411
+ {
2412
+ table: 'order_events',
2413
+ actions: ['insert'],
2414
+ transform: (data) => ({
2415
+ event_id: crypto.randomUUID(),
2416
+ order_id: data.id,
2417
+ customer_id: data.userId,
2418
+ event_type: 'order_created',
2419
+ event_timestamp: new Date(),
2420
+ order_amount: parseFloat(data.amount) || 0,
2421
+ order_status: data.status || 'pending',
2422
+
2423
+ // Business metrics
2424
+ is_large_order: data.amount > 500,
2425
+ order_priority: data.amount > 1000 ? 'high' :
2426
+ data.amount > 100 ? 'medium' : 'low',
2427
+ estimated_fulfillment: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days
2428
+
2429
+ // Metadata
2430
+ created_at: new Date(),
2431
+ source_system: 'production_s3db'
2432
+ })
2433
+ },
2434
+
2435
+ {
2436
+ table: 'order_updates',
2437
+ actions: ['update'],
2438
+ transform: (data, operation, beforeData) => ({
2439
+ update_id: crypto.randomUUID(),
2440
+ order_id: data.id,
2441
+ updated_at: new Date(),
2442
+
2443
+ // Track what changed
2444
+ changed_fields: Object.keys(data).filter(key =>
2445
+ beforeData && data[key] !== beforeData[key]
2446
+ ),
2447
+ previous_status: beforeData?.status,
2448
+ new_status: data.status,
2449
+
2450
+ // Change metadata
2451
+ status_progression: `${beforeData?.status || 'unknown'} -> ${data.status}`,
2452
+ update_source: 'automated_replication'
2453
+ })
2454
+ }
2455
+ ],
2456
+
2457
+ // Conditional replication based on data
2458
+ sensitive_data: {
2459
+ table: 'masked_sensitive_data',
2460
+ actions: ['insert', 'update'],
2461
+ transform: (data) => {
2462
+ // Skip replication for certain sensitive records
2463
+ if (data.privacy_level === 'restricted') {
2464
+ return null;
2465
+ }
2466
+
2467
+ return {
2468
+ id: data.id,
2469
+ // Mask sensitive fields
2470
+ email: data.email ? `${data.email.split('@')[0].slice(0, 3)}***@${data.email.split('@')[1]}` : null,
2471
+ phone: data.phone ? `***-***-${data.phone.slice(-4)}` : null,
2472
+ name: data.name ? `${data.name.charAt(0)}${'*'.repeat(data.name.length - 1)}` : null,
2473
+
2474
+ // Keep non-sensitive data
2475
+ created_at: data.createdAt,
2476
+ status: data.status,
2477
+ category: data.category,
2478
+
2479
+ // Add masking metadata
2480
+ masked_at: new Date(),
2481
+ masking_version: '1.0'
2482
+ };
2483
+ }
2484
+ }
1861
2485
  }
1862
2486
  }
1863
2487
  ```
1864
2488
 
1865
- ### Resource Configuration Formats
2489
+ **Configuration Options:**
1866
2490
 
1867
- Multiple formats supported for resource mapping:
2491
+ | Parameter | Type | Required | Description |
2492
+ |-----------|------|----------|-------------|
2493
+ | `connectionString` | string | Yes | PostgreSQL connection string |
2494
+ | `ssl` | object | No | SSL configuration options |
2495
+ | `pool` | object | No | Connection pool configuration |
2496
+ | `logTable` | string | No | Table name for operation audit logging |
1868
2497
 
1869
- ```javascript
1870
- // 1. Simple array (replicate to same name)
1871
- resources: ['users', 'products']
2498
+ **Pool Configuration Options:**
1872
2499
 
1873
- // 2. Object mapping (source โ†’ destination)
1874
- resources: { users: 'people', products: 'items' }
2500
+ | Parameter | Type | Default | Description |
2501
+ |-----------|------|---------|-------------|
2502
+ | `max` | number | `10` | Maximum number of connections in pool |
2503
+ | `min` | number | `0` | Minimum number of connections in pool |
2504
+ | `idleTimeoutMillis` | number | `10000` | How long a client is allowed to remain idle |
2505
+ | `connectionTimeoutMillis` | number | `0` | Return an error after timeout |
2506
+ | `acquireTimeoutMillis` | number | `0` | Return an error if no connection available |
1875
2507
 
1876
- // 3. Advanced mapping with transformers
2508
+ **Resource Configuration:**
2509
+
2510
+ Resources must be configured as arrays of table configurations:
2511
+
2512
+ ```javascript
1877
2513
  resources: {
1878
- users: [
2514
+ resourceName: [
1879
2515
  {
1880
- resource: 'people',
1881
- transformer: (data) => ({ ...data, fullName: `${data.first} ${data.last}` })
2516
+ table: 'destination_table',
2517
+ actions: ['insert', 'update', 'delete'],
2518
+ transform: (data, operation, beforeData) => ({
2519
+ // Return transformed data or null to skip
2520
+ })
1882
2521
  }
1883
2522
  ]
1884
2523
  }
2524
+ ```
1885
2525
 
1886
- // 4. Action-specific configuration (BigQuery/PostgreSQL)
1887
- resources: {
1888
- users: [
1889
- { actions: ['insert', 'update'], table: 'users_table' },
1890
- { actions: ['insert'], table: 'users_analytics' }
2526
+ **Automatic Features:**
2527
+
2528
+ - **๐Ÿ”„ Connection Pooling**: Efficient database connection management
2529
+ - **๐Ÿ“‹ Audit Logging**: Optional audit trail in specified log table
2530
+ - **๐Ÿ›ก๏ธ Error Handling**: Graceful handling of connection failures and SQL errors
2531
+ - **โšก Bulk Operations**: Optimized for high-throughput replication
2532
+ - **๐ŸŽฏ Flexible Actions**: Support for insert, update, delete operations
2533
+ - **๐Ÿ”ง Custom SQL**: Transform functions receive operation context for complex logic
2534
+
2535
+ ### ๐Ÿ” Monitoring & Health Checks
2536
+
2537
+ Monitor replication health and performance with built-in tools:
2538
+
2539
+ ```javascript
2540
+ // Get replication status for all replicators
2541
+ const replicationStatus = await replicatorPlugin.getReplicationStats();
2542
+
2543
+ console.log('Replication Health:', {
2544
+ totalReplicators: replicationStatus.replicators.length,
2545
+ activeReplicators: replicationStatus.replicators.filter(r => r.status.enabled).length,
2546
+ lastSync: replicationStatus.lastSync,
2547
+ errorRate: replicationStatus.stats.errorRate
2548
+ });
2549
+
2550
+ // Check individual replicator health
2551
+ for (const replicator of replicationStatus.replicators) {
2552
+ console.log(`${replicator.driver} replicator:`, {
2553
+ enabled: replicator.status.enabled,
2554
+ connected: replicator.status.connected,
2555
+ lastActivity: replicator.status.lastActivity,
2556
+ totalOperations: replicator.status.totalOperations,
2557
+ errorCount: replicator.status.errorCount
2558
+ });
2559
+ }
2560
+
2561
+ // Get detailed replication logs
2562
+ const replicationLogs = await replicatorPlugin.getReplicationLogs({
2563
+ resourceName: 'users', // Filter by resource
2564
+ status: 'failed', // Show only failed replications
2565
+ limit: 50, // Limit results
2566
+ offset: 0 // Pagination
2567
+ });
2568
+
2569
+ console.log('Recent failures:', replicationLogs.map(log => ({
2570
+ timestamp: log.timestamp,
2571
+ resource: log.resourceName,
2572
+ operation: log.operation,
2573
+ error: log.error,
2574
+ replicator: log.replicator
2575
+ })));
2576
+
2577
+ // Retry failed replications
2578
+ const retryResult = await replicatorPlugin.retryFailedReplications();
2579
+ console.log(`Retried ${retryResult.retried} failed replications`);
2580
+ ```
2581
+
2582
+ ### ๐Ÿšจ Troubleshooting Guide
2583
+
2584
+ #### Common Issues and Solutions
2585
+
2586
+ **1. Replication Not Happening**
2587
+ ```javascript
2588
+ // Check if replicator is enabled
2589
+ const plugin = s3db.plugins.find(p => p.constructor.name === 'ReplicatorPlugin');
2590
+ console.log('Plugin enabled:', plugin.config.enabled);
2591
+
2592
+ // Check resource configuration
2593
+ console.log('Resources configured:', Object.keys(plugin.config.replicators[0].resources));
2594
+
2595
+ // Listen for debug events
2596
+ plugin.on('replicator_error', (error) => {
2597
+ console.error('Replication error:', error);
2598
+ });
2599
+ ```
2600
+
2601
+ **2. Transform Function Errors**
2602
+ ```javascript
2603
+ // Add error handling in transform functions
2604
+ transform: (data) => {
2605
+ try {
2606
+ return {
2607
+ ...data,
2608
+ fullName: `${data.firstName} ${data.lastName}`,
2609
+ processedAt: new Date().toISOString()
2610
+ };
2611
+ } catch (error) {
2612
+ console.error('Transform error:', error, 'Data:', data);
2613
+ return data; // Fallback to original data
2614
+ }
2615
+ }
2616
+ ```
2617
+
2618
+ **3. Connection Issues**
2619
+ ```javascript
2620
+ // Test replicator connections
2621
+ const testResults = await Promise.allSettled(
2622
+ replicatorPlugin.replicators.map(async (replicator) => {
2623
+ try {
2624
+ const result = await replicator.testConnection();
2625
+ return { replicator: replicator.id, success: result, error: null };
2626
+ } catch (error) {
2627
+ return { replicator: replicator.id, success: false, error: error.message };
2628
+ }
2629
+ })
2630
+ );
2631
+
2632
+ testResults.forEach(result => {
2633
+ if (result.status === 'fulfilled') {
2634
+ console.log(`${result.value.replicator}: ${result.value.success ? 'โœ…' : 'โŒ'} ${result.value.error || ''}`);
2635
+ }
2636
+ });
2637
+ ```
2638
+
2639
+ **4. Performance Issues**
2640
+ ```javascript
2641
+ // Monitor replication performance
2642
+ const performanceMonitor = setInterval(async () => {
2643
+ const stats = await replicatorPlugin.getReplicationStats();
2644
+
2645
+ console.log('Performance Metrics:', {
2646
+ operationsPerSecond: stats.operationsPerSecond,
2647
+ averageLatency: stats.averageLatency,
2648
+ queueSize: stats.queueSize,
2649
+ memoryUsage: process.memoryUsage()
2650
+ });
2651
+
2652
+ // Alert on performance degradation
2653
+ if (stats.averageLatency > 5000) { // 5 seconds
2654
+ console.warn('โš ๏ธ High replication latency detected:', stats.averageLatency + 'ms');
2655
+ }
2656
+ }, 30000); // Check every 30 seconds
2657
+ ```
2658
+
2659
+ ### ๐ŸŽฏ Advanced Use Cases
2660
+
2661
+ #### Multi-Environment Replication Pipeline
2662
+
2663
+ ```javascript
2664
+ const replicatorPlugin = new ReplicatorPlugin({
2665
+ verbose: true,
2666
+ persistReplicatorLog: true,
2667
+ replicators: [
2668
+ // Production backup
2669
+ {
2670
+ driver: 's3db',
2671
+ config: { connectionString: process.env.BACKUP_DB_URL },
2672
+ resources: ['users', 'orders', 'products']
2673
+ },
2674
+
2675
+ // Staging environment sync
2676
+ {
2677
+ driver: 's3db',
2678
+ config: { connectionString: process.env.STAGING_DB_URL },
2679
+ resources: {
2680
+ users: {
2681
+ resource: 'users',
2682
+ transform: (data) => ({
2683
+ ...data,
2684
+ // Remove PII for staging
2685
+ email: data.email?.replace(/(.{2})(.*)(@.*)/, '$1***$3'),
2686
+ phone: '***-***-' + (data.phone?.slice(-4) || '0000')
2687
+ })
2688
+ }
2689
+ }
2690
+ },
2691
+
2692
+ // Analytics warehouse
2693
+ {
2694
+ driver: 'bigquery',
2695
+ config: {
2696
+ projectId: 'analytics-project',
2697
+ datasetId: 'production_data'
2698
+ },
2699
+ resources: {
2700
+ orders: [
2701
+ { table: 'fact_orders', actions: ['insert'] },
2702
+ { table: 'daily_revenue', actions: ['insert'], transform: aggregateDaily }
2703
+ ]
2704
+ }
2705
+ },
2706
+
2707
+ // Real-time events
2708
+ {
2709
+ driver: 'sqs',
2710
+ config: {
2711
+ region: 'us-east-1',
2712
+ queues: {
2713
+ users: process.env.USER_EVENTS_QUEUE,
2714
+ orders: process.env.ORDER_EVENTS_QUEUE
2715
+ }
2716
+ },
2717
+ resources: ['users', 'orders']
2718
+ }
2719
+ ]
2720
+ });
2721
+ ```
2722
+
2723
+ #### Conditional Replication Based on Business Rules
2724
+
2725
+ ```javascript
2726
+ const businessRulesReplicator = new ReplicatorPlugin({
2727
+ replicators: [
2728
+ {
2729
+ driver: 's3db',
2730
+ config: { connectionString: process.env.COMPLIANCE_DB_URL },
2731
+ resources: {
2732
+ users: {
2733
+ resource: 'compliant_users',
2734
+ transform: (data) => {
2735
+ // Only replicate users from certain regions
2736
+ const allowedRegions = ['US', 'EU', 'CA'];
2737
+ if (!allowedRegions.includes(data.region)) {
2738
+ return null; // Skip replication
2739
+ }
2740
+
2741
+ // Apply data retention rules
2742
+ const accountAge = Date.now() - new Date(data.createdAt);
2743
+ const maxAge = 7 * 365 * 24 * 60 * 60 * 1000; // 7 years
2744
+
2745
+ if (accountAge > maxAge && data.status === 'inactive') {
2746
+ return null; // Skip old inactive accounts
2747
+ }
2748
+
2749
+ return {
2750
+ ...data,
2751
+ complianceVersion: '2.0',
2752
+ lastAudit: new Date().toISOString(),
2753
+ retentionCategory: accountAge > maxAge * 0.8 ? 'pending_review' : 'active'
2754
+ };
2755
+ }
2756
+ },
2757
+
2758
+ orders: {
2759
+ resource: 'audit_orders',
2760
+ transform: (data) => {
2761
+ // Only replicate high-value orders for compliance
2762
+ if (data.amount < 10000) return null;
2763
+
2764
+ return {
2765
+ order_id: data.id,
2766
+ customer_id: data.userId,
2767
+ amount: data.amount,
2768
+ currency: data.currency,
2769
+ order_date: data.createdAt,
2770
+ compliance_flag: 'high_value',
2771
+ audit_required: true,
2772
+ retention_years: 10
2773
+ };
2774
+ }
2775
+ }
2776
+ }
2777
+ }
1891
2778
  ]
2779
+ });
2780
+ ```
2781
+
2782
+ #### Event-Driven Architecture Integration
2783
+
2784
+ ```javascript
2785
+ // Custom event handlers for complex workflows
2786
+ replicatorPlugin.on('replicated', async (event) => {
2787
+ const { resourceName, operation, recordId, replicator } = event;
2788
+
2789
+ // Trigger downstream processes
2790
+ if (resourceName === 'orders' && operation === 'insert') {
2791
+ // Trigger inventory update
2792
+ await inventoryService.updateStock(event.data);
2793
+
2794
+ // Send confirmation email
2795
+ await emailService.sendOrderConfirmation(event.data);
2796
+
2797
+ // Update analytics dashboard
2798
+ await analyticsService.recordSale(event.data);
2799
+ }
2800
+
2801
+ if (resourceName === 'users' && operation === 'update') {
2802
+ // Check for important profile changes
2803
+ const criticalFields = ['email', 'phone', 'address'];
2804
+ const changedFields = Object.keys(event.data);
2805
+
2806
+ if (criticalFields.some(field => changedFields.includes(field))) {
2807
+ await auditService.logCriticalChange({
2808
+ userId: recordId,
2809
+ changedFields: changedFields.filter(f => criticalFields.includes(f)),
2810
+ timestamp: new Date()
2811
+ });
2812
+ }
2813
+ }
2814
+ });
2815
+
2816
+ // Handle replication failures with custom logic
2817
+ replicatorPlugin.on('replicator_error', async (error) => {
2818
+ const { resourceName, operation, recordId, replicator, error: errorMessage } = error;
2819
+
2820
+ // Log to external monitoring system
2821
+ await monitoringService.logError({
2822
+ service: 'replication',
2823
+ error: errorMessage,
2824
+ context: { resourceName, operation, recordId, replicator }
2825
+ });
2826
+
2827
+ // Send alerts for critical resources
2828
+ const criticalResources = ['users', 'orders', 'payments'];
2829
+ if (criticalResources.includes(resourceName)) {
2830
+ await alertingService.sendAlert({
2831
+ severity: 'high',
2832
+ message: `Critical replication failure: ${resourceName}`,
2833
+ details: error
2834
+ });
2835
+ }
2836
+
2837
+ // Implement circuit breaker pattern
2838
+ const errorCount = await redis.incr(`replication_errors:${replicator}`);
2839
+ await redis.expire(`replication_errors:${replicator}`, 3600); // 1 hour window
2840
+
2841
+ if (errorCount > 10) {
2842
+ console.warn(`โš ๏ธ Circuit breaker: Disabling ${replicator} due to high error rate`);
2843
+ // Temporarily disable problematic replicator
2844
+ const problematicReplicator = replicatorPlugin.replicators.find(r => r.id === replicator);
2845
+ if (problematicReplicator) {
2846
+ problematicReplicator.enabled = false;
2847
+ }
2848
+ }
2849
+ });
2850
+ ```
2851
+
2852
+ ### ๐ŸŽ›๏ธ Performance Tuning
2853
+
2854
+ #### Optimize Replication Performance
2855
+
2856
+ ```javascript
2857
+ // Configure for high-throughput scenarios
2858
+ const highPerformanceReplicator = new ReplicatorPlugin({
2859
+ batchSize: 500, // Larger batches for better throughput
2860
+ maxRetries: 5, // More retries for reliability
2861
+ timeout: 60000, // Longer timeout for large operations
2862
+
2863
+ replicators: [
2864
+ {
2865
+ driver: 'bigquery',
2866
+ config: {
2867
+ projectId: 'analytics',
2868
+ datasetId: 'high_volume_data',
2869
+ // Use streaming inserts for real-time data
2870
+ streamingInserts: true,
2871
+ insertAllTimeout: 30000
2872
+ },
2873
+ resources: {
2874
+ events: {
2875
+ table: 'raw_events',
2876
+ actions: ['insert'],
2877
+ // Optimize transform for performance
2878
+ transform: (data) => {
2879
+ // Pre-compute expensive operations
2880
+ const eventDate = data.timestamp?.split('T')[0];
2881
+
2882
+ return {
2883
+ event_id: data.id,
2884
+ user_id: data.userId,
2885
+ event_type: data.type,
2886
+ event_date: eventDate,
2887
+ properties: JSON.stringify(data.properties || {}),
2888
+
2889
+ // Batch-friendly fields
2890
+ partition_date: eventDate,
2891
+ ingestion_timestamp: Math.floor(Date.now() / 1000) // Unix timestamp
2892
+ };
2893
+ }
2894
+ }
2895
+ }
2896
+ },
2897
+
2898
+ {
2899
+ driver: 'postgres',
2900
+ config: {
2901
+ connectionString: process.env.POSTGRES_URL,
2902
+ pool: {
2903
+ max: 50, // Larger connection pool
2904
+ min: 10,
2905
+ idleTimeoutMillis: 60000,
2906
+ acquireTimeoutMillis: 10000
2907
+ }
2908
+ },
2909
+ resources: {
2910
+ users: [{
2911
+ table: 'users_cache',
2912
+ actions: ['insert', 'update'],
2913
+ // Use upsert pattern for better performance
2914
+ upsertMode: true,
2915
+ transform: (data) => ({
2916
+ id: data.id,
2917
+ email: data.email,
2918
+ name: data.name,
2919
+ updated_at: new Date(),
2920
+
2921
+ // Optimized for query performance
2922
+ search_vector: `${data.name} ${data.email}`.toLowerCase()
2923
+ })
2924
+ }]
2925
+ }
2926
+ }
2927
+ ]
2928
+ });
2929
+ ```
2930
+
2931
+ ### ๐Ÿ” Security Best Practices
2932
+
2933
+ #### Secure Replication Configuration
2934
+
2935
+ ```javascript
2936
+ const secureReplicator = new ReplicatorPlugin({
2937
+ replicators: [
2938
+ {
2939
+ driver: 'bigquery',
2940
+ config: {
2941
+ projectId: 'secure-analytics',
2942
+ datasetId: 'encrypted_data',
2943
+ // Use service account with minimal permissions
2944
+ credentials: {
2945
+ type: 'service_account',
2946
+ project_id: 'secure-analytics',
2947
+ private_key: process.env.BIGQUERY_PRIVATE_KEY,
2948
+ client_email: 'replication-service@secure-analytics.iam.gserviceaccount.com'
2949
+ }
2950
+ },
2951
+ resources: {
2952
+ users: {
2953
+ table: 'encrypted_users',
2954
+ actions: ['insert', 'update'],
2955
+ transform: (data) => ({
2956
+ // Hash sensitive identifiers
2957
+ user_hash: crypto.createHash('sha256').update(data.id + process.env.SALT).digest('hex'),
2958
+
2959
+ // Encrypt PII fields
2960
+ encrypted_email: encrypt(data.email),
2961
+ encrypted_phone: data.phone ? encrypt(data.phone) : null,
2962
+
2963
+ // Keep non-sensitive analytics data
2964
+ registration_date: data.createdAt?.split('T')[0],
2965
+ account_type: data.accountType,
2966
+ region: data.region,
2967
+
2968
+ // Add encryption metadata
2969
+ encryption_version: '1.0',
2970
+ processed_at: new Date().toISOString()
2971
+ })
2972
+ }
2973
+ }
2974
+ }
2975
+ ]
2976
+ });
2977
+
2978
+ // Encryption utility functions
2979
+ function encrypt(text) {
2980
+ if (!text) return null;
2981
+ const cipher = crypto.createCipher('aes-256-cbc', process.env.ENCRYPTION_KEY);
2982
+ let encrypted = cipher.update(text, 'utf8', 'hex');
2983
+ encrypted += cipher.final('hex');
2984
+ return encrypted;
2985
+ }
2986
+
2987
+ function decrypt(encryptedText) {
2988
+ if (!encryptedText) return null;
2989
+ const decipher = crypto.createDecipher('aes-256-cbc', process.env.ENCRYPTION_KEY);
2990
+ let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
2991
+ decrypted += decipher.final('utf8');
2992
+ return decrypted;
1892
2993
  }
1893
2994
  ```
1894
2995
 
2996
+ ### โœ… Best Practices Summary
2997
+
2998
+ 1. **Configuration Management**
2999
+ - Use environment variables for connection strings and credentials
3000
+ - Implement proper error handling in transform functions
3001
+ - Test replicator connections during application startup
3002
+
3003
+ 2. **Performance Optimization**
3004
+ - Use appropriate batch sizes for your data volume
3005
+ - Configure connection pools for database replicators
3006
+ - Monitor replication lag and throughput
3007
+
3008
+ 3. **Security & Compliance**
3009
+ - Encrypt sensitive data before replication
3010
+ - Implement data masking for non-production environments
3011
+ - Use minimal privilege service accounts
3012
+
3013
+ 4. **Monitoring & Alerting**
3014
+ - Set up alerts for replication failures
3015
+ - Monitor replication lag and error rates
3016
+ - Implement health checks for all replicators
3017
+
3018
+ 5. **Error Handling**
3019
+ - Implement circuit breakers for unreliable targets
3020
+ - Use dead letter queues for failed messages
3021
+ - Log detailed error information for debugging
3022
+
1895
3023
  ### ๐Ÿ”ง Easy Example
1896
3024
 
1897
3025
  ```javascript