queen-mq 0.2.23 → 0.3.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.
Files changed (35) hide show
  1. package/README.md +162 -136
  2. package/client-js/client-v2/LOGGING.md +240 -0
  3. package/client-js/client-v2/Queen.js +389 -0
  4. package/client-js/client-v2/README.md +1883 -0
  5. package/client-js/client-v2/buffer/BufferManager.js +215 -0
  6. package/client-js/client-v2/buffer/MessageBuffer.js +132 -0
  7. package/client-js/client-v2/builders/QueueBuilder.js +724 -0
  8. package/client-js/client-v2/builders/TransactionBuilder.js +110 -0
  9. package/client-js/client-v2/consumer/ConsumerManager.js +390 -0
  10. package/client-js/client-v2/http/HttpClient.js +215 -0
  11. package/client-js/client-v2/http/LoadBalancer.js +50 -0
  12. package/client-js/client-v2/index.js +7 -0
  13. package/client-js/client-v2/utils/defaults.js +54 -0
  14. package/client-js/client-v2/utils/logger.js +54 -0
  15. package/client-js/client-v2/utils/validation.js +31 -0
  16. package/client-js/test-v2/AI_TEST_SUMMARY.md +226 -0
  17. package/client-js/test-v2/GETTING_STARTED.md +154 -0
  18. package/client-js/test-v2/ai_buffering.js +194 -0
  19. package/client-js/test-v2/ai_error_handling.js +223 -0
  20. package/client-js/test-v2/ai_lease_renewal.js +206 -0
  21. package/client-js/test-v2/ai_mixed_scenarios.js +278 -0
  22. package/client-js/test-v2/ai_priority.js +169 -0
  23. package/client-js/test-v2/ai_resources.js +217 -0
  24. package/client-js/test-v2/ai_ttl_retention.js +170 -0
  25. package/client-js/test-v2/complete.js +59 -0
  26. package/client-js/test-v2/consume.js +655 -0
  27. package/client-js/test-v2/dlq.js +82 -0
  28. package/client-js/test-v2/load.js +177 -0
  29. package/client-js/test-v2/pop.js +114 -0
  30. package/client-js/test-v2/push.js +333 -0
  31. package/client-js/test-v2/queue.js +39 -0
  32. package/client-js/test-v2/run.js +187 -0
  33. package/client-js/test-v2/subscription.js +354 -0
  34. package/client-js/test-v2/transaction.js +443 -0
  35. package/package.json +1 -1
