@onlineapps/service-wrapper 2.0.42 → 2.0.44

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  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.42",
3
+ "version": "2.0.44",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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
- // Phase 8: verify infrastructure before creating business queues
102
- await this._verifyInfrastructureReady(serviceName);
103
-
104
- this.logger?.info('✓ Infrastructure verified, creating business queues...');
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('✓ Business queues initialized via QueueManager', {
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 business queues. Missing: ${missingQueues.join(', ')}`);
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.logger?.error('Failed to initialize business queues', {
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 business queues for ${serviceName}: ${queueError.message}`);
127
+ throw new Error(`Failed to initialize workflow queues for ${serviceName}: ${queueError.message}`);
122
128
  }
123
129
 
124
- this.logger?.info('✓ Starting workflow listeners...');
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í front a spuštění konzumerů
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
- // Logujeme je tam
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
- this.logger?.debug(`Processing message from ${queueName}`, {
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
- result = await this.orchestrator.processWorkflowMessage(message, serviceName);
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
  }