- Engine
- Transformer
- Validation
- Loops
- Expressions
The engine. Executes passed BPMN definitions.
Creates a new Engine object where:
options
: Optional objectsource
: Bpmn definition source as String or BuffermoddleContext
: Optional parsed moddle context objectname
: Optional name of engine,moddleOptions
: Optional moddle parse options
Options source
and context
are mutually exclusive.
Moddle options must be used if an extension is required when parsing BPMN-source. The object will be passed on to the constructor of bpmn-moddle
. See camunda-bpmn-moddle for example.
const Bpmn = require('bpmn-engine');
const fs = require('fs');
const engine = new Bpmn.Engine({
source: fs.readFileSync('./test/resources/mother-of-all.bpmn'),
moddleOptions: {
camunda: require('camunda-bpmn-moddle/resources/camunda')
}
});
Execute definition with:
options
: Optional objectlistener
: Listen for activity events, anEventEmitter
objectvariables
: Optional object with instance variablesservices
: Optional object with service definitions
callback
: optional callbackerr
: Error if anydefinition
: Executing definitionsiblings
: List of all definitions including executing
'use strict';
const Bpmn = require('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">
<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 Bpmn.Engine({
source: processXml
});
engine.execute((err, definition) => {
if (err) throw err;
definition.once('end', () => {
console.log('completed')
});
});
An EventEmitter
object with listeners. Event names are composed by activity event name and activity id, e.g. wait-userTask
.
const EventEmitter = require('events').EventEmitter;
const listener = new EventEmitter();
listener.on('wait-userTask', (task, instance) => {
console.log(`${task.type} <${task.id}> of ${instance.id} is waiting for input`);
task.signal('don´t wait for me');
});
engine.execute({
listener: listener
})
A generic event is also emitted, i.e. only the event is emitted.
listener.on('entered', (task, instance) => {
console.log(`${task.type} <${task.id}> of ${instance.id} is entered`);
});
Event arguments are:
activity
: The activityinstance
: The running process instance
NB!
error
events are NOT emitted through the listener. Errors can be caught by an bpmn error event if they are to be handled.
Execution variables are passed as the first argument to #execute
.
'use strict';
const Bpmn = require('bpmn-engine');
const fs = require('fs');
const engine = new Bpmn.Engine({
source: fs.readFileSync('./test/resources/simple-task.bpmn')
});
const variables = {
input: 1
};
engine.execute({
variables: variables
}, (err, instance) => {
if (err) throw err;
instance.once('end', () => {
console.log('completed')
});
});
A service is a module used by e.g. a script tasks or a condition where:
const options = {
services: {
get: {
module: 'request',
type: 'require',
fnName: 'get'
},
checkState: (message) => {
return message.variables.statusCode === 200;
}
}
};
name
: Exposed name in the enginemodule
: Module or global nametype
: Optional type, supported types arerequire
andglobal
, defaults torequire
fnName
: Optional module function name
If the process will be stopped and resumed the module structure is to prefer. But it is also possible to send in functions, aware that they will not be serialized when getting state.
The module name can be a npm module, local module or a global reference. If a local module is used the path must be relative from execution path, i.e. process.cwd()
.
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
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" />
<scriptTask id="scriptTask" scriptFormat="Javascript">
<script>
<![CDATA[
const request = services.request;
const self = this;
request.get('http://example.com/test', (err, resp, body) => {
if (err) return next(err);
self.variables.scriptTaskCompleted = true;
next(null, {result: body});
})
]]>
</script>
</scriptTask>
<endEvent id="theEnd" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="scriptTask" />
<sequenceFlow id="flow2" sourceRef="scriptTask" targetRef="theEnd" />
</process>
</definitions>`;
const engine = new Bpmn.Engine({
source: processXml
});
const services = {
request: {
module: 'request'
},
put: {
module: 'request',
fnName: 'put'
},
require: {
module: 'require',
type: 'global'
},
serviceFn: {
module: './test/helpers/testHelpers',
fnName: 'serviceFn'
}
};
engine.execute({
services: services
}, (err, instance) => {
if (err) throw err;
instance.once('end', () => {
console.log('completed')
});
});
Utility function to get first definition.
callback(err, firstDefinition)
:err
: Occassional Error if definitions failed to loadfirstDefinition
: First found definition
Get definitions. Loads definitions from sources or passed moddle contexts.
callback(err, definitions)
:err
: Occassional Error if definitions failed to loaddefinition
: List of added definitions
const Bpmn = require('bpmn-engine');
const processXml = `
<?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 Bpmn.Engine({
source: processXml
});
engine.getDefinitions((err, definitions) => {
if (err) throw err;
console.log('Loaded', definitions[0].id);
console.log('The definition comes with process', definitions[0].getProcesses()[0].id);
});
Get activities that are in an entered state.
state
: State of enginedefinitions
: List of definitionsstate
: State of definitionchildren
: List of children that are in an entered stateid
: child identered
: Boolean indicating that the child is currently executingwaiting
: Boolean indicating if the task is in a waiting state
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
const definitionSource = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="pending" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn">
<process id="theWaitingGame" isExecutable="true">
<startEvent id="start" />
<parallelGateway id="fork" />
<userTask id="userTask1" />
<userTask id="userTask2">
<extensionElements>
<camunda:formData>
<camunda:formField id="surname" label="Surname" type="string" />
<camunda:formField id="givenName" label="Given name" type="string" />
</camunda:formData>
</extensionElements>
</userTask>
<task id="task" />
<parallelGateway id="join" />
<endEvent id="end" />
<sequenceFlow id="flow1" sourceRef="start" targetRef="fork" />
<sequenceFlow id="flow2" sourceRef="fork" targetRef="userTask1" />
<sequenceFlow id="flow3" sourceRef="fork" targetRef="userTask2" />
<sequenceFlow id="flow4" sourceRef="fork" targetRef="task" />
<sequenceFlow id="flow5" sourceRef="userTask1" targetRef="join" />
<sequenceFlow id="flow6" sourceRef="userTask2" targetRef="join" />
<sequenceFlow id="flow7" sourceRef="task" targetRef="join" />
<sequenceFlow id="flowEnd" sourceRef="join" targetRef="end" />
</process>
</definitions>
`;
const engine = new Bpmn.Engine({
name: 'Pending game',
source: definitionSource,
moddleOptions: {
camunda: require('camunda-bpmn-moddle/resources/camunda')
}
});
const listener = new EventEmitter();
engine.execute({
listener: listener
}, (err, execution) => {
if (err) throw err;
});
setTimeout(() => {
const pending = engine.getPendingActivities();
console.log(JSON.stringify(pending, null, 2));
const task = pending.definitions[0].children.find(c => c.type === 'bpmn:UserTask');
engine.signal(task.id);
}, 300);
Get state of a running execution. Listener events wait
and start
are recommended when saving state.
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
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
const fs = require('fs');
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" />
<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 Bpmn.Engine({
source: processXml
});
const listener = new EventEmitter();
let state;
listener.once('wait-userTask', () => {
state = engine.getState();
fs.writeFileSync('./tmp/some-random-id.json', JSON.stringify(state, null, 2));
console.log(JSON.stringify(state, null, 2));
});
listener.once('start', () => {
state = engine.getState();
fs.writeFileSync('./tmp/some-random-id.json', JSON.stringify(state, null, 2));
});
engine.execute({
listener: listener
}, (err, execution) => {
if (err) throw err;
});
Signal an activity that is waiting.
activityId
: Activity Idmessage
: Activity input message
Returns boolean, true
if signal was approved and false
otherwise.
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
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" />
<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 Bpmn.Engine({
source: processXml
});
const listener = new EventEmitter();
let state;
listener.once('wait', (activity) => {
engine.signal(activity.id);
});
engine.execute({
variables: {
executionId: 'some-random-id'
},
listener: listener
}, (err, execution) => {
if (err) throw err;
});
Stop execution. The instance is terminated.
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
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" />
<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 Bpmn.Engine({
source: processXml
});
const listener = new EventEmitter();
let state;
listener.once('wait-userTask', (activity) => {
engine.stop();
state = engine.getState();
});
engine.execute({
variables: {
executionId: 'some-random-id'
},
listener: listener
}, (err, execution) => {
if (err) throw err;
});
Resume execution function with previously saved engine state.
'use strict';
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
// Retrieve saved state
const state = db.getState('some-random-id', (err, state) => {
if (err) return console.log(err.message);
const engine = Bpmn.Engine.resume(state);
engine.on('end', () => {
console.log('resumed instance completed');
});
});
Engine emits the following events:
error
: An non-recoverable error has occurred, with argumentserr
: The erroreventSource
: The source instance that emitted error, e.g. a task or other activitiy
end
: Execution has completed or was stoppedlastReportingDefinition
: Last reporting definition instance
Each activity and flow emits events when changing state.
enter
: An activity is enteredstart
: An activity is startedwait
: A start event or user task waits for signalend
: A task has ended successfullycancel
: An activity execution was canceledleave
: The execution left the activity, emitted asynchronouslyerror
: An non-recoverable error has occurred The error event will not be emitted if anbpmn:errorEvent
is attached
taken
: The sequence flow was takendiscarded
: The sequence flow was discarded
Basically a wrapper around bpmn-moddle.fromXml
.
sourceXml
: String with bpmn definition sourceoptions
: Options to pass tobpmn-moddle
callback
: callbackerr
: Occasional errordefinition
: Bpmn definitionmoddleContext
: Bpmn moddle context
const Bpmn = require('bpmn-engine');
const processXml = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="transformer" 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>`;
Bpmn.transformer.transform(processXml, {
camunda: require('camunda-bpmn-moddle/resources/camunda')
}, (err, def, moddleContext) => {
const engine = new Bpmn.Engine({
moddleContext: moddleContext
});
engine.execute((err, instance) => {
if (err) throw err;
console.log('Definition started with process', instance.mainProcess.id);
});
});
Bpmn engine exposes validation functions.
Validate moddle context to ensure that it is executable. Returns list of error instances, if any.
moddleContext
: Moddle context object
const Bpmn = require('bpmn-engine');
const EventEmitter = require('events').EventEmitter;
const processXml = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="validate" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process id="theProcess2" isExecutable="true">
<startEvent id="theStart" />
<exclusiveGateway id="decision" default="flow2" />
<endEvent id="end1" />
<endEvent id="end2" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="decision" />
<sequenceFlow id="flow2" sourceRef="decision" targetRef="end2">
<conditionExpression xsi:type="tFormalExpression" language="JavaScript">true</conditionExpression>
</sequenceFlow>
</process>
</definitions>`;
Bpmn.transformer.transform(processXml, {
camunda: require('camunda-bpmn-moddle/resources/camunda')
}, (err, def, moddleContext) => {
console.log(Bpmn.validation.validateModdleContext(moddleContext));
});
Validate execution options to ensure that it is correct. Throws error if invalid.
executeOptions
: Execution options object
Task loops can made based conditions, cardinality, or a collection.
Loop a fixed number of times or until number of iterations match cardinality. The cardinality body an integer or an expression.
<bpmn:loopCardinality xsi:type="bpmn:tFormalExpression">${variables.list.length}</bpmn:loopCardinality>
Loop until condition is met. The condition body can be a script or an expression.
<completionCondition xsi:type="tFormalExpression">\${services.condition(variables.input)}</completionCondition>
Loop all items in a list.
<bpmn:multiInstanceLoopCharacteristics isSequential="true" camunda:collection="\${variables.list}" />
For bpmn-moddle
to read the camunda:collection
namespaced attribute, the engine must be instantiated with moddle options referring camunda-bpmn-moddle/resources/camunda
.
Expressions come in the form of ${<variables or services>.<property name>}
.
The following expressions are supported:
-
${variables.input}
- resolves to the variable input -
${variables.input[0]}
- resolves to first item of the variable input array -
${variables.input[spaced name]}
- resolves to the variable input object propertyspaced name
-
${services.getInput}
- return the service functiongetInput
-
${services.getInput()}
- executes the service functiongetInput
with the argument{services, variables}
-
${services.isBelow(variables.input,2)}
- executes the service functionisBelow
with result ofvariable.input
value and 2
and, as utility:
${true}
- return Boolean valuetrue
${false}
- return Boolean valuefalse
Expressions are supported by many elements, e.g.:
- MultiInstanceLoopCharacteristics
camunda:collection
: variable collection, e.g.${variables.list}
- ServiceTask
camunda:expression
element value. moddleOptionsrequire('camunda-bpmn-moddle/resources/camunda')
must be used.
- SequenceFlow
conditionExpression
element value
- TimerEvent
timeDuration
element value
Expressions in expressions is not supported.