Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

Jobs

Durable task execution with typed inputs, progress, retries, and schedules.

A job exposes work that can continue after the request that started it has ended. Define a typed task, expose it through a job, and let RELKIT persist the run, retry transient failures, and report progress.

Pre-setup

Use a native task provider in local Docker. Inngest is the starting path here; Trigger.dev and Effect MQ also have Docker recipes. Choose one and review its capabilities.

Install Inngest and local tooling

From this guide’s application/package root, install only its selected requirements:

bun add @relkit/docker@0.7.12 @relkit/inngest@0.8.1 @relkit/local@0.7.12 inngest@4.20.0

Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in dependencies; build/type tooling belongs in devDependencies. Installing an adapter does not supply its optional SDK peers or enable the feature’s configuration.

Configure the jobs profile

Merge jobs and defaults.jobs into your root configuration. Keep your existing ID, env import and other settings; the example imports the environment contract from src/platform/env.ts.

/** Declares task application defaults with inspection disabled until selected. */
import { defineApp } from "@relkit/app/config";
import env from "@app/platform/env.js";
import { docker } from "@relkit/docker";
import { inngest } from "@relkit/inngest";

export default defineApp({
  id: "relkit-inngest",
  env,
  server: { port: 3000, maxBodyBytes: 1_048_576 },
  telemetry: { redaction: { mode: "development-redacted", maxBytes: 65_536 } },
  jobs: { default: docker(inngest()) },
  defaults: { jobs: "default" },
});

Docker supplies the local provider’s connection values; there are no new defineEnv secrets for this recipe. Jobs with profile: "default" use this binding. localJob() is the older function-target provider and is not a substitute for the native task provider used here.

Check and start after registering the task and job

Check Docker before continuing. After adding the resource and its service registration, run from the app root:

docker version
bun run check
bun run typecheck
bun run dev

check validates the graph; it starts no containers. dev starts only Docker recipes used by that graph, waits for readiness, and binds their generated connection values. An unused profile alone does not start a container. Keep the server running and use bun run relkit local status in another terminal to inspect the services. Local Docker covers detached startup and stopping services while retaining their volumes.

Use bun run relkit jobs capabilities to inspect the selected provider, then follow Your first durable job to submit and observe a run.

Start here

Quickstart — define a task and trigger its job.

Tasks — the unit of durable execution.

Bindings — choose a provider and bind jobs to it.

Execution policy — timeouts, concurrency, and cancellation.

Retries and idempotency — safe retry behavior.

Clients — trigger, watch, and recover runs.

Schedules — register provider-backed schedules.

Providers — compare native capability evidence.

The model

/** 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;
/** Registers the export task as a public job exposing trigger, get, list and watch with progress/output fields. */
import { defineJob } from "@relkit/app/jobs";
import exportOrders from "../tasks/export-orders.task.js";

const exportOrdersJob = defineJob({
  name: "exportOrders",
  id: "orders.export-orders",
  task: exportOrders,
  client: {
    public: true,
    operations: ["trigger", "get", "list", "watch"],
    fields: ["status", "progress", "output"],
  },
});

export default exportOrdersJob;

Tasks own execution code, schemas, retry policy, limits, and hooks. Jobs own the public API name, admission, schedules, service selection, and client projection. The task ID, version, input schema, output schema, and policy are part of the durable identity of a run.

Defaults worth knowing

  • A run accepts at most 1 MiB of input and returns at most 1 MiB of output.
  • A progress or stream item is limited to 64 KiB.
  • The default retry policy is three attempts, one-second initial delay, 30-second maximum delay, factor two, and no jitter.
  • Run lists default to 25 items and accept at most 100.

Read provider comparison before depending on durable sleep, native listing, or a schedule policy.

Legacy API

The older function-target pages (define, enqueue, first-job, retries, idempotency, and schedules) remain available for migration. New code should use task-first jobs so task identity and compatibility checks are explicit.

Reference and source