s3db.js 11.3.2 → 12.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.
Files changed (82) hide show
  1. package/README.md +102 -8
  2. package/dist/s3db.cjs.js +36664 -15480
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.d.ts +57 -0
  5. package/dist/s3db.es.js +36661 -15531
  6. package/dist/s3db.es.js.map +1 -1
  7. package/mcp/entrypoint.js +58 -0
  8. package/mcp/tools/documentation.js +434 -0
  9. package/mcp/tools/index.js +4 -0
  10. package/package.json +27 -6
  11. package/src/behaviors/user-managed.js +13 -6
  12. package/src/client.class.js +41 -46
  13. package/src/concerns/base62.js +85 -0
  14. package/src/concerns/dictionary-encoding.js +294 -0
  15. package/src/concerns/geo-encoding.js +256 -0
  16. package/src/concerns/high-performance-inserter.js +34 -30
  17. package/src/concerns/ip.js +325 -0
  18. package/src/concerns/metadata-encoding.js +345 -66
  19. package/src/concerns/money.js +193 -0
  20. package/src/concerns/partition-queue.js +7 -4
  21. package/src/concerns/plugin-storage.js +39 -19
  22. package/src/database.class.js +76 -74
  23. package/src/errors.js +0 -4
  24. package/src/plugins/api/auth/api-key-auth.js +88 -0
  25. package/src/plugins/api/auth/basic-auth.js +154 -0
  26. package/src/plugins/api/auth/index.js +112 -0
  27. package/src/plugins/api/auth/jwt-auth.js +169 -0
  28. package/src/plugins/api/index.js +539 -0
  29. package/src/plugins/api/middlewares/index.js +15 -0
  30. package/src/plugins/api/middlewares/validator.js +185 -0
  31. package/src/plugins/api/routes/auth-routes.js +241 -0
  32. package/src/plugins/api/routes/resource-routes.js +304 -0
  33. package/src/plugins/api/server.js +350 -0
  34. package/src/plugins/api/utils/error-handler.js +147 -0
  35. package/src/plugins/api/utils/openapi-generator.js +1240 -0
  36. package/src/plugins/api/utils/response-formatter.js +218 -0
  37. package/src/plugins/backup/streaming-exporter.js +132 -0
  38. package/src/plugins/backup.plugin.js +103 -50
  39. package/src/plugins/cache/s3-cache.class.js +95 -47
  40. package/src/plugins/cache.plugin.js +107 -9
  41. package/src/plugins/concerns/plugin-dependencies.js +313 -0
  42. package/src/plugins/concerns/prometheus-formatter.js +255 -0
  43. package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
  44. package/src/plugins/consumers/sqs-consumer.js +4 -0
  45. package/src/plugins/costs.plugin.js +255 -39
  46. package/src/plugins/eventual-consistency/helpers.js +15 -1
  47. package/src/plugins/geo.plugin.js +873 -0
  48. package/src/plugins/importer/index.js +1020 -0
  49. package/src/plugins/index.js +11 -0
  50. package/src/plugins/metrics.plugin.js +163 -4
  51. package/src/plugins/queue-consumer.plugin.js +6 -27
  52. package/src/plugins/relation.errors.js +139 -0
  53. package/src/plugins/relation.plugin.js +1242 -0
  54. package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
  55. package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
  56. package/src/plugins/replicators/index.js +28 -3
  57. package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
  58. package/src/plugins/replicators/mysql-replicator.class.js +558 -0
  59. package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
  60. package/src/plugins/replicators/postgres-replicator.class.js +182 -7
  61. package/src/plugins/replicators/s3db-replicator.class.js +1 -12
  62. package/src/plugins/replicators/schema-sync.helper.js +601 -0
  63. package/src/plugins/replicators/sqs-replicator.class.js +11 -9
  64. package/src/plugins/replicators/turso-replicator.class.js +416 -0
  65. package/src/plugins/replicators/webhook-replicator.class.js +612 -0
  66. package/src/plugins/state-machine.plugin.js +122 -68
  67. package/src/plugins/tfstate/README.md +745 -0
  68. package/src/plugins/tfstate/base-driver.js +80 -0
  69. package/src/plugins/tfstate/errors.js +112 -0
  70. package/src/plugins/tfstate/filesystem-driver.js +129 -0
  71. package/src/plugins/tfstate/index.js +2660 -0
  72. package/src/plugins/tfstate/s3-driver.js +192 -0
  73. package/src/plugins/ttl.plugin.js +536 -0
  74. package/src/resource.class.js +14 -10
  75. package/src/s3db.d.ts +57 -0
  76. package/src/schema.class.js +366 -32
  77. package/SECURITY.md +0 -76
  78. package/src/partition-drivers/base-partition-driver.js +0 -106
  79. package/src/partition-drivers/index.js +0 -66
  80. package/src/partition-drivers/memory-partition-driver.js +0 -289
  81. package/src/partition-drivers/sqs-partition-driver.js +0 -337
  82. package/src/partition-drivers/sync-partition-driver.js +0 -38
