@steadlake/run
v0.3.2
Published
Durable task execution SDK for Steadlake — define tasks with steps, retries, scheduling, and event-driven workflows.
Readme
@steadlake/run
Durable task execution for your backend. Define tasks with steps, retries, scheduling, and event-driven workflows — without managing infrastructure.
Install
npm install @steadlake/runSetup
Environment variables:
STEADLAKE_API_KEY=sk_live_...
STEADLAKE_PROJECT_ID=proj_...Quick start
1. Define tasks
// tasks/send-email.ts
import { task } from "@steadlake/run";
import { z } from "zod";
export const sendEmail = task({
id: "send-email",
schema: z.object({ to: z.string().email(), subject: z.string() }),
run: async ({ payload }) => {
await emailService.send(payload.to, payload.subject);
return { sent: true };
},
});2. Mount the handler
Next.js
// app/api/steadlake/route.ts
import { createNextHandler } from "@steadlake/run/adapters/next";
import { sendEmail } from "@/tasks/send-email";
export const { POST } = createNextHandler({
baseUrl: process.env.APP_URL!,
tasks: [sendEmail],
});Hono / Generic
import { createHandler } from "@steadlake/run/adapters/generic";
import { sendEmail } from "./tasks/send-email";
const handler = createHandler({
baseUrl: process.env.APP_URL!,
tasks: [sendEmail],
});
app.post("/api/steadlake", (c) => handler(c.req.raw));Cloudflare Workers
import { createWorkerHandler } from "@steadlake/run/adapters/worker";
import { sendEmail } from "./tasks/send-email";
const handler = createWorkerHandler({ tasks: [sendEmail] });
export default {
fetch(req: Request, env: Env) {
if (new URL(req.url).pathname === "/api/steadlake") {
return handler(req, env, env.WORKER_URL);
}
return app.fetch(req, env);
},
};3. Trigger
// anywhere in your server code
import { sendEmail } from "@/tasks/send-email";
await sendEmail.trigger({ to: "[email protected]", subject: "Welcome!" });Steps
Break long tasks into named, durable steps. Completed steps are replayed from state on retry — they never re-execute.
export const importUsers = task({
id: "import-users",
run: async ({ payload, step }) => {
const users = await step.run("fetch", () => api.getUsers(payload.source));
const valid = await step.run("validate", () => users.filter(isValid));
await step.run("insert", () => db.users.bulkInsert(valid));
return { imported: valid.length };
},
});Sleep
await step.sleep("wait", "3d"); // duration: ms, s, m, h, d
await step.sleepUntil("until", date); // Date | ISO stringWait for external event
const shipment = await step.waitForEvent<{ trackingId: string }>("shipped", {
event: "order.shipped",
timeout: "7d",
});Resume by calling run.sendEvent("order.shipped", { trackingId: "..." }).
Human approval
const { approved } = await step.approve("review", {
title: "Approve $10,000 payout?",
timeout: "24h",
});Resume by calling client.resolveApproval(runId, stepId, true).
Emit events to other runs
step.emit("order.shipped", { trackingId: "TRK123" });Retries
export const chargeCustomer = task({
id: "charge-customer",
retry: {
maxAttempts: 5,
backoff: "exponential", // "exponential" | "linear" | "fixed"
},
run: async ({ payload }) => {
await stripe.charges.create({ amount: payload.amount });
},
});Throw FatalError to fail permanently without retrying. Throw RetryableError to retry explicitly.
Cron
export const dailyReport = task({
id: "daily-report",
cron: "0 9 * * 1-5",
run: async () => { ... },
});
// With timezone
export const digest = task({
id: "digest",
cron: { pattern: "0 8 * * *", timezone: "America/New_York" },
run: async () => { ... },
});Queues
export const processVideo = task({
id: "process-video",
queue: {
name: "video",
concurrencyLimit: 3,
rateLimit: { max: 100, duration: "1h" },
},
run: async ({ payload }) => { ... },
});Trigger options
await sendEmail.trigger(payload, {
idempotencyKey: `email-${userId}`,
webhook: "https://myapp.com/webhooks/done",
tags: ["transactional"],
priority: 10,
scheduledAt: new Date(Date.now() + 60_000).toISOString(),
});Wait for result:
const result = await sendEmail.triggerAndWait(payload, { timeoutMs: 30_000 });
console.log(result.status); // "success" | "failed"
console.log(result.result);Error types
| | HTTP | DEW behaviour |
|---|---|---|
| FatalError | 400 | Permanent failure, no retry |
| RetryableError | 500 | Explicit retry request |
| Any other error | 500 | Retry if attempts remain |
Environment variables
| Variable | Description |
|---|---|
| STEADLAKE_API_KEY | Your project API key |
| STEADLAKE_PROJECT_ID | Your project ID |
| STEADLAKE_API_URL | Override platform URL (self-hosted) |
