@zdavison/matador 2.0.2 → 2.0.4

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 (55) hide show
  1. package/dist/core/matador.d.ts +2 -2
  2. package/dist/core/matador.d.ts.map +1 -1
  3. package/dist/core/matador.js +6 -0
  4. package/dist/index.cjs +80 -32
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +3 -3
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/pipeline/pipeline.d.ts +4 -1
  12. package/dist/pipeline/pipeline.d.ts.map +1 -1
  13. package/dist/pipeline/pipeline.js +16 -4
  14. package/dist/pipeline/pipeline.test.js +15 -1
  15. package/dist/topology/builder.d.ts +14 -0
  16. package/dist/topology/builder.d.ts.map +1 -1
  17. package/dist/topology/builder.js +20 -11
  18. package/dist/topology/builder.test.js +138 -0
  19. package/dist/topology/index.d.ts +2 -2
  20. package/dist/topology/index.d.ts.map +1 -1
  21. package/dist/topology/index.js +1 -1
  22. package/dist/topology/types.d.ts +53 -1
  23. package/dist/topology/types.d.ts.map +1 -1
  24. package/dist/topology/types.js +10 -0
  25. package/dist/transport/local/local-transport.d.ts.map +1 -1
  26. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +8 -0
  27. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
  28. package/dist/transport/rabbitmq/rabbitmq-transport.js +42 -22
  29. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts +2 -0
  30. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts.map +1 -0
  31. package/dist/transport/rabbitmq/rabbitmq-transport.test.js +98 -0
  32. package/dist/types/dispatcher.d.ts +14 -0
  33. package/dist/types/dispatcher.d.ts.map +1 -0
  34. package/dist/types/dispatcher.js +1 -0
  35. package/dist/types/index.d.ts +2 -1
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/subscriber.d.ts +20 -4
  38. package/dist/types/subscriber.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/core/matador.ts +14 -1
  41. package/src/index.ts +9 -0
  42. package/src/pipeline/pipeline.test.ts +24 -2
  43. package/src/pipeline/pipeline.ts +28 -6
  44. package/src/topology/builder.test.ts +176 -0
  45. package/src/topology/builder.ts +44 -5
  46. package/src/topology/index.ts +4 -0
  47. package/src/topology/types.ts +75 -1
  48. package/src/transport/local/local-transport.ts +4 -1
  49. package/src/transport/rabbitmq/rabbitmq-transport.test.ts +118 -0
  50. package/src/transport/rabbitmq/rabbitmq-transport.ts +48 -25
  51. package/src/types/dispatcher.ts +18 -0
  52. package/src/types/index.ts +4 -0
  53. package/src/types/subscriber.ts +22 -3
  54. package/test/e2e/rabbitmq-transport.e2e.test.ts +287 -0
  55. package/tsconfig.tsbuildinfo +1 -1
@@ -11,6 +11,7 @@ import {
11
11
  RabbitMQContainer,
12
12
  type StartedRabbitMQContainer,
13
13
  } from '@testcontainers/rabbitmq';
14
+ import type { Topology } from '../../src/topology/types.js';
14
15
  import type { Subscription } from '../../src/transport/index.js';
15
16
  import { RabbitMQTransport } from '../../src/transport/rabbitmq/rabbitmq-transport.js';
16
17
  import {
@@ -221,6 +222,292 @@ describe.skipIf(SKIP_E2E)('RabbitMQ Transport E2E', () => {
221
222
  });
222
223
  });
223
224
 
