API reference › @evolu/common › Task › DisposableRun
Defined in: packages/common/src/Task.ts:1515
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.
Extends
Call Signature
DisposableRun<T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
Defined in: packages/common/src/Task.ts:1515
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 Task uses this Run's current dependencies.
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 { assertSame, assertOk, 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> = (run) => {
assertSame(run.deps.db, db);
return ok();
};
await using run = createRun({ db });
const userResult = await run(loadUser);
const savedResult = await run(saveUser);
assertOk(userResult, "Ada");
assertOk(savedResult, undefined);
Start Tasks with run(task), as shown above.
A Task can also be called directly as task(run), but this is rarely
needed. The call executes the Task inline in the current Run, as if its
body were part of the parent Task, so it does not create a child Run. This
is mainly useful for Task composition helpers.
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:1515
Starts a Task with replacement custom dependencies.
Default deps (RunDefaultDeps) are inherited unless replaced with assignable alternatives.
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:1556
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:1187
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 {
assertFalse,
assertType,
assertTrue,
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, never, DbDep> = async (run) => {
await run.ok(sleep("1s"));
return ok(run.deps.db.name);
};
await using run = createRun();
const fiber = run.abortable(loadUser, { db });
assertType<AbortableFiber<string, never, DbDep>, typeof fiber>();
fiber.abort();
const userResult = await fiber;
assertFalse(userResult.ok);
assertTrue(AbortError.is(userResult.error));
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:1374
Creates a DisposableRun attached to the root Run with this Run's deps.
Use it to give multiple related Tasks a shared lifetime. 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 { assertEqual, assertOk, 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> = (run) =>
ok(run.deps.db.users[0] ?? "Unknown");
const saveUser: Task<void, never, DbDep> = (run) => {
run.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);
assertOk(userResult, "Ada");
assertOk(savedResult, undefined);
assertEqual(db.users, ["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:1318
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.
For a long-lived reusable Run, use Run.create.
Abort masks
import {
assertEqual,
assertOk,
assertTrue,
createRun,
ok,
unabortable,
type Task,
} from "@evolu/common";
const syncUsers: Task<string> = () => ok("synced");
const syncParent = unabortable(async (run) => {
assertEqual(run.snapshot().abortMask, 1);
// Plain daemon — the caller's mask does not follow it, so abort
// requests are observed.
const fiber = run.daemon(syncUsers);
assertEqual(fiber.run.snapshot().abortMask, 0);
// Explicitly masked daemon — finishes once started.
const maskedFiber = run.daemon(unabortable(syncUsers));
assertEqual(maskedFiber.run.snapshot().abortMask, 1);
const firstResult = await fiber;
const secondResult = await maskedFiber;
assertTrue(firstResult.ok);
assertTrue(secondResult.ok);
return ok([firstResult.value, secondResult.value] as const);
});
await using run = createRun();
assertOk(await run(syncParent), ["synced", "synced"]);
Aborting a daemon
import {
assertFalse,
assertTrue,
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;
assertFalse(syncResult.ok);
assertTrue(AbortError.is(syncResult.error));
Disposing a daemon
import {
assertOk,
assertTrue,
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);
assertOk(await run(loadUser), "Ada");
}
assertTrue(syncStopped);
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:1550
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.
Example
import { assertFalse, assertTrue, createRun } from "@evolu/common";
let connectionClosed = false;
{
await using run = createRun();
run.defer(() => {
connectionClosed = true;
});
assertFalse(connectionClosed);
}
assertTrue(connectionClosed);
deps
readonly deps: ConsoleDep & LeakDetectorDep & NativeFetchDep & RandomBytesDep & RandomDep & ReportDefectDep & TimeDep & Partial<RunConfigDep> & D;
Defined in: packages/common/src/Task.ts:1386
Dependencies available to the Task, including RunDefaultDeps.
Inherited from
getState
readonly getState: () => RunState;
Defined in: packages/common/src/Task.ts:1467
Returns the current RunState of this Run.
Inherited from
id
readonly id: string & Brand<"Id">;
Defined in: packages/common/src/Task.ts:1380
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:1131
Runs a Task whose error type is never and returns its Ok
value.
This is the Task equivalent of getOk.
Example
import {
assertEqual,
assertTrue,
createRun,
ok,
type Task,
} from "@evolu/common";
interface Resource extends AsyncDisposable {
readonly value: string;
}
let disposed = false;
const openResource: Task<Resource> = () =>
ok({
value: "resource",
[Symbol.asyncDispose]: () => {
disposed = true;
return Promise.resolve();
},
});
await using run = createRun();
{
await using resource = await run.ok(openResource);
assertEqual(resource.value, "resource");
}
assertTrue(disposed);
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:1462
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 {
assertFalse,
assertTrue,
AbortError,
createRun,
ok,
sleep,
} from "@evolu/common";
let socketClosed = false;
const openSocket = () => ({
close: () => {
socketClosed = true;
},
read: () => Promise.resolve("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;
assertFalse(result.ok);
assertTrue(AbortError.is(result.error));
assertTrue(socketClosed);
Inherited from
onEvent
onEvent:
| ((event: RunEvent) => void)
| undefined;
Defined in: packages/common/src/Task.ts:1482
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:1082
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.
Example
import {
assertEqual,
createRun,
ok,
type Task,
type Typed,
} from "@evolu/common";
const loadConfig: Task<string, ConfigInvalidError> = () => ok("config");
interface ConfigInvalidError extends Typed<"ConfigInvalid"> {}
await using run = createRun();
assertEqual(await run.orThrow(loadConfig), "config");
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:1570
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:1383
The parent Run, if this Run was created as a child.
Inherited from
signal
readonly signal: AbortSignal;
Defined in: packages/common/src/Task.ts:1411
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:1470
Creates a memoized recursive RunSnapshot of the current Run tree.