A mutation that paints its result into the UI before the server confirms is an optimistic update. Pylon’s sync engine ships first-class support for the pattern — the optimistic ghost and the canonical row share an id, so the WebSocket broadcast lands as a field-level merge instead of a delete-then-replace flash.Documentation Index
Fetch the complete documentation index at: https://docs.pylonsync.com/llms.txt
Use this file to discover all available pages before exploring further.
How it works
useMutationcallsoptimistic(args, ctx)to build the ghost row.ctx.idis a freshly-minted Pylon-shaped row id (40-char hex);ctx.nowis a stable timestamp for the gesture.- The hook paints the ghost into the local store via
optimisticInsertWithId. Live queries re-render immediately. - The hook calls the server function with the original args plus
_optimisticId: ctx.id. - The server function passes
args._optimisticIdintoctx.db.insert("Entity", { id, ... }). The runtime validates the id is well-formed and uses it verbatim. - The change log emits a broadcast with
row_id = ctx.id. It arrives at the client over the WebSocket and the local store treats it as an idempotent merge — samerow_id, fields refreshed. - On rejection, the optimistic ghost is rolled back without leaving a tombstone, so retrying the mutation works.
The server function
To accept optimistic ids, your mutation needs one extra arg:_optimisticId is a 40-char lowercase hex string (the shape generateId() produces). Anything else returns an INVALID_ID error before the row is inserted.
If two clients somehow mint the same id, the second insert fails with a typed OPTIMISTIC_ID_CONFLICT error code so retry logic can mint a fresh id and try again.
Multiple rows in one gesture
Some mutations touch more than one entity — accepting an invite, for example, inserts a Membership row AND an AuditLog row. Return an array from the builder:ctx.id, so they’re rolled back together on rejection.
When NOT to use it
Optimistic updates are a UX win when the server almost always succeeds. Skip them when:- The mutation can fail in ways the user must see immediately (e.g. payment, ID verification). Showing the ghost only to remove it 200ms later is worse than a small spinner.
- The server transforms the data significantly (e.g. computes a slug, generates a thumbnail, runs through an AI). The ghost would be visibly different from the canonical, defeating the point.
- The feature is rarely-used (settings dialogs, admin pages). The complexity isn’t worth the polish.
Manual control
For mutations that don’t fit theuseMutation shape (background syncs, multi-step flows), the underlying primitives are exported from @pylonsync/sync:
rollbackOptimisticInsert removes the ghost without leaving a tombstone, so a future legitimate insert with the same id (e.g. eventual consistency from a workflow) isn’t blocked.