@onlineapps/conn-orch-orchestrator 1.0.78 → 1.0.80

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-orchestrator",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -21,11 +21,11 @@
21
21
  "author": "OA Drive Team",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@onlineapps/conn-base-monitoring": "1.0.7",
24
+ "@onlineapps/conn-base-monitoring": "1.0.8",
25
25
  "@onlineapps/conn-infra-mq": "1.1.57",
26
- "@onlineapps/conn-orch-registry": "1.1.33",
27
- "@onlineapps/conn-orch-cookbook": "2.0.16",
28
- "@onlineapps/conn-orch-api-mapper": "1.0.25"
26
+ "@onlineapps/conn-orch-registry": "1.1.34",
27
+ "@onlineapps/conn-orch-cookbook": "2.0.17",
28
+ "@onlineapps/conn-orch-api-mapper": "1.0.26"
29
29
  },
30
30
  "devDependencies": {
31
31
  "jest": "^29.5.0",
@@ -45,28 +45,38 @@ class WorkflowOrchestrator {
45
45
  backoffMultiplier: config.retryBackoffMultiplier || 2
46
46
  };
47
47
 
48
- // Create cookbook router - createRouter is an exported function, not a method
48
+ // Router is REQUIRED (no backward-compatibility fallbacks).
49
49
  const { createRouter } = this.cookbook;
50
- if (typeof createRouter === 'function') {
51
- this.router = createRouter(this.mqClient, this.registryClient, {
52
- logger: this.logger
53
- });
54
- } else {
55
- // Fallback: try calling it as method (backward compatibility)
56
- this.router = this.cookbook.createRouter?.(this.mqClient, this.registryClient, {
57
- logger: this.logger
58
- }) || null;
50
+ if (typeof createRouter !== 'function') {
51
+ throw new Error(
52
+ '[WorkflowOrchestrator] Missing dependency - cookbook.createRouter is required. Fix: provide conn-orch-cookbook router factory.'
53
+ );
54
+ }
55
+ this.router = createRouter(this.mqClient, this.registryClient, { logger: this.logger });
56
+ if (!this.router || typeof this.router.routeToService !== 'function') {
57
+ throw new Error(
58
+ '[WorkflowOrchestrator] Invalid dependency - router.routeToService is required. Fix: cookbook.createRouter must return router with routeToService(serviceName, message).'
59
+ );
59
60
  }
60
61
  }
61
62
 
62
63
  /**
63
- * Get step identifier (supports both V1 'id' and V2 'step_id')
64
+ * Get step identifier (V2.1 only: step_id)
64
65
  * @private
65
66
  * @param {Object} step - Step object
66
67
  * @returns {string} Step identifier
67
68
  */
68
69
  _getStepId(step) {
69
- return step?.step_id || step?.id;
70
+ if (!step || typeof step !== 'object') {
71
+ throw new Error('[WorkflowOrchestrator] Invalid cookbook - Expected step object (V2.1)');
72
+ }
73
+ if (step.id && !step.step_id) {
74
+ throw new Error('[WorkflowOrchestrator] Deprecated cookbook format - "id" is not allowed. Fix: use "step_id" (V2.1).');
75
+ }
76
+ if (!step.step_id) {
77
+ throw new Error('[WorkflowOrchestrator] Invalid cookbook - Missing "step_id" on step (V2.1).');
78
+ }
79
+ return step.step_id;
70
80
  }
71
81
 
72
82
  /**
@@ -87,12 +97,15 @@ class WorkflowOrchestrator {
87
97
  if (cookbookDef.steps.length > 0 && cookbookDef.steps[0].id && !cookbookDef.steps[0].step_id) {
88
98
  throw new Error('FAIL-FAST: V1 format (array with "id") is BANNED. Use V2.1 format with "step_id".');
89
99
  }
100
+ // Validate all steps are V2.1 (fail-fast)
101
+ cookbookDef.steps.forEach((s) => this._getStepId(s));
90
102
  return cookbookDef.steps;
91
103
  }
92
104
 
93
- // V2.0 format (deprecated) - steps is an object keyed by step_id
94
- this.logger.warn('Using deprecated V2.0 cookbook format (steps as object). Please migrate to V2.1 (steps as array with step_id).');
95
- return Object.values(cookbookDef.steps);
105
+ // V2.0 format (deprecated) - steps is an object keyed by step_id -> NOT ALLOWED (fail-fast)
106
+ throw new Error(
107
+ '[WorkflowOrchestrator] Deprecated cookbook format - steps must be an ARRAY (V2.1). Fix: migrate from V2.0 object steps to V2.1 array steps.'
108
+ );
96
109
  }
97
110
 
98
111
  /**
@@ -141,8 +154,7 @@ class WorkflowOrchestrator {
141
154
  // Check if this step is for this service
142
155
  if (step.type === 'task' && step.service !== serviceName) {
143
156
  this.logger.warn(`Step ${current_step} is for ${step.service}, not ${serviceName}`);
144
- const serviceQueue = `${step.service}.workflow`;
145
- await this.mqClient.publish(serviceQueue, message);
157
+ await this.router.routeToService(step.service, message);
146
158
  return { skipped: true, reason: 'wrong_service' };
147
159
  }
148
160
 
@@ -150,8 +162,7 @@ class WorkflowOrchestrator {
150
162
  // If a control-flow step has "service", only that service is allowed to execute it.
151
163
  if (step.type !== 'task' && step.service && step.service !== serviceName) {
152
164
  this.logger.warn(`Control-flow step ${current_step} is pinned to ${step.service}, not ${serviceName}`);
153
- const serviceQueue = `${step.service}.workflow`;
154
- await this.mqClient.publish(serviceQueue, message);
165
+ await this.router.routeToService(step.service, message);
155
166
  return { skipped: true, reason: 'wrong_service_control_flow' };
156
167
  }
157
168
 
@@ -427,15 +438,6 @@ class WorkflowOrchestrator {
427
438
  cookbook: finalCookbook, // UNIFIED cookbook with full state
428
439
  failed_at: new Date().toISOString()
429
440
  });
430
-
431
- // Compatibility: also publish to workflow.dlq for DLQ dashboards/tools that only look for "*.dlq" queues.
432
- // workflow.dlq is an infrastructure queue created by Gateway.
433
- await this.mqClient.publish('workflow.dlq', {
434
- workflow_id,
435
- current_step,
436
- cookbook: finalCookbook,
437
- failed_at: new Date().toISOString()
438
- });
439
441
 
440
442
  // ALSO publish to monitoring.workflow so dashboard shows the DLQ entry
441
443
  try {
@@ -772,19 +774,10 @@ class WorkflowOrchestrator {
772
774
  case 'fork_join':
773
775
  return await this._executeForkJoin(step, context, cookbookDef, serviceName);
774
776
  default: {
775
- // Fallback to legacy executor behavior (does NOT execute nested tasks)
776
- const { CookbookExecutor } = this.cookbook;
777
- if (!CookbookExecutor) {
778
- throw new Error(`[WorkflowOrchestrator] CookbookExecutor missing - Cannot execute control-flow step '${stepId}'`);
779
- }
780
-
781
- const executor = new CookbookExecutor(
782
- { steps: [step] },
783
- { logger: this.logger, defaultTimeout: this.defaultTimeout }
777
+ throw new Error(
778
+ `[WorkflowOrchestrator] Unsupported control-flow step type - "${step.type}" in step "${stepId}". ` +
779
+ 'Expected: steps|foreach|switch|fork_join.'
784
780
  );
785
- executor.context.setInput(context.api_input || {});
786
- executor.context.data.steps = context.steps || {};
787
- return await executor.executeStep(step);
788
781
  }
789
782
  }
790
783
  }
@@ -1046,27 +1039,15 @@ class WorkflowOrchestrator {
1046
1039
  throw new Error(`[WorkflowOrchestrator] FAIL-FAST: Task step '${nextStepId}' missing required 'service'`);
1047
1040
  }
1048
1041
  // Route to specific service
1049
- if (this.router?.routeToService) {
1050
- await this.router.routeToService(nextStep.service, message);
1051
- this.logger.info(`Routed to service: ${nextStep.service}`, {
1052
- step: nextStepId
1053
- });
1054
- } else {
1055
- // Backward compatibility: publish to service's workflow queue directly if router is not available
1056
- const serviceQueue = `${nextStep.service}.workflow`;
1057
- await this.mqClient.publish(serviceQueue, message);
1058
- this.logger.info(`Published to queue: ${serviceQueue}`, {
1059
- step: nextStepId
1060
- });
1061
- }
1042
+ await this.router.routeToService(nextStep.service, message);
1043
+ this.logger.info(`Routed to service: ${nextStep.service}`, { step: nextStepId });
1062
1044
  } else {
1063
1045
  // Control flow steps:
1064
1046
  // - if pinned (step.service), route to that service queue
1065
1047
  // - otherwise route to workflow.control (any business service may execute)
1066
1048
  if (nextStep.service) {
1067
- const serviceQueue = `${nextStep.service}.workflow`;
1068
- await this.mqClient.publish(serviceQueue, message);
1069
- this.logger.info(`Routed control flow to pinned service queue: ${serviceQueue}`, { step: nextStepId });
1049
+ await this.router.routeToService(nextStep.service, message);
1050
+ this.logger.info(`Routed control flow to pinned service: ${nextStep.service}`, { step: nextStepId });
1070
1051
  } else {
1071
1052
  await this.mqClient.publish('workflow.control', message);
1072
1053
  this.logger.info(`Routed control flow to workflow.control`, { step: nextStepId });