API reference@evolu/commonSchedule › Schedule

type Schedule<Output, Input> = (
  deps: ScheduleDeps,
) => (input: Input) => NextResult<readonly [Output, Millis]>;

Defined in: packages/common/src/Schedule.ts:111

Composable scheduling strategies for retry, repeat, rate limiting, and more.

A Schedule uses the State pattern: calling schedule(deps) creates a step function with internal state captured in closures. Each call to step(input) advances that state and returns Ok([Output, Millis]) or Err(Done<void>) to stop. Multiple calls to schedule(deps) create independent state instances.

Err(Done<void>) is terminal. After a step returns it, every subsequent call to that step must also return Err(Done<void>).

With retry and repeat, the initial Task execution happens before the first schedule step. Schedule outputs therefore describe recurrences, not the initial execution. Time-based schedules establish their time origin on the first step call, not when schedule(deps) creates the step.

Composing a retry policy

import {
  assertOk,
  err,
  exponential,
  jitter,
  maxDelay,
  ok,
  retry,
  take,
  testCreateRun,
  type RandomNumber,
  type Task,
} from "@evolu/common";

let attempts = 0;
const fetchData: Task<string, { readonly type: "FetchError" }> = () => {
  attempts++;
  return attempts < 2 ? err({ type: "FetchError" }) : ok("data");
};

const fetchWithRetry = retry(
  fetchData,
  // A jittered, capped, limited exponential backoff.
  jitter("100%")(maxDelay("20s")(take(2)(exponential("100ms")))),
);

await using run = testCreateRun({
  random: { next: () => 0 as RandomNumber },
});
assertOk(await run(fetchWithRetry), "data");

Or use a preset:

import {
  assertTrue,
  ok,
  retry,
  retryStrategyAws,
  type Task,
} from "@evolu/common";

const fetchData: Task<string> = () => ok("data");
const fetchWithRetry = retry(fetchData, retryStrategyAws);

assertTrue(typeof fetchWithRetry === "function");