queen-mq 0.3.0 → 0.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.
Files changed (39) hide show
  1. package/README.md +212 -144
  2. package/client-js/client-v2/Queen.js +28 -0
  3. package/client-js/client-v2/README.md +6 -2
  4. package/client-js/client-v2/builders/QueueBuilder.js +7 -1
  5. package/client-js/client-v2/builders/TransactionBuilder.js +20 -4
  6. package/client-js/client-v2/stream/StreamBuilder.js +72 -0
  7. package/client-js/client-v2/stream/StreamConsumer.js +156 -0
  8. package/client-js/client-v2/stream/Window.js +140 -0
  9. package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
  10. package/client-js/test-v2/consume.js +1 -0
  11. package/client-js/test-v2/maintenance.js +261 -0
  12. package/client-js/test-v2/retention.js +69 -0
  13. package/client-js/test-v2/run.js +7 -1
  14. package/package.json +2 -2
  15. package/client-js/client/client.js +0 -1536
  16. package/client-js/client/index.js +0 -4
  17. package/client-js/client/utils/http.js +0 -173
  18. package/client-js/client/utils/loadBalancer.js +0 -152
  19. package/client-js/client/utils/retry.js +0 -41
  20. package/client-js/services/encryptionService.js +0 -82
  21. package/client-js/services/evictionService.js +0 -160
  22. package/client-js/services/retentionService.js +0 -162
  23. package/client-js/services/startupSync.js +0 -35
  24. package/client-js/test/README.md +0 -224
  25. package/client-js/test/advanced-client-tests.js +0 -761
  26. package/client-js/test/advanced-pattern-tests.js +0 -1137
  27. package/client-js/test/bus-mode-tests.js +0 -361
  28. package/client-js/test/core-tests.js +0 -457
  29. package/client-js/test/edge-case-tests.js +0 -562
  30. package/client-js/test/enterprise-tests.js +0 -637
  31. package/client-js/test/human.js +0 -162
  32. package/client-js/test/partition-locking-tests.js +0 -545
  33. package/client-js/test/partition-transaction-tests.js +0 -482
  34. package/client-js/test/qos0-tests.js +0 -334
  35. package/client-js/test/test-new.js +0 -370
  36. package/client-js/test/utils.js +0 -169
  37. package/client-js/test/window-buffer-test.js +0 -114
  38. package/client-js/utils/logger.js +0 -44
  39. package/client-js/utils/uuid.js +0 -5
