@onlineapps/mq-client-core 5.1.0 → 6.0.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/CHANGELOG.md +329 -0
- package/README.md +74 -12
- package/package.json +2 -2
- package/src/BaseClient.js +80 -66
- package/src/buffer/InMemoryBuffer.js +66 -3
- package/src/buffer/MessageBuffer.js +3 -0
- package/src/config/configSchema.js +3 -13
- package/src/config/defaultConfig.js +3 -5
- package/src/config/queueConfig.js +35 -1
- package/src/index.js +22 -0
- package/src/layers/PublishLayer.js +95 -18
- package/src/transports/rabbitmqClient.js +366 -76
- package/src/utils/errorHandler.js +25 -0
- package/src/utils/publishErrors.js +49 -0
|
@@ -186,7 +186,32 @@ class SerializationError extends Error {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Carry a verdict that is ALREADY written onto the error a caller is about to receive.
|
|
191
|
+
*
|
|
192
|
+
* The verdict of a lost connection is written in one place (`RabbitMQClient._failFatally()`,
|
|
193
|
+
* `_markBrokerRefusal()`: `code: 'MQ_CONNECTION_FATAL'`, `reason`). Every receiver reads it ON
|
|
194
|
+
* the error it is handed — `BaseClient._consumerRefusal()`, `@onlineapps/service-wrapper`
|
|
195
|
+
* FÁZE 0.3/0.7 — never down a `cause` chain (a reader walking the chain would be a fallback
|
|
196
|
+
* over a buried marker, `architecture-principles.md` §3). So every layer that builds an error
|
|
197
|
+
* AROUND a verdict copies `code` and `reason` onto it with this one function (d.1297 consume,
|
|
198
|
+
* d.1300 publish); an error that is not a fatal verdict (a spent cycle carries no code, by
|
|
199
|
+
* contract) leaves the target untouched.
|
|
200
|
+
*
|
|
201
|
+
* @param {Error} error - the error being built; `cause` already set by its builder
|
|
202
|
+
* @param {Error|null|undefined} verdict - what the recovery produced
|
|
203
|
+
* @returns {Error} the same error, with the verdict readable on top when there is one
|
|
204
|
+
*/
|
|
205
|
+
function carryVerdict(error, verdict) {
|
|
206
|
+
if (verdict && verdict.code === 'MQ_CONNECTION_FATAL') {
|
|
207
|
+
error.code = verdict.code;
|
|
208
|
+
error.reason = verdict.reason;
|
|
209
|
+
}
|
|
210
|
+
return error;
|
|
211
|
+
}
|
|
212
|
+
|
|
189
213
|
module.exports = {
|
|
214
|
+
carryVerdict,
|
|
190
215
|
ValidationError,
|
|
191
216
|
ConnectionError,
|
|
192
217
|
CONNECTION_CLOSED_UNEXPECTEDLY,
|
|
@@ -22,6 +22,54 @@ class TransientPublishError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The send channel could not be had because the connection stayed down: the recovery this
|
|
27
|
+
* attempt waited for ended without a connection (a spent cycle — standby; the last cycle or
|
|
28
|
+
* a broker refusal — fatal; a wait that completed into a connection already dead again), or
|
|
29
|
+
* no recovery may start (the client is permanently lost, the caller closed it, recovery is
|
|
30
|
+
* disabled by configuration).
|
|
31
|
+
*
|
|
32
|
+
* A TRANSIENT publish error, by the owner's decision (B1, 2026-10-06, d.1300): the message is
|
|
33
|
+
* held in the buffer like every other transiently failed message and replayed after the next
|
|
34
|
+
* `reconnected` / `unblocked` — one rail with the retry that waited for a reconnect and saw it
|
|
35
|
+
* fail (`PublishLayer`). What sets it apart from the other transient errors, and why it is its
|
|
36
|
+
* own class: retrying the attempt NOW is pointless and harmful — the next use of the client is
|
|
37
|
+
* what starts the next recovery cycle, so `PublishLayer` holds the message at once instead of
|
|
38
|
+
* spending its retry budget (and recovery cycles) on it.
|
|
39
|
+
*
|
|
40
|
+
* `cause` is the error that ended the wait when there was one (the fatal verdict is the very
|
|
41
|
+
* object `onFatal` received), and when that verdict is fatal its `code`/`reason` are carried on
|
|
42
|
+
* this error too (`errorHandler.carryVerdict()`), so a reader finds them where every reader
|
|
43
|
+
* looks. A spent cycle carries no code, by contract.
|
|
44
|
+
*/
|
|
45
|
+
class RecoveryEndedPublishError extends TransientPublishError {
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} message
|
|
48
|
+
* @param {Error|undefined} cause - what ended the recovery, when an object exists for it
|
|
49
|
+
* @param {{replayable: boolean}} verdict - `replayable`: whether a `reconnected` can still
|
|
50
|
+
* come on this client without the caller's action — true after a spent cycle (the next
|
|
51
|
+
* use starts the next one), false once the client is permanently lost, closed by
|
|
52
|
+
* `disconnect()`, or has `reconnectEnabled: false`. It decides whether `PublishLayer`
|
|
53
|
+
* holds the message (d.1301): a hold only makes sense where a reconnect can replay it;
|
|
54
|
+
* anywhere else it would sit until its TTL and be reported LOST, which is a loss with a
|
|
55
|
+
* delay, not a safety. Required: a transient error that cannot say whether a replay can
|
|
56
|
+
* come must not be held on a guess.
|
|
57
|
+
*/
|
|
58
|
+
constructor(message, cause, verdict) {
|
|
59
|
+
super(message, cause);
|
|
60
|
+
this.name = 'RecoveryEndedPublishError';
|
|
61
|
+
if (!verdict || typeof verdict.replayable !== 'boolean') {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'[RecoveryEndedPublishError] Missing required field - replayable (boolean) is required. '
|
|
64
|
+
+ 'Expected: true when a reconnected can still come on this client, false when it never will '
|
|
65
|
+
+ '(permanently lost, disconnect(), reconnectEnabled: false). '
|
|
66
|
+
+ 'Fix: pass { replayable } — RabbitMQClient._recoveryEndedForPublish() derives it from the client state.'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
this.replayable = verdict.replayable;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
25
73
|
class PermanentPublishError extends Error {
|
|
26
74
|
/**
|
|
27
75
|
* @param {string} message - Human readable message
|
|
@@ -468,6 +516,7 @@ module.exports = {
|
|
|
468
516
|
missingQueueDefinitionMessage,
|
|
469
517
|
refusedSendMessage,
|
|
470
518
|
TransientPublishError,
|
|
519
|
+
RecoveryEndedPublishError,
|
|
471
520
|
PermanentPublishError,
|
|
472
521
|
QueueNotFoundError,
|
|
473
522
|
classifyPublishError,
|