@onlineapps/infrastructure-tools 1.0.17 → 1.0.19

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/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@onlineapps/infrastructure-tools",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Infrastructure orchestration utilities for OA Drive infrastructure services (health tracking, queue initialization, service discovery)",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
7
  "test": "jest",
8
8
  "test:unit": "jest --testPathPattern=tests/unit",
9
9
  "test:component": "jest --testPathPattern=tests/component",
10
- "test:integration": "jest --testPathPattern=tests/integration"
10
+ "test:integration": "jest --testPathPattern=tests/integration",
11
+ "prepublishOnly": "cd $(git rev-parse --show-toplevel) && bash scripts/pre-publish-compatibility-check.sh shared/infrastructure-tools",
12
+ "postpublish": "cd $(git rev-parse --show-toplevel) && bash scripts/update-manifest-from-npm.sh && bash scripts/update-all-services.sh"
11
13
  },
12
14
  "keywords": [
13
15
  "infrastructure",
@@ -19,7 +21,7 @@
19
21
  "license": "MIT",
20
22
  "dependencies": {
21
23
  "@onlineapps/mq-client-core": "^1.0.38",
22
- "@onlineapps/service-common": "^1.0.1",
24
+ "@onlineapps/service-common": "^1.0.3",
23
25
  "uuid": "^9.0.1"
24
26
  },
25
27
  "devDependencies": {
@@ -56,6 +56,11 @@ function createHealthPublisher(options) {
56
56
 
57
57
  const queueName = config.queueName || config.infrastructureHealth?.queueName || process.env.INFRASTRUCTURE_HEALTH_QUEUE || 'infrastructure.health.checks';
58
58
  const publishInterval = config.publishInterval || config.infrastructureHealth?.publishInterval || parseInt(process.env.INFRASTRUCTURE_HEALTH_PUBLISH_INTERVAL) || 5000;
59
+
60
+ // Support for disabling health checks via environment variable (useful for library updates)
61
+ const healthChecksDisabled = process.env.DISABLE_HEALTH_CHECKS === 'true' ||
62
+ process.env.DISABLE_HEALTH_CHECKS === '1' ||
63
+ config.disableHealthChecks === true;
59
64
 
60
65
  /**
61
66
  * Publish health check to infrastructure.health.checks queue
@@ -125,11 +130,25 @@ function createHealthPublisher(options) {
125
130
  return;
126
131
  }
127
132
 
133
+ // Check if health checks are disabled (useful for library updates/maintenance)
134
+ if (healthChecksDisabled) {
135
+ logger.warn(`[InfrastructureHealth:${serviceName}] Health checks are disabled (DISABLE_HEALTH_CHECKS=true). Skipping start.`, {
136
+ reason: 'health_checks_disabled',
137
+ serviceName
138
+ });
139
+ return;
140
+ }
141
+
128
142
  // Publish initial health check immediately
129
143
  await publishHealthCheck();
130
144
 
131
145
  // Then publish periodically
132
146
  healthCheckInterval = setInterval(() => {
147
+ // Double-check disabled flag on each interval (allows runtime changes)
148
+ if (healthChecksDisabled) {
149
+ stop();
150
+ return;
151
+ }
133
152
  publishHealthCheck().catch(err => {
134
153
  logger.error(`[InfrastructureHealth:${serviceName}] Error in health check publisher interval`, {
135
154
  error: err.message
@@ -166,7 +185,8 @@ function createHealthPublisher(options) {
166
185
  start,
167
186
  stop,
168
187
  publishNow,
169
- isRunning: () => healthCheckInterval !== null
188
+ isRunning: () => healthCheckInterval !== null,
189
+ isDisabled: () => healthChecksDisabled
170
190
  };
171
191
  }
172
192
 
package/src/index.js CHANGED
@@ -10,8 +10,25 @@
10
10
  * Business services should NOT use this library.
11
11
  */
12
12
 
13
- // Re-export infrastructure readiness utilities from service-common (no duplication)
14
- const { waitForInfrastructureReady, waitForHealthCheckQueueReady, sendMonitoringFailFallbackEmail } = require('@onlineapps/service-common');
13
+ // Re-export all utilities from service-common (infrastructure services should use infrastructure-tools, not service-common directly)
14
+ const {
15
+ // Infrastructure readiness utilities
16
+ waitForInfrastructureReady,
17
+ waitForHealthCheckQueueReady,
18
+ sendMonitoringFailFallbackEmail,
19
+ // Redis utilities
20
+ buildRedisUrl,
21
+ createRedisClient,
22
+ connectRedis,
23
+ // Runtime configuration helpers (NO FALLBACKS for critical infrastructure)
24
+ requireEnv,
25
+ optionalEnv,
26
+ optionalNumberEnv,
27
+ getCriticalConfig,
28
+ getOptionalInfraConfig,
29
+ getServiceConfig,
30
+ getInfrastructureHealthConfig
31
+ } = require('@onlineapps/service-common');
15
32
 
16
33
  // Re-export MQ client core utilities
17
34
  const BaseClient = require('@onlineapps/mq-client-core');
@@ -32,10 +49,23 @@ module.exports = {
32
49
  MQClient: BaseClient, // Alias for compatibility
33
50
  queueConfig,
34
51
 
35
- // Service Common utilities (re-exported for convenience)
52
+ // Service Common utilities (re-exported - infrastructure services should use infrastructure-tools, not service-common directly)
53
+ // Infrastructure readiness
36
54
  waitForInfrastructureReady,
37
55
  waitForHealthCheckQueueReady,
38
56
  sendMonitoringFailFallbackEmail,
57
+ // Redis utilities
58
+ buildRedisUrl,
59
+ createRedisClient,
60
+ connectRedis,
61
+ // Runtime configuration helpers (NO FALLBACKS for critical infrastructure)
62
+ requireEnv,
63
+ optionalEnv,
64
+ optionalNumberEnv,
65
+ getCriticalConfig,
66
+ getOptionalInfraConfig,
67
+ getServiceConfig,
68
+ getInfrastructureHealthConfig,
39
69
 
40
70
  // Orchestration utilities
41
71
  initInfrastructureQueues,
@@ -96,8 +96,98 @@ async function initInfrastructureQueues(channel, options = {}) {
96
96
  // Get unified configuration
97
97
  const config = queueConfig.getInfrastructureQueueConfig(queueName);
98
98
 
99
- // CRITICAL: Check if queue exists with different arguments (406 error)
100
- // If so, delete it and recreate with correct parameters
99
+ // CRITICAL: First check if queue exists and what parameters it has
100
+ // This prevents 406 errors by proactively deleting queues with wrong parameters
101
+ // Use a separate channel for checkQueue to avoid channel closure issues
102
+ // NOTE: checkQueue() does NOT create queue - it only checks if it exists
103
+ let queueExists = false;
104
+ let queueNeedsRecreation = false;
105
+ let checkChannelForDelete = null;
106
+
107
+ try {
108
+ // Use a separate channel for checking queue (to avoid closing workingChannel)
109
+ // CRITICAL: checkQueue() does NOT create queue - it only checks existence
110
+ checkChannelForDelete = await connection.createChannel();
111
+ const queueInfo = await checkChannelForDelete.checkQueue(queueName);
112
+ queueExists = true;
113
+
114
+ // Check if queue has correct arguments
115
+ const currentArgs = queueInfo.arguments || {};
116
+ const expectedArgs = config.arguments || {};
117
+
118
+ // Compare arguments (TTL, max-length, etc.)
119
+ const argsMatch = Object.keys(expectedArgs).every(key => {
120
+ return currentArgs[key] === expectedArgs[key];
121
+ }) && Object.keys(currentArgs).every(key => {
122
+ // Allow extra args in current queue, but required args must match
123
+ return expectedArgs[key] === undefined || currentArgs[key] === expectedArgs[key];
124
+ });
125
+
126
+ if (!argsMatch) {
127
+ logger.warn(`[QueueInit] ⚠ Queue ${queueName} exists with different arguments - will delete and recreate`, {
128
+ current: currentArgs,
129
+ expected: expectedArgs
130
+ });
131
+ queueNeedsRecreation = true;
132
+ } else {
133
+ logger.log(`[QueueInit] ✓ Queue ${queueName} exists with correct parameters`);
134
+ // Close check channel if queue is OK
135
+ try {
136
+ await checkChannelForDelete.close();
137
+ checkChannelForDelete = null;
138
+ } catch (closeErr) {
139
+ // Ignore
140
+ }
141
+ }
142
+ } catch (checkError) {
143
+ // Queue doesn't exist - will be created
144
+ queueExists = false;
145
+ logger.log(`[QueueInit] Queue ${queueName} does not exist - will be created`);
146
+ // Close check channel if queue doesn't exist
147
+ if (checkChannelForDelete) {
148
+ try {
149
+ await checkChannelForDelete.close();
150
+ checkChannelForDelete = null;
151
+ } catch (closeErr) {
152
+ // Ignore
153
+ }
154
+ }
155
+ }
156
+
157
+ // CRITICAL: If queue exists with wrong parameters, delete it first
158
+ // This must be done BEFORE assertQueue to avoid 406 errors
159
+ if (queueNeedsRecreation) {
160
+ try {
161
+ // Use the check channel for deletion (or create new one if check failed)
162
+ const deleteChannel = checkChannelForDelete || await connection.createChannel();
163
+ await deleteChannel.deleteQueue(queueName, { ifEmpty: false });
164
+ logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
165
+ // Close delete channel after use
166
+ try {
167
+ await deleteChannel.close();
168
+ } catch (closeErr) {
169
+ // Ignore close errors
170
+ }
171
+ checkChannelForDelete = null;
172
+
173
+ // Wait to ensure deletion is processed and no frames are pending
174
+ await new Promise(resolve => setTimeout(resolve, 300));
175
+ queueExists = false; // Queue no longer exists, will be created
176
+ } catch (deleteError) {
177
+ logger.error(`[QueueInit] ✗ Failed to delete queue ${queueName}:`, deleteError.message);
178
+ // Clean up check channel if still open
179
+ if (checkChannelForDelete) {
180
+ try {
181
+ await checkChannelForDelete.close();
182
+ } catch (closeErr) {
183
+ // Ignore
184
+ }
185
+ }
186
+ throw new Error(`Cannot delete queue ${queueName} with incorrect parameters: ${deleteError.message}`);
187
+ }
188
+ }
189
+
190
+ // CRITICAL: Now create/verify queue with correct parameters
101
191
  const ensureQueue = async () => {
102
192
  const activeChannel = await getChannel();
103
193
  return activeChannel.assertQueue(queueName, {
@@ -129,9 +219,26 @@ async function initInfrastructureQueues(channel, options = {}) {
129
219
  `[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`
130
220
  );
131
221
  try {
222
+ // CRITICAL: Use a fresh channel for delete operation to avoid closed channel errors
223
+ // Close the current channel first to prevent it from receiving frames during deletion
224
+ if (workingChannel && !workingChannel.__queueInitClosed) {
225
+ try {
226
+ await workingChannel.close();
227
+ workingChannel.__queueInitClosed = true;
228
+ } catch (closeErr) {
229
+ // Ignore - channel may already be closed
230
+ }
231
+ }
232
+
132
233
  const deleteChannel = await getChannel(true);
133
- await deleteChannel.deleteQueue(queueName);
234
+ await deleteChannel.deleteQueue(queueName, { ifEmpty: false });
134
235
  logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
236
+ // Close delete channel after use to avoid channel leaks
237
+ try {
238
+ await deleteChannel.close();
239
+ } catch (closeErr) {
240
+ // Ignore close errors - channel may already be closed
241
+ }
135
242
  } catch (deleteError) {
136
243
  logger.error(
137
244
  `[QueueInit] ✗ Failed to delete queue ${queueName}:`,
@@ -142,12 +249,24 @@ async function initInfrastructureQueues(channel, options = {}) {
142
249
  );
143
250
  }
144
251
 
145
- // Recreate with correct parameters (force fresh channel to avoid closed state)
146
- await getChannel(true);
147
- await ensureQueue();
148
- logger.log(
149
- `[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`
150
- );
252
+ // CRITICAL: Recreate with correct parameters using a fresh channel
253
+ // Wait a brief moment to ensure queue deletion is fully processed and no frames are pending
254
+ await new Promise(resolve => setTimeout(resolve, 200));
255
+ const recreateChannel = await getChannel(true);
256
+ try {
257
+ await recreateChannel.assertQueue(queueName, {
258
+ durable: config.durable !== false,
259
+ arguments: { ...config.arguments }
260
+ });
261
+ logger.log(
262
+ `[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`
263
+ );
264
+ // Update workingChannel to the new channel for subsequent operations
265
+ workingChannel = recreateChannel;
266
+ } catch (recreateError) {
267
+ logger.error(`[QueueInit] ✗ Failed to recreate ${queueName}:`, recreateError.message);
268
+ throw recreateError;
269
+ }
151
270
  } else {
152
271
  logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, assertError.message);
153
272
  throw assertError;