├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .mocharc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.cjs ├── dist ├── Api.js ├── Context.js ├── Environment.js ├── EventBroker.js ├── Expressions.js ├── MessageFormatter.js ├── Scripts.js ├── Timers.js ├── Tracker.js ├── activity │ ├── Activity.js │ ├── ActivityExecution.js │ ├── Dummy.js │ ├── Escalation.js │ ├── ExecutionScope.js │ ├── Message.js │ ├── Signal.js │ └── outbound-evaluator.js ├── condition.js ├── definition │ ├── Definition.js │ └── DefinitionExecution.js ├── error │ ├── BpmnError.js │ └── Errors.js ├── eventDefinitions │ ├── CancelEventDefinition.js │ ├── CompensateEventDefinition.js │ ├── ConditionalEventDefinition.js │ ├── ErrorEventDefinition.js │ ├── EscalationEventDefinition.js │ ├── EventDefinitionExecution.js │ ├── LinkEventDefinition.js │ ├── MessageEventDefinition.js │ ├── SignalEventDefinition.js │ ├── TerminateEventDefinition.js │ ├── TimerEventDefinition.js │ └── index.js ├── events │ ├── BoundaryEvent.js │ ├── EndEvent.js │ ├── IntermediateCatchEvent.js │ ├── IntermediateThrowEvent.js │ ├── StartEvent.js │ └── index.js ├── flows │ ├── Association.js │ ├── MessageFlow.js │ ├── SequenceFlow.js │ └── index.js ├── gateways │ ├── EventBasedGateway.js │ ├── ExclusiveGateway.js │ ├── InclusiveGateway.js │ ├── ParallelGateway.js │ └── index.js ├── getPropertyValue.js ├── index.js ├── io │ ├── BpmnIO.js │ ├── EnvironmentDataObject.js │ ├── EnvironmentDataStore.js │ ├── EnvironmentDataStoreReference.js │ ├── InputOutputSpecification.js │ └── Properties.js ├── messageHelper.js ├── package.json ├── process │ ├── Lane.js │ ├── Process.js │ └── ProcessExecution.js ├── shared.js └── tasks │ ├── CallActivity.js │ ├── LoopCharacteristics.js │ ├── ReceiveTask.js │ ├── ScriptTask.js │ ├── ServiceImplementation.js │ ├── ServiceTask.js │ ├── SignalTask.js │ ├── StandardLoopCharacteristics.js │ ├── SubProcess.js │ ├── Task.js │ ├── Transaction.js │ └── index.js ├── docs ├── Activity.md ├── ActivityExecution.md ├── BpmnIO.md ├── CallActivity.md ├── ConditionalEventDefinition.md ├── Context.md ├── Definition.md ├── Environment.md ├── Examples.md ├── ExecutionScope.md ├── Expression.md ├── Extend.md ├── Extension.md ├── LoopCharacteristics.md ├── ParallelGateway.md ├── Process.md ├── Scripts.md ├── SequenceFlow.md ├── ServiceTask.md ├── SharedApi.md ├── SignalTask.md ├── StartEvent.md ├── TimerEventDefinition.md ├── Timers.md ├── activity-execution.png ├── activity-lifecycle.png └── parallel-join-edgecase.png ├── eslint.config.js ├── eventDefinitions.d.ts ├── events.d.ts ├── flows.d.ts ├── gateways.d.ts ├── index.d.ts ├── package.json ├── src ├── Api.js ├── Context.js ├── Environment.js ├── EventBroker.js ├── Expressions.js ├── MessageFormatter.js ├── Scripts.js ├── Timers.js ├── Tracker.js ├── activity │ ├── Activity.js │ ├── ActivityExecution.js │ ├── Dummy.js │ ├── Escalation.js │ ├── ExecutionScope.js │ ├── Message.js │ ├── Signal.js │ └── outbound-evaluator.js ├── condition.js ├── definition │ ├── Definition.js │ └── DefinitionExecution.js ├── error │ ├── BpmnError.js │ └── Errors.js ├── eventDefinitions │ ├── CancelEventDefinition.js │ ├── CompensateEventDefinition.js │ ├── ConditionalEventDefinition.js │ ├── ErrorEventDefinition.js │ ├── EscalationEventDefinition.js │ ├── EventDefinitionExecution.js │ ├── LinkEventDefinition.js │ ├── MessageEventDefinition.js │ ├── SignalEventDefinition.js │ ├── TerminateEventDefinition.js │ ├── TimerEventDefinition.js │ └── index.js ├── events │ ├── BoundaryEvent.js │ ├── EndEvent.js │ ├── IntermediateCatchEvent.js │ ├── IntermediateThrowEvent.js │ ├── StartEvent.js │ └── index.js ├── flows │ ├── Association.js │ ├── MessageFlow.js │ ├── SequenceFlow.js │ └── index.js ├── gateways │ ├── EventBasedGateway.js │ ├── ExclusiveGateway.js │ ├── InclusiveGateway.js │ ├── ParallelGateway.js │ └── index.js ├── getPropertyValue.js ├── index.js ├── io │ ├── BpmnIO.js │ ├── EnvironmentDataObject.js │ ├── EnvironmentDataStore.js │ ├── EnvironmentDataStoreReference.js │ ├── InputOutputSpecification.js │ └── Properties.js ├── messageHelper.js ├── process │ ├── Lane.js │ ├── Process.js │ └── ProcessExecution.js ├── shared.js └── tasks │ ├── CallActivity.js │ ├── LoopCharacteristics.js │ ├── ReceiveTask.js │ ├── ScriptTask.js │ ├── ServiceImplementation.js │ ├── ServiceTask.js │ ├── SignalTask.js │ ├── StandardLoopCharacteristics.js │ ├── SubProcess.js │ ├── Task.js │ ├── Transaction.js │ └── index.js ├── tasks.d.ts ├── test ├── Api-test.js ├── Context-test.js ├── Environment-test.js ├── MessageFormatter-test.js ├── Timers-test.js ├── activities-test.js ├── activity-api-test.js ├── activity │ ├── Activity-test.js │ ├── ActivityExecution-test.js │ ├── ExecutionScope-test.js │ └── activity-run-test.js ├── bpmn-elements-test.js ├── definition │ ├── Definition-test.js │ └── DefinitionExecution-test.js ├── error │ ├── BpmnError-test.js │ └── Errors-test.js ├── eventDefinitions │ ├── CancelEventDefinition-test.js │ ├── CompensateEventDefinition-test.js │ ├── ConditionalEventDefinition-test.js │ ├── ErrorEventDefinition-test.js │ ├── EscalationEventDefinition-test.js │ ├── EventDefinitionExecution-test.js │ ├── LinkEventDefinition-test.js │ ├── MessageEventDefinition-test.js │ ├── SignalEventDefinition-test.js │ ├── TerminateEventDefinition-test.js │ └── TimerEventDefinition-test.js ├── events │ ├── BoundaryEvent-test.js │ ├── EndEvent-test.js │ ├── IntermediateCatchEvent-test.js │ ├── IntermediateThrowEvent-test.js │ └── StartEvent-test.js ├── expressions-test.js ├── feature │ ├── BoundaryEvent-feature.js │ ├── Definition-feature.js │ ├── EventBasedGateway-feature.js │ ├── Process-feature.js │ ├── activity-feature.js │ ├── activity-io-feature.js │ ├── activity-status-feature.js │ ├── ad-hoc-subprocess-feature.js │ ├── backward-compatability-feature.js │ ├── call-activity-feature.js │ ├── compensation-feature.js │ ├── conditional-event-feature.js │ ├── definition-output-feature.js │ ├── dummy-feature.js │ ├── environment-feature.js │ ├── errors-feature.js │ ├── escalation-feature.js │ ├── expression-feature.js │ ├── extension-feature.js │ ├── format-feature.js │ ├── gateway-feature.js │ ├── io-feature.js │ ├── issues │ │ ├── engine-issues-feature.js │ │ ├── exclusive-gateway-join-feature.js │ │ ├── issue-31-feature.js │ │ ├── issue-32-feature.js │ │ ├── issue-39-feature.js │ │ ├── issue-42-feature.js │ │ ├── issues-feature.js │ │ └── stack-overflow-feature.js │ ├── linking-feature.js │ ├── messaging-feature.js │ ├── multiple-startEvent-feature.js │ ├── noExecutableProcess-feature.js │ ├── outbound-flows-feature.js │ ├── parallel-gateway-feature.js │ ├── performance-feature.js │ ├── recover-resume-feature.js │ ├── script-feature.js │ ├── sequence-flow-feature.js │ ├── service-task-feature.js │ ├── shake-feature.js │ ├── signal-feature.js │ ├── stopAndResume-feature.js │ ├── sub-process-feature.js │ ├── swim-lanes-feature.js │ ├── task-loop-feature.js │ ├── timers-feature.js │ └── transaction-feature.js ├── flows │ ├── Association-test.js │ ├── MessageFlow-test.js │ └── SequenceFlow-test.js ├── gateways │ ├── EventBasedGateway-test.js │ ├── ExclusiveGateway-test.js │ ├── InclusiveGateway-test.js │ └── ParallelGateway-test.js ├── getPropertyValue-test.js ├── helpers │ ├── JavaScripts.js │ ├── factory.js │ ├── setup.js │ └── testHelpers.js ├── io │ ├── DataObject-test.js │ ├── InputOutputSpecification-test.js │ └── Properties-test.js ├── messageHelper-test.js ├── package-test.js ├── process │ ├── Process-test.js │ └── ProcessExecution-test.js ├── resources │ ├── README.md │ ├── activity-execution.bpmn │ ├── activity-lifecycle.bpmn │ ├── ad-hoc-subprocess.bpmn │ ├── async-decision.bpmn │ ├── bound-error-and-timer.bpmn │ ├── bound-error.bpmn │ ├── boundary-non-interupting-timer.bpmn │ ├── boundary-timeout.bpmn │ ├── bpmn-error.bpmn │ ├── call-activity-signal.bpmn │ ├── call-activity.bpmn │ ├── conditional-bound-js-event.bpmn │ ├── conditional-flows.bpmn │ ├── consumer_error.bpmn │ ├── data-store-reference.bpmn │ ├── end-compensate.bpmn │ ├── engine-issue-105_1.bpmn │ ├── engine-issue-105_2.bpmn │ ├── engine-issue-139.bpmn │ ├── engine-issue-180-message.bpmn │ ├── engine-issue-180-signal.bpmn │ ├── engine-issue-180.bpmn │ ├── engine-issue-73.bpmn │ ├── engine-issue-73_2.bpmn │ ├── escalation-start-event.bpmn │ ├── escalation.bpmn │ ├── event-based-gateway-with-same-target.bpmn │ ├── event-based-gateway.bpmn │ ├── exclusive-gateway-as-join.bpmn │ ├── extensions │ │ ├── CamundaExtension.js │ │ └── JsExtension.js │ ├── forms.bpmn │ ├── groups.bpmn │ ├── issue-19.bpmn │ ├── issue-3.bpmn │ ├── issue-31-cm.bpmn │ ├── issue-31.bpmn │ ├── issue-4.bpmn │ ├── issue-42-same-target-sequence-flows.bpmn │ ├── join-inbound.bpmn │ ├── js-bpmn-moddle.json │ ├── lanes.bpmn │ ├── link-event.bpmn │ ├── loop.bpmn │ ├── messageflow-target-process.bpmn │ ├── messaging.bpmn │ ├── misp-loopback.bpmn │ ├── misp-parallel-loopback.bpmn │ ├── mother-of-all-state-5.json │ ├── mother-of-all.bpmn │ ├── multiple-endEvents.bpmn │ ├── multiple-joins.bpmn │ ├── multiple-multiple-inbound.bpmn │ ├── nested-joins.bpmn │ ├── parallel-join-edgecase.bpmn │ ├── process-message.bpmn │ ├── resume_error.bpmn │ ├── send-signal.bpmn │ ├── service-task-io.bpmn │ ├── service-task-operation.bpmn │ ├── service-task.bpmn │ ├── signal-after-signal.bpmn │ ├── signals.bpmn │ ├── simple-task.bpmn │ ├── sub-process.bpmn │ ├── succeeding-joins.bpmn │ ├── swimlanes.bpmn │ ├── task-multiple-inbound.bpmn │ ├── task-with-multi-instance-loop.bpmn │ ├── throw-compensate.bpmn │ ├── timer-event.bpmn │ ├── timer-start-event.bpmn │ ├── timers.bpmn │ ├── transaction.bpmn │ ├── two-executable-processes.bpmn │ └── wait-activities.bpmn ├── shared-test.js └── tasks │ ├── BusinessRuleTask-test.js │ ├── CallActivity-test.js │ ├── LoopCharacteristics-test.js │ ├── ManualTask-test.js │ ├── ReceiveTask-test.js │ ├── ScriptTask-test.js │ ├── SendTask-test.js │ ├── ServiceTask-test.js │ ├── SubProcess-test.js │ ├── Task-test.js │ ├── Transaction-test.js │ ├── UserTask-test.js │ └── isForCompensation-test.js ├── tsconfig.json └── types ├── index.d.ts └── types.d.ts /.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 | # OS X 24 | .DS_Store 25 | 26 | # vim 27 | *.swp 28 | *.swo 29 | 30 | # eslint 31 | .eslintcache 32 | 33 | package-lock.json 34 | .nyc_output 35 | 36 | *.log 37 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "./test/helpers/setup.js", 3 | "reporter": "spec", 4 | "recursive": true, 5 | "timeout": 1000, 6 | "ui": "mocha-cakes-2" 7 | } 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | coverage/ 4 | tmp/ 5 | test/resources/*.bpmn 6 | *.bpmn 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 140, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-elements/3fd23492027fea9720b260f29208a77ce7139580/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpmn-elements 2 | 3 | [![Build](https://github.com/paed01/bpmn-elements/actions/workflows/build.yaml/badge.svg)](https://github.com/paed01/bpmn-elements/actions/workflows/build.yaml)[![Coverage Status](https://coveralls.io/repos/github/paed01/bpmn-elements/badge.svg?branch=master)](https://coveralls.io/github/paed01/bpmn-elements?branch=master) 4 | 5 | Isomorphic JavaScript BPMN 2.0 workflow elements suitable for bundling into frontend script or just required into your nodejs project. 6 | 7 | - [Examples](/docs/Examples.md) 8 | - [Handle extensions](/docs/Extension.md) 9 | - [Write your own behaviour](/docs/Extend.md) 10 | 11 | # Supported elements 12 | 13 | The following elements are tested and supported. 14 | 15 | - [Definition](/docs/Definition.md): Executable BPMN 2 definition 16 | - [Process](/docs/Process.md): Executes and keeps track of activity elements 17 | - AdHocSubProcess 18 | - BpmnError 19 | - BoundaryEvent 20 | - [CallActivity](/docs/CallActivity.md) 21 | - CancelEventDefinition 22 | - [ConditionalEventDefinition](/docs/ConditionalEventDefinition.md) 23 | - CompensateEventDefinition 24 | - compensate by outbound Association 25 | - [DataObject](/docs/BpmnIO.md) 26 | - [DataStore](/docs/BpmnIO.md) 27 | - [DataStoreReference](/docs/BpmnIO.md) 28 | - EndEvent 29 | - Error 30 | - ErrorEventDefinition 31 | - throw 32 | - catch 33 | - EscalationEventDefinition 34 | - throw 35 | - catch 36 | - EventBasedGateway 37 | - ExclusiveGateway 38 | - InclusiveGateway 39 | - IntermediateCatchEvent 40 | - IntermediateThrowEvent 41 | - [InputOutputSpecification](/docs/BpmnIO.md) 42 | - LinkEventDefinition 43 | - throw 44 | - catch 45 | - MessageEventDefinition 46 | - throw 47 | - catch 48 | - MessageFlow 49 | - [MultiInstanceLoopCharacteristics](/docs/LoopCharacteristics.md) 50 | - [ParallelGateway](/docs/ParallelGateway.md) 51 | - Participant 52 | - Lane: exposed on activity 53 | - [Property](/docs/BpmnIO.md) 54 | - ReceiveTask 55 | - ScriptTask 56 | - [SequenceFlow](/docs/SequenceFlow.md) 57 | - ServiceImplementation: ServiceTask implementation attribute behaviour 58 | - [ServiceTask](/docs/ServiceTask.md) 59 | - BusinessRuleTask: Same behaviour as ServiceTask 60 | - SendTask: Same behaviour as ServiceTask 61 | - Signal 62 | - SignalEventDefinition 63 | - throw 64 | - catch 65 | - [SignalTask](/docs/SignalTask.md) 66 | - ManualTask 67 | - UserTask 68 | - [StandardLoopCharacteristics](/docs/LoopCharacteristics.md) 69 | - [StartEvent](/docs/StartEvent.md) 70 | - SubProcess 71 | - Task 72 | - TerminateEventDefinition 73 | - [TimerEventDefinition](/docs/TimerEventDefinition.md) 74 | - timeDuration 75 | - timeDate 76 | - timeCycle 77 | - Transaction 78 | 79 | All activities share the same [base](/docs/Activity.md) and and [api](/docs/SharedApi.md). 80 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function babelRoot(api) { 2 | api.cache(true); 3 | 4 | return { 5 | presets: [ 6 | [ 7 | '@babel/env', 8 | { 9 | targets: { 10 | node: 'current', 11 | }, 12 | }, 13 | ], 14 | ], 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /dist/Api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ActivityApi = ActivityApi; 7 | exports.Api = Api; 8 | exports.DefinitionApi = DefinitionApi; 9 | exports.FlowApi = FlowApi; 10 | exports.ProcessApi = ProcessApi; 11 | var _messageHelper = require("./messageHelper.js"); 12 | var _shared = require("./shared.js"); 13 | function ActivityApi(broker, apiMessage, environment) { 14 | return new Api('activity', broker, apiMessage, environment); 15 | } 16 | function DefinitionApi(broker, apiMessage, environment) { 17 | return new Api('definition', broker, apiMessage, environment); 18 | } 19 | function ProcessApi(broker, apiMessage, environment) { 20 | return new Api('process', broker, apiMessage, environment); 21 | } 22 | function FlowApi(broker, apiMessage, environment) { 23 | return new Api('flow', broker, apiMessage, environment); 24 | } 25 | function Api(pfx, broker, sourceMessage, environment) { 26 | if (!sourceMessage) throw new Error('Api requires message'); 27 | const apiMessage = (0, _messageHelper.cloneMessage)(sourceMessage); 28 | const { 29 | id, 30 | type, 31 | name, 32 | executionId 33 | } = apiMessage.content; 34 | this.id = id; 35 | this.type = type; 36 | this.name = name; 37 | this.executionId = executionId; 38 | this.environment = environment || broker.owner.environment; 39 | this.content = apiMessage.content; 40 | this.fields = apiMessage.fields; 41 | this.messageProperties = apiMessage.properties; 42 | this.broker = broker; 43 | this.owner = broker.owner; 44 | this.messagePrefix = pfx; 45 | } 46 | Api.prototype.cancel = function cancel(message, options) { 47 | this.sendApiMessage('cancel', { 48 | message 49 | }, options); 50 | }; 51 | Api.prototype.discard = function discard() { 52 | this.sendApiMessage('discard'); 53 | }; 54 | Api.prototype.fail = function fail(error) { 55 | this.sendApiMessage('error', { 56 | error 57 | }); 58 | }; 59 | Api.prototype.signal = function signal(message, options) { 60 | this.sendApiMessage('signal', { 61 | message 62 | }, options); 63 | }; 64 | Api.prototype.stop = function stop() { 65 | this.sendApiMessage('stop'); 66 | }; 67 | Api.prototype.resolveExpression = function resolveExpression(expression) { 68 | return this.environment.resolveExpression(expression, { 69 | fields: this.fields, 70 | content: this.content, 71 | properties: this.messageProperties 72 | }, this.owner); 73 | }; 74 | Api.prototype.sendApiMessage = function sendApiMessage(action, content, options) { 75 | const correlationId = options?.correlationId || (0, _shared.getUniqueId)(`${this.id || this.messagePrefix}_signal`); 76 | let key = `${this.messagePrefix}.${action}`; 77 | if (this.executionId) key += `.${this.executionId}`; 78 | this.broker.publish('api', key, this.createMessage(content), { 79 | ...options, 80 | correlationId, 81 | type: action 82 | }); 83 | }; 84 | Api.prototype.getPostponed = function getPostponed(...args) { 85 | if (this.owner.getPostponed) return this.owner.getPostponed(...args); 86 | if (this.owner.isSubProcess && this.owner.execution) return this.owner.execution.getPostponed(...args); 87 | return []; 88 | }; 89 | Api.prototype.createMessage = function createMessage(content) { 90 | return { 91 | ...this.content, 92 | ...content 93 | }; 94 | }; -------------------------------------------------------------------------------- /dist/Expressions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Expressions; 7 | var _getPropertyValue = _interopRequireDefault(require("./getPropertyValue.js")); 8 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 9 | const isExpressionPattern = /^\${(.+?)}$/; 10 | const expressionPattern = /\${(.+?)}/; 11 | function Expressions() { 12 | return { 13 | resolveExpression, 14 | isExpression, 15 | hasExpression 16 | }; 17 | } 18 | function resolveExpression(templatedString, context, expressionFnContext) { 19 | let result = templatedString; 20 | while (expressionPattern.test(result)) { 21 | const expressionMatch = result.match(expressionPattern); 22 | const innerProperty = expressionMatch[1]; 23 | if (innerProperty === 'true') { 24 | return true; 25 | } else if (innerProperty === 'false') { 26 | return false; 27 | } else if (innerProperty === 'null') { 28 | return null; 29 | } else { 30 | const n = Number(innerProperty); 31 | if (!isNaN(n)) return n; 32 | } 33 | const contextValue = (0, _getPropertyValue.default)(context, innerProperty, expressionFnContext); 34 | if (expressionMatch.input === expressionMatch[0]) { 35 | return contextValue; 36 | } 37 | result = result.replace(expressionMatch[0], contextValue === undefined ? '' : contextValue); 38 | } 39 | return result; 40 | } 41 | function isExpression(text) { 42 | if (!text) return false; 43 | return isExpressionPattern.test(text); 44 | } 45 | function hasExpression(text) { 46 | if (!text) return false; 47 | return expressionPattern.test(text); 48 | } -------------------------------------------------------------------------------- /dist/Scripts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Scripts = Scripts; 7 | function Scripts() {} 8 | Scripts.prototype.getScript = function getScript(/*scriptType, activity*/) {}; 9 | Scripts.prototype.register = function register(/*activity*/) {}; -------------------------------------------------------------------------------- /dist/Timers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Timers = Timers; 7 | const kExecuting = Symbol.for('executing'); 8 | const kTimerApi = Symbol.for('timers api'); 9 | const MAX_DELAY = 2147483647; 10 | function Timers(options) { 11 | this.count = 0; 12 | this.options = { 13 | setTimeout, 14 | clearTimeout, 15 | ...options 16 | }; 17 | this[kExecuting] = new Set(); 18 | this.setTimeout = this.setTimeout.bind(this); 19 | this.clearTimeout = this.clearTimeout.bind(this); 20 | } 21 | Object.defineProperty(Timers.prototype, 'executing', { 22 | get() { 23 | return [...this[kExecuting]]; 24 | } 25 | }); 26 | Timers.prototype.register = function register(owner) { 27 | return new RegisteredTimers(this, owner); 28 | }; 29 | Timers.prototype.setTimeout = function wrappedSetTimeout(callback, delay, ...args) { 30 | return this._setTimeout(null, callback, delay, ...args); 31 | }; 32 | Timers.prototype.clearTimeout = function wrappedClearTimeout(ref) { 33 | if (this[kExecuting].delete(ref)) { 34 | ref.timerRef = this.options.clearTimeout(ref.timerRef); 35 | return; 36 | } 37 | return this.options.clearTimeout(ref); 38 | }; 39 | Timers.prototype._setTimeout = function setTimeout(owner, callback, delay, ...args) { 40 | const executing = this[kExecuting]; 41 | const ref = this._getReference(owner, callback, delay, args); 42 | executing.add(ref); 43 | if (delay < MAX_DELAY) { 44 | ref.timerRef = this.options.setTimeout(onTimeout, ref.delay, ...ref.args); 45 | } 46 | return ref; 47 | function onTimeout(...rargs) { 48 | executing.delete(ref); 49 | return callback(...rargs); 50 | } 51 | }; 52 | Timers.prototype._getReference = function getReference(owner, callback, delay, args) { 53 | return new Timer(owner, `timer_${this.count++}`, callback, delay, args); 54 | }; 55 | function RegisteredTimers(timersApi, owner) { 56 | this[kTimerApi] = timersApi; 57 | this.owner = owner; 58 | this.setTimeout = this.setTimeout.bind(this); 59 | this.clearTimeout = this.clearTimeout.bind(this); 60 | } 61 | RegisteredTimers.prototype.setTimeout = function registeredSetTimeout(callback, delay, ...args) { 62 | const timersApi = this[kTimerApi]; 63 | return timersApi._setTimeout(this.owner, callback, delay, ...args); 64 | }; 65 | RegisteredTimers.prototype.clearTimeout = function registeredClearTimeout(ref) { 66 | this[kTimerApi].clearTimeout(ref); 67 | }; 68 | function Timer(owner, timerId, callback, delay, args) { 69 | this.callback = callback; 70 | this.delay = delay; 71 | this.args = args; 72 | this.owner = owner; 73 | this.timerId = timerId; 74 | this.expireAt = new Date(Date.now() + delay); 75 | this.timerRef = null; 76 | } -------------------------------------------------------------------------------- /dist/Tracker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ActivityTracker = ActivityTracker; 7 | function ActivityTracker(parentId) { 8 | this.id = parentId; 9 | this.status = { 10 | wait: new Set(), 11 | execute: new Set(), 12 | timer: new Set() 13 | }; 14 | } 15 | Object.defineProperty(ActivityTracker.prototype, 'activityStatus', { 16 | get() { 17 | const status = this.status; 18 | if (status.execute.size) return 'executing'; 19 | if (status.timer.size) return 'timer'; 20 | return status.wait.size ? 'wait' : 'idle'; 21 | } 22 | }); 23 | ActivityTracker.prototype.track = function track(routingKey, message) { 24 | const content = message.content; 25 | if (content.isAssociation) return; 26 | if (content.isSequenceFlow) return; 27 | if (content.isSubProcess) return; 28 | const executionId = content.executionId; 29 | switch (routingKey) { 30 | case 'activity.enter': 31 | case 'activity.discard': 32 | case 'activity.start': 33 | case 'activity.execution.completed': 34 | case 'activity.execution.error': 35 | case 'activity.end': 36 | this._executing(executionId); 37 | break; 38 | case 'activity.execution.outbound.take': 39 | case 'activity.detach': 40 | case 'activity.call': 41 | case 'activity.wait': 42 | { 43 | if (content.isMultiInstance) this._waiting(content.parent.executionId);else this._waiting(executionId); 44 | break; 45 | } 46 | case 'activity.timer': 47 | this._timer(content.parent.executionId); 48 | break; 49 | case 'activity.leave': 50 | this._leave(executionId); 51 | break; 52 | } 53 | }; 54 | ActivityTracker.prototype._executing = function executing(id) { 55 | const { 56 | wait, 57 | execute 58 | } = this.status; 59 | wait.delete(id); 60 | execute.add(id); 61 | }; 62 | ActivityTracker.prototype._waiting = function waiting(id) { 63 | const { 64 | wait, 65 | execute 66 | } = this.status; 67 | execute.delete(id); 68 | wait.add(id); 69 | }; 70 | ActivityTracker.prototype._timer = function timerFn(id) { 71 | const { 72 | timer, 73 | execute 74 | } = this.status; 75 | execute.delete(id); 76 | timer.add(id); 77 | }; 78 | ActivityTracker.prototype._leave = function leave(id) { 79 | const { 80 | wait, 81 | execute, 82 | timer 83 | } = this.status; 84 | execute.delete(id); 85 | timer.delete(id); 86 | wait.delete(id); 87 | }; -------------------------------------------------------------------------------- /dist/activity/Dummy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = DummyActivity; 7 | var _messageHelper = require("../messageHelper.js"); 8 | function DummyActivity(activityDef) { 9 | const { 10 | id, 11 | type = 'dummy', 12 | name, 13 | parent, 14 | behaviour 15 | } = activityDef; 16 | return { 17 | id, 18 | type, 19 | name, 20 | behaviour: { 21 | ...behaviour 22 | }, 23 | parent: (0, _messageHelper.cloneParent)(parent), 24 | placeholder: true 25 | }; 26 | } -------------------------------------------------------------------------------- /dist/activity/Escalation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Escalation; 7 | function Escalation(signalDef, context) { 8 | const { 9 | id, 10 | type, 11 | name, 12 | parent: originalParent 13 | } = signalDef; 14 | const { 15 | environment 16 | } = context; 17 | const parent = { 18 | ...originalParent 19 | }; 20 | return { 21 | id, 22 | type, 23 | name, 24 | parent, 25 | resolve 26 | }; 27 | function resolve(executionMessage) { 28 | return { 29 | id, 30 | type, 31 | messageType: 'escalation', 32 | name: name && environment.resolveExpression(name, executionMessage), 33 | parent: { 34 | ...parent 35 | } 36 | }; 37 | } 38 | } -------------------------------------------------------------------------------- /dist/activity/ExecutionScope.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = ExecutionScope; 7 | var _messageHelper = require("../messageHelper.js"); 8 | var _Errors = require("../error/Errors.js"); 9 | function ExecutionScope(activity, initMessage) { 10 | const { 11 | id, 12 | type, 13 | environment, 14 | logger 15 | } = activity; 16 | const { 17 | fields, 18 | content, 19 | properties 20 | } = (0, _messageHelper.cloneMessage)(initMessage); 21 | const scope = { 22 | id, 23 | type, 24 | fields, 25 | content, 26 | properties, 27 | environment, 28 | logger, 29 | resolveExpression, 30 | ActivityError: _Errors.ActivityError, 31 | BpmnError: _Errors.BpmnError 32 | }; 33 | return scope; 34 | function resolveExpression(expression) { 35 | return environment.resolveExpression(expression, scope); 36 | } 37 | } -------------------------------------------------------------------------------- /dist/activity/Message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Message; 7 | function Message(messageDef, context) { 8 | const { 9 | id, 10 | type, 11 | name, 12 | parent: originalParent 13 | } = messageDef; 14 | const { 15 | environment 16 | } = context; 17 | const parent = { 18 | ...originalParent 19 | }; 20 | return { 21 | id, 22 | type, 23 | name, 24 | parent, 25 | resolve 26 | }; 27 | function resolve(executionMessage) { 28 | return { 29 | id, 30 | type, 31 | messageType: 'message', 32 | ...(name && { 33 | name: environment.resolveExpression(name, executionMessage) 34 | }), 35 | parent: { 36 | ...parent 37 | } 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /dist/activity/Signal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Signal; 7 | function Signal(signalDef, context) { 8 | const { 9 | id, 10 | type = 'Signal', 11 | name, 12 | parent: originalParent 13 | } = signalDef; 14 | const { 15 | environment 16 | } = context; 17 | const parent = { 18 | ...originalParent 19 | }; 20 | return { 21 | id, 22 | type, 23 | name, 24 | parent, 25 | resolve 26 | }; 27 | function resolve(executionMessage) { 28 | return { 29 | id, 30 | type, 31 | messageType: 'signal', 32 | ...(name && { 33 | name: environment.resolveExpression(name, executionMessage) 34 | }), 35 | parent: { 36 | ...parent 37 | } 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /dist/condition.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ExpressionCondition = ExpressionCondition; 7 | exports.ScriptCondition = ScriptCondition; 8 | var _ExecutionScope = _interopRequireDefault(require("./activity/ExecutionScope.js")); 9 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 10 | /** 11 | * Script condition 12 | * @param {import('types').ElementBase} owner 13 | * @param {any} script 14 | * @param {string} language 15 | */ 16 | function ScriptCondition(owner, script, language) { 17 | this.type = 'script'; 18 | this.language = language; 19 | this._owner = owner; 20 | this._script = script; 21 | } 22 | 23 | /** 24 | * Execute 25 | * @param {any} message 26 | * @param {CallableFunction} callback 27 | */ 28 | ScriptCondition.prototype.execute = function execute(message, callback) { 29 | const owner = this._owner; 30 | try { 31 | return this._script.execute((0, _ExecutionScope.default)(owner, message), callback); 32 | } catch (err) { 33 | if (!callback) throw err; 34 | owner.logger.error(`<${owner.id}>`, err); 35 | callback(err); 36 | } 37 | }; 38 | 39 | /** 40 | * Expression condition 41 | * @param {import('types').ElementBase} owner 42 | * @param {string} expression 43 | */ 44 | function ExpressionCondition(owner, expression) { 45 | this.type = 'expression'; 46 | this.expression = expression; 47 | this._owner = owner; 48 | } 49 | 50 | /** 51 | * Execute 52 | * @param {any} message 53 | * @param {CallableFunction} callback 54 | */ 55 | ExpressionCondition.prototype.execute = function execute(message, callback) { 56 | const owner = this._owner; 57 | try { 58 | const result = owner.environment.resolveExpression(this.expression, message); 59 | if (callback) return callback(null, result); 60 | return result; 61 | } catch (err) { 62 | if (callback) return callback(err); 63 | throw err; 64 | } 65 | }; -------------------------------------------------------------------------------- /dist/error/BpmnError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = BpmnErrorActivity; 7 | function BpmnErrorActivity(errorDef, context) { 8 | const { 9 | id, 10 | type, 11 | name = 'BpmnError', 12 | behaviour = {} 13 | } = errorDef; 14 | const { 15 | environment 16 | } = context; 17 | return { 18 | id, 19 | type, 20 | name, 21 | errorCode: behaviour.errorCode, 22 | resolve 23 | }; 24 | function resolve(executionMessage, error) { 25 | const resolveCtx = { 26 | ...executionMessage, 27 | error 28 | }; 29 | const result = { 30 | id, 31 | type, 32 | messageType: 'throw', 33 | name: name && environment.resolveExpression(name, resolveCtx), 34 | code: behaviour.errorCode && environment.resolveExpression(behaviour.errorCode, resolveCtx) 35 | }; 36 | if (error) result.inner = error; 37 | return result; 38 | } 39 | } -------------------------------------------------------------------------------- /dist/error/Errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.RunError = exports.BpmnError = exports.ActivityError = void 0; 7 | exports.makeErrorFromMessage = makeErrorFromMessage; 8 | var _messageHelper = require("../messageHelper.js"); 9 | class ActivityError extends Error { 10 | constructor(description, sourceMessage, inner) { 11 | super(description); 12 | this.type = 'ActivityError'; 13 | this.name = this.constructor.name; 14 | this.description = description; 15 | if (sourceMessage) this.source = (0, _messageHelper.cloneMessage)(sourceMessage, sourceMessage.content?.error && { 16 | error: undefined 17 | }); 18 | if (inner) { 19 | this.inner = inner; 20 | if (inner.name) this.name = inner.name; 21 | if (inner.code) this.code = inner.code; 22 | } 23 | } 24 | } 25 | exports.ActivityError = ActivityError; 26 | class RunError extends ActivityError { 27 | constructor(...args) { 28 | super(...args); 29 | this.type = 'RunError'; 30 | } 31 | } 32 | exports.RunError = RunError; 33 | class BpmnError extends Error { 34 | constructor(description, behaviour, sourceMessage, inner) { 35 | super(description); 36 | this.type = 'BpmnError'; 37 | this.name = behaviour?.name ?? this.constructor.name; 38 | this.description = description; 39 | this.code = behaviour?.errorCode?.toString() ?? behaviour?.code; 40 | this.id = behaviour?.id; 41 | if (sourceMessage) this.source = (0, _messageHelper.cloneMessage)(sourceMessage, sourceMessage.content?.error && { 42 | error: undefined 43 | }); 44 | if (inner) this.inner = inner; 45 | } 46 | } 47 | exports.BpmnError = BpmnError; 48 | function makeErrorFromMessage(errorMessage) { 49 | const { 50 | content 51 | } = errorMessage; 52 | if (isKnownError(content)) return content; 53 | const { 54 | error 55 | } = content; 56 | if (!error) return new Error(`Malformatted error message with routing key ${errorMessage.fields?.routingKey}`); 57 | if (isKnownError(error)) return error; 58 | switch (error.type) { 59 | case 'ActivityError': 60 | return new ActivityError(error.message || error.description, error.source, error.inner ? error.inner : { 61 | code: error.code, 62 | name: error.name 63 | }); 64 | case 'RunError': 65 | return new RunError(error.message || error.description, error.source, error.inner ? error.inner : { 66 | code: error.code, 67 | name: error.name 68 | }); 69 | case 'BpmnError': 70 | return new BpmnError(error.message || error.description, error, error.source); 71 | } 72 | return error; 73 | } 74 | function isKnownError(test) { 75 | if (test instanceof ActivityError) return test; 76 | if (test instanceof BpmnError) return test; 77 | if (test instanceof Error) return test; 78 | } -------------------------------------------------------------------------------- /dist/eventDefinitions/TerminateEventDefinition.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = TerminateEventDefinition; 7 | var _messageHelper = require("../messageHelper.js"); 8 | function TerminateEventDefinition(activity, eventDefinition) { 9 | const { 10 | id, 11 | broker, 12 | environment 13 | } = activity; 14 | const { 15 | type = 'TerminateEventDefinition' 16 | } = eventDefinition; 17 | this.id = id; 18 | this.type = type; 19 | this.activity = activity; 20 | this.broker = broker; 21 | this.logger = environment.Logger(type.toLowerCase()); 22 | } 23 | TerminateEventDefinition.prototype.execute = function execute(executeMessage) { 24 | const executeContent = executeMessage.content; 25 | const throwContent = (0, _messageHelper.cloneContent)(executeContent, { 26 | state: 'terminate' 27 | }); 28 | throwContent.parent = (0, _messageHelper.shiftParent)(executeContent.parent); 29 | this.logger.debug(`<${executeContent.executionId} (${executeContent.id})> terminate`); 30 | const broker = this.broker; 31 | broker.publish('event', 'process.terminate', throwContent, { 32 | type: 'terminate' 33 | }); 34 | broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeContent)); 35 | }; -------------------------------------------------------------------------------- /dist/eventDefinitions/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "CancelEventDefinition", { 7 | enumerable: true, 8 | get: function () { 9 | return _CancelEventDefinition.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "CompensateEventDefinition", { 13 | enumerable: true, 14 | get: function () { 15 | return _CompensateEventDefinition.default; 16 | } 17 | }); 18 | Object.defineProperty(exports, "ConditionalEventDefinition", { 19 | enumerable: true, 20 | get: function () { 21 | return _ConditionalEventDefinition.default; 22 | } 23 | }); 24 | Object.defineProperty(exports, "ErrorEventDefinition", { 25 | enumerable: true, 26 | get: function () { 27 | return _ErrorEventDefinition.default; 28 | } 29 | }); 30 | Object.defineProperty(exports, "EscalationEventDefinition", { 31 | enumerable: true, 32 | get: function () { 33 | return _EscalationEventDefinition.default; 34 | } 35 | }); 36 | Object.defineProperty(exports, "LinkEventDefinition", { 37 | enumerable: true, 38 | get: function () { 39 | return _LinkEventDefinition.default; 40 | } 41 | }); 42 | Object.defineProperty(exports, "MessageEventDefinition", { 43 | enumerable: true, 44 | get: function () { 45 | return _MessageEventDefinition.default; 46 | } 47 | }); 48 | Object.defineProperty(exports, "SignalEventDefinition", { 49 | enumerable: true, 50 | get: function () { 51 | return _SignalEventDefinition.default; 52 | } 53 | }); 54 | Object.defineProperty(exports, "TerminateEventDefinition", { 55 | enumerable: true, 56 | get: function () { 57 | return _TerminateEventDefinition.default; 58 | } 59 | }); 60 | Object.defineProperty(exports, "TimerEventDefinition", { 61 | enumerable: true, 62 | get: function () { 63 | return _TimerEventDefinition.default; 64 | } 65 | }); 66 | var _CancelEventDefinition = _interopRequireDefault(require("./CancelEventDefinition.js")); 67 | var _CompensateEventDefinition = _interopRequireDefault(require("./CompensateEventDefinition.js")); 68 | var _ConditionalEventDefinition = _interopRequireDefault(require("./ConditionalEventDefinition.js")); 69 | var _ErrorEventDefinition = _interopRequireDefault(require("./ErrorEventDefinition.js")); 70 | var _EscalationEventDefinition = _interopRequireDefault(require("./EscalationEventDefinition.js")); 71 | var _LinkEventDefinition = _interopRequireDefault(require("./LinkEventDefinition.js")); 72 | var _MessageEventDefinition = _interopRequireDefault(require("./MessageEventDefinition.js")); 73 | var _SignalEventDefinition = _interopRequireDefault(require("./SignalEventDefinition.js")); 74 | var _TerminateEventDefinition = _interopRequireDefault(require("./TerminateEventDefinition.js")); 75 | var _TimerEventDefinition = _interopRequireDefault(require("./TimerEventDefinition.js")); 76 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } -------------------------------------------------------------------------------- /dist/events/EndEvent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.EndEventBehaviour = EndEventBehaviour; 7 | exports.default = EndEvent; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _EventDefinitionExecution = _interopRequireDefault(require("../eventDefinitions/EventDefinitionExecution.js")); 10 | var _messageHelper = require("../messageHelper.js"); 11 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 12 | const kExecution = Symbol.for('execution'); 13 | function EndEvent(activityDef, context) { 14 | return new _Activity.default(EndEventBehaviour, { 15 | ...activityDef, 16 | isThrowing: true 17 | }, context); 18 | } 19 | function EndEventBehaviour(activity) { 20 | this.id = activity.id; 21 | this.type = activity.type; 22 | this.broker = activity.broker; 23 | this[kExecution] = activity.eventDefinitions && new _EventDefinitionExecution.default(activity, activity.eventDefinitions); 24 | } 25 | EndEventBehaviour.prototype.execute = function execute(executeMessage) { 26 | const execution = this[kExecution]; 27 | if (execution) { 28 | return execution.execute(executeMessage); 29 | } 30 | return this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeMessage.content)); 31 | }; -------------------------------------------------------------------------------- /dist/events/IntermediateCatchEvent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.IntermediateCatchEventBehaviour = IntermediateCatchEventBehaviour; 7 | exports.default = IntermediateCatchEvent; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _EventDefinitionExecution = _interopRequireDefault(require("../eventDefinitions/EventDefinitionExecution.js")); 10 | var _messageHelper = require("../messageHelper.js"); 11 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 12 | const kExecution = Symbol.for('execution'); 13 | function IntermediateCatchEvent(activityDef, context) { 14 | return new _Activity.default(IntermediateCatchEventBehaviour, activityDef, context); 15 | } 16 | function IntermediateCatchEventBehaviour(activity) { 17 | this.id = activity.id; 18 | this.type = activity.type; 19 | this.broker = activity.broker; 20 | this[kExecution] = activity.eventDefinitions && new _EventDefinitionExecution.default(activity, activity.eventDefinitions); 21 | } 22 | IntermediateCatchEventBehaviour.prototype.execute = function execute(executeMessage) { 23 | const execution = this[kExecution]; 24 | if (execution) { 25 | return execution.execute(executeMessage); 26 | } 27 | const executeContent = executeMessage.content; 28 | const executionId = executeContent.executionId; 29 | const broker = this.broker; 30 | broker.subscribeTmp('api', `activity.#.${executionId}`, this._onApiMessage.bind(this, executeMessage), { 31 | noAck: true, 32 | consumerTag: '_api-behaviour-execution' 33 | }); 34 | return broker.publish('event', 'activity.wait', (0, _messageHelper.cloneContent)(executeContent)); 35 | }; 36 | IntermediateCatchEventBehaviour.prototype._onApiMessage = function onApiMessage(executeMessage, routingKey, message) { 37 | switch (message.properties.type) { 38 | case 'message': 39 | case 'signal': 40 | { 41 | const broker = this.broker; 42 | broker.cancel('_api-behaviour-execution'); 43 | return broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeMessage.content, { 44 | output: message.content.message 45 | })); 46 | } 47 | case 'discard': 48 | { 49 | const broker = this.broker; 50 | broker.cancel('_api-behaviour-execution'); 51 | return broker.publish('execution', 'execute.discard', (0, _messageHelper.cloneContent)(executeMessage.content)); 52 | } 53 | case 'stop': 54 | { 55 | return this.broker.cancel('_api-behaviour-execution'); 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /dist/events/IntermediateThrowEvent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.IntermediateThrowEventBehaviour = IntermediateThrowEventBehaviour; 7 | exports.default = IntermediateThrowEvent; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _EventDefinitionExecution = _interopRequireDefault(require("../eventDefinitions/EventDefinitionExecution.js")); 10 | var _messageHelper = require("../messageHelper.js"); 11 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 12 | const kExecution = Symbol.for('execution'); 13 | function IntermediateThrowEvent(activityDef, context) { 14 | return new _Activity.default(IntermediateThrowEventBehaviour, { 15 | ...activityDef, 16 | isThrowing: true 17 | }, context); 18 | } 19 | function IntermediateThrowEventBehaviour(activity) { 20 | this.id = activity.id; 21 | this.type = activity.type; 22 | this.broker = activity.broker; 23 | this[kExecution] = activity.eventDefinitions && new _EventDefinitionExecution.default(activity, activity.eventDefinitions); 24 | } 25 | IntermediateThrowEventBehaviour.prototype.execute = function execute(executeMessage) { 26 | const execution = this[kExecution]; 27 | if (execution) { 28 | return execution.execute(executeMessage); 29 | } 30 | return this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeMessage.content)); 31 | }; -------------------------------------------------------------------------------- /dist/events/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "BoundaryEvent", { 7 | enumerable: true, 8 | get: function () { 9 | return _BoundaryEvent.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "BoundaryEventBehaviour", { 13 | enumerable: true, 14 | get: function () { 15 | return _BoundaryEvent.BoundaryEventBehaviour; 16 | } 17 | }); 18 | Object.defineProperty(exports, "EndEvent", { 19 | enumerable: true, 20 | get: function () { 21 | return _EndEvent.default; 22 | } 23 | }); 24 | Object.defineProperty(exports, "EndEventBehaviour", { 25 | enumerable: true, 26 | get: function () { 27 | return _EndEvent.EndEventBehaviour; 28 | } 29 | }); 30 | Object.defineProperty(exports, "IntermediateCatchEvent", { 31 | enumerable: true, 32 | get: function () { 33 | return _IntermediateCatchEvent.default; 34 | } 35 | }); 36 | Object.defineProperty(exports, "IntermediateCatchEventBehaviour", { 37 | enumerable: true, 38 | get: function () { 39 | return _IntermediateCatchEvent.IntermediateCatchEventBehaviour; 40 | } 41 | }); 42 | Object.defineProperty(exports, "IntermediateThrowEvent", { 43 | enumerable: true, 44 | get: function () { 45 | return _IntermediateThrowEvent.default; 46 | } 47 | }); 48 | Object.defineProperty(exports, "IntermediateThrowEventBehaviour", { 49 | enumerable: true, 50 | get: function () { 51 | return _IntermediateThrowEvent.IntermediateThrowEventBehaviour; 52 | } 53 | }); 54 | Object.defineProperty(exports, "StartEvent", { 55 | enumerable: true, 56 | get: function () { 57 | return _StartEvent.default; 58 | } 59 | }); 60 | Object.defineProperty(exports, "StartEventBehaviour", { 61 | enumerable: true, 62 | get: function () { 63 | return _StartEvent.StartEventBehaviour; 64 | } 65 | }); 66 | var _BoundaryEvent = _interopRequireWildcard(require("./BoundaryEvent.js")); 67 | var _EndEvent = _interopRequireWildcard(require("./EndEvent.js")); 68 | var _IntermediateCatchEvent = _interopRequireWildcard(require("./IntermediateCatchEvent.js")); 69 | var _IntermediateThrowEvent = _interopRequireWildcard(require("./IntermediateThrowEvent.js")); 70 | var _StartEvent = _interopRequireWildcard(require("./StartEvent.js")); 71 | function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } 72 | function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } -------------------------------------------------------------------------------- /dist/flows/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "Association", { 7 | enumerable: true, 8 | get: function () { 9 | return _Association.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "MessageFlow", { 13 | enumerable: true, 14 | get: function () { 15 | return _MessageFlow.default; 16 | } 17 | }); 18 | Object.defineProperty(exports, "SequenceFlow", { 19 | enumerable: true, 20 | get: function () { 21 | return _SequenceFlow.default; 22 | } 23 | }); 24 | var _Association = _interopRequireDefault(require("./Association.js")); 25 | var _MessageFlow = _interopRequireDefault(require("./MessageFlow.js")); 26 | var _SequenceFlow = _interopRequireDefault(require("./SequenceFlow.js")); 27 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } -------------------------------------------------------------------------------- /dist/gateways/ExclusiveGateway.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ExclusiveGatewayBehaviour = ExclusiveGatewayBehaviour; 7 | exports.default = ExclusiveGateway; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _messageHelper = require("../messageHelper.js"); 10 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 11 | function ExclusiveGateway(activityDef, context) { 12 | return new _Activity.default(ExclusiveGatewayBehaviour, activityDef, context); 13 | } 14 | function ExclusiveGatewayBehaviour(activity) { 15 | const { 16 | id, 17 | type, 18 | broker 19 | } = activity; 20 | this.id = id; 21 | this.type = type; 22 | this.broker = broker; 23 | } 24 | ExclusiveGatewayBehaviour.prototype.execute = function execute({ 25 | content 26 | }) { 27 | this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(content, { 28 | outboundTakeOne: true 29 | })); 30 | }; -------------------------------------------------------------------------------- /dist/gateways/InclusiveGateway.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.InclusiveGatewayBehaviour = InclusiveGatewayBehaviour; 7 | exports.default = InclusiveGateway; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _messageHelper = require("../messageHelper.js"); 10 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 11 | function InclusiveGateway(activityDef, context) { 12 | return new _Activity.default(InclusiveGatewayBehaviour, activityDef, context); 13 | } 14 | function InclusiveGatewayBehaviour(activity) { 15 | const { 16 | id, 17 | type, 18 | broker 19 | } = activity; 20 | this.id = id; 21 | this.type = type; 22 | this.broker = broker; 23 | } 24 | InclusiveGatewayBehaviour.prototype.execute = function execute({ 25 | content 26 | }) { 27 | this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(content)); 28 | }; -------------------------------------------------------------------------------- /dist/gateways/ParallelGateway.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ParallelGatewayBehaviour = ParallelGatewayBehaviour; 7 | exports.default = ParallelGateway; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _messageHelper = require("../messageHelper.js"); 10 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 11 | function ParallelGateway(activityDef, context) { 12 | return new _Activity.default(ParallelGatewayBehaviour, { 13 | ...activityDef, 14 | isParallelGateway: true 15 | }, context); 16 | } 17 | function ParallelGatewayBehaviour(activity) { 18 | const { 19 | id, 20 | type, 21 | broker 22 | } = activity; 23 | this.id = id; 24 | this.type = type; 25 | this.broker = broker; 26 | } 27 | ParallelGatewayBehaviour.prototype.execute = function execute({ 28 | content 29 | }) { 30 | this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(content)); 31 | }; -------------------------------------------------------------------------------- /dist/gateways/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "EventBasedGateway", { 7 | enumerable: true, 8 | get: function () { 9 | return _EventBasedGateway.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "EventBasedGatewayBehaviour", { 13 | enumerable: true, 14 | get: function () { 15 | return _EventBasedGateway.EventBasedGatewayBehaviour; 16 | } 17 | }); 18 | Object.defineProperty(exports, "ExclusiveGateway", { 19 | enumerable: true, 20 | get: function () { 21 | return _ExclusiveGateway.default; 22 | } 23 | }); 24 | Object.defineProperty(exports, "ExclusiveGatewayBehaviour", { 25 | enumerable: true, 26 | get: function () { 27 | return _ExclusiveGateway.ExclusiveGatewayBehaviour; 28 | } 29 | }); 30 | Object.defineProperty(exports, "InclusiveGateway", { 31 | enumerable: true, 32 | get: function () { 33 | return _InclusiveGateway.default; 34 | } 35 | }); 36 | Object.defineProperty(exports, "InclusiveGatewayBehaviour", { 37 | enumerable: true, 38 | get: function () { 39 | return _InclusiveGateway.InclusiveGatewayBehaviour; 40 | } 41 | }); 42 | Object.defineProperty(exports, "ParallelGateway", { 43 | enumerable: true, 44 | get: function () { 45 | return _ParallelGateway.default; 46 | } 47 | }); 48 | Object.defineProperty(exports, "ParallelGatewayBehaviour", { 49 | enumerable: true, 50 | get: function () { 51 | return _ParallelGateway.ParallelGatewayBehaviour; 52 | } 53 | }); 54 | var _EventBasedGateway = _interopRequireWildcard(require("./EventBasedGateway.js")); 55 | var _ExclusiveGateway = _interopRequireWildcard(require("./ExclusiveGateway.js")); 56 | var _InclusiveGateway = _interopRequireWildcard(require("./InclusiveGateway.js")); 57 | var _ParallelGateway = _interopRequireWildcard(require("./ParallelGateway.js")); 58 | function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } 59 | function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } -------------------------------------------------------------------------------- /dist/io/BpmnIO.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = BpmnIO; 7 | function BpmnIO(activity, context) { 8 | this.activity = activity; 9 | this.context = context; 10 | this.type = 'bpmnio'; 11 | const { 12 | ioSpecification: ioSpecificationDef, 13 | properties: propertiesDef 14 | } = activity.behaviour; 15 | this.specification = ioSpecificationDef && new ioSpecificationDef.Behaviour(activity, ioSpecificationDef, context); 16 | this.properties = propertiesDef && new propertiesDef.Behaviour(activity, propertiesDef, context); 17 | } 18 | Object.defineProperty(BpmnIO.prototype, 'hasIo', { 19 | get() { 20 | return this.specification || this.properties; 21 | } 22 | }); 23 | BpmnIO.prototype.activate = function activate(message) { 24 | const properties = this.properties, 25 | specification = this.specification; 26 | if (properties) properties.activate(message); 27 | if (specification) specification.activate(message); 28 | }; 29 | BpmnIO.prototype.deactivate = function deactivate(message) { 30 | const properties = this.properties, 31 | specification = this.specification; 32 | if (properties) properties.deactivate(message); 33 | if (specification) specification.deactivate(message); 34 | }; -------------------------------------------------------------------------------- /dist/io/EnvironmentDataObject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = EnvironmentDataObject; 7 | function EnvironmentDataObject(dataObjectDef, { 8 | environment 9 | }) { 10 | const { 11 | id, 12 | type, 13 | name, 14 | behaviour, 15 | parent 16 | } = dataObjectDef; 17 | this.id = id; 18 | this.type = type; 19 | this.name = name; 20 | this.behaviour = behaviour; 21 | this.parent = parent; 22 | this.environment = environment; 23 | } 24 | EnvironmentDataObject.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 25 | const environment = this.environment; 26 | const value = environment.variables._data?.[this.id]; 27 | const content = this._createContent(value); 28 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 29 | }; 30 | EnvironmentDataObject.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 31 | const environment = this.environment; 32 | environment.variables._data = environment.variables._data || {}; 33 | environment.variables._data[this.id] = value; 34 | const content = this._createContent(value); 35 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 36 | }; 37 | EnvironmentDataObject.prototype._createContent = function createContent(value) { 38 | return { 39 | id: this.id, 40 | type: this.type, 41 | name: this.name, 42 | value 43 | }; 44 | }; -------------------------------------------------------------------------------- /dist/io/EnvironmentDataStore.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = EnvironmentDataStore; 7 | function EnvironmentDataStore(dataStoreDef, { 8 | environment 9 | }) { 10 | const { 11 | id, 12 | type, 13 | name, 14 | behaviour, 15 | parent 16 | } = dataStoreDef; 17 | this.id = id; 18 | this.type = type; 19 | this.name = name; 20 | this.behaviour = behaviour; 21 | this.parent = parent; 22 | this.environment = environment; 23 | } 24 | EnvironmentDataStore.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 25 | const environment = this.environment; 26 | const value = environment.variables._data?.[this.id]; 27 | const content = this._createContent(value); 28 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 29 | }; 30 | EnvironmentDataStore.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 31 | const environment = this.environment; 32 | environment.variables._data = environment.variables._data || {}; 33 | environment.variables._data[this.id] = value; 34 | const content = this._createContent(value); 35 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 36 | }; 37 | EnvironmentDataStore.prototype._createContent = function createContent(value) { 38 | return { 39 | id: this.id, 40 | type: this.type, 41 | name: this.name, 42 | value 43 | }; 44 | }; -------------------------------------------------------------------------------- /dist/io/EnvironmentDataStoreReference.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = EnvironmentDataStoreReference; 7 | function EnvironmentDataStoreReference(dataObjectDef, { 8 | environment 9 | }) { 10 | const { 11 | id, 12 | type, 13 | name, 14 | behaviour, 15 | parent 16 | } = dataObjectDef; 17 | this.id = id; 18 | this.type = type; 19 | this.name = name; 20 | this.behaviour = behaviour; 21 | this.parent = parent; 22 | this.environment = environment; 23 | } 24 | EnvironmentDataStoreReference.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 25 | const environment = this.environment; 26 | const value = environment.variables._data?.[this.id]; 27 | const content = this._createContent(value); 28 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 29 | }; 30 | EnvironmentDataStoreReference.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 31 | const environment = this.environment; 32 | environment.variables._data = environment.variables._data || {}; 33 | environment.variables._data[this.id] = value; 34 | const content = this._createContent(value); 35 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 36 | }; 37 | EnvironmentDataStoreReference.prototype._createContent = function createContent(value) { 38 | return { 39 | id: this.id, 40 | type: this.type, 41 | name: this.name, 42 | value 43 | }; 44 | }; -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /dist/process/Lane.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Lane; 7 | const kProcess = Symbol.for('process'); 8 | function Lane(process, laneDefinition) { 9 | const { 10 | broker, 11 | environment 12 | } = process; 13 | const { 14 | id, 15 | type, 16 | behaviour 17 | } = laneDefinition; 18 | this[kProcess] = process; 19 | this.id = id; 20 | this.type = type; 21 | this.name = behaviour.name; 22 | this.parent = { 23 | id: process.id, 24 | type: process.type 25 | }; 26 | this.behaviour = { 27 | ...behaviour 28 | }; 29 | this.environment = environment; 30 | this.broker = broker; 31 | this.context = process.context; 32 | this.logger = environment.Logger(type.toLowerCase()); 33 | } 34 | Object.defineProperty(Lane.prototype, 'process', { 35 | get() { 36 | return this[kProcess]; 37 | } 38 | }); -------------------------------------------------------------------------------- /dist/shared.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.brokerSafeId = brokerSafeId; 7 | exports.generateId = generateId; 8 | exports.getOptionsAndCallback = getOptionsAndCallback; 9 | exports.getUniqueId = getUniqueId; 10 | const safePattern = /[./\\#*:\s]/g; 11 | function generateId() { 12 | return Math.random().toString(16).substring(2, 12); 13 | } 14 | function brokerSafeId(id) { 15 | return id.replace(safePattern, '_'); 16 | } 17 | function getUniqueId(prefix) { 18 | return `${brokerSafeId(prefix)}_${generateId()}`; 19 | } 20 | function getOptionsAndCallback(optionsOrCallback, callback) { 21 | let options; 22 | if (typeof optionsOrCallback === 'function') { 23 | callback = optionsOrCallback; 24 | } else { 25 | options = optionsOrCallback; 26 | } 27 | return [options, callback]; 28 | } -------------------------------------------------------------------------------- /dist/tasks/ScriptTask.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.ScriptTaskBehaviour = ScriptTaskBehaviour; 7 | exports.default = ScriptTask; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _ExecutionScope = _interopRequireDefault(require("../activity/ExecutionScope.js")); 10 | var _Errors = require("../error/Errors.js"); 11 | var _messageHelper = require("../messageHelper.js"); 12 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 13 | function ScriptTask(activityDef, context) { 14 | return new _Activity.default(ScriptTaskBehaviour, activityDef, context); 15 | } 16 | function ScriptTaskBehaviour(activity) { 17 | const { 18 | id, 19 | type, 20 | behaviour 21 | } = activity; 22 | this.id = id; 23 | this.type = type; 24 | this.scriptFormat = behaviour.scriptFormat; 25 | this.loopCharacteristics = behaviour.loopCharacteristics && new behaviour.loopCharacteristics.Behaviour(activity, behaviour.loopCharacteristics); 26 | this.activity = activity; 27 | const environment = this.environment = activity.environment; 28 | environment.registerScript(activity); 29 | } 30 | ScriptTaskBehaviour.prototype.execute = function execute(executeMessage) { 31 | const executeContent = executeMessage.content; 32 | const loopCharacteristics = this.loopCharacteristics; 33 | if (loopCharacteristics && executeContent.isRootScope) { 34 | return loopCharacteristics.execute(executeMessage); 35 | } 36 | const activity = this.activity; 37 | const scriptFormat = this.scriptFormat; 38 | const script = this.environment.getScript(scriptFormat, activity, (0, _messageHelper.cloneMessage)(executeMessage)); 39 | if (!script) { 40 | return activity.emitFatal(new _Errors.ActivityError(`Script format ${scriptFormat} is unsupported or was not registered for <${activity.id}>`, executeMessage), executeContent); 41 | } 42 | return script.execute((0, _ExecutionScope.default)(activity, executeMessage), scriptCallback); 43 | function scriptCallback(err, output) { 44 | if (err) { 45 | activity.logger.error(`<${executeContent.executionId} (${activity.id})>`, err); 46 | return activity.broker.publish('execution', 'execute.error', (0, _messageHelper.cloneContent)(executeContent, { 47 | error: new _Errors.ActivityError(err.message, executeMessage, err) 48 | }, { 49 | mandatory: true 50 | })); 51 | } 52 | return activity.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeContent, { 53 | output 54 | })); 55 | } 56 | }; -------------------------------------------------------------------------------- /dist/tasks/ServiceImplementation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = ServiceImplementation; 7 | var _ExecutionScope = _interopRequireDefault(require("../activity/ExecutionScope.js")); 8 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 9 | function ServiceImplementation(activity) { 10 | this.type = `${activity.type}:implementation`; 11 | this.implementation = activity.behaviour.implementation; 12 | this.activity = activity; 13 | } 14 | ServiceImplementation.prototype.execute = function execute(executionMessage, callback) { 15 | const activity = this.activity; 16 | const implementation = this.implementation; 17 | const serviceFn = activity.environment.resolveExpression(implementation, executionMessage); 18 | if (typeof serviceFn !== 'function') return callback(new Error(`Implementation ${implementation} did not resolve to a function`)); 19 | serviceFn.call(activity, (0, _ExecutionScope.default)(activity, executionMessage), (err, ...args) => { 20 | callback(err, args); 21 | }); 22 | }; -------------------------------------------------------------------------------- /dist/tasks/StandardLoopCharacteristics.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = StandardLoopCharacteristics; 7 | var _LoopCharacteristics = _interopRequireDefault(require("./LoopCharacteristics.js")); 8 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 9 | function StandardLoopCharacteristics(activity, loopCharacteristics) { 10 | let { 11 | behaviour 12 | } = loopCharacteristics; 13 | behaviour = { 14 | ...behaviour, 15 | isSequential: true 16 | }; 17 | return new _LoopCharacteristics.default(activity, { 18 | ...loopCharacteristics, 19 | behaviour 20 | }); 21 | } -------------------------------------------------------------------------------- /dist/tasks/Task.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.TaskBehaviour = TaskBehaviour; 7 | exports.default = Task; 8 | var _Activity = _interopRequireDefault(require("../activity/Activity.js")); 9 | var _messageHelper = require("../messageHelper.js"); 10 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 11 | function Task(activityDef, context) { 12 | return new _Activity.default(TaskBehaviour, activityDef, context); 13 | } 14 | function TaskBehaviour(activity) { 15 | const { 16 | id, 17 | type, 18 | behaviour, 19 | broker 20 | } = activity; 21 | this.id = id; 22 | this.type = type; 23 | this.loopCharacteristics = behaviour.loopCharacteristics && new behaviour.loopCharacteristics.Behaviour(activity, behaviour.loopCharacteristics); 24 | this.broker = broker; 25 | } 26 | TaskBehaviour.prototype.execute = function execute(executeMessage) { 27 | const executeContent = executeMessage.content; 28 | const loopCharacteristics = this.loopCharacteristics; 29 | if (loopCharacteristics && executeContent.isRootScope) { 30 | return loopCharacteristics.execute(executeMessage); 31 | } 32 | return this.broker.publish('execution', 'execute.completed', (0, _messageHelper.cloneContent)(executeContent)); 33 | }; -------------------------------------------------------------------------------- /dist/tasks/Transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Transaction; 7 | var _SubProcess = _interopRequireDefault(require("./SubProcess.js")); 8 | function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 9 | function Transaction(activityDef, context) { 10 | const transaction = { 11 | type: 'transaction', 12 | ...activityDef, 13 | isTransaction: true 14 | }; 15 | const activity = (0, _SubProcess.default)(transaction, context); 16 | return activity; 17 | } -------------------------------------------------------------------------------- /docs/ActivityExecution.md: -------------------------------------------------------------------------------- 1 | # Activity execution 2 | 3 | Shared activity execution. 4 | 5 | ![Activity execution](https://raw.github.com/paed01/bpmn-elements/master/docs/activity-execution.png) 6 | 7 | ## `new ActivityExecution(activity, context)` 8 | 9 | Arguments: 10 | 11 | - `activity`: parent [activity](/docs/Activity.md) function 12 | - `context`: [shared context](/docs/Context.md) 13 | 14 | Properties: 15 | 16 | - `completed`: has execution completed 17 | - `source`: instance of activity [behaviour](/docs/Extend.md) 18 | 19 | ### `discard()` 20 | 21 | Discard execution. 22 | 23 | ### `execute(executeMessage)` 24 | 25 | Execute activity behaviour with message. 26 | 27 | ### `getApi(message)` 28 | 29 | Get activity [api](/docs/SharedApi.md). 30 | 31 | ### `getPostponed()` 32 | 33 | Get activity executions that are in a postponed state. Returns list of [api](/docs/SharedApi.md). 34 | 35 | ### `getState()` 36 | 37 | Get activity execution state. 38 | 39 | ### `recover([state])` 40 | 41 | Recover activity execution state. 42 | 43 | ### `stop()` 44 | 45 | Stop execution 46 | -------------------------------------------------------------------------------- /docs/BpmnIO.md: -------------------------------------------------------------------------------- 1 | # Bpmn IO 2 | 3 | The implementation of data storage in this project is rather basic. It relies on object assigned to `environment.variables._data`. Use it for reference and preferably override it with your own implementation. 4 | 5 | # DataObject 6 | 7 | Process data object element 8 | 9 | # DataStoreReference 10 | 11 | Process data store reference element. Will be returned since some modellers don't add a datastore when designing. 12 | 13 | # DataStore 14 | 15 | Definition data store 16 | 17 | # InputOutputSpecification 18 | 19 | Activity input/output element 20 | 21 | # Property 22 | 23 | Activity property element 24 | -------------------------------------------------------------------------------- /docs/CallActivity.md: -------------------------------------------------------------------------------- 1 | # CallActivity 2 | 3 | # Behaviour 4 | 5 | Call activity will wait for called process to complete. 6 | 7 | Process defined in the same definition can be started. If the process is not found the call activity expects to be signaled or cancelled. 8 | 9 | Emits `activity.call` event with `calledElement` property containing the id of the process. Expressions can be used to resolve the `calledElement` property value. 10 | 11 | If the call activity is cancelled the target process is discarded. 12 | 13 | If the target process is discarded the call activity is cancelled. 14 | 15 | If the target process throws the call activity is errored. The call activity can catch the error with a boundary error event. 16 | -------------------------------------------------------------------------------- /docs/ConditionalEventDefinition.md: -------------------------------------------------------------------------------- 1 | # ConditionalEventDefinition 2 | 3 | ConditionalEventDefinition behaviour. 4 | 5 | - Checks condition when event is first executed 6 | - Expects to be signalled to check condition again 7 | 8 | ```javascript 9 | import { Definition } from 'bpmn-elements'; 10 | 11 | import testHelpers from '../test/helpers/testHelpers.js'; 12 | import factory from '../test/helpers/factory.js'; 13 | 14 | const boundEventSource = factory.resource('conditional-bound-js-event.bpmn'); 15 | 16 | const context = await testHelpers.context(boundEventSource); 17 | const definition = new Definition(context); 18 | 19 | const waiting = definition.waitFor('wait', (_routingKey, api) => { 20 | return !!api.content.condition; 21 | }); 22 | 23 | const condition1 = definition.waitFor('activity.condition'); 24 | 25 | definition.run(); 26 | 27 | console.log('condition type', (await waiting).content.condition); 28 | console.log('first condition result', (await condition1).content.conditionResult); 29 | 30 | const condition2 = definition.waitFor('activity.condition'); 31 | const completed = definition.waitFor('leave'); 32 | 33 | definition.signal({ id: 'cond' }); 34 | 35 | console.log('signal condition result', (await condition2).content.conditionResult); 36 | 37 | await completed; 38 | ``` 39 | 40 | ## ConditionalEventDefinition events 41 | 42 | ### `activity.wait` 43 | 44 | Fired when condition is waiting for signal. 45 | 46 | ### `activity.condition` 47 | 48 | Fired when condition is checked. 49 | -------------------------------------------------------------------------------- /docs/Context.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | Shared context. 4 | 5 | ## `new Context(serializableContext[, environment])` 6 | 7 | Arguments: 8 | 9 | - `serializableContext`: serializable context. Maybe from [moddle-context-serializer](https://www.npmjs.com/package/moddle-context-serializer) 10 | - `environment`: optional [Environment](/docs/Environment.md) instance 11 | 12 | Returns api. 13 | 14 | Properties: 15 | 16 | - `id`: definition id 17 | - `name`: definition name 18 | - `type`: definition type 19 | - `sid`: some unique id 20 | - `definitionContext`: the passed serializable context 21 | - `environment`: [Environment](/docs/Environment.md) instance 22 | - `owner`: reference to owning process or sub process 23 | 24 | ### `clone([environment])` 25 | 26 | Clone context. 27 | 28 | Arguments: 29 | 30 | - `environment`: optional new environment for cloned context 31 | 32 | Returns clone of context with new activity instances. 33 | 34 | ### `getActivities([scopeId])` 35 | 36 | Get all [activity instances](/docs/Activity.md) scoped to id. 37 | 38 | ### `getActivityById(id)` 39 | 40 | Get [activity instance](/docs/Activity.md) by id. 41 | 42 | ### `getExecutableProcesses()` 43 | 44 | Get executable processes. 45 | 46 | ### `getDataObjectById(id)` 47 | 48 | Get data object by id. 49 | 50 | ### `getMessageFlows()` 51 | 52 | Get data object by id. 53 | 54 | ### `getProcessById(id)` 55 | 56 | Get process by id. 57 | 58 | ### `getProcesses()` 59 | 60 | Get all processes. 61 | 62 | ### `getSequenceFlowById(id)` 63 | 64 | Get sequence flow instances by id. 65 | 66 | ### `getSequenceFlows(scopeId)` 67 | 68 | Get all sequence flow instances and/or scoped to id. 69 | 70 | ### `getInboundSequenceFlows(activityId)` 71 | 72 | Get activity inbound sequence flows. 73 | 74 | ### `getOutboundSequenceFlows(activityId)` 75 | 76 | Get activity outbound sequence flows. 77 | 78 | ### `loadExtensions(activity)` 79 | 80 | Load [extensions](/docs/Extension.md) for activity. 81 | -------------------------------------------------------------------------------- /docs/Environment.md: -------------------------------------------------------------------------------- 1 | # Environment 2 | 3 | Shared environment. 4 | 5 | ## `new Environment([options])` 6 | 7 | Arguments: 8 | 9 | - `options`: optional options 10 | - `variables`: optional variables object 11 | - `output`: optional output object 12 | - `services`: optional named services object, key is name of service and value must be a function 13 | - `settings`: optional settings 14 | - `step`: boolean, true makes activity runs to go forward in steps, defaults to false 15 | - `enableDummyService`: boolean, true returns dummy service function for service task 16 | - `strict`: boolean, [strict mode](#strict-mode) defaults to false 17 | - `batchSize`: optional positive integer to control parallel loop batch size, defaults to 50 18 | - `disableTrackState`: optional boolean to disable tracking of element counters between recover and resume. State of idle elements are not returned when getting state. Recommended if running and recovering really large flows 19 | - `scripts`: [Scripts instance](/docs/Scripts.md) 20 | - `timers`: [Timers instance](/docs/Timers.md) 21 | - `expressions`: expressions handler, defaults to [Expressions instance](/docs/Expression.md) 22 | - `Logger`: optional [Logger](#logger) defaults to a dummy logger that does basically nothing but supply the required log functions 23 | - `extensions`: [extensions](/docs/Extension.md) object 24 | 25 | Properties: 26 | 27 | - `options`: initial options 28 | - `extensions`: extensions 29 | - `output`: output object 30 | - `scripts`: [Scripts instance](/docs/Scripts.md) 31 | - `expressions`: expressions handler 32 | - `services`: services 33 | - `settings`: settings object 34 | - `variables`: getter for variables object 35 | - `Logger`: passed logger initiator 36 | 37 | ### `addService(name, serviceFn)` 38 | 39 | ### `assignVariables(vars)` 40 | 41 | ### `clone([overrideOptions])` 42 | 43 | ### `getScript(scriptType, activity)` 44 | 45 | ### `getServiceByName(name)` 46 | 47 | Get service by name 48 | 49 | ### `getState()` 50 | 51 | ### `registerScript(activity)` 52 | 53 | ### `resolveExpression(expression[, message = {}, expressionFnContext])` 54 | 55 | Resolve expression. 56 | 57 | Arguments: 58 | 59 | - `expression`: expression string 60 | - `message`: optional object from where to resolve expressions, the environment instance is added by default 61 | - `expressionFnContext`: optional call context (this) 62 | 63 | ### `recover(state)` 64 | 65 | ## Strict mode 66 | 67 | If enabled Boundary event with error event definition only catches thrown Bpmn Errors. 68 | 69 | ## Logger 70 | 71 | Logger factory. 72 | 73 | ### `Logger(scope)` 74 | 75 | Create new logger for scope. 76 | 77 | Must return the following logging functions: 78 | 79 | - `debug` 80 | - `error` 81 | - `warn` 82 | 83 | ## Example implementation for nodejs 84 | 85 | ```js 86 | import Debug from 'debug'; 87 | 88 | export function Logger(scope) { 89 | return { 90 | debug: Debug('bpmn-elements:' + scope), 91 | error: Debug('bpmn-elements:error:' + scope), 92 | warn: Debug('bpmn-elements:warn:' + scope), 93 | }; 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/Examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | # ServiceTask example 4 | 5 | ```javascript 6 | import * as elements from 'bpmn-elements'; 7 | 8 | import BpmnModdle from 'bpmn-moddle'; 9 | 10 | import { default as serialize, TypeResolver } from 'moddle-context-serializer'; 11 | 12 | const { Context, Definition } = elements; 13 | const typeResolver = TypeResolver(elements); 14 | 15 | const source = ` 16 | 17 | 18 | 19 | 20 | 21 | `; 22 | 23 | run(); 24 | 25 | async function run() { 26 | const moddleContext = await getModdleContext(source); 27 | const options = { 28 | Logger, 29 | services: { 30 | myService(arg, next) { 31 | next(); 32 | }, 33 | }, 34 | }; 35 | const context = new Context(serialize(moddleContext, typeResolver)); 36 | 37 | const definition = new Definition(context, options); 38 | definition.run(); 39 | } 40 | 41 | function getModdleContext(sourceXml) { 42 | const bpmnModdle = new BpmnModdle(); 43 | return bpmnModdle.fromXML(sourceXml.trim()); 44 | } 45 | 46 | function Logger(scope) { 47 | return { 48 | debug: console.debug.bind(console, 'bpmn-elements:' + scope), 49 | error: console.error.bind(console, 'bpmn-elements:' + scope), 50 | warn: console.warn.bind(console, 'bpmn-elements:' + scope), 51 | }; 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/ExecutionScope.md: -------------------------------------------------------------------------------- 1 | # ExecutionScope 2 | 3 | When calling services and scripts the following scope is provided. 4 | 5 | Properties: 6 | 7 | - `id`: calling element id 8 | - `type`: calling element type 9 | - `fields`: execution message fields 10 | - `content`: execution message content 11 | - `properties`: execution message properties 12 | - `environment`: [environment](/docs/Environment.md) 13 | - `logger`: calling element [logger](/docs/Environment.md#logger) instance 14 | - `ActivityError`: reference to error class 15 | - `BpmnError`: reference to error class 16 | 17 | ## `resolveExpression(expression)` 18 | 19 | Passed to environment resolve expression function. 20 | 21 | ## `ActivityError(message, sourceMessage[, inner])` 22 | 23 | Reference to activity error. 24 | 25 | ## `BpmnError(message[, behaviour, sourceMessage, inner])` 26 | 27 | Reference to Bpmn error. 28 | -------------------------------------------------------------------------------- /docs/Expression.md: -------------------------------------------------------------------------------- 1 | # Expressions 2 | 3 | Expressions handler interface. 4 | 5 | - `Expressions` 6 | - `resolveExpression(expression[, context, fnContext])`: resolve expression 7 | - `isExpression(testString)`: optional function to evaluate if string is an expression 8 | - `hasExpression(testString)`: optional function to evaluate if the string contains an expression 9 | 10 | # Standard 11 | 12 | ## `resolveExpression(expression[, context, fnContext])` 13 | 14 | Resolve expression. 15 | 16 | Arguments: 17 | 18 | - `expression`: expresion templated string 19 | - `context`: optional context from where to resolve expressions 20 | - `fnContext`: optional call context (this) 21 | 22 | ## `isExpression(testString)` 23 | 24 | Evaluate if a string is an expression, and only an expression, e.g. `${environment.variables.supersecret}`. 25 | 26 | ## `hasExpression(testString)` 27 | 28 | Evaluate if a string contains an expression, e.g. `${environment.variables.username}:${environment.variables.supersecret}`. 29 | 30 | ## Default expression handling 31 | 32 | Default expressions come in the form of `${.}`. 33 | 34 | The following expressions are supported: 35 | 36 | - `${environment.variables.input}` - resolves to the variable input 37 | - `${environment.variables.input[0]}` - resolves to first item of the variable input array 38 | - `${environment.variables.input[-1]}` - resolves to last item of the variable input array 39 | - `${environment.variables.input[spaced name]}` - resolves to the variable input object property `spaced name` 40 | 41 | - `${environment.services.getInput}` - return the service function `getInput` 42 | - `${environment.services.getInput()}` - executes the service function `getInput` with the argument `{services, variables}` 43 | - `${environment.services.isBelow(content.input,2)}` - executes the service function `isBelow` with result of `variable.input` value and 2 44 | 45 | - `I, ${content.id}, execute with id ${content.executionId}` - formats a string addressing content object values 46 | 47 | and, as utility: 48 | 49 | - `${true}` - return Boolean value `true` 50 | - `${false}` - return Boolean value `false` 51 | 52 | > Expressions in expressions is **not** supported and has unforeseeable outcome! 53 | 54 | # Community 55 | 56 | ## [`expression-parser`](/aircall/aircall-expression-parser) 57 | 58 | A far more advanced expression parser by [Aircall](/aircall). 59 | 60 | `npm i @aircall/expression-parser` 61 | -------------------------------------------------------------------------------- /docs/LoopCharacteristics.md: -------------------------------------------------------------------------------- 1 | # LoopCharacteristics 2 | 3 | Task loops can made based on conditions, cardinality, and/or a collection. 4 | 5 | ## `bpmn:multiInstanceLoopCharacteristics` 6 | 7 | Multi instance loop. 8 | 9 | ### Sequential 10 | 11 | Sequential loops is the default, or `isSequential="true"` as the scheme states. 12 | 13 | ### Parallel 14 | 15 | Parallel loops, or `isSequential="false"` as the scheme states, requires either collection or cardinality. 16 | 17 | ## `bpmn:standardLoopCharacteristics` 18 | 19 | Behaves as a sequential multi instance loop. 20 | 21 | Cardinality is defined as an XML-attribute: `loopMaximum="4"`. An expression can be used as well. 22 | 23 | ## Cardinality loop 24 | 25 | Loop a fixed number of times or until number of iterations match cardinality. The cardinality body an integer or an [expression](/docs/Expression.md). 26 | 27 | ```xml 28 | ${environment.variables.maxCardinality} 29 | ``` 30 | 31 | or as activity behaviour 32 | 33 | ```json 34 | { 35 | "id": "task1", 36 | "type": "bpmn:UserTask", 37 | "behaviour": { 38 | "loopCharacteristics": { 39 | "loopCardinality": "${environment.variables.maxCardinality}" 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | ## Conditional loop 46 | 47 | Loop until condition is met. The condition body can be a script or an [expression](/docs/Expression.md). 48 | 49 | ```xml 50 | ${environment.services.condition(content.index)} 51 | ``` 52 | 53 | or as activity behaviour 54 | 55 | ```json 56 | { 57 | "id": "task1", 58 | "type": "bpmn:UserTask", 59 | "behaviour": { 60 | "loopCharacteristics": { 61 | "completionCondition": "${environment.services.condition(content.index)}" 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | ## Collection loop 68 | 69 | Loop all items in a list. The `collection` and `elementVariable` attributes are schema extensions. They are picked up and resolved when executing the task. 70 | 71 | ```xml 72 | 73 | ``` 74 | 75 | or as activity behaviour 76 | 77 | ```json 78 | { 79 | "id": "task1", 80 | "type": "bpmn:UserTask", 81 | "behaviour": { 82 | "loopCharacteristics": { 83 | "collection": "${environment.variables.list}", 84 | "elementVariable": "listItem" 85 | } 86 | } 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/ParallelGateway.md: -------------------------------------------------------------------------------- 1 | # ParallelGateway 2 | 3 | Join or fork gateway. 4 | 5 | ## Join Edge case 6 | 7 | There is an edge case where the behaviour of a joining parallel gateway is not clear to yours truly. Suggestions on how to tackle this are appreciated. 8 | 9 | ### Case 10 | 11 | If inbound sequence flows are touched more than once before the last join inbound flow have completed: what is the expected joining gateway behaviour? 12 | 13 | ### Proposed behaviours 14 | 15 | 1. Ignore inbound flows that are touched more than once before all have been touched 16 | 2. Collect all inbound flow actions and complete join as soon as the last inbound flow is touched 17 | 18 | This project do the latter. 19 | 20 | Both behaviour comes with the same caveat. 21 | 22 | ![Edge Case](https://raw.github.com/paed01/bpmn-elements/master/docs/parallel-join-edgecase.png) 23 | 24 | The success of the above example is depending on how the sequence flows are ordered in the source (XML). If the flow `default` comes before `take` or `discard` the process will stall. The default flow will publish a discard initializing the join gateway. As soon as either `take` or `discard` is touched the join gateway will consider the join fulfilled and continue. Thus, the second `task` outbound flow will initiate a new join. But in vain since no more flow actions will come from `decision` gateway. The process has stalled. 25 | 26 | But, if the `default` is placed at after `take` or `discard` in the source, both of them will manage to touch the `toJoin` flow before the `default` flow is touched. 27 | -------------------------------------------------------------------------------- /docs/Scripts.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | Inline scripts handler interface. 4 | 5 | - `Scripts` 6 | - `register(activity)`: register script 7 | - `getScript(scriptType, activity)`: get registered script 8 | - `execute(executionContext, callback)` 9 | 10 | ## `register(activity)` 11 | 12 | Register script. Called from activity behaviour. 13 | 14 | Arguments: 15 | 16 | - `activity`: [activity](/docs/Activity.md) instance 17 | 18 | ## `getScript(scriptType, activity)` 19 | 20 | Get registered script. Called from activity behaviour when executing. 21 | 22 | Arguments: 23 | 24 | - `scriptType`: script type from definition 25 | - `activity`: [activity](/docs/Activity.md) with script 26 | 27 | Must return interface with one required function that is named `execute`. 28 | 29 | The execute function will receive an [execution context](/docs/ExecutionScope.md) and a callback that should be called when the execution is completed. 30 | 31 | ## Example implementation for nodejs 32 | 33 | ```js 34 | import { Script } from 'vm'; 35 | 36 | export function Scripts() { 37 | const scripts = {}; 38 | 39 | return { 40 | getScript, 41 | register, 42 | }; 43 | 44 | function register({ id, type, behaviour, environment }) { 45 | let scriptBody, language; 46 | 47 | switch (type) { 48 | case 'bpmn:SequenceFlow': { 49 | if (!behaviour.conditionExpression) return; 50 | language = behaviour.conditionExpression.language; 51 | if (!language) return; 52 | scriptBody = behaviour.conditionExpression.body; 53 | break; 54 | } 55 | default: { 56 | language = behaviour.scriptFormat; 57 | scriptBody = behaviour.script; 58 | } 59 | } 60 | 61 | if (!/^javascript$/i.test(language)) return; 62 | 63 | const script = new JavaScript(language, `${type}/${id}`, scriptBody, environment); 64 | scripts[id] = script; 65 | 66 | return script; 67 | } 68 | 69 | function getScript(language, { id }) { 70 | return scripts[id]; 71 | } 72 | } 73 | 74 | function JavaScript(language, filename, scriptBody, environment) { 75 | this.id = filename; 76 | this.script = new Script(scriptBody, { filename }); 77 | this.language = language; 78 | this.environment = environment; 79 | } 80 | 81 | JavaScript.prototype.execute = function execute(executionContext, callback) { 82 | const timers = this.environment.timers.register(executionContext); 83 | return this.script.runInNewContext({ ...executionContext, ...timers, next: callback }); 84 | }; 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/SequenceFlow.md: -------------------------------------------------------------------------------- 1 | # SequenceFlow 2 | 3 | Sequence flow behaviour. 4 | 5 | # Conditional flows 6 | 7 | All outbound sequence flows can have conditions. Flows are evaluated in sequence. Default flow will be taken if no other flow was taken. 8 | 9 | Example source: 10 | 11 | ```xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | next(null, this.environment.variables.take4); 20 | 21 | 22 | ${environment.variables.take5} 23 | 24 | 25 | 26 | 27 | 28 | 29 | `; 30 | ``` 31 | 32 | Sequence flows: 33 | 34 | - `to-task2`: default flow. If no other flow was taken then default flow is taken 35 | - `to-task3`: unconditional. Flow is taken 36 | - `to-task4`: script condition. Callback (next) is called with environment variable as result. If result is truthy the flow is taken, otherwise discarded 37 | - `to-task5`: expression condition. Expression will be evaluated and passed as result. If result is truthy the flow is taken, otherwise discarded 38 | -------------------------------------------------------------------------------- /docs/ServiceTask.md: -------------------------------------------------------------------------------- 1 | # ServiceTask 2 | 3 | Service task behaviour. 4 | 5 | To define service task service function you can use an expression in the implementation attribute. The value of the implementation attribute will be picked up by the service task and resolved as an [expression](/docs/Expression.md). 6 | 7 | Example source: 8 | 9 | ```xml 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | `; 18 | ``` 19 | 20 | Define your [environment](/docs/Environment.md) with the service functions. 21 | 22 | ```js 23 | new Environment({ 24 | services: { 25 | get(executionContext, callback) { 26 | callback(); 27 | }, 28 | getService(messageContent) { 29 | return function myService(executionContext, callback) { 30 | callback(); 31 | }; 32 | }, 33 | }, 34 | }); 35 | ``` 36 | 37 | The expressions will be resolved when the service task executes. 38 | 39 | The service function is called with an [execution context](/docs/ExecutionScope.md) and a callback. 40 | -------------------------------------------------------------------------------- /docs/SharedApi.md: -------------------------------------------------------------------------------- 1 | # Shared Api 2 | 3 | Activity, Process, and Definition elements share the same api interface. The element must not necessarely implement listeners for all the api calls. 4 | 5 | The Api is composed from the element event message. 6 | 7 | Api properties: 8 | 9 | - `id`: element id 10 | - `type`: element type 11 | - `name`: element name 12 | - `executionId`: current execution id 13 | - `environment`: shared [environment](/docs/Environment.md) 14 | - `fields`: message fields 15 | - `routingKey`: message routing key 16 | - `content`: message content 17 | - `id`: element id 18 | - `type`: element type 19 | - `executionId`: element execution id 20 | - `parent`: element parent 21 | - `id`: element parent id 22 | - `type`: element parent type 23 | - `executionId`: element parent unique execution id 24 | - `path`: list of parent parents 25 | - `messageProperties`: message properties, 26 | - `messageId`: message id 27 | - `owner`: api owner, i.e. the owning element instance 28 | - `broker`: element [broker](https://github.com/paed01/smqp) 29 | 30 | ### `cancel([message, options])` 31 | 32 | Cancel run. Publishes cancel message via element broker on element broker `api` exchange. 33 | 34 | Arguments: 35 | 36 | - `message`: optional object sent as message 37 | - `options`: optional object with broker message options 38 | - `delegate`: optional boolean to delegate the cancel to all interested parties 39 | 40 | ### `discard()` 41 | 42 | Discard run. Publishes discard message on element broker `api` exchange. 43 | 44 | ### `signal(message[, options])` 45 | 46 | Signal activity. Publishes signal message on element broker `api` exchange. 47 | 48 | Arguments: 49 | 50 | - `message`: signal message 51 | - `options`: optional object with broker message options 52 | - `delegate`: optional boolean to delegate the signal to all interested parties 53 | 54 | ### `fail(error)` 55 | 56 | Fail activity with error. The purpose is to fail user-/signal tasks waiting for user input. The behaviour differs between different type of activities. 57 | 58 | ### `stop()` 59 | 60 | Stop element run. Publishes stop message on element broker `api` exchange. 61 | 62 | ### `resolveExpression(expression)` 63 | 64 | Resolve expression. 65 | 66 | ### `createMessage([overrideContent])` 67 | 68 | Utility function to create new message content from the api message. 69 | 70 | ### `sendApiMessage(action[, content, options])` 71 | 72 | Utility function to publish message with element broker. 73 | 74 | Arguments: 75 | 76 | - `action`: message action, will be prefixed with the element type, e.g. `signal` will be sent as `activity.signal` if used to signal activity 77 | - `content`: optional message content 78 | - `options`: optional object with broker message options 79 | -------------------------------------------------------------------------------- /docs/SignalTask.md: -------------------------------------------------------------------------------- 1 | # SignalTask 2 | 3 | Signal-/User-/Manual task behaviour. 4 | 5 | ```javascript 6 | import * as elements from 'bpmn-elements'; 7 | 8 | import BpmnModdle from 'bpmn-moddle'; 9 | 10 | import { default as serialize, TypeResolver } from 'moddle-context-serializer'; 11 | 12 | const { Context, Definition } = elements; 13 | const typeResolver = TypeResolver(elements); 14 | 15 | const source = ` 16 | 17 | 18 | 19 | 20 | 21 | `; 22 | 23 | (async () => { 24 | const def = await run(); 25 | const [userTask] = def.getPostponed(); 26 | 27 | userTask.fail(new Error('Custom errror')); 28 | })(); 29 | 30 | async function run() { 31 | const moddleContext = await getModdleContext(source); 32 | const options = { 33 | Logger, 34 | }; 35 | const context = new Context(serialize(moddleContext, typeResolver)); 36 | 37 | const definition = new Definition(context, options); 38 | definition.run(); 39 | return definition; 40 | } 41 | 42 | function getModdleContext(sourceXml) { 43 | const bpmnModdle = new BpmnModdle(); 44 | return bpmnModdle.fromXML(sourceXml.trim()); 45 | } 46 | 47 | function Logger(scope) { 48 | return { 49 | debug: console.debug.bind(console, 'bpmn-elements:' + scope), 50 | error: console.error.bind(console, 'bpmn-elements:' + scope), 51 | warn: console.warn.bind(console, 'bpmn-elements:' + scope), 52 | }; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/StartEvent.md: -------------------------------------------------------------------------------- 1 | # StartEvent 2 | 3 | Start event behaviour. 4 | 5 | # Form 6 | 7 | If a form property is available when start event is executed, the event will wait until signaled. But! event definitions have precedence. 8 | 9 | ```javascript 10 | import * as elements from 'bpmn-elements'; 11 | import BpmnModdle from 'bpmn-moddle'; 12 | 13 | import { default as serialize, TypeResolver } from 'moddle-context-serializer'; 14 | 15 | const { Context, Definition } = elements; 16 | const typeResolver = TypeResolver(elements); 17 | 18 | const source = ` 19 | 20 | 21 | 22 | 23 | 24 | `; 25 | 26 | const moddleOptions = { 27 | js: { 28 | name: 'Node bpmn-engine', 29 | uri: 'http://paed01.github.io/bpmn-engine/schema/2020/08/bpmn', 30 | prefix: 'js', 31 | xml: { 32 | tagAlias: 'lowerCase', 33 | }, 34 | types: [ 35 | { 36 | name: 'FormSupported', 37 | isAbstract: true, 38 | extends: ['bpmn:StartEvent', 'bpmn:UserTask'], 39 | properties: [ 40 | { 41 | name: 'formKey', 42 | type: 'String', 43 | isAttr: true, 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | }; 50 | 51 | async function run() { 52 | const moddleContext = await getModdleContext(source); 53 | 54 | const context = new Context(serialize(moddleContext, typeResolver)); 55 | const definition = new Definition(context, { 56 | Logger, 57 | variables: { 58 | remoteFormUrl: 'https://exmple.com', 59 | }, 60 | extensions: { 61 | addFormExtension, 62 | }, 63 | }); 64 | 65 | definition.once('activity.wait', (api) => { 66 | api.owner.logger.debug(api.id, 'waiting for form', api.content.form); 67 | }); 68 | 69 | definition.run(); 70 | 71 | function addFormExtension(activity) { 72 | const { formKey } = activity.behaviour; 73 | if (!formKey) return; 74 | 75 | const { broker } = activity; 76 | const form = formKey === 'whatsYourName' ? { givenName: { type: 'string' } } : { age: { type: 'int' } }; 77 | 78 | broker.subscribeTmp( 79 | 'event', 80 | 'activity.enter', 81 | () => { 82 | broker.publish('format', 'run.input', { form }); 83 | }, 84 | { noAck: true } 85 | ); 86 | } 87 | } 88 | 89 | function getModdleContext(sourceXml) { 90 | const bpmnModdle = new BpmnModdle(moddleOptions); 91 | return bpmnModdle.fromXML(sourceXml.trim()); 92 | } 93 | 94 | function Logger(scope) { 95 | return { 96 | debug: console.debug.bind(console, 'bpmn-elements:' + scope), 97 | error: console.error.bind(console, 'bpmn-elements:' + scope), 98 | warn: console.warn.bind(console, 'bpmn-elements:' + scope), 99 | }; 100 | } 101 | 102 | run(); 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/TimerEventDefinition.md: -------------------------------------------------------------------------------- 1 | # TimerEventDefinition 2 | 3 | TimerEventDefinition behaviour. 4 | 5 | ## TimerEventDefinition events 6 | 7 | The timer event definition publish a number of events. 8 | 9 | ### `activity.timer` 10 | 11 | Fired when the timer is started. 12 | 13 | Object with properties. A subset: 14 | 15 | - `content:` object with activity and timer information 16 | - [`timeDuration`](#timeduration): the resolved time duration if any 17 | - [`timeDate`](#timedate): the resolved expire date if any 18 | - [`timeCycle`](#timecycle): the resolved time cycle if any 19 | - `startedAt`: timer started at date 20 | - `expireAt`: timer expires at date 21 | - `repeat`: repeated timer number of repetitions, -1 means unbounded number of repetitions. NB! Only viable when used in non-interrupting BoundaryEvent 22 | 23 | ### `activity.timeout` 24 | 25 | Fired when the timer has timed out or was cancelled. 26 | 27 | Object with `activity.timer` properties and some: 28 | 29 | - `content:` object with activity and timer information 30 | - `stoppedAt`: stopped at date 31 | - `runningTime`: running for milliseconds 32 | 33 | ## `timeDuration` 34 | 35 | Default support for ISO8601 duration. Will set a timer (`setTimeout`) for the duration and then complete when timed out. Invalid ISI8601 duration will throw and stop the execution. 36 | 37 | Uses [`@0dep/piso`](https://www.npmjs.com/package/@0dep/piso) to parse duration and repetitions. Consequently [ISO8601 intervals](https://en.wikipedia.org/wiki/ISO_8601) are supported. 38 | 39 | ## `timeDate` 40 | 41 | Behaves the same as `timeDuration`. Due date will timeout immediately. An invalid date, like `2023-02-29`, will throw and stop the execution. 42 | 43 | Uses [`@0dep/piso`](https://www.npmjs.com/package/@0dep/piso) to parse date according to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601). 44 | 45 | ## `timeCycle` 46 | 47 | Time cycles are parsed with [`@0dep/piso`](https://www.npmjs.com/package/@0dep/piso) that also handles ISO8601 intervals. 48 | 49 | If another format is used, e.g. cron, you need to handle that by [extending the behavior](#set-your-own-timeout). There are several modules to handle time cycles and this project tries to keep the number of dependencies to a minimum. 50 | 51 | ## Combined `timeDuration` and `timeDate` 52 | 53 | The shortest timeout will be picked to start the timer. 54 | 55 | ## Set your own timeout 56 | 57 | If the parent event start message has an `expireAt` date or `timeout` positive integer property a timer will be started. 58 | 59 | See how to format these messages [here](/docs/Extension.md). 60 | 61 | Another alternative is to override the [parse function](#timereventdefinitionparsetimertype-value). 62 | 63 | ## Api 64 | 65 | Timer event definition api. 66 | 67 | ### `TimerEventDefinition.parse(timerType, value)` 68 | 69 | Parse timer value into expire date. 70 | 71 | Arguments: 72 | 73 | - `timerType`: timer type string, one of `timeDuration`, `timeCycle`, or `timeDate` 74 | - `value`: resolved expression timer string 75 | 76 | Returns object: 77 | 78 | - `expireAt`: expires at date 79 | - `delay`: delay in milliseconds 80 | - `repeat`: repeat number of times 81 | -------------------------------------------------------------------------------- /docs/Timers.md: -------------------------------------------------------------------------------- 1 | # Timers 2 | 3 | Timers handler. The purpose is to keep track of executing timers. Can be added to inline script context to override builtin timers. 4 | 5 | # `Timers(options)` 6 | 7 | Default timers behavior. 8 | 9 | Arguments: 10 | 11 | - `options`: optional object 12 | - `setTimeout`: optional function, defaults to builtin `setTimeout` 13 | - `clearTimeout`: optional function, defaults to builtin `clearTimeout` 14 | 15 | Returns: 16 | 17 | - `executing`: list with executing timers 18 | - `register(owner)`: register timers owner 19 | - `setTimeout`: wrapped options `setTimeout` 20 | - `clearTimeout`: wrapped options `clearTimeout` 21 | 22 | ## `register(owner)` 23 | 24 | Register timers with owner. Called from TimerEventDefinition. 25 | 26 | Arguments: 27 | 28 | - `owner`: owning object, usually the activity in question 29 | 30 | Returns: 31 | 32 | - `setTimeout`: calls `setTimeout` 33 | - `clearTimeout`: clear timeout function 34 | 35 | ## `setTimeout(callback, delay, ...args)` 36 | 37 | Adds timer to list of executing timers, calls options `setTimeout`, and returns timer. 38 | 39 | Returns timer: 40 | 41 | - `timerId`: unique id 42 | - `owner`: registered owner if any, defaults to timers instance 43 | - `timerRef`: return value of builtin or overridden `setTimeout` 44 | 45 | ## `clearTimeout(ref)` 46 | 47 | Removes timer from list of executing timers and calls options `clearTimeout` with `ref.timerRef`. 48 | -------------------------------------------------------------------------------- /docs/activity-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-elements/3fd23492027fea9720b260f29208a77ce7139580/docs/activity-execution.png -------------------------------------------------------------------------------- /docs/activity-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-elements/3fd23492027fea9720b260f29208a77ce7139580/docs/activity-lifecycle.png -------------------------------------------------------------------------------- /docs/parallel-join-edgecase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paed01/bpmn-elements/3fd23492027fea9720b260f29208a77ce7139580/docs/parallel-join-edgecase.png -------------------------------------------------------------------------------- /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-vars': 2, 46 | 'no-unused-expressions': 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 | globals: { 72 | ...globals['shared-node-browser'], 73 | ...globals.es6, 74 | }, 75 | }, 76 | rules, 77 | }, 78 | { 79 | files: ['test/**/*.js'], 80 | languageOptions: { 81 | globals: { 82 | ...globals.node, 83 | ...globals.mocha, 84 | expect: 'readonly', 85 | beforeEachScenario: 'readonly', 86 | Buffer: 'readonly', 87 | Feature: 'readonly', 88 | Scenario: 'readonly', 89 | Given: 'readonly', 90 | When: 'readonly', 91 | Then: 'readonly', 92 | And: 'readonly', 93 | But: 'readonly', 94 | }, 95 | }, 96 | rules: { 97 | 'no-unused-expressions': 0, 98 | 'no-var': 1, 99 | }, 100 | }, 101 | { 102 | ignores: ['coverage/**/*', 'node_modules/**/*', 'tmp/*', 'dist/*'], 103 | }, 104 | ]; 105 | -------------------------------------------------------------------------------- /eventDefinitions.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /events.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /flows.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /gateways.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /src/Api.js: -------------------------------------------------------------------------------- 1 | import { cloneMessage } from './messageHelper.js'; 2 | import { getUniqueId } from './shared.js'; 3 | 4 | export function ActivityApi(broker, apiMessage, environment) { 5 | return new Api('activity', broker, apiMessage, environment); 6 | } 7 | 8 | export function DefinitionApi(broker, apiMessage, environment) { 9 | return new Api('definition', broker, apiMessage, environment); 10 | } 11 | 12 | export function ProcessApi(broker, apiMessage, environment) { 13 | return new Api('process', broker, apiMessage, environment); 14 | } 15 | 16 | export function FlowApi(broker, apiMessage, environment) { 17 | return new Api('flow', broker, apiMessage, environment); 18 | } 19 | 20 | export function Api(pfx, broker, sourceMessage, environment) { 21 | if (!sourceMessage) throw new Error('Api requires message'); 22 | 23 | const apiMessage = cloneMessage(sourceMessage); 24 | 25 | const { id, type, name, executionId } = apiMessage.content; 26 | this.id = id; 27 | this.type = type; 28 | this.name = name; 29 | this.executionId = executionId; 30 | this.environment = environment || broker.owner.environment; 31 | this.content = apiMessage.content; 32 | this.fields = apiMessage.fields; 33 | this.messageProperties = apiMessage.properties; 34 | this.broker = broker; 35 | this.owner = broker.owner; 36 | this.messagePrefix = pfx; 37 | } 38 | 39 | Api.prototype.cancel = function cancel(message, options) { 40 | this.sendApiMessage('cancel', { message }, options); 41 | }; 42 | 43 | Api.prototype.discard = function discard() { 44 | this.sendApiMessage('discard'); 45 | }; 46 | 47 | Api.prototype.fail = function fail(error) { 48 | this.sendApiMessage('error', { error }); 49 | }; 50 | 51 | Api.prototype.signal = function signal(message, options) { 52 | this.sendApiMessage('signal', { message }, options); 53 | }; 54 | 55 | Api.prototype.stop = function stop() { 56 | this.sendApiMessage('stop'); 57 | }; 58 | 59 | Api.prototype.resolveExpression = function resolveExpression(expression) { 60 | return this.environment.resolveExpression( 61 | expression, 62 | { 63 | fields: this.fields, 64 | content: this.content, 65 | properties: this.messageProperties, 66 | }, 67 | this.owner 68 | ); 69 | }; 70 | 71 | Api.prototype.sendApiMessage = function sendApiMessage(action, content, options) { 72 | const correlationId = options?.correlationId || getUniqueId(`${this.id || this.messagePrefix}_signal`); 73 | let key = `${this.messagePrefix}.${action}`; 74 | if (this.executionId) key += `.${this.executionId}`; 75 | this.broker.publish('api', key, this.createMessage(content), { ...options, correlationId, type: action }); 76 | }; 77 | 78 | Api.prototype.getPostponed = function getPostponed(...args) { 79 | if (this.owner.getPostponed) return this.owner.getPostponed(...args); 80 | if (this.owner.isSubProcess && this.owner.execution) return this.owner.execution.getPostponed(...args); 81 | return []; 82 | }; 83 | 84 | Api.prototype.createMessage = function createMessage(content) { 85 | return { 86 | ...this.content, 87 | ...content, 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /src/Expressions.js: -------------------------------------------------------------------------------- 1 | import getPropertyValue from './getPropertyValue.js'; 2 | 3 | const isExpressionPattern = /^\${(.+?)}$/; 4 | const expressionPattern = /\${(.+?)}/; 5 | 6 | export default function Expressions() { 7 | return { 8 | resolveExpression, 9 | isExpression, 10 | hasExpression, 11 | }; 12 | } 13 | 14 | function resolveExpression(templatedString, context, expressionFnContext) { 15 | let result = templatedString; 16 | 17 | while (expressionPattern.test(result)) { 18 | const expressionMatch = result.match(expressionPattern); 19 | const innerProperty = expressionMatch[1]; 20 | 21 | if (innerProperty === 'true') { 22 | return true; 23 | } else if (innerProperty === 'false') { 24 | return false; 25 | } else if (innerProperty === 'null') { 26 | return null; 27 | } else { 28 | const n = Number(innerProperty); 29 | if (!isNaN(n)) return n; 30 | } 31 | 32 | const contextValue = getPropertyValue(context, innerProperty, expressionFnContext); 33 | 34 | if (expressionMatch.input === expressionMatch[0]) { 35 | return contextValue; 36 | } 37 | 38 | result = result.replace(expressionMatch[0], contextValue === undefined ? '' : contextValue); 39 | } 40 | return result; 41 | } 42 | 43 | function isExpression(text) { 44 | if (!text) return false; 45 | return isExpressionPattern.test(text); 46 | } 47 | 48 | function hasExpression(text) { 49 | if (!text) return false; 50 | return expressionPattern.test(text); 51 | } 52 | -------------------------------------------------------------------------------- /src/Scripts.js: -------------------------------------------------------------------------------- 1 | export function Scripts() {} 2 | 3 | Scripts.prototype.getScript = function getScript(/*scriptType, activity*/) {}; 4 | Scripts.prototype.register = function register(/*activity*/) {}; 5 | -------------------------------------------------------------------------------- /src/Timers.js: -------------------------------------------------------------------------------- 1 | const kExecuting = Symbol.for('executing'); 2 | const kTimerApi = Symbol.for('timers api'); 3 | 4 | const MAX_DELAY = 2147483647; 5 | 6 | export function Timers(options) { 7 | this.count = 0; 8 | this.options = { 9 | setTimeout, 10 | clearTimeout, 11 | ...options, 12 | }; 13 | this[kExecuting] = new Set(); 14 | this.setTimeout = this.setTimeout.bind(this); 15 | this.clearTimeout = this.clearTimeout.bind(this); 16 | } 17 | 18 | Object.defineProperty(Timers.prototype, 'executing', { 19 | get() { 20 | return [...this[kExecuting]]; 21 | }, 22 | }); 23 | 24 | Timers.prototype.register = function register(owner) { 25 | return new RegisteredTimers(this, owner); 26 | }; 27 | 28 | Timers.prototype.setTimeout = function wrappedSetTimeout(callback, delay, ...args) { 29 | return this._setTimeout(null, callback, delay, ...args); 30 | }; 31 | 32 | Timers.prototype.clearTimeout = function wrappedClearTimeout(ref) { 33 | if (this[kExecuting].delete(ref)) { 34 | ref.timerRef = this.options.clearTimeout(ref.timerRef); 35 | return; 36 | } 37 | return this.options.clearTimeout(ref); 38 | }; 39 | 40 | Timers.prototype._setTimeout = function setTimeout(owner, callback, delay, ...args) { 41 | const executing = this[kExecuting]; 42 | const ref = this._getReference(owner, callback, delay, args); 43 | executing.add(ref); 44 | if (delay < MAX_DELAY) { 45 | ref.timerRef = this.options.setTimeout(onTimeout, ref.delay, ...ref.args); 46 | } 47 | return ref; 48 | 49 | function onTimeout(...rargs) { 50 | executing.delete(ref); 51 | return callback(...rargs); 52 | } 53 | }; 54 | 55 | Timers.prototype._getReference = function getReference(owner, callback, delay, args) { 56 | return new Timer(owner, `timer_${this.count++}`, callback, delay, args); 57 | }; 58 | 59 | function RegisteredTimers(timersApi, owner) { 60 | this[kTimerApi] = timersApi; 61 | this.owner = owner; 62 | this.setTimeout = this.setTimeout.bind(this); 63 | this.clearTimeout = this.clearTimeout.bind(this); 64 | } 65 | 66 | RegisteredTimers.prototype.setTimeout = function registeredSetTimeout(callback, delay, ...args) { 67 | const timersApi = this[kTimerApi]; 68 | return timersApi._setTimeout(this.owner, callback, delay, ...args); 69 | }; 70 | 71 | RegisteredTimers.prototype.clearTimeout = function registeredClearTimeout(ref) { 72 | this[kTimerApi].clearTimeout(ref); 73 | }; 74 | 75 | function Timer(owner, timerId, callback, delay, args) { 76 | this.callback = callback; 77 | this.delay = delay; 78 | this.args = args; 79 | this.owner = owner; 80 | this.timerId = timerId; 81 | this.expireAt = new Date(Date.now() + delay); 82 | this.timerRef = null; 83 | } 84 | -------------------------------------------------------------------------------- /src/Tracker.js: -------------------------------------------------------------------------------- 1 | export function ActivityTracker(parentId) { 2 | this.id = parentId; 3 | this.status = { wait: new Set(), execute: new Set(), timer: new Set() }; 4 | } 5 | 6 | Object.defineProperty(ActivityTracker.prototype, 'activityStatus', { 7 | get() { 8 | const status = this.status; 9 | if (status.execute.size) return 'executing'; 10 | if (status.timer.size) return 'timer'; 11 | return status.wait.size ? 'wait' : 'idle'; 12 | }, 13 | }); 14 | 15 | ActivityTracker.prototype.track = function track(routingKey, message) { 16 | const content = message.content; 17 | if (content.isAssociation) return; 18 | if (content.isSequenceFlow) return; 19 | if (content.isSubProcess) return; 20 | const executionId = content.executionId; 21 | 22 | switch (routingKey) { 23 | case 'activity.enter': 24 | case 'activity.discard': 25 | case 'activity.start': 26 | case 'activity.execution.completed': 27 | case 'activity.execution.error': 28 | case 'activity.end': 29 | this._executing(executionId); 30 | break; 31 | case 'activity.execution.outbound.take': 32 | case 'activity.detach': 33 | case 'activity.call': 34 | case 'activity.wait': { 35 | if (content.isMultiInstance) this._waiting(content.parent.executionId); 36 | else this._waiting(executionId); 37 | break; 38 | } 39 | case 'activity.timer': 40 | this._timer(content.parent.executionId); 41 | break; 42 | case 'activity.leave': 43 | this._leave(executionId); 44 | break; 45 | } 46 | }; 47 | 48 | ActivityTracker.prototype._executing = function executing(id) { 49 | const { wait, execute } = this.status; 50 | wait.delete(id); 51 | execute.add(id); 52 | }; 53 | 54 | ActivityTracker.prototype._waiting = function waiting(id) { 55 | const { wait, execute } = this.status; 56 | execute.delete(id); 57 | wait.add(id); 58 | }; 59 | 60 | ActivityTracker.prototype._timer = function timerFn(id) { 61 | const { timer, execute } = this.status; 62 | execute.delete(id); 63 | timer.add(id); 64 | }; 65 | 66 | ActivityTracker.prototype._leave = function leave(id) { 67 | const { wait, execute, timer } = this.status; 68 | execute.delete(id); 69 | timer.delete(id); 70 | wait.delete(id); 71 | }; 72 | -------------------------------------------------------------------------------- /src/activity/Dummy.js: -------------------------------------------------------------------------------- 1 | import { cloneParent } from '../messageHelper.js'; 2 | 3 | export default function DummyActivity(activityDef) { 4 | const { id, type = 'dummy', name, parent, behaviour } = activityDef; 5 | return { 6 | id, 7 | type, 8 | name, 9 | behaviour: { ...behaviour }, 10 | parent: cloneParent(parent), 11 | placeholder: true, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/activity/Escalation.js: -------------------------------------------------------------------------------- 1 | export default function Escalation(signalDef, context) { 2 | const { id, type, name, parent: originalParent } = signalDef; 3 | const { environment } = context; 4 | const parent = { ...originalParent }; 5 | 6 | return { 7 | id, 8 | type, 9 | name, 10 | parent, 11 | resolve, 12 | }; 13 | 14 | function resolve(executionMessage) { 15 | return { 16 | id, 17 | type, 18 | messageType: 'escalation', 19 | name: name && environment.resolveExpression(name, executionMessage), 20 | parent: { ...parent }, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/activity/ExecutionScope.js: -------------------------------------------------------------------------------- 1 | import { cloneMessage } from '../messageHelper.js'; 2 | import { ActivityError, BpmnError } from '../error/Errors.js'; 3 | 4 | export default function ExecutionScope(activity, initMessage) { 5 | const { id, type, environment, logger } = activity; 6 | 7 | const { fields, content, properties } = cloneMessage(initMessage); 8 | 9 | const scope = { 10 | id, 11 | type, 12 | fields, 13 | content, 14 | properties, 15 | environment, 16 | logger, 17 | resolveExpression, 18 | ActivityError, 19 | BpmnError, 20 | }; 21 | 22 | return scope; 23 | 24 | function resolveExpression(expression) { 25 | return environment.resolveExpression(expression, scope); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/activity/Message.js: -------------------------------------------------------------------------------- 1 | export default function Message(messageDef, context) { 2 | const { id, type, name, parent: originalParent } = messageDef; 3 | const { environment } = context; 4 | const parent = { ...originalParent }; 5 | 6 | return { 7 | id, 8 | type, 9 | name, 10 | parent, 11 | resolve, 12 | }; 13 | 14 | function resolve(executionMessage) { 15 | return { 16 | id, 17 | type, 18 | messageType: 'message', 19 | ...(name && { name: environment.resolveExpression(name, executionMessage) }), 20 | parent: { ...parent }, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/activity/Signal.js: -------------------------------------------------------------------------------- 1 | export default function Signal(signalDef, context) { 2 | const { id, type = 'Signal', name, parent: originalParent } = signalDef; 3 | const { environment } = context; 4 | const parent = { ...originalParent }; 5 | 6 | return { 7 | id, 8 | type, 9 | name, 10 | parent, 11 | resolve, 12 | }; 13 | 14 | function resolve(executionMessage) { 15 | return { 16 | id, 17 | type, 18 | messageType: 'signal', 19 | ...(name && { name: environment.resolveExpression(name, executionMessage) }), 20 | parent: { ...parent }, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/condition.js: -------------------------------------------------------------------------------- 1 | import ExecutionScope from './activity/ExecutionScope.js'; 2 | 3 | /** 4 | * Script condition 5 | * @param {import('types').ElementBase} owner 6 | * @param {any} script 7 | * @param {string} language 8 | */ 9 | export function ScriptCondition(owner, script, language) { 10 | this.type = 'script'; 11 | this.language = language; 12 | this._owner = owner; 13 | this._script = script; 14 | } 15 | 16 | /** 17 | * Execute 18 | * @param {any} message 19 | * @param {CallableFunction} callback 20 | */ 21 | ScriptCondition.prototype.execute = function execute(message, callback) { 22 | const owner = this._owner; 23 | try { 24 | return this._script.execute(ExecutionScope(owner, message), callback); 25 | } catch (err) { 26 | if (!callback) throw err; 27 | owner.logger.error(`<${owner.id}>`, err); 28 | callback(err); 29 | } 30 | }; 31 | 32 | /** 33 | * Expression condition 34 | * @param {import('types').ElementBase} owner 35 | * @param {string} expression 36 | */ 37 | export function ExpressionCondition(owner, expression) { 38 | this.type = 'expression'; 39 | this.expression = expression; 40 | this._owner = owner; 41 | } 42 | 43 | /** 44 | * Execute 45 | * @param {any} message 46 | * @param {CallableFunction} callback 47 | */ 48 | ExpressionCondition.prototype.execute = function execute(message, callback) { 49 | const owner = this._owner; 50 | try { 51 | const result = owner.environment.resolveExpression(this.expression, message); 52 | if (callback) return callback(null, result); 53 | return result; 54 | } catch (err) { 55 | if (callback) return callback(err); 56 | throw err; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/error/BpmnError.js: -------------------------------------------------------------------------------- 1 | export default function BpmnErrorActivity(errorDef, context) { 2 | const { id, type, name = 'BpmnError', behaviour = {} } = errorDef; 3 | const { environment } = context; 4 | 5 | return { 6 | id, 7 | type, 8 | name, 9 | errorCode: behaviour.errorCode, 10 | resolve, 11 | }; 12 | 13 | function resolve(executionMessage, error) { 14 | const resolveCtx = { ...executionMessage, error }; 15 | const result = { 16 | id, 17 | type, 18 | messageType: 'throw', 19 | name: name && environment.resolveExpression(name, resolveCtx), 20 | code: behaviour.errorCode && environment.resolveExpression(behaviour.errorCode, resolveCtx), 21 | }; 22 | 23 | if (error) result.inner = error; 24 | return result; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/error/Errors.js: -------------------------------------------------------------------------------- 1 | import { cloneMessage } from '../messageHelper.js'; 2 | 3 | export class ActivityError extends Error { 4 | constructor(description, sourceMessage, inner) { 5 | super(description); 6 | this.type = 'ActivityError'; 7 | this.name = this.constructor.name; 8 | this.description = description; 9 | if (sourceMessage) this.source = cloneMessage(sourceMessage, sourceMessage.content?.error && { error: undefined }); 10 | if (inner) { 11 | this.inner = inner; 12 | if (inner.name) this.name = inner.name; 13 | if (inner.code) this.code = inner.code; 14 | } 15 | } 16 | } 17 | 18 | export class RunError extends ActivityError { 19 | constructor(...args) { 20 | super(...args); 21 | this.type = 'RunError'; 22 | } 23 | } 24 | 25 | export class BpmnError extends Error { 26 | constructor(description, behaviour, sourceMessage, inner) { 27 | super(description); 28 | this.type = 'BpmnError'; 29 | this.name = behaviour?.name ?? this.constructor.name; 30 | this.description = description; 31 | this.code = behaviour?.errorCode?.toString() ?? behaviour?.code; 32 | this.id = behaviour?.id; 33 | if (sourceMessage) this.source = cloneMessage(sourceMessage, sourceMessage.content?.error && { error: undefined }); 34 | if (inner) this.inner = inner; 35 | } 36 | } 37 | 38 | export function makeErrorFromMessage(errorMessage) { 39 | const { content } = errorMessage; 40 | 41 | if (isKnownError(content)) return content; 42 | 43 | const { error } = content; 44 | if (!error) return new Error(`Malformatted error message with routing key ${errorMessage.fields?.routingKey}`); 45 | 46 | if (isKnownError(error)) return error; 47 | 48 | switch (error.type) { 49 | case 'ActivityError': 50 | return new ActivityError( 51 | error.message || error.description, 52 | error.source, 53 | error.inner ? error.inner : { code: error.code, name: error.name } 54 | ); 55 | case 'RunError': 56 | return new RunError( 57 | error.message || error.description, 58 | error.source, 59 | error.inner ? error.inner : { code: error.code, name: error.name } 60 | ); 61 | case 'BpmnError': 62 | return new BpmnError(error.message || error.description, error, error.source); 63 | } 64 | 65 | return error; 66 | } 67 | 68 | function isKnownError(test) { 69 | if (test instanceof ActivityError) return test; 70 | if (test instanceof BpmnError) return test; 71 | if (test instanceof Error) return test; 72 | } 73 | -------------------------------------------------------------------------------- /src/eventDefinitions/TerminateEventDefinition.js: -------------------------------------------------------------------------------- 1 | import { cloneContent, shiftParent } from '../messageHelper.js'; 2 | 3 | export default function TerminateEventDefinition(activity, eventDefinition) { 4 | const { id, broker, environment } = activity; 5 | const { type = 'TerminateEventDefinition' } = eventDefinition; 6 | 7 | this.id = id; 8 | this.type = type; 9 | this.activity = activity; 10 | this.broker = broker; 11 | this.logger = environment.Logger(type.toLowerCase()); 12 | } 13 | 14 | TerminateEventDefinition.prototype.execute = function execute(executeMessage) { 15 | const executeContent = executeMessage.content; 16 | 17 | const throwContent = cloneContent(executeContent, { 18 | state: 'terminate', 19 | }); 20 | throwContent.parent = shiftParent(executeContent.parent); 21 | 22 | this.logger.debug(`<${executeContent.executionId} (${executeContent.id})> terminate`); 23 | const broker = this.broker; 24 | broker.publish('event', 'process.terminate', throwContent, { type: 'terminate' }); 25 | broker.publish('execution', 'execute.completed', cloneContent(executeContent)); 26 | }; 27 | -------------------------------------------------------------------------------- /src/eventDefinitions/index.js: -------------------------------------------------------------------------------- 1 | import CancelEventDefinition from './CancelEventDefinition.js'; 2 | import CompensateEventDefinition from './CompensateEventDefinition.js'; 3 | import ConditionalEventDefinition from './ConditionalEventDefinition.js'; 4 | import ErrorEventDefinition from './ErrorEventDefinition.js'; 5 | import EscalationEventDefinition from './EscalationEventDefinition.js'; 6 | import LinkEventDefinition from './LinkEventDefinition.js'; 7 | import MessageEventDefinition from './MessageEventDefinition.js'; 8 | import SignalEventDefinition from './SignalEventDefinition.js'; 9 | import TerminateEventDefinition from './TerminateEventDefinition.js'; 10 | import TimerEventDefinition from './TimerEventDefinition.js'; 11 | 12 | export { 13 | CancelEventDefinition, 14 | CompensateEventDefinition, 15 | ConditionalEventDefinition, 16 | ErrorEventDefinition, 17 | EscalationEventDefinition, 18 | LinkEventDefinition, 19 | MessageEventDefinition, 20 | SignalEventDefinition, 21 | TerminateEventDefinition, 22 | TimerEventDefinition, 23 | }; 24 | -------------------------------------------------------------------------------- /src/events/EndEvent.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import EventDefinitionExecution from '../eventDefinitions/EventDefinitionExecution.js'; 3 | import { cloneContent } from '../messageHelper.js'; 4 | 5 | const kExecution = Symbol.for('execution'); 6 | 7 | export default function EndEvent(activityDef, context) { 8 | return new Activity(EndEventBehaviour, { ...activityDef, isThrowing: true }, context); 9 | } 10 | 11 | export function EndEventBehaviour(activity) { 12 | this.id = activity.id; 13 | this.type = activity.type; 14 | this.broker = activity.broker; 15 | this[kExecution] = activity.eventDefinitions && new EventDefinitionExecution(activity, activity.eventDefinitions); 16 | } 17 | 18 | EndEventBehaviour.prototype.execute = function execute(executeMessage) { 19 | const execution = this[kExecution]; 20 | if (execution) { 21 | return execution.execute(executeMessage); 22 | } 23 | 24 | return this.broker.publish('execution', 'execute.completed', cloneContent(executeMessage.content)); 25 | }; 26 | -------------------------------------------------------------------------------- /src/events/IntermediateCatchEvent.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import EventDefinitionExecution from '../eventDefinitions/EventDefinitionExecution.js'; 3 | import { cloneContent } from '../messageHelper.js'; 4 | 5 | const kExecution = Symbol.for('execution'); 6 | 7 | export default function IntermediateCatchEvent(activityDef, context) { 8 | return new Activity(IntermediateCatchEventBehaviour, activityDef, context); 9 | } 10 | 11 | export function IntermediateCatchEventBehaviour(activity) { 12 | this.id = activity.id; 13 | this.type = activity.type; 14 | this.broker = activity.broker; 15 | this[kExecution] = activity.eventDefinitions && new EventDefinitionExecution(activity, activity.eventDefinitions); 16 | } 17 | 18 | IntermediateCatchEventBehaviour.prototype.execute = function execute(executeMessage) { 19 | const execution = this[kExecution]; 20 | if (execution) { 21 | return execution.execute(executeMessage); 22 | } 23 | 24 | const executeContent = executeMessage.content; 25 | const executionId = executeContent.executionId; 26 | const broker = this.broker; 27 | broker.subscribeTmp('api', `activity.#.${executionId}`, this._onApiMessage.bind(this, executeMessage), { 28 | noAck: true, 29 | consumerTag: '_api-behaviour-execution', 30 | }); 31 | 32 | return broker.publish('event', 'activity.wait', cloneContent(executeContent)); 33 | }; 34 | 35 | IntermediateCatchEventBehaviour.prototype._onApiMessage = function onApiMessage(executeMessage, routingKey, message) { 36 | switch (message.properties.type) { 37 | case 'message': 38 | case 'signal': { 39 | const broker = this.broker; 40 | broker.cancel('_api-behaviour-execution'); 41 | return broker.publish( 42 | 'execution', 43 | 'execute.completed', 44 | cloneContent(executeMessage.content, { 45 | output: message.content.message, 46 | }) 47 | ); 48 | } 49 | case 'discard': { 50 | const broker = this.broker; 51 | broker.cancel('_api-behaviour-execution'); 52 | return broker.publish('execution', 'execute.discard', cloneContent(executeMessage.content)); 53 | } 54 | case 'stop': { 55 | return this.broker.cancel('_api-behaviour-execution'); 56 | } 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/events/IntermediateThrowEvent.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import EventDefinitionExecution from '../eventDefinitions/EventDefinitionExecution.js'; 3 | import { cloneContent } from '../messageHelper.js'; 4 | 5 | const kExecution = Symbol.for('execution'); 6 | 7 | export default function IntermediateThrowEvent(activityDef, context) { 8 | return new Activity(IntermediateThrowEventBehaviour, { ...activityDef, isThrowing: true }, context); 9 | } 10 | 11 | export function IntermediateThrowEventBehaviour(activity) { 12 | this.id = activity.id; 13 | this.type = activity.type; 14 | this.broker = activity.broker; 15 | this[kExecution] = activity.eventDefinitions && new EventDefinitionExecution(activity, activity.eventDefinitions); 16 | } 17 | 18 | IntermediateThrowEventBehaviour.prototype.execute = function execute(executeMessage) { 19 | const execution = this[kExecution]; 20 | if (execution) { 21 | return execution.execute(executeMessage); 22 | } 23 | 24 | return this.broker.publish('execution', 'execute.completed', cloneContent(executeMessage.content)); 25 | }; 26 | -------------------------------------------------------------------------------- /src/events/index.js: -------------------------------------------------------------------------------- 1 | import BoundaryEvent, { BoundaryEventBehaviour } from './BoundaryEvent.js'; 2 | import EndEvent, { EndEventBehaviour } from './EndEvent.js'; 3 | import IntermediateCatchEvent, { IntermediateCatchEventBehaviour } from './IntermediateCatchEvent.js'; 4 | import IntermediateThrowEvent, { IntermediateThrowEventBehaviour } from './IntermediateThrowEvent.js'; 5 | import StartEvent, { StartEventBehaviour } from './StartEvent.js'; 6 | 7 | export { 8 | BoundaryEvent, 9 | BoundaryEventBehaviour, 10 | EndEvent, 11 | EndEventBehaviour, 12 | IntermediateCatchEvent, 13 | IntermediateCatchEventBehaviour, 14 | IntermediateThrowEvent, 15 | IntermediateThrowEventBehaviour, 16 | StartEvent, 17 | StartEventBehaviour, 18 | }; 19 | -------------------------------------------------------------------------------- /src/flows/Association.js: -------------------------------------------------------------------------------- 1 | import { cloneParent } from '../messageHelper.js'; 2 | import { EventBroker } from '../EventBroker.js'; 3 | import { Api } from '../Api.js'; 4 | import { getUniqueId } from '../shared.js'; 5 | 6 | const kCounters = Symbol.for('counters'); 7 | 8 | export default function Association(associationDef, { environment }) { 9 | const { id, type = 'association', name, parent, targetId, sourceId, behaviour = {} } = associationDef; 10 | 11 | this.id = id; 12 | this.type = type; 13 | this.name = name; 14 | this.parent = cloneParent(parent); 15 | this.behaviour = behaviour; 16 | this.sourceId = sourceId; 17 | this.targetId = targetId; 18 | this.isAssociation = true; 19 | this.environment = environment; 20 | const logger = (this.logger = environment.Logger(type.toLowerCase())); 21 | 22 | this[kCounters] = { 23 | take: 0, 24 | discard: 0, 25 | }; 26 | 27 | const { broker, on, once, waitFor } = new EventBroker(this, { prefix: 'association', durable: true, autoDelete: false }); 28 | this.broker = broker; 29 | this.on = on; 30 | this.once = once; 31 | this.waitFor = waitFor; 32 | 33 | logger.debug(`<${id}> init, <${sourceId}> -> <${targetId}>`); 34 | } 35 | 36 | Object.defineProperty(Association.prototype, 'counters', { 37 | get() { 38 | return { ...this[kCounters] }; 39 | }, 40 | }); 41 | 42 | Association.prototype.take = function take(content) { 43 | this.logger.debug(`<${this.id}> take target <${this.targetId}>`); 44 | ++this[kCounters].take; 45 | 46 | this._publishEvent('take', content); 47 | 48 | return true; 49 | }; 50 | 51 | Association.prototype.discard = function discard(content) { 52 | this.logger.debug(`<${this.id}> discard target <${this.targetId}>`); 53 | ++this[kCounters].discard; 54 | 55 | this._publishEvent('discard', content); 56 | 57 | return true; 58 | }; 59 | 60 | Association.prototype.getState = function getState() { 61 | const brokerState = this.broker.getState(true); 62 | if (!brokerState && this.environment.settings.disableTrackState) return; 63 | 64 | return { 65 | id: this.id, 66 | type: this.type, 67 | counters: this.counters, 68 | broker: brokerState, 69 | }; 70 | }; 71 | 72 | Association.prototype.recover = function recover(state) { 73 | Object.assign(this[kCounters], state.counters); 74 | this.broker.recover(state.broker); 75 | }; 76 | 77 | Association.prototype.getApi = function getApi(message) { 78 | return new Api('association', this.broker, message || { content: this._createMessageContent() }); 79 | }; 80 | 81 | Association.prototype.stop = function stop() { 82 | this.broker.stop(); 83 | }; 84 | 85 | Association.prototype._publishEvent = function publishEvent(action, content) { 86 | const eventContent = this._createMessageContent({ 87 | action, 88 | message: content, 89 | sequenceId: getUniqueId(this.id), 90 | }); 91 | 92 | this.broker.publish('event', `association.${action}`, eventContent, { type: action }); 93 | }; 94 | 95 | Association.prototype._createMessageContent = function createMessageContent(override) { 96 | return { 97 | ...override, 98 | id: this.id, 99 | type: this.type, 100 | name: this.name, 101 | sourceId: this.sourceId, 102 | targetId: this.targetId, 103 | isAssociation: true, 104 | parent: cloneParent(this.parent), 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /src/flows/index.js: -------------------------------------------------------------------------------- 1 | import Association from './Association.js'; 2 | import MessageFlow from './MessageFlow.js'; 3 | import SequenceFlow from './SequenceFlow.js'; 4 | 5 | export { Association, MessageFlow, SequenceFlow }; 6 | -------------------------------------------------------------------------------- /src/gateways/EventBasedGateway.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import { cloneContent } from '../messageHelper.js'; 3 | 4 | const kCompleted = Symbol.for('completed'); 5 | const kTargets = Symbol.for('targets'); 6 | 7 | export default function EventBasedGateway(activityDef, context) { 8 | return new Activity(EventBasedGatewayBehaviour, activityDef, context); 9 | } 10 | 11 | export function EventBasedGatewayBehaviour(activity, context) { 12 | this.id = activity.id; 13 | this.type = activity.type; 14 | this.activity = activity; 15 | this.broker = activity.broker; 16 | this.context = context; 17 | this[kTargets] = new Set(activity.outbound.map((flow) => context.getActivityById(flow.targetId))); 18 | } 19 | 20 | EventBasedGatewayBehaviour.prototype.execute = function execute(executeMessage) { 21 | const executeContent = executeMessage.content; 22 | const { executionId, outbound = [], outboundTaken } = executeContent; 23 | 24 | const targets = this[kTargets]; 25 | this[kCompleted] = false; 26 | if (!targets.size) return this._complete(executeContent); 27 | 28 | for (const flow of this.activity.outbound) { 29 | outbound.push({ id: flow.id, action: 'take' }); 30 | } 31 | 32 | if (!this[kCompleted] && outboundTaken) return; 33 | 34 | const targetConsumerTag = `_gateway-listener-${this.id}`; 35 | 36 | const onTargetCompleted = this._onTargetCompleted.bind(this, executeMessage); 37 | for (const target of this[kTargets]) { 38 | target.broker.subscribeOnce('event', 'activity.end', onTargetCompleted, { consumerTag: targetConsumerTag }); 39 | } 40 | 41 | const broker = this.activity.broker; 42 | broker.subscribeOnce('api', `activity.stop.${executionId}`, () => this._stop(), { 43 | consumerTag: '_api-stop-execution', 44 | }); 45 | 46 | this[kCompleted] = false; 47 | 48 | if (!executeMessage.fields.redelivered) { 49 | return broker.publish('execution', 'execute.outbound.take', cloneContent(executeContent, { outboundTaken: true })); 50 | } 51 | }; 52 | 53 | EventBasedGatewayBehaviour.prototype._onTargetCompleted = function onTargetCompleted(executeMessage, _, message, owner) { 54 | const { id: targetId, executionId: targetExecutionId } = message.content; 55 | const executeContent = executeMessage.content; 56 | const executionId = executeContent.executionId; 57 | this.activity.logger.debug(`<${executionId} (${this.id})> <${targetExecutionId}> completed run, discarding the rest`); 58 | 59 | this._stop(); 60 | for (const target of this[kTargets]) { 61 | if (target === owner) continue; 62 | target.discard(); 63 | } 64 | 65 | const completedContent = cloneContent(executeContent, { 66 | taken: { 67 | id: targetId, 68 | executionId: targetExecutionId, 69 | }, 70 | ignoreOutbound: true, 71 | }); 72 | 73 | this._complete(completedContent); 74 | }; 75 | 76 | EventBasedGatewayBehaviour.prototype._complete = function complete(completedContent) { 77 | this[kCompleted] = true; 78 | this.broker.publish('execution', 'execute.completed', cloneContent(completedContent)); 79 | }; 80 | 81 | EventBasedGatewayBehaviour.prototype._stop = function stop() { 82 | const targetConsumerTag = `_gateway-listener-${this.id}`; 83 | for (const target of this[kTargets]) target.broker.cancel(targetConsumerTag); 84 | this.broker.cancel('_api-stop-execution'); 85 | }; 86 | -------------------------------------------------------------------------------- /src/gateways/ExclusiveGateway.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import { cloneContent } from '../messageHelper.js'; 3 | 4 | export default function ExclusiveGateway(activityDef, context) { 5 | return new Activity(ExclusiveGatewayBehaviour, activityDef, context); 6 | } 7 | 8 | export function ExclusiveGatewayBehaviour(activity) { 9 | const { id, type, broker } = activity; 10 | this.id = id; 11 | this.type = type; 12 | this.broker = broker; 13 | } 14 | 15 | ExclusiveGatewayBehaviour.prototype.execute = function execute({ content }) { 16 | this.broker.publish('execution', 'execute.completed', cloneContent(content, { outboundTakeOne: true })); 17 | }; 18 | -------------------------------------------------------------------------------- /src/gateways/InclusiveGateway.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import { cloneContent } from '../messageHelper.js'; 3 | 4 | export default function InclusiveGateway(activityDef, context) { 5 | return new Activity(InclusiveGatewayBehaviour, activityDef, context); 6 | } 7 | 8 | export function InclusiveGatewayBehaviour(activity) { 9 | const { id, type, broker } = activity; 10 | this.id = id; 11 | this.type = type; 12 | this.broker = broker; 13 | } 14 | 15 | InclusiveGatewayBehaviour.prototype.execute = function execute({ content }) { 16 | this.broker.publish('execution', 'execute.completed', cloneContent(content)); 17 | }; 18 | -------------------------------------------------------------------------------- /src/gateways/ParallelGateway.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import { cloneContent } from '../messageHelper.js'; 3 | 4 | export default function ParallelGateway(activityDef, context) { 5 | return new Activity(ParallelGatewayBehaviour, { ...activityDef, isParallelGateway: true }, context); 6 | } 7 | 8 | export function ParallelGatewayBehaviour(activity) { 9 | const { id, type, broker } = activity; 10 | this.id = id; 11 | this.type = type; 12 | this.broker = broker; 13 | } 14 | 15 | ParallelGatewayBehaviour.prototype.execute = function execute({ content }) { 16 | this.broker.publish('execution', 'execute.completed', cloneContent(content)); 17 | }; 18 | -------------------------------------------------------------------------------- /src/gateways/index.js: -------------------------------------------------------------------------------- 1 | import EventBasedGateway, { EventBasedGatewayBehaviour } from './EventBasedGateway.js'; 2 | import ExclusiveGateway, { ExclusiveGatewayBehaviour } from './ExclusiveGateway.js'; 3 | import InclusiveGateway, { InclusiveGatewayBehaviour } from './InclusiveGateway.js'; 4 | import ParallelGateway, { ParallelGatewayBehaviour } from './ParallelGateway.js'; 5 | 6 | export { 7 | EventBasedGateway, 8 | EventBasedGatewayBehaviour, 9 | ExclusiveGateway, 10 | ExclusiveGatewayBehaviour, 11 | InclusiveGateway, 12 | InclusiveGatewayBehaviour, 13 | ParallelGateway, 14 | ParallelGatewayBehaviour, 15 | }; 16 | -------------------------------------------------------------------------------- /src/io/BpmnIO.js: -------------------------------------------------------------------------------- 1 | export default function BpmnIO(activity, context) { 2 | this.activity = activity; 3 | this.context = context; 4 | this.type = 'bpmnio'; 5 | 6 | const { ioSpecification: ioSpecificationDef, properties: propertiesDef } = activity.behaviour; 7 | 8 | this.specification = ioSpecificationDef && new ioSpecificationDef.Behaviour(activity, ioSpecificationDef, context); 9 | this.properties = propertiesDef && new propertiesDef.Behaviour(activity, propertiesDef, context); 10 | } 11 | 12 | Object.defineProperty(BpmnIO.prototype, 'hasIo', { 13 | get() { 14 | return this.specification || this.properties; 15 | }, 16 | }); 17 | 18 | BpmnIO.prototype.activate = function activate(message) { 19 | const properties = this.properties, 20 | specification = this.specification; 21 | if (properties) properties.activate(message); 22 | if (specification) specification.activate(message); 23 | }; 24 | 25 | BpmnIO.prototype.deactivate = function deactivate(message) { 26 | const properties = this.properties, 27 | specification = this.specification; 28 | if (properties) properties.deactivate(message); 29 | if (specification) specification.deactivate(message); 30 | }; 31 | -------------------------------------------------------------------------------- /src/io/EnvironmentDataObject.js: -------------------------------------------------------------------------------- 1 | export default function EnvironmentDataObject(dataObjectDef, { environment }) { 2 | const { id, type, name, behaviour, parent } = dataObjectDef; 3 | this.id = id; 4 | this.type = type; 5 | this.name = name; 6 | this.behaviour = behaviour; 7 | this.parent = parent; 8 | this.environment = environment; 9 | } 10 | 11 | EnvironmentDataObject.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 12 | const environment = this.environment; 13 | const value = environment.variables._data?.[this.id]; 14 | const content = this._createContent(value); 15 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 16 | }; 17 | 18 | EnvironmentDataObject.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 19 | const environment = this.environment; 20 | environment.variables._data = environment.variables._data || {}; 21 | environment.variables._data[this.id] = value; 22 | const content = this._createContent(value); 23 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 24 | }; 25 | 26 | EnvironmentDataObject.prototype._createContent = function createContent(value) { 27 | return { 28 | id: this.id, 29 | type: this.type, 30 | name: this.name, 31 | value, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/io/EnvironmentDataStore.js: -------------------------------------------------------------------------------- 1 | export default function EnvironmentDataStore(dataStoreDef, { environment }) { 2 | const { id, type, name, behaviour, parent } = dataStoreDef; 3 | this.id = id; 4 | this.type = type; 5 | this.name = name; 6 | this.behaviour = behaviour; 7 | this.parent = parent; 8 | this.environment = environment; 9 | } 10 | 11 | EnvironmentDataStore.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 12 | const environment = this.environment; 13 | const value = environment.variables._data?.[this.id]; 14 | const content = this._createContent(value); 15 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 16 | }; 17 | 18 | EnvironmentDataStore.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 19 | const environment = this.environment; 20 | environment.variables._data = environment.variables._data || {}; 21 | environment.variables._data[this.id] = value; 22 | const content = this._createContent(value); 23 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 24 | }; 25 | 26 | EnvironmentDataStore.prototype._createContent = function createContent(value) { 27 | return { 28 | id: this.id, 29 | type: this.type, 30 | name: this.name, 31 | value, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/io/EnvironmentDataStoreReference.js: -------------------------------------------------------------------------------- 1 | export default function EnvironmentDataStoreReference(dataObjectDef, { environment }) { 2 | const { id, type, name, behaviour, parent } = dataObjectDef; 3 | this.id = id; 4 | this.type = type; 5 | this.name = name; 6 | this.behaviour = behaviour; 7 | this.parent = parent; 8 | this.environment = environment; 9 | } 10 | 11 | EnvironmentDataStoreReference.prototype.read = function read(broker, exchange, routingKeyPrefix, messageProperties) { 12 | const environment = this.environment; 13 | const value = environment.variables._data?.[this.id]; 14 | const content = this._createContent(value); 15 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 16 | }; 17 | 18 | EnvironmentDataStoreReference.prototype.write = function write(broker, exchange, routingKeyPrefix, value, messageProperties) { 19 | const environment = this.environment; 20 | environment.variables._data = environment.variables._data || {}; 21 | environment.variables._data[this.id] = value; 22 | const content = this._createContent(value); 23 | return broker.publish(exchange, `${routingKeyPrefix}response`, content, messageProperties); 24 | }; 25 | 26 | EnvironmentDataStoreReference.prototype._createContent = function createContent(value) { 27 | return { 28 | id: this.id, 29 | type: this.type, 30 | name: this.name, 31 | value, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/messageHelper.js: -------------------------------------------------------------------------------- 1 | export function cloneContent(content, extend) { 2 | const { discardSequence, inbound, outbound, parent, sequence } = content; 3 | 4 | const clone = { 5 | ...content, 6 | ...extend, 7 | }; 8 | 9 | if (parent) { 10 | clone.parent = cloneParent(parent); 11 | } 12 | if (discardSequence) { 13 | clone.discardSequence = discardSequence.slice(); 14 | } 15 | if (inbound) { 16 | clone.inbound = inbound.map((c) => cloneContent(c)); 17 | } 18 | if (outbound) { 19 | clone.outbound = outbound.map((c) => cloneContent(c)); 20 | } 21 | if (Array.isArray(sequence)) { 22 | clone.sequence = sequence.map((c) => cloneContent(c)); 23 | } 24 | 25 | return clone; 26 | } 27 | 28 | export function cloneMessage(message, overrideContent) { 29 | return { 30 | fields: { ...message.fields }, 31 | content: cloneContent(message.content, overrideContent), 32 | properties: { ...message.properties }, 33 | }; 34 | } 35 | 36 | export function cloneParent(parent) { 37 | const { path } = parent; 38 | const clone = { ...parent }; 39 | if (!path) return clone; 40 | 41 | clone.path = path.map((p) => { 42 | return { ...p }; 43 | }); 44 | 45 | return clone; 46 | } 47 | 48 | export function unshiftParent(parent, adoptingParent) { 49 | const { id, type, executionId } = adoptingParent; 50 | if (!parent) { 51 | return { 52 | id, 53 | type, 54 | executionId, 55 | }; 56 | } 57 | 58 | const clone = cloneParent(parent); 59 | const { id: parentId, type: parentType, executionId: parentExecutionId } = parent; 60 | clone.id = id; 61 | clone.executionId = executionId; 62 | clone.type = type; 63 | 64 | const path = (clone.path = clone.path || []); 65 | path.unshift({ id: parentId, type: parentType, executionId: parentExecutionId }); 66 | 67 | return clone; 68 | } 69 | 70 | export function shiftParent(parent) { 71 | if (!parent) return; 72 | if (!parent.path || !parent.path.length) return; 73 | 74 | const clone = cloneParent(parent); 75 | const { id, executionId, type } = clone.path.shift(); 76 | clone.id = id; 77 | clone.executionId = executionId; 78 | clone.type = type; 79 | clone.path = clone.path.length ? clone.path : undefined; 80 | return clone; 81 | } 82 | 83 | export function pushParent(parent, ancestor) { 84 | const { id, type, executionId } = ancestor; 85 | if (!parent) return { id, type, executionId }; 86 | 87 | const clone = cloneParent(parent); 88 | if (clone.id === id) { 89 | if (executionId) clone.executionId = executionId; 90 | return clone; 91 | } 92 | const path = (clone.path = clone.path || []); 93 | 94 | for (const p of path) { 95 | if (p.id === id) { 96 | if (executionId) p.executionId = executionId; 97 | return clone; 98 | } 99 | } 100 | 101 | path.push({ id, type, executionId }); 102 | return clone; 103 | } 104 | -------------------------------------------------------------------------------- /src/process/Lane.js: -------------------------------------------------------------------------------- 1 | const kProcess = Symbol.for('process'); 2 | 3 | export default function Lane(process, laneDefinition) { 4 | const { broker, environment } = process; 5 | const { id, type, behaviour } = laneDefinition; 6 | 7 | this[kProcess] = process; 8 | 9 | this.id = id; 10 | this.type = type; 11 | this.name = behaviour.name; 12 | this.parent = { 13 | id: process.id, 14 | type: process.type, 15 | }; 16 | this.behaviour = { ...behaviour }; 17 | this.environment = environment; 18 | this.broker = broker; 19 | this.context = process.context; 20 | this.logger = environment.Logger(type.toLowerCase()); 21 | } 22 | 23 | Object.defineProperty(Lane.prototype, 'process', { 24 | get() { 25 | return this[kProcess]; 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /src/shared.js: -------------------------------------------------------------------------------- 1 | const safePattern = /[./\\#*:\s]/g; 2 | 3 | export function generateId() { 4 | return Math.random().toString(16).substring(2, 12); 5 | } 6 | 7 | export function brokerSafeId(id) { 8 | return id.replace(safePattern, '_'); 9 | } 10 | 11 | export function getUniqueId(prefix) { 12 | return `${brokerSafeId(prefix)}_${generateId()}`; 13 | } 14 | 15 | export function getOptionsAndCallback(optionsOrCallback, callback) { 16 | let options; 17 | if (typeof optionsOrCallback === 'function') { 18 | callback = optionsOrCallback; 19 | } else { 20 | options = optionsOrCallback; 21 | } 22 | 23 | return [options, callback]; 24 | } 25 | -------------------------------------------------------------------------------- /src/tasks/ScriptTask.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import ExecutionScope from '../activity/ExecutionScope.js'; 3 | import { ActivityError } from '../error/Errors.js'; 4 | import { cloneContent, cloneMessage } from '../messageHelper.js'; 5 | 6 | export default function ScriptTask(activityDef, context) { 7 | return new Activity(ScriptTaskBehaviour, activityDef, context); 8 | } 9 | 10 | export function ScriptTaskBehaviour(activity) { 11 | const { id, type, behaviour } = activity; 12 | 13 | this.id = id; 14 | this.type = type; 15 | this.scriptFormat = behaviour.scriptFormat; 16 | 17 | this.loopCharacteristics = 18 | behaviour.loopCharacteristics && new behaviour.loopCharacteristics.Behaviour(activity, behaviour.loopCharacteristics); 19 | this.activity = activity; 20 | const environment = (this.environment = activity.environment); 21 | 22 | environment.registerScript(activity); 23 | } 24 | 25 | ScriptTaskBehaviour.prototype.execute = function execute(executeMessage) { 26 | const executeContent = executeMessage.content; 27 | const loopCharacteristics = this.loopCharacteristics; 28 | if (loopCharacteristics && executeContent.isRootScope) { 29 | return loopCharacteristics.execute(executeMessage); 30 | } 31 | 32 | const activity = this.activity; 33 | const scriptFormat = this.scriptFormat; 34 | const script = this.environment.getScript(scriptFormat, activity, cloneMessage(executeMessage)); 35 | if (!script) { 36 | return activity.emitFatal( 37 | new ActivityError(`Script format ${scriptFormat} is unsupported or was not registered for <${activity.id}>`, executeMessage), 38 | executeContent 39 | ); 40 | } 41 | 42 | return script.execute(ExecutionScope(activity, executeMessage), scriptCallback); 43 | 44 | function scriptCallback(err, output) { 45 | if (err) { 46 | activity.logger.error(`<${executeContent.executionId} (${activity.id})>`, err); 47 | return activity.broker.publish( 48 | 'execution', 49 | 'execute.error', 50 | cloneContent(executeContent, { error: new ActivityError(err.message, executeMessage, err) }, { mandatory: true }) 51 | ); 52 | } 53 | return activity.broker.publish('execution', 'execute.completed', cloneContent(executeContent, { output })); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/tasks/ServiceImplementation.js: -------------------------------------------------------------------------------- 1 | import ExecutionScope from '../activity/ExecutionScope.js'; 2 | 3 | export default function ServiceImplementation(activity) { 4 | this.type = `${activity.type}:implementation`; 5 | this.implementation = activity.behaviour.implementation; 6 | this.activity = activity; 7 | } 8 | 9 | ServiceImplementation.prototype.execute = function execute(executionMessage, callback) { 10 | const activity = this.activity; 11 | const implementation = this.implementation; 12 | const serviceFn = activity.environment.resolveExpression(implementation, executionMessage); 13 | 14 | if (typeof serviceFn !== 'function') return callback(new Error(`Implementation ${implementation} did not resolve to a function`)); 15 | 16 | serviceFn.call(activity, ExecutionScope(activity, executionMessage), (err, ...args) => { 17 | callback(err, args); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/tasks/StandardLoopCharacteristics.js: -------------------------------------------------------------------------------- 1 | import LoopCharacteristics from './LoopCharacteristics.js'; 2 | 3 | export default function StandardLoopCharacteristics(activity, loopCharacteristics) { 4 | let { behaviour } = loopCharacteristics; 5 | behaviour = { ...behaviour, isSequential: true }; 6 | return new LoopCharacteristics(activity, { ...loopCharacteristics, behaviour }); 7 | } 8 | -------------------------------------------------------------------------------- /src/tasks/Task.js: -------------------------------------------------------------------------------- 1 | import Activity from '../activity/Activity.js'; 2 | import { cloneContent } from '../messageHelper.js'; 3 | 4 | export default function Task(activityDef, context) { 5 | return new Activity(TaskBehaviour, activityDef, context); 6 | } 7 | 8 | export function TaskBehaviour(activity) { 9 | const { id, type, behaviour, broker } = activity; 10 | this.id = id; 11 | this.type = type; 12 | this.loopCharacteristics = 13 | behaviour.loopCharacteristics && new behaviour.loopCharacteristics.Behaviour(activity, behaviour.loopCharacteristics); 14 | this.broker = broker; 15 | } 16 | 17 | TaskBehaviour.prototype.execute = function execute(executeMessage) { 18 | const executeContent = executeMessage.content; 19 | const loopCharacteristics = this.loopCharacteristics; 20 | if (loopCharacteristics && executeContent.isRootScope) { 21 | return loopCharacteristics.execute(executeMessage); 22 | } 23 | 24 | return this.broker.publish('execution', 'execute.completed', cloneContent(executeContent)); 25 | }; 26 | -------------------------------------------------------------------------------- /src/tasks/Transaction.js: -------------------------------------------------------------------------------- 1 | import SubProcess from './SubProcess.js'; 2 | 3 | export default function Transaction(activityDef, context) { 4 | const transaction = { type: 'transaction', ...activityDef, isTransaction: true }; 5 | const activity = SubProcess(transaction, context); 6 | return activity; 7 | } 8 | -------------------------------------------------------------------------------- /src/tasks/index.js: -------------------------------------------------------------------------------- 1 | import CallActivity, { CallActivityBehaviour } from './CallActivity.js'; 2 | import ReceiveTask, { ReceiveTaskBehaviour } from './ReceiveTask.js'; 3 | import ScriptTask, { ScriptTaskBehaviour } from './ScriptTask.js'; 4 | import ServiceTask, { ServiceTaskBehaviour } from './ServiceTask.js'; 5 | import SignalTask, { SignalTaskBehaviour } from './SignalTask.js'; 6 | import SubProcess, { SubProcessBehaviour } from './SubProcess.js'; 7 | import Task, { TaskBehaviour } from './Task.js'; 8 | import Transaction from './Transaction.js'; 9 | 10 | export { 11 | CallActivity, 12 | CallActivityBehaviour, 13 | ReceiveTask, 14 | ReceiveTaskBehaviour, 15 | ScriptTask, 16 | ScriptTaskBehaviour, 17 | ServiceTask, 18 | ServiceTaskBehaviour, 19 | SignalTask, 20 | SignalTaskBehaviour, 21 | SubProcess, 22 | SubProcessBehaviour, 23 | Task, 24 | TaskBehaviour, 25 | Transaction, 26 | }; 27 | -------------------------------------------------------------------------------- /tasks.d.ts: -------------------------------------------------------------------------------- 1 | import './types/index.js'; 2 | -------------------------------------------------------------------------------- /test/Api-test.js: -------------------------------------------------------------------------------- 1 | import { Broker } from 'smqp'; 2 | import { ActivityApi } from '../src/Api.js'; 3 | import Environment from '../src/Environment.js'; 4 | 5 | describe('Api', () => { 6 | it('Api without message throws', () => { 7 | expect(() => { 8 | ActivityApi(new Broker(), null, new Environment()); 9 | }).to.throw(Error); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/Timers-test.js: -------------------------------------------------------------------------------- 1 | import ck from 'chronokinesis'; 2 | 3 | import { Timers } from '../src/Timers.js'; 4 | 5 | describe('Timers', () => { 6 | describe('setTimeout', () => { 7 | afterEach(ck.reset); 8 | 9 | it('returns home baked timer object', () => { 10 | ck.freeze('2023-05-25T10:00Z'); 11 | const timers = new Timers({ 12 | setTimeout() { 13 | return 'ref'; 14 | }, 15 | clearTimeout() {}, 16 | }); 17 | 18 | const callback = () => {}; 19 | const timer = timers.setTimeout(callback, 60000, 1); 20 | 21 | expect(timer.callback).to.equal(callback); 22 | expect(timer.delay).to.equal(60000); 23 | expect(timer.args).to.deep.equal([1]); 24 | expect(timer.owner).to.be.null; 25 | expect(timer.timerId).to.be.a('string'); 26 | expect(timer.expireAt).to.deep.equal(new Date('2023-05-25T10:01Z')); 27 | expect(timer.timerRef).to.equal('ref'); 28 | }); 29 | 30 | it('adds timer to list of executing timers', () => { 31 | const timers = new Timers({ 32 | setTimeout() { 33 | return 'ref'; 34 | }, 35 | clearTimeout() {}, 36 | }); 37 | 38 | const callback = () => {}; 39 | const timer = timers.setTimeout(callback, 60000, 1); 40 | 41 | expect(timers.executing).to.have.length(1); 42 | expect(timers.executing[0].timerId).to.be.ok.and.equal(timer.timerId); 43 | }); 44 | }); 45 | 46 | describe('clearTimeout', () => { 47 | it('resets timerRef on timer', () => { 48 | const timers = new Timers({ 49 | setTimeout() { 50 | return 'ref'; 51 | }, 52 | clearTimeout() {}, 53 | }); 54 | 55 | const timer = timers.setTimeout(() => {}, 100); 56 | 57 | expect(timer.timerRef).to.equal('ref'); 58 | 59 | timers.clearTimeout(timer); 60 | 61 | expect(timer.timerRef).to.be.undefined; 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/activity/ExecutionScope-test.js: -------------------------------------------------------------------------------- 1 | import Environment from '../../src/Environment.js'; 2 | import ExecutionScope from '../../src/activity/ExecutionScope.js'; 3 | import { ActivityError, BpmnError } from '../../src/error/Errors.js'; 4 | 5 | describe('ExecutionScope', () => { 6 | it('exposes environment, error classes, and passed message', () => { 7 | const activity = { 8 | id: 'task1', 9 | type: 'task', 10 | environment: new Environment(), 11 | logger: {}, 12 | }; 13 | const message = { 14 | fields: { 15 | routingKey: 'run.execute', 16 | }, 17 | content: { 18 | id: 'task1', 19 | }, 20 | properties: { 21 | messageId: 'm1', 22 | }, 23 | }; 24 | 25 | const scope = ExecutionScope(activity, message); 26 | expect(scope).to.have.property('id', 'task1'); 27 | expect(scope).to.have.property('type', 'task'); 28 | expect(scope).to.have.property('logger', activity.logger); 29 | expect(scope).to.have.property('environment', activity.environment); 30 | expect(scope).to.have.property('BpmnError', BpmnError); 31 | expect(scope).to.have.property('ActivityError', ActivityError); 32 | expect(scope) 33 | .to.have.property('fields') 34 | .that.eql({ 35 | routingKey: 'run.execute', 36 | }) 37 | .but.not.equal(message.fields); 38 | expect(scope) 39 | .to.have.property('content') 40 | .that.eql({ 41 | id: 'task1', 42 | }) 43 | .but.not.equal(message.content); 44 | expect(scope) 45 | .to.have.property('properties') 46 | .that.eql({ 47 | messageId: 'm1', 48 | }) 49 | .but.not.equal(message.properties); 50 | }); 51 | 52 | it('exposes resolve expression', () => { 53 | const activity = { 54 | id: 'task1', 55 | type: 'task', 56 | environment: new Environment({ 57 | variables: { 58 | input: 1, 59 | }, 60 | }), 61 | logger: {}, 62 | }; 63 | const message = { 64 | fields: { 65 | routingKey: 'run.execute', 66 | }, 67 | content: { 68 | id: 'task1', 69 | }, 70 | properties: { 71 | messageId: 'm1', 72 | }, 73 | }; 74 | 75 | const scope = ExecutionScope(activity, message); 76 | expect(scope.resolveExpression('${logger}')).to.equal(activity.logger); 77 | expect(scope.resolveExpression('${environment.variables.input}')).to.equal(1); 78 | expect(scope.resolveExpression('${properties.messageId}')).to.equal('m1'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/bpmn-elements-test.js: -------------------------------------------------------------------------------- 1 | import * as api from '../src/index.js'; 2 | 3 | describe('bpmn-elemements module', () => { 4 | it('exports Timers', () => { 5 | expect(api).to.have.property('Timers').that.is.a('function'); 6 | }); 7 | 8 | it('exports Errors', () => { 9 | expect(api).to.have.property('ActivityError').that.is.a('function'); 10 | expect(api).to.have.property('RunError').that.is.a('function'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/error/BpmnError-test.js: -------------------------------------------------------------------------------- 1 | import BpmnErrorActivity from '../../src/error/BpmnError.js'; 2 | import Environment from '../../src/Environment.js'; 3 | 4 | describe('BpmnError', () => { 5 | it('returns BpmnError instanceof from error', () => { 6 | const bpmnError = BpmnErrorActivity( 7 | { 8 | id: 'Error_0', 9 | name: 'TestError', 10 | }, 11 | { environment: new Environment() } 12 | ); 13 | 14 | const err = bpmnError.resolve({}, new Error('Men')); 15 | 16 | expect(err).to.have.property('id', 'Error_0'); 17 | expect(err).to.have.property('name', 'TestError'); 18 | }); 19 | 20 | it('resolves errorCode expression', () => { 21 | const bpmnError = BpmnErrorActivity( 22 | { 23 | id: 'Error_0', 24 | name: 'TestError', 25 | behaviour: { 26 | errorCode: 'EMES', 27 | }, 28 | }, 29 | { environment: new Environment() } 30 | ); 31 | 32 | const err = bpmnError.resolve( 33 | { 34 | resolveExpression(errorCode) { 35 | return errorCode; 36 | }, 37 | }, 38 | new Error('Men') 39 | ); 40 | 41 | expect(err).to.have.property('code', 'EMES'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/eventDefinitions/TerminateEventDefinition-test.js: -------------------------------------------------------------------------------- 1 | import Environment from '../../src/Environment.js'; 2 | import TerminateEventDefinition from '../../src/eventDefinitions/TerminateEventDefinition.js'; 3 | import { ActivityBroker } from '../../src/EventBroker.js'; 4 | 5 | describe('TerminateEventDefinition', () => { 6 | let event; 7 | beforeEach(() => { 8 | event = { 9 | environment: new Environment(), 10 | broker: ActivityBroker(this).broker, 11 | }; 12 | }); 13 | 14 | it('publishes process terminate on parent broker and completes', () => { 15 | const terminateDefinition = new TerminateEventDefinition(event, {}); 16 | 17 | const messages = []; 18 | event.broker.subscribeTmp( 19 | 'event', 20 | 'process.*', 21 | (_, msg) => { 22 | messages.push(msg); 23 | }, 24 | { noAck: true } 25 | ); 26 | event.broker.subscribeTmp( 27 | 'execution', 28 | '#', 29 | (_, msg) => { 30 | messages.push(msg); 31 | }, 32 | { noAck: true } 33 | ); 34 | 35 | terminateDefinition.execute({ 36 | content: { 37 | id: 'end', 38 | executionId: 'end_0_0', 39 | parent: { 40 | id: 'end', 41 | executionId: 'end_0', 42 | type: 'bpmn:EndEvent', 43 | path: [ 44 | { 45 | id: 'theProcess', 46 | executionId: 'theProcess_0', 47 | }, 48 | ], 49 | }, 50 | }, 51 | }); 52 | 53 | expect(messages).to.have.length(2); 54 | expect(messages[0].fields).to.have.property('routingKey', 'process.terminate'); 55 | expect(messages[0].content).to.have.property('state', 'terminate'); 56 | expect(messages[0].content).to.have.property('parent'); 57 | expect(messages[0].content.parent).to.have.property('id', 'theProcess'); 58 | expect(messages[0].content.parent).to.have.property('executionId', 'theProcess_0'); 59 | 60 | expect(messages[1].fields).to.have.property('routingKey', 'execute.completed'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/feature/activity-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import factory from '../helpers/factory.js'; 3 | import testHelpers from '../helpers/testHelpers.js'; 4 | 5 | Feature('Activity', () => { 6 | Scenario('When a task is discarded by multiple flows', () => { 7 | let definition; 8 | 9 | Given('a process with several decisions all ending up in one manual task', async () => { 10 | const source = factory.resource('consumer_error.bpmn'); 11 | const context = await testHelpers.context(source); 12 | 13 | definition = new Definition(context); 14 | }); 15 | 16 | let wait; 17 | When('definition is run', () => { 18 | wait = definition.waitFor('activity.wait'); 19 | definition.run(); 20 | }); 21 | 22 | Then('manual task eventually executes and waits', () => { 23 | return wait; 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/feature/ad-hoc-subprocess-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import testHelpers from '../helpers/testHelpers.js'; 3 | import factory from '../helpers/factory.js'; 4 | 5 | Feature('Ad-hoc subprocess', () => { 6 | Scenario('Running ad-hoc subprocess', () => { 7 | let context, definition; 8 | Given('a process mathching feature', async () => { 9 | const source = factory.resource('ad-hoc-subprocess.bpmn'); 10 | context = await testHelpers.context(source); 11 | }); 12 | 13 | let leave; 14 | const completedActivities = []; 15 | When('running definition', () => { 16 | definition = new Definition(context); 17 | 18 | definition.broker.subscribeTmp( 19 | 'event', 20 | 'activity.end', 21 | (_, msg) => { 22 | completedActivities.push({ id: msg.content.id, parent: msg.content.parent.id }); 23 | }, 24 | { noAck: true } 25 | ); 26 | 27 | leave = definition.waitFor('leave'); 28 | 29 | definition.run(); 30 | }); 31 | 32 | Then('definition completes', () => { 33 | return leave; 34 | }); 35 | 36 | And('all ad-hoc subprocess activities were taken', () => { 37 | expect(completedActivities).to.deep.equal([ 38 | { id: 'start', parent: 'process_0' }, 39 | { id: 'task1', parent: 'adhoc' }, 40 | { id: 'throw', parent: 'adhoc' }, 41 | { id: 'task2', parent: 'adhoc' }, 42 | { id: 'task3', parent: 'adhoc' }, 43 | { id: 'adhoc', parent: 'process_0' }, 44 | { id: 'end', parent: 'process_0' }, 45 | ]); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/feature/backward-compatability-feature.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import Definition from '../../src/definition/Definition.js'; 3 | import factory from '../helpers/factory.js'; 4 | import testHelpers from '../helpers/testHelpers.js'; 5 | 6 | const motherOfAllSource = factory.resource('mother-of-all.bpmn'); 7 | 8 | Feature('Backward compatability 5.2', () => { 9 | Scenario('Slimmer state', () => { 10 | let context; 11 | before(async () => { 12 | context = await testHelpers.context(motherOfAllSource); 13 | }); 14 | 15 | let definition, state; 16 | Given('a state from version 5', async () => { 17 | state = JSON.parse(await fs.readFile('./test/resources/mother-of-all-state-5.json')); 18 | }); 19 | 20 | let leave; 21 | When('recovered and resumed with state from version 5', () => { 22 | definition = new Definition(context).recover(state); 23 | leave = definition.waitFor('leave'); 24 | definition.resume(); 25 | }); 26 | 27 | And('waiting tasks are signaled', () => { 28 | definition.signal({ id: 'userTask1' }); 29 | definition.signal({ id: 'subUserTask1' }); 30 | }); 31 | 32 | Then('run completes', () => { 33 | return leave; 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/feature/definition-output-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import testHelpers from '../helpers/testHelpers.js'; 3 | 4 | Feature('Definition output', () => { 5 | Scenario('Process completes with output', () => { 6 | let definition; 7 | Given('a process with output', async () => { 8 | const source = ` 9 | 10 | 11 | 12 | 17 | 18 | 19 | `; 20 | 21 | const context = await testHelpers.context(source); 22 | definition = new Definition(context); 23 | }); 24 | 25 | let end; 26 | When('ran', () => { 27 | end = definition.waitFor('end'); 28 | definition.run(); 29 | }); 30 | 31 | Then('run completes', () => { 32 | return end; 33 | }); 34 | 35 | And('definition state contain hoisted process output', () => { 36 | expect(definition.getState().environment.output).to.deep.equal({ foo: 'bar' }); 37 | }); 38 | }); 39 | 40 | Scenario('Process fails after writing output', () => { 41 | let definition; 42 | Given('a process with output', async () => { 43 | const source = ` 44 | 45 | 46 | 47 | 55 | 56 | 57 | `; 58 | 59 | const context = await testHelpers.context(source); 60 | definition = new Definition(context); 61 | }); 62 | 63 | let errored; 64 | When('ran', () => { 65 | errored = definition.waitFor('error'); 66 | definition.run(); 67 | }); 68 | 69 | Then('run fails', () => { 70 | return errored; 71 | }); 72 | 73 | And('definition state contain hoisted process output', () => { 74 | expect(definition.getState().environment.output).to.deep.equal({ foo: 'bar' }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/feature/dummy-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import factory from '../helpers/factory.js'; 3 | import testHelpers from '../helpers/testHelpers.js'; 4 | 5 | const groupsSource = factory.resource('groups.bpmn'); 6 | 7 | Feature('Dummy', () => { 8 | Scenario('Group of elements with categories', () => { 9 | let context, definition; 10 | 11 | let ended; 12 | When('a source with groups is ran', async () => { 13 | context = await testHelpers.context(groupsSource); 14 | definition = new Definition(context); 15 | ended = definition.waitFor('end'); 16 | definition.run(); 17 | }); 18 | 19 | Then('it runs to end', () => { 20 | return ended; 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/feature/expression-feature.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | import Definition from '../../src/definition/Definition.js'; 3 | import { resolveExpression } from '@aircall/expression-parser'; 4 | 5 | Feature('expressions', () => { 6 | Scenario('@aircall/expression-parser', () => { 7 | let definition; 8 | Given('a process with faulty sequence flow condition expression syntax', async () => { 9 | const source = ` 10 | 13 | 14 | 15 | 16 | \${true === "false'} 17 | 18 | 19 | 20 | 21 | 22 | `; 23 | 24 | const context = await testHelpers.context(source); 25 | definition = new Definition(context, { 26 | expressions: { resolveExpression }, 27 | }); 28 | }); 29 | 30 | let errored; 31 | When('definition is ran', () => { 32 | errored = definition.waitFor('error'); 33 | definition.run(); 34 | }); 35 | 36 | Then('run fails with error from expression parser', async () => { 37 | const err = await errored; 38 | expect(err.content.error).to.match(/syntax/i); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/feature/issues/exclusive-gateway-join-feature.js: -------------------------------------------------------------------------------- 1 | import { Definition } from '../../../src/index.js'; 2 | import testHelpers from '../../helpers/testHelpers.js'; 3 | import factory from '../../helpers/factory.js'; 4 | 5 | const source = factory.resource('exclusive-gateway-as-join.bpmn'); 6 | 7 | Feature('Exclusive gateway used for joining', () => { 8 | Scenario('a number of exclusive gateway join and split', () => { 9 | let context, definition, end; 10 | When('running a definition matching the scenario', async () => { 11 | context = await testHelpers.context(source); 12 | 13 | definition = new Definition(context); 14 | end = definition.waitFor('end'); 15 | definition.run(); 16 | }); 17 | 18 | Then('run completes', () => { 19 | return end; 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/feature/script-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import testHelpers from '../helpers/testHelpers.js'; 3 | 4 | class Scripts { 5 | register({ behaviour }) { 6 | if (!/^(javascript|js)$/i.test(behaviour.scriptFormat)) return; 7 | } 8 | compile() {} 9 | getScript() {} 10 | } 11 | 12 | Feature('Script', () => { 13 | Scenario('Register script fails', () => { 14 | let context, definition; 15 | 16 | Given('a process with a script task with unsupported script format', async () => { 17 | const source = ` 18 | 19 | 20 | 21 | 26 | 27 | 28 | `; 29 | 30 | context = await testHelpers.context(source); 31 | }); 32 | 33 | let err; 34 | When('definition run', () => { 35 | definition = new Definition(context, { scripts: new Scripts() }); 36 | try { 37 | definition.run(); 38 | } catch (e) { 39 | err = e; 40 | } 41 | }); 42 | 43 | Then('error is thrown', () => { 44 | expect(err.message).to.equal('Script format python is unsupported or was not registered for '); 45 | definition.stop(); 46 | }); 47 | 48 | Given('async formatting is added', () => { 49 | definition = new Definition(context, { scripts: new Scripts() }); 50 | const task = definition.getActivityById('task'); 51 | task.on('enter', () => { 52 | task.broker 53 | .getQueue('format-run-q') 54 | .queueMessage({ routingKey: 'run.enter.format' }, { endRoutingKey: 'run.enter.complete' }, { persistent: false }); 55 | setImmediate(() => { 56 | task.broker.publish('format', 'run.enter.complete', { data: 1 }, { persistent: false }); 57 | }); 58 | }); 59 | }); 60 | 61 | let waitError; 62 | When('definition run', () => { 63 | waitError = definition.waitFor('error'); 64 | definition.run(); 65 | }); 66 | 67 | Then('error is thrown', async () => { 68 | err = await waitError; 69 | expect(err.content.error.message).to.equal('Script format python is unsupported or was not registered for '); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/feature/service-task-feature.js: -------------------------------------------------------------------------------- 1 | import Definition from '../../src/definition/Definition.js'; 2 | import testHelpers from '../helpers/testHelpers.js'; 3 | 4 | Feature('Service task', () => { 5 | Scenario('Recover and resume mid execution', () => { 6 | let context, definition; 7 | 8 | Given('a process with a service task', async () => { 9 | const source = ` 10 | 11 | 12 | 13 | 14 | `; 15 | 16 | context = await testHelpers.context(source); 17 | }); 18 | 19 | let called = 0; 20 | When('definition run', () => { 21 | definition = new Definition(context, { 22 | services: { 23 | foo() { 24 | ++called; 25 | }, 26 | }, 27 | }); 28 | definition.run(); 29 | }); 30 | 31 | Then('service is called', () => { 32 | expect(called).to.equal(1); 33 | }); 34 | 35 | let state; 36 | Given('state is saved', () => { 37 | state = definition.getState(); 38 | }); 39 | 40 | let end; 41 | When('definition recovered and resumed', () => { 42 | definition = new Definition(context.clone(), { 43 | services: { 44 | foo(...args) { 45 | args.pop()(); 46 | }, 47 | }, 48 | }); 49 | 50 | end = definition.waitFor('leave'); 51 | 52 | definition.recover(state).resume(); 53 | }); 54 | 55 | Then('resumed definition completes', () => { 56 | return end; 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/flows/Association-test.js: -------------------------------------------------------------------------------- 1 | import Environment from '../../src/Environment.js'; 2 | import Association from '../../src/flows/Association.js'; 3 | import { ActivityBroker } from '../../src/EventBroker.js'; 4 | 5 | describe('Association', () => { 6 | it('stop() stops broker', () => { 7 | const activity = ActivityBroker(); 8 | const context = { 9 | environment: new Environment(), 10 | getActivityById() { 11 | return activity; 12 | }, 13 | }; 14 | 15 | const flow = new Association( 16 | { 17 | id: 'association', 18 | type: 'bpmn:Association', 19 | parent: {}, 20 | source: { 21 | id: 'task', 22 | }, 23 | target: { 24 | id: 'task1', 25 | }, 26 | }, 27 | context 28 | ); 29 | 30 | expect(flow.broker.getExchange('event').stopped).to.be.false; 31 | flow.stop(); 32 | expect(flow.broker.getExchange('event').stopped).to.be.true; 33 | }); 34 | 35 | it('getApi() returns message Api', () => { 36 | const activity = ActivityBroker(); 37 | const context = { 38 | environment: new Environment(), 39 | getActivityById() { 40 | return activity; 41 | }, 42 | }; 43 | 44 | const flow = new Association( 45 | { 46 | id: 'association', 47 | type: 'bpmn:Association', 48 | parent: {}, 49 | source: { 50 | id: 'task', 51 | }, 52 | target: { 53 | id: 'task1', 54 | }, 55 | }, 56 | context 57 | ); 58 | 59 | const api = flow.getApi(); 60 | expect(api).to.have.property('id', 'association'); 61 | }); 62 | 63 | it('getApi(message) returns message Api', () => { 64 | const activity = ActivityBroker(); 65 | const context = { 66 | environment: new Environment(), 67 | getActivityById() { 68 | return activity; 69 | }, 70 | }; 71 | 72 | const flow = new Association( 73 | { 74 | id: 'association', 75 | type: 'bpmn:Association', 76 | parent: {}, 77 | source: { 78 | id: 'task', 79 | }, 80 | target: { 81 | id: 'task1', 82 | }, 83 | }, 84 | context 85 | ); 86 | 87 | const api = flow.getApi({ content: { id: 'foo' } }); 88 | expect(api).to.have.property('id', 'foo'); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/helpers/JavaScripts.js: -------------------------------------------------------------------------------- 1 | import { Script } from 'vm'; 2 | 3 | export function Scripts(enableDummy = false) { 4 | this.scripts = new Map(); 5 | this.enableDummy = enableDummy; 6 | } 7 | 8 | Scripts.prototype.register = function register({ id, type, behaviour, logger, environment }) { 9 | let scriptBody, language; 10 | 11 | switch (type) { 12 | case 'bpmn:SequenceFlow': { 13 | if (!behaviour.conditionExpression) return; 14 | language = behaviour.conditionExpression.language; 15 | if (!language) return; 16 | scriptBody = behaviour.conditionExpression.body; 17 | break; 18 | } 19 | default: { 20 | language = behaviour.scriptFormat; 21 | scriptBody = behaviour.script; 22 | } 23 | } 24 | 25 | const filename = `${type}/${id}`; 26 | if (!language || !scriptBody) { 27 | if (this.enableDummy) return; 28 | const script = new DummyScript(language, filename, logger); 29 | this.scripts.set(id, script); 30 | return script; 31 | } 32 | 33 | if (!/^(javascript|js)$/i.test(language)) return; 34 | 35 | const script = new JavaScript(language, filename, scriptBody, environment); 36 | this.scripts.set(id, script); 37 | 38 | return script; 39 | }; 40 | 41 | Scripts.prototype.getScript = function getScript(language, { id }) { 42 | return this.scripts.get(id); 43 | }; 44 | 45 | Scripts.prototype.compile = function compile(language, filename, scriptBody) { 46 | return new Script(scriptBody, { filename }); 47 | }; 48 | 49 | function JavaScript(language, filename, scriptBody, environment) { 50 | this.id = filename; 51 | this.script = new Script(scriptBody, { filename }); 52 | this.language = language; 53 | this.environment = environment; 54 | } 55 | 56 | JavaScript.prototype.execute = function execute(executionContext, callback) { 57 | const timers = this.environment.timers.register(executionContext); 58 | return this.script.runInNewContext({ ...executionContext, ...timers, next: callback, console }); 59 | }; 60 | 61 | function DummyScript(language, filename, logger) { 62 | this.id = filename; 63 | this.isDummy = true; 64 | this.language = language; 65 | this.logger = logger; 66 | } 67 | 68 | DummyScript.prototype.execute = function execute(executionContext, callback) { 69 | const { id, executionId } = executionContext.content; 70 | this.logger.debug(`<${executionId} (${id})> passthrough dummy script ${this.language || 'esperanto'}`); 71 | callback(); 72 | }; 73 | -------------------------------------------------------------------------------- /test/helpers/setup.js: -------------------------------------------------------------------------------- 1 | import 'chai/register-expect.js'; 2 | 3 | process.env.NODE_ENV = 'test'; 4 | Error.stackTraceLimit = 20; 5 | -------------------------------------------------------------------------------- /test/io/DataObject-test.js: -------------------------------------------------------------------------------- 1 | import DataObject from '../../src/io/EnvironmentDataObject.js'; 2 | import Environment from '../../src/Environment.js'; 3 | import { ActivityBroker } from '../../src/EventBroker.js'; 4 | 5 | describe('DataObject', () => { 6 | describe('read', () => { 7 | it('publishes message on passed broker exchange when value was read', () => { 8 | const { broker } = ActivityBroker(); 9 | const dataObject = new DataObject({ id: 'input' }, { environment: new Environment() }); 10 | 11 | let message; 12 | broker.subscribeOnce('format', 'test.#', (_, msg) => { 13 | message = msg; 14 | }); 15 | 16 | dataObject.read(broker, 'format', 'test.'); 17 | 18 | expect(message).to.be.ok; 19 | expect(message.content).to.have.property('id', 'input'); 20 | }); 21 | }); 22 | 23 | describe('write', () => { 24 | it('publishes message on passed broker exchange when value was written', () => { 25 | const { broker } = ActivityBroker(); 26 | const dataObject = new DataObject({ id: 'input' }, { environment: new Environment() }); 27 | 28 | let message; 29 | broker.subscribeOnce('format', 'test.#', (_, msg) => { 30 | message = msg; 31 | }); 32 | 33 | dataObject.write(broker, 'format', 'test.'); 34 | 35 | expect(message).to.be.ok; 36 | expect(message.content).to.have.property('id', 'input'); 37 | }); 38 | }); 39 | 40 | describe('builtin', () => { 41 | it('saves dataObject value in environment variables _data', () => { 42 | const environment = new Environment(); 43 | const { broker } = ActivityBroker(); 44 | const dataObject = new DataObject({ id: 'info' }, { environment }); 45 | 46 | dataObject.write(broker, 'format', 'test', 'me'); 47 | 48 | expect(environment.variables._data).to.have.property('info', 'me'); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/io/Properties-test.js: -------------------------------------------------------------------------------- 1 | import Environment from '../../src/Environment.js'; 2 | import Properties from '../../src/io/Properties.js'; 3 | import { ActivityBroker } from '../../src/EventBroker.js'; 4 | 5 | describe('Properties', () => { 6 | it('activate twice has no effect', () => { 7 | const { broker } = ActivityBroker(); 8 | const props = new Properties( 9 | { 10 | id: 'input', 11 | broker, 12 | environment: new Environment(), 13 | }, 14 | { 15 | values: [], 16 | } 17 | ); 18 | props.activate({ 19 | fields: {}, 20 | content: {}, 21 | }); 22 | props.activate({ 23 | fields: {}, 24 | content: {}, 25 | }); 26 | }); 27 | 28 | it('deactivate twice has no effect', () => { 29 | const { broker } = ActivityBroker(); 30 | const props = new Properties( 31 | { 32 | id: 'input', 33 | broker, 34 | environment: new Environment(), 35 | }, 36 | { 37 | values: [], 38 | } 39 | ); 40 | props.activate({ 41 | fields: {}, 42 | content: {}, 43 | }); 44 | props.deactivate(); 45 | props.deactivate(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/resources/README.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | All modeled with [Camunda modeler](https://camunda.org/download/modeler/). 4 | -------------------------------------------------------------------------------- /test/resources/call-activity-signal.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/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/consumer_error.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow0 6 | 7 | 8 | flow2end 9 | 10 | 11 | 12 | flow3 13 | flow2 14 | flow4 15 | flow2end 16 | 17 | 18 | 19 | flow1 20 | flow3 21 | flow4 22 | 23 | 24 | next(null, false) 25 | 26 | 27 | flow0 28 | flow1 29 | flow2 30 | 31 | 32 | next(null, false) 33 | 34 | 35 | next(null, true) 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/resources/escalation-start-event.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1a2bsh6 6 | 7 | 8 | 9 | SequenceFlow_1a2bsh6 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/resources/extensions/JsExtension.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { brokerSafeId } from '../../../src/shared.js'; 3 | 4 | const moddleOptions = JSON.parse(fs.readFileSync('./test/resources/js-bpmn-moddle.json')); 5 | 6 | export default { 7 | extension: Js, 8 | moddleOptions, 9 | }; 10 | 11 | function Js(activity, context) { 12 | const resultVariable = ResultVariableIo(activity, context); 13 | const formKey = FormKey(activity, context); 14 | 15 | return { 16 | type: 'js:extension', 17 | extensions: { resultVariable, formKey }, 18 | activate(msg) { 19 | if (resultVariable) resultVariable.activate(msg); 20 | if (formKey) formKey.activate(msg); 21 | }, 22 | deactivate() { 23 | if (resultVariable) resultVariable.deactivate(); 24 | if (formKey) formKey.deactivate(); 25 | }, 26 | }; 27 | } 28 | 29 | function ResultVariableIo(activity) { 30 | const { result } = activity.behaviour; 31 | if (!result) return; 32 | 33 | const { id, logger, environment } = activity; 34 | const { broker } = activity; 35 | 36 | const type = 'js:resultvariable'; 37 | let activityConsumer; 38 | 39 | return { 40 | type, 41 | activate, 42 | deactivate, 43 | }; 44 | 45 | function deactivate() { 46 | if (activityConsumer) activityConsumer = activityConsumer.cancel(); 47 | } 48 | 49 | function activate() { 50 | if (activityConsumer) return; 51 | activityConsumer = broker.subscribeTmp('event', 'activity.end', onActivityEnd, { noAck: true }); 52 | } 53 | 54 | function onActivityEnd(_, message) { 55 | const resultName = environment.resolveExpression(result, message.content); 56 | logger.debug(`<${id}> js:extension save to "${resultName}"`); 57 | 58 | environment.output[resultName] = message.content.output; 59 | } 60 | } 61 | 62 | function FormKey(activity, context) { 63 | const { id, logger, behaviour } = activity; 64 | const { formKey } = behaviour; 65 | if (!formKey) return; 66 | 67 | const { broker } = activity; 68 | const { environment } = context; 69 | 70 | const type = 'js:formkey'; 71 | const safeType = brokerSafeId(type).toLowerCase(); 72 | let activityConsumer; 73 | 74 | return { 75 | type, 76 | activate, 77 | deactivate, 78 | }; 79 | 80 | function deactivate() { 81 | if (activityConsumer) activityConsumer = activityConsumer.cancel(); 82 | } 83 | 84 | function activate() { 85 | if (activityConsumer) return; 86 | activityConsumer = broker.subscribeTmp('event', 'activity.start', onActivityStart, { noAck: true, consumerTag: '_' }); 87 | } 88 | 89 | function onActivityStart(_, message) { 90 | const formKeyValue = environment.resolveExpression(formKey, message); 91 | logger.debug(`<${id}> apply form`); 92 | 93 | broker.publish('format', `run.${safeType}.start`, { 94 | form: { 95 | type, 96 | key: formKeyValue, 97 | }, 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /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": "ProcessProperties", 11 | "isAbstract": true, 12 | "extends": ["bpmn:Process"], 13 | "properties": [ 14 | { 15 | "name": "candidateStarterUsers", 16 | "isAttr": true, 17 | "type": "String" 18 | }, 19 | { 20 | "name": "versionTag", 21 | "isAttr": true, 22 | "type": "String" 23 | } 24 | ] 25 | }, 26 | { 27 | "name": "TaskResult", 28 | "isAbstract": true, 29 | "extends": ["bpmn:Task", "bpmn:SubProcess"], 30 | "properties": [ 31 | { 32 | "name": "result", 33 | "isAttr": true, 34 | "type": "String" 35 | } 36 | ] 37 | }, 38 | { 39 | "name": "Output", 40 | "superClass": ["Element"] 41 | }, 42 | { 43 | "name": "Collectable", 44 | "isAbstract": true, 45 | "extends": ["bpmn:MultiInstanceLoopCharacteristics"], 46 | "properties": [ 47 | { 48 | "name": "collection", 49 | "isAttr": true, 50 | "type": "String" 51 | }, 52 | { 53 | "name": "elementVariable", 54 | "isAttr": true, 55 | "type": "String" 56 | } 57 | ] 58 | }, 59 | { 60 | "name": "FormSupported", 61 | "isAbstract": true, 62 | "extends": ["bpmn:StartEvent", "bpmn:UserTask"], 63 | "properties": [ 64 | { 65 | "name": "formKey", 66 | "type": "String", 67 | "isAttr": true 68 | } 69 | ] 70 | }, 71 | { 72 | "name": "ScriptTask", 73 | "isAbstract": true, 74 | "extends": ["bpmn:ScriptTask"], 75 | "properties": [ 76 | { 77 | "name": "resource", 78 | "isAttr": true, 79 | "type": "String" 80 | } 81 | ] 82 | }, 83 | { 84 | "name": "FormalExpression", 85 | "isAbstract": true, 86 | "extends": ["bpmn:FormalExpression"], 87 | "properties": [ 88 | { 89 | "name": "resource", 90 | "isAttr": true, 91 | "type": "String" 92 | } 93 | ] 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /test/resources/link-event.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flow1 6 | 7 | 8 | flow1 9 | flow2 10 | environment.services.log("task1"); next() 11 | 12 | 13 | flow4 14 | 15 | 16 | flow3 17 | 18 | 19 | 20 | flow3 21 | flow4 22 | environment.services.log("task2"); next() 23 | 24 | 25 | flow2 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/resources/process-message.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /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/signal-after-signal.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_16dj1xw 6 | 7 | 8 | 9 | Flow_16dj1xw 10 | Flow_1skrjsj 11 | 12 | 13 | 14 | 15 | Flow_1skrjsj 16 | Flow_1bmd3eh 17 | 18 | 19 | 20 | Flow_1bmd3eh 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 | -------------------------------------------------------------------------------- /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/shared-test.js: -------------------------------------------------------------------------------- 1 | import { generateId, brokerSafeId } from '../src/shared.js'; 2 | 3 | describe('shared', () => { 4 | describe('brokerSafeId', () => { 5 | it('removes whitespace, dots, stars, hashes, and slashes from string', () => { 6 | expect(brokerSafeId(' my\n\rinput')).to.equal('__my__input'); 7 | expect(brokerSafeId('a.b')).to.equal('a_b'); 8 | expect(brokerSafeId('a.b.c')).to.equal('a_b_c'); 9 | expect(brokerSafeId('a*b')).to.equal('a_b'); 10 | expect(brokerSafeId('a*b*c')).to.equal('a_b_c'); 11 | expect(brokerSafeId('a#b')).to.equal('a_b'); 12 | expect(brokerSafeId('a#b#c')).to.equal('a_b_c'); 13 | expect(brokerSafeId('a/b')).to.equal('a_b'); 14 | expect(brokerSafeId('a/b/c')).to.equal('a_b_c'); 15 | expect(brokerSafeId('a\\b')).to.equal('a_b'); 16 | expect(brokerSafeId('a\\b\\c')).to.equal('a_b_c'); 17 | expect(brokerSafeId('a.b*c#d/e\\f')).to.equal('a_b_c_d_e_f'); 18 | }); 19 | }); 20 | 21 | describe('generateId', () => { 22 | it('generates at least 2000 unique ids', () => { 23 | const ids = []; 24 | for (let i = 0; i < 2000; i++) ids.push(generateId()); 25 | 26 | expect(ids.filter((a, idx, self) => self.indexOf(a) === idx).length).to.equal(2000); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/tasks/BusinessRuleTask-test.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | 3 | describe('BusinessRuleTask', () => { 4 | it('behaves like service task', async () => { 5 | const source = ` 6 | 7 | 8 | 9 | 10 | 11 | `; 12 | 13 | const context = await testHelpers.context(source); 14 | context.environment.addService('rule', (...args) => { 15 | args.pop()(null, true); 16 | }); 17 | 18 | const task = context.getActivityById('task'); 19 | 20 | task.activate(); 21 | 22 | const leave = task.waitFor('leave'); 23 | 24 | task.run(); 25 | 26 | const api = await leave; 27 | 28 | expect(api.content.output).to.eql([true]); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/tasks/CallActivity-test.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | 3 | describe('CallActivity', () => { 4 | const source = ` 5 | 6 | 7 | 8 | 9 | 10 | `; 11 | 12 | let context; 13 | beforeEach(async () => { 14 | context = await testHelpers.context(source); 15 | }); 16 | 17 | it('ignores delegate message without message', () => { 18 | const task = context.getActivityById('task'); 19 | 20 | task.run(); 21 | 22 | task.broker.publish('api', 'definition.signal.Def_1', {}, { delegate: true }); 23 | 24 | expect(task.status).to.equal('executing'); 25 | }); 26 | 27 | it('ignores non-delegated api message', () => { 28 | const task = context.getActivityById('task'); 29 | 30 | task.run(); 31 | 32 | task.broker.publish('api', 'definition.signal.Def_1', { message: { id: 'task' } }, { type: 'cancel', delegate: false }); 33 | 34 | expect(task.status).to.equal('executing'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/tasks/ManualTask-test.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | 3 | describe('ManualTask', () => { 4 | const source = ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | `; 15 | 16 | let context; 17 | beforeEach(async () => { 18 | context = await testHelpers.context(source); 19 | }); 20 | 21 | it('keeps execute wait state until signaled', async () => { 22 | const task = context.getActivityById('task'); 23 | 24 | const waiting = task.waitFor('wait'); 25 | const left = task.waitFor('leave'); 26 | task.run(); 27 | 28 | const waitApi = await waiting; 29 | 30 | expect(task.broker.getQueue('run-q').messageCount, 'run queue').to.equal(1); 31 | expect(task.broker.getQueue('execute-q').messageCount, 'execute queue').to.equal(1); 32 | 33 | waitApi.signal(); 34 | 35 | await left; 36 | 37 | expect(task.broker.getQueue('run-q').messageCount, 'run queue').to.equal(0); 38 | expect(task.broker.getQueue('execute-q').messageCount, 'execute queue').to.equal(0); 39 | }); 40 | 41 | it('sets output and completes when signaled', async () => { 42 | const task = context.getActivityById('task'); 43 | 44 | const waiting = task.waitFor('wait'); 45 | const leave = task.waitFor('leave'); 46 | task.activate(); 47 | task.run(); 48 | 49 | const taskApi = await waiting; 50 | taskApi.signal({ data: 1 }); 51 | 52 | const api = await leave; 53 | 54 | expect(api.content.output).to.eql({ data: 1 }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/tasks/SendTask-test.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | 3 | describe('SendTask', () => { 4 | it('behaves like service task', async () => { 5 | const source = ` 6 | 7 | 8 | 9 | 10 | 11 | `; 12 | 13 | const context = await testHelpers.context(source); 14 | context.environment.addService('get', (...args) => { 15 | args.pop()(null, true); 16 | }); 17 | 18 | const task = context.getActivityById('task'); 19 | 20 | task.activate(); 21 | 22 | const leave = task.waitFor('leave'); 23 | 24 | task.run(); 25 | 26 | const api = await leave; 27 | 28 | expect(api.content.output).to.eql([true]); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/tasks/Task-test.js: -------------------------------------------------------------------------------- 1 | import testHelpers from '../helpers/testHelpers.js'; 2 | 3 | describe('Task', () => { 4 | const source = ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | `; 15 | 16 | let context; 17 | beforeEach(async () => { 18 | context = await testHelpers.context(source); 19 | }); 20 | 21 | it('completes immediately when executed', async () => { 22 | const task = context.getActivityById('task'); 23 | 24 | const left = task.waitFor('leave'); 25 | task.run(); 26 | 27 | await left; 28 | 29 | expect(task.broker.getQueue('run-q').messageCount, 'run queue').to.equal(0); 30 | expect(task.broker.getQueue('execute-q').messageCount, 'execute queue').to.equal(0); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /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/types.js"] 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------