@bobtail.software/b-durable 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,232 @@
1
+
2
+ # `@bobtail.software/b-durable`: Composable, Type-Safe, Durable Workflows for TypeScript
3
+
4
+ ![NPM Version](https://img.shields.io/npm/v/@bobtail.software/b-durable.svg)
5
+ ![License](https://img.shields.io/npm/l/@bobtail.software/eslint-plugin-b-durable.svg)
6
+
7
+ `b-durable` is a powerful system that transforms standard `async` functions into **composable, interactive, durable, and resilient workflows**. It lets you write long-running business logic—spanning hours, days, or months—as simple, linear `async/await` code. The system handles state persistence, orchestration, external events, and crash recovery, allowing you to focus on your business logic.
8
+
9
+ ## The Problem
10
+
11
+ Standard `async/await` is great for short-lived operations, but it breaks down for complex, long-running processes:
12
+
13
+ 1. **Fragility**: If your server restarts mid-execution, all in-memory state is lost.
14
+ 2. **Inefficiency**: An operation like `await bSleep('7 days')` is impossible. It would hold a process hostage, consume resources, and wouldn't survive a single deployment.
15
+ 3. **Orchestration Complexity**: Coordinating processes that involve multiple services, human-in-the-loop steps (like approvals), or external system webhooks often leads to a tangled mess of state machines, queues, and database flags.
16
+
17
+ ## The `b-durable` Solution
18
+
19
+ `b-durable` allows you to express this complexity as a single, readable `async` function. The system automatically persists the workflow's state after each `await` step, ensuring it can resume from the exact point of interruption.
20
+
21
+ Imagine orchestrating an e-commerce order. With `b-durable`, the code is as clear as the business process itself:
22
+
23
+ ```typescript
24
+ // Define a reusable sub-workflow for handling payments
25
+ export const paymentWorkflow = bDurable({
26
+ workflow: async (input: { orderId: string; amount: number }, context) => {
27
+ // 1. Call a service to process the payment
28
+ const result = await processPayment({ orderId: input.orderId, amount: input.amount });
29
+ if (!result.success) {
30
+ throw new Error('Payment failed!');
31
+ }
32
+ // 2. Pause durably for fraud checks
33
+ await context.bSleep('30m');
34
+ return { transactionId: result.transactionId };
35
+ },
36
+ });
37
+
38
+ // Define the main order processing workflow
39
+ export const orderProcessingWorkflow = bDurable({
40
+ workflow: async (input: { orderId: string; items: Item[] }, context) => {
41
+ try {
42
+ // 1. Call another workflow and await its result
43
+ const payment = await context.bExecute(paymentWorkflow, { orderId: input.orderId, amount: 99.99 });
44
+ context.log(`Payment successful: ${payment.transactionId}`);
45
+
46
+ // 2. Pause and wait for an external event (e.g., from a UI or webhook)
47
+ const approval = await context.bWaitForEvent('order.approved');
48
+ context.log(`Order approved by ${approval.approverId}`);
49
+
50
+ // 3. Call the final service function
51
+ await shipOrder(input.orderId);
52
+
53
+ return { status: 'completed' };
54
+ } catch (error) {
55
+ // 4. Handle errors durably
56
+ await notifyCustomerOfFailure(input.orderId, error.message);
57
+ await cancelOrder(input.orderId);
58
+ return { status: 'failed', reason: error.message };
59
+ }
60
+ },
61
+ });
62
+ ```
63
+
64
+ ## Core Features
65
+
66
+ - **Composable Orchestration**: Workflows can call other workflows using `await context.bExecute()`, allowing you to build complex processes from smaller, reusable parts. Results and errors are propagated automatically.
67
+ - **Interactive & Event-Driven**: Pause a workflow indefinitely with `await context.bWaitForEvent()` until an external event is received, enabling human-in-the-loop patterns and webhook integrations.
68
+ - **Durable & Resilient**: Workflows survive server restarts, crashes, and deployments, resuming exactly where they left off.
69
+ - **Built-in Error Handling**: Use standard `try/catch` blocks to handle errors from tasks or sub-workflows. Your `catch` block will execute reliably, even if the failure occurs hours after the `try` block started.
70
+ - **Durable Timers**: Use `await context.bSleep('30 days')` to pause workflows for extended periods without consuming server resources.
71
+ - **Type Safety End-to-End**: Leverages TypeScript for type safety across steps, I/O, events, and workflow composition.
72
+ - **Compiler-Powered**: A smart CLI compiler transforms your workflows into a step-by-step executable format, preserving types and ensuring runtime correctness.
73
+
74
+ ## How It Works: Compiler + Runtime
75
+
76
+ 1. **The Smart Compiler (`b-durable-compiler`)**:
77
+ Analyzes your workflow files (`*.workflow.ts`). For each function wrapped in `bDurable(...)`, it:
78
+ - **Maps Control Flow**: Breaks the function into steps at each `await`, analyzing `if/else` and `try/catch` blocks to build a complete state machine.
79
+ - **Identifies Durable Calls**: Differentiates between a durable instruction (`context.bSleep`, `context.bExecute`) and a standard service task call.
80
+ - **Generates Durable Artifacts**: Produces compiled `.mts` files that the runtime can execute step-by-step.
81
+
82
+ 2. **The Durable Runtime**:
83
+ The engine that executes the compiled workflows.
84
+ - **State Persistence**: Uses Redis to store the state, step, and context of every workflow instance.
85
+ - **Orchestration Logic**: Manages parent/child workflow relationships, passing results and errors up the chain.
86
+ - **Event System**: Tracks which workflows are waiting for which events.
87
+ - **Task Queue & Scheduler**: Reliably executes service function calls and manages long-running timers.
88
+
89
+ ## Getting Started
90
+
91
+ ### 1. Installation
92
+
93
+ Install the core library and its peer dependencies. We also highly recommend the ESLint plugin for the best developer experience.
94
+
95
+ ```bash
96
+ pnpm add @bobtail.software/b-durable ioredis
97
+ pnpm add -D @bobtail.software/eslint-plugin-b-durable
98
+ ```
99
+
100
+ ### 2. Set Up ESLint (Highly Recommended)
101
+
102
+ Our ESLint plugin prevents common errors by flagging unsupported code constructs (like loops) inside your workflows.
103
+
104
+ **In `eslint.config.js` (Flat Config):**
105
+ ```javascript
106
+ import bDurablePlugin from '@bobtail.software/eslint-plugin-b-durable';
107
+
108
+ export default [
109
+ // ... your other configs
110
+ bDurablePlugin.configs.recommended,
111
+ ];
112
+ ```
113
+ For legacy `.eslintrc.js` setup, see the [plugin's documentation](link-to-your-eslint-plugin-readme).
114
+
115
+ ### 3. Define a Workflow
116
+
117
+ Create a file ending in `.workflow.ts`. The `(input, context)` signature gives you access to durable functions.
118
+
119
+ ```typescript
120
+ // src/workflows/onboarding.workflow.ts
121
+ import { bDurable, DurableContext } from '@bobtail.software/b-durable';
122
+ import { createUser, sendWelcomeEmail } from '../services';
123
+
124
+ interface OnboardingInput {
125
+ userId: string;
126
+ email: string;
127
+ }
128
+
129
+ export const userOnboardingWorkflow = bDurable({
130
+ workflow: async (input: OnboardingInput, context: DurableContext) => {
131
+ const user = await createUser({ id: input.userId, email: input.email });
132
+
133
+ await context.bSleep('10s');
134
+
135
+ await sendWelcomeEmail(user.email);
136
+
137
+ return { status: 'completed', userId: user.id };
138
+ },
139
+ });
140
+ ```
141
+
142
+ ### 4. Compile Workflows
143
+
144
+ Add a script to your `package.json` to run the compiler.
145
+
146
+ ```json
147
+ // package.json
148
+ "scripts": {
149
+ "compile-workflows": "b-durable-compiler --in src/workflows --out src/generated"
150
+ }
151
+ ```
152
+ Run `pnpm compile-workflows`. This generates the durable definitions in `src/generated`.
153
+
154
+ ### 5. Initialize the Runtime
155
+
156
+ In your application's entry point, initialize the system and start a workflow.
157
+
158
+ ```typescript
159
+ // src/main.ts
160
+ import { bDurableInitialize } from '@bobtail.software/b-durable';
161
+ import Redis from 'ioredis';
162
+ import durableFunctions, { userOnboardingWorkflow } from './generated';
163
+
164
+ async function main() {
165
+ const redis = new Redis();
166
+ const blockingRedis = new Redis(); // Required for reliable queue operations
167
+
168
+ const durableSystem = bDurableInitialize({
169
+ durableFunctions,
170
+ sourceRoot: process.cwd(),
171
+ redisClient: redis,
172
+ blockingRedisClient: blockingRedis,
173
+ });
174
+
175
+ console.log('Durable system ready. Starting workflows...');
176
+
177
+ // --- Start a workflow with a system-generated ID ---
178
+ const workflowId1 = await durableSystem.start(userOnboardingWorkflow, {
179
+ input: {
180
+ userId: `user-${Date.now()}`,
181
+ email: 'test.user@example.com',
182
+ }
183
+ });
184
+ console.log(`Workflow ${workflowId1} started.`);
185
+
186
+ // --- Start a workflow with a predictable, user-provided ID ---
187
+ const orderId = `order-abc-123`;
188
+ const workflowId2 = await durableSystem.start(userOnboardingWorkflow, {
189
+ workflowId: orderId,
190
+ input: {
191
+ userId: 'user-from-order-123',
192
+ email: 'customer@example.com',
193
+ }
194
+ });
195
+ console.log(`Workflow started with predictable ID: ${workflowId2}`);
196
+ // Now you can easily send events using the 'orderId'
197
+ // durableSystem.sendEvent(..., orderId, ...);
198
+ }
199
+
200
+ main().catch(console.error);
201
+ ```
202
+
203
+ ### 6. Run Your Application
204
+
205
+ Run your app (`node src/main.ts`). You'll see the workflow execute, pause, and resume, with all its state managed by `b-durable`.
206
+
207
+ ## Development Setup (for contributors)
208
+
209
+ The project is a `pnpm` monorepo.
210
+
211
+ 1. **Clone & Install**:
212
+ ```bash
213
+ git clone <repository-url>
214
+ cd b-durable-monorepo
215
+ pnpm install
216
+ ```
217
+
218
+ 2. **Run in Development Mode**:
219
+ This command builds the library, compiles example workflows, and starts the example app with hot-reloading.
220
+ ```bash
221
+ pnpm dev
222
+ ```
223
+
224
+ 3. **Run Tests**:
225
+ Tests use Vitest and a real Redis instance.
226
+ ```bash
227
+ pnpm --filter @bobtail.software/b-durable test
228
+ ```
229
+
230
+ ## License
231
+
232
+ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) file for details.