---
id: "packages/active-state/api"
title: "API reference"
description: "Every public export of active-state, with its contract - the package's own reference, and every export generated from the source."
url: "/packages/active-state/api"
locale: "en"
revision: "1030f85c51ec7bbe"
---

# API reference

> [!NOTE]
> **Two views of one API**
>
> First the package's own `docs/API.md`, included unchanged: the contract of
> each export. Then every export of each entry point, generated from the source,
> so nothing the package exports is missing here.

Per-export contract for `@workspace/active-state`, an internal workspace package. This document describes the shape and observable behaviour of every public export; per-symbol TSDoc lives in the TypeScript sources and the compiler is authoritative on types. The decisions behind the contract are in `DECISIONS.md` § Besluit 5, the runtime model is in `ARCHITECTURE.md`, and purpose and scope are in `FUNCTIONEEL-OVERZICHT.md`. The public surface is pinned symbol by symbol in `src/__tests__/public-api.test.ts`.

---

## Import paths

| Entry | Contents |
| --- | --- |
| `@workspace/active-state` | The framework-independent core. Imports no React |
| `@workspace/active-state/react` | The whole core surface again (the same objects), plus `useStore` and `useScoped` (EXPERIMENTAL) |
| `@workspace/active-state/testing` | Test seams and the instrumentation protocol. Internal and unstable; for tests and diagnostics |

A React consumer imports from `/react` only. `react@^19` is an optional peer, required by `/react` and by the React-based parts of a test setup.

## Surface at a glance

| Kind | Exports |
| --- | --- |
| Classes | `Store`, `Derived`, `Effect`, `Operation`, `Scope` |
| Functions | `batch`, `transaction`, `shallow` |
| Errors | `PurityViolation`, `StructuralMutationInTransaction`, `LifecycleViolation`, `DisposedResourceError`, `DependencyCycleError`, `CommitNestingError`, `CommitRevoked`, `FeedbackLimitExceeded` |
| Types | `Source`, `ReadableStore`, `Listener`, `Unsubscribe`, `StoreOptions`, `DerivedOptions`, `ValuesOf`, `EffectOptions`, `EffectContext`, `ScopeOptions`, `ScopeState`, `OperationOptions`, `OperationContext`, `OperationStatus`, `Invocation`, `InvocationId`, `InvocationStatus`, `Outcome`, `CancelReason`, `Policy`, `NotThenable`, `GuardedCall` |
| `/react` only | `useStore`, `UseStoreOptions`, `useScoped` (EXPERIMENTAL), `ScopedFactory` |

---

## Contexts: which calls are allowed where

Every mutating or lifecycle call first checks where it runs. The rules apply to every section below.

| Context | Allowed | Rejected |
| --- | --- | --- |
| **Pure**: Store updaters, every equality, Derived compute, probe listeners | Reads (`.state`, `invocation.status`, `scope.state`), `shallow` | Everything else, with `PurityViolation` |
| **Open Transaction** (`transaction`, `ctx.commit`) | Reads, `setState`/`update`, nested `transaction`/`batch`, unowned Store/Derived construction, `invoke()` (deferred) | Structural and lifecycle calls (`subscribe`, releasing a handle, any `dispose`, Scope calls, scoped construction, Effect/Operation construction, `cancel`), with `StructuralMutationInTransaction`; `ctx.commit` with `CommitNestingError` |
| **Callback or plain code** | Everything | — |

A rejection inside an open Transaction fails that Transaction even when user code catches the error.

---

## `Store<T>`

The writable state authority. One type parameter; writing is invariant in `T`.

```ts
new Store<T>(initial: T, options?: StoreOptions<T>)

interface StoreOptions<T> {
  equal?: (a: T, b: T) => boolean; // "unchanged"; no default (see below)
  label?: string;                  // value-free identifier for instrumentation
  scope?: Scope;                   // disposed with this Scope
}
```

| Member | Contract |
| --- | --- |
| `state` | The current value. O(1), no side effect. Inside a Transaction it holds the Transaction's own writes |
| `setState(next)` | Stores `next` exactly as given; never calls it, whatever its type. A function is an ordinary value |
| `update(fn)` | Stores `fn(current)`. `fn` runs in a pure context; if it throws, nothing is accepted and the error reaches the caller |
| `subscribe(listener)` | Registers `(value, previous) => void` for published transitions. Each call is its own registration with an idempotent release; the same function subscribed twice is delivered twice |
| `asReadonly()` | A memoised read-only projection typed `Source<T>`: the same value and transitions, no write members at type level or at runtime. It is a native source, so it can be a Derived or Effect input |
| `dispose()` | Makes the Store inert: subscriptions released without notification, later writes ignored, reads return the frozen value. Idempotent |

**Acceptance.** A write passes `equal(current, candidate)` when `equal` is set; an equal candidate is not accepted and publishes nothing. An accepted write is stored before the call returns, and no user callback runs between acceptance and storage.

**Publication.** A Store with an accepted write publishes at the next publication boundary, when it has no `equal`, or when `!equal(lastPublished, current)`. Subscribers receive `(value, previous)`, where `previous` is the last published value; unpublished intermediate values never become `previous`.

**No default equality.** A Store without `equal` accepts and publishes every write, also an identical value. Whether a default should exist (`Object.is` or `shallow`) is an open decision (ROADMAP R7). Supply `equal` explicitly where you rely on it.

