@zdavison/matador 2.0.9 → 2.0.10

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 (259) hide show
  1. package/dist/checkpoint/context.d.ts +59 -0
  2. package/dist/checkpoint/context.d.ts.map +1 -0
  3. package/dist/checkpoint/context.js +140 -0
  4. package/dist/checkpoint/context.test.d.ts +2 -0
  5. package/dist/checkpoint/context.test.d.ts.map +1 -0
  6. package/dist/checkpoint/context.test.js +424 -0
  7. package/dist/checkpoint/index.d.ts +7 -0
  8. package/dist/checkpoint/index.d.ts.map +1 -0
  9. package/dist/checkpoint/index.js +6 -0
  10. package/dist/checkpoint/stores/memory.d.ts +29 -0
  11. package/dist/checkpoint/stores/memory.d.ts.map +1 -0
  12. package/dist/checkpoint/stores/memory.js +39 -0
  13. package/{src/checkpoint/stores/noop.ts → dist/checkpoint/stores/noop.d.ts} +5 -13
  14. package/dist/checkpoint/stores/noop.d.ts.map +1 -0
  15. package/dist/checkpoint/stores/noop.js +18 -0
  16. package/dist/checkpoint/stores/stores.test.d.ts +2 -0
  17. package/dist/checkpoint/stores/stores.test.d.ts.map +1 -0
  18. package/dist/checkpoint/stores/stores.test.js +146 -0
  19. package/dist/checkpoint/types.d.ts +119 -0
  20. package/dist/checkpoint/types.d.ts.map +1 -0
  21. package/dist/checkpoint/types.js +1 -0
  22. package/dist/codec/codec.d.ts +29 -0
  23. package/dist/codec/codec.d.ts.map +1 -0
  24. package/dist/codec/codec.js +15 -0
  25. package/dist/codec/header-aware-codec.d.ts +36 -0
  26. package/dist/codec/header-aware-codec.d.ts.map +1 -0
  27. package/dist/codec/header-aware-codec.js +1 -0
  28. package/{src/codec/index.ts → dist/codec/index.d.ts} +2 -7
  29. package/dist/codec/index.d.ts.map +1 -0
  30. package/dist/codec/index.js +3 -0
  31. package/dist/codec/json-codec.d.ts +13 -0
  32. package/dist/codec/json-codec.d.ts.map +1 -0
  33. package/dist/codec/json-codec.js +64 -0
  34. package/dist/codec/rabbitmq-codec.d.ts +28 -0
  35. package/dist/codec/rabbitmq-codec.d.ts.map +1 -0
  36. package/dist/codec/rabbitmq-codec.js +242 -0
  37. package/dist/codec/rabbitmq-codec.test.d.ts +2 -0
  38. package/dist/codec/rabbitmq-codec.test.d.ts.map +1 -0
  39. package/dist/codec/rabbitmq-codec.test.js +433 -0
  40. package/dist/core/fanout.d.ts +59 -0
  41. package/dist/core/fanout.d.ts.map +1 -0
  42. package/dist/core/fanout.js +121 -0
  43. package/dist/core/fanout.test.d.ts +2 -0
  44. package/dist/core/fanout.test.d.ts.map +1 -0
  45. package/dist/core/fanout.test.js +1054 -0
  46. package/{src/core/index.ts → dist/core/index.d.ts} +2 -7
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +3 -0
  49. package/dist/core/matador.d.ts +133 -0
  50. package/dist/core/matador.d.ts.map +1 -0
  51. package/dist/core/matador.js +228 -0
  52. package/dist/core/matador.test.d.ts +2 -0
  53. package/dist/core/matador.test.d.ts.map +1 -0
  54. package/dist/core/matador.test.js +446 -0
  55. package/dist/core/shutdown.d.ts +78 -0
  56. package/dist/core/shutdown.d.ts.map +1 -0
  57. package/dist/core/shutdown.js +111 -0
  58. package/dist/core/shutdown.test.d.ts +2 -0
  59. package/dist/core/shutdown.test.d.ts.map +1 -0
  60. package/dist/core/shutdown.test.js +524 -0
  61. package/dist/errors/checkpoint-errors.d.ts +30 -0
  62. package/dist/errors/checkpoint-errors.d.ts.map +1 -0
  63. package/dist/errors/checkpoint-errors.js +49 -0
  64. package/dist/errors/has-description.d.ts +18 -0
  65. package/dist/errors/has-description.d.ts.map +1 -0
  66. package/dist/errors/has-description.js +9 -0
  67. package/dist/errors/index.d.ts +6 -0
  68. package/dist/errors/index.d.ts.map +1 -0
  69. package/dist/errors/index.js +22 -0
  70. package/dist/errors/matador-errors.d.ts +183 -0
  71. package/dist/errors/matador-errors.d.ts.map +1 -0
  72. package/dist/errors/matador-errors.js +376 -0
  73. package/dist/errors/retry-errors.d.ts +102 -0
  74. package/dist/errors/retry-errors.d.ts.map +1 -0
  75. package/dist/errors/retry-errors.js +155 -0
  76. package/dist/errors/retry-errors.test.d.ts +2 -0
  77. package/dist/errors/retry-errors.test.d.ts.map +1 -0
  78. package/dist/errors/retry-errors.test.js +136 -0
  79. package/dist/hooks/index.d.ts +4 -0
  80. package/dist/hooks/index.d.ts.map +1 -0
  81. package/dist/hooks/index.js +2 -0
  82. package/dist/hooks/safe-hooks.d.ts +32 -0
  83. package/dist/hooks/safe-hooks.d.ts.map +1 -0
  84. package/dist/hooks/safe-hooks.js +120 -0
  85. package/dist/hooks/types.d.ts +172 -0
  86. package/dist/hooks/types.d.ts.map +1 -0
  87. package/dist/hooks/types.js +9 -0
  88. package/dist/index.cjs +3228 -0
  89. package/dist/index.cjs.map +1 -0
  90. package/dist/index.d.cts +23 -0
  91. package/dist/index.d.ts +23 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +31 -0
  94. package/dist/index.js.map +1 -0
  95. package/{src/pipeline/index.ts → dist/pipeline/index.d.ts} +1 -0
  96. package/dist/pipeline/index.d.ts.map +1 -0
  97. package/dist/pipeline/index.js +1 -0
  98. package/dist/pipeline/pipeline.d.ts +63 -0
  99. package/dist/pipeline/pipeline.d.ts.map +1 -0
  100. package/dist/pipeline/pipeline.js +273 -0
  101. package/dist/pipeline/pipeline.test.d.ts +2 -0
  102. package/dist/pipeline/pipeline.test.d.ts.map +1 -0
  103. package/dist/pipeline/pipeline.test.js +1112 -0
  104. package/{src/retry/index.ts → dist/retry/index.d.ts} +1 -1
  105. package/dist/retry/index.d.ts.map +1 -0
  106. package/dist/retry/index.js +1 -0
  107. package/dist/retry/policy.d.ts +43 -0
  108. package/dist/retry/policy.d.ts.map +1 -0
  109. package/dist/retry/policy.js +1 -0
  110. package/dist/retry/standard-policy.d.ts +44 -0
  111. package/dist/retry/standard-policy.d.ts.map +1 -0
  112. package/dist/retry/standard-policy.js +102 -0
  113. package/dist/retry/standard-policy.test.d.ts +2 -0
  114. package/dist/retry/standard-policy.test.d.ts.map +1 -0
  115. package/dist/retry/standard-policy.test.js +196 -0
  116. package/dist/schema/index.d.ts +4 -0
  117. package/dist/schema/index.d.ts.map +1 -0
  118. package/dist/schema/index.js +2 -0
  119. package/dist/schema/registry.d.ts +63 -0
  120. package/dist/schema/registry.d.ts.map +1 -0
  121. package/dist/schema/registry.js +174 -0
  122. package/dist/schema/registry.test.d.ts +2 -0
  123. package/dist/schema/registry.test.d.ts.map +1 -0
  124. package/dist/schema/registry.test.js +278 -0
  125. package/{src/schema/types.ts → dist/schema/types.d.ts} +29 -88
  126. package/dist/schema/types.d.ts.map +1 -0
  127. package/dist/schema/types.js +74 -0
  128. package/dist/schema/types.test.d.ts +2 -0
  129. package/dist/schema/types.test.d.ts.map +1 -0
  130. package/dist/schema/types.test.js +242 -0
  131. package/dist/topology/builder.d.ts +80 -0
  132. package/dist/topology/builder.d.ts.map +1 -0
  133. package/dist/topology/builder.js +165 -0
  134. package/dist/topology/builder.test.d.ts +2 -0
  135. package/dist/topology/builder.test.d.ts.map +1 -0
  136. package/dist/topology/builder.test.js +360 -0
  137. package/dist/topology/index.d.ts +5 -0
  138. package/dist/topology/index.d.ts.map +1 -0
  139. package/dist/topology/index.js +2 -0
  140. package/dist/topology/types.d.ts +128 -0
  141. package/dist/topology/types.d.ts.map +1 -0
  142. package/dist/topology/types.js +28 -0
  143. package/dist/transport/capabilities.d.ts +66 -0
  144. package/dist/transport/capabilities.d.ts.map +1 -0
  145. package/dist/transport/capabilities.js +18 -0
  146. package/dist/transport/connection-manager.d.ts +95 -0
  147. package/dist/transport/connection-manager.d.ts.map +1 -0
  148. package/dist/transport/connection-manager.js +144 -0
  149. package/dist/transport/index.d.ts +11 -0
  150. package/dist/transport/index.d.ts.map +1 -0
  151. package/dist/transport/index.js +5 -0
  152. package/dist/transport/local/local-transport.d.ts +62 -0
  153. package/dist/transport/local/local-transport.d.ts.map +1 -0
  154. package/dist/transport/local/local-transport.js +241 -0
  155. package/dist/transport/local/local-transport.test.d.ts +2 -0
  156. package/dist/transport/local/local-transport.test.d.ts.map +1 -0
  157. package/dist/transport/local/local-transport.test.js +192 -0
  158. package/dist/transport/multi/multi-transport.d.ts +94 -0
  159. package/dist/transport/multi/multi-transport.d.ts.map +1 -0
  160. package/dist/transport/multi/multi-transport.js +184 -0
  161. package/dist/transport/multi/multi-transport.test.d.ts +2 -0
  162. package/dist/transport/multi/multi-transport.test.d.ts.map +1 -0
  163. package/dist/transport/multi/multi-transport.test.js +236 -0
  164. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +83 -0
  165. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -0
  166. package/dist/transport/rabbitmq/rabbitmq-transport.js +541 -0
  167. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts +2 -0
  168. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts.map +1 -0
  169. package/dist/transport/rabbitmq/rabbitmq-transport.test.js +100 -0
  170. package/dist/transport/transport.d.ts +152 -0
  171. package/dist/transport/transport.d.ts.map +1 -0
  172. package/dist/transport/transport.js +1 -0
  173. package/{src/types/common.ts → dist/types/common.d.ts} +8 -20
  174. package/dist/types/common.d.ts.map +1 -0
  175. package/dist/types/common.js +12 -0
  176. package/dist/types/dispatcher.d.ts +14 -0
  177. package/dist/types/dispatcher.d.ts.map +1 -0
  178. package/dist/types/dispatcher.js +1 -0
  179. package/dist/types/envelope.d.ts +131 -0
  180. package/dist/types/envelope.d.ts.map +1 -0
  181. package/dist/types/envelope.js +99 -0
  182. package/dist/types/event.d.ts +92 -0
  183. package/dist/types/event.d.ts.map +1 -0
  184. package/dist/types/event.js +26 -0
  185. package/dist/types/event.test.d.ts +2 -0
  186. package/dist/types/event.test.d.ts.map +1 -0
  187. package/dist/types/event.test.js +130 -0
  188. package/dist/types/index.d.ts +11 -0
  189. package/dist/types/index.d.ts.map +1 -0
  190. package/dist/types/index.js +4 -0
  191. package/dist/types/subscriber.d.ts +227 -0
  192. package/dist/types/subscriber.d.ts.map +1 -0
  193. package/dist/types/subscriber.js +97 -0
  194. package/package.json +6 -2
  195. package/examples/config.ts +0 -126
  196. package/examples/event.ts +0 -26
  197. package/examples/order-event.json +0 -19
  198. package/src/checkpoint/context.test.ts +0 -510
  199. package/src/checkpoint/context.ts +0 -213
  200. package/src/checkpoint/index.ts +0 -30
  201. package/src/checkpoint/stores/memory.ts +0 -47
  202. package/src/checkpoint/stores/stores.test.ts +0 -177
  203. package/src/checkpoint/types.ts +0 -147
  204. package/src/codec/codec.ts +0 -42
  205. package/src/codec/header-aware-codec.ts +0 -41
  206. package/src/codec/json-codec.ts +0 -69
  207. package/src/codec/rabbitmq-codec.test.ts +0 -516
  208. package/src/codec/rabbitmq-codec.ts +0 -336
  209. package/src/core/fanout.test.ts +0 -1350
  210. package/src/core/fanout.ts +0 -184
  211. package/src/core/matador.test.ts +0 -575
  212. package/src/core/matador.ts +0 -357
  213. package/src/core/shutdown.test.ts +0 -853
  214. package/src/core/shutdown.ts +0 -165
  215. package/src/errors/checkpoint-errors.ts +0 -62
  216. package/src/errors/has-description.ts +0 -25
  217. package/src/errors/index.ts +0 -58
  218. package/src/errors/matador-errors.ts +0 -477
  219. package/src/errors/retry-errors.test.ts +0 -175
  220. package/src/errors/retry-errors.ts +0 -188
  221. package/src/hooks/index.ts +0 -14
  222. package/src/hooks/safe-hooks.ts +0 -200
  223. package/src/hooks/types.ts +0 -226
  224. package/src/index.ts +0 -242
  225. package/src/pipeline/pipeline.test.ts +0 -1377
  226. package/src/pipeline/pipeline.ts +0 -393
  227. package/src/retry/policy.ts +0 -46
  228. package/src/retry/standard-policy.test.ts +0 -290
  229. package/src/retry/standard-policy.ts +0 -156
  230. package/src/schema/index.ts +0 -16
  231. package/src/schema/registry.test.ts +0 -339
  232. package/src/schema/registry.ts +0 -229
  233. package/src/schema/types.test.ts +0 -280
  234. package/src/topology/builder.test.ts +0 -451
  235. package/src/topology/builder.ts +0 -238
  236. package/src/topology/index.ts +0 -19
  237. package/src/topology/types.ts +0 -183
  238. package/src/transport/capabilities.ts +0 -88
  239. package/src/transport/connection-manager.ts +0 -218
  240. package/src/transport/index.ts +0 -42
  241. package/src/transport/local/local-transport.test.ts +0 -262
  242. package/src/transport/local/local-transport.ts +0 -330
  243. package/src/transport/multi/multi-transport.test.ts +0 -320
  244. package/src/transport/multi/multi-transport.ts +0 -294
  245. package/src/transport/rabbitmq/rabbitmq-transport.test.ts +0 -120
  246. package/src/transport/rabbitmq/rabbitmq-transport.ts +0 -782
  247. package/src/transport/transport.ts +0 -200
  248. package/src/types/dispatcher.ts +0 -18
  249. package/src/types/envelope.ts +0 -244
  250. package/src/types/event.test.ts +0 -157
  251. package/src/types/event.ts +0 -112
  252. package/src/types/index.ts +0 -62
  253. package/src/types/subscriber.ts +0 -333
  254. package/test/e2e/multi-transport.e2e.test.ts +0 -237
  255. package/test/e2e/rabbitmq-transport.e2e.test.ts +0 -618
  256. package/test/e2e/transport-compliance.e2e.test.ts +0 -506
  257. package/test/integration/matador.integration.test.ts +0 -634
  258. package/tsconfig.json +0 -29
  259. package/tsup.config.ts +0 -13
