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
@@ -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
  /**
@@ -5,6 +5,7 @@ import { pipeline } from 'stream/promises';
5
5
  import path from 'path';
6
6
  import crypto from 'crypto';
7
7
  import tryFn from '../../concerns/try-fn.js';
8
+ import { BackupError } from '../backup.errors.js';
8
9
 
9
10
  /**
10
11
  * FilesystemBackupDriver - Stores backups on local/network filesystem
@@ -31,7 +32,11 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
31
32
  async onSetup() {
32
33
  // Validate path configuration
33
34
  if (!this.config.path) {
34
- throw new Error('FilesystemBackupDriver: path configuration is required');
35
+ throw new BackupError('FilesystemBackupDriver: path configuration is required', {
36
+ operation: 'onSetup',
37
+ driver: 'filesystem',
38
+ suggestion: 'Provide a path in config: new FilesystemBackupDriver({ path: "/path/to/backups" })'
39
+ });
35
40
  }
36
41
 
37
42
  this.log(`Initialized with path: ${this.config.path}`);
@@ -69,13 +74,28 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
69
74
  );
70
75
 
71
76
  if (!createDirOk) {
72
- throw new Error(`Failed to create backup directory: ${createDirErr.message}`);
77
+ throw new BackupError('Failed to create backup directory', {
78
+ operation: 'upload',
79
+ driver: 'filesystem',
80
+ backupId,
81
+ targetDir,
82
+ original: createDirErr,
83
+ suggestion: 'Check directory permissions and disk space'
84
+ });
73
85
  }
74
86
 
75
87
  // Copy backup file
76
88
  const [copyOk, copyErr] = await tryFn(() => copyFile(filePath, targetPath));
77
89
  if (!copyOk) {
78
- throw new Error(`Failed to copy backup file: ${copyErr.message}`);
90
+ throw new BackupError('Failed to copy backup file', {
91
+ operation: 'upload',
92
+ driver: 'filesystem',
93
+ backupId,
94
+ filePath,
95
+ targetPath,
96
+ original: copyErr,
97
+ suggestion: 'Check file permissions and disk space'
98
+ });
79
99
  }
80
100
 
81
101
  // Write manifest
@@ -90,7 +110,14 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
90
110
  if (!manifestOk) {
91
111
  // Clean up backup file if manifest fails
92
112
  await tryFn(() => unlink(targetPath));
93
- throw new Error(`Failed to write manifest: ${manifestErr.message}`);
113
+ throw new BackupError('Failed to write manifest file', {
114
+ operation: 'upload',
115
+ driver: 'filesystem',
116
+ backupId,
117
+ manifestPath,
118
+ original: manifestErr,
119
+ suggestion: 'Check directory permissions and disk space'
120
+ });
94
121
  }
95
122
 
96
123
  // Get file stats
@@ -116,7 +143,13 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
116
143
  // Check if source exists
117
144
  const [existsOk] = await tryFn(() => access(sourcePath));
118
145
  if (!existsOk) {
119
- throw new Error(`Backup file not found: ${sourcePath}`);
146
+ throw new BackupError('Backup file not found', {
147
+ operation: 'download',
148
+ driver: 'filesystem',
149
+ backupId,
150
+ sourcePath,
151
+ suggestion: 'Check if backup exists using list() method'
152
+ });
120
153
  }
121
154
 
122
155
  // Create target directory if needed
@@ -126,7 +159,15 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
126
159
  // Copy file
127
160
  const [copyOk, copyErr] = await tryFn(() => copyFile(sourcePath, targetPath));
128
161
  if (!copyOk) {
129
- throw new Error(`Failed to download backup: ${copyErr.message}`);
162
+ throw new BackupError('Failed to download backup', {
163
+ operation: 'download',
164
+ driver: 'filesystem',
165
+ backupId,
166
+ sourcePath,
167
+ targetPath,
168
+ original: copyErr,
169
+ suggestion: 'Check file permissions and disk space'
170
+ });
130
171
  }
131
172
 
132
173
  this.log(`Downloaded backup ${backupId} from ${sourcePath} to ${targetPath}`);
@@ -150,7 +191,14 @@ export default class FilesystemBackupDriver extends BaseBackupDriver {
150
191
  const [deleteManifestOk] = await tryFn(() => unlink(manifestPath));
151
192
 
152
193
  if (!deleteBackupOk && !deleteManifestOk) {
153
- throw new Error(`Failed to delete backup files for ${backupId}`);
194
+ throw new BackupError('Failed to delete backup files', {
195
+ operation: 'delete',
196
+ driver: 'filesystem',
197
+ backupId,
198
+ backupPath,
199
+ manifestPath,
200
+ suggestion: 'Check file permissions'
201
+ });
154
202
  }
155
203
 
156
204
  this.log(`Deleted backup ${backupId}`);
@@ -2,6 +2,7 @@ import BaseBackupDriver from './base-backup-driver.class.js';
2
2
  import FilesystemBackupDriver from './filesystem-backup-driver.class.js';
3
3
  import S3BackupDriver from './s3-backup-driver.class.js';
4
4
  import MultiBackupDriver from './multi-backup-driver.class.js';
5
+ import { BackupError } from '../backup.errors.js';
5
6
 
6
7
  export {
7
8
  BaseBackupDriver,
@@ -27,11 +28,16 @@ export const BACKUP_DRIVERS = {
27
28
  */
