Quick Start
Install Runlane, define your first task, and run it locally.
This page builds the smallest durable Runlane program:
- Install core, the local lane, and a payload schema library.
- Define one queue and one task.
- Create a local runtime.
- Trigger one run.
- Execute that run and inspect the stored result.
Install
pnpm add @runlane/core @runlane/lane-local zodSmallest Program
Put this in a TypeScript file that runs inside your app or test process:
import { createRunlane, queue, task } from '@runlane/core'
import { createLocalLane } from '@runlane/lane-local'
import * as z from 'zod'
const sentWelcomeEmails: string[] = []
const emailQueue = queue({ name: 'emails', default: true })
const sendWelcomeEmail = task({
id: 'emails.welcome',
queue: emailQueue,
schema: z.object({
userId: z.string(),
}),
run(payload) {
sentWelcomeEmails.push(payload.userId)
},
})
const runlane = createRunlane({
lane: createLocalLane(),
queues: [emailQueue],
tasks: { sendWelcomeEmail },
})
const { run: queuedRun } = await runlane.trigger(runlane.tasks.sendWelcomeEmail, {
userId: 'user_123',
})
const completedRun = await runlane.executeNext()
console.log(queuedRun.status)
console.log(completedRun?.status)
console.log(JSON.stringify(sentWelcomeEmails))Expected output:
queued
succeeded
["user_123"]That is the first successful path: trigger() creates durable work, and executeNext() claims one due run and calls the task handler.
What Happened
queue() creates a provider-neutral queue definition. The queue is registered on the runtime through queues: [emailQueue]. Marking it default: true lets tasks use it when no other queue is selected.
task() defines user code plus the payload validator for that code. Runlane accepts Standard Schema-compatible validators, so Zod works directly. The payload is validated before the run is created and again before the handler runs.
createRunlane() creates a runtime over a lane, a queue registry, and a task catalog. Passing tasks: { sendWelcomeEmail } makes that catalog authoritative and exposes the same handles on runlane.tasks.
createLocalLane() is the ready-to-use in-memory lane from @runlane/lane-local. It stores runs, events, leases, schedules, and outbox rows in the current process. Use it for development, examples, and tests. It is not a production durability boundary, and process exit loses local state.
Trigger And Execute
trigger() validates the payload, writes a run, writes the first delivery request, and returns { run, outcome }. With the default dispatch policy, it also tries to publish the new wakeup immediately.
The returned run is the queued run record. outcome is TriggerOutcomeType.Created for new work and TriggerOutcomeType.ReturnedExisting when idempotency returns an existing owner for the same key.
executeNext() is the deterministic single-run execution primitive. It scans for one due run, claims a lease, records run.started, runs the handler, and persists the attempt result. It returns the executed run, or undefined when no due run is available.
Use a worker when a process should keep polling or drain a backlog:
import { WorkerMode } from '@runlane/core'
const worker = runlane.worker({ mode: WorkerMode.Drain })
await worker.closedDrain mode processes currently due work and exits. Omitting mode starts a polling worker that keeps scanning until stopped.
Inspect The Result
Use the runtime operator API to inspect the stored run:
const storedRun = await runlane.runs.get(queuedRun.id)
console.log(storedRun?.status)For the program above, the stored run is succeeded after executeNext() finishes.
The important invariant is that execution state is not just an in-memory callback result. Even with the local lane, Runlane writes an append-only event history: created, delivery requested, lease claimed, started, then succeeded or another terminal or waiting outcome.
Next Steps
- Use Workers when a process should keep executing queued work.
- Use Local Development for local lane behavior, limits, and testing patterns.
- Use Tasks And Runs for task options, idempotency, singleton keys, and trigger options.
- Use Schedules when
tick()should materialize scheduled runs.
You do not need tick() for the immediate trigger above. Call tick() from maintenance infrastructure when you use schedules, released or retried runs, expired leases, cancellation finalization, or deferred outbox publishing.
Production lane packages follow the same runtime shape. They compose storage and transport adapters into a Lane, then pass that lane into createRunlane().