The engine. Executes passed BPMN 2.0 definitions.
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 extensions
Returns:
name
: engine namebroker
: engine brokerstate
: engine stateactivityStatus
: string, activity statusexecuting
: 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
stopped
: 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 Promise
import 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 definition.
Arguments:
options
: Optional object with options to override the initial engine optionslistener
: Listen for activity events, anEventEmitter
objectvariables
: Optional object with instance variablesservices
: Optional object with service functionsexpressions
: Optional expression handling override
callback
: optional callbackerr
: Error if anyexecution
: Engine execution
Execute 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);
}
);
An 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,
});
Execution 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');
}
);
A 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);
}
);
Get definition by id, returns Promise
Add definition source by source context.
Arguments:
source
: objectsourceContext
: serializable source
import { 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);
}
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);
});
Asynchronous function to get state of a running execution.
The saved state will include the following content:
state
:running
oridle
engineVersion
: module package versionmoddleOptions
: Engine moddleOptionsdefinitions
: List of definitionsstate
: State of definition,pending
,running
, orcompleted
processes
: Object with processes with id as keyvariables
: Execution variablesservices
: Execution serviceschildren
: List of child statesentered
: Boolean indicating if the child is currently executing
import 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 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 engine from state.
Arguments:
state
: engine staterecoverOptions
: optional object with options that will override options passed to the engine at init, but not options recovered from state
import { Engine } from 'bpmn-engine';
const state = fetchSomeState();
const engine = new Engine().recover(state);
Resume execution function with previously saved engine state.
Arguments:
options
: optional resume options objectlistener
: execution listener
callback
: optional callbackerr
: Error if anyexecution
: Resumed engine execution
import { 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 statusexecuting
: 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
isRunning
: 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 Promise
Get activity/element by id. Loops the definitions and returns the first found activity with id.
activityId
: Activity or element id
Returns activity.
Get execution state.
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 objectid
: 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 activity
- options: optional options object
ignoreSameDefinition
: boolean, ignore same definition, used when a signal is forwarded from another definition execution, see example
An 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 }
);
Delegate a cancel message to all interested parties, perhaps a stalled TimerEventDefinition.
Arguments:
message
: optional objectid
: 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 activity
Engine emits the following events:
error
: An non-recoverable error has occurredstop
: Executions was stoppedend
: Execution completed
Each 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 occurred
Events 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 state
flow.take
: The sequence flow was takenflow.discard
: The sequence flow was discardedflow.looped
: The sequence is looped
If not overridden bpmn-elements expressions handler is used.
Try out aircall-expression-parser
by Aircall if you expect advanced expressions with operators.