Liking cljdoc? Tell your friends :D

remuda.snapshot

The client-held recovery snapshot: signing, verification, and hydration.

This is the piece that lets a live view survive a deploy, a server restart, or a dropped connection — and it is extra machinery LiveView does not need. The BEAM's process durability lets Phoenix assume the resident process usually survives; the JVM cannot, so :recoverable state has to live somewhere a server restart cannot reach. That means the browser, which means it crosses a trust boundary and has to be treated as hostile on the way back.

What it carries

{:component :counter          ; which component to remount
 :params    {:user-id 42}     ; its params
 :basis     "tx-1234"         ; optional Source basis token
 :state     {:draft "typed"}}  ; :recoverable fields only (tiers/snapshot)

The component name plus params is what a rebuild needs; route from the original sketch also carried route, which was redundant with it. Everything else in the view is rebuilt server-side — sourced state from :mount, disposable state from its default, derived state recomputed — so keeping it off the wire is what makes the snapshot small enough to ride in a signal.

Defence in depth

Three independent layers, because any one of them alone has a failure mode:

  1. HMAC signature. Detects tampering and forgery. Without it a client could invent a snapshot naming any component with any params.
  2. Schema validation on the decoded payload, before anything is used. The signature proves we produced the bytes, not that they are still meaningful — a snapshot signed by an older version of the app can decode to a shape the current code does not expect. Validation is pluggable (see verify) so the core stays dependency-free ; the built-in default admits only scalars and collections, never records or arbitrary types.
  3. The tier filter, applied later by tiers/restore, which replays only fields declared :recoverable. So even a validly-signed snapshot cannot write a :sourced field or introduce an undeclared key.

Layer 2 is the one worth calling out: it closes the deserialization class of hole where hydration constructs arbitrary types from wire data.

Transport

Encoded to a string and carried in an _-prefixed datastar signal, which datastar excludes from ordinary action requests. So it costs nothing per interaction and is asked for explicitly at reconnect.

The client-held recovery snapshot: signing, verification, and hydration.

This is the piece that lets a live view survive a deploy, a
server restart, or a dropped connection — and it is **extra machinery LiveView
does not need**. The BEAM's process durability lets Phoenix assume the resident
process usually survives; the JVM cannot, so `:recoverable` state has to live
somewhere a server restart cannot reach. That means the browser, which means it
crosses a trust boundary and has to be treated as hostile on the way back.

## What it carries

    {:component :counter          ; which component to remount
     :params    {:user-id 42}     ; its params
     :basis     "tx-1234"         ; optional Source basis token
     :state     {:draft "typed"}}  ; :recoverable fields only (tiers/snapshot)

The component name plus params is what a rebuild needs; `route` from the
original sketch also carried `route`, which was redundant with it. Everything
else in the view is
rebuilt server-side — sourced state from `:mount`, disposable state from its
default, derived state recomputed — so keeping it off the wire is what makes the
snapshot small enough to ride in a signal.

## Defence in depth

Three independent layers, because any one of them alone has a failure mode:

1. **HMAC signature.** Detects tampering and forgery. Without it a client could
   invent a snapshot naming any component with any params.
2. **Schema validation** on the decoded payload, before anything is used. The
   signature proves *we* produced the bytes, not that they are still meaningful
   — a snapshot signed by an older version of the app can decode to a shape the
   current code does not expect. Validation is pluggable (see `verify`) so the
   core stays dependency-free ; the built-in default admits only scalars
   and collections, never records or arbitrary types.
3. **The tier filter**, applied later by `tiers/restore`, which replays only
   fields declared `:recoverable`. So even a validly-signed snapshot cannot
   write a `:sourced` field or introduce an undeclared key.

Layer 2 is the one worth calling out: it closes the deserialization
class of hole where hydration constructs arbitrary types from wire data.

## Transport

Encoded to a string and carried in an `_`-prefixed datastar signal, which
datastar excludes from ordinary action requests. So it costs nothing per
interaction and is asked for explicitly at reconnect.
raw docstring

createclj

(create {:keys [secret]} {:keys [component params basis recoverable]})

Builds a signed snapshot string for a live context.

state is the component's tier declaration and view its current value; only :recoverable fields are included, via tiers/snapshot — which the caller passes in as recoverable, so this namespace does not depend on tiers.

Builds a signed snapshot string for a live context.

`state` is the component's tier declaration and `view` its current value; only
`:recoverable` fields are included, via `tiers/snapshot` — which the caller
passes in as `recoverable`, so this namespace does not depend on `tiers`.
sourceraw docstring

recoverable-stateclj

(recoverable-state verified)

The :state map from a verified snapshot, or nil.

Convenience for the reconnect path, which passes this straight to tiers/restore — where the tier filter applies the final check on which fields may be replayed.

The `:state` map from a verified snapshot, or `nil`.

Convenience for the reconnect path, which passes this straight to
`tiers/restore` — where the tier filter applies the final check on which fields
may be replayed.
sourceraw docstring

safe-value?clj

(safe-value? x)
(safe-value? x depth)

The conservative built-in validator: scalars and plain collections only.

Deliberately excludes records, types, functions, refs and anything else whose reconstruction could run code or exhaust memory. A caller wanting richer validation passes :validate-fn to verify — Malli, for instance — which is how the core stays dependency-free while still supporting 's schema-validation requirement.

Depth-limited, so a deeply nested payload cannot blow the stack during validation itself.

The conservative built-in validator: scalars and plain collections only.

Deliberately excludes records, types, functions, refs and anything else whose
reconstruction could run code or exhaust memory. A caller wanting richer
validation passes `:validate-fn` to `verify` — Malli, for instance — which is
how the core stays dependency-free while still supporting 's
schema-validation requirement.

Depth-limited, so a deeply nested payload cannot blow the stack during
validation itself.
sourceraw docstring

verifyclj

(verify {:keys [secret validate-fn]} signed)

Verifies and decodes a snapshot string.

Returns {:ok true :snapshot {...}}, or {:ok false :reason ...} — never throws on bad input, since every failure here is an expected condition rather than a bug: an old signature after a secret rotation, a truncated signal, a tampered payload.

Options:

  • :secret required, the HMAC key
  • :validate-fn optional (fn [payload] -> boolean), run after the signature and structural checks pass. Defaults to safe-value?. Pass a Malli-backed predicate for 's schema validation without the core taking the dependency.

Order matters and is deliberate: signature first, then structure, then the validator. Nothing decoded is used for anything before the signature checks out.

Verifies and decodes a snapshot string.

Returns `{:ok true :snapshot {...}}`, or `{:ok false :reason ...}` — never
throws on bad input, since every failure here is an expected condition rather
than a bug: an old signature after a secret rotation, a truncated signal, a
tampered payload.

Options:
- `:secret`      required, the HMAC key
- `:validate-fn` optional `(fn [payload] -> boolean)`, run after the signature
                 and structural checks pass. Defaults to `safe-value?`. Pass a
                 Malli-backed predicate for 's schema validation without
                 the core taking the dependency.

Order matters and is deliberate: signature first, then structure, then the
validator. Nothing decoded is used for anything before the signature checks
out.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close