npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@villedemontreal/workit

v6.0.6-dev

Published

Worker for Node.js system with pluggable clients that works with both AWS Step function and Camunda platform powered by TypeScript

Downloads

122

Readme

WorkIt

License: MIT lerna npm

Français

✨Extensible worker for Node.js that works with both AWS Step function and Camunda BPM platforms powered by TypeScript ✨

Motivation

We needed a framework to help us quickly build workers used to execute tasks.

This package can be useful because:

  • Experiment and choose the platform you want without rewritting the business logic. Today, only Camunda and AWS Step function clients are maintained
  • 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.
  • You want to have a worker standardization.
  • Uniformisation. Indeed, you can use both platforms depending project needs.
  • Added features like automated tracing.

Quickstart

Get started in 2 minutes.

Documentation

API

| Package | Description | | --- | ---| | workit-types | This package provides TypeScript interfaces and enums for the Workit core model. | workit-core | This package provides default and no-op implementations of the Workit types

Implementation / Clients

| Package | Description | | ---------------------------------------- | -----------------| | workit-bpm-client | This module provides a full control over the Camunda Bpm platform. It use camunda-external-task-client-js by default. |

Installing

npm i @villedemontreal/workit

or using the generator below

Yo!

This generator will help you during your development with this library. It provides handy tools.

npm i -g @villedemontreal/workit-cli

Install a fresh new project

workit init

Generate tasks from your existing BPMN

workit create task --file /your/path.bpmn

Generate new task

workit create task

How to use

Switching between platforms is easy as specifying a TAG to the IoC.

Run worker

const worker = IoC.get<Worker>(CORE_IDENTIFIER.worker, TAG.camundaBpm);

worker.start();
worker.run();

Deploy a workflow

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
const fullpath = `${process.cwd()}/sample/BPMN_DEMO.bpmn`;
await manager.deployWorkflow(fullpath);

Get workflows

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.getWorkflows()

Get a workflow

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.getWorkflow({ bpmnProcessId: "DEMO" });

Update variables

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.updateVariables({ 
    processInstanceId: "5c50c48e-4691-11e9-8b8f-0242ac110002",
    variables: { amount: 1000 }
});

Publish message

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.publishMessage({
    correlation: {},
    name: "catching",
    variables: { amount: 100 },
    messageId: "5c50c48e-4691-11e9-8b8f-0242ac110002"
});

Create workflow instance

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.createWorkflowInstance({
    bpmnProcessId: "MY_BPMN_KEY",
    variables: {
        hello: "world"
    }
});

Cancel workflow instance

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.cancelWorkflowInstance("4651614f-4b3c-11e9-b5b3-ee5801424400");

Resolve incident

const manager = IoC.get<IWorkflowClient>(CORE_IDENTIFIER.client_manager, TAG.camundaBpm);
await manager.resolveIncident("c84fce6c-518e-11e9-bd78-0242ac110003");

Define tasks (your bpmn activities)

You can define many tasks to one worker. It will handle all messages and will route to the right tasks.

export class HelloWorldTask extends TaskBase<IMessage> {
  // You can type message like IMessage<TBody, TProps> default any
  public execute(message: IMessage): Promise<IMessage> {
      const { properties } = message;
      console.log(`Executing task: ${properties.activityId}`);
      console.log(`${properties.bpmnProcessId}::${properties.processInstanceId} Servus!`);
      // put your business logic here
      return Promise.resolve(message);
  }
}


enum LOCAL_IDENTIFIER {
    // sample_activity must match the activityId in your bpmn
    sample_activity= 'sample_activity'
}

// Register your task
IoC.bindTo(HelloWorldTask, LOCAL_IDENTIFIER.sample_activity);

You can even make complex binding like

IoC.bindTask(HelloWorldTaskV2, LOCAL_IDENTIFIER.activity1, { bpmnProcessId: BPMN_PROCESS_ID, version: 2 });

If you have installed workit-cli, you can do workit create task and everything will be done for you.

Worker life cycle and events

const worker = IoC.get<Worker>(CORE_IDENTIFIER.worker, TAG.camundaBpm);

worker.once('starting', () => {
    // slack notification 
});

worker.once('stopping', () => {
    // close connections
});

worker.once('stopped', () => {
    // slack notification
});

const handler = worker.getProcessHandler();

handler.on('message', (msg: IMessage) => {
    // log/audit
});

