# AtomRegistry

The `AtomRegistry` is the runtime cache and evaluator behind [atoms](https://effect.plants.sh/reactivity/atom/).
A registry owns the **node graph** for a group of atoms: it stores current values,
records parent/child dependencies while atoms are read, and coordinates writes,
refreshes, subscriptions, stream conversions, and node disposal.

An [`Atom`](https://effect.plants.sh/reactivity/atom/) is just a *recipe*. It holds no value of its own — the
same atom object can have a different cached value in every registry it is read from.
This is the key to isolation: **use a separate registry per UI root, per request, per
test, or per route boundary**, and each gets its own independent state.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)
const doubled = Atom.make((get) => get(count) * 2)

const registry = AtomRegistry.make()

registry.set(count, 21)
registry.get(doubled)
// => 42

// A second registry has completely independent state.
const other = AtomRegistry.make()
other.get(doubled)
// => 0
```

## Mental model

- **Reading** an atom (`registry.get`) creates or reuses a node, evaluates the atom
  when its value is missing or stale, and records any nested atom reads as
  dependencies.
- **Writing** a writable atom (`set` / `modify` / `update`) updates its node through
  the atom's write function, invalidates dependents, and notifies listeners after
  batching settles.
- **Subscriptions** (`subscribe`) and **scoped mounts** (`mount`) keep nodes alive.
  When the last listener and dependent child disappear, non-`keepAlive` atoms are
  removed immediately or after their idle TTL.
- **Effects and streams** started by atoms run with the registry scheduler and are
  finalized when their node is rebuilt, removed, reset, or disposed.
- **Disposing** a registry clears its nodes and makes later atom access an error.
**Gotchas:** - Atom identity matters: a *new* atom object creates a *different* node — unless the
  atom is serializable and uses the same serialization key.
- Unobserved atoms without `keepAlive` can be removed, so later reads may rebuild
  derived values, restart effects/streams, and rerun finalizers. See
  [Invalidation & lifecycle](https://effect.plants.sh/reactivity/invalidation/).
- `subscribe` and the instance `mount` method return **release callbacks** — call them
  when the consumer is done. The exported [`mount`](#mount) helper ties that
  release to an Effect `Scope`.

## Reading and writing from Effect

In real applications you usually provide a registry to your Effect program with a
[`layer`](#layer) and read or write atoms with the module-level converters, which
require the `AtomRegistry` service. Use `Effect.gen` for imperative flows.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"
import { Effect } from "effect"

const count = Atom.make(0)
const doubled = Atom.make((get) => get(count) * 2)

const program = Effect.gen(function* () {
  yield* Atom.set(count, 10)
  const value = yield* Atom.get(doubled)
  return value // => 20
})

// Provide a fresh, isolated registry, disposed when the scope closes.
const result = program.pipe(
  Effect.provide(AtomRegistry.layer),
  Effect.scoped
)
```

When an atom produces an [`AsyncResult`](https://effect.plants.sh/reactivity/atom/) (e.g. it wraps an Effect),
use [`getResult`](#getresult) to await its first non-`Initial` value as a normal
Effect, or [`toStreamResult`](#tostreamresult-effect) to observe successive values as a
`Stream`.

---

## Reference

### `make`

Creates a registry directly. Options preload initial atom values, supply a custom task
scheduler, tune `timeoutResolution` (the bucket size for idle sweeps, default `1000`ms),
and set a `defaultIdleTTL` applied to atoms without their own TTL.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)

const registry = AtomRegistry.make({
  initialValues: [[count, 5]],
  defaultIdleTTL: 30_000 // ms; unobserved atoms linger 30s before removal
})

registry.get(count)
// => 5
```

### `AtomRegistry` (service tag)

The `Context.Service` tag for the active runtime cache. Use it to access the registry
inside an Effect, or as the requirement satisfied by [`layer`](#layer).

```ts
import { AtomRegistry } from "effect/unstable/reactivity"
import { Effect } from "effect"

const nodeCount = Effect.gen(function* () {
  const registry = yield* AtomRegistry.AtomRegistry
  return registry.getNodes().size
})
// Effect<number, never, AtomRegistry>
```

### `layer`

The default layer providing a fresh `AtomRegistry`. The registry is disposed when the
layer's scope is finalized.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"
import { Effect } from "effect"

const counter = Atom.make(0)

const program = Atom.update(counter, (n) => n + 1).pipe(
  Effect.andThen(Atom.get(counter))
)

Effect.runPromise(program.pipe(Effect.provide(AtomRegistry.layer)))
// => 1
```

### `layerOptions`

Like [`layer`](#layer) but lets you configure the underlying [`make`](#make) options
(initial values, scheduler, idle TTL).

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)

const TestRegistry = AtomRegistry.layerOptions({
  initialValues: [[count, 100]],
  defaultIdleTTL: 0 // disable idle removal; combine with keepAlive for full control
})
```

### `isAtomRegistry`

Type guard returning `true` when a value carries the `AtomRegistry` type id.

```ts
import { AtomRegistry } from "effect/unstable/reactivity"

AtomRegistry.isAtomRegistry(AtomRegistry.make()) // => true
AtomRegistry.isAtomRegistry({}) // => false
```

### `TypeId`

The literal type id `"~effect/reactivity/AtomRegistry"` used to brand registries.

```ts
import { AtomRegistry } from "effect/unstable/reactivity"

AtomRegistry.TypeId
// => "~effect/reactivity/AtomRegistry"
```

## The `AtomRegistry` interface

The registry instance exposes synchronous methods for direct, non-Effect use (for
example from imperative UI glue). The same operations are available as Effect-returning
converters described in the [next section](#converters-the-effect-facing-api).

### `registry.get`

Reads an atom's current value, creating or rebuilding its node if needed.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const greeting = Atom.make("hello")
const registry = AtomRegistry.make()

registry.get(greeting)
// => "hello"
```

### `registry.set`

Writes a value to a [`Writable`](https://effect.plants.sh/reactivity/atom/) atom, invalidating its dependents.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)
const registry = AtomRegistry.make()

registry.set(count, 42)
registry.get(count)
// => 42
```

### `registry.modify`

Reads the current value, computes `[returnValue, nextValue]`, writes `nextValue`, and
returns `returnValue`. Useful for an atomic read-then-write.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(10)
const registry = AtomRegistry.make()

const previous = registry.modify(count, (n) => [n, n + 1])
// previous => 10
registry.get(count)
// => 11
```

### `registry.update`

Writes the value returned by a function of the current value.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)
const registry = AtomRegistry.make()

registry.update(count, (n) => n + 5)
registry.get(count)
// => 5
```

### `registry.refresh`

Re-runs an atom's refresh logic (or invalidates it when it has none), forcing
recomputation on the next read — e.g. to re-fetch a derived/async atom.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const id = Atom.make(1)
const user = Atom.make((get) => `user-${get(id)}`)
const registry = AtomRegistry.make()

registry.get(user) // => "user-1"
registry.refresh(user) // marks the node stale; next get recomputes
```

### `registry.subscribe`

Registers a listener for value changes and returns a **release callback**. Pass
`{ immediate: true }` to invoke the listener once with the current value right away.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const count = Atom.make(0)
const registry = AtomRegistry.make()

const unsubscribe = registry.subscribe(
  count,
  (value) => console.log("count:", value),
  { immediate: true }
)
// logs: count: 0

registry.set(count, 1) // logs: count: 1
unsubscribe() // stop listening; node may now be removed
```

### `registry.mount`

Keeps an atom alive with a no-op listener and returns a release callback. Equivalent to
`subscribe(atom, () => {}, { immediate: true })`. Prefer the scoped
[`mount`](#mount) helper inside Effect code.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const expensive = Atom.make((get) => computeOnce())
const registry = AtomRegistry.make()

const release = registry.mount(expensive) // node stays alive
// ...later
release()

function computeOnce() {
  return 1
}
```

### `registry.setSerializable`

Preloads an encoded value for a serializable atom's stable key, so the node hydrates
from it on first read (e.g. SSR/state transfer). The encoded value is decoded by the
matching serializable atom.

```ts
import { AtomRegistry } from "effect/unstable/reactivity"

const registry = AtomRegistry.make()
registry.setSerializable("user/profile", { name: "Ada" })
// Consumed when an atom with serialization key "user/profile" is first read.
```

### `registry.getNodes`

Returns the live `ReadonlyMap` of nodes keyed by atom (or serialization key). Mostly
useful for debugging and devtools.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const a = Atom.make(0)
const registry = AtomRegistry.make()
registry.get(a)

registry.getNodes().size
// => 1
```

### `registry.reset`

Removes every node, running their finalizers and clearing idle timers. The registry
remains usable; subsequent reads rebuild from scratch.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const a = Atom.make(0)
const registry = AtomRegistry.make()
registry.set(a, 9)
registry.reset()
registry.get(a)
// => 0 (rebuilt)
```

### `registry.dispose`

Resets the registry and marks it disposed: any later atom access throws. Use a fresh
registry when a whole lifetime should start from empty state. Provided automatically by
[`layer`](#layer) on scope close.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const a = Atom.make(0)
const registry = AtomRegistry.make()
registry.dispose()
// registry.get(a) // throws: registry is disposed
```

### `registry.scheduler` / `registry.schedulerAsync`

The `Scheduler` instances driving synchronous and asynchronous tasks (effect execution,
deferred node removal). You rarely touch these directly; they exist for integration and
advanced control.

```ts
import { AtomRegistry } from "effect/unstable/reactivity"

const registry = AtomRegistry.make()
registry.scheduler // Scheduler (sync)
registry.schedulerAsync // Scheduler (async)
```

### `registry.onNodeAdded` / `registry.onNodeRemoved`

Optional callbacks invoked when a node is added to or removed from the registry. Handy
for devtools, logging, or leak detection.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const registry = AtomRegistry.make()
registry.onNodeAdded = (node) => console.log("added", node.currentState())
registry.onNodeRemoved = (node) => console.log("removed")

registry.get(Atom.make(0)) // logs: added uninitialized (the node fires before its first read)
```

### `Node<A>`

A registry node for a single atom. It exposes the originating `atom`, a `value()`
accessor, the `parents`/`children` dependency links, the `listeners` set, and a
`currentState()` returning `"uninitialized" | "stale" | "valid" | "removed"`.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"

const a = Atom.make(1)
const registry = AtomRegistry.make()
registry.get(a)

const node = registry.getNodes().get(a)!
node.value() // => 1
node.currentState() // => "valid"
node.children.length // => 0
```

## Converters: the Effect-facing API

These module-level functions read or observe atoms through the `AtomRegistry` **service**,
so they return Effects/Streams with `AtomRegistry` in their requirements. They are the
recommended way to touch atom state from `Effect.gen`. The same functions are also
re-exported on the `Atom` module (`Atom.get`, `Atom.set`, …) for ergonomic use.

### `toStream`

Converts any atom into a `Stream` that emits the current value immediately, then every
subsequent change, unsubscribing when the stream scope closes.

The `AtomRegistry.toStream` form is dual: call it on a registry value
(`AtomRegistry.toStream(registry, atom)`) or pipe a registry into it. The `Atom`-module
form (`Atom.toStream`) needs no registry argument because it pulls one from the service:

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"
import { Effect, Stream } from "effect"

const count = Atom.make(0)

const program = Effect.gen(function* () {
  yield* Atom.toStream(count).pipe(
    Stream.take(2),
    Stream.runForEach((n) => Effect.sync(() => console.log(n))),
    Effect.forkScoped
  )
  yield* Atom.set(count, 1)
})
// logs: 0 then 1
```

### `toStreamResult`

Converts an `AsyncResult` atom into a `Stream` of **successful values**: `Initial`
results are skipped, failures fail the stream with their cause, and duplicates are
dropped via `Stream.changes`.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"
import { Effect, Stream } from "effect"

const data = Atom.make(Effect.succeed("ready")) // Atom<AsyncResult<string>>

const values = Effect.gen(function* () {
  return yield* Atom.toStreamResult(data).pipe(Stream.take(1), Stream.runCollect)
})
// => ["ready"]
```

### `getResult`

Reads an `AsyncResult` atom as an Effect, waiting until the result leaves `Initial`
(and through `waiting` results when `suspendOnWaiting: true`). Succeeds with the value
or fails with the result's error.

```ts
import { Atom, AtomRegistry } from "effect/unstable/reactivity"
import { Effect } from "effect"

const user = Atom.make(Effect.succeed({ name: "Ada" }))

const program = Effect.gen(function* () {
  const value = yield* Atom.getResult(user)
  return value.name // => "Ada"
})

Effect.runPromise(program.pipe(Effect.provide(AtomRegistry.layer), Effect.scoped))
```

### `get`

Reads an atom's current value as an Effect.

```ts
import { Atom } from "effect/unstable/reactivity"

const count = Atom.make(7)

Atom.get(count)
// Effect<number, never, AtomRegistry> — yields 7
```

### `set`

Writes a value to a writable atom through the registry service. Dual, so it pipes too.

```ts
import { Atom } from "effect/unstable/reactivity"

const count = Atom.make(0)

Atom.set(count, 5)
// Effect<void, never, AtomRegistry>

count.pipe(Atom.set(5)) // equivalent, piped form
```

### `update`

Writes the result of applying a function to the current value.

```ts
import { Atom } from "effect/unstable/reactivity"
import { Effect } from "effect"

const count = Atom.make(0)

const program = Effect.gen(function* () {
  yield* Atom.update(count, (n) => n + 1)
  return yield* Atom.get(count) // => 1
})
```

### `modify`

Atomically reads, computes `[returnValue, nextValue]`, writes `nextValue`, and yields
`returnValue`.

```ts
import { Atom } from "effect/unstable/reactivity"
import { Effect } from "effect"

const count = Atom.make(10)

const program = Effect.gen(function* () {
  const prev = yield* Atom.modify(count, (n) => [n, n + 1])
  return prev // => 10, count is now 11
})
```

### `refresh`

Requests a refresh of an atom through the service, invalidating it so the next read
recomputes.

```ts
import { Atom } from "effect/unstable/reactivity"

const data = Atom.make((get) => Date.now())

Atom.refresh(data)
// Effect<void, never, AtomRegistry>
```

### `mount`

Mounts an atom for the lifetime of the current Effect `Scope`: it subscribes with a
no-op listener and releases automatically when the scope finalizes. This keeps an
atom (and its effects/streams) alive without an explicit unsubscribe.

```ts
import { Atom } from "effect/unstable/reactivity"
import { Effect } from "effect"

const polling = Atom.make((get) => 0) // imagine a recurring source

const program = Effect.gen(function* () {
  yield* Atom.mount(polling) // stays alive until the scope closes
  // ... do work while the atom is kept warm
})
// Effect<void, never, AtomRegistry | Scope>
```

## Batching internals

When multiple writes happen inside a single tick, the registry **batches**
invalidation: it collects stale nodes, rebuilds them, then notifies listeners once,
so dependents don't observe intermediate states. The relevant symbols — `batch`,
`BatchPhase`, and `batchState` — are exported but marked `@internal`; they back the
runtime's coalescing and are not part of the stable public API. You normally never call
them directly. For how invalidation and node removal flow, see
[Invalidation & lifecycle](https://effect.plants.sh/reactivity/invalidation/).
**Tip:** For defining the atoms a registry evaluates — `Atom.make`, derived atoms, writable
atoms, async/stream atoms, families, and serialization — see the
[Atom](https://effect.plants.sh/reactivity/atom/) page.