queen-mq 0.1.2 → 0.1.3

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.
@@ -22,7 +22,7 @@ export async function syncSystemEvents(client, eventManager, consumerGroup) {
22
22
  }
23
23
 
24
24
  for (const message of result.messages) {
25
- await eventManager.processSystemEvent(message.payload);
25
+ await eventManager.processSystemEvent(message.data);
26
26
  await client.ack(message.transactionId, 'completed', null, consumerGroup);
27
27
  processed++;
28
28
  }
@@ -1000,7 +1000,7 @@ export async function testMultipleConsumersSinglePartitionOrdering(client) {
1000
1000
  limit: batchSize,
1001
1001
  batch: batchSize
1002
1002
  })) {
1003
- const sequenceNumber = msg.payload.sequenceNumber;
1003
+ const sequenceNumber = msg.data.sequenceNumber;
1004
1004
  consumerMessages.push({
1005
1005
  consumerId,
1006
1006
  sequenceNumber,
@@ -340,3 +340,113 @@ export async function testPartitionFIFOOrdering(client) {
340
340
  }
341
341
  }
342
342
 
343
+ /**
344
+ * Test 8: Window Buffer
345
+ * Verifies that windowBuffer delays message consumption across all access modes
346
+ */
347
+ export async function testWindowBuffer(client) {
348
+ startTest('Window Buffer');
349
+
350
+ try {
351
+ const timestamp = Date.now();
352
+ const queueName = 'test-window-buffer-' + timestamp;
353
+ const partition = 'test-partition';
354
+
355
+ // Test CASE 1: Direct partition access
356
+ await client.queue(queueName, { windowBuffer: 5 });
357
+
358
+ // Push a message
359
+ await client.push(`${queueName}/${partition}`, { test: 'case1' });
360
+
361
+ // Try to take immediately - should get nothing
362
+ let gotImmediate = false;
363
+ for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
364
+ gotImmediate = true;
365
+ await client.ack(msg);
366
+ }
367
+
368
+ if (gotImmediate) {
369
+ throw new Error('Got message immediately in direct access - windowBuffer not working');
370
+ }
371
+ log('Direct partition access: windowBuffer correctly delayed message');
372
+
373
+ // Wait for window to pass
374
+ await sleep(6000);
375
+
376
+ // Now should get the message
377
+ let gotDelayed = false;
378
+ for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
379
+ gotDelayed = true;
380
+ await client.ack(msg);
381
+ }
382
+
383
+ if (!gotDelayed) {
384
+ throw new Error('Did not get message after window expired');
385
+ }
386
+ log('Message available after window buffer expired');
387
+
388
+ // Test CASE 2: Queue-level access
389
+ const queue2 = 'test-window-buffer-q2-' + timestamp;
390
+ await client.queue(queue2, { windowBuffer: 5 });
391
+ await client.push(`${queue2}/p1`, { test: 'case2' });
392
+
393
+ let gotCase2Immediate = false;
394
+ for await (const msg of client.take(queue2, { limit: 1 })) {
395
+ gotCase2Immediate = true;
396
+ await client.ack(msg);
397
+ }
398
+
399
+ if (gotCase2Immediate) {
400
+ throw new Error('Got message immediately in queue access - windowBuffer not working');
401
+ }
402
+ log('Queue-level access: windowBuffer correctly delayed message');
403
+
404
+ await sleep(6000);
405
+
406
+ let gotCase2Delayed = false;
407
+ for await (const msg of client.take(queue2, { limit: 1 })) {
408
+ gotCase2Delayed = true;
409
+ await client.ack(msg);
410
+ }
411
+
412
+ if (!gotCase2Delayed) {
413
+ throw new Error('Did not get message after window expired (queue access)');
414
+ }
415
+
416
+ // Test CASE 3: Namespace/task filtered access
417
+ const ns = `test-ns-${timestamp}`;
418
+ const task = `test-task-${timestamp}`;
419
+ const queue3 = 'test-window-buffer-filtered-' + timestamp;
420
+
421
+ await client.queue(queue3, { windowBuffer: 5 }, ns, task);
422
+ await client.push(`${queue3}/p1`, { test: 'case3' });
423
+
424
+ let gotCase3Immediate = false;
425
+ for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
426
+ gotCase3Immediate = true;
427
+ await client.ack(msg);
428
+ }
429
+
430
+ if (gotCase3Immediate) {
431
+ throw new Error('Got message immediately in filtered access - windowBuffer not working');
432
+ }
433
+ log('Namespace/task filtered access: windowBuffer correctly delayed message');
434
+
435
+ await sleep(6000);
436
+
437
+ let gotCase3Delayed = false;
438
+ for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
439
+ gotCase3Delayed = true;
440
+ await client.ack(msg);
441
+ }
442
+
443
+ if (!gotCase3Delayed) {
444
+ throw new Error('Did not get message after window expired (filtered access)');
445
+ }
446
+
447
+ passTest('Window buffer works correctly across all access modes');
448
+ } catch (error) {
449
+ failTest(error);
450
+ }
451
+ }
452
+
@@ -32,7 +32,8 @@ import {
32
32
  testQueueConfiguration,
33
33
  testTakeAndAcknowledgment,
34
34
  testDelayedProcessing,
35
- testPartitionFIFOOrdering
35
+ testPartitionFIFOOrdering,
36
+ testWindowBuffer
36
37
  } from './core-tests.js';
