s3db.js 11.2.3 → 11.2.4
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/dist/s3db.cjs.js +1177 -128
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1172 -129
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/behaviors/enforce-limits.js +28 -4
- package/src/behaviors/index.js +6 -1
- package/src/client.class.js +11 -1
- package/src/concerns/partition-queue.js +7 -1
- package/src/concerns/plugin-storage.js +75 -13
- package/src/database.class.js +19 -4
- package/src/errors.js +306 -27
- package/src/partition-drivers/base-partition-driver.js +12 -2
- package/src/partition-drivers/index.js +7 -1
- package/src/partition-drivers/memory-partition-driver.js +20 -5
- package/src/partition-drivers/sqs-partition-driver.js +6 -1
- package/src/plugins/audit.errors.js +46 -0
- package/src/plugins/backup/base-backup-driver.class.js +36 -6
- package/src/plugins/backup/filesystem-backup-driver.class.js +55 -7
- package/src/plugins/backup/index.js +40 -9
- package/src/plugins/backup/multi-backup-driver.class.js +69 -9
- package/src/plugins/backup/s3-backup-driver.class.js +48 -6
- package/src/plugins/backup.errors.js +45 -0
- package/src/plugins/cache/cache.class.js +8 -1
- package/src/plugins/cache.errors.js +47 -0
- package/src/plugins/cache.plugin.js +8 -1
- package/src/plugins/fulltext.errors.js +46 -0
- package/src/plugins/fulltext.plugin.js +15 -3
- package/src/plugins/metrics.errors.js +46 -0
- package/src/plugins/queue-consumer.plugin.js +31 -4
- package/src/plugins/queue.errors.js +46 -0
- package/src/plugins/replicator.errors.js +46 -0
- package/src/plugins/replicator.plugin.js +40 -5
- package/src/plugins/replicators/base-replicator.class.js +19 -3
- package/src/plugins/replicators/index.js +9 -3
- package/src/plugins/replicators/s3db-replicator.class.js +38 -8
- package/src/plugins/scheduler.errors.js +46 -0
- package/src/plugins/scheduler.plugin.js +79 -19
- package/src/plugins/state-machine.errors.js +47 -0
- package/src/plugins/state-machine.plugin.js +86 -17
- package/src/stream/index.js +6 -1
- package/src/stream/resource-reader.class.js +6 -1
package/src/errors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class BaseError extends Error {
|
|
2
|
-
constructor({ verbose, bucket, key, message, code, statusCode, requestId, awsMessage, original, commandName, commandInput, metadata,
|
|
2
|
+
constructor({ verbose, bucket, key, message, code, statusCode, requestId, awsMessage, original, commandName, commandInput, metadata, description, ...rest }) {
|
|
3
3
|
if (verbose) message = message + `\n\nVerbose:\n\n${JSON.stringify(rest, null, 2)}`;
|
|
4
4
|
super(message);
|
|
5
5
|
|
|
@@ -22,7 +22,6 @@ export class BaseError extends Error {
|
|
|
22
22
|
this.commandName = commandName;
|
|
23
23
|
this.commandInput = commandInput;
|
|
24
24
|
this.metadata = metadata;
|
|
25
|
-
this.suggestion = suggestion;
|
|
26
25
|
this.description = description;
|
|
27
26
|
this.data = { bucket, key, ...rest, verbose, message };
|
|
28
27
|
}
|
|
@@ -41,7 +40,6 @@ export class BaseError extends Error {
|
|
|
41
40
|
commandName: this.commandName,
|
|
42
41
|
commandInput: this.commandInput,
|
|
43
42
|
metadata: this.metadata,
|
|
44
|
-
suggestion: this.suggestion,
|
|
45
43
|
description: this.description,
|
|
46
44
|
data: this.data,
|
|
47
45
|
original: this.original,
|
|
@@ -205,26 +203,26 @@ export function mapAwsError(err, context = {}) {
|
|
|
205
203
|
const metadata = err.$metadata ? { ...err.$metadata } : undefined;
|
|
206
204
|
const commandName = context.commandName;
|
|
207
205
|
const commandInput = context.commandInput;
|
|
208
|
-
let
|
|
206
|
+
let description;
|
|
209
207
|
if (code === 'NoSuchKey' || code === 'NotFound') {
|
|
210
|
-
|
|
211
|
-
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput,
|
|
208
|
+
description = 'The specified key does not exist in the bucket. Check if the key exists and if your credentials have permission to access it.';
|
|
209
|
+
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
212
210
|
}
|
|
213
211
|
if (code === 'NoSuchBucket') {
|
|
214
|
-
|
|
215
|
-
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput,
|
|
212
|
+
description = 'The specified bucket does not exist. Check if the bucket name is correct and if your credentials have permission to access it.';
|
|
213
|
+
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
216
214
|
}
|
|
217
215
|
if (code === 'AccessDenied' || (err.statusCode === 403) || code === 'Forbidden') {
|
|
218
|
-
|
|
219
|
-
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput,
|
|
216
|
+
description = 'Access denied. Check your AWS credentials, IAM permissions, and bucket policy.';
|
|
217
|
+
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
220
218
|
}
|
|
221
219
|
if (code === 'ValidationError' || (err.statusCode === 400)) {
|
|
222
|
-
|
|
223
|
-
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput,
|
|
220
|
+
description = 'Validation error. Check the request parameters and payload format.';
|
|
221
|
+
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
224
222
|
}
|
|
225
223
|
if (code === 'MissingMetadata') {
|
|
226
|
-
|
|
227
|
-
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput,
|
|
224
|
+
description = 'Object metadata is missing or invalid. Check if the object was uploaded correctly.';
|
|
225
|
+
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
228
226
|
}
|
|
229
227
|
// Outros mapeamentos podem ser adicionados aqui
|
|
230
228
|
// Incluir detalhes do erro original para facilitar debug
|
|
@@ -234,32 +232,36 @@ export function mapAwsError(err, context = {}) {
|
|
|
234
232
|
err.statusCode && `Status: ${err.statusCode}`,
|
|
235
233
|
err.stack && `Stack: ${err.stack.split('\n')[0]}`,
|
|
236
234
|
].filter(Boolean).join(' | ');
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput,
|
|
235
|
+
|
|
236
|
+
description = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
|
|
237
|
+
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput, description });
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
export class ConnectionStringError extends S3dbError {
|
|
243
241
|
constructor(message, details = {}) {
|
|
244
|
-
|
|
242
|
+
const description = details.description || 'Invalid connection string format. Check the connection string syntax and credentials.';
|
|
243
|
+
super(message, { ...details, description });
|
|
245
244
|
}
|
|
246
245
|
}
|
|
247
246
|
|
|
248
247
|
export class CryptoError extends S3dbError {
|
|
249
248
|
constructor(message, details = {}) {
|
|
250
|
-
|
|
249
|
+
const description = details.description || 'Cryptography operation failed. Check if the crypto library is available and input is valid.';
|
|
250
|
+
super(message, { ...details, description });
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
export class SchemaError extends S3dbError {
|
|
255
255
|
constructor(message, details = {}) {
|
|
256
|
-
|
|
256
|
+
const description = details.description || 'Schema validation failed. Check schema definition and input data format.';
|
|
257
|
+
super(message, { ...details, description });
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
export class ResourceError extends S3dbError {
|
|
261
262
|
constructor(message, details = {}) {
|
|
262
|
-
|
|
263
|
+
const description = details.description || 'Resource operation failed. Check resource configuration, attributes, and operation context.';
|
|
264
|
+
super(message, { ...details, description });
|
|
263
265
|
Object.assign(this, details);
|
|
264
266
|
}
|
|
265
267
|
}
|
|
@@ -292,14 +294,13 @@ ${details.strictValidation === false
|
|
|
292
294
|
• Update partition definition to use existing fields, OR
|
|
293
295
|
• Use strictValidation: false to skip this check during testing`}
|
|
294
296
|
|
|
295
|
-
Docs: https://
|
|
297
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#partitions
|
|
296
298
|
`.trim();
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
super(message, {
|
|
300
302
|
...details,
|
|
301
|
-
description
|
|
302
|
-
suggestion: details.suggestion || 'Check partition definition, fields, and input values.'
|
|
303
|
+
description
|
|
303
304
|
});
|
|
304
305
|
}
|
|
305
306
|
}
|
|
@@ -362,7 +363,7 @@ Example fix:
|
|
|
362
363
|
await db.connect(); // Plugin initialized here
|
|
363
364
|
await db.createResource({ name: '${resourceName}', ... }); // Analytics resource created here
|
|
364
365
|
|
|
365
|
-
Docs: https://
|
|
366
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/eventual-consistency.md
|
|
366
367
|
`.trim();
|
|
367
368
|
|
|
368
369
|
super(message, {
|
|
@@ -373,8 +374,286 @@ Docs: https://docs.s3db.js.org/plugins/eventual-consistency#troubleshooting
|
|
|
373
374
|
configuredResources,
|
|
374
375
|
registeredResources,
|
|
375
376
|
pluginInitialized,
|
|
376
|
-
description
|
|
377
|
-
|
|
377
|
+
description
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Plugin errors
|
|
383
|
+
export class PluginError extends S3dbError {
|
|
384
|
+
constructor(message, details = {}) {
|
|
385
|
+
const {
|
|
386
|
+
pluginName = 'Unknown',
|
|
387
|
+
operation = 'unknown',
|
|
388
|
+
...rest
|
|
389
|
+
} = details;
|
|
390
|
+
|
|
391
|
+
let description = details.description;
|
|
392
|
+
if (!description) {
|
|
393
|
+
description = `
|
|
394
|
+
Plugin Error
|
|
395
|
+
|
|
396
|
+
Plugin: ${pluginName}
|
|
397
|
+
Operation: ${operation}
|
|
398
|
+
|
|
399
|
+
Possible causes:
|
|
400
|
+
1. Plugin not properly initialized
|
|
401
|
+
2. Plugin configuration is invalid
|
|
402
|
+
3. Plugin dependencies not met
|
|
403
|
+
4. Plugin method called before installation
|
|
404
|
+
|
|
405
|
+
Solution:
|
|
406
|
+
Ensure plugin is added to database and connect() is called before usage.
|
|
407
|
+
|
|
408
|
+
Example:
|
|
409
|
+
const db = new Database({
|
|
410
|
+
bucket: 'my-bucket',
|
|
411
|
+
plugins: [new ${pluginName}({ /* config */ })]
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
await db.connect(); // Plugin installed here
|
|
415
|
+
// Now plugin methods are available
|
|
416
|
+
|
|
417
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/README.md
|
|
418
|
+
`.trim();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
super(message, {
|
|
422
|
+
...rest,
|
|
423
|
+
pluginName,
|
|
424
|
+
operation,
|
|
425
|
+
description
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Plugin storage errors
|
|
431
|
+
export class PluginStorageError extends S3dbError {
|
|
432
|
+
constructor(message, details = {}) {
|
|
433
|
+
const {
|
|
434
|
+
pluginSlug = 'unknown',
|
|
435
|
+
key = '',
|
|
436
|
+
operation = 'unknown',
|
|
437
|
+
...rest
|
|
438
|
+
} = details;
|
|
439
|
+
|
|
440
|
+
let description = details.description;
|
|
441
|
+
if (!description) {
|
|
442
|
+
description = `
|
|
443
|
+
Plugin Storage Error
|
|
444
|
+
|
|
445
|
+
Plugin: ${pluginSlug}
|
|
446
|
+
Key: ${key}
|
|
447
|
+
Operation: ${operation}
|
|
448
|
+
|
|
449
|
+
Possible causes:
|
|
450
|
+
1. Storage not initialized (plugin not installed)
|
|
451
|
+
2. Invalid key format
|
|
452
|
+
3. S3 operation failed
|
|
453
|
+
4. Permissions issue
|
|
454
|
+
|
|
455
|
+
Solution:
|
|
456
|
+
Ensure plugin has access to storage and key is valid.
|
|
457
|
+
|
|
458
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/README.md#plugin-storage
|
|
459
|
+
`.trim();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
super(message, {
|
|
463
|
+
...rest,
|
|
464
|
+
pluginSlug,
|
|
465
|
+
key,
|
|
466
|
+
operation,
|
|
467
|
+
description
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Partition driver errors
|
|
473
|
+
export class PartitionDriverError extends S3dbError {
|
|
474
|
+
constructor(message, details = {}) {
|
|
475
|
+
const {
|
|
476
|
+
driver = 'unknown',
|
|
477
|
+
operation = 'unknown',
|
|
478
|
+
queueSize,
|
|
479
|
+
maxQueueSize,
|
|
480
|
+
...rest
|
|
481
|
+
} = details;
|
|
482
|
+
|
|
483
|
+
let description = details.description;
|
|
484
|
+
if (!description && queueSize !== undefined && maxQueueSize !== undefined) {
|
|
485
|
+
description = `
|
|
486
|
+
Partition Driver Error
|
|
487
|
+
|
|
488
|
+
Driver: ${driver}
|
|
489
|
+
Operation: ${operation}
|
|
490
|
+
Queue Status: ${queueSize}/${maxQueueSize}
|
|
491
|
+
|
|
492
|
+
Possible causes:
|
|
493
|
+
1. Queue is full (backpressure)
|
|
494
|
+
2. Driver not properly configured
|
|
495
|
+
3. SQS permissions issue (if using SQS driver)
|
|
496
|
+
|
|
497
|
+
Solution:
|
|
498
|
+
${queueSize >= maxQueueSize
|
|
499
|
+
? 'Wait for queue to drain or increase maxQueueSize'
|
|
500
|
+
: 'Check driver configuration and permissions'}
|
|
501
|
+
|
|
502
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#partition-drivers
|
|
503
|
+
`.trim();
|
|
504
|
+
} else if (!description) {
|
|
505
|
+
description = `
|
|
506
|
+
Partition Driver Error
|
|
507
|
+
|
|
508
|
+
Driver: ${driver}
|
|
509
|
+
Operation: ${operation}
|
|
510
|
+
|
|
511
|
+
Check driver configuration and permissions.
|
|
512
|
+
|
|
513
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#partition-drivers
|
|
514
|
+
`.trim();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
super(message, {
|
|
518
|
+
...rest,
|
|
519
|
+
driver,
|
|
520
|
+
operation,
|
|
521
|
+
queueSize,
|
|
522
|
+
maxQueueSize,
|
|
523
|
+
description
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Behavior errors
|
|
529
|
+
export class BehaviorError extends S3dbError {
|
|
530
|
+
constructor(message, details = {}) {
|
|
531
|
+
const {
|
|
532
|
+
behavior = 'unknown',
|
|
533
|
+
availableBehaviors = [],
|
|
534
|
+
...rest
|
|
535
|
+
} = details;
|
|
536
|
+
|
|
537
|
+
let description = details.description;
|
|
538
|
+
if (!description) {
|
|
539
|
+
description = `
|
|
540
|
+
Behavior Error
|
|
541
|
+
|
|
542
|
+
Requested: ${behavior}
|
|
543
|
+
Available: ${availableBehaviors.join(', ') || 'body-overflow, body-only, truncate-data, enforce-limits, user-managed'}
|
|
544
|
+
|
|
545
|
+
Possible causes:
|
|
546
|
+
1. Behavior name misspelled
|
|
547
|
+
2. Custom behavior not registered
|
|
548
|
+
|
|
549
|
+
Solution:
|
|
550
|
+
Use one of the available behaviors or register custom behavior.
|
|
551
|
+
|
|
552
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#behaviors
|
|
553
|
+
`.trim();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
super(message, {
|
|
557
|
+
...rest,
|
|
558
|
+
behavior,
|
|
559
|
+
availableBehaviors,
|
|
560
|
+
description
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Stream errors
|
|
566
|
+
export class StreamError extends S3dbError {
|
|
567
|
+
constructor(message, details = {}) {
|
|
568
|
+
const {
|
|
569
|
+
operation = 'unknown',
|
|
570
|
+
resource,
|
|
571
|
+
...rest
|
|
572
|
+
} = details;
|
|
573
|
+
|
|
574
|
+
let description = details.description;
|
|
575
|
+
if (!description) {
|
|
576
|
+
description = `
|
|
577
|
+
Stream Error
|
|
578
|
+
|
|
579
|
+
Operation: ${operation}
|
|
580
|
+
${resource ? `Resource: ${resource}` : ''}
|
|
581
|
+
|
|
582
|
+
Possible causes:
|
|
583
|
+
1. Stream not properly initialized
|
|
584
|
+
2. Resource not available
|
|
585
|
+
3. Network error during streaming
|
|
586
|
+
|
|
587
|
+
Solution:
|
|
588
|
+
Check stream configuration and resource availability.
|
|
589
|
+
|
|
590
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#streaming
|
|
591
|
+
`.trim();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
super(message, {
|
|
595
|
+
...rest,
|
|
596
|
+
operation,
|
|
597
|
+
resource,
|
|
598
|
+
description
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Metadata limit errors (specific for 2KB S3 limit)
|
|
604
|
+
export class MetadataLimitError extends S3dbError {
|
|
605
|
+
constructor(message, details = {}) {
|
|
606
|
+
const {
|
|
607
|
+
totalSize,
|
|
608
|
+
effectiveLimit,
|
|
609
|
+
absoluteLimit = 2047,
|
|
610
|
+
excess,
|
|
611
|
+
resourceName,
|
|
612
|
+
operation,
|
|
613
|
+
...rest
|
|
614
|
+
} = details;
|
|
615
|
+
|
|
616
|
+
let description = details.description;
|
|
617
|
+
if (!description && totalSize && effectiveLimit) {
|
|
618
|
+
description = `
|
|
619
|
+
S3 Metadata Size Limit Exceeded
|
|
620
|
+
|
|
621
|
+
Current Size: ${totalSize} bytes
|
|
622
|
+
Effective Limit: ${effectiveLimit} bytes
|
|
623
|
+
Absolute Limit: ${absoluteLimit} bytes
|
|
624
|
+
${excess ? `Excess: ${excess} bytes` : ''}
|
|
625
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
626
|
+
${operation ? `Operation: ${operation}` : ''}
|
|
627
|
+
|
|
628
|
+
S3 has a hard limit of 2KB (2047 bytes) for object metadata.
|
|
629
|
+
|
|
630
|
+
Solutions:
|
|
631
|
+
1. Use 'body-overflow' behavior to store excess in body
|
|
632
|
+
2. Use 'body-only' behavior to store everything in body
|
|
633
|
+
3. Reduce number of fields
|
|
634
|
+
4. Use shorter field values
|
|
635
|
+
5. Enable advanced metadata encoding
|
|
636
|
+
|
|
637
|
+
Example:
|
|
638
|
+
await db.createResource({
|
|
639
|
+
name: '${resourceName || 'myResource'}',
|
|
640
|
+
behavior: 'body-overflow', // Automatically handles overflow
|
|
641
|
+
attributes: { ... }
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#metadata-size-limits
|
|
645
|
+
`.trim();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
super(message, {
|
|
649
|
+
...rest,
|
|
650
|
+
totalSize,
|
|
651
|
+
effectiveLimit,
|
|
652
|
+
absoluteLimit,
|
|
653
|
+
excess,
|
|
654
|
+
resourceName,
|
|
655
|
+
operation,
|
|
656
|
+
description
|
|
378
657
|
});
|
|
379
658
|
}
|
|
380
659
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
import { PartitionDriverError } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Base class for all partition drivers
|
|
@@ -31,7 +32,11 @@ export class BasePartitionDriver extends EventEmitter {
|
|
|
31
32
|
* @param {Object} operation.data - The data for the operation
|
|
32
33
|
*/
|
|
33
34
|
async queue(operation) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new PartitionDriverError('queue() must be implemented by subclass', {
|
|
36
|
+
driver: this.name || 'BasePartitionDriver',
|
|
37
|
+
operation: 'queue',
|
|
38
|
+
suggestion: 'Extend BasePartitionDriver and implement the queue() method'
|
|
39
|
+
});
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
@@ -57,7 +62,12 @@ export class BasePartitionDriver extends EventEmitter {
|
|
|
57
62
|
break;
|
|
58
63
|
|
|
59
64
|
default:
|
|
60
|
-
throw new
|
|
65
|
+
throw new PartitionDriverError(`Unknown partition operation type: ${type}`, {
|
|
66
|
+
driver: this.name || 'BasePartitionDriver',
|
|
67
|
+
operation: type,
|
|
68
|
+
availableOperations: ['create', 'update', 'delete'],
|
|
69
|
+
suggestion: 'Use one of the supported partition operations: create, update, or delete'
|
|
70
|
+
});
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
this.stats.processed++;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SyncPartitionDriver } from './sync-partition-driver.js';
|
|
2
2
|
import { MemoryPartitionDriver } from './memory-partition-driver.js';
|
|
3
3
|
import { SQSPartitionDriver } from './sqs-partition-driver.js';
|
|
4
|
+
import { PartitionDriverError } from '../errors.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Partition driver factory
|
|
@@ -29,7 +30,12 @@ export class PartitionDriverFactory {
|
|
|
29
30
|
// Get driver class
|
|
30
31
|
const DriverClass = this.drivers[driverName];
|
|
31
32
|
if (!DriverClass) {
|
|
32
|
-
throw new
|
|
33
|
+
throw new PartitionDriverError(`Unknown partition driver: ${driverName}`, {
|
|
34
|
+
driver: driverName,
|
|
35
|
+
operation: 'create',
|
|
36
|
+
availableDrivers: Object.keys(this.drivers),
|
|
37
|
+
suggestion: `Use one of the available drivers: ${Object.keys(this.drivers).join(', ')}, or register a custom driver`
|
|
38
|
+
});
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
// Create and initialize driver
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
2
|
import { PromisePool } from '@supercharge/promise-pool';
|
|
3
|
+
import { PartitionDriverError } from '../errors.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* In-memory partition driver with background processing
|
|
@@ -36,13 +37,19 @@ export class MemoryPartitionDriver extends BasePartitionDriver {
|
|
|
36
37
|
async queue(operation) {
|
|
37
38
|
// Check queue size limit
|
|
38
39
|
if (this.queue.length >= this.maxQueueSize) {
|
|
39
|
-
const error = new
|
|
40
|
+
const error = new PartitionDriverError('Memory queue full - backpressure detected', {
|
|
41
|
+
driver: 'memory',
|
|
42
|
+
operation: 'queue',
|
|
43
|
+
queueSize: this.queue.length,
|
|
44
|
+
maxQueueSize: this.maxQueueSize,
|
|
45
|
+
suggestion: 'Increase maxQueueSize, enable rejectOnFull, or reduce operation rate'
|
|
46
|
+
});
|
|
40
47
|
this.emit('queueFull', { operation, queueSize: this.queue.length });
|
|
41
|
-
|
|
48
|
+
|
|
42
49
|
if (this.options.rejectOnFull) {
|
|
43
50
|
throw error;
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
// Wait for some space
|
|
47
54
|
await this.waitForSpace();
|
|
48
55
|
}
|
|
@@ -193,9 +200,17 @@ export class MemoryPartitionDriver extends BasePartitionDriver {
|
|
|
193
200
|
|
|
194
201
|
while (this.queue.length >= this.maxQueueSize) {
|
|
195
202
|
if (Date.now() - startTime > maxWait) {
|
|
196
|
-
throw new
|
|
203
|
+
throw new PartitionDriverError('Timeout waiting for queue space', {
|
|
204
|
+
driver: 'memory',
|
|
205
|
+
operation: 'waitForSpace',
|
|
206
|
+
queueSize: this.queue.length,
|
|
207
|
+
maxQueueSize: this.maxQueueSize,
|
|
208
|
+
waitedMs: Date.now() - startTime,
|
|
209
|
+
maxWaitMs: maxWait,
|
|
210
|
+
suggestion: 'Queue is full and not draining fast enough. Increase maxQueueSize or concurrency'
|
|
211
|
+
});
|
|
197
212
|
}
|
|
198
|
-
|
|
213
|
+
|
|
199
214
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
200
215
|
}
|
|
201
216
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
2
|
import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
|
|
3
|
+
import { PartitionDriverError } from '../errors.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SQS-based partition driver for distributed processing
|
|
@@ -14,7 +15,11 @@ export class SQSPartitionDriver extends BasePartitionDriver {
|
|
|
14
15
|
// SQS Configuration
|
|
15
16
|
this.queueUrl = options.queueUrl;
|
|
16
17
|
if (!this.queueUrl) {
|
|
17
|
-
throw new
|
|
18
|
+
throw new PartitionDriverError('SQS queue URL is required', {
|
|
19
|
+
driver: 'sqs',
|
|
20
|
+
operation: 'constructor',
|
|
21
|
+
suggestion: 'Provide queueUrl in options: new SQSPartitionDriver({ queueUrl: "https://sqs.region.amazonaws.com/account/queue" })'
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
this.region = options.region || 'us-east-1';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { S3dbError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AuditError - Errors related to audit logging operations
|
|
5
|
+
*
|
|
6
|
+
* Used for audit operations including:
|
|
7
|
+
* - Audit log creation
|
|
8
|
+
* - Change tracking
|
|
9
|
+
* - Audit query and retrieval
|
|
10
|
+
* - Compliance logging
|
|
11
|
+
* - Event recording
|
|
12
|
+
*
|
|
13
|
+
* @extends S3dbError
|
|
14
|
+
*/
|
|
15
|
+
export class AuditError extends S3dbError {
|
|
16
|
+
constructor(message, details = {}) {
|
|
17
|
+
const { resourceName, operation = 'unknown', auditId, ...rest } = details;
|
|
18
|
+
|
|
19
|
+
let description = details.description;
|
|
20
|
+
if (!description) {
|
|
21
|
+
description = `
|
|
22
|
+
Audit Operation Error
|
|
23
|
+
|
|
24
|
+
Operation: ${operation}
|
|
25
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
26
|
+
${auditId ? `Audit ID: ${auditId}` : ''}
|
|
27
|
+
|
|
28
|
+
Common causes:
|
|
29
|
+
1. Audit log storage not accessible
|
|
30
|
+
2. Resource not configured for auditing
|
|
31
|
+
3. Invalid audit log format
|
|
32
|
+
4. Audit query failed
|
|
33
|
+
5. Insufficient permissions
|
|
34
|
+
|
|
35
|
+
Solution:
|
|
36
|
+
Check audit plugin configuration and storage accessibility.
|
|
37
|
+
|
|
38
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/audit.md
|
|
39
|
+
`.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
super(message, { ...rest, resourceName, operation, auditId, description });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default AuditError;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BackupError } from '../backup.errors.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* BaseBackupDriver - Abstract base class for backup drivers
|
|
3
5
|
*
|
|
@@ -38,7 +40,12 @@ export default class BaseBackupDriver {
|
|
|
38
40
|
* @returns {Object} Upload result with destination info
|
|
39
41
|
*/
|
|
40
42
|
async upload(filePath, backupId, manifest) {
|
|
41
|
-
throw new
|
|
43
|
+
throw new BackupError('upload() method must be implemented by subclass', {
|
|
44
|
+
operation: 'upload',
|
|
45
|
+
driver: this.constructor.name,
|
|
46
|
+
backupId,
|
|
47
|
+
suggestion: 'Extend BaseBackupDriver and implement the upload() method'
|
|
48
|
+
});
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/**
|
|
@@ -49,7 +56,12 @@ export default class BaseBackupDriver {
|
|
|
49
56
|
* @returns {string} Path to downloaded file
|
|
50
57
|
*/
|
|
51
58
|
async download(backupId, targetPath, metadata) {
|
|
52
|
-
throw new
|
|
59
|
+
throw new BackupError('download() method must be implemented by subclass', {
|
|
60
|
+
operation: 'download',
|
|
61
|
+
driver: this.constructor.name,
|
|
62
|
+
backupId,
|
|
63
|
+
suggestion: 'Extend BaseBackupDriver and implement the download() method'
|
|
64
|
+
});
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
/**
|
|
@@ -58,7 +70,12 @@ export default class BaseBackupDriver {
|
|
|
58
70
|
* @param {Object} metadata - Backup metadata
|
|
59
71
|
*/
|
|
60
72
|
async delete(backupId, metadata) {
|
|
61
|
-
throw new
|
|
73
|
+
throw new BackupError('delete() method must be implemented by subclass', {
|
|
74
|
+
operation: 'delete',
|
|
75
|
+
driver: this.constructor.name,
|
|
76
|
+
backupId,
|
|
77
|
+
suggestion: 'Extend BaseBackupDriver and implement the delete() method'
|
|
78
|
+
});
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
/**
|
|
@@ -67,7 +84,11 @@ export default class BaseBackupDriver {
|
|
|
67
84
|
* @returns {Array} List of backup metadata
|
|
68
85
|
*/
|
|
69
86
|
async list(options = {}) {
|
|
70
|
-
throw new
|
|
87
|
+
throw new BackupError('list() method must be implemented by subclass', {
|
|
88
|
+
operation: 'list',
|
|
89
|
+
driver: this.constructor.name,
|
|
90
|
+
suggestion: 'Extend BaseBackupDriver and implement the list() method'
|
|
91
|
+
});
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
/**
|
|
@@ -78,7 +99,12 @@ export default class BaseBackupDriver {
|
|
|
78
99
|
* @returns {boolean} True if backup is valid
|
|
79
100
|
*/
|
|
80
101
|
async verify(backupId, expectedChecksum, metadata) {
|
|
81
|
-
throw new
|
|
102
|
+
throw new BackupError('verify() method must be implemented by subclass', {
|
|
103
|
+
operation: 'verify',
|
|
104
|
+
driver: this.constructor.name,
|
|
105
|
+
backupId,
|
|
106
|
+
suggestion: 'Extend BaseBackupDriver and implement the verify() method'
|
|
107
|
+
});
|
|
82
108
|
}
|
|
83
109
|
|
|
84
110
|
/**
|
|
@@ -86,7 +112,11 @@ export default class BaseBackupDriver {
|
|
|
86
112
|
* @returns {string} Driver type
|
|
87
113
|
*/
|
|
88
114
|
getType() {
|
|
89
|
-
throw new
|
|
115
|
+
throw new BackupError('getType() method must be implemented by subclass', {
|
|
116
|
+
operation: 'getType',
|
|
117
|
+
driver: this.constructor.name,
|
|
118
|
+
suggestion: 'Extend BaseBackupDriver and implement the getType() method'
|
|
119
|
+
});
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
/**
|