bpmn-elements
Version:
Executable workflow elements based on BPMN 2.0
128 lines (104 loc) • 3.71 kB
JavaScript
import { ActivityError } from '../error/Errors.js';
import { cloneMessage } from '../messageHelper.js';
export function OutboundEvaluator(activity, outboundFlows) {
this.activity = activity;
this.broker = activity.broker;
const flows = (this.outboundFlows = outboundFlows.slice());
const defaultFlowIdx = flows.findIndex(({ isDefault }) => isDefault);
if (defaultFlowIdx > -1) {
const [defaultFlow] = flows.splice(defaultFlowIdx, 1);
flows.push(defaultFlow);
}
this._onEvaluated = this.onEvaluated.bind(this);
this.evaluateArgs = {};
}
OutboundEvaluator.prototype.evaluate = function evaluate(fromMessage, discardRestAtTake, callback) {
const outboundFlows = this.outboundFlows;
const args = (this.evaluateArgs = {
fromMessage,
evaluationId: fromMessage.content.executionId,
discardRestAtTake,
callback,
conditionMet: false,
result: {},
takenCount: 0,
});
if (!outboundFlows.length) return this.completed();
const flows = (args.flows = outboundFlows.slice());
this.broker.subscribeTmp('execution', 'evaluate.flow.#', this._onEvaluated, {
consumerTag: `_flow-evaluation-${args.evaluationId}`,
});
return this.evaluateFlow(flows.shift());
};
OutboundEvaluator.prototype.onEvaluated = function onEvaluated(routingKey, message) {
const content = message.content;
const { id: flowId, action, evaluationId } = message.content;
const args = this.evaluateArgs;
if (action === 'take') {
args.takenCount++;
args.conditionMet = true;
}
args.result[flowId] = content;
if ('result' in content) {
this.activity.logger.debug(`<${evaluationId} (${this.activity.id})> flow <${flowId}> evaluated to: ${!!content.result}`);
}
let nextFlow = args.flows.shift();
if (!nextFlow) return this.completed();
if (args.discardRestAtTake && args.conditionMet) {
do {
args.result[nextFlow.id] = formatFlowAction(nextFlow, { action: 'discard' });
} while ((nextFlow = args.flows.shift()));
return this.completed();
}
if (args.conditionMet && nextFlow.isDefault) {
args.result[nextFlow.id] = formatFlowAction(nextFlow, { action: 'discard' });
return this.completed();
}
message.ack();
this.evaluateFlow(nextFlow);
};
OutboundEvaluator.prototype.evaluateFlow = function evaluateFlow(flow) {
const broker = this.broker;
const { fromMessage, evaluationId } = this.evaluateArgs;
flow.evaluate(cloneMessage(fromMessage), (err, result) => {
if (err) return this.completed(err);
const action = result ? 'take' : 'discard';
return broker.publish(
'execution',
'evaluate.flow.' + action,
formatFlowAction(flow, {
action,
result,
evaluationId,
}),
{ persistent: false }
);
});
};
OutboundEvaluator.prototype.completed = function completed(err) {
const { callback, evaluationId, fromMessage, result, takenCount } = this.evaluateArgs;
this.broker.cancel(`_flow-evaluation-${evaluationId}`);
if (err) return callback(err);
if (!takenCount && this.outboundFlows.length) {
const nonTakenError = new ActivityError(`<${this.activity.id}> no conditional flow taken`, fromMessage);
return callback(nonTakenError);
}
const message = fromMessage.content.message;
const evaluationResult = [];
for (const flow of Object.values(result)) {
evaluationResult.push({
...flow,
...(message !== undefined && { message }),
});
}
return callback(null, evaluationResult);
};
export function formatFlowAction(flow, options) {
return {
...options,
id: flow.id,
action: options.action,
targetId: flow.targetId,
...(flow.isDefault && { isDefault: true }),
};
}