@onlineapps/conn-orch-orchestrator 1.0.19 → 1.0.21

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.19",
3
+ "version": "1.0.21",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -75,6 +75,9 @@ class WorkflowOrchestrator {
75
75
  const { workflow_id, cookbook: cookbookDef, current_step, context } = message;
76
76
 
77
77
  try {
78
+ // FAIL-FAST: Validate V2 format BEFORE anything else
79
+ this._validateV2Format(cookbookDef);
80
+
78
81
  // Validate cookbook structure
79
82
  this.cookbook.validateCookbook(cookbookDef);
80
83
 
@@ -89,7 +92,7 @@ class WorkflowOrchestrator {
89
92
  // Find current step (V2: step_id only)
90
93
  const step = cookbookDef.steps.find(s => s.step_id === current_step);
91
94
  if (!step) {
92
- throw new Error(`Step not found: ${current_step}`);
95
+ throw new Error(`Step not found: ${current_step}. Available steps: ${cookbookDef.steps.map(s => s.step_id).join(', ')}`);
93
96
  }
94
97
 
95
98
  // Get step_id (V2)
@@ -300,7 +303,9 @@ class WorkflowOrchestrator {
300
303
 
301
304
  /**
302
305
  * Resolve variable references in input object
303
- * Supports ${steps[N].output.field}, ${api_input.field}, ${context.field}
306
+ * Supports both syntaxes:
307
+ * - {{steps.step_id.output.field}}, {{api_input.field}} (preferred, V2)
308
+ * - ${steps[N].output.field}, ${api_input.field} (legacy)
304
309
  * @private
305
310
  * @param {Object} input - Input object with potential references
306
311
  * @param {Object} context - Current context with steps, api_input, etc
@@ -310,12 +315,39 @@ class WorkflowOrchestrator {
310
315
  if (!input) return input;
311
316
 
312
317
  const resolveValue = (value) => {
313
- if (typeof value === 'string' && value.includes('${')) {
314
- // Replace ${...} references
315
- return value.replace(/\$\{([^}]+)\}/g, (match, path) => {
316
- const resolved = this._getValueFromPath(path.trim(), context);
317
- return resolved !== undefined ? resolved : match;
318
- });
318
+ if (typeof value === 'string') {
319
+ let result = value;
320
+
321
+ // Replace {{...}} references (V2 preferred syntax)
322
+ if (result.includes('{{')) {
323
+ result = result.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
324
+ const resolved = this._getValueFromPath(path.trim(), context);
325
+ if (resolved !== undefined) {
326
+ // If resolved value is an object, stringify it for embedding in strings
327
+ if (typeof resolved === 'object' && result !== match) {
328
+ return JSON.stringify(resolved);
329
+ }
330
+ return resolved;
331
+ }
332
+ return match;
333
+ });
334
+ }
335
+
336
+ // Replace ${...} references (legacy syntax)
337
+ if (result.includes('${')) {
338
+ result = result.replace(/\$\{([^}]+)\}/g, (match, path) => {
339
+ const resolved = this._getValueFromPath(path.trim(), context);
340
+ if (resolved !== undefined) {
341
+ if (typeof resolved === 'object' && result !== match) {
342
+ return JSON.stringify(resolved);
343
+ }
344
+ return resolved;
345
+ }
346
+ return match;
347
+ });
348
+ }
349
+
350
+ return result;
319
351
  }
320
352
  if (Array.isArray(value)) {
321
353
  return value.map(resolveValue);
@@ -568,6 +600,63 @@ class WorkflowOrchestrator {
568
600
  const mapper = new ResponseMapper();
569
601
  return mapper.mapResponse(response, mapping);
570
602
  }
603
+
604
+ /**
605
+ * FAIL-FAST: Validate V2 format strictly
606
+ * Rejects V1 format immediately with clear error message
607
+ * @private
608
+ * @param {Object} cookbook - Cookbook definition
609
+ * @throws {Error} If cookbook is not V2 format
610
+ */
611
+ _validateV2Format(cookbook) {
612
+ if (!cookbook) {
613
+ throw new Error('FAIL-FAST: Cookbook is required');
614
+ }
615
+
616
+ // Check version format
617
+ const version = cookbook.version;
618
+ if (!version) {
619
+ throw new Error('FAIL-FAST: Cookbook version is required');
620
+ }
621
+
622
+ // V1 format detection - REJECT immediately
623
+ if (/^1\.\d+\.\d+$/.test(version)) {
624
+ throw new Error(`FAIL-FAST: V1 format (${version}) is DEPRECATED and no longer supported. Use V2 format (2.x.x) with step_id instead of id.`);
625
+ }
626
+
627
+ // Must be V2 format
628
+ if (!/^2\.\d+\.\d+$/.test(version)) {
629
+ throw new Error(`FAIL-FAST: Invalid version format '${version}'. Must be 2.x.x (e.g., 2.0.0)`);
630
+ }
631
+
632
+ // Check steps
633
+ if (!cookbook.steps || !Array.isArray(cookbook.steps) || cookbook.steps.length === 0) {
634
+ throw new Error('FAIL-FAST: Cookbook must have at least one step');
635
+ }
636
+
637
+ // Validate each step has step_id (V2) not id (V1)
638
+ const invalidSteps = [];
639
+ cookbook.steps.forEach((step, index) => {
640
+ if (step.id && !step.step_id) {
641
+ invalidSteps.push(`Step ${index}: has 'id' (V1) but missing 'step_id' (V2)`);
642
+ }
643
+ if (!step.step_id) {
644
+ invalidSteps.push(`Step ${index}: missing required 'step_id'`);
645
+ }
646
+ if (!step.type) {
647
+ invalidSteps.push(`Step ${index} (${step.step_id || 'unknown'}): missing required 'type'`);
648
+ }
649
+ });
650
+
651
+ if (invalidSteps.length > 0) {
652
+ throw new Error(`FAIL-FAST: Invalid V2 cookbook format:\n${invalidSteps.join('\n')}`);
653
+ }
654
+
655
+ this.logger.info('[WorkflowOrchestrator] V2 format validation passed', {
656
+ version: cookbook.version,
657
+ steps: cookbook.steps.map(s => s.step_id)
658
+ });
659
+ }
571
660
  }
572
661
 
573
662
  module.exports = WorkflowOrchestrator;