API reference › @evolu/common › Task › DisposableRun
Defined in: packages/common/src/Task.ts:1555
A Run with explicit disposal.
createRun creates a root DisposableRun. Run.create creates one attached to that root, typically to give a reusable resource its own lifetime. A DisposableRun owns its child Tasks and closure-held cleanup registered with DisposableRun.defer; disposing it shuts down both.
Sync disposal starts shutdown without waiting. Async disposal waits for child Tasks and registered cleanup to finish.
Use createRun at composition roots such as app, server, worker, or test entry points. The common factory is platform-agnostic; platform adapters can wrap it to add global error handling or shutdown integration.
Example
import { createRun, ok, type Task } from "@evolu/common";
await using run = createRun();
const loadData: Task<string> = () => ok("data");
expectOk(await run(loadData), "data");
Example with custom dependencies
import { createRun, type DisposableRun } from "@evolu/common";
interface ConfigDep {
readonly config: { readonly apiUrl: string };
}
await using run = createRun<ConfigDep>({
config: { apiUrl: "https://api.example.com" },
});
expectTypeOf(run).toEqualTypeOf<DisposableRun<ConfigDep>>();
expect(run.deps.config.apiUrl).toBe("https://api.example.com");
Extends
Call Signature
DisposableRun<T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
Defined in: packages/common/src/Task.ts:1555
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
DisposableRun<T, E, Deps>(task: Task<T, E, Deps>, deps: RunCustomDeps<Deps>): Fiber<T, E, Deps>;
Defined in: packages/common/src/Task.ts:1555
A Run with explicit disposal.
createRun creates a root DisposableRun. Run.create creates one attached to that root, typically to give a reusable resource its own lifetime. A DisposableRun owns its child Tasks and closure-held cleanup registered with DisposableRun.defer; disposing it shuts down both.
Sync disposal starts shutdown without waiting. Async disposal waits for child Tasks and registered cleanup to finish.
Use createRun at composition roots such as app, server, worker, or test entry points. The common factory is platform-agnostic; platform adapters can wrap it to add global error handling or shutdown integration.
Example
import { createRun, ok, type Task } from "@evolu/common";
await using run = createRun();
const loadData: Task<string> = () => ok("data");
expectOk(await run(loadData), "data");
Example with custom dependencies
import { createRun, type DisposableRun } from "@evolu/common";
interface ConfigDep {
readonly config: { readonly apiUrl: string };
}
await using run = createRun<ConfigDep>({
config: { apiUrl: "https://api.example.com" },
});
expectTypeOf(run).toEqualTypeOf<DisposableRun<ConfigDep>>();
expect(run.deps.config.apiUrl).toBe("https://api.example.com");
Methods
[asyncDispose]()
asyncDispose: PromiseLike<void>;
Defined in: node_modules/@typescript/old/lib/lib.esnext.disposable.d.ts:38
Inherited from
AsyncDisposable.[asyncDispose]
[dispose]()
dispose: void;
Defined in: node_modules/@typescript/old/lib/lib.esnext.disposable.d.ts:34
Inherited from
Disposable.[dispose]
Properties
abort
readonly abort: (reason?: AbortReason) => void;
Defined in: packages/common/src/Task.ts:1579
Requests abort with an optional AbortReason and starts sync disposal.
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>
Inherited from
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
| Parameter | Type |
|---|---|
deps | RunCustomDeps<Deps> |
Returns
DisposableRun<Deps>
Inherited from
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>
Inherited from
defer
readonly defer: (finalizer: () => Awaitable<void>) => void;
Defined in: packages/common/src/Task.ts:1573
Registers closure-held cleanup owned by this Run.
Finalizers run in LIFO order after child Tasks settle and are awaited by
async disposal. The Run is in Aborted state while they run and
transitions to Settled afterward, so a finalizer cannot start Tasks on
it. Use using for resources owned by a Task stack frame; use defer for
closure-held state whose lifetime is bounded by a reusable DisposableRun.
Sync disposal starts cleanup without waiting and does not throw finalizer defects synchronously. Async disposal awaits cleanup. If a finalizer defects, the defect is reported once, and every async disposal call rejects with the same already-reported AbortError.
Calling defer after disposal starts is a programmer error.
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.
Inherited from
getState
readonly getState: () => RunState;
Defined in: packages/common/src/Task.ts:1476
Returns the current RunState of this Run.
Inherited from
id
readonly id: string & Brand<"Id">;
Defined in: packages/common/src/Task.ts:1396
Unique Id for this Run.
Inherited from
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>
Inherited from
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);
Inherited from
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.
Inherited from
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>>
Inherited from
panic
readonly panic: (defect: unknown) => AbortError;
Defined in: packages/common/src/Task.ts:1593
Shuts down the Run tree because of a defect.
Panic creates a PanicAbortReason from the defect, wraps it in an
AbortError, and reports that AbortError through
ReportDefectDep. The original defect is available as
abortError.reason.defect for diagnostics. The first panic records the
AbortError as the root Run's aborted exit and starts root disposal, which
aborts running Tasks, prevents new Tasks from starting, and waits for
running Tasks to settle. Later panics still report and return their own
AbortError, but do not replace the root Run exit.
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.
Inherited from
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.
Inherited from
snapshot
readonly snapshot: () => RunSnapshot;
Defined in: packages/common/src/Task.ts:1479
Creates a memoized recursive RunSnapshot of the current Run tree.