When multiple users (or multiple tabs) share the same data, a mutation made by one client needs to be reflected everywhere else — without those clients having to poll the server or re-fetch entire datasets. dragonJSON’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/michael-tiger-2010/dragonjson/llms.txt
Use this file to discover all available pages before exploring further.
liveInvalidation option provides a lightweight real-time sync layer that fits on top of any pub/sub transport you already have. The server remains stateless with respect to invalidation: it is your relay that does the broadcasting.
How it works
After every local mutation ($set, $add, or $remove), the client calls sendRelayMessage with a compact message describing what changed. Other clients receive this message via their onReceiveCallback, then:
Mark stale paths
Each path listed in
invalidate is located in the receiving client’s rootData. Object values are replaced with { __next: true } (deferred placeholder); primitive values are deleted.Fire local $on listeners
The receiving client dispatches the event locally — exactly as if
$set, $add, or $remove had been called on that client — firing any $on listeners registered on the affected paths.Setup with WebSocket
The most common configuration is a Socket.io (or any WebSocket) relay. PasssendRelayMessage and onReceiveCallback in the liveInvalidation option:
sendRelayMessage— called automatically after every local mutation. Forwards the relay message to your transport.onReceiveCallback— called once at init. dragonJSON passes it the handler function; you register that handler with your transport so incoming messages are forwarded to dragonJSON.
Setup with BroadcastChannel
For multi-tab synchronisation in the browser, the BroadcastChannel API works as a zero-dependency relay:Relay message shape
Every relay message is a plain JSON-serialisable object with four fields:| Field | Type | Description |
|---|---|---|
invalidate | string[] | Dot-separated paths to mark stale in the receiving client’s cache. |
initiator | string | The operation that triggered the message: "add", "set", or "remove". |
key | string | The specific child key that was affected (e.g. "page1"), or "*" when the key isn’t specific. |
path | string | The dot-separated path of the node where the mutation originated (e.g. "posts"). |
sendRelayMessage callback — for example, to add a room ID, drop messages from certain paths, or log mutations for debugging.
Loop prevention
A client that receives a relay message must not re-broadcast it — otherwise a single mutation would cascade into an infinite relay loop. dragonJSON handles this automatically: when processing an incoming message viaonReceiveCallback, it temporarily sets sendRelayMessage to null before dispatching the local event. This prevents any $on listeners (or internal event dispatch) from triggering a second outbound relay message. The original sendRelayMessage is restored immediately after processing completes.
Server-initiated messages
The relay channel isn’t limited to propagating client mutations. You can also use it for server-push notifications by having your server emit relay-shaped messages on the same channel. The client’s$on listeners will fire when the paths match, giving you a clean hook for real-time server events.
Set up a dedicated path — such as server.relay — and configure your server to emit messages targeting it:
relay.notification and fires the $on listener. You can use this pattern for push notifications, live counters, presence indicators, or any server-driven update.
The relay transport is entirely up to you. dragonJSON only requires two callbacks — one to send and one to receive. Any pub/sub mechanism works.