@onlineapps/conn-orch-orchestrator 1.0.15 → 1.0.18

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.15",
3
+ "version": "1.0.18",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -86,11 +86,14 @@ class WorkflowOrchestrator {
86
86
  delivery: context?.delivery || cookbookDef?.delivery || { handler: 'none' }
87
87
  };
88
88
 
89
- // Find current step
90
- const step = cookbookDef.steps.find(s => s.id === current_step);
89
+ // Find current step (V2: step_id, V1: id - but V1 is deprecated)
90
+ const step = cookbookDef.steps.find(s => (s.step_id || s.id) === current_step);
91
91
  if (!step) {
92
92
  throw new Error(`Step not found: ${current_step}`);
93
93
  }
94
+
95
+ // Get step_id (V2) or id (V1 fallback)
96
+ const stepId = step.step_id || step.id;
94
97
 
95
98
  // Check if this step is for this service
96
99
  if (step.type === 'task' && step.service !== serviceName) {
@@ -112,7 +115,7 @@ class WorkflowOrchestrator {
112
115
  result = await this.cache.get(cacheKey);
113
116
 
114
117
  if (result) {
115
- this.logger.info('Cache hit', { step: step.id, cacheKey });
118
+ this.logger.info('Cache hit', { step: stepId, cacheKey });
116
119
  } else {
117
120
  // Execute the step
118
121
  result = await this._executeStep(step, enrichedContext, cookbookDef);
@@ -123,19 +126,37 @@ class WorkflowOrchestrator {
123
126
  result = await this._executeStep(step, enrichedContext, cookbookDef);
124
127
  }
125
128
 
126
- // Update context with result - steps as ARRAY (consistent with cookbook.steps)
127
- // Each step preserves its definition (id, type, service, operation, input) and adds output
128
- const currentIndex = cookbookDef.steps.findIndex(s => s.id === current_step);
129
+ // Update context with result - steps as OBJECT (V2 format, keyed by step_id)
130
+ // Each step preserves its definition (step_id, type, service, operation, input) and adds output
131
+ const currentIndex = cookbookDef.steps.findIndex(s => (s.step_id || s.id) === current_step);
129
132
  const stepDefinition = cookbookDef.steps[currentIndex];
130
133
 
131
- // Initialize steps array from cookbook if not present
132
- const existingSteps = Array.isArray(enrichedContext.steps)
133
- ? [...enrichedContext.steps]
134
- : cookbookDef.steps.map(s => ({ ...s })); // Deep copy of step definitions
134
+ // Initialize steps object from cookbook if not present
135
+ // V2: steps is an object keyed by step_id, not an array
136
+ let existingSteps = enrichedContext.steps || {};
137
+
138
+ // If steps is still an array (legacy), convert to object
139
+ if (Array.isArray(existingSteps)) {
140
+ existingSteps = {};
141
+ cookbookDef.steps.forEach(s => {
142
+ const sid = s.step_id || s.id;
143
+ if (sid) {
144
+ existingSteps[sid] = { ...s };
145
+ }
146
+ });
147
+ } else if (Object.keys(existingSteps).length === 0) {
148
+ // Initialize from cookbook
149
+ cookbookDef.steps.forEach(s => {
150
+ const sid = s.step_id || s.id;
151
+ if (sid) {
152
+ existingSteps[sid] = { ...s };
153
+ }
154
+ });
155
+ }
135
156
 
136
- // Update the current step with output (preserve id, type, service, operation, input)
137
- existingSteps[currentIndex] = {
138
- ...stepDefinition, // id, type, service, operation, input
157
+ // Update the current step with output (preserve step_id, type, service, operation, input)
158
+ existingSteps[stepId] = {
159
+ ...stepDefinition, // step_id, type, service, operation, input
139
160
  output: result, // Add output from operation
140
161
  status: 'completed',
141
162
  completed_at: new Date().toISOString()
@@ -143,7 +164,7 @@ class WorkflowOrchestrator {
143
164
 
144
165
  const updatedContext = {
145
166
  ...enrichedContext,
146
- steps: existingSteps
167
+ steps: existingSteps // Object keyed by step_id
147
168
  };
148
169
 
149
170
  // Publish workflow.progress AFTER execution (so we have step output)
@@ -177,10 +198,11 @@ class WorkflowOrchestrator {
177
198
 
178
199
  if (nextStep) {
179
200
  // Route to next step
180
- await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id);
201
+ const nextStepId = nextStep.step_id || nextStep.id;
202
+ await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id, nextStepId);
181
203
  } else {
182
204
  // Workflow completed - pass serviceName, cookbook and last step info for monitoring
183
- await this._completeWorkflow(workflow_id, updatedContext, cookbookDef, serviceName, current_step, currentIndex);
205
+ await this._completeWorkflow(workflow_id, updatedContext, cookbookDef, serviceName, stepId, currentIndex);
184
206
  }
185
207
 
186
208
  return {
@@ -266,23 +288,18 @@ class WorkflowOrchestrator {
266
288
  */
267
289
  async _executeTaskStep(step, context) {
268
290
  // 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 keys:`, Object.keys(context || {}));
271
- console.log(`[WorkflowOrchestrator] [RESOLVE] Context.input_file:`, context?.input_file);
272
- console.log(`[WorkflowOrchestrator] [RESOLVE] Context.steps:`, JSON.stringify(context.steps?.map(s => ({ id: s.id, output: s.output }))));
273
-
291
+ // Resolve variable references in step.input (e.g. ${api_input.file}, ${steps[0].output.message})
274
292
  const resolvedInput = this._resolveInputReferences(step.input, context);
275
- console.log(`[WorkflowOrchestrator] [RESOLVE] Step ${step.id} input AFTER:`, JSON.stringify(resolvedInput));
276
293
 
277
294
  // Use API mapper to call the service with resolved input
278
295
  const result = await this.apiMapper.callOperation(
279
- step.operation || step.id,
296
+ step.operation || step.step_id || step.id,
280
297
  resolvedInput,
281
298
  context
282
299
  );
283
300
 
284
301
  this.logger.info(`Task step executed`, {
285
- step: step.id,
302
+ step: step.step_id || step.id,
286
303
  service: step.service,
287
304
  operation: step.operation,
288
305
  inputResolved: !!resolvedInput
@@ -327,16 +344,28 @@ class WorkflowOrchestrator {
327
344
  /**
328
345
  * Get value from dot/bracket notation path
329
346
  * @private
330
- * @param {string} path - Path like "steps[0].output.message" or "api_input.name"
347
+ * @param {string} path - Path like "steps.step_id.output.message" or "api_input.name"
331
348
  * @param {Object} context - Context object
332
349
  * @returns {*} Resolved value or undefined
333
350
  */
334
351
  _getValueFromPath(path, context) {
335
- // Normalize bracket notation to dot notation: steps[0] -> steps.0
336
- let normalized = path.replace(/\[(\d+)\]/g, '.$1');
352
+ // V2: steps is an object keyed by step_id, not an array
353
+ // Support both formats for backward compatibility during migration:
354
+ // - steps.step_id.output.field (V2, preferred)
355
+ // - steps[0].output.field (legacy, deprecated)
356
+
357
+ // Normalize bracket notation to dot notation: steps[0] -> steps.0 (legacy)
358
+ // But prefer step_id access: steps.step_id -> steps.step_id (V2)
359
+ let normalized = path;
360
+
361
+ // Handle legacy array access steps[N] - convert to numeric key
362
+ // But only if steps is actually an array (fallback for old data)
363
+ if (normalized.includes('[') && normalized.includes(']')) {
364
+ normalized = normalized.replace(/\[(\d+)\]/g, '.$1');
365
+ }
337
366
 
338
367
  // Strip prefixes - we're already searching in context object
339
- // Supports: ${context.X}, ${api_input.X}, ${steps[N].X}
368
+ // Supports: ${context.X}, ${api_input.X}, ${steps.step_id.X} (V2)
340
369
  if (normalized.startsWith('context.')) {
341
370
  normalized = normalized.substring(8); // Remove 'context.'
342
371
  }
@@ -347,6 +376,16 @@ class WorkflowOrchestrator {
347
376
  let current = context;
348
377
  for (const part of parts) {
349
378
  if (current === undefined || current === null) return undefined;
379
+
380
+ // Special handling for steps: if accessing by numeric index and steps is object,
381
+ // try to find step by index in cookbook (fallback for legacy)
382
+ if (part === 'steps' && parts.length > 1) {
383
+ current = current.steps;
384
+ // If steps is object (V2), next part is step_id
385
+ // If steps is array (legacy), next part is numeric index
386
+ continue;
387
+ }
388
+
350
389
  current = current[part];
351
390
  }
352
391
  return current;
@@ -381,7 +420,7 @@ class WorkflowOrchestrator {
381
420
  const result = await executor.executeStep(step);
382
421
 
383
422
  this.logger.info(`Control flow step executed`, {
384
- step: step.id,
423
+ step: step.step_id || step.id,
385
424
  type: step.type
386
425
  });
387
426
 
@@ -396,12 +435,13 @@ class WorkflowOrchestrator {
396
435
  * @param {Object} cookbookDef - Full cookbook definition
397
436
  * @param {Object} context - Updated context
398
437
  * @param {string} workflow_id - Workflow ID
438
+ * @param {string} nextStepId - Next step step_id (V2) or id (V1 fallback)
399
439
  */
400
- async _routeToNextStep(nextStep, cookbookDef, context, workflow_id) {
440
+ async _routeToNextStep(nextStep, cookbookDef, context, workflow_id, nextStepId) {
401
441
  const message = {
402
442
  workflow_id,
403
443
  cookbook: cookbookDef,
404
- current_step: nextStep.id,
444
+ current_step: nextStepId || (nextStep.step_id || nextStep.id),
405
445
  context
406
446
  };
407
447
 
@@ -410,21 +450,21 @@ class WorkflowOrchestrator {
410
450
  if (this.router?.routeToService) {
411
451
  await this.router.routeToService(nextStep.service, message);
412
452
  this.logger.info(`Routed to service: ${nextStep.service}`, {
413
- step: nextStep.id
453
+ step: nextStepId || (nextStep.step_id || nextStep.id)
414
454
  });
415
455
  } else {
416
456
  // Fallback: publish to service's workflow queue directly
417
457
  const serviceQueue = `${nextStep.service}.workflow`;
418
458
  await this.mqClient.publish(serviceQueue, message);
419
459
  this.logger.info(`Published to queue: ${serviceQueue}`, {
420
- step: nextStep.id
460
+ step: nextStepId || (nextStep.step_id || nextStep.id)
421
461
  });
422
462
  }
423
463
  } else {
424
464
  // Control flow steps handled by workflow init queue
425
465
  await this.mqClient.publish('workflow.init', message);
426
466
  this.logger.info(`Routed control flow to workflow.init`, {
427
- step: nextStep.id
467
+ step: nextStepId || (nextStep.step_id || nextStep.id)
428
468
  });
429
469
  }
430
470
  }
@@ -495,12 +535,12 @@ class WorkflowOrchestrator {
495
535
  const crypto = require('crypto');
496
536
  const data = JSON.stringify({
497
537
  service: step.service,
498
- operation: step.operation || step.id,
538
+ operation: step.operation || step.step_id || step.id,
499
539
  input: step.input,
500
540
  contextInput: context.api_input
501
541
  });
502
542
  const hash = crypto.createHash('sha256').update(data).digest('hex');
503
- return `workflow:step:${step.service}:${step.operation || step.id}:${hash}`;
543
+ return `workflow:step:${step.service}:${step.operation || step.step_id || step.id}:${hash}`;
504
544
  }
505
545
 
506
546
  /**