API reference@evolu/commonTask › Run

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

Runs a Task with structured concurrency semantics.

Each Run forms a Task tree: child Tasks are bound to it, abort propagates through that tree, and state is observable via snapshots and events.

Run is a callable object — callable because it's convenient to run Tasks as run(task), and an object because it holds state.

Calling run(task) creates a child Run, passes it to the Task, and returns a Fiber. The child is tracked in getChildren()/events while running, then disposed and removed when settled.

Before Task execution, run(task) applies two short-circuit checks:

  • If this Run is not Running, the child is aborted with runStoppedError and the Task is replaced with err(AbortError).
  • If this Run's signal is already aborted and the child is abortable (abortMask === 0), the child is aborted with the same reason and the Task is replaced with err(AbortError).

After execution, the child stores both values: outcome (what the Task returned) and result (what callers observe). If the child signal is aborted at settlement time, result is forced to err(AbortError) even when outcome is ok(...).

That's the whole mechanism: Task is a function that takes a Run and returns an Awaitable Result. run(task) runs the Task via Promise.try(task, run) with aforementioned logic.

See

Extends

Methods

[asyncDispose]()

asyncDispose: PromiseLike<void>;

Defined in: node_modules/.bun/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts:40

Inherited from

AsyncDisposable.[asyncDispose]

Properties

abortMask

readonly abortMask: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<"AbortMask">;

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

The abort mask depth. 0 means abortable, >= 1 means unabortable.


addDeps

readonly addDeps: <E>(extraDeps: E) => Run<D & E>;

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

Adds additional dependencies to this Run and returns it.

Use for runtime-created dependencies — dependencies that cannot be created in the composition root (e.g., app start).

Example

// One-shot
await run.addDeps({ db })(getUser(123));

// Multiple deps at once
await run.addDeps({ db, cache })(task);

// Reusable — config comes from outside (message, file, etc.)
type DbWorkerDeps = DbDep; // or DbDep & CacheDep & ...

const init =
  (config: Config): Task<void, InitError, CreateDbDep> =>
  async (run) => {
    const { createDb } = run.deps;
    await using stack = new AsyncDisposableStack();

    const db = stack.use(await run.orThrow(startApp()));
    if (!db.ok) return db;

    const runWithDb = run.addDeps({ db: db.value });

    await runWithDb(getUser(123));
    await runWithDb(insertUser(user));
    return ok();
  };

FAQ

How does it work?

This is the whole implementation:

run.addDeps = <E extends NewKeys<E, D>>(newDeps: E): Run<D & E> => {
  depsRef.modify((currentDeps) => {
    const duplicate = Object.keys(newDeps).find((k) => k in currentDeps);
    assert(!duplicate, `Dependency '${duplicate}' already added.`);
    return [undefined, { ...currentDeps, ...newDeps }];
  });
  return self as unknown as Run<D & E>;
};

Dependencies are stored in a shared Ref, so addDeps propagates to all runs. The runtime assertion ensures dependencies are created once — automatic deduplication would mask poor design (dependencies should have a single, clear point of creation).

concurrency

readonly concurrency: Concurrency;

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

See


create

readonly create: () => Run<D>;

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

Creates a Run from this Run.

Like createRun, the returned Run is daemon: it stays running until disposed. Unlike createRun, it shares the same Deps as this Run.

Use this for long-lived disposable resources that need to own async work. The resource creates one internal Run with run.create() and uses that Run for all of its work. Disposing the resource then disposes that internal Run, which aborts in-flight child Tasks, waits for them to settle, and rejects later calls through it.

Typical examples are database clients, connection pools, workers, or other reusable resources with async methods and an async dispose operation.

To run a single Task as daemon, use Run.daemon.

daemon

readonly daemon: Run<D>;

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

The root Run of this Task tree.

It is called daemon because that is how it should be used: for long-running work that must not be disposed when the current Task settles. Normal child Runs are disposed by their parent when they settle. The root Run has no parent, so work started with run.daemon(task) is attached to that root Run instead of the current Run and keeps running until the root Run is disposed manually.

In application code, that usually means disposing the root Run on process shutdown in Node.js or when another platform-specific lifecycle hook is available. Browsers do not provide a fully reliable app termination hook.

Example

const myTask: Task<void, never> = async (run) => {
  // Aborted when myTask ends
  run(helperTask);

  // Outlives myTask, aborted when the root Run is disposed
  const backgroundFiber = run.daemon(backgroundSync);

  // Can still be aborted manually if needed
  backgroundFiber.abort();

  return ok();
};

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


deps

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

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

Returns the dependencies passed to createRun.


getChildren

readonly getChildren: () => ReadonlySet<Fiber<any, any, D>>;

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

Returns the current child Fibers.

getState

readonly getState: () => RunState;

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

Returns the current RunState.

id

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

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

Unique Id for this Run.


onAbort

readonly onAbort: (callback: Callback<unknown>) => void;

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

Registers a callback to run when abort is requested.

This is a convenience wrapper around subscribing to this Run's abort signal. The callback receives the abort reason extracted from AbortError.reason rather than the whole AbortError.

If already aborted, the callback is invoked immediately. For unabortable Tasks, the callback is never invoked because their signal never aborts.

Intentionally synchronous and not awaited. The callback runs in the abort request path, which may already be transitioning this Run to Disposing or Settled, so it is too late to start normal Tasks from there.

Use for immediate abort-time reactions such as removing listeners, clearing timers, removing waiters from queues, or resolving pending promises. Do not use it for awaited cleanup or resource ownership. For that, use standard JavaScript resource management with AsyncDisposableStack.

onEvent

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

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

Callback for monitoring Run events.

Called when this Run or any descendant emits a RunEvent. Events bubble up through parent runs, enabling centralized monitoring. Only emitted when RunConfig.eventsEnabled is true.


orThrow

readonly orThrow: <T, E>(task: Task<T, E, D>) => Promise<T>;

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

Runs a Task and throws if the returned Result is an error.

Use this where failure should crash the current flow instead of being handled locally.

This is the async equivalent of getOrThrow. It runs the Task, awaits its Result, and returns the value on success.

When to use:

  • Application startup or composition-root setup where errors must stop the program immediately. In Evolu apps, errors are handled by platform-specific createRun adapters at the app boundary.
  • Module-level constants
  • Test setup with values that are expected to be valid

Prefer await run(task) with an explicit if (!result.ok) check in ordinary application logic where the caller can recover, retry, or choose a different flow.

Throws: Error with the original Task error attached as cause.

parent

readonly parent: Run<D> | null;

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

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


signal

readonly signal: AbortSignal;

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

See

https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal


snapshot

readonly snapshot: () => RunSnapshot;

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

Creates a memoized RunSnapshot of this Run.

Use for monitoring, debugging, or building UI that visualizes Task trees.

Example

// React integration with useSyncExternalStore
const useRunSnapshot = (run: Run) =>
  useSyncExternalStore(
    (callback) => {
      run.onEvent = callback;
      return () => {
        run.onEvent = undefined;
      };
    },
    () => run.snapshot(),
  );