s3db.js 11.2.2 → 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.
Files changed (46) hide show
  1. package/dist/s3db.cjs.js +1650 -136
  2. package/dist/s3db.cjs.js.map +1 -1
  3. package/dist/s3db.es.js +1644 -137
  4. package/dist/s3db.es.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/behaviors/enforce-limits.js +28 -4
  7. package/src/behaviors/index.js +6 -1
  8. package/src/client.class.js +11 -1
  9. package/src/concerns/partition-queue.js +7 -1
  10. package/src/concerns/plugin-storage.js +75 -13
  11. package/src/database.class.js +22 -4
  12. package/src/errors.js +414 -24
  13. package/src/partition-drivers/base-partition-driver.js +12 -2
  14. package/src/partition-drivers/index.js +7 -1
  15. package/src/partition-drivers/memory-partition-driver.js +20 -5
  16. package/src/partition-drivers/sqs-partition-driver.js +6 -1
  17. package/src/plugins/audit.errors.js +46 -0
  18. package/src/plugins/backup/base-backup-driver.class.js +36 -6
  19. package/src/plugins/backup/filesystem-backup-driver.class.js +55 -7
  20. package/src/plugins/backup/index.js +40 -9
  21. package/src/plugins/backup/multi-backup-driver.class.js +69 -9
  22. package/src/plugins/backup/s3-backup-driver.class.js +48 -6
  23. package/src/plugins/backup.errors.js +45 -0
  24. package/src/plugins/cache/cache.class.js +8 -1
  25. package/src/plugins/cache/memory-cache.class.js +216 -33
  26. package/src/plugins/cache.errors.js +47 -0
  27. package/src/plugins/cache.plugin.js +94 -3
  28. package/src/plugins/eventual-consistency/analytics.js +145 -0
  29. package/src/plugins/eventual-consistency/index.js +203 -1
  30. package/src/plugins/fulltext.errors.js +46 -0
  31. package/src/plugins/fulltext.plugin.js +15 -3
  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/resource.class.js +8 -1
  45. package/src/stream/index.js +6 -1
  46. package/src/stream/resource-reader.class.js +6 -1
package/src/errors.js CHANGED
@@ -1,12 +1,12 @@
1
1
  export class BaseError extends Error {
2
- constructor({ verbose, bucket, key, message, code, statusCode, requestId, awsMessage, original, commandName, commandInput, metadata, suggestion, ...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
 
6
6
  if (typeof Error.captureStackTrace === 'function') {
7
7
  Error.captureStackTrace(this, this.constructor);
8
- } else {
9
- this.stack = (new Error(message)).stack;
8
+ } else {
9
+ this.stack = (new Error(message)).stack;
10
10
  }
11
11
 
12
12
  super.name = this.constructor.name;
@@ -22,7 +22,7 @@ export class BaseError extends Error {
22
22
  this.commandName = commandName;
23
23
  this.commandInput = commandInput;
24
24
  this.metadata = metadata;
25
- this.suggestion = suggestion;
25
+ this.description = description;
26
26
  this.data = { bucket, key, ...rest, verbose, message };
27
27
  }
28
28
 
@@ -40,7 +40,7 @@ export class BaseError extends Error {
40
40
  commandName: this.commandName,
41
41
  commandInput: this.commandInput,
42
42
  metadata: this.metadata,
43
- suggestion: this.suggestion,
43
+ description: this.description,
44
44
  data: this.data,
45
45
  original: this.original,
46
46
  stack: this.stack,
@@ -203,26 +203,26 @@ export function mapAwsError(err, context = {}) {
203
203
  const metadata = err.$metadata ? { ...err.$metadata } : undefined;
204
204
  const commandName = context.commandName;
205
205
  const commandInput = context.commandInput;
206
- let suggestion;
206
+ let description;
207
207
  if (code === 'NoSuchKey' || code === 'NotFound') {
208
- suggestion = 'Check if the key exists in the specified bucket and if your credentials have permission.';
209
- 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 });
210
210
  }
211
211
  if (code === 'NoSuchBucket') {
212
- suggestion = 'Check if the bucket exists and if your credentials have permission.';
213
- 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 });
214
214
  }
215
215
  if (code === 'AccessDenied' || (err.statusCode === 403) || code === 'Forbidden') {
216
- suggestion = 'Check your credentials and bucket policy.';
217
- 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 });
218
218
  }
219
219
  if (code === 'ValidationError' || (err.statusCode === 400)) {
220
- suggestion = 'Check the request parameters and payload.';
221
- 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 });
222
222
  }
223
223
  if (code === 'MissingMetadata') {
224
- suggestion = 'Check if the object metadata is present and valid.';
225
- 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 });
226
226
  }
227
227
  // Outros mapeamentos podem ser adicionados aqui
228
228
  // Incluir detalhes do erro original para facilitar debug
@@ -232,38 +232,428 @@ export function mapAwsError(err, context = {}) {
232
232
  err.statusCode && `Status: ${err.statusCode}`,
233
233
  err.stack && `Stack: ${err.stack.split('\n')[0]}`,
234
234
  ].filter(Boolean).join(' | ');