37
38
 
38
39
  import {
@@ -168,6 +169,7 @@ async function runTests() {
168
169
  await runTest(() => testTakeAndAcknowledgment(client));
169
170
  await runTest(() => testDelayedProcessing(client));
170
171
  await runTest(() => testPartitionFIFOOrdering(client));
172
+ await runTest(() => testWindowBuffer(client));
171
173
  }
172
174
 
173
175
  // ============================================
package/src/test/test.js CHANGED
@@ -4147,7 +4147,7 @@ async function testMultipleConsumersSinglePartitionOrdering() {
4147
4147
 
4148
4148
  // Process messages and track their sequence numbers
4149
4149
  for (const msg of result.messages) {
4150
- const sequenceNumber = msg.payload.sequenceNumber;
4150
+ const sequenceNumber = msg.data.sequenceNumber;
4151
4151
  consumerMessages.push({
4152
4152
  consumerId,
4153
4153
  sequenceNumber,
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Test to validate windowBuffer functionality
3
+ * Ensures windowBuffer works correctly for all access modes:
4
+ * - Direct partition access
5
+ * - Queue-level access
6
+ * - Namespace/task filtered access
7
+ */
8
+
9
+ import { Queen } from '../client/index.js';
10
+ import { sleep } from './utils.js';
11
+
12
+ const client = new Queen({
13
+ baseUrl: 'http://localhost:6632'
14
+ });
15
+
16
+ async function testWindowBuffer() {
17
+ console.log('\n🧪 Testing windowBuffer functionality...\n');
18
+
19
+ const timestamp = Date.now();
20
+ const queueName = 'test-window-buffer-' + timestamp;
21
+ const partition = 'test-partition';
22
+
23
+ // Delete queue if it exists (cleanup from previous runs)
24
+ try {
25
+ await client.queueDelete(queueName);
26
+ console.log(`0. Deleted existing queue "${queueName}" (cleanup)`);
27
+ } catch (err) {
28
+ // Queue doesn't exist, that's fine
29
+ }
30
+
31
+ // Configure queue with 10 second window buffer
32
+ console.log(`1. Creating queue "${queueName}" with windowBuffer: 10 seconds`);
33
+ await client.queue(queueName, {
34
+ windowBuffer: 10
35
+ });
36
+
37
+ // Push first message
38
+ console.log('2. Pushing first message at', new Date().toISOString());
39
+ await client.push(`${queueName}/${partition}`, { message: 'Message 1' });
40
+
41
+ // Wait 2 seconds and push second message
42
+ await sleep(2000);
43
+ console.log('3. Pushing second message at', new Date().toISOString());
44
+ await client.push(`${queueName}/${partition}`, { message: 'Message 2' });
45
+
46
+ // Try to pop immediately (should get NOTHING due to windowBuffer)
47
+ console.log('\n4. Trying to pop immediately (should get NOTHING due to 10s windowBuffer)...');
48
+
49
+ // Test CASE 1: Direct partition access
50
+ console.log('\n TEST CASE 1: Direct partition access');
51
+ let case1Result = 0;
52
+ for await (const msg of client.take(`${queueName}/${partition}`, { limit: 10, batch: 10 })) {
53
+ case1Result++;
54
+ await client.ack(msg, true);
55
+ }
56
+ const case1Status = case1Result === 0 ? '✅' : '❌';
57
+ console.log(` ${case1Status} DIRECT ACCESS: Got ${case1Result} messages (expected 0)`);
58
+
59
+ // Test CASE 2: Queue-level access
60
+ console.log('\n TEST CASE 2: Queue-level access');
61
+ let case2Result = 0;
62
+ for await (const msg of client.take(queueName, { limit: 10, batch: 10 })) {
63
+ case2Result++;
64
+ await client.ack(msg, true);
65
+ }
66
+ const case2Status = case2Result === 0 ? '✅' : '❌';
67
+ console.log(` ${case2Status} QUEUE ACCESS: Got ${case2Result} messages (expected 0)`);
68
+
69
+ // Test CASE 3: Namespace/task filtered access (should work correctly)
70
+ console.log('\n TEST CASE 3: Namespace/task filtered access');
71
+
72
+ // Use unique namespace/task per test run to avoid collisions
73
+ const testNamespace = `test-ns-${timestamp}`;
74
+ const testTask = `test-task-${timestamp}`;
75
+ const filteredQueue = `${queueName}-filtered`;
76
+
77
+ // Delete queue if it exists
78
+ try {
79
+ await client.queueDelete(filteredQueue);
80
+ } catch (err) {
81
+ // Queue doesn't exist, that's fine
82
+ }
83
+
84
+ await client.queue(filteredQueue, {
85
+ windowBuffer: 10
86
+ }, testNamespace, testTask);
87
+
88
+ await client.push(`${filteredQueue}/${partition}`, { message: 'Filtered message' });
89
+
90
+ let case3Result = 0;
91
+ for await (const msg of client.take(`namespace:${testNamespace}/task:${testTask}`, { limit: 10, batch: 10 })) {
92
+ case3Result++;
93
+ await client.ack(msg, true);
94
+ }
95
+ const case3Status = case3Result === 0 ? '✅' : '❌';
96
+ console.log(` ${case3Status} FILTERED ACCESS: Got ${case3Result} messages (expected 0)`);
97
+
98
+ console.log('\n📊 SUMMARY:');
99
+ const allPassed = case1Result === 0 && case2Result === 0 && case3Result === 0;
100
+
101
+ if (allPassed) {
102
+ console.log(' ✅ All tests PASSED! windowBuffer is working correctly in all access modes.');
103
+ } else {
104
+ console.log(' Issues found:');
105
+ if (case1Result !== 0) console.log(' ❌ Direct partition access: windowBuffer NOT enforced');
106
+ if (case2Result !== 0) console.log(' ❌ Queue-level access: windowBuffer NOT enforced');
107
+ if (case3Result !== 0) console.log(' ❌ Namespace/task filtered access: windowBuffer NOT enforced');
108
+ }
109
+
110
+ console.log('\n' + (allPassed ? '✅ Test complete. All fixed!' : '❌ Test complete. Issues remain.') + '\n');
111
+ }
112
+
113
+ testWindowBuffer().catch(console.error);
114
+
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="filter-container">
2
+ <div>
3
3
  <div class="flex flex-col sm:flex-row gap-3">
4
4
  <!-- Search by Transaction ID -->
5
5
  <div class="flex-1">
@@ -49,19 +49,6 @@
49
49
  </div>
50
50
  </template>
51
51
 
52
- <style scoped>
53
- .filter-container {
54
- background: #ffffff;
55
- border: none;
56
- box-shadow: none;
57
- border-radius: 0.75rem;
58
- padding: 1rem;
59
- }
60
-
61
- .dark .filter-container {
62
- background: #0a0d14;
63
- }
64
- </style>
65
52
 
66
53
  <script setup>
67
54
  import { ref, watch } from 'vue';
@@ -317,27 +317,27 @@ onUnmounted(() => {
317
317
  }
318
318
 
319
319
 
320
- :deep(.filter-container .input) {
320
+ .filter-flat :deep(.input) {
321
321
  background: transparent;
322
322
  border: 1px solid rgba(156, 163, 175, 0.15);
323
323
  transition: all 0.2s ease;
324
324
  }
325
325
 
326
- :deep(.filter-container .input:hover) {
326
+ .filter-flat :deep(.input:hover) {
327
327
  border-color: rgba(156, 163, 175, 0.25);
328
328
  }
329
329
 
330
- :deep(.filter-container .input:focus) {
330
+ .filter-flat :deep(.input:focus) {
331
331
  background: rgba(244, 63, 94, 0.02);
332
332
  border-color: rgba(244, 63, 94, 0.4);
333
333
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.05);
334
334
  }
335
335
 
336
- .dark :deep(.filter-container .input) {
336
+ .dark .filter-flat :deep(.input) {
337
337
  border-color: rgba(156, 163, 175, 0.1);
338
338
  }
339
339
 
340
- .dark :deep(.filter-container .input:focus) {
340
+ .dark .filter-flat :deep(.input:focus) {
341
341
  background: rgba(244, 63, 94, 0.03);
342
342
  border-color: rgba(244, 63, 94, 0.5);
343
343
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.08);
@@ -18,12 +18,14 @@
18
18
  </div>
19
19
 
20
20
  <!-- Filters -->
21
- <MessageFilters
21
+ <div class="filter-flat">
22
+ <MessageFilters
22
23
  v-model:search="searchQuery"
23
24
  v-model:queue="queueFilter"
24
25
  v-model:status="statusFilter"
25
26
  :queues="queues"
26
- />
27
+ />
28
+ </div>
27
29
 
28
30
  <LoadingSpinner v-if="loading && !messages.length" />
29
31