# Examples
- [Execute](#execute)
- [Listen for events](#listen-for-events)
- [Exclusive gateway](#exclusive-gateway)
- [Script task](#script-task)
- [User task](#user-task)
- [Service task](#service-task)
- [Sequence flow with condition expression](#sequence-flow-with-condition-expression)
- [Task loop over collection](#task-loop-over-collection)
- [Extend form behaviour](#extend-form-behaviour)
- [Extend service task behaviour](#extend-service-task-behaviour)
- [Human performer and potential owner](#human-performer-and-potential-owner)
- [Traverse activities using definition shake](#traverse-activities-using-definition-shake)
- [Persist state on events](#persist-state-on-events)
# Execute
```javascript
import { Engine } from 'bpmn-engine';
const id = Math.floor(Math.random() * 10000);
const source = `
true
`;
const engine = new Engine({
name: 'execution example',
source,
variables: {
id,
},
});
engine.execute((err, execution) => {
console.log('Execution completed with id', execution.environment.variables.id);
});
```
# Listen for events
```javascript
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
`;
const engine = new Engine({
name: 'listen example',
source,
});
const listener = new EventEmitter();
listener.once('wait', (task) => {
task.signal({
ioSpecification: {
dataOutputs: [
{
id: 'userInput',
value: 'von Rosen',
},
],
},
});
});
listener.on('flow.take', (flow) => {
console.log(`flow <${flow.id}> was taken`);
});
engine.once('end', (execution) => {
console.log(execution.environment.variables);
console.log(`User sirname is ${execution.environment.output.data.inputFromUser}`);
});
engine.execute(
{
listener,
},
(err) => {
if (err) throw err;
}
);
```
# Exclusive gateway
An exclusive gateway will receive the available process variables as `this.environment.variables`.
```javascript
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
50);
]]>
`;
const engine = new Engine({
name: 'exclusive gateway example',
source,
});
const listener = new EventEmitter();
listener.on('activity.start', (api) => {
if (api.id === 'end1') throw new Error(`<${api.id}> was not supposed to be taken, check your input`);
if (api.id === 'end2') console.log(`<${api.id}> correct decision was taken`);
});
engine.execute({
listener,
variables: {
input: 51,
},
});
engine.on('end', () => {
console.log('completed');
});
```
# Script task
A script task will receive the data available on the process instance. So if `bent` or another module is needed it has to be passed when starting the process. The script task also has a callback called `next` that has to be called for the task to complete.
The `next` callback takes the following arguments:
- `err`: occasional error
- `result`: optional result of the script
```javascript
import { Engine } from 'bpmn-engine';
import bent from 'bent';
const source = `
`;
const engine = new Engine({
name: 'script task example',
source,
});
engine.execute({
variables: {
scriptTaskCompleted: false,
},
services: {
get: bent('json'),
set,
},
});
engine.on('end', (execution) => {
console.log('Output:', execution.environment.output);
});
function set(activity, name, value) {
activity.logger.debug('set', name, 'to', value);
}
```
# User task
User tasks waits for signal to complete.
```javascript
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
`;
const engine = new Engine({
name: 'user task example 1',
source,
});
const listener = new EventEmitter();
listener.once('wait', (elementApi) => {
elementApi.signal({
sirname: 'von Rosen',
});
});
listener.on('activity.end', (elementApi, engineApi) => {
if (elementApi.content.output) engineApi.environment.output[elementApi.id] = elementApi.content.output;
});
engine.execute(
{
listener,
},
(err, execution) => {
if (err) throw err;
console.log(`User sirname is ${execution.environment.output.task.sirname}`);
}
);
```
# Service task
A service task will receive the data available on the process instance. The signature of the service function is:
- `scope`: activity scope
- `callback`:
- `err`: occasional error
- `result`: service call result
```javascript
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { Engine } from 'bpmn-engine';
import bent from 'bent';
const getJson = bent('json');
const camunda = createRequire(fileURLToPath(import.meta.url))('camunda-bpmn-moddle/resources/camunda.json');
async function getRequest(scope, callback) {
try {
var result = await getJson(scope.environment.variables.apiPath); // eslint-disable-line no-var
} catch (err) {
return callback(null, err);
}
return callback(null, result);
}
const source = `
`;
const engine = new Engine({
name: 'service task example 1',
source,
moddleOptions: {
camunda,
},
extensions: {
saveToResultVariable(activity) {
if (!activity.behaviour.resultVariable) return;
activity.on('end', ({ environment, content }) => {
environment.output[activity.behaviour.resultVariable] = content.output[0];
});
},
},
});
engine.execute(
{
variables: {
apiPath: 'https://example.com/test',
},
services: {
getRequest,
},
},
(err, execution) => {
if (err) throw err;
console.log('Service task output:', execution.environment.output.serviceResult);
}
);
```
or as a expression function call:
```javascript
import { Engine } from 'bpmn-engine';
const source = `
`;
const engine = new Engine({
name: 'service task example 3',
source,
});
engine.execute({
services: {
getService(defaultScope) {
if (!defaultScope.content.id === 'serviceTask') return;
return (executionContext, callback) => {
callback(null, executionContext.environment.variables.input);
};
},
},
variables: {
input: 1,
},
extensions: {
saveToEnvironmentOutput(activity, { environment }) {
activity.on('end', (api) => {
environment.output[api.id] = api.content.output;
});
},
},
});
engine.once('end', (execution) => {
console.log(execution.name, execution.environment.output);
});
```
# Sequence flow with condition expression
```javascript
import { EventEmitter } from 'node:events';
import { Engine } from 'bpmn-engine';
const source = `
\${environment.services.isBelow(environment.variables.input,2)}
`;
const engine = new Engine({
name: 'sequence flow example',
source,
});
const listener = new EventEmitter();
listener.on('activity.end', (elementApi) => {
if (elementApi.id === 'end2') throw new Error(`<${elementApi.id}> should not have been taken`);
});
engine.execute({
listener,
services: {
isBelow: (input, test) => {
return input < test;
},
},
variables: {
input: 2,
},
});
engine.once('end', () => {
console.log('WOHO!');
});
```
# Task loop over collection
```javascript
import { Engine } from 'bpmn-engine';
import { extension, moddleOptions } from '../test/resources/JsExtension.js';
const source = `
`;
const engine = new Engine({
name: 'loop collection',
source,
moddleOptions: {
js: moddleOptions,
},
extensions: {
js: extension,
},
});
let sum = 0;
engine.execute({
services: {
loop: (executionContext, callback) => {
sum += executionContext.content.item;
callback(null, sum);
},
},
variables: {
input: [1, 2, 3, 7],
},
});
engine.once('end', () => {
console.log(sum, 'aught to be 13 blazing fast');
});
```
# Extend form behaviour
Pass an extend function with options.
```javascript
import { EventEmitter } from 'node:events';
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 source = `
`;
const engine = new Engine({
name: 'Pending game',
source,
moddleOptions: {
camunda,
},
extensions: {
camunda: camundaExt,
},
});
const listener = new EventEmitter();
listener.on('wait', (elementApi) => {
if (elementApi.content.form) {
console.log(elementApi.content.form);
return elementApi.signal(
elementApi.content.form.fields.reduce((result, field) => {
if (field.label === 'Surname') result[field.id] = 'von Rosen';
if (field.label === 'Given name') result[field.id] = 'Sebastian';
return result;
}, {})
);
}
elementApi.signal();
});
engine.execute({
listener,
});
function camundaExt(activity) {
if (!activity.behaviour.extensionElements) return;
let form;
for (const extn of activity.behaviour.extensionElements.values) {
if (extn.$type === 'camunda:FormData') {
form = {
fields: extn.fields.map((f) => ({ ...f })),
};
}
}
activity.on('enter', () => {
activity.broker.publish('format', 'run.form', { form });
});
}
```
# Extend service task behaviour
Add your own extension function that act on service task attributes.
```javascript
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 source = `
`;
function ServiceExpression(activity) {
const { type: atype, behaviour, environment } = activity;
const expression = behaviour.expression;
const type = `${atype}:expression`;
return {
type,
expression,
execute,
};
function execute(executionMessage, callback) {
const serviceFn = environment.resolveExpression(expression, executionMessage);
serviceFn.call(activity, executionMessage, (err, result) => {
callback(err, result);
});
}
}
const engine = new Engine({
name: 'extend service task',
source,
moddleOptions: {
camunda,
},
services: {
serviceFn(scope, callback) {
callback(null, { data: 1 });
},
},
extensions: {
camundaServiceTask(activity) {
if (activity.behaviour.expression) {
activity.behaviour.Service = ServiceExpression;
}
if (activity.behaviour.resultVariable) {
activity.on('end', (api) => {
activity.environment.output[activity.behaviour.resultVariable] = api.content.output;
});
}
},
},
});
engine.execute((err, instance) => {
if (err) throw err;
console.log(instance.name, instance.environment.output);
});
```
# Human performer and potential owner
Publish event when human involvement is required.
```javascript
import { EventEmitter } from 'node:events';
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 source = `
\${environment.services.getUser()}
user(pal), group(users)
`;
function humanInvolvement(activity) {
if (!activity.behaviour.resources || !activity.behaviour.resources.length) return;
const humanPerformer = activity.behaviour.resources.find((resource) => resource.type === 'bpmn:HumanPerformer');
const potentialOwner = activity.behaviour.resources.find((resource) => resource.type === 'bpmn:PotentialOwner');
activity.on('enter', (api) => {
activity.broker.publish('format', 'run.call.humans', {
humanPerformer: api.resolveExpression(humanPerformer.expression),
potentialOwner: api.resolveExpression(potentialOwner.expression),
});
});
activity.on('wait', (api) => {
api.owner.broker.publish('event', 'activity.call', { ...api.content });
});
}
const listener = new EventEmitter();
const engine = new Engine({
name: 'call humans',
source,
moddleOptions: {
camunda,
},
services: {
getUser() {
return 'pal';
},
},
extensions: {
humanInvolvement,
},
});
listener.on('activity.call', (api) => {
console.log('Make call to', api.content.humanPerformer);
console.log('Owner:', api.content.potentialOwner);
api.signal();
});
engine.execute({ listener }, (err, instance) => {
if (err) throw err;
console.log(instance.name, 'completed');
});
```
# Traverse activities using definition shake
Shake down the possible sequences an activity can have.
```javascript
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { Engine } from 'bpmn-engine';
import BpmnModdle from 'bpmn-moddle';
import * as elements from 'bpmn-elements';
import Serializer, { TypeResolver } from 'moddle-context-serializer';
const camunda = createRequire(fileURLToPath(import.meta.url))('camunda-bpmn-moddle/resources/camunda.json');
const source = `
toSayHiTask1
toSayHiTask1
toEnd1
toEnd2
toEnd1
toEnd2
`;
(async function IIFE() {
const moddleContext = await new BpmnModdle({
camunda,
}).fromXML(source);
const sourceContext = Serializer(moddleContext, TypeResolver(elements));
const engine = new Engine({
sourceContext,
});
const [definition] = await engine.getDefinitions();
const shakenStarts = definition.shake();
console.log('first sequence', shakenStarts.start[0].sequence.reduce(printSequence, ''));
console.log('second sequence', shakenStarts.start[1].sequence.reduce(printSequence, ''));
function printSequence(res, s) {
if (!res) return s.id;
res += ' -> ' + s.id;
return res;
}
})();
```
# Persist state on events
One way to persist state is to subscribe to activity and engine events through a listener.
In this example the state of the execution is published on a message broker. Subscribers to the broker will hopefylly know how to persist the state.
```js
import { randomUUID } from 'node:crypto';
import { createRequire } from 'node:module';
import { fileURLToPath } = from 'node:url';
import { Engine } from 'bpmn-engine';
import { EventEmitter } from 'ndoe:events';
import { publish } from './dbbroker.js';
import { getSourceSync, getAllowedServices, getExtensions } from './utils.js';
const camundaModdle = createRequire(fileURLToPath(import.meta.url))('camunda-bpmn-moddle/resources/camunda.json')
function ignite(executionId, options = {}) {
const { name, settings } = options;
const listener = new EventEmitter();
listener.on('activity.wait', (_, execution) => {
return publishEvent('bpmn.state.update', {state: execution.getState()});
});
listener.on('activity.end', (_, execution) => {
return publishEvent('bpmn.state.update', {state: execution.getState()});
});
listener.on('activity.timer', (api, execution) => {
return publishEvent('bpmn.state.expires', {
expires: new Date(api.content.startedAt + api.content.timeout),
state: execution.getState(),
});
});
listener.on('activity.timeout', (_, execution) => {
return publishEvent('bpmn.state.expired', {
expired: new Date(),
state: execution.getState(),
});
});
const engine = BpmnEngine({
moddleOptions: {
camunda,
},
...options,
settings: {
...settings,
executionId,
enableDummyService: false,
}
});
engine.once('end', () => {
publishEvent('bpmn.completed');
});
engine.once('error', (err) => {
publishEvent('bpmn.error', {message: err.message, error: err});
});
return { engine, listener };
function publishEvent(routingKey, message) {
publish('events', routingKey, {
name,
executionId,
...message,
});
}
}
const {engine} = ignite(randomUUID(), {
name: 'persisted engine #1',
source: getSourceSync('./mother-of-all.bpmn'),
services: getAllowedServices(),
extensions: getExtensions(),
});
engine.execute();
```