@onlineapps/service-wrapper 2.0.41 → 2.0.43
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 -2
- package/src/ServiceWrapper.js +140 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.43",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@onlineapps/conn-orch-api-mapper": "^1.0.0",
|
|
32
32
|
"@onlineapps/conn-orch-cookbook": "^2.0.0",
|
|
33
33
|
"@onlineapps/conn-orch-orchestrator": "^1.0.1",
|
|
34
|
-
"@onlineapps/conn-orch-registry": "^1.1.
|
|
34
|
+
"@onlineapps/conn-orch-registry": "^1.1.25",
|
|
35
35
|
"@onlineapps/conn-orch-validator": "^2.0.0",
|
|
36
36
|
"@onlineapps/service-common": "^1.0.0",
|
|
37
37
|
"@onlineapps/monitoring-core": "^1.0.0"
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -98,35 +98,46 @@ class ServiceWrapper {
|
|
|
98
98
|
this.logger?.info('✓ Certificate validated (service is validated and registered), verifying infrastructure readiness...');
|
|
99
99
|
|
|
100
100
|
// Condition 2: Infrastructure must be ready before creating business queues
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
// FÁZE 0.8: verify infrastructure before creating workflow queues
|
|
102
|
+
const workflowQueueStartTime = Date.now();
|
|
103
|
+
this._logPhase('0.8', 'Workflow Queue Setup', 'STARTING');
|
|
104
|
+
|
|
105
105
|
try {
|
|
106
|
+
await this._verifyInfrastructureReady(serviceName);
|
|
107
|
+
|
|
108
|
+
this.logger?.info('✓ Infrastructure verified, creating workflow queues...');
|
|
106
109
|
const createdQueues = await this.mqClient.setupServiceQueues(serviceName, { includeWorkflow: true });
|
|
107
|
-
this.logger?.info('✓
|
|
110
|
+
this.logger?.info('✓ Workflow queues initialized via QueueManager', {
|
|
108
111
|
queues: Object.keys(createdQueues).map((k) => createdQueues[k])
|
|
109
112
|
});
|
|
110
113
|
|
|
111
114
|
const requiredQueues = ['main', 'dlq', 'workflow'];
|
|
112
115
|
const missingQueues = requiredQueues.filter((q) => !createdQueues[q]);
|
|
113
116
|
if (missingQueues.length > 0) {
|
|
114
|
-
throw new Error(`Failed to create all required
|
|
117
|
+
throw new Error(`Failed to create all required workflow queues. Missing: ${missingQueues.join(', ')}`);
|
|
115
118
|
}
|
|
119
|
+
|
|
120
|
+
this._logPhase('0.8', 'Workflow Queue Setup', 'PASSED', null, Date.now() - workflowQueueStartTime);
|
|
116
121
|
} catch (queueError) {
|
|
117
|
-
this.
|
|
122
|
+
this._logPhase('0.8', 'Workflow Queue Setup', 'FAILED', queueError, Date.now() - workflowQueueStartTime);
|
|
123
|
+
this.logger?.error('Failed to initialize workflow queues', {
|
|
118
124
|
error: queueError.message,
|
|
119
125
|
stack: queueError.stack
|
|
120
126
|
});
|
|
121
|
-
throw new Error(`Failed to initialize
|
|
127
|
+
throw new Error(`Failed to initialize workflow queues for ${serviceName}: ${queueError.message}`);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
// FÁZE 0.9: Spuštění workflow listenerů
|
|
131
|
+
const workflowListenerStartTime = Date.now();
|
|
132
|
+
this._logPhase('0.9', 'Workflow Listener Startup', 'STARTING');
|
|
133
|
+
|
|
125
134
|
try {
|
|
126
135
|
await this._startWorkflowInitListener();
|
|
127
136
|
await this._startServiceWorkflowListener();
|
|
128
137
|
this.logger?.info('✓ All workflow listeners started successfully');
|
|
138
|
+
this._logPhase('0.9', 'Workflow Listener Startup', 'PASSED', null, Date.now() - workflowListenerStartTime);
|
|
129
139
|
} catch (listenerError) {
|
|
140
|
+
this._logPhase('0.9', 'Workflow Listener Startup', 'FAILED', listenerError, Date.now() - workflowListenerStartTime);
|
|
130
141
|
this.logger?.error('Failed to start workflow listeners', {
|
|
131
142
|
error: listenerError.message,
|
|
132
143
|
stack: listenerError.stack
|
|
@@ -285,13 +296,102 @@ class ServiceWrapper {
|
|
|
285
296
|
}
|
|
286
297
|
}
|
|
287
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Cleanup all stateful resources before restart
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
async _cleanupBeforeRestart() {
|
|
304
|
+
const serviceName = this.config.service?.name || 'unnamed-service';
|
|
305
|
+
console.log(`[CLEANUP] Starting cleanup for ${serviceName} before restart...`);
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
// 1. Cancel all consumers
|
|
309
|
+
if (this.registryClient?.queueManager?.channel) {
|
|
310
|
+
try {
|
|
311
|
+
// Cancel registry consumer
|
|
312
|
+
if (this.registryClient.queueManager.consumerTag) {
|
|
313
|
+
await this.registryClient.queueManager.channel.cancel(this.registryClient.queueManager.consumerTag);
|
|
314
|
+
console.log(`[CLEANUP] ✓ Canceled registry consumer`);
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.warn(`[CLEANUP] Failed to cancel registry consumer:`, err.message);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 2. Close workflow listeners
|
|
322
|
+
if (this.workflowInitConsumerTag) {
|
|
323
|
+
try {
|
|
324
|
+
if (this.mqClient?.channel) {
|
|
325
|
+
await this.mqClient.channel.cancel(this.workflowInitConsumerTag);
|
|
326
|
+
console.log(`[CLEANUP] ✓ Canceled workflow.init consumer`);
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.warn(`[CLEANUP] Failed to cancel workflow.init consumer:`, err.message);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (this.serviceWorkflowConsumerTag) {
|
|
334
|
+
try {
|
|
335
|
+
if (this.mqClient?.channel) {
|
|
336
|
+
await this.mqClient.channel.cancel(this.serviceWorkflowConsumerTag);
|
|
337
|
+
console.log(`[CLEANUP] ✓ Canceled ${serviceName}.workflow consumer`);
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
console.warn(`[CLEANUP] Failed to cancel ${serviceName}.workflow consumer:`, err.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 3. Close RabbitMQ connections
|
|
345
|
+
if (this.mqClient) {
|
|
346
|
+
try {
|
|
347
|
+
await this.mqClient.disconnect();
|
|
348
|
+
console.log(`[CLEANUP] ✓ Closed RabbitMQ connection`);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
console.warn(`[CLEANUP] Failed to close RabbitMQ connection:`, err.message);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (this.registryClient?.queueManager) {
|
|
355
|
+
try {
|
|
356
|
+
await this.registryClient.queueManager.disconnect();
|
|
357
|
+
console.log(`[CLEANUP] ✓ Closed Registry RabbitMQ connection`);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
console.warn(`[CLEANUP] Failed to close Registry RabbitMQ connection:`, err.message);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 4. Close Redis connection (if used)
|
|
364
|
+
if (this.cacheConnector) {
|
|
365
|
+
try {
|
|
366
|
+
await this.cacheConnector.disconnect();
|
|
367
|
+
console.log(`[CLEANUP] ✓ Closed Redis connection`);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.warn(`[CLEANUP] Failed to close Redis connection:`, err.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 5. Clear state variables
|
|
374
|
+
this.workflowInitConsumerTag = null;
|
|
375
|
+
this.serviceWorkflowConsumerTag = null;
|
|
376
|
+
this.isInitialized = false;
|
|
377
|
+
|
|
378
|
+
console.log(`[CLEANUP] ✓ Cleanup completed`);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error(`[CLEANUP] Error during cleanup:`, error.message);
|
|
381
|
+
// Continue with exit even if cleanup fails
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
288
385
|
/**
|
|
289
386
|
* Handle initialization error with restart strategy
|
|
290
387
|
* @private
|
|
291
388
|
*/
|
|
292
|
-
_handleInitializationError(phase, phaseName, error, isTransient = false) {
|
|
389
|
+
async _handleInitializationError(phase, phaseName, error, isTransient = false) {
|
|
293
390
|
this._logPhase(phase, phaseName, 'FAILED', error);
|
|
294
391
|
|
|
392
|
+
// Cleanup before restart
|
|
393
|
+
await this._cleanupBeforeRestart();
|
|
394
|
+
|
|
295
395
|
if (isTransient) {
|
|
296
396
|
// Přechodná chyba → restart může pomoci
|
|
297
397
|
console.error(`[FÁZE ${phase}] Transient error - service will restart (Docker/process manager)`);
|
|
@@ -358,9 +458,10 @@ class ServiceWrapper {
|
|
|
358
458
|
this._logPhase('0.4', 'Infrastructure Health Check', 'SKIPPED', null, 0);
|
|
359
459
|
}
|
|
360
460
|
|
|
361
|
-
// FÁZE 0.5 a 0.6: Vytvoření
|
|
461
|
+
// FÁZE 0.5 a 0.6: Vytvoření registry fronty a spuštění konzumerů
|
|
362
462
|
// Tyto fáze se provádějí v registryClient.init() během Fáze 0.7
|
|
363
|
-
//
|
|
463
|
+
// Vytváří se pouze {serviceName}.registry fronta (pro registry komunikaci)
|
|
464
|
+
// Workflow fronty se vytvoří až po úspěšné registraci (Fáze 0.8)
|
|
364
465
|
|
|
365
466
|
// FÁZE 0.7: Registrace u Registry (pouze pokud vše výše prošlo)
|
|
366
467
|
if (this.config.wrapper?.registry?.enabled !== false) {
|
|
@@ -880,11 +981,12 @@ class ServiceWrapper {
|
|
|
880
981
|
|
|
881
982
|
try {
|
|
882
983
|
// Subscribe to workflow.init queue (for all services)
|
|
883
|
-
await this.mqClient.consume(queueName, async (message) => {
|
|
984
|
+
const consumerTag = await this.mqClient.consume(queueName, async (message) => {
|
|
884
985
|
await this._processWorkflowMessage(message, queueName);
|
|
885
986
|
});
|
|
987
|
+
this.workflowInitConsumerTag = consumerTag;
|
|
886
988
|
|
|
887
|
-
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ workflow.init listener started successfully for queue: ${queueName}`);
|
|
989
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ workflow.init listener started successfully for queue: ${queueName} (consumerTag: ${consumerTag})`);
|
|
888
990
|
} catch (consumeErr) {
|
|
889
991
|
this.logger?.error(`[ServiceWrapper] [CONSUMER] ✗ Failed to start workflow.init listener for queue: ${queueName}`, {
|
|
890
992
|
error: consumeErr.message,
|
|
@@ -913,11 +1015,12 @@ class ServiceWrapper {
|
|
|
913
1015
|
|
|
914
1016
|
try {
|
|
915
1017
|
// Subscribe to service-specific queue
|
|
916
|
-
await this.mqClient.consume(serviceQueue, async (message) => {
|
|
1018
|
+
const consumerTag = await this.mqClient.consume(serviceQueue, async (message) => {
|
|
917
1019
|
await this._processWorkflowMessage(message, serviceQueue);
|
|
918
1020
|
});
|
|
1021
|
+
this.serviceWorkflowConsumerTag = consumerTag;
|
|
919
1022
|
|
|
920
|
-
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ Service-specific workflow listener started successfully for queue: ${serviceQueue}`);
|
|
1023
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ Service-specific workflow listener started successfully for queue: ${serviceQueue} (consumerTag: ${consumerTag})`);
|
|
921
1024
|
} catch (consumeErr) {
|
|
922
1025
|
this.logger?.error(`[ServiceWrapper] [CONSUMER] ✗ Failed to start service-specific workflow listener for queue: ${serviceQueue}`, {
|
|
923
1026
|
error: consumeErr.message,
|
|
@@ -949,11 +1052,17 @@ class ServiceWrapper {
|
|
|
949
1052
|
const isTest = flags.includes('test');
|
|
950
1053
|
const isTier2Validation = flags.includes('tier2-validation');
|
|
951
1054
|
|
|
952
|
-
|
|
1055
|
+
// Log full message structure for debugging
|
|
1056
|
+
this.logger?.info(`[ServiceWrapper] Processing message from ${queueName}`, {
|
|
953
1057
|
workflow_id: message.workflow_id,
|
|
1058
|
+
current_step: message.current_step,
|
|
1059
|
+
workflowId: message.workflowId, // Gateway sends workflowId, not workflow_id
|
|
954
1060
|
step: message.step?.operation || message.operation,
|
|
955
1061
|
flags,
|
|
956
|
-
isTest
|
|
1062
|
+
isTest,
|
|
1063
|
+
messageKeys: Object.keys(message),
|
|
1064
|
+
hasCookbook: !!message.cookbook,
|
|
1065
|
+
hasContext: !!message.context
|
|
957
1066
|
});
|
|
958
1067
|
|
|
959
1068
|
// Check if this message is for our service
|
|
@@ -973,7 +1082,19 @@ class ServiceWrapper {
|
|
|
973
1082
|
result = await this._executeOperation(message.step.operation, message.input || {});
|
|
974
1083
|
} else if (this.orchestrator) {
|
|
975
1084
|
// Delegate to orchestrator for complex workflow processing
|
|
976
|
-
|
|
1085
|
+
// Normalize message format: Gateway sends workflowId, orchestrator expects workflow_id
|
|
1086
|
+
const normalizedMessage = {
|
|
1087
|
+
...message,
|
|
1088
|
+
workflow_id: message.workflow_id || message.workflowId,
|
|
1089
|
+
current_step: message.current_step || (message.cookbook?.steps?.[0]?.id)
|
|
1090
|
+
};
|
|
1091
|
+
this.logger?.info(`[ServiceWrapper] Calling orchestrator.processWorkflowMessage`, {
|
|
1092
|
+
workflow_id: normalizedMessage.workflow_id,
|
|
1093
|
+
current_step: normalizedMessage.current_step,
|
|
1094
|
+
hasCookbook: !!normalizedMessage.cookbook,
|
|
1095
|
+
hasContext: !!normalizedMessage.context
|
|
1096
|
+
});
|
|
1097
|
+
result = await this.orchestrator.processWorkflowMessage(normalizedMessage, serviceName);
|
|
977
1098
|
} else {
|
|
978
1099
|
throw new Error(`Unknown message format or operation: ${JSON.stringify(message)}`);
|
|
979
1100
|
}
|