@@ -1,27 +1,43 @@
1
1
  import tryFn from "#src/concerns/try-fn.js";
2
-
2
+ import requirePluginDependency from "#src/plugins/concerns/plugin-dependencies.js";
3
3
  import BaseReplicator from './base-replicator.class.js';
4
+ import {
5
+ generateBigQuerySchema,
6
+ getBigQueryTableSchema,
7
+ generateBigQuerySchemaUpdate
8
+ } from './schema-sync.helper.js';
4
9
 
5
10
  /**
6
11
  * BigQuery Replicator - Replicate data to Google BigQuery tables
7
- *
12
+ *
8
13
  * ⚠️ REQUIRED DEPENDENCY: You must install the Google Cloud BigQuery SDK:
9
14
  * ```bash
10
15
  * pnpm add @google-cloud/bigquery
11
16
  * ```
12
- *
17
+ *
13
18
  * Configuration:
14
19
  * @param {string} projectId - Google Cloud project ID (required)
15
- * @param {string} datasetId - BigQuery dataset ID (required)
20
+ * @param {string} datasetId - BigQuery dataset ID (required)
16
21
  * @param {Object} credentials - Service account credentials object (optional)
17
22
  * @param {string} location - BigQuery dataset location/region (default: 'US')
18
23
  * @param {string} logTable - Table name for operation logging (optional)
19
- *
24
+ * @param {Object} schemaSync - Schema synchronization configuration
25
+ * @param {boolean} schemaSync.enabled - Enable automatic schema management (default: false)
26
+ * @param {string} schemaSync.strategy - Sync strategy: 'alter' | 'drop-create' | 'validate-only' (default: 'alter')
27
+ * @param {string} schemaSync.onMismatch - Action on schema mismatch: 'error' | 'warn' | 'ignore' (default: 'error')
28
+ * @param {boolean} schemaSync.autoCreateTable - Auto-create table if not exists (default: true)
29
+ * @param {boolean} schemaSync.autoCreateColumns - Auto-add missing columns (default: true, only with strategy: 'alter')
30
+ *
20
31
  * @example
21
32
  * new BigqueryReplicator({
22
33
  * projectId: 'my-gcp-project',
23
34
  * datasetId: 'analytics',
24
- * credentials: JSON.parse(Buffer.from(GOOGLE_CREDENTIALS, 'base64').toString())
35
+ * credentials: JSON.parse(Buffer.from(GOOGLE_CREDENTIALS, 'base64').toString()),
36
+ * schemaSync: {
37
+ * enabled: true,
38
+ * strategy: 'alter',
39
+ * onMismatch: 'error'
40
+ * }
25
41
  * }, {
26
42
  * users: {
27
43
  * table: 'users_table',
@@ -29,7 +45,7 @@ import BaseReplicator from './base-replicator.class.js';
29
45
  * },
30
46
  * orders: 'orders_table'
31
47
  * })
32
- *
48
+ *
33
49
  * See PLUGINS.md for comprehensive configuration documentation.
34
50
  */
