Concepts

Workflows

Define a workflow, group it into an app, and trigger it with an event.

A workflow is a durable function triggered by an event. You define it with defineWorkflow, then serve and register it so Duraton can drive it. The console lists every workflow an app has registered:

Registered workflows in the console

Select a workflow to open its detail drawer: the definition (app, schedule and next fire, retry policy), live stats tiles, and runs-over-time and latency charts - all scoped to that one workflow and filterable by time range - plus a table of just its runs. The charts and stats reuse the same data as the console's Overview, narrowed with a ?workflow= filter on GET /runs, GET /runs/stats, and GET /runs/timeseries. Selecting a run jumps to the Runs view with that run open.

Defining a workflow

import { defineWorkflow } from "@duraton/sdk";

interface OrderData {
  orderId: string;
}

const orderCreated = defineWorkflow<OrderData>({
  name: "order.created",
  retry: { maxAttempts: 3 },
  handler: async (ctx) => {
    const charge = await ctx.step.run("charge", () => chargeCard(ctx.event.data));
    return charge;
  },
});
  • name identifies the workflow. With no triggers, an event of the same name starts it; declare triggers to start it from other events (with filters/wildcards) or a cron schedule.
  • retry sets how steps retry on failure. Optional; see Retries.
  • handler is your workflow body. It receives a context (ctx) and uses ctx.step to do durable work.

The type parameter (<OrderData>) types ctx.event.data, so your event payload is checked.

The handler context

FieldWhat it is
ctx.eventThe triggering event: { name, data }.
ctx.stepThe durable step API - run and sleep. See Steps.
ctx.runIdThe id of this run.
ctx.attemptWhich attempt this run is on.

Serving and registering

Group your workflows into an app - a named set of workflows that run together in one process. serve() turns them into an HTTP handler Duraton calls; register() announces the app to Duraton on startup.

import { register, serve } from "@duraton/sdk";

const invoke = serve({ workflows: [orderCreated] });

Bun.serve({
  port: 6773,
  fetch(req) {
    const url = new URL(req.url);
    if (req.method === "POST" && url.pathname === "/invoke") {
      return invoke(req);
    }
    return new Response("duraton runner");
  },
});

await register({
  engineUrl: process.env.DURATON_ENGINE_URL!,
  app: "order-app",
  url: "http://localhost:6773/invoke",
  workflows: [orderCreated],
});

register() is idempotent - it's safe to call on every startup.

Triggering a run

Send an event whose name matches a workflow. Duraton creates a run and drives it to completion.

curl -X POST $DURATON_ENGINE_URL/events \
  -d '{"name":"order.created","app":"order-app","data":{"orderId":"A1"}}'

The response includes a run id you can use to inspect the run at GET /runs/{id} and its steps at GET /runs/{id}/steps.

On this page