@onlineapps/conn-orch-registry 1.1.23 → 1.1.24
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/README.md +2 -2
- package/docs/REGISTRY_CLIENT_GUIDE.md +1 -1
- package/package.json +1 -1
- package/src/queueManager.js +58 -16
- package/src/registryClient.js +169 -31
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 🚀 Features
|
|
10
10
|
|
|
11
|
-
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `
|
|
11
|
+
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry.register`)
|
|
12
12
|
* Periodic heartbeat messages with metadata
|
|
13
13
|
* API description request/response flow
|
|
14
14
|
* Event-driven API using `EventEmitter`
|
|
@@ -64,7 +64,7 @@ Configuration can be provided via environment variables or constructor options:
|
|
|
64
64
|
| `SERVICE_VERSION` | Service version in SemVer format (required) | — |
|
|
65
65
|
| `HEARTBEAT_INTERVAL` | Interval in ms between heartbeats | `10000` |
|
|
66
66
|
| `API_QUEUE` | Queue name for heartbeat/API messages | `api_services_queuer` |
|
|
67
|
-
| `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `
|
|
67
|
+
| `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `registry.register` |
|
|
68
68
|
|
|
69
69
|
## 📨 Message Formats
|
|
70
70
|
|
|
@@ -124,7 +124,7 @@ async function shutdown() {
|
|
|
124
124
|
| `specificationEndpoint` | string | '/api/v1/specification' | Endpoint for API spec |
|
|
125
125
|
| `heartbeatInterval` | number | 10000 | Heartbeat interval in ms |
|
|
126
126
|
| `apiQueue` | string | 'api_services_queuer' | Queue for API traffic |
|
|
127
|
-
| `registryQueue` | string | '
|
|
127
|
+
| `registryQueue` | string | 'registry.register' | Registry queue name |
|
|
128
128
|
|
|
129
129
|
### Methods
|
|
130
130
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-orch-registry",
|
|
3
|
-
|
|
3
|
+
"version": "1.1.24",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Connector-registry-client provides the core communication mechanism for microservices in this environment. It enables them to interact with a services_registry to receive and fulfill tasks by submitting heartbeats or their API descriptions.",
|
|
6
6
|
"keywords": [
|
package/src/queueManager.js
CHANGED
|
@@ -41,12 +41,44 @@ class QueueManager {
|
|
|
41
41
|
/**
|
|
42
42
|
* Initializes the connection and channel to RabbitMQ.
|
|
43
43
|
* @returns {Promise<void>}
|
|
44
|
+
* @throws {Error} If connection or channel creation fails or times out
|
|
44
45
|
*/
|
|
45
46
|
async init() {
|
|
46
|
-
|
|
47
|
+
const CONNECT_TIMEOUT = 10000; // 10 seconds
|
|
48
|
+
const connectPromise = amqp.connect(this.amqpUrl);
|
|
49
|
+
const connectTimeoutPromise = new Promise((_, reject) => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
reject(new Error(`RabbitMQ connection timeout after ${CONNECT_TIMEOUT}ms. RabbitMQ may be unavailable at ${this.amqpUrl}`));
|
|
52
|
+
}, CONNECT_TIMEOUT);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
this.conn = await Promise.race([connectPromise, connectTimeoutPromise]);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`[QueueManager] Failed to connect to RabbitMQ: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
// Use regular channel instead of ConfirmChannel to avoid RPC reply queue issues
|
|
48
62
|
// ConfirmChannel uses RPC pattern which requires reply queues that may not exist
|
|
49
|
-
|
|
63
|
+
const CHANNEL_TIMEOUT = 5000; // 5 seconds
|
|
64
|
+
const channelPromise = this.conn.createChannel();
|
|
65
|
+
const channelTimeoutPromise = new Promise((_, reject) => {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
reject(new Error(`Channel creation timeout after ${CHANNEL_TIMEOUT}ms`));
|
|
68
|
+
}, CHANNEL_TIMEOUT);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
this.channel = await Promise.race([channelPromise, channelTimeoutPromise]);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Clean up connection on channel creation failure
|
|
75
|
+
try {
|
|
76
|
+
await this.conn.close();
|
|
77
|
+
} catch (closeErr) {
|
|
78
|
+
// Ignore close errors
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`[QueueManager] Failed to create channel: ${error.message}`);
|
|
81
|
+
}
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
/**
|
|
@@ -70,19 +102,20 @@ class QueueManager {
|
|
|
70
102
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
71
103
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
72
104
|
let queueOptions = { durable: true };
|
|
105
|
+
const isInfrastructureQueue = queueConfig?.isInfrastructureQueue
|
|
106
|
+
? queueConfig.isInfrastructureQueue(q)
|
|
107
|
+
: ['workflow.', 'registry.', 'infrastructure.', 'validation.', 'monitoring.', 'delivery.'].some(prefix => q.startsWith(prefix));
|
|
108
|
+
|
|
109
|
+
if (isInfrastructureQueue) {
|
|
110
|
+
const message = `[QueueManager] [REGISTRY] Refusing to assert infrastructure queue '${q}'. ` +
|
|
111
|
+
'These queues are owned by infrastructure services and must be created before business services start.';
|
|
112
|
+
console.error(message);
|
|
113
|
+
throw new Error(message);
|
|
114
|
+
}
|
|
73
115
|
|
|
74
116
|
if (queueConfig) {
|
|
75
117
|
try {
|
|
76
|
-
|
|
77
|
-
const infraConfig = queueConfig.getInfrastructureQueueConfig(q);
|
|
78
|
-
queueOptions = {
|
|
79
|
-
durable: infraConfig.durable !== false,
|
|
80
|
-
arguments: { ...infraConfig.arguments }
|
|
81
|
-
};
|
|
82
|
-
console.log(`[QueueManager] [REGISTRY] [QUEUE] Using infrastructure queue config for ${q}:`, JSON.stringify(queueOptions));
|
|
83
|
-
} else {
|
|
84
|
-
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
85
|
-
}
|
|
118
|
+
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
86
119
|
} catch (configErr) {
|
|
87
120
|
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Failed to get config for ${q}, using defaults:`, configErr.message);
|
|
88
121
|
}
|
|
@@ -93,13 +126,22 @@ class QueueManager {
|
|
|
93
126
|
// Directly assert queue with canonical configuration (idempotent)
|
|
94
127
|
try {
|
|
95
128
|
const assertStartTime = Date.now();
|
|
96
|
-
|
|
129
|
+
const ASSERT_TIMEOUT = 5000; // 5 seconds
|
|
130
|
+
const assertPromise = this.channel.assertQueue(q, queueOptions);
|
|
131
|
+
const assertTimeoutPromise = new Promise((_, reject) => {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
reject(new Error(`assertQueue timeout after ${ASSERT_TIMEOUT}ms`));
|
|
134
|
+
}, ASSERT_TIMEOUT);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await Promise.race([assertPromise, assertTimeoutPromise]);
|
|
97
138
|
const assertEndTime = Date.now();
|
|
98
139
|
console.log(`[QueueManager] [REGISTRY] [QUEUE] ✓ Queue ${q} asserted (took ${assertEndTime - assertStartTime}ms)`);
|
|
99
140
|
} catch (assertErr) {
|
|
100
|
-
|
|
101
|
-
console.error(`[QueueManager] [REGISTRY] [QUEUE]
|
|
102
|
-
|
|
141
|
+
const errorMsg = assertErr.message || String(assertErr);
|
|
142
|
+
console.error(`[QueueManager] [REGISTRY] [QUEUE] ✗ Failed to assert queue ${q}: ${errorMsg}`);
|
|
143
|
+
console.error(`[QueueManager] [REGISTRY] [QUEUE] Error code: ${assertErr.code || 'N/A'}`);
|
|
144
|
+
throw new Error(`[QueueManager] Failed to assert queue ${q}: ${errorMsg}`);
|
|
103
145
|
}
|
|
104
146
|
}
|
|
105
147
|
}
|
package/src/registryClient.js
CHANGED
|
@@ -92,7 +92,8 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
92
92
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
93
93
|
|
|
94
94
|
// Start consuming service response queue for registry responses
|
|
95
|
-
|
|
95
|
+
const CONSUME_TIMEOUT = 5000; // 5 seconds
|
|
96
|
+
const consumeResponsePromise = this.queueManager.channel.consume(
|
|
96
97
|
this.serviceResponseQueue,
|
|
97
98
|
msg => {
|
|
98
99
|
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
@@ -100,17 +101,38 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
100
101
|
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceResponseQueue} (queue may be deleted or connection closed)`);
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
try {
|
|
105
|
+
this._handleRegistryMessage(msg);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`[RegistryClient] ${this.serviceName}: Error in consume callback:`, error);
|
|
108
|
+
// Nack message on error
|
|
109
|
+
try {
|
|
110
|
+
this.queueManager.channel.nack(msg, false, false);
|
|
111
|
+
} catch (nackErr) {
|
|
112
|
+
console.error(`[RegistryClient] ${this.serviceName}: Failed to nack message:`, nackErr);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
104
115
|
},
|
|
105
116
|
{ noAck: false }
|
|
106
117
|
);
|
|
118
|
+
const consumeResponseTimeoutPromise = new Promise((_, reject) => {
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
reject(new Error(`consume() timeout for ${this.serviceResponseQueue} after ${CONSUME_TIMEOUT}ms`));
|
|
121
|
+
}, CONSUME_TIMEOUT);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await Promise.race([consumeResponsePromise, consumeResponseTimeoutPromise]);
|
|
126
|
+
} catch (consumeErr) {
|
|
127
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Failed to start consumer on ${this.serviceResponseQueue}: ${consumeErr.message}`);
|
|
128
|
+
}
|
|
107
129
|
|
|
108
130
|
// CRITICAL: Also listen on registry event queue for certificate delivery
|
|
109
131
|
console.log(`[RegistryClient] [CONSUMER] About to consume from ${this.serviceRegistryQueue}`);
|
|
110
132
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters`);
|
|
111
133
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
112
134
|
|
|
113
|
-
|
|
135
|
+
const consumeRegistryPromise = this.queueManager.channel.consume(
|
|
114
136
|
this.serviceRegistryQueue,
|
|
115
137
|
msg => {
|
|
116
138
|
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
@@ -118,10 +140,31 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
118
140
|
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceRegistryQueue} (queue may be deleted or connection closed)`);
|
|
119
141
|
return;
|
|
120
142
|
}
|
|
121
|
-
|
|
143
|
+
try {
|
|
144
|
+
this._handleRegistryMessage(msg);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error(`[RegistryClient] ${this.serviceName}: Error in consume callback:`, error);
|
|
147
|
+
// Nack message on error
|
|
148
|
+
try {
|
|
149
|
+
this.queueManager.channel.nack(msg, false, false);
|
|
150
|
+
} catch (nackErr) {
|
|
151
|
+
console.error(`[RegistryClient] ${this.serviceName}: Failed to nack message:`, nackErr);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
122
154
|
},
|
|
123
155
|
{ noAck: false }
|
|
124
156
|
);
|
|
157
|
+
const consumeRegistryTimeoutPromise = new Promise((_, reject) => {
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
reject(new Error(`consume() timeout for ${this.serviceRegistryQueue} after ${CONSUME_TIMEOUT}ms`));
|
|
160
|
+
}, CONSUME_TIMEOUT);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await Promise.race([consumeRegistryPromise, consumeRegistryTimeoutPromise]);
|
|
165
|
+
} catch (consumeErr) {
|
|
166
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Failed to start consumer on ${this.serviceRegistryQueue}: ${consumeErr.message}`);
|
|
167
|
+
}
|
|
125
168
|
}
|
|
126
169
|
|
|
127
170
|
/**
|
|
@@ -138,10 +181,16 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
138
181
|
return; // Don't try to parse or ack null message
|
|
139
182
|
}
|
|
140
183
|
|
|
184
|
+
// Log which queue this message came from (if available in msg properties)
|
|
185
|
+
const queueName = msg.fields?.routingKey || msg.fields?.exchange || 'unknown';
|
|
186
|
+
console.log(`[RegistryClient] ${this.serviceName}: [MESSAGE] Processing message from queue: ${queueName}`);
|
|
187
|
+
|
|
141
188
|
let payload;
|
|
142
189
|
try {
|
|
143
190
|
payload = JSON.parse(msg.content.toString());
|
|
191
|
+
console.log(`[RegistryClient] ${this.serviceName}: [MESSAGE] Parsed payload type: ${payload.type}, requestId: ${payload.requestId}`);
|
|
144
192
|
} catch (err) {
|
|
193
|
+
console.error(`[RegistryClient] ${this.serviceName}: [MESSAGE] Failed to parse message:`, err);
|
|
145
194
|
this.emit('error', err);
|
|
146
195
|
// Only nack if message is not null
|
|
147
196
|
if (msg) {
|
|
@@ -165,22 +214,34 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
165
214
|
// Handle registration response from registry
|
|
166
215
|
// Registry sends 'register.confirmed' after validation
|
|
167
216
|
if ((payload.type === 'registerResponse' || payload.type === 'register.confirmed') && payload.requestId) {
|
|
168
|
-
console.log(`[RegistryClient] ${this.serviceName}: Received ${payload.type} message`, {
|
|
217
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Received ${payload.type} message`, {
|
|
169
218
|
requestId: payload.requestId,
|
|
170
219
|
hasPendingRegistrations: !!(this.pendingRegistrations),
|
|
171
220
|
pendingCount: this.pendingRegistrations ? this.pendingRegistrations.size : 0,
|
|
172
221
|
hasMatchingRequest: !!(this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)),
|
|
173
|
-
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : []
|
|
222
|
+
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : [],
|
|
223
|
+
queueName
|
|
174
224
|
});
|
|
175
225
|
|
|
176
226
|
if (this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)) {
|
|
227
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Found matching pending registration, extracting resolve function...`);
|
|
177
228
|
const { resolve } = this.pendingRegistrations.get(payload.requestId);
|
|
178
229
|
this.pendingRegistrations.delete(payload.requestId);
|
|
179
|
-
|
|
180
|
-
|
|
230
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] ✓ Resolve function extracted, pending registration removed`);
|
|
231
|
+
|
|
232
|
+
// CRITICAL: Resolve promise synchronously to ensure it's called before any async operations
|
|
233
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Starting resolve process for requestId: ${payload.requestId}`);
|
|
234
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload keys:`, Object.keys(payload));
|
|
235
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.success:`, payload.success);
|
|
236
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.validated:`, payload.validated);
|
|
237
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.hasCertificate:`, !!payload.certificate);
|
|
238
|
+
if (payload.certificate) {
|
|
239
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.certificate.id:`, payload.certificate.id);
|
|
240
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.certificate.serviceName:`, payload.certificate.serviceName);
|
|
241
|
+
}
|
|
181
242
|
|
|
182
243
|
// Resolve the registration promise with the response
|
|
183
|
-
|
|
244
|
+
const registrationResult = {
|
|
184
245
|
success: payload.success || false,
|
|
185
246
|
message: payload.message || 'Registration processed',
|
|
186
247
|
serviceName: this.serviceName,
|
|
@@ -188,7 +249,31 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
188
249
|
registrationId: payload.registrationId,
|
|
189
250
|
validated: payload.validated || payload.success, // Consider successful registration as validated
|
|
190
251
|
certificate: payload.certificate || null // Include certificate from Registry
|
|
191
|
-
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Created registrationResult:`, JSON.stringify({
|
|
255
|
+
success: registrationResult.success,
|
|
256
|
+
validated: registrationResult.validated,
|
|
257
|
+
hasCertificate: !!registrationResult.certificate,
|
|
258
|
+
certificateId: registrationResult.certificate?.id || 'none',
|
|
259
|
+
serviceName: registrationResult.serviceName,
|
|
260
|
+
version: registrationResult.version
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] About to call resolve() function...`);
|
|
264
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] resolve function type:`, typeof resolve);
|
|
265
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] resolve function exists:`, !!resolve);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// CRITICAL: Call resolve synchronously - this must happen immediately
|
|
269
|
+
resolve(registrationResult);
|
|
270
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] ✓ resolve() called successfully`);
|
|
271
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Promise should now be resolved`);
|
|
272
|
+
} catch (resolveError) {
|
|
273
|
+
console.error(`[RegistryClient] ${this.serviceName}: [RESOLVE] ✗ Error calling resolve():`, resolveError);
|
|
274
|
+
console.error(`[RegistryClient] ${this.serviceName}: [RESOLVE] Error stack:`, resolveError.stack);
|
|
275
|
+
throw resolveError;
|
|
276
|
+
}
|
|
192
277
|
} else {
|
|
193
278
|
console.warn(`[RegistryClient] ${this.serviceName}: ⚠️ Received ${payload.type} with requestId ${payload.requestId}, but no matching pending registration found`);
|
|
194
279
|
}
|
|
@@ -225,25 +310,42 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
225
310
|
}
|
|
226
311
|
|
|
227
312
|
if (!this.queueManager?.conn) {
|
|
228
|
-
|
|
229
|
-
return;
|
|
313
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: MQ connection not ready, cannot verify infrastructure queue ${queueName}. Ensure RabbitMQ is accessible.`);
|
|
230
314
|
}
|
|
231
315
|
|
|
232
|
-
|
|
316
|
+
// Add timeout to prevent hanging
|
|
317
|
+
const QUEUE_VERIFY_TIMEOUT = 10000; // 10 seconds
|
|
318
|
+
const verifyPromise = (async () => {
|
|
319
|
+
const verificationChannel = await this.queueManager.conn.createChannel();
|
|
320
|
+
try {
|
|
321
|
+
await verificationChannel.checkQueue(queueName);
|
|
322
|
+
this._verifiedInfraQueues.add(queueName);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error.code === 404) {
|
|
325
|
+
throw new Error(`[RegistryClient] Infrastructure queue '${queueName}' is missing. Ensure the responsible infrastructure service has initialized it.`);
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
} finally {
|
|
329
|
+
try {
|
|
330
|
+
await verificationChannel.close();
|
|
331
|
+
} catch (closeErr) {
|
|
332
|
+
console.warn(`[RegistryClient] ${this.serviceName}: Failed to close verification channel for ${queueName}:`, closeErr.message);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
|
|
337
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
338
|
+
setTimeout(() => {
|
|
339
|
+
reject(new Error(`[RegistryClient] ${this.serviceName}: Timeout verifying infrastructure queue '${queueName}' after ${QUEUE_VERIFY_TIMEOUT}ms. Queue may not exist or RabbitMQ is unresponsive.`));
|
|
340
|
+
}, QUEUE_VERIFY_TIMEOUT);
|
|
341
|
+
});
|
|
342
|
+
|
|
233
343
|
try {
|
|
234
|
-
await
|
|
235
|
-
this._verifiedInfraQueues.add(queueName);
|
|
344
|
+
await Promise.race([verifyPromise, timeoutPromise]);
|
|
236
345
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
346
|
+
// Clear verification cache on error so we can retry
|
|
347
|
+
this._verifiedInfraQueues.delete(queueName);
|
|
240
348
|
throw error;
|
|
241
|
-
} finally {
|
|
242
|
-
try {
|
|
243
|
-
await verificationChannel.close();
|
|
244
|
-
} catch (closeErr) {
|
|
245
|
-
console.warn(`[RegistryClient] ${this.serviceName}: Failed to close verification channel for ${queueName}:`, closeErr.message);
|
|
246
|
-
}
|
|
247
349
|
}
|
|
248
350
|
}
|
|
249
351
|
|
|
@@ -323,21 +425,57 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
323
425
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
324
426
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
325
427
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue}`);
|
|
326
|
-
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
await this._ensureInfrastructureQueue(this.registryQueue);
|
|
431
|
+
} catch (queueError) {
|
|
432
|
+
const errorMsg = `[RegistryClient] ${this.serviceName}: Cannot register - infrastructure queue verification failed: ${queueError.message}`;
|
|
433
|
+
console.error(errorMsg);
|
|
434
|
+
throw new Error(errorMsg);
|
|
435
|
+
}
|
|
436
|
+
|
|
327
437
|
console.log(`[RegistryClient] ${this.serviceName}: Sending registration message to queue: ${this.registryQueue}`);
|
|
328
438
|
console.log(`[RegistryClient] ${this.serviceName}: Message type: ${msg.type}, serviceName: ${msg.serviceName}, version: ${msg.version}`);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
439
|
+
|
|
440
|
+
// Send message synchronously (sendToQueue is synchronous, returns boolean)
|
|
441
|
+
// But check channel state first to fail fast
|
|
442
|
+
if (!this.queueManager.channel) {
|
|
443
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Cannot publish - channel not initialized. Ensure MQ connection is established.`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (this.queueManager.channel.closed) {
|
|
447
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Cannot publish - channel is closed. MQ connection may be lost.`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const sent = this.queueManager.channel.sendToQueue(
|
|
452
|
+
this.registryQueue,
|
|
453
|
+
Buffer.from(JSON.stringify(msg)),
|
|
454
|
+
{ persistent: true }
|
|
455
|
+
);
|
|
456
|
+
if (!sent) {
|
|
457
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: sendToQueue returned false - queue may be full or channel backpressure active.`);
|
|
458
|
+
}
|
|
459
|
+
console.log(`[RegistryClient] ${this.serviceName}: ✓ Registration message sent, waiting for response...`);
|
|
460
|
+
} catch (sendError) {
|
|
461
|
+
const errorMsg = `[RegistryClient] ${this.serviceName}: Failed to send registration message: ${sendError.message}`;
|
|
462
|
+
console.error(errorMsg);
|
|
463
|
+
throw new Error(errorMsg);
|
|
464
|
+
}
|
|
335
465
|
|
|
336
466
|
this.emit('registerSent', msg);
|
|
337
467
|
|
|
338
468
|
// Wait for registration response from registry
|
|
339
469
|
try {
|
|
470
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] Waiting for registration response promise...`);
|
|
340
471
|
const response = await responsePromise;
|
|
472
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] ✓ Promise resolved, response received:`, {
|
|
473
|
+
hasResponse: !!response,
|
|
474
|
+
responseType: typeof response,
|
|
475
|
+
responseKeys: response ? Object.keys(response) : [],
|
|
476
|
+
hasSuccess: !!response?.success,
|
|
477
|
+
hasCertificate: !!response?.certificate
|
|
478
|
+
});
|
|
341
479
|
return response;
|
|
342
480
|
} catch (error) {
|
|
343
481
|
return {
|