**Disposed Stores** ignore writes silently; with an instrumentation listener attached, each ignored call is a `write.ignored` event (see [`/testing`](#the-testing-entry-testing)).

---

## Readables: `Source<T>`, `ReadableStore<T>`, `Listener<T>`, `Unsubscribe`

```ts
interface ReadableStore<T> {
  readonly state: T;
  subscribe(listener: Listener<T>): Unsubscribe;
}
type Listener<T> = (value: T, previous: T) => void;
type Unsubscribe = () => void;
```

- **`ReadableStore<T>`** is the smallest readable shape. `useStore` accepts any value that satisfies it.
- **`Source<T>`** is a *native* graph source: a `Store`, a readonly projection, a `Derived` or an Operation's `status`. It is exported as a type only; the runtime recognises a source by a private brand, not by its shape, so a cast or impostor is rejected as an input. `Source<T>` is covariant and exposes only `state` and `subscribe`.

---

## `Derived<T, I>`

Computed state over explicit inputs.

```ts
new Derived({
  inputs: [a, b],                 // native sources, copied and frozen at construction
  compute: (valueA, valueB) => …, // positional input values; pure and deterministic
  equal?: (x, y) => boolean,      // default Object.is
  label?: string,
  scope?: Scope,
});

type ValuesOf<I extends readonly Source<unknown>[]> = {
  readonly [K in keyof I]: I[K] extends Source<infer V> ? V : never;
};
```

`I` is inferred from `inputs` (a `const` type parameter), so no `as const` is needed; `Derived<T>` alone names any Derived of `T`.

| Member | Contract |
| --- | --- |
| `state` | Always the value for the current inputs, observed or not, also inside an open `batch` or Transaction. Construction computes nothing; the first read or observation does |
| `subscribe(listener)` | Observes the Derived: the first observer activates it (with catch-up), the last release deactivates it |
| `dispose()` | Releases subscriptions, stops observing inputs and freezes the last value |

- **Freshness and publication are independent.** A read never creates, suppresses or consumes a notification. An observed Derived publishes when its settled value differs from its last published value under `equal`; the same command notifies the same observers with or without intermediate reads.
- **A Derived is a value**, not a reducer: `compute` receives the input values and nothing else. Memory across evaluations belongs in a Store.
- **Failure.** A throwing `compute` or `equal` reaches the reader; nothing is cached and a later read retries. During settlement the failure goes to the error sink and the node and its dependents publish nothing that round.
- **Cycles.** Declared inputs cannot form a cycle. A compute that reads its own Derived through a closure throws `DependencyCycleError`. A read of an undeclared source is not tracked (unsupported; visible as `derived.undeclared-read` with a listener).
- **Disposed.** Reads return the frozen value; a Derived disposed before it ever produced a value throws `DisposedResourceError`.

---

## `Scope`

A lifetime container.

```ts
new Scope(options?: {parent?: Scope; pending?: boolean; label?: string})
type ScopeState = 'pending' | 'active' | 'disposing' | 'disposed';
```

| Member | Contract |
| --- | --- |
| `state` | The current `ScopeState` |
| `dispose()` | Tears down every member in reverse registration order (a child Scope completely at its own position), attempts every step, then throws the failure (one error as itself, several as `AggregateError`). Publishes nothing. Idempotent |
| `onDispose(fn)` | Registers a disposer, called once at its position |
| `activate()` | Activates a pending model from its root, atomically: every deferred acquisition (observations, Effect starts, queued invocations) succeeds, or all are undone, the model stays pending and the error is rethrown. Rejected with `LifecycleViolation` on a non-root pending Scope |

- **Ownership.** Stores and Deriveds may be unowned; Effects and Operations always have a Scope. Individually disposed members leave their Scope at once.
- **Pending Scopes** (`pending: true`, or any child of a pending parent) are acquisition-free: nothing they own starts an Effect, admits an invocation or observes an outside source until the root is activated. A pending owner only delays the start: an invocation made inside a Transaction or `ctx.commit` that reverts ends `cancelled('transaction-reverted')` and a later activation does not start it. A discarded pending model is referenced by nothing in the runtime. A factory that builds one must only construct resources; `fetch`, direct external subscriptions and DOM work belong in an Effect or Operation.
- **Disposed resources are inert**: writes are ignored, `invoke` returns an already cancelled invocation, `subscribe` returns a no-op handle, reads return the frozen value. Two calls still throw: a call from a pure context, and reading a Derived that was disposed before it ever produced a value (`DisposedResourceError`).

---

## `Effect<I>`

An owned reaction to published input transitions. Not a Derived and not React's `useEffect`.

```ts
new Effect({
  scope,                          // required
  inputs: [a, b],
  run: ([valueA, valueB], ctx) => { …; return () => {/* cleanup */}; },
  immediate?: boolean,
  label?: string,
});
interface EffectContext { readonly scope: Scope } // the run Scope
```

- **When it runs.** In the reaction phase of a round in which at least one input published, after every subscriber of that round, with the complete input tuple captured before the round's first callback. Runs never nest. If an input failed or stayed undecided in that round, the Effect is skipped.
- **`immediate`.** The Effect is owed an initial run. It never begins before the constructor has returned; it runs at the first publication boundary after the start (a later write, the close of an enclosing `batch`/Transaction, a later round, or the activation of a pending Scope). No microtask or timer is introduced.
- **Run Scope.** Each run receives `ctx.scope`. Resources created in it live until the run ends, and only the Effect disposes it.
- **Teardown order**, on every path (rerun, `dispose()`, owner disposal, disposal during the run): the cleanup, then the run Scope, then, when the Effect stops, unobserving the inputs.
- **Synchronous only.** A run or cleanup that returns a promise is a diagnosed misuse (`TypeError` to the error sink or the `dispose()` caller). It is never awaited, and its rejection is reported, never left unhandled. The type cannot rule out an async cleanup; the runtime check is the guarantee.
- **Failures** of a run or cleanup go to the error sink; the Effect stays started and runs again on its next trigger.

---

## `Operation<TInput, TResult>`

Owned in-flight work with identity, policy, status and typed outcomes.

```ts
new Operation<TInput = void, TResult = unknown>({
  scope,                                    // required; disposal cancels everything
  policy: 'latest-wins' | 'drop-while-running' | 'queue',
  run: (input, ctx) => PromiseLike<TResult> | TResult,
  label?: string,
});
```

| Member | Contract |
| --- | --- |
| `invoke(input?)` | Creates an `Invocation`. It never runs `run` inline: outside every boundary it is admitted before `invoke()` returns; inside an open Transaction, `batch`, running turn or pending Scope it waits for every one of those gates. Made inside a Transaction (or a `ctx.commit`) it belongs to that frame whatever its owner's state: a revert cancels it with `transaction-reverted`, a commit hands it to the next gate. On a disposed Operation it returns an invocation already `cancelled('owner-disposed')`. For `TInput = void` it takes no argument |
| `status` | A native `Source<OperationStatus<TResult>>`: `{phase: 'idle' \| 'running', running, queued, last}`. Usable as a Derived or Effect input and with `useStore`. Written only by the runtime, never inside a Transaction |
| `dispose()` | Cancels every invocation with `owner-disposed`, aborts running signals and freezes the status |

**Policies.** `latest-wins`: a new admission supersedes the running invocation. `drop-while-running`: a new invocation is `cancelled('dropped')` while one runs. `queue`: FIFO, one at a time. "Latest" is creation order.

**`Invocation<TResult>`**: `id` (per Operation, in creation order), `status` (`'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'superseded'`), `outcome` (a promise that never rejects), `cancel()` (`requested`; no effect once ended; rejected inside an open Transaction).

**`Outcome<TResult>`**: `{status: 'succeeded', value}`, `{status: 'failed', error: unknown}` (the original thrown or rejected value), `{status: 'cancelled', reason: CancelReason}` or `{status: 'superseded', by: InvocationId}`. `CancelReason` is `'requested' | 'owner-disposed' | 'dropped' | 'transaction-reverted'`. Cancellation and supersession are outcomes, not failures.

**`OperationContext`** (the `ctx` of `run`):

- `signal`: an `AbortSignal`, aborted when the invocation is cancelled or superseded.
- `invocation`: its `InvocationId`.
- `commit(fn)`: runs `fn` as an atomic commit authorized for this invocation. Result, in this order: not current at entry → `fn` does not run, `false`; currency lost during the commit → everything reverted, `false`; otherwise a failure inside it (also one user code caught) → everything reverted and that first failure rethrown; otherwise committed, `true`. Cancelling or disposing from inside a commit is rejected and reverts it. A commit inside another open Transaction throws `CommitNestingError`. A callback that visibly returns a promise does not compile.
- `isCurrent()`: whether a commit would be admitted now. Not a substitute for `commit`.

**The stale-write guarantee covers `ctx.commit` only.** A direct Store write from the async body after an `await` is unsupported and unprotected; nothing can attribute it to the invocation. Late settlements after a terminal outcome are absorbed, never unhandled.

---

## `batch(fn)` and `transaction(fn)`

```ts
function batch<T>(fn: () => NotThenable<T>): T;
function transaction<T>(fn: () => NotThenable<T>): T;
type NotThenable<T> = T extends PromiseLike<unknown> ? never : T;
```

**`batch`** groups publication only. Writes inside are accepted and readable at once; nothing is published until the outermost `batch` (or open Transaction) closes, and then synchronously, before `batch` returns. **It never reverts anything**: if `fn` throws, its writes survive, are published, and then the error is rethrown. Nested batches deepen the grouping.

**`transaction`** is synchronous, local and all-or-nothing:

- Writes happen in place, so reads inside see them, for Stores and Deriveds alike.
- A nested `transaction` joins the open one: one frame, no savepoints.
- The frame fails when a failure crosses an active-state boundary inside it (a nested `transaction` or `batch` that throws, a throwing updater or equality, a failing Derived read, a rejected call), also when user code catches it; `fn` throwing fails it too. A plain throw caught entirely inside user code does not.
- A failed frame restores every Store and Derived cache it touched, publishes nothing of its own work (whatever the equality) and rethrows its first failure. A committed frame publishes when the outermost hold closes, before the outermost call returns.
- Structural and lifecycle calls are rejected inside it. `invoke()` is deferred: admitted after commit (or, for an Operation whose Scope is pending, after that Scope's activation), `cancelled('transaction-reverted')` after a revert, whatever its owner's state.
- Only local state is reverted; network requests, DOM changes or copies made to other variables are not.

**Nesting.** A Transaction inside a `batch` is atomic on its own; the batch's earlier writes survive its failure. A `batch` inside a Transaction shares its publication, and an error escaping the batch fails the Transaction.

**Synchronous only.** A callback that returns a thenable is refused with a `TypeError` (the Transaction reverts; a `batch` publishes its writes first), and a rejection handler is attached so nothing becomes unhandled. `NotThenable` catches direct async callbacks at compile time; it cannot see through `unknown`, `void`, wide types or generic callbacks, and the runtime check is the guarantee.

---

## `shallow(a, b)`

Shallow equality over an explicit set of value families: `Object.is`; arrays (same length, same holes, element-wise `Object.is`); `Map`s and `Set`s; `Date`s by timestamp; `URL`s by `href`; two objects with the same prototype and the same own keys (strings and symbols, enumerable or not) with `Object.is` values; two empty plain objects. Everything else is **not** equal: an instance that owns no property (a `Promise`, a `WeakMap`, a class keeping its state in private fields or prototype getters) is never called equal to another instance. Symmetric for deterministic values; reads own properties, so an own getter or proxy trap runs and may throw. It compares one level deep. It is the default equality of `useStore` and the usual `equal` for an object- or array-valued Store.

---

## Errors

All are `Error` subclasses with a matching `name`.

| Error | Thrown when |
| --- | --- |
| `PurityViolation` | A mutation or lifecycle call from a pure context (`call: GuardedCall` names it) |
| `StructuralMutationInTransaction` | A structural or lifecycle call inside an open Transaction (`call` names it); it also fails that Transaction |
| `LifecycleViolation` | A lifecycle call the lifetime model forbids, such as activating a non-root pending Scope |
| `DisposedResourceError` | Reading a Derived that was disposed before it ever produced a value |
| `DependencyCycleError` | A Derived is demanded during its own demand (a compute or equality reads it through a closure) |
| `CommitNestingError` | `ctx.commit` inside another open Transaction or commit |
| `CommitRevoked` | A Store write inside a commit whose invocation lost its authority |
| `FeedbackLimitExceeded` | Reported to the error sink when a turn exhausts its budget of 100 steps; the remaining work is suspended, not dropped, and resumes at the next publication boundary |

**Where failures go.** Failures of your own call (an updater, an acceptance equality, a failing read, a `batch`/`transaction` callback, a rejected call) reach you synchronously. Failures without a synchronous caller (subscribers, Effect runs and cleanups, settlement, publication decisions, the budget) are collected per turn and reported once when the turn ends: one error as itself, several as an `AggregateError`, to the error sink. The default sink calls `globalThis.reportError` where available and otherwise rethrows in a microtask. They do not reach React Error Boundaries. Tests replace the sink through [`/testing`](#the-testing-entry-testing).

---

## `useStore(readable, selector?, options?)` — `/react`

```ts
function useStore<T>(
  readable: ReadableStore<T>,
  selector?: undefined,
  options?: UseStoreOptions<T>,
): T;
function useStore<T, S>(
  readable: ReadableStore<T>,
  selector: (state: T) => S,
  options?: UseStoreOptions<S>,
): S;
interface UseStoreOptions<S> {
  readonly equal?: (a: S, b: S) => boolean;
} // default shallow
```

- `useSyncExternalStore` and nothing more: the snapshot is `readable.state` read during render; React subscribes after commit and releases on cleanup. Subscribing to a Derived activates it; the last release deactivates it. Passing a different readable replaces the subscription.
- **Selection cache.** A retained selection is reused only for the same readable and the same equality function; it is returned without re-selecting only when the selector and the snapshot are identical too, and kept by reference when `equal` calls the new selection equal. An inline selector that builds a new object every render does not loop.
- The selector and `equal` run during render and must be pure.
- The server snapshot is the same getter; it is consistent only where the host supplies the same per-request initial data to server and client. No Provider or context is read.

---

## `useScoped(factory)` — `/react`, EXPERIMENTAL

```ts
type ScopedFactory<T> = (scope: Scope) => T;
function useScoped<T>(factory: ScopedFactory<T>): T;
```

Owns a model for the committed lifetime of a component. **Experimental**: exported after its exit gate passed (EXP-01 … EXP-08, see `ARCHITECTURE.md` § 11) and the export was approved (R14, T8-1). It has one production consumer and stays experimental: its shape and behaviour may still change with evidence from further use.

**Supported host (the evidence).** React 19.3.0 with react-dom 19.3.0 (`createRoot`, `hydrateRoot`), development and production builds, in Chromium (through `playwright-core`) and in jsdom. Other React versions are not claimed.

**Lifecycle contract.**

- **Render.** The first render calls `factory` with a pending Scope. A render that is discarded, or rendered twice in development, builds a model that never starts anything and that nothing references.
- **Commit.** The model is activated in the layout phase of the commit that keeps it, before any passive subscription of that commit.
- **End.** When React tears the component's effects down (unmount, or a hidden `Activity`), the component's subscriptions are released at once and it does not render again for the model. The model itself is disposed at the next microtask checkpoint; from then on its Effects are stopped, its invocations end `cancelled('owner-disposed')` and later commits are refused. A development `StrictMode` rehearsal sets the effects up again before that checkpoint and keeps the model. A tree hidden by a re-suspending Suspense boundary keeps its model.
- **The grace before the disposal is not side-effect-free.** Until the checkpoint the model's Scope is still `active`: a continuation queued ahead of it still runs and may commit, and the model's own Effects react to that commit, including any external work they do. Only the disposal ends the model's authority.
- **Identity.** One model per component lifetime; new props do not rebuild it, so a consumer that needs a new model remounts with a `key`. The one exception is a component whose model was disposed while React kept its state (a hidden `Activity` shown again): it gets a new model from the latest factory; the old one stays disposed.
- **After the end.** A disposed model's lifetime is over, whether it ended with its component or was replaced: its Operations never regain commit authority, and its subscriptions and Effects are released. A command still invoked on it, such as a handler that captured it, obeys the disposed-resource rules of [`Scope`](#scope): writes are ignored and invocations end cancelled, but reading a model Derived that was never evaluated throws `DisposedResourceError` (R12). Consumers avoid invoking commands on an ended model, for example by first checking that its Scope is still `active`.
- **A failing activation** reaches the nearest error boundary; the model stays pending and ends with the component.

**Factory obligations.** The factory runs during render and must only construct resources in the Scope it receives: Stores, Deriveds, Effects, Operations, child Scopes, and invocations of its own Operations. It must not fetch, subscribe to an outside source directly, write outside state or touch the DOM; that work belongs in an Effect or Operation of the model. A factory that disposes its Scope gets a `LifecycleViolation`.

**Known limits.**

- The absence of React subscribers is no guard. A side effect that must not outlive the component bases its decision on the model's ownership where it acts: an outcome that `succeeded` before unmount stays `succeeded`, so a side effect after an awaited outcome, such as navigation, checks at that point that the model's Scope is still `active`; during the grace the Scope still reports `active`.
- A settlement failure of an observed Derived is not a React update; it surfaces through the error sink, and in the component at its next render.

---

## The testing entry: `/testing`

Test seams that work on the core without React, and the internal, **unstable** instrumentation protocol. Not for production code paths. No export gives access to a revision, depth, edge list or private member.

| Export | Purpose |
| --- | --- |
| `captureErrors(): ErrorCapture` | Installs a capturing error sink. `take()` returns the captured errors and marks them asserted; `release()` restores the previous sink and throws `UnassertedErrors` if a captured error was never taken |
| `replaceErrorSink(sink)` | Replaces the sink until the returned function is called (prefer `captureErrors`) |
| `record(): Recorder` | Records the instrumentation stream until `detach()`: `events`, `ofType(type)`, `about(resource)`, `idOf(resource)` |
| `publicationsByRound(events)` | The published resources per round, by label, sorted within a round, for read/no-read comparisons |
| `inspectScope`, `inspectStore`, `inspectDerived`, `inspectEffect`, `inspectOperation` | Plain descriptions: state, live members by kind, subscription and observer counts, lifecycle state, live invocations |
| `observerCount(source)`, `waitingCount(derived)` | Direct observers of a native source; acquisitions a pending barrier holds |
| `inspectCoordinator()`, `resumeSuspended()` | Pending work, queued admissions and the suspended backlog; resume a backlog deterministically |
| `controlledRun(options?)` | A `run` whose invocations are held until the test resolves or rejects each by id; exposes started ids, inputs, contexts, abort state, and prefix and abort-listener injection |
| `forceCancellation(invocation)` | Cancels as an unexpected terminal transition would, bypassing the guards, to exercise commit revocation |
| Types: `ErrorCapture`, `Recorder`, `ScopeInspection`, `ControlledRun`, `ControlledRunOptions`, `InstrumentationEvent` | — |

**Instrumentation events** carry a sequence number, a type from a closed set, detached resource and Scope references (identifiers and labels), a cause chain (`direct`, `batch`, `transaction`, `operation`, `effect`, `subscriber`, `disposal`, each with its `parent`) and value-free detail. They never contain state values, diffs, error objects, stacks or timings. Without a listener nothing is produced. Misuse diagnostics are events (`write.ignored`, `derived.undeclared-read`, `lifetime.inversion`, `turn.suspended`); the runtime itself never logs, warns or reads `process.env`. A development or test setup that wants warnings attaches a listener through `record()`.

---

## What is proved, and what is not

- **Core and `useStore`**: Node/jsdom behaviour tests under `src/**/__tests__/`, including a real `React.StrictMode` wrapper, a suspended transition, `hydrateRoot` with matching data, server rendering and readable replacement. The tests are the executable specification of this document.
- **`useScoped`**: the eight exit criteria in jsdom and in Chromium on the host stated above.
- **Not claimed**: other React versions, React Compiler compatibility, serialization or hydration of state (the host supplies consistent data), and every browser beyond Chromium.

## All exports, from the source

### `@workspace/active-state`

**batch**: `<T>(fn: () => NotThenable<T>) => T`

Groups publication: writes made inside `fn` are accepted and readable at
once, and nothing is published until the outermost `batch` (or open
Transaction) closes. Closing the outermost hold publishes synchronously,
before `batch` returns or rethrows. `batch` never reverts anything: if `fn`
throws, its writes survive and are published, then the error is rethrown.
Inside an open Transaction, an error escaping `fn` fails that Transaction.
Returns `fn`'s value.

**transaction**: `<T>(fn: () => NotThenable<T>) => T`

Runs `fn` atomically. Writes are made in place, so reads inside see them
(Stores and Deriveds alike), and nothing is published while it runs. A
nested `transaction` joins the open one: there is one frame and no
savepoint. The frame fails when a failure crosses an active-state boundary
inside it (a nested `transaction` or `batch` that throws, a throwing
updater or equality, a failing Derived read, a rejected call), also when
user code catches it; `fn` throwing fails it too. A failed frame restores
every Store and Derived cache it touched, publishes nothing of its own work
and rethrows its first failure; a committed one publishes when the outermost
hold closes, before the outermost call returns. Structural and lifecycle
calls are rejected inside it. Returns `fn`'s value.

**shallow**: `<T>(objA: T, objB: T) => boolean`

Shallow equality over an explicit set of value families.

Equal when:
- `Object.is` already says so (identity, `NaN`, and every primitive);
- both are arrays with the same length, the same holes, and element-wise
`Object.is` values;
- both are `Map`s with the same size, the same keys, and `Object.is` values;
- both are `Set`s with the same size and the same members;
- both are `Date`s with the same timestamp;
- both are `URL`s with the same `href`;
- both share a prototype and have the same own property keys — strings and
symbols, enumerable or not — with `Object.is` values;
- both are empty plain objects (prototype `Object.prototype` or `null`).

Everything else is NOT equal, and deliberately so. An instance that owns no
property — a `Promise`, a `WeakMap`, a class that keeps its state in private
fields or exposes it only through prototype getters — is opaque: two
distinct opaque instances are never called equal, because nothing on them
can show that they are. Prototype getters are not read, and two objects with
different prototypes are never equal however well their keys line up. This
comparator decides whether a component re-renders; an invented equality
hides a state change.

Every rule compares both values the same way, so for deterministic values
`shallow(a, b) === shallow(b, a)`. A getter or proxy trap whose answer
changes between reads is outside that promise.

Comparison reads own properties, so an own getter or a proxy trap runs, and
one that throws reaches the caller. That is deliberate too: absorbing the
failure would answer a question this function could not answer.

Nothing is compared more than one level deep: nested objects and arrays are
compared by identity.

**Scope**

```ts
class Scope {
  constructor(options?: ScopeOptions);
  get state(): ScopeState;
  activate(): void;
  dispose(): void;
  onDispose(disposer: () => void): () => void;
}
```

Owns the lifetime of the resources registered with it. Disposal is
complete, ordered (reverse registration, a child Scope torn down at its own
position) and idempotent. A pending Scope strongly owns its pending
children, but its model root is not referenced from outside until it is
activated.

**Operation**

```ts
class Operation<TInput = void, TResult = unknown> {
  readonly status: Source<OperationStatus<TResult>>;
  constructor(options: OperationOptions<TInput, TResult>);
  invoke(...args: [
    TInput
  ] extends [
    void
  ] ? [
  ] : [
    input: TInput
  ]): Invocation<TResult>;
  dispose(): void;
}
```

Client-owned work in flight, with identity, an owner, a status and a
concurrency policy. Each `invoke()` creates an invocation that is admitted
by the runtime, never started inline: behind an open Transaction it waits
for the commit (and is cancelled `transaction-reverted` on a revert), behind
a `batch` for its publication boundary, in a pending Scope for the model's
activation. A pending owner delays the start only: an invocation made
inside a Transaction that reverts is cancelled whatever its owner's state.
Admission applies the policy and starts the run.

A stale invocation cannot change newer state through `ctx.commit`: a commit
takes effect only while its invocation is current for the whole commit.
Disposing the owning Scope (or the Operation) cancels every invocation with
`owner-disposed`, aborts running signals and freezes the status.

**Derived**

```ts
class Derived<T, const I extends readonly Source<unknown>[] = readonly Source<unknown>[]> extends Source<T> {
  constructor(options: DerivedOptions<T, I>);
  get state(): T;
  subscribe(listener: Listener<T>): Unsubscribe;
  dispose(): void;
}
```

Computed state over explicit inputs. Construction computes nothing; the
first read or observation does. A read always returns the value for the
current inputs, observed or not. While observed (subscribed, or an input of
an observed Derived) it is maintained by write-time invalidation and
publishes when its settled value differs from its last published value
under `equal`; reading it never creates or consumes a publication.

**Effect**

```ts
class Effect<const I extends readonly Source<unknown>[] = readonly Source<unknown>[]> {
  constructor(options: EffectOptions<I>);
  dispose(): void;
}
```

An owned, lifecycle-bound reaction. It has no value and publishes nothing:
it observes its explicit inputs directly and runs in the reaction phase of
a round in which one of them published, after every subscriber of that
round, with the complete tuple of its input values captured before the
round's first callback. A round in which an input failed to settle or to
be decided does not run it.

Runs never nest: a write made by a run is published in a later round. Each
run gets a fresh run Scope; a run ends exactly once, in one order, however
it ends: its cleanup, then its run Scope's disposal, and when the Effect
stops, the release of its inputs. The Effect is the only owner of that
order; its Scope's disposal reaches the run Scope through the Effect.

Constructed in an active Scope it starts at once (it observes its inputs);
in a pending Scope it starts when the model is activated. Construction
never runs the Effect: an immediate Effect's initial run waits for the
first publication boundary after the start. Construction is rejected in a
pure context and inside an open Transaction.

**Store**

```ts
class Store<T> extends Source<T> {
  constructor(initial: T, options?: StoreOptions<T>);
  get state(): T;
  readonly setState: (next: T) => void;
  readonly update: (fn: (current: T) => T) => void;
  subscribe(listener: Listener<T>): Unsubscribe;
  asReadonly(): Source<T>;
  dispose(): void;
}
```

The writable state authority. Its value slot is the only authority for its
value: a write is accepted and stored before the call returns, and no user
callback runs between acceptance and storage.

No default equality is applied: whether a Store without `equal` should get
one is an open product decision, so today every write without `equal` is
accepted and published.

**CommitNestingError**

```ts
class CommitNestingError extends Error {
  constructor();
}
```

Thrown when an Operation commit is attempted while another atomic frame is
open: a commit owns its own authorized frame and never joins someone
else's. The rejection fails that open frame.

**CommitRevoked**

```ts
class CommitRevoked extends Error {
  constructor();
}
```

Thrown by a Store write inside a commit whose invocation lost its authority
while the commit was open. Nothing of such a commit survives.

**DependencyCycleError**

```ts
class DependencyCycleError extends Error {
  constructor();
}
```

Thrown when a Derived is demanded while its own demand is still in
progress: its compute or equality reads it again through a closure. Declared
inputs cannot form a cycle, because every input exists before its dependent.

**DisposedResourceError**

```ts
class DisposedResourceError extends Error {
  constructor(message: string);
}
```

Thrown by a read that would need a value a disposed resource never had: a
Derived disposed before its first evaluation cannot compute after disposal.

**FeedbackLimitExceeded**

```ts
class FeedbackLimitExceeded extends Error {
  readonly steps: number;
  constructor(steps: number);
}
```

Reported to the error sink when a turn exhausts its step budget. The work
that was still pending is retained as the suspended backlog, not dropped.

**LifecycleViolation**

```ts
class LifecycleViolation extends Error {
  constructor(message: string);
}
```

Thrown when a lifecycle call is made on a resource whose lifetime model
does not allow it, such as activating a pending Scope that is not the root
of its pending model.

**PurityViolation**

```ts
class PurityViolation extends Error {
  readonly call: GuardedCall;
  constructor(call: GuardedCall);
}
```

Thrown when an active-state mutation or lifecycle call is made from a pure
context: a Store updater, any equality function, or an instrumentation
listener. The rejected call changes nothing.

**StructuralMutationInTransaction**

```ts
class StructuralMutationInTransaction extends Error {
  readonly call: GuardedCall;
  constructor(call: GuardedCall);
}
```

Thrown when a structural or lifecycle call is made inside an open
Transaction: subscribing or releasing, disposing, activating, registering a
disposer, or constructing an owned resource. A Transaction reverts local
state only, so it never has to restore lifetime or observation topology; the
rejected call changes nothing and fails the Transaction.

**NotThenable**

```ts
type NotThenable<T> = T extends PromiseLike<unknown> ? never : T;
```

A callback result the compiler can see is not a promise or other thenable.
It cannot see through `unknown`, `void`, a wide declared type or a generic
callback; the runtime check of `batch` and `transaction` is the guarantee.

**Listener**

```ts
type Listener<T> = (value: T, previous: T) => void;
```

A subscriber receives the captured transition of its round.

**ReadableStore**

```ts
interface ReadableStore<T> {
  readonly state: T;
  subscribe(listener: Listener<T>): Unsubscribe;
}
```

The smallest readable shape: the current value plus a synchronous
subscription with an idempotent release. Every native source satisfies it;
a value that merely satisfies it is readable (by the React adapter, for
instance) but is not a native source and cannot be a Derived input.

**Source**

```ts
abstract class Source<out T> {
  protected constructor();
  abstract get state(): T;
  abstract subscribe(listener: Listener<T>): Unsubscribe;
}
```

A native graph source: a readable the runtime created itself (a Store, its
readonly projection, a Derived). The graph record is private; whether a
value is a native source is decided by that private field, never by its
structural shape, so a cast, an impostor or an object that merely inherits
from a source is not one. Covariant: a source of a narrower value is a
source of a wider one.

**Unsubscribe**

```ts
type Unsubscribe = () => void;
```

Releases one subscription. Idempotent.

**ScopeOptions**

```ts
interface ScopeOptions {
  readonly parent?: Scope;
  readonly pending?: boolean;
  readonly label?: string;
}
```

**ScopeState**

```ts
type ScopeState = 'pending' | 'active' | 'disposing' | 'disposed';
```

**CancelReason**

```ts
type CancelReason =
  'requested' | 'owner-disposed' | 'dropped' | 'transaction-reverted';
```

Why an invocation was cancelled: its handle asked for it, its owner was
disposed, the policy dropped it while another one ran, or the atomic frame it
was created in reverted.

**Invocation**

```ts
interface Invocation<TResult> {
  readonly id: InvocationId;
  readonly status: InvocationStatus;
  readonly outcome: Promise<Outcome<TResult>>;
  cancel(): void;
}
```

One call of an Operation: inspection and control.

**InvocationId**

```ts
type InvocationId = number;
```

Identifies an invocation within its Operation, in creation order.

**InvocationStatus**

```ts
type InvocationStatus =
  'queued' | 'running' | Outcome<unknown>['status'];
```

The lifecycle of an invocation: `queued` until it is admitted and its run
starts, `running` until its run settles, then exactly one terminal outcome.

**OperationContext**

```ts
interface OperationContext {
  readonly signal: AbortSignal;
  readonly invocation: InvocationId;
  commit<R>(fn: () => NotThenable<R>): boolean;
  isCurrent(): boolean;
}
```

What a run receives besides its input.

**OperationOptions**

```ts
interface OperationOptions<TInput, TResult> {
  readonly scope: Scope;
  readonly policy: Policy;
  readonly run: (
    input: TInput,
    ctx: OperationContext,
  ) => PromiseLike<TResult> | TResult;
  readonly label?: string;
}
```

**OperationStatus**

```ts
interface OperationStatus<TResult> {
  readonly phase: 'idle' | 'running';
  readonly running: InvocationId | null;
  readonly queued: number;
  readonly last: Outcome<TResult> | null;
}
```

The status of an Operation's policy: whether an invocation runs, how many
wait in its queue, and the outcome of the last invocation that reached
admission. Invocations still waiting for their gate (an open Transaction or
`batch`, a pending owner) are not part of it.

**Outcome**

```ts
type Outcome<TResult> =
  | {readonly status: 'succeeded'; readonly value: TResult}
  | {readonly status: 'failed'; readonly error: unknown}
  | {readonly status: 'cancelled'; readonly reason: CancelReason}
  | {readonly status: 'superseded'; readonly by: InvocationId};
```

How an invocation ended. It never rejects: cancellation and supersession are
outcomes, not failures, and a failure carries the original thrown or
rejected value, unchanged.

**Policy**

```ts
type Policy = 'latest-wins' | 'drop-while-running' | 'queue';
```

How an Operation treats a new invocation while another one is running:
`latest-wins` supersedes the running one, `drop-while-running` cancels the
new one (`dropped`), `queue` runs them one at a time in creation order.

**DerivedOptions**

```ts
interface DerivedOptions<T, I extends readonly Source<unknown>[]> {
  readonly inputs: I;
  readonly compute: (...values: ValuesOf<I>) => T;
  readonly equal?: (a: T, b: T) => boolean;
  readonly label?: string;
  readonly scope?: Scope;
}
```

**ValuesOf**

```ts
type ValuesOf<I extends readonly Source<unknown>[]> = {
  readonly [K in keyof I]: I[K] extends Source<infer V> ? V : never;
};
```

The value types of an input list, position by position.

**EffectContext**

```ts
interface EffectContext {
  readonly scope: Scope;
}
```

What one run of an Effect receives besides its input values.

**EffectOptions**

```ts
interface EffectOptions<I extends readonly Source<unknown>[]> {
  readonly scope: Scope;
  readonly inputs: I;
  readonly run: (
    values: ValuesOf<I>,
    ctx: EffectContext,
  ) => void | (() => void);
  readonly immediate?: boolean;
  readonly label?: string;
}
```

**StoreOptions**

```ts
interface StoreOptions<T> {
  readonly equal?: (a: NoInfer<T>, b: NoInfer<T>) => boolean;
  readonly label?: string;
  readonly scope?: Scope;
}
```

**GuardedCall**

```ts
type GuardedCall =
  | 'setState'
  | 'update'
  | 'subscribe'
  | 'unsubscribe'
  | 'dispose'
  | 'construct'
  | 'activate'
  | 'onDispose'
  | 'resume'
  | 'batch'
  | 'transaction'
  | 'invoke'
  | 'cancel'
  | 'commit';
```

The active-state operations that change state or lifetime. A guarded
entry point names itself with one of these when it rejects a call.

### `@workspace/active-state/react`

The root entry again, plus:

**useScoped**: `<T>(factory: ScopedFactory<T>) => T`

EXPERIMENTAL. Owns a model for the committed lifetime of a component.

- **Render.** The first render calls `factory` with a pending Scope. A
render that is discarded, or rendered twice in development, builds a model
that never starts anything and is referenced by nothing.
- **Commit.** The model is activated in the layout phase of the commit that
keeps it, before any passive subscription of that commit, so a component
that reads a model Derived subscribes to an active Derived.
- **End of the lifetime.** When React tears the component's effects down
(unmount, or a hidden `Activity`), the component's subscriptions are
released at once and it does not render again for the model. The model
itself is disposed at the next microtask checkpoint. Until then its Scope
is still `active`: a continuation queued ahead of the checkpoint still
runs and may commit, and the model's own Effects react to that commit,
side effects included. The grace is not side-effect-free. From the
checkpoint on its Effects are stopped, its invocations end
`cancelled('owner-disposed')` and no later commit takes effect. A
development `StrictMode` rehearsal sets the effects up again before that
checkpoint, which cancels the disposal. A tree hidden by a re-suspending
Suspense boundary keeps its passive effects, so its model stays active.
- **Replacement.** A model is never recreated to follow new props: the
factory is read once per model, and a consumer that needs a new model
remounts the component with a `key`. The one exception is a component
whose model was disposed while React kept its state (a hidden `Activity`
that is shown again): it gets a new model from the latest factory; the old
one stays disposed.
- **After the end.** A disposed model's lifetime is over, whether it ended
with its component or was replaced: its Operations never regain commit
authority, and its subscriptions and Effects are released. A command
still invoked on it, such as a handler that captured it, obeys the
disposed-resource rules: writes are ignored and invocations end
cancelled, but reading a model Derived that was never evaluated throws
`DisposedResourceError`. Consumers avoid invoking commands on an ended
model, for example by first checking that its Scope is still `active`.

Nothing stops a factory from performing untracked side effects; see
`ScopedFactory`. The absence of React subscribers is no guard: an external
side effect that must not outlive the component, such as navigation after
an awaited outcome, decides on the model's ownership where it acts (its
Scope still `active`, the invocation's outcome), knowing that the Scope
still reports `active` during the grace before the disposal checkpoint.

**useStore**: `{ <T>(readable: ReadableStore<T>, selector?: undefined, options?: UseStoreOptions<T>): T; <T, S>(readable: ReadableStore<T>, selector: (state: NoInfer<T>) => S, options?: UseStoreOptions<S>): S; }`

Reads any readable (a Store, its readonly projection, a Derived, an
Operation status, or any value that satisfies `ReadableStore`) and
re-renders the component when a published transition changes the selection.

It is React's external-store subscription and nothing more: the snapshot is
`readable.state`, read during render, so a render always sees the current
value; the subscription is taken by React after commit and released by
React's cleanup (subscribing to a Derived activates it, the last release
deactivates it); a different readable replaces the subscription. The
runtime decides what is published and when, so a `batch`, a Transaction and
an Operation commit reach the component only through their publication, and
a reverted Transaction never does. No Provider or context is read.

The server snapshot is the same getter: it is consistent only where the host
supplies the same per-request initial data to the server and the client.

**ScopedFactory**

```ts
type ScopedFactory<T> = (scope: Scope) => T;
```

Builds a model inside the Scope it is given. It runs while React renders,
possibly for a render that never commits and possibly twice, so it must only
construct resources: Stores, Deriveds, Effects, Operations and child Scopes
in that Scope, and invocations of its own Operations. While the model is
pending none of them starts or observes anything outside it. What the
runtime cannot see is the factory's own obligation: it must not fetch,
subscribe to an outside source directly, write outside state or touch the
DOM; such work belongs in an Effect or an Operation of the model.

**UseStoreOptions**

```ts
interface UseStoreOptions<S> {
  readonly equal?: (a: S, b: S) => boolean;
}
```

### `@workspace/active-state/testing`

**replaceErrorSink**: `(sink: ErrorSink) => () => void`

Replaces the error sink until the returned function is called. Prefer
`captureErrors`, which also fails on unasserted errors.

**UnassertedErrors**

```ts
class UnassertedErrors extends AggregateError {
  constructor(errors: readonly unknown[]);
}
```

Thrown by `ErrorCapture.release` when captured errors were never taken.

**captureErrors**: `() => ErrorCapture`

Installs a capturing sink. Reports are delivered synchronously when a turn
ends, so what a scenario caused is capturable directly after it ran.

**publicationsByRound**: `(events: readonly InstrumentationEvent[]) => string[][]`

The resources published per round, by label (or kind when unlabelled),
sorted within each round: order between the resources of one round is not
promised. Two recordings of the same command with and without intermediate
reads must give the same summary.

**record**: `() => Recorder`

Records the instrumentation stream until detached.

**inspectScope**: `(scope: Scope) => ScopeInspection`

**inspectStore**: `<T>(store: Store<T>) => StoreDescription`

**inspectDerived**: `<T>(derived: Derived<T>) => DerivedDescription`

**inspectEffect**: `<I extends readonly Source<unknown>[]>(effect: Effect<I>) => EffectDescription`

The lifecycle of an Effect (`created`, `started`, `stopping`, `stopped`)
and whether it holds the Scope of a run that has not ended. No input, run
record or cleanup is exposed.

**waitingCount**: `<T>(derived: Derived<T>) => number`

How many refused observation acquisitions `derived` holds while its pending
model has not activated. A plain count; no dependent or edge is exposed.

**observerCount**: `(source: Source<unknown>) => number`

The direct observers of a native source: its live subscriptions (for a
Derived, those whose observation is not deferred) plus the Deriveds that
observe it as an input. A plain count; no edge is exposed.

**inspectCoordinator**: `() => CoordinatorSnapshot`

Pending work, queued admissions and the suspended backlog, if any.

**resumeSuspended**: `() => void`

Resumes a suspended backlog deterministically, as the next boundary would.

**inspectOperation**: `<TInput, TResult>(operation: Operation<TInput, TResult>) => OperationDescription`

Whether an Operation is disposed and how many of its invocations have not
ended. Plain counts; no invocation record is exposed.

**forceCancellation**: `<TResult>(invocation: Invocation<TResult>) => void`

Cancels `invocation` as an unexpected terminal transition would, bypassing
the call guards that make that impossible in ordinary use. Called inside the
invocation's own commit, it exercises the commit's revocation, which the
guards otherwise keep unreachable.

**controlledRun**: `<TInput = void, TResult = unknown>(options?: ControlledRunOptions<TInput>) => ControlledRun<TInput, TResult>`

**InstrumentationEvent**

```ts
interface InstrumentationEvent {
  readonly seq: number;
  readonly type: EventType;
  readonly resource?: ResourceRef;
  readonly scope?: ScopeRef;
  readonly cause: Cause;
  readonly suppressed?: number;
  readonly detail?: EventDetail;
}
```

**ErrorCapture**

```ts
interface ErrorCapture {
  take(): unknown[];
  release(): void;
}
```

**Recorder**

```ts
interface Recorder {
  readonly events: readonly InstrumentationEvent[];
  ofType(type: EventType): InstrumentationEvent[];
  about(resource: object): InstrumentationEvent[];
  idOf(resource: object): number;
  detach(): void;
}
```

**ScopeInspection**

```ts
interface ScopeInspection {
  readonly state: ScopeState;
  readonly members: readonly MemberDescription[];
}
```

**ControlledRunOptions**

```ts
interface ControlledRunOptions<TInput> {
  readonly prefix?: (input: TInput, ctx: OperationContext) => void;
  readonly onAbort?: (id: InvocationId, ctx: OperationContext) => void;
}
```

**ControlledRun**

```ts
interface ControlledRun<TInput, TResult> {
  readonly run: (input: TInput, ctx: OperationContext) => Promise<TResult>;
  readonly started: readonly InvocationId[];
  input(id: InvocationId): TInput;
  context(id: InvocationId): OperationContext;
  aborted(id: InvocationId): boolean;
  resolve(id: InvocationId, value: TResult): void;
  reject(id: InvocationId, reason: unknown): void;
}
```

A run whose invocations are held until the test resolves or rejects each
one by its identifier, in any order. It records what started and whether
each signal was aborted, and lets the test reach every run's context.
Identifiers are per Operation, so one controlled run serves one Operation.
