@onlineapps/conn-orch-orchestrator 1.0.17 → 1.0.19

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.19",
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 only)
90
+ const step = cookbookDef.steps.find(s => s.step_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)
96
+ const stepId = step.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,27 @@ 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 === 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
+ // Initialize from cookbook if empty
139
+ if (Object.keys(existingSteps).length === 0) {
140
+ cookbookDef.steps.forEach(s => {
141
+ if (s.step_id) {
142
+ existingSteps[s.step_id] = { ...s };
143
+ }
144
+ });
145
+ }
135
146
 
136
- // Update the current step with output (preserve id, type, service, operation, input)
137
- existingSteps[currentIndex] = {
138
- ...stepDefinition, // id, type, service, operation, input
147
+ // Update the current step with output (preserve step_id, type, service, operation, input)
148
+ existingSteps[stepId] = {
149
+ ...stepDefinition, // step_id, type, service, operation, input
139
150
  output: result, // Add output from operation
140
151
  status: 'completed',
141
152
  completed_at: new Date().toISOString()
@@ -143,7 +154,7 @@ class WorkflowOrchestrator {
143
154
 
144
155
  const updatedContext = {
145
156
  ...enrichedContext,
146
- steps: existingSteps
157
+ steps: existingSteps // Object keyed by step_id
147
158
  };
148
159
 
149
160
  // Publish workflow.progress AFTER execution (so we have step output)
@@ -177,10 +188,11 @@ class WorkflowOrchestrator {
177
188
 
178
189
  if (nextStep) {
179
190
  // Route to next step
180
- await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id);
191
+ const nextStepId = nextStep.step_id;
192
+ await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id, nextStepId);
181
193
  } else {
182
194
  // Workflow completed - pass serviceName, cookbook and last step info for monitoring
183
- await this._completeWorkflow(workflow_id, updatedContext, cookbookDef, serviceName, current_step, currentIndex);
195
+ await this._completeWorkflow(workflow_id, updatedContext, cookbookDef, serviceName, stepId, currentIndex);
184
196
  }
185
197
 
186
198
  return {
@@ -271,13 +283,13 @@ class WorkflowOrchestrator {
271
283
 
272
284
  // Use API mapper to call the service with resolved input
273
285
  const result = await this.apiMapper.callOperation(
274
- step.operation || step.id,
286
+ step.operation,
275
287
  resolvedInput,
276
288
  context
277
289
  );
278
290
 
279
291
  this.logger.info(`Task step executed`, {
280
- step: step.id,
292
+ step: step.step_id,
281
293
  service: step.service,
282
294
  operation: step.operation,
283
295
  inputResolved: !!resolvedInput
@@ -322,16 +334,28 @@ class WorkflowOrchestrator {
322
334
  /**
323
335
  * Get value from dot/bracket notation path
324
336
  * @private
325
- * @param {string} path - Path like "steps[0].output.message" or "api_input.name"
337
+ * @param {string} path - Path like "steps.step_id.output.message" or "api_input.name"
326
338
  * @param {Object} context - Context object
327
339
  * @returns {*} Resolved value or undefined
328
340
  */
329
341
  _getValueFromPath(path, context) {
330
- // Normalize bracket notation to dot notation: steps[0] -> steps.0
331
- let normalized = path.replace(/\[(\d+)\]/g, '.$1');
342
+ // V2: steps is an object keyed by step_id, not an array
343
+ // Support both formats for backward compatibility during migration:
344
+ // - steps.step_id.output.field (V2, preferred)
345
+ // - steps[0].output.field (legacy, deprecated)
346
+
347
+ // Normalize bracket notation to dot notation: steps[0] -> steps.0 (legacy)
348
+ // But prefer step_id access: steps.step_id -> steps.step_id (V2)
349
+ let normalized = path;
350
+
351
+ // Handle legacy array access steps[N] - convert to numeric key
352
+ // But only if steps is actually an array (fallback for old data)
353
+ if (normalized.includes('[') && normalized.includes(']')) {
354
+ normalized = normalized.replace(/\[(\d+)\]/g, '.$1');
355
+ }
332
356
 
333
357
  // Strip prefixes - we're already searching in context object
334
- // Supports: ${context.X}, ${api_input.X}, ${steps[N].X}
358
+ // Supports: ${context.X}, ${api_input.X}, ${steps.step_id.X} (V2)
335
359
  if (normalized.startsWith('context.')) {
336
360
  normalized = normalized.substring(8); // Remove 'context.'
337
361
  }
@@ -342,6 +366,16 @@ class WorkflowOrchestrator {
342
366
  let current = context;
343
367
  for (const part of parts) {
344
368
  if (current === undefined || current === null) return undefined;
369
+
370
+ // Special handling for steps: if accessing by numeric index and steps is object,
371
+ // try to find step by index in cookbook (fallback for legacy)
372
+ if (part === 'steps' && parts.length > 1) {
373
+ current = current.steps;
374
+ // If steps is object (V2), next part is step_id
375
+ // If steps is array (legacy), next part is numeric index
376
+ continue;
377
+ }
378
+
345
379
  current = current[part];
346
380
  }
347
381
  return current;
@@ -376,7 +410,7 @@ class WorkflowOrchestrator {
376
410
  const result = await executor.executeStep(step);
377
411
 
378
412
  this.logger.info(`Control flow step executed`, {
379
- step: step.id,
413
+ step: step.step_id,
380
414
  type: step.type
381
415
  });
382
416
 
@@ -391,12 +425,13 @@ class WorkflowOrchestrator {
391
425
  * @param {Object} cookbookDef - Full cookbook definition
392
426
  * @param {Object} context - Updated context
393
427
  * @param {string} workflow_id - Workflow ID
428
+ * @param {string} nextStepId - Next step step_id (V2) or id (V1 fallback)
394
429
  */
395
- async _routeToNextStep(nextStep, cookbookDef, context, workflow_id) {
430
+ async _routeToNextStep(nextStep, cookbookDef, context, workflow_id, nextStepId) {
396
431
  const message = {
397
432
  workflow_id,
398
433
  cookbook: cookbookDef,
399
- current_step: nextStep.id,
434
+ current_step: nextStepId,
400
435
  context
401
436
  };
402
437
 
@@ -405,21 +440,21 @@ class WorkflowOrchestrator {
405
440
  if (this.router?.routeToService) {
406
441
  await this.router.routeToService(nextStep.service, message);
407
442
  this.logger.info(`Routed to service: ${nextStep.service}`, {
408
- step: nextStep.id
443
+ step: nextStepId
409
444
  });
410
445
  } else {
411
446
  // Fallback: publish to service's workflow queue directly
412
447
  const serviceQueue = `${nextStep.service}.workflow`;
413
448
  await this.mqClient.publish(serviceQueue, message);
414
449
  this.logger.info(`Published to queue: ${serviceQueue}`, {
415
- step: nextStep.id
450
+ step: nextStepId
416
451
  });
417
452
  }
418
453
  } else {
419
454
  // Control flow steps handled by workflow init queue
420
455
  await this.mqClient.publish('workflow.init', message);
421
456
  this.logger.info(`Routed control flow to workflow.init`, {
422
- step: nextStep.id
457
+ step: nextStepId
423
458
  });
424
459
  }
425
460
  }
@@ -490,12 +525,12 @@ class WorkflowOrchestrator {
490
525
  const crypto = require('crypto');
491
526
  const data = JSON.stringify({
492
527
  service: step.service,
493
- operation: step.operation || step.id,
528
+ operation: step.operation,
494
529
  input: step.input,
495
530
  contextInput: context.api_input
496
531
  });
497
532
  const hash = crypto.createHash('sha256').update(data).digest('hex');
498
- return `workflow:step:${step.service}:${step.operation || step.id}:${hash}`;
533
+ return `workflow:step:${step.service}:${step.operation}:${hash}`;
499
534
  }
500
535
 
501
536
  /**