235
-
236
- suggestion = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
237
- 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 });
238
238
  }
239
239
 
240
240
  export class ConnectionStringError extends S3dbError {
241
241
  constructor(message, details = {}) {
242
- 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 });
243
244
  }
244
245
  }
245
246
 
246
247
  export class CryptoError extends S3dbError {
247
248
  constructor(message, details = {}) {
248
- 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 });
249
251
  }
250
252
  }
251
253
 
252
254
  export class SchemaError extends S3dbError {
253
255
  constructor(message, details = {}) {
254
- 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 });
255
258
  }
256
259
  }
257
260
 
258
261
  export class ResourceError extends S3dbError {
259
262
  constructor(message, details = {}) {
260
- 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 });
261
265
  Object.assign(this, details);
262
266
  }
263
267
  }
264
268
 
265
269
  export class PartitionError extends S3dbError {
266
270
  constructor(message, details = {}) {
267
- super(message, { ...details, suggestion: details.suggestion || 'Check partition definition, fields, and input values.' });
271
+ // Generate description if not provided
272
+ let description = details.description;
273
+ if (!description && details.resourceName && details.partitionName && details.fieldName) {
274
+ const { resourceName, partitionName, fieldName, availableFields = [] } = details;
275
+ description = `
276
+ Partition Field Validation Error
277
+
278
+ Resource: ${resourceName}
279
+ Partition: ${partitionName}
280
+ Missing Field: ${fieldName}
281
+
282
+ Available fields in schema:
283
+ ${availableFields.map(f => ` • ${f}`).join('\n') || ' (no fields defined)'}
284
+
285
+ Possible causes:
286
+ 1. Field was removed from schema but partition still references it
287
+ 2. Typo in partition field name
288
+ 3. Nested field path is incorrect (use dot notation like 'utm.source')
289
+
290
+ Solution:
291
+ ${details.strictValidation === false
292
+ ? ' • Update partition definition to use existing fields'
293
+ : ` • Add missing field to schema, OR
294
+ • Update partition definition to use existing fields, OR
295
+ • Use strictValidation: false to skip this check during testing`}
296
+
297
+ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#partitions
298
+ `.trim();
299
+ }
300
+
301
+ super(message, {
302
+ ...details,
303
+ description
304
+ });
305
+ }
306
+ }
307
+
308
+ export class AnalyticsNotEnabledError extends S3dbError {
309
+ constructor(details = {}) {
310
+ const {
311
+ pluginName = 'EventualConsistency',
312
+ resourceName = 'unknown',
313
+ field = 'unknown',
314
+ configuredResources = [],
315
+ registeredResources = [],
316
+ pluginInitialized = false,
317
+ ...rest
318
+ } = details;
319
+
320
+ const message = `Analytics not enabled for ${resourceName}.${field}`;
321
+
322
+ // Generate diagnostic description
323
+ const description = `
324
+ Analytics Not Enabled
325
+
326
+ Plugin: ${pluginName}
327
+ Resource: ${resourceName}
328
+ Field: ${field}
329
+
330
+ Diagnostics:
331
+ • Plugin initialized: ${pluginInitialized ? '✓ Yes' : '✗ No'}
332
+ • Analytics resources created: ${registeredResources.length}/${configuredResources.length}
333
+ ${configuredResources.map(r => {
334
+ const exists = registeredResources.includes(r);
335
+ return ` ${exists ? '✓' : '✗'} ${r}${!exists ? ' (missing)' : ''}`;
336
+ }).join('\n')}
337
+
338
+ Possible causes:
339
+ 1. Resource not created yet - Analytics resources are created when db.createResource() is called
340
+ 2. Resource created before plugin initialization - Plugin must be initialized before resources
341
+ 3. Field not configured in analytics.resources config
342
+
343
+ Correct initialization order:
344
+ 1. Create database: const db = new Database({ ... })
345
+ 2. Install plugins: await db.connect() (triggers plugin.install())
346
+ 3. Create resources: await db.createResource({ name: '${resourceName}', ... })
347
+ 4. Analytics resources are auto-created by plugin
348
+
349
+ Example fix:
350
+ const db = new Database({
351
+ bucket: 'my-bucket',
352
+ plugins: [new EventualConsistencyPlugin({
353
+ resources: {
354
+ '${resourceName}': {
355
+ fields: {
356
+ '${field}': { type: 'counter', analytics: true }
357
+ }
358
+ }
359
+ }
360
+ })]
361
+ });
362
+
363
+ await db.connect(); // Plugin initialized here
364
+ await db.createResource({ name: '${resourceName}', ... }); // Analytics resource created here
365
+
366
+ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/eventual-consistency.md
367
+ `.trim();
368
+
369
+ super(message, {
370
+ ...rest,
371
+ pluginName,
372
+ resourceName,
373
+ field,
374
+ configuredResources,
375
+ registeredResources,
376
+ pluginInitialized,
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
657
+ });
268
658
  }
269
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;