UNPKG

bpmn-elements

Version:

Executable workflow elements based on BPMN 2.0

518 lines (517 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Definition = Definition; exports.default = void 0; var _DefinitionExecution = _interopRequireDefault(require("./DefinitionExecution.js")); var _Api = require("../Api.js"); var _EventBroker = require("../EventBroker.js"); var _shared = require("../shared.js"); var _Errors = require("../error/Errors.js"); var _messageHelper = require("../messageHelper.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const kConsuming = Symbol.for('consuming'); const kCounters = Symbol.for('counters'); const kExec = Symbol.for('execution'); const kExecuteMessage = Symbol.for('executeMessage'); const kMessageHandlers = Symbol.for('messageHandlers'); const kStateMessage = Symbol.for('stateMessage'); const kStatus = Symbol.for('status'); const kStopped = Symbol.for('stopped'); var _default = exports.default = Definition; function Definition(context, options) { if (!(this instanceof Definition)) return new Definition(context, options); if (!context) throw new Error('No context'); const { id, name, type = 'definition' } = context; this.id = id; this.type = type; this.name = name; let environment; if (options) { environment = this.environment = context.environment.clone(options); this.context = context.clone(environment); } else { environment = this.environment = context.environment; this.context = context; } this[kCounters] = { completed: 0, discarded: 0 }; this[kStopped] = false; this[kExec] = new Map(); const onBrokerReturn = this._onBrokerReturnFn.bind(this); this[kMessageHandlers] = { onBrokerReturn, onApiMessage: this._onApiMessage.bind(this), onRunMessage: this._onRunMessage.bind(this), onExecutionMessage: this._onExecutionMessage.bind(this) }; const { broker, on, once, waitFor, emit, emitFatal } = (0, _EventBroker.DefinitionBroker)(this, onBrokerReturn); this.broker = broker; this.on = on; this.once = once; this.waitFor = waitFor; this.emit = emit; this.emitFatal = emitFatal; this.logger = environment.Logger(type.toLowerCase()); } Object.defineProperties(Definition.prototype, { counters: { get() { return { ...this[kCounters] }; } }, execution: { get() { return this[kExec].get('execution'); } }, executionId: { get() { return this[kExec].get('executionId'); } }, isRunning: { get() { if (!this[kConsuming]) return false; return !!this.status; } }, status: { get() { return this[kStatus]; } }, stopped: { get() { return this[kStopped]; } }, activityStatus: { get() { const execution = this[kExec].get('execution'); return execution?.activityStatus || 'idle'; } } }); Definition.prototype.run = function run(optionsOrCallback, optionalCallback) { const [runOptions, callback] = (0, _shared.getOptionsAndCallback)(optionsOrCallback, optionalCallback); if (this.isRunning) { const err = new Error('definition is already running'); if (callback) return callback(err); throw err; } if (callback) { addConsumerCallbacks(this, callback); } const exec = this[kExec]; const executionId = (0, _shared.getUniqueId)(this.id); exec.set('executionId', executionId); const content = this._createMessage({ ...runOptions }); const broker = this.broker; broker.publish('run', 'run.enter', content); broker.publish('run', 'run.start', (0, _messageHelper.cloneContent)(content)); broker.publish('run', 'run.execute', (0, _messageHelper.cloneContent)(content)); this.logger.debug(`<${executionId} (${this.id})> run`); this._activateRunConsumers(); return this; }; Definition.prototype.resume = function resume(callback) { if (this.isRunning) { const err = new Error('cannot resume running definition'); if (callback) return callback(err); throw err; } this[kStopped] = false; if (!this.status) return this; if (callback) { addConsumerCallbacks(this, callback); } this.logger.debug(`<${this.executionId} (${this.id})> resume`); const content = this._createMessage(); this.broker.publish('run', 'run.resume', content, { persistent: false }); this._activateRunConsumers(); return this; }; Definition.prototype.getState = function getState() { return this._createMessage({ status: this.status, stopped: this.stopped, counters: this.counters, environment: this.environment.getState(), execution: this.execution?.getState(), broker: this.broker.getState(true) }); }; Definition.prototype.recover = function recover(state) { if (this.isRunning) throw new Error('cannot recover running definition'); if (!state) return this; this[kStopped] = !!state.stopped; this[kStatus] = state.status; const exec = this[kExec]; exec.set('executionId', state.executionId); if (state.counters) { this[kCounters] = { ...this[kCounters], ...state.counters }; } this.environment.recover(state.environment); if (state.execution) { exec.set('execution', new _DefinitionExecution.default(this, this.context).recover(state.execution)); } this.broker.recover(state.broker); return this; }; Definition.prototype.shake = function shake(startId) { let result = {}; let bps; if (startId) { const startActivity = this.getActivityById(startId); if (!startActivity) return; const bp = this.getProcessById(startActivity.parent.id); if (!bp) return; bps = [bp]; } else bps = this.getProcesses(); bps.forEach(bp => { result = { ...result, ...this._shakeProcess(bp, startId) }; }); return result; }; Definition.prototype._shakeProcess = function shakeProcess(shakeBp, startId) { let shovel; if (!shakeBp.isRunning) { shovel = shakeBp.broker.createShovel('shaker', { exchange: 'event', pattern: '*.shake#' }, { broker: this.broker, exchange: 'event' }); } const shakeResult = shakeBp.shake(startId); if (shovel) shakeBp.broker.closeShovel('shaker'); return shakeResult; }; Definition.prototype.getProcesses = function getProcesses() { const execution = this.execution; if (execution) return execution.getProcesses(); return this.context.getProcesses(); }; Definition.prototype.getExecutableProcesses = function getExecutableProcesses() { const execution = this.execution; if (execution) return execution.getExecutableProcesses(); return this.context.getExecutableProcesses(); }; Definition.prototype.getRunningProcesses = function getRunningProcesses() { const execution = this.execution; if (!execution) return []; return execution.getRunningProcesses(); }; Definition.prototype.getProcessById = function getProcessById(processId) { return this.getProcesses().find(p => p.id === processId); }; Definition.prototype.getActivityById = function getActivityById(childId) { const bps = this.getProcesses(); for (const bp of bps) { const child = bp.getActivityById(childId); if (child) return child; } return null; }; Definition.prototype.getElementById = function getElementById(elementId) { return this.context.getActivityById(elementId); }; Definition.prototype.getPostponed = function getPostponed(...args) { const execution = this.execution; if (!execution) return []; return execution.getPostponed(...args); }; Definition.prototype.getApi = function getApi(message) { const execution = this.execution; if (execution) return execution.getApi(message); message = message || this[kStateMessage]; if (!message) throw new Error('Definition is not running'); return (0, _Api.DefinitionApi)(this.broker, message); }; Definition.prototype.signal = function signal(message) { return this.getApi().signal(message, { delegate: true }); }; Definition.prototype.cancelActivity = function cancelActivity(message) { return this.getApi().cancel(message, { delegate: true }); }; Definition.prototype.sendMessage = function sendMessage(message) { const messageContent = { message }; let messageType = 'message'; const reference = message?.id && this.getElementById(message.id); if (reference?.resolve) { const resolvedReference = reference.resolve(this._createMessage({ message })); messageType = resolvedReference.messageType || messageType; messageContent.message = { ...message, ...resolvedReference }; } return this.getApi().sendApiMessage(messageType, messageContent, { delegate: true }); }; Definition.prototype.stop = function stop() { if (!this.isRunning) return; this.getApi().stop(); }; Definition.prototype._activateRunConsumers = function activateRunConsumers() { this[kConsuming] = true; const broker = this.broker; const { onApiMessage, onRunMessage } = this[kMessageHandlers]; broker.subscribeTmp('api', `definition.*.${this.executionId}`, onApiMessage, { noAck: true, consumerTag: '_definition-api' }); broker.getQueue('run-q').assertConsumer(onRunMessage, { exclusive: true, consumerTag: '_definition-run' }); }; Definition.prototype._deactivateRunConsumers = function deactivateRunConsumers() { const broker = this.broker; broker.cancel('_definition-api'); broker.cancel('_definition-run'); broker.cancel('_definition-execution'); this[kConsuming] = false; }; Definition.prototype._createMessage = function createMessage(override) { return { id: this.id, type: this.type, name: this.name, executionId: this.executionId, ...override }; }; Definition.prototype._onRunMessage = function onRunMessage(routingKey, message) { const { content, fields } = message; if (routingKey === 'run.resume') { return this._onResumeMessage(message); } const exec = this[kExec]; this[kStateMessage] = message; switch (routingKey) { case 'run.enter': { this.logger.debug(`<${this.executionId} (${this.id})> enter`); this[kStatus] = 'entered'; if (fields.redelivered) break; exec.delete('execution'); this._publishEvent('enter', content); break; } case 'run.start': { this.logger.debug(`<${this.executionId} (${this.id})> start`); this[kStatus] = 'start'; this._publishEvent('start', content); break; } case 'run.execute': { this[kStatus] = 'executing'; const executeMessage = (0, _messageHelper.cloneMessage)(message); let execution = exec.get('execution'); if (fields.redelivered && !execution) { executeMessage.fields.redelivered = undefined; } this[kExecuteMessage] = message; this.broker.getQueue('execution-q').assertConsumer(this[kMessageHandlers].onExecutionMessage, { exclusive: true, consumerTag: '_definition-execution' }); if (!execution) { execution = new _DefinitionExecution.default(this, this.context); exec.set('execution', execution); } if (executeMessage.fields.redelivered) { this._publishEvent('resume', content); } return execution.execute(executeMessage); } case 'run.end': { if (this[kStatus] === 'end') break; this[kCounters].completed++; this.logger.debug(`<${this.executionId} (${this.id})> completed`); this[kStatus] = 'end'; this.broker.publish('run', 'run.leave', content); this._publishEvent('end', content); break; } case 'run.error': { this._publishEvent('error', { ...content, error: fields.redelivered ? (0, _Errors.makeErrorFromMessage)(message) : content.error }, { mandatory: true }); break; } case 'run.discarded': { if (this[kStatus] === 'discarded') break; this[kCounters].discarded++; this[kStatus] = 'discarded'; this.broker.publish('run', 'run.leave', content); break; } case 'run.leave': { message.ack(); this[kStatus] = undefined; this._deactivateRunConsumers(); this._publishEvent('leave', this._createMessage()); return; } } message.ack(); }; Definition.prototype._onResumeMessage = function onResumeMessage(message) { message.ack(); const stateMessage = this[kStateMessage]; switch (stateMessage.fields.routingKey) { case 'run.discarded': case 'run.end': case 'run.leave': break; default: return; } if (!stateMessage.fields.redelivered) return; this._debug(`resume from ${this.status}`); return this.broker.publish('run', stateMessage.fields.routingKey, (0, _messageHelper.cloneContent)(stateMessage.content), stateMessage.properties); }; Definition.prototype._onExecutionMessage = function onExecutionMessage(routingKey, message) { const { content, properties } = message; const messageType = properties.type; message.ack(); switch (messageType) { case 'stopped': { return this._onStop(); } case 'error': { this.broker.publish('run', 'run.error', content); this.broker.publish('run', 'run.discarded', content); break; } default: { this.broker.publish('run', 'run.end', content); } } const executeMessage = this[kExecuteMessage]; this[kExecuteMessage] = null; executeMessage.ack(); }; Definition.prototype._onApiMessage = function onApiMessage(routingKey, message) { if (message.properties.type === 'stop') { const execution = this.execution; if (!execution || execution.completed) { this._onStop(); } } }; Definition.prototype._publishEvent = function publishEvent(action, content, msgOpts) { const execution = this.execution; this.broker.publish('event', `definition.${action}`, execution ? execution._createMessage(content) : (0, _messageHelper.cloneContent)(content), { type: action, ...msgOpts }); }; Definition.prototype._onStop = function onStop() { this[kStopped] = true; this._deactivateRunConsumers(); return this._publishEvent('stop', this._createMessage()); }; Definition.prototype._onBrokerReturnFn = function onBrokerReturn(message) { if (message.properties.type === 'error') { this._deactivateRunConsumers(); const err = (0, _Errors.makeErrorFromMessage)(message); throw err; } }; Definition.prototype._reset = function reset() { this[kExec].delete('executionId'); this._deactivateRunConsumers(); this.broker.purgeQueue('run-q'); this.broker.purgeQueue('execution-q'); }; Definition.prototype._debug = function debug(msg) { this.logger.debug(`<${this.id}> ${msg}`); }; function addConsumerCallbacks(definition, callback) { const broker = definition.broker; clearConsumers(); broker.subscribeOnce('event', 'definition.stop', cbLeave, { consumerTag: '_definition-callback-stop' }); broker.subscribeOnce('event', 'definition.leave', cbLeave, { consumerTag: '_definition-callback-leave' }); broker.subscribeOnce('event', 'definition.error', cbError, { consumerTag: '_definition-callback-error' }); function cbLeave(_, message) { clearConsumers(); return callback(null, definition.getApi(message)); } function cbError(_, message) { clearConsumers(); definition._reset(); return callback((0, _Errors.makeErrorFromMessage)(message)); } function clearConsumers() { broker.cancel('_definition-callback-stop'); broker.cancel('_definition-callback-leave'); broker.cancel('_definition-callback-error'); } }