@onlineapps/service-wrapper 3.2.1 → 3.3.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/package.json +3 -3
- package/src/ServiceWrapper.js +10 -186
- package/src/index.js +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@onlineapps/conn-infra-secrets": "1.0.0",
|
|
33
33
|
"@onlineapps/conn-orch-cookbook": "2.1.3",
|
|
34
34
|
"@onlineapps/conn-orch-orchestrator": "2.1.1",
|
|
35
|
-
"@onlineapps/conn-orch-registry": "1.2.
|
|
36
|
-
"@onlineapps/conn-orch-validator": "3.
|
|
35
|
+
"@onlineapps/conn-orch-registry": "1.2.2",
|
|
36
|
+
"@onlineapps/conn-orch-validator": "3.2.0",
|
|
37
37
|
"@onlineapps/monitoring-core": "1.0.24",
|
|
38
38
|
"@onlineapps/runtime-config": "1.0.2",
|
|
39
39
|
"@onlineapps/service-common": "1.1.3",
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -802,11 +802,11 @@ class ServiceWrapper {
|
|
|
802
802
|
// Konfigurace se načítá v konstruktoru, takže tady jen logujeme
|
|
803
803
|
this._logPhase('0.1', 'Configuration Load', 'PASSED', null, Date.now() - startTime);
|
|
804
804
|
|
|
805
|
-
// FÁZE 0.1b:
|
|
806
|
-
//
|
|
807
|
-
//
|
|
805
|
+
// FÁZE 0.1b: Operations→Handler alignment is validated by
|
|
806
|
+
// _validateOperationsHandlerAlignment(), invoked from the v3 pipeline
|
|
807
|
+
// block below (after connectors are ready).
|
|
808
808
|
// See RFC api/docs/architecture/biz-service-invocation-model.md §5.9 step 5.
|
|
809
|
-
this._logPhase('0.1b', 'Operations-
|
|
809
|
+
this._logPhase('0.1b', 'Operations-Handler Alignment', 'DEFERRED', null, 0);
|
|
810
810
|
|
|
811
811
|
// FÁZE 0.15: Runtime integration connector contract (health + readiness source of truth)
|
|
812
812
|
if (this.config.wrapper?.health?.enabled !== false) {
|
|
@@ -1791,9 +1791,8 @@ class ServiceWrapper {
|
|
|
1791
1791
|
|
|
1792
1792
|
const timeout = this.config.wrapper?.timeout || 30000;
|
|
1793
1793
|
|
|
1794
|
-
//
|
|
1795
|
-
// (intra-service handler dispatch, RFC §5.9, §5.10)
|
|
1796
|
-
// through the retired conn-orch-api-mapper.
|
|
1794
|
+
// Orchestrator dispatches via service-wrapper.invokeOperation
|
|
1795
|
+
// (intra-service handler dispatch, RFC §5.9, §5.10).
|
|
1797
1796
|
this.orchestrator = OrchestratorConnector.create({
|
|
1798
1797
|
mqClient: this.mqClient,
|
|
1799
1798
|
registryClient: this.registryClient,
|
|
@@ -2017,8 +2016,7 @@ class ServiceWrapper {
|
|
|
2017
2016
|
// Process based on message type
|
|
2018
2017
|
let result;
|
|
2019
2018
|
if (message.operation && this.operations?.operations?.[message.operation]) {
|
|
2020
|
-
// v3 handler-registry dispatch.
|
|
2021
|
-
// _executeOperation HTTP-loopback path.
|
|
2019
|
+
// v3 handler-registry dispatch.
|
|
2022
2020
|
// See api/docs/architecture/biz-service-invocation-model.md §5.9.
|
|
2023
2021
|
result = await this._dispatchViaInvokeOperation(message, message.operation);
|
|
2024
2022
|
} else if (message.step?.operation && this.operations?.operations?.[message.step.operation]) {
|
|
@@ -2121,9 +2119,8 @@ class ServiceWrapper {
|
|
|
2121
2119
|
|
|
2122
2120
|
/**
|
|
2123
2121
|
* Adapt an MQ workflow message into the v3 invokeOperation contract,
|
|
2124
|
-
* invoke it, and unwrap the returned envelope
|
|
2125
|
-
*
|
|
2126
|
-
* on error) so the surrounding _processWorkflowMessage flow is unchanged.
|
|
2122
|
+
* invoke it, and unwrap the returned envelope (result object on success,
|
|
2123
|
+
* throws on error) so the surrounding _processWorkflowMessage flow is unchanged.
|
|
2127
2124
|
*
|
|
2128
2125
|
* See RFC §5.9 for envelope shape.
|
|
2129
2126
|
* @private
|
|
@@ -2165,82 +2162,6 @@ class ServiceWrapper {
|
|
|
2165
2162
|
throw err;
|
|
2166
2163
|
}
|
|
2167
2164
|
|
|
2168
|
-
/**
|
|
2169
|
-
* Execute operation by calling HTTP endpoint (v2 HTTP-loopback path).
|
|
2170
|
-
*
|
|
2171
|
-
* v2 legacy — no longer invoked from the MQ dispatch path; retained for
|
|
2172
|
-
* any downstream caller until P5.1 cleanup.
|
|
2173
|
-
*
|
|
2174
|
-
* @private
|
|
2175
|
-
*/
|
|
2176
|
-
async _executeOperation(operationName, input) {
|
|
2177
|
-
const operation = this.operations?.operations?.[operationName];
|
|
2178
|
-
if (!operation) {
|
|
2179
|
-
throw new Error(`Unknown operation: ${operationName}`);
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
const serviceUrl = this.config.service?.url;
|
|
2183
|
-
if (!serviceUrl) {
|
|
2184
|
-
throw new Error('[ServiceWrapper] Missing configuration - service.url is required');
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
const url = new URL(operation.endpoint, serviceUrl).toString();
|
|
2188
|
-
const method = operation.method || 'POST';
|
|
2189
|
-
|
|
2190
|
-
this.logger?.debug(`Executing operation ${operationName} via ${method} ${url}`);
|
|
2191
|
-
|
|
2192
|
-
// Make HTTP request using fetch (Node.js 18+) or http module
|
|
2193
|
-
const http = require('http');
|
|
2194
|
-
|
|
2195
|
-
return new Promise((resolve, reject) => {
|
|
2196
|
-
const postData = JSON.stringify(input);
|
|
2197
|
-
|
|
2198
|
-
const target = new URL(url);
|
|
2199
|
-
const targetPort = target.port
|
|
2200
|
-
? parseInt(target.port, 10)
|
|
2201
|
-
: (target.protocol === 'https:' ? 443 : 80);
|
|
2202
|
-
|
|
2203
|
-
const options = {
|
|
2204
|
-
hostname: target.hostname,
|
|
2205
|
-
port: targetPort,
|
|
2206
|
-
path: `${target.pathname}${target.search}`,
|
|
2207
|
-
method: method,
|
|
2208
|
-
headers: {
|
|
2209
|
-
'Content-Type': 'application/json',
|
|
2210
|
-
'Content-Length': Buffer.byteLength(postData)
|
|
2211
|
-
}
|
|
2212
|
-
};
|
|
2213
|
-
|
|
2214
|
-
const req = http.request(options, (res) => {
|
|
2215
|
-
let data = '';
|
|
2216
|
-
|
|
2217
|
-
res.on('data', (chunk) => {
|
|
2218
|
-
data += chunk;
|
|
2219
|
-
});
|
|
2220
|
-
|
|
2221
|
-
res.on('end', () => {
|
|
2222
|
-
try {
|
|
2223
|
-
const result = JSON.parse(data);
|
|
2224
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
2225
|
-
resolve(result);
|
|
2226
|
-
} else {
|
|
2227
|
-
reject(new Error(`Operation failed: ${result.error || data}`));
|
|
2228
|
-
}
|
|
2229
|
-
} catch (error) {
|
|
2230
|
-
reject(new Error(`Invalid response: ${data}`));
|
|
2231
|
-
}
|
|
2232
|
-
});
|
|
2233
|
-
});
|
|
2234
|
-
|
|
2235
|
-
req.on('error', (error) => {
|
|
2236
|
-
reject(error);
|
|
2237
|
-
});
|
|
2238
|
-
|
|
2239
|
-
req.write(postData);
|
|
2240
|
-
req.end();
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
2165
|
/**
|
|
2245
2166
|
* Extract all registered Express routes from the app router stack.
|
|
2246
2167
|
* Handles nested Router instances (e.g. app.use('/api', router)).
|
|
@@ -2347,7 +2268,7 @@ class ServiceWrapper {
|
|
|
2347
2268
|
}
|
|
2348
2269
|
|
|
2349
2270
|
/**
|
|
2350
|
-
*
|
|
2271
|
+
* Alignment validator for the v3 handler-registry.
|
|
2351
2272
|
*
|
|
2352
2273
|
* Cross-checks that every operation declared in operations.json has a
|
|
2353
2274
|
* registered handler in HandlerRegistry, and that no handler is registered
|
|
@@ -2434,103 +2355,6 @@ class ServiceWrapper {
|
|
|
2434
2355
|
}
|
|
2435
2356
|
}
|
|
2436
2357
|
|
|
2437
|
-
/**
|
|
2438
|
-
* Bidirectional validation: operations.json <-> Express routes.
|
|
2439
|
-
* Check A: every declared operation has a matching Express route.
|
|
2440
|
-
* Check B: every /api/* route has a matching operation or is in _exclude.
|
|
2441
|
-
*
|
|
2442
|
-
* v2 legacy — no longer invoked; scheduled for removal in P5.1.
|
|
2443
|
-
*
|
|
2444
|
-
* @private
|
|
2445
|
-
* @throws {Error} Permanent error listing all mismatches
|
|
2446
|
-
*/
|
|
2447
|
-
_validateOperationsRouteAlignment() {
|
|
2448
|
-
const ops = this.operations?.operations || {};
|
|
2449
|
-
const excludeEntries = this.operations?._exclude || [];
|
|
2450
|
-
const expressRoutes = this._extractExpressRoutes();
|
|
2451
|
-
|
|
2452
|
-
const routeSet = new Set(
|
|
2453
|
-
expressRoutes.map(r => `${r.method}:${r.path}`)
|
|
2454
|
-
);
|
|
2455
|
-
|
|
2456
|
-
const errors = [];
|
|
2457
|
-
|
|
2458
|
-
// Check A: every operation endpoint+method must exist as an Express route
|
|
2459
|
-
for (const [opName, opDef] of Object.entries(ops)) {
|
|
2460
|
-
const method = (opDef.method || 'POST').toUpperCase();
|
|
2461
|
-
const endpoint = opDef.endpoint;
|
|
2462
|
-
if (!endpoint) continue;
|
|
2463
|
-
|
|
2464
|
-
if (!routeSet.has(`${method}:${endpoint}`)) {
|
|
2465
|
-
errors.push(`Operation '${opName}' endpoint ${method} ${endpoint} not found in Express routes`);
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
// Build lookup: endpoint -> set of operation methods
|
|
2470
|
-
const opEndpoints = new Map();
|
|
2471
|
-
for (const opDef of Object.values(ops)) {
|
|
2472
|
-
const method = (opDef.method || 'POST').toUpperCase();
|
|
2473
|
-
const endpoint = opDef.endpoint;
|
|
2474
|
-
if (!endpoint) continue;
|
|
2475
|
-
if (!opEndpoints.has(endpoint)) opEndpoints.set(endpoint, new Set());
|
|
2476
|
-
opEndpoints.get(endpoint).add(method);
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
|
-
// Build _exclude lookup
|
|
2480
|
-
const excludeSet = new Set(
|
|
2481
|
-
excludeEntries.map(e => `${(e.method || 'GET').toUpperCase()}:${e.endpoint}`)
|
|
2482
|
-
);
|
|
2483
|
-
|
|
2484
|
-
// Check B: every /api/* route must have a matching operation or be in _exclude
|
|
2485
|
-
for (const route of expressRoutes) {
|
|
2486
|
-
if (!route.path.startsWith('/api')) continue;
|
|
2487
|
-
|
|
2488
|
-
const key = `${route.method}:${route.path}`;
|
|
2489
|
-
if (excludeSet.has(key)) continue;
|
|
2490
|
-
|
|
2491
|
-
const endpointOps = opEndpoints.get(route.path);
|
|
2492
|
-
if (!endpointOps) {
|
|
2493
|
-
errors.push(`Route ${route.method} ${route.path} has no matching operation in operations.json`);
|
|
2494
|
-
continue;
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
if (!endpointOps.has(route.method)) {
|
|
2498
|
-
// Tolerate GET alongside a declared POST (common browser-test pattern)
|
|
2499
|
-
if (route.method === 'GET' && endpointOps.size > 0) continue;
|
|
2500
|
-
errors.push(`Route ${route.method} ${route.path} has no matching operation in operations.json`);
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
|
-
// Extension 1: specificationEndpoint must match an Express route
|
|
2505
|
-
// See: docs/standards/api-versioning-contract.md
|
|
2506
|
-
const specEndpoint = this.config.service?.specificationEndpoint;
|
|
2507
|
-
if (specEndpoint && !routeSet.has(`GET:${specEndpoint}`)) {
|
|
2508
|
-
errors.push(
|
|
2509
|
-
`specificationEndpoint '${specEndpoint}' not found in Express routes — ` +
|
|
2510
|
-
`config claims this path but no GET route is registered`
|
|
2511
|
-
);
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
|
-
// Extension 3: operations.json endpoints under /api/ must be versioned
|
|
2515
|
-
// See: docs/standards/api-versioning-contract.md
|
|
2516
|
-
const versionPattern = /^\/api\/v\d+\//;
|
|
2517
|
-
for (const [opName, opDef] of Object.entries(ops)) {
|
|
2518
|
-
if (opDef.endpoint?.startsWith('/api') && !versionPattern.test(opDef.endpoint)) {
|
|
2519
|
-
errors.push(
|
|
2520
|
-
`Operation '${opName}' endpoint '${opDef.endpoint}' missing version — ` +
|
|
2521
|
-
`expected /api/v{N}/... (see docs/standards/api-versioning-contract.md)`
|
|
2522
|
-
);
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
if (errors.length > 0) {
|
|
2527
|
-
throw new Error(
|
|
2528
|
-
`[ServiceWrapper] Operations-Routes alignment failed (${errors.length} issue${errors.length > 1 ? 's' : ''}):\n` +
|
|
2529
|
-
errors.map(e => ` - ${e}`).join('\n')
|
|
2530
|
-
);
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
2358
|
/**
|
|
2535
2359
|
* Delete local validation proof and trigger re-validation.
|
|
2536
2360
|
* Called when Registry sends a revalidation request (infra version change).
|
package/src/index.js
CHANGED
|
@@ -18,9 +18,10 @@ const runtimeCfg = require('./config');
|
|
|
18
18
|
const pkg = require('../package.json');
|
|
19
19
|
const { createTenantContextMiddleware } = require('./createTenantContextMiddleware');
|
|
20
20
|
|
|
21
|
-
// Note: WorkflowProcessor
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// Note: WorkflowProcessor functionality has been moved to
|
|
22
|
+
// @onlineapps/conn-orch-orchestrator. The former ApiCaller /
|
|
23
|
+
// @onlineapps/conn-orch-api-mapper was retired 2026-04-21 (ADR-0001);
|
|
24
|
+
// per-operation dispatch happens in-process via HandlerRegistry.
|
|
24
25
|
|
|
25
26
|
// Note: HTTP logging middleware is now in each service's middlewares/httpLogging.js
|
|
26
27
|
// It uses lazy logger access via lib/logger.js (set after wrapper.initialize())
|