s3db.js 11.3.2 → 12.0.0
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/README.md +102 -8
- package/dist/s3db.cjs.js +36664 -15480
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +57 -0
- package/dist/s3db.es.js +36661 -15531
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +27 -6
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +41 -46
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +39 -19
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +539 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +350 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +14 -10
- package/src/s3db.d.ts +57 -0
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
package/SECURITY.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Supported Versions
|
|
4
|
-
|
|
5
|
-
| Version | Supported |
|
|
6
|
-
| ------- | ------------------ |
|
|
7
|
-
| 11.x.x | :white_check_mark: |
|
|
8
|
-
| < 11.0 | :x: |
|
|
9
|
-
|
|
10
|
-
## Known Security Advisories
|
|
11
|
-
|
|
12
|
-
### Development Dependencies
|
|
13
|
-
|
|
14
|
-
The following vulnerabilities exist in **development-only** dependencies and **do not affect** the published npm package or runtime security:
|
|
15
|
-
|
|
16
|
-
#### pkg (GHSA-22r3-9w55-cj54) - MODERATE
|
|
17
|
-
- **Status**: Acknowledged, monitored
|
|
18
|
-
- **Impact**: Local privilege escalation
|
|
19
|
-
- **Scope**: Only affects developers running `pnpm run build:binaries`
|
|
20
|
-
- **Mitigation**: pkg is deprecated and archived. No patched version available (`<0.0.0`).
|
|
21
|
-
- **Risk Assessment**: LOW - Only used for creating standalone binaries during release process
|
|
22
|
-
- **Future Plans**: Migrate to Node.js Single Executable Applications (SEA) when stable
|
|
23
|
-
|
|
24
|
-
#### tar-fs - HIGH
|
|
25
|
-
- **Status**: RESOLVED in v11.1.1+
|
|
26
|
-
- **Fix**: Updated to patched version 2.1.4+
|
|
27
|
-
|
|
28
|
-
## Reporting a Vulnerability
|
|
29
|
-
|
|
30
|
-
If you discover a security vulnerability in the **runtime code** (not dev dependencies), please report it by:
|
|
31
|
-
|
|
32
|
-
1. **DO NOT** open a public issue
|
|
33
|
-
2. Email: [security contact - update this]
|
|
34
|
-
3. Include:
|
|
35
|
-
- Description of the vulnerability
|
|
36
|
-
- Steps to reproduce
|
|
37
|
-
- Potential impact
|
|
38
|
-
- Suggested fix (if any)
|
|
39
|
-
|
|
40
|
-
### Response Timeline
|
|
41
|
-
|
|
42
|
-
- **Initial Response**: Within 48 hours
|
|
43
|
-
- **Status Update**: Within 7 days
|
|
44
|
-
- **Fix Timeline**: Depends on severity
|
|
45
|
-
- Critical: 7 days
|
|
46
|
-
- High: 14 days
|
|
47
|
-
- Medium: 30 days
|
|
48
|
-
- Low: 60 days
|
|
49
|
-
|
|
50
|
-
## Security Best Practices
|
|
51
|
-
|
|
52
|
-
### For Users
|
|
53
|
-
|
|
54
|
-
1. **Always encrypt sensitive data**: Use `secret` field type for passwords, tokens, etc.
|
|
55
|
-
2. **Validate credentials**: Never commit AWS credentials to version control
|
|
56
|
-
3. **Use IAM policies**: Implement least-privilege access for S3 buckets
|
|
57
|
-
4. **Enable paranoid mode**: For production, use `paranoid: true` for soft deletes
|
|
58
|
-
5. **Audit hooks**: Review serialized functions before deploying to production
|
|
59
|
-
|
|
60
|
-
### For Contributors
|
|
61
|
-
|
|
62
|
-
1. **No secrets in tests**: Use environment variables or LocalStack
|
|
63
|
-
2. **Validate input**: All user input should be validated before S3 operations
|
|
64
|
-
3. **Handle errors safely**: Never expose AWS error details to end users
|
|
65
|
-
4. **Review dependencies**: Run `pnpm audit` before submitting PRs
|
|
66
|
-
5. **Test encryption**: Verify `secret` fields are actually encrypted in S3
|
|
67
|
-
|
|
68
|
-
## Audit Configuration
|
|
69
|
-
|
|
70
|
-
This project uses `audit-level=high` in `.npmrc` to focus on critical vulnerabilities affecting production. Moderate/low severity issues in dev-only dependencies are monitored but may not block releases if:
|
|
71
|
-
|
|
72
|
-
- They only affect development tools
|
|
73
|
-
- No patch is available
|
|
74
|
-
- The risk is assessed as acceptable
|
|
75
|
-
|
|
76
|
-
Current audit threshold: **HIGH** (ignores moderate/low in dev dependencies)
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
import { PartitionDriverError } from '../errors.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Base class for all partition drivers
|
|
6
|
-
* Defines the interface that all drivers must implement
|
|
7
|
-
*/
|
|
8
|
-
export class BasePartitionDriver extends EventEmitter {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
super();
|
|
11
|
-
this.options = options;
|
|
12
|
-
this.stats = {
|
|
13
|
-
queued: 0,
|
|
14
|
-
processed: 0,
|
|
15
|
-
failed: 0,
|
|
16
|
-
processing: 0
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Initialize the driver
|
|
22
|
-
*/
|
|
23
|
-
async initialize() {
|
|
24
|
-
// Override in subclasses if needed
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Queue partition operations for processing
|
|
29
|
-
* @param {Object} operation - The partition operation to queue
|
|
30
|
-
* @param {string} operation.type - 'create', 'update', or 'delete'
|
|
31
|
-
* @param {Object} operation.resource - The resource instance
|
|
32
|
-
* @param {Object} operation.data - The data for the operation
|
|
33
|
-
*/
|
|
34
|
-
async queue(operation) {
|
|
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
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Process a single partition operation
|
|
44
|
-
*/
|
|
45
|
-
async processOperation(operation) {
|
|
46
|
-
const { type, resource, data } = operation;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
this.stats.processing++;
|
|
50
|
-
|
|
51
|
-
switch (type) {
|
|
52
|
-
case 'create':
|
|
53
|
-
await resource.createPartitionReferences(data.object);
|
|
54
|
-
break;
|
|
55
|
-
|
|
56
|
-
case 'update':
|
|
57
|
-
await resource.handlePartitionReferenceUpdates(data.original, data.updated);
|
|
58
|
-
break;
|
|
59
|
-
|
|
60
|
-
case 'delete':
|
|
61
|
-
await resource.deletePartitionReferences(data.object);
|
|
62
|
-
break;
|
|
63
|
-
|
|
64
|
-
default:
|
|
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
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.stats.processed++;
|
|
74
|
-
this.emit('processed', operation);
|
|
75
|
-
|
|
76
|
-
} catch (error) {
|
|
77
|
-
this.stats.failed++;
|
|
78
|
-
this.emit('error', { operation, error });
|
|
79
|
-
throw error;
|
|
80
|
-
} finally {
|
|
81
|
-
this.stats.processing--;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Flush any pending operations
|
|
87
|
-
*/
|
|
88
|
-
async flush() {
|
|
89
|
-
// Override in subclasses if needed
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get driver statistics
|
|
94
|
-
*/
|
|
95
|
-
getStats() {
|
|
96
|
-
return { ...this.stats };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Shutdown the driver
|
|
101
|
-
*/
|
|
102
|
-
async shutdown() {
|
|
103
|
-
await this.flush();
|
|
104
|
-
this.removeAllListeners();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { SyncPartitionDriver } from './sync-partition-driver.js';
|
|
2
|
-
import { MemoryPartitionDriver } from './memory-partition-driver.js';
|
|
3
|
-
import { SQSPartitionDriver } from './sqs-partition-driver.js';
|
|
4
|
-
import { PartitionDriverError } from '../errors.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Partition driver factory
|
|
8
|
-
*/
|
|
9
|
-
export class PartitionDriverFactory {
|
|
10
|
-
static drivers = {
|
|
11
|
-
sync: SyncPartitionDriver,
|
|
12
|
-
memory: MemoryPartitionDriver,
|
|
13
|
-
sqs: SQSPartitionDriver
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create a partition driver instance
|
|
18
|
-
* @param {string|Object} config - Driver name or configuration object
|
|
19
|
-
* @returns {BasePartitionDriver} Driver instance
|
|
20
|
-
*/
|
|
21
|
-
static create(config) {
|
|
22
|
-
// Handle string shorthand
|
|
23
|
-
if (typeof config === 'string') {
|
|
24
|
-
config = { driver: config };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Default to memory driver
|
|
28
|
-
const driverName = config.driver || 'memory';
|
|
29
|
-
|
|
30
|
-
// Get driver class
|
|
31
|
-
const DriverClass = this.drivers[driverName];
|
|
32
|
-
if (!DriverClass) {
|
|
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
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Create and initialize driver
|
|
42
|
-
const driver = new DriverClass(config);
|
|
43
|
-
|
|
44
|
-
return driver;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Register a custom driver
|
|
49
|
-
*/
|
|
50
|
-
static register(name, DriverClass) {
|
|
51
|
-
this.drivers[name] = DriverClass;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get available driver names
|
|
56
|
-
*/
|
|
57
|
-
static getAvailableDrivers() {
|
|
58
|
-
return Object.keys(this.drivers);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Export individual drivers
|
|
63
|
-
export { BasePartitionDriver } from './base-partition-driver.js';
|
|
64
|
-
export { SyncPartitionDriver } from './sync-partition-driver.js';
|
|
65
|
-
export { MemoryPartitionDriver } from './memory-partition-driver.js';
|
|
66
|
-
export { SQSPartitionDriver } from './sqs-partition-driver.js';
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
|
-
import { PromisePool } from '@supercharge/promise-pool';
|
|
3
|
-
import { PartitionDriverError } from '../errors.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* In-memory partition driver with background processing
|
|
7
|
-
* Queues operations in memory and processes them asynchronously
|
|
8
|
-
* Fast and efficient for single-instance applications
|
|
9
|
-
*/
|
|
10
|
-
export class MemoryPartitionDriver extends BasePartitionDriver {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
super(options);
|
|
13
|
-
this.name = 'memory';
|
|
14
|
-
|
|
15
|
-
// Configuration
|
|
16
|
-
this.batchSize = options.batchSize || 100;
|
|
17
|
-
this.concurrency = options.concurrency || 10;
|
|
18
|
-
this.flushInterval = options.flushInterval || 1000;
|
|
19
|
-
this.maxQueueSize = options.maxQueueSize || 10000;
|
|
20
|
-
this.maxRetries = options.maxRetries || 3;
|
|
21
|
-
|
|
22
|
-
// State
|
|
23
|
-
this.queue = [];
|
|
24
|
-
this.isProcessing = false;
|
|
25
|
-
this.flushTimer = null;
|
|
26
|
-
this.retryQueue = [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async initialize() {
|
|
30
|
-
// Start background processor
|
|
31
|
-
this.startProcessor();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Add operation to in-memory queue
|
|
36
|
-
*/
|
|
37
|
-
async queue(operation) {
|
|
38
|
-
// Check queue size limit
|
|
39
|
-
if (this.queue.length >= this.maxQueueSize) {
|
|
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
|
-
});
|
|
47
|
-
this.emit('queueFull', { operation, queueSize: this.queue.length });
|
|
48
|
-
|
|
49
|
-
if (this.options.rejectOnFull) {
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Wait for some space
|
|
54
|
-
await this.waitForSpace();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Add to queue with metadata
|
|
58
|
-
const queueItem = {
|
|
59
|
-
...operation,
|
|
60
|
-
id: `${Date.now()}-${Math.random()}`,
|
|
61
|
-
queuedAt: new Date(),
|
|
62
|
-
attempts: 0
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
this.queue.push(queueItem);
|
|
66
|
-
this.stats.queued++;
|
|
67
|
-
|
|
68
|
-
// Auto-flush when batch size reached
|
|
69
|
-
if (this.queue.length >= this.batchSize) {
|
|
70
|
-
this.triggerFlush();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
success: true,
|
|
75
|
-
driver: 'memory',
|
|
76
|
-
queuePosition: this.queue.length,
|
|
77
|
-
queueId: queueItem.id
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Start the background processor
|
|
83
|
-
*/
|
|
84
|
-
startProcessor() {
|
|
85
|
-
// Set up periodic flush
|
|
86
|
-
if (this.flushInterval > 0) {
|
|
87
|
-
this.flushTimer = setInterval(() => {
|
|
88
|
-
if (this.queue.length > 0 && !this.isProcessing) {
|
|
89
|
-
this.processQueue();
|
|
90
|
-
}
|
|
91
|
-
}, this.flushInterval);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Trigger immediate flush
|
|
97
|
-
*/
|
|
98
|
-
triggerFlush() {
|
|
99
|
-
if (!this.isProcessing) {
|
|
100
|
-
setImmediate(() => this.processQueue());
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Process queued operations in batches
|
|
106
|
-
*/
|
|
107
|
-
async processQueue() {
|
|
108
|
-
if (this.isProcessing || this.queue.length === 0) return;
|
|
109
|
-
|
|
110
|
-
this.isProcessing = true;
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
// Take a batch from the queue
|
|
114
|
-
const batch = this.queue.splice(0, this.batchSize);
|
|
115
|
-
|
|
116
|
-
// Process in parallel with concurrency control
|
|
117
|
-
const { results, errors } = await PromisePool
|
|
118
|
-
.for(batch)
|
|
119
|
-
.withConcurrency(this.concurrency)
|
|
120
|
-
.process(async (item) => {
|
|
121
|
-
try {
|
|
122
|
-
await this.processOperation(item);
|
|
123
|
-
return { success: true, item };
|
|
124
|
-
} catch (error) {
|
|
125
|
-
return this.handleError(item, error);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Handle successful results
|
|
130
|
-
const successful = results.filter(r => r.success);
|
|
131
|
-
this.emit('batchProcessed', {
|
|
132
|
-
processed: successful.length,
|
|
133
|
-
failed: errors.length,
|
|
134
|
-
retried: results.filter(r => r.retried).length
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
} finally {
|
|
138
|
-
this.isProcessing = false;
|
|
139
|
-
|
|
140
|
-
// Continue processing if more items
|
|
141
|
-
if (this.queue.length > 0) {
|
|
142
|
-
setImmediate(() => this.processQueue());
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Process retry queue if needed
|
|
146
|
-
if (this.retryQueue.length > 0) {
|
|
147
|
-
this.processRetryQueue();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Handle processing errors with retry logic
|
|
154
|
-
*/
|
|
155
|
-
handleError(item, error) {
|
|
156
|
-
item.attempts++;
|
|
157
|
-
item.lastError = error;
|
|
158
|
-
|
|
159
|
-
if (item.attempts < this.maxRetries) {
|
|
160
|
-
// Add to retry queue with exponential backoff
|
|
161
|
-
const delay = Math.min(1000 * Math.pow(2, item.attempts - 1), 30000);
|
|
162
|
-
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
this.retryQueue.push(item);
|
|
165
|
-
if (!this.isProcessing) {
|
|
166
|
-
this.processRetryQueue();
|
|
167
|
-
}
|
|
168
|
-
}, delay);
|
|
169
|
-
|
|
170
|
-
this.emit('retry', { item, error, attempt: item.attempts, delay });
|
|
171
|
-
return { success: false, retried: true, item };
|
|
172
|
-
} else {
|
|
173
|
-
// Max retries exceeded
|
|
174
|
-
this.emit('failed', { item, error, attempts: item.attempts });
|
|
175
|
-
return { success: false, retried: false, item };
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Process retry queue
|
|
181
|
-
*/
|
|
182
|
-
async processRetryQueue() {
|
|
183
|
-
if (this.retryQueue.length === 0) return;
|
|
184
|
-
|
|
185
|
-
// Move retry items back to main queue
|
|
186
|
-
const retryItems = this.retryQueue.splice(0, this.batchSize);
|
|
187
|
-
this.queue.unshift(...retryItems);
|
|
188
|
-
|
|
189
|
-
// Trigger processing
|
|
190
|
-
this.triggerFlush();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Wait for queue space
|
|
195
|
-
*/
|
|
196
|
-
async waitForSpace() {
|
|
197
|
-
const checkInterval = 100;
|
|
198
|
-
const maxWait = 30000;
|
|
199
|
-
const startTime = Date.now();
|
|
200
|
-
|
|
201
|
-
while (this.queue.length >= this.maxQueueSize) {
|
|
202
|
-
if (Date.now() - startTime > maxWait) {
|
|
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
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Force flush all pending operations
|
|
220
|
-
*/
|
|
221
|
-
async flush() {
|
|
222
|
-
// Process all remaining items
|
|
223
|
-
while (this.queue.length > 0 || this.retryQueue.length > 0 || this.isProcessing) {
|
|
224
|
-
await this.processQueue();
|
|
225
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Get detailed statistics
|
|
231
|
-
*/
|
|
232
|
-
getStats() {
|
|
233
|
-
return {
|
|
234
|
-
...super.getStats(),
|
|
235
|
-
queueLength: this.queue.length,
|
|
236
|
-
retryQueueLength: this.retryQueue.length,
|
|
237
|
-
isProcessing: this.isProcessing,
|
|
238
|
-
memoryUsage: this.estimateMemoryUsage()
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Estimate memory usage of the queue
|
|
244
|
-
*/
|
|
245
|
-
estimateMemoryUsage() {
|
|
246
|
-
// Rough estimate: 1KB per queue item
|
|
247
|
-
const bytes = (this.queue.length + this.retryQueue.length) * 1024;
|
|
248
|
-
return {
|
|
249
|
-
bytes,
|
|
250
|
-
mb: (bytes / 1024 / 1024).toFixed(2)
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Shutdown the driver
|
|
256
|
-
*/
|
|
257
|
-
async shutdown() {
|
|
258
|
-
// Stop the flush timer
|
|
259
|
-
if (this.flushTimer) {
|
|
260
|
-
clearInterval(this.flushTimer);
|
|
261
|
-
this.flushTimer = null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Flush remaining items
|
|
265
|
-
await this.flush();
|
|
266
|
-
|
|
267
|
-
// Clear queues
|
|
268
|
-
this.queue = [];
|
|
269
|
-
this.retryQueue = [];
|
|
270
|
-
|
|
271
|
-
await super.shutdown();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
getInfo() {
|
|
275
|
-
return {
|
|
276
|
-
name: this.name,
|
|
277
|
-
mode: 'asynchronous',
|
|
278
|
-
description: 'In-memory queue with background processing',
|
|
279
|
-
config: {
|
|
280
|
-
batchSize: this.batchSize,
|
|
281
|
-
concurrency: this.concurrency,
|
|
282
|
-
flushInterval: this.flushInterval,
|
|
283
|
-
maxQueueSize: this.maxQueueSize,
|
|
284
|
-
maxRetries: this.maxRetries
|
|
285
|
-
},
|
|
286
|
-
stats: this.getStats()
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
}
|