@onlineapps/conn-orch-orchestrator 1.0.17 → 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.17",
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 {
@@ -271,13 +293,13 @@ class WorkflowOrchestrator {
271
293
 
272
294
  // Use API mapper to call the service with resolved input
273
295
  const result = await this.apiMapper.callOperation(
274
- step.operation || step.id,
296
+ step.operation || step.step_id || step.id,
275
297
  resolvedInput,
276
298
  context
277
299
  );
278
300
 
279
301
  this.logger.info(`Task step executed`, {
280
- step: step.id,
302
+ step: step.step_id || step.id,
281
303
  service: step.service,
282
304
  operation: step.operation,
283
305
  inputResolved: !!resolvedInput
@@ -322,16 +344,28 @@ class WorkflowOrchestrator {
322
344
  /**
323
345
  * Get value from dot/bracket notation path
324
346
  * @private
325
- * @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"
326
348
  * @param {Object} context - Context object
327
349
  * @returns {*} Resolved value or undefined
328
350
  */
329
351
  _getValueFromPath(path, context) {
330
- // Normalize bracket notation to dot notation: steps[0] -> steps.0
331
- 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
+ }
332
366
 
333
367
  // Strip prefixes - we're already searching in context object
334
- // Supports: ${context.X}, ${api_input.X}, ${steps[N].X}
368
+ // Supports: ${context.X}, ${api_input.X}, ${steps.step_id.X} (V2)
335
369
  if (normalized.startsWith('context.')) {
336
370
  normalized = normalized.substring(8); // Remove 'context.'
337
371
  }
@@ -342,6 +376,16 @@ class WorkflowOrchestrator {
342
376
  let current = context;
343
377
  for (const part of parts) {
344
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
+
345
389
  current = current[part];
346
390
  }
347
391
  return current;
@@ -376,7 +420,7 @@ class WorkflowOrchestrator {
376
420
  const result = await executor.executeStep(step);
377
421
 
378
422
  this.logger.info(`Control flow step executed`, {
379
- step: step.id,
423
+ step: step.step_id || step.id,
380
424
  type: step.type
381
425
  });
382
426
 
@@ -391,12 +435,13 @@ class WorkflowOrchestrator {
391
435
  * @param {Object} cookbookDef - Full cookbook definition
392
436
  * @param {Object} context - Updated context
393
437
  * @param {string} workflow_id - Workflow ID
438
+ * @param {string} nextStepId - Next step step_id (V2) or id (V1 fallback)
394
439
  */
395
- async _routeToNextStep(nextStep, cookbookDef, context, workflow_id) {
440
+ async _routeToNextStep(nextStep, cookbookDef, context, workflow_id, nextStepId) {
396
441
  const message = {
397
442
  workflow_id,
398
443
  cookbook: cookbookDef,
399
- current_step: nextStep.id,
444
+ current_step: nextStepId || (nextStep.step_id || nextStep.id),
400
445
  context
401
446
  };
402
447
 
@@ -405,21 +450,21 @@ class WorkflowOrchestrator {
405
450
  if (this.router?.routeToService) {
406
451
  await this.router.routeToService(nextStep.service, message);
407
452
  this.logger.info(`Routed to service: ${nextStep.service}`, {
408
- step: nextStep.id
453
+ step: nextStepId || (nextStep.step_id || nextStep.id)
409
454
  });
410
455
  } else {
411
456
  // Fallback: publish to service's workflow queue directly
412
457
  const serviceQueue = `${nextStep.service}.workflow`;
413
458
  await this.mqClient.publish(serviceQueue, message);
414
459
  this.logger.info(`Published to queue: ${serviceQueue}`, {
415
- step: nextStep.id
460
+ step: nextStepId || (nextStep.step_id || nextStep.id)
416
461
  });
417
462
  }
418
463
  } else {
419
464
  // Control flow steps handled by workflow init queue
420
465
  await this.mqClient.publish('workflow.init', message);
421
466
  this.logger.info(`Routed control flow to workflow.init`, {
422
- step: nextStep.id
467
+ step: nextStepId || (nextStep.step_id || nextStep.id)
423
468
  });
424
469
  }
425
470
  }
@@ -490,12 +535,12 @@ class WorkflowOrchestrator {
490
535
  const crypto = require('crypto');
491
536
  const data = JSON.stringify({
492
537
  service: step.service,
493
- operation: step.operation || step.id,
538
+ operation: step.operation || step.step_id || step.id,
494
539
  input: step.input,
495
540
  contextInput: context.api_input
496
541
  });
497
542
  const hash = crypto.createHash('sha256').update(data).digest('hex');
498
- return `workflow:step:${step.service}:${step.operation || step.id}:${hash}`;
543
+ return `workflow:step:${step.service}:${step.operation || step.step_id || step.id}:${hash}`;
499
544
  }
500
545
 
501
546
  /**