35
51
  class BigqueryReplicator extends BaseReplicator {
@@ -42,6 +58,15 @@ class BigqueryReplicator extends BaseReplicator {
42
58
  this.location = config.location || 'US';
43
59
  this.logTable = config.logTable;
44
60
 
61
+ // Schema sync configuration
62
+ this.schemaSync = {
63
+ enabled: config.schemaSync?.enabled || false,
64
+ strategy: config.schemaSync?.strategy || 'alter',
65
+ onMismatch: config.schemaSync?.onMismatch || 'error',
66
+ autoCreateTable: config.schemaSync?.autoCreateTable !== false,
67
+ autoCreateColumns: config.schemaSync?.autoCreateColumns !== false
68
+ };
69
+
45
70
  // Parse resources configuration
46
71
  this.resources = this.parseResourcesConfig(resources);
47
72
  }
@@ -113,6 +138,10 @@ class BigqueryReplicator extends BaseReplicator {
113
138
 
114
139
  async initialize(database) {
115
140
  await super.initialize(database);
141
+
142
+ // Validate plugin dependencies are installed
143
+ await requirePluginDependency('bigquery-replicator');
144
+
116
145
  const [ok, err, sdk] = await tryFn(() => import('@google-cloud/bigquery'));
117
146
  if (!ok) {
118
147
  if (this.config.verbose) {
@@ -127,6 +156,12 @@ class BigqueryReplicator extends BaseReplicator {
127
156
  credentials: this.credentials,
128
157
  location: this.location
129
158
  });
159
+
160
+ // Sync schemas if enabled
161
+ if (this.schemaSync.enabled) {
162
+ await this.syncSchemas(database);
163
+ }
164
+
130
165
  this.emit('initialized', {
131
166
  replicator: this.name,
132
167
  projectId: this.projectId,
@@ -135,6 +170,142 @@ class BigqueryReplicator extends BaseReplicator {
135
170
  });
136
171
  }
137
172
 
173
+ /**
174
+ * Sync table schemas based on S3DB resource definitions
175
+ */
176
+ async syncSchemas(database) {
177
+ for (const [resourceName, tableConfigs] of Object.entries(this.resources)) {
178
+ const [okRes, errRes, resource] = await tryFn(async () => {
179
+ return await database.getResource(resourceName);
180
+ });
181
+
182
+ if (!okRes) {
183
+ if (this.config.verbose) {
184
+ console.warn(`[BigQueryReplicator] Could not get resource ${resourceName} for schema sync: ${errRes.message}`);
185
+ }
186
+ continue;
187
+ }
188
+
189
+ const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
190
+
191
+ for (const tableConfig of tableConfigs) {
192
+ const tableName = tableConfig.table;
193
+
194
+ const [okSync, errSync] = await tryFn(async () => {
195
+ await this.syncTableSchema(tableName, attributes);
196
+ });
197
+
198
+ if (!okSync) {
199
+ const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
200
+
201
+ if (this.schemaSync.onMismatch === 'error') {
202
+ throw new Error(message);
203
+ } else if (this.schemaSync.onMismatch === 'warn') {
204
+ console.warn(`[BigQueryReplicator] ${message}`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ this.emit('schema_sync_completed', {
211
+ replicator: this.name,
212
+ resources: Object.keys(this.resources)
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Sync a single table schema in BigQuery
218
+ */
219
+ async syncTableSchema(tableName, attributes) {
220
+ const dataset = this.bigqueryClient.dataset(this.datasetId);
221
+ const table = dataset.table(tableName);
222
+
223
+ // Check if table exists
224
+ const [exists] = await table.exists();
225
+
226
+ if (!exists) {
227
+ if (!this.schemaSync.autoCreateTable) {
228
+ throw new Error(`Table ${tableName} does not exist and autoCreateTable is disabled`);
229
+ }
230
+
231
+ if (this.schemaSync.strategy === 'validate-only') {
232
+ throw new Error(`Table ${tableName} does not exist (validate-only mode)`);
233
+ }
234
+
235
+ // Create table with schema
236
+ const schema = generateBigQuerySchema(attributes);
237
+
238
+ if (this.config.verbose) {
239
+ console.log(`[BigQueryReplicator] Creating table ${tableName} with schema:`, schema);
240
+ }
241
+
242
+ await dataset.createTable(tableName, { schema });
243
+
244
+ this.emit('table_created', {
245
+ replicator: this.name,
246
+ tableName,
247
+ attributes: Object.keys(attributes)
248
+ });
249
+
250
+ return;
251
+ }
252
+
253
+ // Table exists - check for schema changes
254
+ if (this.schemaSync.strategy === 'drop-create') {
255
+ if (this.config.verbose) {
256
+ console.warn(`[BigQueryReplicator] Dropping and recreating table ${tableName}`);
257
+ }
258
+
259
+ await table.delete();
260
+ const schema = generateBigQuerySchema(attributes);
261
+ await dataset.createTable(tableName, { schema });
262
+
263
+ this.emit('table_recreated', {
264
+ replicator: this.name,
265
+ tableName,
266
+ attributes: Object.keys(attributes)
267
+ });
268
+
269
+ return;
270
+ }
271
+
272
+ if (this.schemaSync.strategy === 'alter' && this.schemaSync.autoCreateColumns) {
273
+ const existingSchema = await getBigQueryTableSchema(this.bigqueryClient, this.datasetId, tableName);
274
+ const newFields = generateBigQuerySchemaUpdate(attributes, existingSchema);
275
+
276
+ if (newFields.length > 0) {
277
+ if (this.config.verbose) {
278
+ console.log(`[BigQueryReplicator] Adding ${newFields.length} field(s) to table ${tableName}:`, newFields);
279
+ }
280
+
281
+ // Get current schema
282
+ const [metadata] = await table.getMetadata();
283
+ const currentSchema = metadata.schema.fields;
284
+
285
+ // Add new fields to existing schema
286
+ const updatedSchema = [...currentSchema, ...newFields];
287
+
288
+ // Update table schema
289
+ await table.setMetadata({ schema: updatedSchema });
290
+
291
+ this.emit('table_altered', {
292
+ replicator: this.name,
293
+ tableName,
294
+ addedColumns: newFields.length
295
+ });
296
+ }
297
+ }
298
+
299
+ if (this.schemaSync.strategy === 'validate-only') {
300
+ const existingSchema = await getBigQueryTableSchema(this.bigqueryClient, this.datasetId, tableName);
301
+ const newFields = generateBigQuerySchemaUpdate(attributes, existingSchema);
302
+
303
+ if (newFields.length > 0) {
304
+ throw new Error(`Table ${tableName} schema mismatch. Missing columns: ${newFields.length}`);
305
+ }
306
+ }
307
+ }
308
+
138
309
  shouldReplicateResource(resourceName) {
139
310
  return this.resources.hasOwnProperty(resourceName);
140
311
  }
@@ -434,7 +605,8 @@ class BigqueryReplicator extends BaseReplicator {
434
605
  projectId: this.projectId,
435
606
  datasetId: this.datasetId,
436
607
  resources: this.resources,
437
- logTable: this.logTable
608
+ logTable: this.logTable,
609
+ schemaSync: this.schemaSync
438
610
  };
439
611
  }
440
612
  }
@@ -0,0 +1,383 @@
1
+ import tryFn from "#src/concerns/try-fn.js";
2
+ import requirePluginDependency from "#src/plugins/concerns/plugin-dependencies.js";
3
+ import BaseReplicator from './base-replicator.class.js';
4
+ import { ReplicationError } from '../replicator.errors.js';
5
+
6
+ /**
7
+ * DynamoDB Replicator - Replicate data to AWS DynamoDB tables
8
+ *
9
+ * ⚠️ REQUIRED DEPENDENCY: You must install the AWS SDK:
10
+ * ```bash
11
+ * pnpm add @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
12
+ * ```
13
+ *
14
+ * Configuration:
15
+ * @param {string} region - AWS region (required)
16
+ * @param {string} accessKeyId - AWS access key (optional, uses AWS SDK default chain)
17
+ * @param {string} secretAccessKey - AWS secret key (optional)
18
+ * @param {string} endpoint - Custom endpoint for DynamoDB Local (optional)
19
+ * @param {Object} credentials - AWS credentials object (optional)
20
+ *
21
+ * @example
22
+ * new DynamoDBReplicator({
23
+ * region: 'us-east-1',
24
+ * accessKeyId: 'YOUR_ACCESS_KEY',
25
+ * secretAccessKey: 'YOUR_SECRET_KEY'
26
+ * }, {
27
+ * users: [{ actions: ['insert', 'update', 'delete'], table: 'UsersTable' }],
28
+ * orders: 'OrdersTable'
29
+ * })
30
+ *
31
+ * // DynamoDB Local example
32
+ * new DynamoDBReplicator({
33
+ * region: 'us-east-1',
34
+ * endpoint: 'http://localhost:8000'
35
+ * }, {
36
+ * users: 'Users'
37
+ * })
38
+ *
39
+ * See PLUGINS.md for comprehensive configuration documentation.
40
+ */
41
+ class DynamoDBReplicator extends BaseReplicator {
42
+ constructor(config = {}, resources = {}) {
43
+ super(config);
44
+ this.region = config.region || 'us-east-1';
45
+ this.accessKeyId = config.accessKeyId;
46
+ this.secretAccessKey = config.secretAccessKey;
47
+ this.endpoint = config.endpoint;
48
+ this.credentials = config.credentials;
49
+ this.client = null;
50
+ this.docClient = null;
51
+
52
+ // Parse resources configuration
53
+ this.resources = this.parseResourcesConfig(resources);
54
+ }
55
+
56
+ parseResourcesConfig(resources) {
57
+ const parsed = {};
58
+
59
+ for (const [resourceName, config] of Object.entries(resources)) {
60
+ if (typeof config === 'string') {
61
+ // Short form: just table name
62
+ parsed[resourceName] = [{
63
+ table: config,
64
+ actions: ['insert'],
65
+ primaryKey: 'id' // Default primary key
66
+ }];
67
+ } else if (Array.isArray(config)) {
68
+ // Array form: multiple table mappings
69
+ parsed[resourceName] = config.map(item => {
70
+ if (typeof item === 'string') {
71
+ return { table: item, actions: ['insert'], primaryKey: 'id' };
72
+ }
73
+ return {
74
+ table: item.table,
75
+ actions: item.actions || ['insert'],
76
+ primaryKey: item.primaryKey || 'id',
77
+ sortKey: item.sortKey // Optional sort key
78
+ };
79
+ });
80
+ } else if (typeof config === 'object') {
81
+ // Single object form
82
+ parsed[resourceName] = [{
83
+ table: config.table,
84
+ actions: config.actions || ['insert'],
85
+ primaryKey: config.primaryKey || 'id',
86
+ sortKey: config.sortKey
87
+ }];
88
+ }
89
+ }
90
+
91
+ return parsed;
92
+ }
93
+
94
+ validateConfig() {
95
+ const errors = [];
96
+ // Region defaults to us-east-1, so it's always set
97
+ // Only validate if explicitly set to empty string
98
+ if (this.region === '') {
99
+ errors.push('AWS region is required');
100
+ }
101
+ if (Object.keys(this.resources).length === 0) {
102
+ errors.push('At least one resource must be configured');
103
+ }
104
+
105
+ // Validate resource configurations
106
+ for (const [resourceName, tables] of Object.entries(this.resources)) {
107
+ for (const tableConfig of tables) {
108
+ if (!tableConfig.table) {
109
+ errors.push(`Table name is required for resource '${resourceName}'`);
110
+ }
111
+ if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
112
+ errors.push(`Actions array is required for resource '${resourceName}'`);
113
+ }
114
+ }
115
+ }
116
+
117
+ return { isValid: errors.length === 0, errors };
118
+ }
119
+
120
+ async initialize(database) {
121
+ await super.initialize(database);
122
+
123
+ // Load AWS SDK dependencies
124
+ const { DynamoDBClient } = requirePluginDependency('@aws-sdk/client-dynamodb', 'DynamoDBReplicator');
125
+ const { DynamoDBDocumentClient, PutCommand, UpdateCommand, DeleteCommand } = requirePluginDependency('@aws-sdk/lib-dynamodb', 'DynamoDBReplicator');
126
+
127
+ // Store command constructors for later use
128
+ this.PutCommand = PutCommand;
129
+ this.UpdateCommand = UpdateCommand;
130
+ this.DeleteCommand = DeleteCommand;
131
+
132
+ const [ok, err] = await tryFn(async () => {
133
+ const clientConfig = {
134
+ region: this.region
135
+ };
136
+
137
+ if (this.endpoint) {
138
+ clientConfig.endpoint = this.endpoint;
139
+ }
140
+
141
+ if (this.credentials) {
142
+ clientConfig.credentials = this.credentials;
143
+ } else if (this.accessKeyId && this.secretAccessKey) {
144
+ clientConfig.credentials = {
145
+ accessKeyId: this.accessKeyId,
146
+ secretAccessKey: this.secretAccessKey
147
+ };
148
+ }
149
+
150
+ this.client = new DynamoDBClient(clientConfig);
151
+ this.docClient = DynamoDBDocumentClient.from(this.client);
152
+
153
+ // Test connection by listing tables
154
+ const { ListTablesCommand } = requirePluginDependency('@aws-sdk/client-dynamodb', 'DynamoDBReplicator');
155
+ await this.client.send(new ListTablesCommand({ Limit: 1 }));
156
+ });
157
+
158
+ if (!ok) {
159
+ throw new ReplicationError('Failed to connect to DynamoDB', {
160
+ operation: 'initialize',
161
+ replicatorClass: 'DynamoDBReplicator',
162
+ region: this.region,
163
+ endpoint: this.endpoint,
164
+ original: err,
165
+ suggestion: 'Check AWS credentials and ensure DynamoDB is accessible'
166
+ });
167
+ }
168
+
169
+ this.emit('connected', {
170
+ replicator: 'DynamoDBReplicator',
171
+ region: this.region,
172
+ endpoint: this.endpoint || 'default'
173
+ });
174
+ }
175
+
176
+ shouldReplicateResource(resourceName) {
177
+ return this.resources.hasOwnProperty(resourceName);
178
+ }
179
+
180
+ async replicate(resourceName, operation, data, id) {
181
+ if (!this.resources[resourceName]) {
182
+ throw new ReplicationError('Resource not configured for replication', {
183
+ operation: 'replicate',
184
+ replicatorClass: 'DynamoDBReplicator',
185
+ resourceName,
186
+ configuredResources: Object.keys(this.resources),
187
+ suggestion: 'Add resource to replicator resources configuration'
188
+ });
189
+ }
190
+
191
+ const results = [];
192
+
193
+ for (const tableConfig of this.resources[resourceName]) {
194
+ if (!tableConfig.actions.includes(operation)) {
195
+ continue; // Skip if operation not allowed for this table
196
+ }
197
+
198
+ const [ok, error, result] = await tryFn(async () => {
199
+ switch (operation) {
200
+ case 'insert':
201
+ return await this._putItem(tableConfig.table, data);
202
+ case 'update':
203
+ return await this._updateItem(tableConfig.table, id, data, tableConfig);
204
+ case 'delete':
205
+ return await this._deleteItem(tableConfig.table, id, tableConfig);
206
+ default:
207
+ throw new ReplicationError(`Unsupported operation: ${operation}`, {
208
+ operation: 'replicate',
209
+ replicatorClass: 'DynamoDBReplicator',
210
+ invalidOperation: operation,
211
+ supportedOperations: ['insert', 'update', 'delete']
212
+ });
213
+ }
214
+ });
215
+
216
+ if (ok) {
217
+ results.push(result);
218
+ } else {
219
+ this.emit('replication_error', {
220
+ resource: resourceName,
221
+ operation,
222
+ table: tableConfig.table,
223
+ error: error.message
224
+ });
225
+
226
+ if (this.config.verbose) {
227
+ console.error(`[DynamoDBReplicator] Failed to replicate ${operation} for ${resourceName}:`, error);
228
+ }
229
+ }
230
+ }
231
+
232
+ return results.length > 0 ? results[0] : null;
233
+ }
234
+
235
+ async _putItem(table, data) {
236
+ const cleanData = this._cleanInternalFields(data);
237
+
238
+ const command = new this.PutCommand({
239
+ TableName: table,
240
+ Item: cleanData
241
+ });
242
+
243
+ const result = await this.docClient.send(command);
244
+ return result;
245
+ }
246
+
247
+ async _updateItem(table, id, data, tableConfig) {
248
+ const cleanData = this._cleanInternalFields(data);
249
+
250
+ // Build update expression
251
+ const updateExpressions = [];
252
+ const expressionAttributeNames = {};
253
+ const expressionAttributeValues = {};
254
+
255
+ let index = 0;
256
+ for (const [key, value] of Object.entries(cleanData)) {
257
+ // Skip primary key and sort key
258
+ if (key === tableConfig.primaryKey || key === tableConfig.sortKey) {
259
+ continue;
260
+ }
261
+
262
+ const attrName = `#attr${index}`;
263
+ const attrValue = `:val${index}`;
264
+
265
+ expressionAttributeNames[attrName] = key;
266
+ expressionAttributeValues[attrValue] = value;
267
+ updateExpressions.push(`${attrName} = ${attrValue}`);
268
+ index++;
269
+ }
270
+
271
+ // Build key
272
+ const key = { [tableConfig.primaryKey]: id };
273
+ if (tableConfig.sortKey && cleanData[tableConfig.sortKey]) {
274
+ key[tableConfig.sortKey] = cleanData[tableConfig.sortKey];
275
+ }
276
+
277
+ const command = new this.UpdateCommand({
278
+ TableName: table,
279
+ Key: key,
280
+ UpdateExpression: `SET ${updateExpressions.join(', ')}`,
281
+ ExpressionAttributeNames: expressionAttributeNames,
282
+ ExpressionAttributeValues: expressionAttributeValues,
283
+ ReturnValues: 'ALL_NEW'
284
+ });
285
+
286
+ const result = await this.docClient.send(command);
287
+ return result;
288
+ }
289
+
290
+ async _deleteItem(table, id, tableConfig) {
291
+ const key = { [tableConfig.primaryKey]: id };
292
+
293
+ const command = new this.DeleteCommand({
294
+ TableName: table,
295
+ Key: key
296
+ });
297
+
298
+ const result = await this.docClient.send(command);
299
+ return result;
300
+ }
301
+
302
+ _cleanInternalFields(data) {
303
+ if (!data || typeof data !== 'object') return data;
304
+
305
+ const cleanData = { ...data };
306
+
307
+ // Remove internal s3db fields
308
+ Object.keys(cleanData).forEach(key => {
309
+ if (key.startsWith('$') || key.startsWith('_')) {
310
+ delete cleanData[key];
311
+ }
312
+ });
313
+
314
+ return cleanData;
315
+ }
316
+
317
+ async replicateBatch(resourceName, records) {
318
+ const results = [];
319
+ const errors = [];
320
+
321
+ // DynamoDB batch operations (up to 25 items)
322
+ // For now, process sequentially (can be optimized with BatchWriteItem)
323
+ for (const record of records) {
324
+ const [ok, err, result] = await tryFn(() =>
325
+ this.replicate(resourceName, record.operation, record.data, record.id)
326
+ );
327
+
328
+ if (ok) {
329
+ results.push(result);
330
+ } else {
331
+ errors.push({ id: record.id, error: err.message });
332
+ }
333
+ }
334
+
335
+ return {
336
+ success: errors.length === 0,
337
+ results,
338
+ errors,
339
+ total: records.length
340
+ };
341
+ }
342
+
343
+ async testConnection() {
344
+ const [ok, err] = await tryFn(async () => {
345
+ if (!this.client) {
346
+ throw new Error('Client not initialized');
347
+ }
348
+
349
+ const { ListTablesCommand } = requirePluginDependency('@aws-sdk/client-dynamodb', 'DynamoDBReplicator');
350
+ await this.client.send(new ListTablesCommand({ Limit: 1 }));
351
+ return true;
352
+ });
353
+
354
+ if (!ok) {
355
+ this.emit('connection_error', { replicator: 'DynamoDBReplicator', error: err.message });
356
+ return false;
357
+ }
358
+
359
+ return true;
360
+ }
361
+
362
+ async getStatus() {
363
+ const baseStatus = await super.getStatus();
364
+ return {
365
+ ...baseStatus,
366
+ connected: !!this.client,
367
+ region: this.region,
368
+ endpoint: this.endpoint || 'default',
369
+ resources: Object.keys(this.resources)
370
+ };
371
+ }
372
+
373
+ async cleanup() {
374
+ if (this.client) {
375
+ this.client.destroy();
376
+ this.client = null;
377
+ this.docClient = null;
378
+ }
379
+ await super.cleanup();
380
+ }
381
+ }
382
+
383
+ export default DynamoDBReplicator;
@@ -1,11 +1,29 @@
1
1
  import BaseReplicator from './base-replicator.class.js';
2
2
  import BigqueryReplicator from './bigquery-replicator.class.js';
3
+ import DynamoDBReplicator from './dynamodb-replicator.class.js';
4
+ import MongoDBReplicator from './mongodb-replicator.class.js';
5
+ import MySQLReplicator from './mysql-replicator.class.js';
6
+ import PlanetScaleReplicator from './planetscale-replicator.class.js';
3
7
  import PostgresReplicator from './postgres-replicator.class.js';
4
8
  import S3dbReplicator from './s3db-replicator.class.js';
5
9
  import SqsReplicator from './sqs-replicator.class.js';
10
+ import TursoReplicator from './turso-replicator.class.js';
11
+ import WebhookReplicator from './webhook-replicator.class.js';
6
12
  import { ReplicationError } from '../replicator.errors.js';
7
13
 
8
- export { BaseReplicator, BigqueryReplicator, PostgresReplicator, S3dbReplicator, SqsReplicator };
14
+ export {
15
+ BaseReplicator,
16
+ BigqueryReplicator,
17
+ DynamoDBReplicator,
18
+ MongoDBReplicator,
19
+ MySQLReplicator,
20
+ PlanetScaleReplicator,
21
+ PostgresReplicator,
22
+ S3dbReplicator,
23
+ SqsReplicator,
24
+ TursoReplicator,
25
+ WebhookReplicator
26
+ };
9
27
 
10
28
  /**
11
29
  * Available replicator drivers
@@ -14,12 +32,19 @@ export const REPLICATOR_DRIVERS = {
14
32
  s3db: S3dbReplicator,
15
33
  sqs: SqsReplicator,
16
34
  bigquery: BigqueryReplicator,
17
- postgres: PostgresReplicator
35
+ postgres: PostgresReplicator,
36
+ mysql: MySQLReplicator,
37
+ mariadb: MySQLReplicator, // MariaDB uses the same driver as MySQL
38
+ planetscale: PlanetScaleReplicator,
39
+ turso: TursoReplicator,
40
+ dynamodb: DynamoDBReplicator,
41
+ mongodb: MongoDBReplicator,
42
+ webhook: WebhookReplicator
18
43
  };
19
44
 
20
45
  /**
21
46
  * Create a replicator instance based on driver type
22
- * @param {string} driver - Driver type (s3db, sqs, bigquery, postgres)
47
+ * @param {string} driver - Driver type (s3db, sqs, bigquery, postgres, mysql, mariadb, planetscale, turso, dynamodb, mongodb, webhook)
23
48
  * @param {Object} config - Replicator configuration
24
49
  * @returns {BaseReplicator} Replicator instance
25
50
  */