@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 CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## 🚀 Features
10
10
 
11
- * Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry_office`)
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 | `registry_office` |
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 | 'registry_office' | Registry queue name |
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
- "version": "1.1.23",
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": [
@@ -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
- this.conn = await amqp.connect(this.amqpUrl);
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
- this.channel = await this.conn.createChannel();
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
- if (queueConfig.isInfrastructureQueue(q)) {
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
- await this.channel.assertQueue(q, queueOptions);
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
- console.error(`[QueueManager] [REGISTRY] [QUEUE] Failed to assert queue ${q}:`, assertErr.message);
101
- console.error(`[QueueManager] [REGISTRY] [QUEUE] Error code: ${assertErr.code}`);
102
- throw 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}`);
103
145
  }
104
146
  }
105
147
  }
@@ -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
- await this.queueManager.channel.consume(
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
- this._handleRegistryMessage(msg);
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
- await this.queueManager.channel.consume(
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
- this._handleRegistryMessage(msg);
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
- console.log(`[RegistryClient] ${this.serviceName}: ✓ Resolving registration promise for requestId: ${payload.requestId}`);
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
- resolve({
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
- console.warn(`[RegistryClient] ${this.serviceName}: MQ connection not ready, cannot verify infrastructure queue ${queueName}`);
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
- const verificationChannel = await this.queueManager.conn.createChannel();
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 verificationChannel.checkQueue(queueName);
235
- this._verifiedInfraQueues.add(queueName);
344
+ await Promise.race([verifyPromise, timeoutPromise]);
236
345
  } catch (error) {
237
- if (error.code === 404) {
238
- throw new Error(`[RegistryClient] Infrastructure queue '${queueName}' is missing. Ensure the responsible infrastructure service has initialized it.`);
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
- await this._ensureInfrastructureQueue(this.registryQueue);
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
- this.queueManager.channel.sendToQueue(
330
- this.registryQueue,
331
- Buffer.from(JSON.stringify(msg)),
332
- { persistent: true }
333
- );
334
- console.log(`[RegistryClient] ${this.serviceName}: ✓ Registration message sent, waiting for response...`);
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 {