Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22, 24, latest]
node-version: [20, 22, 24, latest]
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ config/_revision
package-lock.json

*.log

# Claude Code
CLAUDE.md
.claude
1 change: 1 addition & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"reporter": "spec",
"timeout": 1000,
"ui": "mocha-cakes-2",
"spec": ["test/**/*-feature.js", "test/**/*-test.js"],
"require": ["chai/register-expect.js"]
}
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18
20
65 changes: 65 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

`.nvmrc` pins Node 18; `engines.node` is `>=18`. The test reporter `@bonniernews/hot-bev` and `mocha-cakes-2` UI handle parallel execution and Gherkin-style Features.

- `npm test` — runs `mocha -p` with the hot-bev reporter (parallel). `posttest` then runs `lint`, `toc` (regenerates API doc TOCs), and `dist`.
- `npm run wintest` — same suite, plain `mocha` (no `-p`/parallel; for environments where worker pools misbehave).
- `npm run lint` — eslint (cached) + prettier check.
- `npm run cov:html` / `npm run test:lcov` — coverage variants via c8.
- `npm run toc` — regenerates the table of contents in `docs/API.md` and `docs/Examples.md` via the in-repo `scripts/generate-api-toc.js` (no `markdown-toc` dep). It takes the markdown files as a comma- or space-separated argument, rewrites the block between `<!-- toc -->` and `<!-- tocstop -->`, skips the first H1 (the doc title — `docs/API.md` keeps a plain `# API Reference` title for this), and disambiguates duplicate anchors GitHub-style (`#getstate`, `#getstate-1`).
- `npm run test-md` — runs `texample` against `docs/API.md`, `docs/Examples.md`, `docs/Upgrade.md`. **Not** in the regular `posttest` chain; run manually when API doc examples change.
- `npm run dist` — rollup builds `src/index.js` → `lib/index.cjs`. The footer trick `module.exports = Object.assign(exports.default, exports)` is what lets the CJS bundle's `require('bpmn-engine')` return the `Engine` constructor _and_ expose named exports — don't remove it.
- Single test file: `npx mocha test/Engine-test.js`. Single test: append `--grep "<pattern>"`. Default mocha timeout is 1000ms (see `.mocharc.json`); feature tests under `test/feature/` use the `mocha-cakes-2` BDD UI (`Feature`/`Scenario`/`Given`/`When`/`Then`).

## Architecture

The `Engine` is a thin orchestrator on top of four heavyweights — every file in `src/` is glue:

```
bpmn-moddle (parse XML)
moddle-context-serializer ──► persistable JSON state (serialize / deserialize)
bpmn-elements (behaviour functions: Process, Task, *EventDefinition, …)
smqp (peer dep) ── message broker driving execution
```

`src/index.js` (~630 lines) wires these together:

1. **Construction** — `Engine(options)` builds a `TypeResolver` from `bpmn-elements` + user `elements` overrides, creates an `Elements.Environment`, and instantiates a `smqp` `Broker` for engine-level events. Defaults can be passed via `Logger`, `scripts` (defaults to `JavaScripts.js` — Node `vm`-based inline script handler), `extensions`, etc.
2. **Source loading** — `addSource({ source | sourceContext })` queues sources. `source` is raw BPMN XML; `sourceContext` is a serializer output (skips XML parse). Both end up as `moddle-context-serializer` `SerializableContext`s.
3. **Execution** — `execute()` resolves all pending sources to `Definition`s (from `bpmn-elements`), starts them via the broker, and listens to broker events to surface activity status (`executing` / `timer` / `wait` / `idle`).
4. **State** — `getState()` walks each definition and pulls its serializable state; `recover(state)` reconstructs definitions; `resume()` restarts execution. As of v25, **a running engine cannot be recovered** — must be stopped first or use a fresh `Engine` instance (see CHANGELOG).

### Conventions worth knowing

