@zdavison/matador 2.0.1
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.
- package/cli.ts +453 -0
- package/dist/checkpoint/context.d.ts +59 -0
- package/dist/checkpoint/context.d.ts.map +1 -0
- package/dist/checkpoint/context.js +140 -0
- package/dist/checkpoint/context.test.d.ts +2 -0
- package/dist/checkpoint/context.test.d.ts.map +1 -0
- package/dist/checkpoint/context.test.js +424 -0
- package/dist/checkpoint/index.d.ts +7 -0
- package/dist/checkpoint/index.d.ts.map +1 -0
- package/dist/checkpoint/index.js +6 -0
- package/dist/checkpoint/stores/memory.d.ts +29 -0
- package/dist/checkpoint/stores/memory.d.ts.map +1 -0
- package/dist/checkpoint/stores/memory.js +39 -0
- package/dist/checkpoint/stores/noop.d.ts +14 -0
- package/dist/checkpoint/stores/noop.d.ts.map +1 -0
- package/dist/checkpoint/stores/noop.js +18 -0
- package/dist/checkpoint/stores/stores.test.d.ts +2 -0
- package/dist/checkpoint/stores/stores.test.d.ts.map +1 -0
- package/dist/checkpoint/stores/stores.test.js +146 -0
- package/dist/checkpoint/types.d.ts +119 -0
- package/dist/checkpoint/types.d.ts.map +1 -0
- package/dist/checkpoint/types.js +1 -0
- package/dist/codec/codec.d.ts +29 -0
- package/dist/codec/codec.d.ts.map +1 -0
- package/dist/codec/codec.js +15 -0
- package/dist/codec/header-aware-codec.d.ts +36 -0
- package/dist/codec/header-aware-codec.d.ts.map +1 -0
- package/dist/codec/header-aware-codec.js +1 -0
- package/dist/codec/index.d.ts +6 -0
- package/dist/codec/index.d.ts.map +1 -0
- package/dist/codec/index.js +3 -0
- package/dist/codec/json-codec.d.ts +13 -0
- package/dist/codec/json-codec.d.ts.map +1 -0
- package/dist/codec/json-codec.js +64 -0
- package/dist/codec/rabbitmq-codec.d.ts +28 -0
- package/dist/codec/rabbitmq-codec.d.ts.map +1 -0
- package/dist/codec/rabbitmq-codec.js +242 -0
- package/dist/codec/rabbitmq-codec.test.d.ts +2 -0
- package/dist/codec/rabbitmq-codec.test.d.ts.map +1 -0
- package/dist/codec/rabbitmq-codec.test.js +433 -0
- package/dist/core/fanout.d.ts +59 -0
- package/dist/core/fanout.d.ts.map +1 -0
- package/dist/core/fanout.js +121 -0
- package/dist/core/fanout.test.d.ts +2 -0
- package/dist/core/fanout.test.d.ts.map +1 -0
- package/dist/core/fanout.test.js +1055 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/core/matador.d.ts +133 -0
- package/dist/core/matador.d.ts.map +1 -0
- package/dist/core/matador.js +222 -0
- package/dist/core/matador.test.d.ts +2 -0
- package/dist/core/matador.test.d.ts.map +1 -0
- package/dist/core/matador.test.js +446 -0
- package/dist/core/shutdown.d.ts +78 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +111 -0
- package/dist/core/shutdown.test.d.ts +2 -0
- package/dist/core/shutdown.test.d.ts.map +1 -0
- package/dist/core/shutdown.test.js +524 -0
- package/dist/errors/checkpoint-errors.d.ts +30 -0
- package/dist/errors/checkpoint-errors.d.ts.map +1 -0
- package/dist/errors/checkpoint-errors.js +49 -0
- package/dist/errors/has-description.d.ts +18 -0
- package/dist/errors/has-description.d.ts.map +1 -0
- package/dist/errors/has-description.js +9 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +22 -0
- package/dist/errors/matador-errors.d.ts +183 -0
- package/dist/errors/matador-errors.d.ts.map +1 -0
- package/dist/errors/matador-errors.js +376 -0
- package/dist/errors/retry-errors.d.ts +67 -0
- package/dist/errors/retry-errors.d.ts.map +1 -0
- package/dist/errors/retry-errors.js +108 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/safe-hooks.d.ts +34 -0
- package/dist/hooks/safe-hooks.d.ts.map +1 -0
- package/dist/hooks/safe-hooks.js +135 -0
- package/dist/hooks/types.d.ts +189 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +9 -0
- package/dist/index.cjs +3156 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/index.d.ts +3 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline.d.ts +60 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +261 -0
- package/dist/pipeline/pipeline.test.d.ts +2 -0
- package/dist/pipeline/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline/pipeline.test.js +1065 -0
- package/dist/retry/index.d.ts +4 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/retry/index.js +1 -0
- package/dist/retry/policy.d.ts +43 -0
- package/dist/retry/policy.d.ts.map +1 -0
- package/dist/retry/policy.js +1 -0
- package/dist/retry/standard-policy.d.ts +44 -0
- package/dist/retry/standard-policy.d.ts.map +1 -0
- package/dist/retry/standard-policy.js +102 -0
- package/dist/retry/standard-policy.test.d.ts +2 -0
- package/dist/retry/standard-policy.test.d.ts.map +1 -0
- package/dist/retry/standard-policy.test.js +190 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/registry.d.ts +63 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +171 -0
- package/dist/schema/registry.test.d.ts +2 -0
- package/dist/schema/registry.test.d.ts.map +1 -0
- package/dist/schema/registry.test.js +278 -0
- package/dist/schema/types.d.ts +158 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +74 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +243 -0
- package/dist/topology/builder.d.ts +66 -0
- package/dist/topology/builder.d.ts.map +1 -0
- package/dist/topology/builder.js +156 -0
- package/dist/topology/builder.test.d.ts +2 -0
- package/dist/topology/builder.test.d.ts.map +1 -0
- package/dist/topology/builder.test.js +222 -0
- package/dist/topology/index.d.ts +5 -0
- package/dist/topology/index.d.ts.map +1 -0
- package/dist/topology/index.js +2 -0
- package/dist/topology/types.d.ts +76 -0
- package/dist/topology/types.d.ts.map +1 -0
- package/dist/topology/types.js +18 -0
- package/dist/transport/capabilities.d.ts +66 -0
- package/dist/transport/capabilities.d.ts.map +1 -0
- package/dist/transport/capabilities.js +18 -0
- package/dist/transport/connection-manager.d.ts +95 -0
- package/dist/transport/connection-manager.d.ts.map +1 -0
- package/dist/transport/connection-manager.js +144 -0
- package/dist/transport/index.d.ts +11 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/local/local-transport.d.ts +62 -0
- package/dist/transport/local/local-transport.d.ts.map +1 -0
- package/dist/transport/local/local-transport.js +241 -0
- package/dist/transport/local/local-transport.test.d.ts +2 -0
- package/dist/transport/local/local-transport.test.d.ts.map +1 -0
- package/dist/transport/local/local-transport.test.js +192 -0
- package/dist/transport/multi/multi-transport.d.ts +94 -0
- package/dist/transport/multi/multi-transport.d.ts.map +1 -0
- package/dist/transport/multi/multi-transport.js +184 -0
- package/dist/transport/multi/multi-transport.test.d.ts +2 -0
- package/dist/transport/multi/multi-transport.test.d.ts.map +1 -0
- package/dist/transport/multi/multi-transport.test.js +236 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +73 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.js +518 -0
- package/dist/transport/transport.d.ts +152 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +1 -0
- package/dist/types/common.d.ts +41 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +12 -0
- package/dist/types/envelope.d.ts +82 -0
- package/dist/types/envelope.d.ts.map +1 -0
- package/dist/types/envelope.js +36 -0
- package/dist/types/event.d.ts +96 -0
- package/dist/types/event.d.ts.map +1 -0
- package/dist/types/event.js +26 -0
- package/dist/types/event.test.d.ts +2 -0
- package/dist/types/event.test.d.ts.map +1 -0
- package/dist/types/event.test.js +130 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/subscriber.d.ts +207 -0
- package/dist/types/subscriber.d.ts.map +1 -0
- package/dist/types/subscriber.js +99 -0
- package/examples/config.ts +126 -0
- package/examples/event.ts +26 -0
- package/examples/order-event.json +19 -0
- package/package.json +66 -0
- package/src/checkpoint/context.test.ts +510 -0
- package/src/checkpoint/context.ts +213 -0
- package/src/checkpoint/index.ts +30 -0
- package/src/checkpoint/stores/memory.ts +47 -0
- package/src/checkpoint/stores/noop.ts +22 -0
- package/src/checkpoint/stores/stores.test.ts +177 -0
- package/src/checkpoint/types.ts +147 -0
- package/src/codec/codec.ts +42 -0
- package/src/codec/header-aware-codec.ts +41 -0
- package/src/codec/index.ts +11 -0
- package/src/codec/json-codec.ts +69 -0
- package/src/codec/rabbitmq-codec.test.ts +516 -0
- package/src/codec/rabbitmq-codec.ts +336 -0
- package/src/core/fanout.test.ts +1351 -0
- package/src/core/fanout.ts +184 -0
- package/src/core/index.ts +12 -0
- package/src/core/matador.test.ts +575 -0
- package/src/core/matador.ts +344 -0
- package/src/core/shutdown.test.ts +853 -0
- package/src/core/shutdown.ts +165 -0
- package/src/errors/checkpoint-errors.ts +62 -0
- package/src/errors/has-description.ts +25 -0
- package/src/errors/index.ts +57 -0
- package/src/errors/matador-errors.ts +477 -0
- package/src/errors/retry-errors.ts +134 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/safe-hooks.ts +223 -0
- package/src/hooks/types.ts +248 -0
- package/src/index.ts +231 -0
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/pipeline.test.ts +1317 -0
- package/src/pipeline/pipeline.ts +371 -0
- package/src/retry/index.ts +4 -0
- package/src/retry/policy.ts +46 -0
- package/src/retry/standard-policy.test.ts +282 -0
- package/src/retry/standard-policy.ts +156 -0
- package/src/schema/index.ts +16 -0
- package/src/schema/registry.test.ts +339 -0
- package/src/schema/registry.ts +226 -0
- package/src/schema/types.test.ts +281 -0
- package/src/schema/types.ts +217 -0
- package/src/topology/builder.test.ts +275 -0
- package/src/topology/builder.ts +199 -0
- package/src/topology/index.ts +15 -0
- package/src/topology/types.ts +109 -0
- package/src/transport/capabilities.ts +88 -0
- package/src/transport/connection-manager.ts +218 -0
- package/src/transport/index.ts +42 -0
- package/src/transport/local/local-transport.test.ts +262 -0
- package/src/transport/local/local-transport.ts +327 -0
- package/src/transport/multi/multi-transport.test.ts +320 -0
- package/src/transport/multi/multi-transport.ts +294 -0
- package/src/transport/rabbitmq/rabbitmq-transport.ts +753 -0
- package/src/transport/transport.ts +200 -0
- package/src/types/common.ts +53 -0
- package/src/types/envelope.ts +152 -0
- package/src/types/event.test.ts +157 -0
- package/src/types/event.ts +118 -0
- package/src/types/index.ts +52 -0
- package/src/types/subscriber.ts +310 -0
- package/test/e2e/multi-transport.e2e.test.ts +236 -0
- package/test/e2e/rabbitmq-transport.e2e.test.ts +327 -0
- package/test/e2e/transport-compliance.e2e.test.ts +505 -0
- package/test/integration/matador.integration.test.ts +634 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,327 @@
|
|
|
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 { Subscription } from '../../src/transport/index.js';
|
|
15
|
+
import { RabbitMQTransport } from '../../src/transport/rabbitmq/rabbitmq-transport.js';
|
|
16
|
+
import {
|
|
17
|
+
createTestEnvelope,
|
|
18
|
+
createTestTopology,
|
|
19
|
+
} from './transport-compliance.e2e.test.js';
|
|
20
|
+
|
|
21
|
+
// Skip tests if docker is not available
|
|
22
|
+
const SKIP_E2E = process.env.SKIP_E2E_TESTS === 'true';
|
|
23
|
+
|
|
24
|
+
describe.skipIf(SKIP_E2E)('RabbitMQ Transport E2E', () => {
|
|
25
|
+
let container: StartedRabbitMQContainer;
|
|
26
|
+
let connectionUrl: string;
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
// Start RabbitMQ container
|
|
30
|
+
container = await new RabbitMQContainer('rabbitmq:3.13-management')
|
|
31
|
+
.withExposedPorts(5672, 15672)
|
|
32
|
+
.start();
|
|
33
|
+
|
|
34
|
+
connectionUrl = container.getAmqpUrl();
|
|
35
|
+
console.log(`RabbitMQ container started at ${connectionUrl}`);
|
|
36
|
+
}, 120_000);
|
|
37
|
+
|
|
38
|
+
afterAll(async () => {
|
|
39
|
+
if (container) {
|
|
40
|
+
await container.stop();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('RabbitMQ-specific features', () => {
|
|
45
|
+
let transport: RabbitMQTransport;
|
|
46
|
+
let subscriptions: Subscription[] = [];
|
|
47
|
+
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
transport = new RabbitMQTransport({
|
|
50
|
+
url: connectionUrl,
|
|
51
|
+
quorumQueues: false, // Use classic queues for faster tests
|
|
52
|
+
defaultPrefetch: 5,
|
|
53
|
+
});
|
|
54
|
+
await transport.connect();
|
|
55
|
+
await transport.applyTopology(createTestTopology(`test-${Date.now()}`));
|
|
56
|
+
subscriptions = [];
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
for (const sub of subscriptions) {
|
|
61
|
+
if (sub.isActive) {
|
|
62
|
+
await sub.unsubscribe();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (transport.isConnected()) {
|
|
66
|
+
await transport.disconnect();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should report correct capabilities', () => {
|
|
71
|
+
expect(transport.name).toBe('rabbitmq');
|
|
72
|
+
expect(transport.capabilities.deliveryModes).toContain('at-least-once');
|
|
73
|
+
expect(transport.capabilities.deadLetterRouting).toBe('native');
|
|
74
|
+
expect(transport.capabilities.attemptTracking).toBe(true);
|
|
75
|
+
expect(transport.capabilities.concurrencyModel).toBe('prefetch');
|
|
76
|
+
expect(transport.capabilities.priorities).toBe(true);
|
|
77
|
+
// delayedMessages depends on plugin availability
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle message priority', async () => {
|
|
81
|
+
const topology = createTestTopology(`priority-${Date.now()}`);
|
|
82
|
+
topology.queues[0] = { ...topology.queues[0], priorities: true };
|
|
83
|
+
|
|
84
|
+
await transport.applyTopology(topology);
|
|
85
|
+
const queueName = `${topology.namespace}.events`;
|
|
86
|
+
|
|
87
|
+
const receivedOrder: number[] = [];
|
|
88
|
+
|
|
89
|
+
const subscription = await transport.subscribe(
|
|
90
|
+
queueName,
|
|
91
|
+
async (env, receipt) => {
|
|
92
|
+
receivedOrder.push((env.data as { priority: number }).priority);
|
|
93
|
+
await transport.complete(receipt);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
subscriptions.push(subscription);
|
|
97
|
+
|
|
98
|
+
// Send messages with different priorities
|
|
99
|
+
await transport.send(
|
|
100
|
+
queueName,
|
|
101
|
+
createTestEnvelope({ eventKey: 'priority.1' }),
|
|
102
|
+
{ priority: 1 },
|
|
103
|
+
);
|
|
104
|
+
await transport.send(
|
|
105
|
+
queueName,
|
|
106
|
+
createTestEnvelope({ eventKey: 'priority.5' }),
|
|
107
|
+
{ priority: 5 },
|
|
108
|
+
);
|
|
109
|
+
await transport.send(
|
|
110
|
+
queueName,
|
|
111
|
+
createTestEnvelope({ eventKey: 'priority.10' }),
|
|
112
|
+
{ priority: 10 },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Wait for all messages
|
|
116
|
+
await waitFor(() => receivedOrder.length >= 3, 5000);
|
|
117
|
+
|
|
118
|
+
// Priority is not strictly guaranteed in RabbitMQ,
|
|
119
|
+
// but higher priority messages should generally come first
|
|
120
|
+
expect(receivedOrder.length).toBe(3);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should track attempt number in headers', async () => {
|
|
124
|
+
const topology = createTestTopology(`attempts-${Date.now()}`);
|
|
125
|
+
await transport.applyTopology(topology);
|
|
126
|
+
const queueName = `${topology.namespace}.events`;
|
|
127
|
+
|
|
128
|
+
let attemptNumber = 0;
|
|
129
|
+
|
|
130
|
+
const subscription = await transport.subscribe(
|
|
131
|
+
queueName,
|
|
132
|
+
async (_env, receipt) => {
|
|
133
|
+
attemptNumber = receipt.attemptNumber;
|
|
134
|
+
await transport.complete(receipt);
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
subscriptions.push(subscription);
|
|
138
|
+
|
|
139
|
+
await transport.send(queueName, createTestEnvelope());
|
|
140
|
+
|
|
141
|
+
await waitFor(() => attemptNumber > 0, 5000);
|
|
142
|
+
|
|
143
|
+
expect(attemptNumber).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle prefetch/concurrency per queue', async () => {
|
|
147
|
+
const topology = createTestTopology(`prefetch-${Date.now()}`);
|
|
148
|
+
await transport.applyTopology(topology);
|
|
149
|
+
const queueName = `${topology.namespace}.events`;
|
|
150
|
+
|
|
151
|
+
const processing = new Set<string>();
|
|
152
|
+
let maxConcurrent = 0;
|
|
153
|
+
|
|
154
|
+
const subscription = await transport.subscribe(
|
|
155
|
+
queueName,
|
|
156
|
+
async (env, receipt) => {
|
|
157
|
+
processing.add(env.id);
|
|
158
|
+
maxConcurrent = Math.max(maxConcurrent, processing.size);
|
|
159
|
+
|
|
160
|
+
// Simulate processing time
|
|
161
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
162
|
+
|
|
163
|
+
processing.delete(env.id);
|
|
164
|
+
await transport.complete(receipt);
|
|
165
|
+
},
|
|
166
|
+
{ concurrency: 3 },
|
|
167
|
+
);
|
|
168
|
+
subscriptions.push(subscription);
|
|
169
|
+
|
|
170
|
+
// Send more messages than prefetch allows
|
|
171
|
+
for (let i = 0; i < 10; i++) {
|
|
172
|
+
await transport.send(queueName, createTestEnvelope({ id: `msg-${i}` }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Wait for all messages to be processed
|
|
176
|
+
await waitFor(() => processing.size === 0, 10000);
|
|
177
|
+
|
|
178
|
+
// Should never exceed prefetch
|
|
179
|
+
expect(maxConcurrent).toBeLessThanOrEqual(3);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should throw when sending delayed message without plugin', async () => {
|
|
183
|
+
// Standard RabbitMQ container doesn't have delayed message plugin
|
|
184
|
+
const topology = createTestTopology(`delay-${Date.now()}`);
|
|
185
|
+
await transport.applyTopology(topology);
|
|
186
|
+
const queueName = `${topology.namespace}.events`;
|
|
187
|
+
|
|
188
|
+
// Should throw because plugin is not available
|
|
189
|
+
await expect(
|
|
190
|
+
transport.send(queueName, createTestEnvelope(), { delay: 1000 }),
|
|
191
|
+
).rejects.toThrow('delayed message exchange plugin');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should report delayedMessages capability based on plugin', () => {
|
|
195
|
+
// Standard RabbitMQ without plugin should report false
|
|
196
|
+
expect(transport.capabilities.delayedMessages).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('reconnection behavior', () => {
|
|
201
|
+
it('should handle disconnect and reconnect', async () => {
|
|
202
|
+
const transport = new RabbitMQTransport({
|
|
203
|
+
url: connectionUrl,
|
|
204
|
+
connection: {
|
|
205
|
+
maxReconnectAttempts: 3,
|
|
206
|
+
initialReconnectDelay: 100,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await transport.connect();
|
|
211
|
+
expect(transport.isConnected()).toBe(true);
|
|
212
|
+
|
|
213
|
+
await transport.disconnect();
|
|
214
|
+
expect(transport.isConnected()).toBe(false);
|
|
215
|
+
|
|
216
|
+
// Reconnect
|
|
217
|
+
await transport.connect();
|
|
218
|
+
expect(transport.isConnected()).toBe(true);
|
|
219
|
+
|
|
220
|
+
await transport.disconnect();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('channel per queue isolation', () => {
|
|
225
|
+
let transport: RabbitMQTransport;
|
|
226
|
+
const subscriptions: Subscription[] = [];
|
|
227
|
+
|
|
228
|
+
beforeEach(async () => {
|
|
229
|
+
transport = new RabbitMQTransport({
|
|
230
|
+
url: connectionUrl,
|
|
231
|
+
quorumQueues: false,
|
|
232
|
+
defaultPrefetch: 2,
|
|
233
|
+
});
|
|
234
|
+
await transport.connect();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
afterEach(async () => {
|
|
238
|
+
for (const sub of subscriptions) {
|
|
239
|
+
if (sub.isActive) {
|
|
240
|
+
await sub.unsubscribe();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (transport.isConnected()) {
|
|
244
|
+
await transport.disconnect();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should have independent concurrency per queue', async () => {
|
|
249
|
+
const topology = createTestTopology(`isolation-${Date.now()}`);
|
|
250
|
+
await transport.applyTopology(topology);
|
|
251
|
+
|
|
252
|
+
const queue1 = `${topology.namespace}.events`;
|
|
253
|
+
const queue2 = `${topology.namespace}.notifications`;
|
|
254
|
+
|
|
255
|
+
const queue1Processing = new Set<string>();
|
|
256
|
+
const queue2Processing = new Set<string>();
|
|
257
|
+
let queue1MaxConcurrent = 0;
|
|
258
|
+
let queue2MaxConcurrent = 0;
|
|
259
|
+
|
|
260
|
+
// Subscribe to both queues with different concurrency
|
|
261
|
+
const sub1 = await transport.subscribe(
|
|
262
|
+
queue1,
|
|
263
|
+
async (env, receipt) => {
|
|
264
|
+
queue1Processing.add(env.id);
|
|
265
|
+
queue1MaxConcurrent = Math.max(
|
|
266
|
+
queue1MaxConcurrent,
|
|
267
|
+
queue1Processing.size,
|
|
268
|
+
);
|
|
269
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
270
|
+
queue1Processing.delete(env.id);
|
|
271
|
+
await transport.complete(receipt);
|
|
272
|
+
},
|
|
273
|
+
{ concurrency: 2 },
|
|
274
|
+
);
|
|
275
|
+
subscriptions.push(sub1);
|
|
276
|
+
|
|
277
|
+
const sub2 = await transport.subscribe(
|
|
278
|
+
queue2,
|
|
279
|
+
async (env, receipt) => {
|
|
280
|
+
queue2Processing.add(env.id);
|
|
281
|
+
queue2MaxConcurrent = Math.max(
|
|
282
|
+
queue2MaxConcurrent,
|
|
283
|
+
queue2Processing.size,
|
|
284
|
+
);
|
|
285
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
286
|
+
queue2Processing.delete(env.id);
|
|
287
|
+
await transport.complete(receipt);
|
|
288
|
+
},
|
|
289
|
+
{ concurrency: 3 },
|
|
290
|
+
);
|
|
291
|
+
subscriptions.push(sub2);
|
|
292
|
+
|
|
293
|
+
// Send messages to both queues
|
|
294
|
+
for (let i = 0; i < 6; i++) {
|
|
295
|
+
await transport.send(queue1, createTestEnvelope({ id: `q1-${i}` }));
|
|
296
|
+
await transport.send(queue2, createTestEnvelope({ id: `q2-${i}` }));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Wait for all to complete
|
|
300
|
+
await waitFor(
|
|
301
|
+
() => queue1Processing.size === 0 && queue2Processing.size === 0,
|
|
302
|
+
10000,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Each queue should respect its own prefetch
|
|
306
|
+
expect(queue1MaxConcurrent).toBeLessThanOrEqual(2);
|
|
307
|
+
expect(queue2MaxConcurrent).toBeLessThanOrEqual(3);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Waits for a condition to be true, with timeout.
|
|
314
|
+
*/
|
|
315
|
+
async function waitFor(
|
|
316
|
+
condition: () => boolean,
|
|
317
|
+
timeoutMs: number,
|
|
318
|
+
intervalMs = 50,
|
|
319
|
+
): Promise<void> {
|
|
320
|
+
const startTime = Date.now();
|
|
321
|
+
while (!condition()) {
|
|
322
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
323
|
+
throw new Error(`Timeout waiting for condition after ${timeoutMs}ms`);
|
|
324
|
+
}
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
326
|
+
}
|
|
327
|
+
}
|