{"id":13408254,"url":"https://github.com/VilledeMontreal/workit","last_synced_at":"2025-03-14T12:32:32.591Z","repository":{"id":34932889,"uuid":"192401032","full_name":"VilledeMontreal/workit","owner":"VilledeMontreal","description":"Extensible worker for Node.js that works with both AWS Step function and Camunda BPM platforms powered by TypeScript","archived":false,"fork":false,"pushed_at":"2024-05-06T19:52:58.000Z","size":12117,"stargazers_count":64,"open_issues_count":7,"forks_count":13,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-05T03:09:32.806Z","etag":null,"topics":["aws-step-functions","camunda","hacktoberfest","microservices","nodejs","opentelemetry","typescript","worker"],"latest_commit_sha":null,"homepage":"https://villedemontreal.github.io/workit/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VilledeMontreal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-17T18:47:50.000Z","updated_at":"2024-10-12T21:20:50.000Z","dependencies_parsed_at":"2024-01-16T12:50:02.976Z","dependency_job_id":"6ef20f69-0465-48a4-8097-8349d583df86","html_url":"https://github.com/VilledeMontreal/workit","commit_stats":{"total_commits":228,"total_committers":10,"mean_commits":22.8,"dds":"0.45175438596491224","last_synced_commit":"7d925b100db8342e1f2d6a67b51c679d9cac09a1"},"previous_names":[],"tags_count":107,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VilledeMontreal%2Fworkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VilledeMontreal%2Fworkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VilledeMontreal%2Fworkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VilledeMontreal%2Fworkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VilledeMontreal","download_url":"https://codeload.github.com/VilledeMontreal/workit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243573168,"owners_count":20312880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-step-functions","camunda","hacktoberfest","microservices","nodejs","opentelemetry","typescript","worker"],"created_at":"2024-07-30T20:00:51.720Z","updated_at":"2025-03-14T12:32:27.576Z","avatar_url":"https://github.com/VilledeMontreal.png","language":"TypeScript","funding_links":[],"categories":["Projects","Clients and Programming Framework Integrations"],"sub_categories":[],"readme":"# WorkIt\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) ![npm](https://img.shields.io/npm/v/@villedemontreal/workit-types)\n\n[Français](README_FR.md)\n\n✨Extensible worker for Node.js that works with both AWS Step function and Camunda BPM platforms powered by TypeScript ✨\n\n## Motivation\n\nWe needed a framework to help us quickly build workers used to execute tasks.\n\nThis package can be useful because:\n-   Experiment and choose the platform you want without rewritting the business logic. Today, only Camunda and AWS Step function clients are maintained\n-   Instead of depending directly from a Camunda client, this project provides an abstraction layer. This way it’s easier to change the client or to make your own.\n-   You want to have a worker standardization.\n-   Uniformisation. Indeed, you can use both platforms depending project needs.\n-   Added features like automated tracing.\n\n## Quickstart\n\n[Get started in 2 minutes](getting-started/README.md).\n\n## Documentation\n\n-   [Documentation is available in this folder](packages/workit/.docs/)\n-   Comprehensive API documentation is available [online](https://villedemontreal.github.io/workit/) and in the `docs` subdirectory\n-   [Examples](examples)\n\n### API\n\n| Package | Description |\n| --- | ---|\n| [workit-types](https://github.com/VilledeMontreal/workit/tree/master/packages/workit-types) | This package provides TypeScript interfaces and enums for the Workit core model. \n| [workit-core](https://github.com/VilledeMontreal/workit/tree/master/packages/workit-core) | This package provides default and no-op implementations of the Workit types \n\n### Implementation / Clients\n\n| Package                                  | Description |\n| ---------------------------------------- | -----------------|\n| [workit-bpm-client](https://github.com/VilledeMontreal/workit/tree/master/packages/workit-bpm-client) | This module provides a full control over the Camunda Bpm platform.\u003cbr\u003e It use [`camunda-external-task-client-js`](https://github.com/camunda/camunda-external-task-client-js) by default. |\n| [workit-stepfunction-client](https://github.com/VilledeMontreal/workit/tree/master/packages/workit-stepfunction-client) | This module provides a full control over the Step functions platform.\u003cbr\u003e It use `@aws-sdk/client-sqs`, `@aws-sdk/client-sfn` by default. |\n\n## Installing\n\n```bash\nnpm i @villedemontreal/workit\n```\n\n## How to use\n\nSwitching between platforms is easy as specifying a `TAG` to the IoC.\n\n### Run worker\n\n```javascript\nconst worker = IoC.get\u003cWorker\u003e(CORE_IDENTIFIER.worker, TAG.camundaBpm);\n\nworker.start();\nworker.run();\n```\n\n### Deploy a workflow\n\n```javascript\nconst manager = IoC.get\u003cIWorkflowClient\u003e(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);\nconst fullpath = `${process.cwd()}/sample/BPMN_DEMO.bpmn`;\nawait manager.deployWorkflow(fullpath);\n```\n\n### Get workflows\n\n```javascript\nconst manager = IoC.get\u003cIWorkflowClient\u003e(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);\nawait manager.getWorkflows()\n```\n\n### Get a workflow\n\n```javascript\nconst manager = IoC.get\u003cIWorkflowClient\u003e(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);\nawait manager.getWorkflow({ bpmnProcessId: \"DEMO\" });\n```\n\n### Create workflow instance\n\n```javascript\nconst manager = IoC.get\u003cIWorkflowClient\u003e(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);\nawait manager.createWorkflowInstance({\n    bpmnProcessId: \"MY_BPMN_KEY\",\n    variables: {\n        hello: \"world\"\n    }\n});\n```\n\n### Define tasks (your bpmn activities)\n\nYou can define many tasks to one worker. It will handle all messages and will route to the right tasks.\n\n```javascript\nexport class HelloWorldTask extends TaskBase\u003cIMessage\u003e {\n  // You can type message like IMessage\u003cTBody, TProps\u003e default any\n  public execute(message: IMessage): Promise\u003cIMessage\u003e {\n      const { properties } = message;\n      console.log(`Executing task: ${properties.activityId}`);\n      console.log(`${properties.bpmnProcessId}::${properties.processInstanceId} Servus!`);\n      // put your business logic here\n      return Promise.resolve(message);\n  }\n}\n\nenum LOCAL_IDENTIFIER {\n    // sample_activity must match the activityId in your bpmn\n    sample_activity= 'sample_activity'\n}\n\n// Register your task\nIoC.bindTo(HelloWorldTask, LOCAL_IDENTIFIER.sample_activity);\n```\n\nYou can even make complex binding like\n```javascript\nIoC.bindTask(HelloWorldTaskV2, LOCAL_IDENTIFIER.activity1, { bpmnProcessId: BPMN_PROCESS_ID, version: 2 });\n```\n\nIf you have installed `workit-cli`, you can do `workit create task` \nand everything will be done for you.\n\n### Worker life cycle and events\n\n```javascript\nconst worker = IoC.get\u003cWorker\u003e(CORE_IDENTIFIER.worker, TAG.camundaBpm);\n\nworker.once('starting', () =\u003e {\n    // slack notification \n});\n\nworker.once('stopping', () =\u003e {\n    // close connections\n});\n\nworker.once('stopped', () =\u003e {\n    // slack notification\n});\n\nconst handler = worker.getProcessHandler();\n\nhandler.on('message', (msg: IMessage) =\u003e {\n    // log/audit\n});\n\nhandler.on('message-handled', (err: Error, msg: IMessage) =\u003e {\n    if (err) {\n        // something wrong\n    } else {\n        // everything is fine\n    }\n});\n\nworker.start();\nworker.run(); // Promise\nworker.stop(); // Promise\n```\n\n### Interceptors\n\n```javascript\nconst workerConfig = {\n  interceptors: [\n    async (message: IMessage): Promise\u003cIMessage\u003e =\u003e {\n      // do something before we execute task.\n      return message;\n    }\n  ]\n};\n\nIoC.bindToObject(workerConfig, CORE_IDENTIFIER.worker_config);\n```\n\n### OpenTelemetry\nBy default, we bound a `NoopTracer` but you can provide your own and it must extend [Tracer](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-api/src/trace/tracer.ts#L29).We strongly recommand to use this kind of pattern in your task: [Domain Probe pattern](https://martinfowler.com/articles/domain-oriented-observability.html#DomainProbesEnableCleanerMore-focusedTests). But here an example:\n\n```javascript\n// Simply bind your custom tracer object like this\nIoC.bindToObject(tracer, CORE_IDENTIFIER.tracer);\n```\n\n```javascript\nexport class HelloWorldTask extends TaskBase\u003cIMessage\u003e {\n  private readonly _tracer: Tracer;\n    \n  constructor(tracer: Tracer) {\n        this._tracer = tracer\n  }\n\n  public async execute(message: IMessage): Promise\u003cIMessage\u003e {\n      const { properties } = message;\n      \n      console.log(`Executing task: ${properties.activityId}`);\n      console.log(`${properties.bpmnProcessId}::${properties.processInstanceId} Servus!`);\n\n      // This call will be traced automatically\n      const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1');\n      \n      // you can also create a custom trace like this :\n      const currentSpan = tracer.getCurrentSpan();\n      const span = this._tracer.startSpan('customSpan', {\n        parent: currentSpan,\n        kind: SpanKind.CLIENT,\n        attributes: { key: 'value' },\n      });\n      \n      console.log();\n      console.log('data:');\n      console.log(response.data);\n      // put your business logic here\n\n      // finish the span scope\n      span.end();\n      \n      return Promise.resolve(message);\n  }\n}\n```\nYou can look to `sample` folder where we provide an example (parallel.ts) using [Jaeger](https://www.jaegertracing.io/docs/latest/).\n\n[See get started section with OpenTelemetry](packages/workit/.docs/WORKER.md#add-traces-to-your-worker-with-opentelemetry)\n\n### Define your config for the platform you want to use\n\nTODO show for step function\n\n### Define your strategies in case of failure or success\n\nBy default, we define simple strategy for success or failure. \nWe strongly recommend you to provide yours as your app trigger specific exceptions.\nStrategies are automatically handled.\nIf an exeption is bubble up from the task, failure strategy  is raised, otherwise it's success.\n\n```javascript\n// the idea is to create your own but imagine that your worker works mainly with HTTP REST API\nclass ServerErrorHandler extends ErrorHandlerBase {\n  constructor(config: { maxRetries: number }) {\n    super(config);\n  }\n\n  public isHandled(error: IErrorResponse\u003cIResponse\u003cIApiError\u003e\u003e): boolean {\n    return error.response.status \u003e= 500;\n  }\n  public handle(error: IErrorResponse\u003cIResponse\u003cIApiError\u003e\u003e, message: IMessage): Failure {\n    const retries = this.getRetryValue(message);\n    return new Failure(error.message, this.buildErrorDetails(error, message), retries, 2000 * retries);\n  }\n}\n\n// You got the idea...\n\n// You could create also\n// BadRequestErrorHandler\n// TimeoutErrorHandler\n// UnManagedErrorHandler\n// ...\n// Then you could build your strategy\n/// \"FailureStrategy\" implements \"IFailureStrategy\", this interface is provided by workit\nconst strategy = new FailureStrategy([\n  new AxiosApiErrorHandler(errorConfig, [\n    new BadRequestErrorHandler(errorConfig),\n    new TimeoutErrorHandler(errorConfig),\n    new ServerErrorHandler(errorConfig),\n    new UnManagedErrorHandler(errorConfig),\n    //...\n  ]),\n  new ErrorHandler(errorConfig)\n]);\n// worker will use your new strategy\nIoC.bindToObject(strategy, CORE_IDENTIFIER.failure_strategy);\n```\n\n## Running the tests\n\nWe use Jest.\n\n```bash\nnpm test\n```\n\n## Built With\n\n*   [camunda-external-task-client-js](https://github.com/camunda/camunda-external-task-client-js) - nodejs client for Camunda BPM\n*   [@aws-sdk/client-sqs](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sqs/) - nodejs client for receiving messages from the queue\n*   [@aws-sdk/client-sfn](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sfn/) - nodejs client for managing state machines and acknowledging process\n*   [inversify](https://github.com/inversify/InversifyJS) - dependency injection\n*   [opentelemetry](https://opentelemetry.io/) - add instrumentation to the operations (provides a single set of APIs, libraries to capture distributed traces)\n\n## Philosophy\n\n1.  Allow Javascript developers to write code that adheres to the SOLID principles.\n2.  Facilitate and encourage the adherence to the best OOP and IoC practices.\n3.  Add as little runtime overhead as possible.\n\n## Docker\n\n### Bpmn platform\n```bash\ndocker run -d --name camunda -p 8080:8080 camunda/camunda-bpm-platform:latest\n// Go: http://localhost:8080/camunda - user/password : `demo/demo`\n```\n[More details](https://github.com/camunda/docker-camunda-bpm-platform)\n\n## TODO\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand\u003c/summary\u003e\n\n-   Add tests\n-   Improve docs\n-   Make sample and confirm compatibility with DMN\n-   Adding a common exception error codes between Manager clients\n-   Add metrics by using prometheus lib\n\u003c/details\u003e\n\n## Versionning\n\nWe use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/VilledeMontreal/workit/tags).\n\nworkit | AWS Step function | Camunda BPM\n-- | -- | -- \n\\\u003e=6.0.0 | all | 7.6 to latest\n\n## Maintainers\n\nSee the list of [contributors](CONTRIBUTORS.md) who participated in this project.\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVilledeMontreal%2Fworkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FVilledeMontreal%2Fworkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVilledeMontreal%2Fworkit/lists"}