s3db.js 11.2.3 → 11.2.5

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 (52) hide show
  1. package/dist/s3db-cli.js +588 -74
  2. package/dist/s3db.cjs.js +2472 -150
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.es.js +2464 -151
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +2 -1
  7. package/src/behaviors/enforce-limits.js +28 -4
  8. package/src/behaviors/index.js +6 -1
  9. package/src/client.class.js +11 -1
  10. package/src/concerns/base62.js +70 -0
  11. package/src/concerns/partition-queue.js +7 -1
  12. package/src/concerns/plugin-storage.js +75 -13
  13. package/src/database.class.js +19 -4
  14. package/src/errors.js +306 -27
  15. package/src/partition-drivers/base-partition-driver.js +12 -2
  16. package/src/partition-drivers/index.js +7 -1
  17. package/src/partition-drivers/memory-partition-driver.js +20 -5
  18. package/src/partition-drivers/sqs-partition-driver.js +6 -1
  19. package/src/plugins/audit.errors.js +46 -0
  20. package/src/plugins/backup/base-backup-driver.class.js +36 -6
  21. package/src/plugins/backup/filesystem-backup-driver.class.js +55 -7
  22. package/src/plugins/backup/index.js +40 -9
  23. package/src/plugins/backup/multi-backup-driver.class.js +69 -9
  24. package/src/plugins/backup/s3-backup-driver.class.js +48 -6
  25. package/src/plugins/backup.errors.js +45 -0
  26. package/src/plugins/cache/cache.class.js +8 -1
  27. package/src/plugins/cache.errors.js +47 -0
  28. package/src/plugins/cache.plugin.js +8 -1
  29. package/src/plugins/fulltext.errors.js +46 -0
  30. package/src/plugins/fulltext.plugin.js +15 -3
  31. package/src/plugins/index.js +1 -0
  32. package/src/plugins/metrics.errors.js +46 -0
  33. package/src/plugins/queue-consumer.plugin.js +31 -4
  34. package/src/plugins/queue.errors.js +46 -0
  35. package/src/plugins/replicator.errors.js +46 -0
  36. package/src/plugins/replicator.plugin.js +40 -5
  37. package/src/plugins/replicators/base-replicator.class.js +19 -3
  38. package/src/plugins/replicators/index.js +9 -3
  39. package/src/plugins/replicators/s3db-replicator.class.js +38 -8
  40. package/src/plugins/scheduler.errors.js +46 -0
  41. package/src/plugins/scheduler.plugin.js +79 -19
  42. package/src/plugins/state-machine.errors.js +47 -0
  43. package/src/plugins/state-machine.plugin.js +86 -17
  44. package/src/plugins/vector/distances.js +173 -0
  45. package/src/plugins/vector/kmeans.js +367 -0
  46. package/src/plugins/vector/metrics.js +369 -0
  47. package/src/plugins/vector/vector-error.js +43 -0
  48. package/src/plugins/vector.plugin.js +687 -0
  49. package/src/schema.class.js +232 -41
  50. package/src/stream/index.js +6 -1
  51. package/src/stream/resource-reader.class.js +6 -1
  52. package/src/validator.class.js +8 -0
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, suggestion, description, ...rest }) {
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 suggestion;
206
+ let description;
209
207
  if (code === 'NoSuchKey' || code === 'NotFound') {
210
- suggestion = 'Check if the key exists in the specified bucket and if your credentials have permission.';
211
- return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- suggestion = 'Check if the bucket exists and if your credentials have permission.';
215
- return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- suggestion = 'Check your credentials and bucket policy.';
219
- return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- suggestion = 'Check the request parameters and payload.';
223
- return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- suggestion = 'Check if the object metadata is present and valid.';
227
- return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- suggestion = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
239
- return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput, suggestion });
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
- super(message, { ...details, suggestion: 'Check the connection string format and credentials.' });
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
- super(message, { ...details, suggestion: 'Check if the crypto library is available and input is valid.' });
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
- super(message, { ...details, suggestion: 'Check schema definition and input data.' });
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
- super(message, { ...details, suggestion: details.suggestion || 'Check resource configuration, attributes, and operation context.' });
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://docs.s3db.js.org/resources/partitions#validation
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://docs.s3db.js.org/plugins/eventual-consistency#troubleshooting
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
- suggestion: 'Ensure resources are created after plugin initialization. Check plugin configuration and resource creation order.'
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 Error('queue() must be implemented by subclass');
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 Error(`Unknown partition operation type: ${type}`);
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 Error(`Unknown partition driver: ${driverName}. Available: ${Object.keys(this.drivers).join(', ')}`);
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 Error(`Memory queue full (${this.maxQueueSize} items)`);
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 Error('Timeout waiting for queue space');
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 Error('SQS queue URL is required for SQSPartitionDriver');
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 Error('upload() method must be implemented by subclass');
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 Error('download() method must be implemented by subclass');
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 Error('delete() method must be implemented by subclass');
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 Error('list() method must be implemented by subclass');
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 Error('verify() method must be implemented by subclass');
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 Error('getType() method must be implemented by subclass');
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
  /**