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
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
|
-
import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
|
|
3
|
-
import { PartitionDriverError } from '../errors.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* SQS-based partition driver for distributed processing
|
|
7
|
-
* Sends partition operations to SQS for processing by workers
|
|
8
|
-
* Ideal for high-volume, distributed systems
|
|
9
|
-
*/
|
|
10
|
-
export class SQSPartitionDriver extends BasePartitionDriver {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
super(options);
|
|
13
|
-
this.name = 'sqs';
|
|
14
|
-
|
|
15
|
-
// SQS Configuration
|
|
16
|
-
this.queueUrl = options.queueUrl;
|
|
17
|
-
if (!this.queueUrl) {
|
|
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
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
this.region = options.region || 'us-east-1';
|
|
26
|
-
this.credentials = options.credentials;
|
|
27
|
-
this.dlqUrl = options.dlqUrl; // Dead Letter Queue
|
|
28
|
-
this.messageGroupId = options.messageGroupId || 's3db-partitions';
|
|
29
|
-
this.visibilityTimeout = options.visibilityTimeout || 300; // 5 minutes
|
|
30
|
-
this.batchSize = options.batchSize || 10; // SQS max batch size
|
|
31
|
-
|
|
32
|
-
// Worker configuration
|
|
33
|
-
this.isWorker = options.isWorker || false;
|
|
34
|
-
this.workerConcurrency = options.workerConcurrency || 5;
|
|
35
|
-
this.pollInterval = options.pollInterval || 1000;
|
|
36
|
-
|
|
37
|
-
// Initialize SQS client
|
|
38
|
-
this.sqsClient = new SQSClient({
|
|
39
|
-
region: this.region,
|
|
40
|
-
credentials: this.credentials
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.workerRunning = false;
|
|
44
|
-
this.messageBuffer = [];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async initialize() {
|
|
48
|
-
// Start worker if configured
|
|
49
|
-
if (this.isWorker) {
|
|
50
|
-
await this.startWorker();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Send partition operation to SQS
|
|
56
|
-
*/
|
|
57
|
-
async queue(operation) {
|
|
58
|
-
try {
|
|
59
|
-
// Prepare message
|
|
60
|
-
const message = {
|
|
61
|
-
id: `${Date.now()}-${Math.random()}`,
|
|
62
|
-
timestamp: new Date().toISOString(),
|
|
63
|
-
operation: {
|
|
64
|
-
type: operation.type,
|
|
65
|
-
resourceName: operation.resource.name,
|
|
66
|
-
data: this.serializeData(operation.data)
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Buffer messages for batch sending
|
|
71
|
-
this.messageBuffer.push(message);
|
|
72
|
-
this.stats.queued++;
|
|
73
|
-
|
|
74
|
-
// Send batch when buffer is full
|
|
75
|
-
if (this.messageBuffer.length >= this.batchSize) {
|
|
76
|
-
await this.flushMessages();
|
|
77
|
-
} else {
|
|
78
|
-
// Schedule flush if not already scheduled
|
|
79
|
-
if (!this.flushTimeout) {
|
|
80
|
-
this.flushTimeout = setTimeout(() => this.flushMessages(), 100);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
success: true,
|
|
86
|
-
driver: 'sqs',
|
|
87
|
-
messageId: message.id,
|
|
88
|
-
queueUrl: this.queueUrl
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
} catch (error) {
|
|
92
|
-
this.emit('error', { operation, error });
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Flush buffered messages to SQS
|
|
99
|
-
*/
|
|
100
|
-
async flushMessages() {
|
|
101
|
-
if (this.messageBuffer.length === 0) return;
|
|
102
|
-
|
|
103
|
-
clearTimeout(this.flushTimeout);
|
|
104
|
-
this.flushTimeout = null;
|
|
105
|
-
|
|
106
|
-
const messages = this.messageBuffer.splice(0, this.batchSize);
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
// For FIFO queues, add deduplication ID
|
|
110
|
-
const isFifo = this.queueUrl.includes('.fifo');
|
|
111
|
-
|
|
112
|
-
for (const message of messages) {
|
|
113
|
-
const params = {
|
|
114
|
-
QueueUrl: this.queueUrl,
|
|
115
|
-
MessageBody: JSON.stringify(message),
|
|
116
|
-
MessageAttributes: {
|
|
117
|
-
Type: {
|
|
118
|
-
DataType: 'String',
|
|
119
|
-
StringValue: message.operation.type
|
|
120
|
-
},
|
|
121
|
-
Resource: {
|
|
122
|
-
DataType: 'String',
|
|
123
|
-
StringValue: message.operation.resourceName
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
if (isFifo) {
|
|
129
|
-
params.MessageGroupId = this.messageGroupId;
|
|
130
|
-
params.MessageDeduplicationId = message.id;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
await this.sqsClient.send(new SendMessageCommand(params));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.emit('messagesSent', { count: messages.length });
|
|
137
|
-
|
|
138
|
-
} catch (error) {
|
|
139
|
-
// Return messages to buffer for retry
|
|
140
|
-
this.messageBuffer.unshift(...messages);
|
|
141
|
-
this.emit('sendError', { error, messages: messages.length });
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Start SQS worker to process messages
|
|
148
|
-
*/
|
|
149
|
-
async startWorker() {
|
|
150
|
-
if (this.workerRunning) return;
|
|
151
|
-
|
|
152
|
-
this.workerRunning = true;
|
|
153
|
-
this.emit('workerStarted', { concurrency: this.workerConcurrency });
|
|
154
|
-
|
|
155
|
-
// Start multiple concurrent workers
|
|
156
|
-
for (let i = 0; i < this.workerConcurrency; i++) {
|
|
157
|
-
this.pollMessages(i);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Poll SQS for messages
|
|
163
|
-
*/
|
|
164
|
-
async pollMessages(workerId) {
|
|
165
|
-
while (this.workerRunning) {
|
|
166
|
-
try {
|
|
167
|
-
// Receive messages from SQS
|
|
168
|
-
const params = {
|
|
169
|
-
QueueUrl: this.queueUrl,
|
|
170
|
-
MaxNumberOfMessages: 10,
|
|
171
|
-
WaitTimeSeconds: 20, // Long polling
|
|
172
|
-
VisibilityTimeout: this.visibilityTimeout,
|
|
173
|
-
MessageAttributeNames: ['All']
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const response = await this.sqsClient.send(new ReceiveMessageCommand(params));
|
|
177
|
-
|
|
178
|
-
if (response.Messages && response.Messages.length > 0) {
|
|
179
|
-
// Process messages
|
|
180
|
-
for (const message of response.Messages) {
|
|
181
|
-
await this.processMessage(message, workerId);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
} catch (error) {
|
|
186
|
-
this.emit('pollError', { workerId, error });
|
|
187
|
-
// Wait before retrying
|
|
188
|
-
await new Promise(resolve => setTimeout(resolve, this.pollInterval));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Process a single SQS message
|
|
195
|
-
*/
|
|
196
|
-
async processMessage(message, workerId) {
|
|
197
|
-
try {
|
|
198
|
-
// Parse message body
|
|
199
|
-
const data = JSON.parse(message.Body);
|
|
200
|
-
const operation = {
|
|
201
|
-
type: data.operation.type,
|
|
202
|
-
data: this.deserializeData(data.operation.data)
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// Process the partition operation
|
|
206
|
-
// Note: We need the actual resource instance to process
|
|
207
|
-
// This would typically be handled by a separate worker service
|
|
208
|
-
this.emit('processingMessage', { workerId, messageId: message.MessageId });
|
|
209
|
-
|
|
210
|
-
// In a real implementation, you'd look up the resource and process:
|
|
211
|
-
// await this.processOperation(operation);
|
|
212
|
-
|
|
213
|
-
// Delete message from queue after successful processing
|
|
214
|
-
await this.sqsClient.send(new DeleteMessageCommand({
|
|
215
|
-
QueueUrl: this.queueUrl,
|
|
216
|
-
ReceiptHandle: message.ReceiptHandle
|
|
217
|
-
}));
|
|
218
|
-
|
|
219
|
-
this.stats.processed++;
|
|
220
|
-
this.emit('messageProcessed', { workerId, messageId: message.MessageId });
|
|
221
|
-
|
|
222
|
-
} catch (error) {
|
|
223
|
-
this.stats.failed++;
|
|
224
|
-
this.emit('processError', { workerId, error, messageId: message.MessageId });
|
|
225
|
-
|
|
226
|
-
// Message will become visible again after VisibilityTimeout
|
|
227
|
-
// and eventually move to DLQ if configured
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Serialize data for SQS transport
|
|
233
|
-
*/
|
|
234
|
-
serializeData(data) {
|
|
235
|
-
// Remove circular references and functions
|
|
236
|
-
return JSON.parse(JSON.stringify(data, (key, value) => {
|
|
237
|
-
if (typeof value === 'function') return undefined;
|
|
238
|
-
if (value instanceof Buffer) return value.toString('base64');
|
|
239
|
-
return value;
|
|
240
|
-
}));
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Deserialize data from SQS
|
|
245
|
-
*/
|
|
246
|
-
deserializeData(data) {
|
|
247
|
-
return data;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Stop the worker
|
|
252
|
-
*/
|
|
253
|
-
async stopWorker() {
|
|
254
|
-
this.workerRunning = false;
|
|
255
|
-
this.emit('workerStopped');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Force flush all pending messages
|
|
260
|
-
*/
|
|
261
|
-
async flush() {
|
|
262
|
-
await this.flushMessages();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get queue metrics from SQS
|
|
267
|
-
*/
|
|
268
|
-
async getQueueMetrics() {
|
|
269
|
-
try {
|
|
270
|
-
const { Attributes } = await this.sqsClient.send(new GetQueueAttributesCommand({
|
|
271
|
-
QueueUrl: this.queueUrl,
|
|
272
|
-
AttributeNames: [
|
|
273
|
-
'ApproximateNumberOfMessages',
|
|
274
|
-
'ApproximateNumberOfMessagesNotVisible',
|
|
275
|
-
'ApproximateNumberOfMessagesDelayed'
|
|
276
|
-
]
|
|
277
|
-
}));
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
messagesAvailable: parseInt(Attributes.ApproximateNumberOfMessages || 0),
|
|
281
|
-
messagesInFlight: parseInt(Attributes.ApproximateNumberOfMessagesNotVisible || 0),
|
|
282
|
-
messagesDelayed: parseInt(Attributes.ApproximateNumberOfMessagesDelayed || 0)
|
|
283
|
-
};
|
|
284
|
-
} catch (error) {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get detailed statistics
|
|
291
|
-
*/
|
|
292
|
-
async getStats() {
|
|
293
|
-
const baseStats = super.getStats();
|
|
294
|
-
const queueMetrics = await this.getQueueMetrics();
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
...baseStats,
|
|
298
|
-
bufferLength: this.messageBuffer.length,
|
|
299
|
-
isWorker: this.isWorker,
|
|
300
|
-
workerRunning: this.workerRunning,
|
|
301
|
-
queue: queueMetrics
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Shutdown the driver
|
|
307
|
-
*/
|
|
308
|
-
async shutdown() {
|
|
309
|
-
// Stop worker if running
|
|
310
|
-
await this.stopWorker();
|
|
311
|
-
|
|
312
|
-
// Flush remaining messages
|
|
313
|
-
await this.flush();
|
|
314
|
-
|
|
315
|
-
// Clear buffer
|
|
316
|
-
this.messageBuffer = [];
|
|
317
|
-
|
|
318
|
-
await super.shutdown();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
getInfo() {
|
|
322
|
-
return {
|
|
323
|
-
name: this.name,
|
|
324
|
-
mode: 'distributed',
|
|
325
|
-
description: 'SQS-based queue for distributed partition processing',
|
|
326
|
-
config: {
|
|
327
|
-
queueUrl: this.queueUrl,
|
|
328
|
-
region: this.region,
|
|
329
|
-
dlqUrl: this.dlqUrl,
|
|
330
|
-
isWorker: this.isWorker,
|
|
331
|
-
workerConcurrency: this.workerConcurrency,
|
|
332
|
-
visibilityTimeout: this.visibilityTimeout
|
|
333
|
-
},
|
|
334
|
-
stats: this.getStats()
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { BasePartitionDriver } from './base-partition-driver.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Synchronous partition driver
|
|
5
|
-
* Creates partitions immediately during insert/update/delete
|
|
6
|
-
* Use this when data consistency is critical
|
|
7
|
-
*/
|
|
8
|
-
export class SyncPartitionDriver extends BasePartitionDriver {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
super(options);
|
|
11
|
-
this.name = 'sync';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Process partition operations synchronously
|
|
16
|
-
*/
|
|
17
|
-
async queue(operation) {
|
|
18
|
-
this.stats.queued++;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
// Process immediately and wait for completion
|
|
22
|
-
await this.processOperation(operation);
|
|
23
|
-
return { success: true, driver: 'sync' };
|
|
24
|
-
} catch (error) {
|
|
25
|
-
// Re-throw to make the main operation fail
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
getInfo() {
|
|
31
|
-
return {
|
|
32
|
-
name: this.name,
|
|
33
|
-
mode: 'synchronous',
|
|
34
|
-
description: 'Processes partitions immediately, blocking the main operation',
|
|
35
|
-
stats: this.getStats()
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|