WorkflowSteps

Steps

The ctx contract — step, sleep and waitForEvent

Steps: the ctx contract

Every durable operation goes through the run context (ctx) so the engine can record, skip and resume it deterministically.

MethodWhat it does
ctx.step(id, fn)Runs fn exactly once; records the result. On replay the recorded value is returned without re-running.
ctx.sleep(id, duration)Durably pauses until the duration elapses.
ctx.waitForEvent(id, name, opts?)Durably suspends until a matching runtime.signal(...) arrives, or the optional timeout elapses.
ctx.payloadThe validated trigger payload (typed via your Standard Schema).
ctx.runIdThe id of the current run.

The one rule: anything that must happen exactly once must be wrapped in ctx.step. Code outside step / sleep / waitForEvent re-executes on every replay, by design.

ctx.step(id, fn)

const userId = await ctx.step("create-user", () => db.users.insert(ctx.payload));

Each id must be stable and unique within the workflow. The recorded output must be JSON-serialisable, because it is persisted in the run's history and read back on replay.

ctx.sleep(id, duration)

duration is one of:

  • a number of milliseconds — ctx.sleep("a", 5000)
  • a structured amount — ctx.sleep("a", { amount: 2, unit: "hours" }) (unit: ms / milliseconds, seconds, minutes, hours, days, weeks)
  • a cron expression — ctx.sleep("a", { cron: "0 9 * * *", tz: "Europe/Berlin" })

A cron sleep resolves to the next occurrence from the moment the run suspends (not from when it was triggered).

ctx.waitForEvent(id, name, opts?)

const payload = await ctx.waitForEvent<MyEvent>("await-approval", "approval", {
    timeout: { amount: 1, unit: "days" },
});

The run suspends until runtime.signal(runId, "approval", payload) is called. The call returns the signalled payload, or undefined if the optional timeout elapses first. A wait without a timeout is only ever advanced by signal — it is never swept.

Failures

If the run function (or a step's fn) throws, the run transitions to failed and the serialised error is available on the result and via getRun. Failures are terminal; there is no automatic retry — wrap a step in your own retry/back-off if you need one, keeping it inside a single ctx.step so the whole retry is recorded as one unit.

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues