API reference › @evolu/common › Task › 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
| Parameter | Type |
|---|---|
task | Task<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
| Parameter | Type |
|---|---|
task | Task<T, E, Deps> |
deps | RunCustomDeps<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
Call Signature
<Deps>(deps: RunCustomDeps<Deps>): DisposableRun<Deps>;
Type Parameters
| Type Parameter |
|---|
Deps extends object |
Parameters
| Parameter | Type |
|---|---|
deps | RunCustomDeps<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
| Parameter | Type |
|---|---|
task | Task<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
| Parameter | Type |
|---|---|
task | Task<T, E, Deps> |
deps | RunCustomDeps<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
| Parameter | Type |
|---|---|
task | Task<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
| Parameter | Type |
|---|---|
task | Task<T, never, Deps> |
deps | RunCustomDeps<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
| Parameter | Type |
|---|---|
task | TaskWithError<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
| Parameter | Type |
|---|---|
task | TaskWithError<TTask> |
deps | RunCustomDeps<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.