# AtomRef & Hydration

This page covers two small, related corners of the reactivity layer:

- **`AtomRef`** — standalone observable state cells that do **not** go through an
  `AtomRegistry`. They are ideal for form state, local view models, and ordered
  collections where callers need direct mutation methods plus change
  notifications.
- **`Hydration`** — moving serializable registry state between
  `AtomRegistry` instances: snapshot atoms on the server (`dehydrate`), ship the
  encoded values, and load them into a client registry (`hydrate`) before the
  atoms are first read.

Both ```
**Unstable module:** The reactivity modules live under `effect/unstable/reactivity` and may change
  between minor releases.

## Part 1 — AtomRef

### Mental model

An `AtomRef` is a value cell with a stable `key` and a subscriber list. Unlike
[atoms](https://effect.plants.sh/reactivity/atom/), it carries its current value directly and exposes
imperative `set` / `update` / `prop` methods — there is no registry to read it
through.

```ts
// A standalone, mutable reactive cell.
const count = AtomRef.make(0)

count.value // => 0

// Subscribe to changes. Returns an unsubscribe function.
const unsubscribe = count.subscribe((n) => console.log("count is now", n))

count.set(1) // logs: count is now 1
count.update((n) => n + 1) // logs: count is now 2
count.value // => 2

unsubscribe()
```

Three rules drive everything below:

1. **Notifications are equality-aware.** Setting a value that is
   `Equal.equals` to the current value does **not** notify subscribers. Derived
   (`map`) and focused (`prop`) subscriptions only emit when *their* projected
   value changes.
2. **Direct mutation does not notify.** Mutating an object or array stored in a
   ref in place (`count.value.push(...)`) will not trigger listeners. Always go
   through `set`, `update`, or a `prop` ref so a fresh value is produced.
3. **`toArray` on a collection returns raw values, not refs.**

### Common case: a form view model

The service/Layer style works here too — keep the local cell on a small view
model and expose focused refs to the UI:

```ts
interface SignupForm {
  readonly email: string
  readonly password: string
  readonly accepted: boolean
}

const form = AtomRef.make<SignupForm>({
  email: "",
  password: "",
  accepted: false
})

// Focused, individually-subscribable field refs.
const email = form.prop("email")
const accepted = form.prop("accepted")

// A derived, read-only validity view.
const isValid = form.map((f) => f.email.includes("@") && f.password.length >= 8)

isValid.subscribe((ok) => console.log("can submit:", ok))

email.set("ada@example.com") // updates form.value.email; isValid recomputes
accepted.update((b) => !b) // => true

form.value
// => { email: "ada@example.com", password: "", accepted: true }
```

Note that mutating a `prop` ref rebuilds the parent value immutably (object
spread, or array slice for array entries), so the parent's subscribers fire too.

---

## Part 2 — Hydration (server → client transfer)

`Hydration` snapshots atoms marked with [`Atom.serializable`](https://effect.plants.sh/reactivity/atom-combinators/)
in one [`AtomRegistry`](https://effect.plants.sh/reactivity/registry/) and loads those encoded values
into another registry **before** the atoms are first read. This is the standard
SSR / browser-bootstrap handoff: compute on the server, ship the snapshot in the
HTML, rehydrate on the client.

Only the stable serialization key, the encoded value, a dehydration timestamp,
and an optional async handoff are transferred — atom *identity* is not. The
target registry must contain atoms with **matching keys and compatible codecs**.

### Round trip: server → client

```ts
// A serializable atom. The same definition must exist on both ends.
const userId = Atom.make(0).pipe(
  Atom.serializable({ key: "userId", schema: Schema.Number })
)

// --- Server ---
const server = AtomRegistry.make()
server.set(userId, 42)

// Snapshot only the serializable atoms.
const snapshot = Hydration.dehydrate(server)
// => [{ "~effect/reactivity/DehydratedAtom": true,
//       key: "userId", value: 42, dehydratedAt: 171... }]

// Serialize and embed in HTML, e.g.
// `<script>window.__STATE__ = ${JSON.stringify(snapshot)}</script>`

// --- Client ---
const client = AtomRegistry.make()
Hydration.hydrate(client, snapshot)