- **Symbol-keyed private state.** All instance internals on `Engine`/execution use `Symbol.for('engine')`, `Symbol.for('environment')`, etc. This is the project's privacy convention — don't replace with `#fields` (would break `Object.assign`-based extension and the recover/resume state walking).
- **`getOptionsAndCallback.js`** — small helper that lets every public method accept either `(options, callback)` or `(callback)` style. Preserves the legacy callback-friendly API while the implementation returns Promises.
- **Extensions live under `src/extensions/`.** `ProcessOutputDataObject.js` is the canonical example — extensions plug into `bpmn-elements` via the engine's `extensions` option and are typically called for specific element types. The wider BPMN ecosystem — docs, guides, and additional extensions for this stack — is published at [0dep.se](https://0dep.se); check there before writing a new extension from scratch.
- **Test fixtures under `test/resources/`** are real `.bpmn` XML files plus a few `.json` extensions. `test/resources/JsExtension.js` and `test/resources/js-bpmn-moddle.json` define a custom moddle extension namespace used across multiple feature tests.
- **`mocha-cakes-2` ≠ standard mocha.** Files under `test/feature/` use Gherkin-flavored globals (`Feature`, `Scenario`, `Given`, `When`, `Then`, `And`, `But`). They're ESLint-allowed via the eslint config's globals.

## Build & publishing

- `type: module` in `package.json`; ESM is `src/index.js`, CJS is `lib/index.cjs` (rollup), types are hand-maintained at `types/index.d.ts` (the published `declare module 'bpmn-engine'` surface) with shared interface definitions in `types/interfaces.d.ts` (referenced by `src` JSDoc via the tsconfig `types` path).
- `prepack` runs `dist`, so `npm publish` always ships a fresh CJS bundle. `lib/index.cjs` is tracked in git — regenerate (`npm run dist`) and commit alongside source changes that affect the public API.
- Tests import via package self-reference (`import { Engine } from 'bpmn-engine'`), not `'../src/index.js'`. This exercises the same module resolution consumers use.

## Dependency notes

- **`moddle-context-serializer`** — this engine is the primary consumer that calls `.serialize()` / `deserialize()` for state persistence. The serialized JSON is a _runtime persistence format_ for live engines, so changes to it break upgrade-path for deployed engines, not just code. Keep that in mind when bumping the dep — even a "harmless" field rename in the serializer's output ripples here.
- **`bpmn-elements`** — supplies the behaviour function registry passed to `TypeResolver`. Adding new element types means adding an entry there first.
- **`smqp`** is a `peerDependency` (`>=9`) — same-maintainer message broker that drives both engine-level and definition-level event flow. Consumers install it directly. Coordinated breaking changes are feasible since the same hand maintains it.
- **`bpmn-moddle`** is a regular dep (currently `^9.0.4`). v10 changed default → named export but `src/index.js` still does `import BpmnModdle from 'bpmn-moddle'` — bumping to v10 will break the import.

## Downstream consumers

This engine is the user-facing entry point of the BPMN library stack — most application code that runs BPMN processes uses `bpmn-engine` directly, not the lower-level `bpmn-elements` / `moddle-context-serializer`. State persistence shape (the JSON returned by `getState()`) is therefore the most consumer-sensitive surface — schema changes break stored process state on upgrade.
53 changes: 35 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
# Changelog

## [25.0.1] - 2025-11-14
## v26.0.1 - 2026-07-07

- declare `bpmn-elements`, `bpmn-moddle`, `moddle-context-serializer`, and `debug` as peer dependencies (install and pin them yourself)
- verify `bpmn-moddle` 9 and 10 both work — see [bpmn-moddle 9 vs 10](/docs/bpmn-moddle.md)

## v26.0.0 - 2026-06-13

### Breaking

