openworkflow 0.5.0 → 0.6.0
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 +40 -347
- package/dist/{testing → backend-test}/backend.testsuite.d.ts +1 -1
- package/dist/backend-test/backend.testsuite.d.ts.map +1 -0
- package/dist/{testing → backend-test}/backend.testsuite.js +8 -9
- package/dist/backend-test/index.d.ts +2 -0
- package/dist/backend-test/index.d.ts.map +1 -0
- package/dist/{testing → backend-test}/index.js +0 -1
- package/dist/backend.js +0 -1
- package/dist/backend.testsuite.d.ts +20 -0
- package/dist/backend.testsuite.d.ts.map +1 -0
- package/dist/backend.testsuite.js +1090 -0
- package/dist/bin/openworkflow.js +0 -1
- package/dist/chaos.test.d.ts +2 -0
- package/dist/chaos.test.d.ts.map +1 -0
- package/dist/chaos.test.js +88 -0
- package/dist/client.js +0 -1
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +311 -0
- package/dist/core/duration.js +0 -1
- package/dist/core/duration.test.d.ts +2 -0
- package/dist/core/duration.test.d.ts.map +1 -0
- package/dist/core/duration.test.js +263 -0
- package/dist/core/error.js +0 -1
- package/dist/core/error.test.d.ts +2 -0
- package/dist/core/error.test.d.ts.map +1 -0
- package/dist/core/error.test.js +60 -0
- package/dist/core/json.js +0 -1
- package/dist/core/result.js +0 -1
- package/dist/core/result.test.d.ts +2 -0
- package/dist/core/result.test.d.ts.map +1 -0
- package/dist/core/result.test.js +11 -0
- package/dist/core/retry.js +0 -1
- package/dist/core/schema.js +0 -1
- package/dist/core/step.js +0 -1
- package/dist/core/step.test.d.ts +2 -0
- package/dist/core/step.test.d.ts.map +1 -0
- package/dist/core/step.test.js +266 -0
- package/dist/core/workflow.js +0 -1
- package/dist/core/workflow.test.d.ts +2 -0
- package/dist/core/workflow.test.d.ts.map +1 -0
- package/dist/core/workflow.test.js +113 -0
- package/dist/driver.d.ts +116 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +1 -0
- package/dist/execution.js +0 -1
- package/dist/execution.test.d.ts +2 -0
- package/dist/execution.test.d.ts.map +1 -0
- package/dist/execution.test.js +381 -0
- package/dist/factory.d.ts +74 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +72 -0
- package/dist/index.js +0 -1
- package/dist/internal.d.ts +4 -5
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -4
- package/dist/{backend-sqlite/index.d.ts → node-sqlite/backend.d.ts} +14 -4
- package/dist/node-sqlite/backend.d.ts.map +1 -0
- package/dist/{backend-sqlite/index.js → node-sqlite/backend.js} +23 -5
- package/dist/node-sqlite/index.d.ts +11 -0
- package/dist/node-sqlite/index.d.ts.map +1 -0
- package/dist/node-sqlite/index.js +7 -0
- package/dist/{backend-sqlite → node-sqlite}/sqlite.d.ts +2 -3
- package/dist/node-sqlite/sqlite.d.ts.map +1 -0
- package/dist/{backend-sqlite → node-sqlite}/sqlite.js +0 -1
- package/dist/{pg → postgres}/backend.d.ts +3 -1
- package/dist/postgres/backend.d.ts.map +1 -0
- package/dist/{pg → postgres}/backend.js +5 -5
- package/dist/postgres/backend.test.d.ts +2 -0
- package/dist/postgres/backend.test.d.ts.map +1 -0
- package/dist/postgres/backend.test.js +19 -0
- package/dist/postgres/driver.d.ts +81 -0
- package/dist/postgres/driver.d.ts.map +1 -0
- package/dist/postgres/driver.js +63 -0
- package/dist/postgres/index.d.ts +11 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +7 -0
- package/dist/postgres/internal.d.ts +2 -0
- package/dist/postgres/internal.d.ts.map +1 -0
- package/dist/postgres/internal.js +1 -0
- package/dist/postgres/postgres.d.ts.map +1 -0
- package/dist/{backend-postgres → postgres}/postgres.js +0 -1
- package/dist/postgres/postgres.test.d.ts +2 -0
- package/dist/postgres/postgres.test.d.ts.map +1 -0
- package/dist/postgres/postgres.test.js +45 -0
- package/dist/postgres/scripts/db-migrate.d.ts.map +1 -0
- package/dist/{pg → postgres}/scripts/db-migrate.js +0 -1
- package/dist/postgres/scripts/db-reset.d.ts.map +1 -0
- package/dist/{pg → postgres}/scripts/db-reset.js +0 -1
- package/dist/{pg → postgres}/scripts/squawk.d.ts.map +1 -1
- package/dist/{pg → postgres}/scripts/squawk.js +0 -1
- package/dist/postgres/vitest.global-setup.d.ts.map +1 -0
- package/dist/{pg → postgres}/vitest.global-setup.js +0 -1
- package/dist/postgres.d.ts +2 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +1 -0
- package/dist/registry.js +0 -1
- package/dist/registry.test.d.ts +2 -0
- package/dist/registry.test.d.ts.map +1 -0
- package/dist/registry.test.js +109 -0
- package/dist/sqlite/backend.d.ts +3 -1
- package/dist/sqlite/backend.d.ts.map +1 -1
- package/dist/sqlite/backend.js +5 -5
- package/dist/sqlite/backend.test.d.ts +2 -0
- package/dist/sqlite/backend.test.d.ts.map +1 -0
- package/dist/sqlite/backend.test.js +50 -0
- package/dist/sqlite/driver.d.ts +79 -0
- package/dist/sqlite/driver.d.ts.map +1 -0
- package/dist/sqlite/driver.js +62 -0
- package/dist/sqlite/index.d.ts +12 -2
- package/dist/sqlite/index.d.ts.map +1 -1
- package/dist/sqlite/index.js +11 -3
- package/dist/sqlite/internal.d.ts +2 -0
- package/dist/sqlite/internal.d.ts.map +1 -0
- package/dist/sqlite/internal.js +1 -0
- package/dist/sqlite/sqlite.js +0 -1
- package/dist/sqlite/sqlite.test.d.ts +2 -0
- package/dist/sqlite/sqlite.test.d.ts.map +1 -0
- package/dist/sqlite/sqlite.test.js +171 -0
- package/dist/sqlite.d.ts +2 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/worker.js +0 -1
- package/dist/worker.test.d.ts +2 -0
- package/dist/worker.test.d.ts.map +1 -0
- package/dist/worker.test.js +900 -0
- package/dist/workflow.js +0 -1
- package/dist/workflow.test.d.ts +2 -0
- package/dist/workflow.test.d.ts.map +1 -0
- package/dist/workflow.test.js +84 -0
- package/package.json +19 -5
- package/dist/backend-postgres/index.d.ts +0 -44
- package/dist/backend-postgres/index.d.ts.map +0 -1
- package/dist/backend-postgres/index.js +0 -535
- package/dist/backend-postgres/index.js.map +0 -1
- package/dist/backend-postgres/postgres.d.ts.map +0 -1
- package/dist/backend-postgres/postgres.js.map +0 -1
- package/dist/backend-sqlite/index.d.ts.map +0 -1
- package/dist/backend-sqlite/index.js.map +0 -1
- package/dist/backend-sqlite/sqlite.d.ts.map +0 -1
- package/dist/backend-sqlite/sqlite.js.map +0 -1
- package/dist/backend.js.map +0 -1
- package/dist/bin/openworkflow.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -34
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -49
- package/dist/config.js.map +0 -1
- package/dist/core/duration.js.map +0 -1
- package/dist/core/error.js.map +0 -1
- package/dist/core/json.js.map +0 -1
- package/dist/core/result.js.map +0 -1
- package/dist/core/retry.js.map +0 -1
- package/dist/core/schema.js.map +0 -1
- package/dist/core/step.js.map +0 -1
- package/dist/core/workflow.js.map +0 -1
- package/dist/execution.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal.js.map +0 -1
- package/dist/pg/backend.d.ts.map +0 -1
- package/dist/pg/backend.js.map +0 -1
- package/dist/pg/index.d.ts +0 -3
- package/dist/pg/index.d.ts.map +0 -1
- package/dist/pg/index.js +0 -3
- package/dist/pg/index.js.map +0 -1
- package/dist/pg/postgres.d.ts +0 -42
- package/dist/pg/postgres.d.ts.map +0 -1
- package/dist/pg/postgres.js +0 -234
- package/dist/pg/postgres.js.map +0 -1
- package/dist/pg/scripts/db-migrate.d.ts.map +0 -1
- package/dist/pg/scripts/db-migrate.js.map +0 -1
- package/dist/pg/scripts/db-reset.d.ts.map +0 -1
- package/dist/pg/scripts/db-reset.js.map +0 -1
- package/dist/pg/scripts/squawk.js.map +0 -1
- package/dist/pg/vitest.global-setup.d.ts.map +0 -1
- package/dist/pg/vitest.global-setup.js.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/sqlite/backend.js.map +0 -1
- package/dist/sqlite/index.js.map +0 -1
- package/dist/sqlite/sqlite.js.map +0 -1
- package/dist/testing/backend.testsuite.d.ts.map +0 -1
- package/dist/testing/backend.testsuite.js.map +0 -1
- package/dist/testing/index.d.ts +0 -2
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/worker.js.map +0 -1
- package/dist/workflow.js.map +0 -1
- /package/dist/{backend-postgres → postgres}/postgres.d.ts +0 -0
- /package/dist/{pg → postgres}/scripts/db-migrate.d.ts +0 -0
- /package/dist/{pg → postgres}/scripts/db-reset.d.ts +0 -0
- /package/dist/{pg → postgres}/scripts/squawk.d.ts +0 -0
- /package/dist/{pg → postgres}/vitest.global-setup.d.ts +0 -0
package/README.md
CHANGED
|
@@ -8,16 +8,10 @@ OpenWorkflow is a TypeScript framework for building durable, resumable workflows
|
|
|
8
8
|
that can pause for seconds or months, survive crashes and deploys, and resume
|
|
9
9
|
exactly where they left off - all without extra servers to manage.
|
|
10
10
|
|
|
11
|
-
Define a workflow in a few lines:
|
|
12
|
-
|
|
13
11
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
import { OpenWorkflow } from "openworkflow";
|
|
16
|
-
|
|
17
|
-
const backend = BackendSqlite.connect("openworkflow/backend.db");
|
|
18
|
-
const ow = new OpenWorkflow({ backend });
|
|
12
|
+
import { defineWorkflow } from "openworkflow";
|
|
19
13
|
|
|
20
|
-
const sendWelcomeEmail =
|
|
14
|
+
export const sendWelcomeEmail = defineWorkflow(
|
|
21
15
|
{ name: "send-welcome-email" },
|
|
22
16
|
async ({ input, step }) => {
|
|
23
17
|
const user = await step.run({ name: "fetch-user" }, async () => {
|
|
@@ -43,363 +37,62 @@ const sendWelcomeEmail = ow.defineWorkflow(
|
|
|
43
37
|
);
|
|
44
38
|
```
|
|
45
39
|
|
|
46
|
-
> OpenWorkflow is in active development and moving quickly. Check out the
|
|
47
|
-
> [Roadmap](#roadmap) for what’s coming next.
|
|
48
|
-
|
|
49
40
|
## Quick Start
|
|
50
41
|
|
|
51
|
-
Prerequisites
|
|
52
|
-
|
|
53
|
-
- Node.js
|
|
54
|
-
- PostgreSQL (and/or SQLite)
|
|
42
|
+
**Prerequisites:** Node.js & PostgreSQL (or SQLite)
|
|
55
43
|
|
|
56
|
-
###
|
|
57
|
-
|
|
58
|
-
Install and set up OpenWorkflow with:
|
|
44
|
+
### Install
|
|
59
45
|
|
|
60
46
|
```bash
|
|
61
47
|
npx @openworkflow/cli init
|
|
62
48
|
```
|
|
63
49
|
|
|
64
|
-
The CLI will
|
|
65
|
-
|
|
66
|
-
and a `worker` script.
|
|
67
|
-
|
|
68
|
-
### 2. Start a worker
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm run worker
|
|
72
|
-
# or
|
|
73
|
-
npx @openworkflow/cli worker start
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
This runs the worker using `openworkflow.config.ts` and auto-loads workflows
|
|
77
|
-
from the configured directories (default: `openworkflow/`).
|
|
78
|
-
|
|
79
|
-
### 3. Run workflows from your app
|
|
80
|
-
|
|
81
|
-
Edit the generated workflow file and run it from your application code using the
|
|
82
|
-
OpenWorkflow client APIs (see examples below).
|
|
83
|
-
|
|
84
|
-
That's it. Your workflow is now durable, resumable, and fault-tolerant.
|
|
85
|
-
|
|
86
|
-
## Core Concepts
|
|
87
|
-
|
|
88
|
-
### Workflows
|
|
89
|
-
|
|
90
|
-
Workflows are durable functions. They can contain multiple steps, make external
|
|
91
|
-
API calls, query databases, and perform complex logic. If a workflow is
|
|
92
|
-
interrupted (crash, deploy, server restart), it resumes from its last completed
|
|
93
|
-
step.
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
const workflow = ow.defineWorkflow(
|
|
97
|
-
{ name: "my-workflow" },
|
|
98
|
-
async ({ input, step }) => {
|
|
99
|
-
// Your workflow logic here
|
|
100
|
-
return result;
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Steps
|
|
106
|
-
|
|
107
|
-
Steps are the building blocks of workflows. Each step is executed exactly once
|
|
108
|
-
and its result is memoized. Steps let you break workflows into checkpoints.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
const result = await step.run({ name: "step-name" }, async () => {
|
|
112
|
-
// This function runs once. If the workflow restarts,
|
|
113
|
-
// this returns the cached result instead of re-running.
|
|
114
|
-
return await someAsyncWork();
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Why steps matter**: Imagine a workflow that charges a credit card, then sends
|
|
119
|
-
an email. Without steps, if your server crashes after charging the card, the
|
|
120
|
-
workflow would retry from the beginning and charge the customer twice. With
|
|
121
|
-
steps, the charge is memoized. The retry skips it and goes straight to sending
|
|
122
|
-
the email.
|
|
123
|
-
|
|
124
|
-
### Workers
|
|
125
|
-
|
|
126
|
-
Workers are long-running processes that poll your database for pending workflows
|
|
127
|
-
and execute them. Run workers via the CLI so workflow discovery stays in sync
|
|
128
|
-
with your `openworkflow.config.ts`:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npm run worker
|
|
132
|
-
# or
|
|
133
|
-
npx @openworkflow/cli worker start
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Or, for more control, you can write your own workers:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { BackendSqlite } from "@openworkflow/backend-sqlite";
|
|
140
|
-
import { OpenWorkflow } from "openworkflow";
|
|
141
|
-
|
|
142
|
-
const backend = BackendSqlite.connect("openworkflow/backend.db");
|
|
143
|
-
const ow = new OpenWorkflow({ backend });
|
|
144
|
-
|
|
145
|
-
const worker = ow.newWorker({ concurrency: 20 });
|
|
146
|
-
await worker.start();
|
|
147
|
-
|
|
148
|
-
// & to shut down...
|
|
149
|
-
await worker.stop(); // waits for in-flight workflows to complete
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Workers are stateless. They can be started, stopped, and deployed independently.
|
|
153
|
-
Your database is the source of truth.
|
|
154
|
-
|
|
155
|
-
### How it Works
|
|
156
|
-
|
|
157
|
-
1. **Your app starts a workflow**: A row is inserted into the `workflow_runs`
|
|
158
|
-
table with status `pending`.
|
|
159
|
-
2. **A worker picks it up**: The worker polls the database, claims the workflow,
|
|
160
|
-
and sets its status to `running`.
|
|
161
|
-
3. **The worker executes steps**: Each step is recorded in the `step_attempts`
|
|
162
|
-
table. If a step succeeds, its result is cached.
|
|
163
|
-
4. **The workflow completes**: The worker updates the `workflow_run` status to
|
|
164
|
-
`completed` or `failed`.
|
|
165
|
-
5. **If the worker crashes**: The workflow becomes visible to other workers via
|
|
166
|
-
a heartbeat timeout. Another worker picks it up, loads the cached step
|
|
167
|
-
results, and resumes from the next step.
|
|
168
|
-
|
|
169
|
-
## Advanced Patterns
|
|
170
|
-
|
|
171
|
-
### Parallel Steps
|
|
172
|
-
|
|
173
|
-
Run multiple steps concurrently using `Promise.all`:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
const [user, subscription, settings] = await Promise.all([
|
|
177
|
-
step.run({ name: "fetch-user" }, async () => {
|
|
178
|
-
await db.users.findOne({ id: input.userId });
|
|
179
|
-
}),
|
|
180
|
-
step.run({ name: "fetch-subscription" }, async () => {
|
|
181
|
-
await stripe.subscriptions.retrieve(input.subId);
|
|
182
|
-
}),
|
|
183
|
-
step.run({ name: "fetch-settings" }, async () => {
|
|
184
|
-
await db.settings.findOne({ userId: input.userId });
|
|
185
|
-
}),
|
|
186
|
-
]);
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Each step is still memoized individually. If the workflow crashes mid-execution,
|
|
190
|
-
completed steps return instantly on resume.
|
|
191
|
-
|
|
192
|
-
### Automatic Retries
|
|
193
|
-
|
|
194
|
-
Steps can retry automatically with exponential backoff:
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
const data = await step.run({ name: "fetch-external-api" }, async () => {
|
|
198
|
-
// If this throws, the step retries automatically
|
|
199
|
-
return await externalAPI.getData();
|
|
200
|
-
});
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Configure retry behavior at the workflow or step level (coming soon) or handle
|
|
204
|
-
errors explicitly in your step functions.
|
|
205
|
-
|
|
206
|
-
### Sleeping (Pausing) Workflows
|
|
207
|
-
|
|
208
|
-
You can pause a workflow until a future time and, because sleeping releases the
|
|
209
|
-
worker slot, you can pause thousands of workflows without tying up compute:
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
// Pause for 1 hour (durable, non-blocking)
|
|
213
|
-
await step.sleep("wait-one-hour", "1h");
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
The sleep step is memoized after it completes. If the workflow is replayed again
|
|
217
|
-
(e.g. due to a later retry) the completed sleep is not re-applied.
|
|
218
|
-
|
|
219
|
-
#### Duration Formats
|
|
220
|
-
|
|
221
|
-
Durations accept a number followed by a unit:
|
|
222
|
-
|
|
223
|
-
| Unit | Aliases | Examples |
|
|
224
|
-
| ------------ | --------------------- | ---------------- |
|
|
225
|
-
| milliseconds | `ms`, `msec`, `msecs` | `100ms`, `1.5ms` |
|
|
226
|
-
| seconds | `s`, `sec`, `secs` | `5s`, `0.25s` |
|
|
227
|
-
| minutes | `m`, `min`, `mins` | `2m`, `1.5m` |
|
|
228
|
-
| hours | `h`, `hr`, `hrs` | `1h`, `0.25h` |
|
|
229
|
-
| days | `d`, `day(s)` | `1d`, `0.5d` |
|
|
230
|
-
| weeks | `w`, `week(s)` | `1w`, `2w` |
|
|
231
|
-
| months | `mo`, `month(s)` | `1mo`, `2mo` |
|
|
232
|
-
| years | `y`, `yr`, `yrs` | `1y`, `2yr` |
|
|
233
|
-
|
|
234
|
-
See more examples of accepted duration formats and aliases in the
|
|
235
|
-
[tests](https://github.com/openworkflowdev/openworkflow/blob/main/packages/openworkflow/core/duration.test.ts).
|
|
236
|
-
|
|
237
|
-
### Type Safety
|
|
238
|
-
|
|
239
|
-
Workflows are fully typed. Define input and output types for compile-time
|
|
240
|
-
safety:
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
interface ProcessOrderInput {
|
|
244
|
-
orderId: string;
|
|
245
|
-
userId: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
interface ProcessOrderOutput {
|
|
249
|
-
paymentId: string;
|
|
250
|
-
shipmentId: string;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const processOrder = ow.defineWorkflow<ProcessOrderInput, ProcessOrderOutput>(
|
|
254
|
-
{ name: "process-order" },
|
|
255
|
-
async ({ input, step }) => {
|
|
256
|
-
// input is typed as ProcessOrderInput
|
|
257
|
-
// return type must match ProcessOrderOutput
|
|
258
|
-
return { paymentId: "...", shipmentId: "..." };
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### Waiting for Results
|
|
264
|
-
|
|
265
|
-
You can wait for a workflow to complete and get its result:
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
const run = await myWorkflow.run({ data: "..." });
|
|
269
|
-
|
|
270
|
-
// Wait for the workflow to finish (polls the database)
|
|
271
|
-
const result = await run.result();
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Canceling Workflows
|
|
275
|
-
|
|
276
|
-
You can cancel a workflow that is pending, running, or sleeping to prevent a
|
|
277
|
-
workflow from continuing on to the next step:
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
const handle = await myWorkflow.run({ data: "..." });
|
|
281
|
-
|
|
282
|
-
// Cancel the workflow
|
|
283
|
-
await handle.cancel();
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Workflow Versioning
|
|
287
|
-
|
|
288
|
-
When you need to change workflow logic, use versioning for backwards
|
|
289
|
-
compatibility.
|
|
290
|
-
|
|
291
|
-
Define a workflow with an optional version:
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
const workflow = ow.defineWorkflow(
|
|
295
|
-
{ name: "my-workflow", version: "v2" },
|
|
296
|
-
async ({ input, step, version }) => {
|
|
297
|
-
if (version === "v2") {
|
|
298
|
-
// v2 runs go here
|
|
299
|
-
await step.run({ name: "new-step" }, async () => {
|
|
300
|
-
// legacy logic
|
|
301
|
-
});
|
|
302
|
-
} else {
|
|
303
|
-
// v1 runs go here
|
|
304
|
-
await step.run({ name: "old-step" }, async () => {
|
|
305
|
-
// ...
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
},
|
|
309
|
-
);
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Validating Workflow Inputs
|
|
313
|
-
|
|
314
|
-
You can require `.run()` callers to provide specific inputs by supplying a
|
|
315
|
-
`schema` when defining the workflow. The schema is evaluated before the run is
|
|
316
|
-
enqueued, so invalid requests fail immediately.
|
|
317
|
-
|
|
318
|
-
```ts
|
|
319
|
-
import { z } from "zod";
|
|
320
|
-
|
|
321
|
-
const summarizeDoc = ow.defineWorkflow(
|
|
322
|
-
{
|
|
323
|
-
name: "summarize",
|
|
324
|
-
schema: z.object({
|
|
325
|
-
docUrl: z.string().url(),
|
|
326
|
-
}),
|
|
327
|
-
},
|
|
328
|
-
async ({ input, step }) => {
|
|
329
|
-
// `input` has type { docUrl: string }
|
|
330
|
-
},
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
// Throws before enqueueing the workflow because the input isn't a URL
|
|
334
|
-
await summarizeDoc.run({ docUrl: "not-a-url" });
|
|
335
|
-
```
|
|
50
|
+
The CLI will guide you through setup and generate everything you need to get
|
|
51
|
+
started.
|
|
336
52
|
|
|
337
|
-
|
|
338
|
-
custom logic or lightweight checks). Libraries such as Zod, ArkType, Valibot,
|
|
339
|
-
and Yup.
|
|
53
|
+
For more details, check out our [docs](https://openworkflow.dev/docs).
|
|
340
54
|
|
|
341
|
-
##
|
|
55
|
+
## Features
|
|
342
56
|
|
|
343
|
-
- **
|
|
344
|
-
- **
|
|
345
|
-
- **
|
|
346
|
-
|
|
347
|
-
- **
|
|
348
|
-
- **
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
- **Namespaces** (optional): Use `namespaceId` in your backend configuration to
|
|
356
|
-
isolate workflows per environment:
|
|
357
|
-
```ts
|
|
358
|
-
const backend = await BackendPostgres.connect(postgresUrl, {
|
|
359
|
-
namespaceId: "production",
|
|
360
|
-
});
|
|
361
|
-
```
|
|
57
|
+
- ✅ **Durable** - Workflows survive crashes and deploys
|
|
58
|
+
- ✅ **Resumable** - Pick up exactly where you left off
|
|
59
|
+
- ✅ **Type-safe** - Full TypeScript support
|
|
60
|
+
- ✅ **Step memoization** - Never repeat completed work
|
|
61
|
+
- ✅ **Automatic retries** - Built-in exponential backoff
|
|
62
|
+
- ✅ **Long pauses** - Sleep for seconds or months
|
|
63
|
+
- ✅ **Parallel execution** - Run steps concurrently
|
|
64
|
+
- ✅ **No extra servers** - Uses your existing database
|
|
65
|
+
- ✅ **Dashboard included** - Monitor and debug workflows
|
|
66
|
+
- ✅ **Production ready** - PostgreSQL and SQLite support
|
|
362
67
|
|
|
363
|
-
##
|
|
68
|
+
## Documentation
|
|
364
69
|
|
|
365
|
-
-
|
|
366
|
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
|
|
70
|
+
- [Documentation](https://openworkflow.dev/docs)
|
|
71
|
+
- [Quick Start Guide](https://openworkflow.dev/docs/quickstart)
|
|
72
|
+
- [Core Concepts](https://openworkflow.dev/docs/core-concepts)
|
|
73
|
+
- [Advanced Patterns](https://openworkflow.dev/docs/advanced-patterns)
|
|
74
|
+
- [Production Checklist](https://openworkflow.dev/docs/production)
|
|
370
75
|
|
|
371
|
-
##
|
|
76
|
+
## Architecture
|
|
372
77
|
|
|
373
|
-
|
|
78
|
+
Read
|
|
79
|
+
[ARCHITECTURE.md](https://github.com/openworkflowdev/openworkflow/blob/main/ARCHITECTURE.md)
|
|
80
|
+
for a deep dive into how OpenWorkflow works under the hood.
|
|
374
81
|
|
|
375
|
-
|
|
376
|
-
- ✅ CLI (`npx @openworkflow/cli`)
|
|
377
|
-
- ✅ Worker with concurrency control
|
|
378
|
-
- ✅ Step memoization & retries
|
|
379
|
-
- ✅ Graceful shutdown
|
|
380
|
-
- ✅ Parallel step execution
|
|
381
|
-
- ✅ Sleeping (pausing) workflows
|
|
382
|
-
- ✅ Workflow versioning
|
|
383
|
-
- ✅ Workflow cancelation
|
|
82
|
+
## Examples
|
|
384
83
|
|
|
385
|
-
|
|
84
|
+
Check out
|
|
85
|
+
[examples/](https://github.com/openworkflowdev/openworkflow/tree/main/examples)
|
|
86
|
+
for working examples.
|
|
386
87
|
|
|
387
|
-
|
|
388
|
-
> workflow and step state directly in PostgreSQL or SQLite (workflow_runs and
|
|
389
|
-
> step_attempts tables). A dashboard is planned for an upcoming release to make
|
|
390
|
-
> debugging and monitoring much easier.
|
|
88
|
+
## Contributing
|
|
391
89
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
- Configurable retry policies
|
|
396
|
-
- Signals for external events
|
|
397
|
-
- Native OpenTelemetry integration
|
|
398
|
-
- Additional backends (Redis)
|
|
399
|
-
- Additional languages (Go, Python)
|
|
90
|
+
We welcome contributions! Please read
|
|
91
|
+
[CONTRIBUTING.md](https://github.com/openworkflowdev/openworkflow/blob/main/CONTRIBUTING.md)
|
|
92
|
+
before submitting a pull request.
|
|
400
93
|
|
|
401
|
-
##
|
|
94
|
+
## Community
|
|
402
95
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
https://
|
|
96
|
+
- [GitHub Issues](https://github.com/openworkflowdev/openworkflow/issues) -
|
|
97
|
+
Report bugs and request features
|
|
98
|
+
- [Roadmap](https://openworkflow.dev/docs/roadmap) - See what's coming next
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.testsuite.d.ts","sourceRoot":"","sources":["../../backend-test/backend.testsuite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAM7C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA8sC7D"}
|
|
@@ -88,7 +88,7 @@ export function testBackend(options) {
|
|
|
88
88
|
const second = await createPendingWorkflowRun(backend);
|
|
89
89
|
const listed = await backend.listWorkflowRuns({});
|
|
90
90
|
const listedIds = listed.data.map((run) => run.id);
|
|
91
|
-
expect(listedIds).toEqual([
|
|
91
|
+
expect(listedIds).toEqual([second.id, first.id]);
|
|
92
92
|
await teardown(backend);
|
|
93
93
|
});
|
|
94
94
|
test("paginates workflow runs", async () => {
|
|
@@ -101,8 +101,8 @@ export function testBackend(options) {
|
|
|
101
101
|
// p1
|
|
102
102
|
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
103
103
|
expect(page1.data).toHaveLength(2);
|
|
104
|
-
expect(page1.data[0]?.id).toBe(runs[
|
|
105
|
-
expect(page1.data[1]?.id).toBe(runs[
|
|
104
|
+
expect(page1.data[0]?.id).toBe(runs[4]?.id);
|
|
105
|
+
expect(page1.data[1]?.id).toBe(runs[3]?.id);
|
|
106
106
|
expect(page1.pagination.next).not.toBeNull();
|
|
107
107
|
expect(page1.pagination.prev).toBeNull();
|
|
108
108
|
// p2
|
|
@@ -112,7 +112,7 @@ export function testBackend(options) {
|
|
|
112
112
|
});
|
|
113
113
|
expect(page2.data).toHaveLength(2);
|
|
114
114
|
expect(page2.data[0]?.id).toBe(runs[2]?.id);
|
|
115
|
-
expect(page2.data[1]?.id).toBe(runs[
|
|
115
|
+
expect(page2.data[1]?.id).toBe(runs[1]?.id);
|
|
116
116
|
expect(page2.pagination.next).not.toBeNull();
|
|
117
117
|
expect(page2.pagination.prev).not.toBeNull();
|
|
118
118
|
// p3
|
|
@@ -121,7 +121,7 @@ export function testBackend(options) {
|
|
|
121
121
|
after: page2.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
122
122
|
});
|
|
123
123
|
expect(page3.data).toHaveLength(1);
|
|
124
|
-
expect(page3.data[0]?.id).toBe(runs[
|
|
124
|
+
expect(page3.data[0]?.id).toBe(runs[0]?.id);
|
|
125
125
|
expect(page3.pagination.next).toBeNull();
|
|
126
126
|
expect(page3.pagination.prev).not.toBeNull();
|
|
127
127
|
// p2 again
|
|
@@ -131,7 +131,7 @@ export function testBackend(options) {
|
|
|
131
131
|
});
|
|
132
132
|
expect(page2Back.data).toHaveLength(2);
|
|
133
133
|
expect(page2Back.data[0]?.id).toBe(runs[2]?.id);
|
|
134
|
-
expect(page2Back.data[1]?.id).toBe(runs[
|
|
134
|
+
expect(page2Back.data[1]?.id).toBe(runs[1]?.id);
|
|
135
135
|
expect(page2Back.pagination.next).toEqual(page2.pagination.next);
|
|
136
136
|
expect(page2Back.pagination.prev).toEqual(page2.pagination.prev);
|
|
137
137
|
await teardown(backend);
|
|
@@ -151,10 +151,10 @@ export function testBackend(options) {
|
|
|
151
151
|
runs.push(await createPendingWorkflowRun(backend));
|
|
152
152
|
}
|
|
153
153
|
runs.sort((a, b) => {
|
|
154
|
-
const timeDiff =
|
|
154
|
+
const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
|
|
155
155
|
if (timeDiff !== 0)
|
|
156
156
|
return timeDiff;
|
|
157
|
-
return
|
|
157
|
+
return b.id.localeCompare(a.id);
|
|
158
158
|
});
|
|
159
159
|
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
160
160
|
expect(page1.data).toHaveLength(2);
|
|
@@ -1088,4 +1088,3 @@ function newDateInOneYear() {
|
|
|
1088
1088
|
function sleep(ms) {
|
|
1089
1089
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1090
1090
|
}
|
|
1091
|
-
//# sourceMappingURL=backend.testsuite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../backend-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/backend.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Backend } from "./backend.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the Backend test suite.
|
|
4
|
+
*/
|
|
5
|
+
export interface TestBackendOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new isolated Backend instance.
|
|
8
|
+
*/
|
|
9
|
+
setup: () => Promise<Backend>;
|
|
10
|
+
/**
|
|
11
|
+
* Cleans up a Backend instance.
|
|
12
|
+
*/
|
|
13
|
+
teardown: (backend: Backend) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs the Backend test suite.
|
|
17
|
+
* @param options - Test suite options
|
|
18
|
+
*/
|
|
19
|
+
export declare function testBackend(options: TestBackendOptions): void;
|
|
20
|
+
//# sourceMappingURL=backend.testsuite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.testsuite.d.ts","sourceRoot":"","sources":["../backend.testsuite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAM5C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAgtC7D"}
|