The engine. Executes passed BPMN 2.0 definitions.
new Engine([options])Creates a new Engine.
Arguments:
options: Optional options, passed to environment:
disableDummyScript: optional boolean to disable dummy script supplied to empty ScriptTaskelements: optional object with element type mapping overrideexpressions: optional override expressions handlerextendFn: optional extend serializer functionLogger: optional Logger factory, defaults to debug loggermoddleContext: optional BPMN 2.0 definition moddle contextmoddleOptions: optional bpmn-moddle options to be passed to bpmn-moddlename: optional name of engine,scripts: optional inline script handler, defaults to nodejs vm module handling, i.e. JavaScriptsource: optional BPMN 2.0 definition source as stringsourceContext: optional serialized context supplied by moddle-context-serializertimers: Timers instancetypeResolver: optional type resolver function passed to moddle-context-serializerextensions: optional behavior extensionsReturns:
name: engine namebroker: engine brokerstate: engine stateactivityStatus: string, activity status
executing: at least one activity is executing, e.g. a service task making a asynchronous requesttimer: at least one activity is waiting for a timer to complete, usually only TimerEventDefinition’swait: at least one activity is waiting for a signal of some sort, e.g. user tasks, intermediate catch events, etcidle: idle, no activities are runningstopped: boolean stoppedexecution: current engine executionenvironment: engine environmentlogger: engine loggerasync execute(): execute definitionasync getDefinitionById(): get definition by idasync getDefinitions(): get all definitionsasync getState(): get execution serialized staterecover(): recover from stateasync resume(): resume executionasync stop(): stop executionwaitFor(): wait for engine events, returns Promiseimport fs from 'node:fs';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { Engine } from 'bpmn-engine';
const camunda = createRequire(fileURLToPath(import.meta.url))('camunda-bpmn-moddle/resources/camunda.json');
const engine = new Engine({
name: 'mother of all',
source: fs.readFileSync('./test/resources/mother-of-all.bpmn'),
moddleOptions: {
camunda,
},
});
execute([options[, callback]])Execute definition.
Arguments:
options: Optional object with options to override the initial engine options
listener: Listen for activity events, an EventEmitter objectvariables: Optional object with instance variablesservices: Optional object with service functionsexpressions: Optional expression handling overridecallback: optional callback
err: Error if anyexecution: Engine executionExecute options overrides the initial options passed to the engine before executing the definition.
Returns Execution API
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<dataObjectReference id="inputFromUserRef" dataObjectRef="inputFromUser" />
<dataObject id="inputFromUser" />
<startEvent id="theStart" />
<userTask id="userTask">
<ioSpecification id="inputSpec">
<dataOutput id="userInput" />
</ioSpecification>
<dataOutputAssociation id="associatedWith" sourceRef="userInput" targetRef="inputFromUserRef" />
</userTask>
<endEvent id="theEnd" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<sequenceFlow id="flow2" sourceRef="userTask" targetRef="theEnd" />
</process>
</definitions>`;
const engine = new Engine({
name: 'first',
source,
variables: {
data: {
inputFromUser: 0,
},
},
});
const listener = new EventEmitter();
listener.on('activity.wait', (elementApi) => {
elementApi.owner.logger.debug(`<${elementApi.executionId} (${elementApi.id})> signal with io`, elementApi.content.ioSpecification);
elementApi.signal({
ioSpecification: {
dataOutputs: [
{
id: 'userInput',
value: 2,
},
],
},
});
});
engine.execute(
{
listener,
variables: {
data: {
inputFromUser: 1,
},
},
},
(err, execution) => {
if (err) throw err;
console.log('completed with overridden listener', execution.environment.output);
}
);
listenerAn EventEmitter object with listeners. Listen for activity events.
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<userTask id="userTask" />
</process>
</definitions>`;
const engine = new Engine({
name: 'first listener',
source,
});
const listener = new EventEmitter();
listener.on('activity.enter', (elementApi, engineApi) => {
console.log(`${elementApi.type} <${elementApi.id}> of ${engineApi.name} is entered`);
});
listener.on('activity.wait', (elemntApi, instance) => {
console.log(`${elemntApi.type} <${elemntApi.id}> of ${instance.name} is waiting for input`);
elemntApi.signal('don´t wait for me');
});
engine.execute({
listener,
});
variablesExecution variables are passed as the first argument to #execute.
import fs from 'node:fs';
import { Engine } from 'bpmn-engine';
const engine = new Engine({
name: 'using variables',
source: fs.readFileSync('./test/resources/simple-task.bpmn'),
});
const variables = {
input: 1,
};
engine.execute(
{
variables,
},
(err, engineApi) => {
if (err) throw err;
console.log('completed');
}
);
servicesA service is a function exposed on environment.services.
import { Engine } from 'bpmn-engine';
import bent from 'bent';
const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<startEvent id="theStart" />
<scriptTask id="scriptTask" scriptFormat="Javascript">
<script>
<![CDATA[
const get = environment.services.get;
const self = this;
get('https://example.com/test').then((body) => {
environment.variables.scriptTaskCompleted = true;
next(null, {result: body});
}).catch(next)
]]>
</script>
</scriptTask>
<endEvent id="theEnd" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="scriptTask" />
<sequenceFlow id="flow2" sourceRef="scriptTask" targetRef="theEnd" />
</process>
</definitions>`;
const engine = new Engine({
name: 'services doc',
source,
});
engine.execute(
{
services: {
get: bent('json'),
},
},
(err, engineApi) => {
if (err) throw err;
console.log('completed', engineApi.name, engineApi.environment.variables);
}
);
async getDefinitionById(id)Get definition by id, returns Promise
addSource({sourceContext})Add definition source by source context.
Arguments:
source: object
sourceContext: serializable sourceimport { EventEmitter } from 'node:events';
import BpmnModdle from 'bpmn-moddle';
import * as elements from 'bpmn-elements';
import { Engine } from 'bpmn-engine';
import Serializer, { TypeResolver } from 'moddle-context-serializer';
const engine = new Engine({
name: 'add source',
});
(async function IIFE(source) {
const sourceContext = await getContext(source);
engine.addSource({
sourceContext,
});
const listener = new EventEmitter();
listener.once('activity.wait', (api) => {
console.log(api.name, 'is waiting');
api.signal();
});
await engine.execute({
listener,
});
await engine.waitFor('end');
})(`
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<startEvent id="start" />
<sequenceFlow id="flow1" sourceRef="start" targetRef="task" />
<userTask id="task" name="lazy source user" />
<sequenceFlow id="flow2" sourceRef="task" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
`);
async function getContext(source, options) {
const moddleContext = await getModdleContext(source, options);
if (moddleContext.warnings) {
moddleContext.warnings.forEach(({ error, message, element, property }) => {
if (error) return console.error(message);
console.error(`<${element.id}> ${property}:`, message);
});
}
const types = TypeResolver({
...elements,
...options?.elements,
});
return Serializer(moddleContext, types, options?.extendFn);
}
function getModdleContext(source, options) {
const bpmnModdle = new BpmnModdle(options);
return bpmnModdle.fromXML(source);
}
getDefinitions()Get all definitions
import { Engine } from 'bpmn-engine';
const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="Definition_42" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<startEvent id="theStart" />
<userTask id="userTask" />
<endEvent id="theEnd" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<sequenceFlow id="flow2" sourceRef="userTask" targetRef="theEnd" />
</process>
</definitions>`;
const engine = new Engine({
source,
});
engine.getDefinitions().then((definitions) => {
console.log('Loaded', definitions[0].id);
console.log('The definition comes with process', definitions[0].getProcesses()[0].id);
});
getState()Asynchronous function to get state of a running execution.
The saved state will include the following content:
state: running or idleengineVersion: module package versionmoddleOptions: Engine moddleOptionsdefinitions: List of definitions
state: State of definition, pending, running, or completedprocesses: Object with processes with id as key
variables: Execution variablesservices: Execution serviceschildren: List of child states
entered: Boolean indicating if the child is currently executingimport fs from 'node:fs/promises';
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const processXml = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<userTask id="userTask" />
<sequenceFlow id="flow2" sourceRef="userTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>`;
const engine = new Engine({
source: processXml,
});
const listener = new EventEmitter();
let state;
listener.once('activity.wait', async () => {
state = await engine.getState();
await fs.writeFile('./tmp/some-random-id.json', JSON.stringify(state, null, 2));
});
listener.once('activity.start', async () => {
state = await engine.getState();
await fs.writeFile('./tmp/some-random-id.json', JSON.stringify(state, null, 2));
});
engine.execute({
listener,
});
stop()Stop execution. The instance is terminated.
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess" isExecutable="true">
<startEvent id="theStart" />
<userTask id="userTask" />
<endEvent id="theEnd" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<sequenceFlow id="flow2" sourceRef="userTask" targetRef="theEnd" />
</process>
</definitions>`;
const engine = new Engine({
source,
});
const listener = new EventEmitter();
let state;
listener.once('activity.wait', async () => {
engine.stop();
state = await engine.getState();
});
engine.execute({
variables: {
executionId: 'some-random-id',
},
listener,
});
recover(state[, recoverOptions])Recover engine from state.
NB! Will throw an error if the engine is running.
Arguments:
state: engine staterecoverOptions: optional object with options that will override options passed to the engine at init, but not options recovered from stateimport { Engine } from 'bpmn-engine';
const state = fetchSomeState();
const engine = new Engine().recover(state);
resume([options, [callback]])Resume execution function with previously saved engine state.
NB! Attempting to resume a running engine returns error in callback or throws if callback is not passed.
Arguments:
options: optional resume options object
listener: execution listenercallback: optional callback
err: Error if anyexecution: Resumed engine executionimport { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const state = fetchSomeState();
const engine = new Engine().recover(state);
const listener = new EventEmitter();
engine.resume({ listener }, () => {
console.log('completed');
});
name: engine namestate: execution statestopped: is execution stopped?broker: engine message brokerenvironment: execution environmentdefinitions: list of definitionsactivityStatus: string, execution activity status, e.g. if wait or timer no activities are executing, if executing there can be both running timers and waiting tasks, if timer there can be waiting activities but no executing activities, and so forth
executing: at least one activity is executing, e.g. a service task making a asynchronous requesttimer: at least one activity is waiting for a timer to complete, usually only TimerEventDefinition’swait: at least one activity is waiting for a signal of some sort, e.g. user tasks, intermediate catch events, etcidle: idle, no activities are running, the engine is not running at allisRunning: are any definition running?getActivityById(activityId)(#getactivitybyid-activityid): get activity/element by id, returns first found among definitionsgetState(): get execution stategetPostponed(): get postponed activities, i.e. activities waiting for some interaction, signal, or timersignal(message): send signal to execution, distributed to all definitionscancelActivity(message): send cancel activity to execution, distributed to all definitionsstop(): stop executionwaitFor(event): wait for engine events, returns PromisegetActivityById(activityId)Get activity/element by id. Loops the definitions and returns the first found activity with id.
activityId: Activity or element idReturns activity.
getState()Get execution state.
signal(message[, options])Delegate a signal message to all interested parties, usually MessageEventDefinition, SignalEventDefinition, SignalTask (user, manual), ReceiveTask, or a StartEvent that has a form.
Arguments:
message: optional object
id: optional task/element id to signal, also matched with Message and Signal id. If not passed only anonymous Signal- and MessageEventDefinitions will pick up the signal.executionId: optional execution id to signal, specially for looped tasks, also works for signal tasks that are not looped[name]*: any other properties will be forwarded as message to activityignoreSameDefinition: boolean, ignore same definition, used when a signal is forwarded from another definition execution, see exampleAn example on how to setup signal forwarding between definitions:
engine.broker.subscribeTmp(
'event',
'activity.signal',
(routingKey, msg) => {
engine.execution.signal(msg.content.message, { ignoreSameDefinition: true });
},
{ noAck: true }
);
cancelActivity(message)Delegate a cancel message to all interested parties, perhaps a stalled TimerEventDefinition.
Arguments:
message: optional object
id: optional activity id to cancel executionexecutionId: optional execution id to signal, useful for an event with multiple event defintions[name]*: any other properties will be forwarded as message to activityEngine emits the following events:
error: An non-recoverable error has occurredstop: Executions was stoppedend: Execution completedEach activity and flow emits events when changing state.
activity.enter: An activity is enteredactivity.start: An activity is startedactivity.wait: The activity is postponed for some reason, e.g. a user task is waiting to be signaled or a message is expectedwait: Same as aboveactivity.end: An activity has ended successfullyactivity.leave: The execution left the activityactivity.stop: Activity run was stoppedactivity.throw: An recoverable error was thrownactivity.error: An non-recoverable error has occurredEvents are emitted with api with execution properties
name: engine namestate: state of execution, i.e running or idlestopped: is the execution stoppedenvironment: engine environmentdefinitions: executing definitionsstop(): stop executiongetState(): get execution serializable stategetPostponed(): get activities in a postponed stateflow.take: The sequence flow was takenflow.discard: The sequence flow was discardedflow.looped: The sequence is loopedIf not overridden bpmn-elements expressions handler is used.
Try out aircall-expression-parser by Aircall if you expect advanced expressions with operators.