API reference@evolu/commonTask › Run

Defined in: packages/common/src/Task.ts:1094

A callable object that starts Tasks and owns their lifetimes.

A Run is both a function and an object. Calling run(task) starts the Task in a child Run and returns a Fiber. The object exposes the Run's dependencies, abort signal, abort state, snapshots, and monitoring events.

Each Task started with run(task) gets its own child Run. The child is tracked while the Task runs. When the Task settles, the child Run is disposed: its signal aborts for cleanup, unfinished descendants are requested to stop and awaited, and the child is closed so later starts throw synchronously. The parent removes the child only after that cleanup finishes.

To make a child Task failure part of a parent Task result, await or return the child Fiber from the parent. If a parent Task returns before awaiting or returning a child Fiber, cleanup still waits for the child. A child defect during that cleanup panics and aborts the root Run, but the parent Fiber keeps the Result already returned by the parent Task.

Disposing a Run requests abort and prevents new child Tasks from starting. Async disposal waits for current children to settle. Abort requests propagate through the Run tree. Abort masking helpers such as unabortable keep run.signal un-aborted while masked Tasks run.

A Task that returns a Result resolves its Fiber with that Result. A Task that observes abort and throws AbortError rejects a direct run(task) Fiber. Use Run.abortable when abort should be handled as an ordinary Result error; do not catch AbortError from run(task) to model expected cancellation. A Task that throws or rejects with anything else is a defect: the root Run panics, all running Tasks are aborted, and later Tasks are prevented from starting. A Fiber rejects with AbortError whose reason is PanicAbortReason; an AbortableFiber returns that AbortError as an Err.

Runs also provide dependency injection. run.deps contains default dependencies plus the current custom dependencies. Child Runs inherit custom deps by default; run(task, deps) replaces custom deps for that Task while default deps are inherited unless replaced with assignable alternatives.

See

Extended by

Call Signature

Run<T, E>(task: Task<T, E, D>): Fiber<T, E, D>;

Defined in: packages/common/src/Task.ts:1146

Starts a Task, invokes it with a child Run, and returns a Fiber.

The Fiber resolves with the Task Result. Await or return the Fiber to make the child outcome part of the current Task result. Discard it with void when the outcome does not matter; the Run tree supervises the Fiber, so a discarded Fiber's abort never surfaces as an unhandled rejection while defects are still reported. Use Run.daemon for work that should outlive the current Task.

The optional deps argument replaces the custom deps available to the Task. Default deps (RunDefaultDeps) are inherited unless replaced with assignable alternatives.

The Fiber rejects when the Task observes abort by throwing AbortError. It also rejects with AbortError whose reason is PanicAbortReason when the Task defects and panics the Run tree. Use Run.abortable when abort or panic should be returned as an Err; do not catch AbortError from run(task) to model expected cancellation.

Calling a disposed Run is a programmer error and throws synchronously before a Fiber is created.

Example

import { createRun, ok, type Task } from "@evolu/common";

interface Db {
  readonly name: string;
}
interface DbDep {
  readonly db: Db;
}

const db: Db = { name: "main" };
const loadUser: Task<string> = () => ok("Ada");
const saveUser: Task<void, never, DbDep> = ({ deps }) => {
  expect(deps.db).toBe(db);
  return ok();
};

await using run = createRun();
const userResult = await run(loadUser);
const savedResult = await run(saveUser, { db });
expectOk(userResult, "Ada");
expectOk(savedResult, undefined);

Call Signature

Run<T, E, Deps>(task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): Fiber<T, E, Deps>;

Defined in: packages/common/src/Task.ts:1148

A callable object that starts Tasks and owns their lifetimes.

A Run is both a function and an object. Calling run(task) starts the Task in a child Run and returns a Fiber. The object exposes the Run's dependencies, abort signal, abort state, snapshots, and monitoring events.

Each Task started with run(task) gets its own child Run. The child is tracked while the Task runs. When the Task settles, the child Run is disposed: its signal aborts for cleanup, unfinished descendants are requested to stop and awaited, and the child is closed so later starts throw synchronously. The parent removes the child only after that cleanup finishes.

To make a child Task failure part of a parent Task result, await or return the child Fiber from the parent. If a parent Task returns before awaiting or returning a child Fiber, cleanup still waits for the child. A child defect during that cleanup panics and aborts the root Run, but the parent Fiber keeps the Result already returned by the parent Task.

Disposing a Run requests abort and prevents new child Tasks from starting. Async disposal waits for current children to settle. Abort requests propagate through the Run tree. Abort masking helpers such as unabortable keep run.signal un-aborted while masked Tasks run.

