@onlineapps/service-common 1.0.5 → 1.0.6
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 +2 -3
- package/src/config.js +40 -0
- package/src/defaults.js +21 -0
- package/src/index.js +1 -13
- package/src/redisClient.js +3 -2
- package/src/runtime-config.js +7 -8
- package/tests/unit/defaults.test.js +45 -0
- package/src/monitoring-publish.js +0 -133
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-common",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Common utilities for both infrastructure services and business services",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"prepublishOnly": "../../scripts/pre-publish-compatibility-check.sh",
|
|
8
|
-
"postpublish": "../../scripts/update-manifest-from-npm.sh && ../../scripts/update-all-services.sh",
|
|
9
7
|
"test": "jest",
|
|
10
8
|
"test:unit": "jest tests/unit",
|
|
11
9
|
"test:integration": "jest tests/integration"
|
|
@@ -19,6 +17,7 @@
|
|
|
19
17
|
"author": "OA Drive Team",
|
|
20
18
|
"license": "MIT",
|
|
21
19
|
"dependencies": {
|
|
20
|
+
"@onlineapps/runtime-config": "1.0.1",
|
|
22
21
|
"nodemailer": "^6.9.8",
|
|
23
22
|
"redis": "^4.6.0"
|
|
24
23
|
},
|
package/src/config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runtime configuration schema for @onlineapps/service-common.
|
|
5
|
+
*
|
|
6
|
+
* Uses @onlineapps/runtime-config for unified priority:
|
|
7
|
+
* 1. Explicit config
|
|
8
|
+
* 2. Environment variable
|
|
9
|
+
* 3. Owner defaults (from ./defaults.js)
|
|
10
|
+
*
|
|
11
|
+
* This module owns infrastructure connection defaults (Redis, RabbitMQ)
|
|
12
|
+
* that are used as fallbacks by other modules.
|
|
13
|
+
*
|
|
14
|
+
* @module @onlineapps/service-common/config
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { createRuntimeConfig } = require('@onlineapps/runtime-config');
|
|
18
|
+
const DEFAULTS = require('./defaults');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runtime config schema for service-common
|
|
22
|
+
*/
|
|
23
|
+
const runtimeCfg = createRuntimeConfig({
|
|
24
|
+
defaults: DEFAULTS,
|
|
25
|
+
schema: {
|
|
26
|
+
// Redis defaults
|
|
27
|
+
redisHost: { env: 'REDIS_HOST', defaultKey: 'redisHost' },
|
|
28
|
+
redisPort: { env: 'REDIS_PORT', defaultKey: 'redisPort' },
|
|
29
|
+
redisUrl: { env: 'REDIS_URL', defaultKey: 'redisUrl' },
|
|
30
|
+
|
|
31
|
+
// RabbitMQ defaults
|
|
32
|
+
rabbitmqUrl: { env: 'RABBITMQ_URL', defaultKey: 'rabbitmqUrl' },
|
|
33
|
+
|
|
34
|
+
// Infrastructure health
|
|
35
|
+
infrastructureHealthQueueName: { env: 'INFRASTRUCTURE_HEALTH_QUEUE', defaultKey: 'infrastructureHealthQueueName' },
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
module.exports = runtimeCfg;
|
|
40
|
+
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Module-owned defaults for @onlineapps/service-common.
|
|
5
|
+
*
|
|
6
|
+
* Ownership rule:
|
|
7
|
+
* - These defaults are owned by service-common.
|
|
8
|
+
* - Callers can override via explicit config or environment variables.
|
|
9
|
+
* - Keep infra-related defaults here to avoid duplication across files.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
redisHost: 'api_node_cache',
|
|
14
|
+
redisPort: '6379',
|
|
15
|
+
redisUrl: 'redis://api_node_cache:6379',
|
|
16
|
+
|
|
17
|
+
rabbitmqUrl: 'amqp://guest:guest@api_services_queuer:5672',
|
|
18
|
+
infrastructureHealthQueueName: 'infrastructure.health.checks',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
|
package/src/index.js
CHANGED
|
@@ -32,12 +32,6 @@ const {
|
|
|
32
32
|
getServiceConfig,
|
|
33
33
|
getInfrastructureHealthConfig
|
|
34
34
|
} = require('./runtime-config');
|
|
35
|
-
const {
|
|
36
|
-
publishToMonitoringResilient,
|
|
37
|
-
publishToMonitoringWorkflow,
|
|
38
|
-
publishToMonitoringServices,
|
|
39
|
-
isQueueUnavailableError
|
|
40
|
-
} = require('./monitoring-publish');
|
|
41
35
|
|
|
42
36
|
module.exports = {
|
|
43
37
|
// Infrastructure readiness utilities (used by both infrastructure and business services)
|
|
@@ -65,13 +59,7 @@ module.exports = {
|
|
|
65
59
|
getInfrastructureHealthConfig,
|
|
66
60
|
|
|
67
61
|
// Reporting utilities
|
|
68
|
-
sendMonitoringFailFallbackEmail
|
|
69
|
-
|
|
70
|
-
// Resilient monitoring publish utilities
|
|
71
|
-
publishToMonitoringResilient,
|
|
72
|
-
publishToMonitoringWorkflow,
|
|
73
|
-
publishToMonitoringServices,
|
|
74
|
-
isQueueUnavailableError
|
|
62
|
+
sendMonitoringFailFallbackEmail
|
|
75
63
|
};
|
|
76
64
|
|
|
77
65
|
|
package/src/redisClient.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const { createClient } = require('redis');
|
|
16
|
+
const DEFAULTS = require('./defaults');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Build a Redis connection URL from environment.
|
|
@@ -41,8 +42,8 @@ function buildRedisUrl(options = {}) {
|
|
|
41
42
|
|
|
42
43
|
// Priority 2: REDIS_HOST + REDIS_PORT (if REDIS_URL not set)
|
|
43
44
|
// Fallback for docker-compose environment (defined in library, not in docker-compose.yml)
|
|
44
|
-
const host = env.REDIS_HOST || defaults.host ||
|
|
45
|
-
const port = env.REDIS_PORT || defaults.port ||
|
|
45
|
+
const host = env.REDIS_HOST || defaults.host || DEFAULTS.redisHost;
|
|
46
|
+
const port = env.REDIS_PORT || defaults.port || DEFAULTS.redisPort;
|
|
46
47
|
|
|
47
48
|
return `redis://${host}:${port}`;
|
|
48
49
|
}
|
package/src/runtime-config.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* - No hidden dependencies on hardcoded values
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
const runtimeCfg = require('./config');
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Get required environment variable or throw error
|
|
17
19
|
* @param {string} name - Environment variable name
|
|
@@ -94,14 +96,11 @@ function getCriticalConfig() {
|
|
|
94
96
|
* @returns {Object} Critical infrastructure config with fallbacks
|
|
95
97
|
*/
|
|
96
98
|
function getCriticalConfigWithFallbacks() {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
const redisFallback = 'redis://api_node_cache:6379';
|
|
100
|
-
const rabbitmqFallback = 'amqp://guest:guest@api_services_queuer:5672';
|
|
101
|
-
|
|
99
|
+
// Uses runtime-config for unified priority: ENV → owner defaults
|
|
100
|
+
// Fallbacks are defined in ./defaults.js (module-owned)
|
|
102
101
|
return {
|
|
103
|
-
REDIS_URL:
|
|
104
|
-
RABBITMQ_URL:
|
|
102
|
+
REDIS_URL: runtimeCfg.get('redisUrl'),
|
|
103
|
+
RABBITMQ_URL: runtimeCfg.get('rabbitmqUrl')
|
|
105
104
|
};
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -144,7 +143,7 @@ function getServiceConfig(options = {}) {
|
|
|
144
143
|
*/
|
|
145
144
|
function getInfrastructureHealthConfig() {
|
|
146
145
|
return {
|
|
147
|
-
queueName:
|
|
146
|
+
queueName: runtimeCfg.get('infrastructureHealthQueueName'),
|
|
148
147
|
publishInterval: optionalNumberEnv('INFRASTRUCTURE_HEALTH_PUBLISH_INTERVAL', 5000),
|
|
149
148
|
waitMaxTime: optionalNumberEnv('INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME', 300000),
|
|
150
149
|
waitCheckInterval: optionalNumberEnv('INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL', 5000),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const defaults = require('../../src/defaults');
|
|
4
|
+
const { getCriticalConfigWithFallbacks, getInfrastructureHealthConfig } = require('../../src/runtime-config');
|
|
5
|
+
const { buildRedisUrl } = require('../../src/redisClient');
|
|
6
|
+
|
|
7
|
+
describe('@onlineapps/service-common defaults @unit', () => {
|
|
8
|
+
test('should export stable module-owned defaults', () => {
|
|
9
|
+
expect(defaults.redisUrl).toBe('redis://api_node_cache:6379');
|
|
10
|
+
expect(defaults.rabbitmqUrl).toBe('amqp://guest:guest@api_services_queuer:5672');
|
|
11
|
+
expect(defaults.infrastructureHealthQueueName).toBe('infrastructure.health.checks');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('getCriticalConfigWithFallbacks should use module defaults when env is missing', () => {
|
|
15
|
+
const oldRedis = process.env.REDIS_URL;
|
|
16
|
+
const oldRabbit = process.env.RABBITMQ_URL;
|
|
17
|
+
delete process.env.REDIS_URL;
|
|
18
|
+
delete process.env.RABBITMQ_URL;
|
|
19
|
+
|
|
20
|
+
const cfg = getCriticalConfigWithFallbacks();
|
|
21
|
+
expect(cfg.REDIS_URL).toBe(defaults.redisUrl);
|
|
22
|
+
expect(cfg.RABBITMQ_URL).toBe(defaults.rabbitmqUrl);
|
|
23
|
+
|
|
24
|
+
if (oldRedis !== undefined) process.env.REDIS_URL = oldRedis;
|
|
25
|
+
if (oldRabbit !== undefined) process.env.RABBITMQ_URL = oldRabbit;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('getInfrastructureHealthConfig should default queue name from module defaults', () => {
|
|
29
|
+
const old = process.env.INFRASTRUCTURE_HEALTH_QUEUE;
|
|
30
|
+
delete process.env.INFRASTRUCTURE_HEALTH_QUEUE;
|
|
31
|
+
|
|
32
|
+
const cfg = getInfrastructureHealthConfig();
|
|
33
|
+
expect(cfg.queueName).toBe(defaults.infrastructureHealthQueueName);
|
|
34
|
+
|
|
35
|
+
if (old !== undefined) process.env.INFRASTRUCTURE_HEALTH_QUEUE = old;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('buildRedisUrl should fall back to module defaults host/port', () => {
|
|
39
|
+
const url = buildRedisUrl({ env: {}, defaults: {} });
|
|
40
|
+
expect(url).toBe(defaults.redisUrl);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resilient monitoring publish helper
|
|
3
|
-
*
|
|
4
|
-
* Publishes to monitoring queues (monitoring.workflow, monitoring.services) with fail-safe behavior.
|
|
5
|
-
* If the queue doesn't exist or RabbitMQ is unavailable, logs the error and continues without failing.
|
|
6
|
-
*
|
|
7
|
-
* This ensures services can start and operate even if monitoring queues aren't ready yet.
|
|
8
|
-
* Monitoring queues are created when health:all is true, so this is a temporary state during startup.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Check if error indicates queue doesn't exist or RabbitMQ is unavailable
|
|
13
|
-
*/
|
|
14
|
-
function isQueueUnavailableError(error) {
|
|
15
|
-
if (!error) return false;
|
|
16
|
-
|
|
17
|
-
const errorMessage = error.message || String(error);
|
|
18
|
-
const errorCode = error.code || '';
|
|
19
|
-
|
|
20
|
-
// RabbitMQ connection errors
|
|
21
|
-
if (errorCode === 'ECONNREFUSED' || errorCode === 'ENOTFOUND' || errorCode === 'ETIMEDOUT') {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Queue doesn't exist errors (from RabbitMQ)
|
|
26
|
-
if (errorMessage.includes('NOT_FOUND') ||
|
|
27
|
-
errorMessage.includes('404') ||
|
|
28
|
-
errorMessage.includes('no queue') ||
|
|
29
|
-
errorMessage.includes('queue does not exist') ||
|
|
30
|
-
errorMessage.includes('Channel closed') ||
|
|
31
|
-
errorMessage.includes('Connection closed')) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Resilient publish to monitoring queue
|
|
40
|
-
*
|
|
41
|
-
* @param {Object} mqClient - MQ client instance (must have publish method)
|
|
42
|
-
* @param {string} queueName - Queue name (e.g., 'monitoring.workflow', 'monitoring.services')
|
|
43
|
-
* @param {Object} message - Message to publish
|
|
44
|
-
* @param {Object} logger - Logger instance (optional, for logging)
|
|
45
|
-
* @param {Object} context - Additional context for logging (optional)
|
|
46
|
-
* @returns {Promise<boolean>} - true if published successfully, false otherwise
|
|
47
|
-
*/
|
|
48
|
-
async function publishToMonitoringResilient(mqClient, queueName, message, logger = null, context = {}) {
|
|
49
|
-
if (!mqClient || typeof mqClient.publish !== 'function') {
|
|
50
|
-
if (logger) {
|
|
51
|
-
logger.warn('Monitoring publish skipped: mqClient not available', context);
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
await mqClient.publish(queueName, message);
|
|
58
|
-
|
|
59
|
-
if (logger) {
|
|
60
|
-
logger.debug(`Published to ${queueName}`, {
|
|
61
|
-
...context,
|
|
62
|
-
event_type: message.event_type,
|
|
63
|
-
workflow_id: message.workflow_id,
|
|
64
|
-
service_name: message.service_name
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return true;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
// Check if this is a queue unavailable error
|
|
71
|
-
if (isQueueUnavailableError(error)) {
|
|
72
|
-
// Queue doesn't exist yet or RabbitMQ is unavailable - log but don't fail
|
|
73
|
-
if (logger) {
|
|
74
|
-
logger.warn(`Monitoring queue ${queueName} not available (queue may not exist yet or RabbitMQ unavailable)`, {
|
|
75
|
-
...context,
|
|
76
|
-
error: error.message,
|
|
77
|
-
event_type: message.event_type,
|
|
78
|
-
workflow_id: message.workflow_id,
|
|
79
|
-
service_name: message.service_name
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Other errors - log as warning but don't fail
|
|
86
|
-
if (logger) {
|
|
87
|
-
logger.warn(`Failed to publish to monitoring queue ${queueName}`, {
|
|
88
|
-
...context,
|
|
89
|
-
error: error.message,
|
|
90
|
-
errorCode: error.code,
|
|
91
|
-
event_type: message.event_type,
|
|
92
|
-
workflow_id: message.workflow_id,
|
|
93
|
-
service_name: message.service_name
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Resilient publish to monitoring.workflow queue
|
|
103
|
-
*
|
|
104
|
-
* @param {Object} mqClient - MQ client instance
|
|
105
|
-
* @param {Object} message - Message with event_type, workflow_id, etc.
|
|
106
|
-
* @param {Object} logger - Logger instance (optional)
|
|
107
|
-
* @param {Object} context - Additional context for logging (optional)
|
|
108
|
-
* @returns {Promise<boolean>}
|
|
109
|
-
*/
|
|
110
|
-
async function publishToMonitoringWorkflow(mqClient, message, logger = null, context = {}) {
|
|
111
|
-
return publishToMonitoringResilient(mqClient, 'monitoring.workflow', message, logger, context);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Resilient publish to monitoring.services queue
|
|
116
|
-
*
|
|
117
|
-
* @param {Object} mqClient - MQ client instance
|
|
118
|
-
* @param {Object} message - Message with event_type, service_name, etc.
|
|
119
|
-
* @param {Object} logger - Logger instance (optional)
|
|
120
|
-
* @param {Object} context - Additional context for logging (optional)
|
|
121
|
-
* @returns {Promise<boolean>}
|
|
122
|
-
*/
|
|
123
|
-
async function publishToMonitoringServices(mqClient, message, logger = null, context = {}) {
|
|
124
|
-
return publishToMonitoringResilient(mqClient, 'monitoring.services', message, logger, context);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = {
|
|
128
|
-
publishToMonitoringResilient,
|
|
129
|
-
publishToMonitoringWorkflow,
|
|
130
|
-
publishToMonitoringServices,
|
|
131
|
-
isQueueUnavailableError
|
|
132
|
-
};
|
|
133
|
-
|