@zdavison/matador 4.0.2 → 4.0.3
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/dist/core/fanout.d.ts +51 -0
- package/dist/core/fanout.d.ts.map +1 -1
- package/dist/core/fanout.js +141 -28
- package/dist/core/fanout.test.js +533 -7
- package/dist/core/matador.d.ts +33 -0
- package/dist/core/matador.d.ts.map +1 -1
- package/dist/core/matador.js +5 -1
- package/dist/core/matador.test.js +242 -6
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/errors/matador-errors.d.ts +10 -8
- package/dist/errors/matador-errors.d.ts.map +1 -1
- package/dist/errors/matador-errors.js +18 -14
- package/dist/index.cjs +215 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/transport/local/local-transport.d.ts +6 -0
- package/dist/transport/local/local-transport.d.ts.map +1 -1
- package/dist/transport/local/local-transport.js +35 -18
- package/dist/transport/local/local-transport.test.js +40 -4
- package/dist/transport/multi/multi-transport.d.ts.map +1 -1
- package/dist/transport/multi/multi-transport.js +3 -0
- package/dist/transport/multi/multi-transport.test.js +82 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
- package/dist/transport/rabbitmq/rabbitmq-transport.js +31 -27
- package/dist/transport/transport.d.ts +6 -0
- package/dist/transport/transport.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/fanout.test.js
CHANGED
|
@@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
|
|
2
2
|
import { TransportSendError } from '../errors/index.js';
|
|
3
3
|
import { SafeHooks } from '../hooks/index.js';
|
|
4
4
|
import { SchemaRegistry } from '../schema/index.js';
|
|
5
|
-
import { TopologyBuilder } from '../topology/index.js';
|
|
6
|
-
import { MultiTransport } from '../transport/index.js';
|
|
5
|
+
import { TopologyBuilder, resolveTargetQueueName } from '../topology/index.js';
|
|
6
|
+
import { LocalTransport, MultiTransport } from '../transport/index.js';
|
|
7
7
|
import { MatadorEvent, createSubscriber, createSubscriberStub, } from '../types/index.js';
|
|
8
8
|
import { FanoutEngine } from './fanout.js';
|
|
9
9
|
class UserCreatedEvent extends MatadorEvent {
|
|
@@ -35,6 +35,20 @@ class UserCreatedEventWithMetadata extends MatadorEvent {
|
|
|
35
35
|
this.metadata = metadata;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Waits for a triggered retry-buffer flush to fully settle, including the
|
|
40
|
+
* inter-item throttle sleeps — a single macrotask tick isn't enough once the
|
|
41
|
+
* flush loop sleeps between items.
|
|
42
|
+
*/
|
|
43
|
+
async function waitForFlush(fanout) {
|
|
44
|
+
const deadline = Date.now() + 1000;
|
|
45
|
+
while (fanout.eventsBeingEnqueuedCount > 0) {
|
|
46
|
+
if (Date.now() > deadline) {
|
|
47
|
+
throw new Error('waitForFlush: timed out waiting for flush to settle');
|
|
48
|
+
}
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
38
52
|
const testTopology = TopologyBuilder.create()
|
|
39
53
|
.withNamespace('test')
|
|
40
54
|
.addQueue('events')
|
|
@@ -1125,6 +1139,50 @@ describe('FanoutEngine', () => {
|
|
|
1125
1139
|
expect(result.errors).toHaveLength(0);
|
|
1126
1140
|
expect(result.subscribersSent).toBe(0);
|
|
1127
1141
|
});
|
|
1142
|
+
it('should buffer instead of reporting success when a stub subscriber has no local subscriber', async () => {
|
|
1143
|
+
// LocalTransport rejects sends to a queue with no active subscriber in
|
|
1144
|
+
// this process. A stub subscriber's real implementation lives in another
|
|
1145
|
+
// service, so this queue will never have one here — the send must fail
|
|
1146
|
+
// and be buffered, not reported as delivered.
|
|
1147
|
+
const stub = createSubscriberStub({ name: 'remote-service' });
|
|
1148
|
+
schema.register(UserCreatedEvent, [stub]);
|
|
1149
|
+
const onEnqueueSuccess = mock(async () => { });
|
|
1150
|
+
const hooksInstance = new SafeHooks({ onEnqueueSuccess });
|
|
1151
|
+
const localTransport = new LocalTransport();
|
|
1152
|
+
await localTransport.connect();
|
|
1153
|
+
await localTransport.applyTopology(testTopology);
|
|
1154
|
+
const fanoutWithLocal = new FanoutEngine({
|
|
1155
|
+
transport: localTransport,
|
|
1156
|
+
schema,
|
|
1157
|
+
hooks: hooksInstance,
|
|
1158
|
+
topology: testTopology,
|
|
1159
|
+
defaultQueue: 'events',
|
|
1160
|
+
// Fast interval so the test doesn't have to wait on the 30s default
|
|
1161
|
+
// to prove the message is actually held for retry, not dropped.
|
|
1162
|
+
retryIntervalMs: 10,
|
|
1163
|
+
});
|
|
1164
|
+
const event = new UserCreatedEvent({
|
|
1165
|
+
userId: '123',
|
|
1166
|
+
email: 'test@example.com',
|
|
1167
|
+
});
|
|
1168
|
+
const result = await fanoutWithLocal.send(UserCreatedEvent, event);
|
|
1169
|
+
expect(result.errors).toHaveLength(0);
|
|
1170
|
+
expect(result.subscribersSent).toBe(0);
|
|
1171
|
+
expect(onEnqueueSuccess).not.toHaveBeenCalled();
|
|
1172
|
+
// The message never reached LocalTransport's own storage (enqueue()
|
|
1173
|
+
// throws before storing), so it can only still exist in-memory if
|
|
1174
|
+
// FanoutEngine actually buffered it. Prove that by attaching a
|
|
1175
|
+
// subscriber and letting the periodic flush retry the send — if the
|
|
1176
|
+
// message had been dropped instead of buffered, nothing would arrive.
|
|
1177
|
+
const received = [];
|
|
1178
|
+
await localTransport.subscribe(resolveTargetQueueName(testTopology, 'events'), async (envelope) => {
|
|
1179
|
+
received.push(envelope);
|
|
1180
|
+
});
|
|
1181
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
1182
|
+
expect(received).toHaveLength(1);
|
|
1183
|
+
expect(onEnqueueSuccess).toHaveBeenCalledTimes(1);
|
|
1184
|
+
fanoutWithLocal.dispose();
|
|
1185
|
+
});
|
|
1128
1186
|
it('should flush buffered messages when onConnected fires', async () => {
|
|
1129
1187
|
let connectedCallback;
|
|
1130
1188
|
const failThenSucceed = mock(async () => {
|
|
@@ -1163,7 +1221,7 @@ describe('FanoutEngine', () => {
|
|
|
1163
1221
|
expect(connectedCallback).toBeDefined();
|
|
1164
1222
|
connectedCallback?.();
|
|
1165
1223
|
// Give the async flush a chance to run
|
|
1166
|
-
await
|
|
1224
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1167
1225
|
expect(transportWithReconnect.send).toHaveBeenCalledTimes(2);
|
|
1168
1226
|
});
|
|
1169
1227
|
it('should re-buffer on flush failure and retry on next reconnect', async () => {
|
|
@@ -1199,12 +1257,12 @@ describe('FanoutEngine', () => {
|
|
|
1199
1257
|
expect(transportWithReconnect.send).toHaveBeenCalledTimes(1);
|
|
1200
1258
|
// Simulate reconnect — flush fails, item should be re-buffered
|
|
1201
1259
|
connectedCallback?.();
|
|
1202
|
-
await
|
|
1260
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1203
1261
|
// send was attempted again during the flush
|
|
1204
1262
|
expect(transportWithReconnect.send).toHaveBeenCalledTimes(2);
|
|
1205
1263
|
// Simulate a second reconnect — should retry again
|
|
1206
1264
|
connectedCallback?.();
|
|
1207
|
-
await
|
|
1265
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1208
1266
|
expect(transportWithReconnect.send).toHaveBeenCalledTimes(3);
|
|
1209
1267
|
});
|
|
1210
1268
|
it('should not buffer when transport works fine', async () => {
|
|
@@ -1238,7 +1296,7 @@ describe('FanoutEngine', () => {
|
|
|
1238
1296
|
expect(result.errors).toHaveLength(0);
|
|
1239
1297
|
// onConnected fires — nothing buffered so flush is a no-op
|
|
1240
1298
|
connectedCallback?.();
|
|
1241
|
-
await
|
|
1299
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1242
1300
|
// Only one call total (the original send — not a spurious re-send)
|
|
1243
1301
|
expect(successTransport.send).toHaveBeenCalledTimes(1);
|
|
1244
1302
|
});
|
|
@@ -1309,7 +1367,7 @@ describe('FanoutEngine', () => {
|
|
|
1309
1367
|
// But it was also buffered — it should retry on reconnect
|
|
1310
1368
|
failingTransport.send.mockImplementation(async () => 'mock');
|
|
1311
1369
|
connectedCallback?.();
|
|
1312
|
-
await
|
|
1370
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1313
1371
|
expect(failingTransport.send).toHaveBeenCalledTimes(2);
|
|
1314
1372
|
});
|
|
1315
1373
|
it('should unsubscribe the onConnected listener when dispose() is called', () => {
|
|
@@ -1440,5 +1498,473 @@ describe('FanoutEngine', () => {
|
|
|
1440
1498
|
expect(localMock.send).toHaveBeenCalledTimes(1);
|
|
1441
1499
|
expect(fanoutWithMulti.eventsBeingEnqueuedCount).toBe(0);
|
|
1442
1500
|
});
|
|
1501
|
+
it('should buffer (not report success) when a real MultiTransport falls back to local for a stub subscriber', async () => {
|
|
1502
|
+
const stub = createSubscriberStub({ name: 'remote-service' });
|
|
1503
|
+
schema.register(UserCreatedEvent, [stub]);
|
|
1504
|
+
const rabbitMock = {
|
|
1505
|
+
...transport,
|
|
1506
|
+
name: 'rabbitmq',
|
|
1507
|
+
send: mock(async () => {
|
|
1508
|
+
throw new Error('RabbitMQ down');
|
|
1509
|
+
}),
|
|
1510
|
+
};
|
|
1511
|
+
const localTransport = new LocalTransport();
|
|
1512
|
+
await localTransport.connect();
|
|
1513
|
+
await localTransport.applyTopology(testTopology);
|
|
1514
|
+
const multiTransport = new MultiTransport({
|
|
1515
|
+
transports: [rabbitMock, localTransport],
|
|
1516
|
+
});
|
|
1517
|
+
const onEnqueueSuccess = mock(async () => { });
|
|
1518
|
+
const hooksInstance = new SafeHooks({ onEnqueueSuccess });
|
|
1519
|
+
const fanoutWithMulti = new FanoutEngine({
|
|
1520
|
+
transport: multiTransport,
|
|
1521
|
+
schema,
|
|
1522
|
+
hooks: hooksInstance,
|
|
1523
|
+
topology: testTopology,
|
|
1524
|
+
defaultQueue: 'events',
|
|
1525
|
+
// Fast interval so the test doesn't have to wait on the 30s default
|
|
1526
|
+
// to prove the message is actually held for retry, not dropped.
|
|
1527
|
+
retryIntervalMs: 10,
|
|
1528
|
+
});
|
|
1529
|
+
const event = new UserCreatedEvent({
|
|
1530
|
+
userId: '123',
|
|
1531
|
+
email: 'test@example.com',
|
|
1532
|
+
});
|
|
1533
|
+
const result = await fanoutWithMulti.send(UserCreatedEvent, event);
|
|
1534
|
+
expect(result.subscribersSent).toBe(0);
|
|
1535
|
+
expect(result.errors).toHaveLength(0);
|
|
1536
|
+
expect(onEnqueueSuccess).not.toHaveBeenCalled();
|
|
1537
|
+
expect(rabbitMock.send).toHaveBeenCalledTimes(1);
|
|
1538
|
+
// Neither rabbitmq nor local ever stored the message durably (rabbit
|
|
1539
|
+
// threw, local's enqueue() throws with no subscriber), so it can only
|
|
1540
|
+
// still be in memory if FanoutEngine actually buffered it. Prove that
|
|
1541
|
+
// by attaching a local subscriber and letting the periodic flush retry.
|
|
1542
|
+
const received = [];
|
|
1543
|
+
await localTransport.subscribe(resolveTargetQueueName(testTopology, 'events'), async (envelope) => {
|
|
1544
|
+
received.push(envelope);
|
|
1545
|
+
});
|
|
1546
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
1547
|
+
expect(received).toHaveLength(1);
|
|
1548
|
+
expect(onEnqueueSuccess).toHaveBeenCalledTimes(1);
|
|
1549
|
+
fanoutWithMulti.dispose();
|
|
1550
|
+
});
|
|
1551
|
+
it('should re-buffer a stub message that falls back to local again on flush, then deliver once the primary recovers', async () => {
|
|
1552
|
+
const stub = createSubscriberStub({ name: 'remote-service' });
|
|
1553
|
+
schema.register(UserCreatedEvent, [stub]);
|
|
1554
|
+
let rabbitUp = false;
|
|
1555
|
+
let connectedCallback;
|
|
1556
|
+
const rabbitMock = {
|
|
1557
|
+
...transport,
|
|
1558
|
+
name: 'rabbitmq',
|
|
1559
|
+
send: mock(async () => {
|
|
1560
|
+
if (!rabbitUp)
|
|
1561
|
+
throw new Error('RabbitMQ down');
|
|
1562
|
+
return 'rabbitmq';
|
|
1563
|
+
}),
|
|
1564
|
+
onConnected: (cb) => {
|
|
1565
|
+
connectedCallback = cb;
|
|
1566
|
+
return () => { };
|
|
1567
|
+
},
|
|
1568
|
+
};
|
|
1569
|
+
const localTransport = new LocalTransport();
|
|
1570
|
+
await localTransport.connect();
|
|
1571
|
+
await localTransport.applyTopology(testTopology);
|
|
1572
|
+
const multiTransport = new MultiTransport({
|
|
1573
|
+
transports: [rabbitMock, localTransport],
|
|
1574
|
+
});
|
|
1575
|
+
const onEnqueueSuccess = mock(async () => { });
|
|
1576
|
+
const hooksInstance = new SafeHooks({ onEnqueueSuccess });
|
|
1577
|
+
const fanoutWithMulti = new FanoutEngine({
|
|
1578
|
+
transport: multiTransport,
|
|
1579
|
+
schema,
|
|
1580
|
+
hooks: hooksInstance,
|
|
1581
|
+
topology: testTopology,
|
|
1582
|
+
defaultQueue: 'events',
|
|
1583
|
+
});
|
|
1584
|
+
const event = new UserCreatedEvent({
|
|
1585
|
+
userId: '123',
|
|
1586
|
+
email: 'test@example.com',
|
|
1587
|
+
});
|
|
1588
|
+
// Initial send: rabbit down, falls back to local, buffered (not delivered).
|
|
1589
|
+
await fanoutWithMulti.send(UserCreatedEvent, event);
|
|
1590
|
+
expect(onEnqueueSuccess).not.toHaveBeenCalled();
|
|
1591
|
+
// Reconnect fires while rabbit is still down — flush falls back to local
|
|
1592
|
+
// again and must re-buffer, not report success.
|
|
1593
|
+
connectedCallback?.();
|
|
1594
|
+
await waitForFlush(fanoutWithMulti);
|
|
1595
|
+
expect(onEnqueueSuccess).not.toHaveBeenCalled();
|
|
1596
|
+
// Rabbit recovers — next flush should deliver for real via rabbitmq.
|
|
1597
|
+
rabbitUp = true;
|
|
1598
|
+
connectedCallback?.();
|
|
1599
|
+
await waitForFlush(fanoutWithMulti);
|
|
1600
|
+
expect(onEnqueueSuccess).toHaveBeenCalledTimes(1);
|
|
1601
|
+
expect(onEnqueueSuccess).toHaveBeenCalledWith(expect.objectContaining({ transport: 'rabbitmq' }));
|
|
1602
|
+
});
|
|
1603
|
+
it('should retry via the periodic interval even without a reconnect event', async () => {
|
|
1604
|
+
// Covers the case where the transport stays connected the whole time
|
|
1605
|
+
// but an individual publish keeps failing — onConnected never fires
|
|
1606
|
+
// again for that, so only the interval can recover it.
|
|
1607
|
+
const subscriber = createSubscriber({
|
|
1608
|
+
name: 'handle-user',
|
|
1609
|
+
description: 'Handles user events',
|
|
1610
|
+
callback: async () => { },
|
|
1611
|
+
});
|
|
1612
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1613
|
+
let shouldFail = true;
|
|
1614
|
+
const flakyTransport = {
|
|
1615
|
+
...transport,
|
|
1616
|
+
send: mock(async () => {
|
|
1617
|
+
if (shouldFail)
|
|
1618
|
+
throw new Error('Publish nacked');
|
|
1619
|
+
return 'mock';
|
|
1620
|
+
}),
|
|
1621
|
+
// Deliberately no onConnected — the transport never disconnects.
|
|
1622
|
+
};
|
|
1623
|
+
const fanoutWithInterval = new FanoutEngine({
|
|
1624
|
+
transport: flakyTransport,
|
|
1625
|
+
schema,
|
|
1626
|
+
hooks,
|
|
1627
|
+
topology: testTopology,
|
|
1628
|
+
defaultQueue: 'events',
|
|
1629
|
+
retryIntervalMs: 20,
|
|
1630
|
+
});
|
|
1631
|
+
const event = new UserCreatedEvent({
|
|
1632
|
+
userId: '123',
|
|
1633
|
+
email: 'test@example.com',
|
|
1634
|
+
});
|
|
1635
|
+
await fanoutWithInterval.send(UserCreatedEvent, event);
|
|
1636
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(1);
|
|
1637
|
+
shouldFail = false;
|
|
1638
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1639
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(2);
|
|
1640
|
+
fanoutWithInterval.dispose();
|
|
1641
|
+
});
|
|
1642
|
+
it('should stop retrying the rest of the buffer after 10 consecutive failures in a flush pass', async () => {
|
|
1643
|
+
let connectedCallback;
|
|
1644
|
+
let shouldFail = true;
|
|
1645
|
+
const flakyTransport = {
|
|
1646
|
+
...transport,
|
|
1647
|
+
send: mock(async () => {
|
|
1648
|
+
if (shouldFail)
|
|
1649
|
+
throw new Error('Broker rejecting everything');
|
|
1650
|
+
return 'mock';
|
|
1651
|
+
}),
|
|
1652
|
+
onConnected: (cb) => {
|
|
1653
|
+
connectedCallback = cb;
|
|
1654
|
+
return () => { };
|
|
1655
|
+
},
|
|
1656
|
+
};
|
|
1657
|
+
const fanoutWithReconnect = new FanoutEngine({
|
|
1658
|
+
transport: flakyTransport,
|
|
1659
|
+
schema,
|
|
1660
|
+
hooks,
|
|
1661
|
+
topology: testTopology,
|
|
1662
|
+
defaultQueue: 'events',
|
|
1663
|
+
});
|
|
1664
|
+
const subscriber = createSubscriber({
|
|
1665
|
+
name: 'handle-user',
|
|
1666
|
+
description: 'Handles user events',
|
|
1667
|
+
callback: async () => { },
|
|
1668
|
+
});
|
|
1669
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1670
|
+
const event = new UserCreatedEvent({
|
|
1671
|
+
userId: '123',
|
|
1672
|
+
email: 'test@example.com',
|
|
1673
|
+
});
|
|
1674
|
+
// 12 sends buffered; flush bails after the 10th consecutive failure,
|
|
1675
|
+
// leaving 2 untried.
|
|
1676
|
+
for (let i = 0; i < 12; i++) {
|
|
1677
|
+
await fanoutWithReconnect.send(UserCreatedEvent, event);
|
|
1678
|
+
}
|
|
1679
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(12);
|
|
1680
|
+
connectedCallback?.();
|
|
1681
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1682
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(22);
|
|
1683
|
+
// Recovery: next flush delivers all 12 re-buffered/untried items.
|
|
1684
|
+
shouldFail = false;
|
|
1685
|
+
connectedCallback?.();
|
|
1686
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1687
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(34);
|
|
1688
|
+
});
|
|
1689
|
+
it('should respect a custom maxConsecutiveFlushFailures threshold', async () => {
|
|
1690
|
+
let connectedCallback;
|
|
1691
|
+
const flakyTransport = {
|
|
1692
|
+
...transport,
|
|
1693
|
+
send: mock(async () => {
|
|
1694
|
+
throw new Error('Broker rejecting everything');
|
|
1695
|
+
}),
|
|
1696
|
+
onConnected: (cb) => {
|
|
1697
|
+
connectedCallback = cb;
|
|
1698
|
+
return () => { };
|
|
1699
|
+
},
|
|
1700
|
+
};
|
|
1701
|
+
const fanoutWithCustomThreshold = new FanoutEngine({
|
|
1702
|
+
transport: flakyTransport,
|
|
1703
|
+
schema,
|
|
1704
|
+
hooks,
|
|
1705
|
+
topology: testTopology,
|
|
1706
|
+
defaultQueue: 'events',
|
|
1707
|
+
maxConsecutiveFlushFailures: 3,
|
|
1708
|
+
});
|
|
1709
|
+
const subscriber = createSubscriber({
|
|
1710
|
+
name: 'handle-user',
|
|
1711
|
+
description: 'Handles user events',
|
|
1712
|
+
callback: async () => { },
|
|
1713
|
+
});
|
|
1714
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1715
|
+
const event = new UserCreatedEvent({
|
|
1716
|
+
userId: '123',
|
|
1717
|
+
email: 'test@example.com',
|
|
1718
|
+
});
|
|
1719
|
+
// 5 sends buffered; flush should bail after the 3rd consecutive
|
|
1720
|
+
// failure, leaving 2 untried, instead of the default threshold of 10.
|
|
1721
|
+
for (let i = 0; i < 5; i++) {
|
|
1722
|
+
await fanoutWithCustomThreshold.send(UserCreatedEvent, event);
|
|
1723
|
+
}
|
|
1724
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(5);
|
|
1725
|
+
connectedCallback?.();
|
|
1726
|
+
await waitForFlush(fanoutWithCustomThreshold);
|
|
1727
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(8);
|
|
1728
|
+
});
|
|
1729
|
+
it('should never bail out of a flush pass when maxConsecutiveFlushFailures is disabled (0)', async () => {
|
|
1730
|
+
let connectedCallback;
|
|
1731
|
+
const flakyTransport = {
|
|
1732
|
+
...transport,
|
|
1733
|
+
send: mock(async () => {
|
|
1734
|
+
throw new Error('Broker rejecting everything');
|
|
1735
|
+
}),
|
|
1736
|
+
onConnected: (cb) => {
|
|
1737
|
+
connectedCallback = cb;
|
|
1738
|
+
return () => { };
|
|
1739
|
+
},
|
|
1740
|
+
};
|
|
1741
|
+
const fanoutWithBreakerDisabled = new FanoutEngine({
|
|
1742
|
+
transport: flakyTransport,
|
|
1743
|
+
schema,
|
|
1744
|
+
hooks,
|
|
1745
|
+
topology: testTopology,
|
|
1746
|
+
defaultQueue: 'events',
|
|
1747
|
+
maxConsecutiveFlushFailures: 0,
|
|
1748
|
+
});
|
|
1749
|
+
const subscriber = createSubscriber({
|
|
1750
|
+
name: 'handle-user',
|
|
1751
|
+
description: 'Handles user events',
|
|
1752
|
+
callback: async () => { },
|
|
1753
|
+
});
|
|
1754
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1755
|
+
const event = new UserCreatedEvent({
|
|
1756
|
+
userId: '123',
|
|
1757
|
+
email: 'test@example.com',
|
|
1758
|
+
});
|
|
1759
|
+
// 15 sends buffered, well past the default breaker threshold of 10;
|
|
1760
|
+
// with the breaker disabled every single one should still be attempted.
|
|
1761
|
+
for (let i = 0; i < 15; i++) {
|
|
1762
|
+
await fanoutWithBreakerDisabled.send(UserCreatedEvent, event);
|
|
1763
|
+
}
|
|
1764
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(15);
|
|
1765
|
+
connectedCallback?.();
|
|
1766
|
+
await waitForFlush(fanoutWithBreakerDisabled);
|
|
1767
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(30);
|
|
1768
|
+
});
|
|
1769
|
+
it('should not trip the failure breaker on isolated failures interspersed with successes', async () => {
|
|
1770
|
+
let connectedCallback;
|
|
1771
|
+
let phase = 'buffering';
|
|
1772
|
+
let flushCallIndex = 0;
|
|
1773
|
+
const onEnqueueSuccess = mock(async () => { });
|
|
1774
|
+
const hooksInstance = new SafeHooks({ onEnqueueSuccess });
|
|
1775
|
+
const mostlyHealthyTransport = {
|
|
1776
|
+
...transport,
|
|
1777
|
+
send: mock(async () => {
|
|
1778
|
+
if (phase === 'buffering') {
|
|
1779
|
+
throw new Error('Initially down');
|
|
1780
|
+
}
|
|
1781
|
+
flushCallIndex++;
|
|
1782
|
+
// Every 3rd flushed item fails — isolated, never two in a row.
|
|
1783
|
+
if (flushCallIndex % 3 === 0)
|
|
1784
|
+
throw new Error('Occasional nack');
|
|
1785
|
+
return 'mock';
|
|
1786
|
+
}),
|
|
1787
|
+
onConnected: (cb) => {
|
|
1788
|
+
connectedCallback = cb;
|
|
1789
|
+
return () => { };
|
|
1790
|
+
},
|
|
1791
|
+
};
|
|
1792
|
+
const fanoutWithReconnect = new FanoutEngine({
|
|
1793
|
+
transport: mostlyHealthyTransport,
|
|
1794
|
+
schema,
|
|
1795
|
+
hooks: hooksInstance,
|
|
1796
|
+
topology: testTopology,
|
|
1797
|
+
defaultQueue: 'events',
|
|
1798
|
+
});
|
|
1799
|
+
const subscriber = createSubscriber({
|
|
1800
|
+
name: 'handle-user',
|
|
1801
|
+
description: 'Handles user events',
|
|
1802
|
+
callback: async () => { },
|
|
1803
|
+
});
|
|
1804
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1805
|
+
const event = new UserCreatedEvent({
|
|
1806
|
+
userId: '123',
|
|
1807
|
+
email: 'test@example.com',
|
|
1808
|
+
});
|
|
1809
|
+
for (let i = 0; i < 9; i++) {
|
|
1810
|
+
await fanoutWithReconnect.send(UserCreatedEvent, event);
|
|
1811
|
+
}
|
|
1812
|
+
expect(mostlyHealthyTransport.send).toHaveBeenCalledTimes(9);
|
|
1813
|
+
// Every 3rd flushed item fails, but never two in a row, so the
|
|
1814
|
+
// breaker (threshold 10) never trips and all 9 get attempted.
|
|
1815
|
+
phase = 'flushing';
|
|
1816
|
+
connectedCallback?.();
|
|
1817
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1818
|
+
expect(mostlyHealthyTransport.send).toHaveBeenCalledTimes(9 + 9);
|
|
1819
|
+
expect(onEnqueueSuccess).toHaveBeenCalledTimes(9 - 3);
|
|
1820
|
+
});
|
|
1821
|
+
it('should drop untried messages that no longer fit once maxRetryBufferSize is hit by concurrent buffering', async () => {
|
|
1822
|
+
let connectedCallback;
|
|
1823
|
+
let phase = 'buffering';
|
|
1824
|
+
let flushCallIndex = 0;
|
|
1825
|
+
let triggeredConcurrentBuffering = false;
|
|
1826
|
+
let fanoutWithReconnect;
|
|
1827
|
+
const onEnqueueError = mock(async () => { });
|
|
1828
|
+
const hooksInstance = new SafeHooks({ onEnqueueError });
|
|
1829
|
+
const subscriber = createSubscriber({
|
|
1830
|
+
name: 'handle-user',
|
|
1831
|
+
description: 'Handles user events',
|
|
1832
|
+
callback: async () => { },
|
|
1833
|
+
});
|
|
1834
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1835
|
+
const event = new UserCreatedEvent({
|
|
1836
|
+
userId: '123',
|
|
1837
|
+
email: 'test@example.com',
|
|
1838
|
+
});
|
|
1839
|
+
const flakyTransport = {
|
|
1840
|
+
...transport,
|
|
1841
|
+
send: mock(async () => {
|
|
1842
|
+
if (phase === 'buffering') {
|
|
1843
|
+
throw new Error('Initially down');
|
|
1844
|
+
}
|
|
1845
|
+
flushCallIndex++;
|
|
1846
|
+
// Simulate a concurrent send() buffering more items mid-flush.
|
|
1847
|
+
if (flushCallIndex === 5 && !triggeredConcurrentBuffering) {
|
|
1848
|
+
triggeredConcurrentBuffering = true;
|
|
1849
|
+
for (let i = 0; i < 4; i++) {
|
|
1850
|
+
await fanoutWithReconnect.send(UserCreatedEvent, event);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
throw new Error('Still down');
|
|
1854
|
+
}),
|
|
1855
|
+
onConnected: (cb) => {
|
|
1856
|
+
connectedCallback = cb;
|
|
1857
|
+
return () => { };
|
|
1858
|
+
},
|
|
1859
|
+
};
|
|
1860
|
+
fanoutWithReconnect = new FanoutEngine({
|
|
1861
|
+
transport: flakyTransport,
|
|
1862
|
+
schema,
|
|
1863
|
+
hooks: hooksInstance,
|
|
1864
|
+
topology: testTopology,
|
|
1865
|
+
defaultQueue: 'events',
|
|
1866
|
+
maxRetryBufferSize: 13,
|
|
1867
|
+
});
|
|
1868
|
+
// 11 sends buffered under the cap of 13 — nothing dropped yet.
|
|
1869
|
+
for (let i = 0; i < 11; i++) {
|
|
1870
|
+
await fanoutWithReconnect.send(UserCreatedEvent, event);
|
|
1871
|
+
}
|
|
1872
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(11);
|
|
1873
|
+
// Flush: 10 consecutive failures trip the breaker, leaving the 11th
|
|
1874
|
+
// item untried. The 4 concurrently-buffered items (injected mid-pass)
|
|
1875
|
+
// fill the buffer to its cap of 13 by the time the streak trips, so
|
|
1876
|
+
// both the 10th flush item (existing buffer-full path) and the
|
|
1877
|
+
// untried 11th item (rebufferUntried's buffer-full path) get dropped.
|
|
1878
|
+
phase = 'flushing';
|
|
1879
|
+
connectedCallback?.();
|
|
1880
|
+
await waitForFlush(fanoutWithReconnect);
|
|
1881
|
+
// 11 initial + 10 flush-loop attempts + 4 injected.
|
|
1882
|
+
expect(flakyTransport.send).toHaveBeenCalledTimes(11 + 10 + 4);
|
|
1883
|
+
expect(onEnqueueError).toHaveBeenCalledTimes(2);
|
|
1884
|
+
});
|
|
1885
|
+
it('should stop the periodic retry timer after dispose()', async () => {
|
|
1886
|
+
const subscriber = createSubscriber({
|
|
1887
|
+
name: 'handle-user',
|
|
1888
|
+
description: 'Handles user events',
|
|
1889
|
+
callback: async () => { },
|
|
1890
|
+
});
|
|
1891
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1892
|
+
const failingTransport = {
|
|
1893
|
+
...transport,
|
|
1894
|
+
send: mock(async () => {
|
|
1895
|
+
throw new Error('Connection lost');
|
|
1896
|
+
}),
|
|
1897
|
+
};
|
|
1898
|
+
const fanoutWithInterval = new FanoutEngine({
|
|
1899
|
+
transport: failingTransport,
|
|
1900
|
+
schema,
|
|
1901
|
+
hooks,
|
|
1902
|
+
topology: testTopology,
|
|
1903
|
+
defaultQueue: 'events',
|
|
1904
|
+
retryIntervalMs: 20,
|
|
1905
|
+
});
|
|
1906
|
+
const event = new UserCreatedEvent({
|
|
1907
|
+
userId: '123',
|
|
1908
|
+
email: 'test@example.com',
|
|
1909
|
+
});
|
|
1910
|
+
await fanoutWithInterval.send(UserCreatedEvent, event);
|
|
1911
|
+
expect(failingTransport.send).toHaveBeenCalledTimes(1);
|
|
1912
|
+
fanoutWithInterval.dispose();
|
|
1913
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1914
|
+
// No further flush attempts after dispose, despite the buffered message.
|
|
1915
|
+
expect(failingTransport.send).toHaveBeenCalledTimes(1);
|
|
1916
|
+
});
|
|
1917
|
+
it('should drop a message after exceeding maxRetryAttempts instead of retrying forever', async () => {
|
|
1918
|
+
const subscriber = createSubscriber({
|
|
1919
|
+
name: 'handle-user',
|
|
1920
|
+
description: 'Handles user events',
|
|
1921
|
+
callback: async () => { },
|
|
1922
|
+
});
|
|
1923
|
+
schema.register(UserCreatedEvent, [subscriber]);
|
|
1924
|
+
let connectedCallback;
|
|
1925
|
+
const failingTransport = {
|
|
1926
|
+
...transport,
|
|
1927
|
+
send: mock(async () => {
|
|
1928
|
+
throw new Error('Still down');
|
|
1929
|
+
}),
|
|
1930
|
+
onConnected: (cb) => {
|
|
1931
|
+
connectedCallback = cb;
|
|
1932
|
+
return () => { };
|
|
1933
|
+
},
|
|
1934
|
+
};
|
|
1935
|
+
const onEnqueueError = mock(async () => { });
|
|
1936
|
+
const hooksInstance = new SafeHooks({
|
|
1937
|
+
onEnqueueError,
|
|
1938
|
+
});
|
|
1939
|
+
const fanoutWithMaxAttempts = new FanoutEngine({
|
|
1940
|
+
transport: failingTransport,
|
|
1941
|
+
schema,
|
|
1942
|
+
hooks: hooksInstance,
|
|
1943
|
+
topology: testTopology,
|
|
1944
|
+
defaultQueue: 'events',
|
|
1945
|
+
maxRetryAttempts: 2,
|
|
1946
|
+
});
|
|
1947
|
+
const event = new UserCreatedEvent({
|
|
1948
|
+
userId: '123',
|
|
1949
|
+
email: 'test@example.com',
|
|
1950
|
+
});
|
|
1951
|
+
// Initial send fails and buffers (attempts: 0).
|
|
1952
|
+
await fanoutWithMaxAttempts.send(UserCreatedEvent, event);
|
|
1953
|
+
expect(onEnqueueError).not.toHaveBeenCalled();
|
|
1954
|
+
// First flush fails: attempts becomes 1, still under the cap, re-buffered.
|
|
1955
|
+
connectedCallback?.();
|
|
1956
|
+
await waitForFlush(fanoutWithMaxAttempts);
|
|
1957
|
+
expect(onEnqueueError).not.toHaveBeenCalled();
|
|
1958
|
+
// Second flush fails: attempts becomes 2, meets the cap, dropped for good.
|
|
1959
|
+
connectedCallback?.();
|
|
1960
|
+
await waitForFlush(fanoutWithMaxAttempts);
|
|
1961
|
+
expect(onEnqueueError).toHaveBeenCalledTimes(1);
|
|
1962
|
+
// A further reconnect must not retry it again — it's gone.
|
|
1963
|
+
connectedCallback?.();
|
|
1964
|
+
await waitForFlush(fanoutWithMaxAttempts);
|
|
1965
|
+
expect(onEnqueueError).toHaveBeenCalledTimes(1);
|
|
1966
|
+
// 1 initial + 2 flush attempts = 3 total send() calls; no 4th.
|
|
1967
|
+
expect(failingTransport.send).toHaveBeenCalledTimes(3);
|
|
1968
|
+
});
|
|
1443
1969
|
});
|
|
1444
1970
|
});
|
package/dist/core/matador.d.ts
CHANGED
|
@@ -33,6 +33,39 @@ export interface MatadorConfig {
|
|
|
33
33
|
* won't persist across retries.
|
|
34
34
|
*/
|
|
35
35
|
readonly checkpointStore?: CheckpointStore | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Maximum number of in-memory buffered sends held for retry when the
|
|
38
|
+
* transport is unavailable.
|
|
39
|
+
*
|
|
40
|
+
* @default 5000
|
|
41
|
+
*/
|
|
42
|
+
readonly maxRetryBufferSize?: number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum number of retry attempts for a buffered message before it's
|
|
45
|
+
* dropped and reported via onEnqueueError, instead of being retried forever.
|
|
46
|
+
*
|
|
47
|
+
* @default undefined (no limit — retries until the buffer flushes successfully)
|
|
48
|
+
*/
|
|
49
|
+
readonly maxRetryAttempts?: number | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* How often (in ms) to attempt flushing the retry buffer, independent of
|
|
52
|
+
* transport reconnect events. Covers the case where the transport stays
|
|
53
|
+
* connected but an individual publish keeps failing.
|
|
54
|
+
*
|
|
55
|
+
* @default 30000. Pass 0 to disable the interval and rely on
|
|
56
|
+
* onConnected/manual flushes only.
|
|
57
|
+
*/
|
|
58
|
+
readonly retryIntervalMs?: number | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Number of consecutive flush failures tolerated within a single flush
|
|
61
|
+
* pass before bailing out early and re-buffering the untried remainder,
|
|
62
|
+
* instead of retrying every buffered message against a broker that's
|
|
63
|
+
* rejecting everything.
|
|
64
|
+
*
|
|
65
|
+
* @default 10. Pass 0 (or a negative number) to disable the breaker and
|
|
66
|
+
* always attempt every buffered message on every flush pass.
|
|
67
|
+
*/
|
|
68
|
+
readonly maxConsecutiveFlushFailures?: number | undefined;
|
|
36
69
|
}
|
|
37
70
|
/**
|
|
38
71
|
* Matador - Transport-agnostic event processing library.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matador.d.ts","sourceRoot":"","sources":["../../src/core/matador.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAQ/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,KAAK,EACL,UAAU,EACV,YAAY,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGnE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAE5B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAE/B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAErD,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAEnC,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAE/C,6BAA6B;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAE9D;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"matador.d.ts","sourceRoot":"","sources":["../../src/core/matador.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAQ/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,KAAK,EACL,UAAU,EACV,YAAY,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGnE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAE5B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAE/B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAErD,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAEnC,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAE/C,6BAA6B;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAE9D;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IAEvD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE/C;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE9C;;;;;;;;OAQG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3D;AAED;;;;;;;;;GASG;AACH,qBAAa,OAAQ,YAAW,UAAU;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,OAAO,CAAS;IAExB;;;;;;OAMG;gBACS,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,YAAY;IAgDvD;;OAEG;IACH,QAAQ,CAAC,CAAC,EACR,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,WAAW,EAAE,SAAS,aAAa,EAAE,GACpC,IAAI;IAKP;;;;;;;;;;;;;;;;;OAiBG;IACH,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAc3C;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiE5B;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,CAAC,EACV,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,UAAU,CAAC;IAChB,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAuC3E;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACG,WAAW,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAatD;;;;;;;;;;;OAWG;IACG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAQvC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAU/B;;OAEG;IACH,WAAW,IAAI,OAAO;YAIR,cAAc;CAM7B"}
|
package/dist/core/matador.js
CHANGED
|
@@ -66,6 +66,10 @@ export class Matador {
|
|
|
66
66
|
hooks: this.hooks,
|
|
67
67
|
topology: this.topology,
|
|
68
68
|
defaultQueue,
|
|
69
|
+
maxRetryBufferSize: config.maxRetryBufferSize,
|
|
70
|
+
maxRetryAttempts: config.maxRetryAttempts,
|
|
71
|
+
retryIntervalMs: config.retryIntervalMs,
|
|
72
|
+
maxConsecutiveFlushFailures: config.maxConsecutiveFlushFailures,
|
|
69
73
|
});
|
|
70
74
|
// Create shutdown manager
|
|
71
75
|
this.shutdownManager = new ShutdownManager(() => this.fanout.eventsBeingEnqueuedCount, () => this.unsubscribeAll(), () => this.transport.disconnect(), config.shutdownConfig);
|
|
@@ -247,7 +251,7 @@ export class Matador {
|
|
|
247
251
|
}
|
|
248
252
|
async unsubscribeAll() {
|
|
249
253
|
for (const subscription of this.subscriptions) {
|
|
250
|
-
await subscription.unsubscribe();
|
|
254
|
+
await (subscription.pauseForShutdown ?? subscription.unsubscribe)();
|
|
251
255
|
}
|
|
252
256
|
this.subscriptions.length = 0;
|
|
253
257
|
}
|