s3db.js 13.3.0 → 13.4.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 +34 -10
- package/dist/s3db.cjs.js +102 -23
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +102 -23
- package/dist/s3db.es.js.map +1 -1
- package/package.json +15 -2
- package/src/plugins/audit.plugin.js +427 -0
- package/src/plugins/costs.plugin.js +524 -0
- package/src/plugins/fulltext.plugin.js +484 -0
- package/src/plugins/metrics.plugin.js +575 -0
- package/src/plugins/queue-consumer.plugin.js +607 -19
- package/src/plugins/state-machine.plugin.js +132 -26
|
@@ -1,27 +1,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # QueueConsumerPlugin - Queue Message Consumer for s3db.js
|
|
3
|
+
*
|
|
4
|
+
* ## Overview
|
|
5
|
+
*
|
|
6
|
+
* The QueueConsumerPlugin consumes messages from queue services (AWS SQS, RabbitMQ)
|
|
7
|
+
* and automatically maps them to s3db.js resource operations (insert, update, delete).
|
|
8
|
+
* Perfect for event-driven architectures and asynchronous data processing.
|
|
9
|
+
*
|
|
10
|
+
* ## Features
|
|
11
|
+
*
|
|
12
|
+
* 1. **Multi-Driver Support** - SQS, RabbitMQ, and custom drivers
|
|
13
|
+
* 2. **Automatic Operation Mapping** - Messages automatically execute resource operations
|
|
14
|
+
* 3. **Flexible Configuration** - Configure multiple consumers with different queues
|
|
15
|
+
* 4. **Error Handling** - Built-in error handling with custom hooks
|
|
16
|
+
* 5. **Message Format** - Simple JSON format: { resource, action, data }
|
|
17
|
+
* 6. **Resource Routing** - Route messages to specific resources
|
|
18
|
+
* 7. **Driver-Specific Options** - AWS credentials, RabbitMQ URLs, prefetch, etc.
|
|
19
|
+
*
|
|
20
|
+
* ## Configuration
|
|
21
|
+
*
|
|
22
|
+
* ```javascript
|
|
23
|
+
* import { Database } from 's3db.js';
|
|
24
|
+
* import { QueueConsumerPlugin } from 's3db.js/plugins/queue-consumer';
|
|
25
|
+
*
|
|
26
|
+
* const db = new Database({
|
|
27
|
+
* connectionString: 's3://bucket/db'
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // SQS Configuration
|
|
31
|
+
* await db.use(new QueueConsumerPlugin({
|
|
32
|
+
* consumers: [
|
|
33
|
+
* {
|
|
34
|
+
* driver: 'sqs',
|
|
35
|
+
* config: {
|
|
36
|
+
* region: 'us-east-1',
|
|
37
|
+
* credentials: {
|
|
38
|
+
* accessKeyId: 'YOUR_ACCESS_KEY',
|
|
39
|
+
* secretAccessKey: 'YOUR_SECRET_KEY'
|
|
40
|
+
* },
|
|
41
|
+
* pollingInterval: 1000, // Poll every 1 second
|
|
42
|
+
* maxMessages: 10 // Max messages per poll
|
|
43
|
+
* },
|
|
44
|
+
* consumers: [
|
|
45
|
+
* {
|
|
46
|
+
* resources: 'users',
|
|
47
|
+
* queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-queue'
|
|
48
|
+
* },
|
|
49
|
+
* {
|
|
50
|
+
* resources: ['orders', 'shipments'],
|
|
51
|
+
* queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/orders-queue'
|
|
52
|
+
* }
|
|
53
|
+
* ]
|
|
54
|
+
* }
|
|
55
|
+
* ]
|
|
56
|
+
* }));
|
|
57
|
+
*
|
|
58
|
+
* // RabbitMQ Configuration
|
|
59
|
+
* await db.use(new QueueConsumerPlugin({
|
|
60
|
+
* consumers: [
|
|
61
|
+
* {
|
|
62
|
+
* driver: 'rabbitmq',
|
|
63
|
+
* config: {
|
|
64
|
+
* amqpUrl: 'amqp://user:pass@localhost:5672',
|
|
65
|
+
* prefetch: 10,
|
|
66
|
+
* reconnectInterval: 2000
|
|
67
|
+
* },
|
|
68
|
+
* consumers: [
|
|
69
|
+
* {
|
|
70
|
+
* resources: 'users',
|
|
71
|
+
* queueName: 'users-queue'
|
|
72
|
+
* }
|
|
73
|
+
* ]
|
|
74
|
+
* }
|
|
75
|
+
* ]
|
|
76
|
+
* }));
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* ## Usage Examples
|
|
80
|
+
*
|
|
81
|
+
* ### Basic Queue Consumer (SQS)
|
|
82
|
+
*
|
|
83
|
+
* ```javascript
|
|
84
|
+
* const db = new Database({ connectionString: 's3://bucket/db' });
|
|
85
|
+
*
|
|
86
|
+
* await db.use(new QueueConsumerPlugin({
|
|
87
|
+
* consumers: [
|
|
88
|
+
* {
|
|
89
|
+
* driver: 'sqs',
|
|
90
|
+
* config: {
|
|
91
|
+
* region: 'us-east-1',
|
|
92
|
+
* credentials: {
|
|
93
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
94
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
95
|
+
* }
|
|
96
|
+
* },
|
|
97
|
+
* consumers: [
|
|
98
|
+
* {
|
|
99
|
+
* resources: 'users',
|
|
100
|
+
* queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-queue'
|
|
101
|
+
* }
|
|
102
|
+
* ]
|
|
103
|
+
* }
|
|
104
|
+
* ]
|
|
105
|
+
* }));
|
|
106
|
+
*
|
|
107
|
+
* await db.start();
|
|
108
|
+
*
|
|
109
|
+
* // Plugin will now consume messages from the queue
|
|
110
|
+
* // Message format: { resource: 'users', action: 'insert', data: { ... } }
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* ### Message Format
|
|
114
|
+
*
|
|
115
|
+
* ```javascript
|
|
116
|
+
* // INSERT operation
|
|
117
|
+
* {
|
|
118
|
+
* "resource": "users",
|
|
119
|
+
* "action": "insert",
|
|
120
|
+
* "data": {
|
|
121
|
+
* "id": "u1",
|
|
122
|
+
* "name": "John Doe",
|
|
123
|
+
* "email": "john@example.com"
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* // UPDATE operation
|
|
128
|
+
* {
|
|
129
|
+
* "resource": "users",
|
|
130
|
+
* "action": "update",
|
|
131
|
+
* "data": {
|
|
132
|
+
* "id": "u1",
|
|
133
|
+
* "name": "Jane Doe"
|
|
134
|
+
* }
|
|
135
|
+
* }
|
|
136
|
+
*
|
|
137
|
+
* // DELETE operation
|
|
138
|
+
* {
|
|
139
|
+
* "resource": "users",
|
|
140
|
+
* "action": "delete",
|
|
141
|
+
* "data": {
|
|
142
|
+
* "id": "u1"
|
|
143
|
+
* }
|
|
144
|
+
* }
|
|
145
|
+
*
|
|
146
|
+
* // Messages can be nested in $body for SQS SNS integration
|
|
147
|
+
* {
|
|
148
|
+
* "$body": {
|
|
149
|
+
* "resource": "users",
|
|
150
|
+
* "action": "insert",
|
|
151
|
+
* "data": { ... }
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* ### Multiple Consumers
|
|
157
|
+
*
|
|
158
|
+
* ```javascript
|
|
159
|
+
* await db.use(new QueueConsumerPlugin({
|
|
160
|
+
* consumers: [
|
|
161
|
+
* // SQS Consumer
|
|
162
|
+
* {
|
|
163
|
+
* driver: 'sqs',
|
|
164
|
+
* config: {
|
|
165
|
+
* region: 'us-east-1',
|
|
166
|
+
* credentials: { ... }
|
|
167
|
+
* },
|
|
168
|
+
* consumers: [
|
|
169
|
+
* { resources: 'users', queueUrl: 'https://...' },
|
|
170
|
+
* { resources: 'orders', queueUrl: 'https://...' }
|
|
171
|
+
* ]
|
|
172
|
+
* },
|
|
173
|
+
* // RabbitMQ Consumer
|
|
174
|
+
* {
|
|
175
|
+
* driver: 'rabbitmq',
|
|
176
|
+
* config: {
|
|
177
|
+
* amqpUrl: 'amqp://localhost:5672'
|
|
178
|
+
* },
|
|
179
|
+
* consumers: [
|
|
180
|
+
* { resources: 'notifications', queueName: 'notifications-queue' }
|
|
181
|
+
* ]
|
|
182
|
+
* }
|
|
183
|
+
* ]
|
|
184
|
+
* }));
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* ### Sending Messages to Queue
|
|
188
|
+
*
|
|
189
|
+
* ```javascript
|
|
190
|
+
* // AWS SQS Example (using AWS SDK)
|
|
191
|
+
* import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
|
|
192
|
+
*
|
|
193
|
+
* const sqsClient = new SQSClient({ region: 'us-east-1' });
|
|
194
|
+
*
|
|
195
|
+
* await sqsClient.send(new SendMessageCommand({
|
|
196
|
+
* QueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-queue',
|
|
197
|
+
* MessageBody: JSON.stringify({
|
|
198
|
+
* resource: 'users',
|
|
199
|
+
* action: 'insert',
|
|
200
|
+
* data: {
|
|
201
|
+
* id: 'u1',
|
|
202
|
+
* name: 'John Doe',
|
|
203
|
+
* email: 'john@example.com'
|
|
204
|
+
* }
|
|
205
|
+
* })
|
|
206
|
+
* }));
|
|
207
|
+
*
|
|
208
|
+
* // RabbitMQ Example (using amqplib)
|
|
209
|
+
* import amqp from 'amqplib';
|
|
210
|
+
*
|
|
211
|
+
* const connection = await amqp.connect('amqp://localhost');
|
|
212
|
+
* const channel = await connection.createChannel();
|
|
213
|
+
*
|
|
214
|
+
* await channel.sendToQueue(
|
|
215
|
+
* 'users-queue',
|
|
216
|
+
* Buffer.from(JSON.stringify({
|
|
217
|
+
* resource: 'users',
|
|
218
|
+
* action: 'insert',
|
|
219
|
+
* data: {
|
|
220
|
+
* id: 'u1',
|
|
221
|
+
* name: 'John Doe',
|
|
222
|
+
* email: 'john@example.com'
|
|
223
|
+
* }
|
|
224
|
+
* }))
|
|
225
|
+
* );
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* ## Best Practices
|
|
229
|
+
*
|
|
230
|
+
* ### 1. Use Resource-Specific Queues
|
|
231
|
+
*
|
|
232
|
+
* ```javascript
|
|
233
|
+
* // GOOD: Separate queues per resource
|
|
234
|
+
* await db.use(new QueueConsumerPlugin({
|
|
235
|
+
* consumers: [
|
|
236
|
+
* {
|
|
237
|
+
* driver: 'sqs',
|
|
238
|
+
* config: { region: 'us-east-1' },
|
|
239
|
+
* consumers: [
|
|
240
|
+
* { resources: 'users', queueUrl: 'https://.../users-queue' },
|
|
241
|
+
* { resources: 'orders', queueUrl: 'https://.../orders-queue' },
|
|
242
|
+
* { resources: 'products', queueUrl: 'https://.../products-queue' }
|
|
243
|
+
* ]
|
|
244
|
+
* }
|
|
245
|
+
* ]
|
|
246
|
+
* }));
|
|
247
|
+
*
|
|
248
|
+
* // OK: Single queue for multiple related resources
|
|
249
|
+
* await db.use(new QueueConsumerPlugin({
|
|
250
|
+
* consumers: [
|
|
251
|
+
* {
|
|
252
|
+
* driver: 'sqs',
|
|
253
|
+
* config: { region: 'us-east-1' },
|
|
254
|
+
* consumers: [
|
|
255
|
+
* {
|
|
256
|
+
* resources: ['orders', 'order_items', 'shipments'],
|
|
257
|
+
* queueUrl: 'https://.../order-processing-queue'
|
|
258
|
+
* }
|
|
259
|
+
* ]
|
|
260
|
+
* }
|
|
261
|
+
* ]
|
|
262
|
+
* }));
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* ### 2. Configure Appropriate Polling Intervals
|
|
266
|
+
*
|
|
267
|
+
* ```javascript
|
|
268
|
+
* // High-throughput (frequent polling)
|
|
269
|
+
* await db.use(new QueueConsumerPlugin({
|
|
270
|
+
* consumers: [{
|
|
271
|
+
* driver: 'sqs',
|
|
272
|
+
* config: {
|
|
273
|
+
* pollingInterval: 500, // Poll every 500ms
|
|
274
|
+
* maxMessages: 10 // Process up to 10 messages
|
|
275
|
+
* },
|
|
276
|
+
* consumers: [...]
|
|
277
|
+
* }]
|
|
278
|
+
* }));
|
|
279
|
+
*
|
|
280
|
+
* // Low-throughput (less frequent polling)
|
|
281
|
+
* await db.use(new QueueConsumerPlugin({
|
|
282
|
+
* consumers: [{
|
|
283
|
+
* driver: 'sqs',
|
|
284
|
+
* config: {
|
|
285
|
+
* pollingInterval: 5000, // Poll every 5 seconds
|
|
286
|
+
* maxMessages: 1 // Process 1 message at a time
|
|
287
|
+
* },
|
|
288
|
+
* consumers: [...]
|
|
289
|
+
* }]
|
|
290
|
+
* }));
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* ### 3. Validate Messages Before Processing
|
|
294
|
+
*
|
|
295
|
+
* ```javascript
|
|
296
|
+
* // The plugin automatically validates message structure
|
|
297
|
+
* // Ensure your messages include:
|
|
298
|
+
* // - resource: string (required)
|
|
299
|
+
* // - action: 'insert' | 'update' | 'delete' (required)
|
|
300
|
+
* // - data: object (required)
|
|
301
|
+
*
|
|
302
|
+
* // Example of invalid message (will throw QueueError)
|
|
303
|
+
* {
|
|
304
|
+
* "action": "insert", // ❌ Missing 'resource'
|
|
305
|
+
* "data": { ... }
|
|
306
|
+
* }
|
|
307
|
+
*
|
|
308
|
+
* // Example of valid message
|
|
309
|
+
* {
|
|
310
|
+
* "resource": "users", // ✅
|
|
311
|
+
* "action": "insert", // ✅
|
|
312
|
+
* "data": { ... } // ✅
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*
|
|
316
|
+
* ### 4. Use Dead Letter Queues (DLQ)
|
|
317
|
+
*
|
|
318
|
+
* ```javascript
|
|
319
|
+
* // Configure DLQ in AWS SQS Console or via AWS SDK
|
|
320
|
+
* // Messages that fail repeatedly will be sent to DLQ for manual review
|
|
321
|
+
*
|
|
322
|
+
* // Example: Configure DLQ with AWS CDK
|
|
323
|
+
* const dlq = new sqs.Queue(this, 'UsersDLQ', {
|
|
324
|
+
* queueName: 'users-dlq'
|
|
325
|
+
* });
|
|
326
|
+
*
|
|
327
|
+
* const queue = new sqs.Queue(this, 'UsersQueue', {
|
|
328
|
+
* queueName: 'users-queue',
|
|
329
|
+
* deadLetterQueue: {
|
|
330
|
+
* queue: dlq,
|
|
331
|
+
* maxReceiveCount: 3 // Retry 3 times before sending to DLQ
|
|
332
|
+
* }
|
|
333
|
+
* });
|
|
334
|
+
* ```
|
|
335
|
+
*
|
|
336
|
+
* ## Performance Considerations
|
|
337
|
+
*
|
|
338
|
+
* ### Message Processing Throughput
|
|
339
|
+
*
|
|
340
|
+
* - **SQS**: Up to 100 messages/second with default settings
|
|
341
|
+
* - **RabbitMQ**: Up to 1000+ messages/second with prefetch=10
|
|
342
|
+
* - Processing time depends on resource operation complexity
|
|
343
|
+
*
|
|
344
|
+
* ### Optimization Tips
|
|
345
|
+
*
|
|
346
|
+
* ```javascript
|
|
347
|
+
* // 1. Increase maxMessages for batch processing (SQS)
|
|
348
|
+
* await db.use(new QueueConsumerPlugin({
|
|
349
|
+
* consumers: [{
|
|
350
|
+
* driver: 'sqs',
|
|
351
|
+
* config: {
|
|
352
|
+
* maxMessages: 10 // Process 10 messages per poll
|
|
353
|
+
* },
|
|
354
|
+
* consumers: [...]
|
|
355
|
+
* }]
|
|
356
|
+
* }));
|
|
357
|
+
*
|
|
358
|
+
* // 2. Increase prefetch for higher throughput (RabbitMQ)
|
|
359
|
+
* await db.use(new QueueConsumerPlugin({
|
|
360
|
+
* consumers: [{
|
|
361
|
+
* driver: 'rabbitmq',
|
|
362
|
+
* config: {
|
|
363
|
+
* prefetch: 20 // Process 20 messages concurrently
|
|
364
|
+
* },
|
|
365
|
+
* consumers: [...]
|
|
366
|
+
* }]
|
|
367
|
+
* }));
|
|
368
|
+
*
|
|
369
|
+
* // 3. Use multiple consumers for parallel processing
|
|
370
|
+
* await db.use(new QueueConsumerPlugin({
|
|
371
|
+
* consumers: [
|
|
372
|
+
* { driver: 'sqs', config: {...}, consumers: [{resources: 'users', queueUrl: '...'}] },
|
|
373
|
+
* { driver: 'sqs', config: {...}, consumers: [{resources: 'orders', queueUrl: '...'}] }
|
|
374
|
+
* ]
|
|
375
|
+
* }));
|
|
376
|
+
* ```
|
|
377
|
+
*
|
|
378
|
+
* ## Troubleshooting
|
|
379
|
+
*
|
|
380
|
+
* ### Messages Not Being Consumed
|
|
381
|
+
*
|
|
382
|
+
* ```javascript
|
|
383
|
+
* // Check if plugin is started
|
|
384
|
+
* await db.start(); // Must call start() to begin consuming
|
|
385
|
+
*
|
|
386
|
+
* // Check if consumers are running
|
|
387
|
+
* const plugin = db.plugins.QueueConsumerPlugin;
|
|
388
|
+
* console.log(plugin.consumers); // Should show active consumers
|
|
389
|
+
*
|
|
390
|
+
* // Check queue URL/name is correct
|
|
391
|
+
* console.log(plugin.driversConfig);
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* ### Resource Not Found Error
|
|
395
|
+
*
|
|
396
|
+
* ```javascript
|
|
397
|
+
* // Error: Resource 'users' not found
|
|
398
|
+
*
|
|
399
|
+
* // Ensure resource is created before starting plugin
|
|
400
|
+
* await db.createResource({
|
|
401
|
+
* name: 'users',
|
|
402
|
+
* attributes: { ... }
|
|
403
|
+
* });
|
|
404
|
+
*
|
|
405
|
+
* await db.use(new QueueConsumerPlugin({...}));
|
|
406
|
+
* await db.start();
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* ### Invalid Message Format
|
|
410
|
+
*
|
|
411
|
+
* ```javascript
|
|
412
|
+
* // Error: Resource not found in message
|
|
413
|
+
* // Ensure message includes 'resource' field
|
|
414
|
+
*
|
|
415
|
+
* // Error: Action not found in message
|
|
416
|
+
* // Ensure message includes 'action' field
|
|
417
|
+
*
|
|
418
|
+
* // Error: Unsupported action 'create'
|
|
419
|
+
* // Use 'insert', 'update', or 'delete' only
|
|
420
|
+
*
|
|
421
|
+
* // Check message format
|
|
422
|
+
* console.log(JSON.stringify({
|
|
423
|
+
* resource: 'users', // ✅ Required
|
|
424
|
+
* action: 'insert', // ✅ Required (insert/update/delete)
|
|
425
|
+
* data: { id: 'u1', ... } // ✅ Required
|
|
426
|
+
* }, null, 2));
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* ### SQS Credentials Issues
|
|
430
|
+
*
|
|
431
|
+
* ```javascript
|
|
432
|
+
* // Use environment variables
|
|
433
|
+
* await db.use(new QueueConsumerPlugin({
|
|
434
|
+
* consumers: [{
|
|
435
|
+
* driver: 'sqs',
|
|
436
|
+
* config: {
|
|
437
|
+
* region: process.env.AWS_REGION || 'us-east-1',
|
|
438
|
+
* credentials: {
|
|
439
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
440
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
441
|
+
* }
|
|
442
|
+
* },
|
|
443
|
+
* consumers: [...]
|
|
444
|
+
* }]
|
|
445
|
+
* }));
|
|
446
|
+
*
|
|
447
|
+
* // Or use IAM role (no credentials needed)
|
|
448
|
+
* await db.use(new QueueConsumerPlugin({
|
|
449
|
+
* consumers: [{
|
|
450
|
+
* driver: 'sqs',
|
|
451
|
+
* config: { region: 'us-east-1' }, // Uses IAM role automatically
|
|
452
|
+
* consumers: [...]
|
|
453
|
+
* }]
|
|
454
|
+
* }));
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* ## Real-World Use Cases
|
|
458
|
+
*
|
|
459
|
+
* ### 1. Event-Driven Data Sync
|
|
460
|
+
*
|
|
461
|
+
* ```javascript
|
|
462
|
+
* // Sync data from external system to s3db via queue
|
|
463
|
+
* await db.use(new QueueConsumerPlugin({
|
|
464
|
+
* consumers: [{
|
|
465
|
+
* driver: 'sqs',
|
|
466
|
+
* config: { region: 'us-east-1' },
|
|
467
|
+
* consumers: [
|
|
468
|
+
* { resources: 'users', queueUrl: 'https://.../external-users-queue' },
|
|
469
|
+
* { resources: 'products', queueUrl: 'https://.../external-products-queue' }
|
|
470
|
+
* ]
|
|
471
|
+
* }]
|
|
472
|
+
* }));
|
|
473
|
+
*
|
|
474
|
+
* // External system sends messages to SQS
|
|
475
|
+
* // s3db automatically processes them
|
|
476
|
+
* ```
|
|
477
|
+
*
|
|
478
|
+
* ### 2. Asynchronous Writes
|
|
479
|
+
*
|
|
480
|
+
* ```javascript
|
|
481
|
+
* // Handle high-volume writes asynchronously
|
|
482
|
+
* // API enqueues writes → Queue Consumer processes them
|
|
483
|
+
*
|
|
484
|
+
* // In API
|
|
485
|
+
* await sqsClient.send(new SendMessageCommand({
|
|
486
|
+
* QueueUrl: 'https://.../analytics-queue',
|
|
487
|
+
* MessageBody: JSON.stringify({
|
|
488
|
+
* resource: 'page_views',
|
|
489
|
+
* action: 'insert',
|
|
490
|
+
* data: { page: '/home', timestamp: new Date(), userId: 'u1' }
|
|
491
|
+
* })
|
|
492
|
+
* }));
|
|
493
|
+
*
|
|
494
|
+
* // Queue Consumer processes asynchronously
|
|
495
|
+
* await db.use(new QueueConsumerPlugin({
|
|
496
|
+
* consumers: [{
|
|
497
|
+
* driver: 'sqs',
|
|
498
|
+
* config: { pollingInterval: 100, maxMessages: 10 },
|
|
499
|
+
* consumers: [{ resources: 'page_views', queueUrl: '...' }]
|
|
500
|
+
* }]
|
|
501
|
+
* }));
|
|
502
|
+
* ```
|
|
503
|
+
*
|
|
504
|
+
* ### 3. Microservices Integration
|
|
505
|
+
*
|
|
506
|
+
* ```javascript
|
|
507
|
+
* // Multiple microservices send events to shared queue
|
|
508
|
+
* // s3db consumes and stores all events
|
|
509
|
+
*
|
|
510
|
+
* await db.use(new QueueConsumerPlugin({
|
|
511
|
+
* consumers: [{
|
|
512
|
+
* driver: 'rabbitmq',
|
|
513
|
+
* config: { amqpUrl: 'amqp://localhost' },
|
|
514
|
+
* consumers: [
|
|
515
|
+
* { resources: 'events', queueName: 'service-events' }
|
|
516
|
+
* ]
|
|
517
|
+
* }]
|
|
518
|
+
* }));
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* ### 4. ETL Pipeline
|
|
522
|
+
*
|
|
523
|
+
* ```javascript
|
|
524
|
+
* // Extract → Transform → Load pipeline
|
|
525
|
+
* // Extract: External system → SQS
|
|
526
|
+
* // Transform: Lambda/Worker → Modified message → SQS
|
|
527
|
+
* // Load: s3db consumes and stores
|
|
528
|
+
*
|
|
529
|
+
* await db.use(new QueueConsumerPlugin({
|
|
530
|
+
* consumers: [{
|
|
531
|
+
* driver: 'sqs',
|
|
532
|
+
* config: { region: 'us-east-1' },
|
|
533
|
+
* consumers: [
|
|
534
|
+
* { resources: 'raw_data', queueUrl: 'https://.../raw-queue' },
|
|
535
|
+
* { resources: 'processed_data', queueUrl: 'https://.../processed-queue' }
|
|
536
|
+
* ]
|
|
537
|
+
* }]
|
|
538
|
+
* }));
|
|
539
|
+
* ```
|
|
540
|
+
*
|
|
541
|
+
* ## API Reference
|
|
542
|
+
*
|
|
543
|
+
* ### Constructor Options
|
|
544
|
+
*
|
|
545
|
+
* ```typescript
|
|
546
|
+
* interface QueueConsumerPluginOptions {
|
|
547
|
+
* consumers: Array<{
|
|
548
|
+
* driver: 'sqs' | 'rabbitmq' | string;
|
|
549
|
+
* config: DriverConfig;
|
|
550
|
+
* consumers: Array<{
|
|
551
|
+
* resources: string | string[];
|
|
552
|
+
* queueUrl?: string; // For SQS
|
|
553
|
+
* queueName?: string; // For RabbitMQ
|
|
554
|
+
* [key: string]: any; // Driver-specific options
|
|
555
|
+
* }>;
|
|
556
|
+
* }>;
|
|
557
|
+
* }
|
|
558
|
+
*
|
|
559
|
+
* // SQS Driver Config
|
|
560
|
+
* interface SQSDriverConfig {
|
|
561
|
+
* region: string;
|
|
562
|
+
* credentials?: {
|
|
563
|
+
* accessKeyId: string;
|
|
564
|
+
* secretAccessKey: string;
|
|
565
|
+
* };
|
|
566
|
+
* pollingInterval?: number; // Default: 1000ms
|
|
567
|
+
* maxMessages?: number; // Default: 10
|
|
568
|
+
* }
|
|
569
|
+
*
|
|
570
|
+
* // RabbitMQ Driver Config
|
|
571
|
+
* interface RabbitMQDriverConfig {
|
|
572
|
+
* amqpUrl: string;
|
|
573
|
+
* prefetch?: number; // Default: 10
|
|
574
|
+
* reconnectInterval?: number; // Default: 2000ms
|
|
575
|
+
* }
|
|
576
|
+
* ```
|
|
577
|
+
*
|
|
578
|
+
* ### Message Structure
|
|
579
|
+
*
|
|
580
|
+
* ```typescript
|
|
581
|
+
* interface QueueMessage {
|
|
582
|
+
* resource: string; // Resource name
|
|
583
|
+
* action: 'insert' | 'update' | 'delete'; // Operation
|
|
584
|
+
* data: object; // Operation data
|
|
585
|
+
* }
|
|
586
|
+
*
|
|
587
|
+
* // Optional: Nested in $body
|
|
588
|
+
* interface NestedQueueMessage {
|
|
589
|
+
* $body: QueueMessage;
|
|
590
|
+
* }
|
|
591
|
+
* ```
|
|
592
|
+
*
|
|
593
|
+
* ### Supported Actions
|
|
594
|
+
*
|
|
595
|
+
* - `insert` - Creates new record (calls `resource.insert(data)`)
|
|
596
|
+
* - `update` - Updates existing record (calls `resource.update(data.id, data)`)
|
|
597
|
+
* - `delete` - Deletes record (calls `resource.delete(data.id)`)
|
|
598
|
+
*
|
|
599
|
+
* ## Notes
|
|
600
|
+
*
|
|
601
|
+
* - Messages are processed sequentially per consumer
|
|
602
|
+
* - Failed messages are retried based on queue configuration
|
|
603
|
+
* - Plugin automatically stops all consumers on `db.stop()`
|
|
604
|
+
* - Double-nested messages (SNS→SQS) are automatically unwrapped
|
|
605
|
+
* - Error handling can be customized via `onError` callback
|
|
606
|
+
*/
|
|
607
|
+
|
|
1
608
|
import { Plugin } from './plugin.class.js';
|
|
2
609
|
import { createConsumer } from './consumers/index.js';
|
|
3
610
|
import tryFn from "../concerns/try-fn.js";
|
|
4
611
|
import { QueueError } from "./queue.errors.js";
|
|
5
612
|
|
|
6
|
-
// Example configuration for SQS:
|
|
7
|
-
// const plugin = new QueueConsumerPlugin({
|
|
8
|
-
// driver: 'sqs',
|
|
9
|
-
// queues: { users: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue' },
|
|
10
|
-
// region: 'us-east-1',
|
|
11
|
-
// credentials: { accessKeyId: '...', secretAccessKey: '...' },
|
|
12
|
-
// poolingInterval: 1000,
|
|
13
|
-
// maxMessages: 10,
|
|
14
|
-
// });
|
|
15
|
-
//
|
|
16
|
-
// Example configuration for RabbitMQ:
|
|
17
|
-
// const plugin = new QueueConsumerPlugin({
|
|
18
|
-
// driver: 'rabbitmq',
|
|
19
|
-
// queues: { users: 'users-queue' },
|
|
20
|
-
// amqpUrl: 'amqp://user:pass@localhost:5672',
|
|
21
|
-
// prefetch: 10,
|
|
22
|
-
// reconnectInterval: 2000,
|
|
23
|
-
// });
|
|
24
|
-
|
|
25
613
|
export class QueueConsumerPlugin extends Plugin {
|
|
26
614
|
constructor(options = {}) {
|
|
27
615
|
super(options);
|