@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 +1 -1
- package/src/WorkflowOrchestrator.js +76 -36
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 {
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
//
|
|
336
|
-
|
|
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
|
|
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
|
/**
|