queen-mq 0.2.0 → 0.2.11

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 (84) hide show
  1. package/README.md +165 -769
  2. package/{src → client-js}/benchmark/consumer.js +5 -3
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/{src → client-js}/client/client.js +272 -6
  5. package/{src → client-js}/client/index.js +1 -4
  6. package/{src → client-js}/client/utils/http.js +3 -2
  7. package/{src → client-js}/client/utils/retry.js +7 -1
  8. package/{src → client-js}/test/core-tests.js +7 -2
  9. package/{src → client-js}/test/edge-case-tests.js +2 -1
  10. package/client-js/test/human.js +162 -0
  11. package/client-js/test/qos0-tests.js +334 -0
  12. package/{src → client-js}/test/test-new.js +42 -0
  13. package/package.json +8 -17
  14. package/init-db.js +0 -20
  15. package/src/cluster-server.js +0 -242
  16. package/src/config.js +0 -229
  17. package/src/database/connection.js +0 -129
  18. package/src/database/poolManager.js +0 -199
  19. package/src/database/schema-v2.sql +0 -278
  20. package/src/managers/eventManager.js +0 -59
  21. package/src/managers/queueManagerOptimized.js +0 -1634
  22. package/src/managers/resourceCache.js +0 -96
  23. package/src/managers/systemEventManager.js +0 -132
  24. package/src/routes/ack.js +0 -26
  25. package/src/routes/configure.js +0 -46
  26. package/src/routes/messages.js +0 -368
  27. package/src/routes/pop.js +0 -69
  28. package/src/routes/push.js +0 -28
  29. package/src/routes/resources.js +0 -330
  30. package/src/routes/status.js +0 -1037
  31. package/src/server.js +0 -1633
  32. package/src/test/MIGRATION_ISSUES.md +0 -174
  33. package/src/test/test.js +0 -4524
  34. package/src/utils/streaming.js +0 -231
  35. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  36. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  37. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  38. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  39. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  40. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  41. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  42. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  43. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  44. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  45. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  46. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  47. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  48. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  49. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  50. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  51. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  52. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  53. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  54. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  55. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  56. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  57. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  58. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  59. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  60. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  63. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  64. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  65. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  66. package/src/webapp-dist/index.html +0 -15
  67. package/src/websocket/wsServer.js +0 -228
  68. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  69. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  70. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  71. /package/{src → client-js}/services/encryptionService.js +0 -0
  72. /package/{src → client-js}/services/evictionService.js +0 -0
  73. /package/{src → client-js}/services/retentionService.js +0 -0
  74. /package/{src → client-js}/services/startupSync.js +0 -0
  75. /package/{src → client-js}/test/README.md +0 -0
  76. /package/{src → client-js}/test/advanced-client-tests.js +0 -0
  77. /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
  78. /package/{src → client-js}/test/bus-mode-tests.js +0 -0
  79. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  80. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  81. /package/{src → client-js}/test/utils.js +0 -0
  82. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  83. /package/{src → client-js}/utils/logger.js +0 -0
  84. /package/{src → client-js}/utils/uuid.js +0 -0
@@ -3,7 +3,8 @@ import fs from 'fs';
3
3
 
4
4
  const QUEUE_NAME = 'benchmark-queue-001';
5
5
  const NUMBER_OF_CONSUMERS = 10;
6
- const BATCH_SIZE = 10;
6
+ const STARTING_PARTITION = parseInt(process.argv[2]) || 0;
7
+ const BATCH_SIZE = 10000;
7
8
  const CONSUME_MODE = 'partition';
8
9
 
9
10
  // Global metrics tracking
