@zdavison/matador 2.0.9 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/checkpoint/context.d.ts +59 -0
  2. package/dist/checkpoint/context.d.ts.map +1 -0
  3. package/dist/checkpoint/context.js +140 -0
  4. package/dist/checkpoint/context.test.d.ts +2 -0
  5. package/dist/checkpoint/context.test.d.ts.map +1 -0
  6. package/dist/checkpoint/context.test.js +424 -0
  7. package/dist/checkpoint/index.d.ts +7 -0
  8. package/dist/checkpoint/index.d.ts.map +1 -0
  9. package/dist/checkpoint/index.js +6 -0
  10. package/dist/checkpoint/stores/memory.d.ts +29 -0
  11. package/dist/checkpoint/stores/memory.d.ts.map +1 -0
  12. package/dist/checkpoint/stores/memory.js +39 -0
  13. package/{src/checkpoint/stores/noop.ts → dist/checkpoint/stores/noop.d.ts} +5 -13
  14. package/dist/checkpoint/stores/noop.d.ts.map +1 -0
  15. package/dist/checkpoint/stores/noop.js +18 -0
  16. package/dist/checkpoint/stores/stores.test.d.ts +2 -0
  17. package/dist/checkpoint/stores/stores.test.d.ts.map +1 -0
  18. package/dist/checkpoint/stores/stores.test.js +146 -0
  19. package/dist/checkpoint/types.d.ts +119 -0
  20. package/dist/checkpoint/types.d.ts.map +1 -0
  21. package/dist/checkpoint/types.js +1 -0
  22. package/dist/codec/codec.d.ts +29 -0
  23. package/dist/codec/codec.d.ts.map +1 -0
  24. package/dist/codec/codec.js +15 -0
  25. package/dist/codec/header-aware-codec.d.ts +36 -0
  26. package/dist/codec/header-aware-codec.d.ts.map +1 -0
  27. package/dist/codec/header-aware-codec.js +1 -0
  28. package/{src/codec/index.ts → dist/codec/index.d.ts} +2 -7
  29. package/dist/codec/index.d.ts.map +1 -0
  30. package/dist/codec/index.js +3 -0
  31. package/dist/codec/json-codec.d.ts +13 -0
  32. package/dist/codec/json-codec.d.ts.map +1 -0
  33. package/dist/codec/json-codec.js +64 -0
  34. package/dist/codec/rabbitmq-codec.d.ts +28 -0
  35. package/dist/codec/rabbitmq-codec.d.ts.map +1 -0
  36. package/dist/codec/rabbitmq-codec.js +242 -0
  37. package/dist/codec/rabbitmq-codec.test.d.ts +2 -0
  38. package/dist/codec/rabbitmq-codec.test.d.ts.map +1 -0
  39. package/dist/codec/rabbitmq-codec.test.js +433 -0
  40. package/dist/core/fanout.d.ts +59 -0
  41. package/dist/core/fanout.d.ts.map +1 -0
  42. package/dist/core/fanout.js +121 -0
  43. package/dist/core/fanout.test.d.ts +2 -0
  44. package/dist/core/fanout.test.d.ts.map +1 -0
  45. package/dist/core/fanout.test.js +1054 -0
  46. package/{src/core/index.ts → dist/core/index.d.ts} +2 -7
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +3 -0
  49. package/dist/core/matador.d.ts +133 -0
  50. package/dist/core/matador.d.ts.map +1 -0
  51. package/dist/core/matador.js +228 -0
  52. package/dist/core/matador.test.d.ts +2 -0
  53. package/dist/core/matador.test.d.ts.map +1 -0
  54. package/dist/core/matador.test.js +446 -0
  55. package/dist/core/shutdown.d.ts +78 -0
  56. package/dist/core/shutdown.d.ts.map +1 -0
  57. package/dist/core/shutdown.js +111 -0
  58. package/dist/core/shutdown.test.d.ts +2 -0
  59. package/dist/core/shutdown.test.d.ts.map +1 -0
  60. package/dist/core/shutdown.test.js +524 -0
  61. package/dist/errors/checkpoint-errors.d.ts +30 -0
  62. package/dist/errors/checkpoint-errors.d.ts.map +1 -0
  63. package/dist/errors/checkpoint-errors.js +49 -0
  64. package/dist/errors/has-description.d.ts +18 -0
  65. package/dist/errors/has-description.d.ts.map +1 -0
  66. package/dist/errors/has-description.js +9 -0
  67. package/dist/errors/index.d.ts +6 -0
  68. package/dist/errors/index.d.ts.map +1 -0
  69. package/dist/errors/index.js +22 -0
  70. package/dist/errors/matador-errors.d.ts +183 -0
  71. package/dist/errors/matador-errors.d.ts.map +1 -0
  72. package/dist/errors/matador-errors.js +376 -0
  73. package/dist/errors/retry-errors.d.ts +102 -0
  74. package/dist/errors/retry-errors.d.ts.map +1 -0
  75. package/dist/errors/retry-errors.js +155 -0
  76. package/dist/errors/retry-errors.test.d.ts +2 -0
  77. package/dist/errors/retry-errors.test.d.ts.map +1 -0
  78. package/dist/errors/retry-errors.test.js +136 -0
  79. package/dist/hooks/index.d.ts +4 -0
  80. package/dist/hooks/index.d.ts.map +1 -0
  81. package/dist/hooks/index.js +2 -0
  82. package/dist/hooks/safe-hooks.d.ts +32 -0
  83. package/dist/hooks/safe-hooks.d.ts.map +1 -0
  84. package/dist/hooks/safe-hooks.js +120 -0
  85. package/dist/hooks/types.d.ts +172 -0
  86. package/dist/hooks/types.d.ts.map +1 -0
  87. package/dist/hooks/types.js +9 -0
  88. package/dist/index.cjs +3228 -0
  89. package/dist/index.cjs.map +1 -0
  90. package/dist/index.d.cts +23 -0
  91. package/dist/index.d.ts +23 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +31 -0
  94. package/dist/index.js.map +1 -0
  95. package/{src/pipeline/index.ts → dist/pipeline/index.d.ts} +1 -0
  96. package/dist/pipeline/index.d.ts.map +1 -0
  97. package/dist/pipeline/index.js +1 -0
  98. package/dist/pipeline/pipeline.d.ts +63 -0
  99. package/dist/pipeline/pipeline.d.ts.map +1 -0
  100. package/dist/pipeline/pipeline.js +273 -0
  101. package/dist/pipeline/pipeline.test.d.ts +2 -0
  102. package/dist/pipeline/pipeline.test.d.ts.map +1 -0
  103. package/dist/pipeline/pipeline.test.js +1112 -0
  104. package/{src/retry/index.ts → dist/retry/index.d.ts} +1 -1
  105. package/dist/retry/index.d.ts.map +1 -0
  106. package/dist/retry/index.js +1 -0
  107. package/dist/retry/policy.d.ts +43 -0
  108. package/dist/retry/policy.d.ts.map +1 -0
  109. package/dist/retry/policy.js +1 -0
  110. package/dist/retry/standard-policy.d.ts +44 -0
  111. package/dist/retry/standard-policy.d.ts.map +1 -0
  112. package/dist/retry/standard-policy.js +102 -0
  113. package/dist/retry/standard-policy.test.d.ts +2 -0
  114. package/dist/retry/standard-policy.test.d.ts.map +1 -0
  115. package/dist/retry/standard-policy.test.js +196 -0
  116. package/dist/schema/index.d.ts +4 -0
  117. package/dist/schema/index.d.ts.map +1 -0
  118. package/dist/schema/index.js +2 -0
  119. package/dist/schema/registry.d.ts +63 -0
  120. package/dist/schema/registry.d.ts.map +1 -0
  121. package/dist/schema/registry.js +174 -0
  122. package/dist/schema/registry.test.d.ts +2 -0
  123. package/dist/schema/registry.test.d.ts.map +1 -0
  124. package/dist/schema/registry.test.js +278 -0
  125. package/{src/schema/types.ts → dist/schema/types.d.ts} +29 -88
  126. package/dist/schema/types.d.ts.map +1 -0
  127. package/dist/schema/types.js +74 -0
  128. package/dist/schema/types.test.d.ts +2 -0
  129. package/dist/schema/types.test.d.ts.map +1 -0
  130. package/dist/schema/types.test.js +242 -0
  131. package/dist/topology/builder.d.ts +80 -0
  132. package/dist/topology/builder.d.ts.map +1 -0
  133. package/dist/topology/builder.js +165 -0
  134. package/dist/topology/builder.test.d.ts +2 -0
  135. package/dist/topology/builder.test.d.ts.map +1 -0
  136. package/dist/topology/builder.test.js +360 -0
  137. package/dist/topology/index.d.ts +5 -0
  138. package/dist/topology/index.d.ts.map +1 -0
  139. package/dist/topology/index.js +2 -0
  140. package/dist/topology/types.d.ts +128 -0
  141. package/dist/topology/types.d.ts.map +1 -0
  142. package/dist/topology/types.js +28 -0
  143. package/dist/transport/capabilities.d.ts +66 -0
  144. package/dist/transport/capabilities.d.ts.map +1 -0
  145. package/dist/transport/capabilities.js +18 -0
  146. package/dist/transport/connection-manager.d.ts +95 -0
  147. package/dist/transport/connection-manager.d.ts.map +1 -0
  148. package/dist/transport/connection-manager.js +144 -0
  149. package/dist/transport/index.d.ts +11 -0
  150. package/dist/transport/index.d.ts.map +1 -0
  151. package/dist/transport/index.js +5 -0
  152. package/dist/transport/local/local-transport.d.ts +62 -0
  153. package/dist/transport/local/local-transport.d.ts.map +1 -0
  154. package/dist/transport/local/local-transport.js +241 -0
  155. package/dist/transport/local/local-transport.test.d.ts +2 -0
  156. package/dist/transport/local/local-transport.test.d.ts.map +1 -0
  157. package/dist/transport/local/local-transport.test.js +192 -0
  158. package/dist/transport/multi/multi-transport.d.ts +94 -0
  159. package/dist/transport/multi/multi-transport.d.ts.map +1 -0
  160. package/dist/transport/multi/multi-transport.js +184 -0
  161. package/dist/transport/multi/multi-transport.test.d.ts +2 -0
  162. package/dist/transport/multi/multi-transport.test.d.ts.map +1 -0
  163. package/dist/transport/multi/multi-transport.test.js +236 -0
  164. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +83 -0
  165. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -0
  166. package/dist/transport/rabbitmq/rabbitmq-transport.js +541 -0
  167. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts +2 -0
  168. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts.map +1 -0
  169. package/dist/transport/rabbitmq/rabbitmq-transport.test.js +100 -0
  170. package/dist/transport/transport.d.ts +152 -0
  171. package/dist/transport/transport.d.ts.map +1 -0
  172. package/dist/transport/transport.js +1 -0
  173. package/{src/types/common.ts → dist/types/common.d.ts} +8 -20
  174. package/dist/types/common.d.ts.map +1 -0
  175. package/dist/types/common.js +12 -0
  176. package/dist/types/dispatcher.d.ts +14 -0
  177. package/dist/types/dispatcher.d.ts.map +1 -0
  178. package/dist/types/dispatcher.js +1 -0
  179. package/dist/types/envelope.d.ts +131 -0
  180. package/dist/types/envelope.d.ts.map +1 -0
  181. package/dist/types/envelope.js +99 -0
  182. package/dist/types/event.d.ts +92 -0
  183. package/dist/types/event.d.ts.map +1 -0
  184. package/dist/types/event.js +26 -0
  185. package/dist/types/event.test.d.ts +2 -0
  186. package/dist/types/event.test.d.ts.map +1 -0
  187. package/dist/types/event.test.js +130 -0
  188. package/dist/types/index.d.ts +11 -0
  189. package/dist/types/index.d.ts.map +1 -0
  190. package/dist/types/index.js +4 -0
  191. package/dist/types/subscriber.d.ts +227 -0
  192. package/dist/types/subscriber.d.ts.map +1 -0
  193. package/dist/types/subscriber.js +97 -0
  194. package/package.json +6 -2
  195. package/examples/config.ts +0 -126
  196. package/examples/event.ts +0 -26
  197. package/examples/order-event.json +0 -19
  198. package/src/checkpoint/context.test.ts +0 -510
  199. package/src/checkpoint/context.ts +0 -213
  200. package/src/checkpoint/index.ts +0 -30
  201. package/src/checkpoint/stores/memory.ts +0 -47
  202. package/src/checkpoint/stores/stores.test.ts +0 -177
  203. package/src/checkpoint/types.ts +0 -147
  204. package/src/codec/codec.ts +0 -42
  205. package/src/codec/header-aware-codec.ts +0 -41
  206. package/src/codec/json-codec.ts +0 -69
  207. package/src/codec/rabbitmq-codec.test.ts +0 -516
  208. package/src/codec/rabbitmq-codec.ts +0 -336
  209. package/src/core/fanout.test.ts +0 -1350
  210. package/src/core/fanout.ts +0 -184
  211. package/src/core/matador.test.ts +0 -575
  212. package/src/core/matador.ts +0 -357
  213. package/src/core/shutdown.test.ts +0 -853
  214. package/src/core/shutdown.ts +0 -165
  215. package/src/errors/checkpoint-errors.ts +0 -62
  216. package/src/errors/has-description.ts +0 -25
  217. package/src/errors/index.ts +0 -58
  218. package/src/errors/matador-errors.ts +0 -477
  219. package/src/errors/retry-errors.test.ts +0 -175
  220. package/src/errors/retry-errors.ts +0 -188
  221. package/src/hooks/index.ts +0 -14
  222. package/src/hooks/safe-hooks.ts +0 -200
  223. package/src/hooks/types.ts +0 -226
  224. package/src/index.ts +0 -242
  225. package/src/pipeline/pipeline.test.ts +0 -1377
  226. package/src/pipeline/pipeline.ts +0 -393
  227. package/src/retry/policy.ts +0 -46
  228. package/src/retry/standard-policy.test.ts +0 -290
  229. package/src/retry/standard-policy.ts +0 -156
  230. package/src/schema/index.ts +0 -16
  231. package/src/schema/registry.test.ts +0 -339
  232. package/src/schema/registry.ts +0 -229
  233. package/src/schema/types.test.ts +0 -280
  234. package/src/topology/builder.test.ts +0 -451
  235. package/src/topology/builder.ts +0 -238
  236. package/src/topology/index.ts +0 -19
  237. package/src/topology/types.ts +0 -183
  238. package/src/transport/capabilities.ts +0 -88
  239. package/src/transport/connection-manager.ts +0 -218
  240. package/src/transport/index.ts +0 -42
  241. package/src/transport/local/local-transport.test.ts +0 -262
  242. package/src/transport/local/local-transport.ts +0 -330
  243. package/src/transport/multi/multi-transport.test.ts +0 -320
  244. package/src/transport/multi/multi-transport.ts +0 -294
  245. package/src/transport/rabbitmq/rabbitmq-transport.test.ts +0 -120
  246. package/src/transport/rabbitmq/rabbitmq-transport.ts +0 -782
  247. package/src/transport/transport.ts +0 -200
  248. package/src/types/dispatcher.ts +0 -18
  249. package/src/types/envelope.ts +0 -244
  250. package/src/types/event.test.ts +0 -157
  251. package/src/types/event.ts +0 -112
  252. package/src/types/index.ts +0 -62
  253. package/src/types/subscriber.ts +0 -333
  254. package/test/e2e/multi-transport.e2e.test.ts +0 -237
  255. package/test/e2e/rabbitmq-transport.e2e.test.ts +0 -618
  256. package/test/e2e/transport-compliance.e2e.test.ts +0 -506
  257. package/test/integration/matador.integration.test.ts +0 -634
  258. package/tsconfig.json +0 -29
  259. package/tsup.config.ts +0 -13
