@vytches/ddd-messaging 0.26.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/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("@vytches/ddd-logging"),d=require("@vytches/ddd-utils");var o=(i=>(i.PENDING="PENDING",i.PROCESSING="PROCESSING",i.PROCESSED="PROCESSED",i.FAILED="FAILED",i))(o||{}),n=(i=>(i.LOW="low",i.NORMAL="normal",i.HIGH="high",i.CRITICAL="critical",i))(n||{});class u{}class g{static createMessage(e,t,s){const r={id:d.LibUtils.getUUID(),messageType:e,payload:t,metadata:s?.metadata||{},status:o.PENDING,attempts:0,createdAt:new Date,priority:s?.priority||n.NORMAL};return s?.processAfter&&(r.processAfter=s.processAfter),r}static createDelayedMessage(e,t,s,r){const a=new Date(Date.now()+s);return g.createMessage(e,t,{...r,processAfter:a})}static createHighPriorityMessage(e,t,s){return g.createMessage(e,t,{...s,priority:n.HIGH})}static createFromIntegrationEvent(e,t){const s={metadata:{...e.metadata||{},...t?.metadata||{}}};return t?.priority&&(s.priority=t.priority),t?.processAfter&&(s.processAfter=t.processAfter),g.createMessage(`integration_event:${e.eventName}`,e.payload,s)}}class h{constructor(e,t={}){this.handlers=new Map,this.middlewares=[],this.isRunning=!1,this.logger=l.Logger.create("OutboxProcessor"),this.repository=e,this.options={batchSize:t.batchSize??10,maxRetries:t.maxRetries??3,processingInterval:t.processingInterval??5e3,priorityOrder:t.priorityOrder??[n.CRITICAL,n.HIGH,n.NORMAL,n.LOW],messageTimeout:t.messageTimeout??3e4,enableLogging:t.enableLogging??!1}}registerHandler(e,t){this.handlers.set(e,t),this.logger.debug(`Registered handler for message type: ${e}`)}use(e){this.middlewares.push(e),this.logger.debug("Registered middleware")}start(){if(this.isRunning){this.logger.warn("Processor is already running");return}this.isRunning=!0,this.logger.info("Starting outbox processor"),this.scheduleProcessing()}stop(){if(!this.isRunning){this.logger.warn("Processor is not running");return}this.isRunning=!1,this.processingTimer&&(clearTimeout(this.processingTimer),this.processingTimer=void 0),this.logger.info("Stopped outbox processor")}async processBatch(){if(!this.isRunning)return;const[e,t]=await d.safeRun(()=>this.repository.getUnprocessedMessages(this.options.batchSize,this.options.priorityOrder));if(e){const a=e instanceof Error?e.message:String(e);this.logger.error(`Error retrieving messages: ${a}`);return}const s=t;if(!s||!Array.isArray(s)||s.length===0){this.logger.debug("No pending messages to process");return}this.logger.info(`Processing ${s.length} messages`);const r=s.map(a=>this.processMessage(a));await Promise.allSettled(r),this.logger.info(`Completed processing batch of ${s.length} messages`)}async processMessage(e){const[t]=await d.safeRun(async()=>{await this.repository.updateStatus(e.id,o.PROCESSING),await this.buildPipeline(e)(e),await this.repository.updateStatus(e.id,o.PROCESSED),this.logger.debug(`Successfully processed message ${e.id}`)});t&&await this.handleMessageError(e,t)}buildPipeline(e){const t=this.handlers.get(e.messageType);if(!t)throw new Error(`No handler registered for message type: ${e.messageType}`);const s=async r=>{await Promise.race([t.handle(r),new Promise((a,c)=>setTimeout(()=>c(new Error("Message processing timeout")),this.options.messageTimeout))])};return this.middlewares.reduceRight((r,a)=>a(r),s)}async handleMessageError(e,t){try{const s=await this.repository.incrementAttempt(e.id);this.logger.warn(`Message ${e.id} failed, attempt ${s}: ${t?.message}`),s>=this.options.maxRetries?(await this.repository.updateStatus(e.id,o.FAILED,t),this.logger.error(`Message ${e.id} marked as failed after ${s} attempts`)):(await this.repository.updateStatus(e.id,o.PENDING),this.logger.info(`Message ${e.id} scheduled for retry (attempt ${s+1})`))}catch(s){const r=s instanceof Error?s.message:String(s);this.logger.error(`Error updating message status: ${r}`)}}scheduleProcessing(){this.isRunning&&(this.processingTimer=setTimeout(async()=>{await this.processBatch(),this.scheduleProcessing()},this.options.processingInterval))}log(e){this.options.enableLogging&&this.logger.info(e)}async getStats(){return{isRunning:this.isRunning,batchSize:this.options.batchSize,maxRetries:this.options.maxRetries,processingInterval:this.options.processingInterval,registeredHandlers:Array.from(this.handlers.keys()),middlewareCount:this.middlewares.length}}}class p{constructor(e){this.eventBus=e}async handle(e){const s={eventName:e.messageType.startsWith("integration_event:")?e.messageType.replace("integration_event:",""):e.messageType,eventId:e.id,occurredOn:e.createdAt,version:1,...e.payload,...e.metadata};await this.eventBus.publish(s)}}class y{constructor(e,t={}){this.logger=l.Logger.create("OutboxService"),this.repository=e,this.options={defaultPriority:t.defaultPriority??n.NORMAL,defaultMetadata:t.defaultMetadata??{},autoCleanup:t.autoCleanup??!1,cleanupAfterDays:t.cleanupAfterDays??7,enableLogging:t.enableLogging??!1}}async saveMessage(e,t,s){const r=g.createMessage(e,t,{priority:this.options.defaultPriority,metadata:{...this.options.defaultMetadata,...s?.metadata},...s}),a=await this.repository.saveMessage(r);return this.logger.debug(`Saved message ${a} of type ${e}`),a}async saveMessages(e){const t=e.map(({messageType:r,payload:a,options:c})=>g.createMessage(r,a,{priority:this.options.defaultPriority,metadata:{...this.options.defaultMetadata,...c?.metadata},...c})),s=await this.repository.saveBatch(t);return this.logger.debug(`Saved batch of ${e.length} messages`),s}async scheduleMessage(e,t,s,r){const a=g.createDelayedMessage(e,t,s,{priority:this.options.defaultPriority,metadata:{...this.options.defaultMetadata,...r?.metadata},...r}),c=await this.repository.scheduleMessage(a,a.processAfter);return this.logger.debug(`Scheduled message ${c} of type ${e} for processing in ${s}ms`),c}async saveHighPriorityMessage(e,t,s){return this.saveMessage(e,t,{...s,priority:n.HIGH})}async saveCriticalMessage(e,t,s){return this.saveMessage(e,t,{...s,priority:n.CRITICAL})}async saveDomainEvent(e,t){const s=g.createFromIntegrationEvent({eventName:e.eventName,payload:e,metadata:{eventId:e?.metadata?.eventId,occurredOn:e?.metadata?.occurredOn,version:e?.metadata?.version}},{priority:this.options.defaultPriority,metadata:{...this.options.defaultMetadata,...t?.metadata},...t}),r=await this.repository.saveMessage(s);return this.logger.debug(`Saved domain event ${e.eventName} as message ${r}`),r}async getMessage(e){return this.repository.getById(e)}async getPendingMessages(e=100){return this.repository.getUnprocessedMessages(e)}async retryMessage(e){await this.repository.updateStatus(e,o.PENDING),this.logger.info(`Reset message ${e} to pending for retry`)}async retryMessages(e){await this.repository.updateStatusBatch(e,o.PENDING),this.logger.info(`Reset ${e.length} messages to pending for retry`)}async cleanup(e){const t=e??this.options.cleanupAfterDays,s=new Date;s.setDate(s.getDate()-t);const r=await this.repository.deleteByStatusAndAge(s,o.PROCESSED);return this.logger.info(`Cleaned up ${r} processed messages older than ${t} days`),r}async getStats(){const e=this.repository;return e.getMessageCountByStatus?{totalPending:e.getMessageCountByStatus(o.PENDING),totalProcessing:e.getMessageCountByStatus(o.PROCESSING),totalProcessed:e.getMessageCountByStatus(o.PROCESSED),totalFailed:e.getMessageCountByStatus(o.FAILED)}:{totalPending:(await this.repository.getUnprocessedMessages(1e3)).length,totalProcessing:0,totalProcessed:0,totalFailed:0}}log(e){this.options.enableLogging&&this.logger.info(e)}}exports.EventBusOutboxHandler=p;exports.IOutboxRepository=u;exports.MessagePriority=n;exports.MessageStatus=o;exports.OutboxMessageFactory=g;exports.OutboxProcessor=h;exports.OutboxService=y;
@@ -0,0 +1,3 @@
1
+ export type { IOutboxMessage, OutboxMessageOptions, IOutboxMessageHandler, OutboxMiddleware, OutboxProcessorOptions, OutboxServiceOptions, } from './outbox';
2
+ export { MessageStatus, MessagePriority } from './outbox';
3
+ export { IOutboxRepository, OutboxMessageFactory, OutboxProcessor, EventBusOutboxHandler, OutboxService, } from './outbox';
package/dist/index.js ADDED
@@ -0,0 +1,411 @@
1
+ import { Logger as l } from "@vytches/ddd-logging";
2
+ import { LibUtils as h, safeRun as d } from "@vytches/ddd-utils";
3
+ var o = /* @__PURE__ */ ((i) => (i.PENDING = "PENDING", i.PROCESSING = "PROCESSING", i.PROCESSED = "PROCESSED", i.FAILED = "FAILED", i))(o || {}), n = /* @__PURE__ */ ((i) => (i.LOW = "low", i.NORMAL = "normal", i.HIGH = "high", i.CRITICAL = "critical", i))(n || {});
4
+ class y {
5
+ }
6
+ class g {
7
+ /**
8
+ * Creates a new outbox message
9
+ * @param messageType Type of the message
10
+ * @param payload Message payload
11
+ * @param options Additional options
12
+ * @returns Outbox message
13
+ */
14
+ static createMessage(e, t, s) {
15
+ const r = {
16
+ id: h.getUUID(),
17
+ messageType: e,
18
+ payload: t,
19
+ metadata: s?.metadata || {},
20
+ status: o.PENDING,
21
+ attempts: 0,
22
+ createdAt: /* @__PURE__ */ new Date(),
23
+ priority: s?.priority || n.NORMAL
24
+ };
25
+ return s?.processAfter && (r.processAfter = s.processAfter), r;
26
+ }
27
+ /**
28
+ * Creates a delayed outbox message
29
+ * @param messageType Type of the message
30
+ * @param payload Message payload
31
+ * @param delayMs Delay in milliseconds
32
+ * @param options Additional options
33
+ * @returns Outbox message scheduled for later processing
34
+ */
35
+ static createDelayedMessage(e, t, s, r) {
36
+ const a = new Date(Date.now() + s);
37
+ return g.createMessage(e, t, {
38
+ ...r,
39
+ processAfter: a
40
+ });
41
+ }
42
+ /**
43
+ * Creates a high priority outbox message
44
+ * @param messageType Type of the message
45
+ * @param payload Message payload
46
+ * @param options Additional options
47
+ * @returns High priority outbox message
48
+ */
49
+ static createHighPriorityMessage(e, t, s) {
50
+ return g.createMessage(e, t, {
51
+ ...s,
52
+ priority: n.HIGH
53
+ });
54
+ }
55
+ /**
56
+ * Creates an outbox message from an integration event
57
+ * @param event Integration event
58
+ * @param options Additional options
59
+ * @returns Outbox message containing the integration event
60
+ */
61
+ static createFromIntegrationEvent(e, t) {
62
+ const s = {
63
+ metadata: {
64
+ ...e.metadata || {},
65
+ ...t?.metadata || {}
66
+ }
67
+ };
68
+ return t?.priority && (s.priority = t.priority), t?.processAfter && (s.processAfter = t.processAfter), g.createMessage(
69
+ `integration_event:${e.eventName}`,
70
+ e.payload,
71
+ s
72
+ );
73
+ }
74
+ }
75
+ class m {
76
+ constructor(e, t = {}) {
77
+ this.handlers = /* @__PURE__ */ new Map(), this.middlewares = [], this.isRunning = !1, this.logger = l.create("OutboxProcessor"), this.repository = e, this.options = {
78
+ batchSize: t.batchSize ?? 10,
79
+ maxRetries: t.maxRetries ?? 3,
80
+ processingInterval: t.processingInterval ?? 5e3,
81
+ priorityOrder: t.priorityOrder ?? [
82
+ n.CRITICAL,
83
+ n.HIGH,
84
+ n.NORMAL,
85
+ n.LOW
86
+ ],
87
+ messageTimeout: t.messageTimeout ?? 3e4,
88
+ enableLogging: t.enableLogging ?? !1
89
+ };
90
+ }
91
+ /**
92
+ * Registers a message handler for a specific message type
93
+ */
94
+ registerHandler(e, t) {
95
+ this.handlers.set(e, t), this.logger.debug(`Registered handler for message type: ${e}`);
96
+ }
97
+ /**
98
+ * Registers middleware for message processing pipeline
99
+ */
100
+ use(e) {
101
+ this.middlewares.push(e), this.logger.debug("Registered middleware");
102
+ }
103
+ /**
104
+ * Starts the processor with automatic interval processing
105
+ */
106
+ start() {
107
+ if (this.isRunning) {
108
+ this.logger.warn("Processor is already running");
109
+ return;
110
+ }
111
+ this.isRunning = !0, this.logger.info("Starting outbox processor"), this.scheduleProcessing();
112
+ }
113
+ /**
114
+ * Stops the processor
115
+ */
116
+ stop() {
117
+ if (!this.isRunning) {
118
+ this.logger.warn("Processor is not running");
119
+ return;
120
+ }
121
+ this.isRunning = !1, this.processingTimer && (clearTimeout(this.processingTimer), this.processingTimer = void 0), this.logger.info("Stopped outbox processor");
122
+ }
123
+ /**
124
+ * Processes a single batch of pending messages.
125
+ *
126
+ * **Dispatch model:** messages within a batch are dispatched **in parallel**
127
+ * via `Promise.allSettled`. A failure in message N does **not** block
128
+ * messages N+1..N+M. Each failure is isolated, retry-counted via
129
+ * `incrementAttempt`, and reset to `PENDING` (or marked `FAILED` after
130
+ * `maxRetries` attempts) — independently of sibling messages.
131
+ *
132
+ * **Ordering:** parallel dispatch means message processing order is
133
+ * **not guaranteed** within a batch, even if `priorityOrder` was used to
134
+ * select which messages to fetch. If you need strict ordering, set
135
+ * `batchSize: 1` (one message per batch = sequential by definition).
136
+ *
137
+ * **Throughput:** for N messages and dispatch latency L, total batch time
138
+ * is approximately L (parallel) rather than N*L (sequential). For 200
139
+ * messages at 10ms dispatch each: ~10ms parallel vs ~2s sequential.
140
+ *
141
+ * **Quiescence:** if there are no pending messages, returns immediately
142
+ * without invoking handlers.
143
+ *
144
+ * @public
145
+ * @stable
146
+ * @since 0.1.0
147
+ */
148
+ async processBatch() {
149
+ if (!this.isRunning)
150
+ return;
151
+ const [e, t] = await d(
152
+ () => this.repository.getUnprocessedMessages(this.options.batchSize, this.options.priorityOrder)
153
+ );
154
+ if (e) {
155
+ const a = e instanceof Error ? e.message : String(e);
156
+ this.logger.error(`Error retrieving messages: ${a}`);
157
+ return;
158
+ }
159
+ const s = t;
160
+ if (!s || !Array.isArray(s) || s.length === 0) {
161
+ this.logger.debug("No pending messages to process");
162
+ return;
163
+ }
164
+ this.logger.info(`Processing ${s.length} messages`);
165
+ const r = s.map((a) => this.processMessage(a));
166
+ await Promise.allSettled(r), this.logger.info(`Completed processing batch of ${s.length} messages`);
167
+ }
168
+ /**
169
+ * Processes a single message through the middleware pipeline
170
+ */
171
+ async processMessage(e) {
172
+ const [t] = await d(async () => {
173
+ await this.repository.updateStatus(e.id, o.PROCESSING), await this.buildPipeline(e)(e), await this.repository.updateStatus(e.id, o.PROCESSED), this.logger.debug(`Successfully processed message ${e.id}`);
174
+ });
175
+ t && await this.handleMessageError(e, t);
176
+ }
177
+ /**
178
+ * Builds the middleware pipeline for message processing
179
+ */
180
+ buildPipeline(e) {
181
+ const t = this.handlers.get(e.messageType);
182
+ if (!t)
183
+ throw new Error(`No handler registered for message type: ${e.messageType}`);
184
+ const s = async (r) => {
185
+ await Promise.race([
186
+ t.handle(r),
187
+ new Promise(
188
+ (a, c) => setTimeout(
189
+ () => c(new Error("Message processing timeout")),
190
+ this.options.messageTimeout
191
+ )
192
+ )
193
+ ]);
194
+ };
195
+ return this.middlewares.reduceRight((r, a) => a(r), s);
196
+ }
197
+ /**
198
+ * Handles errors during message processing
199
+ */
200
+ async handleMessageError(e, t) {
201
+ try {
202
+ const s = await this.repository.incrementAttempt(e.id);
203
+ this.logger.warn(`Message ${e.id} failed, attempt ${s}: ${t?.message}`), s >= this.options.maxRetries ? (await this.repository.updateStatus(e.id, o.FAILED, t), this.logger.error(`Message ${e.id} marked as failed after ${s} attempts`)) : (await this.repository.updateStatus(e.id, o.PENDING), this.logger.info(`Message ${e.id} scheduled for retry (attempt ${s + 1})`));
204
+ } catch (s) {
205
+ const r = s instanceof Error ? s.message : String(s);
206
+ this.logger.error(`Error updating message status: ${r}`);
207
+ }
208
+ }
209
+ /**
210
+ * Schedules the next processing cycle
211
+ */
212
+ scheduleProcessing() {
213
+ this.isRunning && (this.processingTimer = setTimeout(async () => {
214
+ await this.processBatch(), this.scheduleProcessing();
215
+ }, this.options.processingInterval));
216
+ }
217
+ /**
218
+ * Logs messages if logging is enabled
219
+ */
220
+ log(e) {
221
+ this.options.enableLogging && this.logger.info(e);
222
+ }
223
+ /**
224
+ * Gets processor statistics
225
+ */
226
+ async getStats() {
227
+ return {
228
+ isRunning: this.isRunning,
229
+ batchSize: this.options.batchSize,
230
+ maxRetries: this.options.maxRetries,
231
+ processingInterval: this.options.processingInterval,
232
+ registeredHandlers: Array.from(this.handlers.keys()),
233
+ middlewareCount: this.middlewares.length
234
+ };
235
+ }
236
+ }
237
+ class f {
238
+ constructor(e) {
239
+ this.eventBus = e;
240
+ }
241
+ async handle(e) {
242
+ const s = {
243
+ eventName: e.messageType.startsWith("integration_event:") ? e.messageType.replace("integration_event:", "") : e.messageType,
244
+ eventId: e.id,
245
+ occurredOn: e.createdAt,
246
+ version: 1,
247
+ ...e.payload,
248
+ ...e.metadata
249
+ };
250
+ await this.eventBus.publish(s);
251
+ }
252
+ }
253
+ class M {
254
+ constructor(e, t = {}) {
255
+ this.logger = l.create("OutboxService"), this.repository = e, this.options = {
256
+ defaultPriority: t.defaultPriority ?? n.NORMAL,
257
+ defaultMetadata: t.defaultMetadata ?? {},
258
+ autoCleanup: t.autoCleanup ?? !1,
259
+ cleanupAfterDays: t.cleanupAfterDays ?? 7,
260
+ enableLogging: t.enableLogging ?? !1
261
+ };
262
+ }
263
+ /**
264
+ * Saves a message to the outbox
265
+ */
266
+ async saveMessage(e, t, s) {
267
+ const r = g.createMessage(e, t, {
268
+ priority: this.options.defaultPriority,
269
+ metadata: { ...this.options.defaultMetadata, ...s?.metadata },
270
+ ...s
271
+ }), a = await this.repository.saveMessage(r);
272
+ return this.logger.debug(`Saved message ${a} of type ${e}`), a;
273
+ }
274
+ /**
275
+ * Saves multiple messages to the outbox in a batch
276
+ */
277
+ async saveMessages(e) {
278
+ const t = e.map(
279
+ ({ messageType: r, payload: a, options: c }) => g.createMessage(r, a, {
280
+ priority: this.options.defaultPriority,
281
+ metadata: { ...this.options.defaultMetadata, ...c?.metadata },
282
+ ...c
283
+ })
284
+ ), s = await this.repository.saveBatch(t);
285
+ return this.logger.debug(`Saved batch of ${e.length} messages`), s;
286
+ }
287
+ /**
288
+ * Schedules a message for delayed processing
289
+ */
290
+ async scheduleMessage(e, t, s, r) {
291
+ const a = g.createDelayedMessage(e, t, s, {
292
+ priority: this.options.defaultPriority,
293
+ metadata: { ...this.options.defaultMetadata, ...r?.metadata },
294
+ ...r
295
+ }), c = await this.repository.scheduleMessage(a, a.processAfter);
296
+ return this.logger.debug(
297
+ `Scheduled message ${c} of type ${e} for processing in ${s}ms`
298
+ ), c;
299
+ }
300
+ /**
301
+ * Saves a high priority message to the outbox
302
+ */
303
+ async saveHighPriorityMessage(e, t, s) {
304
+ return this.saveMessage(e, t, {
305
+ ...s,
306
+ priority: n.HIGH
307
+ });
308
+ }
309
+ /**
310
+ * Saves a critical priority message to the outbox
311
+ */
312
+ async saveCriticalMessage(e, t, s) {
313
+ return this.saveMessage(e, t, {
314
+ ...s,
315
+ priority: n.CRITICAL
316
+ });
317
+ }
318
+ /**
319
+ * Converts a domain event to an outbox message and saves it
320
+ */
321
+ async saveDomainEvent(e, t) {
322
+ const s = g.createFromIntegrationEvent(
323
+ {
324
+ eventName: e.eventName,
325
+ payload: e,
326
+ metadata: {
327
+ eventId: e?.metadata?.eventId,
328
+ occurredOn: e?.metadata?.occurredOn,
329
+ version: e?.metadata?.version
330
+ }
331
+ },
332
+ {
333
+ priority: this.options.defaultPriority,
334
+ metadata: { ...this.options.defaultMetadata, ...t?.metadata },
335
+ ...t
336
+ }
337
+ ), r = await this.repository.saveMessage(s);
338
+ return this.logger.debug(`Saved domain event ${e.eventName} as message ${r}`), r;
339
+ }
340
+ /**
341
+ * Gets a message by ID
342
+ */
343
+ async getMessage(e) {
344
+ return this.repository.getById(e);
345
+ }
346
+ /**
347
+ * Gets pending messages (for monitoring purposes)
348
+ */
349
+ async getPendingMessages(e = 100) {
350
+ return this.repository.getUnprocessedMessages(e);
351
+ }
352
+ /**
353
+ * Retries a failed message by resetting it to pending status
354
+ */
355
+ async retryMessage(e) {
356
+ await this.repository.updateStatus(e, o.PENDING), this.logger.info(`Reset message ${e} to pending for retry`);
357
+ }
358
+ /**
359
+ * Retries multiple failed messages
360
+ */
361
+ async retryMessages(e) {
362
+ await this.repository.updateStatusBatch(e, o.PENDING), this.logger.info(`Reset ${e.length} messages to pending for retry`);
363
+ }
364
+ /**
365
+ * Cleans up old processed messages
366
+ */
367
+ async cleanup(e) {
368
+ const t = e ?? this.options.cleanupAfterDays, s = /* @__PURE__ */ new Date();
369
+ s.setDate(s.getDate() - t);
370
+ const r = await this.repository.deleteByStatusAndAge(
371
+ s,
372
+ o.PROCESSED
373
+ );
374
+ return this.logger.info(`Cleaned up ${r} processed messages older than ${t} days`), r;
375
+ }
376
+ /**
377
+ * Gets outbox statistics
378
+ */
379
+ async getStats() {
380
+ const e = this.repository;
381
+ return e.getMessageCountByStatus ? {
382
+ totalPending: e.getMessageCountByStatus(o.PENDING),
383
+ totalProcessing: e.getMessageCountByStatus(o.PROCESSING),
384
+ totalProcessed: e.getMessageCountByStatus(o.PROCESSED),
385
+ totalFailed: e.getMessageCountByStatus(o.FAILED)
386
+ } : {
387
+ totalPending: (await this.repository.getUnprocessedMessages(1e3)).length,
388
+ totalProcessing: 0,
389
+ // Would need separate query
390
+ totalProcessed: 0,
391
+ // Would need separate query
392
+ totalFailed: 0
393
+ // Would need separate query
394
+ };
395
+ }
396
+ /**
397
+ * Logs messages if logging is enabled
398
+ */
399
+ log(e) {
400
+ this.options.enableLogging && this.logger.info(e);
401
+ }
402
+ }
403
+ export {
404
+ f as EventBusOutboxHandler,
405
+ y as IOutboxRepository,
406
+ n as MessagePriority,
407
+ o as MessageStatus,
408
+ g as OutboxMessageFactory,
409
+ m as OutboxProcessor,
410
+ M as OutboxService
411
+ };
@@ -0,0 +1,5 @@
1
+ export * from './outbox-interfaces';
2
+ export * from './outbox-repository.interface';
3
+ export * from './outbox-message-factory';
4
+ export * from './outbox-processor';
5
+ export * from './outbox-service';
@@ -0,0 +1,50 @@
1
+ export declare enum MessageStatus {
2
+ PENDING = "PENDING",
3
+ PROCESSING = "PROCESSING",
4
+ PROCESSED = "PROCESSED",
5
+ FAILED = "FAILED"
6
+ }
7
+ export declare enum MessagePriority {
8
+ LOW = "low",
9
+ NORMAL = "normal",
10
+ HIGH = "high",
11
+ CRITICAL = "critical"
12
+ }
13
+ export interface IOutboxMessage<T = unknown> {
14
+ /** Unique identifier for the message */
15
+ id: string;
16
+ /** Type of message */
17
+ messageType: string;
18
+ /** Message payload */
19
+ payload: T;
20
+ /** Additional metadata */
21
+ metadata: Record<string, unknown>;
22
+ /** Processing status */
23
+ status: MessageStatus;
24
+ /** Processing attempts count */
25
+ attempts: number;
26
+ /** When the message was created */
27
+ createdAt: Date;
28
+ /** When to process the message (for delayed processing) */
29
+ processAfter?: Date;
30
+ /** Priority level */
31
+ priority?: MessagePriority;
32
+ /** Last error message (if any) */
33
+ lastError?: string;
34
+ }
35
+ export interface OutboxMessageOptions {
36
+ /** When to process the message (for delayed processing) */
37
+ processAfter?: Date;
38
+ /** Priority level */
39
+ priority?: MessagePriority;
40
+ /** Additional metadata */
41
+ metadata?: Record<string, unknown>;
42
+ }
43
+ export interface IOutboxMessageHandler<T = unknown> {
44
+ /**
45
+ * Handles a message from the outbox
46
+ * @param message Message to handle
47
+ */
48
+ handle(message: IOutboxMessage<T>): Promise<void>;
49
+ }
50
+ export type OutboxMiddleware = (next: (message: IOutboxMessage) => Promise<void>) => (message: IOutboxMessage) => Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { IOutboxMessage, OutboxMessageOptions } from './outbox-interfaces';
2
+ export declare class OutboxMessageFactory {
3
+ /**
4
+ * Creates a new outbox message
5
+ * @param messageType Type of the message
6
+ * @param payload Message payload
7
+ * @param options Additional options
8
+ * @returns Outbox message
9
+ */
10
+ static createMessage<T = unknown>(messageType: string, payload: T, options?: OutboxMessageOptions): IOutboxMessage<T>;
11
+ /**
12
+ * Creates a delayed outbox message
13
+ * @param messageType Type of the message
14
+ * @param payload Message payload
15
+ * @param delayMs Delay in milliseconds
16
+ * @param options Additional options
17
+ * @returns Outbox message scheduled for later processing
18
+ */
19
+ static createDelayedMessage<T = unknown>(messageType: string, payload: T, delayMs: number, options?: OutboxMessageOptions): IOutboxMessage<T>;
20
+ /**
21
+ * Creates a high priority outbox message
22
+ * @param messageType Type of the message
23
+ * @param payload Message payload
24
+ * @param options Additional options
25
+ * @returns High priority outbox message
26
+ */
27
+ static createHighPriorityMessage<T = unknown>(messageType: string, payload: T, options?: OutboxMessageOptions): IOutboxMessage<T>;
28
+ /**
29
+ * Creates an outbox message from an integration event
30
+ * @param event Integration event
31
+ * @param options Additional options
32
+ * @returns Outbox message containing the integration event
33
+ */
34
+ static createFromIntegrationEvent<T = unknown>(event: {
35
+ eventName: string;
36
+ payload?: T;
37
+ metadata?: Record<string, unknown>;
38
+ }, options?: OutboxMessageOptions): IOutboxMessage<T>;
39
+ }
@@ -0,0 +1,105 @@
1
+ import { IEventBus } from '../../../contracts/src/index.ts';
2
+ import { IOutboxMessage, IOutboxMessageHandler, OutboxMiddleware, MessagePriority } from './outbox-interfaces';
3
+ import { IOutboxRepository } from './outbox-repository.interface';
4
+ export interface OutboxProcessorOptions {
5
+ /** Maximum number of messages to process in one batch */
6
+ batchSize?: number;
7
+ /** Maximum retry attempts for failed messages */
8
+ maxRetries?: number;
9
+ /** Processing interval in milliseconds */
10
+ processingInterval?: number;
11
+ /** Priority order for message processing */
12
+ priorityOrder?: MessagePriority[];
13
+ /** Timeout for processing a single message */
14
+ messageTimeout?: number;
15
+ /** Enable logging */
16
+ enableLogging?: boolean;
17
+ }
18
+ export declare class OutboxProcessor {
19
+ private readonly repository;
20
+ private readonly options;
21
+ private readonly handlers;
22
+ private readonly middlewares;
23
+ private isRunning;
24
+ private processingTimer?;
25
+ private readonly logger;
26
+ constructor(repository: IOutboxRepository, options?: OutboxProcessorOptions);
27
+ /**
28
+ * Registers a message handler for a specific message type
29
+ */
30
+ registerHandler(messageType: string, handler: IOutboxMessageHandler): void;
31
+ /**
32
+ * Registers middleware for message processing pipeline
33
+ */
34
+ use(middleware: OutboxMiddleware): void;
35
+ /**
36
+ * Starts the processor with automatic interval processing
37
+ */
38
+ start(): void;
39
+ /**
40
+ * Stops the processor
41
+ */
42
+ stop(): void;
43
+ /**
44
+ * Processes a single batch of pending messages.
45
+ *
46
+ * **Dispatch model:** messages within a batch are dispatched **in parallel**
47
+ * via `Promise.allSettled`. A failure in message N does **not** block
48
+ * messages N+1..N+M. Each failure is isolated, retry-counted via
49
+ * `incrementAttempt`, and reset to `PENDING` (or marked `FAILED` after
50
+ * `maxRetries` attempts) — independently of sibling messages.
51
+ *
52
+ * **Ordering:** parallel dispatch means message processing order is
53
+ * **not guaranteed** within a batch, even if `priorityOrder` was used to
54
+ * select which messages to fetch. If you need strict ordering, set
55
+ * `batchSize: 1` (one message per batch = sequential by definition).
56
+ *
57
+ * **Throughput:** for N messages and dispatch latency L, total batch time
58
+ * is approximately L (parallel) rather than N*L (sequential). For 200
59
+ * messages at 10ms dispatch each: ~10ms parallel vs ~2s sequential.
60
+ *
61
+ * **Quiescence:** if there are no pending messages, returns immediately
62
+ * without invoking handlers.
63
+ *
64
+ * @public
65
+ * @stable
66
+ * @since 0.1.0
67
+ */
68
+ processBatch(): Promise<void>;
69
+ /**
70
+ * Processes a single message through the middleware pipeline
71
+ */
72
+ private processMessage;
73
+ /**
74
+ * Builds the middleware pipeline for message processing
75
+ */
76
+ private buildPipeline;
77
+ /**
78
+ * Handles errors during message processing
79
+ */
80
+ private handleMessageError;
81
+ /**
82
+ * Schedules the next processing cycle
83
+ */
84
+ private scheduleProcessing;
85
+ /**
86
+ * Logs messages if logging is enabled
87
+ */
88
+ private log;
89
+ /**
90
+ * Gets processor statistics
91
+ */
92
+ getStats(): Promise<{
93
+ isRunning: boolean;
94
+ batchSize: number;
95
+ maxRetries: number;
96
+ processingInterval: number;
97
+ registeredHandlers: string[];
98
+ middlewareCount: number;
99
+ }>;
100
+ }
101
+ export declare class EventBusOutboxHandler implements IOutboxMessageHandler {
102
+ private readonly eventBus;
103
+ constructor(eventBus: IEventBus);
104
+ handle(message: IOutboxMessage): Promise<void>;
105
+ }