s3db.js 13.6.1 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -15
- package/dist/s3db.cjs +72446 -39022
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72172 -38790
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +85 -50
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/index.js +168 -40
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
|
+
import { PromisePool } from '@supercharge/promise-pool';
|
|
2
3
|
import { ReplicationError } from '../replicator.errors.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -11,6 +12,7 @@ export class BaseReplicator extends EventEmitter {
|
|
|
11
12
|
this.config = config;
|
|
12
13
|
this.name = this.constructor.name;
|
|
13
14
|
this.enabled = config.enabled !== false; // Default to enabled unless explicitly disabled
|
|
15
|
+
this.batchConcurrency = Math.max(1, config.batchConcurrency ?? 5);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -89,6 +91,92 @@ export class BaseReplicator extends EventEmitter {
|
|
|
89
91
|
this.emit('cleanup', { replicator: this.name });
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Update the default batch concurrency at runtime
|
|
96
|
+
* @param {number} value
|
|
97
|
+
*/
|
|
98
|
+
setBatchConcurrency(value) {
|
|
99
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
100
|
+
throw new ReplicationError('Batch concurrency must be a positive number', {
|
|
101
|
+
operation: 'setBatchConcurrency',
|
|
102
|
+
replicatorClass: this.name,
|
|
103
|
+
providedValue: value,
|
|
104
|
+
suggestion: 'Provide a positive integer value greater than zero.'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
this.batchConcurrency = Math.floor(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generic helper to process batches with controlled concurrency
|
|
112
|
+
* @param {Array} records - Items to process
|
|
113
|
+
* @param {Function} handler - Async handler executed for each record
|
|
114
|
+
* @param {Object} options
|
|
115
|
+
* @param {number} [options.concurrency] - Concurrency override
|
|
116
|
+
* @param {Function} [options.mapError] - Maps thrown errors before collection
|
|
117
|
+
* @returns {Promise<{results: Array, errors: Array}>}
|
|
118
|
+
*/
|
|
119
|
+
async processBatch(records = [], handler, { concurrency, mapError } = {}) {
|
|
120
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
121
|
+
return { results: [], errors: [] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof handler !== 'function') {
|
|
125
|
+
throw new ReplicationError('processBatch requires an async handler function', {
|
|
126
|
+
operation: 'processBatch',
|
|
127
|
+
replicatorClass: this.name,
|
|
128
|
+
suggestion: 'Provide an async handler: async (record) => { ... }'
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const limit = Math.max(1, concurrency ?? this.batchConcurrency ?? 5);
|
|
133
|
+
const results = [];
|
|
134
|
+
const errors = [];
|
|
135
|
+
|
|
136
|
+
await PromisePool
|
|
137
|
+
.withConcurrency(limit)
|
|
138
|
+
.for(records)
|
|
139
|
+
.process(async record => {
|
|
140
|
+
try {
|
|
141
|
+
const result = await handler(record);
|
|
142
|
+
results.push(result);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (typeof mapError === 'function') {
|
|
145
|
+
const mapped = mapError(error, record);
|
|
146
|
+
if (mapped !== undefined) {
|
|
147
|
+
errors.push(mapped);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
errors.push({ record, error });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return { results, errors };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Helper to build replication errors with contextual metadata
|
|
160
|
+
* @param {string} message
|
|
161
|
+
* @param {Object} details
|
|
162
|
+
* @returns {ReplicationError}
|
|
163
|
+
*/
|
|
164
|
+
createError(message, details = {}) {
|
|
165
|
+
return new ReplicationError(message, {
|
|
166
|
+
replicatorClass: this.name,
|
|
167
|
+
operation: details.operation || 'unknown',
|
|
168
|
+
resourceName: details.resourceName,
|
|
169
|
+
statusCode: details.statusCode ?? 500,
|
|
170
|
+
retriable: details.retriable ?? false,
|
|
171
|
+
suggestion: details.suggestion,
|
|
172
|
+
description: details.description,
|
|
173
|
+
docs: details.docs,
|
|
174
|
+
hint: details.hint,
|
|
175
|
+
metadata: details.metadata,
|
|
176
|
+
...details
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
92
180
|
/**
|
|
93
181
|
* Validate replicator configuration
|
|
94
182
|
* @returns {Object} Validation result
|
|
@@ -98,4 +186,4 @@ export class BaseReplicator extends EventEmitter {
|
|
|
98
186
|
}
|
|
99
187
|
}
|
|
100
188
|
|
|
101
|
-
export default BaseReplicator;
|
|
189
|
+
export default BaseReplicator;
|
|
@@ -87,7 +87,12 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
87
87
|
_validateMutability(mutability) {
|
|
88
88
|
const validModes = ['append-only', 'mutable', 'immutable'];
|
|
89
89
|
if (!validModes.includes(mutability)) {
|
|
90
|
-
throw
|
|
90
|
+
throw this.createError(`Invalid mutability mode: ${mutability}`, {
|
|
91
|
+
operation: 'config',
|
|
92
|
+
statusCode: 400,
|
|
93
|
+
retriable: false,
|
|
94
|
+
suggestion: `Use one of the supported mutability modes: ${validModes.join(', ')}.`
|
|
95
|
+
});
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
@@ -213,11 +218,12 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
213
218
|
continue;
|
|
214
219
|
}
|
|
215
220
|
|
|
216
|
-
|
|
221
|
+
// Use $schema for reliable access to resource definition
|
|
222
|
+
const allAttributes = resource.$schema.attributes || {};
|
|
217
223
|
|
|
218
224
|
// Filter out plugin attributes - they are internal and should not be replicated
|
|
219
|
-
const pluginAttrNames = resource.
|
|
220
|
-
? Object.values(resource
|
|
225
|
+
const pluginAttrNames = resource.$schema._pluginAttributes
|
|
226
|
+
? Object.values(resource.$schema._pluginAttributes).flat()
|
|
221
227
|
: [];
|
|
222
228
|
const attributes = Object.fromEntries(
|
|
223
229
|
Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
|
|
@@ -235,7 +241,15 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
235
241
|
const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
|
|
236
242
|
|
|
237
243
|
if (this.schemaSync.onMismatch === 'error') {
|
|
238
|
-
throw
|
|
244
|
+
throw this.createError(message, {
|
|
245
|
+
operation: 'schemaSync',
|
|
246
|
+
resourceName,
|
|
247
|
+
tableName,
|
|
248
|
+
statusCode: 409,
|
|
249
|
+
retriable: errSync?.retriable ?? false,
|
|
250
|
+
suggestion: 'Review the BigQuery table schema and align it with the S3DB resource definition or relax schemaSync.onMismatch.',
|
|
251
|
+
docs: 'docs/plugins/replicator.md'
|
|
252
|
+
});
|
|
239
253
|
} else if (this.schemaSync.onMismatch === 'warn') {
|
|
240
254
|
console.warn(`[BigQueryReplicator] ${message}`);
|
|
241
255
|
}
|
|
@@ -261,11 +275,23 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
261
275
|
|
|
262
276
|
if (!exists) {
|
|
263
277
|
if (!this.schemaSync.autoCreateTable) {
|
|
264
|
-
throw
|
|
278
|
+
throw this.createError(`Table ${tableName} does not exist and autoCreateTable is disabled`, {
|
|
279
|
+
operation: 'schemaSync',
|
|
280
|
+
tableName,
|
|
281
|
+
statusCode: 404,
|
|
282
|
+
retriable: false,
|
|
283
|
+
suggestion: 'Create the BigQuery table manually or enable schemaSync.autoCreateTable.'
|
|
284
|
+
});
|
|
265
285
|
}
|
|
266
286
|
|
|
267
287
|
if (this.schemaSync.strategy === 'validate-only') {
|
|
268
|
-
throw
|
|
288
|
+
throw this.createError(`Table ${tableName} does not exist (validate-only mode)`, {
|
|
289
|
+
operation: 'schemaSync',
|
|
290
|
+
tableName,
|
|
291
|
+
statusCode: 404,
|
|
292
|
+
retriable: false,
|
|
293
|
+
suggestion: 'Provision the table before running validate-only checks or switch the schemaSync.strategy to alter.'
|
|
294
|
+
});
|
|
269
295
|
}
|
|
270
296
|
|
|
271
297
|
// Create table with schema (including tracking fields based on mutability)
|
|
@@ -339,7 +365,13 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
339
365
|
const newFields = generateBigQuerySchemaUpdate(attributes, existingSchema, mutability);
|
|
340
366
|
|
|
341
367
|
if (newFields.length > 0) {
|
|
342
|
-
throw
|
|
368
|
+
throw this.createError(`Table ${tableName} schema mismatch. Missing columns: ${newFields.length}`, {
|
|
369
|
+
operation: 'schemaValidation',
|
|
370
|
+
tableName,
|
|
371
|
+
statusCode: 409,
|
|
372
|
+
retriable: false,
|
|
373
|
+
suggestion: 'Update the BigQuery table schema to include the missing columns or enable schemaSync.autoCreateColumns.'
|
|
374
|
+
});
|
|
343
375
|
}
|
|
344
376
|
}
|
|
345
377
|
}
|
|
@@ -555,7 +587,14 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
555
587
|
throw error;
|
|
556
588
|
}
|
|
557
589
|
} else {
|
|
558
|
-
throw
|
|
590
|
+
throw this.createError(`Unsupported operation: ${operation}`, {
|
|
591
|
+
operation: 'replicate',
|
|
592
|
+
resourceName,
|
|
593
|
+
tableName: tableConfig.table,
|
|
594
|
+
statusCode: 400,
|
|
595
|
+
retriable: false,
|
|
596
|
+
suggestion: 'Replicator supports insert, update, or delete actions. Adjust the resources configuration accordingly.'
|
|
597
|
+
});
|
|
559
598
|
}
|
|
560
599
|
|
|
561
600
|
results.push({
|
|
@@ -634,26 +673,31 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
634
673
|
}
|
|
635
674
|
|
|
636
675
|
async replicateBatch(resourceName, records) {
|
|
637
|
-
const results =
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const [ok, err, res] = await tryFn(() => this.replicate(
|
|
676
|
+
const { results, errors } = await this.processBatch(
|
|
677
|
+
records,
|
|
678
|
+
async (record) => {
|
|
679
|
+
const [ok, err, res] = await tryFn(() => this.replicate(
|
|
642
680
|
resourceName,
|
|
643
681
|
record.operation,
|
|
644
682
|
record.data,
|
|
645
683
|
record.id,
|
|
646
684
|
record.beforeData
|
|
647
685
|
));
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
686
|
+
if (!ok) {
|
|
687
|
+
throw err;
|
|
688
|
+
}
|
|
689
|
+
return res;
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
concurrency: this.config.batchConcurrency,
|
|
693
|
+
mapError: (error, record) => {
|
|
694
|
+
if (this.config.verbose) {
|
|
695
|
+
console.warn(`[BigqueryReplicator] Batch replication failed for record ${record.id}: ${error.message}`);
|
|
696
|
+
}
|
|
697
|
+
return { id: record.id, error: error.message };
|
|
653
698
|
}
|
|
654
|
-
errors.push({ id: record.id, error: err.message });
|
|
655
699
|
}
|
|
656
|
-
|
|
700
|
+
);
|
|
657
701
|
|
|
658
702
|
// Log errors if any occurred during batch processing
|
|
659
703
|
if (errors.length > 0) {
|
|
@@ -699,4 +743,4 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
699
743
|
}
|
|
700
744
|
}
|
|
701
745
|
|
|
702
|
-
export default BigqueryReplicator;
|
|
746
|
+
export default BigqueryReplicator;
|
|
@@ -315,22 +315,24 @@ class DynamoDBReplicator extends BaseReplicator {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
async replicateBatch(resourceName, records) {
|
|
318
|
-
const results =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
318
|
+
const { results, errors } = await this.processBatch(
|
|
319
|
+
records,
|
|
320
|
+
async (record) => {
|
|
321
|
+
const [ok, err, result] = await tryFn(() =>
|
|
322
|
+
this.replicate(resourceName, record.operation, record.data, record.id)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (!ok) {
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
327
328
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
return result;
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
concurrency: this.config.batchConcurrency,
|
|
333
|
+
mapError: (error, record) => ({ id: record.id, error: error.message })
|
|
332
334
|
}
|
|
333
|
-
|
|
335
|
+
);
|
|
334
336
|
|
|
335
337
|
return {
|
|
336
338
|
success: errors.length === 0,
|
|
@@ -343,7 +345,12 @@ class DynamoDBReplicator extends BaseReplicator {
|
|
|
343
345
|
async testConnection() {
|
|
344
346
|
const [ok, err] = await tryFn(async () => {
|
|
345
347
|
if (!this.client) {
|
|
346
|
-
throw
|
|
348
|
+
throw this.createError('Client not initialized', {
|
|
349
|
+
operation: 'testConnection',
|
|
350
|
+
statusCode: 503,
|
|
351
|
+
retriable: true,
|
|
352
|
+
suggestion: 'Call initialize() before testing connectivity or ensure the DynamoDB client was created successfully.'
|
|
353
|
+
});
|
|
347
354
|
}
|
|
348
355
|
|
|
349
356
|
const { ListTablesCommand } = requirePluginDependency('@aws-sdk/client-dynamodb', 'DynamoDBReplicator');
|
|
@@ -324,22 +324,24 @@ class MongoDBReplicator extends BaseReplicator {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
async replicateBatch(resourceName, records) {
|
|
327
|
-
const results =
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
const { results, errors } = await this.processBatch(
|
|
328
|
+
records,
|
|
329
|
+
async (record) => {
|
|
330
|
+
const [ok, err, result] = await tryFn(() =>
|
|
331
|
+
this.replicate(resourceName, record.operation, record.data, record.id)
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
if (!ok) {
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
336
337
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
return result;
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
concurrency: this.config.batchConcurrency,
|
|
342
|
+
mapError: (error, record) => ({ id: record.id, error: error.message })
|
|
341
343
|
}
|
|
342
|
-
|
|
344
|
+
);
|
|
343
345
|
|
|
344
346
|
return {
|
|
345
347
|
success: errors.length === 0,
|
|
@@ -352,7 +354,12 @@ class MongoDBReplicator extends BaseReplicator {
|
|
|
352
354
|
async testConnection() {
|
|
353
355
|
const [ok, err] = await tryFn(async () => {
|
|
354
356
|
if (!this.client) {
|
|
355
|
-
throw
|
|
357
|
+
throw this.createError('Client not initialized', {
|
|
358
|
+
operation: 'testConnection',
|
|
359
|
+
statusCode: 503,
|
|
360
|
+
retriable: true,
|
|
361
|
+
suggestion: 'Ensure initialize() was called and the MongoDB client connected before testing connectivity.'
|
|
362
|
+
});
|
|
356
363
|
}
|
|
357
364
|
|
|
358
365
|
await this.db.admin().ping();
|
|
@@ -242,7 +242,15 @@ class MySQLReplicator extends BaseReplicator {
|
|
|
242
242
|
const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
|
|
243
243
|
|
|
244
244
|
if (this.schemaSync.onMismatch === 'error') {
|
|
245
|
-
throw
|
|
245
|
+
throw this.createError(message, {
|
|
246
|
+
operation: 'schemaSync',
|
|
247
|
+
resourceName,
|
|
248
|
+
tableName,
|
|
249
|
+
statusCode: 409,
|
|
250
|
+
retriable: errSync?.retriable ?? false,
|
|
251
|
+
suggestion: 'Update the MySQL table schema to match the resource definition or adjust schemaSync.onMismatch.',
|
|
252
|
+
docs: 'docs/plugins/replicator.md'
|
|
253
|
+
});
|
|
246
254
|
} else if (this.schemaSync.onMismatch === 'warn') {
|
|
247
255
|
console.warn(`[MySQLReplicator] ${message}`);
|
|
248
256
|
}
|
|
@@ -268,11 +276,23 @@ class MySQLReplicator extends BaseReplicator {
|
|
|
268
276
|
|
|
269
277
|
if (!existingSchema) {
|
|
270
278
|
if (!this.schemaSync.autoCreateTable) {
|
|
271
|
-
throw
|
|
279
|
+
throw this.createError(`Table ${tableName} does not exist and autoCreateTable is disabled`, {
|
|
280
|
+
operation: 'schemaSync',
|
|
281
|
+
tableName,
|
|
282
|
+
statusCode: 404,
|
|
283
|
+
retriable: false,
|
|
284
|
+
suggestion: 'Provision the table manually or enable schemaSync.autoCreateTable.'
|
|
285
|
+
});
|
|
272
286
|
}
|
|
273
287
|
|
|
274
288
|
if (this.schemaSync.strategy === 'validate-only') {
|
|
275
|
-
throw
|
|
289
|
+
throw this.createError(`Table ${tableName} does not exist (validate-only mode)`, {
|
|
290
|
+
operation: 'schemaSync',
|
|
291
|
+
tableName,
|
|
292
|
+
statusCode: 404,
|
|
293
|
+
retriable: false,
|
|
294
|
+
suggestion: 'Create the table before running validate-only checks or choose the alter strategy.'
|
|
295
|
+
});
|
|
276
296
|
}
|
|
277
297
|
|
|
278
298
|
// Create table
|
|
@@ -336,7 +356,13 @@ class MySQLReplicator extends BaseReplicator {
|
|
|
336
356
|
const alterStatements = generateMySQLAlterTable(tableName, attributes, existingSchema);
|
|
337
357
|
|
|
338
358
|
if (alterStatements.length > 0) {
|
|
339
|
-
throw
|
|
359
|
+
throw this.createError(`Table ${tableName} schema mismatch. Missing columns: ${alterStatements.length}`, {
|
|
360
|
+
operation: 'schemaValidation',
|
|
361
|
+
tableName,
|
|
362
|
+
statusCode: 409,
|
|
363
|
+
retriable: false,
|
|
364
|
+
suggestion: 'Add the missing columns to the MySQL table or enable schemaSync.autoCreateColumns.'
|
|
365
|
+
});
|
|
340
366
|
}
|
|
341
367
|
}
|
|
342
368
|
} finally {
|
|
@@ -498,20 +524,24 @@ class MySQLReplicator extends BaseReplicator {
|
|
|
498
524
|
}
|
|
499
525
|
|
|
500
526
|
async replicateBatch(resourceName, records) {
|
|
501
|
-
const results =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
527
|
+
const { results, errors } = await this.processBatch(
|
|
528
|
+
records,
|
|
529
|
+
async (record) => {
|
|
530
|
+
const [ok, err, result] = await tryFn(() =>
|
|
531
|
+
this.replicate(resourceName, record.operation, record.data, record.id)
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
if (!ok) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
508
537
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
538
|
+
return result;
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
concurrency: this.config.batchConcurrency,
|
|
542
|
+
mapError: (error, record) => ({ id: record.id, error: error.message })
|
|
513
543
|
}
|
|
514
|
-
|
|
544
|
+
);
|
|
515
545
|
|
|
516
546
|
return {
|
|
517
547
|
success: errors.length === 0,
|
|
@@ -524,7 +554,12 @@ class MySQLReplicator extends BaseReplicator {
|
|
|
524
554
|
async testConnection() {
|
|
525
555
|
const [ok, err] = await tryFn(async () => {
|
|
526
556
|
if (!this.pool) {
|
|
527
|
-
throw
|
|
557
|
+
throw this.createError('Pool not initialized', {
|
|
558
|
+
operation: 'testConnection',
|
|
559
|
+
statusCode: 503,
|
|
560
|
+
retriable: true,
|
|
561
|
+
suggestion: 'Call initialize() before testing the connection or ensure the pool was not cleaned up prematurely.'
|
|
562
|
+
});
|
|
528
563
|
}
|
|
529
564
|
|
|
530
565
|
const connection = await this.pool.promise().getConnection();
|
|
@@ -204,7 +204,15 @@ class PlanetScaleReplicator extends BaseReplicator {
|
|
|
204
204
|
const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
|
|
205
205
|
|
|
206
206
|
if (this.schemaSync.onMismatch === 'error') {
|
|
207
|
-
throw
|
|
207
|
+
throw this.createError(message, {
|
|
208
|
+
operation: 'schemaSync',
|
|
209
|
+
resourceName,
|
|
210
|
+
tableName,
|
|
211
|
+
statusCode: 409,
|
|
212
|
+
retriable: errSync?.retriable ?? false,
|
|
213
|
+
suggestion: 'Align the PlanetScale table schema with the resource definition or relax schemaSync.onMismatch.',
|
|
214
|
+
docs: 'docs/plugins/replicator.md'
|
|
215
|
+
});
|
|
208
216
|
} else if (this.schemaSync.onMismatch === 'warn') {
|
|
209
217
|
console.warn(`[PlanetScaleReplicator] ${message}`);
|
|
210
218
|
}
|
|
@@ -227,11 +235,23 @@ class PlanetScaleReplicator extends BaseReplicator {
|
|
|
227
235
|
|
|
228
236
|
if (!existingSchema) {
|
|
229
237
|
if (!this.schemaSync.autoCreateTable) {
|
|
230
|
-
throw
|
|
238
|
+
throw this.createError(`Table ${tableName} does not exist and autoCreateTable is disabled`, {
|
|
239
|
+
operation: 'schemaSync',
|
|
240
|
+
tableName,
|
|
241
|
+
statusCode: 404,
|
|
242
|
+
retriable: false,
|
|
243
|
+
suggestion: 'Create the table manually or enable schemaSync.autoCreateTable.'
|
|
244
|
+
});
|
|
231
245
|
}
|
|
232
246
|
|
|
233
247
|
if (this.schemaSync.strategy === 'validate-only') {
|
|
234
|
-
throw
|
|
248
|
+
throw this.createError(`Table ${tableName} does not exist (validate-only mode)`, {
|
|
249
|
+
operation: 'schemaSync',
|
|
250
|
+
tableName,
|
|
251
|
+
statusCode: 404,
|
|
252
|
+
retriable: false,
|
|
253
|
+
suggestion: 'Provision the table before running validate-only checks or choose the alter strategy.'
|
|
254
|
+
});
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
// Create table
|
|
@@ -295,7 +315,13 @@ class PlanetScaleReplicator extends BaseReplicator {
|
|
|
295
315
|
const alterStatements = generateMySQLAlterTable(tableName, attributes, existingSchema);
|
|
296
316
|
|
|
297
317
|
if (alterStatements.length > 0) {
|
|
298
|
-
throw
|
|
318
|
+
throw this.createError(`Table ${tableName} schema mismatch. Missing columns: ${alterStatements.length}`, {
|
|
319
|
+
operation: 'schemaValidation',
|
|
320
|
+
tableName,
|
|
321
|
+
statusCode: 409,
|
|
322
|
+
retriable: false,
|
|
323
|
+
suggestion: 'Add the missing columns to the PlanetScale table or enable schemaSync.autoCreateColumns.'
|
|
324
|
+
});
|
|
299
325
|
}
|
|
300
326
|
}
|
|
301
327
|
}
|
|
@@ -247,7 +247,15 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
247
247
|
const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
|
|
248
248
|
|
|
249
249
|
if (this.schemaSync.onMismatch === 'error') {
|
|
250
|
-
throw
|
|
250
|
+
throw this.createError(message, {
|
|
251
|
+
operation: 'schemaSync',
|
|
252
|
+
resourceName,
|
|
253
|
+
tableName,
|
|
254
|
+
statusCode: 409,
|
|
255
|
+
retriable: errSync?.retriable ?? false,
|
|
256
|
+
suggestion: 'Align the PostgreSQL table schema with the resource definition or relax schemaSync.onMismatch.',
|
|
257
|
+
docs: 'docs/plugins/replicator.md'
|
|
258
|
+
});
|
|
251
259
|
} else if (this.schemaSync.onMismatch === 'warn') {
|
|
252
260
|
console.warn(`[PostgresReplicator] ${message}`);
|
|
253
261
|
}
|
|
@@ -272,11 +280,23 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
272
280
|
if (!existingSchema) {
|
|
273
281
|
// Table doesn't exist
|
|
274
282
|
if (!this.schemaSync.autoCreateTable) {
|
|
275
|
-
throw
|
|
283
|
+
throw this.createError(`Table ${tableName} does not exist and autoCreateTable is disabled`, {
|
|
284
|
+
operation: 'schemaSync',
|
|
285
|
+
tableName,
|
|
286
|
+
statusCode: 404,
|
|
287
|
+
retriable: false,
|
|
288
|
+
suggestion: 'Create the table manually or enable schemaSync.autoCreateTable to let the replicator provision it.'
|
|
289
|
+
});
|
|
276
290
|
}
|
|
277
291
|
|
|
278
292
|
if (this.schemaSync.strategy === 'validate-only') {
|
|
279
|
-
throw
|
|
293
|
+
throw this.createError(`Table ${tableName} does not exist (validate-only mode)`, {
|
|
294
|
+
operation: 'schemaSync',
|
|
295
|
+
tableName,
|
|
296
|
+
statusCode: 404,
|
|
297
|
+
retriable: false,
|
|
298
|
+
suggestion: 'Provision the destination table before running validate-only schema checks or switch to the alter strategy.'
|
|
299
|
+
});
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
// Create table
|
|
@@ -343,7 +363,13 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
343
363
|
const alterStatements = generatePostgresAlterTable(tableName, attributes, existingSchema);
|
|
344
364
|
|
|
345
365
|
if (alterStatements.length > 0) {
|
|
346
|
-
throw
|
|
366
|
+
throw this.createError(`Table ${tableName} schema mismatch. Missing columns: ${alterStatements.length}`, {
|
|
367
|
+
operation: 'schemaValidation',
|
|
368
|
+
tableName,
|
|
369
|
+
statusCode: 409,
|
|
370
|
+
retriable: false,
|
|
371
|
+
suggestion: 'Update the PostgreSQL table to include the missing columns or allow the replicator to manage schema changes.'
|
|
372
|
+
});
|
|
347
373
|
}
|
|
348
374
|
}
|
|
349
375
|
}
|
|
@@ -416,7 +442,14 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
416
442
|
const sql = `DELETE FROM ${table} WHERE id=$1 RETURNING *`;
|
|
417
443
|
result = await this.client.query(sql, [id]);
|
|
418
444
|
} else {
|
|
419
|
-
throw
|
|
445
|
+
throw this.createError(`Unsupported operation: ${operation}`, {
|
|
446
|
+
operation: 'replicate',
|
|
447
|
+
resourceName,
|
|
448
|
+
tableName: table,
|
|
449
|
+
statusCode: 400,
|
|
450
|
+
retriable: false,
|
|
451
|
+
suggestion: 'Use one of the supported actions: insert, update, or delete.'
|
|
452
|
+
});
|
|
420
453
|
}
|
|
421
454
|
|
|
422
455
|
results.push({
|
|
@@ -484,36 +517,38 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
484
517
|
}
|
|
485
518
|
|
|
486
519
|
async replicateBatch(resourceName, records) {
|
|
487
|
-
const results =
|
|
488
|
-
const errors = [];
|
|
489
|
-
|
|
490
|
-
for (const record of records) {
|
|
520
|
+
const { results, errors } = await this.processBatch(records, async (record) => {
|
|
491
521
|
const [ok, err, res] = await tryFn(() => this.replicate(
|
|
492
|
-
resourceName,
|
|
493
|
-
record.operation,
|
|
494
|
-
record.data,
|
|
495
|
-
record.id,
|
|
522
|
+
resourceName,
|
|
523
|
+
record.operation,
|
|
524
|
+
record.data,
|
|
525
|
+
record.id,
|
|
496
526
|
record.beforeData
|
|
497
527
|
));
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
528
|
+
|
|
529
|
+
if (!ok) {
|
|
530
|
+
throw err;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return res;
|
|
534
|
+
}, {
|
|
535
|
+
concurrency: this.config.batchConcurrency,
|
|
536
|
+
mapError: (error, record) => {
|
|
501
537
|
if (this.config.verbose) {
|
|
502
|
-
console.warn(`[PostgresReplicator] Batch replication failed for record ${record.id}: ${
|
|
538
|
+
console.warn(`[PostgresReplicator] Batch replication failed for record ${record.id}: ${error.message}`);
|
|
503
539
|
}
|
|
504
|
-
|
|
540
|
+
return { id: record.id, error: error.message };
|
|
505
541
|
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Log errors if any occurred during batch processing
|
|
542
|
+
});
|
|
543
|
+
|
|
509
544
|
if (errors.length > 0) {
|
|
510
545
|
console.warn(`[PostgresReplicator] Batch replication completed with ${errors.length} error(s) for ${resourceName}:`, errors);
|
|
511
546
|
}
|
|
512
|
-
|
|
513
|
-
return {
|
|
514
|
-
success: errors.length === 0,
|
|
515
|
-
results,
|
|
516
|
-
errors
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
success: errors.length === 0,
|
|
550
|
+
results,
|
|
551
|
+
errors
|
|
517
552
|
};
|
|
518
553
|
}
|
|
519
554
|
|
|
@@ -562,4 +597,4 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
562
597
|
}
|
|
563
598
|
|
|
564
599
|
|
|
565
|
-
export default PostgresReplicator;
|
|
600
|
+
export default PostgresReplicator;
|