@onlineapps/conn-orch-orchestrator 1.0.9 → 1.0.13

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.9",
3
+ "version": "1.0.13",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -37,10 +37,18 @@ class WorkflowOrchestrator {
37
37
  this.logger = config.logger || console;
38
38
  this.defaultTimeout = config.defaultTimeout || 30000;
39
39
 
40
- // Create cookbook router
41
- this.router = this.cookbook.createRouter(this.mqClient, this.registryClient, {
42
- logger: this.logger
43
- });
40
+ // Create cookbook router - createRouter is an exported function, not a method
41
+ const { createRouter } = this.cookbook;
42
+ if (typeof createRouter === 'function') {
43
+ this.router = createRouter(this.mqClient, this.registryClient, {
44
+ logger: this.logger
45
+ });
46
+ } else {
47
+ // Fallback: try calling it as method (backward compatibility)
48
+ this.router = this.cookbook.createRouter?.(this.mqClient, this.registryClient, {
49
+ logger: this.logger
50
+ }) || null;
51
+ }
44
52
  }
45
53
 
46
54
  /**
@@ -87,36 +95,16 @@ class WorkflowOrchestrator {
87
95
  // Check if this step is for this service
88
96
  if (step.type === 'task' && step.service !== serviceName) {
89
97
  this.logger.warn(`Step ${current_step} is for ${step.service}, not ${serviceName}`);
90
- await this.router.routeToService(step.service, message);
98
+ if (this.router?.routeToService) {
99
+ await this.router.routeToService(step.service, message);
100
+ } else {
101
+ // Fallback: publish to service's workflow queue directly
102
+ const serviceQueue = `${step.service}.workflow`;
103
+ await this.mqClient.publish(serviceQueue, message);
104
+ }
91
105
  return { skipped: true, reason: 'wrong_service' };
92
106
  }
93
107
 
94
- // Publish workflow.progress to monitoring.workflow using unified helper
95
- const { publishToMonitoringWorkflow } = require('@onlineapps/mq-client-core').monitoring;
96
- try {
97
- const stepIndex = cookbookDef.steps.findIndex(s => s.id === current_step);
98
- console.log(`[WorkflowOrchestrator] [PROGRESS] Publishing progress for ${workflow_id}, step: ${current_step}, index: ${stepIndex}`);
99
- await publishToMonitoringWorkflow(this.mqClient, {
100
- event_type: 'progress',
101
- workflow_id: workflow_id,
102
- service_name: serviceName,
103
- step_index: stepIndex >= 0 ? stepIndex : 0,
104
- step_id: current_step,
105
- cookbook: cookbookDef,
106
- context: enrichedContext,
107
- status: 'in_progress',
108
- timestamp: new Date().toISOString()
109
- }, this.logger, { workflow_id, step_id: current_step });
110
- console.log(`[WorkflowOrchestrator] [PROGRESS] ✓ Progress published for ${workflow_id}`);
111
- } catch (monitoringError) {
112
- // Don't fail workflow if monitoring publish fails (helper already logs)
113
- console.error(`[WorkflowOrchestrator] [PROGRESS] ✗ Failed to publish progress:`, monitoringError);
114
- this.logger.warn('Failed to publish workflow.progress to monitoring', {
115
- workflow_id,
116
- error: monitoringError.message || String(monitoringError)
117
- });
118
- }
119
-
120
108
  // Check cache if available
121
109
  let result;
122
110
  if (this.cache && step.type === 'task') {
@@ -158,6 +146,32 @@ class WorkflowOrchestrator {
158
146
  steps: existingSteps
159
147
  };
160
148
 
149
+ // Publish workflow.progress AFTER execution (so we have step output)
150
+ const { publishToMonitoringWorkflow } = require('@onlineapps/mq-client-core').monitoring;
151
+ try {
152
+ console.log(`[WorkflowOrchestrator] [PROGRESS] Publishing progress for ${workflow_id}, step: ${current_step}, index: ${currentIndex}`);
153
+ await publishToMonitoringWorkflow(this.mqClient, {
154
+ event_type: 'progress',
155
+ workflow_id: workflow_id,
156
+ service_name: serviceName,
157
+ step_index: currentIndex >= 0 ? currentIndex : 0,
158
+ step_id: current_step,
159
+ cookbook: cookbookDef,
160
+ context: updatedContext, // Context WITH step output
161
+ output: result, // Step output directly
162
+ status: 'completed', // Step completed (not in_progress)
163
+ timestamp: new Date().toISOString()
164
+ }, this.logger, { workflow_id, step_id: current_step });
165
+ console.log(`[WorkflowOrchestrator] [PROGRESS] ✓ Progress published for ${workflow_id}`);
166
+ } catch (monitoringError) {
167
+ // Don't fail workflow if monitoring publish fails
168
+ console.error(`[WorkflowOrchestrator] [PROGRESS] ✗ Failed to publish progress:`, monitoringError);
169
+ this.logger.warn('Failed to publish workflow.progress to monitoring', {
170
+ workflow_id,
171
+ error: monitoringError.message || String(monitoringError)
172
+ });
173
+ }
174
+
161
175
  // Find next step (currentIndex already defined above)
162
176
  const nextStep = cookbookDef.steps[currentIndex + 1];
163
177
 
@@ -251,21 +265,82 @@ class WorkflowOrchestrator {
251
265
  * @returns {Promise<Object>} API call result
252
266
  */