handler.on('message-handled', (err: Error, msg: IMessage) => {
    if (err) {
        // something wrong
    } else {
        // everything is fine
    }
});

worker.start();
worker.run(); // Promise
worker.stop(); // Promise

Interceptors

const workerConfig = {
  interceptors: [
    async (message: IMessage): Promise<IMessage> => {
      // do something before we execute task.
      return message;
    }
  ]
};

IoC.bindToObject(workerConfig, CORE_IDENTIFIER.worker_config);

OpenTelemetry

By default, we bound a NoopTracer but you can provide your own and it must extend Tracer.We strongly recommand to use this kind of pattern in your task: Domain Probe pattern. But here an example:

// Simply bind your custom tracer object like this
IoC.bindToObject(tracer, CORE_IDENTIFIER.tracer);
export class HelloWorldTask extends TaskBase<IMessage> {
  private readonly _tracer: Tracer;
    
  constructor(tracer: Tracer) {
        this._tracer = tracer
  }

  public async execute(message: IMessage): Promise<IMessage> {
      const { properties } = message;
      
      console.log(`Executing task: ${properties.activityId}`);
      console.log(`${properties.bpmnProcessId}::${properties.processInstanceId} Servus!`);

      // This call will be traced automatically
      const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
      
      // you can also create a custom trace like this :
      const currentSpan = tracer.getCurrentSpan();
      const span = this._tracer.startSpan('customSpan', {
        parent: currentSpan,
        kind: SpanKind.CLIENT,
        attributes: { key: 'value' },
      });
      
      console.log();
      console.log('data:');
      console.log(response.data);
      // put your business logic here

      // finish the span scope
      span.end();
      
      return Promise.resolve(message);
  }
}

You can look to sample folder where we provide an example (parallel.ts) using Jaeger.

See get started section with OpenTelemetry

Define your config for the platform you want to use

TODO show for step function

Define your strategies in case of failure or success

By default, we define simple strategy for success or failure. We strongly recommend you to provide yours as your app trigger specific exceptions. Strategies are automatically handled. If an exeption is bubble up from the task, failure strategy is raised, otherwise it's success.

// the idea is to create your own but imagine that your worker works mainly with HTTP REST API
class ServerErrorHandler extends ErrorHandlerBase {
  constructor(config: { maxRetries: number }) {
    super(config);
  }

  public isHandled(error: IErrorResponse<IResponse<IApiError>>): boolean {
    return error.response.status >= 500;
  }
  public handle(error: IErrorResponse<IResponse<IApiError>>, message: IMessage): Failure {
    const retries = this.getRetryValue(message);
    return new Failure(error.message, this.buildErrorDetails(error, message), retries, 2000 * retries);
  }
}

// You got the idea...

// You could create also
// BadRequestErrorHandler
// TimeoutErrorHandler
// UnManagedErrorHandler
// ...
// Then you could build your strategy
/// "FailureStrategy" implements "IFailureStrategy", this interface is provided by workit
const strategy = new FailureStrategy([
  new AxiosApiErrorHandler(errorConfig, [
    new BadRequestErrorHandler(errorConfig),
    new TimeoutErrorHandler(errorConfig),
    new ServerErrorHandler(errorConfig),
    new UnManagedErrorHandler(errorConfig),
    //...
  ]),
  new ErrorHandler(errorConfig)
]);
// worker will use your new strategy
IoC.bindToObject(strategy, CORE_IDENTIFIER.failure_strategy);

Running the tests

We use Jest.

npm test

Built With

Philosophy

  1. Allow Javascript developers to write code that adheres to the SOLID principles.
  2. Facilitate and encourage the adherence to the best OOP and IoC practices.
  3. Add as little runtime overhead as possible.

Docker

Bpmn platform

docker run -d --name camunda -p 8080:8080 camunda/camunda-bpm-platform:latest
// Go: http://localhost:8080/camunda - user/password : `demo/demo`

More details

TODO

  • Add tests
  • Improve docs
  • Make sample and confirm compatibility with DMN
  • Adding a common exception error codes between Manager clients
  • Add metrics by using prometheus lib

Versionning

We use SemVer for versioning. For the versions available, see the tags on this repository.

workit | AWS Step function | Camunda BPM -- | -- | -- >=6.0.0 | TODO | 7.6 to latest

Maintainers

See the list of contributors who participated in this project.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

License

This project is licensed under the MIT License - see the LICENSE file for details