@onlineapps/mq-client-core 2.0.1 → 3.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.
@@ -1,49 +1,57 @@
1
1
  'use strict';
2
2
 
3
3
  const { QueueNotFoundError } = require('../utils/publishErrors');
4
+ const { ValidationError } = require('../utils/errorHandler');
5
+ const { assertLogger } = require('@onlineapps/logger-contract');
4
6
 
5
7
  /**
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: totéž + zakládání front, ale POUZE těch, které nemají vlastníka
11
- * (viz Queue Creation níže). Scope říká, ČÍ ten klient je — ne co smí založit.
8
+ * RecoveryWorker odpovídá na problémy publish cesty (connection recovery,
9
+ * hlášení chybějící fronty).
12
10
  *
13
- * Queue Creationvlastnictví fronty rozhoduje, ne scope:
14
- * - infrastrukturní fronty (`queueConfig.isInfrastructureQueue`) zakládá vlastnící
15
- * infra služba předem tento worker je nikdy nezaloží, chybu propustí dál;
16
- * - business fronty (`queueConfig.isBusinessQueue`, tj. `{service}.{workflow|queue|dlq}`)
17
- * zakládá vlastnící služba přes `setupServiceQueues()` po registraci, s argumenty
18
- * z queueConfig (TTL, DLQ) tento worker je nikdy nezaloží, chybu propustí dál;
19
- * - ostatní jména (ani infra, ani business vzor) smí založit, pokud to scope
20
- * a `queueCreationFilter` dovolují buď přes `queueCreationCallback`
21
- * (deleguje na QueueManager), nebo přímo.
11
+ * Zakládání frontNEZAKLÁDÁ ŽÁDNOU. Fronta vzniká výhradně z deklarace:
12
+ * infrastrukturní ji zakládá vlastnící infra služba při bootu, business ji
13
+ * zakládá vlastnící služba přes `setupServiceQueues()` nad šablonou z
14
+ * `queueConfig` po registraci, a jméno, které `queueConfig` neklasifikuje ani
15
+ * jako jedno, nezakládá nikdo. Rozhodnutí vlastníka
16
+ * (`docs/governance/confirmations/mq-consumer-contract.md` 006) říká obojí
17
+ * větou: *„queues come into being only from declarations"* a *„A future family
18
+ * of queues that a service must create ad hoc gets a `queueConfig` template
19
+ * first, never a generic create-anything method."*
20
+ *
21
+ * Do d.419 tady byly DRUHÉ dveře k těm, které publish cesta v témže kroku
22
+ * zavírala: `createQueue()` a delegující `queueCreationCallback` zakládaly
23
+ * frontu bez vlastníka s `durable=true, arguments={}` — bez TTL a bez cesty pro
24
+ * nedoručitelné zprávy. Taková fronta je od d.259 nekonzumovatelná
25
+ * (`consume()` odmítne frontu, které konfigurace žádnou cestu nedeklaruje), takže
26
+ * ty dveře vyráběly fronty, do kterých lze publikovat a nelze z nich číst.
27
+ * Čtyři otázky (`change-discipline.md` § Removing something removes its
28
+ * declaration) jsou zodpovězené v CHANGELOGu dávky.
29
+ *
30
+ * `scope` tedy už o zakládání front nerozhoduje; říká jen, čí ten klient je.
22
31
  */