@@ -21,7 +22,7 @@ const metrics = {
21
22
 
22
23
  async function consumer(consumerId, partition) {
23
24
  const q = new Queen({
24
- baseUrls: ['http://localhost:6632'],
25
+ baseUrls: [ 'http://localhost:6632'],
25
26
  timeout: 30000
26
27
  });
27
28
 
@@ -126,8 +127,9 @@ metrics.startTime = Date.now();
126
127
 
127
128
  // Launch all consumers
128
129
  const consumerPromises = [];
130
+ //
129
131
  for (let i = 0; i < NUMBER_OF_CONSUMERS; i++) {
130
- consumerPromises.push(consumer(i, i));
132
+ consumerPromises.push(consumer(i, i ));
131
133
  }
132
134
 
133
135
  // Wait for all consumers to complete
@@ -1,9 +1,9 @@
1
1
  import { Queen } from '../client/client.js';
2
2
  import fs from 'fs';
3
3
 
4
- const TOTAL_MESSAGES = 100000;
4
+ const TOTAL_MESSAGES = 1000000;
5
5
  const PARTITIONS = 10;
6
- const MAX_BATCH_SIZE = 1000; // Reduced from 10000
6
+ const MAX_BATCH_SIZE = 10000; // Reduced from 10000
7
7
  const PUSH_PARALLEL = 10; // Reduced from 10 to avoid lock contention
8
8
  const QUEUE_NAME = 'benchmark-queue-001';
9
9
 
@@ -12,11 +12,16 @@ const q = new Queen({
12
12
  timeout: 120000
13
13
  });
14
14
 
15
- //await q.queueDelete(QUEUE_NAME);
16
- await q.queue(QUEUE_NAME, {
15
+
16
+ try {
17
+ await q.queueDelete(QUEUE_NAME);
18
+ await q.queue(QUEUE_NAME, {
17
19
  leaseTime: 10,
18
20
  retryLimit: 3
19
21
  }, { namespace: 'benchmark' });
22
+ } catch (e) {
23
+ console.error(e);
24
+ }
20
25
 
21
26
 
22
27
 
@@ -1,6 +1,7 @@
1
1
  import { createHttpClient, createLoadBalancedHttpClient } from './utils/http.js';
2
2
  import { withRetry } from './utils/retry.js';
3
3
  import { createLoadBalancer, LoadBalancingStrategy } from './utils/loadBalancer.js';
4
+ import { generateUUID } from '../utils/uuid.js';
4
5
 
5
6
  /**
6
7
  * Minimalist Queen Message Queue Client
@@ -16,6 +17,7 @@ export class Queen {
16
17
  #http;
17
18
  #loadBalancer;
18
19
  #connected = false;
20
+ #bufferManager;
19
21
 
20
22
  constructor(config = {}) {
21
23
  this.#config = {
@@ -27,6 +29,7 @@ export class Queen {
27
29
  retryDelay: 1000,
28
30
  ...config
29
31
  };
32
+ this.#bufferManager = new BufferManager(this);
30
33
  }
31
34
 
32
35
  /**
@@ -146,7 +149,7 @@ export class Queen {
146
149
  * Push messages to a queue
147
150
  * @param {string} address - Queue address (e.g., "myqueue" or "myqueue/partition")
148
151
  * @param {Object|Array} payload - Single message or array of messages
149
- * @param {Object} options - Optional message properties { transactionId, traceId }
152
+ * @param {Object} options - Optional message properties { transactionId, traceId, buffer }
150
153
  */
151
154
  async push(address, payload, options = {}) {
152
155
  await this.#ensureConnected();
@@ -167,7 +170,7 @@ export class Queen {
167
170
  queue,
168
171
  partition,
169
172
  payload: item._payload || item,
170
- transactionId: item._transactionId || item.transactionId
173
+ transactionId: item._transactionId || item.transactionId || generateUUID() // Always generate if missing
171
174
  };
172
175
 
173
176
  // Include traceId if provided and valid UUID
@@ -184,7 +187,9 @@ export class Queen {
184
187
  queue,
185
188
  partition,
186
189
  payload: item,
187
- transactionId: options.transactionId || (item && typeof item === 'object' ? item.transactionId : undefined)
190
+ transactionId: options.transactionId ||
191
+ (item && typeof item === 'object' ? item.transactionId : undefined) ||
192
+ generateUUID() // Always generate if missing
188
193
  };
189
194
 
190
195
  // Include traceId if provided and valid UUID
@@ -196,10 +201,26 @@ export class Queen {
196
201
  return result;
197
202
  });
198
203
 
204
+ // Check if client-side buffering is enabled
205
+ if (options.buffer && typeof options.buffer === 'object') {
206
+ return this.#pushBuffered(address, formattedItems, options.buffer);
207
+ }
208
+
209
+ // Build request body for immediate push
210
+ const requestBody = { items: formattedItems };
211
+
212
+ // Add server-side buffering flag if specified
213
+ if (options.buffer === true || options.qos0 === true) {
214
+ requestBody.qos0 = true; // Server still uses 'qos0' internally
215
+ }
216
+
217
+ // Legacy support: bufferMs/bufferMax are aliases for buffer
218
+ if (options.bufferMs !== undefined || options.bufferMax !== undefined) {
219
+ requestBody.qos0 = true;
220
+ }
221
+
199
222
  const result = await withRetry(
200
- () => this.#http.post('/api/v1/push', {
201
- items: formattedItems
202
- }),
223
+ () => this.#http.post('/api/v1/push', requestBody),
203
224
  this.#config.retryAttempts,
204
225
  this.#config.retryDelay
205
226
  );
@@ -211,6 +232,25 @@ export class Queen {
211
232
  return result;
212
233
  }
213
234
 
235
+ /**
236
+ * Push messages using client-side buffering
237
+ * @private
238
+ */
239
+ #pushBuffered(address, formattedItems, bufferOptions) {
240
+ // Validate buffer options
241
+ if (!bufferOptions.size || !bufferOptions.time) {
242
+ throw new Error('Buffer options must include both size and time');
243
+ }
244
+
245
+ // Add each formatted message to the buffer
246
+ for (const item of formattedItems) {
247
+ this.#bufferManager.addMessage(address, item, bufferOptions);
248
+ }
249
+
250
+ // Return immediately (non-blocking)
251
+ return { buffered: true, count: formattedItems.length };
252
+ }
253
+
214
254
  /**
215
255
  * Internal method that yields batches of messages (arrays)
216
256
  * @private
@@ -266,6 +306,9 @@ export class Queen {
266
306
  if (subscriptionMode) params.append('subscriptionMode', subscriptionMode);
267
307
  if (subscriptionFrom) params.append('subscriptionFrom', subscriptionFrom);
268
308
 
309
+ // Add auto-ack option if specified
310
+ if (options.autoAck) params.append('autoAck', 'true');
311
+
269
312
  // Determine the endpoint based on parameters
270
313
  if (queue) {
271
314
  if (partition && partition !== 'Default') {
@@ -668,10 +711,45 @@ export class Queen {
668
711
  return result;
669
712
  }
670
713
 
714
+ /**
715
+ * Manually flush a specific buffer
716
+ * @param {string} address - Queue address to flush
717
+ * @returns {Promise<void>}
718
+ */
719
+ async flushBuffer(address) {
720
+ return this.#bufferManager.flushBuffer(address);
721
+ }
722
+
723
+ /**
724
+ * Manually flush all buffers
725
+ * @returns {Promise<void>}
726
+ */
727
+ async flushAllBuffers() {
728
+ return this.#bufferManager.flushAllBuffers();
729
+ }
730
+
731
+ /**
732
+ * Get buffer statistics
733
+ * @returns {Object} Buffer statistics
734
+ */
735
+ getBufferStats() {
736
+ return this.#bufferManager.getStats();
737
+ }
738
+
671
739
  /**
672
740
  * Close the client connection
673
741
  */
674
742
  async close() {
743
+ // Flush all buffers before closing
744
+ try {
745
+ await this.#bufferManager.flushAllBuffers();
746
+ } catch (error) {
747
+ console.warn('Error flushing buffers during close:', error);
748
+ }
749
+
750
+ // Cleanup buffer manager
751
+ this.#bufferManager.cleanup();
752
+
675
753
  this.#connected = false;
676
754
  this.#http = null;
677
755
  this.#loadBalancer = null;
@@ -702,6 +780,194 @@ export class Queen {
702
780
  get _http() {
703
781
  return this.#http;
704
782
  }
783
+
784
+ get _config() {
785
+ return this.#config;
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Message Buffer for client-side buffering
791
+ */
792
+ class MessageBuffer {
793
+ constructor(address, options, flushCallback) {
794
+ this.address = address;
795
+ this.messages = [];
796
+ this.options = options;
797
+ this.flushCallback = flushCallback;
798
+ this.timer = null;
799
+ this.firstMessageTime = null;
800
+ this.flushing = false;
801
+ }
802
+
803
+ add(formattedMessage) {
804
+ // Set first message time if this is the first message
805
+ if (this.messages.length === 0) {
806
+ this.firstMessageTime = Date.now();
807
+ this.#startTimer();
808
+ }
809
+
810
+ this.messages.push(formattedMessage);
811
+
812
+ // Check if we should flush based on size
813
+ if (this.messages.length >= this.options.size) {
814
+ this.#triggerFlush();
815
+ }
816
+ }
817
+
818
+ #startTimer() {
819
+ if (this.timer) return; // Timer already running
820
+
821
+ this.timer = setTimeout(() => {
822
+ this.#triggerFlush();
823
+ }, this.options.time);
824
+ }
825
+
826
+ #triggerFlush() {
827
+ if (this.flushing || this.messages.length === 0) return;
828
+
829
+ // Clear timer
830
+ if (this.timer) {
831
+ clearTimeout(this.timer);
832
+ this.timer = null;
833
+ }
834
+
835
+ // Trigger flush via callback
836
+ this.flushCallback(this.address);
837
+ }
838
+
839
+ extractMessages() {
840
+ const messages = [...this.messages];
841
+ this.messages = [];
842
+ this.firstMessageTime = null;
843
+ this.flushing = false;
844
+
845
+ if (this.timer) {
846
+ clearTimeout(this.timer);
847
+ this.timer = null;
848
+ }
849
+
850
+ return messages;
851
+ }
852
+
853
+ cleanup() {
854
+ if (this.timer) {
855
+ clearTimeout(this.timer);
856
+ this.timer = null;
857
+ }
858
+ this.messages = [];
859
+ this.firstMessageTime = null;
860
+ this.flushing = false;
861
+ }
862
+ }
863
+
864
+ /**
865
+ * Buffer Manager for client-side message buffering
866
+ */
867
+ class BufferManager {
868
+ constructor(client) {
869
+ this.client = client;
870
+ this.buffers = new Map(); // address -> MessageBuffer
871
+ this.flushCount = 0;
872
+ }
873
+
874
+ addMessage(address, formattedMessage, bufferOptions) {
875
+ const bufferKey = address;
876
+
877
+ if (!this.buffers.has(bufferKey)) {
878
+ this.buffers.set(bufferKey, new MessageBuffer(
879
+ address,
880
+ bufferOptions,
881
+ (addr) => this.#flushBuffer(addr)
882
+ ));
883
+ }
884
+
885
+ const buffer = this.buffers.get(bufferKey);
886
+ buffer.add(formattedMessage);
887
+ }
888
+
889
+ async #flushBuffer(address) {
890
+ const buffer = this.buffers.get(address);
891
+ if (!buffer || buffer.flushing || buffer.messages.length === 0) {
892
+ return;
893
+ }
894
+
895
+ buffer.flushing = true;
896
+
897
+ try {
898
+ const messages = buffer.extractMessages();
899
+
900
+ if (messages.length === 0) return;
901
+
902
+ // Call the original push logic with the buffered messages
903
+ const requestBody = { items: messages };
904
+
905
+ await withRetry(
906
+ () => this.client._http.post('/api/v1/push', requestBody),
907
+ this.client._config.retryAttempts,
908
+ this.client._config.retryDelay
909
+ );
910
+
911
+ this.flushCount++;
912
+
913
+ // Call onFlush callback if provided
914
+ if (buffer.options.onFlush) {
915
+ buffer.options.onFlush(address, messages.length);
916
+ }
917
+
918
+ // Remove empty buffer
919
+ this.buffers.delete(address);
920
+
921
+ } catch (error) {
922
+ buffer.flushing = false;
923
+
924
+ // Call onError callback if provided
925
+ if (buffer.options.onError) {
926
+ buffer.options.onError(address, error);
927
+ }
928
+
929
+ throw error;
930
+ }
931
+ }
932
+
933
+ async flushBuffer(address) {
934
+ return this.#flushBuffer(address);
935
+ }
936
+
937
+ async flushAllBuffers() {
938
+ const flushPromises = [];
939
+ for (const address of this.buffers.keys()) {
940
+ flushPromises.push(this.#flushBuffer(address));
941
+ }
942
+ await Promise.all(flushPromises);
943
+ }
944
+
945
+ getStats() {
946
+ let totalBufferedMessages = 0;
947
+ let oldestBufferAge = 0;
948
+
949
+ for (const buffer of this.buffers.values()) {
950
+ totalBufferedMessages += buffer.messages.length;
951
+ if (buffer.firstMessageTime) {
952
+ const age = Date.now() - buffer.firstMessageTime;
953
+ oldestBufferAge = Math.max(oldestBufferAge, age);
954
+ }
955
+ }
956
+
957
+ return {
958
+ activeBuffers: this.buffers.size,
959
+ totalBufferedMessages,
960
+ oldestBufferAge,
961
+ flushesPerformed: this.flushCount
962
+ };
963
+ }
964
+
965
+ cleanup() {
966
+ for (const buffer of this.buffers.values()) {
967
+ buffer.cleanup();
968
+ }
969
+ this.buffers.clear();
970
+ }
705
971
  }
706
972
 
707
973
  /**
@@ -1,7 +1,4 @@
1
1
  export { LoadBalancingStrategy } from './utils/loadBalancer.js';
2
2
 
3
3
  // New minimalist interface
4
- export { Queen } from './client.js';
5
-
6
- // Server
7
- export { QueenServer } from '../server.js';
4
+ export { Queen } from './client.js';
@@ -1,4 +1,5 @@
1
1
  export const httpRequest = async (url, options = {}) => {
2
+ //console.log('httpRequest', url, options);
2
3
  const response = await fetch(url, {
3
4
  ...options,
4
5
  headers: {
@@ -6,7 +7,7 @@ export const httpRequest = async (url, options = {}) => {
6
7
  ...options.headers
7
8
  }
8
9
  });
9
-
10
+ //console.log('response', response);
10
11
  // Handle 204 No Content
11
12
  if (response.status === 204) {
12
13
  return null; // No content
@@ -143,7 +144,7 @@ export const createLoadBalancedHttpClient = ({ baseUrls, loadBalancer, timeout =
143
144
  return await executeRequest(url);
144
145
  } catch (error) {
145
146
  lastError = error;
146
- console.warn(`Request failed for ${url}: ${error.message}`);
147
+ console.warn(`Request failed for ${url}: ${method} ${path} - ${error.message}`);
147
148
 
148
149
  // Don't retry on client errors (4xx)
149
150
  if (error.status && error.status >= 400 && error.status < 500) {
@@ -1,4 +1,10 @@
1
- import config from '../../config.js';
1
+ const config = {
2
+ CLIENT: {
3
+ DEFAULT_RETRY_ATTEMPTS: 3,
4
+ DEFAULT_RETRY_DELAY: 1000,
5
+ DEFAULT_RETRY_BACKOFF: 2
6
+ }
7
+ }
2
8
 
3
9
  export const withRetry = async (fn, attempts = config.CLIENT.DEFAULT_RETRY_ATTEMPTS, delay = config.CLIENT.DEFAULT_RETRY_DELAY, backoff = config.CLIENT.DEFAULT_RETRY_BACKOFF) => {
4
10
  let lastError;
@@ -113,19 +113,23 @@ export async function testBatchMessagePush(client) {
113
113
 
114
114
  // Push batch
115
115
  await client.push(`${queue}/batch-partition`, messages);
116
-
116
+
117
117
  // Verify messages were stored
118
118
  const count = await getMessageCount(queue, 'batch-partition');
119
+
119
120
  if (count !== batchSize) {
120
121
  throw new Error(`Expected ${batchSize} messages in database, got ${count}`);
121
122
  }
122
-
123
+
123
124
  // Take all messages
124
125
  const received = [];
125
126
  for await (const msg of client.take(`${queue}/batch-partition`, { limit: batchSize })) {
126
127
  received.push(msg);
128
+
127
129
  await client.ack(msg);
128
130
  }
131
+
132
+
129
133
 
130
134
  if (received.length !== batchSize) {
131
135
  throw new Error(`Expected ${batchSize} messages, got ${received.length}`);
@@ -233,6 +237,7 @@ export async function testDelayedProcessing(client) {
233
237
  await client.queue(queue, {
234
238
  delayedProcessing: 2 // 2 seconds delay
235
239
  });
240
+
236
241
 
237
242
  const startTime = Date.now();
238
243
 
@@ -105,10 +105,11 @@ export async function testVeryLargePayloads(client) {
105
105
 
106
106
  // Take and verify
107
107
  let received = null;
108
- for await (const msg of client.take(queue, { limit: 1 })) {
108
+ for await (const msg of client.take(queue, { limit: 1, timeout: 20000 })) {
109
109
  received = msg.data;
110
110
  await client.ack(msg);
111
111
  }
112
+
112
113
 
113
114
  if (!received || !received.array || received.array.length !== 10000) {
114
115
  throw new Error('Large payload corrupted');
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Test written by an human
3
+ */
4
+
5
+ import { startTest, passTest, failTest, sleep, log } from './utils.js';
6
+
7
+ /**
8
+ * Test: Pipeline API - Basic Processing
9
+ */
10
+ export async function testCreateQueue(client) {
11
+ startTest('Create Queue', 'human');
12
+
13
+ try {
14
+ const queue = 'test-create-queue-hm';
15
+ await client.queue(queue, { leaseTime: 30 });
16
+ passTest('Queue created successfully');
17
+ } catch (error) {
18
+ failTest('Create Queue', error );
19
+ }
20
+ }
21
+
22
+ export async function workerQueueTest(client) {
23
+ startTest('Worker Queue', 'human');
24
+
25
+ try {
26
+ const queue = 'test-worker-queue-hm';
27
+ const processedQueue = queue + '-processed';
28
+ await client.queueDelete(queue);
29
+ await client.queueDelete(processedQueue);
30
+ await client.queue(queue, { leaseTime: 30 });
31
+ await client.queue(processedQueue, { leaseTime: 30 });
32
+ const pushMessages = async (count) => {
33
+ let messages = [];
34
+ for (let i = 0; i < count; i++) {
35
+ messages.push({ id: i, data: `Message ${i}` });
36
+ }
37
+ await client.push(queue, messages);
38
+ }
39
+
40
+
41
+ // Test 1
42
+ await pushMessages(10);
43
+
44
+ let counter1 = 0;
45
+ await client
46
+ .pipeline(queue)
47
+ .take(10)
48
+ .process(async (message) => {
49
+ counter1++;
50
+ return { id: message.data.id, data: message.data.data };
51
+ })
52
+ .execute();
53
+
54
+ if (counter1 !== 10) {
55
+ throw new Error('Expected 10 messages to be processed, got ' + counter1);
56
+ }
57
+
58
+ // Test 2
59
+ await pushMessages(100);
60
+
61
+ let counter2 = 0;
62
+ await client
63
+ .pipeline(queue)
64
+ .take(10)
65
+ .processBatch(async (messages) => {
66
+ counter2 += messages.length;
67
+ return messages;
68
+ })
69
+ .repeat({ maxIterations: 1, continuous: false })
70
+ .execute();
71
+
72
+ if (counter2 !== 10) {
73
+ throw new Error('Expected 10 messages to be processed, got ' + counter2);
74
+ }
75
+
76
+ // Test 3
77
+ let counter3 = 0;
78
+ await client
79
+ .pipeline(queue)
80
+ .take(10)
81
+ .processBatch(async (messages) => {
82
+ counter3 += messages.length;
83
+ return messages;
84
+ })
85
+ .repeat({ maxIterations: 9, continuous: false })
86
+ .execute();
87
+
88
+ if (counter3 !== 90) {
89
+ throw new Error('Expected 90 messages to be processed, got ' + counter3);
90
+ }
91
+
92
+ await pushMessages(100);
93
+
94
+ await client
95
+ .pipeline(queue)
96
+ .take(10)
97
+ .processBatch(async (messages) => {
98
+ return messages;
99
+ })
100
+ .atomically((tx, originalMessages, processedMessages) => {
101
+ tx.ack(originalMessages);
102
+ })
103
+ .repeat({ maxIterations: 10, continuous: true })
104
+ .execute();
105
+
106
+ const messagesPopped = await client.takeSingleBatch(queue);
107
+ let counter4 = messagesPopped.length;
108
+ if (counter4 !== 0) {
109
+ throw new Error('Expected 0 message to be processed, got ' + counter4);
110
+ }
111
+
112
+ // Test transaction that works correctly
113
+ await pushMessages(100);
114
+
115
+ await client
116
+ .pipeline(queue)
117
+ .take(10)
118
+ .processBatch(async (messages) => {
119
+ return messages;
120
+ })
121
+ .atomically((tx, originalMessages, processedMessages) => {
122
+ tx.ack(originalMessages);
123
+ tx.push(processedQueue, processedMessages);
124
+ })
125
+ .repeat({ maxIterations: 10, continuous: true })
126
+ .execute();
127
+
128
+ const messagesPopped2 = [];
129
+ for await (const messages of client.takeBatch(processedQueue, { batch: 100 })) {
130
+ messagesPopped2.push(...messages);
131
+ }
132
+ if (messagesPopped2.length !== 100) {
133
+ throw new Error('Expected 100 message to be processed, got ' + counter5);
134
+ }
135
+
136
+ // Test transaction that fails
137
+
138
+ await pushMessages(100);
139
+
140
+ try {
141
+ await client
142
+ .pipeline(queue)
143
+ .take(10)
144
+ .processBatch(async (messages) => {
145
+ return messages;
146
+ })
147
+ .atomically((tx, originalMessages, processedMessages) => {
148
+ originalMessages.map(x => x.leaseId = 'invalid-lease-id-12345');
149
+ tx.ack(originalMessages);
150
+ tx.push(processedQueue, processedMessages);
151
+ })
152
+ .repeat({ maxIterations: 10, continuous: true })
153
+ .execute();
154
+ failTest('Expected transaction to fail, got ' + error);
155
+ } catch (error) {}
156
+
157
+ passTest('Worker Queue test passed');
158
+ } catch (error) {
159
+ console.error(error);
160
+ failTest('Worker Queue test failed', error);
161
+ }
162
+ }