A Task that returns a Result resolves its Fiber with that Result. A Task that observes abort and throws AbortError rejects a direct run(task) Fiber. Use Run.abortable when abort should be handled as an ordinary Result error; do not catch AbortError from run(task) to model expected cancellation. A Task that throws or rejects with anything else is a defect: the root Run panics, all running Tasks are aborted, and later Tasks are prevented from starting. A Fiber rejects with AbortError whose reason is PanicAbortReason; an AbortableFiber returns that AbortError as an Err.

Runs also provide dependency injection. run.deps contains default dependencies plus the current custom dependencies. Child Runs inherit custom deps by default; run(task, deps) replaces custom deps for that Task while default deps are inherited unless replaced with assignable alternatives.

See

Properties

abortable

readonly abortable: {
<T, E>  (task: Task<T, E, D>): AbortableFiber<T, E, D>;
<T, E, Deps>  (task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): AbortableFiber<T, E, Deps>;
};

Defined in: packages/common/src/Task.ts:1230

Runs a Task and returns an AbortableFiber.

An AbortableFiber is a Fiber that can request abort with .abort() or async disposal. If the Task throws or rejects with AbortError, the Fiber catches it and returns it as a Result error. Use this API instead of catching AbortError from run(task) when abort is an expected outcome. Check AbortError.reason to distinguish explicit abort, normal Run disposal, and panic-driven shutdown.

Use deps to replace the custom deps available to the Task. Default deps (RunDefaultDeps) are inherited unless explicitly replaced with assignable alternatives.

Example

import {
  AbortError,
  createRun,
  ok,
  sleep,
  type AbortableFiber,
  type Task,
} from "@evolu/common";

interface DbDep {
  readonly db: { readonly name: string };
}
const db = { name: "main" };
const loadUser: Task<string, "LoadUserError", DbDep> = async (run) => {
  await run.ok(sleep("1s"));
  return ok(run.deps.db.name);
};

await using run = createRun();
const fiber = run.abortable(loadUser, { db });
expectTypeOf(fiber).toEqualTypeOf<
  AbortableFiber<string, "LoadUserError", DbDep>
>();
fiber.abort();
const userResult = await fiber;
assert(!userResult.ok);
expect(AbortError.is(userResult.error)).toBe(true);

Call Signature

<T, E>(task: Task<T, E, D>): AbortableFiber<T, E, D>;
Type Parameters
Type Parameter
T
E
Parameters
ParameterType
taskTask<T, E, D>
Returns

AbortableFiber<T, E, D>

Call Signature

<T, E, Deps>(task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): AbortableFiber<T, E, Deps>;
Type Parameters
Type Parameter
T
E
Deps extends object
Parameters
ParameterType
taskTask<T, E, Deps>
depsRunCustomDeps<Deps>
Returns

AbortableFiber<T, E, Deps>


create

readonly create: {
  (): DisposableRun<D>;
<Deps>  (deps: RunCustomDeps<Deps>): DisposableRun<Deps>;
};

Defined in: packages/common/src/Task.ts:1390

Creates a DisposableRun attached to the root Run with this Run's deps.

Use it when you need a Run that can be reused across multiple operations. For a single long-lived Task, use Run.daemon.

Use deps to replace the created Run's custom deps. Default deps (RunDefaultDeps) are inherited unless explicitly replaced with assignable alternatives.

A recorded abort request prevents creating a Run: run.create throws AbortError even while the caller's abort mask keeps run.signal un-aborted, because a detached Run must not start under a scope that is shutting down.

Example

import { createRun, ok, type Task } from "@evolu/common";

interface DbDep {
  readonly db: { readonly users: Array<string> };
}
const db = { users: ["Ada"] };
const loadUser: Task<string, never, DbDep> = ({ deps }) =>
  ok(deps.db.users[0] ?? "Unknown");
const saveUser: Task<void, never, DbDep> = ({ deps }) => {
  deps.db.users.push("Grace");
  return ok();
};

await using run = createRun();
await using createdRun = run.create({ db });
const userResult = await createdRun(loadUser);
const savedResult = await createdRun(saveUser);
expectOk(userResult, "Ada");
expectOk(savedResult, undefined);
expect(db.users).toEqual(["Ada", "Grace"]);

Call Signature

(): DisposableRun<D>;
Returns

DisposableRun<D>

Call Signature

<Deps>(deps: RunCustomDeps<Deps>): DisposableRun<Deps>;
Type Parameters
Type Parameter
Deps extends object
Parameters
ParameterType
depsRunCustomDeps<Deps>
Returns

DisposableRun<Deps>


daemon

