@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.
@@ -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
+