23
32
  class RecoveryWorker {
24
33
  /**
25
34
  * @param {Object} options
26
35
  * @param {Object} options.client - RabbitMQClient instance
27
36
  * @param {string} [options.scope='infrastructure'] - 'infrastructure' nebo 'business'
28
- * @param {Function} [options.queueCreationCallback] - Callback for queue creation: async (queueName, options) => void
29
- * Should delegate to QueueManager (e.g., setupServiceQueues or ensureQueue)
30
- * @param {Object} [options.logger] - Logger
37
+ * @param {Object} options.logger - Logger s info/warn/error/debug (povinny)
31
38
  */
32
39
  constructor(options = {}) {
33
40
  if (!options.client) {
34
- throw new Error('RecoveryWorker requires a client instance');
41
+ throw new ValidationError(
42
+ '[RecoveryWorker] options.client is required - Expected: the RabbitMQClient whose publish failures the worker answers. '
43
+ + 'Fix: construct it as new RecoveryWorker({ client, logger }).'
44
+ );
35
45
  }
36
46
 
37
47
  this._client = options.client;
38
48
  this._scope = options.scope || 'infrastructure';
39
- this._logger = options.logger || console;
40
-
41
- // Scope configuration
42
- this._queueCreationEnabled = this._scope === 'business';
43
- this._queueCreationFilter = options.queueCreationFilter || null;
44
-
45
- // Queue creation callback - delegates to QueueManager if provided
46
- this._queueCreationCallback = options.queueCreationCallback || null;
49
+ this._logger = assertLogger(
50
+ 'RecoveryWorker',
51
+ options.logger,
52
+ 'the worker reports every recovery decision it makes about a queue that does not exist',
53
+ 'pass options.logger (the transport forwards the one your service built)'
54
+ );
47
55
  }
48
56
 
49
57
  /**
@@ -54,11 +62,16 @@ class RecoveryWorker {
54
62
  async handleTransientError(error, context) {
55
63
  // Transient errors jsou už bufferované v PublishLayer
56
64
  // Recovery worker jen loguje a může triggerovat další akce
57
- this._logger?.warn?.(`[RecoveryWorker] Transient error handled (buffered): ${error.message}`, context);
65
+ this._logger.warn(`[RecoveryWorker] Transient error handled (buffered): ${error.message}`, context);
58
66
  }
59
67
 
60
68
  /**
61
- * Handle QueueNotFoundError
69
+ * Report a queue that does not exist, and rethrow.
70
+ *
71
+ * Every class of name ends the same way — the worker creates nothing — and the
72
+ * three branches differ only in which rule the reader has to act on: who owns
73
+ * the queue, and how it is meant to come into being.
74
+ *
62
75
  * @param {QueueNotFoundError} error
63
76
  * @param {Object} context - { queue, buffer, options }
64
77
  */
@@ -69,7 +82,7 @@ class RecoveryWorker {
69
82
 
70
83
  // Infrastructure queues must exist - cannot create
71
84
  if (error.isInfrastructure) {
72
- this._logger?.error?.(`[RecoveryWorker] Cannot create infrastructure queue '${error.queueName}' - must exist before publishing`);
85
+ this._logger.error(`[RecoveryWorker] Cannot create infrastructure queue '${error.queueName}' - must exist before publishing`);
73
86
  throw error;
74
87
  }
75
88
 
@@ -89,61 +102,25 @@ class RecoveryWorker {
89
102
  // never meant "may create business queues", and ConnectorMQClient.js:74 sets
90
103
  // it for every biz service, so that reading made the rule vacuous.
91
104
  if (error.kind === 'business') {
92
- this._logger?.error?.(`[RecoveryWorker] Cannot create business queue '${error.queueName}' - it is created by its owning service via setupServiceQueues() after registration`);
93
- throw error;
94
- }
95
-
96
- // Everything else - create if scope allows
97
- if (this._queueCreationEnabled) {
98
- // Check filter if provided
99
- if (this._queueCreationFilter && !this._queueCreationFilter(error.queueName)) {
100
- this._logger?.warn?.(`[RecoveryWorker] Queue creation filtered out for '${error.queueName}'`);
101
- throw error;
102
- }
103
-
104
- try {
105
- // Use queue creation callback if provided (delegates to QueueManager)
106
- // Otherwise fall back to direct queue creation (backward compatibility)
107
- if (this._queueCreationCallback) {
108
- this._logger?.info?.(`[RecoveryWorker] Delegating queue creation to callback for '${error.queueName}'`);
109
- await this._queueCreationCallback(error.queueName, context.options);
110
- this._logger?.info?.(`[RecoveryWorker] ✓ Queue '${error.queueName}' created via callback`);
111
- } else {
112
- // Fallback: direct queue creation (for backward compatibility)
113
- await this.createQueue(error.queueName, context.options);
114
- this._logger?.info?.(`[RecoveryWorker] ✓ Created queue '${error.queueName}' (direct)`);
115
- }
116
- } catch (createErr) {
117
- this._logger?.error?.(`[RecoveryWorker] Failed to create queue '${error.queueName}': ${createErr.message}`);
118
- throw error;
119
- }
120
- } else {
121
- this._logger?.error?.(`[RecoveryWorker] Cannot create queue '${error.queueName}' - scope is '${this._scope}' (queue creation disabled)`);
105
+ this._logger.error(`[RecoveryWorker] Cannot create business queue '${error.queueName}' - it is created by its owning service via setupServiceQueues() after registration`);
122
106
  throw error;
123
107
  }
124
- }
125
108
 
126
- /**
127
- * Create queue (business services only)
128
- * @private
129
- */
130
- async createQueue(queueName, options = {}) {
131
- if (!this._queueCreationEnabled) {
132
- throw new Error(`Queue creation not enabled for scope '${this._scope}'`);
133
- }
134
-
135
- // Use queue channel to create queue
136
- await this._client._ensureQueueChannel();
137
-
138
- // Guard: ensure queue channel is available after ensure
139
- if (!this._client._queueChannel || this._client._queueChannel.closed) {
140
- throw new Error(`Cannot create queue ${queueName}: queue channel is not available (connection may be closed)`);
141
- }
142
-
143
- const queueOptions = options.queueOptions || { durable: this._client._config.durable };
144
- await this._client._queueChannel.assertQueue(queueName, queueOptions);
109
+ // Everything else — a name `queueConfig` classifies as neither. Until d.419
110
+ // this was the one branch that DID create, and it is the same "one door
111
+ // refuses while the other opens" the business branch above records, one class
112
+ // of name further out: the publish path refused the name and this handler,
113
+ // answering the very error that refusal raised, declared it anyway. There is
114
+ // no owner to start and no template to call, so the report names the only
115
+ // thing the reader can act on the missing declaration.
116
+ this._logger.error(
117
+ `[RecoveryWorker] Cannot create queue '${error.queueName}' - no section of queueConfig declares it, `
118
+ + 'and a queue is only ever brought into being from a declaration. Fix: add a template for this '
119
+ + 'name to queueConfig and let its owner declare it (confirmation mq-consumer-contract 003), or '
120
+ + 'publish to a queue that already has one.'
121
+ );
122
+ throw error;
145
123
  }
146
124
  }
147
125
 
148
126
  module.exports = RecoveryWorker;
149
-
@@ -1,57 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Placeholder Redis buffer.
5
- *
6
- * Záměrně JE minimalistický: v tomto projektu zatím nemáme přímou Redis závislost v mq-client-core.
7
- * Třída je připravená na budoucí rozšíření – aktuálně pouze no-op / deleguje na in-memory fallback.
8
- */
9
-
10
- class RedisBuffer {
11
- /**
12
- * @param {Object} options
13
- * @param {Object} [options.redisClient] - Volitelný Redis klient (s metodami set/get/lpush/brpop atd.)
14
- * @param {Function} [options.logger] - Logger (console-like)
15
- */
16
- constructor(options = {}) {
17
- this._redis = options.redisClient || null;
18
- this._logger = options.logger || console;
19
-
20
- if (!this._redis) {
21
- this._logger?.info?.(
22
- '[RedisBuffer] No redisClient provided - RedisBuffer is effectively disabled (will not persist messages)'
23
- );
24
- }
25
- }
26
-
27
- /**
28
- * Přidá zprávu do persistentního bufferu.
29
- * Aktuální implementace je no-op, pokud není k dispozici redisClient.
30
- */
31
- async add(/* queue, buffer, options, priority */) {
32
- if (!this._redis) {
33
- // No-op; fallback na InMemoryBuffer zajišťuje MessageBuffer
34
- return;
35
- }
36
- // Budoucí rozšíření: implementace zápisu do Redis seznamu/streamu.
37
- }
38
-
39
- /**
40
- * Flushne všechny zprávy z Redis bufferu přes poskytnutý flushFn.
41
- * Aktuální implementace je no-op, pokud není k dispozici redisClient.
42
- *
43
- * @param {Function} flushFn - async (queue, buffer, options) => void
44
- * @returns {Promise<number>} - počet flushnutých zpráv
45
- */
46
- async flush(flushFn) {
47
- if (!this._redis) {
48
- return 0;
49
- }
50
- // Budoucí rozšíření: čtení z Redis (např. list/stream) a volání flushFn.
51
- return 0;
52
- }
53
- }
54
-
55
- module.exports = RedisBuffer;
56
-
57
-