@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,524 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
|
2
|
+
import { ShutdownManager } from './shutdown.js';
|
|
3
|
+
describe('ShutdownManager', () => {
|
|
4
|
+
let getEnqueueCount;
|
|
5
|
+
let stopReceiving;
|
|
6
|
+
let disconnectTransport;
|
|
7
|
+
let manager;
|
|
8
|
+
let enqueuedCount;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
enqueuedCount = 0;
|
|
11
|
+
getEnqueueCount = mock(() => enqueuedCount);
|
|
12
|
+
stopReceiving = mock(async () => { });
|
|
13
|
+
disconnectTransport = mock(async () => { });
|
|
14
|
+
});
|
|
15
|
+
describe('static factory', () => {
|
|
16
|
+
it('should create instance via static create method', () => {
|
|
17
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
18
|
+
expect(manager).toBeInstanceOf(ShutdownManager);
|
|
19
|
+
expect(manager.state).toBe('running');
|
|
20
|
+
});
|
|
21
|
+
it('should accept partial config overrides', () => {
|
|
22
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, { gracefulShutdownTimeout: 5000 });
|
|
23
|
+
expect(manager).toBeInstanceOf(ShutdownManager);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('initial state', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
29
|
+
});
|
|
30
|
+
it('should start in running state', () => {
|
|
31
|
+
expect(manager.state).toBe('running');
|
|
32
|
+
});
|
|
33
|
+
it('should allow enqueue initially', () => {
|
|
34
|
+
expect(manager.isEnqueueAllowed).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('should report idle when no events processing', () => {
|
|
37
|
+
const state = manager.getHandlersState();
|
|
38
|
+
expect(state.isIdle).toBe(true);
|
|
39
|
+
expect(state.eventsBeingProcessed).toBe(0);
|
|
40
|
+
expect(state.eventsBeingEnqueued).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('getHandlersState', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
46
|
+
});
|
|
47
|
+
it('should return correct handler state with no activity', () => {
|
|
48
|
+
const state = manager.getHandlersState();
|
|
49
|
+
expect(state.eventsBeingProcessed).toBe(0);
|
|
50
|
+
expect(state.eventsBeingEnqueued).toBe(0);
|
|
51
|
+
expect(state.isIdle).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
it('should track events being processed', () => {
|
|
54
|
+
manager.incrementProcessing();
|
|
55
|
+
manager.incrementProcessing();
|
|
56
|
+
const state = manager.getHandlersState();
|
|
57
|
+
expect(state.eventsBeingProcessed).toBe(2);
|
|
58
|
+
expect(state.isIdle).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it('should track events being enqueued via callback', () => {
|
|
61
|
+
enqueuedCount = 3;
|
|
62
|
+
const state = manager.getHandlersState();
|
|
63
|
+
expect(state.eventsBeingEnqueued).toBe(3);
|
|
64
|
+
expect(state.isIdle).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
it('should report not idle when processing exists', () => {
|
|
67
|
+
manager.incrementProcessing();
|
|
68
|
+
enqueuedCount = 0;
|
|
69
|
+
const state = manager.getHandlersState();
|
|
70
|
+
expect(state.isIdle).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
it('should report not idle when enqueuing exists', () => {
|
|
73
|
+
enqueuedCount = 2;
|
|
74
|
+
const state = manager.getHandlersState();
|
|
75
|
+
expect(state.isIdle).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('should report idle only when both counters are zero', () => {
|
|
78
|
+
manager.incrementProcessing();
|
|
79
|
+
manager.incrementProcessing();
|
|
80
|
+
enqueuedCount = 1;
|
|
81
|
+
let state = manager.getHandlersState();
|
|
82
|
+
expect(state.isIdle).toBe(false);
|
|
83
|
+
manager.decrementProcessing();
|
|
84
|
+
state = manager.getHandlersState();
|
|
85
|
+
expect(state.isIdle).toBe(false);
|
|
86
|
+
manager.decrementProcessing();
|
|
87
|
+
state = manager.getHandlersState();
|
|
88
|
+
expect(state.isIdle).toBe(false);
|
|
89
|
+
enqueuedCount = 0;
|
|
90
|
+
state = manager.getHandlersState();
|
|
91
|
+
expect(state.isIdle).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('processing counter', () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
97
|
+
});
|
|
98
|
+
it('should increment processing counter', () => {
|
|
99
|
+
manager.incrementProcessing();
|
|
100
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(1);
|
|
101
|
+
manager.incrementProcessing();
|
|
102
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(2);
|
|
103
|
+
});
|
|
104
|
+
it('should decrement processing counter', () => {
|
|
105
|
+
manager.incrementProcessing();
|
|
106
|
+
manager.incrementProcessing();
|
|
107
|
+
manager.incrementProcessing();
|
|
108
|
+
manager.decrementProcessing();
|
|
109
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(2);
|
|
110
|
+
manager.decrementProcessing();
|
|
111
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('graceful shutdown', () => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
117
|
+
});
|
|
118
|
+
it('should transition through all shutdown states', async () => {
|
|
119
|
+
const states = [];
|
|
120
|
+
const checkState = async () => {
|
|
121
|
+
states.push(manager.state);
|
|
122
|
+
};
|
|
123
|
+
stopReceiving = mock(async () => {
|
|
124
|
+
await checkState();
|
|
125
|
+
});
|
|
126
|
+
disconnectTransport = mock(async () => {
|
|
127
|
+
await checkState();
|
|
128
|
+
});
|
|
129
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
130
|
+
const initialState = manager.state;
|
|
131
|
+
await manager.shutdown();
|
|
132
|
+
const finalState = manager.state;
|
|
133
|
+
expect(initialState).toBe('running');
|
|
134
|
+
expect(states[0]).toBe('stopping-receive');
|
|
135
|
+
expect(states[1]).toBe('disconnecting');
|
|
136
|
+
expect(finalState).toBe('stopped');
|
|
137
|
+
});
|
|
138
|
+
it('should call stopReceiving during shutdown', async () => {
|
|
139
|
+
await manager.shutdown();
|
|
140
|
+
expect(stopReceiving).toHaveBeenCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
it('should call disconnectTransport during shutdown', async () => {
|
|
143
|
+
await manager.shutdown();
|
|
144
|
+
expect(disconnectTransport).toHaveBeenCalledTimes(1);
|
|
145
|
+
});
|
|
146
|
+
it('should disable enqueue during shutdown', async () => {
|
|
147
|
+
expect(manager.isEnqueueAllowed).toBe(true);
|
|
148
|
+
await manager.shutdown();
|
|
149
|
+
expect(manager.isEnqueueAllowed).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it('should wait for in-flight messages to complete', async () => {
|
|
152
|
+
manager.incrementProcessing();
|
|
153
|
+
manager.incrementProcessing();
|
|
154
|
+
const shutdownPromise = manager.shutdown();
|
|
155
|
+
expect(manager.state).toBe('stopping-receive');
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
157
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
158
|
+
manager.decrementProcessing();
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
160
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
161
|
+
manager.decrementProcessing();
|
|
162
|
+
await shutdownPromise;
|
|
163
|
+
expect(manager.state).toBe('stopped');
|
|
164
|
+
});
|
|
165
|
+
it('should wait for enqueued messages to complete', async () => {
|
|
166
|
+
enqueuedCount = 2;
|
|
167
|
+
const shutdownPromise = manager.shutdown();
|
|
168
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
169
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
170
|
+
enqueuedCount = 0;
|
|
171
|
+
await shutdownPromise;
|
|
172
|
+
expect(manager.state).toBe('stopped');
|
|
173
|
+
});
|
|
174
|
+
it('should wait for both processing and enqueued to complete', async () => {
|
|
175
|
+
manager.incrementProcessing();
|
|
176
|
+
enqueuedCount = 1;
|
|
177
|
+
const shutdownPromise = manager.shutdown();
|
|
178
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
179
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
180
|
+
manager.decrementProcessing();
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
182
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
183
|
+
enqueuedCount = 0;
|
|
184
|
+
await shutdownPromise;
|
|
185
|
+
expect(manager.state).toBe('stopped');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('shutdown timeout', () => {
|
|
189
|
+
it('should force shutdown after timeout', async () => {
|
|
190
|
+
const logs = [];
|
|
191
|
+
const mockLogger = {
|
|
192
|
+
debug: (msg) => logs.push(`debug: ${msg}`),
|
|
193
|
+
info: (msg) => logs.push(`info: ${msg}`),
|
|
194
|
+
warn: (msg) => logs.push(`warn: ${msg}`),
|
|
195
|
+
error: (msg) => logs.push(`error: ${msg}`),
|
|
196
|
+
};
|
|
197
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
198
|
+
gracefulShutdownTimeout: 100,
|
|
199
|
+
idlePollingInterval: 20,
|
|
200
|
+
logger: mockLogger,
|
|
201
|
+
});
|
|
202
|
+
manager.incrementProcessing();
|
|
203
|
+
manager.incrementProcessing();
|
|
204
|
+
await manager.shutdown();
|
|
205
|
+
expect(manager.state).toBe('stopped');
|
|
206
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(2);
|
|
207
|
+
expect(logs.some((log) => log.includes('Shutdown timeout reached'))).toBe(true);
|
|
208
|
+
expect(logs.some((log) => log.includes('2 events still processing'))).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
it('should use custom timeout configuration', async () => {
|
|
211
|
+
const logs = [];
|
|
212
|
+
const mockLogger = {
|
|
213
|
+
debug: () => { },
|
|
214
|
+
info: () => { },
|
|
215
|
+
warn: (msg) => logs.push(msg),
|
|
216
|
+
error: () => { },
|
|
217
|
+
};
|
|
218
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
219
|
+
gracefulShutdownTimeout: 50,
|
|
220
|
+
idlePollingInterval: 10,
|
|
221
|
+
logger: mockLogger,
|
|
222
|
+
});
|
|
223
|
+
manager.incrementProcessing();
|
|
224
|
+
const startTime = Date.now();
|
|
225
|
+
await manager.shutdown();
|
|
226
|
+
const duration = Date.now() - startTime;
|
|
227
|
+
expect(duration).toBeGreaterThanOrEqual(50);
|
|
228
|
+
expect(duration).toBeLessThan(200);
|
|
229
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
230
|
+
});
|
|
231
|
+
it('should poll at configured interval', async () => {
|
|
232
|
+
let pollCount = 0;
|
|
233
|
+
const countingGetEnqueueCount = mock(() => {
|
|
234
|
+
pollCount++;
|
|
235
|
+
return 1;
|
|
236
|
+
});
|
|
237
|
+
manager = new ShutdownManager(countingGetEnqueueCount, stopReceiving, disconnectTransport, {
|
|
238
|
+
gracefulShutdownTimeout: 100,
|
|
239
|
+
idlePollingInterval: 20,
|
|
240
|
+
});
|
|
241
|
+
await manager.shutdown();
|
|
242
|
+
expect(pollCount).toBeGreaterThan(3);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe('idempotent shutdown', () => {
|
|
246
|
+
beforeEach(() => {
|
|
247
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
248
|
+
});
|
|
249
|
+
it('should be idempotent when called multiple times', async () => {
|
|
250
|
+
await manager.shutdown();
|
|
251
|
+
expect(manager.state).toBe('stopped');
|
|
252
|
+
await manager.shutdown();
|
|
253
|
+
expect(manager.state).toBe('stopped');
|
|
254
|
+
await manager.shutdown();
|
|
255
|
+
expect(manager.state).toBe('stopped');
|
|
256
|
+
expect(stopReceiving).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(disconnectTransport).toHaveBeenCalledTimes(1);
|
|
258
|
+
});
|
|
259
|
+
it('should handle concurrent shutdown calls', async () => {
|
|
260
|
+
const results = await Promise.all([
|
|
261
|
+
manager.shutdown(),
|
|
262
|
+
manager.shutdown(),
|
|
263
|
+
manager.shutdown(),
|
|
264
|
+
]);
|
|
265
|
+
expect(manager.state).toBe('stopped');
|
|
266
|
+
expect(results).toHaveLength(3);
|
|
267
|
+
expect(stopReceiving).toHaveBeenCalledTimes(1);
|
|
268
|
+
expect(disconnectTransport).toHaveBeenCalledTimes(1);
|
|
269
|
+
});
|
|
270
|
+
it('should not restart shutdown sequence if already shutting down', async () => {
|
|
271
|
+
manager.incrementProcessing();
|
|
272
|
+
const shutdown1 = manager.shutdown();
|
|
273
|
+
expect(manager.state).toBe('stopping-receive');
|
|
274
|
+
const shutdown2 = manager.shutdown();
|
|
275
|
+
manager.decrementProcessing();
|
|
276
|
+
await Promise.all([shutdown1, shutdown2]);
|
|
277
|
+
expect(manager.state).toBe('stopped');
|
|
278
|
+
expect(stopReceiving).toHaveBeenCalledTimes(1);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
describe('forceStop', () => {
|
|
282
|
+
beforeEach(() => {
|
|
283
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
284
|
+
});
|
|
285
|
+
it('should stop immediately without waiting', async () => {
|
|
286
|
+
manager.incrementProcessing();
|
|
287
|
+
manager.incrementProcessing();
|
|
288
|
+
enqueuedCount = 5;
|
|
289
|
+
await manager.forceStop();
|
|
290
|
+
expect(manager.state).toBe('stopped');
|
|
291
|
+
expect(manager.isEnqueueAllowed).toBe(false);
|
|
292
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(2);
|
|
293
|
+
expect(manager.getHandlersState().eventsBeingEnqueued).toBe(5);
|
|
294
|
+
});
|
|
295
|
+
it('should call stopReceiving', async () => {
|
|
296
|
+
await manager.forceStop();
|
|
297
|
+
expect(stopReceiving).toHaveBeenCalledTimes(1);
|
|
298
|
+
});
|
|
299
|
+
it('should call disconnectTransport', async () => {
|
|
300
|
+
await manager.forceStop();
|
|
301
|
+
expect(disconnectTransport).toHaveBeenCalledTimes(1);
|
|
302
|
+
});
|
|
303
|
+
it('should complete much faster than graceful shutdown', async () => {
|
|
304
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
305
|
+
gracefulShutdownTimeout: 1000,
|
|
306
|
+
});
|
|
307
|
+
manager.incrementProcessing();
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
await manager.forceStop();
|
|
310
|
+
const duration = Date.now() - startTime;
|
|
311
|
+
expect(duration).toBeLessThan(100);
|
|
312
|
+
});
|
|
313
|
+
it('should disable enqueue', async () => {
|
|
314
|
+
expect(manager.isEnqueueAllowed).toBe(true);
|
|
315
|
+
await manager.forceStop();
|
|
316
|
+
expect(manager.isEnqueueAllowed).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
describe('state transitions', () => {
|
|
320
|
+
beforeEach(() => {
|
|
321
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
322
|
+
});
|
|
323
|
+
it('should follow correct sequence: running -> stopping-receive -> waiting-handlers -> stopping-enqueue -> disconnecting -> stopped', async () => {
|
|
324
|
+
const states = [];
|
|
325
|
+
states.push(manager.state);
|
|
326
|
+
const originalStopReceiving = stopReceiving;
|
|
327
|
+
const originalDisconnect = disconnectTransport;
|
|
328
|
+
stopReceiving = mock(async () => {
|
|
329
|
+
await originalStopReceiving();
|
|
330
|
+
states.push(manager.state);
|
|
331
|
+
});
|
|
332
|
+
disconnectTransport = mock(async () => {
|
|
333
|
+
await originalDisconnect();
|
|
334
|
+
states.push(manager.state);
|
|
335
|
+
});
|
|
336
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
337
|
+
manager.incrementProcessing();
|
|
338
|
+
const shutdownPromise = manager.shutdown();
|
|
339
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
340
|
+
states.push(manager.state);
|
|
341
|
+
manager.decrementProcessing();
|
|
342
|
+
await shutdownPromise;
|
|
343
|
+
states.push(manager.state);
|
|
344
|
+
expect(states[0]).toBe('running');
|
|
345
|
+
expect(states[1]).toBe('stopping-receive');
|
|
346
|
+
expect(states[2]).toBe('waiting-handlers');
|
|
347
|
+
expect(states[3]).toBe('disconnecting');
|
|
348
|
+
expect(states[4]).toBe('stopped');
|
|
349
|
+
});
|
|
350
|
+
it('should remain in waiting-handlers while events are processing', async () => {
|
|
351
|
+
manager.incrementProcessing();
|
|
352
|
+
const shutdownPromise = manager.shutdown();
|
|
353
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
354
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
356
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
357
|
+
manager.decrementProcessing();
|
|
358
|
+
await shutdownPromise;
|
|
359
|
+
expect(manager.state).toBe('stopped');
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
describe('event handler deregistration during shutdown', () => {
|
|
363
|
+
beforeEach(() => {
|
|
364
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
365
|
+
});
|
|
366
|
+
it('should block new enqueue operations after stopping-enqueue state', async () => {
|
|
367
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
368
|
+
idlePollingInterval: 20,
|
|
369
|
+
});
|
|
370
|
+
manager.incrementProcessing();
|
|
371
|
+
expect(manager.isEnqueueAllowed).toBe(true);
|
|
372
|
+
const shutdownPromise = manager.shutdown();
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
374
|
+
expect(manager.state).toBe('waiting-handlers');
|
|
375
|
+
expect(manager.isEnqueueAllowed).toBe(true);
|
|
376
|
+
manager.decrementProcessing();
|
|
377
|
+
await shutdownPromise;
|
|
378
|
+
expect(manager.state).toBe('stopped');
|
|
379
|
+
expect(manager.isEnqueueAllowed).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
it('should allow completion of in-flight events during shutdown', async () => {
|
|
382
|
+
manager.incrementProcessing();
|
|
383
|
+
manager.incrementProcessing();
|
|
384
|
+
const initialCount = manager.getHandlersState().eventsBeingProcessed;
|
|
385
|
+
const shutdownPromise = manager.shutdown();
|
|
386
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
387
|
+
manager.decrementProcessing();
|
|
388
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(initialCount - 1);
|
|
389
|
+
manager.decrementProcessing();
|
|
390
|
+
await shutdownPromise;
|
|
391
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(0);
|
|
392
|
+
expect(manager.state).toBe('stopped');
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
describe('configuration', () => {
|
|
396
|
+
it('should use default configuration when none provided', () => {
|
|
397
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
398
|
+
expect(manager).toBeInstanceOf(ShutdownManager);
|
|
399
|
+
});
|
|
400
|
+
it('should accept custom gracefulShutdownTimeout', async () => {
|
|
401
|
+
const logs = [];
|
|
402
|
+
const mockLogger = {
|
|
403
|
+
debug: () => { },
|
|
404
|
+
info: () => { },
|
|
405
|
+
warn: (msg) => logs.push(msg),
|
|
406
|
+
error: () => { },
|
|
407
|
+
};
|
|
408
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
409
|
+
gracefulShutdownTimeout: 10,
|
|
410
|
+
idlePollingInterval: 5,
|
|
411
|
+
logger: mockLogger,
|
|
412
|
+
});
|
|
413
|
+
manager.incrementProcessing();
|
|
414
|
+
const startTime = Date.now();
|
|
415
|
+
await manager.shutdown();
|
|
416
|
+
const duration = Date.now() - startTime;
|
|
417
|
+
expect(duration).toBeGreaterThanOrEqual(10);
|
|
418
|
+
expect(duration).toBeLessThan(100);
|
|
419
|
+
});
|
|
420
|
+
it('should accept custom logger', async () => {
|
|
421
|
+
const logs = [];
|
|
422
|
+
const mockLogger = {
|
|
423
|
+
debug: (msg) => logs.push(msg),
|
|
424
|
+
info: (msg) => logs.push(msg),
|
|
425
|
+
warn: (msg) => logs.push(msg),
|
|
426
|
+
error: (msg) => logs.push(msg),
|
|
427
|
+
};
|
|
428
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
429
|
+
gracefulShutdownTimeout: 50,
|
|
430
|
+
logger: mockLogger,
|
|
431
|
+
});
|
|
432
|
+
manager.incrementProcessing();
|
|
433
|
+
await manager.shutdown();
|
|
434
|
+
expect(logs.some((log) => log.includes('Shutdown timeout reached'))).toBe(true);
|
|
435
|
+
});
|
|
436
|
+
it('should accept custom idlePollingInterval', async () => {
|
|
437
|
+
let pollCount = 0;
|
|
438
|
+
const countingGetEnqueueCount = mock(() => {
|
|
439
|
+
pollCount++;
|
|
440
|
+
if (pollCount > 2) {
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
return 1;
|
|
444
|
+
});
|
|
445
|
+
manager = new ShutdownManager(countingGetEnqueueCount, stopReceiving, disconnectTransport, {
|
|
446
|
+
idlePollingInterval: 10,
|
|
447
|
+
gracefulShutdownTimeout: 1000,
|
|
448
|
+
});
|
|
449
|
+
const startTime = Date.now();
|
|
450
|
+
await manager.shutdown();
|
|
451
|
+
const duration = Date.now() - startTime;
|
|
452
|
+
expect(pollCount).toBeGreaterThanOrEqual(2);
|
|
453
|
+
expect(duration).toBeGreaterThanOrEqual(20);
|
|
454
|
+
expect(duration).toBeLessThan(200);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
describe('error handling', () => {
|
|
458
|
+
it('should handle errors in stopReceiving', async () => {
|
|
459
|
+
const error = new Error('Stop receiving failed');
|
|
460
|
+
stopReceiving = mock(async () => {
|
|
461
|
+
throw error;
|
|
462
|
+
});
|
|
463
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
464
|
+
await expect(manager.shutdown()).rejects.toThrow('Stop receiving failed');
|
|
465
|
+
});
|
|
466
|
+
it('should handle errors in disconnectTransport', async () => {
|
|
467
|
+
const error = new Error('Disconnect failed');
|
|
468
|
+
disconnectTransport = mock(async () => {
|
|
469
|
+
throw error;
|
|
470
|
+
});
|
|
471
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
472
|
+
await expect(manager.shutdown()).rejects.toThrow('Disconnect failed');
|
|
473
|
+
});
|
|
474
|
+
it('should handle errors in getEnqueueCount callback', async () => {
|
|
475
|
+
getEnqueueCount = mock(() => {
|
|
476
|
+
throw new Error('Count failed');
|
|
477
|
+
});
|
|
478
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
479
|
+
expect(() => manager.getHandlersState()).toThrow('Count failed');
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
describe('edge cases', () => {
|
|
483
|
+
beforeEach(() => {
|
|
484
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
|
|
485
|
+
});
|
|
486
|
+
it('should handle shutdown with no active events', async () => {
|
|
487
|
+
await manager.shutdown();
|
|
488
|
+
expect(manager.state).toBe('stopped');
|
|
489
|
+
expect(manager.getHandlersState().isIdle).toBe(true);
|
|
490
|
+
});
|
|
491
|
+
it('should handle rapid increment/decrement cycles', () => {
|
|
492
|
+
for (let i = 0; i < 100; i++) {
|
|
493
|
+
manager.incrementProcessing();
|
|
494
|
+
}
|
|
495
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(100);
|
|
496
|
+
for (let i = 0; i < 100; i++) {
|
|
497
|
+
manager.decrementProcessing();
|
|
498
|
+
}
|
|
499
|
+
expect(manager.getHandlersState().eventsBeingProcessed).toBe(0);
|
|
500
|
+
expect(manager.getHandlersState().isIdle).toBe(true);
|
|
501
|
+
});
|
|
502
|
+
it('should handle events completing exactly at timeout', async () => {
|
|
503
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
504
|
+
gracefulShutdownTimeout: 100,
|
|
505
|
+
idlePollingInterval: 50,
|
|
506
|
+
});
|
|
507
|
+
manager.incrementProcessing();
|
|
508
|
+
const shutdownPromise = manager.shutdown();
|
|
509
|
+
setTimeout(() => {
|
|
510
|
+
manager.decrementProcessing();
|
|
511
|
+
}, 90);
|
|
512
|
+
await shutdownPromise;
|
|
513
|
+
expect(manager.state).toBe('stopped');
|
|
514
|
+
});
|
|
515
|
+
it('should handle zero timeout gracefully', async () => {
|
|
516
|
+
manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport, {
|
|
517
|
+
gracefulShutdownTimeout: 0,
|
|
518
|
+
});
|
|
519
|
+
manager.incrementProcessing();
|
|
520
|
+
await manager.shutdown();
|
|
521
|
+
expect(manager.state).toBe('stopped');
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MatadorError } from './matador-errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a duplicate io() key is used within a single subscriber execution.
|
|
4
|
+
* Each io() call must have a unique key to prevent cache collisions.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DuplicateIoKeyError extends MatadorError {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly subscriberName: string;
|
|
9
|
+
readonly description: string;
|
|
10
|
+
constructor(key: string, subscriberName: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when checkpoint store operations fail.
|
|
14
|
+
*/
|
|
15
|
+
export declare class CheckpointStoreError extends MatadorError {
|
|
16
|
+
readonly operation: 'get' | 'set' | 'delete';
|
|
17
|
+
readonly envelopeId: string;
|
|
18
|
+
readonly cause: Error;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
constructor(operation: 'get' | 'set' | 'delete', envelopeId: string, cause: Error);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Type guard for DuplicateIoKeyError.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isDuplicateIoKeyError(error: unknown): error is DuplicateIoKeyError;
|
|
26
|
+
/**
|
|
27
|
+
* Type guard for CheckpointStoreError.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isCheckpointStoreError(error: unknown): error is CheckpointStoreError;
|
|
30
|
+
//# sourceMappingURL=checkpoint-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint-errors.d.ts","sourceRoot":"","sources":["../../src/errors/checkpoint-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;aASjC,GAAG,EAAE,MAAM;aACX,cAAc,EAAE,MAAM;IATxC,QAAQ,CAAC,WAAW,SAKmB;gBAGrB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM;CAOzC;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;aAOlC,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ;aACnC,UAAU,EAAE,MAAM;aAClB,KAAK,EAAE,KAAK;IAR9B,QAAQ,CAAC,WAAW,SAGsD;gBAGxD,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,EACnC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,KAAK;CAM/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,mBAAmB,CAE9B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAE/B"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MatadorError } from './matador-errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a duplicate io() key is used within a single subscriber execution.
|
|
4
|
+
* Each io() call must have a unique key to prevent cache collisions.
|
|
5
|
+
*/
|
|
6
|
+
export class DuplicateIoKeyError extends MatadorError {
|
|
7
|
+
key;
|
|
8
|
+
subscriberName;
|
|
9
|
+
description = 'The same io() key was used multiple times in a single subscriber execution. ' +
|
|
10
|
+
'Each io() call must have a unique key to ensure correct checkpoint behavior. ' +
|
|
11
|
+
'ACTION: Ensure all io() keys are unique within the subscriber. ' +
|
|
12
|
+
'For dynamic operations (e.g., loops), include a unique identifier in the key ' +
|
|
13
|
+
'(e.g., `process-item-${item.id}`).';
|
|
14
|
+
constructor(key, subscriberName) {
|
|
15
|
+
super(`Duplicate io key "${key}" in subscriber "${subscriberName}". ` +
|
|
16
|
+
`Each io() call must have a unique key.`);
|
|
17
|
+
this.key = key;
|
|
18
|
+
this.subscriberName = subscriberName;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when checkpoint store operations fail.
|
|
23
|
+
*/
|
|
24
|
+
export class CheckpointStoreError extends MatadorError {
|
|
25
|
+
operation;
|
|
26
|
+
envelopeId;
|
|
27
|
+
cause;
|
|
28
|
+
description = 'A checkpoint store operation failed. This may cause issues with resumable subscribers. ' +
|
|
29
|
+
'ACTION: Check the underlying storage system (Redis, etc.) for connectivity issues. ' +
|
|
30
|
+
'The subscriber may re-execute operations that were already completed.';
|
|
31
|
+
constructor(operation, envelopeId, cause) {
|
|
32
|
+
super(`Checkpoint store ${operation} failed for envelope "${envelopeId}": ${cause.message}`);
|
|
33
|
+
this.operation = operation;
|
|
34
|
+
this.envelopeId = envelopeId;
|
|
35
|
+
this.cause = cause;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Type guard for DuplicateIoKeyError.
|
|
40
|
+
*/
|
|
41
|
+
export function isDuplicateIoKeyError(error) {
|
|
42
|
+
return error instanceof DuplicateIoKeyError;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Type guard for CheckpointStoreError.
|
|
46
|
+
*/
|
|
47
|
+
export function isCheckpointStoreError(error) {
|
|
48
|
+
return error instanceof CheckpointStoreError;
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for errors that provide a human-readable description.
|
|
3
|
+
* The description explains what the error means and what actions
|
|
4
|
+
* users should take when encountering it in logs/observability.
|
|
5
|
+
*/
|
|
6
|
+
export interface HasDescription {
|
|
7
|
+
/**
|
|
8
|
+
* Human-readable description of the error type.
|
|
9
|
+
* Intended for logging and observability to help users understand
|
|
10
|
+
* the error and determine appropriate remediation steps.
|
|
11
|
+
*/
|
|
12
|
+
readonly description: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if an error implements HasDescription.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasDescription(error: unknown): error is HasDescription;
|
|
18
|
+
//# sourceMappingURL=has-description.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has-description.d.ts","sourceRoot":"","sources":["../../src/errors/has-description.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAOtE"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { HasDescription } from './has-description.js';
|
|
2
|
+
export { hasDescription } from './has-description.js';
|
|
3
|
+
export { DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
|
|
4
|
+
export { MatadorError, isMatadorError, NotStartedError, isNotStartedError, ShutdownInProgressError, TransportNotConnectedError, isTransportNotConnectedError, TransportClosedError, TransportSendError, AllTransportsFailedError, DelayedMessagesNotSupportedError, EventNotRegisteredError, isEventNotRegisteredError, SubscriberNotRegisteredError, isSubscriberNotRegisteredError, NoSubscribersExistError, InvalidSchemaError, SubscriberIsStubError, LocalTransportCannotProcessStubError, QueueNotFoundError, InvalidEventError, MessageMaybePoisonedError, isMessageMaybePoisonedError, IdempotentMessageCannotRetryError, isIdempotentMessageCannotRetryError, TimeoutError, } from './matador-errors.js';
|
|
5
|
+
export { CheckpointStoreError, DuplicateIoKeyError, isCheckpointStoreError, isDuplicateIoKeyError, } from './checkpoint-errors.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EACL,SAAS,EACT,OAAO,EACP,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAEL,YAAY,EACZ,cAAc,EAEd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EAEvB,0BAA0B,EAC1B,4BAA4B,EAC5B,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,gCAAgC,EAEhC,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,oCAAoC,EAEpC,kBAAkB,EAElB,iBAAiB,EAEjB,yBAAyB,EACzB,2BAA2B,EAC3B,iCAAiC,EACjC,mCAAmC,EAEnC,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAEL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { hasDescription } from './has-description.js';
|
|
2
|
+
export { DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
|
|
3
|
+
export {
|
|
4
|
+
// Base class
|
|
5
|
+
MatadorError, isMatadorError,
|
|
6
|
+
// Lifecycle errors
|
|
7
|
+
NotStartedError, isNotStartedError, ShutdownInProgressError,
|
|
8
|
+
// Transport errors
|
|
9
|
+
TransportNotConnectedError, isTransportNotConnectedError, TransportClosedError, TransportSendError, AllTransportsFailedError, DelayedMessagesNotSupportedError,
|
|
10
|
+
// Schema & configuration errors
|
|
11
|
+
EventNotRegisteredError, isEventNotRegisteredError, SubscriberNotRegisteredError, isSubscriberNotRegisteredError, NoSubscribersExistError, InvalidSchemaError, SubscriberIsStubError, LocalTransportCannotProcessStubError,
|
|
12
|
+
// Queue errors
|
|
13
|
+
QueueNotFoundError,
|
|
14
|
+
// Event validation errors
|
|
15
|
+
InvalidEventError,
|
|
16
|
+
// Message processing errors
|
|
17
|
+
MessageMaybePoisonedError, isMessageMaybePoisonedError, IdempotentMessageCannotRetryError, isIdempotentMessageCannotRetryError,
|
|
18
|
+
// Timeout errors
|
|
19
|
+
TimeoutError, } from './matador-errors.js';
|
|
20
|
+
export {
|
|
21
|
+
// Checkpoint errors
|
|
22
|
+
CheckpointStoreError, DuplicateIoKeyError, isCheckpointStoreError, isDuplicateIoKeyError, } from './checkpoint-errors.js';
|