Mirror / Mirrored state

How it fits together

mermaid
flowchart LR
  subgraph app["application"]
    direction TB
    model["Model — the only truth"]
    slice["slice: a writable projection<br/>encoded with defaults elided"]
    model --> slice
  end
  subgraph stores["stores"]
    direction TB
    url["URL<br/>?filter=active&page=2"]
    kv["KeyValueStore<br/>one JSON document per key"]
  end
  slice -- "entry writes what changed" --> url
  slice -- "entry writes what changed" --> kv
  url -. "reduce(model, url) on navigation and load" .-> model
  kv -. "reduce(model, MirrorRestored) from restore" .-> model

Declared from the application:

ts
const Filters = Mirror.url(App, {
  fields: [App.model.filter, App.model.page, App.model.q],
  keys: { q: { history: 'replace' } },
})
const Prefs = Mirror.kv(App, { key: 'todo/prefs', scope: userId, fields: [App.model.sidebar, App.model.draft] })

Wired into update, the URL hook the runtime already has, and one Subscription entry per mirror:

ts
const update = (model, message) => {
  if (Mirror.reduces(message)) return { model: Prefs.reduce(model, message) }
  if (message._tag === 'UrlChanged') return { model: Filters.reduce(model, message.url) }
  …
}
const subscriptions = Subscription.make<Model, Message, KeyValueStore>()(() => ({
  ...Filters.subscriptions,
  ...Prefs.subscriptions,
}))

Under Sync.mount, the url option is where a URL mirror plugs in (init: (model, url) => Filters.reduce(model, url), onUrlChange), and subscriptions and resources carry the entries and the store layer.