UNPKG

bpmn-elements

Version:

Executable workflow elements based on BPMN 2.0

171 lines (135 loc) 5.24 kB
import { cloneParent, cloneContent } from '../messageHelper.js'; import { getUniqueId } from '../shared.js'; import { EventBroker } from '../EventBroker.js'; import { FlowApi } from '../Api.js'; import { ScriptCondition, ExpressionCondition } from '../condition.js'; const kCounters = Symbol.for('counters'); export default SequenceFlow; function SequenceFlow(flowDef, { environment }) { const { id, type = 'sequenceflow', name, parent, targetId, sourceId, isDefault, behaviour = {} } = flowDef; this.id = id; this.type = type; this.name = name; this.parent = cloneParent(parent); this.behaviour = behaviour; this.sourceId = sourceId; this.targetId = targetId; this.isDefault = isDefault; this.isSequenceFlow = true; this.environment = environment; const logger = (this.logger = environment.Logger(type.toLowerCase())); this[kCounters] = { looped: 0, take: 0, discard: 0, }; const { broker, on, once, waitFor, emitFatal } = new EventBroker(this, { prefix: 'flow', durable: true, autoDelete: false }); this.broker = broker; this.on = on; this.once = once; this.waitFor = waitFor; this.emitFatal = emitFatal; environment.registerScript(this); logger.debug(`<${id}> init, <${sourceId}> -> <${targetId}>`); } Object.defineProperty(SequenceFlow.prototype, 'counters', { get() { return { ...this[kCounters] }; }, }); SequenceFlow.prototype.take = function take(content) { const sequenceId = content?.sequenceId; this.logger.debug(`<${sequenceId} (${this.id})> take, target <${this.targetId}>`); ++this[kCounters].take; this._publishEvent('take', content); return true; }; SequenceFlow.prototype.discard = function discard(content = {}) { const sequenceId = content?.sequenceId ?? getUniqueId(this.id); const discardSequence = (content.discardSequence = (content.discardSequence || []).slice()); if (discardSequence.indexOf(this.targetId) > -1) { ++this[kCounters].looped; this.logger.debug(`<${this.id}> discard loop detected <${this.sourceId}> -> <${this.targetId}>. Stop.`); return this._publishEvent('looped', content); } discardSequence.push(this.sourceId); this.logger.debug(`<${sequenceId} (${this.id})> discard, target <${this.targetId}>`); ++this[kCounters].discard; this._publishEvent('discard', content); }; SequenceFlow.prototype.getState = function getState() { const brokerState = this.broker.getState(true); if (!brokerState && this.environment.settings.disableTrackState) return; return { id: this.id, type: this.type, counters: this.counters, broker: brokerState, }; }; SequenceFlow.prototype.recover = function recover(state) { Object.assign(this[kCounters], state.counters); this.broker.recover(state.broker); }; SequenceFlow.prototype.getApi = function getApi(message) { return FlowApi(this.broker, message || { content: this.createMessage() }); }; SequenceFlow.prototype.stop = function stop() { this.broker.stop(); }; SequenceFlow.prototype.shake = function shake(message) { const content = cloneContent(message.content); content.sequence = content.sequence || []; content.sequence.push({ id: this.id, type: this.type, isSequenceFlow: true, targetId: this.targetId }); if (content.id === this.targetId) return this.broker.publish('event', 'flow.shake.loop', content, { persistent: false, type: 'shake' }); for (const s of message.content.sequence || []) { if (s.id === this.id) return this.broker.publish('event', 'flow.shake.loop', content, { persistent: false, type: 'shake' }); } this.broker.publish('event', 'flow.shake', content, { persistent: false, type: 'shake' }); }; SequenceFlow.prototype.getCondition = function getCondition() { const conditionExpression = this.behaviour.conditionExpression; if (!conditionExpression) return null; const { language } = conditionExpression; const script = this.environment.getScript(language, this); if (script) { return new ScriptCondition(this, script, language); } if (!conditionExpression.body) { const msg = language ? `Condition expression script ${language} is unsupported or was not registered` : 'Condition expression without body is unsupported'; return this.emitFatal(new Error(msg), this.createMessage()); } return new ExpressionCondition(this, conditionExpression.body); }; SequenceFlow.prototype.createMessage = function createMessage(override) { return { ...override, id: this.id, type: this.type, name: this.name, sourceId: this.sourceId, targetId: this.targetId, isSequenceFlow: true, isDefault: this.isDefault, parent: cloneParent(this.parent), }; }; SequenceFlow.prototype.evaluate = function evaluate(fromMessage, callback) { if (this.isDefault) { return callback(null, true); } const flowCondition = this.getCondition(); if (!flowCondition) { return callback(null, true); } flowCondition.execute(fromMessage, callback); }; SequenceFlow.prototype._publishEvent = function publishEvent(action, content) { const eventContent = this.createMessage({ action, ...content, }); this.broker.publish('event', `flow.${action}`, eventContent, { type: action }); };