├── .editorconfig ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .mocharc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── docs ├── API.md ├── Examples.md └── Upgrade.md ├── eslint.config.js ├── images └── mother-of-all.png ├── lib └── index.cjs ├── package.json ├── rollup.config.js ├── scripts └── generate-api-toc.js ├── src ├── JavaScripts.js ├── Logger.js ├── extensions │ └── ProcessOutputDataObject.js ├── getOptionsAndCallback.js └── index.js ├── test ├── Engine-test.js ├── feature │ ├── Engine-feature.js │ ├── backward-compatibility-feature.js │ ├── call-activity-feature.js │ ├── conditional-event-feature.js │ ├── engine-output-feature.js │ ├── extend-feature.js │ ├── issues-feature.js │ ├── issues │ │ ├── issue-163-feature.js │ │ ├── issue-172-feature.js │ │ ├── issue-187-feature.js │ │ └── issue-199-feature.js │ ├── multiple-sources-feature.js │ ├── resume-feature.js │ ├── scripts-feature.js │ ├── shake-feature.js │ └── timers-feature.js ├── helpers │ ├── factory.js │ └── testHelpers.js ├── issues-test.js ├── lib │ └── JavaScripts-test.js └── resources │ ├── JsExtension.js │ ├── README.md │ ├── bound-error-and-timer.bpmn │ ├── bound-error.bpmn │ ├── boundary-non-interupting-timer.bpmn │ ├── boundary-timeout.bpmn │ ├── call-activity.bpmn │ ├── conditional-bound-js-event.bpmn │ ├── diagram_1.bpmn │ ├── forms.bpmn │ ├── issue-139.bpmn │ ├── issue-163.bpmn │ ├── issue-187.bpmn │ ├── issue-19.bpmn │ ├── issue-4.bpmn │ ├── js-bpmn-moddle.json │ ├── lanes.bpmn │ ├── loop.bpmn │ ├── messaging.bpmn │ ├── mother-of-all.bpmn │ ├── multiple-endEvents.bpmn │ ├── multiple-joins.bpmn │ ├── multiple-multiple-inbound.bpmn │ ├── one-or-the-other.bpmn │ ├── send-signal.bpmn │ ├── service-task-io.bpmn │ ├── service-task-operation.bpmn │ ├── service-task.bpmn │ ├── signals.bpmn │ ├── simple-task.bpmn │ ├── sub-process.bpmn │ ├── succeeding-joins.bpmn │ ├── task-multiple-inbound.bpmn │ ├── task-with-multi-instance-loop.bpmn │ ├── timer-event.bpmn │ ├── timer-start-event.bpmn │ └── timers.bpmn ├── tsconfig.json └── types └── bpmn-engine.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Set default charset and indentation style 12 | [*.{js,json,html,dust,styl,yml,md}] 13 | charset = utf-8 14 | indent_style = space 15 | indent_size = 2 16 | trim_trailing_whitespace = true 17 | max_line_length = 160 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18, 20, 22, latest] 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm i 23 | - run: npm run test:lcov 24 | - name: Coveralls 25 | uses: coverallsapp/github-action@v2 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | flag-name: run-${{ matrix.node-version }} 29 | parallel: true 30 | 31 | finish: 32 | needs: build 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Coveralls Finished 36 | uses: coverallsapp/github-action@v2 37 | with: 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | parallel-finished: true 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory and npm log 2 | node_modules 3 | npm-debug.log 4 | tmp 5 | 6 | # Code coverage results 7 | /coverage 8 | coverage* 9 | 10 | # Built package 11 | /*.tgz 12 | 13 | # Project files 14 | .tern-port 15 | .tern-project 16 | .idea 17 | *.iml 18 | .vscode 19 | 20 | # Compiled client resources 21 | /public/ 22 | 23 | # Revision file 24 | config/_revision 25 | 26 | # OS X 27 | .DS_Store 28 | 29 | # vim 30 | *.swp 31 | *.swo 32 | 33 | # eslint 34 | .eslintcache 35 | 36 | package-lock.json 37 | 38 | *.log 39 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "recursive": true, 3 | "reporter": "spec", 4 | "timeout": 1000, 5 | "ui": "mocha-cakes-2", 6 | "require": ["chai/register-expect.js"] 7 | } 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/**/*.cjs 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 140, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-engine/ef5552e39a0095e02547b6d2ba688a17e223ed52/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpmn-engine 2 | 3 | [![Project Status: Active - The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 4 | 5 | [![Build](https://github.com/paed01/bpmn-engine/actions/workflows/build.yaml/badge.svg)](https://github.com/paed01/bpmn-engine/actions/workflows/build.yaml)[![Build status](https://ci.appveyor.com/api/projects/status/670n39fivq1g3nu5/branch/master?svg=true)](https://ci.appveyor.com/project/paed01/bpmn-engine/branch/master)[![Coverage Status](https://coveralls.io/repos/github/paed01/bpmn-engine/badge.svg?branch=master)](https://coveralls.io/github/paed01/bpmn-engine?branch=master) 6 | 7 | # Introduction 8 | 9 | BPMN 2.0 execution engine. Open source javascript workflow engine. 10 | 11 | - [API](/docs/API.md) 12 | - [Changelog](/CHANGELOG.md) 13 | - [Examples](/docs/Examples.md) 14 | - [Upgrade version](/docs/Upgrade.md) 15 | - [Supported elements](#supported-elements) 16 | - [Debug](#debug) 17 | - [Example process](#a-pretty-image-of-a-process) 18 | - [Acknowledgments](#acknowledgments) 19 | 20 | # Supported elements 21 | 22 | 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. 23 | 24 | The aim is to, at least, have BPMN 2.0 [core support](https://www.omg.org/bpmn/Samples/Elements/Core_BPMN_Elements.htm). 25 | 26 | # Debug 27 | 28 | 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. 29 | 30 | More granular debugging can be achieved by filtering on element type: 31 | 32 | ```sh 33 | DEBUG=*scripttask*,*:error:* 34 | ``` 35 | 36 | or on Windows PowerShell: 37 | 38 | ```powershell 39 | $env:DEBUG='bpmn-engine:*' 40 | ``` 41 | 42 | and to turn it off you need to: 43 | 44 | ```powershell 45 | $env:DEBUG='' 46 | ``` 47 | 48 | # A pretty image of a process 49 | 50 | ![Mother of all](https://raw.github.com/paed01/bpmn-engine/master/images/mother-of-all.png) 51 | 52 | # Acknowledgments 53 | 54 | The **bpmn-engine** resides upon the excellent library [bpmn-io/bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle) developed by [bpmn.io](https://bpmn.io/) 55 | 56 | All diagrams are designed with [Camunda modeler](https://camunda.com/download/modeler/). 57 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '18' 4 | - nodejs_version: '20' 5 | 6 | platform: 7 | - x64 8 | 9 | install: 10 | - ps: Install-Product node $env:nodejs_version $env:platform 11 | - npm install 12 | 13 | build: off 14 | 15 | test_script: 16 | - node --version 17 | - npm --version 18 | - npm run wintest 19 | -------------------------------------------------------------------------------- /docs/Upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrade 2 | 3 | # < v14 4 | 5 | Since v14 of the engine output is no longer shared between definition and processes. To upgrade a saved state before version 14 you can run the following script that adds process environment to state. 6 | 7 | ```javascript 8 | export function upgradeStateToVersion14(state) { 9 | const stateVersion = getSemverVersion(state.engineVersion); 10 | if (!stateVersion || stateVersion.major >= 14) return state; 11 | 12 | return polyfillProcessEnvironment(state); 13 | } 14 | 15 | function polyfillProcessEnvironment(state) { 16 | if (!state.definitions?.length) return state; 17 | 18 | const polyfilledState = JSON.parse(JSON.stringify(state)); 19 | for (const definition of polyfilledState.definitions) { 20 | if (!definition.environment) continue; 21 | if (!definition.execution) continue; 22 | if (!definition.execution.processes) continue; 23 | 24 | for (const bp of definition.execution.processes) { 25 | addProcessEnvironment(definition.environment, bp); 26 | } 27 | } 28 | 29 | return polyfilledState; 30 | } 31 | 32 | function addProcessEnvironment(environment, processState) { 33 | processState.environment = JSON.parse(JSON.stringify(environment)); 34 | } 35 | 36 | function getSemverVersion(version) { 37 | if (typeof version !== 'string') return; 38 | const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); 39 | if (!match) return; 40 | const [, major, minor, patch] = match; 41 | return { 42 | major: Number(major), 43 | minor: Number(minor), 44 | patch: Number(patch), 45 | }; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | 4 | const rules = { 5 | 'dot-notation': [2, { allowKeywords: true }], 6 | 'eol-last': 2, 7 | eqeqeq: 2, 8 | 'linebreak-style': ['error', 'unix'], 9 | 'no-alert': 2, 10 | 'no-array-constructor': 2, 11 | 'no-caller': 2, 12 | 'no-catch-shadow': 2, 13 | 'no-console': 1, 14 | 'no-eval': 2, 15 | 'no-extend-native': 2, 16 | 'no-extra-bind': 2, 17 | 'no-fallthrough': 'off', 18 | 'no-implied-eval': 2, 19 | 'no-iterator': 2, 20 | 'no-label-var': 2, 21 | 'no-labels': 2, 22 | 'no-lone-blocks': 2, 23 | 'no-loop-func': 2, 24 | 'no-multi-spaces': 2, 25 | 'no-multi-str': 2, 26 | 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0, maxBOF: 0 }], 27 | 'no-new-func': 2, 28 | 'no-new-object': 2, 29 | 'no-new-wrappers': 2, 30 | 'no-octal-escape': 2, 31 | 'no-path-concat': 2, 32 | 'no-process-exit': 2, 33 | 'no-proto': 2, 34 | 'no-prototype-builtins': 2, 35 | 'no-return-assign': 2, 36 | 'no-script-url': 2, 37 | 'no-sequences': 2, 38 | 'no-shadow-restricted-names': 2, 39 | 'no-shadow': 2, 40 | 'no-spaced-func': 2, 41 | 'no-trailing-spaces': 2, 42 | 'no-undef-init': 2, 43 | 'no-undef': 2, 44 | 'no-underscore-dangle': 0, 45 | 'no-unused-expressions': 2, 46 | 'no-unused-vars': 2, 47 | 'no-use-before-define': 0, 48 | 'no-var': 2, 49 | 'no-with': 2, 50 | 'prefer-const': ['error', { destructuring: 'all' }], 51 | 'require-atomic-updates': 0, 52 | 'require-await': 2, 53 | 'semi-spacing': [2, { before: false, after: true }], 54 | semi: [2, 'always'], 55 | 'space-before-blocks': 2, 56 | 'space-before-function-paren': [2, { anonymous: 'never', named: 'never' }], 57 | 'space-infix-ops': 2, 58 | 'space-unary-ops': [2, { words: true, nonwords: false }], 59 | 'unicode-bom': ['error', 'never'], 60 | yoda: [2, 'never'], 61 | }; 62 | 63 | export default [ 64 | js.configs.recommended, 65 | { 66 | languageOptions: { 67 | parserOptions: { 68 | sourceType: 'module', 69 | ecmaVersion: 2020, 70 | }, 71 | }, 72 | rules, 73 | }, 74 | { 75 | files: ['src/**/*.js', 'scripts/**/*.js'], 76 | languageOptions: { 77 | globals: { 78 | ...globals.nodeBuiltin, 79 | }, 80 | }, 81 | }, 82 | { 83 | files: ['test/**/*.js'], 84 | languageOptions: { 85 | globals: { 86 | ...globals.nodeBuiltin, 87 | ...globals.mocha, 88 | expect: 'readonly', 89 | beforeEachScenario: 'readonly', 90 | Buffer: 'readonly', 91 | Feature: 'readonly', 92 | Scenario: 'readonly', 93 | Given: 'readonly', 94 | When: 'readonly', 95 | Then: 'readonly', 96 | And: 'readonly', 97 | But: 'readonly', 98 | }, 99 | }, 100 | rules: { 101 | 'no-unused-expressions': 0, 102 | 'no-var': 1, 103 | }, 104 | }, 105 | { 106 | ignores: ['coverage/**/*', 'node_modules/**/*', 'tmp/*', 'lib/*'], 107 | }, 108 | ]; 109 | -------------------------------------------------------------------------------- /images/mother-of-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-engine/ef5552e39a0095e02547b6d2ba688a17e223ed52/images/mother-of-all.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpmn-engine", 3 | "description": "BPMN 2.0 execution engine. Open source javascript workflow engine.", 4 | "version": "25.0.0", 5 | "type": "module", 6 | "module": "./src/index.js", 7 | "main": "./lib/index.cjs", 8 | "types": "./types/bpmn-engine.d.ts", 9 | "sideEffects": false, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/paed01/bpmn-engine.git" 13 | }, 14 | "author": { 15 | "name": "Pål Edman", 16 | "url": "https://github.com/paed01" 17 | }, 18 | "engines": { 19 | "node": ">=18" 20 | }, 21 | "files": [ 22 | "lib/", 23 | "src/", 24 | "types/" 25 | ], 26 | "scripts": { 27 | "test": "mocha -p -R @bonniernews/hot-bev", 28 | "posttest": "npm run lint && npm run toc && npm run dist", 29 | "lint": "eslint . --cache && prettier . -c --cache", 30 | "wintest": "mocha", 31 | "cov:html": "c8 -r html -r text mocha -p -R @bonniernews/hot-bev", 32 | "test:lcov": "c8 -r lcov mocha && npm run lint", 33 | "toc": "node scripts/generate-api-toc ./docs/API.md,./docs/Examples.md", 34 | "test-md": "texample ./docs/API.md,./docs/Examples.md,./docs/Upgrade.md", 35 | "dist": "rollup -c", 36 | "prepack": "npm run dist" 37 | }, 38 | "keywords": [ 39 | "workflow", 40 | "engine", 41 | "process", 42 | "automation", 43 | "bpmn", 44 | "bpmn 2" 45 | ], 46 | "license": "MIT", 47 | "licenses": [ 48 | { 49 | "type": "MIT", 50 | "url": "https://github.com/paed01/bpmn-engine/master/LICENSE" 51 | } 52 | ], 53 | "devDependencies": { 54 | "@bonniernews/hot-bev": "^0.4.0", 55 | "@rollup/plugin-commonjs": "^28.0.0", 56 | "@types/bpmn-moddle": "^5.1.6", 57 | "@types/node": "^18.19.31", 58 | "bent": "^7.3.12", 59 | "c8": "^10.0.0", 60 | "camunda-bpmn-moddle": "^7.0.1", 61 | "chai": "^5.1.0", 62 | "chronokinesis": "^6.0.0", 63 | "eslint": "^9.10.0", 64 | "markdown-toc": "^1.2.0", 65 | "mocha": "^11.0.1", 66 | "mocha-cakes-2": "^3.3.0", 67 | "prettier": "^3.2.5", 68 | "rollup": "^4.10.0", 69 | "texample": "^0.0.8" 70 | }, 71 | "dependencies": { 72 | "bpmn-elements": "^17.0.0", 73 | "bpmn-moddle": "^9.0.1", 74 | "debug": "^4.3.7", 75 | "moddle-context-serializer": "^4.3.0" 76 | }, 77 | "peerDependencies": { 78 | "smqp": ">=9" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | import { fileURLToPath } from 'node:url'; 3 | 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | 6 | const nodeRequire = createRequire(fileURLToPath(import.meta.url)); 7 | const { module, main, dependencies, peerDependencies } = nodeRequire('./package.json'); 8 | 9 | export default { 10 | input: module, 11 | plugins: [ 12 | commonjs({ 13 | sourceMap: false, 14 | }), 15 | ], 16 | output: [ 17 | { 18 | file: main, 19 | format: 'cjs', 20 | exports: 'named', 21 | footer: 'module.exports = Object.assign(exports.default, exports);', 22 | }, 23 | ], 24 | external: ['node:module', 'node:url', 'node:vm', 'node:events', ...Object.keys({ ...dependencies, ...peerDependencies })], 25 | }; 26 | -------------------------------------------------------------------------------- /scripts/generate-api-toc.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import { createRequire } from 'node:module'; 3 | import { fileURLToPath } from 'node:url'; 4 | import Toc from 'markdown-toc'; 5 | 6 | const nodeRequire = createRequire(fileURLToPath(import.meta.url)); 7 | const { version } = nodeRequire('../package.json'); 8 | 9 | const filenames = getFileNames(); 10 | 11 | function getFileNames() { 12 | const arg = process.argv[2] || './API.md'; 13 | return arg.split(','); 14 | } 15 | 16 | function generate(filename) { 17 | const api = fs.readFileSync(filename, 'utf8'); 18 | const tocOptions = { 19 | bullets: '-', 20 | slugify(text) { 21 | return text 22 | .toLowerCase() 23 | .replace(/\s/g, '-') 24 | .replace(/[^\w-]/g, ''); 25 | }, 26 | }; 27 | 28 | const output = Toc.insert(api, tocOptions).replace( 29 | /(.|\n)*/, 30 | '\n\n# ' + version + ' API Reference\n\n' 31 | ); 32 | 33 | fs.writeFileSync(filename, output); 34 | } 35 | 36 | filenames.forEach(generate); 37 | -------------------------------------------------------------------------------- /src/JavaScripts.js: -------------------------------------------------------------------------------- 1 | import { Script } from 'node:vm'; 2 | 3 | export default function Scripts(disableDummy) { 4 | if (!(this instanceof Scripts)) return new Scripts(disableDummy); 5 | this.scripts = new Map(); 6 | this.disableDummy = disableDummy; 7 | } 8 | 9 | Scripts.prototype.register = function register({ id, type, behaviour, logger, environment }) { 10 | let scriptBody, language; 11 | 12 | switch (type) { 13 | case 'bpmn:SequenceFlow': { 14 | if (!behaviour.conditionExpression) return; 15 | language = behaviour.conditionExpression.language; 16 | if (!language) return; 17 | scriptBody = behaviour.conditionExpression.body; 18 | break; 19 | } 20 | default: { 21 | language = behaviour.scriptFormat; 22 | scriptBody = behaviour.script; 23 | } 24 | } 25 | 26 | const filename = `${type}/${id}`; 27 | if (!language || !scriptBody) { 28 | if (this.disableDummy) return; 29 | const script = new DummyScript(language, filename, logger); 30 | this.scripts.set(id, script); 31 | return script; 32 | } 33 | 34 | if (!/^(javascript|js)$/i.test(language)) return; 35 | 36 | const script = new JavaScript(language, filename, scriptBody, environment); 37 | this.scripts.set(id, script); 38 | 39 | return script; 40 | }; 41 | 42 | Scripts.prototype.getScript = function getScript(language, { id }) { 43 | return this.scripts.get(id); 44 | }; 45 | 46 | function JavaScript(language, filename, scriptBody, environment) { 47 | this.id = filename; 48 | this.script = new Script(scriptBody, { filename }); 49 | this.language = language; 50 | this.environment = environment; 51 | } 52 | 53 | JavaScript.prototype.execute = function execute(executionContext, callback) { 54 | const timers = this.environment.timers.register(executionContext); 55 | return this.script.runInNewContext({ ...executionContext, ...timers, next: callback }); 56 | }; 57 | 58 | function DummyScript(language, filename, logger) { 59 | this.id = filename; 60 | this.isDummy = true; 61 | this.language = language; 62 | this.logger = logger; 63 | } 64 | 65 | DummyScript.prototype.execute = function execute(executionContext, callback) { 66 | const { id, executionId } = executionContext.content; 67 | this.logger.debug(`<${executionId} (${id})> passthrough dummy script ${this.language || 'esperanto'}`); 68 | callback(); 69 | }; 70 | -------------------------------------------------------------------------------- /src/Logger.js: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | 3 | export default function Logger(scope) { 4 | return { 5 | debug: Debug('bpmn-engine:' + scope), 6 | error: Debug('bpmn-engine:error:' + scope), 7 | warn: Debug('bpmn-engine:warn:' + scope), 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/extensions/ProcessOutputDataObject.js: -------------------------------------------------------------------------------- 1 | const kDataObjectDef = Symbol.for('data object definition'); 2 | 3 | export default function ProcessOutputDataObject(dataObjectDef, { environment }) { 4 | this[kDataObjectDef] = dataObjectDef; 5 | this.environment = environment; 6 | this.behaviour = dataObjectDef.behaviour; 7 | this.name = dataObjectDef.name; 8 | this.parent = dataObjectDef.parent; 9 | } 10 | 11 | Object.defineProperties(ProcessOutputDataObject.prototype, { 12 | id: { 13 | get() { 14 | return this[kDataObjectDef].id; 15 | }, 16 | }, 17 | type: { 18 | get() { 19 | return this[kDataObjectDef].type; 20 | }, 21 | }, 22 | }); 23 | 24 | ProcessOutputDataObject.prototype.read = function readDataObject(broker, exchange, routingKeyPrefix, messageProperties) { 25 | const environment = this.environment; 26 | const { id, name, type } = this; 27 | const value = environment.variables.data?.[this.id]; 28 | return broker.publish(exchange, `${routingKeyPrefix}response`, { id, name, type, value }, messageProperties); 29 | }; 30 | 31 | ProcessOutputDataObject.prototype.write = function writeDataObject(broker, exchange, routingKeyPrefix, value, messageProperties) { 32 | const environment = this.environment; 33 | const { id, name, type } = this; 34 | 35 | const data = (environment.variables.data = environment.variables.data || {}); 36 | data[id] = value; 37 | 38 | const outputData = (environment.output.data = environment.output.data || {}); 39 | outputData[id] = value; 40 | return broker.publish(exchange, `${routingKeyPrefix}response`, { id, name, type, value }, messageProperties); 41 | }; 42 | -------------------------------------------------------------------------------- /src/getOptionsAndCallback.js: -------------------------------------------------------------------------------- 1 | export default function getOptionsAndCallback(optionsOrCallback, callback) { 2 | let options; 3 | if (typeof optionsOrCallback === 'function') { 4 | callback = optionsOrCallback; 5 | } else { 6 | options = optionsOrCallback; 7 | } 8 | 9 | return [options, callback]; 10 | } 11 | -------------------------------------------------------------------------------- /test/feature/backward-compatibility-feature.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | import { Engine } from '../../src/index.js'; 3 | 4 | Feature('Backward compatability', () => { 5 | Scenario('Version 13 state without process environment', () => { 6 | let engine, listener; 7 | Given('a bpmn source that access environment variables', () => { 8 | const source = ` 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | 33 | `; 34 | 35 | engine = Engine({ 36 | name: 'Engine feature', 37 | source, 38 | settings: { 39 | mySettings: { bar: 'baz' }, 40 | }, 41 | services: { 42 | serviceFn() {}, 43 | }, 44 | }); 45 | }); 46 | 47 | And('a listener', () => { 48 | listener = new EventEmitter(); 49 | }); 50 | 51 | let waiting; 52 | And('listening once for wait', () => { 53 | waiting = new Promise((resolve) => { 54 | listener.once('wait', (api) => { 55 | resolve(api); 56 | }); 57 | }); 58 | }); 59 | 60 | When('source is executed', () => { 61 | engine.execute({ listener }); 62 | }); 63 | 64 | And('user task is in a waiting state', async () => { 65 | await waiting; 66 | }); 67 | 68 | let state; 69 | Then('process state is saved without environment', async () => { 70 | await engine.stop(); 71 | 72 | state = await engine.getState(); 73 | 74 | expect(state).to.have.property('state', 'stopped'); 75 | expect(state.definitions).to.have.length(1); 76 | expect(state.definitions[0].source).to.be.ok; 77 | expect(state.definitions[0].execution.processes[0]).to.be.ok; 78 | }); 79 | 80 | Given('in version 13 and below process state environment output is shared with definition', () => { 81 | state.engineVersion = '13.0.0'; 82 | state.definitions[0].environment.output = state.definitions[0].execution.processes[0].environment.output; 83 | }); 84 | 85 | And('process state environment is not present', () => { 86 | delete state.definitions[0].execution.processes[0].environment; 87 | }); 88 | 89 | let recovered; 90 | When('engine is recovered with old state in latest version', () => { 91 | const upgradedState = upgradeStateToVersion14(state); 92 | recovered = Engine({ 93 | name: 'Recovered engine', 94 | }).recover(upgradedState); 95 | }); 96 | 97 | let execution; 98 | And('resumed', async () => { 99 | execution = await recovered.resume(); 100 | }); 101 | 102 | let ended; 103 | And('user task is signaled', () => { 104 | ended = recovered.waitFor('end'); 105 | 106 | execution.signal({ id: 'task' }); 107 | }); 108 | 109 | Then('run completes', async () => { 110 | await ended; 111 | expect(execution.environment.output.result).to.deep.include({ 112 | foo: 'bar', 113 | bar: 'baz', 114 | fields: 'run.execute', 115 | content: 'theProcess', 116 | }); 117 | expect(execution.environment.output.result).to.have.property('properties'); 118 | }); 119 | }); 120 | }); 121 | 122 | function upgradeStateToVersion14(state) { 123 | const stateVersion = getSemverVersion(state.engineVersion); 124 | if (!stateVersion || stateVersion.major >= 14) return state; 125 | 126 | return polyfillProcessEnvironment(state); 127 | } 128 | 129 | function polyfillProcessEnvironment(state) { 130 | if (!state.definitions?.length) return state; 131 | 132 | const polyfilledState = JSON.parse(JSON.stringify(state)); 133 | for (const definition of polyfilledState.definitions) { 134 | if (!definition.environment) continue; 135 | if (!definition.execution) continue; 136 | if (!definition.execution.processes) continue; 137 | 138 | for (const bp of definition.execution.processes) { 139 | addProcessEnvironment(definition.environment, bp); 140 | } 141 | } 142 | 143 | return polyfilledState; 144 | } 145 | 146 | function addProcessEnvironment(environment, processState) { 147 | processState.environment = JSON.parse(JSON.stringify(environment)); 148 | } 149 | 150 | function getSemverVersion(version) { 151 | if (typeof version !== 'string') return; 152 | const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); 153 | if (!match) return; 154 | const [, major, minor, patch] = match; 155 | return { 156 | major: Number(major), 157 | minor: Number(minor), 158 | patch: Number(patch), 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /test/feature/engine-output-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../src/index.js'; 2 | 3 | Feature('Engine output', () => { 4 | Scenario('Process completes with output', () => { 5 | let engine; 6 | Given('a process with output', () => { 7 | const source = ` 8 | 9 | 10 | 11 | 16 | 17 | 18 | `; 19 | 20 | engine = new Engine({ source }); 21 | }); 22 | 23 | let end; 24 | When('ran', () => { 25 | end = engine.waitFor('end'); 26 | engine.execute(); 27 | }); 28 | 29 | Then('run completes', () => { 30 | return end; 31 | }); 32 | 33 | And('engine state contain hoisted process output', async () => { 34 | expect((await engine.getState()).environment.output).to.deep.equal({ foo: 'bar' }); 35 | }); 36 | 37 | Given('a process with call activity and a called process', () => { 38 | const source = ` 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | `; 61 | 62 | engine = new Engine({ source }); 63 | }); 64 | 65 | When('ran', () => { 66 | end = engine.waitFor('end'); 67 | engine.execute(); 68 | }); 69 | 70 | Then('run completes', () => { 71 | return end; 72 | }); 73 | 74 | And('engine state contain hoisted main process output', async () => { 75 | expect((await engine.getState()).environment.output).to.deep.equal({ foo: 'bar' }); 76 | }); 77 | }); 78 | 79 | Scenario('process fails', () => { 80 | let engine; 81 | Given('a process with output', () => { 82 | const source = ` 83 | 84 | 85 | 86 | 94 | 95 | 96 | `; 97 | 98 | engine = new Engine({ source }); 99 | }); 100 | 101 | let errored; 102 | When('ran', () => { 103 | errored = engine.waitFor('error'); 104 | engine.execute(); 105 | }); 106 | 107 | Then('run fails', () => { 108 | return errored; 109 | }); 110 | 111 | And('engine state contain hoisted process output', async () => { 112 | expect((await engine.getState()).environment.output).to.deep.equal({ foo: 'bar' }); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/feature/issues/issue-163-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../../src/index.js'; 2 | import * as factory from '../../helpers/factory.js'; 3 | 4 | Feature('issue 163', () => { 5 | Scenario('Get sub process postponed', () => { 6 | let engine, execution; 7 | When('one usertask and two user tasks in sub process and finally a user task', async () => { 8 | const source = factory.resource('issue-163.bpmn'); 9 | 10 | engine = Engine({ 11 | source, 12 | }); 13 | 14 | execution = await engine.execute(); 15 | }); 16 | 17 | let postponed; 18 | Then('execution waits for both first user task', () => { 19 | postponed = execution.getPostponed(); 20 | expect(postponed).to.have.length(1); 21 | expect(postponed.find(({ type }) => type === 'bpmn:UserTask')).to.be.ok; 22 | }); 23 | 24 | When('first user task is signaled', () => { 25 | execution.signal({ id: postponed[0].id }); 26 | }); 27 | 28 | Then('execution has started sub process', () => { 29 | postponed = execution.getPostponed(); 30 | expect(postponed).to.have.length(1); 31 | expect(postponed.find(({ type }) => type === 'bpmn:SubProcess')).to.be.ok; 32 | }); 33 | 34 | let subPostponed; 35 | And('first user task in sub process is waiting to be signaled', () => { 36 | subPostponed = postponed[0].getPostponed(); 37 | expect(subPostponed).to.have.length(2); 38 | expect(subPostponed.find(({ type }) => type === 'bpmn:UserTask')).to.be.ok; 39 | }); 40 | 41 | When('first subprocess user task is signaled', () => { 42 | execution.signal({ id: subPostponed.find(({ type }) => type === 'bpmn:UserTask').id }); 43 | }); 44 | 45 | Then('execution is still waiting for sub process', () => { 46 | postponed = execution.getPostponed(); 47 | expect(postponed).to.have.length(1); 48 | expect(postponed.find(({ type }) => type === 'bpmn:SubProcess')).to.be.ok; 49 | }); 50 | 51 | And('second user task in sub process is waiting to be signaled', () => { 52 | subPostponed = postponed[0].getPostponed(); 53 | expect(subPostponed).to.have.length(2); 54 | expect(subPostponed.find(({ type }) => type === 'bpmn:UserTask')).to.be.ok; 55 | }); 56 | 57 | When('second subprocess user task is signaled', () => { 58 | execution.signal({ id: subPostponed.find(({ type }) => type === 'bpmn:UserTask').id }); 59 | }); 60 | 61 | Then('execution is waiting for final user task', () => { 62 | postponed = execution.getPostponed(); 63 | expect(postponed).to.have.length(1); 64 | expect(postponed.find(({ type }) => type === 'bpmn:UserTask')).to.be.ok; 65 | }); 66 | 67 | let ended; 68 | When('final user task is signaled', () => { 69 | ended = engine.waitFor('end'); 70 | execution.signal({ id: postponed[0].id }); 71 | }); 72 | 73 | Then('execution completes', () => { 74 | return ended; 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/feature/issues/issue-172-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../../src/index.js'; 2 | 3 | Feature('issue 172', () => { 4 | Scenario('Postpone intermediate throw event by formatting', () => { 5 | let engine; 6 | const agiCalls = []; 7 | Given('start event, intermediate throw event, and end event', () => { 8 | const source = ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | `; 18 | 19 | engine = Engine({ 20 | name: 'issue-172', 21 | source, 22 | services: { 23 | agi(...args) { 24 | return new Promise((resolve) => 25 | process.nextTick(() => { 26 | agiCalls.push(args); 27 | resolve({ called: true }); 28 | }) 29 | ); 30 | }, 31 | }, 32 | extensions: { 33 | dingdong(activity) { 34 | if (activity.type === 'bpmn:Process') return; 35 | return { 36 | activate() { 37 | activity.on( 38 | 'enter', 39 | async (elementApi) => { 40 | activity.broker.publish('format', 'run.format.start', { endRoutingKey: 'run.format.complete' }); 41 | const formatting = await elementApi.environment.services.agi(elementApi.content); 42 | activity.broker.publish('format', 'run.format.complete', formatting); 43 | }, 44 | { consumerTag: 'format-on-enter' } 45 | ); 46 | }, 47 | deactivate() { 48 | activity.broker.cancel('format-on-enter'); 49 | }, 50 | }; 51 | }, 52 | }, 53 | }); 54 | }); 55 | 56 | let end; 57 | const activityEnded = []; 58 | When('executed', () => { 59 | end = engine.waitFor('end'); 60 | engine.broker.subscribeTmp('event', 'activity.end', (_, message) => activityEnded.push(message.content), { noAck: true }); 61 | return engine.execute(); 62 | }); 63 | 64 | Then('execution completed', () => { 65 | return end; 66 | }); 67 | 68 | And('start event is formatted', () => { 69 | expect(agiCalls).to.have.length(3); 70 | expect(agiCalls[0][0].type).to.equal('bpmn:StartEvent'); 71 | expect(activityEnded).to.have.length(3); 72 | expect(activityEnded[0].type).to.equal('bpmn:StartEvent'); 73 | expect(activityEnded[0].called).to.be.true; 74 | }); 75 | 76 | And('intermediate throw event is formatted', () => { 77 | expect(agiCalls[1][0].type).to.equal('bpmn:IntermediateThrowEvent'); 78 | expect(activityEnded[1].type).to.equal('bpmn:IntermediateThrowEvent'); 79 | expect(activityEnded[1].called).to.be.true; 80 | }); 81 | 82 | And('end event is formatted', () => { 83 | expect(agiCalls[2][0].type).to.equal('bpmn:EndEvent'); 84 | expect(activityEnded[2].type).to.equal('bpmn:EndEvent'); 85 | expect(activityEnded[2].called).to.be.true; 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/feature/issues/issue-187-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../../src/index.js'; 2 | import * as factory from '../../helpers/factory.js'; 3 | import { camundaBpmnModdle as camunda } from '../../helpers/testHelpers.js'; 4 | 5 | const source = factory.resource('issue-187.bpmn'); 6 | 7 | Feature('issue 187 - Complete condition in multi-instance loop with custom expression parser', () => { 8 | Scenario('user task is looped over list of handlers', () => { 9 | let engine; 10 | Given('start event, intermediate throw event, and end event', () => { 11 | engine = getEngine187(); 12 | }); 13 | 14 | let end; 15 | When('executed', () => { 16 | end = engine.waitFor('end'); 17 | return engine.execute(); 18 | }); 19 | 20 | And('first manual task is signaled', () => { 21 | engine.execution.signal({ id: engine.execution.getPostponed()[0].id }); 22 | }); 23 | 24 | And('first user task is signaled', () => { 25 | engine.execution.signal({ id: engine.execution.getPostponed()[0].id }); 26 | }); 27 | 28 | let loopedTask; 29 | Then('looped user task is waiting', () => { 30 | loopedTask = engine.execution.getPostponed().find((api) => api.content.isMultiInstance); 31 | expect(loopedTask).to.be.ok; 32 | }); 33 | 34 | When('first handler signals with pass', () => { 35 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: true }); 36 | }); 37 | 38 | Then('first handler iterated user task is waiting', () => { 39 | loopedTask = engine.execution.getPostponed().find((api) => api.content.isMultiInstance); 40 | expect(loopedTask).to.be.ok; 41 | }); 42 | 43 | When('second and third handler signals with pass', () => { 44 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: true }); 45 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: true }); 46 | }); 47 | 48 | let task; 49 | Then('a modify manual task is waiting', () => { 50 | task = engine.execution.getPostponed().pop(); 51 | expect(task).to.be.ok; 52 | }); 53 | 54 | When('manual task is signalled', () => { 55 | engine.execution.signal({ id: task.id }); 56 | }); 57 | 58 | Then('execution loops back to first user task', () => { 59 | task = engine.execution.getPostponed().pop(); 60 | expect(task).to.be.ok; 61 | }); 62 | 63 | When('first user task is signalled again', () => { 64 | engine.execution.signal({ id: task.id }); 65 | }); 66 | 67 | Then('withdrawal task is waiting', () => { 68 | task = engine.execution.getPostponed().find(({ id }) => id === 'task_with_draw'); 69 | expect(task).to.be.ok; 70 | }); 71 | 72 | When('withdrawal task is signalled', () => { 73 | engine.execution.signal({ id: task.id }); 74 | }); 75 | 76 | Then('handler loop is cancelled', () => { 77 | expect(engine.execution.getPostponed().find((api) => api.content.isMultiInstance)).to.not.be.ok; 78 | }); 79 | 80 | And('modify manual task is waiting again', () => { 81 | task = engine.execution.getPostponed().pop(); 82 | expect(task.type).to.equal('bpmn:ManualTask'); 83 | }); 84 | 85 | When('manual task is signalled', () => { 86 | engine.execution.signal({ id: task.id }); 87 | }); 88 | 89 | Then('execution loops back to first user task', () => { 90 | task = engine.execution.getPostponed().pop(); 91 | expect(task).to.be.ok; 92 | }); 93 | 94 | When('first user task is signalled again', () => { 95 | engine.execution.signal({ id: task.id }); 96 | }); 97 | 98 | Then('first handler iterated user task is waiting again', () => { 99 | loopedTask = engine.execution.getPostponed().find((api) => api.content.isMultiInstance); 100 | expect(loopedTask).to.be.ok; 101 | }); 102 | 103 | When('first handler decides to break', () => { 104 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, break: true }); 105 | }); 106 | 107 | Then('run completes', () => { 108 | return end; 109 | }); 110 | 111 | When('running again', () => { 112 | engine = getEngine187(); 113 | end = engine.waitFor('end'); 114 | return engine.execute(); 115 | }); 116 | 117 | And('first manual and user tasks are signaled', () => { 118 | engine.execution.signal({ id: engine.execution.getPostponed()[0].id }); 119 | engine.execution.signal({ id: engine.execution.getPostponed()[0].id }); 120 | }); 121 | 122 | When('all handlers signals with with reject', () => { 123 | loopedTask = engine.execution.getPostponed().find((api) => api.content.isMultiInstance); 124 | 125 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: false }); 126 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: false }); 127 | engine.execution.signal({ executionId: loopedTask.getExecuting().pop().executionId, pass: false }); 128 | }); 129 | 130 | Then('run completes', () => { 131 | return end; 132 | }); 133 | }); 134 | }); 135 | 136 | function getEngine187() { 137 | const values = {}; 138 | return new Engine({ 139 | name: 'issue-187', 140 | moddleOptions: { 141 | camunda, 142 | }, 143 | source, 144 | variables: { 145 | values, 146 | handlers: [{ id: 1 }, { id: 2 }, { id: 3 }], 147 | }, 148 | services: { 149 | set_value: function setValue(key, value) { 150 | values[key] = value; 151 | }, 152 | get_value(key) { 153 | return values[key]; 154 | }, 155 | get_handlers() { 156 | return Promise.resolve([{ id: 1 }, { id: 2 }, { id: 3 }]); 157 | }, 158 | gen_remind_task() {}, 159 | }, 160 | extensions: { 161 | inputOutputExtension, 162 | }, 163 | }); 164 | } 165 | 166 | function inputOutputExtension(element) { 167 | if (element.type === 'bpmn:Process') return; 168 | 169 | let io; 170 | const resultVariable = element.behaviour.resultVariable; 171 | if (element.behaviour.extensionElements?.values) { 172 | const extendValues = element.behaviour.extensionElements.values; 173 | io = extendValues.reduce((result, extension) => { 174 | if (extension.$type === 'camunda:InputOutput') { 175 | result.input = extension.inputParameters; 176 | result.output = extension.outputParameters; 177 | } 178 | return result; 179 | }, {}); 180 | } 181 | 182 | return { 183 | activate() { 184 | if (io) element.on('enter', onEnter, { consumerTag: 'format_on_enter' }); 185 | element.on('end', onEnd, { consumerTag: 'format_on_end' }); 186 | }, 187 | deactivate() { 188 | element.broker.cancel('format_on_enter'); 189 | element.broker.cancel('format_on_end'); 190 | }, 191 | }; 192 | 193 | function onEnter(elementApi) { 194 | const input = io?.input?.reduce((result, { name, value }) => { 195 | result[name] = elementApi.resolveExpression(value); 196 | return result; 197 | }, {}); 198 | 199 | element.broker.publish('format', 'run.io', { input }); 200 | } 201 | 202 | function onEnd(elementApi) { 203 | if ('output' in elementApi.content) { 204 | elementApi.environment.output[resultVariable || elementApi.id] = elementApi.content.output; 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /test/feature/issues/issue-199-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../../src/index.js'; 2 | 3 | const source = ` 4 | 5 | 6 | 7 | 8 | Flow_1 9 | 10 | 11 | 12 | Flow_1 13 | Flow_2 14 | this.environment.services.log('hello from Script task 1'); 15 | next(); 16 | 17 | 18 | 19 | Flow_2 20 | Flow_3 21 | 22 | 23 | 24 | Flow_3 25 | Flow_4 26 | this.environment.services.log('hello from Script task 2'); 27 | next(); 28 | 29 | 30 | 31 | Flow_4 32 | 33 | 34 | 35 | `; 36 | 37 | Feature('issue 199 - Issue with Script Tasks After State Recovery in bpmn-engine', () => { 38 | Scenario('execute, recover resume with same engine instance', () => { 39 | let engine; 40 | Given('an engine with user task flanked by two script tasks', () => { 41 | engine = new Engine({ 42 | name: 'first', 43 | source, 44 | services: { 45 | log() {}, 46 | }, 47 | }); 48 | }); 49 | 50 | let execution; 51 | When('executed', async () => { 52 | execution = await engine.execute(); 53 | }); 54 | 55 | Then('engine should be in a running state', () => { 56 | expect(execution.state).to.equal('running'); 57 | }); 58 | 59 | let end; 60 | When('user task is signalled', () => { 61 | end = engine.waitFor('end'); 62 | execution.signal({ id: 'UserTask_1' }); 63 | }); 64 | 65 | Then('run completed', () => { 66 | return end; 67 | }); 68 | 69 | Given('a new engine instance', () => { 70 | engine = new Engine({ 71 | name: 'second', 72 | source, 73 | services: { 74 | log() {}, 75 | }, 76 | }); 77 | }); 78 | 79 | let state; 80 | When('executed and get state', async () => { 81 | execution = await engine.execute(); 82 | state = execution.getState(); 83 | }); 84 | 85 | Then('engine should be in a running state', () => { 86 | expect(execution.state).to.equal('running'); 87 | }); 88 | 89 | Given('execution is stopped', () => { 90 | return execution.stop(); 91 | }); 92 | 93 | When('same instance is recovered with options', () => { 94 | engine.recover(state, { 95 | services: { 96 | log() {}, 97 | }, 98 | }); 99 | }); 100 | 101 | And('resumed', () => { 102 | engine.resume(); 103 | }); 104 | 105 | Then('engine is still in a running state', () => { 106 | expect(engine.execution.state).to.equal('running'); 107 | }); 108 | 109 | When('resumed execution user task is signalled', () => { 110 | end = engine.waitFor('end'); 111 | engine.execution.signal({ id: 'UserTask_1' }); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/feature/scripts-feature.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../src/index.js'; 2 | 3 | Feature('Scripts', () => { 4 | Scenario('Process with scripts', () => { 5 | let engine, source; 6 | Given('a bpmn source with empty script task', () => { 7 | source = ` 8 | 10 | 11 | 12 | 13 | `; 14 | }); 15 | 16 | And('an engine loaded with disabled dummy script', () => { 17 | engine = Engine({ 18 | name: 'Script feature', 19 | source, 20 | disableDummyScript: true, 21 | }); 22 | }); 23 | 24 | let error; 25 | When('source is executed', async () => { 26 | error = await engine.execute().catch((err) => err); 27 | }); 28 | 29 | Then('execution errored', () => { 30 | expect(error).to.match(/not registered/); 31 | }); 32 | 33 | When('ran again falsy disable dummy script', () => { 34 | engine = Engine({ 35 | name: 'Script feature', 36 | source, 37 | disableDummyScript: false, 38 | }); 39 | }); 40 | 41 | let end; 42 | When('source is executed', async () => { 43 | end = engine.waitFor('end'); 44 | error = await engine.execute().catch((err) => err); 45 | }); 46 | 47 | Then('execution completed', () => { 48 | return end; 49 | }); 50 | }); 51 | 52 | Scenario('Process with setTimeout in inline scripts task', () => { 53 | let engine, source; 54 | Given('a bpmn source with script task with setTimeout', () => { 55 | source = ` 56 | 58 | 59 | 60 | 65 | 66 | 67 | `; 68 | }); 69 | 70 | let end; 71 | When('source is executed', () => { 72 | engine = Engine({ 73 | name: 'Script feature', 74 | source, 75 | disableDummyScript: true, 76 | }); 77 | 78 | end = engine.waitFor('end'); 79 | 80 | engine.execute(); 81 | }); 82 | 83 | Then('execution completed', () => { 84 | return end; 85 | }); 86 | }); 87 | 88 | Scenario('Process with long running timer can be stopped', () => { 89 | let engine, source; 90 | Given('a bpmn source with script task with setTimeout', () => { 91 | source = ` 92 | 94 | 95 | 96 | 101 | 102 | 103 | `; 104 | }); 105 | 106 | When('source is executed', () => { 107 | engine = Engine({ 108 | name: 'Script feature', 109 | source, 110 | disableDummyScript: true, 111 | }); 112 | 113 | engine.execute(); 114 | }); 115 | 116 | Then('timer is running', () => { 117 | expect(engine.environment.timers.executing).to.have.length(1); 118 | }); 119 | 120 | When('execution is stopped', () => { 121 | return engine.stop(); 122 | }); 123 | 124 | Then('timer is stopped', () => { 125 | expect(engine.environment.timers.executing).to.have.length(0); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/feature/shake-feature.js: -------------------------------------------------------------------------------- 1 | import * as testHelpers from '../helpers/testHelpers.js'; 2 | import { Engine } from '../../src/index.js'; 3 | 4 | Feature('Shake', () => { 5 | Scenario('Determine run sequences for an activity', () => { 6 | let engine, source; 7 | Given('a bpmn source with user tasks', () => { 8 | source = ` 9 | 12 | 13 | 14 | 15 | 16 | 17 | `; 18 | }); 19 | 20 | let definition; 21 | And('an engine loaded with extension for fetching form and saving output', async () => { 22 | engine = Engine({ 23 | name: 'Shake feature', 24 | sourceContext: await testHelpers.context(source), 25 | }); 26 | 27 | [definition] = await engine.getDefinitions(); 28 | }); 29 | 30 | let result; 31 | When('start task is shaken', () => { 32 | result = definition.shake('task1'); 33 | }); 34 | 35 | Then('run sequences are determined', () => { 36 | expect(result).to.have.property('task1').with.length(1); 37 | expect(result.task1[0]).to.have.property('sequence').with.length(3); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/helpers/factory.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | export function valid(definitionId) { 5 | if (!definitionId) definitionId = 'valid'; 6 | return ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | next(null, true); 18 | 19 | 20 | `; 21 | } 22 | 23 | export function invalid() { 24 | return ` 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | next(null, false); 35 | 36 | 37 | `; 38 | } 39 | 40 | export function userTask(userTaskId = 'userTask', definitionId = 'Def_1') { 41 | return ` 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | input_1 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | `; 66 | } 67 | 68 | export function multipleInbound() { 69 | return ` 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | `; 84 | } 85 | 86 | export function resource(name) { 87 | return fs.readFileSync(path.join('./test/resources', name)); 88 | } 89 | -------------------------------------------------------------------------------- /test/helpers/testHelpers.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | import { fileURLToPath } from 'node:url'; 3 | 4 | import BpmnModdle from 'bpmn-moddle'; 5 | import * as Elements from 'bpmn-elements'; 6 | import Logger from '../../src/Logger.js'; 7 | import serializer, { TypeResolver } from 'moddle-context-serializer'; 8 | 9 | const nodeRequire = createRequire(fileURLToPath(import.meta.url)); 10 | export const camundaBpmnModdle = nodeRequire('camunda-bpmn-moddle/resources/camunda.json'); 11 | 12 | export async function context(source, options) { 13 | const logger = Logger('test-helpers:context'); 14 | const moddleCtx = await moddleContext(source, options); 15 | 16 | if (moddleCtx.warnings) { 17 | moddleCtx.warnings.forEach(({ error, message, element, property }) => { 18 | if (error) return logger.error(message); 19 | logger.error(`<${element.id}> ${property}:`, message); 20 | }); 21 | } 22 | 23 | const types = TypeResolver({ 24 | ...Elements, 25 | ...options?.elements, 26 | }); 27 | 28 | return serializer(moddleCtx, types, options?.extendFn); 29 | } 30 | 31 | export function moddleContext(source, options) { 32 | const bpmnModdle = new BpmnModdle(options); 33 | return bpmnModdle.fromXML(Buffer.isBuffer(source) ? source.toString() : source); 34 | } 35 | 36 | export function serializeModdleContext({ rootElement, rootHandler, elementsById, references, warnings }) { 37 | const serializedRoot = JSON.parse(JSON.stringify(rootElement || rootHandler.element)); 38 | 39 | const clonedContext = { 40 | rootElement: serializedRoot, 41 | elementsById: JSON.parse(JSON.stringify(elementsById)), 42 | references: JSON.parse(JSON.stringify(references)), 43 | warnings: warnings.slice(), 44 | }; 45 | return clonedContext; 46 | } 47 | -------------------------------------------------------------------------------- /test/issues-test.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | import { Engine } from '../src/index.js'; 3 | import * as factory from './helpers/factory.js'; 4 | 5 | describe('issues', () => { 6 | describe('issue 19 - save state', () => { 7 | it('make sure there is something to save on activity start event', async () => { 8 | const messages = []; 9 | const services = { 10 | timeout: (cb, time) => { 11 | setTimeout(cb, time); 12 | }, 13 | log: (message) => { 14 | messages.push(message); 15 | }, 16 | }; 17 | 18 | const listener = new EventEmitter(); 19 | const engine = new Engine({ 20 | name: 'Engine', 21 | source: factory.resource('issue-19.bpmn'), 22 | listener, 23 | }); 24 | 25 | const states = []; 26 | 27 | listener.on('activity.start', (activity, engineApi) => { 28 | states.push(engineApi.getState()); 29 | }); 30 | 31 | const end = engine.waitFor('end'); 32 | 33 | await engine.execute({ 34 | listener, 35 | variables: { 36 | timeout: 100, 37 | }, 38 | services, 39 | }); 40 | 41 | await end; 42 | 43 | expect(states).to.have.length(7); 44 | 45 | for (const state of states) { 46 | expect(state).to.have.property('definitions').with.length(1); 47 | expect(state.definitions[0]).to.have.property('execution'); 48 | expect(state.definitions[0].execution).to.have.property('processes').with.length(1); 49 | expect(state.definitions[0].execution.processes[0]).to.have.property('execution'); 50 | expect(state.definitions[0].execution.processes[0].execution).to.have.property('children').with.length(7); 51 | 52 | const children = state.definitions[0].execution.processes[0].execution.children; 53 | 54 | expect(children.map(({ id }) => id)).to.eql(['Start', 'Parallel1', 'Task_A', 'Task_B', 'Parallel2', 'Task_C', 'End']); 55 | } 56 | 57 | let [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[0].definitions[0].execution.processes[0].execution.children; 58 | expect(Start, 'state 0 Start').to.have.property('status', 'started'); 59 | expect(Parallel1, 'state 0 Parallel1').to.not.have.property('status'); 60 | 61 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[1].definitions[0].execution.processes[0].execution.children; 62 | expect(Start, 'state 1 Start').to.have.property('status', 'end'); 63 | expect(Parallel1, 'state 1 Parallel1').to.have.property('status', 'started'); 64 | expect(Task_A, 'state 1 Task_A').to.not.have.property('status'); 65 | 66 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[2].definitions[0].execution.processes[0].execution.children; 67 | expect(Parallel1, 'state 2 Parallel1').to.have.property('status', 'end'); 68 | expect(Task_A, 'state 2 Task_A').to.have.property('status', 'started'); 69 | expect(Task_B, 'state 2 Task_B').to.not.have.property('status'); 70 | 71 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[3].definitions[0].execution.processes[0].execution.children; 72 | expect(Parallel1, 'state 3 Parallel1').to.have.property('status', 'end'); 73 | expect(Task_A, 'state 3 Task_A').to.not.have.property('status'); 74 | expect(Task_B, 'state 3 Task_B').to.have.property('status', 'started'); 75 | expect(Parallel2, 'state 3 Parallel2').to.not.have.property('status'); 76 | 77 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[4].definitions[0].execution.processes[0].execution.children; 78 | expect(Task_A, 'state 4 Task_A').to.not.have.property('status'); 79 | expect(Task_B, 'state 4 Task_B').to.have.property('status', 'end'); 80 | expect(Parallel2, 'state 4 Parallel2').to.have.property('status', 'started'); 81 | 82 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[5].definitions[0].execution.processes[0].execution.children; 83 | expect(Parallel2, 'state 5 Parallel2').to.have.property('status', 'end'); 84 | expect(Task_C, 'state 5 Task_C').to.have.property('status', 'started'); 85 | expect(End, 'state 5 End').to.not.have.property('status'); 86 | 87 | [Start, Parallel1, Task_A, Task_B, Parallel2, Task_C, End] = states[6].definitions[0].execution.processes[0].execution.children; 88 | expect(Task_C, 'state 6 Task_C').to.have.property('status', 'end'); 89 | expect(End, 'state 6 End').to.have.property('status', 'started'); 90 | }); 91 | }); 92 | 93 | describe('issue 74 - setting engine output from script', () => { 94 | const source = ` 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | environment.output.test = "set from script"; next() 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | `; 119 | 120 | it('engine output is not altered during execution', async () => { 121 | const listener = new EventEmitter(); 122 | const engine = new Engine({ 123 | name: 'Engine', 124 | source, 125 | listener, 126 | }); 127 | 128 | listener.on('activity.wait', (api) => { 129 | // When we get to the UserTask, the ScriptTask should be completed and set the output 130 | expect(api.environment.output.test, 'execution output').to.equal('set from script'); 131 | expect(engine.environment.output.test, 'engine output').to.be.undefined; 132 | }); 133 | 134 | await engine.execute({ listener }); 135 | }); 136 | 137 | it('merges execution output to engine output from a script at engine completion', async () => { 138 | const listener = new EventEmitter(); 139 | const engine = new Engine({ 140 | name: 'Engine', 141 | source, 142 | listener, 143 | }); 144 | 145 | listener.on('activity.wait', (exec) => exec.signal()); 146 | 147 | await engine.execute({ listener }); 148 | 149 | expect(engine.environment.output.test).to.equal('set from script'); 150 | }); 151 | 152 | it('resumes with correct state', async () => { 153 | let state; 154 | 155 | const listener = new EventEmitter(); 156 | const engine = new Engine({ 157 | name: 'Engine', 158 | source, 159 | listener, 160 | }); 161 | 162 | const resumeListener = new EventEmitter(); 163 | const resumeEngine = new Engine({ 164 | name: 'Engine', 165 | source, 166 | listener: resumeListener, 167 | }); 168 | 169 | resumeListener.on('activity.wait', (exec) => { 170 | expect(exec.environment.output.test).to.equal('set from script'); 171 | }); 172 | 173 | listener.on('activity.wait', (exec) => { 174 | expect(exec.environment.output.test).to.equal('set from script'); 175 | 176 | engine.getState().then((s) => { 177 | state = s; 178 | engine.stop(); 179 | }); 180 | }); 181 | 182 | await engine.execute({ listener }); 183 | 184 | resumeEngine.recover(state); 185 | 186 | await resumeEngine.resume({ listener: resumeListener }); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/lib/JavaScripts-test.js: -------------------------------------------------------------------------------- 1 | import { JavaScripts } from '../../src/index.js'; 2 | 3 | describe('JavaScripts', () => { 4 | it('can be invoked without new', () => { 5 | JavaScripts(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/resources/JsExtension.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module'; 2 | import { fileURLToPath } from 'url'; 3 | 4 | const safePattern = /[./\\#*:\s]/g; 5 | 6 | const nodeRequire = createRequire(fileURLToPath(import.meta.url)); 7 | export const moddleOptions = nodeRequire('./js-bpmn-moddle.json'); 8 | 9 | export function extension(activity, context) { 10 | const resultVariable = ResultVariableIo(activity, context); 11 | const formKey = FormKey(activity, context); 12 | 13 | return { 14 | type: 'js:extension', 15 | extensions: { resultVariable, formKey }, 16 | activate(msg) { 17 | if (resultVariable) resultVariable.activate(msg); 18 | if (formKey) formKey.activate(msg); 19 | }, 20 | deactivate() { 21 | if (resultVariable) resultVariable.deactivate(); 22 | if (formKey) formKey.deactivate(); 23 | }, 24 | }; 25 | } 26 | 27 | function ResultVariableIo(activity, context) { 28 | const { id, logger, behaviour } = activity; 29 | const { result } = behaviour; 30 | if (!result) return; 31 | 32 | const { broker } = activity; 33 | const { environment } = context; 34 | 35 | const type = 'js:resultvariable'; 36 | let activityConsumer; 37 | 38 | return { 39 | type, 40 | activate, 41 | deactivate, 42 | }; 43 | 44 | function deactivate() { 45 | if (activityConsumer) activityConsumer = activityConsumer.cancel(); 46 | } 47 | 48 | function activate() { 49 | if (activityConsumer) return; 50 | activityConsumer = broker.subscribeTmp('event', 'activity.end', onActivityEnd, { noAck: true }); 51 | } 52 | 53 | function onActivityEnd(_, message) { 54 | const resultName = environment.resolveExpression(result, message.content); 55 | logger.debug(`<${id}>`, 'js:extension save to', `"${resultName}"`); 56 | 57 | environment.output[resultName] = message.content.output; 58 | } 59 | } 60 | 61 | function FormKey(activity, context) { 62 | const { id, logger, behaviour } = activity; 63 | const { formKey } = behaviour; 64 | if (!formKey) return; 65 | 66 | const { broker } = activity; 67 | const { environment } = context; 68 | 69 | const type = 'js:formkey'; 70 | const safeType = brokerSafeId(type).toLowerCase(); 71 | let activityConsumer; 72 | 73 | return { 74 | type, 75 | activate, 76 | deactivate, 77 | }; 78 | 79 | function deactivate() { 80 | if (activityConsumer) activityConsumer = activityConsumer.cancel(); 81 | } 82 | 83 | function activate() { 84 | if (activityConsumer) return; 85 | activityConsumer = broker.subscribeTmp('event', 'activity.start', onActivityStart, { noAck: true }); 86 | } 87 | 88 | function onActivityStart(_, message) { 89 | const formKeyValue = environment.resolveExpression(formKey, message); 90 | logger.debug(`<${id}> apply form`); 91 | 92 | broker.publish('format', `run.${safeType}.start`, { 93 | form: { 94 | type, 95 | key: formKeyValue, 96 | }, 97 | }); 98 | } 99 | } 100 | 101 | function brokerSafeId(id) { 102 | return id.replace(safePattern, '_'); 103 | } 104 | -------------------------------------------------------------------------------- /test/resources/README.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | All modeled with [Camunda modeler](https://camunda.org/download/modeler/). 4 | -------------------------------------------------------------------------------- /test/resources/bound-error-and-timer.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow3 10 | 11 | 12 | 13 | flow1 14 | flow2 15 | 21 | 22 | 23 | 24 | flow2 25 | 26 | 27 | 28 | flow3 29 | 30 | 31 | 32 | SequenceFlow_0blswmr 33 | 34 | PT0.05S 35 | 36 | 37 | 38 | 39 | SequenceFlow_0blswmr 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /test/resources/bound-error.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow3 10 | 11 | 12 | 13 | flow1 14 | flow2 15 | if (!variables.input) { 16 | next(new Error("Input is missing")); 17 | } else if (variables.input === 2) { 18 | } else { 19 | next(); 20 | } 21 | 22 | 23 | 24 | flow2 25 | 26 | 27 | 28 | flow3 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /test/resources/boundary-non-interupting-timer.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0pw2ggp 6 | 7 | 8 | SequenceFlow_0pw2ggp 9 | SequenceFlow_0ipd9l9 10 | 11 | 12 | 13 | SequenceFlow_1t341i2 14 | 15 | 16 | 17 | SequenceFlow_1muatwf 18 | 19 | PT0.01S 20 | 21 | 22 | 23 | 24 | 25 | SequenceFlow_0ipd9l9 26 | SequenceFlow_1muatwf 27 | SequenceFlow_1t341i2 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /test/resources/boundary-timeout.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | flow4 9 | 10 | 11 | 12 | 13 | 14 | 15 | flow1 16 | flow2 17 | 18 | 19 | flow3 20 | 21 | PT0.1S 22 | 23 | 24 | 25 | 26 | 27 | 28 | flow3 29 | flow2 30 | flow4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /test/resources/call-activity.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Flow_1j9hgwz 10 | 11 | 12 | Flow_1j9hgwz 13 | Flow_1w33u3r 14 | 15 | 16 | Flow_1w33u3r 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/resources/conditional-bound-js-event.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to-task 6 | 7 | 8 | 9 | to-end 10 | 11 | 12 | 13 | to-task 14 | to-end 15 | 16 | 17 | to-end-cond 18 | 19 | 20 | 21 | to-end-cond 22 | 23 | next(null, properties.type === 'signal'); 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /test/resources/diagram_1.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0jv2k1t 6 | 7 | 8 | 9 | SequenceFlow_0y5jed8 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | http://example.com/api/${name}/${index} 19 | ${true} 20 | 21 | 22 | ${result[0].statusCode} 23 | 24 | get 25 | 26 | 27 | SequenceFlow_0jv2k1t 28 | SequenceFlow_0y5jed8 29 | 30 | 10 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/resources/forms.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | SequenceFlow_0lj5hpw 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ${variables.suggestedStartDate} 27 | 28 | 29 | 30 | SequenceFlow_0lj5hpw 31 | SequenceFlow_0apdac1 32 | 33 | 34 | SequenceFlow_0apdac1 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/resources/issue-139.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_0dk0y7o 6 | 7 | 8 | Flow_0dk0y7o 9 | Flow_076cdg5 10 | 11 | 12 | DataObjectReference_11hq8qm 13 | Property_0qusu4o 14 | 15 | 16 | 17 | 18 | Flow_076cdg5 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /test/resources/issue-163.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_0jrmoo3 6 | 7 | 8 | 9 | Flow_0jrmoo3 10 | Flow_0qs3el8 11 | 12 | 13 | Flow_0qs3el8 14 | Flow_1rh83yq 15 | 16 | Flow_00x1izw 17 | 18 | 19 | Flow_00x1izw 20 | 21 | 22 | 23 | 24 | 25 | Flow_1rh83yq 26 | Flow_0tefdeh 27 | 28 | 29 | 30 | Flow_0tefdeh 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/resources/issue-19.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startToParallel1 6 | 7 | 8 | startToParallel1 9 | Parallel1ToTaskA 10 | Parallel1ToTaskB 11 | 12 | 13 | Parallel1ToTaskA 14 | TaskAToParallel2 15 | 16 | 17 | Parallel1ToTaskB 18 | TaskBToParallel2 19 | { this.environment.services.log('Resume Task B!'); next() }, this.environment.variables.timeout); 22 | ]]> 23 | 24 | 25 | TaskAToParallel2 26 | TaskBToParallel2 27 | Parallel2ToTaskC 28 | 29 | 30 | Parallel2ToTaskC 31 | TaskCToEnd 32 | 33 | 34 | TaskCToEnd 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /test/resources/issue-4.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | 9 | SequenceFlow_1sxkbig 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | send-email 20 | 21 | 22 | 23 | 24 | 25 | 26 | SequenceFlow_1 27 | SequenceFlow_2 28 | 29 | 30 | SequenceFlow_2 31 | SequenceFlow_1sxkbig 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/resources/js-bpmn-moddle.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node bpmn-engine", 3 | "uri": "http://paed01.github.io/bpmn-engine/schema/2017/08/bpmn", 4 | "prefix": "js", 5 | "xml": { 6 | "tagAlias": "lowerCase" 7 | }, 8 | "types": [ 9 | { 10 | "name": "Task", 11 | "isAbstract": true, 12 | "extends": ["bpmn:Task"], 13 | "properties": [ 14 | { 15 | "name": "result", 16 | "isAttr": true, 17 | "type": "String" 18 | } 19 | ] 20 | }, 21 | { 22 | "name": "Output", 23 | "superClass": ["Element"] 24 | }, 25 | { 26 | "name": "Collectable", 27 | "isAbstract": true, 28 | "extends": ["bpmn:MultiInstanceLoopCharacteristics"], 29 | "properties": [ 30 | { 31 | "name": "collection", 32 | "isAttr": true, 33 | "type": "String" 34 | }, 35 | { 36 | "name": "elementVariable", 37 | "isAttr": true, 38 | "type": "String" 39 | } 40 | ] 41 | }, 42 | { 43 | "name": "FormSupported", 44 | "isAbstract": true, 45 | "extends": ["bpmn:StartEvent", "bpmn:UserTask"], 46 | "properties": [ 47 | { 48 | "name": "formKey", 49 | "isAttr": true, 50 | "type": "String" 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /test/resources/lanes.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | mainStartEvent 13 | task1 14 | mainEndEvent 15 | intermediate 16 | 17 | 18 | 19 | flow1 20 | 21 | 22 | 23 | 24 | Empty 25 | I'm done 26 | 10 27 | 28 | 29 | flow1 30 | flow2 31 | 32 | 33 | flow3 34 | 35 | 36 | flow2 37 | flow3 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | flow-p-1 47 | 48 | 49 | 50 | flow-p-1 51 | flow-p-2 52 | 53 | 54 | 55 | 56 | Done! 57 | 58 | `${messageToMain} Aswell!`; 59 | 60 | 61 | 62 | flow-p-2 63 | flow-p-3 64 | 65 | 66 | flow-p-3 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /test/resources/loop.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow2 10 | flow5 11 | flow3 12 | 13 | 14 | 15 | flow5 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | flow3 24 | flow4 25 | 27 | 28 | 29 | flow1 30 | flow4 31 | flow2 32 | next(); 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/resources/messaging.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SequenceFlow_146nqos 11 | 12 | 13 | SequenceFlow_1p64wos 14 | 15 | 16 | 17 | 18 | ${variables.user} 19 | ${variables.to} 20 | 21 | 22 | SequenceFlow_1ivyh5l 23 | SequenceFlow_1p64wos 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | SequenceFlow_146nqos 32 | SequenceFlow_1ivyh5l 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | SequenceFlow_1cdyq00 42 | 43 | 44 | 45 | 46 | ${from} 47 | ${to} 48 | 49 | 50 | SequenceFlow_1cdyq00 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /test/resources/multiple-endEvents.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow2 10 | 11 | 12 | 13 | 14 | flow3 15 | flow4 16 | 18 | 19 | 20 | 21 | flow5 22 | 23 | 24 | 25 | 26 | flow6 27 | flow7 28 | 30 | 31 | 32 | flow7 33 | 34 | 35 | 36 | flow1 37 | flow2 38 | flow3 39 | 40 | 41 | flow4 42 | flow5 43 | flow6 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /test/resources/multiple-multiple-inbound.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0q2oaww 6 | 7 | 8 | 9 | 10 | ${variables.condition1} 11 | ${variables.condition2} 12 | 13 | 14 | SequenceFlow_0q2oaww 15 | default-flow-2 16 | default-flow-1 17 | taskflow-1 18 | 19 | 20 | 21 | 22 | 23 | ${true} 24 | 25 | 26 | taskflow-1 27 | condflow-1 28 | default-flow-1 29 | 30 | 31 | 32 | 33 | 34 | ${true} 35 | 36 | 37 | condflow-1 38 | condflow-2 39 | default-flow-2 40 | 41 | 42 | ${variables.tookCondition1} 43 | 44 | 45 | condflow-2 46 | 47 | 48 | ${variables.tookCondition2} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /test/resources/one-or-the-other.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_0geshlo 6 | 7 | 8 | 9 | Flow_0geshlo 10 | Flow_1scac6z 11 | Flow_1sz3hry 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Flow_0v64u42 20 | 21 | 22 | Flow_07mced2 23 | Flow_0jihxmk 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Flow_1scac6z 34 | Flow_07mced2 35 | 36 | 37 | Flow_1sz3hry 38 | Flow_1kngrgq 39 | 40 | 41 | 42 | Flow_0jihxmk 43 | Flow_1l5wpwy 44 | Flow_0v64u42 45 | 46 | 47 | 48 | Flow_1kngrgq 49 | Flow_1l5wpwy 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/resources/send-signal.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to-set-spot-price 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ${content.output.form.newPrice} 15 | 16 | 17 | to-set-spot-price 18 | to-signal-new-price 19 | 20 | 21 | to-end 22 | 23 | 24 | 25 | 26 | ${environment.output.spotPrice} 27 | ${environment.output.spotPrice} 28 | 29 | 30 | to-signal-new-price 31 | to-end 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/resources/service-task-io.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0d7zaqt 6 | 7 | 8 | 9 | SequenceFlow_0sekure 10 | 11 | 12 | 13 | 14 | 15 | 16 | variables.apiPath 17 | 18 | ${true} 19 | 20 | 21 | 22 | ${result[1]} 23 | 24 | 25 | 26 | 27 | 28 | SequenceFlow_0d7zaqt 29 | SequenceFlow_0sekure 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /test/resources/service-task-operation.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | inputMessage 12 | outputMessage 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/resources/service-task.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow2 10 | 11 | 12 | 13 | flow1 14 | flow2 15 | 16 | 17 | flow3 18 | 19 | 20 | 21 | 22 | flow3 23 | 24 | 25 | 26 | flow4 27 | 28 | PT0.05S 29 | 30 | 31 | 32 | flow4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /test/resources/simple-task.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PT0.01S 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/resources/sub-process.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | 9 | flow1 10 | flow2 11 | 12 | 13 | subFlow1 14 | 15 | 16 | subFlow1 17 | 19 | 20 | 21 | 22 | flow2 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /test/resources/succeeding-joins.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_01pzds7 6 | 7 | 8 | 9 | SequenceFlow_01pzds7 10 | SequenceFlow_1yhsttk 11 | SequenceFlow_0f0dkku 12 | SequenceFlow_0kfqg1l 13 | 14 | 15 | SequenceFlow_1yhsttk 16 | flow-up-join1 17 | SequenceFlow_05akcgw 18 | 19 | 20 | 21 | 22 | SequenceFlow_1334ker 23 | 24 | 25 | 26 | ${variables.takeJoin1} 27 | 28 | 29 | SequenceFlow_0kfqg1l 30 | SequenceFlow_01624fc 31 | SequenceFlow_12ip5vh 32 | 33 | 34 | 35 | 36 | SequenceFlow_0f0dkku 37 | flow-up-join1 38 | SequenceFlow_01624fc 39 | SequenceFlow_14a3u6g 40 | 41 | 42 | 43 | 44 | ${variables.takeJoin2} 45 | 46 | 47 | SequenceFlow_14a3u6g 48 | SequenceFlow_05akcgw 49 | SequenceFlow_12ip5vh 50 | SequenceFlow_1334ker 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /test/resources/task-multiple-inbound.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0jtx7d0 6 | 7 | 8 | SequenceFlow_0reamwp 9 | flow2 10 | 11 | 12 | 13 | flow2 14 | flow3 15 | SequenceFlow_0ruy5f5 16 | 23 | 24 | 25 | endFlow 26 | 27 | 28 | flow3 29 | 30 | PT0.1S 31 | 32 | 33 | 34 | 35 | SequenceFlow_0ruy5f5 36 | endFlow 37 | loopFlow 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | SequenceFlow_0jtx7d0 47 | loopFlow 48 | SequenceFlow_0reamwp 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /test/resources/task-with-multi-instance-loop.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_00cfpc8 6 | 7 | 8 | 9 | SequenceFlow_00cfpc8 10 | SequenceFlow_0givraz 11 | 12 | 10 13 | ${services.loopCondition} 14 | 15 | 17 | 18 | 19 | SequenceFlow_0givraz 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/resources/timer-event.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0evszjw 6 | 7 | 8 | SequenceFlow_0evszjw 9 | SequenceFlow_01f3n3g 10 | 11 | 12 | 13 | 14 | SequenceFlow_01f3n3g 15 | SequenceFlow_02raz3t 16 | 17 | PT0.05S 18 | 19 | 20 | 21 | SequenceFlow_02raz3t 22 | SequenceFlow_1au7566 23 | 24 | 25 | 26 | SequenceFlow_1au7566 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/resources/timer-start-event.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_009rhrh 6 | 7 | 8 | SequenceFlow_009rhrh 9 | SequenceFlow_013jd7g 10 | 11 | 12 | 13 | 14 | SequenceFlow_013jd7g 15 | SequenceFlow_0ttpgeb 16 | SequenceFlow_0efld9d 17 | 18 | 19 | 20 | SequenceFlow_0efld9d 21 | 22 | 23 | 24 | SequenceFlow_0ttpgeb 25 | 26 | PT0.05S 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /test/resources/timers.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to-task 6 | 7 | R3/PT10H 8 | 9 | 10 | 11 | 12 | to-duration-end 13 | 14 | PT1M 15 | 16 | 17 | 18 | to-end 19 | 20 | 21 | 22 | to-duration-end 23 | 24 | 25 | 26 | 27 | to-catch-date 28 | to-user-due 29 | 30 | ${environment.variables.catchDate} 31 | 32 | 33 | 34 | 35 | to-user-due 36 | to-end 37 | 38 | 39 | to-task 40 | to-catch-date 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*", "types"], 3 | "compilerOptions": { 4 | "emitDeclarationOnly": true, 5 | "sourceMap": false, 6 | "rootDir": "src", 7 | "lib": ["es2017"], 8 | "target": "es2017", 9 | "outDir": "./tmp/ignore", 10 | "declaration": true, 11 | "noEmitOnError": true, 12 | "noErrorTruncation": true, 13 | "allowJs": true, 14 | "checkJs": false, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": false, 18 | "allowSyntheticDefaultImports": true, 19 | "strict": true, 20 | "stripInternal": true, 21 | "noImplicitThis": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "typeRoots": ["./node_modules/@types"], 25 | "paths": { 26 | "types": ["./types/bpmn-engine.js"] 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------