readonly daemon: {
<T, E>  (task: Task<T, E, D>): AbortableFiber<T, E, D>;
<T, E, Deps>  (task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): AbortableFiber<T, E, Deps>;
};

Defined in: packages/common/src/Task.ts:1341

Runs a Task as daemon and returns an AbortableFiber.

Normal child Runs are disposed after their Task settles. Tasks started by run.daemon detach their lifetime from the current Task and attach to the root Run, so they keep running until they settle or the root Run is disposed. Calling .abort() or async-disposing the returned Fiber requests abort. Keep the returned Fiber for lifetime control.

The daemon receives deps derived from the Run that starts it, not from the root Run: deps replace that Run's custom deps for the daemon Task, while lifetime is attached to the root Run. Default deps (RunDefaultDeps) are inherited unless explicitly replaced with assignable alternatives.

The caller's abort mask is not inherited. A daemon detaches to the root, so a mask-inheriting daemon could never observe abort and would hang root disposal. Wrap the daemon Task with unabortable when it must finish once started.

A recorded abort request prevents starting a daemon: run.daemon throws AbortError even while the caller's abort mask keeps run.signal un-aborted, because detached work must not spawn under a scope that is shutting down.

Example

import { createRun, ok, unabortable, type Task } from "@evolu/common";

const syncUsers: Task<string> = () => ok("synced");
const syncParent = unabortable(async (run) => {
  expect(run.snapshot().abortMask).toBe(1);

  // Plain daemon — the caller's mask does not follow it, so abort
  // requests are observed.
  const fiber = run.daemon(syncUsers);
  expect(fiber.run.snapshot().abortMask).toBe(0);

  // Explicitly masked daemon — finishes once started.
  const maskedFiber = run.daemon(unabortable(syncUsers));
  expect(maskedFiber.run.snapshot().abortMask).toBe(1);
  const firstResult = await fiber;
  const secondResult = await maskedFiber;
  assert(firstResult.ok);
  assert(secondResult.ok);
  return ok([firstResult.value, secondResult.value] as const);
});

await using run = createRun();
expectOk(await run(syncParent), ["synced", "synced"]);

For a long-lived reusable Run, use Run.create.

Example

import { AbortError, createRun, ok, sleep, type Task } from "@evolu/common";

interface DbDep {
  readonly db: { readonly name: string };
}
const db = { name: "main" };
const syncUsers: Task<void, never, DbDep> = async (run) => {
  await run.ok(sleep("1s"));
  return ok();
};

await using run = createRun();
const fiber = run.daemon(syncUsers, { db });
fiber.abort();
const syncResult = await fiber;
assert(!syncResult.ok);
expect(AbortError.is(syncResult.error)).toBe(true);
import { createRun, ok, waitForAbort, type Task } from "@evolu/common";

let syncStopped = false;
const syncUsers: Task<never> = async (run) => {
  using _ = run.onAbort(() => {
    syncStopped = true;
  });
  return await run(waitForAbort);
};
const loadUser: Task<string> = () => ok("Ada");

await using run = createRun();
{
  // Async disposal requests abort and waits for the daemon to stop.
  await using _syncFiber = run.daemon(syncUsers);
  expectOk(await run(loadUser), "Ada");
}
expect(syncStopped).toBe(true);

Call Signature

<T, E>(task: Task<T, E, D>): AbortableFiber<T, E, D>;
Type Parameters
Type Parameter
T
E
Parameters
ParameterType
taskTask<T, E, D>
Returns

AbortableFiber<T, E, D>

Call Signature

<T, E, Deps>(task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): AbortableFiber<T, E, Deps>;
Type Parameters
Type Parameter
T
E
Deps extends object
Parameters
ParameterType
taskTask<T, E, Deps>
depsRunCustomDeps<Deps>
Returns

AbortableFiber<T, E, Deps>


deps

readonly deps: ConsoleDep & LeakDetectorDep & NativeFetchDep & RandomBytesDep & RandomDep & ReportDefectDep & TimeDep & Partial<RunConfigDep> & D;

Defined in: packages/common/src/Task.ts:1402

Dependencies available to the Task, including RunDefaultDeps.


getState

readonly getState: () => RunState;

Defined in: packages/common/src/Task.ts:1476

Returns the current RunState of this Run.

id

readonly id: string & Brand<"Id">;

Defined in: packages/common/src/Task.ts:1396

Unique Id for this Run.


ok

readonly ok: {
<T>  (task: Task<T, never, D>): Promise<T>;
<T, Deps>  (task: Task<T, never, Deps>, deps: RunCustomDeps<Deps>): Promise<T>;
};

Defined in: packages/common/src/Task.ts:1176

Runs a Task whose error type is never and returns its Ok value.

