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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { S3dbError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CacheError - Errors related to cache operations
|
|
5
|
+
*
|
|
6
|
+
* Used for cache operations including:
|
|
7
|
+
* - Cache driver initialization and setup
|
|
8
|
+
* - Cache get/set/delete operations
|
|
9
|
+
* - Cache invalidation and warming
|
|
10
|
+
* - Driver-specific operations (memory, filesystem, S3)
|
|
11
|
+
* - Resource-level caching
|
|
12
|
+
*
|
|
13
|
+
* @extends S3dbError
|
|
14
|
+
*/
|
|
15
|
+
export class CacheError extends S3dbError {
|
|
16
|
+
constructor(message, details = {}) {
|
|
17
|
+
const { driver = 'unknown', operation = 'unknown', resourceName, key, ...rest } = details;
|
|
18
|
+
|
|
19
|
+
let description = details.description;
|
|
20
|
+
if (!description) {
|
|
21
|
+
description = `
|
|
22
|
+
Cache Operation Error
|
|
23
|
+
|
|
24
|
+
Driver: ${driver}
|
|
25
|
+
Operation: ${operation}
|
|
26
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
27
|
+
${key ? `Key: ${key}` : ''}
|
|
28
|
+
|
|
29
|
+
Common causes:
|
|
30
|
+
1. Invalid cache key format
|
|
31
|
+
2. Cache driver not properly initialized
|
|
32
|
+
3. Resource not found or not cached
|
|
33
|
+
4. Memory limits exceeded
|
|
34
|
+
5. Filesystem permissions issues
|
|
35
|
+
|
|
36
|
+
Solution:
|
|
37
|
+
Check cache configuration and ensure the cache driver is properly initialized.
|
|
38
|
+
|
|
39
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/cache.md
|
|
40
|
+
`.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
super(message, { ...rest, driver, operation, resourceName, key, description });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default CacheError;
|
|
@@ -8,6 +8,7 @@ import MemoryCache from "./cache/memory-cache.class.js";
|
|
|
8
8
|
import { FilesystemCache } from "./cache/filesystem-cache.class.js";
|
|
9
9
|
import { PartitionAwareFilesystemCache } from "./cache/partition-aware-filesystem-cache.class.js";
|
|
10
10
|
import tryFn from "../concerns/try-fn.js";
|
|
11
|
+
import { CacheError } from "./cache.errors.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Cache Plugin Configuration
|
|
@@ -555,7 +556,13 @@ export class CachePlugin extends Plugin {
|
|
|
555
556
|
async warmCache(resourceName, options = {}) {
|
|
556
557
|
const resource = this.database.resources[resourceName];
|
|
557
558
|
if (!resource) {
|
|
558
|
-
throw new
|
|
559
|
+
throw new CacheError('Resource not found for cache warming', {
|
|
560
|
+
operation: 'warmCache',
|
|
561
|
+
driver: this.driver?.constructor.name,
|
|
562
|
+
resourceName,
|
|
563
|
+
availableResources: Object.keys(this.database.resources),
|
|
564
|
+
suggestion: 'Check resource name spelling or ensure resource has been created'
|
|
565
|
+
});
|
|
559
566
|
}
|
|
560
567
|
|
|
561
568
|
const { includePartitions = true, sampleSize = 100 } = options;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { S3dbError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FulltextError - Errors related to fulltext search operations
|
|
5
|
+
*
|
|
6
|
+
* Used for fulltext search operations including:
|
|
7
|
+
* - Index creation and updates
|
|
8
|
+
* - Search query execution
|
|
9
|
+
* - Index configuration
|
|
10
|
+
* - Text analysis and tokenization
|
|
11
|
+
* - Search result ranking
|
|
12
|
+
*
|
|
13
|
+
* @extends S3dbError
|
|
14
|
+
*/
|
|
15
|
+
export class FulltextError extends S3dbError {
|
|
16
|
+
constructor(message, details = {}) {
|
|
17
|
+
const { resourceName, query, operation = 'unknown', ...rest } = details;
|
|
18
|
+
|
|
19
|
+
let description = details.description;
|
|
20
|
+
if (!description) {
|
|
21
|
+
description = `
|
|
22
|
+
Fulltext Search Operation Error
|
|
23
|
+
|
|
24
|
+
Operation: ${operation}
|
|
25
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
26
|
+
${query ? `Query: ${query}` : ''}
|
|
27
|
+
|
|
28
|
+
Common causes:
|
|
29
|
+
1. Resource not indexed for fulltext search
|
|
30
|
+
2. Invalid query syntax
|
|
31
|
+
3. Index not built yet
|
|
32
|
+
4. Search configuration missing
|
|
33
|
+
5. Field not indexed
|
|
34
|
+
|
|
35
|
+
Solution:
|
|
36
|
+
Ensure resource is configured for fulltext search and index is built.
|
|
37
|
+
|
|
38
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/fulltext.md
|
|
39
|
+
`.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
super(message, { ...rest, resourceName, query, operation, description });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default FulltextError;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Plugin from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
|
+
import { FulltextError } from "./fulltext.errors.js";
|
|
3
4
|
|
|
4
5
|
export class FullTextPlugin extends Plugin {
|
|
5
6
|
constructor(options = {}) {
|
|
@@ -399,14 +400,20 @@ export class FullTextPlugin extends Plugin {
|
|
|
399
400
|
// Search and return full records
|
|
400
401
|
async searchRecords(resourceName, query, options = {}) {
|
|
401
402
|
const searchResults = await this.search(resourceName, query, options);
|
|
402
|
-
|
|
403
|
+
|
|
403
404
|
if (searchResults.length === 0) {
|
|
404
405
|
return [];
|
|
405
406
|
}
|
|
406
407
|
|
|
407
408
|
const resource = this.database.resources[resourceName];
|
|
408
409
|
if (!resource) {
|
|
409
|
-
throw new
|
|
410
|
+
throw new FulltextError(`Resource '${resourceName}' not found`, {
|
|
411
|
+
operation: 'searchRecords',
|
|
412
|
+
resourceName,
|
|
413
|
+
query,
|
|
414
|
+
availableResources: Object.keys(this.database.resources),
|
|
415
|
+
suggestion: 'Check resource name or ensure resource is created before searching'
|
|
416
|
+
});
|
|
410
417
|
}
|
|
411
418
|
|
|
412
419
|
const recordIds = searchResults.map(result => result.recordId);
|
|
@@ -430,7 +437,12 @@ export class FullTextPlugin extends Plugin {
|
|
|
430
437
|
async rebuildIndex(resourceName) {
|
|
431
438
|
const resource = this.database.resources[resourceName];
|
|
432
439
|
if (!resource) {
|
|
433
|
-
throw new
|
|
440
|
+
throw new FulltextError(`Resource '${resourceName}' not found`, {
|
|
441
|
+
operation: 'rebuildIndex',
|
|
442
|
+
resourceName,
|
|
443
|
+
availableResources: Object.keys(this.database.resources),
|
|
444
|
+
suggestion: 'Check resource name or ensure resource is created before rebuilding index'
|
|
445
|
+
});
|
|
434
446
|
}
|
|
435
447
|
|
|
436
448
|
// Clear existing indexes for this resource
|