@onlineapps/conn-orch-registry 1.1.22 → 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 +2 -1
- package/src/queueManager.js +65 -52
- package/src/registryClient.js +200 -127
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": [
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@onlineapps/conn-base-storage": "^1.0.0",
|
|
42
|
+
"@onlineapps/mq-client-core": "^1.0.26",
|
|
42
43
|
"amqplib": "^0.10.9",
|
|
43
44
|
"axios": "^1.12.2",
|
|
44
45
|
"dotenv": "^16.6.1",
|
package/src/queueManager.js
CHANGED
|
@@ -19,15 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const amqp = require('amqplib');
|
|
22
|
-
|
|
23
|
-
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
24
|
-
let queueConfig;
|
|
25
|
-
try {
|
|
26
|
-
queueConfig = require('@onlineapps/conn-infra-mq/src/config/queueConfig');
|
|
27
|
-
} catch (err) {
|
|
28
|
-
console.warn('[QueueManager] [REGISTRY] queueConfig not available, using defaults:', err.message);
|
|
29
|
-
queueConfig = null;
|
|
30
|
-
}
|
|
22
|
+
const queueConfig = require('@onlineapps/mq-client-core/src/config/queueConfig');
|
|
31
23
|
|
|
32
24
|
/**
|
|
33
25
|
* Queue manager for the microservice connector.
|
|
@@ -49,12 +41,44 @@ class QueueManager {
|
|
|
49
41
|
/**
|
|
50
42
|
* Initializes the connection and channel to RabbitMQ.
|
|
51
43
|
* @returns {Promise<void>}
|
|
44
|
+
* @throws {Error} If connection or channel creation fails or times out
|
|
52
45
|
*/
|
|
53
46
|
async init() {
|
|
54
|
-
|
|
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
|
+
|
|
55
61
|
// Use regular channel instead of ConfirmChannel to avoid RPC reply queue issues
|
|
56
62
|
// ConfirmChannel uses RPC pattern which requires reply queues that may not exist
|
|
57
|
-
|
|
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
|
+
}
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
/**
|
|
@@ -68,10 +92,7 @@ class QueueManager {
|
|
|
68
92
|
}
|
|
69
93
|
|
|
70
94
|
// Default queues for the registry client
|
|
71
|
-
const baseQueues = [
|
|
72
|
-
'workflow'
|
|
73
|
-
// Note: ${serviceName}.registry queue is created by RegistryEventConsumer when needed
|
|
74
|
-
];
|
|
95
|
+
const baseQueues = [];
|
|
75
96
|
|
|
76
97
|
const queuesToCreate = baseQueues.concat(additionalQueues);
|
|
77
98
|
|
|
@@ -81,19 +102,20 @@ class QueueManager {
|
|
|
81
102
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
82
103
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
83
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
|
+
}
|
|
84
115
|
|
|
85
116
|
if (queueConfig) {
|
|
86
117
|
try {
|
|
87
|
-
|
|
88
|
-
const infraConfig = queueConfig.getInfrastructureQueueConfig(q);
|
|
89
|
-
queueOptions = {
|
|
90
|
-
durable: infraConfig.durable !== false,
|
|
91
|
-
arguments: { ...infraConfig.arguments }
|
|
92
|
-
};
|
|
93
|
-
console.log(`[QueueManager] [REGISTRY] [QUEUE] Using infrastructure queue config for ${q}:`, JSON.stringify(queueOptions));
|
|
94
|
-
} else {
|
|
95
|
-
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
96
|
-
}
|
|
118
|
+
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
97
119
|
} catch (configErr) {
|
|
98
120
|
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Failed to get config for ${q}, using defaults:`, configErr.message);
|
|
99
121
|
}
|
|
@@ -101,34 +123,25 @@ class QueueManager {
|
|
|
101
123
|
console.warn(`[QueueManager] [REGISTRY] [QUEUE] queueConfig not available, using default options for ${q}`);
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
//
|
|
105
|
-
// If queue doesn't exist (404), then assertQueue to create it with correct options
|
|
126
|
+
// Directly assert queue with canonical configuration (idempotent)
|
|
106
127
|
try {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
throw assertErr;
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
// Other error (including 406) - queue exists with different args
|
|
128
|
-
// Log warning and continue without asserting
|
|
129
|
-
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} exists with different arguments, using as-is:`, checkErr.message);
|
|
130
|
-
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Error code: ${checkErr.code}`);
|
|
131
|
-
}
|
|
128
|
+
const assertStartTime = Date.now();
|
|
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]);
|
|
138
|
+
const assertEndTime = Date.now();
|
|
139
|
+
console.log(`[QueueManager] [REGISTRY] [QUEUE] ✓ Queue ${q} asserted (took ${assertEndTime - assertStartTime}ms)`);
|
|
140
|
+
} catch (assertErr) {
|
|
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}`);
|
|
132
145
|
}
|
|
133
146
|
}
|
|
134
147
|
}
|
package/src/registryClient.js
CHANGED
|
@@ -19,46 +19,8 @@ const EventEmitter = require('events');
|
|
|
19
19
|
const QueueManager = require('./queueManager');
|
|
20
20
|
const RegistryEventConsumer = require('./registryEventConsumer');
|
|
21
21
|
const { v4: uuidv4 } = require('uuid');
|
|
22
|
+
const queueConfig = require('@onlineapps/mq-client-core/src/config/queueConfig');
|
|
22
23
|
|
|
23
|
-
// CRITICAL: Import queueConfig to ensure consistent queue parameters
|
|
24
|
-
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
25
|
-
let queueConfig;
|
|
26
|
-
try {
|
|
27
|
-
queueConfig = require('@onlineapps/conn-infra-mq/src/config/queueConfig');
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.warn('[RegistryClient] queueConfig not available, using defaults:', err.message);
|
|
30
|
-
queueConfig = null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Helper function to get queue options from queueConfig.js
|
|
35
|
-
* @param {string} queueName - Queue name
|
|
36
|
-
* @returns {Object} Queue options with correct parameters (TTL, max-length, etc.)
|
|
37
|
-
*/
|
|
38
|
-
function getQueueOptions(queueName) {
|
|
39
|
-
let queueOptions = { durable: true };
|
|
40
|
-
|
|
41
|
-
if (queueConfig) {
|
|
42
|
-
try {
|
|
43
|
-
if (queueConfig.isInfrastructureQueue(queueName)) {
|
|
44
|
-
const infraConfig = queueConfig.getInfrastructureQueueConfig(queueName);
|
|
45
|
-
queueOptions = {
|
|
46
|
-
durable: infraConfig.durable !== false,
|
|
47
|
-
arguments: { ...infraConfig.arguments }
|
|
48
|
-
};
|
|
49
|
-
console.log(`[RegistryClient] [QUEUE] Using infrastructure queue config for ${queueName}:`, JSON.stringify(queueOptions));
|
|
50
|
-
} else {
|
|
51
|
-
console.warn(`[RegistryClient] [QUEUE] Queue ${queueName} is not an infrastructure queue, using default options`);
|
|
52
|
-
}
|
|
53
|
-
} catch (configErr) {
|
|
54
|
-
console.warn(`[RegistryClient] [QUEUE] Failed to get config for ${queueName}, using defaults:`, configErr.message);
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
console.warn(`[RegistryClient] [QUEUE] queueConfig not available, using default options for ${queueName}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return queueOptions;
|
|
61
|
-
}
|
|
62
24
|
|
|
63
25
|
class ServiceRegistryClient extends EventEmitter {
|
|
64
26
|
/**
|
|
@@ -95,6 +57,7 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
95
57
|
|
|
96
58
|
// Validation proof (injected via constructor - DEPENDENCY INJECTION)
|
|
97
59
|
this.validationProof = validationProof;
|
|
60
|
+
this._verifiedInfraQueues = new Set();
|
|
98
61
|
}
|
|
99
62
|
|
|
100
63
|
/**
|
|
@@ -118,8 +81,8 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
118
81
|
// Create service-specific registry event queue (for certificate delivery)
|
|
119
82
|
this.serviceRegistryQueue = `${this.serviceName}.registry`;
|
|
120
83
|
|
|
121
|
-
// Ensure existence of
|
|
122
|
-
await this.queueManager.ensureQueues([this.
|
|
84
|
+
// Ensure existence of service-specific queues only (infrastructure queues must already exist)
|
|
85
|
+
await this.queueManager.ensureQueues([this.serviceResponseQueue, this.serviceRegistryQueue]);
|
|
123
86
|
|
|
124
87
|
// CRITICAL: Before consume(), we must assertQueue with correct parameters
|
|
125
88
|
// amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters
|
|
@@ -129,7 +92,8 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
129
92
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
130
93
|
|
|
131
94
|
// Start consuming service response queue for registry responses
|
|
132
|
-
|
|
95
|
+
const CONSUME_TIMEOUT = 5000; // 5 seconds
|
|
96
|
+
const consumeResponsePromise = this.queueManager.channel.consume(
|
|
133
97
|
this.serviceResponseQueue,
|
|
134
98
|
msg => {
|
|
135
99
|
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
@@ -137,17 +101,38 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
137
101
|
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceResponseQueue} (queue may be deleted or connection closed)`);
|
|
138
102
|
return;
|
|
139
103
|
}
|
|
140
|
-
|
|
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
|
+
}
|
|
141
115
|
},
|
|
142
116
|
{ noAck: false }
|
|
143
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
|
+
}
|
|
144
129
|
|
|
145
130
|
// CRITICAL: Also listen on registry event queue for certificate delivery
|
|
146
131
|
console.log(`[RegistryClient] [CONSUMER] About to consume from ${this.serviceRegistryQueue}`);
|
|
147
132
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters`);
|
|
148
133
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
149
134
|
|
|
150
|
-
|
|
135
|
+
const consumeRegistryPromise = this.queueManager.channel.consume(
|
|
151
136
|
this.serviceRegistryQueue,
|
|
152
137
|
msg => {
|
|
153
138
|
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
@@ -155,10 +140,31 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
155
140
|
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceRegistryQueue} (queue may be deleted or connection closed)`);
|
|
156
141
|
return;
|
|
157
142
|
}
|
|
158
|
-
|
|
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
|
+
}
|
|
159
154
|
},
|
|
160
155
|
{ noAck: false }
|
|
161
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
|
+
}
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
/**
|
|
@@ -175,10 +181,16 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
175
181
|
return; // Don't try to parse or ack null message
|
|
176
182
|
}
|
|
177
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
|
+
|
|
178
188
|
let payload;
|
|
179
189
|
try {
|
|
180
190
|
payload = JSON.parse(msg.content.toString());
|
|
191
|
+
console.log(`[RegistryClient] ${this.serviceName}: [MESSAGE] Parsed payload type: ${payload.type}, requestId: ${payload.requestId}`);
|
|
181
192
|
} catch (err) {
|
|
193
|
+
console.error(`[RegistryClient] ${this.serviceName}: [MESSAGE] Failed to parse message:`, err);
|
|
182
194
|
this.emit('error', err);
|
|
183
195
|
// Only nack if message is not null
|
|
184
196
|
if (msg) {
|
|
@@ -202,22 +214,34 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
202
214
|
// Handle registration response from registry
|
|
203
215
|
// Registry sends 'register.confirmed' after validation
|
|
204
216
|
if ((payload.type === 'registerResponse' || payload.type === 'register.confirmed') && payload.requestId) {
|
|
205
|
-
console.log(`[RegistryClient] ${this.serviceName}: Received ${payload.type} message`, {
|
|
217
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Received ${payload.type} message`, {
|
|
206
218
|
requestId: payload.requestId,
|
|
207
219
|
hasPendingRegistrations: !!(this.pendingRegistrations),
|
|
208
220
|
pendingCount: this.pendingRegistrations ? this.pendingRegistrations.size : 0,
|
|
209
221
|
hasMatchingRequest: !!(this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)),
|
|
210
|
-
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : []
|
|
222
|
+
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : [],
|
|
223
|
+
queueName
|
|
211
224
|
});
|
|
212
225
|
|
|
213
226
|
if (this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)) {
|
|
227
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Found matching pending registration, extracting resolve function...`);
|
|
214
228
|
const { resolve } = this.pendingRegistrations.get(payload.requestId);
|
|
215
229
|
this.pendingRegistrations.delete(payload.requestId);
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
}
|
|
218
242
|
|
|
219
243
|
// Resolve the registration promise with the response
|
|
220
|
-
|
|
244
|
+
const registrationResult = {
|
|
221
245
|
success: payload.success || false,
|
|
222
246
|
message: payload.message || 'Registration processed',
|
|
223
247
|
serviceName: this.serviceName,
|
|
@@ -225,7 +249,31 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
225
249
|
registrationId: payload.registrationId,
|
|
226
250
|
validated: payload.validated || payload.success, // Consider successful registration as validated
|
|
227
251
|
certificate: payload.certificate || null // Include certificate from Registry
|
|
228
|
-
}
|
|
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
|
+
}
|
|
229
277
|
} else {
|
|
230
278
|
console.warn(`[RegistryClient] ${this.serviceName}: ⚠️ Received ${payload.type} with requestId ${payload.requestId}, but no matching pending registration found`);
|
|
231
279
|
}
|
|
@@ -241,6 +289,66 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
241
289
|
}
|
|
242
290
|
}
|
|
243
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Ensure an infrastructure queue exists without mutating it.
|
|
294
|
+
* Uses a temporary channel so failures don't kill the primary channel.
|
|
295
|
+
* @param {string} queueName
|
|
296
|
+
* @returns {Promise<void>}
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
async _ensureInfrastructureQueue(queueName) {
|
|
300
|
+
if (!queueName) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!queueConfig?.isInfrastructureQueue(queueName)) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (this._verifiedInfraQueues.has(queueName)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!this.queueManager?.conn) {
|
|
313
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: MQ connection not ready, cannot verify infrastructure queue ${queueName}. Ensure RabbitMQ is accessible.`);
|
|
314
|
+
}
|
|
315
|
+
|
|
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
|
+
|
|
343
|
+
try {
|
|
344
|
+
await Promise.race([verifyPromise, timeoutPromise]);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
// Clear verification cache on error so we can retry
|
|
347
|
+
this._verifiedInfraQueues.delete(queueName);
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
244
352
|
/**
|
|
245
353
|
* Registers the service with the registry.
|
|
246
354
|
* Registry will validate if the service is properly tested and valid.
|
|
@@ -318,49 +426,56 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
318
426
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
319
427
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue}`);
|
|
320
428
|
|
|
321
|
-
let queueOptions = { durable: true };
|
|
322
429
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
|
|
437
|
+
console.log(`[RegistryClient] ${this.serviceName}: Sending registration message to queue: ${this.registryQueue}`);
|
|
438
|
+
console.log(`[RegistryClient] ${this.serviceName}: Message type: ${msg.type}, serviceName: ${msg.serviceName}, version: ${msg.version}`);
|
|
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.`);
|
|
334
444
|
}
|
|
335
445
|
|
|
336
|
-
|
|
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
|
+
|
|
337
450
|
try {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} else {
|
|
346
|
-
console.warn(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} exists with different arguments:`, checkErr.message);
|
|
347
|
-
// If 406 or other error, queue exists - proceed to publish
|
|
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.`);
|
|
348
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);
|
|
349
464
|
}
|
|
350
|
-
console.log(`[RegistryClient] ${this.serviceName}: Sending registration message to queue: ${this.registryQueue}`);
|
|
351
|
-
console.log(`[RegistryClient] ${this.serviceName}: Message type: ${msg.type}, serviceName: ${msg.serviceName}, version: ${msg.version}`);
|
|
352
|
-
this.queueManager.channel.sendToQueue(
|
|
353
|
-
this.registryQueue,
|
|
354
|
-
Buffer.from(JSON.stringify(msg)),
|
|
355
|
-
{ persistent: true }
|
|
356
|
-
);
|
|
357
|
-
console.log(`[RegistryClient] ${this.serviceName}: ✓ Registration message sent, waiting for response...`);
|
|
358
465
|
|
|
359
466
|
this.emit('registerSent', msg);
|
|
360
467
|
|
|
361
468
|
// Wait for registration response from registry
|
|
362
469
|
try {
|
|
470
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] Waiting for registration response promise...`);
|
|
363
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
|
+
});
|
|
364
479
|
return response;
|
|
365
480
|
} catch (error) {
|
|
366
481
|
return {
|
|
@@ -388,21 +503,7 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
388
503
|
// Send deregistration message to registry
|
|
389
504
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
390
505
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue} (deregister)`);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Use checkQueue to avoid 406 PRECONDITION-FAILED closing the channel
|
|
394
|
-
try {
|
|
395
|
-
await this.queueManager.channel.checkQueue(this.registryQueue);
|
|
396
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Queue ${this.registryQueue} exists`);
|
|
397
|
-
} catch (checkErr) {
|
|
398
|
-
if (checkErr.code === 404) {
|
|
399
|
-
console.log(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} does not exist (404), creating with options:`, JSON.stringify(queueOptions));
|
|
400
|
-
await this.queueManager.channel.assertQueue(this.registryQueue, queueOptions);
|
|
401
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Created queue ${this.registryQueue}`);
|
|
402
|
-
} else {
|
|
403
|
-
console.warn(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} exists with different arguments:`, checkErr.message);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
506
|
+
await this._ensureInfrastructureQueue(this.registryQueue);
|
|
406
507
|
this.queueManager.channel.sendToQueue(
|
|
407
508
|
this.registryQueue,
|
|
408
509
|
Buffer.from(JSON.stringify(msg)),
|
|
@@ -447,21 +548,7 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
447
548
|
};
|
|
448
549
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
449
550
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue} (heartbeat)`);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Use checkQueue to avoid 406 PRECONDITION-FAILED closing the channel
|
|
453
|
-
try {
|
|
454
|
-
await this.queueManager.channel.checkQueue(this.registryQueue);
|
|
455
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Queue ${this.registryQueue} exists`);
|
|
456
|
-
} catch (checkErr) {
|
|
457
|
-
if (checkErr.code === 404) {
|
|
458
|
-
console.log(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} does not exist (404), creating with options:`, JSON.stringify(queueOptions));
|
|
459
|
-
await this.queueManager.channel.assertQueue(this.registryQueue, queueOptions);
|
|
460
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Created queue ${this.registryQueue}`);
|
|
461
|
-
} else {
|
|
462
|
-
console.warn(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} exists with different arguments:`, checkErr.message);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
551
|
+
await this._ensureInfrastructureQueue(this.registryQueue);
|
|
465
552
|
this.queueManager.channel.sendToQueue(
|
|
466
553
|
this.registryQueue,
|
|
467
554
|
Buffer.from(JSON.stringify(msg)),
|
|
@@ -507,21 +594,7 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
507
594
|
};
|
|
508
595
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
509
596
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue} (apiDescription)`);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
// Use checkQueue to avoid 406 PRECONDITION-FAILED closing the channel
|
|
513
|
-
try {
|
|
514
|
-
await this.queueManager.channel.checkQueue(this.registryQueue);
|
|
515
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Queue ${this.registryQueue} exists`);
|
|
516
|
-
} catch (checkErr) {
|
|
517
|
-
if (checkErr.code === 404) {
|
|
518
|
-
console.log(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} does not exist (404), creating with options:`, JSON.stringify(queueOptions));
|
|
519
|
-
await this.queueManager.channel.assertQueue(this.registryQueue, queueOptions);
|
|
520
|
-
console.log(`[RegistryClient] [PUBLISH] ✓ Created queue ${this.registryQueue}`);
|
|
521
|
-
} else {
|
|
522
|
-
console.warn(`[RegistryClient] [PUBLISH] Queue ${this.registryQueue} exists with different arguments:`, checkErr.message);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
597
|
+
await this._ensureInfrastructureQueue(this.registryQueue);
|
|
525
598
|
this.queueManager.channel.sendToQueue(
|
|
526
599
|
this.registryQueue,
|
|
527
600
|
Buffer.from(JSON.stringify(msg)),
|