This is the Task equivalent of getOk.

Call Signature

<T>(task: Task<T, never, D>): Promise<T>;
Type Parameters
Type Parameter
T
Parameters
ParameterType
taskTask<T, never, D>
Returns

Promise<T>

Call Signature

<T, Deps>(task: Task<T, never, Deps>, deps: RunCustomDeps<Deps>): Promise<T>;
Type Parameters
Type Parameter
T
Deps extends object
Parameters
ParameterType
taskTask<T, never, Deps>
depsRunCustomDeps<Deps>
Returns

Promise<T>


onAbort

readonly onAbort: (callback: (abortError: AbortError) => void) =>
  | Disposable
  | null;

Defined in: packages/common/src/Task.ts:1471

Registers a synchronous callback for observed run.signal aborts.

The callback runs when this Run observes abort, including normal Run disposal. Masked Runs can record an abort request without aborting run.signal, so this callback does not run for every recorded request. If this Run is already aborted, the callback runs immediately and no callback is registered. Dispose the returned registration to release the callback before abort. Returns null when already aborted, which is safe in a using declaration.

Example

import { AbortError, createRun, ok, sleep } from "@evolu/common";

let socketClosed = false;
const openSocket = () => ({
  close: () => {
    socketClosed = true;
  },
  read: async () => "message",
});

await using run = createRun();
const fiber = run.abortable(async (run) => {
  const socket = openSocket();
  using closeOnAbort = run.onAbort(() => {
    socket.close();
  });

  await run.ok(sleep("1s"));
  const message = await socket.read();
  return ok(message);
});
fiber.abort();
const result = await fiber;
assert(!result.ok);
expect(AbortError.is(result.error)).toBe(true);
expect(socketClosed).toBe(true);

onEvent

onEvent:
  | ((event: RunEvent) => void)
  | undefined;

Defined in: packages/common/src/Task.ts:1491

Callback for monitoring Run events emitted by this Run or descendants.

Event handlers are observers, not part of Task control flow. Handler defects are reported via ReportDefectDep.reportDefect; they do not panic the root Run or change Run state.

Do not call Run APIs or Fiber control methods from event handlers. Event handlers must only observe and report.


orThrow

readonly orThrow: {
<TTask>  (task: TaskWithError<TTask>): Promise<InferTaskOk<TTask>>;
<Deps, TTask>  (task: TaskWithError<TTask>, deps: RunCustomDeps<Deps>): Promise<InferTaskOk<TTask>>;
};

Defined in: packages/common/src/Task.ts:1161

Runs a Task whose error type is not never and throws if the returned Result is an error.

This is the Task equivalent of getOrThrow. Use it where a declared Result error should crash the current flow instead of being handled locally.

Call Signature

<TTask>(task: TaskWithError<TTask>): Promise<InferTaskOk<TTask>>;
Type Parameters
Type Parameter
TTask extends Task<any, any, D>
Parameters
ParameterType
taskTaskWithError<TTask>
Returns

Promise<InferTaskOk<TTask>>

Call Signature

<Deps, TTask>(task: TaskWithError<TTask>, deps: RunCustomDeps<Deps>): Promise<InferTaskOk<TTask>>;
Type Parameters
Type Parameter
Deps extends object
TTask extends Task<any, any, Deps>
Parameters
ParameterType
taskTaskWithError<TTask>
depsRunCustomDeps<Deps>
Returns

Promise<InferTaskOk<TTask>>


parent

readonly parent: Run<unknown> | null;

Defined in: packages/common/src/Task.ts:1399

The parent Run, if this Run was created as a child.


signal

readonly signal: AbortSignal;

Defined in: packages/common/src/Task.ts:1427

Abort signal for the Task.

Aborts when this Run is disposed. While the Task is running, it also aborts when a parent abort request reaches this Run and the Task is not wrapped with unabortable.

After the Task settles, this Run is disposed. If the signal has not already aborted, disposal aborts it with the recorded abort error for aborted or panicked exits, and with runDisposedAbortReason after successful completion.

In masked Tasks, an abort request can be recorded in Run.getState without being observed by this signal. If the masked Task completes successfully, this signal still aborts with runDisposedAbortReason during disposal.

Pass this signal to cancellation-aware APIs such as fetch. For cleanup callbacks, use Run.onAbort instead of addEventListener. Run's internal cleanup also listens on this signal, so abort listeners must not call stopImmediatePropagation — it would suppress later-registered listeners, including Run.onAbort callbacks.


snapshot

readonly snapshot: () => RunSnapshot;

Defined in: packages/common/src/Task.ts:1479

Creates a memoized recursive RunSnapshot of the current Run tree.