API reference › @evolu/common › Schedule › Schedule
type Schedule<Output, Input = unknown> = (
deps: ScheduleDeps,
) => (input: Input) => NextResult<readonly [Output, Millis]>;
Defined in: packages/common/src/Schedule.ts:113
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.
They measure elapsed time on Time.performance, so a system clock adjustment
cannot distort it. On platforms where that clock stops while the device
sleeps, a suspended interval measures as little or no elapsed time, so a
schedule does not see the gap the wall clock saw. A time box outlives the
wall-clock deadline it was given, as during and maxElapsed do.
resetScheduleAfter does not treat the sleep as inactivity, and
compensate, fixed and windowed do not treat it as time
to catch up on.
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 { ok, retry, retryStrategyAws, type Task } from "@evolu/common";
const fetchData: Task<string> = () => ok("data");
const _fetchWithRetry = retry(fetchData, retryStrategyAws);