@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 +1 -1
- package/src/WorkflowOrchestrator.js +75 -30
package/package.json
CHANGED
|
@@ -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:
|
|
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
|
|
127
|
-
// Each step preserves its definition (
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
137
|
-
existingSteps[
|
|
138
|
-
...stepDefinition, //
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
331
|
-
|
|
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
|
|
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
|
/**
|