Tasks
The typed, durable unit of job execution.
A task defines the input, result, and behavior of work that a job can run durably.
Scaffold a task
From the application root, with an existing orders service:
bun run relkit add task export-orders --service orders
This creates src/services/orders/tasks/export-orders.task.ts with the stable ID
orders.export-orders-task, version 1, and a durable execution model. It registers the
task on the service. Replace the placeholder schemas and handler with your operation; keep
its identity and version stable. Expose it through a
task-first job when callers need admission, run handles, and observation.
For every task scaffolding flag, see the task command reference.
Define the task
A task contains the code and schemas for one durable operation. A task is not a request handler: it may be resumed, retried, observed, or run after the original process exits.
/** Produces demonstration CSV through the ordinary order helper and emits validated progress around the durable export. */
import { defineTask } from "@relkit/app/tasks";
import { z } from "@relkit/app/schema";
import { exportOrderCsv } from "../helpers/fake-business.js";
const exportOrders = defineTask({
id: "orders.export-orders",
version: "1",
execution: "durable",
input: z.object({ orderIds: z.array(z.string()) }),
output: z.object({ csv: z.string(), exported: z.number().int().nonnegative() }),
progress: z.object({
completed: z.number().int().nonnegative(),
total: z.number().int().positive(),
}),
handler: async ({ orderIds }, context) => {
await context.progress.emit({ completed: 0, total: orderIds.length });
const csv = exportOrderCsv(orderIds);
await context.progress.emit({ completed: orderIds.length, total: orderIds.length });
return { csv, exported: orderIds.length };
},
});
export default exportOrders;
Use a stable task ID. The version identifies a compatible task definition, not a deployment build. Schema hashes and the client fingerprint are recorded with each job definition so migration checks can reject accidental contract changes.
Tasks can emit typed progress, return a bounded output, sleep when their provider supports durable sleep, and throw declared errors for retry or terminal failure. Keep business side effects idempotent and put provider-specific code behind the integration boundary. A retryable task does not expose durable sleep.
See durations and sleep, execution policy, and retries and idempotency.
Use from the frontend
A task does not become a browser endpoint by itself. Give it a job with a client policy, then use the typed job client to trigger and observe it. The task’s schemas determine input and output; the job’s policy determines permitted operations and visible fields.