225
+ describe('exact queue definitions with transport options', () => {
226
+ let transport: RabbitMQTransport;
227
+ const subscriptions: Subscription[] = [];
228
+
229
+ beforeEach(async () => {
230
+ transport = new RabbitMQTransport({
231
+ url: connectionUrl,
232
+ quorumQueues: false,
233
+ defaultPrefetch: 5,
234
+ });
235
+ await transport.connect();
236
+ });
237
+
238
+ afterEach(async () => {
239
+ for (const sub of subscriptions) {
240
+ if (sub.isActive) {
241
+ await sub.unsubscribe();
242
+ }
243
+ }
244
+ if (transport.isConnected()) {
245
+ await transport.disconnect();
246
+ }
247
+ });
248
+
249
+ it('should apply exact RabbitMQ options when asserting queue', async () => {
250
+ const namespace = `exact-opts-${Date.now()}`;
251
+ const exactQueueName = `${namespace}.shared.custom-queue`;
252
+
253
+ const topology: Topology = {
254
+ namespace,
255
+ queues: [
256
+ {
257
+ name: exactQueueName,
258
+ exact: true,
259
+ transport: {
260
+ rabbitmq: {
261
+ options: {
262
+ durable: true,
263
+ arguments: {
264
+ 'x-max-length': 1000,
265
+ 'x-message-ttl': 60000,
266
+ },
267
+ },
268
+ },
269
+ },
270
+ },
271
+ ],
272
+ deadLetter: {
273
+ unhandled: { enabled: false },
274
+ undeliverable: { enabled: false },
275
+ },
276
+ retry: {
277
+ enabled: true,
278
+ defaultDelayMs: 1000,
279
+ maxDelayMs: 30000,
280
+ },
281
+ };
282
+
283
+ // Should not throw - queue options should be applied correctly
284
+ await transport.applyTopology(topology);
285
+
286
+ // Verify queue works by sending and receiving a message
287
+ let receivedMessage = false;
288
+ const subscription = await transport.subscribe(
289
+ exactQueueName,
290
+ async (_env, receipt) => {
291
+ receivedMessage = true;
292
+ await transport.complete(receipt);
293
+ },
294
+ );
295
+ subscriptions.push(subscription);
296
+
297
+ await transport.send(exactQueueName, createTestEnvelope());
298
+ await waitFor(() => receivedMessage, 5000);
299
+
300
+ expect(receivedMessage).toBe(true);
301
+ });
302
+
303
+ it('should create retry queue for exact queue when retry is enabled', async () => {
304
+ const namespace = `exact-retry-${Date.now()}`;
305
+ const exactQueueName = `${namespace}.shared.retry-queue`;
306
+
307
+ const topology: Topology = {
308
+ namespace,
309
+ queues: [
310
+ {
311
+ name: exactQueueName,
312
+ exact: true,
313
+ transport: {
314
+ rabbitmq: {
315
+ options: {
316
+ durable: true,
317
+ },
318
+ },
319
+ },
320
+ },
321
+ ],
322
+ deadLetter: {
323
+ unhandled: { enabled: false },
324
+ undeliverable: { enabled: false },
325
+ },
326
+ retry: {
327
+ enabled: true,
328
+ defaultDelayMs: 1000,
329
+ maxDelayMs: 30000,
330
+ },
331
+ };
332
+
333
+ // Apply topology - should create both work queue and retry queue
334
+ await transport.applyTopology(topology);
335
+
336
+ // Verify the main queue works
337
+ let receivedCount = 0;
338
+ const subscription = await transport.subscribe(
339
+ exactQueueName,
340
+ async (_env, receipt) => {
341
+ receivedCount++;
342
+ await transport.complete(receipt);
343
+ },
344
+ );
345
+ subscriptions.push(subscription);
346
+
347
+ await transport.send(exactQueueName, createTestEnvelope());
348
+ await waitFor(() => receivedCount === 1, 5000);
349
+
350
+ expect(receivedCount).toBe(1);
351
+ });
352
+
353
+ it('should use custom dead letter exchange from exact options', async () => {
354
+ const namespace = `exact-dlx-${Date.now()}`;
355
+ const exactQueueName = `${namespace}.shared.dlx-queue`;
356
+ const customDlxExchange = `${namespace}.custom-dlx`;
357
+
358
+ // First create the custom DLX exchange manually
359
+ // (In real usage, this would be managed externally)
360
+
361
+ const topology: Topology = {
362
+ namespace,
363
+ queues: [
364
+ {
365
+ name: exactQueueName,
366
+ exact: true,
367
+ transport: {
368
+ rabbitmq: {
369
+ options: {
370
+ durable: true,
371
+ deadLetterExchange: customDlxExchange,
372
+ },
373
+ },
374
+ },
375
+ },
376
+ ],
377
+ deadLetter: {
378
+ unhandled: { enabled: false },
379
+ undeliverable: { enabled: false },
380
+ },
381
+ retry: {
382
+ enabled: false,
383
+ defaultDelayMs: 1000,
384
+ maxDelayMs: 30000,
385
+ },
386
+ };
387
+
388
+ // Should not throw - custom DLX should be set
389
+ await transport.applyTopology(topology);
390
+
391
+ // Verify queue works
392
+ let receivedMessage = false;
393
+ const subscription = await transport.subscribe(
394
+ exactQueueName,
395
+ async (_env, receipt) => {
396
+ receivedMessage = true;
397
+ await transport.complete(receipt);
398
+ },
399
+ );
400
+ subscriptions.push(subscription);
401
+
402
+ await transport.send(exactQueueName, createTestEnvelope());
403
+ await waitFor(() => receivedMessage, 5000);
404
+
405
+ expect(receivedMessage).toBe(true);
406
+ });
407
+
408
+ it('should apply RabbitMQ options without exact mode', async () => {
409
+ const namespace = `opts-no-exact-${Date.now()}`;
410
+
411
+ const topology: Topology = {
412
+ namespace,
413
+ queues: [
414
+ {
415
+ name: 'custom-options-queue',
416
+ // exact: false (default) - namespace prefix will be added
417
+ transport: {
418
+ rabbitmq: {
419
+ options: {
420
+ durable: true,
421
+ arguments: {
422
+ 'x-max-length': 500,
423
+ },
424
+ },
425
+ },
426
+ },
427
+ },
428
+ ],
429
+ deadLetter: {
430
+ unhandled: { enabled: false },
431
+ undeliverable: { enabled: false },
432
+ },
433
+ retry: {
434
+ enabled: true,
435
+ defaultDelayMs: 1000,
436
+ maxDelayMs: 30000,
437
+ },
438
+ };
439
+
440
+ await transport.applyTopology(topology);
441
+
442
+ // Queue name should have namespace prefix since exact: false
443
+ const queueName = `${namespace}.custom-options-queue`;
444
+
445
+ let receivedMessage = false;
446
+ const subscription = await transport.subscribe(
447
+ queueName,
448
+ async (_env, receipt) => {
449
+ receivedMessage = true;
450
+ await transport.complete(receipt);
451
+ },
452
+ );
453
+ subscriptions.push(subscription);
454
+
455
+ await transport.send(queueName, createTestEnvelope());
456
+ await waitFor(() => receivedMessage, 5000);
457
+
458
+ expect(receivedMessage).toBe(true);
459
+ });
460
+
461
+ it('should allow queue name with dots when exact: true', async () => {
462
+ const namespace = `exact-dots-${Date.now()}`;
463
+ const exactQueueName = 'matador.shared.id-platform.events';
464
+
465
+ const topology: Topology = {
466
+ namespace,
467
+ queues: [
468
+ {
469
+ name: exactQueueName,
470
+ exact: true,
471
+ transport: {
472
+ rabbitmq: {
473
+ options: {
474
+ durable: true,
475
+ },
476
+ },
477
+ },
478
+ },
479
+ ],
480
+ deadLetter: {
481
+ unhandled: { enabled: false },
482
+ undeliverable: { enabled: false },
483
+ },
484
+ retry: {
485
+ enabled: true,
486
+ defaultDelayMs: 1000,
487
+ maxDelayMs: 30000,
488
+ },
489
+ };
490
+
491
+ await transport.applyTopology(topology);
492
+
493
+ // Verify queue with dots in name works
494
+ let receivedMessage = false;
495
+ const subscription = await transport.subscribe(
496
+ exactQueueName,
497
+ async (_env, receipt) => {
498
+ receivedMessage = true;
499
+ await transport.complete(receipt);
500
+ },
501
+ );
502
+ subscriptions.push(subscription);
503
+
504
+ await transport.send(exactQueueName, createTestEnvelope());
505
+ await waitFor(() => receivedMessage, 5000);
506
+
507
+ expect(receivedMessage).toBe(true);
508
+ });
509
+ });
510
+
224
511
  describe('channel per queue isolation', () => {
225
512
  let transport: RabbitMQTransport;
226
513
  const subscriptions: Subscription[] = [];