@onlineapps/service-wrapper 2.2.7 → 2.2.9
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/jest.config.js +2 -1
- package/package.json +2 -1
- package/src/ServiceWrapper.js +56 -4
- package/src/createTenantContextMiddleware.js +4 -1
- package/src/index.js +11 -2
package/jest.config.js
CHANGED
|
@@ -27,7 +27,8 @@ module.exports = {
|
|
|
27
27
|
'@onlineapps/conn-base-logger': '<rootDir>/tests/mocks/connectors.js',
|
|
28
28
|
'@onlineapps/conn-orch-orchestrator': '<rootDir>/tests/mocks/connectors.js',
|
|
29
29
|
'@onlineapps/conn-orch-api-mapper': '<rootDir>/tests/mocks/connectors.js',
|
|
30
|
-
'@onlineapps/conn-orch-cookbook': '<rootDir>/tests/mocks/connectors.js'
|
|
30
|
+
'@onlineapps/conn-orch-cookbook': '<rootDir>/tests/mocks/connectors.js',
|
|
31
|
+
'@onlineapps/conn-base-state': '<rootDir>/tests/mocks/connectors.js'
|
|
31
32
|
},
|
|
32
33
|
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
|
|
33
34
|
verbose: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.9",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@onlineapps/conn-base-cache": "1.0.9",
|
|
28
|
+
"@onlineapps/conn-base-state": "1.0.1",
|
|
28
29
|
"@onlineapps/conn-base-monitoring": "1.0.10",
|
|
29
30
|
"@onlineapps/conn-infra-error-handler": "1.0.9",
|
|
30
31
|
"@onlineapps/conn-infra-mq": "1.1.69",
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -20,6 +20,7 @@ const OrchestratorConnector = require('@onlineapps/conn-orch-orchestrator');
|
|
|
20
20
|
const ApiMapperConnector = require('@onlineapps/conn-orch-api-mapper');
|
|
21
21
|
const CookbookConnector = require('@onlineapps/conn-orch-cookbook');
|
|
22
22
|
const CacheConnector = require('@onlineapps/conn-base-cache');
|
|
23
|
+
const StateConnector = require('@onlineapps/conn-base-state');
|
|
23
24
|
const ErrorHandlerConnector = require('@onlineapps/conn-infra-error-handler');
|
|
24
25
|
const { ValidationOrchestrator } = require('@onlineapps/conn-orch-validator');
|
|
25
26
|
const runtimeCfg = require('./config');
|
|
@@ -415,13 +416,22 @@ class ServiceWrapper {
|
|
|
415
416
|
}
|
|
416
417
|
}
|
|
417
418
|
|
|
418
|
-
// 4. Close Redis
|
|
419
|
+
// 4. Close Redis connections (cache + state)
|
|
419
420
|
if (this.cacheConnector) {
|
|
420
421
|
try {
|
|
421
422
|
await this.cacheConnector.disconnect();
|
|
422
|
-
console.log(`[CLEANUP] ✓ Closed Redis connection`);
|
|
423
|
+
console.log(`[CLEANUP] ✓ Closed Redis cache connection`);
|
|
423
424
|
} catch (err) {
|
|
424
|
-
console.warn(`[CLEANUP] Failed to close Redis connection:`, err.message);
|
|
425
|
+
console.warn(`[CLEANUP] Failed to close Redis cache connection:`, err.message);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this.stateConnector) {
|
|
430
|
+
try {
|
|
431
|
+
await this.stateConnector.disconnect();
|
|
432
|
+
console.log(`[CLEANUP] ✓ Closed Redis state connection`);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.warn(`[CLEANUP] Failed to close Redis state connection:`, err.message);
|
|
425
435
|
}
|
|
426
436
|
}
|
|
427
437
|
|
|
@@ -574,6 +584,11 @@ class ServiceWrapper {
|
|
|
574
584
|
}
|
|
575
585
|
}
|
|
576
586
|
|
|
587
|
+
// Initialize state connector if configured (critical — let it throw)
|
|
588
|
+
if (this.config.wrapper?.state?.enabled === true) {
|
|
589
|
+
await this._initializeState();
|
|
590
|
+
}
|
|
591
|
+
|
|
577
592
|
// Setup health checks
|
|
578
593
|
if (this.config.wrapper?.health?.enabled !== false) {
|
|
579
594
|
this._setupHealthChecks();
|
|
@@ -1089,6 +1104,37 @@ class ServiceWrapper {
|
|
|
1089
1104
|
this.logger?.info('Cache connector initialized');
|
|
1090
1105
|
}
|
|
1091
1106
|
|
|
1107
|
+
/**
|
|
1108
|
+
* Initialize state connector (persistent Redis state, no TTL)
|
|
1109
|
+
* @private
|
|
1110
|
+
*/
|
|
1111
|
+
async _initializeState() {
|
|
1112
|
+
const stateConfig = this.config.wrapper?.state;
|
|
1113
|
+
const serviceName = stateConfig?.serviceName || this.config.service?.name;
|
|
1114
|
+
if (!serviceName) {
|
|
1115
|
+
throw new Error('[ServiceWrapper] state.serviceName or service.name is required when state is enabled');
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const cacheUrl = this.config.wrapper?.cache?.url;
|
|
1119
|
+
let host, port;
|
|
1120
|
+
if (cacheUrl?.startsWith('redis://')) {
|
|
1121
|
+
const parsed = new URL(cacheUrl);
|
|
1122
|
+
host = parsed.hostname;
|
|
1123
|
+
port = parseInt(parsed.port, 10);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
this.stateConnector = new StateConnector({
|
|
1127
|
+
host: stateConfig?.host || host,
|
|
1128
|
+
port: stateConfig?.port || port,
|
|
1129
|
+
password: stateConfig?.password,
|
|
1130
|
+
db: stateConfig?.db,
|
|
1131
|
+
serviceName,
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
await this.stateConnector.connect();
|
|
1135
|
+
this.logger?.info('State connector initialized');
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1092
1138
|
/**
|
|
1093
1139
|
* Setup health check endpoint
|
|
1094
1140
|
* @private
|
|
@@ -1105,7 +1151,8 @@ class ServiceWrapper {
|
|
|
1105
1151
|
http: 'healthy',
|
|
1106
1152
|
mq: 'disabled',
|
|
1107
1153
|
registry: this.registryClient ? 'healthy' : 'disabled',
|
|
1108
|
-
cache: this.cacheConnector ? 'healthy' : 'disabled'
|
|
1154
|
+
cache: this.cacheConnector ? 'healthy' : 'disabled',
|
|
1155
|
+
state: this.stateConnector ? 'healthy' : 'disabled'
|
|
1109
1156
|
}
|
|
1110
1157
|
};
|
|
1111
1158
|
|
|
@@ -2061,6 +2108,11 @@ class ServiceWrapper {
|
|
|
2061
2108
|
await this.cacheConnector.disconnect();
|
|
2062
2109
|
}
|
|
2063
2110
|
|
|
2111
|
+
// Disconnect from state
|
|
2112
|
+
if (this.stateConnector) {
|
|
2113
|
+
await this.stateConnector.disconnect();
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2064
2116
|
// Disconnect from MQ
|
|
2065
2117
|
if (this.mqClient) {
|
|
2066
2118
|
await this.mqClient.disconnect();
|
|
@@ -54,7 +54,10 @@ function createTenantContextMiddleware(options = {}) {
|
|
|
54
54
|
|
|
55
55
|
function sendJson(res, statusCode, payload) {
|
|
56
56
|
if (!res || typeof res.status !== 'function' || typeof res.json !== 'function') {
|
|
57
|
-
throw new Error(
|
|
57
|
+
throw new Error(
|
|
58
|
+
'[service-wrapper][TenantContext] res.status/res.json unavailable - ' +
|
|
59
|
+
'middleware is likely running before expressInit (check bootstrap stack order)'
|
|
60
|
+
);
|
|
58
61
|
}
|
|
59
62
|
return res.status(statusCode).json(payload);
|
|
60
63
|
}
|
package/src/index.js
CHANGED
|
@@ -93,7 +93,16 @@ async function bootstrap(serviceRoot, options = {}) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
stack.pop();
|
|
96
|
-
|
|
96
|
+
// Insert AFTER expressInit (which sets up res.status/res.json via setPrototypeOf).
|
|
97
|
+
// Position 0 = query, 1 = expressInit. Tenant context must run after both.
|
|
98
|
+
const expressInitIdx = stack.findIndex(l => l.name === 'expressInit');
|
|
99
|
+
if (expressInitIdx === -1) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'[service-wrapper][TenantContext] Express stack missing expressInit layer - ' +
|
|
102
|
+
'cannot guarantee tenant context runs after Express initialization'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
stack.splice(expressInitIdx + 1, 0, lastLayer);
|
|
97
106
|
|
|
98
107
|
// Register /health route BEFORE service's 404 handler.
|
|
99
108
|
// ServiceWrapper._setupHealthChecks() will later set _healthImpl with actual MQ checks.
|
|
@@ -111,7 +120,7 @@ async function bootstrap(serviceRoot, options = {}) {
|
|
|
111
120
|
const healthLayer = stack.pop();
|
|
112
121
|
if (healthLayer) {
|
|
113
122
|
const catchAllIdx = stack.findIndex((layer, i) =>
|
|
114
|
-
i > 0 && !layer.route && !['query', 'expressInit', 'jsonParser', 'urlencodedParser'].includes(layer.name)
|
|
123
|
+
i > 0 && !layer.route && !['query', 'expressInit', 'tenantContextMiddleware', 'jsonParser', 'urlencodedParser'].includes(layer.name)
|
|
115
124
|
);
|
|
116
125
|
if (catchAllIdx > 0) {
|
|
117
126
|
stack.splice(catchAllIdx, 0, healthLayer);
|