# Atom

An `Atom<A>` is a **declaration of how to read a value**. It does not hold state
on its own — the [`AtomRegistry`](https://effect.plants.sh/reactivity/atom-registry/) is the runtime
owner that evaluates the read, caches the result, records dependency edges, runs
effects and streams, and disposes nodes that are no longer observed.

This page is about **declaring** atoms: synchronous state, effect- and
stream-backed values exposed as [`AsyncResult`](https://effect.plants.sh/reactivity/async-result/),
command-style functions, pull-based streams, runtime-backed atoms, and
parameterized families. For **reading and writing** atoms from Effect code (`get`,
`set`, `update`, `refresh`, `mount`) see the
[Registry page](https://effect.plants.sh/reactivity/atom-registry/), and for **deriving / transforming**
atoms (`map`, `transform`, `swr`, `debounce`, `keepAlive`, ...) see
[Atom combinators](https://effect.plants.sh/reactivity/atom-combinators/).

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

// Writable state atom — created at module scope so its identity is stable.
const count = Atom.make(0)

// Derived atom: reading `get(count)` builds a dependency edge.
const doubled = Atom.make((get) => get(count) * 2)

const registry = AtomRegistry.make()

registry.get(count) // => 0
registry.get(doubled) // => 0

registry.set(count, 5)
registry.get(doubled) // => 10  (recomputed because `count` changed)
```

## The mental model

Three rules explain almost all atom behavior:

1. **A regular `get(atom)` inside a read function creates a dependency.** When
   that dependency changes or is refreshed, every atom that read it is
   invalidated and recomputed on the next read. Use `get.once(atom)` to read the
   current value *without* creating an edge.
2. **Atom identity is the cache key.** The same atom object can hold different
   cached values in different registries. So define atoms once at module scope.
   For atoms parameterized by an input value, use [`family`](#family) — never
   build a fresh atom inline on every render.
3. **Cache lifetime belongs to the registry, not the atom.** An unobserved atom
   that is not `keepAlive` may be disposed immediately (or after its `idleTTL`),
   which releases finalizers and rebuilds effects, streams, and derived state on
   the next read. Reading an `Effect`-backed atom does not by itself keep
   external data subscribed.
**Disposal gotcha:** If an atom backed by an `Effect`/`Stream` must stay subscribed even when nothing
is reading it, mark it [`keepAlive`](https://effect.plants.sh/reactivity/atom-combinators/#keepalive) or
give it an [`idleTTL`](https://effect.plants.sh/reactivity/atom-combinators/#setidlettl). Otherwise its
work is torn down as soon as the last observer goes away.

## The four kinds of read

`Atom.make` is overloaded to cover the common cases. What you pass decides the
atom's value type:

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

// 1. A plain value -> a Writable<A> state atom.
const name = Atom.make("Alice") // Writable<string>

// 2. A read function -> a derived Atom<A> (sync).
const greeting = Atom.make((get) => `Hello, ${get(name)}`) // Atom<string>

// 3. An Effect -> Atom<AsyncResult<A, E>>.
const user = Atom.make(
  Effect.succeed({ id: 1, name: "Alice" })
) // Atom<AsyncResult<{ id: number; name: string }, never>>

// 4. A Stream -> Atom<AsyncResult<A, E | NoSuchElementError>>.
const ticks = Atom.make(
  Stream.tick("1 second").pipe(Stream.as(1))
) // Atom<AsyncResult<number, NoSuchElementError>>
```

For effects and streams you can also pass a **function** `(get) => Effect | Stream`
so the work can depend on other atoms:

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

const userId = Atom.make(1)

const user = Atom.make((get) =>
  Effect.succeed({ id: get(userId), name: "Alice" })
)
// When `userId` changes, the effect re-runs and the AsyncResult transitions
// through waiting -> success.
```
**Why AsyncResult?:** Effect- and stream-backed atoms never block. Their value is an
[`AsyncResult`](https://effect.plants.sh/reactivity/async-result/) — `Initial`, `Success`, or `Failure`,
each with a `waiting` overlay — so a UI can render the current state while newer
work is in flight.

## Service / Layer style with a runtime

Real applications run effects that need services. Wrap a `Layer` in an
[`AtomRuntime`](#atomruntime) and use its `atom` / `fn` constructors; every effect
read by those atoms is provided the layer's context.

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

class Users extends Context.Service<Users>()("app/Users", {
  succeed: {
    byId: (id: number) => Effect.succeed({ id, name: `user-${id}` })
  }
}) {}

// A runtime atom built from a Layer (shares a MemoMap across the app).
const runtime = Atom.runtime(Users.layer)

const userId = Atom.make(1)

// `runtime.atom` provides `Users` to the effect automatically.
const user = runtime.atom((get) =>
  Effect.gen(function* () {
    const users = yield* Users
    return yield* users.byId(get(userId))
  })
)
// => Atom<AsyncResult<{ id: number; name: string }, never>>
```

---

# Reference

## Models and guards

### `Atom<A>`

The core interface. An atom carries a `read` function plus caching metadata. All
fields except `read` are optional in spirit and usually set via
[combinators](https://effect.plants.sh/reactivity/atom-combinators/) rather than by hand.

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

interface Atom<A> {
  readonly read: (get: Atom.AtomContext) => A
  readonly keepAlive: boolean // stay cached with no observers?
  readonly lazy: boolean // defer recompute while unobserved?
  readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
  readonly label?: readonly [name: string, stack: string]
  readonly idleTTL?: number // ms to keep alive after going idle
  readonly initialValueTarget?: Atom<A>
}

const a = Atom.make(1)
a.keepAlive // => false
a.lazy // => true
```

### `isAtom`

Type guard for any `Atom`.

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

Atom.isAtom(Atom.make(1)) // => true
Atom.isAtom(42) // => false
```

### `Writable<R, W>`

An atom that can also be written to. `R` is the read type, `W` the write input
type (often `R`, but e.g. command atoms accept different write inputs). Carries a
`write(ctx, value)` function in addition to `read`.

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

const count = Atom.make(0) // Writable<number, number>
const registry = AtomRegistry.make()
registry.set(count, 3)
registry.get(count) // => 3
```

### `isWritable`

Type guard narrowing an `Atom<R>` to `Writable<R, W>`.

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

Atom.isWritable(Atom.make(0)) // => true
Atom.isWritable(Atom.make((get) => 1)) // => false (read-only derived atom)
```

### `TypeId` / `WritableTypeId`

The runtime brand strings used by the guards above.

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

Atom.TypeId // => "~effect/reactivity/Atom"
Atom.WritableTypeId // => "~effect/reactivity/Atom/Writable"
```

### Utility types

Type-level helpers for extracting an atom's value shape.

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

declare const a: Atom.Atom<number>
declare const r: Atom.Atom<AsyncResult.AsyncResult<string, Error>>

type T = Atom.Type<typeof a> // => number
type S = Atom.Success<typeof r> // => string
type F = Atom.Failure<typeof r> // => Error

// Atom.PullSuccess<T> extracts the item type of a `pull` atom's PullResult.
// Atom.WithoutSerializable<T> strips serializable metadata, keeping
// Writable read/write types when the source is writable.
```

## Context passed to read / write functions

### `AtomContext`

The `get` callback passed to every read function. Calling it as a function
(`get(atom)`) reads a dependency; it also has methods for one-shot reads,
awaiting `AsyncResult`/`Option` values, subscriptions, finalizers, and writing
back to itself or other atoms.

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

const source = Atom.make(1)

const derived = Atom.make((get) => {
  get(source) // dependency edge: recompute when `source` changes
  get.once(source) // read current value, NO dependency edge
  get.self<number>() // previous value of THIS atom, as Option
  get.addFinalizer(() => {
    /* runs when this atom is disposed */
  })
  return get.once(source) + 1
})
```

Notable `AtomContext` members:

- `get(atom)` / `get.get(atom)` — read with a dependency edge.
- `get.once(atom)` — read current value, no edge.
- `get.result(asyncResultAtom, { suspendOnWaiting? })` — await an `AsyncResult`
  atom as an `Effect<A, E>` (with edge); `get.resultOnce(...)` without an edge.
- `get.some(optionAtom)` / `get.someOnce(...)` — await an `Atom<Option<A>>` as an
  `Effect<A>`, suspending until the option is `Some` (`get.some` adds an edge,
  `get.someOnce` does not).
- `get.self<A>()` — this atom's current cached value as `Option<A>`.
- `get.setSelf(a)` — set this atom's value (used by async read functions).
- `get.refreshSelf()` / `get.refresh(atom)` — request recomputation.
- `get.set(writable, value)` / `get.setResult(writable, value)` — write to
  another writable atom.
- `get.subscribe(atom, f, { immediate? })` — observe changes via a callback.
- `get.stream(atom, opts)` / `get.streamResult(asyncResultAtom, opts)` — observe
  as a `Stream`.
- `get.mount(atom)` — keep an atom mounted for this read's lifetime.
- `get.addFinalizer(f)` — run cleanup when this atom is disposed.
- `get.registry` — the owning [`AtomRegistry`](https://effect.plants.sh/reactivity/atom-registry/).

### `WriteContext<A>`

The context passed to a writable atom's `write` function. It is intentionally
smaller than `AtomContext`: read other atoms, refresh or set the current atom,
and write to other writables.

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

const log = Atom.make<Array<string>>([])

const command = Atom.writable(
  () => null,
  (ctx, message: string) => {
    ctx.get(log) // read another atom
    ctx.set(log, [...ctx.get(log), message]) // write another writable
    ctx.setSelf(null) // set this atom
    ctx.refreshSelf() // request recompute
  }
)
```

### `FnContext`

The context passed to [`fn`](#fn) and [`fnSync`](#fnsync) computations. Like
`AtomContext` but tailored for command atoms (it has `result`, `setResult`,
`some`, `stream`, `streamResult`, `subscribe`, `self`, `setSelf`, `set`,
`refresh`, `mount`, `addFinalizer`, and `registry`), but it omits the edge-free
one-shot reads `once`, `resultOnce`, and `someOnce`. See `fn` below for usage.

## Constructors

### `readable`

The low-level read-only atom constructor: a `read` function and an optional
`refresh` registration callback. Most code uses [`make`](#make) instead.

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

const now = Atom.readable(() => 42)
AtomRegistry.make().get(now) // => 42
```

### `writable`

The low-level writable constructor: `read` and `write` functions (plus optional
`refresh`). The building block behind `make(value)`, `fn`, `subscriptionRef`, etc.

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

// A clamped counter: writes are stored via setSelf inside `write`.
const clamped = Atom.writable<number, number>(
  (get) => Option.getOrElse(get.self<number>(), () => 0),
  (ctx, value) => ctx.setSelf(Math.max(0, Math.min(10, value)))
)

const registry = AtomRegistry.make()
registry.set(clamped, 99)
registry.get(clamped) // => 10
```

### `make`

The primary, overloaded constructor. Dispatches on what you pass:

| You pass | You get |
| --- | --- |
| a plain value `A` | `Writable<A>` (state atom) |
| `(get) => A` | `Atom<A>` (derived sync) |
| `Effect<A, E, ...>` or `(get) => Effect<...>` | `Atom<AsyncResult<A, E>>` |
| `Stream<A, E, ...>` or `(get) => Stream<...>` | `Atom<AsyncResult<A, E \| NoSuchElementError>>` |

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

Atom.make(0) // => Writable<number>
Atom.make((get) => get(Atom.make(0)) + 1) // => Atom<number>
Atom.make(Effect.succeed("hi")) // => Atom<AsyncResult<string, never>>

// Effect/Stream options: seed an initial value, or run uninterruptibly.
Atom.make(Effect.succeed(1), { initialValue: 0, uninterruptible: true })
```

### `subscriptionRef`

Exposes a `SubscriptionRef` (or an effect producing one) as a writable atom.
Reading observes ref changes; writing sets the ref. When given a bare ref the
atom value is `A`; when given an effect it is `AsyncResult<A, E>`.

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

const program = Effect.gen(function* () {
  const ref = yield* SubscriptionRef.make(0)

  const atom = Atom.subscriptionRef(ref) // Writable<number>
  const registry = AtomRegistry.make()

  registry.get(atom) // => 0
  yield* SubscriptionRef.set(ref, 5)
  registry.get(atom) // => 5  (atom tracks the ref)
})
```

### `fnSync`

A synchronous **command atom**: a writable whose value is the result of the last
call. Writing an argument re-runs the function. Without an `initialValue` the read
type is `Option<A>` (`None` before the first call); with one it is `A`.

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

const slugify = Atom.fnSync((title: string) => title.toLowerCase().replace(/\s+/g, "-"))

const registry = AtomRegistry.make()
registry.get(slugify) // => Option.none()
registry.set(slugify, "Hello World")
registry.get(slugify) // => Option.some("hello-world")

// With initialValue the result type is the bare value:
const counter = Atom.fnSync(
  (n: number, get) => n * 2,
  { initialValue: 0 }
)
```

### `fn`

An **effectful command atom**. Writing an argument starts an `Effect` (or
`Stream`) and the atom's value is its `AsyncResult`. The result type is
[`AtomResultFn<Arg, A, E>`](#atomresultfn). Writes also accept the [`Reset`](#reset)
and [`Interrupt`](#interrupt) control symbols.

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

const search = Atom.fn((query: string) =>
  Effect.succeed([`result for ${query}`])
)
// => AtomResultFn<string, string[], never>

const registry = AtomRegistry.make()
registry.get(search) // => AsyncResult.Initial (no call yet)
registry.set(search, "effect") // starts the effect -> waiting -> success
registry.set(search, Atom.Reset) // back to Initial
registry.set(search, Atom.Interrupt) // interrupt the running effect

// `concurrent: true` keeps overlapping calls running and joins them;
// otherwise a new write interrupts the previous one.
const upload = Atom.fn(
  (file: string) => Effect.succeed(file),
  { concurrent: true }
)
```

When you need the argument generic fixed first (e.g. for a typed write input),
call `fn<Arg>()` with no other args to get a curried constructor:

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

const byId = Atom.fn<number>()((id, get) => Effect.succeed({ id }))
// => AtomResultFn<number, { id: number }, never>
```

### `pull`

A pull-based stream atom. The first read pulls the initial chunk; each write
(`void`) pulls the next chunk. Items accumulate by default into a
[`PullResult`](#pullresult), unless `disableAccumulation` is set. Useful for
"load more" / infinite-scroll style streams.

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

const pages = Atom.pull(Stream.fromIterable([1, 2, 3, 4]))
// => Writable<PullResult<number>, void>

const registry = AtomRegistry.make()
registry.get(pages) // pulls first chunk -> AsyncResult success { done, items }
registry.set(pages, undefined) // pull the next chunk

// Don't accumulate — each pull replaces the items.
Atom.pull(Stream.fromIterable([1, 2, 3]), { disableAccumulation: true })
```

### `family`

Memoizes an atom factory so the **same input returns the same atom object** —
giving parameterized atoms a stable identity (and therefore a stable cache key).
Uses weak references where the platform supports them, so unused entries can be
collected.

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

// One atom per user id, created on demand and cached by id.
const userById = Atom.family((id: number) =>
  Atom.make(Effect.succeed({ id, name: `user-${id}` }))
)

userById(1) === userById(1) // => true  (same atom object)
userById(1) === userById(2) // => false
```
**Tip:** Always wrap parameterized atoms in `family` instead of building them inline. A
fresh `Atom.make(...)` on every call has a fresh identity, so the registry can
never find the cached value — it recomputes every time.

### `context`

Creates a [`RuntimeFactory`](#runtimefactory) backed by a supplied
`Layer.MemoMap`. Use this when you want runtime atoms to share layer memoization
with a specific memo map.

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

const factory = Atom.context({ memoMap: Layer.makeMemoMapUnsafe() })
// `factory(layer)` then produces AtomRuntime values sharing that MemoMap.
```

### `defaultMemoMap`

The default `Layer.MemoMap` used by the module-level [`runtime`](#runtime)
factory.

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

Atom.defaultMemoMap // => Layer.MemoMap (shared by Atom.runtime)
```

### `runtime`

The default [`RuntimeFactory`](#runtimefactory), built on `defaultMemoMap`. Call
it with a `Layer` to get an [`AtomRuntime`](#atomruntime).

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

class Config extends Context.Service<Config>()("app/Config", {
  succeed: { apiUrl: "https://example.com" }
}) {}

const runtime = Atom.runtime(Config.layer)

const apiUrl = runtime.atom(
  Effect.map(Config, (c) => c.apiUrl)
)
// => Atom<AsyncResult<string, never>>
```

### `withReactivity`

`runtime.withReactivity(keys)` — refresh an atom whenever the given invalidation
keys change in the default `Reactivity` runtime. (See
[Reactivity](https://effect.plants.sh/reactivity/reactivity/) for keys and invalidation.)

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

const todos = Atom.make(Effect.succeed([])).pipe(
  Atom.withReactivity(["todos"])
)
// Refreshes whenever the "todos" key is invalidated.
```

## Runtime models

### `AtomRuntime`

The atom returned by a [`RuntimeFactory`](#runtimefactory). Its value is the built
`Context`, and it exposes context-aware constructors: `runtime.atom`,
`runtime.fn`, `runtime.pull`, and `runtime.subscriptionRef`. Each runs its
effect/stream with the runtime's services provided, so reads can use `R` and fail
with the layer's error `ER`.

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

class Db extends Context.Service<Db>()("app/Db", {
  succeed: { query: (sql: string) => Effect.succeed([sql]) }
}) {}

const runtime = Atom.runtime(Db.layer)

const rows = runtime.atom((get) =>
  Effect.flatMap(Db, (db) => db.query("select 1"))
)

const mutate = runtime.fn((sql: string, get) =>
  Effect.flatMap(Db, (db) => db.query(sql))
)

runtime.layer // => Atom<Layer<Db, never>> (the underlying layer atom)
```

### `RuntimeFactory`

A callable that turns a `Layer` (or `(get) => Layer`) into an `AtomRuntime`,
sharing a `Layer.MemoMap`. Also exposes `memoMap`, `addGlobalLayer` (merge a layer
into every runtime it produces), and `withReactivity`.

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

const factory = Atom.runtime
factory.memoMap // => Layer.MemoMap
factory.addGlobalLayer(Layer.empty) // merged into all runtimes from this factory
```

## Command-atom models and control symbols

### `AtomResultFn`

The type returned by [`fn`](#fn): a `Writable<AsyncResult<A, E>, Arg | Reset | Interrupt>`.
You read its `AsyncResult` and write arguments (or control symbols) to it.

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

// AtomResultFn<Arg, A, E> ≈ Writable<AsyncResult<A, E>, Arg | Reset | Interrupt>
declare const f: Atom.AtomResultFn<string, number, Error>
```

### `Reset`

Control symbol written to an `AtomResultFn` to clear it back to the `Initial`
state.

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

const fn = Atom.fn((n: number) => Effect.succeed(n))
const registry = AtomRegistry.make()
registry.set(fn, 1) // success
registry.set(fn, Atom.Reset) // => AsyncResult.Initial again
```

### `Interrupt`

Control symbol written to an `AtomResultFn` to interrupt the currently running
computation.

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

const fn = Atom.fn((ms: number) => Effect.sleep(ms).pipe(Effect.as(ms)))
const registry = AtomRegistry.make()
registry.set(fn, 10000) // starts a long-running effect (waiting)
registry.set(fn, Atom.Interrupt) // => Failure with an interrupt cause
```

### `PullResult`

The `AsyncResult` produced by a [`pull`](#pull) atom: a success carrying a
non-empty `items` batch and a `done` flag, or a `NoSuchElementError` failure when
the stream completes with no items.

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

// PullResult<A, E> = AsyncResult<{ done: boolean; items: NonEmptyArray<A> },
//                                E | NoSuchElementError>
declare const r: Atom.PullResult<number>
```

## Per-atom metadata helpers

These return a copy of an atom with adjusted lifetime/identity metadata. They live
in the same module but pair naturally with construction; the full set of
derivation combinators is on the
[Atom combinators](https://effect.plants.sh/reactivity/atom-combinators/) page.

### `setIdleTTL`

Disposes an atom only after it has been idle (unobserved) for the given duration;
an infinite duration is equivalent to `keepAlive`.

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

const cached = Atom.make(0).pipe(Atom.setIdleTTL("30 seconds"))
cached.idleTTL // => 30000
```

### `initialValue`

Pairs an atom with a preload value, producing a `[Atom<A>, A]` tuple you can hand
to a registry as a seed.

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

const count = Atom.make(0)
const seed = Atom.initialValue(count, 10) // => [count, 10]

const registry = AtomRegistry.make({ initialValues: [seed] })
registry.get(count) // => 10
```

## Reading and writing from Effect code

To read or mutate atoms from inside an `Effect`, use the registry-aware helpers
exported by this module — `Atom.get`, `Atom.set`, `Atom.update`, `Atom.modify`,
`Atom.getResult`, `Atom.refresh`, `Atom.mount`, `Atom.toStream`, and
`Atom.toStreamResult`. They all require the `AtomRegistry` service and are
documented on the [Atom Registry](https://effect.plants.sh/reactivity/atom-registry/) page.

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

const count = Atom.make(0)

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