# 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(); ```