@vytches/ddd-messaging 0.29.3 → 0.31.0-alpha.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/LLMGUIDE.md +137 -36
- package/README.md +86 -71
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +225 -136
- package/dist/outbox/outbox-interfaces.d.ts +19 -0
- package/dist/outbox/outbox-processor.d.ts +48 -6
- package/dist/outbox/outbox-repository.interface.d.ts +66 -9
- package/dist/outbox/outbox-service.d.ts +1 -6
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
var n = /* @__PURE__ */ ((i) => (i.PENDING = "PENDING", i.PROCESSING = "PROCESSING", i.PROCESSED = "PROCESSED", i.FAILED = "FAILED", i))(n || {}),
|
|
4
|
-
|
|
1
|
+
import { LibUtils as y, safeRun as g } from "@vytches/ddd-utils";
|
|
2
|
+
import { internalLogger as c } from "@vytches/ddd-contracts/internal";
|
|
3
|
+
var n = /* @__PURE__ */ ((i) => (i.PENDING = "PENDING", i.PROCESSING = "PROCESSING", i.PROCESSED = "PROCESSED", i.FAILED = "FAILED", i))(n || {}), l = /* @__PURE__ */ ((i) => (i.LOW = "low", i.NORMAL = "normal", i.HIGH = "high", i.CRITICAL = "critical", i))(l || {});
|
|
4
|
+
const f = [
|
|
5
|
+
"critical",
|
|
6
|
+
"high",
|
|
7
|
+
"normal",
|
|
8
|
+
"low"
|
|
9
|
+
/* LOW */
|
|
10
|
+
];
|
|
11
|
+
function R(i, e, t = f) {
|
|
12
|
+
const s = t.includes(i) ? t.indexOf(i) : t.length, a = t.includes(e) ? t.indexOf(e) : t.length;
|
|
13
|
+
return s - a;
|
|
14
|
+
}
|
|
15
|
+
class w {
|
|
16
|
+
/**
|
|
17
|
+
* Atomically claims a batch of unprocessed messages: reads PENDING
|
|
18
|
+
* messages *and* marks them `PROCESSING` in the same operation, so two
|
|
19
|
+
* concurrent callers (two processor instances, or two racing calls on one
|
|
20
|
+
* instance) never both receive the same message.
|
|
21
|
+
*
|
|
22
|
+
* **Optional, and non-atomic by default.** This method is declared with
|
|
23
|
+
* `?` — it is NOT part of the required abstract contract, so existing
|
|
24
|
+
* `IOutboxRepository` subclasses keep compiling unmodified (AC#5). The
|
|
25
|
+
* default implementation provided here is a convenience shim only: it
|
|
26
|
+
* calls {@link getUnprocessedMessages} and then `updateStatusBatch(...,
|
|
27
|
+
* PROCESSING)` as two separate steps. **That is NOT atomic** — it has the
|
|
28
|
+
* exact same read-then-mark race window as calling
|
|
29
|
+
* `getUnprocessedMessages` + `updateStatus` manually, and is safe only for
|
|
30
|
+
* a single-worker deployment. Override `claimBatch` in a concrete
|
|
31
|
+
* repository (e.g. backed by `SELECT ... FOR UPDATE SKIP LOCKED`, or a
|
|
32
|
+
* single atomic `UPDATE ... WHERE status='PENDING' LIMIT n RETURNING *`)
|
|
33
|
+
* to get real multi-worker/multi-processor safety.
|
|
34
|
+
*
|
|
35
|
+
* `OutboxProcessor` only calls this method when
|
|
36
|
+
* `OutboxProcessorOptions.useClaimBatch` is explicitly set to `true`
|
|
37
|
+
* (opt-in, default `false`) — see AC#1/AC#2. When enabled, the processor
|
|
38
|
+
* skips its own redundant `updateStatus(id, PROCESSING)` call for messages
|
|
39
|
+
* returned by `claimBatch`, since claiming already performed that mark.
|
|
40
|
+
*
|
|
41
|
+
* Handlers should be idempotent to side effects regardless — transient
|
|
42
|
+
* failures or duplicate-claim races (e.g. with the non-atomic default
|
|
43
|
+
* implementation) may still cause a handler to run more than once.
|
|
44
|
+
*
|
|
45
|
+
* @param limit - Maximum number of messages to claim.
|
|
46
|
+
* @param priorityOrder - See {@link getUnprocessedMessages}.
|
|
47
|
+
* @param messageTypes - See {@link getUnprocessedMessages}.
|
|
48
|
+
* @returns Messages that were claimed (already marked `PROCESSING`).
|
|
49
|
+
*/
|
|
50
|
+
async claimBatch(e, t, s) {
|
|
51
|
+
const a = await this.getUnprocessedMessages(e, t, s);
|
|
52
|
+
return a.length === 0 ? a : (await this.updateStatusBatch(
|
|
53
|
+
a.map((r) => r.id),
|
|
54
|
+
n.PROCESSING
|
|
55
|
+
), a.map((r) => ({ ...r, status: n.PROCESSING })));
|
|
56
|
+
}
|
|
5
57
|
/**
|
|
6
58
|
* Schedules a retry for a failed message at a specific future time.
|
|
7
59
|
*
|
|
@@ -43,7 +95,7 @@ class m {
|
|
|
43
95
|
return Promise.resolve(0);
|
|
44
96
|
}
|
|
45
97
|
}
|
|
46
|
-
class
|
|
98
|
+
class d {
|
|
47
99
|
/**
|
|
48
100
|
* Creates a new outbox message
|
|
49
101
|
* @param messageType Type of the message
|
|
@@ -52,17 +104,17 @@ class h {
|
|
|
52
104
|
* @returns Outbox message
|
|
53
105
|
*/
|
|
54
106
|
static createMessage(e, t, s) {
|
|
55
|
-
const
|
|
56
|
-
id:
|
|
107
|
+
const a = {
|
|
108
|
+
id: y.getUUID(),
|
|
57
109
|
messageType: e,
|
|
58
110
|
payload: t,
|
|
59
111
|
metadata: s?.metadata || {},
|
|
60
112
|
status: n.PENDING,
|
|
61
113
|
attempts: 0,
|
|
62
114
|
createdAt: /* @__PURE__ */ new Date(),
|
|
63
|
-
priority: s?.priority ||
|
|
115
|
+
priority: s?.priority || l.NORMAL
|
|
64
116
|
};
|
|
65
|
-
return s?.processAfter && (
|
|
117
|
+
return s?.processAfter && (a.processAfter = s.processAfter), a;
|
|
66
118
|
}
|
|
67
119
|
/**
|
|
68
120
|
* Creates a delayed outbox message
|
|
@@ -72,11 +124,11 @@ class h {
|
|
|
72
124
|
* @param options Additional options
|
|
73
125
|
* @returns Outbox message scheduled for later processing
|
|
74
126
|
*/
|
|
75
|
-
static createDelayedMessage(e, t, s,
|
|
76
|
-
const
|
|
77
|
-
return
|
|
78
|
-
...
|
|
79
|
-
processAfter:
|
|
127
|
+
static createDelayedMessage(e, t, s, a) {
|
|
128
|
+
const r = new Date(Date.now() + s);
|
|
129
|
+
return d.createMessage(e, t, {
|
|
130
|
+
...a,
|
|
131
|
+
processAfter: r
|
|
80
132
|
});
|
|
81
133
|
}
|
|
82
134
|
/**
|
|
@@ -87,9 +139,9 @@ class h {
|
|
|
87
139
|
* @returns High priority outbox message
|
|
88
140
|
*/
|
|
89
141
|
static createHighPriorityMessage(e, t, s) {
|
|
90
|
-
return
|
|
142
|
+
return d.createMessage(e, t, {
|
|
91
143
|
...s,
|
|
92
|
-
priority:
|
|
144
|
+
priority: l.HIGH
|
|
93
145
|
});
|
|
94
146
|
}
|
|
95
147
|
/**
|
|
@@ -105,52 +157,53 @@ class h {
|
|
|
105
157
|
...t?.metadata || {}
|
|
106
158
|
}
|
|
107
159
|
};
|
|
108
|
-
return t?.priority && (s.priority = t.priority), t?.processAfter && (s.processAfter = t.processAfter),
|
|
160
|
+
return t?.priority && (s.priority = t.priority), t?.processAfter && (s.processAfter = t.processAfter), d.createMessage(
|
|
109
161
|
`integration_event:${e.eventName}`,
|
|
110
162
|
e.payload,
|
|
111
163
|
s
|
|
112
164
|
);
|
|
113
165
|
}
|
|
114
166
|
}
|
|
115
|
-
class
|
|
167
|
+
class P {
|
|
116
168
|
constructor(e, t = {}) {
|
|
117
|
-
this.handlers = /* @__PURE__ */ new Map(), this.middlewares = [], this.isRunning = !1, this.
|
|
169
|
+
this.handlers = /* @__PURE__ */ new Map(), this.middlewares = [], this.isRunning = !1, this.warnedMissingClaimBatch = !1;
|
|
118
170
|
const s = t.batchSize ?? 10;
|
|
119
171
|
if (s > 1e4)
|
|
120
172
|
throw new RangeError(`OutboxProcessor: batchSize must be ≤ 10,000, got ${s}`);
|
|
121
173
|
if (t.retryBackoff) {
|
|
122
|
-
const { initial: o, multiplier:
|
|
123
|
-
if (o <= 0 ||
|
|
174
|
+
const { initial: o, multiplier: h, maxDelay: u } = t.retryBackoff;
|
|
175
|
+
if (o <= 0 || h <= 0 || u <= 0)
|
|
124
176
|
throw new RangeError(
|
|
125
|
-
`OutboxProcessor: retryBackoff fields must be positive numbers (initial=${o}, multiplier=${
|
|
177
|
+
`OutboxProcessor: retryBackoff fields must be positive numbers (initial=${o}, multiplier=${h}, maxDelay=${u})`
|
|
126
178
|
);
|
|
127
179
|
}
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
180
|
+
const a = t.messageTimeout ?? 3e4, r = t.crashRecoveryThresholdMs ?? Math.max(3e5, a);
|
|
181
|
+
if (r < a)
|
|
130
182
|
throw new RangeError(
|
|
131
|
-
`OutboxProcessor: crashRecoveryThresholdMs (${
|
|
183
|
+
`OutboxProcessor: crashRecoveryThresholdMs (${r}) must be ≥ messageTimeout (${a}) to avoid resetting in-flight messages`
|
|
132
184
|
);
|
|
133
185
|
this.repository = e, this.options = {
|
|
134
186
|
batchSize: s,
|
|
135
187
|
maxRetries: t.maxRetries ?? 3,
|
|
136
188
|
processingInterval: t.processingInterval ?? 5e3,
|
|
137
189
|
priorityOrder: t.priorityOrder ?? [
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
190
|
+
l.CRITICAL,
|
|
191
|
+
l.HIGH,
|
|
192
|
+
l.NORMAL,
|
|
193
|
+
l.LOW
|
|
142
194
|
],
|
|
143
|
-
messageTimeout:
|
|
195
|
+
messageTimeout: a,
|
|
144
196
|
enableLogging: t.enableLogging ?? !1,
|
|
145
197
|
retryBackoff: t.retryBackoff,
|
|
146
198
|
messageTypes: t.messageTypes,
|
|
147
199
|
crashRecoveryIntervalMs: t.crashRecoveryIntervalMs,
|
|
148
|
-
crashRecoveryThresholdMs:
|
|
200
|
+
crashRecoveryThresholdMs: r,
|
|
149
201
|
adaptiveRepoll: t.adaptiveRepoll ?? !1,
|
|
150
202
|
adaptiveRepollMaxConsecutive: t.adaptiveRepollMaxConsecutive ?? 5,
|
|
151
203
|
adaptiveRepollPauseMs: t.adaptiveRepollPauseMs ?? 50,
|
|
152
204
|
startupJitterMs: t.startupJitterMs ?? 0,
|
|
153
|
-
hooks: t.hooks
|
|
205
|
+
hooks: t.hooks,
|
|
206
|
+
useClaimBatch: t.useClaimBatch ?? !1
|
|
154
207
|
};
|
|
155
208
|
}
|
|
156
209
|
/**
|
|
@@ -162,31 +215,54 @@ class v {
|
|
|
162
215
|
try {
|
|
163
216
|
t();
|
|
164
217
|
} catch (s) {
|
|
165
|
-
|
|
166
|
-
|
|
218
|
+
c.error(
|
|
219
|
+
`OutboxProcessor: hook ${e} threw and was ignored`,
|
|
220
|
+
s instanceof Error ? s : void 0,
|
|
221
|
+
{ hookName: e }
|
|
222
|
+
);
|
|
167
223
|
}
|
|
168
224
|
}
|
|
169
225
|
/**
|
|
170
226
|
* Registers a message handler for a specific message type
|
|
171
227
|
*/
|
|
172
228
|
registerHandler(e, t) {
|
|
173
|
-
this.handlers.set(e, t)
|
|
229
|
+
this.handlers.set(e, t);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Registers a catch-all handler invoked when no type-specific handler matches.
|
|
233
|
+
*
|
|
234
|
+
* NOTE: Registering a default handler removes the implicit type allowlist —
|
|
235
|
+
* all future message types, including ones added by mistake or injected via
|
|
236
|
+
* compromised storage, will be dispatched to this handler. Validate
|
|
237
|
+
* `message.messageType` inside the handler against a known-types set if your
|
|
238
|
+
* domain requires an explicit allowlist.
|
|
239
|
+
*
|
|
240
|
+
* NOTE: When `messageTypes` filter option is set, the default handler handles
|
|
241
|
+
* only types that pass through that filter. To handle all types, leave
|
|
242
|
+
* `messageTypes` unset.
|
|
243
|
+
*
|
|
244
|
+
* Second/subsequent call emits a warning and replaces the previous handler.
|
|
245
|
+
*/
|
|
246
|
+
registerDefaultHandler(e) {
|
|
247
|
+
this.defaultHandler && c.warn(
|
|
248
|
+
"registerDefaultHandler: replacing existing default handler — previous handler discarded"
|
|
249
|
+
), this.defaultHandler = e;
|
|
174
250
|
}
|
|
175
251
|
/**
|
|
176
252
|
* Registers middleware for message processing pipeline
|
|
177
253
|
*/
|
|
178
254
|
use(e) {
|
|
179
|
-
this.middlewares.push(e)
|
|
255
|
+
this.middlewares.push(e);
|
|
180
256
|
}
|
|
181
257
|
/**
|
|
182
258
|
* Starts the processor with automatic interval processing
|
|
183
259
|
*/
|
|
184
260
|
start() {
|
|
185
261
|
if (this.isRunning) {
|
|
186
|
-
|
|
262
|
+
c.warn("OutboxProcessor: processor is already running");
|
|
187
263
|
return;
|
|
188
264
|
}
|
|
189
|
-
this.isRunning = !0
|
|
265
|
+
this.isRunning = !0;
|
|
190
266
|
const e = this.options.startupJitterMs > 0 ? Math.floor(Math.random() * this.options.startupJitterMs) : 0;
|
|
191
267
|
this.scheduleProcessing(0, this.options.processingInterval + e), this.scheduleCrashRecovery();
|
|
192
268
|
}
|
|
@@ -195,10 +271,10 @@ class v {
|
|
|
195
271
|
*/
|
|
196
272
|
stop() {
|
|
197
273
|
if (!this.isRunning) {
|
|
198
|
-
|
|
274
|
+
c.warn("OutboxProcessor: processor is not running");
|
|
199
275
|
return;
|
|
200
276
|
}
|
|
201
|
-
this.isRunning = !1, this.processingTimer && (clearTimeout(this.processingTimer), this.processingTimer = void 0), this.crashRecoveryTimer && (clearTimeout(this.crashRecoveryTimer), this.crashRecoveryTimer = void 0)
|
|
277
|
+
this.isRunning = !1, this.processingTimer && (clearTimeout(this.processingTimer), this.processingTimer = void 0), this.crashRecoveryTimer && (clearTimeout(this.crashRecoveryTimer), this.crashRecoveryTimer = void 0);
|
|
202
278
|
}
|
|
203
279
|
/**
|
|
204
280
|
* Processes a single batch of pending messages.
|
|
@@ -233,65 +309,82 @@ class v {
|
|
|
233
309
|
const e = this.options.batchSize;
|
|
234
310
|
if (!this.isRunning)
|
|
235
311
|
return { processed: 0, batchSize: e };
|
|
236
|
-
const
|
|
237
|
-
|
|
312
|
+
const t = this.repository.claimBatch, s = this.options.useClaimBatch === !0 && typeof t == "function";
|
|
313
|
+
this.options.useClaimBatch && !s && !this.warnedMissingClaimBatch && (this.warnedMissingClaimBatch = !0, c.warn(
|
|
314
|
+
"OutboxProcessor: useClaimBatch is enabled but the repository does not implement claimBatch() — falling back to getUnprocessedMessages (no atomic-claim protection)"
|
|
315
|
+
));
|
|
316
|
+
const [a, r] = await g(
|
|
317
|
+
() => s && t ? t.call(
|
|
318
|
+
this.repository,
|
|
319
|
+
e,
|
|
320
|
+
this.options.priorityOrder,
|
|
321
|
+
this.options.messageTypes
|
|
322
|
+
) : this.repository.getUnprocessedMessages(
|
|
238
323
|
e,
|
|
239
324
|
this.options.priorityOrder,
|
|
240
325
|
this.options.messageTypes
|
|
241
326
|
)
|
|
242
327
|
);
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
328
|
+
if (a)
|
|
329
|
+
return c.error(
|
|
330
|
+
"OutboxProcessor: error retrieving messages",
|
|
331
|
+
a instanceof Error ? a : void 0
|
|
332
|
+
), { processed: 0, batchSize: e };
|
|
333
|
+
const o = r;
|
|
334
|
+
if (!o || !Array.isArray(o) || o.length === 0)
|
|
335
|
+
return { processed: 0, batchSize: e };
|
|
336
|
+
const h = Date.now(), m = (await Promise.allSettled(
|
|
337
|
+
o.map((p) => this.processMessage(p, s))
|
|
253
338
|
)).filter(
|
|
254
|
-
(
|
|
339
|
+
(p) => p.status === "rejected" || p.status === "fulfilled" && p.value === !1
|
|
255
340
|
).length;
|
|
256
|
-
return this.
|
|
341
|
+
return this.safelyInvokeHook(
|
|
257
342
|
"onBatchComplete",
|
|
258
343
|
() => this.options.hooks?.onBatchComplete?.({
|
|
259
|
-
processed:
|
|
260
|
-
failed:
|
|
344
|
+
processed: o.length,
|
|
345
|
+
failed: m,
|
|
261
346
|
batchSize: e,
|
|
262
|
-
durationMs: Date.now() -
|
|
347
|
+
durationMs: Date.now() - h
|
|
263
348
|
})
|
|
264
|
-
), { processed:
|
|
349
|
+
), { processed: o.length, batchSize: e };
|
|
265
350
|
}
|
|
266
351
|
/**
|
|
267
352
|
* Processes a single message through the middleware pipeline.
|
|
353
|
+
* @param alreadyClaimed When `true`, the message was already marked
|
|
354
|
+
* `PROCESSING` by `IOutboxRepository.claimBatch()` — the redundant
|
|
355
|
+
* `updateStatus(id, PROCESSING)` call is skipped.
|
|
268
356
|
* @returns `true` if the message was processed successfully, `false` if it failed.
|
|
269
357
|
*/
|
|
270
|
-
async processMessage(e) {
|
|
271
|
-
const [
|
|
272
|
-
await this.repository.updateStatus(e.id, n.PROCESSING), await this.buildPipeline(e)(e), await this.repository.updateStatus(e.id, n.PROCESSED)
|
|
358
|
+
async processMessage(e, t = !1) {
|
|
359
|
+
const [s] = await g(async () => {
|
|
360
|
+
t || await this.repository.updateStatus(e.id, n.PROCESSING), await this.buildPipeline(e)(e), await this.repository.updateStatus(e.id, n.PROCESSED);
|
|
273
361
|
});
|
|
274
|
-
return
|
|
362
|
+
return s ? (await this.handleMessageError(e, s), !1) : !0;
|
|
275
363
|
}
|
|
276
364
|
/**
|
|
277
365
|
* Builds the middleware pipeline for message processing
|
|
278
366
|
*/
|
|
279
367
|
buildPipeline(e) {
|
|
280
|
-
const t = this.handlers.get(e.messageType);
|
|
368
|
+
const t = this.handlers.get(e.messageType) ?? this.defaultHandler;
|
|
281
369
|
if (!t)
|
|
282
370
|
throw new Error(`No handler registered for message type: ${e.messageType}`);
|
|
283
|
-
const s = async (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
(a,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
371
|
+
const s = async (a) => {
|
|
372
|
+
let r;
|
|
373
|
+
try {
|
|
374
|
+
await Promise.race([
|
|
375
|
+
t.handle(a),
|
|
376
|
+
new Promise((o, h) => {
|
|
377
|
+
r = setTimeout(
|
|
378
|
+
() => h(new Error("Message processing timeout")),
|
|
379
|
+
this.options.messageTimeout
|
|
380
|
+
);
|
|
381
|
+
})
|
|
382
|
+
]);
|
|
383
|
+
} finally {
|
|
384
|
+
r !== void 0 && clearTimeout(r);
|
|
385
|
+
}
|
|
293
386
|
};
|
|
294
|
-
return this.middlewares.reduceRight((
|
|
387
|
+
return this.middlewares.reduceRight((a, r) => r(a), s);
|
|
295
388
|
}
|
|
296
389
|
/**
|
|
297
390
|
* Handles errors during message processing
|
|
@@ -299,24 +392,30 @@ class v {
|
|
|
299
392
|
async handleMessageError(e, t) {
|
|
300
393
|
try {
|
|
301
394
|
const s = await this.repository.incrementAttempt(e.id);
|
|
302
|
-
if (
|
|
395
|
+
if (c.warn(
|
|
396
|
+
`OutboxProcessor: message ${e.id} failed, attempt ${s}: ${y.sanitizeLogMessage(t?.message ?? String(t))}`
|
|
397
|
+
), this.safelyInvokeHook(
|
|
303
398
|
"onMessageFailed",
|
|
304
399
|
() => this.options.hooks?.onMessageFailed?.(e, t, s)
|
|
305
400
|
), s >= this.options.maxRetries)
|
|
306
|
-
await this.repository.updateStatus(e.id, n.FAILED, t),
|
|
401
|
+
await this.repository.updateStatus(e.id, n.FAILED, t), c.error("OutboxProcessor: message permanently failed", t, {
|
|
402
|
+
messageId: e.id,
|
|
403
|
+
attempts: s
|
|
404
|
+
}), this.safelyInvokeHook(
|
|
307
405
|
"onPermanentFailure",
|
|
308
406
|
() => this.options.hooks?.onPermanentFailure?.(e, t)
|
|
309
407
|
);
|
|
310
408
|
else if (this.options.retryBackoff) {
|
|
311
|
-
const { initial:
|
|
312
|
-
await this.repository.updateStatus(e.id, n.PENDING), await this.repository.scheduleRetry(e.id,
|
|
313
|
-
`Message ${e.id} scheduled for retry in ${l}ms (attempt ${s})`
|
|
314
|
-
);
|
|
409
|
+
const { initial: a, multiplier: r, maxDelay: o } = this.options.retryBackoff, h = Math.min(a * Math.pow(r, s - 1), o), u = new Date(Date.now() + h);
|
|
410
|
+
await this.repository.updateStatus(e.id, n.PENDING), await this.repository.scheduleRetry(e.id, u);
|
|
315
411
|
} else
|
|
316
|
-
await this.repository.updateStatus(e.id, n.PENDING)
|
|
412
|
+
await this.repository.updateStatus(e.id, n.PENDING);
|
|
317
413
|
} catch (s) {
|
|
318
|
-
|
|
319
|
-
|
|
414
|
+
c.error(
|
|
415
|
+
"OutboxProcessor: error updating message status",
|
|
416
|
+
s instanceof Error ? s : void 0,
|
|
417
|
+
{ messageId: e.id }
|
|
418
|
+
);
|
|
320
419
|
}
|
|
321
420
|
}
|
|
322
421
|
/**
|
|
@@ -335,8 +434,8 @@ class v {
|
|
|
335
434
|
this.isRunning && (this.processingTimer = setTimeout(async () => {
|
|
336
435
|
const s = await this.processBatch();
|
|
337
436
|
if (this.options.adaptiveRepoll && s.processed > 0 && s.processed >= s.batchSize) {
|
|
338
|
-
const
|
|
339
|
-
this.scheduleProcessing(
|
|
437
|
+
const r = e + 1, o = r > this.options.adaptiveRepollMaxConsecutive ? this.options.adaptiveRepollPauseMs : 0;
|
|
438
|
+
this.scheduleProcessing(r, o);
|
|
340
439
|
} else
|
|
341
440
|
this.scheduleProcessing(0, this.options.processingInterval);
|
|
342
441
|
}, t));
|
|
@@ -356,19 +455,17 @@ class v {
|
|
|
356
455
|
* logged, never thrown — crash recovery must not crash the processor.
|
|
357
456
|
*/
|
|
358
457
|
async runCrashRecovery() {
|
|
359
|
-
const e = new Date(Date.now() - this.options.crashRecoveryThresholdMs), [t, s] = await
|
|
458
|
+
const e = new Date(Date.now() - this.options.crashRecoveryThresholdMs), [t, s] = await g(() => this.repository.resetStaleProcessing(e));
|
|
360
459
|
if (t) {
|
|
361
|
-
|
|
362
|
-
|
|
460
|
+
c.error(
|
|
461
|
+
"OutboxProcessor: crash recovery failed",
|
|
462
|
+
t instanceof Error ? t : void 0
|
|
463
|
+
);
|
|
363
464
|
return;
|
|
364
465
|
}
|
|
365
|
-
s && s > 0 &&
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
* Logs messages if logging is enabled
|
|
369
|
-
*/
|
|
370
|
-
log(e) {
|
|
371
|
-
this.options.enableLogging && this.logger.info(e);
|
|
466
|
+
s && s > 0 && c.warn(
|
|
467
|
+
`OutboxProcessor: crash recovery reset ${s} stale PROCESSING message(s) to PENDING`
|
|
468
|
+
);
|
|
372
469
|
}
|
|
373
470
|
/**
|
|
374
471
|
* Gets processor statistics
|
|
@@ -380,11 +477,12 @@ class v {
|
|
|
380
477
|
maxRetries: this.options.maxRetries,
|
|
381
478
|
processingInterval: this.options.processingInterval,
|
|
382
479
|
registeredHandlers: Array.from(this.handlers.keys()),
|
|
383
|
-
middlewareCount: this.middlewares.length
|
|
480
|
+
middlewareCount: this.middlewares.length,
|
|
481
|
+
hasDefaultHandler: this.defaultHandler !== void 0
|
|
384
482
|
};
|
|
385
483
|
}
|
|
386
484
|
}
|
|
387
|
-
class
|
|
485
|
+
class I {
|
|
388
486
|
constructor(e) {
|
|
389
487
|
this.eventBus = e;
|
|
390
488
|
}
|
|
@@ -400,10 +498,10 @@ class M {
|
|
|
400
498
|
await this.eventBus.publish(s);
|
|
401
499
|
}
|
|
402
500
|
}
|
|
403
|
-
class
|
|
501
|
+
class S {
|
|
404
502
|
constructor(e, t = {}) {
|
|
405
|
-
this.
|
|
406
|
-
defaultPriority: t.defaultPriority ??
|
|
503
|
+
this.repository = e, this.options = {
|
|
504
|
+
defaultPriority: t.defaultPriority ?? l.NORMAL,
|
|
407
505
|
defaultMetadata: t.defaultMetadata ?? {},
|
|
408
506
|
autoCleanup: t.autoCleanup ?? !1,
|
|
409
507
|
cleanupAfterDays: t.cleanupAfterDays ?? 7,
|
|
@@ -414,38 +512,36 @@ class R {
|
|
|
414
512
|
* Saves a message to the outbox
|
|
415
513
|
*/
|
|
416
514
|
async saveMessage(e, t, s) {
|
|
417
|
-
const
|
|
515
|
+
const a = d.createMessage(e, t, {
|
|
418
516
|
priority: this.options.defaultPriority,
|
|
419
517
|
metadata: { ...this.options.defaultMetadata, ...s?.metadata },
|
|
420
518
|
...s
|
|
421
|
-
})
|
|
422
|
-
return this.
|
|
519
|
+
});
|
|
520
|
+
return await this.repository.saveMessage(a);
|
|
423
521
|
}
|
|
424
522
|
/**
|
|
425
523
|
* Saves multiple messages to the outbox in a batch
|
|
426
524
|
*/
|
|
427
525
|
async saveMessages(e) {
|
|
428
526
|
const t = e.map(
|
|
429
|
-
({ messageType:
|
|
527
|
+
({ messageType: a, payload: r, options: o }) => d.createMessage(a, r, {
|
|
430
528
|
priority: this.options.defaultPriority,
|
|
431
529
|
metadata: { ...this.options.defaultMetadata, ...o?.metadata },
|
|
432
530
|
...o
|
|
433
531
|
})
|
|
434
|
-
)
|
|
435
|
-
return this.
|
|
532
|
+
);
|
|
533
|
+
return await this.repository.saveBatch(t);
|
|
436
534
|
}
|
|
437
535
|
/**
|
|
438
536
|
* Schedules a message for delayed processing
|
|
439
537
|
*/
|
|
440
|
-
async scheduleMessage(e, t, s,
|
|
441
|
-
const
|
|
538
|
+
async scheduleMessage(e, t, s, a) {
|
|
539
|
+
const r = d.createDelayedMessage(e, t, s, {
|
|
442
540
|
priority: this.options.defaultPriority,
|
|
443
|
-
metadata: { ...this.options.defaultMetadata, ...
|
|
444
|
-
...
|
|
445
|
-
})
|
|
446
|
-
return this.
|
|
447
|
-
`Scheduled message ${o} of type ${e} for processing in ${s}ms`
|
|
448
|
-
), o;
|
|
541
|
+
metadata: { ...this.options.defaultMetadata, ...a?.metadata },
|
|
542
|
+
...a
|
|
543
|
+
});
|
|
544
|
+
return await this.repository.scheduleMessage(r, r.processAfter);
|
|
449
545
|
}
|
|
450
546
|
/**
|
|
451
547
|
* Saves a high priority message to the outbox
|
|
@@ -453,7 +549,7 @@ class R {
|
|
|
453
549
|
async saveHighPriorityMessage(e, t, s) {
|
|
454
550
|
return this.saveMessage(e, t, {
|
|
455
551
|
...s,
|
|
456
|
-
priority:
|
|
552
|
+
priority: l.HIGH
|
|
457
553
|
});
|
|
458
554
|
}
|
|
459
555
|
/**
|
|
@@ -462,14 +558,14 @@ class R {
|
|
|
462
558
|
async saveCriticalMessage(e, t, s) {
|
|
463
559
|
return this.saveMessage(e, t, {
|
|
464
560
|
...s,
|
|
465
|
-
priority:
|
|
561
|
+
priority: l.CRITICAL
|
|
466
562
|
});
|
|
467
563
|
}
|
|
468
564
|
/**
|
|
469
565
|
* Converts a domain event to an outbox message and saves it
|
|
470
566
|
*/
|
|
471
567
|
async saveDomainEvent(e, t) {
|
|
472
|
-
const s =
|
|
568
|
+
const s = d.createFromIntegrationEvent(
|
|
473
569
|
{
|
|
474
570
|
eventName: e.eventName,
|
|
475
571
|
payload: e,
|
|
@@ -484,8 +580,8 @@ class R {
|
|
|
484
580
|
metadata: { ...this.options.defaultMetadata, ...t?.metadata },
|
|
485
581
|
...t
|
|
486
582
|
}
|
|
487
|
-
)
|
|
488
|
-
return this.
|
|
583
|
+
);
|
|
584
|
+
return await this.repository.saveMessage(s);
|
|
489
585
|
}
|
|
490
586
|
/**
|
|
491
587
|
* Gets a message by ID
|
|
@@ -503,25 +599,23 @@ class R {
|
|
|
503
599
|
* Retries a failed message by resetting it to pending status
|
|
504
600
|
*/
|
|
505
601
|
async retryMessage(e) {
|
|
506
|
-
await this.repository.updateStatus(e, n.PENDING)
|
|
602
|
+
await this.repository.updateStatus(e, n.PENDING);
|
|
507
603
|
}
|
|
508
604
|
/**
|
|
509
605
|
* Retries multiple failed messages
|
|
510
606
|
*/
|
|
511
607
|
async retryMessages(e) {
|
|
512
|
-
await this.repository.updateStatusBatch(e, n.PENDING)
|
|
608
|
+
await this.repository.updateStatusBatch(e, n.PENDING);
|
|
513
609
|
}
|
|
514
610
|
/**
|
|
515
611
|
* Cleans up old processed messages
|
|
516
612
|
*/
|
|
517
613
|
async cleanup(e) {
|
|
518
614
|
const t = e ?? this.options.cleanupAfterDays, s = /* @__PURE__ */ new Date();
|
|
519
|
-
s.setDate(s.getDate() - t)
|
|
520
|
-
const r = await this.repository.deleteByStatusAndAge(
|
|
615
|
+
return s.setDate(s.getDate() - t), await this.repository.deleteByStatusAndAge(
|
|
521
616
|
s,
|
|
522
617
|
n.PROCESSED
|
|
523
618
|
);
|
|
524
|
-
return this.logger.info(`Cleaned up ${r} processed messages older than ${t} days`), r;
|
|
525
619
|
}
|
|
526
620
|
/**
|
|
527
621
|
* Gets outbox statistics
|
|
@@ -543,19 +637,14 @@ class R {
|
|
|
543
637
|
// Would need separate query
|
|
544
638
|
};
|
|
545
639
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Logs messages if logging is enabled
|
|
548
|
-
*/
|
|
549
|
-
log(e) {
|
|
550
|
-
this.options.enableLogging && this.logger.info(e);
|
|
551
|
-
}
|
|
552
640
|
}
|
|
553
641
|
export {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
642
|
+
I as EventBusOutboxHandler,
|
|
643
|
+
w as IOutboxRepository,
|
|
644
|
+
l as MessagePriority,
|
|
557
645
|
n as MessageStatus,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
646
|
+
d as OutboxMessageFactory,
|
|
647
|
+
P as OutboxProcessor,
|
|
648
|
+
S as OutboxService,
|
|
649
|
+
R as comparePriority
|
|
561
650
|
};
|
|
@@ -48,3 +48,22 @@ export interface IOutboxMessageHandler<T = unknown> {
|
|
|
48
48
|
handle(message: IOutboxMessage<T>): Promise<void>;
|
|
49
49
|
}
|
|
50
50
|
export type OutboxMiddleware = (next: (message: IOutboxMessage) => Promise<void>) => (message: IOutboxMessage) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Compares two MessagePriority values for sorting purposes.
|
|
53
|
+
*
|
|
54
|
+
* Returns a negative number if `a` should be processed before `b`,
|
|
55
|
+
* positive if `b` before `a`, 0 if equal.
|
|
56
|
+
*
|
|
57
|
+
* @param order - Processing order array (first element = highest priority).
|
|
58
|
+
* Defaults to [CRITICAL, HIGH, NORMAL, LOW].
|
|
59
|
+
* Values absent from the array sort LAST (treated as lowest priority).
|
|
60
|
+
* Passing a partial array is safe — missing values are not promoted.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Sort messages by priority (highest first):
|
|
64
|
+
* messages.sort((a, b) => comparePriority(a.priority, b.priority));
|
|
65
|
+
*
|
|
66
|
+
* // Custom order:
|
|
67
|
+
* messages.sort((a, b) => comparePriority(a.priority, b.priority, ['high', 'critical', 'normal', 'low']));
|
|
68
|
+
*/
|
|
69
|
+
export declare function comparePriority(a: MessagePriority, b: MessagePriority, order?: MessagePriority[]): number;
|