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.
- package/dist/s3db.cjs.js +1650 -136
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1644 -137
- 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 +22 -4
- package/src/errors.js +414 -24
- 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/memory-cache.class.js +216 -33
- package/src/plugins/cache.errors.js +47 -0
- package/src/plugins/cache.plugin.js +94 -3
- package/src/plugins/eventual-consistency/analytics.js +145 -0
- package/src/plugins/eventual-consistency/index.js +203 -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/resource.class.js +8 -1
- package/src/stream/index.js +6 -1
- 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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
206
|
+
let description;
|
|
207
207
|
if (code === 'NoSuchKey' || code === 'NotFound') {
|
|
208
|
-
|
|
209
|
-
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput,
|
|
208
|
+
description = 'The specified key does not exist in the bucket. Check if the key exists and if your credentials have permission to access it.';
|
|
209
|
+
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
210
210
|
}
|
|
211
211
|
if (code === 'NoSuchBucket') {
|
|
212
|
-
|
|
213
|
-
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput,
|
|
212
|
+
description = 'The specified bucket does not exist. Check if the bucket name is correct and if your credentials have permission to access it.';
|
|
213
|
+
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
214
214
|
}
|
|
215
215
|
if (code === 'AccessDenied' || (err.statusCode === 403) || code === 'Forbidden') {
|
|
216
|
-
|
|
217
|
-
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput,
|
|
216
|
+
description = 'Access denied. Check your AWS credentials, IAM permissions, and bucket policy.';
|
|
217
|
+
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
218
218
|
}
|
|
219
219
|
if (code === 'ValidationError' || (err.statusCode === 400)) {
|
|
220
|
-
|
|
221
|
-
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput,
|
|
220
|
+
description = 'Validation error. Check the request parameters and payload format.';
|
|
221
|
+
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
222
222
|
}
|
|
223
223
|
if (code === 'MissingMetadata') {
|
|
224
|
-
|
|
225
|
-
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput,
|
|
224
|
+
description = 'Object metadata is missing or invalid. Check if the object was uploaded correctly.';
|
|
225
|
+
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
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
|
-
|
|
237
|
-
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput,
|
|
235
|
+
|
|
236
|
+
description = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
|
|
237
|
+
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput, description });
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export class ConnectionStringError extends S3dbError {
|
|
241
241
|
constructor(message, details = {}) {
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
35
|
+
throw new PartitionDriverError('queue() must be implemented by subclass', {
|
|
36
|
+
driver: this.name || 'BasePartitionDriver',
|
|
37
|
+
operation: 'queue',
|
|
38
|
+
suggestion: 'Extend BasePartitionDriver and implement the queue() method'
|
|
39
|
+
});
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
@@ -57,7 +62,12 @@ export class BasePartitionDriver extends EventEmitter {
|
|
|
57
62
|
break;
|
|
58
63
|
|
|
59
64
|
default:
|
|
60
|
-
throw new
|
|
65
|
+
throw new PartitionDriverError(`Unknown partition operation type: ${type}`, {
|
|
66
|
+
driver: this.name || 'BasePartitionDriver',
|
|
67
|
+
operation: type,
|
|
68
|
+
availableOperations: ['create', 'update', 'delete'],
|
|
69
|
+
suggestion: 'Use one of the supported partition operations: create, update, or delete'
|
|
70
|
+
});
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
this.stats.processed++;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SyncPartitionDriver } from './sync-partition-driver.js';
|
|
2
2
|
import { MemoryPartitionDriver } from './memory-partition-driver.js';
|
|
3
3
|
import { SQSPartitionDriver } from './sqs-partition-driver.js';
|
|
4
|
+
import { PartitionDriverError } from '../errors.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Partition driver factory
|
|
@@ -29,7 +30,12 @@ export class PartitionDriverFactory {
|
|
|
29
30
|
// Get driver class
|
|
30
31
|
const DriverClass = this.drivers[driverName];
|
|
31
32
|
if (!DriverClass) {
|
|
32
|
-
throw new
|
|
33
|
+
throw new PartitionDriverError(`Unknown partition driver: ${driverName}`, {
|
|
34
|
+
driver: driverName,
|
|
35
|
+
operation: 'create',
|
|
36
|
+
availableDrivers: Object.keys(this.drivers),
|
|
37
|
+
suggestion: `Use one of the available drivers: ${Object.keys(this.drivers).join(', ')}, or register a custom driver`
|
|
38
|
+
});
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
// Create and initialize driver
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
2
|
import { PromisePool } from '@supercharge/promise-pool';
|
|
3
|
+
import { PartitionDriverError } from '../errors.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* In-memory partition driver with background processing
|
|
@@ -36,13 +37,19 @@ export class MemoryPartitionDriver extends BasePartitionDriver {
|
|
|
36
37
|
async queue(operation) {
|
|
37
38
|
// Check queue size limit
|
|
38
39
|
if (this.queue.length >= this.maxQueueSize) {
|
|
39
|
-
const error = new
|
|
40
|
+
const error = new PartitionDriverError('Memory queue full - backpressure detected', {
|
|
41
|
+
driver: 'memory',
|
|
42
|
+
operation: 'queue',
|
|
43
|
+
queueSize: this.queue.length,
|
|
44
|
+
maxQueueSize: this.maxQueueSize,
|
|
45
|
+
suggestion: 'Increase maxQueueSize, enable rejectOnFull, or reduce operation rate'
|
|
46
|
+
});
|
|
40
47
|
this.emit('queueFull', { operation, queueSize: this.queue.length });
|
|
41
|
-
|
|
48
|
+
|
|
42
49
|
if (this.options.rejectOnFull) {
|
|
43
50
|
throw error;
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
// Wait for some space
|
|
47
54
|
await this.waitForSpace();
|
|
48
55
|
}
|
|
@@ -193,9 +200,17 @@ export class MemoryPartitionDriver extends BasePartitionDriver {
|
|
|
193
200
|
|
|
194
201
|
while (this.queue.length >= this.maxQueueSize) {
|
|
195
202
|
if (Date.now() - startTime > maxWait) {
|
|
196
|
-
throw new
|
|
203
|
+
throw new PartitionDriverError('Timeout waiting for queue space', {
|
|
204
|
+
driver: 'memory',
|
|
205
|
+
operation: 'waitForSpace',
|
|
206
|
+
queueSize: this.queue.length,
|
|
207
|
+
maxQueueSize: this.maxQueueSize,
|
|
208
|
+
waitedMs: Date.now() - startTime,
|
|
209
|
+
maxWaitMs: maxWait,
|
|
210
|
+
suggestion: 'Queue is full and not draining fast enough. Increase maxQueueSize or concurrency'
|
|
211
|
+
});
|
|
197
212
|
}
|
|
198
|
-
|
|
213
|
+
|
|
199
214
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
200
215
|
}
|
|
201
216
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
2
|
import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
|
|
3
|
+
import { PartitionDriverError } from '../errors.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SQS-based partition driver for distributed processing
|
|
@@ -14,7 +15,11 @@ export class SQSPartitionDriver extends BasePartitionDriver {
|
|
|
14
15
|
// SQS Configuration
|
|
15
16
|
this.queueUrl = options.queueUrl;
|
|
16
17
|
if (!this.queueUrl) {
|
|
17
|
-
throw new
|
|
18
|
+
throw new PartitionDriverError('SQS queue URL is required', {
|
|
19
|
+
driver: 'sqs',
|
|
20
|
+
operation: 'constructor',
|
|
21
|
+
suggestion: 'Provide queueUrl in options: new SQSPartitionDriver({ queueUrl: "https://sqs.region.amazonaws.com/account/queue" })'
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
this.region = options.region || 'us-east-1';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { S3dbError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AuditError - Errors related to audit logging operations
|
|
5
|
+
*
|
|
6
|
+
* Used for audit operations including:
|
|
7
|
+
* - Audit log creation
|
|
8
|
+
* - Change tracking
|
|
9
|
+
* - Audit query and retrieval
|
|
10
|
+
* - Compliance logging
|
|
11
|
+
* - Event recording
|
|
12
|
+
*
|
|
13
|
+
* @extends S3dbError
|
|
14
|
+
*/
|
|
15
|
+
export class AuditError extends S3dbError {
|
|
16
|
+
constructor(message, details = {}) {
|
|
17
|
+
const { resourceName, operation = 'unknown', auditId, ...rest } = details;
|
|
18
|
+
|
|
19
|
+
let description = details.description;
|
|
20
|
+
if (!description) {
|
|
21
|
+
description = `
|
|
22
|
+
Audit Operation Error
|
|
23
|
+
|
|
24
|
+
Operation: ${operation}
|
|
25
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
26
|
+
${auditId ? `Audit ID: ${auditId}` : ''}
|
|
27
|
+
|
|
28
|
+
Common causes:
|
|
29
|
+
1. Audit log storage not accessible
|
|
30
|
+
2. Resource not configured for auditing
|
|
31
|
+
3. Invalid audit log format
|
|
32
|
+
4. Audit query failed
|
|
33
|
+
5. Insufficient permissions
|
|
34
|
+
|
|
35
|
+
Solution:
|
|
36
|
+
Check audit plugin configuration and storage accessibility.
|
|
37
|
+
|
|
38
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/audit.md
|
|
39
|
+
`.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
super(message, { ...rest, resourceName, operation, auditId, description });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default AuditError;
|