@@ -1,457 +0,0 @@
1
- /**
2
- * Core Feature Tests for Queen Message Queue
3
- * Tests: Queue configuration, push, take, acknowledgment, delayed processing, FIFO ordering
4
- */
5
-
6
- import { startTest, passTest, failTest, sleep, dbPool, getMessageCount, log } from './utils.js';
7
-
8
- /**
9
- * Test 1: Queue Creation Policy
10
- * Verifies that queues must be created via queue() method
11
- */
12
- export async function testQueueCreationPolicy(client) {
13
- startTest('Queue Creation Policy');
14
-
15
- try {
16
- // Test 1: Push to non-existent queue should fail
17
- const nonExistentQueue = 'queue-that-does-not-exist-' + Date.now();
18
- try {
19
- await client.push(nonExistentQueue, { test: 'should fail' });
20
- throw new Error('Push to non-existent queue should have failed');
21
- } catch (error) {
22
- if (!error.message.includes('does not exist')) {
23
- throw error;
24
- }
25
- log('Push to non-existent queue correctly failed', 'success');
26
- }
27
-
28
- // Test 2: Create queue via queue() method
29
- const testQueue = 'test-creation-policy-' + Date.now();
30
- await client.queue(testQueue, {
31
- retryLimit: 3,
32
- priority: 5
33
- }, {
34
- namespace: 'test',
35
- task: 'creation-policy'
36
- });
37
- log('Queue created via queue() method', 'success');
38
-
39
- // Test 3: Push to configured queue should succeed
40
- await client.push(testQueue, { test: 'message 1' });
41
- log('Push to configured queue succeeded', 'success');
42
-
43
- // Test 4: Push to new partition (should create on-demand)
44
- await client.push(`${testQueue}/NewPartition`, { test: 'message 2' });
45
- log('Partition created on-demand', 'success');
46
-
47
- passTest('Queue creation policy working correctly');
48
- } catch (error) {
49
- failTest(error);
50
- }
51
- }
52
-
53
- /**
54
- * Test 2: Single Message Push
55
- */
56
- export async function testSingleMessagePush(client) {
57
- startTest('Single Message Push');
58
-
59
- try {
60
- const queue = 'test-single-push';
61
-
62
- // Configure queue first
63
- await client.queue(queue, {});
64
-
65
- // Push single message
66
- await client.push(queue, {
67
- message: 'Single test message',
68
- timestamp: Date.now()
69
- });
70
-
71
- // Verify message was stored
72
- const count = await getMessageCount(queue);
73
- if (count !== 1) {
74
- throw new Error(`Expected 1 message in database, got ${count}`);
75
- }
76
-
77
- // Take and verify the message
78
- let messageReceived = false;
79
- for await (const msg of client.take(queue, { limit: 1 })) {
80
- if (msg.data && msg.data.message === 'Single test message') {
81
- messageReceived = true;
82
- await client.ack(msg);
83
- }
84
- }
85
-
86
- if (!messageReceived) {
87
- throw new Error('Failed to receive pushed message');
88
- }
89
-
90
- passTest('Single message pushed successfully');
91
- } catch (error) {
92
- failTest(error);
93
- }
94
- }
95
-
96
- /**
97
- * Test 3: Batch Message Push
98
- */
99
- export async function testBatchMessagePush(client) {
100
- startTest('Batch Message Push');
101
-
102
- try {
103
- const queue = 'test-batch-push';
104
-
105
- // Configure queue first
106
- await client.queue(queue, {});
107
-
108
- const batchSize = 5;
109
- const messages = Array.from({ length: batchSize }, (_, i) => ({
110
- message: `Batch message ${i + 1}`,
111
- index: i + 1
112
- }));
113
-
114
- // Push batch
115
- await client.push(`${queue}/batch-partition`, messages);
116
-
117
- // Verify messages were stored
118
- const count = await getMessageCount(queue, 'batch-partition');
119
-
120
- if (count !== batchSize) {
121
- throw new Error(`Expected ${batchSize} messages in database, got ${count}`);
122
- }
123
-
124
- // Take all messages
125
- const received = [];
126
- for await (const msg of client.take(`${queue}/batch-partition`, { limit: batchSize })) {
127
- received.push(msg);
128
-
129
- await client.ack(msg);
130
- }
131
-
132
-
133
-
134
- if (received.length !== batchSize) {
135
- throw new Error(`Expected ${batchSize} messages, got ${received.length}`);
136
- }
137
-
138
- passTest(`Batch of ${batchSize} messages pushed successfully`);
139
- } catch (error) {
140
- failTest(error);
141
- }
142
- }
143
-
144
- /**
145
- * Test 4: Queue Configuration
146
- */
147
- export async function testQueueConfiguration(client) {
148
- startTest('Queue Configuration');
149
-
150
- try {
151
- const queue = 'test-config-queue';
152
-
153
- await client.queue(queue, {
154
- leaseTime: 600,
155
- retryLimit: 5,
156
- priority: 8,
157
- delayedProcessing: 2,
158
- windowBuffer: 3
159
- });
160
-
161
- // Verify configuration was applied by checking database
162
- const result = await dbPool.query(`
163
- SELECT lease_time, retry_limit, priority, delayed_processing, window_buffer
164
- FROM queen.queues
165
- WHERE name = $1
166
- `, [queue]);
167
-
168
- if (result.rows.length === 0) {
169
- throw new Error('Configured queue not found in database');
170
- }
171
-
172
- const row = result.rows[0];
173
- if (row.lease_time !== 600 || row.retry_limit !== 5) {
174
- throw new Error('Configuration options not saved correctly');
175
- }
176
-
177
- passTest('Queue configuration applied successfully');
178
- } catch (error) {
179
- failTest(error);
180
- }
181
- }
182
-
183
- /**
184
- * Test 5: Take and Acknowledgment
185
- */
186
- export async function testTakeAndAcknowledgment(client) {
187
- startTest('Take and Acknowledgment');
188
-
189
- try {
190
- const queue = 'test-take-ack';
191
-
192
- // Configure queue first
193
- await client.queue(queue, {});
194
-
195
- // Push test messages
196
- await client.push(`${queue}/ack-partition`, [
197
- { message: 'Message to complete' },
198
- { message: 'Message to fail' }
199
- ]);
200
-
201
- await sleep(100);
202
-
203
- // Take messages
204
- const messages = [];
205
- for await (const msg of client.take(`${queue}/ack-partition`, { limit: 2, batch: 2 })) {
206
- messages.push(msg);
207
- }
208
-
209
- if (messages.length !== 2) {
210
- throw new Error('Expected 2 messages from take');
211
- }
212
-
213
- // Acknowledge first as completed
214
- await client.ack(messages[0], true);
215
-
216
- // Acknowledge second as failed
217
- await client.ack(messages[1], false, {
218
- error: 'Test failure'
219
- });
220
-
221
- passTest('Take and acknowledgment work correctly');
222
- } catch (error) {
223
- failTest(error);
224
- }
225
- }
226
-
227
- /**
228
- * Test 6: Delayed Processing
229
- */
230
- export async function testDelayedProcessing(client) {
231
- startTest('Delayed Processing');
232
-
233
- try {
234
- const queue = 'test-delayed-queue';
235
-
236
- // Configure queue with delayed processing
237
- await client.queue(queue, {
238
- delayedProcessing: 2 // 2 seconds delay
239
- });
240
-
241
-
242
- const startTime = Date.now();
243
-
244
- // Push message
245
- await client.push(queue, {
246
- message: 'Delayed message',
247
- sentAt: startTime
248
- });
249
-
250
- // Try to take immediately (should get nothing)
251
- let gotImmediate = false;
252
- for await (const msg of client.take(queue, { limit: 1 })) {
253
- gotImmediate = true;
254
- }
255
-
256
- if (gotImmediate) {
257
- throw new Error('Got message immediately when it should be delayed');
258
- }
259
-
260
- // Wait for delay period plus buffer
261
- await sleep(2500);
262
-
263
- // Try to take again (should get the message now)
264
- let gotDelayed = false;
265
- let processingDelay = 0;
266
-
267
- for await (const msg of client.take(queue, { limit: 1 })) {
268
- gotDelayed = true;
269
- processingDelay = Date.now() - startTime;
270
- await client.ack(msg);
271
- }
272
-
273
- if (!gotDelayed) {
274
- throw new Error('Did not get delayed message after delay period');
275
- }
276
-
277
- if (processingDelay < 2000) {
278
- throw new Error(`Message processed too early: ${processingDelay}ms`);
279
- }
280
-
281
- passTest(`Delayed processing works correctly (${processingDelay}ms delay)`);
282
- } catch (error) {
283
- failTest(error);
284
- }
285
- }
286
-
287
- /**
288
- * Test 7: FIFO Ordering Within Partitions
289
- */
290
- export async function testPartitionFIFOOrdering(client) {
291
- startTest('FIFO Ordering Within Partitions');
292
-
293
- try {
294
- const queue = 'test-partition-fifo';
295
-
296
- // Configure queue
297
- await client.queue(queue, {
298
- priority: 0
299
- });
300
-
301
- const partitions = [
302
- { name: 'ultra-high', priority: 100 },
303
- { name: 'high', priority: 10 },
304
- { name: 'medium', priority: 5 },
305
- { name: 'low', priority: 1 }
306
- ];
307
-
308
- // Create partitions manually in DB
309
- for (const partition of partitions) {
310
- await dbPool.query(`
311
- INSERT INTO queen.partitions (queue_id, name)
312
- SELECT q.id, $1
313
- FROM queen.queues q
314
- WHERE q.name = $2
315
- ON CONFLICT (queue_id, name) DO NOTHING
316
- `, [partition.name, queue]);
317
- }
318
-
319
- // Push messages to partitions
320
- for (const p of partitions) {
321
- await client.push(`${queue}/${p.name}`, {
322
- message: `${p.name} priority message`,
323
- priority: p.priority
324
- });
325
- }
326
-
327
- await sleep(100);
328
-
329
- // Take messages from different partitions
330
- const allMessages = [];
331
- for (let i = 0; i < partitions.length; i++) {
332
- for await (const msg of client.take(queue, { limit: 1, batch: 1 })) {
333
- allMessages.push(msg);
334
- await client.ack(msg);
335
- }
336
- }
337
-
338
- if (allMessages.length !== partitions.length) {
339
- throw new Error(`Expected ${partitions.length} messages, got ${allMessages.length}`);
340
- }
341
-
342
- passTest('FIFO ordering within partitions works correctly');
343
- } catch (error) {
344
- failTest(error);
345
- }
346
- }
347
-
348
- /**
349
- * Test 8: Window Buffer
350
- * Verifies that windowBuffer delays message consumption across all access modes
351
- */
352
- export async function testWindowBuffer(client) {
353
- startTest('Window Buffer');
354
-
355
- try {
356
- const timestamp = Date.now();
357
- const queueName = 'test-window-buffer-' + timestamp;
358
- const partition = 'test-partition';
359
-
360
- // Test CASE 1: Direct partition access
361
- await client.queue(queueName, { windowBuffer: 5 });
362
-
363
- // Push a message
364
- await client.push(`${queueName}/${partition}`, { test: 'case1' });
365
-
366
- // Try to take immediately - should get nothing
367
- let gotImmediate = false;
368
- for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
369
- gotImmediate = true;
370
- await client.ack(msg);
371
- }
372
-
373
- if (gotImmediate) {
374
- throw new Error('Got message immediately in direct access - windowBuffer not working');
375
- }
376
- log('Direct partition access: windowBuffer correctly delayed message');
377
-
378
- // Wait for window to pass
379
- await sleep(6000);
380
-
381
- // Now should get the message
382
- let gotDelayed = false;
383
- for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
384
- gotDelayed = true;
385
- await client.ack(msg);
386
- }
387
-
388
- if (!gotDelayed) {
389
- throw new Error('Did not get message after window expired');
390
- }
391
- log('Message available after window buffer expired');
392
-
393
- // Test CASE 2: Queue-level access
394
- const queue2 = 'test-window-buffer-q2-' + timestamp;
395
- await client.queue(queue2, { windowBuffer: 5 });
396
- await client.push(`${queue2}/p1`, { test: 'case2' });
397
-
398
- let gotCase2Immediate = false;
399
- for await (const msg of client.take(queue2, { limit: 1 })) {
400
- gotCase2Immediate = true;
401
- await client.ack(msg);
402
- }
403
-
404
- if (gotCase2Immediate) {
405
- throw new Error('Got message immediately in queue access - windowBuffer not working');
406
- }
407
- log('Queue-level access: windowBuffer correctly delayed message');
408
-
409
- await sleep(6000);
410
-
411
- let gotCase2Delayed = false;
412
- for await (const msg of client.take(queue2, { limit: 1 })) {
413
- gotCase2Delayed = true;
414
- await client.ack(msg);
415
- }
416
-
417
- if (!gotCase2Delayed) {
418
- throw new Error('Did not get message after window expired (queue access)');
419
- }
420
-
421
- // Test CASE 3: Namespace/task filtered access
422
- const ns = `test-ns-${timestamp}`;
423
- const task = `test-task-${timestamp}`;
424
- const queue3 = 'test-window-buffer-filtered-' + timestamp;
425
-
426
- await client.queue(queue3, { windowBuffer: 5 }, { namespace: ns, task: task });
427
- await client.push(`${queue3}/p1`, { test: 'case3' });
428
-
429
- let gotCase3Immediate = false;
430
- for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
431
- gotCase3Immediate = true;
432
- await client.ack(msg);
433
- }
434
-
435
- if (gotCase3Immediate) {
436
- throw new Error('Got message immediately in filtered access - windowBuffer not working');
437
- }
438
- log('Namespace/task filtered access: windowBuffer correctly delayed message');
439
-
440
- await sleep(6000);
441
-
442
- let gotCase3Delayed = false;
443
- for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
444
- gotCase3Delayed = true;
445
- await client.ack(msg);
446
- }
447
-
448
- if (!gotCase3Delayed) {
449
- throw new Error('Did not get message after window expired (filtered access)');
450
- }
451
-
452
- passTest('Window buffer works correctly across all access modes');
453
- } catch (error) {
454
- failTest(error);
455
- }
456
- }
457
-