@onlineapps/mq-client-core 1.0.36 → 1.0.38
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/package.json +1 -1
- package/src/buffer/InMemoryBuffer.js +118 -0
- package/src/buffer/MessageBuffer.js +107 -0
- package/src/buffer/RedisBuffer.js +57 -0
- package/src/index.js +12 -0
- package/src/layers/PublishLayer.js +242 -0
- package/src/monitoring/PublishMonitor.js +138 -0
- package/src/transports/rabbitmqClient.js +910 -183
- package/src/utils/publishErrors.js +105 -0
- package/src/workers/RecoveryWorker.js +99 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Specialized error types for publish/reconnect flow.
|
|
5
|
+
* These errors umožní čisté oddělení publish vrstvy a recovery workeru.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class TransientPublishError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} message - Human readable message
|
|
11
|
+
* @param {Error} [cause] - Underlying error
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'TransientPublishError';
|
|
16
|
+
this.cause = cause || null;
|
|
17
|
+
this.retryable = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class PermanentPublishError extends Error {
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} message - Human readable message
|
|
24
|
+
* @param {Error} [cause] - Underlying error
|
|
25
|
+
*/
|
|
26
|
+
constructor(message, cause) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'PermanentPublishError';
|
|
29
|
+
this.cause = cause || null;
|
|
30
|
+
this.retryable = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class QueueNotFoundError extends PermanentPublishError {
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} queueName
|
|
37
|
+
* @param {boolean} isInfrastructure
|
|
38
|
+
* @param {Error} [cause]
|
|
39
|
+
*/
|
|
40
|
+
constructor(queueName, isInfrastructure, cause) {
|
|
41
|
+
const baseMessage = isInfrastructure
|
|
42
|
+
? `Cannot publish to infrastructure queue ${queueName}: queue does not exist. Infrastructure queues must be created explicitly with correct arguments (TTL, max-length, etc.) before publishing.`
|
|
43
|
+
: `Queue ${queueName} does not exist`;
|
|
44
|
+
super(baseMessage, cause);
|
|
45
|
+
this.name = 'QueueNotFoundError';
|
|
46
|
+
this.queueName = queueName;
|
|
47
|
+
this.isInfrastructure = !!isInfrastructure;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Best-effort klasifikace chyb z publishu do specializovaných typů.
|
|
53
|
+
* Vrací původní chybu, pokud neodpovídá žádnému známému patternu.
|
|
54
|
+
*
|
|
55
|
+
* @param {Error} err
|
|
56
|
+
* @returns {Error} - buď speciální error, nebo původní err
|
|
57
|
+
*/
|
|
58
|
+
function classifyPublishError(err) {
|
|
59
|
+
if (!err) {
|
|
60
|
+
return err;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Už je to náš specializovaný error – ponecháme jak je
|
|
64
|
+
if (
|
|
65
|
+
err instanceof TransientPublishError ||
|
|
66
|
+
err instanceof PermanentPublishError ||
|
|
67
|
+
err instanceof QueueNotFoundError
|
|
68
|
+
) {
|
|
69
|
+
return err;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const message = (err.message || String(err)).toLowerCase();
|
|
73
|
+
|
|
74
|
+
// Queue neexistuje – trvalý problém, nerozbitný retry
|
|
75
|
+
if (message.includes('queue does not exist') || message.includes('no queue') || err.code === 404) {
|
|
76
|
+
// Nevíme, zda je to infra/biz – necháme isInfrastructure=false, publish vrstva si může dovodit sama
|
|
77
|
+
return new QueueNotFoundError('unknown', false, err);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Typicky transientní problémy connection/channel
|
|
81
|
+
if (
|
|
82
|
+
message.includes('connection closing') ||
|
|
83
|
+
message.includes('connection closed') ||
|
|
84
|
+
message.includes('connection ended') ||
|
|
85
|
+
message.includes('channel closed') ||
|
|
86
|
+
message.includes('channel ended') ||
|
|
87
|
+
message.includes('timeout') ||
|
|
88
|
+
message.includes('confirmation timeout') ||
|
|
89
|
+
message.includes('reconnecting')
|
|
90
|
+
) {
|
|
91
|
+
return new TransientPublishError(err.message || String(err), err);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ostatní považujeme za permanentní – ale explicitně označíme typ
|
|
95
|
+
return new PermanentPublishError(err.message || String(err), err);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
TransientPublishError,
|
|
100
|
+
PermanentPublishError,
|
|
101
|
+
QueueNotFoundError,
|
|
102
|
+
classifyPublishError,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { QueueNotFoundError } = require('../utils/publishErrors');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RecoveryWorker - řeší problémy s publish (connection recovery, queue creation)
|
|
7
|
+
*
|
|
8
|
+
* Scope:
|
|
9
|
+
* - infrastructure: connection recovery, channel recreation, consumer re-registration (NENÍ queue creation)
|
|
10
|
+
* - business: connection recovery, channel recreation, consumer re-registration, queue creation (pokud není infra queue)
|
|
11
|
+
*/
|
|
12
|
+
class RecoveryWorker {
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {Object} options.client - RabbitMQClient instance
|
|
16
|
+
* @param {string} [options.scope='infrastructure'] - 'infrastructure' nebo 'business'
|
|
17
|
+
* @param {Object} [options.logger] - Logger
|
|
18
|
+
*/
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
if (!options.client) {
|
|
21
|
+
throw new Error('RecoveryWorker requires a client instance');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this._client = options.client;
|
|
25
|
+
this._scope = options.scope || 'infrastructure';
|
|
26
|
+
this._logger = options.logger || console;
|
|
27
|
+
|
|
28
|
+
// Scope configuration
|
|
29
|
+
this._queueCreationEnabled = this._scope === 'business';
|
|
30
|
+
this._queueCreationFilter = options.queueCreationFilter || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle transient publish error
|
|
35
|
+
* @param {Error} error - TransientPublishError
|
|
36
|
+
* @param {Object} context - { queue, buffer, options }
|
|
37
|
+
*/
|
|
38
|
+
async handleTransientError(error, context) {
|
|
39
|
+
// Transient errors jsou už bufferované v PublishLayer
|
|
40
|
+
// Recovery worker jen loguje a může triggerovat další akce
|
|
41
|
+
this._logger?.warn?.(`[RecoveryWorker] Transient error handled (buffered): ${error.message}`, context);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handle QueueNotFoundError
|
|
46
|
+
* @param {QueueNotFoundError} error
|
|
47
|
+
* @param {Object} context - { queue, buffer, options }
|
|
48
|
+
*/
|
|
49
|
+
async handleQueueNotFound(error, context) {
|
|
50
|
+
if (!(error instanceof QueueNotFoundError)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Infrastructure queues must exist - cannot create
|
|
55
|
+
if (error.isInfrastructure) {
|
|
56
|
+
this._logger?.error?.(`[RecoveryWorker] Cannot create infrastructure queue '${error.queueName}' - must exist before publishing`);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Business queues - create if scope allows
|
|
61
|
+
if (this._queueCreationEnabled) {
|
|
62
|
+
// Check filter if provided
|
|
63
|
+
if (this._queueCreationFilter && !this._queueCreationFilter(error.queueName)) {
|
|
64
|
+
this._logger?.warn?.(`[RecoveryWorker] Queue creation filtered out for '${error.queueName}'`);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await this.createQueue(error.queueName, context.options);
|
|
70
|
+
this._logger?.info?.(`[RecoveryWorker] ✓ Created queue '${error.queueName}'`);
|
|
71
|
+
} catch (createErr) {
|
|
72
|
+
this._logger?.error?.(`[RecoveryWorker] Failed to create queue '${error.queueName}': ${createErr.message}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
this._logger?.error?.(`[RecoveryWorker] Cannot create queue '${error.queueName}' - scope is '${this._scope}' (queue creation disabled)`);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create queue (business services only)
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
async createQueue(queueName, options = {}) {
|
|
86
|
+
if (!this._queueCreationEnabled) {
|
|
87
|
+
throw new Error(`Queue creation not enabled for scope '${this._scope}'`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Use queue channel to create queue
|
|
91
|
+
await this._client._ensureQueueChannel();
|
|
92
|
+
|
|
93
|
+
const queueOptions = options.queueOptions || { durable: this._client._config.durable };
|
|
94
|
+
await this._client._queueChannel.assertQueue(queueName, queueOptions);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = RecoveryWorker;
|
|
99
|
+
|