Schedules and workflows
Run code on a cron schedule, react to events, and write durable multi-step workflows that survive restarts and sleep. No queue, worker or scheduler to set up.
Every omg.dev app with server code can run background jobs. Put them in a file
in functions/, deploy, and omg.dev registers them. There is no queue, worker
or scheduler to run. An app that is asleep wakes up when a job is due.
Cron schedules
cron(schedule, handler) runs a function on a 5-field cron expression, in UTC.
import { cron, db } from "@omg-dev/server"
// Every day at 09:00 UTC
export const digest = cron("0 9 * * *", async () => {
const tasks = await db.getAll("tasks")
await db.insert("digests", { count: tasks.length, at: new Date().toISOString() })
})The schedule must be a string literal. The build reads it to register the job.
Durable workflows
workflow(name, fn) declares a function made of steps. Each step.run() is
recorded. If the app restarts or goes to sleep, the workflow continues from the
last finished step and does not run a finished step again. step.sleep() can
wait for seconds or for days.
import { workflow, startWorkflow, email } from "@omg-dev/server"
export const onboarding = workflow("onboarding", async (step, user: { email: string }) => {
await step.run("welcome", () =>
email.send({ to: user.email, subject: "Welcome", html: "<p>Glad you are here.</p>" }),
)
await step.sleep("one-day", 24 * 60 * 60 * 1000)
await step.run("tips", () =>
email.send({ to: user.email, subject: "Three tips", html: "<p>…</p>" }),
)
})
// Start a run from any route, for example after sign-up:
export async function POST(req: Request) {
const user = await req.json()
const { runId } = await startWorkflow("onboarding", user, { id: `onboarding-${user.email}` })
return Response.json({ runId })
}One rule: put every side effect (a database write, an email, an API call)
inside step.run(). Code between steps can run again when the workflow
resumes.
Pass id to startWorkflow to start a run only once for the same key.
Events
on(topic, handler) reacts to an event. emit(topic, payload) sends one.
schedule(atMs, topic, payload) sends one at a later time.
import { on, emit, schedule } from "@omg-dev/server"
export const welcome = on("user.signup", async (_ctx, payload: { email: string }) => {
// …
})
// Somewhere in a route:
await emit("user.signup", { email: "ada@example.com" })
await schedule(Date.now() + 60 * 60 * 1000, "reminder.due", { taskId: "t1" })Local development
With bun run dev, cron jobs, events and workflows run in the dev server
process. Local workflow runs are not durable: a restart loses them. Deployed
apps keep every step.