@@ -1,618 +0,0 @@
1
- import {
2
- afterAll,
3
- afterEach,
4
- beforeAll,
5
- beforeEach,
6
- describe,
7
- expect,
8
- it,
9
- } from 'bun:test';
10
- import {
11
- RabbitMQContainer,
12
- type StartedRabbitMQContainer,
13
- } from '@testcontainers/rabbitmq';
14
- import type { Topology } from '../../src/topology/types.js';
15
- import type { Subscription } from '../../src/transport/index.js';
16
- import { RabbitMQTransport } from '../../src/transport/rabbitmq/rabbitmq-transport.js';
17
- import {
18
- createTestEnvelope,
19
- createTestTopology,
20
- } from './transport-compliance.e2e.test.js';
21
-
22
- // Skip tests if docker is not available
23
- const SKIP_E2E = process.env.SKIP_E2E_TESTS === 'true';
24
-
25
- describe.skipIf(SKIP_E2E)('RabbitMQ Transport E2E', () => {
26
- let container: StartedRabbitMQContainer;
27
- let connectionUrl: string;
28
-
29
- beforeAll(async () => {
30
- // Start RabbitMQ container
31
- container = await new RabbitMQContainer('rabbitmq:3.13-management')
32
- .withExposedPorts(5672, 15672)
33
- .start();
34
-
35
- connectionUrl = container.getAmqpUrl();
36
- console.log(`RabbitMQ container started at ${connectionUrl}`);
37
- }, 120_000);
38
-
39
- afterAll(async () => {
40
- if (container) {
41
- await container.stop();
42
- }
43
- });
44
-
45
- describe('RabbitMQ-specific features', () => {
46
- let transport: RabbitMQTransport;
47
- let subscriptions: Subscription[] = [];
48
-
49
- beforeEach(async () => {
50
- transport = new RabbitMQTransport({
51
- url: connectionUrl,
52
- connectionName: 'matador-e2e-test',
53
- quorumQueues: false, // Use classic queues for faster tests
54
- defaultPrefetch: 5,
55
- });
56
- await transport.connect();
57
- await transport.applyTopology(createTestTopology(`test-${Date.now()}`));
58
- subscriptions = [];
59
- });
60
-
61
- afterEach(async () => {
62
- for (const sub of subscriptions) {
63
- if (sub.isActive) {
64
- await sub.unsubscribe();
65
- }
66
- }
67
- if (transport.isConnected()) {
68
- await transport.disconnect();
69
- }
70
- });
71
-
72
- it('should report correct capabilities', () => {
73
- expect(transport.name).toBe('rabbitmq');
74
- expect(transport.capabilities.deliveryModes).toContain('at-least-once');
75
- expect(transport.capabilities.deadLetterRouting).toBe('native');
76
- expect(transport.capabilities.attemptTracking).toBe(true);
77
- expect(transport.capabilities.concurrencyModel).toBe('prefetch');
78
- expect(transport.capabilities.priorities).toBe(true);
79
- // delayedMessages depends on plugin availability
80
- });
81
-
82
- it('should handle message priority', async () => {
83
- const topology = createTestTopology(`priority-${Date.now()}`);
84
- topology.queues[0] = { ...topology.queues[0], priorities: true };
85
-
86
- await transport.applyTopology(topology);
87
- const queueName = `${topology.namespace}.events`;
88
-
89
- const receivedOrder: number[] = [];
90
-
91
- const subscription = await transport.subscribe(
92
- queueName,
93
- async (env, receipt) => {
94
- receivedOrder.push((env.data as { priority: number }).priority);
95
- await transport.complete(receipt);
96
- },
97
- );
98
- subscriptions.push(subscription);
99
-
100
- // Send messages with different priorities
101
- await transport.send(
102
- queueName,
103
- createTestEnvelope({ eventKey: 'priority.1' }),
104
- { priority: 1 },
105
- );
106
- await transport.send(
107
- queueName,
108
- createTestEnvelope({ eventKey: 'priority.5' }),
109
- { priority: 5 },
110
- );
111
- await transport.send(
112
- queueName,
113
- createTestEnvelope({ eventKey: 'priority.10' }),
114
- { priority: 10 },
115
- );
116
-
117
- // Wait for all messages
118
- await waitFor(() => receivedOrder.length >= 3, 5000);
119
-
120
- // Priority is not strictly guaranteed in RabbitMQ,
121
- // but higher priority messages should generally come first
122
- expect(receivedOrder.length).toBe(3);
123
- });
124
-
125
- it('should track attempt number in headers', async () => {
126
- const topology = createTestTopology(`attempts-${Date.now()}`);
127
- await transport.applyTopology(topology);
128
- const queueName = `${topology.namespace}.events`;
129
-
130
- let attemptNumber = 0;
131
-
132
- const subscription = await transport.subscribe(
133
- queueName,
134
- async (_env, receipt) => {
135
- attemptNumber = receipt.attemptNumber;
136
- await transport.complete(receipt);
137
- },
138
- );
139
- subscriptions.push(subscription);
140
-
141
- await transport.send(queueName, createTestEnvelope());
142
-
143
- await waitFor(() => attemptNumber > 0, 5000);
144
-
145
- expect(attemptNumber).toBe(1);
146
- });
147
-
148
- it('should handle prefetch/concurrency per queue', async () => {
149
- const topology = createTestTopology(`prefetch-${Date.now()}`);
150
- await transport.applyTopology(topology);
151
- const queueName = `${topology.namespace}.events`;
152
-
153
- const processing = new Set<string>();
154
- let maxConcurrent = 0;
155
-
156
- const subscription = await transport.subscribe(
157
- queueName,
158
- async (env, receipt) => {
159
- processing.add(env.id);
160
- maxConcurrent = Math.max(maxConcurrent, processing.size);
161
-
162
- // Simulate processing time
163
- await new Promise((resolve) => setTimeout(resolve, 100));
164
-
165
- processing.delete(env.id);
166
- await transport.complete(receipt);
167
- },
168
- { concurrency: 3 },
169
- );
170
- subscriptions.push(subscription);
171
-
172
- // Send more messages than prefetch allows
173
- for (let i = 0; i < 10; i++) {
174
- await transport.send(queueName, createTestEnvelope({ id: `msg-${i}` }));
175
- }
176
-
177
- // Wait for all messages to be processed
178
- await waitFor(() => processing.size === 0, 10000);
179
-
180
- // Should never exceed prefetch
181
- expect(maxConcurrent).toBeLessThanOrEqual(3);
182
- });
183
-
184
- it('should throw when sending delayed message without plugin', async () => {
185
- // Standard RabbitMQ container doesn't have delayed message plugin
186
- const topology = createTestTopology(`delay-${Date.now()}`);
187
- await transport.applyTopology(topology);
188
- const queueName = `${topology.namespace}.events`;
189
-
190
- // Should throw because plugin is not available
191
- await expect(
192
- transport.send(queueName, createTestEnvelope(), { delay: 1000 }),
193
- ).rejects.toThrow('delayed message exchange plugin');
194
- });
195
-
196
- it('should report delayedMessages capability based on plugin', () => {
197
- // Standard RabbitMQ without plugin should report false
198
- expect(transport.capabilities.delayedMessages).toBe(false);
199
- });
200
- });
201
-
202
- describe('reconnection behavior', () => {
203
- it('should handle disconnect and reconnect', async () => {
204
- const transport = new RabbitMQTransport({
205
- url: connectionUrl,
206
- connectionName: 'matador-reconnect-test',
207
- connection: {
208
- maxReconnectAttempts: 3,
209
- initialReconnectDelay: 100,
210
- },
211
- });
212
-
213
- await transport.connect();
214
- expect(transport.isConnected()).toBe(true);
215
-
216
- await transport.disconnect();
217
- expect(transport.isConnected()).toBe(false);
218
-
219
- // Reconnect
220
- await transport.connect();
221
- expect(transport.isConnected()).toBe(true);
222
-
223
- await transport.disconnect();
224
- });
225
- });
226
-
227
- describe('exact queue definitions with transport options', () => {
228
- let transport: RabbitMQTransport;
229
- const subscriptions: Subscription[] = [];
230
-
231
- beforeEach(async () => {
232
- transport = new RabbitMQTransport({
233
- url: connectionUrl,
234
- connectionName: 'matador-exact-queue-test',
235
- quorumQueues: false,
236
- defaultPrefetch: 5,
237
- });
238
- await transport.connect();
239
- });
240
-
241
- afterEach(async () => {
242
- for (const sub of subscriptions) {
243
- if (sub.isActive) {
244
- await sub.unsubscribe();
245
- }
246
- }
247
- if (transport.isConnected()) {
248
- await transport.disconnect();
249
- }
250
- });
251
-
252
- it('should apply exact RabbitMQ options when asserting queue', async () => {
253
- const namespace = `exact-opts-${Date.now()}`;
254
- const exactQueueName = `${namespace}.shared.custom-queue`;
255
-
256
- const topology: Topology = {
257
- namespace,
258
- queues: [
259
- {
260
- name: exactQueueName,
261
- exact: true,
262
- transport: {
263
- rabbitmq: {
264
- options: {
265
- durable: true,
266
- arguments: {
267
- 'x-max-length': 1000,
268
- 'x-message-ttl': 60000,
269
- },
270
- },
271
- },
272
- },
273
- },
274
- ],
275
- deadLetter: {
276
- unhandled: { enabled: false },
277
- undeliverable: { enabled: false },
278
- },
279
- retry: {
280
- enabled: true,
281
- defaultDelayMs: 1000,
282
- maxDelayMs: 30000,
283
- },
284
- };
285
-
286
- // Should not throw - queue options should be applied correctly
287
- await transport.applyTopology(topology);
288
-
289
- // Verify queue works by sending and receiving a message
290
- let receivedMessage = false;
291
- const subscription = await transport.subscribe(
292
- exactQueueName,
293
- async (_env, receipt) => {
294
- receivedMessage = true;
295
- await transport.complete(receipt);
296
- },
297
- );
298
- subscriptions.push(subscription);
299
-
300
- await transport.send(exactQueueName, createTestEnvelope());
301
- await waitFor(() => receivedMessage, 5000);
302
-
303
- expect(receivedMessage).toBe(true);
304
- });
305
-
306
- it('should create retry queue for exact queue when retry is enabled', async () => {
307
- const namespace = `exact-retry-${Date.now()}`;
308
- const exactQueueName = `${namespace}.shared.retry-queue`;
309
-
310
- const topology: Topology = {
311
- namespace,
312
- queues: [
313
- {
314
- name: exactQueueName,
315
- exact: true,
316
- transport: {
317
- rabbitmq: {
318
- options: {
319
- durable: true,
320
- },
321
- },
322
- },
323
- },
324
- ],
325
- deadLetter: {
326
- unhandled: { enabled: false },
327
- undeliverable: { enabled: false },
328
- },
329
- retry: {
330
- enabled: true,
331
- defaultDelayMs: 1000,
332
- maxDelayMs: 30000,
333
- },
334
- };
335
-
336
- // Apply topology - should create both work queue and retry queue
337
- await transport.applyTopology(topology);
338
-
339
- // Verify the main queue works
340
- let receivedCount = 0;
341
- const subscription = await transport.subscribe(
342
- exactQueueName,
343
- async (_env, receipt) => {
344
- receivedCount++;
345
- await transport.complete(receipt);
346
- },
347
- );
348
- subscriptions.push(subscription);
349
-
350
- await transport.send(exactQueueName, createTestEnvelope());
351
- await waitFor(() => receivedCount === 1, 5000);
352
-
353
- expect(receivedCount).toBe(1);
354
- });
355
-
356
- it('should use custom dead letter exchange from exact options', async () => {
357
- const namespace = `exact-dlx-${Date.now()}`;
358
- const exactQueueName = `${namespace}.shared.dlx-queue`;
359
- const customDlxExchange = `${namespace}.custom-dlx`;
360
-
361
- // First create the custom DLX exchange manually
362
- // (In real usage, this would be managed externally)
363
-
364
- const topology: Topology = {
365
- namespace,
366
- queues: [
367
- {
368
- name: exactQueueName,
369
- exact: true,
370
- transport: {
371
- rabbitmq: {
372
- options: {
373
- durable: true,
374
- deadLetterExchange: customDlxExchange,
375
- },
376
- },
377
- },
378
- },
379
- ],
380
- deadLetter: {
381
- unhandled: { enabled: false },
382
- undeliverable: { enabled: false },
383
- },
384
- retry: {
385
- enabled: false,
386
- defaultDelayMs: 1000,
387
- maxDelayMs: 30000,
388
- },
389
- };
390
-
391
- // Should not throw - custom DLX should be set
392
- await transport.applyTopology(topology);
393
-
394
- // Verify queue works
395
- let receivedMessage = false;
396
- const subscription = await transport.subscribe(
397
- exactQueueName,
398
- async (_env, receipt) => {
399
- receivedMessage = true;
400
- await transport.complete(receipt);
401
- },
402
- );
403
- subscriptions.push(subscription);
404
-
405
- await transport.send(exactQueueName, createTestEnvelope());
406
- await waitFor(() => receivedMessage, 5000);
407
-
408
- expect(receivedMessage).toBe(true);
409
- });
410
-
411
- it('should apply RabbitMQ options without exact mode', async () => {
412
- const namespace = `opts-no-exact-${Date.now()}`;
413
-
414
- const topology: Topology = {
415
- namespace,
416
- queues: [
417
- {
418
- name: 'custom-options-queue',
419
- // exact: false (default) - namespace prefix will be added
420
- transport: {
421
- rabbitmq: {
422
- options: {
423
- durable: true,
424
- arguments: {
425
- 'x-max-length': 500,
426
- },
427
- },
428
- },
429
- },
430
- },
431
- ],
432
- deadLetter: {
433
- unhandled: { enabled: false },
434
- undeliverable: { enabled: false },
435
- },
436
- retry: {
437
- enabled: true,
438
- defaultDelayMs: 1000,
439
- maxDelayMs: 30000,
440
- },
441
- };
442
-
443
- await transport.applyTopology(topology);
444
-
445
- // Queue name should have namespace prefix since exact: false
446
- const queueName = `${namespace}.custom-options-queue`;
447
-
448
- let receivedMessage = false;
449
- const subscription = await transport.subscribe(
450
- queueName,
451
- async (_env, receipt) => {
452
- receivedMessage = true;
453
- await transport.complete(receipt);
454
- },
455
- );
456
- subscriptions.push(subscription);
457
-
458
- await transport.send(queueName, createTestEnvelope());
459
- await waitFor(() => receivedMessage, 5000);
460
-
461
- expect(receivedMessage).toBe(true);
462
- });
463
-
464
- it('should allow queue name with dots when exact: true', async () => {
465
- const namespace = `exact-dots-${Date.now()}`;
466
- const exactQueueName = 'matador.shared.id-platform.events';
467
-
468
- const topology: Topology = {
469
- namespace,
470
- queues: [
471
- {
472
- name: exactQueueName,
473
- exact: true,
474
- transport: {
475
- rabbitmq: {
476
- options: {
477
- durable: true,
478
- },
479
- },
480
- },
481
- },
482
- ],
483
- deadLetter: {
484
- unhandled: { enabled: false },
485
- undeliverable: { enabled: false },
486
- },
487
- retry: {
488
- enabled: true,
489
- defaultDelayMs: 1000,
490
- maxDelayMs: 30000,
491
- },
492
- };
493
-
494
- await transport.applyTopology(topology);
495
-
496
- // Verify queue with dots in name works
497
- let receivedMessage = false;
498
- const subscription = await transport.subscribe(
499
- exactQueueName,
500
- async (_env, receipt) => {
501
- receivedMessage = true;
502
- await transport.complete(receipt);
503
- },
504
- );
505
- subscriptions.push(subscription);
506
-
507
- await transport.send(exactQueueName, createTestEnvelope());
508
- await waitFor(() => receivedMessage, 5000);
509
-
510
- expect(receivedMessage).toBe(true);
511
- });
512
- });
513
-
514
- describe('channel per queue isolation', () => {
515
- let transport: RabbitMQTransport;
516
- const subscriptions: Subscription[] = [];
517
-
518
- beforeEach(async () => {
519
- transport = new RabbitMQTransport({
520
- url: connectionUrl,
521
- connectionName: 'matador-isolation-test',
522
- quorumQueues: false,
523
- defaultPrefetch: 2,
524
- });
525
- await transport.connect();
526
- });
527
-
528
- afterEach(async () => {
529
- for (const sub of subscriptions) {
530
- if (sub.isActive) {
531
- await sub.unsubscribe();
532
- }
533
- }
534
- if (transport.isConnected()) {
535
- await transport.disconnect();
536
- }
537
- });
538
-
539
- it('should have independent concurrency per queue', async () => {
540
- const topology = createTestTopology(`isolation-${Date.now()}`);
541
- await transport.applyTopology(topology);
542
-
543
- const queue1 = `${topology.namespace}.events`;
544
- const queue2 = `${topology.namespace}.notifications`;
545
-
546
- const queue1Processing = new Set<string>();
547
- const queue2Processing = new Set<string>();
548
- let queue1MaxConcurrent = 0;
549
- let queue2MaxConcurrent = 0;
550
-
551
- // Subscribe to both queues with different concurrency
552
- const sub1 = await transport.subscribe(
553
- queue1,
554
- async (env, receipt) => {
555
- queue1Processing.add(env.id);
556
- queue1MaxConcurrent = Math.max(
557
- queue1MaxConcurrent,
558
- queue1Processing.size,
559
- );
560
- await new Promise((resolve) => setTimeout(resolve, 50));
561
- queue1Processing.delete(env.id);
562
- await transport.complete(receipt);
563
- },
564
- { concurrency: 2 },
565
- );
566
- subscriptions.push(sub1);
567
-
568
- const sub2 = await transport.subscribe(
569
- queue2,
570
- async (env, receipt) => {
571
- queue2Processing.add(env.id);
572
- queue2MaxConcurrent = Math.max(
573
- queue2MaxConcurrent,
574
- queue2Processing.size,
575
- );
576
- await new Promise((resolve) => setTimeout(resolve, 50));
577
- queue2Processing.delete(env.id);
578
- await transport.complete(receipt);
579
- },
580
- { concurrency: 3 },
581
- );
582
- subscriptions.push(sub2);
583
-
584
- // Send messages to both queues
585
- for (let i = 0; i < 6; i++) {
586
- await transport.send(queue1, createTestEnvelope({ id: `q1-${i}` }));
587
- await transport.send(queue2, createTestEnvelope({ id: `q2-${i}` }));
588
- }
589
-
590
- // Wait for all to complete
591
- await waitFor(
592
- () => queue1Processing.size === 0 && queue2Processing.size === 0,
593
- 10000,
594
- );
595
-
596
- // Each queue should respect its own prefetch
597
- expect(queue1MaxConcurrent).toBeLessThanOrEqual(2);
598
- expect(queue2MaxConcurrent).toBeLessThanOrEqual(3);
599
- });
600
- });
601
- });
602
-
603
- /**
604
- * Waits for a condition to be true, with timeout.
605
- */
606
- async function waitFor(
607
- condition: () => boolean,
608
- timeoutMs: number,
609
- intervalMs = 50,
610
- ): Promise<void> {
611
- const startTime = Date.now();
612
- while (!condition()) {
613
- if (Date.now() - startTime > timeoutMs) {
614
- throw new Error(`Timeout waiting for condition after ${timeoutMs}ms`);
615
- }
616
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
617
- }
618
- }