- major update [`bpmn-elements@18`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md) — non-selected gateway flows are no longer discarded
- update to [`moddle-context-serializer@6`](https://github.com/paed01/moddle-context-serializer/blob/master/CHANGELOG.md)
- require `smqp@>=13` peer dependency

### Other

- ship hand-maintained TypeScript declarations

## v25.0.1 - 2025-11-14

- provenance release to get green badge next to npm package version
- bump all provenance updated packages

## [25.0.0] - 2025-03-17
## v25.0.0 - 2025-03-17

It has been possible to recover a running engine. The execution was overwritten and all references to timers etc was lost. This stops with this version. Either stop the the execution or wait for it to end if the engine should be re-used. It is highly recommended to initiate a new Engine when recovering from state.

Expand All @@ -17,44 +34,44 @@ It has been possible to recover a running engine. The execution was overwritten

- minor update of moddle-context-serializer brings some performance improvement

## [24.0.1] - 2025-03-14
## v24.0.1 - 2025-03-14

- fix recover engine with options not keeping scripts and logger from when initiated

> NB! Next major versions will not accept recovering a running engine.

## [24.0.0] - 2025-02-08
## v24.0.0 - 2025-02-08

- major update [`bpmn-elements@17`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md), something about mitigating weird formatting behaviour

## [23.0.2] - 2024-10-22
## v23.0.2 - 2024-10-22

- move `smqp` to peerDependencies since it's included in `bpmn-elements`
- patch [`bpmn-elements@16.2.1`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)
- use optional chaining and remove futile object creations

## [23.0.1] - 2024-09-08
## v23.0.1 - 2024-09-08

- patch [`bpmn-elements@16.1.0`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)
- fix AppVeyor build badge

## [23.0.0] - 2024-08-08
## v23.0.0 - 2024-08-08

### Breaking

- changed `ConditionalEventDefinition` behaviour in [`bpmn-elements@16`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)

## [22.0.2] - 2024-07-10
## v22.0.2 - 2024-07-10

- patch [`bpmn-elements@15.0.3`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)

## [22.0.1] - 2024-06-19
## v22.0.1 - 2024-06-19

- for a number of years the `extendFn` option has been accepted, but never passed to moddle-context-serializer. Now it is
- fix type declaration for options `extendFn` and `TypeResolver`
- type declare that a generic listener instance with an emit function works just as well as a full blown new EventEmitter

## [22.0.0] - 2024-06-14
## v22.0.0 - 2024-06-14

Performance tweaks.

Expand All @@ -66,48 +83,48 @@ Performance tweaks.
- drop support for node 14 and 16
- JavaScripts.Script property scripts is now a Map

## [21.1.0] - 2024-05-17
## v21.1.0 - 2024-05-17

- minor bump [`bpmn-elements@14.1.0`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md), addresses issue #180

## [21.0.0] - 2024-05-04
## v21.0.0 - 2024-05-04

Stop execution if invalid time duration, cycle, or date is encountered.

### Breaking

- invalid `TimerEventDefinition` timer type value stops execution according to [`bpmn-elements@14`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md). Old behaviour can be achieved by using bpmn-elements@13

## 20.0.2
## v20.0.2

- patch away package prettier from smqp

## 20.0.1
## v20.0.1

- hoist current definition environment output to engine environment output if run fails
- Major bump `bpmn-moddle@9`
- Minor bump [`moddle-context-serializer@4.2`](https://github.com/paed01/moddle-context-serializer/blob/master/CHANGELOG.md)
- Minor bump [`bpmn-elements@13.2.0`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)

## 20.0.0
## v20.0.0

- turn into esm with exports for node
- build with node >= 18, should still work with earlier versions but proceed with caution and make tests
- remove eslint formatting rules in favor of prettier, touched basically all files but now it is "pretty"
- prototype `ProcessOutputDataObject` and make properties id and type readonly

## 19.0.1
## v19.0.1

- Patch [`bpmn-elements`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)

## 19.0.0
## v19.0.0

Upgrade is recommended since nasty evergroving state size is fixed.

- Major bump [`bpmn-elements@13`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)
- remove enumerable flag on prototype properties

## 18.0.0
## v18.0.0

Only breaking if multi-instance sub-process executions are inspected after sub-process run is completed. Picture a multi-instance sequential sub-process with a cardinality of 100. One hundred items in a list occupies some memory. That will not stand. Consequently, they are now removed when iteration completes and eventually collected by gc.

Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,47 @@ BPMN 2.0 execution engine. Open source javascript workflow engine.
- [Changelog](/CHANGELOG.md)
- [Examples](/docs/Examples.md)
- [Upgrade version](/docs/Upgrade.md)
- [Peer dependencies](#peer-dependencies)
- [bpmn-moddle 9 vs 10](/docs/bpmn-moddle.md)
- [Supported elements](#supported-elements)
- [Extensions](#extensions)
- [Debug](#debug)
- [Example process](#a-pretty-image-of-a-process)
- [Acknowledgments](#acknowledgments)

# Peer dependencies

The runtime dependencies are declared as **peer dependencies**, so you install and pin them yourself and the engine reuses your single copy:

```sh
npm install bpmn-engine bpmn-elements bpmn-moddle moddle-context-serializer debug smqp
```

| Peer dependency | Range | Role |
| ---------------------------------------------------------------------------------- | --------- | ------------------------------------------------- |
| [`bpmn-elements`](https://github.com/paed01/bpmn-elements) | `>=18` | element behaviour functions |
| [`bpmn-moddle`](https://github.com/bpmn-io/bpmn-moddle) | `>=9` | BPMN XML parser ([9 vs 10](/docs/bpmn-moddle.md)) |
| [`moddle-context-serializer`](https://github.com/paed01/moddle-context-serializer) | `>=6` | persistable source context |
| [`smqp`](https://github.com/paed01/smqp) | `^13.0.1` | message broker driving execution |
| [`debug`](https://github.com/debug-js/debug) | `>=4` | logging |

`bpmn-moddle` spans a major version on purpose — see [bpmn-moddle 9 vs 10](/docs/bpmn-moddle.md) for the import change and how persisted state stays compatible across the upgrade.

# Supported elements

See [bpmn-elements](https://github.com/paed01/bpmn-elements) for supported elements. The engine only support elements and attributes included in the BPMN 2.0 scheme, but can be extended to understand other schemas and elements.

The aim is to, at least, have BPMN 2.0 [core support](https://www.omg.org/bpmn/Samples/Elements/Core_BPMN_Elements.htm).

# Extensions

The engine can be extended to understand other schemas and elements. Docs, guides, and ready-made extensions for the BPMN stack are published at [0dep.se](https://0dep.se) — check there before writing a new extension from scratch.

Two ready-made extensions:

- [`@0dep/bpmn-extensions`](https://github.com/zerodep/bpmn-extensions)
- [`@onify/flow-extensions`](https://github.com/onify/flow-extensions)

# Debug

This package is shipped with [debug](https://github.com/debug-js/debug) activated with environment variable `DEBUG=bpmn-engine:*`. You can also provide your own logger.
Expand Down
3 changes: 1 addition & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
environment:
matrix:
- nodejs_version: '18'
- nodejs_version: '20'
- nodejs_version: '22'

platform:
- x64
Expand Down
17 changes: 8 additions & 9 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<!-- version -->

# 25.0.1 API Reference

<!-- versionstop -->
# API Reference

<!-- toc -->

Expand All @@ -21,7 +17,7 @@
- [`resume([options, [callback]])`](#resumeoptions-callback)
- [Execution API](#execution-api)
- [`getActivityById(activityId)`](#getactivitybyidactivityid)
- [`getState()`](#getstate)
- [`getState()`](#getstate-1)
- [`signal(message[, options])`](#signalmessage-options)
- [`cancelActivity(message)`](#cancelactivitymessage)
- [Engine events](#engine-events)
Expand Down Expand Up @@ -319,7 +315,7 @@ 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';
import { Serializer, TypeResolver } from 'moddle-context-serializer';

const engine = new Engine({
name: 'add source',
Expand Down Expand Up @@ -413,11 +409,14 @@ Asynchronous function to get state of a running execution.

The saved state will include the following content:

- `state`: `running` or `idle`
- `name`: engine name
- `state`: `running`, `idle`, `stopped`, or `error`
- `stopped`: boolean stopped
- `engineVersion`: module package version
- `moddleOptions`: Engine moddleOptions
- `environment`: serialized engine environment
- `definitions`: List of definitions
- `state`: State of definition, `pending`, `running`, or `completed`
- `source`: serialized definition source
- `processes`: Object with processes with id as key
- `variables`: Execution variables
- `services`: Execution services
Expand Down
Loading