253
267
  async _executeTaskStep(step, context) {
254
- // Use API mapper to call the service
268
+ // Resolve variable references in step.input (e.g. ${steps[0].output.message})
269
+ console.log(`[WorkflowOrchestrator] [RESOLVE] Step ${step.id} input BEFORE:`, JSON.stringify(step.input));
270
+ console.log(`[WorkflowOrchestrator] [RESOLVE] Context.steps:`, JSON.stringify(context.steps?.map(s => ({ id: s.id, output: s.output }))));
271
+
272
+ const resolvedInput = this._resolveInputReferences(step.input, context);
273
+ console.log(`[WorkflowOrchestrator] [RESOLVE] Step ${step.id} input AFTER:`, JSON.stringify(resolvedInput));
274
+
275
+ // Use API mapper to call the service with resolved input
255
276
  const result = await this.apiMapper.callOperation(
256
277
  step.operation || step.id,
257
- step.input,
278
+ resolvedInput,
258
279
  context
259
280
  );
260
281
 
261
282
  this.logger.info(`Task step executed`, {
262
283
  step: step.id,
263
284
  service: step.service,
264
- operation: step.operation
285
+ operation: step.operation,
286
+ inputResolved: !!resolvedInput
265
287
  });
266
288
 
267
289
  return result;
268
290
  }
291
+
292
+ /**
293
+ * Resolve variable references in input object
294
+ * Supports ${steps[N].output.field}, ${api_input.field}, ${context.field}
295
+ * @private
296
+ * @param {Object} input - Input object with potential references
297
+ * @param {Object} context - Current context with steps, api_input, etc
298
+ * @returns {Object} Resolved input object
299
+ */
300
+ _resolveInputReferences(input, context) {
301
+ if (!input) return input;
302
+
303
+ const resolveValue = (value) => {
304
+ if (typeof value === 'string' && value.includes('${')) {
305
+ // Replace ${...} references
306
+ return value.replace(/\$\{([^}]+)\}/g, (match, path) => {
307
+ const resolved = this._getValueFromPath(path.trim(), context);
308
+ return resolved !== undefined ? resolved : match;
309
+ });
310
+ }
311
+ if (Array.isArray(value)) {
312
+ return value.map(resolveValue);
313
+ }
314
+ if (value && typeof value === 'object') {
315
+ return Object.fromEntries(
316
+ Object.entries(value).map(([k, v]) => [k, resolveValue(v)])
317
+ );
318
+ }
319
+ return value;
320
+ };
321
+
322
+ return resolveValue(input);
323
+ }
324
+
325
+ /**
326
+ * Get value from dot/bracket notation path
327
+ * @private
328
+ * @param {string} path - Path like "steps[0].output.message" or "api_input.name"
329
+ * @param {Object} context - Context object
330
+ * @returns {*} Resolved value or undefined
331
+ */
332
+ _getValueFromPath(path, context) {
333
+ // Normalize bracket notation to dot notation: steps[0] -> steps.0
334
+ const normalized = path.replace(/\[(\d+)\]/g, '.$1');
335
+ const parts = normalized.split('.');
336
+
337
+ let current = context;
338
+ for (const part of parts) {
339
+ if (current === undefined || current === null) return undefined;
340
+ current = current[part];
341
+ }
342
+ return current;
343
+ }
269
344
 
270
345
  /**
271
346
  * Execute control flow step (foreach, switch, fork_join, etc)
@@ -322,10 +397,19 @@ class WorkflowOrchestrator {
322
397
 
323
398
  if (nextStep.type === 'task') {
324
399
  // Route to specific service
325
- await this.router.routeToService(nextStep.service, message);
326
- this.logger.info(`Routed to service: ${nextStep.service}`, {
327
- step: nextStep.id
328
- });
400
+ if (this.router?.routeToService) {
401
+ await this.router.routeToService(nextStep.service, message);
402
+ this.logger.info(`Routed to service: ${nextStep.service}`, {
403
+ step: nextStep.id
404
+ });
405
+ } else {
406
+ // Fallback: publish to service's workflow queue directly
407
+ const serviceQueue = `${nextStep.service}.workflow`;
408
+ await this.mqClient.publish(serviceQueue, message);
409
+ this.logger.info(`Published to queue: ${serviceQueue}`, {
410
+ step: nextStep.id
411
+ });
412
+ }
329
413
  } else {
330
414
  // Control flow steps handled by workflow init queue
331
415
  await this.mqClient.publish('workflow.init', message);