Skip to content

Latest commit

 

History

History
669 lines (524 loc) · 20.3 KB

API.md

File metadata and controls

669 lines (524 loc) · 20.3 KB

23.0.2 API Reference

Engine

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 ScriptTask
    • elements: optional object with element type mapping override
    • expressions: optional override expressions handler
    • extendFn: optional extend serializer function
    • Logger: optional Logger factory, defaults to debug logger
    • moddleContext: optional BPMN 2.0 definition moddle context
    • moddleOptions: optional bpmn-moddle options to be passed to bpmn-moddle
    • name: optional name of engine,
    • scripts: optional inline script handler, defaults to nodejs vm module handling, i.e. JavaScript
    • source: optional BPMN 2.0 definition source as string
    • sourceContext: optional serialized context supplied by moddle-context-serializer
    • timers: Timers instance
    • typeResolver: optional type resolver function passed to moddle-context-serializer
    • extensions: optional behavior extensions

Returns:

  • name: engine name
  • broker: engine broker
  • state: engine state
  • activityStatus: string, activity status
    • executing: at least one activity is executing, e.g. a service task making a asynchronous request
    • timer: at least one activity is waiting for a timer to complete, usually only TimerEventDefinition's
    • wait: at least one activity is waiting for a signal of some sort, e.g. user tasks, intermediate catch events, etc
    • idle: idle, no activities are running
  • stopped: boolean stopped
  • execution: current engine execution
  • environment: engine environment
  • logger: engine logger
  • async execute(): execute definition
  • async getDefinitionById(): get definition by id
  • async getDefinitions(): get all definitions
  • async getState(): get execution serialized state
  • recover(): recover from state
  • async resume(): resume execution
  • async stop(): stop execution
  • waitFor(): 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([options[, callback]])

Execute definition.

Arguments:

  • options: Optional object with options to override the initial engine options
    • listener: Listen for activity events, an EventEmitter object
    • variables: Optional object with instance variables
    • services: Optional object with service functions
    • expressions: Optional expression handling override
  • callback: optional callback
    • err: Error if any
    • execution: 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);
  }
);

Execution listener

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

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');
  }
);

Execution services

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);
  }
);

async getDefinitionById(id)

Get definition by id, returns Promise

addSource({sourceContext})

Add definition source by source context.

Arguments:

  • source: object
    • sourceContext: 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);
}

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 idle
  • engineVersion: module package version
  • moddleOptions: Engine moddleOptions
  • definitions: List of definitions
    • state: State of definition, pending, running, or completed
    • processes: Object with processes with id as key
      • variables: Execution variables
      • services: Execution services
      • children: List of child states
        • entered: 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()

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.

Arguments:

  • state: engine state
  • recoverOptions: 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([options, [callback]])

Resume execution function with previously saved engine state.

Arguments:

  • options: optional resume options object
  • callback: optional callback
    • err: Error if any
    • execution: 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');
});

Execution API

  • name: engine name
  • state: execution state
  • stopped: is execution stopped?
  • broker: engine message broker
  • environment: execution environment
  • definitions: list of definitions
  • activityStatus: string, execution activity status
    • executing: at least one activity is executing, e.g. a service task making a asynchronous request
    • timer: at least one activity is waiting for a timer to complete, usually only TimerEventDefinition's
    • wait: at least one activity is waiting for a signal of some sort, e.g. user tasks, intermediate catch events, etc
    • idle: idle, no activities are running
  • isRunning: are any definition running?
  • getActivityById(activityId)(#getactivitybyid-activityid): get activity/element by id, returns first found among definitions
  • getState(): get execution state
  • getPostponed(): get postponed activities, i.e. activities waiting for some interaction, signal, or timer
  • signal(message): send signal to execution, distributed to all definitions
  • cancelActivity(message): send cancel activity to execution, distributed to all definitions
  • stop(): stop execution
  • waitFor(event): wait for engine events, returns Promise

getActivityById(activityId)

Get activity/element by id. Loops the definitions and returns the first found activity with id.

  • activityId: Activity or element id

Returns 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 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 }
);

cancelActivity(message)

Delegate a cancel message to all interested parties, perhaps a stalled TimerEventDefinition.

Arguments:

  • message: optional object
    • id: optional activity id to cancel execution
    • executionId: 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 events

Engine emits the following events:

  • error: An non-recoverable error has occurred
  • stop: Executions was stopped
  • end: Execution completed

Activity events

Each activity and flow emits events when changing state.

  • activity.enter: An activity is entered
  • activity.start: An activity is started
  • activity.wait: The activity is postponed for some reason, e.g. a user task is waiting to be signaled or a message is expected
  • wait: Same as above
  • activity.end: An activity has ended successfully
  • activity.leave: The execution left the activity
  • activity.stop: Activity run was stopped
  • activity.throw: An recoverable error was thrown
  • activity.error: An non-recoverable error has occurred

Event Api

Events are emitted with api with execution properties

  • name: engine name
  • state: state of execution, i.e running or idle
  • stopped: is the execution stopped
  • environment: engine environment
  • definitions: executing definitions
  • stop(): stop execution
  • getState(): get execution serializable state
  • getPostponed(): get activities in a postponed state

Sequence flow events

  • flow.take: The sequence flow was taken
  • flow.discard: The sequence flow was discarded
  • flow.looped: The sequence is looped

Expressions

If not overridden bpmn-elements expressions handler is used.

Try out aircall-expression-parser by Aircall if you expect advanced expressions with operators.