// The atom now reads the server-computed value on first access.
client.get(userId) // => 42
```
**Tip:** Pair this with [`Atom.withServerValue`](https://effect.plants.sh/reactivity/atom-combinators/) to give
  an atom a server-only initial value, and with
  [`AsyncResult.Schema`](https://effect.plants.sh/reactivity/async-result/) to make an async atom's
  result serializable.

---

## AtomRef reference

### `TypeId`

The literal/runtime tag identifying every `AtomRef` value:
`"~effect/reactivity/AtomRef"`.

```ts
const ref = AtomRef.make(0)
ref[AtomRef.TypeId] // => "~effect/reactivity/AtomRef"
```

### `ReadonlyRef<A>` (model)

A read-only reactive reference. Exposes a stable `key`, the current `value`,
`subscribe`, and `map`. Equality and hashing are based on the current value
(it implements `Equal.Equal`). `map` results and other read-only views are
typed as `ReadonlyRef`.

```ts
const a = AtomRef.make(1)
const view: AtomRef.ReadonlyRef<number> = a.map((n) => n * 10)

view.value // => 10
view.key // => stable string id, e.g. "AtomRef-1"

// Equality compares the underlying value.
Equal.equals(AtomRef.make(1), AtomRef.make(1)) // => true
```

### `AtomRef<A>` (model)

A mutable reactive reference: everything in `ReadonlyRef` plus `prop`, `set`,
and `update`.

```ts
const ref: AtomRef.AtomRef<{ x: number }> = AtomRef.make({ x: 0 })
ref.set({ x: 1 }).update((v) => ({ x: v.x + 1 }))
ref.value // => { x: 2 }
```

### `Collection<A>` (model)

A `ReadonlyRef<ReadonlyArray<AtomRef<A>>>` whose items are themselves mutable
refs. Adds `push`, `insertAt`, `remove`, and `toArray`. Changes to an item ref
also notify the collection's subscribers.

```ts
const todos: AtomRef.Collection<string> = AtomRef.collection(["buy milk"])
todos.value.length // => 1
todos.value[0].value // => "buy milk"
```

### `make`

Creates a mutable `AtomRef<A>` initialized with the supplied value.

```ts
const name = AtomRef.make("Ada")
name.value // => "Ada"
name.set("Grace").value // => "Grace"
```

### `collection`

Creates a `Collection<A>` from an iterable of initial values. Each item is
wrapped in its own `AtomRef`.

```ts
const list = AtomRef.collection([1, 2, 3])
list.toArray() // => [1, 2, 3]
```

### `ref.value`

The current value of the ref. On `map`/`prop` refs it is computed lazily from
the parent on access.

```ts
const n = AtomRef.make(41)
n.value // => 41
n.set(42)
n.value // => 42
```

### `ref.key`

A stable, generated string identifier for the ref (`"AtomRef-N"`). Useful as a
React key or for debugging; it does not change across `set`/`update`.

```ts
const r = AtomRef.make(0)
r.key // => "AtomRef-0" (counter value varies)
```

### `ref.subscribe`

Registers a listener and returns an unsubscribe function. The listener fires on
every change whose new value is **not** `Equal.equals` to the previous one.

```ts
const r = AtomRef.make(0)
const stop = r.subscribe((v) => console.log("changed to", v))

r.set(0) // no log — Equal.equals to current
r.set(1) // logs: changed to 1
stop() // detach the listener
```

### `ref.map`

Derives a read-only `ReadonlyRef<B>`. The derived subscription only emits when
the projected value actually changes, so it doubles as a memoized selector.

```ts
const user = AtomRef.make({ name: "Ada", age: 36 })
const name = user.map((u) => u.name)

name.subscribe((n) => console.log("name:", n))

user.set({ name: "Ada", age: 37 }) // no log — name unchanged
user.set({ name: "Grace", age: 37 }) // logs: name: Grace
name.value // => "Grace"
```

### `ref.prop`

Focuses a mutable `AtomRef` on a nested object key or array index. Reads pull
from the parent; writes rebuild the parent value immutably and notify both the
prop ref and the parent. `prop` chains, so you can drill into deeply nested
structures.

```ts
const state = AtomRef.make({ user: { name: "Ada" }, tags: ["a", "b"] })

const name = state.prop("user").prop("name")
name.set("Grace")
state.value.user.name // => "Grace"

