@steadlake/run 0.3.0 → 0.3.1
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 +231 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,239 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @steadlake/run
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Durable task execution for your backend. Define tasks with steps, retries, scheduling, and event-driven workflows — without managing infrastructure.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
|
|
8
|
+
npm install @steadlake/run
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Setup
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
**Environment variables:**
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
STEADLAKE_API_KEY=sk_live_...
|
|
17
|
+
STEADLAKE_PROJECT_ID=proj_...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
### 1. Define tasks
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// tasks/send-email.ts
|
|
26
|
+
import { task } from "@steadlake/run";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
|
|
29
|
+
export const sendEmail = task({
|
|
30
|
+
id: "send-email",
|
|
31
|
+
schema: z.object({ to: z.string().email(), subject: z.string() }),
|
|
32
|
+
run: async ({ payload }) => {
|
|
33
|
+
await emailService.send(payload.to, payload.subject);
|
|
34
|
+
return { sent: true };
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Mount the handler
|
|
40
|
+
|
|
41
|
+
**Next.js**
|
|
42
|
+
```ts
|
|
43
|
+
// app/api/steadlake/route.ts
|
|
44
|
+
import { createNextHandler } from "@steadlake/run/adapters/next";
|
|
45
|
+
import { sendEmail } from "@/tasks/send-email";
|
|
46
|
+
|
|
47
|
+
export const { POST } = createNextHandler({
|
|
48
|
+
baseUrl: process.env.APP_URL!,
|
|
49
|
+
tasks: [sendEmail],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Hono / Generic**
|
|
54
|
+
```ts
|
|
55
|
+
import { createHandler } from "@steadlake/run/adapters/generic";
|
|
56
|
+
import { sendEmail } from "./tasks/send-email";
|
|
57
|
+
|
|
58
|
+
const handler = createHandler({
|
|
59
|
+
baseUrl: process.env.APP_URL!,
|
|
60
|
+
tasks: [sendEmail],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
app.post("/api/steadlake", (c) => handler(c.req.raw));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Cloudflare Workers**
|
|
67
|
+
```ts
|
|
68
|
+
import { createWorkerHandler } from "@steadlake/run/adapters/worker";
|
|
69
|
+
import { sendEmail } from "./tasks/send-email";
|
|
70
|
+
|
|
71
|
+
const handler = createWorkerHandler({ tasks: [sendEmail] });
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
fetch(req: Request, env: Env) {
|
|
75
|
+
if (new URL(req.url).pathname === "/api/steadlake") {
|
|
76
|
+
return handler(req, env, env.WORKER_URL);
|
|
77
|
+
}
|
|
78
|
+
return app.fetch(req, env);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Trigger
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// anywhere in your server code
|
|
87
|
+
import { sendEmail } from "@/tasks/send-email";
|
|
88
|
+
|
|
89
|
+
await sendEmail.trigger({ to: "user@example.com", subject: "Welcome!" });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Steps
|
|
95
|
+
|
|
96
|
+
Break long tasks into named, durable steps. Completed steps are replayed from state on retry — they never re-execute.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
export const importUsers = task({
|
|
100
|
+
id: "import-users",
|
|
101
|
+
run: async ({ payload, step }) => {
|
|
102
|
+
const users = await step.run("fetch", () => api.getUsers(payload.source));
|
|
103
|
+
const valid = await step.run("validate", () => users.filter(isValid));
|
|
104
|
+
await step.run("insert", () => db.users.bulkInsert(valid));
|
|
105
|
+
return { imported: valid.length };
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Sleep
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
await step.sleep("wait", "3d"); // duration: ms, s, m, h, d
|
|
114
|
+
await step.sleepUntil("until", date); // Date | ISO string
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Wait for external event
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const shipment = await step.waitForEvent<{ trackingId: string }>("shipped", {
|
|
121
|
+
event: "order.shipped",
|
|
122
|
+
timeout: "7d",
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Resume by calling `run.sendEvent("order.shipped", { trackingId: "..." })`.
|
|
127
|
+
|
|
128
|
+
### Human approval
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const { approved } = await step.approve("review", {
|
|
132
|
+
title: "Approve $10,000 payout?",
|
|
133
|
+
timeout: "24h",
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Resume by calling `client.resolveApproval(runId, stepId, true)`.
|
|
138
|
+
|
|
139
|
+
### Emit events to other runs
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
step.emit("order.shipped", { trackingId: "TRK123" });
|
|
13
143
|
```
|
|
14
144
|
|
|
15
|
-
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Retries
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
export const chargeCustomer = task({
|
|
151
|
+
id: "charge-customer",
|
|
152
|
+
retry: {
|
|
153
|
+
maxAttempts: 5,
|
|
154
|
+
backoff: "exponential", // "exponential" | "linear" | "fixed"
|
|
155
|
+
},
|
|
156
|
+
run: async ({ payload }) => {
|
|
157
|
+
await stripe.charges.create({ amount: payload.amount });
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Throw `FatalError` to fail permanently without retrying. Throw `RetryableError` to retry explicitly.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Cron
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
export const dailyReport = task({
|
|
170
|
+
id: "daily-report",
|
|
171
|
+
cron: "0 9 * * 1-5",
|
|
172
|
+
run: async () => { ... },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// With timezone
|
|
176
|
+
export const digest = task({
|
|
177
|
+
id: "digest",
|
|
178
|
+
cron: { pattern: "0 8 * * *", timezone: "America/New_York" },
|
|
179
|
+
run: async () => { ... },
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Queues
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
export const processVideo = task({
|
|
189
|
+
id: "process-video",
|
|
190
|
+
queue: {
|
|
191
|
+
name: "video",
|
|
192
|
+
concurrencyLimit: 3,
|
|
193
|
+
rateLimit: { max: 100, duration: "1h" },
|
|
194
|
+
},
|
|
195
|
+
run: async ({ payload }) => { ... },
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Trigger options
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
await sendEmail.trigger(payload, {
|
|
205
|
+
idempotencyKey: `email-${userId}`,
|
|
206
|
+
webhook: "https://myapp.com/webhooks/done",
|
|
207
|
+
tags: ["transactional"],
|
|
208
|
+
priority: 10,
|
|
209
|
+
scheduledAt: new Date(Date.now() + 60_000).toISOString(),
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Wait for result:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const result = await sendEmail.triggerAndWait(payload, { timeoutMs: 30_000 });
|
|
217
|
+
console.log(result.status); // "success" | "failed"
|
|
218
|
+
console.log(result.result);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Error types
|
|
224
|
+
|
|
225
|
+
| | HTTP | DEW behaviour |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `FatalError` | 400 | Permanent failure, no retry |
|
|
228
|
+
| `RetryableError` | 500 | Explicit retry request |
|
|
229
|
+
| Any other error | 500 | Retry if attempts remain |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Environment variables
|
|
234
|
+
|
|
235
|
+
| Variable | Description |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `STEADLAKE_API_KEY` | Your project API key |
|
|
238
|
+
| `STEADLAKE_PROJECT_ID` | Your project ID |
|
|
239
|
+
| `STEADLAKE_API_URL` | Override platform URL (self-hosted) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steadlake/run",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Durable task execution SDK for Steadlake — define tasks with steps, retries, scheduling, and event-driven workflows.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"format:check": "oxfmt --check .",
|
|
37
37
|
"changeset": "changeset",
|
|
38
38
|
"version": "changeset version",
|
|
39
|
-
"release": "npm run build &&
|
|
39
|
+
"release": "npm run build && changeset publish"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@steadlake/core": "^0.3.2",
|