Skip to content

typescript-workflow/durabull

Repository files navigation

πŸƒ Durabull

Durable, replay-safe workflow orchestration for TypeScript & Node.js β€” powered by BullMQ and Redis.

Generator-based workflows that combine the elegance of Laravel Workflow with the reliability of Temporal.

License Node.js TypeScript Status


✨ Overview

Durabull brings generator-based workflow orchestration to TypeScript. Author workflows as async *execute() coroutines, orchestrate idempotent activities, and run them safely on top of BullMQ and Redis β€” with full deterministic replay guarantees.


πŸš€ Quick Start

1. Install dependencies

npm install durabull bullmq ioredis
# or
pnpm add durabull bullmq ioredis

2. Configure Durabull

import { Durabull } from 'durabull';

const durabull = new Durabull({
  redisUrl: process.env.REDIS_URL ?? 'redis://127.0.0.1:6379',
  queues: {
    workflow: 'durabull-workflow',
    activity: 'durabull-activity',
  },
  serializer: 'json',
  pruneAge: '30 days',
  // Optional: Queue routing for multi-tenant support
  // The context object is passed from WorkflowStub.make(WorkflowClass, { context: { ... } })
  queueRouter: (workflowName, context) => {
    const tenant = context?.tenantId;
    return tenant ? {
      workflow: `tenant-${tenant}-workflow`,
      activity: `tenant-${tenant}-activity`,
    } : undefined;
  },
  // Optional: Lifecycle hooks for observability
  lifecycleHooks: {
    workflow: {
      onStart: async (id, name, args) => console.log(`Workflow ${name} started`),
      onComplete: async (id, name, output) => console.log(`Workflow ${name} completed`),
      onFailed: async (id, name, error) => console.error(`Workflow ${name} failed`, error),
    },
  },
  // logger: optional structured logger with info/warn/error/debug methods
});

durabull.setActive();

3. Create an Activity

import { Activity } from 'durabull';

export class SayHello extends Activity<[string], string> {
  tries = 3;
  timeout = 5; // seconds

  async execute(name: string): Promise<string> {
    return `Hello, ${name}!`;
  }
}

4. Create a Workflow

import { Workflow, ActivityStub } from 'durabull';
import { SayHello } from './SayHello';

export class GreetingWorkflow extends Workflow<[string], string> {
  async *execute(name: string) {
    const message = yield ActivityStub.make(SayHello, name);
    return message;
  }
}

5. Execute the Workflow

import { WorkflowStub } from 'durabull';
import { GreetingWorkflow } from './GreetingWorkflow';

const wf = await WorkflowStub.make(GreetingWorkflow);
await wf.start('World');

console.log(await wf.output()); // "Hello, World!"

πŸͺ Webhooks

Expose workflows via HTTP using createWebhookRouter.

import { createWebhookRouter, TokenAuthStrategy } from 'durabull';
import { GreetingWorkflow } from './GreetingWorkflow';

const router = createWebhookRouter({
  authStrategy: new TokenAuthStrategy('my-secret-token'),
});

router.registerWebhookWorkflow('greeting', GreetingWorkflow);

// Use with Express/Fastify/etc.
app.post('/webhooks/*', async (req, res) => {
  const response = await router.handle({
    method: req.method,
    path: req.path,
    headers: req.headers,
    body: req.body,
  });
  res.status(response.statusCode).send(response.body);
});

🧠 Why Durabull?

Capability Description
🧩 Generator-based workflows Use async *execute() and yield for deterministic orchestration.
βš™οΈ Idempotent activities Encapsulate retries, backoff, and heartbeats for safe IO.
⏳ Deterministic replay Rebuild workflow state from event history β€” crash-safe and restart-safe.
πŸ’¬ Signals & Queries Interact dynamically with live workflows via decorators.
🧡 Saga & Compensation Built-in support for distributed transactions.
⏱ Timers & Await Durable timers via WorkflowStub.timer() and WorkflowStub.await().
🩺 Observability Full event history, heartbeats, and pruning controls.
πŸͺ Webhooks Trigger workflows and signals via HTTP with pluggable auth.

πŸ’‘ Core Concepts

🧭 Workflows

Extend Workflow and implement async *execute().

Use ActivityStub.make() or ActivityStub.all() to orchestrate sequential or parallel work.

import { Workflow, ActivityStub } from 'durabull';
import { ChargeCard, EmailReceipt } from './activities';

export class CheckoutWorkflow extends Workflow<[string, number], string> {
  async *execute(orderId, amount) {
    const chargeId = yield ActivityStub.make(ChargeCard, orderId, amount);
    yield ActivityStub.make(EmailReceipt, orderId, chargeId);
    return chargeId;
  }
}

⚑ Activities

Extend Activity and implement execute(). Configure retry logic and call this.heartbeat() for long-running jobs.

import { Activity, NonRetryableError } from 'durabull';

export class ChargeCard extends Activity<[string, number], string> {
  tries = 5;
  timeout = 15;

  async execute(orderId, amount) {
    const res = await paymentGateway.charge(orderId, amount);
    if (!res.ok) throw new NonRetryableError(res.error);
    return res.chargeId;
  }
}

🧩 Examples

Run any example from the /examples directory:

npm run example:greeting          # Basic workflow + activity

🧭 Documentation

  • πŸ“˜ DOCS.md β€” Full Durabull guide for TypeScript users
  • πŸ’‘ examples/ β€” Complete working examples
  • πŸ§ͺ tests/ β€” Jest test suite covering all core behaviors

🀝 Contributing

We welcome contributions!

  1. Fork the repository
  2. Create a new branch (feat/my-feature)
  3. Write tests and ensure npm test passes
  4. Lint code (npm run lint)
  5. Open a pull request with a clear description

License

Durabull is open-source software licensed under the MIT License. Β© 2025 Durabull contributors.

About

Durable, replay-safe workflow orchestration for TypeScript & Node.js powered by BullMQ and Redis.

Resources

License

Stars

Watchers

Forks

Packages

No packages published