├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── release-drafter.yml
└── workflows
│ ├── build-docs.yml
│ ├── tag-and-publish.yml
│ ├── test-camunda-saas-push.yml
│ └── test.yml
├── .gitignore
├── .husky
├── pre-commit
└── prepare-commit-msg
├── .npmignore
├── .npmrc
├── .nvmrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOP.md
├── LICENSE
├── README.md
├── bin
└── zeebe-node
├── design
├── ZBBatchWorker-state-machine.bpmn
├── grpc-error-handling.md
└── grpc-state-machine.bpmn
├── docker
├── application.yaml
└── docker-compose.yml
├── docs
├── assets
│ ├── css
│ │ └── main.css
│ ├── images
│ │ ├── icons.png
│ │ ├── icons@2x.png
│ │ ├── widgets.png
│ │ └── widgets@2x.png
│ └── js
│ │ ├── main.js
│ │ └── search.js
├── classes
│ ├── BpmnParser.html
│ ├── ZBClient.html
│ ├── ZBLogger.html
│ └── ZBWorker.html
├── enums
│ ├── PartitionBrokerHealth.html
│ └── PartitionBrokerRole.html
├── index.html
├── interfaces
│ ├── ActivateInstruction.html
│ ├── ActivateJobsRequest.html
│ ├── ActivateJobsResponse.html
│ ├── ActivatedJob.html
│ ├── BasicAuthConfig.html
│ ├── BrokerInfo.html
│ ├── CamundaCloudConfig.html
│ ├── CompleteJobRequest.html
│ ├── CreateProcessBaseRequest.html
│ ├── CreateProcessInstanceBaseRequest.html
│ ├── CreateProcessInstanceReq.html
│ ├── CreateProcessInstanceRequest.html
│ ├── CreateProcessInstanceResponse.html
│ ├── CreateProcessInstanceWithResultReq.html
│ ├── CreateProcessInstanceWithResultRequest.html
│ ├── CreateProcessInstanceWithResultResponse.html
│ ├── DecisionDeployment.html
│ ├── DecisionMetadata.html
│ ├── DecisionRequirementsDeployment.html
│ ├── DecisionRequirementsMetadata.html
│ ├── DeployProcessBuffer.html
│ ├── DeployProcessRequest.html
│ ├── DeployProcessResponse.html
│ ├── DeployResourceRequest.html
│ ├── DeployResourceResponse.html
│ ├── EvaluateDecisionRequest.html
│ ├── EvaluateDecisionResponse.html
│ ├── EvaluatedDecision.html
│ ├── EvaluatedDecisionInput.html
│ ├── EvaluatedDecisionOutput.html
│ ├── FailJobRequest.html
│ ├── ICustomHeaders.html
│ ├── IInputVariables.html
│ ├── IOutputVariables.html
│ ├── IProcessVariables.html
│ ├── JSONDoc.html
│ ├── Job.html
│ ├── JobCompletionInterface.html
│ ├── JobFailureConfiguration.html
│ ├── KeyedObject.html
│ ├── ListProcessResponse.html
│ ├── ModifyProcessInstanceRequest.html
│ ├── ModifyProcessInstanceResponse.html
│ ├── OperationOptionsNoRetry.html
│ ├── OperationOptionsWithRetry.html
│ ├── Partition.html
│ ├── ProcessDeployment.html
│ ├── ProcessInstanceCreationStartInstruction.html
│ ├── ProcessMetadata.html
│ ├── ProcessRequestObject.html
│ ├── PublishMessageRequest.html
│ ├── PublishMessageResponse.html
│ ├── PublishStartMessageRequest.html
│ ├── ResolveIncidentRequest.html
│ ├── Resource.html
│ ├── SetVariablesRequest.html
│ ├── TerminateInstruction.html
│ ├── ThrowErrorRequest.html
│ ├── TopologyResponse.html
│ ├── UpdateJobRetriesRequest.html
│ ├── VariableInstruction.html
│ ├── ZBBatchWorkerConfig.html
│ ├── ZBClientOptions.html
│ ├── ZBCustomLogger.html
│ ├── ZBGrpc.html
│ ├── ZBLogMessage.html
│ ├── ZBLoggerConfig.html
│ ├── ZBLoggerOptions.html
│ ├── ZBWorkerBaseConfig.html
│ ├── ZBWorkerConfig.html
│ ├── ZBWorkerOptions.html
│ └── ZeebeJob.html
└── modules.html
├── example
├── README.md
├── connector.js
├── connector_test.bpmn
├── process.js
├── start-process-instance.js
├── test.bpmn
└── worker.js
├── interfaces
└── index.d.ts
├── jest.config.js
├── package-lock.json
├── package.json
├── prettier.config.js
├── proto
└── zeebe.proto
├── src
├── .editorconfig
├── __mocks__
│ ├── fs.ts
│ └── zbClientOptions.ts
├── __tests__
│ ├── BpmnParser.spec.ts
│ ├── ConfigurationHydrator.spec.ts
│ ├── OAuthProvider.spec.ts
│ ├── StdOut.spec.ts
│ ├── ZBClient-unmocked.spec.ts
│ ├── ZBClient.spec.ts
│ ├── __snapshots__
│ │ └── ZBClient-unmocked.spec.ts.snap
│ ├── disconnection
│ │ └── disconnect.spec.ts
│ ├── integration
│ │ ├── Client-BroadcastSignal.spec.ts
│ │ ├── Client-BrokenBpmn.spec.ts
│ │ ├── Client-ConnectionError.spec.ts
│ │ ├── Client-CreateProcessInstanceWithResult.spec.ts
│ │ ├── Client-DeployProcess.spec.ts
│ │ ├── Client-DeployResource.spec.ts
│ │ ├── Client-EvaluateDecision.spec.ts
│ │ ├── Client-MessageStart.spec.ts
│ │ ├── Client-ModifyProcessInstance.spec.ts
│ │ ├── Client-PublishMessage.spec.ts
│ │ ├── Client-ThrowError.spec.ts
│ │ ├── Client-integration.spec.ts
│ │ ├── Client-onReady.spec.ts
│ │ ├── Client-setVariables.spec.ts
│ │ ├── Client-startProcess.spec.ts
│ │ ├── Topology.spec.ts
│ │ ├── TypeSurfaceAreaTest.spec.ts
│ │ ├── Worker-1.0-complete.spec.ts
│ │ ├── Worker-BatchWorker.spec.ts
│ │ ├── Worker-Failure-Backoff-Retry.spec.ts
│ │ ├── Worker-Failure-Retries.spec.ts
│ │ ├── Worker-Failure.spec.ts
│ │ ├── Worker-LongPoll.spec.ts
│ │ ├── Worker-RaiseIncident.spec.ts
│ │ ├── Worker-fetchVariable.spec.ts
│ │ ├── Worker-integration.spec.ts
│ │ └── Worker-onReady.spec.ts
│ ├── local-integration
│ │ └── OnConnectionError.spec.ts
│ ├── stringifyVariables.spec.ts
│ └── testdata
│ │ ├── BpmnParser.bpmn
│ │ ├── BpmnParser2.bpmn
│ │ ├── Client-BrokenBpmn.bpmn
│ │ ├── Client-DeployWorkflow.bpmn
│ │ ├── Client-MessageStart.bpmn
│ │ ├── Client-SkipFirstTask.bpmn
│ │ ├── Client-ThrowError.bpmn
│ │ ├── Signal.bpmn
│ │ ├── Worker-Failure-Retries.bpmn
│ │ ├── Worker-Failure1.bpmn
│ │ ├── Worker-Failure2.bpmn
│ │ ├── Worker-Failure3.bpmn
│ │ ├── Worker-LongPoll.bpmn
│ │ ├── Worker-RaiseIncident.bpmn
│ │ ├── await-outcome-long.bpmn
│ │ ├── await-outcome.bpmn
│ │ ├── await-outcome.bpmn.zip
│ │ ├── conditional-pathway.bpmn
│ │ ├── decision.dmn
│ │ ├── disconnection.bpmn
│ │ ├── form_1.form
│ │ ├── generic-test.bpmn
│ │ ├── hello-world-complete.bpmn
│ │ ├── hello-world.bpmn
│ │ ├── modeller7File.bpmn
│ │ └── quarantine-duration.dmn
├── index.ts
├── lib
│ ├── BpmnParser.ts
│ ├── ConfigurationHydrator.ts
│ ├── ConnectionFactory.ts
│ ├── EnvFunction.ts
│ ├── GrpcClient.ts
│ ├── GrpcError.ts
│ ├── GrpcMiddleware.ts
│ ├── JobBatcher.ts
│ ├── MockStdOut.ts
│ ├── OAuthProvider.ts
│ ├── Queue.ts
│ ├── SimpleLogger.ts
│ ├── StatefulLogInterceptor.ts
│ ├── TypedEmitter.ts
│ ├── ZBJsonLogger.ts
│ ├── ZBLogger.ts
│ ├── ZBWorkerBase.ts
│ ├── ZBWorkerSignature.ts
│ ├── cancelProcesses.ts
│ ├── createUniqueTaskType.ts
│ ├── deployWorkflow
│ │ ├── impure.ts
│ │ └── pure.ts
│ ├── index.ts
│ ├── interfaces-1.0.ts
│ ├── interfaces-grpc-1.0.ts
│ ├── interfaces-published-contract.ts
│ ├── stringifyVariables.ts
│ └── utils.ts
├── tsconfig.json
└── zb
│ ├── ZBBatchWorker.ts
│ ├── ZBClient.ts
│ └── ZBWorker.ts
├── tsconfig.json
└── tslint.json
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Expected Behavior
4 |
5 |
6 | ## Current Behavior
7 |
8 |
9 | ## Possible Solution
10 |
11 |
12 | ## Steps to Reproduce
13 |
14 |
15 | 1.
16 | 2.
17 | 3.
18 | 4.
19 |
20 | ## Context (Environment)
21 |
22 |
23 |
24 |
25 |
26 | ## Detailed Description
27 |
28 |
29 | ## Possible Implementation
30 |
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Fixes #
2 |
3 | ## Proposed Changes
4 |
5 | -
6 | -
7 | -
8 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: "v$RESOLVED_VERSION 🌈"
2 | tag-template: "v$RESOLVED_VERSION"
3 | categories:
4 | - title: "🚀 Features"
5 | labels:
6 | - "feature"
7 | - "enhancement"
8 | - title: "🐛 Bug Fixes"
9 | labels:
10 | - "fix"
11 | - "bugfix"
12 | - "bug"
13 | - title: "🧰 Maintenance"
14 | label: "chore"
15 | - title: "Breaking Changes"
16 | label: "breaking"
17 | - title: "Deprecations"
18 | label: "deprecation"
19 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
20 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
21 | version-resolver:
22 | major:
23 | labels:
24 | - "major"
25 | minor:
26 | labels:
27 | - "minor"
28 | patch:
29 | labels:
30 | - "patch"
31 | default: patch
32 | template: |
33 | ## Changes
34 |
35 | $CHANGES
36 |
--------------------------------------------------------------------------------
/.github/workflows/build-docs.yml:
--------------------------------------------------------------------------------
1 | name: Build and publish docs
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: 'Version'
8 | required: true
9 |
10 | jobs:
11 | tag-and-publish:
12 | runs-on: ubuntu-latest
13 | services:
14 | elasticsearch:
15 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
16 | env:
17 | discovery.type: single-node
18 | cluster.name: docker-cluster
19 | bootstrap.memory_lock: true
20 | xpack.security.enabled: false
21 | ES_JAVA_OPTS: -Xms1024m -Xmx1024m
22 | ports:
23 | - 9200:9200
24 | - 9300:9300
25 | zeebe:
26 | image: camunda/zeebe:8.3.3
27 | env:
28 | JAVA_TOOL_OPTIONS: "-Xms512m -Xmx512m"
29 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_CLASSNAME: io.camunda.zeebe.exporter.ElasticsearchExporter
30 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_URL: http://elasticsearch:9200
31 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_BULK_SIZE: 1
32 | ZEEBE_BROKER_BACKPRESSURE_ENABLED: false
33 | ports:
34 | - 26500:26500
35 | steps:
36 | - name: Checkout repository
37 | uses: actions/checkout@v3
38 | with:
39 | fetch-depth: 0
40 | - name: Setup Node
41 | uses: actions/setup-node@v3
42 | with:
43 | node-version: 16.17.0
44 | cache: 'npm'
45 | - name: Install
46 | run: npm ci --ignore-scripts
47 | - name: Run integration tests
48 | run: npm run test:integration
49 | - name: Run tests
50 | run: npm run test
51 | - name: Run local integration tests
52 | run: npm run test:local
53 | - name: Build
54 | run: npm run build
55 | - name: Set version
56 | run: |
57 | git config user.name 'github-actions[bot]'
58 | git config user.email 'github-actions[bot]@users.noreply.github.com'
59 |
60 | - name: Build Docs
61 | run: npm run docs
62 |
63 | - name: Deploy Docs
64 | uses: JamesIves/github-pages-deploy-action@v4
65 | with:
66 | folder: docs
67 | branch: gh-pages
68 |
--------------------------------------------------------------------------------
/.github/workflows/tag-and-publish.yml:
--------------------------------------------------------------------------------
1 | name: Tag and publish a new version
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: 'Version'
8 | required: true
9 |
10 | jobs:
11 | tag-and-publish:
12 | runs-on: ubuntu-latest
13 | services:
14 | elasticsearch:
15 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
16 | env:
17 | discovery.type: single-node
18 | cluster.name: docker-cluster
19 | bootstrap.memory_lock: true
20 | xpack.security.enabled: false
21 | ES_JAVA_OPTS: -Xms1024m -Xmx1024m
22 | ports:
23 | - 9200:9200
24 | - 9300:9300
25 | zeebe:
26 | image: camunda/zeebe:8.3.3
27 | env:
28 | JAVA_TOOL_OPTIONS: "-Xms512m -Xmx512m"
29 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_CLASSNAME: io.camunda.zeebe.exporter.ElasticsearchExporter
30 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_URL: http://elasticsearch:9200
31 | ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_BULK_SIZE: 1
32 | ZEEBE_BROKER_BACKPRESSURE_ENABLED: false
33 | ports:
34 | - 26500:26500
35 | steps:
36 | - name: Checkout repository
37 | uses: actions/checkout@v3
38 | with:
39 | fetch-depth: 0
40 | - name: Setup Node
41 | uses: actions/setup-node@v3
42 | with:
43 | node-version: 16.17.0
44 | cache: 'npm'
45 | - name: Install
46 | run: npm ci --ignore-scripts
47 | - name: Run integration tests
48 | run: npm run test:integration
49 | - name: Run tests
50 | run: npm run test
51 | - name: Run local integration tests
52 | run: npm run test:local
53 | - name: Build
54 | run: npm run build
55 | - name: Set version
56 | run: |
57 | git config user.name 'github-actions[bot]'
58 | git config user.email 'github-actions[bot]@users.noreply.github.com'
59 | npm version ${{ inputs.version }} -m "Publish v%s"
60 | - name: Publish to NPM
61 | env:
62 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
63 | run: npm publish --access=public
64 | - name: Push changes
65 | run: git push --follow-tags
66 |
67 | - name: Build Docs
68 | run: npm run docs
69 |
70 | - name: Deploy Docs
71 | uses: JamesIves/github-pages-deploy-action@v4
72 | with:
73 | folder: docs
74 | branch: gh-pages
75 |
--------------------------------------------------------------------------------
/.github/workflows/test-camunda-saas-push.yml:
--------------------------------------------------------------------------------
1 | name: "Test on Camunda 8 SaaS"
2 | on: # main branch changes
3 | push:
4 | jobs:
5 | test-on-saas:
6 | runs-on: ubuntu-latest
7 | timeout-minutes: 10
8 | steps:
9 | - uses: actions/checkout@v1
10 | - run: |
11 | npm install
12 | npm run build
13 | npm run test:integration
14 | env:
15 | ZEEBE_ADDRESS: ${{ secrets.ZEEBE_ADDRESS }}
16 | ZEEBE_CLIENT_ID: ${{ secrets.ZEEBE_CLIENT_ID }}
17 | ZEEBE_AUTHORIZATION_SERVER_URL: ${{ secrets.ZEEBE_AUTHORIZATION_SERVER_URL }}
18 | ZEEBE_CLIENT_SECRET: ${{ secrets.ZEEBE_CLIENT_SECRET }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 | jobs:
5 | test:
6 | name: test
7 | runs-on: ubuntu-latest
8 | services:
9 | zeebe:
10 | image: camunda/zeebe:8.3.3
11 | ports:
12 | - 26500:26500
13 | steps:
14 | - name: Checkout repository
15 | uses: actions/checkout@master
16 | - name: Set up Node.js
17 | uses: actions/setup-node@master
18 | with:
19 | node-version: 16.17.0
20 | - run: npm install
21 | - run: npm run test:docker
22 | # env:
23 | # ZEEBE_ADDRESS: ${{ secrets.ZEEBE_ADDRESS }}
24 | # ZEEBE_CLIENT_ID: ${{ secrets.ZEEBE_CLIENT_ID }}
25 | # ZEEBE_AUTHORIZATION_SERVER_URL: ${{ secrets.ZEEBE_AUTHORIZATION_SERVER_URL }}
26 | # ZEEBE_CLIENT_SECRET: ${{ secrets.ZEEBE_CLIENT_SECRET }}
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .vscode
5 | cctest.ts
6 | t.ts
7 | yolo.bpmn
8 | crash-*.log
9 | camunda-cloud.env
10 | tsconfig.tsbuildinfo
11 | .env
12 | ignore-this
13 | .dccache
14 | target/npmlist.json
15 | *.env
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm test && npm run test:disconnect && npm run-script build && npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | exec < /dev/tty && git cz --hook || true
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github
2 | .circleci
3 | guide
4 | src
5 | design
6 | docs
7 | .env
8 | *.env
9 | .vscode
10 | .husky
11 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
2 | //registry.npmjs.org/:_authToken=${NPM_TOKEN}
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.17.0
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at code-of-conduct@zeebe.io. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
--------------------------------------------------------------------------------
/DEVELOP.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | _Development conventions._
4 |
5 | ## Creating a new branch
6 |
7 | Please create a new branch for each feature or fix, and branch from `master`.
8 |
9 | ## Precommit hooks
10 |
11 | There is a precommit hook that runs the disconnection test, which requires Docker to be installed and for no Zeebe container to be running.
12 |
13 | ## Commit messages
14 |
15 | We use [AngularJS's commit message conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines), enforced by [commitizen](https://github.com/commitizen/cz-cli). This is used by [semantic-release](https://www.npmjs.com/package/semantic-release) to automate releases to NPM when PRs are merged into master.
16 |
17 | Run `git commit` without the `-m` flag to get a wizard that will guide you through creating an appropriate commit message.
18 |
19 | ## Pull Requests
20 |
21 | Please [squash and rebase commits on master](https://blog.carbonfive.com/always-squash-and-rebase-your-git-commits/).
22 |
23 | Pull Requests must be labelled before they are merged to master. This is used by [Release Drafter](https://github.com/release-drafter/release-drafter#readme) to automate Release Notes on GitHub.
24 |
25 | ## Publishing a new npm package
26 |
27 | The npm package publishing is handled by a GitHub Workflow using [semantic-release](https://www.npmjs.com/package/semantic-release).
28 |
29 | A new package will be released whenever a PR is merged into master.
30 |
31 | ## Disconnection test
32 |
33 | There is a test that checks that the client can reconnect to a rescheduled broker, or a broker that starts after the client application.
34 |
35 | It can be run with `npm run test:disconnection` and it is run automatically when committing changes. It requires Docker to be running on your machine.
36 |
37 | ## Debug logging
38 |
39 | Try this for the insane level of trace logging:
40 |
41 | ```
42 | DEBUG=grpc,worker GRPC_TRACE=all GRPC_VERBOSITY=DEBUG jest Worker-LongPoll --detectOpenHandles
43 | ```
44 |
45 | To log from the Node engine itself (useful in tracking down grpc issues in grpc-js):
46 |
47 | ```
48 | NODE_DEBUG=http2 GRPC_TRACE=channel,call_stream GRPC_VERBOSITY=DEBUG ZEEBE_NODE_LOGLEVEL=debug ZEEBE_NODE_PUREJS=true npm run test:integration
49 | ```
50 |
51 | To get extended stack traces:
52 |
53 | ```
54 | NODE_DEBUG=http2 GRPC_TRACE=channel,call_stream ZEEBE_NODE_PUREJS=true node --expose-internals --expose-gc node_modules/.bin/jest --runInBand --testPathIgnorePatterns disconnection --detectOpenHandles --verbose true Worker-Failure
55 | ```
56 |
57 | ```
58 | valgrind node --expose-internals --expose-gc node_modules/.bin/jest --runInBand --testPathIgnorePatterns disconnection --detectOpenHandles Worker-Failure
59 | ```
60 |
61 | ## Scaffold a Ubuntu machine for dev
62 |
63 | sudo apt install -y build-essential
64 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
65 | source ~/.bashrc
66 | nvm install 14
67 | git clone https://github.com/camunda-community-hub/zeebe-client-node-js.git
68 | cd zeebe-client-node-js
69 | npm i
70 |
71 | # Set Camunda Cloud env variables
72 |
73 | ZEEBE_NODE_PUREJS=true node_modules/.bin/jest Worker-Failure
74 |
--------------------------------------------------------------------------------
/bin/zeebe-node:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const ZB = require('../dist')
3 |
4 | const workflowFile = Array.from(process.argv)[2]
5 | console.log(workflowFile)
6 | ;(async () => {
7 | console.log(await ZB.BpmnParser.scaffold(workflowFile))
8 | })()
9 |
--------------------------------------------------------------------------------
/design/grpc-error-handling.md:
--------------------------------------------------------------------------------
1 | # GRPC Channel Error Handling
2 |
3 | There are a few things that can go wrong on the Grpc channel:
4 |
5 | - **No resolvable DNS address**. In this case, the stream emits an error with `code`: `14`, and `details`: `Name resolution failed for target nobroker:26500`.
6 | - **Resolvable address, but no broker**
7 | - **Broker goes away**. This can be due to a Docker restart, or a K8s pod reschedule (for example, in Camunda Cloud).
8 | - **Intermittent Network Error**
9 | - **Business Error**. For example:
10 | - **Broker backpressure**. This returns error code 8.
11 |
--------------------------------------------------------------------------------
/docker/application.yaml:
--------------------------------------------------------------------------------
1 | # Zeebe Standalone Broker configuration file (with embedded gateway)
2 |
3 | # This file is based on broker.standalone.yaml.template but stripped down to contain only a limited
4 | # set of configuration options. These are a good starting point to get to know Zeebe.
5 | # For advanced configuration options, have a look at the templates in this folder.
6 |
7 | # !!! Note that this configuration is not suitable for running a standalone gateway. !!!
8 | # If you want to run a standalone gateway node, please have a look at gateway.yaml.template
9 |
10 | # ----------------------------------------------------
11 | # Byte sizes
12 | # For buffers and others must be specified as strings and follow the following
13 | # format: "10U" where U (unit) must be replaced with KB = Kilobytes, MB = Megabytes or GB = Gigabytes.
14 | # If unit is omitted then the default unit is simply bytes.
15 | # Example:
16 | # sendBufferSize = "16MB" (creates a buffer of 16 Megabytes)
17 | #
18 | # Time units
19 | # Timeouts, intervals, and the likes, must be specified either in the standard ISO-8601 format used
20 | # by java.time.Duration, or as strings with the following format: "VU", where:
21 | # - V is a numerical value (e.g. 1, 5, 10, etc.)
22 | # - U is the unit, one of: ms = Millis, s = Seconds, m = Minutes, or h = Hours
23 | #
24 | # Paths:
25 | # Relative paths are resolved relative to the installation directory of the broker.
26 | # ----------------------------------------------------
27 |
28 | zeebe:
29 | broker:
30 | gateway:
31 | # Enable the embedded gateway to start on broker startup.
32 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_GATEWAY_ENABLE.
33 | enable: true
34 |
35 | network:
36 | # Sets the port the embedded gateway binds to.
37 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_GATEWAY_NETWORK_PORT.
38 | port: 26500
39 |
40 | security:
41 | # Enables TLS authentication between clients and the gateway
42 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_GATEWAY_SECURITY_ENABLED.
43 | enabled: false
44 |
45 | network:
46 | # Controls the default host the broker should bind to. Can be overwritten on a
47 | # per binding basis for client, management and replication
48 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_NETWORK_HOST.
49 | host: 0.0.0.0
50 |
51 | data:
52 | # Specify a list of directories in which data is stored.
53 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_DATA_DIRECTORIES.
54 | directories: [ data ]
55 | # The size of data log segment files.
56 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_DATA_LOGSEGMENTSIZE.
57 | logSegmentSize: 512MB
58 | # How often we take snapshots of streams (time unit)
59 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_DATA_SNAPSHOTPERIOD.
60 | snapshotPeriod: 15m
61 |
62 | cluster:
63 | # Specifies the Zeebe cluster size.
64 | # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_CLUSTERSIZE.
65 | clusterSize: 1
66 | # Controls the replication factor, which defines the count of replicas per partition.
67 | # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_REPLICATIONFACTOR.
68 | replicationFactor: 1
69 | # Controls the number of partitions, which should exist in the cluster.
70 | # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_PARTITIONSCOUNT.
71 | partitionsCount: 1
72 |
73 | threads:
74 | # Controls the number of non-blocking CPU threads to be used.
75 | # WARNING: You should never specify a value that is larger than the number of physical cores
76 | # available. Good practice is to leave 1-2 cores for ioThreads and the operating
77 | # system (it has to run somewhere). For example, when running Zeebe on a machine
78 | # which has 4 cores, a good value would be 2.
79 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_THREADS_CPUTHREADCOUNT
80 | cpuThreadCount: 2
81 | # Controls the number of io threads to be used.
82 | # This setting can also be overridden using the environment variable ZEEBE_BROKER_THREADS_IOTHREADCOUNT
83 | ioThreadCount: 2
84 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | services:
4 | zeebe:
5 | restart: always
6 | container_name: zeebe_broker
7 | image: camunda/zeebe:8.3.3
8 | environment:
9 | - ZEEBE_LOG_LEVEL=debug
10 | volumes:
11 | - ./application.yaml:/usr/local/zeebe/config/application.yaml
12 | ports:
13 | - "26500:26500"
14 | - "9600:9600"
15 |
--------------------------------------------------------------------------------
/docs/assets/images/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/camunda-community-hub/zeebe-client-node-js/7969ce1808c96a87519cb1a3f279287f30637c4b/docs/assets/images/icons.png
--------------------------------------------------------------------------------
/docs/assets/images/icons@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/camunda-community-hub/zeebe-client-node-js/7969ce1808c96a87519cb1a3f279287f30637c4b/docs/assets/images/icons@2x.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/camunda-community-hub/zeebe-client-node-js/7969ce1808c96a87519cb1a3f279287f30637c4b/docs/assets/images/widgets.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/camunda-community-hub/zeebe-client-node-js/7969ce1808c96a87519cb1a3f279287f30637c4b/docs/assets/images/widgets@2x.png
--------------------------------------------------------------------------------
/docs/interfaces/ICustomHeaders.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ICustomHeaders | zeebe-node
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | - Preparing search index...
24 | - The search index is not available
25 |
26 |
zeebe-node
27 |
28 |
48 |
49 |
50 |
51 |
52 |
53 |
61 |
Interface ICustomHeaders
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Hierarchy
70 |
71 | -
72 | ICustomHeaders
73 |
74 |
75 |
76 |
77 | Indexable
78 | [key: string]: any
79 |
80 |
81 |
101 |
102 |
103 |
134 |
135 |
Generated using TypeDoc
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/docs/interfaces/KeyedObject.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KeyedObject | zeebe-node
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | - Preparing search index...
24 | - The search index is not available
25 |
26 |
zeebe-node
27 |
28 |
48 |
49 |
50 |
51 |
52 |
53 |
61 |
Interface KeyedObject
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Hierarchy
70 |
71 | -
72 | KeyedObject
73 |
74 |
75 |
76 |
77 | Indexable
78 | [key: string]: any
79 |
80 |
81 |
101 |
102 |
103 |
134 |
135 |
Generated using TypeDoc
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/docs/interfaces/ModifyProcessInstanceResponse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ModifyProcessInstanceResponse | zeebe-node
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | - Preparing search index...
24 | - The search index is not available
25 |
26 |
zeebe-node
27 |
28 |
48 |
49 |
50 |
51 |
52 |
53 |
61 |
Interface ModifyProcessInstanceResponse
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Hierarchy
70 |
71 | -
72 | ModifyProcessInstanceResponse
73 |
74 |
75 |
76 |
77 |
97 |
98 |
99 |
130 |
131 |
Generated using TypeDoc
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example Code
2 |
3 | ## Deploy Example Process
4 |
5 | To deploy the example process:
6 |
7 | ```bash
8 | node process.js
9 | ```
10 |
11 | ## Start Example Worker
12 |
13 | To start a example worker:
14 |
15 | ```bash
16 | node worker.js
17 | ```
18 |
19 | ## Create Example Process Instances
20 |
21 | To create example process instances:
22 |
23 | ```bash
24 | node start-process-instance.js
25 | ```
26 |
--------------------------------------------------------------------------------
/example/connector.js:
--------------------------------------------------------------------------------
1 | const { ZBClient } = require('../dist');
2 |
3 | const zbc = new ZBClient({
4 | // loglevel: 'DEBUG',
5 | // camundaCloud: {
6 | // clientId: process.env.CLIENT_ID,
7 | // clientSecret: process.env.CLIENT_SECRET,
8 | // clusterId: process.env.CLUSTER_ID,
9 | // clusterRegion: process.env.CLUSTER_REGION,
10 | // },
11 | });
12 |
13 | zbc.deployProcess('./connector_test.bpmn').then(res => {
14 | console.log(res);
15 | zbc.createProcessInstance('connectortest1', {}).then(console.log);
16 | });
17 |
18 | let lastJobReceived = new Date();
19 |
20 | const zbWorker = zbc.createWorker({
21 | taskType: 'camunda-cloud-connector',
22 | taskHandler: (job) => {
23 | const now = new Date();
24 | zbWorker.log(
25 | `received new zb job! Seconds passed since last receive: ${
26 | (now.valueOf() - lastJobReceived.valueOf()) / 1000
27 | }`
28 | );
29 | lastJobReceived = new Date();
30 | return job.complete();
31 | },
32 | });
33 |
34 | zbWorker.on('ready', () => {
35 | zbWorker.log('Zeebe worker is ready!');
36 | });
37 |
--------------------------------------------------------------------------------
/example/process.js:
--------------------------------------------------------------------------------
1 | // const ZB = require('zeebe-node');
2 | const ZB = require('../dist')
3 |
4 | ; (async () => {
5 | const zbc = new ZB.ZBClient({
6 | onConnectionError: () => console.log('Connection Error'),
7 | onReady: () => console.log('Ready to work'),
8 | })
9 | const topology = await zbc.topology()
10 | console.log(JSON.stringify(topology, null, 2))
11 |
12 | const res = await zbc.deployProcess('./test.bpmn')
13 | setTimeout(() => console.log(res), 5000)
14 | })()
15 |
--------------------------------------------------------------------------------
/example/start-process-instance.js:
--------------------------------------------------------------------------------
1 | // const ZB = require('zeebe-node');
2 | const ZB = require('../dist')
3 |
4 | const jobs = (async () => {
5 | const zbc = new ZB.ZBClient()
6 | for (let i = 0; i < 10; i++) {
7 | const result = await zbc.createProcessInstance('test-process', {
8 | testData: `process #${i}`,
9 | })
10 | console.log(result)
11 | }
12 | })()
13 |
--------------------------------------------------------------------------------
/example/worker.js:
--------------------------------------------------------------------------------
1 | // const ZB = require('zeebe-node');
2 | const ZB = require('../dist')
3 |
4 | ;(async () => {
5 | const zbc = new ZB.ZBClient()
6 | const topology = await zbc.topology()
7 | console.log(JSON.stringify(topology, null, 2))
8 |
9 | await zbc.deployProcess('./test.bpmn')
10 |
11 | zbc.createWorker({
12 | taskType: 'demo-service',
13 | taskHandler: job => {
14 | console.log(job.variables)
15 | job.complete()
16 | },
17 | }) // handler)
18 |
19 | setTimeout(() => {
20 | console.log('Closing client...')
21 | zbc.close().then(() => console.log('All workers closed'))
22 | }, 10 * 60 * 1000)
23 | })()
24 |
--------------------------------------------------------------------------------
/interfaces/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from '../dist/lib/interfaces-1.0'
2 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | testPathIgnorePatterns: ['node_modules', 'dist'],
5 | collectCoverageFrom: ['!src/__tests__/lib/cancelProcesses.ts']
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zeebe-node",
3 | "version": "8.3.2",
4 | "description": "The Node.js client library for the Zeebe Workflow Automation Engine.",
5 | "keywords": [
6 | "zeebe",
7 | "zeebe.io",
8 | "microservices",
9 | "orchestration",
10 | "bpmn",
11 | "conductor",
12 | "Camunda",
13 | "Netflix",
14 | "cloud",
15 | "automation",
16 | "process",
17 | "workflow",
18 | "Uber",
19 | "Cadence"
20 | ],
21 | "homepage": "https://camunda-community-hub.github.io/zeebe-client-node-js/",
22 | "bugs": {
23 | "url": "https://github.com/camunda-community-hub/zeebe-client-node-js/issues"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/camunda-community-hub/zeebe-client-node-js"
28 | },
29 | "main": "dist/index.js",
30 | "bin": {
31 | "zeebe-node": "bin/zeebe-node"
32 | },
33 | "scripts": {
34 | "cm": "cz",
35 | "debug": "NODE_DEBUG=http2 GRPC_TRACE=channel,call_stream GRPC_VERBOSITY=DEBUG ../node/out/Debug/node --inspect-brk node_modules/.bin/jest Worker-Failure",
36 | "docs": "typedoc --tsconfig src/tsconfig.json src/index.ts",
37 | "build": "tsc --build src/tsconfig.json",
38 | "watch": "tsc --build src/tsconfig.json -w",
39 | "watch-docs": "typedoc --watch --tsconfig src/tsconfig.json src/index.ts",
40 | "prepare": "husky install && npm run build",
41 | "test": "jest --detectOpenHandles --testPathIgnorePatterns integration local-integration disconnection",
42 | "test:integration": "jest --runInBand --testPathIgnorePatterns disconnection --detectOpenHandles --verbose true",
43 | "test:local": "jest --runInBand --verbose true --detectOpenHandles local-integration",
44 | "test:docker": "jest --runInBand --testPathIgnorePatterns disconnection local-integration --detectOpenHandles --verbose true",
45 | "test:disconnect": "jest --runInBand --verbose true --detectOpenHandles disconnection",
46 | "test&docs": "npm test && npm run docs",
47 | "dev": "tsc-watch --onSuccess \"npm run test&docs\" -p tsconfig.json --outDir dist"
48 | },
49 | "lint-staged": {
50 | "*.{md,markdown}": [
51 | "prettier --write",
52 | "remark",
53 | "git add"
54 | ],
55 | "*.{json,css,html,yaml,yml}": [
56 | "prettier --write",
57 | "git add"
58 | ],
59 | "*.{ts,tsx,js,jsx}": [
60 | "prettier --write",
61 | "tslint -c tslint.json --fix",
62 | "git add"
63 | ]
64 | },
65 | "dependencies": {
66 | "@grpc/grpc-js": "1.9.7",
67 | "@grpc/proto-loader": "0.7.10",
68 | "chalk": "^2.4.2",
69 | "console-stamp": "^3.0.2",
70 | "dayjs": "^1.8.15",
71 | "debug": "^4.2.0",
72 | "fast-xml-parser": "^4.1.3",
73 | "fp-ts": "^2.5.1",
74 | "got": "^11.8.5",
75 | "long": "^4.0.0",
76 | "promise-retry": "^1.1.1",
77 | "stack-trace": "0.0.10",
78 | "typed-duration": "^1.0.12",
79 | "uuid": "^7.0.3"
80 | },
81 | "devDependencies": {
82 | "@sitapati/testcontainers": "^2.8.1",
83 | "@types/debug": "0.0.31",
84 | "@types/got": "^9.6.9",
85 | "@types/jest": "^27.5.2",
86 | "@types/node": "^14.17.1",
87 | "@types/promise-retry": "^1.1.3",
88 | "@types/stack-trace": "0.0.29",
89 | "@types/uuid": "^3.4.4",
90 | "commitizen": "^4.2.4",
91 | "cz-conventional-changelog": "^3.3.0",
92 | "delay": "^4.3.0",
93 | "husky": "^7.0.0",
94 | "jest": "^27.2.3",
95 | "jest-environment-node-debug": "^2.0.0",
96 | "lint-staged": "^11.0.0",
97 | "operate-api-client": "^1.1.3",
98 | "prettier": "^1.19.1",
99 | "remark": "^13.0.0",
100 | "remark-cli": "^9.0.0",
101 | "remark-lint": "^8.0.0",
102 | "remark-preset-lint-recommended": "^5.0.0",
103 | "ts-jest": "^27.0.5",
104 | "tsc-watch": "^4.4.0",
105 | "tslint": "^6.1.3",
106 | "tslint-config-prettier": "^1.18.0",
107 | "typedoc": "^0.21.10",
108 | "typescript": "^4.4.4"
109 | },
110 | "author": {
111 | "name": "Josh Wulf",
112 | "email": "josh.wulf@camunda.com"
113 | },
114 | "contributors": [
115 | {
116 | "name": "Timothy Colbert"
117 | },
118 | {
119 | "name": "Jarred Filmer"
120 | },
121 | {
122 | "name": "Colin Raddatz"
123 | },
124 | {
125 | "name": "Olivier Albertini"
126 | },
127 | {
128 | "name": "Patrick Dehn"
129 | }
130 | ],
131 | "engines": {
132 | "node": ">=16.6.1"
133 | },
134 | "license": "Apache-2.0",
135 | "config": {
136 | "commitizen": {
137 | "path": "./node_modules/cz-conventional-changelog"
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | endOfLine: 'lf',
3 | jsxSingleQuote: true,
4 | overrides: [
5 | {
6 | files: ['*.yaml', '*.yml'],
7 | options: {
8 | singleQuote: false,
9 | tabWidth: 2,
10 | useTabs: false,
11 | },
12 | },
13 | ],
14 | semi: false,
15 | singleQuote: true,
16 | tabWidth: 4,
17 | trailingComma: 'es5',
18 | useTabs: true,
19 | }
20 |
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 |
7 | [*.{ts,js,json}]
8 | indent_style = tab
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.{py,md,markdown}]
13 | indent_style = space
14 | indent_size = 4
15 | trim_trailing_whitespace = true
16 | insert_final_newline = true
17 |
18 | [*.{yml,yaml}]
19 | indent_style = space
20 | indent_size = 2
21 | trim_trailing_whitespace = true
22 | insert_final_newline = true
23 |
--------------------------------------------------------------------------------
/src/__mocks__/fs.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | readFileSync() {
5 | return 'file-contents'
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/src/__mocks__/zbClientOptions.ts:
--------------------------------------------------------------------------------
1 | import { Loglevel } from '../lib/interfaces-published-contract'
2 |
3 | export const clientOptions = {
4 | _loglevel: 'NONE' as Loglevel,
5 | // it's a setter!
6 | set loglevel(value: Loglevel) {
7 | this._loglevel = value
8 | },
9 | get loglevel(): Loglevel {
10 | return this._loglevel
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/src/__tests__/BpmnParser.spec.ts:
--------------------------------------------------------------------------------
1 | import { BpmnParser } from '..'
2 |
3 | const testBpmnFile = __dirname + '/testdata/BpmnParser2.bpmn'
4 | const simpleTestBpmnFile = __dirname + '/testdata/BpmnParser.bpmn'
5 | const modeller7File = __dirname + '/testdata/modeller7File.bpmn'
6 |
7 | const parsed = BpmnParser.parseBpmn(testBpmnFile)
8 | const parsedSimple = BpmnParser.parseBpmn(simpleTestBpmnFile)
9 |
10 | test('parses a bpmn file to an Object', () => {
11 | expect(typeof parsed).toBe('object')
12 | expect(typeof parsedSimple).toBe('object')
13 | })
14 |
15 | test('can parse a file with a message with no name', async () => {
16 | const parsedv7 = await BpmnParser.generateConstantsForBpmnFiles(
17 | modeller7File
18 | )
19 | // console.log(parsedv7)
20 | expect(typeof parsedv7).toBe('string')
21 | })
22 |
23 | test('gets a unique list of task types when passed an object', async () => {
24 | const taskTypes = await BpmnParser.getTaskTypes(parsed)
25 | expect(taskTypes.length).toBe(2)
26 | })
27 |
28 | test('gets a list of unique task types when passed an array', async () => {
29 | const taskTypes = await BpmnParser.getTaskTypes([parsed, parsedSimple])
30 | expect(taskTypes.length).toBe(3)
31 | })
32 |
33 | test('gets a list of unique message names when passed an object', async () => {
34 | const messageNames = await BpmnParser.getMessageNames(parsed)
35 | expect(messageNames.length).toBe(2)
36 | })
37 |
38 | test('gets a list of unique message names when passed an array', async () => {
39 | const messageNames = await BpmnParser.getMessageNames([
40 | parsed,
41 | parsedSimple,
42 | ])
43 | expect(messageNames.length).toBe(3)
44 | })
45 |
46 | test('Returns a constants file for a single Bpmn file', async () => {
47 | const constants = await BpmnParser.generateConstantsForBpmnFiles(
48 | testBpmnFile
49 | )
50 | expect(constants.indexOf('console-log')).not.toBe(-1)
51 | })
52 |
53 | test('Returns a constants file for an array of Bpmn files', async () => {
54 | const constants = await BpmnParser.generateConstantsForBpmnFiles([
55 | testBpmnFile,
56 | simpleTestBpmnFile,
57 | ])
58 | expect(
59 | constants
60 | .split(' ')
61 | .join('')
62 | .split('\n')
63 | .join('')
64 | .indexOf(
65 | `exportenumMessageName{MSG_EMIT_FRAME="MSG-EMIT_FRAME",MSG_EMIT_FRAME_1="MSG-EMIT_FRAME`
66 | )
67 | ).not.toBe(-1)
68 | })
69 |
--------------------------------------------------------------------------------
/src/__tests__/StdOut.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '..'
2 | import { MockStdOut } from '../lib/MockStdOut'
3 |
4 | jest.setTimeout(15000)
5 |
6 | test('uses an injected stdout', done => {
7 | const mockStd = new MockStdOut()
8 | const z = new ZBClient({ stdout: mockStd, eagerConnection: false })
9 |
10 | // tslint:disable-next-line: no-console
11 | z.createWorker({
12 | taskType: 'test',
13 | taskHandler: job => {
14 | return job.complete()
15 | },
16 | })
17 | setTimeout(() => {
18 | z.close()
19 | }, 2000)
20 | setTimeout(() => {
21 | expect(mockStd.messages.length > 0).toBe(true)
22 | done()
23 | }, 4000)
24 | })
25 |
--------------------------------------------------------------------------------
/src/__tests__/ZBClient-unmocked.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '..'
2 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
3 |
4 | jest.setTimeout(13000)
5 |
6 | test('ZBClient constructor throws an exception when there is no broker and retry is false', async () => {
7 | const zbc = new ZBClient('localhoster', { retry: false })
8 | expect.assertions(1)
9 | try {
10 | await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
11 | } catch (e: any) {
12 | expect(e.message.indexOf('14 UNAVAILABLE:')).toEqual(0)
13 | }
14 | await zbc.close()
15 | })
16 |
17 | test('cancelProcessInstance throws an exception when workflowInstanceKey is malformed', async () => {
18 | const zbc = new ZBClient('localhoster', { retry: false })
19 | expect.assertions(1)
20 | try {
21 | await zbc.cancelProcessInstance('hello-world')
22 | } catch (e: any) {
23 | expect(e).toMatchSnapshot()
24 | }
25 | await zbc.close()
26 | })
27 |
--------------------------------------------------------------------------------
/src/__tests__/ZBClient.spec.ts:
--------------------------------------------------------------------------------
1 | import { Loglevel, ZBClient } from '..'
2 | const clientOptions = {
3 | loglevel: 'NONE' as Loglevel,
4 | }
5 | process.env.ZEEBE_NODE_LOG_LEVEL = 'NONE'
6 | const previousLogLevelEnv = process.env.ZEEBE_NODE_LOG_LEVEL
7 |
8 | beforeEach(() => {
9 | process.env.ZEEBE_NODE_LOG_LEVEL =
10 | process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
11 | })
12 | afterEach(() => {
13 | process.env.ZEEBE_NODE_LOG_LEVEL = previousLogLevelEnv
14 | })
15 | test('ZBClient constructor creates a new ZBClient', () => {
16 | const zbc = new ZBClient()
17 | expect(zbc instanceof ZBClient).toBe(true)
18 | zbc.close()
19 | })
20 | test('ZBClient constructor appends the port number 26500 to the gatewayAddress by default', () => {
21 | const zbc = new ZBClient('localhost')
22 | expect(zbc.gatewayAddress).toBe('localhost:26500')
23 | zbc.close()
24 | })
25 | test('ZBClient constructor accepts a custom port number for the gatewayAddress', () => {
26 | const zbc = new ZBClient('localhost:123')
27 | expect(zbc.gatewayAddress).toBe('localhost:123')
28 | zbc.close()
29 | })
30 | test('ZBClient constructor takes client options passed in Ctor when ZEEBE_NODE_LOG_LEVEL is not defined', () => {
31 | process.env.ZEEBE_NODE_LOG_LEVEL = ''
32 | clientOptions.loglevel = 'NONE'
33 | const z = new ZBClient(clientOptions)
34 | expect(z.loglevel).toBe('NONE')
35 | z.close()
36 | })
37 | test('ZEEBE_NODE_LOG_LEVEL precedes options passed in Ctor', () => {
38 | process.env.ZEEBE_NODE_LOG_LEVEL = 'NONE'
39 | clientOptions.loglevel = 'DEBUG'
40 | const z = new ZBClient(clientOptions)
41 | expect(z.loglevel).toBe('NONE')
42 | z.close()
43 | })
44 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/ZBClient-unmocked.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`cancelProcessInstance throws an exception when workflowInstanceKey is malformed 1`] = `
4 | [Error:
5 | processInstanceKey is malformed, value : hello-world
6 | ]
7 | `;
8 |
--------------------------------------------------------------------------------
/src/__tests__/disconnection/disconnect.spec.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable-next-line: no-implicit-dependencies
2 | import { GenericContainer, Wait } from '@sitapati/testcontainers'
3 | import { ZBClient } from '../..'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 |
7 | const ZEEBE_DOCKER_TAG = '8.0.2'
8 |
9 | jest.setTimeout(900000)
10 |
11 | let container
12 | let worker
13 |
14 | afterEach(async () => {
15 | await container?.stop()
16 | await worker?.close()
17 | })
18 |
19 | function log(msg) {
20 | // tslint:disable-next-line: no-console
21 | console.log(new Date().toString(), msg) // @DEBUG
22 | }
23 |
24 | test('reconnects after a pod reschedule', () =>
25 | new Promise(async resolve => {
26 | let readyCount = 0
27 | let errorCount = 0
28 | const delay = timeout =>
29 | new Promise(res => setTimeout(() => res(null), timeout))
30 |
31 | // tslint:disable-next-line: no-console
32 | log('##### Starting container (reconnects after a pod reschedule)') // @DEBUG
33 |
34 | container = await new GenericContainer(
35 | 'camunda/zeebe',
36 | ZEEBE_DOCKER_TAG,
37 | undefined,
38 | 26500
39 | )
40 | .withExposedPorts(26500)
41 | .withWaitStrategy(Wait.forLogMessage('Broker is ready!'))
42 | .start()
43 |
44 | await delay(10000)
45 |
46 | const zbc = new ZBClient(`localhost`)
47 | // tslint:disable-next-line: no-console
48 | log('##### Deploying workflow') // @DEBUG
49 |
50 | await zbc.deployProcess('./src/__tests__/testdata/disconnection.bpmn')
51 | worker = zbc
52 | .createWorker({
53 | longPoll: 10000,
54 | pollInterval: 300,
55 | taskHandler: job => {
56 | // tslint:disable-next-line: no-console
57 | log('##### Executing task handler') // @DEBUG
58 |
59 | return job.complete()
60 | },
61 | taskType: 'disconnection-task',
62 | })
63 | .on('connectionError', () => {
64 | errorCount++
65 | })
66 | .on('ready', () => {
67 | readyCount++
68 | })
69 |
70 | // tslint:disable-next-line: no-console
71 | log('##### Starting workflow') // @DEBUG
72 |
73 | const wf = await zbc.createProcessInstanceWithResult({
74 | bpmnProcessId: 'disconnection',
75 | requestTimeout: 25000,
76 | variables: {},
77 | })
78 | expect(wf.bpmnProcessId).toBeTruthy()
79 |
80 | // tslint:disable-next-line: no-console
81 | log('##### Workflow finished') // @DEBUG
82 |
83 | // tslint:disable-next-line: no-console
84 | log('##### Stopping container...') // @DEBUG
85 |
86 | await container.stop()
87 |
88 | // tslint:disable-next-line: no-console
89 | log('##### Container stopped.') // @DEBUG
90 |
91 | // tslint:disable-next-line: no-console
92 | log('##### Starting container....') // @DEBUG
93 |
94 | container = await new GenericContainer(
95 | 'camunda/zeebe',
96 | ZEEBE_DOCKER_TAG,
97 | undefined,
98 | 26500
99 | )
100 | .withExposedPorts(26500)
101 | .withEnv('ZEEBE_LOG_LEVEL', 'trace')
102 | .withWaitStrategy(Wait.forLogMessage('Broker is ready!'))
103 | .start()
104 |
105 | // tslint:disable-next-line: no-console
106 | log('##### Container started.') // @DEBUG
107 |
108 | await delay(10000)
109 |
110 | // tslint:disable-next-line: no-console
111 | log('##### Deploying workflow 2') // @DEBUG
112 | await zbc.deployProcess('./src/__tests__/testdata/disconnection.bpmn')
113 |
114 | // tslint:disable-next-line: no-console
115 | // console.log('Workflow 2 deployed', _) // @DEBUG
116 |
117 | await delay(15000)
118 |
119 | // tslint:disable-next-line: no-console
120 | log('##### Starting workflow 2') // @DEBUG
121 |
122 | const wf1 = await zbc.createProcessInstanceWithResult(
123 | 'disconnection',
124 | {}
125 | )
126 | expect(wf1.bpmnProcessId).toBeTruthy()
127 | await worker.close()
128 | await container.stop()
129 | container = undefined
130 | worker = undefined
131 | expect(readyCount).toBe(2)
132 | expect(errorCount).toBe(2) // Had to increment to 2 for the pure JS client. Investigate this later.
133 | resolve(null)
134 | }))
135 |
136 | test('a worker that started first, connects to a broker that starts later', () =>
137 | new Promise(async resolve => {
138 | let readyCount = 0
139 | let errorCount = 0
140 |
141 | const delay = timeout =>
142 | new Promise(res => setTimeout(() => res(null), timeout))
143 |
144 | const zbc = new ZBClient(`localhost`)
145 | worker = zbc
146 | .createWorker({
147 | taskHandler: job => job.complete(),
148 | taskType: 'disconnection-task',
149 | })
150 | .on('connectionError', () => {
151 | errorCount++
152 | })
153 | .on('ready', () => {
154 | readyCount++
155 | })
156 |
157 | container = await new GenericContainer(
158 | 'camunda/zeebe',
159 | ZEEBE_DOCKER_TAG,
160 | undefined,
161 | 26500
162 | )
163 | .withExposedPorts(26500)
164 | .withWaitStrategy(Wait.forLogMessage('Broker is ready!'))
165 | .start()
166 |
167 | await delay(10000)
168 |
169 | await zbc.deployProcess('./src/__tests__/testdata/disconnection.bpmn')
170 | await delay(1000) // Ensure deployment has happened
171 | const wf = await zbc.createProcessInstanceWithResult(
172 | 'disconnection',
173 | {}
174 | )
175 | expect(wf.bpmnProcessId).toBeTruthy()
176 | await worker.close()
177 | await container.stop()
178 | container = undefined
179 | worker = undefined
180 | expect(readyCount).toBe(1)
181 | expect(errorCount).toBe(1)
182 | resolve(null)
183 | }))
184 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-BroadcastSignal.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(60000)
7 |
8 |
9 | const zbc = new ZBClient()
10 | let pid: string
11 | let wf: CreateProcessInstanceResponse | undefined
12 |
13 | beforeAll(async () => {
14 | const res = await zbc.deployResource({
15 | processFilename: `./src/__tests__/testdata/Signal.bpmn`
16 | })
17 | pid = res.deployments[0].process.bpmnProcessId
18 | await cancelProcesses(pid)
19 | })
20 |
21 | afterEach(async () => {
22 | try {
23 | if (wf?.processInstanceKey) {
24 | await zbc.cancelProcessInstance(wf.processInstanceKey)
25 | }
26 | } catch (e: any) {
27 | // console.log('Caught NOT FOUND') // @DEBUG
28 | }
29 | })
30 |
31 | afterAll(async () => {
32 | await zbc.close()
33 | await cancelProcesses(pid)
34 | })
35 |
36 | test('Can start a process with a signal', () => new Promise(async resolve => {
37 | zbc.createWorker({
38 | taskType: 'signal-service-task',
39 | taskHandler: job => {
40 | const ack = job.complete()
41 | expect (job.variables.success).toBe(true)
42 | resolve(null)
43 | return ack
44 | }
45 | })
46 | await zbc.deployResource({
47 | processFilename: `./src/__tests__/testdata/Signal.bpmn`
48 | })
49 | const res = await zbc.broadcastSignal({
50 | signalName: 'test-signal',
51 | variables: {
52 | success: true
53 | }
54 | })
55 | expect(res.key).toBeTruthy()
56 | })
57 | )
58 |
59 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-BrokenBpmn.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
4 |
5 | const zbc = new ZBClient()
6 |
7 | afterAll(async () => {
8 | await zbc.close() // Makes sure we don't forget to close connection
9 | })
10 |
11 | test('does not retry the deployment of a broken BPMN file', async () => {
12 | expect.assertions(1)
13 | try {
14 | await zbc.deployProcess(
15 | './src/__tests__/testdata/Client-BrokenBpmn.bpmn'
16 | )
17 | } catch (e: any) {
18 | expect(e.message.indexOf('3 INVALID_ARGUMENT:')).toBe(0)
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-ConnectionError.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | jest.setTimeout(10000)
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 |
6 | test(`Calls the onConnectionError handler if there is no broker and eagerConnection: true`, () =>
7 | new Promise(async done => {
8 | let called = false
9 | const zbc2 = new ZBClient('localtoast: 267890', {
10 | eagerConnection: true,
11 | onConnectionError: () => {
12 | called = true
13 | },
14 | }) // Broker doesn't exist!!!
15 | setTimeout(async () => {
16 | expect(called).toBe(true)
17 | await zbc2.close()
18 | done(null)
19 | }, 7000)
20 | }))
21 |
22 | test(`Sets connected:false if there is no broker and no setting of eagerConnection`, () =>
23 | new Promise(async done => {
24 | const zbc2 = new ZBClient('localtoast: 267890') // Broker doesn't exist!!!
25 | setTimeout(async () => {
26 | expect(zbc2.connected).toBe(false)
27 | await zbc2.close()
28 | done(null)
29 | }, 5000)
30 | }))
31 |
32 | test(`Sets connected:false if there is no broker and eagerConnection: true`, done => {
33 | const zbc2 = new ZBClient('localtoast: 267890', {
34 | eagerConnection: true,
35 | }) // Broker doesn't exist!!!
36 | setTimeout(async () => {
37 | expect(zbc2.connected).toBe(false)
38 | await zbc2.close()
39 | done()
40 | }, 5000)
41 | })
42 |
43 | test(`Does emit the connectionError event if there is no broker and eagerConnection: true`, done => {
44 | let called = 0
45 | const zbc2 = new ZBClient('localtoast: 267890', {
46 | eagerConnection: true,
47 | }).on('connectionError', () => {
48 | called++
49 | })
50 |
51 | setTimeout(async () => {
52 | expect(called).toBe(1)
53 | await zbc2.close()
54 | done()
55 | }, 7000)
56 | })
57 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-CreateProcessInstanceWithResult.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { DeployProcessResponse, ZBClient } from '../..'
3 |
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 | jest.setTimeout(25000)
6 |
7 | const zbc = new ZBClient()
8 | let test1: DeployProcessResponse
9 | let test2: DeployProcessResponse
10 | let test3: DeployProcessResponse
11 |
12 | beforeAll(async () => {
13 | test1 = await zbc.deployProcess('./src/__tests__/testdata/await-outcome.bpmn')
14 | test2 = await zbc.deployProcess('./src/__tests__/testdata/await-outcome-long.bpmn')
15 | test3 = await zbc.deployProcess('./src/__tests__/testdata/await-outcome.bpmn')
16 | await cancelProcesses(test1.processes[0].bpmnProcessId)
17 | await cancelProcesses(test2.processes[0].bpmnProcessId)
18 | await cancelProcesses(test3.processes[0].bpmnProcessId)
19 | })
20 |
21 | afterAll(async () => {
22 | await zbc.close() // Makes sure we don't forget to close connection
23 | await cancelProcesses(test1.processes[0].bpmnProcessId)
24 | await cancelProcesses(test2.processes[0].bpmnProcessId)
25 | await cancelProcesses(test3.processes[0].bpmnProcessId)
26 | })
27 |
28 | test('Awaits a process outcome', async () => {
29 | const processId = test1.processes[0].bpmnProcessId
30 | const result = await zbc.createProcessInstanceWithResult({
31 | bpmnProcessId: processId,
32 | variables: {
33 | sourceValue: 5
34 | }
35 | })
36 | expect(result.variables.sourceValue).toBe(5)
37 | })
38 |
39 | test('can override the gateway timeout', async () => {
40 | const processId = test2.processes[0].bpmnProcessId
41 | const result = await zbc.createProcessInstanceWithResult({
42 | bpmnProcessId: processId,
43 | requestTimeout: 25000,
44 | variables: {
45 | otherValue: 'rome',
46 | sourceValue: 5,
47 | }
48 | })
49 | expect(result.variables.sourceValue).toBe(5)
50 | })
51 |
52 | test('fetches a subset of variables', async () => {
53 | const processId = test3.processes[0].bpmnProcessId
54 | const result = await zbc.createProcessInstanceWithResult({
55 | bpmnProcessId: processId,
56 | fetchVariables: ['otherValue'],
57 | variables: {
58 | otherValue: 'rome',
59 | sourceValue: 5,
60 | },
61 | })
62 | expect(result.variables.sourceValue).toBe(undefined)
63 | expect(result.variables.otherValue).toBe('rome')
64 | })
65 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-DeployProcess.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../../index'
2 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
3 | jest.setTimeout(20000)
4 |
5 | test('deploys a process', async () => {
6 | const zbc = new ZBClient()
7 | const result = await zbc.deployProcess(`./src/__tests__/testdata/Client-DeployWorkflow.bpmn`)
8 | await zbc.close()
9 | expect(result.processes[0].bpmnProcessId).toBeTruthy()
10 | })
11 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-DeployResource.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient, BpmnParser } from '../../index'
3 | import fs from 'fs'
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 | jest.setTimeout(20000)
6 |
7 | const zbc = new ZBClient()
8 | const bpmnString = fs.readFileSync(`./src/__tests__/testdata/Client-DeployWorkflow.bpmn`, 'utf8')
9 | const expectedPid = BpmnParser.getProcessId(bpmnString)
10 |
11 | beforeAll(async () =>
12 | await cancelProcesses(expectedPid)
13 | )
14 |
15 | afterAll(async () =>
16 | await zbc.close()
17 | )
18 |
19 | test('deploys a process', async () => {
20 | const result = await zbc.deployResource({
21 | process: Buffer.from(bpmnString),
22 | name: `Client-DeployWorkflow.bpmn`,
23 | })
24 | expect(result.deployments[0].process.bpmnProcessId).toBe(expectedPid)
25 | })
26 | test('deploys a process from a file', async () => {
27 | const result = await zbc.deployResource({
28 | processFilename: `./src/__tests__/testdata/Client-DeployWorkflow.bpmn`,
29 | })
30 | expect(result.deployments[0].process.version).toBeGreaterThanOrEqual(1)
31 | })
32 | test('deploys a DMN table from a filename', async () => {
33 | const result = await zbc.deployResource({
34 | decisionFilename: './src/__tests__/testdata/quarantine-duration.dmn',
35 | })
36 | expect(result.deployments[0].decision.decisionKey).not.toBeNull()
37 | })
38 | test('deploys a DMN table', async () => {
39 | const decision = fs.readFileSync(
40 | './src/__tests__/testdata/quarantine-duration.dmn'
41 | )
42 | const result = await zbc.deployResource({
43 | decision,
44 | name: 'quarantine-duration.dmn',
45 | })
46 | expect(result.deployments[0].decision.decisionKey).not.toBeNull()
47 | })
48 | test('deploys a Form', async () => {
49 | const form = fs.readFileSync(
50 | './src/__tests__/testdata/form_1.form'
51 | )
52 | const result = await zbc.deployResource({
53 | form,
54 | name: 'form_1.form',
55 | })
56 | expect(result.deployments[0].form).not.toBeNull()
57 | })
58 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-EvaluateDecision.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../../index'
2 |
3 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
4 |
5 | test("EvaluateDecision", async () => {
6 | const zbc = new ZBClient()
7 | const res = await zbc.deployResource({
8 | decisionFilename: `./src/__tests__/testdata/decision.dmn`
9 | })
10 |
11 | const dmnDecisionName = "My Decision"
12 | expect(res.deployments[0].decision.dmnDecisionName).toBe(dmnDecisionName)
13 |
14 | const dmnDecisionId = "Decision_13dmfgp"
15 | const r = await zbc.evaluateDecision({
16 | decisionId: dmnDecisionId,
17 | variables: {season: "fall"}
18 | })
19 | expect(r.evaluatedDecisions.length).toBe(1)
20 |
21 | await zbc.close()
22 | })
23 | /**
24 | {
25 | "evaluatedDecisions": [
26 | {
27 | "matchedRules": [],
28 | "evaluatedInputs": [
29 | {
30 | "inputId": "Input_1",
31 | "inputName": "season",
32 | "inputValue": "\"fall\""
33 | }
34 | ],
35 | "decisionKey": "2251799813848760",
36 | "decisionId": "Decision_13dmfgp",
37 | "decisionName": "My Decision",
38 | "decisionVersion": 1,
39 | "decisionType": "DECISION_TABLE",
40 | "decisionOutput": "null"
41 | }
42 | ],
43 | "decisionKey": "2251799813848760",
44 | "decisionId": "Decision_13dmfgp",
45 | "decisionName": "My Decision",
46 | "decisionVersion": 1,
47 | "decisionRequirementsId": "Definitions_1j6sjj9",
48 | "decisionRequirementsKey": "2251799813848759",
49 | "decisionOutput": "null",
50 | "failedDecisionId": "",
51 | "failureMessage": ""
52 | }
53 | */
54 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-MessageStart.spec.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from 'uuid'
2 | import { DeployProcessResponse, ZBClient } from '../..'
3 | import { cancelProcesses } from '../../lib/cancelProcesses'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(45000)
7 | let test1: DeployProcessResponse
8 | const zbc = new ZBClient()
9 |
10 | beforeAll(async () => {
11 | test1 = await zbc.deployProcess('./src/__tests__/testdata/Client-MessageStart.bpmn')
12 | await cancelProcesses(test1.processes[0].bpmnProcessId)
13 | })
14 |
15 | afterAll(async () => {
16 | await zbc.close()
17 | await cancelProcesses(test1.processes[0].bpmnProcessId)
18 | })
19 |
20 | test('Can start a process with a message', () =>
21 | new Promise(async done => {
22 |
23 | const randomId = uuid()
24 |
25 | // Wait 1 second to make sure the deployment is complete
26 | await new Promise(res => setTimeout(() => res(null), 1000))
27 |
28 | await zbc.publishStartMessage({
29 | name:'MSG-START_JOB',
30 | timeToLive: 2000,
31 | variables: {
32 | testKey: randomId,
33 | },
34 | })
35 |
36 | zbc.createWorker({
37 | taskType: 'console-log-msg-start',
38 | taskHandler: async job => {
39 | const res = await job.complete()
40 | expect(job.variables.testKey).toBe(randomId) // Makes sure the worker isn't responding to another message
41 | done(null)
42 | return res
43 | },
44 | loglevel: 'NONE',
45 | })
46 | }))
47 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-ModifyProcessInstance.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from "../../lib/cancelProcesses";
2 | import { ZBClient } from "../../index";
3 |
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 |
6 | const zbc = new ZBClient()
7 | let pid: string
8 | let processModelId: string
9 |
10 | beforeAll(async () => {
11 | const res = await zbc.deployProcess('./src/__tests__/testdata/Client-SkipFirstTask.bpmn')
12 | processModelId = res.processes[0].bpmnProcessId
13 | })
14 | afterAll(async () => {
15 | zbc.cancelProcessInstance(pid).catch(_ => _)
16 | await zbc.close()
17 | await cancelProcesses(processModelId)
18 | })
19 |
20 | test('Modify Process Instance', done =>{
21 | zbc.deployProcess('./src/__tests__/testdata/Client-SkipFirstTask.bpmn')
22 | zbc.createWorker({
23 | taskType: 'second_service_task',
24 | taskHandler: job => {
25 | expect(job.variables.second).toBe(1)
26 | return job.complete().then(() => done())
27 | }
28 | })
29 | zbc.createProcessInstance({
30 | bpmnProcessId: 'SkipFirstTask',
31 | variables: {}
32 | }).then(res => {
33 | pid = res.processInstanceKey
34 | zbc.modifyProcessInstance({
35 | processInstanceKey: res.processInstanceKey,
36 | activateInstructions: [{
37 | elementId: 'second_service_task',
38 | ancestorElementInstanceKey: "-1",
39 | variableInstructions: [{
40 | scopeId: '',
41 | variables: { second: 1}
42 | }]
43 | }]
44 | })
45 | })
46 | })
47 |
48 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-PublishMessage.spec.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuid } from 'uuid'
2 | import { DeployProcessResponse, ZBClient } from '../..'
3 | import { cancelProcesses } from '../../lib/cancelProcesses'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(45000)
7 | const zbc = new ZBClient()
8 | let deploy: DeployProcessResponse
9 |
10 | beforeAll(async () => {
11 | deploy = await zbc.deployProcess('./src/__tests__/testdata/Client-MessageStart.bpmn')
12 | })
13 |
14 | afterAll(async () => {
15 | await zbc.close()
16 | await cancelProcesses(deploy.processes[0].bpmnProcessId)
17 | })
18 |
19 | test('Can publish a message', () =>
20 | new Promise(async done => {
21 | const randomId = uuid()
22 |
23 | // Wait 1 second to make sure the deployment is complete
24 | await new Promise(res => setTimeout(() => res(null), 1000))
25 |
26 | await zbc.publishMessage({
27 | name: 'MSG-START_JOB',
28 | variables: {
29 | testKey: randomId,
30 | },
31 | correlationKey: 'something'
32 | })
33 |
34 | zbc.createWorker({
35 | taskType: 'console-log-msg-start',
36 | taskHandler: async job => {
37 | const res = await job.complete()
38 | expect(job.variables.testKey).toBe(randomId) // Makes sure the worker isn't responding to another message
39 | done(null)
40 | return res
41 | },
42 | loglevel: 'NONE',
43 | })
44 | }))
45 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-ThrowError.spec.ts:
--------------------------------------------------------------------------------
1 | import { Duration } from 'typed-duration'
2 | import { ZBClient } from '../..'
3 | import { cancelProcesses } from '../../lib/cancelProcesses'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(25000)
7 |
8 | let processId: string
9 |
10 | let zbc: ZBClient
11 |
12 | beforeAll(async () => {
13 | const zb = new ZBClient()
14 | processId = (await zb.deployProcess('./src/__tests__/testdata/Client-ThrowError.bpmn')).processes[0].bpmnProcessId
15 | cancelProcesses(processId)
16 | await zb.close()
17 | })
18 |
19 | beforeEach(() => {
20 | zbc = new ZBClient()
21 | })
22 |
23 | afterEach(async () => {
24 | await zbc.close()
25 | })
26 |
27 | afterAll(async () => {
28 | cancelProcesses(processId)
29 | })
30 |
31 | test('Throws a business error that is caught in the process', async () => {
32 | zbc.createWorker({
33 | taskHandler: job =>
34 | job.error('BUSINESS_ERROR', "Well, that didn't work"),
35 | taskType: 'throw-bpmn-error-task',
36 | timeout: Duration.seconds.of(30),
37 | })
38 | zbc.createWorker({
39 | taskType: 'sad-flow',
40 | taskHandler: job =>
41 | job.complete({
42 | bpmnErrorCaught: true,
43 | }),
44 | })
45 | const result = await zbc.createProcessInstanceWithResult({
46 | bpmnProcessId: processId,
47 | requestTimeout: 20000,
48 | variables: {}
49 | })
50 | expect(result.variables.bpmnErrorCaught).toBe(true)
51 | })
52 |
53 | test('Can set variables when throwing a BPMN Error', async () => {
54 | zbc.createWorker({
55 | taskHandler: job =>
56 | job.error({
57 | errorCode: 'BUSINESS_ERROR',
58 | errorMessage: "Well, that didn't work",
59 | variables: {something: "someValue"}
60 | }),
61 | taskType: 'throw-bpmn-error-task',
62 | timeout: Duration.seconds.of(30),
63 | })
64 | zbc.createWorker({
65 | taskType: 'sad-flow',
66 | taskHandler: job =>
67 | job.complete({
68 | bpmnErrorCaught: true,
69 | }),
70 | })
71 | const result = await zbc.createProcessInstanceWithResult({
72 | bpmnProcessId: processId,
73 | requestTimeout: 20000,
74 | variables: {}
75 | })
76 | expect(result.variables.bpmnErrorCaught).toBe(true)
77 | // expect(result.variables.something).toBe("someValue")
78 | })
79 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-integration.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../../index'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 |
8 | let zbc: ZBClient
9 | let wf: CreateProcessInstanceResponse
10 | let processId: string
11 |
12 | beforeAll(async () => {
13 | const client = new ZBClient()
14 | const res = await client.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
15 | processId = res.processes[0].bpmnProcessId
16 | await cancelProcesses(processId)
17 | await client.close()
18 | })
19 |
20 | beforeEach(() => {
21 | zbc = new ZBClient()
22 | })
23 |
24 | afterEach(async() => {
25 | await zbc.close()
26 | if (wf && wf.processInstanceKey) {
27 | await zbc.cancelProcessInstance(wf.processInstanceKey).catch(e => e) // Cleanup any active processes
28 | }
29 | await cancelProcesses(processId)
30 | })
31 |
32 | afterAll(async () => {
33 | await zbc.close() // .then(() => console.log(`ZBClient closed`))
34 | await cancelProcesses(processId)
35 | })
36 |
37 | test('Can get the broker topology', async () => {
38 | const res = await zbc.topology()
39 | expect(res?.brokers).toBeTruthy()
40 | })
41 |
42 | test('Can create a worker', async() => {
43 | const worker = zbc.createWorker({
44 | taskType: 'TASK_TYPE',
45 | taskHandler: job => job.complete(),
46 | loglevel: 'NONE',
47 | })
48 | expect(worker).toBeTruthy()
49 | await worker.close()
50 | })
51 |
52 | test('Can cancel a process', async () => {
53 | const client = new ZBClient()
54 | const process = await client.createProcessInstance({
55 | bpmnProcessId: processId,
56 | variables: {}
57 | })
58 | const key = process.processInstanceKey
59 | expect(key).toBeTruthy()
60 | await client.cancelProcessInstance(key)
61 | try {
62 | await client.cancelProcessInstance(key) // A call to cancel a process that doesn't exist should throw
63 | } catch (e: any) {
64 | expect(1).toBe(1)
65 | }
66 | await client.close()
67 | })
68 |
69 | test("does not retry to cancel a process instance that doesn't exist", async () => {
70 | // See: https://github.com/zeebe-io/zeebe/issues/2680
71 | // await zbc.cancelProcessInstance('123LoL')
72 | try {
73 | await zbc.cancelProcessInstance(2251799813686202)
74 | } catch (e: any) {
75 | expect(e.message.indexOf('5 NOT_FOUND:')).toBe(0)
76 | }
77 | expect.assertions(1)
78 | })
79 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-onReady.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | jest.setTimeout(30000)
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 |
6 | test(`Doesn't call the onReady handler if there is no broker`, () =>
7 | new Promise(async done => {
8 | let called = false
9 | const zbc2 = new ZBClient('localtoast: 267890', {
10 | onReady: () => {
11 | called = true
12 | },
13 | }) // Broker doesn't exist!!!
14 | setTimeout(async () => {
15 | expect(called).toBe(false)
16 | await zbc2.close()
17 | done(null)
18 | }, 4000)
19 | }))
20 |
21 | test(`Does call the onReady handler if there is a broker and eagerConnection is true`, done => {
22 | let called = 0
23 | const zbc2 = new ZBClient({
24 | eagerConnection: true,
25 | onReady: () => {
26 | called++
27 | },
28 | })
29 |
30 | setTimeout(async () => {
31 | expect(called).toBe(1)
32 | await zbc2.close()
33 | done()
34 | }, 6000)
35 | })
36 |
37 | test(`Does set connected to true if there is a broker`, done => {
38 | const zbc2 = new ZBClient({
39 | eagerConnection: true,
40 | })
41 |
42 | setTimeout(async () => {
43 | expect(zbc2.connected).toBe(true)
44 | await zbc2.close()
45 | done()
46 | }, 6000)
47 | })
48 |
49 | test(`Does emit the ready event if there is a broker and eagerConnection: true`, done => {
50 | let called = 0
51 | const zbc2 = new ZBClient({ eagerConnection: true })
52 | zbc2.on('ready', () => {
53 | called++
54 | })
55 |
56 | setTimeout(async () => {
57 | expect(called).toBe(1)
58 | expect(zbc2.connected).toBe(true)
59 | await zbc2.close()
60 | done()
61 | }, 6000)
62 | })
63 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-setVariables.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse, DeployProcessResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 |
8 | const trace = async (result: T) => {
9 | // tslint:disable-next-line: no-console
10 | // console.log(result)
11 | return result
12 | }
13 |
14 | const zbc = new ZBClient()
15 | let wf: CreateProcessInstanceResponse
16 | let deploy: DeployProcessResponse
17 | let processId: string
18 |
19 | beforeAll(async () => {
20 | deploy = await zbc.deployProcess('./src/__tests__/testdata/conditional-pathway.bpmn')
21 | processId = deploy.processes[0].bpmnProcessId
22 | await cancelProcesses(processId)
23 | })
24 |
25 | afterAll(() =>
26 | new Promise(async done => {
27 | try {
28 | if (wf?.processInstanceKey) {
29 | zbc.cancelProcessInstance(wf.processInstanceKey) // Cleanup any active processes
30 | }
31 | } finally {
32 | await zbc.close() // Makes sure we don't forget to close connection
33 | done(null)
34 | }
35 | await cancelProcesses(processId)
36 | })
37 | )
38 |
39 | test('Can update process variables with setVariables', () =>
40 | new Promise(async done => {
41 | jest.setTimeout(30000)
42 |
43 | wf = await zbc
44 | .createProcessInstance({
45 | bpmnProcessId: processId,
46 | variables: {
47 | conditionVariable: true
48 | }
49 | })
50 | .then(trace)
51 |
52 | const wfi = wf?.processInstanceKey
53 | expect(wfi).toBeTruthy()
54 |
55 | zbc.setVariables({
56 | elementInstanceKey: wfi,
57 | local: false,
58 | variables: {
59 | conditionVariable: false,
60 | },
61 | }).then(trace)
62 |
63 | zbc.createWorker({
64 | taskType: 'wait',
65 | taskHandler: async job => {
66 | expect(job?.processInstanceKey).toBe(wfi)
67 | trace(`Completing wait job for ${job.processInstanceKey}`)
68 | return job.complete()
69 | },
70 | loglevel: 'INFO',
71 | })
72 |
73 | zbc.createWorker({
74 | taskType: 'pathB',
75 | taskHandler: async job => {
76 | expect(job?.processInstanceKey).toBe(wfi)
77 | expect(job?.variables?.conditionVariable).toBe(false)
78 | const res1 = job.complete()
79 | done(null)
80 | return res1
81 | },
82 | loglevel: 'INFO',
83 | })
84 | }))
85 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Client-startProcess.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 |
8 | let zbc = new ZBClient()
9 | let wf: CreateProcessInstanceResponse
10 | let id: string | null
11 | let processId: string
12 | let processId2: string
13 |
14 | beforeAll(async () => {
15 | const res = await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
16 | processId = res.processes[0].bpmnProcessId
17 | processId2 = (await zbc.deployProcess('./src/__tests__/testdata/Client-SkipFirstTask.bpmn')).processes[0].bpmnProcessId
18 | await cancelProcesses(processId)
19 | await cancelProcesses(processId2)
20 | await zbc.close()
21 | })
22 |
23 | beforeEach(() => {
24 | zbc = new ZBClient()
25 | })
26 |
27 | afterEach(async () => {
28 | if (id) {
29 | await zbc.cancelProcessInstance(id).catch(_ => _)
30 | id = null
31 | }
32 | await zbc.close()
33 | })
34 |
35 | afterAll(async () => {
36 | if (id) {
37 | zbc.cancelProcessInstance(id).catch(_ => _)
38 | id = null
39 | }
40 | await zbc.close() // Makes sure we don't forget to close connection
41 | await cancelProcesses(processId)
42 | await cancelProcesses(processId)
43 | })
44 |
45 | test('Can start a process', async () => {
46 | wf = await zbc.createProcessInstance({
47 | bpmnProcessId: processId,
48 | variables: {}
49 | })
50 | await zbc.cancelProcessInstance(wf.processInstanceKey)
51 | expect(wf.bpmnProcessId).toBe(processId)
52 | expect(wf.processInstanceKey).toBeTruthy()
53 | })
54 |
55 | test('Can start a process at an arbitrary point', done => {
56 | const random = Math.random()
57 | const worker = zbc.createWorker({
58 | taskType: "second_service_task",
59 | taskHandler: job => {
60 | expect(job.variables.id).toBe(random)
61 | return job.complete().then(finish)
62 | }
63 | })
64 | const finish = () =>
65 | worker.close().then(() => done())
66 | zbc.createProcessInstance({
67 | bpmnProcessId: 'SkipFirstTask',
68 | variables: { id: random },
69 | startInstructions: [{elementId: 'second_service_task'}]
70 | }).then(res => (id = res.processInstanceKey))
71 | })
72 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Topology.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
4 |
5 | const zbc = new ZBClient({
6 | eagerConnection: false,
7 | })
8 |
9 | test('it can get the topology', async () => {
10 | const res = await zbc.topology()
11 | expect(Array.isArray(res?.brokers)).toBe(true)
12 | await zbc.close()
13 | })
14 |
--------------------------------------------------------------------------------
/src/__tests__/integration/TypeSurfaceAreaTest.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient, ZBWorkerTaskHandler } from '../../index'
2 |
3 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
4 |
5 | jest.setTimeout(7000)
6 |
7 | test("Hasn't broken any public type contracts", async () => {
8 | const zbc = new ZBClient({
9 | loglevel: 'NONE',
10 | })
11 | const handler: ZBWorkerTaskHandler = (job, worker) => {
12 | worker.log(job.bpmnProcessId)
13 | return job.complete()
14 | }
15 | zbc.createWorker({ taskType: 'nope', taskHandler: handler })
16 | await zbc.close()
17 | expect(true).toBeTruthy()
18 | })
19 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-1.0-complete.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 | const zbc = new ZBClient()
8 | let wf: CreateProcessInstanceResponse | undefined
9 |
10 | let processId1: string
11 | let processId2: string
12 | let processId3: string
13 |
14 | beforeAll(async () => {
15 | const res1 = await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
16 | processId1 = res1.processes[0].bpmnProcessId
17 | await cancelProcesses(processId1)
18 | const res2 = await zbc.deployProcess('./src/__tests__/testdata/hello-world-complete.bpmn')
19 | processId2 = res2.processes[0].bpmnProcessId
20 | await cancelProcesses(processId2)
21 | const res3 = await zbc.deployProcess('./src/__tests__/testdata/conditional-pathway.bpmn')
22 | processId3 = res3.processes[0].bpmnProcessId
23 | await cancelProcesses(processId3)
24 | })
25 |
26 | afterEach(async () => {
27 | try {
28 | if (wf?.processInstanceKey) {
29 | await zbc.cancelProcessInstance(wf.processInstanceKey).catch(e => e)
30 | }
31 | } catch {
32 |
33 | }
34 | })
35 |
36 | afterAll(async() => {
37 | await zbc.close()
38 | await cancelProcesses(processId1)
39 | await cancelProcesses(processId2)
40 | await cancelProcesses(processId3)
41 | })
42 |
43 | test('Can service a task', () =>
44 | new Promise(async done => {
45 | wf = await zbc.createProcessInstance({
46 | bpmnProcessId: processId1,
47 | variables: {}
48 | })
49 | zbc.createWorker({
50 | taskType: 'console-log',
51 | taskHandler: async job => {
52 | expect(job.processInstanceKey).toBe(wf?.processInstanceKey)
53 | const res1 = await job.complete(job.variables)
54 | done(null)
55 | return res1
56 | },
57 | loglevel: 'NONE',
58 | })
59 | }))
60 |
61 | test('Can service a task with complete.success', () =>
62 | new Promise(async done => {
63 | wf = await zbc.createProcessInstance({
64 | bpmnProcessId: processId2,
65 | variables: {}
66 | })
67 | zbc.createWorker({
68 | taskType: 'console-log-complete',
69 | taskHandler: async job => {
70 | expect(job.processInstanceKey).toBe(wf?.processInstanceKey)
71 | const res1 = await job.complete(job.variables)
72 | done(null)
73 | return res1
74 | },
75 | loglevel: 'NONE',
76 | })
77 | }))
78 |
79 | test('Can update process variables with complete.success()', () =>
80 | new Promise(async done => {
81 | wf = await zbc.createProcessInstance({
82 | bpmnProcessId: processId3,
83 | variables: {
84 | conditionVariable: true
85 | }
86 | })
87 | const wfi = wf?.processInstanceKey
88 | expect(wfi).toBeTruthy()
89 |
90 | zbc.createWorker({
91 | taskType: 'wait',
92 | taskHandler: async job => {
93 | expect(job.processInstanceKey).toBe(wfi)
94 | return job.complete({
95 | conditionVariable: false,
96 | })
97 | },
98 | loglevel: 'NONE',
99 | })
100 |
101 | zbc.createWorker({
102 | taskType: 'pathB',
103 | taskHandler: async job => {
104 | expect(job.processInstanceKey).toBe(wfi)
105 | expect(job.variables.conditionVariable).toBe(false)
106 | const res1 = await job.complete(job.variables)
107 | wf = undefined
108 | done(null)
109 | return res1
110 | },
111 | loglevel: 'NONE',
112 | })
113 | }))
114 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-BatchWorker.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { Duration, ZBClient } from '../..'
3 |
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 | jest.setTimeout(30000)
6 | const zbc = new ZBClient()
7 | let processId: string
8 |
9 | beforeAll(async () => {
10 | const res = await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
11 | processId = res.processes[0].bpmnProcessId
12 | await cancelProcesses(processId)
13 | })
14 |
15 | afterAll(async () => {
16 | await zbc.close() // Makes sure we don't forget to close connection
17 | await cancelProcesses(processId)
18 | })
19 |
20 | test('BatchWorker gets ten jobs', () =>
21 | new Promise(async done => {
22 | for (let i = 0; i < 10; i++) {
23 | await zbc.createProcessInstance({
24 | bpmnProcessId: processId,
25 | variables: {}
26 | })
27 | }
28 |
29 | const w = zbc.createBatchWorker({
30 | jobBatchMaxTime: Duration.seconds.from(120),
31 | jobBatchMinSize: 10,
32 | loglevel: 'NONE',
33 | taskHandler: async jobs => {
34 | expect(jobs.length).toBe(10)
35 | const res1 = await Promise.all(jobs.map(job => job.complete()))
36 | await w.close()
37 | done(null)
38 | return res1
39 | },
40 | taskType: 'console-log',
41 | })
42 | }))
43 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-Failure-Backoff-Retry.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 |
6 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
7 | jest.setTimeout(60000)
8 |
9 | let zbc = new ZBClient()
10 | let wf: CreateProcessInstanceResponse | undefined
11 | let processId: string
12 |
13 | beforeAll(async () => {
14 | const res = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure1.bpmn')
15 | processId = res.processes[0].bpmnProcessId
16 | await cancelProcesses(processId)
17 | })
18 |
19 | afterEach(async () => {
20 | try {
21 | if (wf?.processInstanceKey) {
22 | await zbc.cancelProcessInstance(wf.processInstanceKey)
23 | }
24 | } catch (e: any) {
25 | // console.log('Caught NOT FOUND') // @DEBUG
26 | } finally {
27 |
28 | }
29 | })
30 |
31 | afterAll(async () => {
32 | await zbc.close()
33 | await cancelProcesses(processId)
34 | })
35 |
36 | test('Can specify a retryBackoff with complete.failure()', () =>
37 | new Promise(async resolve => {
38 |
39 | wf = await zbc.createProcessInstance({
40 | bpmnProcessId: processId,
41 | variables: {
42 | conditionVariable: true
43 | }
44 | })
45 | const wfi = wf.processInstanceKey
46 | expect(wfi).toBeTruthy()
47 |
48 | await zbc.setVariables({
49 | elementInstanceKey: wfi,
50 | local: false,
51 | variables: {
52 | conditionVariable: false,
53 | },
54 | })
55 |
56 | let then = new Date()
57 | const w = zbc.createWorker({
58 | taskType: 'wait-worker-failure',
59 | taskHandler: async job => {
60 | // Succeed on the third attempt
61 | if (job.retries === 1) {
62 | const now = new Date()
63 | const res1 = await job.complete()
64 | expect(job.processInstanceKey).toBe(wfi)
65 | expect((now as any) - (then as any)).toBeGreaterThan(1800)
66 | wf = undefined
67 |
68 | zbc.cancelProcessInstance(wfi)
69 | resolve(null)
70 | await w.close()
71 | return res1
72 | }
73 | then = new Date()
74 | // Fail on the first attempt, with a 2s backoff
75 | return job.fail({
76 | errorMessage:
77 | 'Triggering a retry with a two second back-off',
78 | retryBackOff: 2000,
79 | retries: 1,
80 | })
81 | },
82 | loglevel: 'NONE',
83 | })
84 | }))
85 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-Failure-Retries.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(60000)
7 |
8 | let zbc: ZBClient
9 | let wf: CreateProcessInstanceResponse | undefined
10 |
11 | beforeEach(() => {
12 | zbc = new ZBClient()
13 | })
14 |
15 | afterEach(async () => {
16 | try {
17 | if (wf?.processInstanceKey) {
18 | await zbc.cancelProcessInstance(wf.processInstanceKey)
19 | }
20 | } catch (e: any) {
21 | // console.log('Caught NOT FOUND') // @DEBUG
22 | } finally {
23 | await zbc.close() // Makes sure we don't forget to close connection
24 | }
25 | })
26 |
27 | test('Decrements the retries count by default', () =>
28 | new Promise(async done => {
29 | const res = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure-Retries.bpmn')
30 | await cancelProcesses(res.processes[0].bpmnProcessId)
31 | wf = await zbc.createProcessInstance({
32 | bpmnProcessId: 'worker-failure-retries',
33 | variables: {
34 | conditionVariable: true
35 | }
36 | })
37 | let called = false
38 |
39 | const worker = zbc.createWorker({
40 | taskType: 'service-task-worker-failure-retries',
41 | taskHandler: job => {
42 | if (!called) {
43 | expect(job.retries).toBe(100)
44 | called = true
45 | return job.fail('Some reason')
46 | }
47 | expect(job.retries).toBe(99)
48 | done(null)
49 | return job.complete().then(async res => {
50 | await worker.close()
51 | return res
52 | })
53 | }
54 | })
55 | })
56 | )
57 |
58 | test('Set the retries to a specific number when provided with one via simple signature', () =>
59 | new Promise(async done => {
60 | const res = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure-Retries.bpmn')
61 | cancelProcesses(res.processes[0].bpmnProcessId)
62 | wf = await zbc.createProcessInstance({
63 | bpmnProcessId: 'worker-failure-retries',
64 | variables: {
65 | conditionVariable: true
66 | }
67 | })
68 | let called = false
69 |
70 | const worker = zbc.createWorker({
71 | taskType: 'service-task-worker-failure-retries',
72 | taskHandler: job => {
73 | if (!called) {
74 | expect(job.retries).toBe(100)
75 | called = true
76 | return job.fail('Some reason', 101)
77 | }
78 | expect(job.retries).toBe(101)
79 | done(null)
80 | return job.complete().then(async res => {
81 | await worker.close()
82 | return res
83 | })
84 | }
85 | })
86 | })
87 | )
88 |
89 | test('Set the retries to a specific number when provided with one via object signature', () =>
90 | new Promise(async done => {
91 | const res = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure-Retries.bpmn')
92 | await cancelProcesses(res.processes[0].bpmnProcessId)
93 | wf = await zbc.createProcessInstance({
94 | bpmnProcessId: 'worker-failure-retries',
95 | variables: {
96 | conditionVariable: true
97 | }
98 | })
99 | let called = false
100 |
101 | const worker = zbc.createWorker({
102 | taskType: 'service-task-worker-failure-retries',
103 | taskHandler: job => {
104 | if (!called) {
105 | expect(job.retries).toBe(100)
106 | called = true
107 | return job.fail({ errorMessage: 'Some reason', retries: 101})
108 | }
109 | expect(job.retries).toBe(101)
110 | done(null)
111 | return job.complete().then(async res => {
112 | await worker.close()
113 | return res
114 | })
115 | }
116 | })
117 | })
118 | )
119 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-Failure.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse, DeployProcessResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(60000)
7 |
8 | const zbc = new ZBClient()
9 | let wf: CreateProcessInstanceResponse | undefined
10 |
11 | let wf1: DeployProcessResponse
12 | let wf2: DeployProcessResponse
13 | let wf3: DeployProcessResponse
14 | let bpmnProcessId1: string
15 | let bpmnProcessId2: string
16 | let bpmnProcessId3: string
17 |
18 | beforeAll(async () => {
19 | wf1 = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure1.bpmn')
20 | bpmnProcessId1 = wf1.processes[0].bpmnProcessId
21 | wf2 = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure2.bpmn')
22 | bpmnProcessId2 = wf2.processes[0].bpmnProcessId
23 | wf3 = await zbc.deployProcess('./src/__tests__/testdata/Worker-Failure3.bpmn')
24 | bpmnProcessId3 = wf3.processes[0].bpmnProcessId
25 | await cancelProcesses(bpmnProcessId1)
26 | await cancelProcesses(bpmnProcessId2)
27 | await cancelProcesses(bpmnProcessId3)
28 | })
29 |
30 | afterEach(async () => {
31 | try {
32 | if (wf?.processInstanceKey) {
33 | await zbc.cancelProcessInstance(wf.processInstanceKey)
34 | }
35 | } catch (e: any) {
36 | // console.log('Caught NOT FOUND') // @DEBUG
37 | }
38 | })
39 |
40 | afterAll(async() => {
41 | await zbc.close()
42 | await cancelProcesses(bpmnProcessId1)
43 | await cancelProcesses(bpmnProcessId2)
44 | await cancelProcesses(bpmnProcessId3)
45 | })
46 |
47 | test('Causes a retry with complete.failure()', () =>
48 | new Promise(async resolve => {
49 | wf = await zbc.createProcessInstance({
50 | bpmnProcessId: bpmnProcessId1,
51 | variables: {
52 | conditionVariable: true
53 | }
54 | })
55 | const wfi = wf.processInstanceKey
56 | expect(wfi).toBeTruthy()
57 |
58 | await zbc.setVariables({
59 | elementInstanceKey: wfi,
60 | local: false,
61 | variables: {
62 | conditionVariable: false,
63 | },
64 | })
65 |
66 | zbc.createWorker({
67 | taskType: 'wait-worker-failure',
68 | taskHandler: async job => {
69 | // Succeed on the third attempt
70 | if (job.retries === 1) {
71 | const res1 = await job.complete()
72 | expect(job.processInstanceKey).toBe(wfi)
73 | expect(job.retries).toBe(1)
74 | wf = undefined
75 |
76 | resolve(null)
77 | return res1
78 | }
79 | return job.fail('Triggering a retry')
80 | },
81 | loglevel: 'NONE',
82 | })
83 | }))
84 |
85 | test('Does not fail a process when the handler throws, by default', () =>
86 | new Promise(async done => {
87 | wf = await zbc.createProcessInstance({
88 | bpmnProcessId: bpmnProcessId2,
89 | variables: {}
90 | })
91 |
92 | let alreadyFailed = false
93 |
94 | // Faulty worker - throws an unhandled exception in task handler
95 | const w = zbc.createWorker({
96 | taskType: 'console-log-worker-failure-2',
97 | taskHandler: async job => {
98 | if (alreadyFailed) {
99 | await zbc.cancelProcessInstance(wf!.processInstanceKey) // throws if not found. Should NOT throw in this test
100 | job.complete()
101 | return w.close().then(() => done(null))
102 | }
103 | alreadyFailed = true
104 | throw new Error(
105 | 'Unhandled exception in task handler for testing purposes'
106 | ) // Will be caught in the library
107 | },
108 |
109 | loglevel: 'NONE',
110 | pollInterval: 10000,
111 | })
112 | }))
113 |
114 | test('Fails a process when the handler throws and options.failProcessOnException is set', () =>
115 | new Promise(async done => {
116 |
117 | wf = await zbc.createProcessInstance({
118 | bpmnProcessId: bpmnProcessId3,
119 | variables: {}
120 | })
121 |
122 | let alreadyFailed = false
123 |
124 | // Faulty worker
125 | const w = zbc.createWorker({
126 | taskType: 'console-log-worker-failure-3',
127 | taskHandler: job => {
128 | if (alreadyFailed) {
129 | // It polls multiple times a second, and we need it to only throw once
130 | return job.forward()
131 | }
132 | alreadyFailed = true
133 | testProcessInstanceExists() // waits 1000ms then checks
134 | throw new Error(
135 | 'Unhandled exception in task handler for test purposes'
136 | ) // Will be caught in the library
137 | },
138 | failProcessOnException: true,
139 | loglevel: 'NONE',
140 | })
141 |
142 | function testProcessInstanceExists() {
143 | setTimeout(async () => {
144 | try {
145 | await zbc.cancelProcessInstance(wf!.processInstanceKey) // throws if not found. SHOULD throw in this test
146 | } catch (e: any) {
147 | w.close().then(() => done(null))
148 | }
149 | }, 1500)
150 | }
151 | }))
152 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-LongPoll.spec.ts:
--------------------------------------------------------------------------------
1 | import * as uuid from 'uuid'
2 | import { ZBClient } from '../..'
3 | import { cancelProcesses } from '../../lib/cancelProcesses'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 |
7 | jest.setTimeout(40000)
8 |
9 | let processId: string
10 |
11 | const zbcLongPoll = new ZBClient({
12 | longPoll: 60000,
13 | })
14 |
15 | afterAll(async () => {
16 | await zbcLongPoll.close()
17 | await cancelProcesses(processId)
18 | })
19 |
20 | beforeAll(async () => {
21 | const res = await zbcLongPoll.deployProcess('./src/__tests__/testdata/Worker-LongPoll.bpmn')
22 | processId = res.processes[0].bpmnProcessId
23 | await cancelProcesses(processId)
24 | })
25 |
26 | test('Does long poll by default', done => {
27 | const worker = zbcLongPoll.createWorker({
28 | taskType: uuid.v4(),
29 | taskHandler: job => job.complete(job.variables),
30 | loglevel: 'NONE',
31 | debug: true,
32 | })
33 | // Wait to outside 10s - it should have polled once when it gets the job
34 | setTimeout(async () => {
35 | expect(worker.pollCount).toBe(1)
36 | done()
37 | }, 30000)
38 | })
39 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-RaiseIncident.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
4 |
5 | /**
6 | * Note: This test needs to be modified to leave its process instance active so the incident can be manually verified
7 | */
8 | jest.setTimeout(30000)
9 |
10 | let processInstanceKey: string
11 | const zbc = new ZBClient()
12 | let processId: string
13 |
14 | beforeAll(async () => {
15 | const res = await zbc.deployProcess('./src/__tests__/testdata/Worker-RaiseIncident.bpmn')
16 | processId = res.processes[0].bpmnProcessId
17 | await cancelProcesses(processId)
18 | })
19 |
20 | afterAll(async () => {
21 | zbc.cancelProcessInstance(processInstanceKey)
22 | await zbc.close()
23 | await cancelProcesses(processId)
24 | })
25 |
26 | test('Can raise an Operate incident with complete.failure()', () =>
27 | new Promise(async done => {
28 | const wf = await zbc.createProcessInstance({
29 | bpmnProcessId: processId,
30 | variables: {
31 | conditionVariable: true
32 | }
33 | })
34 | processInstanceKey = wf.processInstanceKey
35 | expect(processInstanceKey).toBeTruthy()
36 |
37 | await zbc.setVariables({
38 | elementInstanceKey: processInstanceKey,
39 | local: false,
40 | variables: {
41 | conditionVariable: false,
42 | },
43 | })
44 |
45 | await zbc.createWorker({
46 | taskType: 'wait-raise-incident',
47 | taskHandler: async job => {
48 | expect(job.processInstanceKey).toBe(processInstanceKey)
49 | return job.complete(job.variables)
50 | },
51 | loglevel: 'NONE',
52 | })
53 |
54 | await zbc.createWorker({
55 | taskType: 'pathB-raise-incident',
56 | taskHandler: async job => {
57 | expect(job.processInstanceKey).toBe(processInstanceKey)
58 | expect(job.variables.conditionVariable).toBe(false)
59 | const res1 = await job.fail('Raise an incident in Operate', 0)
60 | // Manually verify that an incident has been raised
61 | await job.cancelWorkflow()
62 | // comment out the preceding line for the verification test
63 | done(null)
64 | return res1
65 | },
66 | maxJobsToActivate: 1,
67 | loglevel: 'NONE',
68 | })
69 | }))
70 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-fetchVariable.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 | const zbc= new ZBClient()
8 | let wf: CreateProcessInstanceResponse | undefined
9 | let processId: string
10 |
11 | beforeAll(async () => {
12 | const res = await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
13 | processId = res.processes[0].bpmnProcessId
14 | await cancelProcesses(processId)
15 | })
16 |
17 | afterEach(async () => {
18 | try {
19 | if (wf?.processInstanceKey) {
20 | await zbc.cancelProcessInstance(wf.processInstanceKey).catch(e => e)
21 | }
22 | } catch {
23 |
24 | }
25 | })
26 |
27 | afterAll(async () => {
28 | await cancelProcesses(processId)
29 | await zbc.close()
30 | })
31 |
32 | test('Can retrieve only specified variables using fetchVariable', () =>
33 | new Promise(async done => {
34 | wf = await zbc.createProcessInstance({
35 | bpmnProcessId: processId,
36 | variables: {
37 | var1: 'foo',
38 | var2: 'bar'
39 | }
40 | })
41 |
42 | zbc.createWorker({
43 | fetchVariable: ['var2'],
44 | taskType: 'console-log',
45 | taskHandler: async job => {
46 | expect(job.processInstanceKey).toBe(wf?.processInstanceKey)
47 | expect(job.variables.var2).toEqual('bar')
48 | expect((job.variables as any).var1).not.toBeDefined()
49 | const res1 = await job.complete(job.variables)
50 | done(null)
51 | return res1
52 | },
53 | loglevel: 'NONE',
54 | })
55 | }))
56 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-integration.spec.ts:
--------------------------------------------------------------------------------
1 | import { cancelProcesses } from '../../lib/cancelProcesses'
2 | import { ZBClient } from '../..'
3 | import { CreateProcessInstanceResponse } from '../../lib/interfaces-grpc-1.0'
4 |
5 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
6 | jest.setTimeout(30000)
7 | const zbc = new ZBClient()
8 | let wf: CreateProcessInstanceResponse | undefined
9 | let processId1: string
10 | let processId2: string
11 | let processId3: string
12 |
13 | beforeAll(async () => {
14 | const res1 = await zbc.deployProcess('./src/__tests__/testdata/hello-world.bpmn')
15 | processId1 = res1.processes[0].bpmnProcessId
16 | await cancelProcesses(processId1)
17 | const res2 = await zbc.deployProcess('./src/__tests__/testdata/hello-world-complete.bpmn')
18 | processId2 = res2.processes[0].bpmnProcessId
19 | await cancelProcesses(processId2)
20 | const res3 = await zbc.deployProcess('./src/__tests__/testdata/conditional-pathway.bpmn')
21 | processId3 = res3.processes[0].bpmnProcessId
22 | await cancelProcesses(processId3)
23 | })
24 |
25 | afterEach(async () => {
26 | try {
27 | if (wf?.processInstanceKey) {
28 | await zbc.cancelProcessInstance(wf.processInstanceKey).catch(e => e)
29 | }
30 | } catch {
31 | }
32 | })
33 |
34 | afterAll(async () => {
35 | await cancelProcesses(processId1)
36 | await cancelProcesses(processId2)
37 | await cancelProcesses(processId3)
38 | await zbc.close()
39 | })
40 |
41 | test('Can service a task', () =>
42 | new Promise(async done => {
43 | wf = await zbc.createProcessInstance({
44 | bpmnProcessId: processId1,
45 | variables: {}
46 | })
47 | zbc.createWorker({
48 | taskType: 'console-log',
49 | taskHandler: async job => {
50 | expect(job.processInstanceKey).toBe(wf?.processInstanceKey)
51 | const res1 = await job.complete(job.variables)
52 | done(null)
53 | return res1
54 | },
55 | loglevel: 'NONE',
56 | })
57 | }))
58 |
59 | test('Can service a task with complete.success', () =>
60 | new Promise(async done => {
61 | wf = await zbc.createProcessInstance({
62 | bpmnProcessId: processId2,
63 | variables: {}
64 | })
65 | zbc.createWorker({
66 | taskType: 'console-log-complete',
67 | taskHandler: async job => {
68 | expect(job.processInstanceKey).toBe(wf?.processInstanceKey)
69 | const res1 = await job.complete(job.variables)
70 | done(null)
71 | return res1
72 | },
73 | loglevel: 'NONE',
74 | })
75 | }))
76 |
77 | test('Can update process variables with complete.success()', () =>
78 | new Promise(async done => {
79 | wf = await zbc.createProcessInstance({
80 | bpmnProcessId: processId3,
81 | variables: {
82 | conditionVariable: true
83 | }
84 | })
85 | const wfi = wf?.processInstanceKey
86 | expect(wfi).toBeTruthy()
87 | zbc.createWorker({
88 | taskType: 'wait',
89 | taskHandler: async job => {
90 | expect(job.processInstanceKey).toBe(wfi)
91 | return job.complete({
92 | conditionVariable: false,
93 | })
94 | },
95 | loglevel: 'NONE',
96 | })
97 |
98 | zbc.createWorker({
99 | taskType: 'pathB',
100 | taskHandler: async job => {
101 | expect(job.processInstanceKey).toBe(wfi)
102 | expect(job.variables.conditionVariable).toBe(false)
103 | const res1 = await job.complete(job.variables)
104 | wf = undefined
105 | done(null)
106 | return res1
107 | },
108 | loglevel: 'NONE',
109 | })
110 | }))
111 |
--------------------------------------------------------------------------------
/src/__tests__/integration/Worker-onReady.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | jest.setTimeout(40000)
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 |
6 | test(`Worker emits the ready event once if there is a broker`, done => {
7 | let called = 0
8 | const zbc2 = new ZBClient()
9 | zbc2.createWorker({
10 | taskHandler: job => job.complete(),
11 | taskType: 'nonsense-task',
12 | }).on('ready', () => {
13 | called++
14 | })
15 | setTimeout(async () => {
16 | expect(called).toBe(1)
17 | await zbc2.close()
18 | done()
19 | }, 12000)
20 | })
21 |
22 | test(`Does set connected: true if there is a broker and eagerConnection: true`, done => {
23 | const zbc2 = new ZBClient({
24 | eagerConnection: true,
25 | })
26 | setTimeout(async () => {
27 | expect(zbc2.connected).toBe(true)
28 | await zbc2.close()
29 | done()
30 | }, 7000)
31 | })
32 |
33 | test(`Does not set connected: true if there is a broker and eagerConnection: false`, done => {
34 | const zbc2 = new ZBClient()
35 | setTimeout(async () => {
36 | expect(zbc2.connected).toBe(false)
37 | await zbc2.close()
38 | done()
39 | }, 7000)
40 | })
41 |
42 | test(`Does not call the onReady handler if there is no broker`, done => {
43 | let called = 0
44 | const zbc2 = new ZBClient('nobroker')
45 | zbc2.createWorker({
46 | onReady: () => {
47 | called++
48 | },
49 | taskHandler: job => job.complete(),
50 | taskType: 'nonsense-task',
51 | })
52 | setTimeout(async () => {
53 | expect(called).toBe(0)
54 | await zbc2.close()
55 | done()
56 | }, 5000)
57 | })
58 |
59 | test(`Does not emit the ready event if there is no broker`, done => {
60 | let called = 0
61 | const zbc2 = new ZBClient('nobroker')
62 | zbc2.createWorker({
63 | loglevel: 'NONE',
64 | taskHandler: job => job.complete(),
65 | taskType: 'nonsense-task',
66 | }).on('ready', () => {
67 | called++
68 | })
69 | setTimeout(async () => {
70 | expect(called).toBe(0)
71 | await zbc2.close()
72 | done()
73 | }, 5000)
74 | })
75 |
76 | test(`Worker calls the onReady handler once if there is a broker`, done => {
77 | let called = 0
78 | const zbc2 = new ZBClient()
79 | zbc2.createWorker({
80 | onReady: () => {
81 | called++
82 | },
83 | taskHandler: job => job.complete(),
84 | taskType: 'nonsense-task',
85 | })
86 | setTimeout(async () => {
87 | expect(called).toBe(1)
88 | await zbc2.close()
89 | done()
90 | }, 12000)
91 | })
92 |
--------------------------------------------------------------------------------
/src/__tests__/local-integration/OnConnectionError.spec.ts:
--------------------------------------------------------------------------------
1 | import { ZBClient } from '../..'
2 |
3 | jest.setTimeout(16000)
4 | process.env.ZEEBE_NODE_LOG_LEVEL = process.env.ZEEBE_NODE_LOG_LEVEL || 'NONE'
5 |
6 | xtest(`Calls the onConnectionError handler if there is no broker and eagerConnection:true`, () =>
7 | new Promise(async done => {
8 | let calledA = 0
9 | const zbc2 = new ZBClient('localtoast: 267890', {
10 | eagerConnection: true,
11 | onConnectionError: () => {
12 | calledA++
13 | },
14 | })
15 | setTimeout(async () => {
16 | expect(calledA).toBe(1)
17 | await zbc2.close()
18 | done(null)
19 | }, 5000)
20 | }))
21 |
22 | xtest(`Does not call the onConnectionError handler if there is a broker`, () =>
23 | new Promise(done => {
24 | let calledB = 0
25 | const zbc2 = new ZBClient({
26 | onConnectionError: () => {
27 | // tslint:disable-next-line: no-debugger
28 | debugger
29 | calledB++
30 | // tslint:disable-next-line: no-console
31 | console.log(
32 | 'onConnection Error was called when there *is* a broker'
33 | )
34 | // throw new Error(
35 | // 'onConnection Error was called when there *is* a broker'
36 | // )
37 | },
38 | })
39 | setTimeout(async () => {
40 | expect(calledB).toBe(0)
41 | await zbc2.close()
42 | done(null)
43 | }, 5000)
44 | }))
45 |
46 | xtest(`Calls ZBClient onConnectionError once when there is no broker, eagerConnection:true, and workers with no handler`, () =>
47 | new Promise(done => {
48 | let calledC = 0
49 | const zbc2 = new ZBClient('localtoast:234532534', {
50 | eagerConnection: true,
51 | onConnectionError: () => {
52 | calledC++
53 | },
54 | })
55 | zbc2.createWorker({
56 | taskType: 'whatever',
57 | taskHandler: job => job.complete(),
58 | })
59 | zbc2.createWorker({
60 | taskType: 'whatever',
61 | taskHandler: job => job.complete(),
62 | })
63 | setTimeout(() => {
64 | zbc2.close()
65 | expect(calledC).toBe(1)
66 | done(null)
67 | }, 10000)
68 | }))
69 |
70 | xtest(`Calls ZBClient onConnectionError when there no broker, for the client and each worker with a handler`, () =>
71 | new Promise(async done => {
72 | let calledD = 0
73 | const zbc2 = new ZBClient('localtoast:234532534', {
74 | onConnectionError: () => {
75 | calledD++
76 | },
77 | })
78 | zbc2.createWorker({
79 | taskType: 'whatever',
80 | taskHandler: job => job.complete(),
81 | onConnectionError: () => calledD++,
82 | })
83 | setTimeout(() => {
84 | zbc2.close()
85 | expect(calledD).toBe(2)
86 | done(null)
87 | }, 10000)
88 | }))
89 |
90 | xtest(`Debounces onConnectionError`, () =>
91 | new Promise(async done => {
92 | let called = 0
93 | const zbc2 = new ZBClient('localtoast:234532534', {
94 | onConnectionError: () => {
95 | called++
96 | },
97 | })
98 | zbc2.createWorker({
99 | taskType: 'whatever',
100 | taskHandler: job => job.complete(),
101 | onConnectionError: () => called++,
102 | })
103 | setTimeout(() => {
104 | zbc2.close()
105 | expect(called).toBe(2) // toBeLessThanOrEqual(1)
106 | done(null)
107 | }, 15000)
108 | }))
109 |
110 | xtest(`Trailing parameter worker onConnectionError handler API works`, () =>
111 | new Promise(done => {
112 | let calledE = 0
113 | const zbc2 = new ZBClient('localtoast:234532534', {})
114 | zbc2.createWorker({
115 | taskType: 'whatever',
116 | taskHandler: job => job.complete(),
117 | onConnectionError: () => calledE++,
118 | })
119 | setTimeout(async () => {
120 | await zbc2.close()
121 | expect(calledE).toBe(1)
122 | done(null)
123 | }, 10000)
124 | }))
125 |
126 | xtest(`Does not call the onConnectionError handler if there is a business error`, () =>
127 | new Promise(async done => {
128 | let calledF = 0
129 | let wf = 'arstsrasrateiuhrastulyharsntharsie'
130 | const zbc2 = new ZBClient({
131 | onConnectionError: () => {
132 | // tslint:disable-next-line: no-console
133 | console.log('OnConnectionError!!!! Incrementing calledF') // @DEBUG
134 | const e = new Error()
135 | // tslint:disable-next-line: no-console
136 | console.log(e.stack) // @DEBUG
137 | calledF++
138 | },
139 | })
140 |
141 | zbc2.createProcessInstance({
142 | bpmnProcessId: wf,
143 | variables: {}
144 | }).catch(() => {
145 | wf = 'throw error away'
146 | })
147 | setTimeout(async () => {
148 | expect(zbc2.connected).toBe(true)
149 | expect(calledF).toBe(0)
150 | await zbc2.close()
151 | done(null)
152 | }, 10000)
153 | }))
154 |
--------------------------------------------------------------------------------
/src/__tests__/stringifyVariables.spec.ts:
--------------------------------------------------------------------------------
1 | import { parseVariables, stringifyVariables } from '../lib/stringifyVariables'
2 |
3 | // tslint:disable:object-literal-sort-keys
4 | const jobObject = {
5 | key: '37274',
6 | jobHeaders: {
7 | bpmnProcessId: 'parallel-subtasks',
8 | elementId: 'ServiceTask_0i7cog1',
9 | elementInstanceKey: '37270',
10 | workflowDefinitionVersion: 1,
11 | workflowInstanceKey: '34027',
12 | workflowKey: '1',
13 | },
14 | customHeaders: {},
15 | worker: '26fe8907-f518-4f8d-bd75-06acaec3c154',
16 | retries: 3,
17 | deadline: '1547595187455',
18 | variables: {
19 | jobId: '7ead71d8-30c9-4eda-81e7-f2ada6d7d0da',
20 | subtaskCount: 200,
21 | tasksCompleted: null,
22 | },
23 | type: 'sub-task',
24 | }
25 |
26 | const expectedStringifiedVariables =
27 | '{"jobId":"7ead71d8-30c9-4eda-81e7-f2ada6d7d0da","subtaskCount":200,"tasksCompleted":null}'
28 |
29 | const jobDictionary = {
30 | key: '37274',
31 | customHeaders: {},
32 | worker: '26fe8907-f518-4f8d-bd75-06acaec3c154',
33 | retries: 3,
34 | deadline: '1547595187455',
35 | variables:
36 | '{"jobId":"7ead71d8-30c9-4eda-81e7-f2ada6d7d0da","subtaskCount":200,"tasksCompleted":null}',
37 | type: 'sub-task',
38 | }
39 |
40 | test('stringifyVariables returns a new object', () => {
41 | expect(stringifyVariables(jobObject)).not.toEqual(jobObject)
42 | })
43 |
44 | test('stringifyVariables stringifies the variables key of a job object', () => {
45 | const stringified = stringifyVariables(jobObject)
46 | expect(typeof stringified.variables).toBe('string')
47 | expect(stringified.variables).toBe(expectedStringifiedVariables)
48 | })
49 |
50 | test('parseVariables returns a new object', () => {
51 | expect(parseVariables(jobDictionary)).not.toEqual(jobDictionary)
52 | })
53 |
54 | test('parseVariables parses the payload key of a job object to JSON', () => {
55 | expect(typeof parseVariables(jobDictionary).variables).toBe('object')
56 | })
57 |
58 | test('parseVariables correctly parses the payload string', () => {
59 | const parsed = parseVariables(jobDictionary)
60 | expect(parsed.variables.jobId).toEqual(
61 | '7ead71d8-30c9-4eda-81e7-f2ada6d7d0da'
62 | )
63 | expect(parsed.variables.subtaskCount).toEqual(200)
64 | expect(parsed.variables.tasksCompleted).toBeNull()
65 | expect(Object.keys(parsed.variables).length).toBe(3)
66 | })
67 |
68 | test('parseVariables returns an object with all the keys of the original', () => {
69 | const parsed = parseVariables(jobDictionary)
70 | expect(Object.keys(parsed).length).toBe(7)
71 | expect(parsed.key).toBe('37274')
72 | expect(parsed.worker).toBe('26fe8907-f518-4f8d-bd75-06acaec3c154')
73 | })
74 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Client-BrokenBpmn.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_17rbbvu
20 |
21 |
22 | SequenceFlow_112zghv
23 | SequenceFlow_17rbbvu
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Client-MessageStart.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Flow_06412wm
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Flow_12d1ngd
17 | Flow_06412wm
18 |
19 |
20 | Flow_12d1ngd
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Client-SkipFirstTask.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_13h0ndl
6 |
7 |
8 |
9 | Flow_15zn6mn
10 |
11 |
12 |
13 |
14 |
15 |
16 | Flow_027zokm
17 | Flow_15zn6mn
18 |
19 |
20 |
21 |
22 |
23 | Flow_13h0ndl
24 | Flow_027zokm
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Signal.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_0dwell7
6 |
7 |
8 |
9 |
10 | Flow_16lx2ly
11 |
12 |
13 |
14 |
15 |
16 |
17 | Flow_0dwell7
18 | Flow_16lx2ly
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Worker-Failure-Retries.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_112zghv
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Worker-Failure2.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_17rbbvu
20 |
21 |
22 | SequenceFlow_112zghv
23 | SequenceFlow_17rbbvu
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Worker-Failure3.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_17rbbvu
20 |
21 |
22 | SequenceFlow_112zghv
23 | SequenceFlow_17rbbvu
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/Worker-LongPoll.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_17rbbvu
20 |
21 |
22 | SequenceFlow_112zghv
23 | SequenceFlow_17rbbvu
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/await-outcome-long.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0xqfx4k
6 |
7 |
8 | SequenceFlow_0xqfx4k
9 | SequenceFlow_0cji6zj
10 |
11 | PT20S
12 |
13 |
14 |
15 | SequenceFlow_0cji6zj
16 |
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/await-outcome.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0xqfx4k
6 |
7 |
8 | SequenceFlow_0xqfx4k
9 | SequenceFlow_0cji6zj
10 |
11 | PT1S
12 |
13 |
14 |
15 | SequenceFlow_0cji6zj
16 |
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/await-outcome.bpmn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/camunda-community-hub/zeebe-client-node-js/7969ce1808c96a87519cb1a3f279287f30637c4b/src/__tests__/testdata/await-outcome.bpmn.zip
--------------------------------------------------------------------------------
/src/__tests__/testdata/decision.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | season
8 |
9 |
10 |
11 |
12 |
13 | "Fall"
14 |
15 |
16 | "Potato fries"
17 |
18 |
19 |
20 |
21 | "Winter"
22 |
23 |
24 | "Roast Apple"
25 |
26 |
27 |
28 |
29 | "Spring"
30 |
31 |
32 | "Steak"
33 |
34 |
35 |
36 |
37 | "Summer"
38 |
39 |
40 | "Light Salad and Kombucha"
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/disconnection.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_1xgy4qo
6 |
7 |
8 |
9 |
10 |
11 |
12 | SequenceFlow_1xgy4qo
13 | SequenceFlow_0wp39y1
14 |
15 |
16 | SequenceFlow_0wp39y1
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/form_1.form:
--------------------------------------------------------------------------------
1 | {
2 | "components": [
3 | {
4 | "label": "Number",
5 | "type": "number",
6 | "layout": {
7 | "row": "Row_1h8ufhy",
8 | "columns": null
9 | },
10 | "id": "Field_1qlk4je",
11 | "key": "field_19ab8r7"
12 | },
13 | {
14 | "label": "Text area",
15 | "type": "textarea",
16 | "layout": {
17 | "row": "Row_08n9bxz",
18 | "columns": null
19 | },
20 | "id": "Field_1xphwg1",
21 | "key": "field_0t0ghcb"
22 | },
23 | {
24 | "label": "Checkbox",
25 | "type": "checkbox",
26 | "layout": {
27 | "row": "Row_0shlcir",
28 | "columns": null
29 | },
30 | "id": "Field_07ux5ad",
31 | "key": "field_0oqjpgs"
32 | }
33 | ],
34 | "type": "default",
35 | "id": "Form_07zfo8o",
36 | "executionPlatform": "Camunda Cloud",
37 | "executionPlatformVersion": "8.2.0",
38 | "exporter": {
39 | "name": "Camunda Modeler",
40 | "version": "5.14.0"
41 | },
42 | "schemaVersion": 10
43 | }
--------------------------------------------------------------------------------
/src/__tests__/testdata/generic-test.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_008b7bk
6 |
7 |
8 |
9 | SequenceFlow_06y8siu
10 |
11 |
12 |
13 |
14 |
15 |
16 | SequenceFlow_008b7bk
17 | SequenceFlow_06y8siu
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 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/hello-world-complete.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_112zghv
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/hello-world.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SequenceFlow_0fp53hs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0fp53hs
15 | SequenceFlow_112zghv
16 |
17 |
18 |
19 | SequenceFlow_112zghv
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/__tests__/testdata/quarantine-duration.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | employment_category
13 |
14 |
15 | "critical infrastructure","non-critical infrastructure"
16 |
17 |
18 |
19 |
20 | age
21 |
22 |
23 |
24 |
25 | People who are 30 and under recover faster, and we need to keep critical infrastructure running
26 |
27 | "critical infrastructure"
28 |
29 |
30 | <=30
31 |
32 |
33 | "P5D"
34 |
35 |
36 |
37 | People who are 30 and under recover faster
38 |
39 | "non-critical infrastructure"
40 |
41 |
42 | <=30
43 |
44 |
45 | "P7D"
46 |
47 |
48 |
49 | People who are over 30 take longer to recover
50 |
51 |
52 |
53 |
54 | >30
55 |
56 |
57 | "P10D"
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ZBJsonLogger } from './lib/ZBJsonLogger'
2 | export { BpmnParser } from './lib/BpmnParser'
3 | export * from './lib/interfaces-1.0'
4 | export * from './lib/interfaces-published-contract'
5 | export * from './lib/interfaces-grpc-1.0'
6 | export * from './zb/ZBClient'
7 | export * from './zb/ZBWorker'
8 | export { ZBLogger } from './lib/ZBLogger'
9 | export { ZBSimpleLogger } from './lib/SimpleLogger'
10 | export { Duration } from 'typed-duration'
11 |
--------------------------------------------------------------------------------
/src/lib/ConnectionFactory.ts:
--------------------------------------------------------------------------------
1 | import { GrpcClientCtor } from './GrpcClient'
2 | import { GrpcMiddleware } from './GrpcMiddleware'
3 | import { ZBLoggerConfig } from './interfaces-1.0'
4 | import { StatefulLogInterceptor } from './StatefulLogInterceptor'
5 |
6 | export type GrpcConnectionProfile = 'CAMUNDA_CLOUD' | 'VANILLA'
7 | export interface Characteristics {
8 | startupTime: number
9 | _tag: GrpcConnectionProfile
10 | }
11 |
12 | export const ConnectionCharacteristics: {
13 | [key in GrpcConnectionProfile]: Characteristics
14 | } = {
15 | CAMUNDA_CLOUD: {
16 | _tag: 'CAMUNDA_CLOUD',
17 | startupTime: parseInt(
18 | process.env.ZEEBE_INITIAL_CONNECTION_TOLERANCE || '6000',
19 | 10
20 | ),
21 | },
22 | VANILLA: {
23 | _tag: 'VANILLA',
24 | startupTime: parseInt(
25 | process.env.ZEEBE_INITIAL_CONNECTION_TOLERANCE || '0',
26 | 10
27 | ),
28 | },
29 | }
30 |
31 | export type State = 'ERROR' | 'CONNECTED' | 'UNKNOWN'
32 |
33 | export class ConnectionFactory {
34 | public static getGrpcClient({
35 | grpcConfig,
36 | logConfig,
37 | }: {
38 | grpcConfig: GrpcClientCtor
39 | logConfig: ZBLoggerConfig
40 | }) {
41 | const characteristics = ConnectionFactory.getCharacteristics(
42 | grpcConfig.host
43 | )
44 | const log = new StatefulLogInterceptor({ characteristics, logConfig })
45 | const grpcClient = new GrpcMiddleware({
46 | characteristics,
47 | config: grpcConfig,
48 | log,
49 | }).getGrpcClient()
50 | const _close = grpcClient.close.bind(grpcClient)
51 | grpcClient.close = async () => {
52 | log.close()
53 | _close()
54 | return null
55 | }
56 |
57 | return { grpcClient, log }
58 | }
59 |
60 | private static getCharacteristics(host: string): Characteristics {
61 | const isCamundaCloud = host.includes('zeebe.camunda.io')
62 | const profile = isCamundaCloud ? 'CAMUNDA_CLOUD' : 'VANILLA'
63 | return ConnectionCharacteristics[profile]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/lib/EnvFunction.ts:
--------------------------------------------------------------------------------
1 | type EnvFunction = , K extends string>(
2 | keys: T
3 | ) => {
4 | [key1 in T[number]]: string
5 | }
6 |
7 | export const getEnv: EnvFunction = keys => {
8 | return keys.reduce(
9 | (prev, current) => ({
10 | ...prev,
11 | [current]: process.env[current],
12 | }),
13 | {} as any
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/GrpcError.ts:
--------------------------------------------------------------------------------
1 | export const GrpcError = {
2 | OK: 0 as 0,
3 | CANCELLED: 1 as 1,
4 | UNKNOWN: 2 as 2,
5 | INVALID_ARGUMENT: 3 as 3,
6 | DEADLINE_EXCEEDED: 4 as 4,
7 | NOT_FOUND: 5 as 5,
8 | ALREADY_EXISTS: 6 as 6,
9 | PERMISSION_DENIED: 7 as 7,
10 | UNAUTHENTICATED: 16 as 16,
11 | RESOURCE_EXHAUSTED: 8 as 8,
12 | FAILED_PRECONDITION: 9 as 9,
13 | ABORTED: 10 as 10,
14 | OUT_OF_RANGE: 11 as 11,
15 | UNIMPLEMENTED: 12 as 12,
16 | INTERNAL: 13 as 13,
17 | UNAVAILABLE: 14 as 14,
18 | DATA_LOSS: 15 as 15,
19 | };
20 |
--------------------------------------------------------------------------------
/src/lib/GrpcMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { ConnectionStatusEvent } from '../zb/ZBClient'
2 | import { Characteristics, State } from './ConnectionFactory'
3 | import { GrpcClient, GrpcClientCtor, MiddlewareSignals } from './GrpcClient'
4 | import { StatefulLogInterceptor } from './StatefulLogInterceptor'
5 |
6 | export class GrpcMiddleware {
7 | public blocking: boolean
8 | public state: State
9 | public log: StatefulLogInterceptor
10 | private grpcClient: GrpcClient
11 | private characteristics: Characteristics
12 | private blockingTimer?: NodeJS.Timeout
13 |
14 | constructor({
15 | characteristics,
16 | config,
17 | log,
18 | }: {
19 | characteristics: Characteristics
20 | config: GrpcClientCtor
21 | log: StatefulLogInterceptor
22 | }) {
23 | this.characteristics = characteristics
24 | this.blocking = this.characteristics.startupTime > 0
25 | this.state = 'UNKNOWN'
26 | log.logDebug(`Grpc Middleware blocking: ${this.blocking}`)
27 | if (this.blocking) {
28 | this.blockingTimer = setTimeout(() => {
29 | this.blocking = false
30 | log.logDebug(`Grpc Middleware state: ${this.state}`)
31 | if (this.state === 'ERROR') {
32 | this.emitError(new Error(`Did not establish connection before deadline ${this.characteristics.startupTime}ms`))
33 | } else if (this.state === 'CONNECTED') {
34 | this.emitReady()
35 | } else if (this.state === 'UNKNOWN') {
36 | this.grpcClient.emit(ConnectionStatusEvent.unknown)
37 | }
38 | }, this.characteristics.startupTime)
39 | }
40 | this.log = log
41 | this.grpcClient = this.createInterceptedGrpcClient(config)
42 | }
43 | public getGrpcClient = () => this.grpcClient
44 |
45 | private createInterceptedGrpcClient(config: GrpcClientCtor) {
46 | const grpcClient = new GrpcClient(config)
47 | const logInterceptor = this.log
48 | const _close = grpcClient.close.bind(grpcClient)
49 | grpcClient.close = async () => {
50 | if (this.blockingTimer) {
51 | clearTimeout(this.blockingTimer)
52 | }
53 | _close()
54 | return null
55 | }
56 | grpcClient.on(MiddlewareSignals.Log.Debug, logInterceptor.logDebug)
57 | grpcClient.on(MiddlewareSignals.Log.Info, logInterceptor.logInfo)
58 | grpcClient.on(MiddlewareSignals.Log.Error, logInterceptor.logError)
59 | grpcClient.on(MiddlewareSignals.Event.Error, (err) => {
60 | this.state = 'ERROR'
61 | logInterceptor.connectionError()
62 | if (!this.blocking) {
63 | this.emitError(err)
64 | }
65 | })
66 | grpcClient.on(MiddlewareSignals.Event.Ready, () => {
67 | this.state = 'CONNECTED'
68 | logInterceptor.ready()
69 | if (!this.blocking) {
70 | this.emitReady()
71 | logInterceptor.logDebug(`Middleware emits ready`)
72 | } else {
73 | logInterceptor.logDebug(`Blocked ready emit`)
74 | }
75 | })
76 | grpcClient.on(
77 | MiddlewareSignals.Event.GrpcInterceptError,
78 | this.handleExceptionalGrpc
79 | )
80 | return grpcClient
81 | }
82 |
83 | private emitError = (err: Error) =>
84 | this.grpcClient.emit(ConnectionStatusEvent.connectionError, err)
85 | private emitReady = () => this.grpcClient.emit(ConnectionStatusEvent.ready)
86 | private handleExceptionalGrpc = ({
87 | callStatus,
88 | options,
89 | }: {
90 | callStatus: GrpcCallStatus
91 | options: GrpcOptions
92 | }) => {
93 | if (options.method_definition.path === 'not-happening') {
94 | this.log.logDebug(
95 | 'This is to stop the compiler choking on an unused parameter while I figure out which cases to handle.'
96 | )
97 | }
98 | if (callStatus.code === 1 && callStatus.details.includes('503')) {
99 | this.log.logError(
100 | 'The gateway returned HTTP Error 503 (Bad Gateway). This can be a transient failure while a Kubernetes node in Camunda Cloud is being pre-empted.'
101 | )
102 | }
103 | }
104 | }
105 |
106 | interface GrpcCallStatus {
107 | code: number
108 | details: string
109 | }
110 | interface GrpcOptions {
111 | method_definition: {
112 | /** The full path of the call, i.e. '/gateway_protocol.Gateway/SetVariables' */
113 | path: string
114 | requestStream: boolean
115 | responseStream: boolean
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/lib/JobBatcher.ts:
--------------------------------------------------------------------------------
1 | import { ZBBatchWorker } from '../zb/ZBBatchWorker'
2 | import {
3 | BatchedJob,
4 | ICustomHeaders,
5 | IInputVariables,
6 | IOutputVariables,
7 | ZBBatchWorkerTaskHandler,
8 | } from './interfaces-1.0'
9 | import { Queue } from './Queue'
10 |
11 | export class JobBatcher {
12 | private batchedJobs: Queue = new Queue()
13 | private handler: ZBBatchWorkerTaskHandler
14 | private timeout: number
15 | private batchSize: number
16 | private worker: ZBBatchWorker
17 | private batchExecutionTimerHandle: any
18 | constructor({
19 | handler,
20 | timeout,
21 | batchSize,
22 | worker,
23 | }: {
24 | handler: ZBBatchWorkerTaskHandler
25 | timeout: number
26 | batchSize: number
27 | worker: ZBBatchWorker
28 | }) {
29 | this.handler = handler
30 | this.timeout = timeout
31 | this.batchSize = batchSize
32 | this.worker = worker
33 | }
34 |
35 | public batch(
36 | batch: Array<
37 | BatchedJob
38 | >
39 | ) {
40 | if (!this.batchExecutionTimerHandle) {
41 | this.batchExecutionTimerHandle = setTimeout(
42 | () => this.execute(),
43 | this.timeout * 1000
44 | )
45 | }
46 | batch.forEach(this.batchedJobs.push)
47 | if (this.batchedJobs.length() >= this.batchSize) {
48 | clearTimeout(this.batchExecutionTimerHandle)
49 | this.execute()
50 | }
51 | }
52 |
53 | private execute() {
54 | this.batchExecutionTimerHandle = undefined
55 | this.worker.debug(
56 | `Executing batched handler with ${this.batchedJobs.length()} jobs`
57 | )
58 | try {
59 | this.handler(this.batchedJobs.drain(), this.worker)
60 | } catch (e: any) {
61 | this.worker.error(
62 | `An unhandled exception occurred in the worker task handler!`
63 | )
64 | this.worker.error(e.message)
65 | this.worker.error(e)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/lib/MockStdOut.ts:
--------------------------------------------------------------------------------
1 | import { ZBCustomLogger } from './interfaces-published-contract'
2 | export class MockStdOut implements ZBCustomLogger {
3 | public messages: string[] = []
4 |
5 | public info(message: string) {
6 | this.messages.push(message)
7 | }
8 |
9 | public error(message: string) {
10 | this.messages.push(message)
11 | }
12 |
13 | public debug(message: string) {
14 | this.messages.push(message)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/lib/Queue.ts:
--------------------------------------------------------------------------------
1 | export class Queue {
2 | private q: T[] = []
3 | public push = (element: T) => this.q.push(element)
4 |
5 | public pop = (): T | undefined => this.q.shift()
6 |
7 | public isEmpty = (): boolean => this.q.length > 0
8 |
9 | public drain = () => this.q.splice(0, this.q.length)
10 |
11 | public length = (): number => this.q.length
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/SimpleLogger.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 | import { Loglevel } from './interfaces-published-contract'
3 |
4 | export interface Logger {
5 | error: LogFn
6 | info: LogFn
7 | debug: LogFn
8 | }
9 | type LogFn = (logMessage: string) => void
10 |
11 | /**
12 | * Simple logger for ZBClient
13 | */
14 | const logger = (loglevel: Loglevel): LogFn => (logMessage: string): void => {
15 | let message: string
16 | try {
17 | const parsedMessage = JSON.parse(logMessage)
18 | const gRPC =
19 | parsedMessage.id === 'gRPC Channel' ? ' [gRPC Channel]:' : ''
20 | const taskType = parsedMessage.taskType
21 | ? ` [${parsedMessage.taskType}]`
22 | : ''
23 | const msg =
24 | typeof parsedMessage.message === 'object'
25 | ? JSON.stringify(parsedMessage.message)
26 | : parsedMessage.message
27 | message = `| zeebe | ${gRPC}${taskType} ${loglevel}: ${msg}`
28 | } catch (e: any) {
29 | message = logMessage
30 | }
31 | const time = dayjs().format('HH:mm:ss.SSS')
32 | const err = new Error('Debug stack trace')
33 | const stack = err.stack!.split('\n')
34 |
35 | // tslint:disable: no-console
36 | const loggers = {
37 | DEBUG: console.info,
38 | ERROR: console.error,
39 | INFO: console.info,
40 | }
41 | const logMethod = loggers[loglevel]
42 | const info =
43 | loglevel === 'DEBUG'
44 | ? `${time} ${message}\n${stack}`
45 | : `${time} ${message}`
46 | logMethod(info)
47 | }
48 |
49 | export const ZBSimpleLogger: Logger = {
50 | debug: logger('DEBUG'),
51 | error: logger('ERROR'),
52 | info: logger('INFO'),
53 | }
54 |
--------------------------------------------------------------------------------
/src/lib/StatefulLogInterceptor.ts:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk'
2 | import { Characteristics, State } from './ConnectionFactory'
3 | import { ZBLoggerConfig } from './interfaces-1.0'
4 | import { ZBLogger } from './ZBLogger'
5 |
6 | export class StatefulLogInterceptor {
7 | public characteristics: Characteristics
8 | public log: ZBLogger
9 | public blocking: boolean
10 | public state: State = 'ERROR'
11 | public errors = []
12 | public logs = []
13 | public initialConnection: boolean
14 | private blockingTimer?: NodeJS.Timeout
15 | constructor({
16 | characteristics,
17 | logConfig,
18 | }: {
19 | characteristics: Characteristics
20 | logConfig: ZBLoggerConfig
21 | }) {
22 | this.characteristics = characteristics
23 | this.log = new ZBLogger(logConfig)
24 | this.initialConnection = false
25 | this.blocking =
26 | characteristics.startupTime > 0 && this.log.loglevel !== 'DEBUG'
27 | if (this.blocking) {
28 | this.logDirect(
29 | chalk.yellowBright(
30 | 'Authenticating client with Camunda Cloud...'
31 | )
32 | )
33 | this.blockingTimer = setTimeout(() => {
34 | if (!this.blocking) {
35 | return
36 | }
37 | this.blocking = false
38 | return this.state === 'ERROR'
39 | ? this.emptyErrors()
40 | : this.emptyLogs()
41 | }, this.characteristics.startupTime)
42 | }
43 | }
44 |
45 | public close() {
46 | if (this.blockingTimer) {
47 | clearTimeout(this.blockingTimer)
48 | }
49 | }
50 |
51 | public logError = err => this.error(err)
52 | public logInfo = msg => this.info(msg)
53 | public logDebug = (msg, ...args) => this.log.debug(msg, ...args)
54 | public logDirect = msg => this.log._tag === 'ZBCLIENT' && this.log.info(msg)
55 | public connectionError = () => {
56 | this.state = 'ERROR'
57 | }
58 | public ready = () => {
59 | this.state = 'CONNECTED'
60 | if (this.blocking) {
61 | this.blocking = false
62 | this.emptyLogs()
63 | }
64 | }
65 | private emptyErrors() {
66 | if (this.errors.length === 0) {
67 | return
68 | }
69 | this.errors.forEach(err => this.logError(err))
70 | this.logDirect(chalk.redBright('Error connecting to Camunda Cloud.'))
71 | this.errors = []
72 | }
73 | private emptyLogs() {
74 | if (!this.initialConnection) {
75 | this.initialConnection = true
76 | this.logDirect(
77 | chalk.greenBright(
78 | 'Established encrypted connection to Camunda Cloud.'
79 | )
80 | )
81 | }
82 | if (this.logs.length === 0) {
83 | return
84 | }
85 | this.logs.forEach(msg => this.logInfo(msg))
86 | this.logs = []
87 | }
88 | private wrap = (store: string[]) => (
89 | logmethod: (msg: any, ...optionalParameters: any[]) => void
90 | ) => (msg: string) => {
91 | if (this.blocking && this.state === 'ERROR') {
92 | store.push(msg)
93 | return
94 | }
95 | logmethod(msg)
96 | }
97 | // tslint:disable-next-line: member-ordering
98 | private info = this.wrap(this.logs)(m => this.log.info(m))
99 |
100 | // tslint:disable-next-line: member-ordering
101 | private error = this.wrap(this.errors)(e => this.log.error(e))
102 | }
103 |
--------------------------------------------------------------------------------
/src/lib/TypedEmitter.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events'
2 |
3 | type EventMap = Record
4 |
5 | type EventKey = string & keyof T
6 | type EventReceiver = () => void
7 |
8 | interface Emitter {
9 | on>(eventName: K, fn: EventReceiver): void
10 | off>(eventName: K, fn: EventReceiver): void
11 | emit>(eventName: K, params?: T[K]): void
12 | }
13 |
14 | export class TypedEmitter implements Emitter {
15 | private emitter = new EventEmitter()
16 | public on>(eventName: K, fn: EventReceiver) {
17 | this.emitter.on(eventName, fn)
18 | return this
19 | }
20 |
21 | public off>(eventName: K, fn: EventReceiver) {
22 | this.emitter.off(eventName, fn)
23 | }
24 |
25 | public emit>(eventName: K, params?: T[K]) {
26 | this.emitter.emit(eventName, params)
27 | }
28 |
29 | public removeAllListeners() {
30 | this.emitter.removeAllListeners()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/lib/ZBJsonLogger.ts:
--------------------------------------------------------------------------------
1 | export const ZBJsonLogger = {
2 | // tslint:disable-next-line: no-console
3 | error: console.error,
4 | // tslint:disable-next-line: no-console
5 | info: console.log,
6 | // tslint:disable-next-line: no-console
7 | debug: console.log,
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/ZBLogger.ts:
--------------------------------------------------------------------------------
1 | import { Chalk } from 'chalk'
2 | import dayjs from 'dayjs'
3 | import * as stackTrace from 'stack-trace'
4 | import { Duration, MaybeTimeDuration } from 'typed-duration'
5 | import { ConfigurationHydrator } from './ConfigurationHydrator'
6 | import { ZBLoggerConfig } from './interfaces-1.0'
7 | import { Loglevel } from './interfaces-published-contract'
8 |
9 | export class ZBLogger {
10 | // tslint:disable-next-line: variable-name
11 | public _tag: 'ZBCLIENT' | 'ZBWORKER'
12 | public loglevel: Loglevel
13 | private colorFn: Chalk
14 | private taskType?: string
15 | private id?: string
16 | private stdout: any
17 | private colorise: boolean
18 | private pollInterval?: MaybeTimeDuration
19 | private namespace: string | undefined
20 |
21 | constructor({
22 | loglevel,
23 | color,
24 | id,
25 | namespace,
26 | stdout,
27 | taskType,
28 | colorise,
29 | pollInterval,
30 | _tag,
31 | }: ZBLoggerConfig) {
32 | this._tag = _tag
33 | this.colorFn = color || ((m => m) as any)
34 | this.taskType = taskType
35 | this.id = id
36 | if (Array.isArray(namespace)) {
37 | namespace = namespace.join(' ')
38 | }
39 | this.namespace = namespace
40 | this.loglevel =
41 | ConfigurationHydrator.getLogLevelFromEnv() || loglevel || 'INFO'
42 | this.stdout = stdout || console
43 | this.colorise = colorise !== false
44 | this.pollInterval = pollInterval
45 | ? Duration.milliseconds.from(pollInterval)
46 | : pollInterval
47 | }
48 |
49 | public info(message: any, ...optionalParameters) {
50 | if (this.loglevel === 'NONE' || this.loglevel === 'ERROR') {
51 | return
52 | }
53 | const frame = stackTrace.get()[1]
54 | const msg =
55 | optionalParameters.length > 0
56 | ? this.makeMessage(frame, 30, message, optionalParameters)
57 | : this.makeMessage(frame, 30, message)
58 | this.stdout.info(msg)
59 | }
60 |
61 | public error(message, ...optionalParameters: any[]) {
62 | if (this.loglevel === 'NONE') {
63 | return
64 | }
65 | const frame = stackTrace.get()[1]
66 |
67 | const msg =
68 | optionalParameters.length > 0
69 | ? this.makeMessage(frame, 50, message, optionalParameters)
70 | : this.makeMessage(frame, 50, message)
71 | this.stdout.error(msg)
72 | }
73 |
74 | public debug(message: any, ...optionalParameters: any[]) {
75 | if (this.loglevel !== 'DEBUG') {
76 | return
77 | }
78 | const frame = stackTrace.get()[1]
79 |
80 | const msg =
81 | optionalParameters.length > 0
82 | ? this.makeMessage(frame, 20, message, optionalParameters)
83 | : this.makeMessage(frame, 20, message)
84 | if (this.stdout === console) {
85 | this.stdout.info(this._colorise(msg))
86 | } else {
87 | this.stdout.info(msg)
88 | }
89 | }
90 |
91 | public log(message: any, ...optionalParameters: any[]) {
92 | if (this.loglevel === 'NONE' || this.loglevel === 'ERROR') {
93 | return
94 | }
95 | const frame = stackTrace.get()[1]
96 |
97 | const msg =
98 | optionalParameters.length > 0
99 | ? this.makeMessage(frame, 30, message, optionalParameters)
100 | : this.makeMessage(frame, 30, message)
101 | this.stdout.info(msg)
102 | }
103 |
104 | private makeMessage(
105 | frame: stackTrace.StackFrame,
106 | level: number,
107 | message,
108 | ...optionalParameters
109 | ) {
110 | // tslint:disable: object-literal-sort-keys
111 | const msg: any = {
112 | timestamp: new Date(),
113 | context: `${frame.getFileName()}:${frame.getLineNumber()}`,
114 | id: this.id,
115 | level,
116 | message,
117 | time: dayjs().format('YYYY MMM-DD HH:mm:ssA'),
118 | }
119 |
120 | if (this.pollInterval) {
121 | msg.pollInterval = this.pollInterval
122 | }
123 | if (this.namespace) {
124 | msg.namespace = this.namespace
125 | }
126 | if (this.taskType) {
127 | msg.taskType = this.taskType
128 | }
129 | if (optionalParameters.length > 0) {
130 | ;(msg as any).data = optionalParameters
131 | }
132 | return JSON.stringify(msg)
133 | }
134 |
135 | private _colorise(message: string) {
136 | if (this.colorise) {
137 | // Only colorise console
138 | if (this.colorFn && typeof this.colorFn === 'function') {
139 | return this.colorFn(message)
140 | } else {
141 | return message
142 | }
143 | }
144 | return message
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/lib/ZBWorkerSignature.ts:
--------------------------------------------------------------------------------
1 | import * as ZB from './interfaces-1.0'
2 | import { ZBClientOptions } from './interfaces-published-contract'
3 |
4 | function isConfig(
5 | config: any
6 | ): config is ZB.ZBBatchWorkerConfig {
7 | return typeof config === 'object'
8 | }
9 |
10 | const cleanEmpty = obj =>
11 | Object.entries(obj)
12 | .map(([k, v]) => [
13 | k,
14 | v && typeof v === 'object'
15 | ? !Array.isArray(v)
16 | ? cleanEmpty(v)
17 | : v
18 | : v,
19 | ])
20 | .reduce((a, [k, v]) => (v == null ? a : { ...a, [k]: v }), {})
21 |
22 | export function decodeCreateZBWorkerSig<
23 | WorkerInputVariables,
24 | CustomHeaderShape,
25 | WorkerOutputVariables
26 | >(config) {
27 | const coerceConf = config.idOrTaskTypeOrConfig
28 | const conf = isConfig(coerceConf) ? coerceConf : undefined
29 | if (conf) {
30 | return cleanEmpty({
31 | id: conf.id,
32 | onConnectionError: conf.onConnectionError,
33 | onReady: conf.onReady,
34 | options: {
35 | ...conf,
36 | onReady: undefined,
37 | taskHandler: undefined,
38 | taskType: undefined,
39 | },
40 | taskHandler: conf.taskHandler,
41 | taskType: conf.taskType,
42 | })
43 | }
44 | const isShorthandSig = typeof config.taskTypeOrTaskHandler === 'function'
45 | const taskHandler: ZB.ZBWorkerTaskHandler<
46 | WorkerInputVariables,
47 | CustomHeaderShape,
48 | WorkerOutputVariables
49 | > = isShorthandSig
50 | ? (config.taskTypeOrTaskHandler as ZB.ZBWorkerTaskHandler<
51 | WorkerInputVariables,
52 | CustomHeaderShape,
53 | WorkerOutputVariables
54 | >)
55 | : (config.taskHandlerOrOptions as ZB.ZBWorkerTaskHandler<
56 | WorkerInputVariables,
57 | CustomHeaderShape,
58 | WorkerOutputVariables
59 | >)
60 | const id: string | null = isShorthandSig
61 | ? (config.idOrTaskTypeOrConfig as string)
62 | : null
63 | const taskType: string = isShorthandSig
64 | ? (config.idOrTaskTypeOrConfig as string)
65 | : (config.taskTypeOrTaskHandler as string)
66 | const options: ZB.ZBWorkerOptions & ZBClientOptions =
67 | (isShorthandSig
68 | ? config.taskHandlerOrOptions
69 | : config.optionsOrOnConnectionError) || {}
70 | const onConnectionError = isShorthandSig
71 | ? config.optionsOrOnConnectionError
72 | : config.onConnectionError ||
73 | options.onConnectionError ||
74 | config.onConnectionError
75 | const onReady = options.onReady
76 | return cleanEmpty({
77 | id,
78 | onConnectionError,
79 | onReady,
80 | options,
81 | taskHandler,
82 | taskType,
83 | })
84 | }
85 |
--------------------------------------------------------------------------------
/src/lib/cancelProcesses.ts:
--------------------------------------------------------------------------------
1 | import { OperateApiClient } from 'operate-api-client'
2 |
3 | const operate = createClient()
4 |
5 | export async function cancelProcesses(processDefinitionKey: string) {
6 | if (!operate) { return }
7 | const processes = await operate.searchProcessInstances({
8 | filter: {
9 | processDefinitionKey: +processDefinitionKey
10 | }
11 | })
12 | await Promise.all(processes.items.map(item =>
13 | operate.deleteProcessInstance(+item.bpmnProcessId)
14 | ))
15 | }
16 |
17 |
18 | function createClient() {
19 | try {
20 | return new OperateApiClient()
21 | } catch (e: any) {
22 | // console.log(e.message)
23 | // console.log(`Running without access to Operate`)
24 | return null
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/createUniqueTaskType.ts:
--------------------------------------------------------------------------------
1 | // import { readFileSync } from 'fs'
2 | // import { v4 as uuid } from 'uuid'
3 | // // Replace a tasktype in a bpmn model with a unique tasktype
4 | // // This deals with stateful tests
5 | // export function createUniqueTaskType({
6 | // bpmnFilePath,
7 | // taskTypes,
8 | // messages,
9 | // }: {
10 | // bpmnFilePath: string
11 | // taskTypes: string[]
12 | // messages: string[]
13 | // }): {
14 | // bpmn: Buffer
15 | // taskTypes: { [key: string]: string }
16 | // messages: { [key: string]: string }
17 | // processId: string
18 | // } {
19 | // const bpmn = readFileSync(bpmnFilePath, 'utf8')
20 | // const newTaskTypes = taskTypes.map(t => ({ [t]: uuid() }))
21 | // const newMessages = messages.map(m => ({ [m]: uuid() }))
22 |
23 | // const replacedTasks =
24 | // newTaskTypes.length > 0
25 | // ? newTaskTypes.reduce(
26 | // (p, c) =>
27 | // p
28 | // .split(
29 | // ` p.split(Object.keys(c)[0]).join(c[Object.keys(c)[0]]),
45 | // replacedTasks
46 | // )
47 | // : replacedTasks
48 |
49 | // const processIdPieces = replacedMessages.split(' extends ZBWorkerBase {
14 | private jobBatchMaxTime: number
15 | private jobBuffer: { batch: (job: any) => void }
16 | constructor(
17 | config: ZBBatchWorkerConstructorConfig<
18 | InputVariables,
19 | Headers,
20 | OutputVariables
21 | >
22 | ) {
23 | super(config)
24 | this.jobBatchMaxTime = config.options.jobBatchMaxTime
25 | if (this.timeout < this.jobBatchMaxTime) {
26 | const jobBatchMaxTimeout = this.jobBatchMaxTime
27 | this.log(`\n`)
28 | this.log(chalk.redBright(`=================================`))
29 | this.log(
30 | `${chalk.yellowBright('WARNING:')} The ${
31 | this.taskType
32 | } batch worker is ${chalk.yellowBright('MISCONFIGURED')}.`
33 | )
34 | this.log(
35 | `Its settings can ${chalk.yellowBright(
36 | 'RESULT IN ITS JOBS TIMING OUT'
37 | )}.`
38 | )
39 | this.log(
40 | `The ${chalk.greenBright(
41 | 'jobBatchMaxTimeout'
42 | )}: ${jobBatchMaxTimeout} is longer than the ${chalk.greenBright(
43 | 'timeout'
44 | )}: ${this.timeout}.`
45 | )
46 | this.log(
47 | `This can lead to jobs timing out and retried by the broker before this worker executes their batch.`
48 | )
49 | this.log(
50 | chalk.redBright(
51 | 'This is probably not the behaviour you want. Read the docs, and reconsider your life choices.'
52 | )
53 | )
54 | this.log(
55 | chalk.yellowBright(
56 | `Recommended: Reconfigure the ZBBatchWorker to have a higher timeout than its jobBatchMaxTimeout setting.`
57 | )
58 | )
59 | this.log(chalk.redBright(`=================================`))
60 | this.log(`\n`)
61 | }
62 | this.jobBuffer = new JobBatcher({
63 | batchSize: this.jobBatchMinSize,
64 | handler: this.taskHandler as ZB.ZBBatchWorkerTaskHandler<
65 | any,
66 | any,
67 | any
68 | >,
69 | timeout: this.jobBatchMaxTime,
70 | worker: this,
71 | })
72 | }
73 |
74 | protected async handleJobs(jobs: ZB.Job[]) {
75 | const batchedJobs = jobs.map(
76 | (job): ZB.BatchedJob => ({
77 | ...job,
78 | ...this.makeCompleteHandlers(job),
79 | })
80 | )
81 | this.jobBuffer.batch(batchedJobs)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/zb/ZBWorker.ts:
--------------------------------------------------------------------------------
1 | import * as ZB from '../lib/interfaces-1.0'
2 |
3 | import { ZBWorkerBase, ZBWorkerConstructorConfig } from '../lib/ZBWorkerBase'
4 |
5 | export class ZBWorker<
6 | WorkerInputVariables,
7 | CustomHeaderShape,
8 | WorkerOutputVariables
9 | > extends ZBWorkerBase<
10 | WorkerInputVariables,
11 | CustomHeaderShape,
12 | WorkerOutputVariables
13 | > {
14 | constructor(
15 | config: ZBWorkerConstructorConfig<
16 | WorkerInputVariables,
17 | CustomHeaderShape,
18 | WorkerOutputVariables
19 | >
20 | ) {
21 | super(config)
22 | }
23 |
24 | protected handleJobs(
25 | jobs: ZB.Job[]
26 | ) {
27 | // Call task handler for each new job
28 | jobs.forEach(async job => this.handleJob(job))
29 | }
30 |
31 | protected async handleJob(
32 | job: ZB.Job
33 | ) {
34 | try {
35 | /**
36 | * complete.success(variables?: object) and complete.failure(errorMessage: string, retries?: number)
37 | *
38 | * To halt execution of the business process and raise an incident in Operate, call
39 | * complete.failure(errorMessage, 0)
40 | */
41 |
42 | const workerCallback = this.makeCompleteHandlers(job)
43 |
44 | await (this.taskHandler as ZB.ZBWorkerTaskHandler<
45 | WorkerInputVariables,
46 | CustomHeaderShape,
47 | WorkerOutputVariables
48 | >)(
49 | {
50 | ...job,
51 | cancelWorkflow: workerCallback.cancelWorkflow,
52 | complete: workerCallback.complete,
53 | fail: workerCallback.fail,
54 | error: workerCallback.error,
55 | forward: workerCallback.forward,
56 | },
57 | this
58 | )
59 | } catch (e: any) {
60 | this.logger.logError(
61 | `Caught an unhandled exception in a task handler for process instance ${job.processInstanceKey}:`
62 | )
63 | this.logger.logDebug(job)
64 | this.logger.logError(e.message)
65 | if (this.cancelWorkflowOnException) {
66 | const { processInstanceKey } = job
67 | this.logger.logDebug(
68 | `Cancelling process instance ${processInstanceKey}`
69 | )
70 | try {
71 | await this.zbClient.cancelProcessInstance(
72 | processInstanceKey
73 | )
74 | } finally {
75 | this.drainOne()
76 | }
77 | } else {
78 | this.logger.logInfo(`Failing job ${job.key}`)
79 | const retries = job.retries - 1
80 | try {
81 | this.zbClient.failJob({
82 | errorMessage: `Unhandled exception in task handler ${e}`,
83 | jobKey: job.key,
84 | retries,
85 | retryBackOff: 0,
86 | })
87 | } catch (e: any) {
88 | this.logger.logDebug(e)
89 | } finally {
90 | this.drainOne()
91 | if (retries > 0) {
92 | this.logger.logDebug(
93 | `The Zeebe engine will handle the retry. Retries left: ${retries}`
94 | )
95 | } else {
96 | this.logger.logDebug('No retries left for this task')
97 | }
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true /* Required because referenced by src/tsconfig.json */,
4 | /* Basic Options */
5 | "target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
7 | "types": ["node", "jest"],
8 | "lib": ["es2018"],
9 | "declaration": true /* Generates corresponding '.d.ts' file. */,
10 | "sourceMap": true /* Generates corresponding '.map' file. */,
11 | // "outDir": "dist" /* Redirect output structure to the directory. */,
12 | // "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
13 | /* Strict Type-Checking Options */
14 | "forceConsistentCasingInFileNames": true,
15 | "strict": true /* Enable all strict type-checking options. */,
16 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
17 | "noImplicitReturns": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "strictNullChecks": true,
21 | "esModuleInterop": true,
22 | "experimentalDecorators": true,
23 | "resolveJsonModule": true,
24 | "plugins": [
25 | {
26 | "name": "typescript-tslint-plugin"
27 | }
28 | ]
29 | },
30 | "include": ["./package.json"]
31 | }
32 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:latest", "tslint-config-prettier"],
4 | "jsRules": {},
5 | "rules": {
6 | "interface-name": false,
7 | "indent": [true, "tabs", 4],
8 | "semicolon": false,
9 | "quotemark": false,
10 | "trailing-comma": false,
11 | "arrow-parens": false,
12 | "no-submodule-imports": false
13 | },
14 | "rulesDirectory": [],
15 | "linterOptions": {
16 | "exclude": [
17 | "./node_modules/**/*",
18 | "./dist/**/*",
19 | "./example/**/*",
20 | "./docs/**/*"
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------