28
29
  export function createBackupDriver(driver, config = {}) {
29
30
  const DriverClass = BACKUP_DRIVERS[driver];
30
-
31
+
31
32
  if (!DriverClass) {
32
- throw new Error(`Unknown backup driver: ${driver}. Available drivers: ${Object.keys(BACKUP_DRIVERS).join(', ')}`);
33
+ throw new BackupError(`Unknown backup driver: ${driver}`, {
34
+ operation: 'createBackupDriver',
35
+ driver,
36
+ availableDrivers: Object.keys(BACKUP_DRIVERS),
37
+ suggestion: `Use one of the available drivers: ${Object.keys(BACKUP_DRIVERS).join(', ')}`
38
+ });
33
39
  }
34
-
40
+
35
41
  return new DriverClass(config);
36
42
  }
37
43
 
@@ -43,18 +49,32 @@ export function createBackupDriver(driver, config = {}) {
43
49
  */
44
50
  export function validateBackupConfig(driver, config = {}) {
45
51
  if (!driver || typeof driver !== 'string') {
46
- throw new Error('Driver type must be a non-empty string');
52
+ throw new BackupError('Driver type must be a non-empty string', {
53
+ operation: 'validateBackupConfig',
54
+ driver,
55
+ suggestion: 'Provide a valid driver type string (filesystem, s3, or multi)'
56
+ });
47
57
  }
48
58
 
49
59
  if (!BACKUP_DRIVERS[driver]) {
50
- throw new Error(`Unknown backup driver: ${driver}. Available drivers: ${Object.keys(BACKUP_DRIVERS).join(', ')}`);
60
+ throw new BackupError(`Unknown backup driver: ${driver}`, {
61
+ operation: 'validateBackupConfig',
62
+ driver,
63
+ availableDrivers: Object.keys(BACKUP_DRIVERS),
64
+ suggestion: `Use one of the available drivers: ${Object.keys(BACKUP_DRIVERS).join(', ')}`
65
+ });
51
66
  }
52
67
 
53
68
  // Driver-specific validation
54
69
  switch (driver) {
55
70
  case 'filesystem':
56
71
  if (!config.path) {
57
- throw new Error('FilesystemBackupDriver requires "path" configuration');
72
+ throw new BackupError('FilesystemBackupDriver requires "path" configuration', {
73
+ operation: 'validateBackupConfig',
74
+ driver: 'filesystem',
75
+ config,
76
+ suggestion: 'Provide a "path" property in config: { path: "/path/to/backups" }'
77
+ });
58
78
  }
59
79
  break;
60
80
 
@@ -64,13 +84,24 @@ export function validateBackupConfig(driver, config = {}) {
64
84
 
65
85
  case 'multi':
66
86
  if (!Array.isArray(config.destinations) || config.destinations.length === 0) {
67
- throw new Error('MultiBackupDriver requires non-empty "destinations" array');
87
+ throw new BackupError('MultiBackupDriver requires non-empty "destinations" array', {
88
+ operation: 'validateBackupConfig',
89
+ driver: 'multi',
90
+ config,
91
+ suggestion: 'Provide destinations array: { destinations: [{ driver: "s3", config: {...} }] }'
92
+ });
68
93
  }
69
-
94
+
70
95
  // Validate each destination
71
96
  config.destinations.forEach((dest, index) => {
72
97
  if (!dest.driver) {
73
- throw new Error(`Destination ${index} must have a "driver" property`);
98
+ throw new BackupError(`Destination ${index} must have a "driver" property`, {
99
+ operation: 'validateBackupConfig',
100
+ driver: 'multi',
101
+ destinationIndex: index,
102
+ destination: dest,
103
+ suggestion: 'Each destination must have a driver property: { driver: "s3", config: {...} }'
104
+ });
74
105
  }
75
106
 
76
107
  // Recursive validation for nested drivers
@@ -1,6 +1,7 @@
1
1
  import BaseBackupDriver from './base-backup-driver.class.js';
2
2
  import { createBackupDriver } from './index.js';
3
3
  import tryFn from '../../concerns/try-fn.js';
4
+ import { BackupError } from '../backup.errors.js';
4
5
 
5
6
  /**
6
7
  * MultiBackupDriver - Manages multiple backup destinations
@@ -34,13 +35,24 @@ export default class MultiBackupDriver extends BaseBackupDriver {
34
35
 
35
36
  async onSetup() {
36
37
  if (!Array.isArray(this.config.destinations) || this.config.destinations.length === 0) {
37
- throw new Error('MultiBackupDriver: destinations array is required and must not be empty');
38
+ throw new BackupError('MultiBackupDriver requires non-empty destinations array', {
39
+ operation: 'onSetup',
40
+ driver: 'multi',
41
+ destinationsProvided: this.config.destinations,
42
+ suggestion: 'Provide destinations array: { destinations: [{ driver: "s3", config: {...} }, { driver: "filesystem", config: {...} }] }'
43
+ });
38
44
  }
39
45
 
40
46
  // Create and setup all driver instances
41
47
  for (const [index, destConfig] of this.config.destinations.entries()) {
42
48
  if (!destConfig.driver) {
43
- throw new Error(`MultiBackupDriver: destination[${index}] must have a driver type`);
49
+ throw new BackupError(`Destination ${index} missing driver type`, {
50
+ operation: 'onSetup',
51
+ driver: 'multi',
52
+ destinationIndex: index,
53
+ destination: destConfig,
54
+ suggestion: 'Each destination must have a driver property: { driver: "s3", config: {...} } or { driver: "filesystem", config: {...} }'
55
+ });
44
56
  }
45
57
 
46
58
  try {
@@ -51,10 +63,18 @@ export default class MultiBackupDriver extends BaseBackupDriver {
51
63
  config: destConfig,
52
64
  index
53
65
  });
54
-
66
+
55
67
  this.log(`Setup destination ${index}: ${destConfig.driver}`);
56
68
  } catch (error) {
57
- throw new Error(`Failed to setup destination ${index} (${destConfig.driver}): ${error.message}`);
69
+ throw new BackupError(`Failed to setup destination ${index}`, {
70
+ operation: 'onSetup',
71
+ driver: 'multi',
72
+ destinationIndex: index,
73
+ destinationDriver: destConfig.driver,
74
+ destinationConfig: destConfig.config,
75
+ original: error,
76
+ suggestion: 'Check destination driver configuration and ensure dependencies are available'
77
+ });
58
78
  }
59
79
  }
60
80
 
@@ -92,7 +112,15 @@ export default class MultiBackupDriver extends BaseBackupDriver {
92
112
  }
93
113
  }
94
114
 
95
- throw new Error(`All priority destinations failed: ${errors.map(e => `${e.destination}: ${e.error}`).join('; ')}`);
115
+ throw new BackupError('All priority destinations failed', {
116
+ operation: 'upload',
117
+ driver: 'multi',
118
+ strategy: 'priority',
119
+ backupId,
120
+ totalDestinations: this.drivers.length,
121
+ failures: errors,
122
+ suggestion: 'Check destination configurations and ensure at least one destination is accessible'
123
+ });
96
124
  }
97
125
 
98
126
  // For 'all' and 'any' strategies, upload to all destinations
@@ -128,11 +156,29 @@ export default class MultiBackupDriver extends BaseBackupDriver {
128
156
  const failedResults = allResults.filter(r => r.status === 'failed');
129
157
 
130
158
  if (strategy === 'all' && failedResults.length > 0) {
131
- throw new Error(`Some destinations failed: ${failedResults.map(r => `${r.destination}: ${r.error}`).join('; ')}`);
159
+ throw new BackupError('Some destinations failed with strategy "all"', {
160
+ operation: 'upload',
161
+ driver: 'multi',
162
+ strategy: 'all',
163
+ backupId,
164
+ totalDestinations: this.drivers.length,
165
+ successCount: successResults.length,
166
+ failedCount: failedResults.length,
167
+ failures: failedResults,
168
+ suggestion: 'All destinations must succeed with "all" strategy. Use "any" strategy to tolerate failures, or fix failing destinations.'
169
+ });
132
170
  }
133
171
 
134
172
  if (strategy === 'any' && successResults.length === 0) {
135
- throw new Error(`All destinations failed: ${failedResults.map(r => `${r.destination}: ${r.error}`).join('; ')}`);
173
+ throw new BackupError('All destinations failed with strategy "any"', {
174
+ operation: 'upload',
175
+ driver: 'multi',
176
+ strategy: 'any',
177
+ backupId,
178
+ totalDestinations: this.drivers.length,
179
+ failures: failedResults,
180
+ suggestion: 'At least one destination must succeed with "any" strategy. Check all destination configurations.'
181
+ });
136
182
  }
137
183
 
138
184
  return allResults;
@@ -160,7 +206,14 @@ export default class MultiBackupDriver extends BaseBackupDriver {
160
206
  }
161
207
  }
162
208
 
163
- throw new Error(`Failed to download backup from any destination`);
209
+ throw new BackupError('Failed to download backup from any destination', {
210
+ operation: 'download',
211
+ driver: 'multi',
212
+ backupId,
213
+ targetPath,
214
+ attemptedDestinations: destinations.length,
215
+ suggestion: 'Check if backup exists in at least one destination and destinations are accessible'
216
+ });
164
217
  }
165
218
 
166
219
  async delete(backupId, metadata) {
@@ -188,7 +241,14 @@ export default class MultiBackupDriver extends BaseBackupDriver {
188
241
  }
189
242
 
190
243
  if (successCount === 0 && errors.length > 0) {
191
- throw new Error(`Failed to delete from any destination: ${errors.join('; ')}`);
244
+ throw new BackupError('Failed to delete from any destination', {
245
+ operation: 'delete',
246
+ driver: 'multi',
247
+ backupId,
248
+ attemptedDestinations: destinations.length,
249
+ failures: errors,
250
+ suggestion: 'Check if backup exists in destinations and destinations are accessible with delete permissions'
251
+ });
192
252
  }
193
253
 
194
254
  if (errors.length > 0) {
@@ -4,6 +4,7 @@ import { stat } from 'fs/promises';
4
4
  import path from 'path';
5
5
  import crypto from 'crypto';
6
6
  import tryFn from '../../concerns/try-fn.js';
7
+ import { BackupError } from '../backup.errors.js';
7
8
 
8
9
  /**
9
10
  * S3BackupDriver - Stores backups in S3-compatible storage
@@ -43,11 +44,19 @@ export default class S3BackupDriver extends BaseBackupDriver {
43
44
  }
44
45
 
45
46
  if (!this.config.client) {
46
- throw new Error('S3BackupDriver: client is required (either via config or database)');
47
+ throw new BackupError('S3BackupDriver: client is required', {
48
+ operation: 'onSetup',
49
+ driver: 's3',
50
+ suggestion: 'Provide a client in config or ensure database has a client configured'
51
+ });
47
52
  }
48
53
 
49
54
  if (!this.config.bucket) {
50
- throw new Error('S3BackupDriver: bucket is required (either via config or database)');
55
+ throw new BackupError('S3BackupDriver: bucket is required', {
56
+ operation: 'onSetup',
57
+ driver: 's3',
58
+ suggestion: 'Provide a bucket in config or ensure database has a bucket configured'
59
+ });
51
60
  }
52
61
 
53
62
  this.log(`Initialized with bucket: ${this.config.bucket}, path: ${this.config.path}`);
@@ -108,7 +117,15 @@ export default class S3BackupDriver extends BaseBackupDriver {
108
117
  });
109
118
 
110
119
  if (!uploadOk) {
111
- throw new Error(`Failed to upload backup file: ${uploadErr.message}`);
120
+ throw new BackupError('Failed to upload backup file to S3', {
121
+ operation: 'upload',
122
+ driver: 's3',
123
+ backupId,
124
+ bucket: this.config.bucket,
125
+ key: backupKey,
126
+ original: uploadErr,
127
+ suggestion: 'Check S3 permissions and bucket configuration'
128
+ });
112
129
  }
113
130
 
114
131
  // Upload manifest
@@ -133,7 +150,15 @@ export default class S3BackupDriver extends BaseBackupDriver {
133
150
  bucket: this.config.bucket,
134
151
  key: backupKey
135
152
  }));
136
- throw new Error(`Failed to upload manifest: ${manifestErr.message}`);
153
+ throw new BackupError('Failed to upload manifest to S3', {
154
+ operation: 'upload',
155
+ driver: 's3',
156
+ backupId,
157
+ bucket: this.config.bucket,
158
+ manifestKey,
159
+ original: manifestErr,
160
+ suggestion: 'Check S3 permissions and bucket configuration'
161
+ });
137
162
  }
138
163
 
139
164
  this.log(`Uploaded backup ${backupId} to s3://${this.config.bucket}/${backupKey} (${fileSize} bytes)`);
@@ -161,7 +186,16 @@ export default class S3BackupDriver extends BaseBackupDriver {
161
186
  );
162
187
 
163
188
  if (!downloadOk) {
164
- throw new Error(`Failed to download backup: ${downloadErr.message}`);
189
+ throw new BackupError('Failed to download backup from S3', {
190
+ operation: 'download',
191
+ driver: 's3',
192
+ backupId,
193
+ bucket: this.config.bucket,
194
+ key: backupKey,
195
+ targetPath,
196
+ original: downloadErr,
197
+ suggestion: 'Check if backup exists and S3 permissions are correct'
198
+ });
165
199
  }
166
200
 
167
201
  this.log(`Downloaded backup ${backupId} from s3://${this.config.bucket}/${backupKey} to ${targetPath}`);
@@ -189,7 +223,15 @@ export default class S3BackupDriver extends BaseBackupDriver {
189
223
  );
190
224
 
191
225
  if (!deleteBackupOk && !deleteManifestOk) {
192
- throw new Error(`Failed to delete backup objects for ${backupId}`);
226
+ throw new BackupError('Failed to delete backup from S3', {
227
+ operation: 'delete',
228
+ driver: 's3',
229
+ backupId,
230
+ bucket: this.config.bucket,
231
+ backupKey,
232
+ manifestKey,
233
+ suggestion: 'Check S3 delete permissions'
234
+ });
193
235
  }
194
236
 
195
237
  this.log(`Deleted backup ${backupId} from S3`);
@@ -0,0 +1,45 @@
1
+ import { S3dbError } from '../errors.js';
2
+
3
+ /**
4
+ * BackupError - Errors related to backup operations
5
+ *
6
+ * Used for backup driver operations including:
7
+ * - Driver initialization and setup
8
+ * - Backup upload/download/delete operations
9
+ * - Driver configuration validation
10
+ * - Multi-destination backup strategies
11
+ *
12
+ * @extends S3dbError
13
+ */
14
+ export class BackupError extends S3dbError {
15
+ constructor(message, details = {}) {
16
+ const { driver = 'unknown', operation = 'unknown', backupId, ...rest } = details;
17
+
18
+ let description = details.description;
19
+ if (!description) {
20
+ description = `
21
+ Backup Operation Error
22
+
23
+ Driver: ${driver}
24
+ Operation: ${operation}
25
+ ${backupId ? `Backup ID: ${backupId}` : ''}
26
+
27
+ Common causes:
28
+ 1. Invalid backup driver configuration
29
+ 2. Destination storage not accessible
30
+ 3. Insufficient permissions
31
+ 4. Network connectivity issues
32
+ 5. Invalid backup file format
33
+
34
+ Solution:
35
+ Check driver configuration and ensure destination storage is accessible.
36
+
37
+ Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/backup.md
38
+ `.trim();
39
+ }
40
+
41
+ super(message, { ...rest, driver, operation, backupId, description });
42
+ }
43
+ }
44
+
45
+ export default BackupError;
@@ -1,4 +1,5 @@
1
1
  import EventEmitter from "events";
2
+ import { CacheError } from "../cache.errors.js";
2
3
 
3
4
  export class Cache extends EventEmitter {
4
5
  constructor(config = {}) {
@@ -13,7 +14,13 @@ export class Cache extends EventEmitter {
13
14
 
14
15
  validateKey(key) {
15
16
  if (key === null || key === undefined || typeof key !== 'string' || !key) {
16
- throw new Error('Invalid key');
17
+ throw new CacheError('Invalid cache key', {
18
+ operation: 'validateKey',
19
+ driver: this.constructor.name,
20
+ key,
21
+ keyType: typeof key,
22
+ suggestion: 'Cache key must be a non-empty string'
23
+ });
17
24
  }
18
25
  }
19
26