// Array entries focus by numeric index.
const firstTag = state.prop("tags").prop(0)
firstTag.update((t) => t.toUpperCase())
state.value.tags // => ["A", "b"]
```

### `ref.set`

Replaces the whole value and notifies subscribers (unless equal to the current
value). Returns the same ref for chaining.

```ts
const r = AtomRef.make(1)
r.set(2).set(3).value // => 3
```

### `ref.update`

Computes the next value from the current one via `set`. Same notify/equality
semantics; returns the ref.

```ts
const r = AtomRef.make(10)
r.update((n) => n * 2).value // => 20
```

### `collection.push`

Appends a new item (wrapped in its own ref) to the end and notifies. Returns
the collection.

```ts
const list = AtomRef.collection<number>([1])
list.push(2).push(3)
list.toArray() // => [1, 2, 3]
```

### `collection.insertAt`

Inserts a new item ref at the given index and notifies. Returns the collection.

```ts
const list = AtomRef.collection(["a", "c"])
list.insertAt(1, "b")
list.toArray() // => ["a", "b", "c"]
```

### `collection.remove`

Removes the given item ref by identity (no-op if not present) and notifies.
Returns the collection.

```ts
const list = AtomRef.collection(["x", "y"])
const yRef = list.value[1]
list.remove(yRef)
list.toArray() // => ["x"]
```

### `collection.toArray`

Returns a fresh array of the current raw item **values** (not the refs).
Mutating an individual item ref also fires the collection's subscribers.

```ts
const list = AtomRef.collection([{ done: false }])
list.subscribe(() => console.log("collection changed"))

// Mutating an item ref notifies the collection.
list.value[0].update((t) => ({ done: true })) // logs: collection changed
list.toArray() // => [{ done: true }]
```

---

## Hydration reference

### `DehydratedAtom` (model)

Marker interface for entries in a dehydrated registry snapshot. Carries the tag
`"~effect/reactivity/DehydratedAtom": true`. `dehydrate` returns an array of
these; use `toValues` to view them as the concrete record shape.

```ts
const entries: ReadonlyArray<Hydration.DehydratedAtom> = []
```

### `DehydratedAtomValue` (model)

The concrete snapshot record: the atom's serialization `key`, the encoded
`value`, the `dehydratedAt` timestamp, and an optional `resultPromise` (used
only for `AsyncResult.Initial` values encoded with `encodeInitialAs: "promise"`).

```ts
const entry: Hydration.DehydratedAtomValue = {
  "~effect/reactivity/DehydratedAtom": true,
  key: "userId",
  value: 42,
  dehydratedAt: Date.now()
}
```

### `dehydrate`

Encodes the serializable atoms currently held by a registry into an array of
`DehydratedAtom`. Atoms not marked with `Atom.serializable` are skipped.

The `encodeInitialAs` option controls how `AsyncResult.Initial` values are
handled:

- `"ignore"` (default) — skip atoms still in the initial state.
- `"value-only"` — encode the initial value as-is.
- `"promise"` — attach a live `resultPromise` that resolves when the atom leaves
  the initial state, so a still-loading server fetch can complete after the
  snapshot is taken.

```ts
const registry = AtomRegistry.make()

// Default: ignore atoms still in AsyncResult.Initial.
Hydration.dehydrate(registry) // => Array<DehydratedAtom>

// Wait for in-flight initial results to settle before they ship.
Hydration.dehydrate(registry, { encodeInitialAs: "promise" })
```
**Caution:** The `resultPromise` is a live JavaScript promise. It can be handed to another
  registry in the same runtime, but it cannot be sent through JSON or across a
  process boundary.

### `toValues`

Narrows an array of generic `DehydratedAtom` entries to the concrete
`DehydratedAtomValue` records, so you can read `key` / `value` / `dehydratedAt`.

```ts
const state = Hydration.dehydrate(AtomRegistry.make())
const values = Hydration.toValues(state)
values.map((v) => v.key) // => string[] of serialization keys
```

### `hydrate`

Applies dehydrated state to a target registry by serialization key. Each entry
is preloaded with `registry.setSerializable`, so the matching atom decodes the
value with its own codec on first read. Entries carrying a `resultPromise`
update the registry node (or preload the resolved value) once the promise
resolves.

```ts
const client = AtomRegistry.make()
const snapshot = [
  {
    "~effect/reactivity/DehydratedAtom": true as const,
    key: "userId",
    value: 42,
    dehydratedAt: Date.now()
  }
]

Hydration.hydrate(client, snapshot)
// matching `userId` atom now reads 42 before any computation runs
```

## See also

- [Atom](https://effect.plants.sh/reactivity/atom/) — registry-backed reactive values.
- [Atom combinators](https://effect.plants.sh/reactivity/atom-combinators/) — `serializable`,
  `withServerValue`, and friends used by hydration.
- [AsyncResult](https://effect.plants.sh/reactivity/async-result/) — the result type whose
  `Initial`/loading states the `encodeInitialAs` option handles.
- [Registry](https://effect.plants.sh/reactivity/registry/) — the `AtomRegistry` that hydration reads
  from and writes to.