@zdavison/matador 2.0.8 → 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 (77) hide show
  1. package/dist/index.d.cts +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/package.json +6 -2
  5. package/examples/config.ts +0 -126
  6. package/examples/event.ts +0 -26
  7. package/examples/order-event.json +0 -19
  8. package/src/checkpoint/context.test.ts +0 -510
  9. package/src/checkpoint/context.ts +0 -213
  10. package/src/checkpoint/index.ts +0 -30
  11. package/src/checkpoint/stores/memory.ts +0 -47
  12. package/src/checkpoint/stores/noop.ts +0 -22
  13. package/src/checkpoint/stores/stores.test.ts +0 -177
  14. package/src/checkpoint/types.ts +0 -147
  15. package/src/codec/codec.ts +0 -42
  16. package/src/codec/header-aware-codec.ts +0 -41
  17. package/src/codec/index.ts +0 -11
  18. package/src/codec/json-codec.ts +0 -69
  19. package/src/codec/rabbitmq-codec.test.ts +0 -516
  20. package/src/codec/rabbitmq-codec.ts +0 -336
  21. package/src/core/fanout.test.ts +0 -1350
  22. package/src/core/fanout.ts +0 -184
  23. package/src/core/index.ts +0 -12
  24. package/src/core/matador.test.ts +0 -575
  25. package/src/core/matador.ts +0 -357
  26. package/src/core/shutdown.test.ts +0 -853
  27. package/src/core/shutdown.ts +0 -165
  28. package/src/errors/checkpoint-errors.ts +0 -62
  29. package/src/errors/has-description.ts +0 -25
  30. package/src/errors/index.ts +0 -58
  31. package/src/errors/matador-errors.ts +0 -477
  32. package/src/errors/retry-errors.test.ts +0 -175
  33. package/src/errors/retry-errors.ts +0 -188
  34. package/src/hooks/index.ts +0 -14
  35. package/src/hooks/safe-hooks.ts +0 -200
  36. package/src/hooks/types.ts +0 -226
  37. package/src/index.ts +0 -241
  38. package/src/pipeline/index.ts +0 -2
  39. package/src/pipeline/pipeline.test.ts +0 -1377
  40. package/src/pipeline/pipeline.ts +0 -393
  41. package/src/retry/index.ts +0 -4
  42. package/src/retry/policy.ts +0 -46
  43. package/src/retry/standard-policy.test.ts +0 -290
  44. package/src/retry/standard-policy.ts +0 -156
  45. package/src/schema/index.ts +0 -16
  46. package/src/schema/registry.test.ts +0 -339
  47. package/src/schema/registry.ts +0 -229
  48. package/src/schema/types.test.ts +0 -280
  49. package/src/schema/types.ts +0 -217
  50. package/src/topology/builder.test.ts +0 -451
  51. package/src/topology/builder.ts +0 -238
  52. package/src/topology/index.ts +0 -19
  53. package/src/topology/types.ts +0 -183
  54. package/src/transport/capabilities.ts +0 -88
  55. package/src/transport/connection-manager.ts +0 -218
  56. package/src/transport/index.ts +0 -42
  57. package/src/transport/local/local-transport.test.ts +0 -262
  58. package/src/transport/local/local-transport.ts +0 -330
  59. package/src/transport/multi/multi-transport.test.ts +0 -320
  60. package/src/transport/multi/multi-transport.ts +0 -294
  61. package/src/transport/rabbitmq/rabbitmq-transport.test.ts +0 -120
  62. package/src/transport/rabbitmq/rabbitmq-transport.ts +0 -782
  63. package/src/transport/transport.ts +0 -200
  64. package/src/types/common.ts +0 -53
  65. package/src/types/dispatcher.ts +0 -18
  66. package/src/types/envelope.ts +0 -244
  67. package/src/types/event.test.ts +0 -157
  68. package/src/types/event.ts +0 -112
  69. package/src/types/index.ts +0 -62
  70. package/src/types/subscriber.ts +0 -333
  71. package/test/e2e/multi-transport.e2e.test.ts +0 -237
  72. package/test/e2e/rabbitmq-transport.e2e.test.ts +0 -618
  73. package/test/e2e/transport-compliance.e2e.test.ts +0 -506
  74. package/test/integration/matador.integration.test.ts +0 -634
  75. package/tsconfig.json +0 -29
  76. package/tsconfig.tsbuildinfo +0 -1
  77. 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';