<!--
Sitemap:
- [rxfy](/index): Typed, normalized, reactive state — built on RxJS
- [Comparison](/comparison): rxfy versus Redux Toolkit, MobX, Jotai, TanStack Query, and TanStack DB
- [Inspired by](/inspired-by): the libraries and ideas rxfy grew out of
- [Agent Skills](/agent-skills): Accurate rxfy context for AI coding assistants
- [Examples](/examples): Runnable apps, from client-only to fully synced
- [Changelog](/changelog)
- [Getting Started](/getting-started)
- [Create Store](/getting-started/create-store): Normalized reactive state in a client-only app
- [Add SSR](/getting-started/add-ssr): Render the first paint on the server, hydrate with no refetch
- [Add Sync Client](/getting-started/add-sync-client): The full stack: the server publishes, the client syncs
- [Core Concepts](/core-concepts): The ideas rxfy is built on
- [Observables](/core-concepts/observables): A value that changes over time, that you can subscribe to
- [Normalization](/core-concepts/normalization): Store each entity once, reference it by id
- [Late Unwrapping](/core-concepts/late-unwrapping): Unwrap async state at the leaf, not the trunk
- [Server-Side Rendering](/core-concepts/ssr): Dehydrate on the server, hydrate with no refetch
- [rxfy](/rxfy): The core package: atoms, lenses, models, and states
- [createModel](/rxfy/create-model): Typed entities in normalized storage
- [defineState](/rxfy/define-state): Typed, normalized state shapes with fetching and mutations
- [createAtom](/rxfy/create-atom): A reactive cell with synchronous get, set, and modify
- [createLens](/rxfy/create-lens): A two-way view into part of an Atom
- [React Bindings](/react): Hooks and helpers for using rxfy in React
- [useStateData](/react/use-state-data): Fetch, normalize, and subscribe to a query
- [useStatePagedData](/react/use-state-paged-data): Paginated and infinite-scroll lists
- [useModelStore](/react/use-model-store): Subscribe to one normalized entity by id
- [useAtom](/react/use-atom): Two-way binding for any IAtom
- [Pending](/react/pending): Render pending, rejected, and fulfilled UI for any observable
- [usePending](/react/use-pending): The status value behind Pending
- [useObservable](/react/use-observable): Bind a raw Observable to React
- [Sync Client in React](/react/sync-client): StoreProvider, useSyncClient, and update handles
- [rxfy-client](/framework/client): The framework-agnostic browser sync runtime
- [createSyncClient](/framework/client/create-sync-client): Connect a transport and drive the sync loop
- [readSsrGrants](/framework/client/read-ssr-grants): Lift SSR-embedded channel grants
- [rxfy-server](/framework/server): Bind Drizzle tables, write, and publish sync updates
- [defineResource](/framework/server/define-resource): Tie a Drizzle table to an rxfy model
- [createSync](/framework/server/create-server): Wire a storage adapter, hub, and secret into a Live object
- [createInMemoryHub](/framework/server/hub): The socket-keyed pub/sub backbone
- [Writes](/framework/server/writes): sync.create / sync.update / sync.delete and touch
- [Storage adapters](/framework/server/storage-adapters): Persist writes with Drizzle or in memory
- [Sync messages](/framework/server/messages): What travels between server and client
- [Grants](/framework/server/grants): The server signs what it serves; the client subscribes with the token
- [rxfy-ws](/framework/ws): The default WebSocket transport
- [createWsServer](/framework/ws/server): Attach a Hub to WebSocket connections
- [createWsClient](/framework/ws/client): The browser transport with reconnect and replay
- [Custom transports](/framework/ws/custom-transport): Bring your own ClientTransport
- [Guides](/guides): Task-focused walkthroughs of common rxfy patterns
- [Pagination and infinite scroll](/guides/pagination): Load and append pages into one normalized list
-->

# Sync messages \[What travels between server and client]

The sync stack speaks a small message protocol. You never construct or parse these messages
yourself — `rxfy-server` publishes them from [writes](/framework/server/writes), `rxfy-ws`
carries them, and the Sync Client applies them — but knowing their shape helps when debugging
the wire or [bringing your own transport](/framework/ws/custom-transport).

All messages carry a `v` field (protocol version) and a `kind` discriminant.

## Server → client

| Type           | `kind`    | Fields                            | Description                                                                                                             |
| -------------- | --------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `PatchMessage` | `"patch"` | `v`, `kind`, `name`, `id`, `data` | Sync entity update. Holders of `name:id` apply it in place. `data` is validated against the model schema on the client. |
| `StaleMessage` | `"stale"` | `v`, `kind`, `channel`            | Structural change signal for a state channel. Clients increment a local staleness counter so they know to re-fetch.     |

`sync.update` publishes a `patch` on the entity topic; `sync.create` / `sync.delete` /
`sync.touch` publish `stale` on the touched channels — see
[Writes](/framework/server/writes) for the full behaviour.

## Client → server

| Type               | `kind`        | Fields                           | Description                                                                                                                                                                                                                          |
| ------------------ | ------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SubscribeMessage` | `"subscribe"` | `v`, `kind`, `grant`, `entities` | Present a signed channel [grant](/framework/server/grants) plus the raw `name:id` entity topics the payload normalized into. The channel is authorized by the grant; entity topics are accepted alongside any currently-valid grant. |

`subscribe` is the client's **only** outbound frame. The client sends one per served state (lifted
from the payload's `$grant`) and replays its whole grant set on reconnect — see
[Grants](/framework/server/grants).

## Wire encoding

The built-in transport serializes messages with
[superjson](https://github.com/blitz-js/superjson), so rich types (`Date`, `Map`, `Set`,
`BigInt`, …) survive the wire intact. Malformed payloads, unknown `kind`s, and version
mismatches are rejected at the parsing layer.

## Versioning

Every message embeds the current protocol version as `v`, and parsers require an exact match —
there is no negotiation or backward compatibility layer. A version bump means a coordinated
upgrade of the server and all clients before traffic resumes.

:::info\[The `rxfy-protocol` package]
The message types and codec live in [`rxfy-protocol`](https://www.npmjs.com/package/rxfy-protocol),
a dependency of `rxfy-server`, `rxfy-ws`, `rxfy-client`, and `rxfy-react` that installs
automatically with them.
You rarely import it directly — its main direct use is building a
[custom transport](/framework/ws/custom-transport) or a non-JS client against the wire contract.
:::