@@ -0,0 +1,443 @@
1
+ export async function transactionBasicPushAck(client) {
2
+ const queueA = await client.queue('test-queue-v2-txn-basic-a').create()
3
+ const queueB = await client.queue('test-queue-v2-txn-basic-b').create()
4
+ if (!queueA.configured || !queueB.configured) {
5
+ return { success: false, message: 'Queues not created' }
6
+ }
7
+
8
+ // Push a message to queue A
9
+ await client.queue('test-queue-v2-txn-basic-a').push([{ data: { value: 1 } }])
10
+
11
+ // Pop from A and push to B in a transaction
12
+ const messages = await client.queue('test-queue-v2-txn-basic-a').batch(1).wait(false).pop()
13
+ if (messages.length === 0) {
14
+ return { success: false, message: 'No message to consume' }
15
+ }
16
+
17
+ await client
18
+ .transaction()
19
+ .queue('test-queue-v2-txn-basic-b')
20
+ .push([{ data: { value: messages[0].data.value + 1 } }])
21
+ .ack(messages[0])
22
+ .commit()
23
+
24
+ // Verify message moved to B
25
+ const resultB = await client.queue('test-queue-v2-txn-basic-b').batch(1).wait(false).pop()
26
+ const resultA = await client.queue('test-queue-v2-txn-basic-a').batch(1).wait(false).pop()
27
+
28
+ return {
29
+ success: resultB.length === 1 && resultA.length === 0 && resultB[0].data.value === 2,
30
+ message: 'Basic transaction push+ack completed'
31
+ }
32
+ }
33
+
34
+ export async function transactionMultiplePushes(client) {
35
+ const queueA = await client.queue('test-queue-v2-txn-multi-a').create()
36
+ const queueB = await client.queue('test-queue-v2-txn-multi-b').create()
37
+ const queueC = await client.queue('test-queue-v2-txn-multi-c').create()
38
+ if (!queueA.configured || !queueB.configured || !queueC.configured) {
39
+ return { success: false, message: 'Queues not created' }
40
+ }
41
+
42
+ // Push to queue A
43
+ await client.queue('test-queue-v2-txn-multi-a').push([{ data: { id: 'source' } }])
44
+
45
+ // Pop from A
46
+ const messages = await client.queue('test-queue-v2-txn-multi-a').batch(1).wait(false).pop()
47
+ if (messages.length === 0) {
48
+ return { success: false, message: 'No message to consume' }
49
+ }
50
+
51
+ // Transaction: Push to B and C, ack from A
52
+ await client
53
+ .transaction()
54
+ .queue('test-queue-v2-txn-multi-b')
55
+ .push([{ data: { id: 'b', source: messages[0].data.id } }])
56
+ .queue('test-queue-v2-txn-multi-c')
57
+ .push([{ data: { id: 'c', source: messages[0].data.id } }])
58
+ .ack(messages[0])
59
+ .commit()
60
+
61
+ // Verify messages in B and C
62
+ const resultB = await client.queue('test-queue-v2-txn-multi-b').batch(1).wait(false).pop()
63
+ const resultC = await client.queue('test-queue-v2-txn-multi-c').batch(1).wait(false).pop()
64
+ const resultA = await client.queue('test-queue-v2-txn-multi-a').batch(1).wait(false).pop()
65
+
66
+ return {
67
+ success: resultB.length === 1 && resultC.length === 1 && resultA.length === 0,
68
+ message: 'Multiple pushes in transaction completed'
69
+ }
70
+ }
71
+
72
+ export async function transactionMultipleAcks(client) {
73
+ const queueA = await client.queue('test-queue-v2-txn-multi-ack-a').create()
74
+ const queueB = await client.queue('test-queue-v2-txn-multi-ack-b').create()
75
+ if (!queueA.configured || !queueB.configured) {
76
+ return { success: false, message: 'Queues not created' }
77
+ }
78
+
79
+ // Push 3 messages to queue A
80
+ await client.queue('test-queue-v2-txn-multi-ack-a').push([
81
+ { data: { value: 1 } },
82
+ { data: { value: 2 } },
83
+ { data: { value: 3 } }
84
+ ])
85
+
86
+ // Pop 3 messages from A
87
+ const messages = await client.queue('test-queue-v2-txn-multi-ack-a').batch(3).wait(false).pop()
88
+ if (messages.length !== 3) {
89
+ return { success: false, message: `Expected 3 messages, got ${messages.length}` }
90
+ }
91
+
92
+ // Transaction: Ack all 3, push summary to B
93
+ const sum = messages.reduce((acc, msg) => acc + msg.data.value, 0)
94
+ await client
95
+ .transaction()
96
+ .ack(messages[0])
97
+ .ack(messages[1])
98
+ .ack(messages[2])
99
+ .queue('test-queue-v2-txn-multi-ack-b')
100
+ .push([{ data: { sum } }])
101
+ .commit()
102
+
103
+ // Verify
104
+ const resultA = await client.queue('test-queue-v2-txn-multi-ack-a').batch(3).wait(false).pop()
105
+ const resultB = await client.queue('test-queue-v2-txn-multi-ack-b').batch(1).wait(false).pop()
106
+
107
+ return {
108
+ success: resultA.length === 0 && resultB.length === 1 && resultB[0].data.sum === 6,
109
+ message: 'Multiple acks in transaction completed'
110
+ }
111
+ }
112
+
113
+ export async function transactionAckWithStatus(client) {
114
+ const queueA = await client.queue('test-queue-v2-txn-ack-status-a').create()
115
+ const queueB = await client.queue('test-queue-v2-txn-ack-status-b').create()
116
+ if (!queueA.configured || !queueB.configured) {
117
+ return { success: false, message: 'Queues not created' }
118
+ }
119
+
120
+ // Push a message
121
+ await client.queue('test-queue-v2-txn-ack-status-a').push([{ data: { value: 1 } }])
122
+
123
+ // Pop the message
124
+ const messages = await client.queue('test-queue-v2-txn-ack-status-a').batch(1).wait(false).pop()
125
+ if (messages.length === 0) {
126
+ return { success: false, message: 'No message to consume' }
127
+ }
128
+
129
+ // ACK with custom status in a transaction
130
+ await client
131
+ .transaction()
132
+ .ack(messages[0], 'failed') // Ack with 'failed' status
133
+ .queue('test-queue-v2-txn-ack-status-b')
134
+ .push([{ data: { processed: true } }])
135
+ .commit()
136
+
137
+ // Verify message was acked and new message was pushed
138
+ const resultA = await client.queue('test-queue-v2-txn-ack-status-a').batch(1).wait(false).pop()
139
+ const resultB = await client.queue('test-queue-v2-txn-ack-status-b').batch(1).wait(false).pop()
140
+
141
+ return {
142
+ success: resultA.length === 0 && resultB.length === 1,
143
+ message: 'Transaction with custom ack status completed'
144
+ }
145
+ }
146
+
147
+ export async function transactionAtomicity(client) {
148
+ const queueA = await client.queue('test-queue-v2-txn-atomic-a').create()
149
+ const queueB = await client.queue('test-queue-v2-txn-atomic-b').create()
150
+ if (!queueA.configured || !queueB.configured) {
151
+ return { success: false, message: 'Queues not created' }
152
+ }
153
+
154
+ // Push a message to A
155
+ await client.queue('test-queue-v2-txn-atomic-a').push([{ data: { value: 100 } }])
156
+
157
+ // Pop from A
158
+ const messages = await client.queue('test-queue-v2-txn-atomic-a').batch(1).wait(false).pop()
159
+ if (messages.length === 0) {
160
+ return { success: false, message: 'No message to consume' }
161
+ }
162
+
163
+ // Successful transaction
164
+ await client
165
+ .transaction()
166
+ .ack(messages[0])
167
+ .queue('test-queue-v2-txn-atomic-b')
168
+ .push([{ data: { value: 200 } }])
169
+ .commit()
170
+
171
+ // Both operations should have succeeded atomically
172
+ const resultA = await client.queue('test-queue-v2-txn-atomic-a').batch(1).wait(false).pop()
173
+ const resultB = await client.queue('test-queue-v2-txn-atomic-b').batch(1).wait(false).pop()
174
+
175
+ return {
176
+ success: resultA.length === 0 && resultB.length === 1 && resultB[0].data.value === 200,
177
+ message: 'Transaction atomicity verified'
178
+ }
179
+ }
180
+
181
+ export async function transactionChainedProcessing(client) {
182
+ const queue1 = await client.queue('test-queue-v2-txn-chain-1').create()
183
+ const queue2 = await client.queue('test-queue-v2-txn-chain-2').create()
184
+ const queue3 = await client.queue('test-queue-v2-txn-chain-3').create()
185
+ if (!queue1.configured || !queue2.configured || !queue3.configured) {
186
+ return { success: false, message: 'Queues not created' }
187
+ }
188
+
189
+ // Initial message
190
+ await client.queue('test-queue-v2-txn-chain-1').push([{ data: { step: 1, value: 10 } }])
191
+
192
+ // Transaction 1: Process from queue1 to queue2
193
+ const msg1 = await client.queue('test-queue-v2-txn-chain-1').batch(1).wait(false).pop()
194
+ if (msg1.length === 0) {
195
+ return { success: false, message: 'No message in queue 1' }
196
+ }
197
+
198
+ await client
199
+ .transaction()
200
+ .queue('test-queue-v2-txn-chain-2')
201
+ .push([{ data: { step: 2, value: msg1[0].data.value * 2 } }])
202
+ .ack(msg1[0])
203
+ .commit()
204
+
205
+ // Transaction 2: Process from queue2 to queue3
206
+ const msg2 = await client.queue('test-queue-v2-txn-chain-2').batch(1).wait(false).pop()
207
+ if (msg2.length === 0) {
208
+ return { success: false, message: 'No message in queue 2' }
209
+ }
210
+
211
+ await client
212
+ .transaction()
213
+ .queue('test-queue-v2-txn-chain-3')
214
+ .push([{ data: { step: 3, value: msg2[0].data.value + 5 } }])
215
+ .ack(msg2[0])
216
+ .commit()
217
+
218
+ // Verify final result
219
+ const result = await client.queue('test-queue-v2-txn-chain-3').batch(1).wait(false).pop()
220
+ const empty1 = await client.queue('test-queue-v2-txn-chain-1').batch(1).wait(false).pop()
221
+ const empty2 = await client.queue('test-queue-v2-txn-chain-2').batch(1).wait(false).pop()
222
+
223
+ return {
224
+ success: result.length === 1 && result[0].data.value === 25 && empty1.length === 0 && empty2.length === 0,
225
+ message: 'Chained transaction processing completed'
226
+ }
227
+ }
228
+
229
+ export async function transactionBatchPush(client) {
230
+ const queueA = await client.queue('test-queue-v2-txn-batch-a').create()
231
+ const queueB = await client.queue('test-queue-v2-txn-batch-b').create()
232
+ if (!queueA.configured || !queueB.configured) {
233
+ return { success: false, message: 'Queues not created' }
234
+ }
235
+
236
+ // Push source message
237
+ await client.queue('test-queue-v2-txn-batch-a').push([{ data: { id: 'batch-source' } }])
238
+
239
+ // Pop from A
240
+ const messages = await client.queue('test-queue-v2-txn-batch-a').batch(1).wait(false).pop()
241
+ if (messages.length === 0) {
242
+ return { success: false, message: 'No message to consume' }
243
+ }
244
+
245
+ // Transaction: Push multiple messages to B in a single batch, ack from A
246
+ await client
247
+ .transaction()
248
+ .queue('test-queue-v2-txn-batch-b')
249
+ .push([
250
+ { data: { index: 1 } },
251
+ { data: { index: 2 } },
252
+ { data: { index: 3 } },
253
+ { data: { index: 4 } },
254
+ { data: { index: 5 } }
255
+ ])
256
+ .ack(messages[0])
257
+ .commit()
258
+
259
+ // Verify all 5 messages in B
260
+ const resultB = await client.queue('test-queue-v2-txn-batch-b').batch(10).wait(false).pop()
261
+ const resultA = await client.queue('test-queue-v2-txn-batch-a').batch(1).wait(false).pop()
262
+
263
+ return {
264
+ success: resultB.length === 5 && resultA.length === 0,
265
+ message: 'Batch push in transaction completed'
266
+ }
267
+ }
268
+
269
+ export async function transactionWithPartitions(client) {
270
+ const queue = await client.queue('test-queue-v2-txn-partitions').create()
271
+ if (!queue.configured) {
272
+ return { success: false, message: 'Queue not created' }
273
+ }
274
+
275
+ // Push messages to different partitions
276
+ await client.queue('test-queue-v2-txn-partitions').partition('p1').push([{ data: { partition: 'p1', value: 1 } }])
277
+ await client.queue('test-queue-v2-txn-partitions').partition('p2').push([{ data: { partition: 'p2', value: 2 } }])
278
+
279
+ // Pop from both partitions
280
+ const msg1 = await client.queue('test-queue-v2-txn-partitions').partition('p1').batch(1).wait(false).pop()
281
+ const msg2 = await client.queue('test-queue-v2-txn-partitions').partition('p2').batch(1).wait(false).pop()
282
+
283
+ if (msg1.length === 0 || msg2.length === 0) {
284
+ return { success: false, message: 'Messages not found in partitions' }
285
+ }
286
+
287
+ // Transaction: Ack both messages from different partitions
288
+ await client
289
+ .transaction()
290
+ .ack(msg1[0])
291
+ .ack(msg2[0])
292
+ .commit()
293
+
294
+ // Verify both partitions are empty
295
+ const result1 = await client.queue('test-queue-v2-txn-partitions').partition('p1').batch(1).wait(false).pop()
296
+ const result2 = await client.queue('test-queue-v2-txn-partitions').partition('p2').batch(1).wait(false).pop()
297
+
298
+ return {
299
+ success: result1.length === 0 && result2.length === 0,
300
+ message: 'Transaction with multiple partitions completed'
301
+ }
302
+ }
303
+
304
+ export async function transactionWithConsumer(client) {
305
+ const queueA = await client.queue('test-queue-v2-txn-consumer-a').create()
306
+ const queueB = await client.queue('test-queue-v2-txn-consumer-b').create()
307
+ if (!queueA.configured || !queueB.configured) {
308
+ return { success: false, message: 'Queues not created' }
309
+ }
310
+
311
+ // Push messages
312
+ await client.queue('test-queue-v2-txn-consumer-a').push([
313
+ { data: { value: 1 } },
314
+ { data: { value: 2 } },
315
+ { data: { value: 3 } }
316
+ ])
317
+
318
+ let processedCount = 0
319
+
320
+ // Consume with transaction
321
+ await client
322
+ .queue('test-queue-v2-txn-consumer-a')
323
+ .batch(1)
324
+ .wait(false)
325
+ .limit(3)
326
+ .each()
327
+ .autoAck(false) // Manual ack in transaction
328
+ .consume(async (msg) => {
329
+ await client
330
+ .transaction()
331
+ .queue('test-queue-v2-txn-consumer-b')
332
+ .push([{ data: { value: msg.data.value * 10 } }])
333
+ .ack(msg)
334
+ .commit()
335
+
336
+ processedCount++
337
+ })
338
+
339
+ // Verify results
340
+ const resultA = await client.queue('test-queue-v2-txn-consumer-a').batch(10).wait(false).pop()
341
+ const resultB = await client.queue('test-queue-v2-txn-consumer-b').batch(10).wait(false).pop()
342
+
343
+ return {
344
+ success: processedCount === 3 && resultA.length === 0 && resultB.length === 3,
345
+ message: 'Transaction with consumer completed'
346
+ }
347
+ }
348
+
349
+ export async function transactionEmptyCommit(client) {
350
+ const queue = await client.queue('test-queue-v2-txn-empty').create()
351
+ if (!queue.configured) {
352
+ return { success: false, message: 'Queue not created' }
353
+ }
354
+
355
+ // Empty transaction - should throw error
356
+ let errorThrown = false
357
+ try {
358
+ await client.transaction().commit()
359
+ } catch (error) {
360
+ errorThrown = error.message === 'Transaction has no operations to commit'
361
+ }
362
+
363
+ return {
364
+ success: errorThrown,
365
+ message: 'Empty transaction correctly throws error'
366
+ }
367
+ }
368
+
369
+ export async function transactionLargePayload(client) {
370
+ const queueA = await client.queue('test-queue-v2-txn-large-a').create()
371
+ const queueB = await client.queue('test-queue-v2-txn-large-b').create()
372
+ if (!queueA.configured || !queueB.configured) {
373
+ return { success: false, message: 'Queues not created' }
374
+ }
375
+
376
+ // Push a large message
377
+ const largeData = {
378
+ items: Array.from({ length: 1000 }, (_, i) => ({ id: i, data: `item-${i}` }))
379
+ }
380
+ await client.queue('test-queue-v2-txn-large-a').push([{ data: largeData }])
381
+
382
+ // Pop and process in transaction
383
+ const messages = await client.queue('test-queue-v2-txn-large-a').batch(1).wait(false).pop()
384
+ if (messages.length === 0) {
385
+ return { success: false, message: 'No message to consume' }
386
+ }
387
+
388
+ await client
389
+ .transaction()
390
+ .queue('test-queue-v2-txn-large-b')
391
+ .push([{ data: { count: messages[0].data.items.length } }])
392
+ .ack(messages[0])
393
+ .commit()
394
+
395
+ // Verify
396
+ const resultB = await client.queue('test-queue-v2-txn-large-b').batch(1).wait(false).pop()
397
+
398
+ return {
399
+ success: resultB.length === 1 && resultB[0].data.count === 1000,
400
+ message: 'Large payload transaction completed'
401
+ }
402
+ }
403
+
404
+ export async function transactionMultipleQueues(client) {
405
+ const queueA = await client.queue('test-queue-v2-txn-multi-q-a').create()
406
+ const queueB = await client.queue('test-queue-v2-txn-multi-q-b').create()
407
+ const queueC = await client.queue('test-queue-v2-txn-multi-q-c').create()
408
+ if (!queueA.configured || !queueB.configured || !queueC.configured) {
409
+ return { success: false, message: 'Queues not created' }
410
+ }
411
+
412
+ // Push messages to A and B
413
+ await client.queue('test-queue-v2-txn-multi-q-a').push([{ data: { source: 'A' } }])
414
+ await client.queue('test-queue-v2-txn-multi-q-b').push([{ data: { source: 'B' } }])
415
+
416
+ // Pop from both
417
+ const msgA = await client.queue('test-queue-v2-txn-multi-q-a').batch(1).wait(false).pop()
418
+ const msgB = await client.queue('test-queue-v2-txn-multi-q-b').batch(1).wait(false).pop()
419
+
420
+ if (msgA.length === 0 || msgB.length === 0) {
421
+ return { success: false, message: 'Messages not found' }
422
+ }
423
+
424
+ // Transaction: Ack from A and B, push to C
425
+ await client
426
+ .transaction()
427
+ .ack(msgA[0])
428
+ .ack(msgB[0])
429
+ .queue('test-queue-v2-txn-multi-q-c')
430
+ .push([{ data: { merged: true } }])
431
+ .commit()
432
+
433
+ // Verify all operations succeeded
434
+ const resultA = await client.queue('test-queue-v2-txn-multi-q-a').batch(1).wait(false).pop()
435
+ const resultB = await client.queue('test-queue-v2-txn-multi-q-b').batch(1).wait(false).pop()
436
+ const resultC = await client.queue('test-queue-v2-txn-multi-q-c').batch(1).wait(false).pop()
437
+
438
+ return {
439
+ success: resultA.length === 0 && resultB.length === 0 && resultC.length === 1,
440
+ message: 'Transaction across multiple queues completed'
441
+ }
442
+ }
443
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "queen-mq",
3
- "version": "0.2.23",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "High-performance C++ message queue backed by PostgreSQL",
6
6
  "main": "client-js/client/index.js",