@@ -1,393 +0,0 @@
1
- import type { Checkpoint, CheckpointStore } from '../checkpoint/index.js';
2
- import { NoOpCheckpointStore, ResumableContext } from '../checkpoint/index.js';
3
- import type { Codec } from '../codec/index.js';
4
- import { CodecDecodeError } from '../codec/index.js';
5
- import {
6
- SubscriberIsStubError,
7
- SubscriberNotRegisteredError,
8
- } from '../errors/index.js';
9
- import type { SafeHooks } from '../hooks/index.js';
10
- import type { RetryDecision, RetryPolicy } from '../retry/index.js';
11
- import type { SchemaRegistry } from '../schema/index.js';
12
- import type { MessageReceipt, Transport } from '../transport/index.js';
13
- import type {
14
- CallbackContext,
15
- Dispatcher,
16
- Envelope,
17
- ResumableCallbackContext,
18
- SubscriberDefinition,
19
- } from '../types/index.js';
20
- import { isResumableSubscriber } from '../types/index.js';
21
-
22
- /**
23
- * Configuration for the processing pipeline.
24
- */
25
- export interface PipelineConfig {
26
- readonly transport: Transport;
27
- readonly schema: SchemaRegistry;
28
- readonly codec: Codec;
29
- readonly retryPolicy: RetryPolicy;
30
- readonly hooks: SafeHooks;
31
- /** Optional checkpoint store for resumable subscribers */
32
- readonly checkpointStore?: CheckpointStore | undefined;
33
- /** Dispatcher (matador) for sending events from subscriber callbacks */
34
- readonly dispatcher: Dispatcher;
35
- }
36
-
37
- /**
38
- * Result of pipeline processing.
39
- */
40
- export interface ProcessResult {
41
- readonly success: boolean;
42
- readonly envelope?: Envelope | undefined;
43
- readonly subscriber?: SubscriberDefinition | undefined;
44
- readonly error?: Error | undefined;
45
- readonly decision?: RetryDecision | undefined;
46
- readonly durationMs: number;
47
- }
48
-
49
- /**
50
- * Processing pipeline for incoming messages.
51
- *
52
- * Handles the complete message lifecycle:
53
- * 1. Decode envelope from raw bytes
54
- * 2. Lookup subscriber from schema
55
- * 3. Execute subscriber callback with hooks
56
- * 4. Handle success/failure with retry policy
57
- */
58
- export class ProcessingPipeline {
59
- private readonly transport: Transport;
60
- private readonly schema: SchemaRegistry;
61
- private readonly codec: Codec;
62
- private readonly retryPolicy: RetryPolicy;
63
- private readonly hooks: SafeHooks;
64
- private readonly checkpointStore: CheckpointStore;
65
- private readonly dispatcher: Dispatcher;
66
-
67
- constructor(config: PipelineConfig) {
68
- this.transport = config.transport;
69
- this.schema = config.schema;
70
- this.codec = config.codec;
71
- this.retryPolicy = config.retryPolicy;
72
- this.hooks = config.hooks;
73
- this.checkpointStore = config.checkpointStore ?? new NoOpCheckpointStore();
74
- this.dispatcher = config.dispatcher;
75
- }
76
-
77
- /**
78
- * Processes a raw message from the transport.
79
- */
80
- async process(
81
- rawMessage: Uint8Array,
82
- receipt: MessageReceipt,
83
- ): Promise<ProcessResult> {
84
- const startTime = performance.now();
85
-
86
- // 1. Decode envelope
87
- let envelope: Envelope;
88
- try {
89
- envelope = this.codec.decode(rawMessage);
90
- } catch (error) {
91
- const decodeError =
92
- error instanceof CodecDecodeError
93
- ? error
94
- : new CodecDecodeError('Unknown decode error', error);
95
-
96
- await this.transport.complete(receipt);
97
- await this.hooks.onDecodeError({
98
- error: decodeError,
99
- rawMessage,
100
- sourceQueue: receipt.sourceQueue,
101
- transport: receipt.sourceTransport,
102
- });
103
-
104
- return {
105
- success: false,
106
- error: decodeError,
107
- durationMs: performance.now() - startTime,
108
- };
109
- }
110
-
111
- // 2. Lookup subscriber from schema
112
- const subscriberDef = this.schema.getSubscriberDefinition(
113
- envelope.docket.eventKey,
114
- envelope.docket.targetSubscriber,
115
- );
116
-
117
- if (!subscriberDef) {
118
- const error = new SubscriberNotRegisteredError(
119
- envelope.docket.targetSubscriber,
120
- envelope.docket.eventKey,
121
- );
122
-
123
- // Retry unhandled messages (likely deployment timing issue)
124
- const decision = this.getUnhandledRetryDecision(envelope, receipt, error);
125
- await this.handleRetryDecision(receipt, envelope, decision);
126
-
127
- return {
128
- success: false,
129
- envelope,
130
- error,
131
- decision,
132
- durationMs: performance.now() - startTime,
133
- };
134
- }
135
-
136
- // Get executable subscriber
137
- const subscriber = this.schema.getExecutableSubscriber(
138
- envelope.docket.eventKey,
139
- envelope.docket.targetSubscriber,
140
- );
141
-
142
- if (!subscriber) {
143
- // Subscriber is a stub (remote implementation)
144
- const error = new SubscriberIsStubError(envelope.docket.targetSubscriber);
145
-
146
- // Retry stub messages (likely deployment timing issue)
147
- const decision = this.getUnhandledRetryDecision(envelope, receipt, error);
148
- await this.handleRetryDecision(receipt, envelope, decision);
149
-
150
- return {
151
- success: false,
152
- envelope,
153
- subscriber: subscriberDef,
154
- error,
155
- decision,
156
- durationMs: performance.now() - startTime,
157
- };
158
- }
159
-
160
- // 3. Execute subscriber callback with hooks
161
- let result: unknown;
162
- let error: Error | undefined;
163
- let context: ResumableContext | undefined;
164
-
165
- // For resumable subscribers, load existing checkpoint and create context
166
- const isResumable = isResumableSubscriber(subscriber);
167
- let existingCheckpoint: Checkpoint | undefined;
168
-
169
- if (isResumable) {
170
- existingCheckpoint = await this.checkpointStore.get(envelope.id);
171
-
172
- if (existingCheckpoint) {
173
- await this.hooks.onCheckpointLoaded?.({
174
- envelope,
175
- subscriber: subscriberDef,
176
- checkpoint: existingCheckpoint,
177
- cachedSteps: Object.keys(existingCheckpoint.completedSteps).length,
178
- });
179
- }
180
-
181
- context = new ResumableContext({
182
- store: this.checkpointStore,
183
- envelope,
184
- subscriber: subscriberDef,
185
- existingCheckpoint,
186
- hooks: {
187
- onCheckpointHit: (ctx) => this.hooks.onCheckpointHit?.(ctx),
188
- onCheckpointMiss: (ctx) => this.hooks.onCheckpointMiss?.(ctx),
189
- },
190
- });
191
- }
192
-
193
- // Create base callback context with matador dispatcher
194
- const callbackContext: CallbackContext = { matador: this.dispatcher };
195
-
196
- await this.hooks.onWorkerWrap(envelope, subscriberDef, async () => {
197
- await this.hooks.onWorkerBeforeProcess(envelope, subscriberDef);
198
-
199
- try {
200
- if (isResumable && context) {
201
- // Resumable subscriber: pass combined context (checkpoint + matador)
202
- // Cast needed because TypeScript can't narrow the union type based on isResumable
203
- const resumableCallback = subscriber.callback as (
204
- envelope: Envelope,
205
- context: ResumableCallbackContext,
206
- ) => Promise<void> | void;
207
- // Create combined context by binding class methods and adding matador
208
- const fullContext: ResumableCallbackContext = {
209
- io: context.io.bind(context),
210
- all: context.all.bind(context),
211
- attempt: context.attempt,
212
- isRetry: context.isRetry,
213
- matador: this.dispatcher,
214
- };
215
- result = await resumableCallback(envelope, fullContext);
216
- } else {
217
- // Standard subscriber: pass envelope and callback context
218
- const standardCallback = subscriber.callback as (
219
- envelope: Envelope,
220
- context: CallbackContext,
221
- ) => Promise<void> | void;
222
- result = await standardCallback(envelope, callbackContext);
223
- }
224
- } catch (e) {
225
- error = e instanceof Error ? e : new Error(String(e));
226
- }
227
- });
228
-
229
- const durationMs = performance.now() - startTime;
230
-
231
- // 4. Handle success
232
- if (!error) {
233
- // Clear checkpoint on success for resumable subscribers
234
- if (context) {
235
- await context.clear();
236
- await this.hooks.onCheckpointCleared?.({
237
- envelope,
238
- subscriber: subscriberDef,
239
- reason: 'success',
240
- });
241
- }
242
-
243
- await this.transport.complete(receipt);
244
- await this.hooks.onWorkerSuccess({
245
- envelope,
246
- subscriber: subscriberDef,
247
- result,
248
- durationMs,
249
- transport: receipt.sourceTransport,
250
- });
251
-
252
- return {
253
- success: true,
254
- envelope,
255
- subscriber: subscriberDef,
256
- durationMs,
257
- };
258
- }
259
-
260
- // 5. Handle failure - consult retry policy
261
- const decision = this.retryPolicy.shouldRetry({
262
- envelope,
263
- error,
264
- subscriber: subscriberDef,
265
- receipt,
266
- });
267
-
268
- // Update envelope with error info
269
- envelope.docket.lastError = error.message;
270
- envelope.docket.firstError ??= error.message;
271
-
272
- // Clear checkpoint on dead-letter (terminal state)
273
- if (decision.action === 'dead-letter' && context) {
274
- await context.clear();
275
- await this.hooks.onCheckpointCleared?.({
276
- envelope,
277
- subscriber: subscriberDef,
278
- reason: 'dead-letter',
279
- });
280
- }
281
-
282
- await this.handleRetryDecision(receipt, envelope, decision);
283
-
284
- await this.hooks.onWorkerError({
285
- envelope,
286
- subscriber: subscriberDef,
287
- error,
288
- durationMs,
289
- decision,
290
- transport: receipt.sourceTransport,
291
- });
292
-
293
- return {
294
- success: false,
295
- envelope,
296
- subscriber: subscriberDef,
297
- error,
298
- decision,
299
- durationMs,
300
- };
301
- }
302
-
303
- private async handleRetryDecision(
304
- receipt: MessageReceipt,
305
- envelope: Envelope,
306
- decision: RetryDecision,
307
- ): Promise<void> {
308
- switch (decision.action) {
309
- case 'retry': {
310
- // Increment attempts and schedule retry
311
- envelope.docket.attempts++;
312
- envelope.docket.scheduledFor = new Date(
313
- Date.now() + decision.delay,
314
- ).toISOString();
315
-
316
- await this.transport.send(receipt.sourceQueue, envelope, {
317
- delay: decision.delay,
318
- });
319
- await this.transport.complete(receipt);
320
- break;
321
- }
322
-
323
- case 'dead-letter': {
324
- await this.sendToDeadLetter(
325
- receipt,
326
- envelope,
327
- decision.queue,
328
- decision.reason,
329
- );
330
- break;
331
- }
332
-
333
- case 'discard': {
334
- await this.transport.complete(receipt);
335
- break;
336
- }
337
- }
338
- }
339
-
340
- private async sendToDeadLetter(
341
- receipt: MessageReceipt,
342
- envelope: Envelope,
343
- dlqName: string,
344
- reason: string,
345
- ): Promise<void> {
346
- envelope.docket.originalQueue ??= receipt.sourceQueue;
347
-
348
- if (this.transport.sendToDeadLetter) {
349
- await this.transport.sendToDeadLetter(receipt, dlqName, envelope, reason);
350
- } else {
351
- // Manual: send to DLQ then complete original
352
- const fullDlqName = `${receipt.sourceQueue}.${dlqName}`;
353
- await this.transport.send(fullDlqName, envelope);
354
- await this.transport.complete(receipt);
355
- }
356
- }
357
-
358
- /**
359
- * Gets retry decision for unhandled messages (subscriber not found).
360
- * Uses same retry policy logic but sends to 'unhandled' DLQ after max attempts.
361
- */
362
- private getUnhandledRetryDecision(
363
- envelope: Envelope,
364
- receipt: MessageReceipt,
365
- error: Error,
366
- ): RetryDecision {
367
- // Create a synthetic subscriber definition for retry policy
368
- const syntheticSubscriber: SubscriberDefinition = {
369
- name: envelope.docket.targetSubscriber,
370
- description: 'Synthetic subscriber for unhandled message retry',
371
- idempotent: 'yes', // Safe to retry unhandled messages
372
- importance: envelope.docket.importance ?? 'should-investigate',
373
- };
374
-
375
- const decision = this.retryPolicy.shouldRetry({
376
- envelope,
377
- error,
378
- subscriber: syntheticSubscriber,
379
- receipt,
380
- });
381
-
382
- // Override dead-letter queue to 'unhandled' instead of 'undeliverable'
383
- if (decision.action === 'dead-letter') {
384
- return {
385
- action: 'dead-letter',
386
- queue: 'unhandled',
387
- reason: decision.reason,
388
- };
389
- }
390
-
391
- return decision;
392
- }
393
- }
@@ -1,46 +0,0 @@
1
- import type { MessageReceipt } from '../transport/index.js';
2
- import type { Envelope, SubscriberDefinition } from '../types/index.js';
3
-
4
- /**
5
- * Context provided to retry policy for decision making.
6
- */
7
- export interface RetryContext {
8
- /** The message envelope */
9
- readonly envelope: Envelope;
10
-
11
- /** The error that caused the failure */
12
- readonly error: Error;
13
-
14
- /** The subscriber definition */
15
- readonly subscriber: SubscriberDefinition;
16
-
17
- /** Message receipt with delivery information */
18
- readonly receipt: MessageReceipt;
19
- }
20
-
21
- /**
22
- * Decision returned by retry policy.
23
- */
24
- export type RetryDecision =
25
- | { readonly action: 'retry'; readonly delay: number }
26
- | {
27
- readonly action: 'dead-letter';
28
- readonly queue: string;
29
- readonly reason: string;
30
- }
31
- | { readonly action: 'discard'; readonly reason: string };
32
-
33
- /**
34
- * Interface for retry policies.
35
- */
36
- export interface RetryPolicy {
37
- /**
38
- * Determines what to do with a failed message.
39
- */
40
- shouldRetry(context: RetryContext): RetryDecision;
41
-
42
- /**
43
- * Calculates the delay for a retry attempt.
44
- */
45
- getDelay(context: RetryContext): number;
46
- }