@saga-bus/core 0.1.0
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/index.cjs +1097 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +650 -0
- package/dist/index.d.ts +650 -0
- package/dist/index.js +1057 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for all messages in the saga bus.
|
|
3
|
+
* Every message must have a `type` discriminator.
|
|
4
|
+
*/
|
|
5
|
+
interface BaseMessage {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Envelope wrapping a message with metadata for transport.
|
|
10
|
+
*/
|
|
11
|
+
interface MessageEnvelope<T extends BaseMessage = BaseMessage> {
|
|
12
|
+
/** Unique identifier for this message instance */
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/** Message type discriminator (mirrors payload.type) */
|
|
15
|
+
readonly type: T["type"];
|
|
16
|
+
/** The actual message payload */
|
|
17
|
+
readonly payload: T;
|
|
18
|
+
/** Transport and application headers */
|
|
19
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
20
|
+
/** When the message was created */
|
|
21
|
+
readonly timestamp: Date;
|
|
22
|
+
/** Optional partition key for ordered delivery */
|
|
23
|
+
readonly partitionKey?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Options for subscribing to messages on a transport.
|
|
28
|
+
*/
|
|
29
|
+
interface TransportSubscribeOptions {
|
|
30
|
+
/** The endpoint/topic/queue to subscribe to */
|
|
31
|
+
readonly endpoint: string;
|
|
32
|
+
/** Maximum concurrent message handlers (default: 1) */
|
|
33
|
+
readonly concurrency?: number;
|
|
34
|
+
/** Consumer group name for competing consumers */
|
|
35
|
+
readonly group?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for publishing messages on a transport.
|
|
39
|
+
*/
|
|
40
|
+
interface TransportPublishOptions {
|
|
41
|
+
/** The endpoint/topic/exchange to publish to */
|
|
42
|
+
readonly endpoint: string;
|
|
43
|
+
/** Routing/partition key */
|
|
44
|
+
readonly key?: string;
|
|
45
|
+
/** Additional headers to include */
|
|
46
|
+
readonly headers?: Record<string, string>;
|
|
47
|
+
/** Delay delivery by this many milliseconds */
|
|
48
|
+
readonly delayMs?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Transport interface for sending and receiving messages.
|
|
52
|
+
* Implementations include in-memory, RabbitMQ, Kafka, etc.
|
|
53
|
+
*/
|
|
54
|
+
interface Transport {
|
|
55
|
+
/**
|
|
56
|
+
* Start the transport (connect, initialize resources).
|
|
57
|
+
*/
|
|
58
|
+
start(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Stop the transport (disconnect, cleanup).
|
|
61
|
+
*/
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to messages on an endpoint.
|
|
65
|
+
*/
|
|
66
|
+
subscribe<TMessage extends BaseMessage>(options: TransportSubscribeOptions, handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Publish a message to an endpoint.
|
|
69
|
+
*/
|
|
70
|
+
publish<TMessage extends BaseMessage>(message: TMessage, options: TransportPublishOptions): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Metadata tracked for every saga instance.
|
|
75
|
+
*/
|
|
76
|
+
interface SagaStateMetadata {
|
|
77
|
+
/** Unique identifier for this saga instance */
|
|
78
|
+
readonly sagaId: string;
|
|
79
|
+
/** Optimistic concurrency version */
|
|
80
|
+
readonly version: number;
|
|
81
|
+
/** When the saga was created */
|
|
82
|
+
readonly createdAt: Date;
|
|
83
|
+
/** When the saga was last updated */
|
|
84
|
+
readonly updatedAt: Date;
|
|
85
|
+
/** Whether the saga has completed */
|
|
86
|
+
readonly isCompleted: boolean;
|
|
87
|
+
/** When the saga was archived (if applicable) */
|
|
88
|
+
readonly archivedAt?: Date | null;
|
|
89
|
+
/** W3C traceparent header for distributed tracing correlation */
|
|
90
|
+
readonly traceParent?: string | null;
|
|
91
|
+
/** W3C tracestate header for distributed tracing */
|
|
92
|
+
readonly traceState?: string | null;
|
|
93
|
+
/** Configured timeout duration in milliseconds */
|
|
94
|
+
readonly timeoutMs?: number | null;
|
|
95
|
+
/** When the timeout expires (if set) */
|
|
96
|
+
readonly timeoutExpiresAt?: Date | null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Base interface for saga state. All saga states must include metadata.
|
|
100
|
+
*/
|
|
101
|
+
interface SagaState {
|
|
102
|
+
readonly metadata: SagaStateMetadata;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Storage interface for saga instances.
|
|
106
|
+
* Implementations include in-memory, Postgres, Prisma, etc.
|
|
107
|
+
*/
|
|
108
|
+
interface SagaStore<TState extends SagaState> {
|
|
109
|
+
/**
|
|
110
|
+
* Get a saga instance by its ID.
|
|
111
|
+
*/
|
|
112
|
+
getById(sagaName: string, sagaId: string): Promise<TState | null>;
|
|
113
|
+
/**
|
|
114
|
+
* Get a saga instance by correlation ID.
|
|
115
|
+
*/
|
|
116
|
+
getByCorrelationId(sagaName: string, correlationId: string): Promise<TState | null>;
|
|
117
|
+
/**
|
|
118
|
+
* Insert a new saga instance.
|
|
119
|
+
* @throws if a saga with the same ID already exists
|
|
120
|
+
*/
|
|
121
|
+
insert(sagaName: string, correlationId: string, state: TState): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Update an existing saga instance with optimistic concurrency.
|
|
124
|
+
* @param expectedVersion - The version the state should be at before update
|
|
125
|
+
* @throws ConcurrencyError if version mismatch
|
|
126
|
+
*/
|
|
127
|
+
update(sagaName: string, state: TState, expectedVersion: number): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Delete a saga instance.
|
|
130
|
+
*/
|
|
131
|
+
delete(sagaName: string, sagaId: string): Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Context available to saga handlers.
|
|
135
|
+
*/
|
|
136
|
+
interface SagaContext {
|
|
137
|
+
/** Name of the saga being executed */
|
|
138
|
+
readonly sagaName: string;
|
|
139
|
+
/** Unique ID of this saga instance */
|
|
140
|
+
readonly sagaId: string;
|
|
141
|
+
/** Correlation ID for this saga */
|
|
142
|
+
readonly correlationId: string;
|
|
143
|
+
/** The message envelope being processed */
|
|
144
|
+
readonly envelope: MessageEnvelope;
|
|
145
|
+
/** Arbitrary metadata for middleware/handlers */
|
|
146
|
+
readonly metadata: Record<string, unknown>;
|
|
147
|
+
/**
|
|
148
|
+
* Publish a message to another endpoint.
|
|
149
|
+
*/
|
|
150
|
+
publish<TMessage extends BaseMessage>(message: TMessage, options?: Partial<TransportPublishOptions>): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Schedule a message for delayed delivery.
|
|
153
|
+
*/
|
|
154
|
+
schedule<TMessage extends BaseMessage>(message: TMessage, delayMs: number, options?: Partial<TransportPublishOptions>): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Mark the saga as complete.
|
|
157
|
+
*/
|
|
158
|
+
complete(): void;
|
|
159
|
+
/**
|
|
160
|
+
* Set a metadata value.
|
|
161
|
+
*/
|
|
162
|
+
setMetadata(key: string, value: unknown): void;
|
|
163
|
+
/**
|
|
164
|
+
* Get a metadata value.
|
|
165
|
+
*/
|
|
166
|
+
getMetadata<T = unknown>(key: string): T | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Set a timeout for this saga. When the timeout expires, a SagaTimeoutExpired
|
|
169
|
+
* message will be published to the saga. Calling this again resets the timeout.
|
|
170
|
+
* @param delayMs - Timeout duration in milliseconds
|
|
171
|
+
*/
|
|
172
|
+
setTimeout(delayMs: number): void;
|
|
173
|
+
/**
|
|
174
|
+
* Clear any pending timeout for this saga.
|
|
175
|
+
*/
|
|
176
|
+
clearTimeout(): void;
|
|
177
|
+
/**
|
|
178
|
+
* Get the remaining time until timeout expires.
|
|
179
|
+
* @returns Milliseconds remaining, or null if no timeout is set
|
|
180
|
+
*/
|
|
181
|
+
getTimeoutRemaining(): number | null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Result returned from a saga handler.
|
|
185
|
+
*/
|
|
186
|
+
interface SagaHandlerResult<TState extends SagaState> {
|
|
187
|
+
/** The updated saga state */
|
|
188
|
+
readonly newState: TState;
|
|
189
|
+
/** Whether the saga should be marked as complete */
|
|
190
|
+
readonly isCompleted?: boolean;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Correlation configuration for a message type.
|
|
194
|
+
*/
|
|
195
|
+
interface SagaCorrelation<TMessage extends BaseMessage> {
|
|
196
|
+
/** Whether this message can start a new saga instance */
|
|
197
|
+
readonly canStart: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Extract the correlation ID from a message.
|
|
200
|
+
* @returns null if the message cannot be correlated
|
|
201
|
+
*/
|
|
202
|
+
getCorrelationId(message: TMessage): string | null;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A complete saga definition produced by the DSL builder.
|
|
206
|
+
*/
|
|
207
|
+
interface SagaDefinition<TState extends SagaState, TMessageUnion extends BaseMessage> {
|
|
208
|
+
/** Unique name for this saga */
|
|
209
|
+
readonly name: string;
|
|
210
|
+
/** All message types this saga handles */
|
|
211
|
+
readonly handledMessageTypes: ReadonlyArray<TMessageUnion["type"]>;
|
|
212
|
+
/**
|
|
213
|
+
* Get correlation configuration for a message.
|
|
214
|
+
*/
|
|
215
|
+
getCorrelation<T extends TMessageUnion>(message: T): SagaCorrelation<T>;
|
|
216
|
+
/**
|
|
217
|
+
* Create initial state for a new saga instance.
|
|
218
|
+
*/
|
|
219
|
+
createInitialState<T extends TMessageUnion>(message: T, ctx: SagaContext): Promise<TState>;
|
|
220
|
+
/**
|
|
221
|
+
* Handle a message and return the updated state.
|
|
222
|
+
*/
|
|
223
|
+
handle<T extends TMessageUnion>(message: T, state: TState, ctx: SagaContext): Promise<SagaHandlerResult<TState>>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Context passed through the middleware pipeline.
|
|
228
|
+
*/
|
|
229
|
+
interface SagaPipelineContext {
|
|
230
|
+
/** The message being processed */
|
|
231
|
+
readonly envelope: MessageEnvelope;
|
|
232
|
+
/** Name of the saga */
|
|
233
|
+
readonly sagaName: string;
|
|
234
|
+
/** Correlation ID */
|
|
235
|
+
readonly correlationId: string;
|
|
236
|
+
/** Saga instance ID (set after state is loaded/created) */
|
|
237
|
+
sagaId?: string;
|
|
238
|
+
/** Existing state loaded from store (null if new saga) */
|
|
239
|
+
existingState?: SagaState | null;
|
|
240
|
+
/** State before handler execution */
|
|
241
|
+
preState?: SagaState;
|
|
242
|
+
/** State after handler execution */
|
|
243
|
+
postState?: SagaState;
|
|
244
|
+
/** Result from the handler */
|
|
245
|
+
handlerResult?: SagaHandlerResult<SagaState>;
|
|
246
|
+
/** Arbitrary metadata */
|
|
247
|
+
readonly metadata: Record<string, unknown>;
|
|
248
|
+
/** Error if one occurred */
|
|
249
|
+
error?: unknown;
|
|
250
|
+
/** Trace context to store with saga (set by tracing middleware) */
|
|
251
|
+
traceContext?: {
|
|
252
|
+
traceParent: string;
|
|
253
|
+
traceState: string | null;
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* Set trace context to be stored with the saga state.
|
|
257
|
+
* Called by tracing middleware for new sagas.
|
|
258
|
+
*/
|
|
259
|
+
setTraceContext(traceParent: string, traceState: string | null): void;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Middleware function for the saga pipeline.
|
|
263
|
+
*/
|
|
264
|
+
type SagaMiddleware = (ctx: SagaPipelineContext, next: () => Promise<void>) => Promise<void>;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Logger interface for saga bus.
|
|
268
|
+
*/
|
|
269
|
+
interface Logger {
|
|
270
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
271
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
272
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
273
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Metrics interface for saga bus.
|
|
277
|
+
*/
|
|
278
|
+
interface Metrics {
|
|
279
|
+
/**
|
|
280
|
+
* Increment a counter.
|
|
281
|
+
*/
|
|
282
|
+
increment(name: string, value?: number, tags?: Record<string, string>): void;
|
|
283
|
+
/**
|
|
284
|
+
* Record a duration/timing.
|
|
285
|
+
*/
|
|
286
|
+
recordDuration(name: string, ms: number, tags?: Record<string, string>): void;
|
|
287
|
+
/**
|
|
288
|
+
* Set a gauge value.
|
|
289
|
+
*/
|
|
290
|
+
gauge(name: string, value: number, tags?: Record<string, string>): void;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Tracer interface for distributed tracing.
|
|
294
|
+
*/
|
|
295
|
+
interface Tracer {
|
|
296
|
+
/**
|
|
297
|
+
* Execute a function within a named span.
|
|
298
|
+
*/
|
|
299
|
+
withSpan<T>(name: string, ctx: SagaPipelineContext, fn: () => Promise<T>): Promise<T>;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Error handler for determining retry behavior.
|
|
303
|
+
*/
|
|
304
|
+
interface ErrorHandler {
|
|
305
|
+
/**
|
|
306
|
+
* Handle an error and determine the action.
|
|
307
|
+
* @returns "retry" to retry, "dlq" to send to DLQ, "drop" to discard
|
|
308
|
+
*/
|
|
309
|
+
handle(error: unknown, ctx: SagaPipelineContext): Promise<"retry" | "dlq" | "drop">;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Registration of a saga with its store.
|
|
314
|
+
*/
|
|
315
|
+
interface SagaRegistration<TState extends SagaState, TMessageUnion extends BaseMessage> {
|
|
316
|
+
readonly definition: SagaDefinition<TState, TMessageUnion>;
|
|
317
|
+
/** Store for this saga. If not provided, uses the default store from BusConfig. */
|
|
318
|
+
readonly store?: SagaStore<TState>;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Retry policy configuration.
|
|
322
|
+
*/
|
|
323
|
+
interface WorkerRetryPolicy {
|
|
324
|
+
/** Maximum number of attempts before sending to DLQ */
|
|
325
|
+
readonly maxAttempts: number;
|
|
326
|
+
/** Base delay between retries in milliseconds */
|
|
327
|
+
readonly baseDelayMs: number;
|
|
328
|
+
/** Maximum delay between retries in milliseconds */
|
|
329
|
+
readonly maxDelayMs: number;
|
|
330
|
+
/** Backoff strategy */
|
|
331
|
+
readonly backoff: "linear" | "exponential";
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Timeout bounds configuration to prevent accidental extreme values.
|
|
335
|
+
*/
|
|
336
|
+
interface TimeoutBounds {
|
|
337
|
+
/** Minimum allowed timeout in milliseconds (default: 1000 = 1 second) */
|
|
338
|
+
readonly minMs?: number;
|
|
339
|
+
/** Maximum allowed timeout in milliseconds (default: 604800000 = 7 days) */
|
|
340
|
+
readonly maxMs?: number;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Context provided when a message fails correlation.
|
|
344
|
+
*/
|
|
345
|
+
interface CorrelationFailureContext {
|
|
346
|
+
/** The message envelope that failed correlation */
|
|
347
|
+
readonly envelope: MessageEnvelope;
|
|
348
|
+
/** Name of the saga that couldn't correlate the message */
|
|
349
|
+
readonly sagaName: string;
|
|
350
|
+
/** The message type */
|
|
351
|
+
readonly messageType: string;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Handler for messages that fail correlation.
|
|
355
|
+
*/
|
|
356
|
+
type CorrelationFailureHandler = (ctx: CorrelationFailureContext) => Promise<"drop" | "dlq"> | "drop" | "dlq";
|
|
357
|
+
/**
|
|
358
|
+
* Worker configuration for message processing.
|
|
359
|
+
*/
|
|
360
|
+
interface WorkerConfig {
|
|
361
|
+
/** Default concurrency for all subscriptions */
|
|
362
|
+
readonly defaultConcurrency?: number;
|
|
363
|
+
/** Timeout for graceful shutdown in milliseconds */
|
|
364
|
+
readonly shutdownTimeoutMs?: number;
|
|
365
|
+
/** Default retry policy */
|
|
366
|
+
readonly retryPolicy?: WorkerRetryPolicy;
|
|
367
|
+
/** Per-saga configuration overrides */
|
|
368
|
+
readonly sagas?: Record<string, {
|
|
369
|
+
readonly concurrency?: number;
|
|
370
|
+
readonly retryPolicy?: WorkerRetryPolicy;
|
|
371
|
+
}>;
|
|
372
|
+
/** Function to generate DLQ endpoint names */
|
|
373
|
+
readonly dlqNaming?: (endpoint: string) => string;
|
|
374
|
+
/** Timeout bounds to prevent accidental extreme values */
|
|
375
|
+
readonly timeoutBounds?: TimeoutBounds;
|
|
376
|
+
/** Handler for messages that fail correlation (default: "drop") */
|
|
377
|
+
readonly onCorrelationFailure?: CorrelationFailureHandler;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Configuration for creating a bus instance.
|
|
381
|
+
*/
|
|
382
|
+
interface BusConfig {
|
|
383
|
+
/** Transport implementation */
|
|
384
|
+
readonly transport: Transport;
|
|
385
|
+
/** Default store for all sagas. Can be overridden per-saga in the registration. */
|
|
386
|
+
readonly store?: SagaStore<SagaState>;
|
|
387
|
+
/** Registered sagas */
|
|
388
|
+
readonly sagas: ReadonlyArray<SagaRegistration<SagaState, BaseMessage>>;
|
|
389
|
+
/** Middleware pipeline */
|
|
390
|
+
readonly middleware?: ReadonlyArray<SagaMiddleware>;
|
|
391
|
+
/** Logger implementation */
|
|
392
|
+
readonly logger?: Logger;
|
|
393
|
+
/** Metrics implementation */
|
|
394
|
+
readonly metrics?: Metrics;
|
|
395
|
+
/** Tracer implementation */
|
|
396
|
+
readonly tracer?: Tracer;
|
|
397
|
+
/** Error handler implementation */
|
|
398
|
+
readonly errorHandler?: ErrorHandler;
|
|
399
|
+
/** Worker configuration */
|
|
400
|
+
readonly worker?: WorkerConfig;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* The main bus interface for publishing messages.
|
|
404
|
+
*/
|
|
405
|
+
interface Bus {
|
|
406
|
+
/**
|
|
407
|
+
* Start the bus (connect transport, subscribe to endpoints).
|
|
408
|
+
*/
|
|
409
|
+
start(): Promise<void>;
|
|
410
|
+
/**
|
|
411
|
+
* Stop the bus (graceful shutdown).
|
|
412
|
+
*/
|
|
413
|
+
stop(): Promise<void>;
|
|
414
|
+
/**
|
|
415
|
+
* Check if the bus is running.
|
|
416
|
+
*/
|
|
417
|
+
isRunning(): boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Publish a message.
|
|
420
|
+
*/
|
|
421
|
+
publish<TMessage extends BaseMessage>(message: TMessage, options?: Partial<TransportPublishOptions>): Promise<void>;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Thrown when optimistic concurrency check fails.
|
|
426
|
+
*/
|
|
427
|
+
declare class ConcurrencyError extends Error {
|
|
428
|
+
readonly sagaId: string;
|
|
429
|
+
readonly expectedVersion: number;
|
|
430
|
+
readonly actualVersion?: number;
|
|
431
|
+
constructor(sagaId: string, expectedVersion: number, actualVersion?: number);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Marker class for transient errors that should be retried.
|
|
435
|
+
*/
|
|
436
|
+
declare class TransientError extends Error {
|
|
437
|
+
readonly cause?: Error;
|
|
438
|
+
constructor(message: string, cause?: Error);
|
|
439
|
+
static wrap(error: Error): TransientError;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Thrown when message validation fails.
|
|
443
|
+
*/
|
|
444
|
+
declare class ValidationError extends Error {
|
|
445
|
+
readonly field?: string;
|
|
446
|
+
readonly value?: unknown;
|
|
447
|
+
constructor(message: string, field?: string, value?: unknown);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Context attached to processing errors for better observability.
|
|
451
|
+
*/
|
|
452
|
+
interface SagaErrorContext {
|
|
453
|
+
readonly sagaName: string;
|
|
454
|
+
readonly correlationId: string;
|
|
455
|
+
readonly sagaId?: string;
|
|
456
|
+
readonly messageType: string;
|
|
457
|
+
readonly messageId: string;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Wraps errors that occur during saga processing with context.
|
|
461
|
+
* Used by BusImpl to provide richer error context to error handlers.
|
|
462
|
+
*/
|
|
463
|
+
declare class SagaProcessingError extends Error {
|
|
464
|
+
readonly context: SagaErrorContext;
|
|
465
|
+
readonly cause: Error;
|
|
466
|
+
constructor(cause: Error, context: SagaErrorContext);
|
|
467
|
+
/**
|
|
468
|
+
* Check if an error is a SagaProcessingError and extract context.
|
|
469
|
+
*/
|
|
470
|
+
static extractContext(error: unknown): SagaErrorContext | null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Options for correlating a message type.
|
|
475
|
+
*/
|
|
476
|
+
interface CorrelateOptions {
|
|
477
|
+
/** Whether this message type can start a new saga instance */
|
|
478
|
+
canStart?: boolean;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Handler function signature.
|
|
482
|
+
*/
|
|
483
|
+
type SagaHandler<TState extends SagaState, TMessage extends BaseMessage> = (message: TMessage, state: TState, ctx: SagaContext) => Promise<SagaHandlerResult<TState>>;
|
|
484
|
+
/**
|
|
485
|
+
* State guard function.
|
|
486
|
+
*/
|
|
487
|
+
type StateGuard<TState extends SagaState> = (state: TState) => boolean;
|
|
488
|
+
/**
|
|
489
|
+
* Handler registration with optional state guard.
|
|
490
|
+
*/
|
|
491
|
+
interface HandlerRegistration<TState extends SagaState, TMessage extends BaseMessage> {
|
|
492
|
+
messageType: string;
|
|
493
|
+
guard?: StateGuard<TState>;
|
|
494
|
+
handler: SagaHandler<TState, TMessage>;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Initial state factory function.
|
|
498
|
+
*/
|
|
499
|
+
type InitialStateFactory<TState extends SagaState, TMessage extends BaseMessage> = (message: TMessage, ctx: SagaContext) => TState | Promise<TState>;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Builder for configuring a message handler with optional state guards.
|
|
503
|
+
*/
|
|
504
|
+
declare class HandlerBuilder<TState extends SagaState, TMessages extends BaseMessage, TCurrentMessage extends TMessages> {
|
|
505
|
+
private readonly parent;
|
|
506
|
+
private readonly messageType;
|
|
507
|
+
private guard?;
|
|
508
|
+
constructor(parent: SagaMachineBuilder<TState, TMessages>, messageType: TCurrentMessage["type"]);
|
|
509
|
+
/**
|
|
510
|
+
* Add a state guard that must pass for the handler to execute.
|
|
511
|
+
* Multiple guards can be chained with multiple .when() calls.
|
|
512
|
+
*/
|
|
513
|
+
when(guard: StateGuard<TState>): HandlerBuilder<TState, TMessages, TCurrentMessage>;
|
|
514
|
+
/**
|
|
515
|
+
* Register the handler function and return to the parent builder.
|
|
516
|
+
*/
|
|
517
|
+
handle(handler: SagaHandler<TState, TCurrentMessage>): SagaMachineBuilder<TState, TMessages>;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Fluent builder for creating saga definitions.
|
|
522
|
+
*
|
|
523
|
+
* @example
|
|
524
|
+
* ```typescript
|
|
525
|
+
* const saga = createSagaMachine<OrderState, OrderMessages>()
|
|
526
|
+
* .name("OrderSaga")
|
|
527
|
+
* .correlate("OrderSubmitted", msg => msg.orderId, { canStart: true })
|
|
528
|
+
* .correlate("*", msg => msg.orderId)
|
|
529
|
+
* .initial<OrderSubmitted>((msg, ctx) => ({ ... }))
|
|
530
|
+
* .on("PaymentCaptured").when(s => s.status === "pending").handle(...)
|
|
531
|
+
* .build();
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
declare class SagaMachineBuilder<TState extends SagaState, TMessages extends BaseMessage> {
|
|
535
|
+
private sagaName?;
|
|
536
|
+
private readonly correlations;
|
|
537
|
+
private wildcardCorrelation?;
|
|
538
|
+
private readonly handlers;
|
|
539
|
+
private initialFactory?;
|
|
540
|
+
/**
|
|
541
|
+
* Set the saga name.
|
|
542
|
+
*/
|
|
543
|
+
name(sagaName: string): this;
|
|
544
|
+
/**
|
|
545
|
+
* Define how to correlate messages to saga instances.
|
|
546
|
+
*
|
|
547
|
+
* @param messageType - The message type to correlate, or "*" for wildcard
|
|
548
|
+
* @param getCorrelationId - Function to extract correlation ID from message
|
|
549
|
+
* @param options - Correlation options (e.g., canStart)
|
|
550
|
+
*/
|
|
551
|
+
correlate<TType extends TMessages["type"] | "*">(messageType: TType, getCorrelationId: TType extends "*" ? (message: TMessages) => string | null : (message: Extract<TMessages, {
|
|
552
|
+
type: TType;
|
|
553
|
+
}>) => string | null, options?: CorrelateOptions): this;
|
|
554
|
+
/**
|
|
555
|
+
* Define the initial state factory for new saga instances.
|
|
556
|
+
*
|
|
557
|
+
* @param factory - Function to create initial state from the starting message
|
|
558
|
+
*/
|
|
559
|
+
initial<TStartMessage extends TMessages>(factory: InitialStateFactory<TState, TStartMessage>): this;
|
|
560
|
+
/**
|
|
561
|
+
* Start defining a handler for a specific message type.
|
|
562
|
+
*
|
|
563
|
+
* @param messageType - The message type to handle
|
|
564
|
+
* @returns A HandlerBuilder for chaining .when() and .handle()
|
|
565
|
+
*/
|
|
566
|
+
on<TType extends TMessages["type"]>(messageType: TType): HandlerBuilder<TState, TMessages, Extract<TMessages, {
|
|
567
|
+
type: TType;
|
|
568
|
+
}>>;
|
|
569
|
+
/**
|
|
570
|
+
* Internal method called by HandlerBuilder to register a handler.
|
|
571
|
+
* @internal
|
|
572
|
+
*/
|
|
573
|
+
_registerHandler(registration: HandlerRegistration<TState, TMessages>): this;
|
|
574
|
+
/**
|
|
575
|
+
* Build the saga definition.
|
|
576
|
+
*
|
|
577
|
+
* @throws Error if required configuration is missing
|
|
578
|
+
*/
|
|
579
|
+
build(): SagaDefinition<TState, TMessages>;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Create a new saga machine builder.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```typescript
|
|
586
|
+
* const saga = createSagaMachine<OrderState, OrderMessages>()
|
|
587
|
+
* .name("OrderSaga")
|
|
588
|
+
* .correlate("OrderSubmitted", msg => msg.orderId, { canStart: true })
|
|
589
|
+
* .initial<OrderSubmitted>((msg, ctx) => ({ ... }))
|
|
590
|
+
* .on("PaymentCaptured").handle(...)
|
|
591
|
+
* .build();
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
declare function createSagaMachine<TState extends SagaState, TMessages extends BaseMessage>(): SagaMachineBuilder<TState, TMessages>;
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Create a new bus instance.
|
|
598
|
+
*/
|
|
599
|
+
declare function createBus(config: BusConfig): Bus;
|
|
600
|
+
|
|
601
|
+
/** Default timeout bounds */
|
|
602
|
+
declare const DEFAULT_TIMEOUT_BOUNDS: Required<TimeoutBounds>;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Default console-based logger implementation.
|
|
606
|
+
*/
|
|
607
|
+
declare class DefaultLogger implements Logger {
|
|
608
|
+
private readonly prefix;
|
|
609
|
+
constructor(prefix?: string);
|
|
610
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
611
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
612
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
613
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Default error handler implementation.
|
|
618
|
+
* Classifies errors as transient (retry) or permanent (DLQ).
|
|
619
|
+
*/
|
|
620
|
+
declare class DefaultErrorHandler implements ErrorHandler {
|
|
621
|
+
handle(error: unknown, _ctx: SagaPipelineContext): Promise<"retry" | "dlq" | "drop">;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Create a custom error handler with additional transient patterns.
|
|
625
|
+
*/
|
|
626
|
+
declare function createErrorHandler(options?: {
|
|
627
|
+
additionalTransientPatterns?: RegExp[];
|
|
628
|
+
customClassifier?: (error: unknown, ctx: SagaPipelineContext) => "retry" | "dlq" | "drop" | null;
|
|
629
|
+
}): ErrorHandler;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Header names for retry tracking.
|
|
633
|
+
*/
|
|
634
|
+
declare const RETRY_HEADERS: {
|
|
635
|
+
readonly ATTEMPT: "x-saga-attempt";
|
|
636
|
+
readonly FIRST_SEEN: "x-saga-first-seen";
|
|
637
|
+
readonly ORIGINAL_ENDPOINT: "x-saga-original-endpoint";
|
|
638
|
+
readonly ERROR_MESSAGE: "x-saga-error-message";
|
|
639
|
+
readonly ERROR_TYPE: "x-saga-error-type";
|
|
640
|
+
};
|
|
641
|
+
/**
|
|
642
|
+
* Default retry policy.
|
|
643
|
+
*/
|
|
644
|
+
declare const DEFAULT_RETRY_POLICY: WorkerRetryPolicy;
|
|
645
|
+
/**
|
|
646
|
+
* Default DLQ naming function.
|
|
647
|
+
*/
|
|
648
|
+
declare function defaultDlqNaming(endpoint: string): string;
|
|
649
|
+
|
|
650
|
+
export { type BaseMessage, type Bus, type BusConfig, ConcurrencyError, type CorrelateOptions, type CorrelationFailureContext, type CorrelationFailureHandler, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUT_BOUNDS, DefaultErrorHandler, DefaultLogger, type ErrorHandler, type InitialStateFactory, type Logger, type MessageEnvelope, type Metrics, RETRY_HEADERS, type SagaContext, type SagaCorrelation, type SagaDefinition, type SagaErrorContext, type SagaHandler, type SagaHandlerResult, SagaMachineBuilder, type SagaMiddleware, type SagaPipelineContext, SagaProcessingError, type SagaRegistration, type SagaState, type SagaStateMetadata, type SagaStore, type StateGuard, type TimeoutBounds, type Tracer, TransientError, type Transport, type TransportPublishOptions, type TransportSubscribeOptions, ValidationError, type WorkerConfig, type WorkerRetryPolicy, createBus, createErrorHandler, createSagaMachine, defaultDlqNaming };
|