durabull
Version:
A durable workflow engine built on top of BullMQ and Redis
231 lines (169 loc) โข 6.72 kB
Markdown
**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.
<p align="center">
<img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg">
<img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg">
<img alt="TypeScript" src="https://img.shields.io/badge/types-TypeScript-blue.svg">
<img alt="Status" src="https://img.shields.io/badge/status-early%20access-orange.svg">
</p>
---
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.
---
```bash
npm install durabull bullmq ioredis
pnpm add durabull bullmq ioredis
```
```typescript
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();
```
```typescript
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}!`;
}
}
```
```typescript
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;
}
}
```
```typescript
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!"
```
Expose workflows via HTTP using `createWebhookRouter`.
```typescript
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);
});
```
---
| 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. |
---
Extend `Workflow` and implement `async *execute()`.
Use `ActivityStub.make()` or `ActivityStub.all()` to orchestrate sequential or parallel work.
```typescript
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;
}
}
```
Extend `Activity` and implement `execute()`.
Configure retry logic and call `this.heartbeat()` for long-running jobs.
```typescript
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;
}
}
```
---
Run any example from the `/examples` directory:
```bash
npm run example:greeting
```
---
* ๐ [`DOCS.md`](./DOCS.md) โ Full Durabull guide for TypeScript users
* ๐ก [`examples/`](./examples) โ Complete working examples
* ๐งช [`tests/`](./tests) โ Jest test suite covering all core behaviors
---
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](./LICENSE)**.
ยฉ 2025 Durabull contributors.