@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,451 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
- import { TopologyBuilder, TopologyValidationError } from './builder.js';
3
- import { resolveQueueName } from './types.js';
4
-
5
- describe('TopologyBuilder', () => {
6
- describe('withNamespace', () => {
7
- it('should set the namespace', () => {
8
- const topology = TopologyBuilder.create()
9
- .withNamespace('myapp')
10
- .addQueue('events')
11
- .build();
12
-
13
- expect(topology.namespace).toBe('myapp');
14
- });
15
-
16
- it('should reject empty namespace', () => {
17
- const builder = TopologyBuilder.create()
18
- .withNamespace('')
19
- .addQueue('events');
20
-
21
- expect(() => builder.build()).toThrow(TopologyValidationError);
22
- });
23
-
24
- it('should reject namespace starting with number', () => {
25
- const builder = TopologyBuilder.create()
26
- .withNamespace('123app')
27
- .addQueue('events');
28
-
29
- expect(() => builder.build()).toThrow(TopologyValidationError);
30
- });
31
-
32
- it('should allow hyphens and underscores in namespace', () => {
33
- const topology = TopologyBuilder.create()
34
- .withNamespace('my-app_v2')
35
- .addQueue('events')
36
- .build();
37
-
38
- expect(topology.namespace).toBe('my-app_v2');
39
- });
40
- });
41
-
42
- describe('addQueue', () => {
43
- it('should add a queue with defaults', () => {
44
- const topology = TopologyBuilder.create()
45
- .withNamespace('test')
46
- .addQueue('events')
47
- .build();
48
-
49
- expect(topology.queues).toHaveLength(1);
50
- expect(topology.queues[0]?.name).toBe('events');
51
- });
52
-
53
- it('should add queue with options', () => {
54
- const topology = TopologyBuilder.create()
55
- .withNamespace('test')
56
- .addQueue('events', {
57
- concurrency: 5,
58
- consumerTimeout: 30000,
59
- priorities: true,
60
- })
61
- .build();
62
-
63
- expect(topology.queues[0]?.concurrency).toBe(5);
64
- expect(topology.queues[0]?.consumerTimeout).toBe(30000);
65
- expect(topology.queues[0]?.priorities).toBe(true);
66
- });
67
-
68
- it('should add multiple queues', () => {
69
- const topology = TopologyBuilder.create()
70
- .withNamespace('test')
71
- .addQueue('events')
72
- .addQueue('notifications')
73
- .addQueue('analytics')
74
- .build();
75
-
76
- expect(topology.queues).toHaveLength(3);
77
- });
78
-
79
- it('should reject duplicate queue names', () => {
80
- const builder = TopologyBuilder.create()
81
- .withNamespace('test')
82
- .addQueue('events')
83
- .addQueue('events');
84
-
85
- expect(() => builder.build()).toThrow('Duplicate queue name');
86
- });
87
-
88
- it('should reject empty queue name', () => {
89
- const builder = TopologyBuilder.create()
90
- .withNamespace('test')
91
- .addQueue('');
92
-
93
- expect(() => builder.build()).toThrow('Queue name cannot be empty');
94
- });
95
-
96
- it('should reject queue names starting with number', () => {
97
- const builder = TopologyBuilder.create()
98
- .withNamespace('test')
99
- .addQueue('123queue');
100
-
101
- expect(() => builder.build()).toThrow('must start with a letter');
102
- });
103
-
104
- it('should reject invalid concurrency', () => {
105
- const builder = TopologyBuilder.create()
106
- .withNamespace('test')
107
- .addQueue('events', { concurrency: 0 });
108
-
109
- expect(() => builder.build()).toThrow('concurrency must be at least 1');
110
- });
111
-
112
- it('should reject negative consumer timeout', () => {
113
- const builder = TopologyBuilder.create()
114
- .withNamespace('test')
115
- .addQueue('events', { consumerTimeout: -1 });
116
-
117
- expect(() => builder.build()).toThrow(
118
- 'consumer timeout must be non-negative',
119
- );
120
- });
121
- });
122
-
123
- describe('withDeadLetter', () => {
124
- it('should configure dead letter settings', () => {
125
- const topology = TopologyBuilder.create()
126
- .withNamespace('test')
127
- .addQueue('events')
128
- .withDeadLetter({
129
- unhandled: { enabled: false },
130
- undeliverable: { enabled: true },
131
- })
132
- .build();
133
-
134
- expect(topology.deadLetter.unhandled.enabled).toBe(false);
135
- expect(topology.deadLetter.undeliverable.enabled).toBe(true);
136
- });
137
-
138
- it('should merge with defaults', () => {
139
- const topology = TopologyBuilder.create()
140
- .withNamespace('test')
141
- .addQueue('events')
142
- .withDeadLetter({ unhandled: { enabled: false } })
143
- .build();
144
-
145
- // undeliverable should keep default
146
- expect(topology.deadLetter.unhandled.enabled).toBe(false);
147
- expect(topology.deadLetter.undeliverable.enabled).toBe(true);
148
- });
149
- });
150
-
151
- describe('withRetry', () => {
152
- it('should configure retry settings', () => {
153
- const topology = TopologyBuilder.create()
154
- .withNamespace('test')
155
- .addQueue('events')
156
- .withRetry({
157
- enabled: true,
158
- defaultDelayMs: 5000,
159
- maxDelayMs: 600000,
160
- })
161
- .build();
162
-
163
- expect(topology.retry.enabled).toBe(true);
164
- expect(topology.retry.defaultDelayMs).toBe(5000);
165
- expect(topology.retry.maxDelayMs).toBe(600000);
166
- });
167
-
168
- it('should reject negative default delay', () => {
169
- const builder = TopologyBuilder.create()
170
- .withNamespace('test')
171
- .addQueue('events')
172
- .withRetry({ defaultDelayMs: -1 });
173
-
174
- expect(() => builder.build()).toThrow('delay must be non-negative');
175
- });
176
-
177
- it('should reject max delay less than default', () => {
178
- const builder = TopologyBuilder.create()
179
- .withNamespace('test')
180
- .addQueue('events')
181
- .withRetry({
182
- defaultDelayMs: 10000,
183
- maxDelayMs: 5000,
184
- });
185
-
186
- expect(() => builder.build()).toThrow(
187
- 'Max retry delay must be greater than or equal to default',
188
- );
189
- });
190
- });
191
-
192
- describe('withoutRetry', () => {
193
- it('should disable retry', () => {
194
- const topology = TopologyBuilder.create()
195
- .withNamespace('test')
196
- .addQueue('events')
197
- .withoutRetry()
198
- .build();
199
-
200
- expect(topology.retry.enabled).toBe(false);
201
- });
202
- });
203
-
204
- describe('withoutDeadLetter', () => {
205
- it('should disable dead letter queues', () => {
206
- const topology = TopologyBuilder.create()
207
- .withNamespace('test')
208
- .addQueue('events')
209
- .withoutDeadLetter()
210
- .build();
211
-
212
- expect(topology.deadLetter.unhandled.enabled).toBe(false);
213
- expect(topology.deadLetter.undeliverable.enabled).toBe(false);
214
- });
215
- });
216
-
217
- describe('validate', () => {
218
- it('should return issues without throwing', () => {
219
- const builder = TopologyBuilder.create();
220
- const issues = builder.validate();
221
-
222
- expect(issues.length).toBeGreaterThan(0);
223
- expect(issues).toContain('Namespace is required');
224
- expect(issues).toContain('At least one queue is required');
225
- });
226
-
227
- it('should return empty array for valid topology', () => {
228
- const builder = TopologyBuilder.create()
229
- .withNamespace('test')
230
- .addQueue('events');
231
-
232
- const issues = builder.validate();
233
- expect(issues).toHaveLength(0);
234
- });
235
- });
236
-
237
- describe('build', () => {
238
- it('should throw TopologyValidationError with issues', () => {
239
- const builder = TopologyBuilder.create();
240
-
241
- try {
242
- builder.build();
243
- expect(true).toBe(false); // Should not reach here
244
- } catch (error) {
245
- expect(error).toBeInstanceOf(TopologyValidationError);
246
- expect((error as TopologyValidationError).issues).toContain(
247
- 'Namespace is required',
248
- );
249
- }
250
- });
251
-
252
- it('should return immutable topology', () => {
253
- const topology = TopologyBuilder.create()
254
- .withNamespace('test')
255
- .addQueue('events')
256
- .build();
257
-
258
- // Verify structure
259
- expect(topology.namespace).toBe('test');
260
- expect(topology.queues).toHaveLength(1);
261
- expect(topology.deadLetter.unhandled.enabled).toBe(true);
262
- expect(topology.retry.enabled).toBe(true);
263
- });
264
- });
265
-
266
- describe('exact queue option', () => {
267
- it('should mark queue as exact (external)', () => {
268
- const topology = TopologyBuilder.create()
269
- .withNamespace('test')
270
- .addQueue('external-queue', { exact: true })
271
- .build();
272
-
273
- expect(topology.queues[0]?.exact).toBe(true);
274
- });
275
-
276
- it('should allow dots in queue name when exact: true', () => {
277
- const topology = TopologyBuilder.create()
278
- .withNamespace('test')
279
- .addQueue('matador.shared.id-platform', { exact: true })
280
- .build();
281
-
282
- expect(topology.queues[0]?.name).toBe('matador.shared.id-platform');
283
- expect(topology.queues[0]?.exact).toBe(true);
284
- });
285
-
286
- it('should reject dots in queue name when exact: false', () => {
287
- const builder = TopologyBuilder.create()
288
- .withNamespace('test')
289
- .addQueue('invalid.queue.name');
290
-
291
- expect(() => builder.build()).toThrow('must start with a letter');
292
- });
293
-
294
- it('should allow transport-specific RabbitMQ options with exact queue', () => {
295
- const topology = TopologyBuilder.create()
296
- .withNamespace('test')
297
- .addQueue('matador.shared.id-platform', {
298
- exact: true,
299
- transport: {
300
- rabbitmq: {
301
- options: {
302
- durable: true,
303
- deadLetterExchange: 'matador.shared.dlx-undeliverable',
304
- arguments: {
305
- 'x-queue-type': 'quorum',
306
- },
307
- },
308
- },
309
- },
310
- })
311
- .build();
312
-
313
- expect(topology.queues[0]?.name).toBe('matador.shared.id-platform');
314
- expect(topology.queues[0]?.exact).toBe(true);
315
- expect(topology.queues[0]?.transport?.rabbitmq?.options?.durable).toBe(
316
- true,
317
- );
318
- expect(
319
- topology.queues[0]?.transport?.rabbitmq?.options?.deadLetterExchange,
320
- ).toBe('matador.shared.dlx-undeliverable');
321
- expect(
322
- topology.queues[0]?.transport?.rabbitmq?.options?.arguments?.[
323
- 'x-queue-type'
324
- ],
325
- ).toBe('quorum');
326
- });
327
- });
328
-
329
- describe('addQueue with QueueDefinition object', () => {
330
- it('should accept a QueueDefinition object', () => {
331
- const queueDef = {
332
- name: 'events',
333
- concurrency: 5,
334
- consumerTimeout: 30000,
335
- };
336
-
337
- const topology = TopologyBuilder.create()
338
- .withNamespace('test')
339
- .addQueue(queueDef)
340
- .build();
341
-
342
- expect(topology.queues).toHaveLength(1);
343
- expect(topology.queues[0]?.name).toBe('events');
344
- expect(topology.queues[0]?.concurrency).toBe(5);
345
- expect(topology.queues[0]?.consumerTimeout).toBe(30000);
346
- });
347
-
348
- it('should allow reusing queue definitions across builders', () => {
349
- const sharedQueue = {
350
- name: 'shared-queue',
351
- concurrency: 10,
352
- exact: true,
353
- };
354
-
355
- const topology1 = TopologyBuilder.create()
356
- .withNamespace('app1')
357
- .addQueue(sharedQueue)
358
- .build();
359
-
360
- const topology2 = TopologyBuilder.create()
361
- .withNamespace('app2')
362
- .addQueue(sharedQueue)
363
- .build();
364
-
365
- expect(topology1.queues[0]).toEqual(sharedQueue);
366
- expect(topology2.queues[0]).toEqual(sharedQueue);
367
- });
368
-
369
- it('should work with queue() alias', () => {
370
- const queueDef = {
371
- name: 'analytics',
372
- priorities: true,
373
- };
374
-
375
- const topology = TopologyBuilder.create()
376
- .withNamespace('test')
377
- .queue(queueDef)
378
- .build();
379
-
380
- expect(topology.queues[0]?.name).toBe('analytics');
381
- expect(topology.queues[0]?.priorities).toBe(true);
382
- });
383
-
384
- it('should mix QueueDefinition objects with name+options style', () => {
385
- const reusableQueue = {
386
- name: 'shared',
387
- concurrency: 3,
388
- };
389
-
390
- const topology = TopologyBuilder.create()
391
- .withNamespace('test')
392
- .addQueue(reusableQueue)
393
- .addQueue('local', { concurrency: 1 })
394
- .build();
395
-
396
- expect(topology.queues).toHaveLength(2);
397
- expect(topology.queues[0]?.name).toBe('shared');
398
- expect(topology.queues[1]?.name).toBe('local');
399
- });
400
-
401
- it('should validate QueueDefinition objects the same way', () => {
402
- const invalidQueue = {
403
- name: '123invalid',
404
- concurrency: 5,
405
- };
406
-
407
- const builder = TopologyBuilder.create()
408
- .withNamespace('test')
409
- .addQueue(invalidQueue);
410
-
411
- expect(() => builder.build()).toThrow('must start with a letter');
412
- });
413
- });
414
- });
415
-
416
- describe('resolveQueueName', () => {
417
- it('should return namespace.name for regular queues', () => {
418
- const queueDef = { name: 'events' };
419
- expect(resolveQueueName('myapp', queueDef)).toBe('myapp.events');
420
- });
421
-
422
- it('should return name as-is when exact: true', () => {
423
- const queueDef = { name: 'matador.shared.id-platform', exact: true };
424
- expect(resolveQueueName('myapp', queueDef)).toBe(
425
- 'matador.shared.id-platform',
426
- );
427
- });
428
-
429
- it('should return namespace.name when exact: false', () => {
430
- const queueDef = { name: 'events', exact: false };
431
- expect(resolveQueueName('myapp', queueDef)).toBe('myapp.events');
432
- });
433
-
434
- it('should work with full QueueDefinition including transport options', () => {
435
- const queueDef = {
436
- name: 'matador.shared.id-platform',
437
- exact: true,
438
- transport: {
439
- rabbitmq: {
440
- options: {
441
- durable: true,
442
- deadLetterExchange: 'matador.shared.dlx-undeliverable',
443
- },
444
- },
445
- },
446
- };
447
- expect(resolveQueueName('myapp', queueDef)).toBe(
448
- 'matador.shared.id-platform',
449
- );
450
- });
451
- });
@@ -1,238 +0,0 @@
1
- import type { HasDescription } from '../errors/index.js';
2
- import type {
3
- DeadLetterConfig,
4
- QueueDefinition,
5
- RetryConfig,
6
- Topology,
7
- } from './types.js';
8
-
9
- /**
10
- * Options for adding a queue.
11
- * Excludes 'name' as that is provided as the first argument to addQueue.
12
- */
13
- export type QueueOptions = Omit<QueueDefinition, 'name'>;
14
-
15
- /**
16
- * Type guard to check if the argument is a QueueDefinition object.
17
- */
18
- function isQueueDefinition(
19
- arg: string | QueueDefinition,
20
- ): arg is QueueDefinition {
21
- return typeof arg === 'object' && arg !== null && 'name' in arg;
22
- }
23
-
24
- /**
25
- * Error thrown when topology validation fails.
26
- */
27
- export class TopologyValidationError extends Error implements HasDescription {
28
- readonly description =
29
- 'The topology configuration is invalid. Check the issues array for ' +
30
- 'specific validation failures such as missing namespace, invalid queue ' +
31
- 'names, or conflicting settings. This error occurs during Matador ' +
32
- 'initialization and must be fixed in the configuration.';
33
-
34
- constructor(
35
- message: string,
36
- public readonly issues: readonly string[],
37
- ) {
38
- super(message);
39
- this.name = 'TopologyValidationError';
40
- }
41
- }
42
-
43
- /**
44
- * Fluent builder for creating Topology configurations.
45
- */
46
- export class TopologyBuilder {
47
- /**
48
- * Creates a new TopologyBuilder instance.
49
- */
50
- static create(): TopologyBuilder {
51
- return new TopologyBuilder();
52
- }
53
-
54
- private namespace = '';
55
- private queues: QueueDefinition[] = [];
56
- private deadLetter: DeadLetterConfig = {
57
- unhandled: { enabled: true },
58
- undeliverable: { enabled: true },
59
- };
60
- private retry: RetryConfig = {
61
- enabled: true,
62
- defaultDelayMs: 1000,
63
- maxDelayMs: 300000, // 5 minutes
64
- };
65
-
66
- /**
67
- * Sets the namespace prefix for all queues.
68
- */
69
- withNamespace(namespace: string): this {
70
- this.namespace = namespace;
71
- return this;
72
- }
73
-
74
- /**
75
- * Adds a queue to the topology.
76
- * @param definition - A complete QueueDefinition object
77
- */
78
- addQueue(definition: QueueDefinition): this;
79
- /**
80
- * Adds a queue to the topology.
81
- * @param name - Queue name
82
- * @param options - Queue options
83
- */
84
- addQueue(name: string, options?: QueueOptions): this;
85
- addQueue(
86
- nameOrDefinition: string | QueueDefinition,
87
- options: QueueOptions = {},
88
- ): this {
89
- if (isQueueDefinition(nameOrDefinition)) {
90
- this.queues.push(nameOrDefinition);
91
- } else {
92
- this.queues.push({ name: nameOrDefinition, ...options });
93
- }
94
- return this;
95
- }
96
-
97
- /**
98
- * Alias for addQueue().
99
- * @param definition - A complete QueueDefinition object
100
- */
101
- queue(definition: QueueDefinition): this;
102
- /**
103
- * Alias for addQueue().
104
- * @param name - Queue name
105
- * @param options - Queue options
106
- */
107
- queue(name: string, options?: QueueOptions): this;
108
- queue(
109
- nameOrDefinition: string | QueueDefinition,
110
- options: QueueOptions = {},
111
- ): this {
112
- if (isQueueDefinition(nameOrDefinition)) {
113
- return this.addQueue(nameOrDefinition);
114
- }
115
- return this.addQueue(nameOrDefinition, options);
116
- }
117
-
118
- /**
119
- * Configures dead-letter queue settings.
120
- */
121
- withDeadLetter(config: Partial<DeadLetterConfig>): this {
122
- this.deadLetter = {
123
- unhandled: config.unhandled ?? this.deadLetter.unhandled,
124
- undeliverable: config.undeliverable ?? this.deadLetter.undeliverable,
125
- };
126
- return this;
127
- }
128
-
129
- /**
130
- * Configures retry settings.
131
- */
132
- withRetry(config: Partial<RetryConfig>): this {
133
- this.retry = {
134
- enabled: config.enabled ?? this.retry.enabled,
135
- defaultDelayMs: config.defaultDelayMs ?? this.retry.defaultDelayMs,
136
- maxDelayMs: config.maxDelayMs ?? this.retry.maxDelayMs,
137
- };
138
- return this;
139
- }
140
-
141
- /**
142
- * Disables retry functionality.
143
- */
144
- withoutRetry(): this {
145
- this.retry = { ...this.retry, enabled: false };
146
- return this;
147
- }
148
-
149
- /**
150
- * Disables dead-letter queues.
151
- */
152
- withoutDeadLetter(): this {
153
- this.deadLetter = {
154
- unhandled: { enabled: false },
155
- undeliverable: { enabled: false },
156
- };
157
- return this;
158
- }
159
-
160
- /**
161
- * Validates the topology configuration.
162
- */
163
- validate(): readonly string[] {
164
- const issues: string[] = [];
165
-
166
- if (!this.namespace || this.namespace.trim() === '') {
167
- issues.push('Namespace is required');
168
- } else if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(this.namespace)) {
169
- issues.push(
170
- 'Namespace must start with a letter and contain only alphanumeric characters, underscores, and hyphens',
171
- );
172
- }
173
-
174
- if (this.queues.length === 0) {
175
- issues.push('At least one queue is required');
176
- }
177
-
178
- const queueNames = new Set<string>();
179
- for (const queue of this.queues) {
180
- if (!queue.name || queue.name.trim() === '') {
181
- issues.push('Queue name cannot be empty');
182
- } else if (!queue.exact && !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(queue.name)) {
183
- // Skip pattern validation for exact queues (allows names like 'matador.shared.id-platform')
184
- issues.push(
185
- `Queue name "${queue.name}" must start with a letter and contain only alphanumeric characters, underscores, and hyphens`,
186
- );
187
- } else if (queueNames.has(queue.name)) {
188
- issues.push(`Duplicate queue name: "${queue.name}"`);
189
- } else {
190
- queueNames.add(queue.name);
191
- }
192
-
193
- if (queue.concurrency !== undefined && queue.concurrency < 1) {
194
- issues.push(`Queue "${queue.name}" concurrency must be at least 1`);
195
- }
196
-
197
- if (queue.consumerTimeout !== undefined && queue.consumerTimeout < 0) {
198
- issues.push(
199
- `Queue "${queue.name}" consumer timeout must be non-negative`,
200
- );
201
- }
202
- }
203
-
204
- if (this.retry.enabled) {
205
- if (this.retry.defaultDelayMs < 0) {
206
- issues.push('Default retry delay must be non-negative');
207
- }
208
- if (this.retry.maxDelayMs < this.retry.defaultDelayMs) {
209
- issues.push(
210
- 'Max retry delay must be greater than or equal to default delay',
211
- );
212
- }
213
- }
214
-
215
- return issues;
216
- }
217
-
218
- /**
219
- * Builds the topology configuration.
220
- * @throws TopologyValidationError if validation fails
221
- */
222
- build(): Topology {
223
- const issues = this.validate();
224
- if (issues.length > 0) {
225
- throw new TopologyValidationError(
226
- `Invalid topology: ${issues.join('; ')}`,
227
- issues,
228
- );
229
- }
230
-
231
- return {
232
- namespace: this.namespace,
233
- queues: [...this.queues],
234
- deadLetter: this.deadLetter,
235
- retry: this.retry,
236
- };
237
- }
238
- }
@@ -1,19 +0,0 @@
1
- export type {
2
- DeadLetterConfig,
3
- DeadLetterQueueConfig,
4
- QueueDefinition,
5
- RabbitMQQueueDefinition,
6
- RabbitMQQueueOptions,
7
- RetryConfig,
8
- Topology,
9
- TransportQueueOptions,
10
- } from './types.js';
11
- export {
12
- getDeadLetterQueueName,
13
- getQualifiedQueueName,
14
- getRetryQueueName,
15
- resolveQueueName,
16
- } from './types.js';
17
-
18
- export type { QueueOptions } from './builder.js';
19
- export { TopologyBuilder, TopologyValidationError } from './builder.js';