@onlineapps/infrastructure-tools 1.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.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @onlineapps/infrastructure-tools
2
+
3
+ Infrastructure orchestration utilities for OA Drive infrastructure services.
4
+
5
+ ## Purpose
6
+
7
+ This library provides utilities for infrastructure services to coordinate their initialization, health tracking, and queue management. It is **NOT** intended for business services.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @onlineapps/infrastructure-tools
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Health Publisher
18
+
19
+ Publish health checks to `infrastructure.health.checks` queue:
20
+
21
+ ```javascript
22
+ const { createBaseClientAdapter } = require('@onlineapps/infrastructure-tools');
23
+ const BaseClient = require('@onlineapps/mq-client-core');
24
+
25
+ const mqClient = new BaseClient({ ... });
26
+ await mqClient.connect();
27
+
28
+ const healthPublisher = createBaseClientAdapter(
29
+ mqClient,
30
+ 'gateway',
31
+ () => ({
32
+ mq: mqClient.isConnected() ? 'healthy' : 'unhealthy',
33
+ redis: 'healthy',
34
+ http: 'healthy'
35
+ }),
36
+ config.infrastructureHealth,
37
+ logger
38
+ );
39
+
40
+ await healthPublisher.start();
41
+ ```
42
+
43
+ ### Wait for Infrastructure Ready
44
+
45
+ Wait for all infrastructure services to be ready before creating queues:
46
+
47
+ ```javascript
48
+ const { waitForInfrastructureReady } = require('@onlineapps/infrastructure-tools');
49
+
50
+ await waitForInfrastructureReady({
51
+ redisUrl: 'redis://api_node_cache:6379',
52
+ maxWait: 300000, // 5 minutes
53
+ checkInterval: 5000, // 5 seconds
54
+ logger: logger
55
+ });
56
+ ```
57
+
58
+ ### Initialize Infrastructure Queues
59
+
60
+ Initialize infrastructure queues with correct parameters:
61
+
62
+ ```javascript
63
+ const { initInfrastructureQueues } = require('@onlineapps/infrastructure-tools');
64
+
65
+ await initInfrastructureQueues(channel, {
66
+ queues: ['workflow.init'], // Only create specific queues
67
+ connection: connection,
68
+ logger: logger
69
+ });
70
+ ```
71
+
72
+ ## API
73
+
74
+ ### `waitForInfrastructureReady(options)`
75
+
76
+ Waits for all infrastructure services to be reported as healthy by Registry.
77
+
78
+ **Options:**
79
+ - `redisUrl` (string): Redis URL (default: from ENV or `redis://api_node_cache:6379`)
80
+ - `maxWait` (number): Maximum wait time in ms (default: 300000 = 5 minutes)
81
+ - `checkInterval` (number): Check interval in ms (default: 5000 = 5 seconds)
82
+ - `logger` (Object): Logger instance (default: console)
83
+
84
+ **Returns:** `Promise<boolean>`
85
+
86
+ ### `initInfrastructureQueues(channel, options)`
87
+
88
+ Initializes infrastructure queues with correct parameters from `queueConfig`.
89
+
90
+ **Options:**
91
+ - `queues` (Array<string>): Specific queues to create (default: all infrastructure queues)
92
+ - `connection` (Object): RabbitMQ connection (for channel recreation)
93
+ - `logger` (Object): Logger instance (default: console)
94
+ - `queueConfig` (Object): Queue config instance (default: from mq-client-core)
95
+
96
+ **Returns:** `Promise<void>`
97
+
98
+ ### `createHealthPublisher(options)`
99
+
100
+ Creates a health publisher instance with custom publish function.
101
+
102
+ **Options:**
103
+ - `serviceName` (string): Service name (required)
104
+ - `version` (string): Service version (default: '1.0.0')
105
+ - `publishFunction` (Function): Function to publish message (required)
106
+ - `getHealthData` (Function): Function to get health data (required)
107
+ - `config` (Object): Configuration with `infrastructureHealth` settings
108
+ - `logger` (Object): Logger instance (default: console)
109
+
110
+ **Returns:** Health publisher instance with `start()`, `stop()`, `publishNow()` methods
111
+
112
+ ### `createBaseClientAdapter(baseClient, serviceName, getHealthData, config, logger)`
113
+
114
+ Creates health publisher adapter for BaseClient (from mq-client-core).
115
+
116
+ ### `createAmqplibAdapter(connection, channel, serviceName, getHealthData, config, logger)`
117
+
118
+ Creates health publisher adapter for amqplib (direct connection + channel).
119
+
120
+ ## Dependencies
121
+
122
+ - `@onlineapps/mq-client-core` - For queue configuration
123
+ - `redis` - For health status checking
124
+
125
+ ## Related Libraries
126
+
127
+ - `@onlineapps/mq-client-core` - Core MQ operations and queue configuration
128
+ - `@onlineapps/monitoring-core` - Business monitoring (traces, metrics, logs)
129
+
130
+ ## License
131
+
132
+ MIT
133
+
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@onlineapps/infrastructure-tools",
3
+ "version": "1.0.0",
4
+ "description": "Infrastructure orchestration utilities for OA Drive infrastructure services (health tracking, queue initialization, service discovery)",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "jest",
8
+ "test:unit": "jest --testPathPattern=tests/unit",
9
+ "test:component": "jest --testPathPattern=tests/component",
10
+ "test:integration": "jest --testPathPattern=tests/integration"
11
+ },
12
+ "keywords": [
13
+ "infrastructure",
14
+ "orchestration",
15
+ "health-tracking",
16
+ "service-discovery"
17
+ ],
18
+ "author": "OnlineApps",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@onlineapps/mq-client-core": "^1.0.24",
22
+ "redis": "^4.6.0"
23
+ },
24
+ "devDependencies": {
25
+ "jest": "^29.7.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }
31
+
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Infrastructure Health Publisher
5
+ *
6
+ * Publishes health check messages to infrastructure.health.checks queue.
7
+ * Used by infrastructure services to report their health status to Registry.
8
+ *
9
+ * Supports multiple MQ abstractions via adapter pattern:
10
+ * - BaseClient (from mq-client-core)
11
+ * - amqplib directly (connection + channel)
12
+ * - Custom publish function
13
+ */
14
+
15
+ /**
16
+ * Create infrastructure health publisher
17
+ * @param {Object} options - Configuration options
18
+ * @param {string} options.serviceName - Service name (e.g., 'gateway', 'registry', 'validator')
19
+ * @param {string} [options.version] - Service version (default: '1.0.0')
20
+ * @param {Function} options.publishFunction - Function to publish message: async (queueName, data) => void
21
+ * @param {Function} options.getHealthData - Function to get current health status: () => Object
22
+ * @param {Object} [options.config] - Configuration object with infrastructureHealth settings
23
+ * @param {string} [options.config.queueName] - Queue name (default: 'infrastructure.health.checks')
24
+ * @param {number} [options.config.publishInterval] - Publish interval in ms (default: 5000)
25
+ * @param {Object} [options.logger] - Logger instance (default: console)
26
+ * @returns {Object} Health publisher instance
27
+ */
28
+ function createHealthPublisher(options) {
29
+ const {
30
+ serviceName,
31
+ version = '1.0.0',
32
+ publishFunction,
33
+ getHealthData,
34
+ config = {},
35
+ logger = console
36
+ } = options;
37
+
38
+ if (!serviceName) {
39
+ throw new Error('serviceName is required');
40
+ }
41
+ if (typeof publishFunction !== 'function') {
42
+ throw new Error('publishFunction must be a function');
43
+ }
44
+ if (typeof getHealthData !== 'function') {
45
+ throw new Error('getHealthData must be a function');
46
+ }
47
+
48
+ let healthCheckInterval = null;
49
+ let isPublishing = false;
50
+
51
+ const queueName = config.queueName || config.infrastructureHealth?.queueName || process.env.INFRASTRUCTURE_HEALTH_QUEUE || 'infrastructure.health.checks';
52
+ const publishInterval = config.publishInterval || config.infrastructureHealth?.publishInterval || parseInt(process.env.INFRASTRUCTURE_HEALTH_PUBLISH_INTERVAL) || 5000;
53
+
54
+ /**
55
+ * Publish health check to infrastructure.health.checks queue
56
+ * @returns {Promise<void>}
57
+ */
58
+ async function publishHealthCheck() {
59
+ if (isPublishing) {
60
+ logger.warn(`[InfrastructureHealth:${serviceName}] Health check already in progress, skipping`);
61
+ return;
62
+ }
63
+
64
+ try {
65
+ isPublishing = true;
66
+ const healthData = {
67
+ serviceName,
68
+ version,
69
+ status: 'UP',
70
+ timestamp: new Date().toISOString(),
71
+ components: getHealthData()
72
+ };
73
+
74
+ await publishFunction(queueName, healthData);
75
+
76
+ logger.debug(`[InfrastructureHealth:${serviceName}] Published health check`, {
77
+ queue: queueName,
78
+ status: healthData.status
79
+ });
80
+ } catch (error) {
81
+ logger.error(`[InfrastructureHealth:${serviceName}] Failed to publish health check`, {
82
+ error: error.message,
83
+ stack: error.stack
84
+ });
85
+ } finally {
86
+ isPublishing = false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Start publishing health checks periodically
92
+ * @returns {Promise<void>}
93
+ */
94
+ async function start() {
95
+ if (healthCheckInterval) {
96
+ logger.warn(`[InfrastructureHealth:${serviceName}] Health check publisher already started`);
97
+ return;
98
+ }
99
+
100
+ // Publish initial health check immediately
101
+ await publishHealthCheck();
102
+
103
+ // Then publish periodically
104
+ healthCheckInterval = setInterval(() => {
105
+ publishHealthCheck().catch(err => {
106
+ logger.error(`[InfrastructureHealth:${serviceName}] Error in health check publisher interval`, {
107
+ error: err.message
108
+ });
109
+ });
110
+ }, publishInterval);
111
+
112
+ logger.info(`[InfrastructureHealth:${serviceName}] Health check publisher started`, {
113
+ interval: publishInterval,
114
+ queue: queueName
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Stop publishing health checks
120
+ */
121
+ function stop() {
122
+ if (healthCheckInterval) {
123
+ clearInterval(healthCheckInterval);
124
+ healthCheckInterval = null;
125
+ logger.info(`[InfrastructureHealth:${serviceName}] Health check publisher stopped`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Publish health check immediately (manual trigger)
131
+ * @returns {Promise<void>}
132
+ */
133
+ async function publishNow() {
134
+ await publishHealthCheck();
135
+ }
136
+
137
+ return {
138
+ start,
139
+ stop,
140
+ publishNow,
141
+ isRunning: () => healthCheckInterval !== null
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Create health publisher adapter for BaseClient (from mq-client-core)
147
+ * @param {Object} baseClient - BaseClient instance
148
+ * @param {string} serviceName - Service name
149
+ * @param {Function} getHealthData - Function to get health data
150
+ * @param {Object} config - Configuration
151
+ * @param {Object} logger - Logger instance
152
+ * @returns {Object} Health publisher instance
153
+ */
154
+ function createBaseClientAdapter(baseClient, serviceName, getHealthData, config, logger) {
155
+ const publishFunction = async (queueName, data) => {
156
+ if (!baseClient || typeof baseClient.publish !== 'function') {
157
+ throw new Error('BaseClient instance must have publish() method');
158
+ }
159
+ await baseClient.publish(queueName, data);
160
+ };
161
+
162
+ return createHealthPublisher({
163
+ serviceName,
164
+ publishFunction,
165
+ getHealthData,
166
+ config,
167
+ logger
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Create health publisher adapter for amqplib (connection + channel)
173
+ * @param {Object} connection - AMQP connection
174
+ * @param {Object} channel - AMQP channel
175
+ * @param {string} serviceName - Service name
176
+ * @param {Function} getHealthData - Function to get health data
177
+ * @param {Object} config - Configuration
178
+ * @param {Object} logger - Logger instance
179
+ * @returns {Object} Health publisher instance
180
+ */
181
+ function createAmqplibAdapter(connection, channel, serviceName, getHealthData, config, logger) {
182
+ const publishFunction = async (queueName, data) => {
183
+ if (!channel || channel.closed) {
184
+ throw new Error('AMQP channel is not available or closed');
185
+ }
186
+ // Ensure queue exists (it should be created by Registry, but assert just in case)
187
+ await channel.assertQueue(queueName, { durable: true });
188
+ await channel.sendToQueue(
189
+ queueName,
190
+ Buffer.from(JSON.stringify(data)),
191
+ { persistent: true }
192
+ );
193
+ };
194
+
195
+ return createHealthPublisher({
196
+ serviceName,
197
+ publishFunction,
198
+ getHealthData,
199
+ config,
200
+ logger
201
+ });
202
+ }
203
+
204
+ module.exports = {
205
+ createHealthPublisher,
206
+ createBaseClientAdapter,
207
+ createAmqplibAdapter
208
+ };
209
+
package/src/index.js ADDED
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @onlineapps/infrastructure-tools
5
+ *
6
+ * Infrastructure orchestration utilities for OA Drive infrastructure services.
7
+ * Provides health tracking, queue initialization, and service discovery utilities.
8
+ *
9
+ * NOTE: This library is for infrastructure services only.
10
+ * Business services should NOT use this library.
11
+ */
12
+
13
+ const { waitForInfrastructureReady } = require('./orchestration/waitForInfrastructureReady');
14
+ const { initInfrastructureQueues } = require('./orchestration/initInfrastructureQueues');
15
+ const {
16
+ createHealthPublisher,
17
+ createBaseClientAdapter,
18
+ createAmqplibAdapter
19
+ } = require('./health/healthPublisher');
20
+
21
+ module.exports = {
22
+ // Orchestration utilities
23
+ waitForInfrastructureReady,
24
+ initInfrastructureQueues,
25
+
26
+ // Health tracking utilities
27
+ createHealthPublisher,
28
+ createBaseClientAdapter,
29
+ createAmqplibAdapter
30
+ };
31
+
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * initInfrastructureQueues.js
5
+ *
6
+ * Utility for infrastructure services to initialize infrastructure queues.
7
+ * Uses queueConfig from @onlineapps/mq-client-core for configuration.
8
+ *
9
+ * NOTE: This is for infrastructure services (gateway, monitoring, etc.)
10
+ * Business services should use @onlineapps/conn-infra-mq connector instead.
11
+ */
12
+
13
+ /**
14
+ * Initialize all infrastructure queues
15
+ * @param {Object} channel - RabbitMQ channel
16
+ * @param {Object} options - Options
17
+ * @param {Array<string>} [options.queues] - Specific queues to create (default: all infrastructure queues)
18
+ * @param {Object} [options.logger] - Logger instance (default: console)
19
+ * @param {Object} [options.connection] - RabbitMQ connection (for channel recreation)
20
+ * @param {Object} [options.queueConfig] - Queue config instance (default: from mq-client-core)
21
+ * @returns {Promise<void>}
22
+ */
23
+ async function initInfrastructureQueues(channel, options = {}) {
24
+ if (!channel) {
25
+ throw new Error('initInfrastructureQueues requires a valid channel');
26
+ }
27
+
28
+ const logger = options.logger || console;
29
+ const connection = options.connection || channel.connection;
30
+
31
+ // Load queueConfig from mq-client-core
32
+ // NOTE: queueConfig is part of mq-client-core because it's used by both:
33
+ // - Infrastructure services (via infrastructure-tools)
34
+ // - Business services (via conn-infra-mq connector)
35
+ const queueConfig = options.queueConfig || require('@onlineapps/mq-client-core/src/config/queueConfig');
36
+
37
+ const queuesToCreate = options.queues || [
38
+ // Workflow infrastructure queues
39
+ 'workflow.init',
40
+ 'workflow.completed',
41
+ 'workflow.failed',
42
+ 'workflow.dlq',
43
+ // Registry infrastructure queues
44
+ 'registry.register',
45
+ 'registry.heartbeats'
46
+ ];
47
+
48
+ const watchChannel = (ch) => {
49
+ if (!ch) return ch;
50
+ if (!ch.__queueInitLinked) {
51
+ ch.__queueInitLinked = true;
52
+ ch.once('close', () => {
53
+ ch.__queueInitClosed = true;
54
+ logger.warn('[QueueInit] Queue channel closed');
55
+ });
56
+ ch.on('error', (err) => {
57
+ ch.__queueInitClosed = true;
58
+ logger.warn(`[QueueInit] Queue channel error: ${err.message}`);
59
+ });
60
+ }
61
+ return ch;
62
+ };
63
+
64
+ let workingChannel = watchChannel(channel);
65
+
66
+ const getChannel = async (forceNew = false) => {
67
+ if (!forceNew && workingChannel && !workingChannel.__queueInitClosed) {
68
+ return workingChannel;
69
+ }
70
+ if (!connection) {
71
+ throw new Error('Queue channel closed and no connection reference available to recreate it');
72
+ }
73
+ workingChannel = watchChannel(await connection.createChannel());
74
+ return workingChannel;
75
+ };
76
+
77
+ logger.log(`[QueueInit] Initializing ${queuesToCreate.length} infrastructure queues...`);
78
+
79
+ for (const queueName of queuesToCreate) {
80
+ try {
81
+ // Verify it's an infrastructure queue
82
+ if (!queueConfig.isInfrastructureQueue(queueName)) {
83
+ logger.warn(`[QueueInit] Skipping ${queueName} - not an infrastructure queue`);
84
+ continue;
85
+ }
86
+
87
+ // Get unified configuration
88
+ const config = queueConfig.getInfrastructureQueueConfig(queueName);
89
+
90
+ // CRITICAL: Check if queue exists with different arguments (406 error)
91
+ // If so, delete it and recreate with correct parameters
92
+ const ensureQueue = async () => {
93
+ const activeChannel = await getChannel();
94
+ return activeChannel.assertQueue(queueName, {
95
+ durable: config.durable !== false,
96
+ arguments: { ...config.arguments }
97
+ });
98
+ };
99
+
100
+ try {
101
+ await ensureQueue();
102
+ logger.log(`[QueueInit] ✓ Created/verified infrastructure queue: ${queueName}`);
103
+ } catch (assertError) {
104
+ if (assertError.code === 406) {
105
+ logger.warn(
106
+ `[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`
107
+ );
108
+ try {
109
+ const deleteChannel = await getChannel(true);
110
+ await deleteChannel.deleteQueue(queueName);
111
+ logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
112
+ } catch (deleteError) {
113
+ logger.error(
114
+ `[QueueInit] ✗ Failed to delete queue ${queueName}:`,
115
+ deleteError.message
116
+ );
117
+ throw new Error(
118
+ `Cannot delete queue ${queueName} with incorrect parameters: ${deleteError.message}`
119
+ );
120
+ }
121
+
122
+ // Recreate with correct parameters (force fresh channel to avoid closed state)
123
+ await getChannel(true);
124
+ await ensureQueue();
125
+ logger.log(
126
+ `[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`
127
+ );
128
+ } else {
129
+ logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, assertError.message);
130
+ throw assertError;
131
+ }
132
+ }
133
+ } catch (error) {
134
+ logger.error(`[QueueInit] ✗ Failed to initialize queue ${queueName}:`, error.message);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ logger.log(`[QueueInit] Infrastructure queues initialization complete`);
140
+ }
141
+
142
+ module.exports = {
143
+ initInfrastructureQueues
144
+ };
145
+
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * waitForInfrastructureReady.js
5
+ *
6
+ * Waits for all infrastructure services to be verified as running and healthy.
7
+ * Used by infrastructure services before creating queues to ensure system consistency.
8
+ *
9
+ * This prevents race conditions where queues are created before all services are ready.
10
+ *
11
+ * **How it works:**
12
+ * 1. Connects to Redis (where Registry stores infrastructure health status)
13
+ * 2. Checks `infrastructure:health:all` key every 5 seconds
14
+ * 3. If key is `"true"` → all infrastructure services are UP → return success
15
+ * 4. If key is `"false"` or missing → wait and retry
16
+ * 5. If timeout reached → throw error
17
+ *
18
+ * **Why Redis (not HTTP):**
19
+ * - Fast: In-memory lookup, no network overhead
20
+ * - Reliable: Redis is already required infrastructure
21
+ * - Low latency: Direct key lookup, no HTTP parsing
22
+ * - Consistent: Same data source as Registry uses
23
+ * - No single point of failure: Redis can be clustered
24
+ */
25
+
26
+ /**
27
+ * Wait for all infrastructure services to be ready
28
+ * @param {Object} options - Options
29
+ * @param {string} [options.redisUrl] - Redis URL (default: REDIS_URL env or redis://api_node_cache:6379)
30
+ * @param {number} [options.maxWait] - Maximum wait time in ms (default: INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME env or 300000 = 5 minutes)
31
+ * @param {number} [options.checkInterval] - Check interval in ms (default: INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL env or 5000 = 5 seconds)
32
+ * @param {Object} [options.logger] - Logger instance (default: console)
33
+ * @returns {Promise<boolean>} - True if all infrastructure services are ready
34
+ * @throws {Error} - If timeout is reached
35
+ *
36
+ * Note: Default values can be overridden via ENV variables or options parameter.
37
+ * Registry config (config.infrastructureHealth) is the source of truth for these defaults.
38
+ */
39
+ async function waitForInfrastructureReady(options = {}) {
40
+ const redisUrl = options.redisUrl || process.env.REDIS_URL || 'redis://api_node_cache:6379';
41
+ const maxWait = options.maxWait || parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME) || 300000; // 5 minutes
42
+ const checkInterval = options.checkInterval || parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL) || 5000; // 5 seconds
43
+ const logger = options.logger || console;
44
+
45
+ const startTime = Date.now();
46
+ let attemptCount = 0;
47
+ let redis = null;
48
+
49
+ logger.log('[InfrastructureReady] Waiting for all infrastructure services to be ready...');
50
+ logger.log(`[InfrastructureReady] Redis URL: ${redisUrl}`);
51
+ logger.log(`[InfrastructureReady] Max wait: ${maxWait}ms, Check interval: ${checkInterval}ms`);
52
+
53
+ try {
54
+ // Connect to Redis
55
+ const { createClient } = require('redis');
56
+ redis = createClient({ url: redisUrl });
57
+
58
+ redis.on('error', (err) => {
59
+ logger.log(`[InfrastructureReady] Redis error: ${err.message}`);
60
+ });
61
+
62
+ await redis.connect();
63
+ logger.log('[InfrastructureReady] Connected to Redis');
64
+
65
+ while (Date.now() - startTime < maxWait) {
66
+ attemptCount++;
67
+
68
+ try {
69
+ // Check Redis key: infrastructure:health:all
70
+ const allHealthy = await redis.get('infrastructure:health:all');
71
+
72
+ if (allHealthy === 'true') {
73
+ // All services are UP, we can proceed
74
+ const elapsed = Date.now() - startTime;
75
+ logger.log(`[InfrastructureReady] ✓ All infrastructure services are ready (took ${elapsed}ms, ${attemptCount} attempts)`);
76
+
77
+ // Optionally log individual service status
78
+ // Note: Service names should match config.infrastructureServices in Registry
79
+ const serviceKeys = ['gateway', 'registry', 'validator', 'delivery', 'monitoring'];
80
+ const statuses = {};
81
+ for (const serviceName of serviceKeys) {
82
+ const status = await redis.get(`infrastructure:health:${serviceName}`);
83
+ if (status) {
84
+ statuses[serviceName] = JSON.parse(status);
85
+ }
86
+ }
87
+ logger.log(`[InfrastructureReady] Infrastructure status:`, JSON.stringify(statuses, null, 2));
88
+
89
+ return true;
90
+ }
91
+
92
+ // Not all services ready yet
93
+ logger.log(`[InfrastructureReady] Attempt ${attemptCount}: Not all services ready (allHealthy: ${allHealthy || 'null'})`);
94
+ logger.log(`[InfrastructureReady] Waiting ${checkInterval}ms before next check...`);
95
+
96
+ } catch (error) {
97
+ // Redis might not be ready yet, or connection issue
98
+ logger.log(`[InfrastructureReady] Attempt ${attemptCount}: Redis check failed (${error.message})`);
99
+ logger.log(`[InfrastructureReady] Waiting ${checkInterval}ms before retry...`);
100
+ }
101
+
102
+ // Wait before next check
103
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
104
+ }
105
+
106
+ // Timeout reached
107
+ const elapsed = Date.now() - startTime;
108
+ throw new Error(
109
+ `Infrastructure services not ready within ${maxWait}ms (${elapsed}ms elapsed, ${attemptCount} attempts). ` +
110
+ `Check Redis keys: infrastructure:health:*`
111
+ );
112
+
113
+ } finally {
114
+ // Clean up Redis connection
115
+ if (redis && redis.isReady) {
116
+ await redis.quit();
117
+ logger.log('[InfrastructureReady] Redis connection closed');
118
+ }
119
+ }
120
+ }
121
+
122
+ module.exports = {
123
+ waitForInfrastructureReady
124
+ };
125
+