Skip to main content

Documentation 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.

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’s 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:
1

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.
2

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.
3

Trigger re-fetches on next access

Because the paths are now stale, the next await on any of them will fetch fresh data from the server automatically.
No server-side coordination is required beyond broadcasting the relay message. The server only needs to handle mutations and reads as it normally would.

Setup with WebSocket

The most common configuration is a Socket.io (or any WebSocket) relay. Pass sendRelayMessage and onReceiveCallback in the liveInvalidation option:
const [server] = dragonJSON("https://api.example.com/data", {
  liveInvalidation: {
    sendRelayMessage: (msg) => socket.emit("invalidate", msg),
    onReceiveCallback: (cb) => socket.on("invalidate", cb),
  }
});
  • 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.
When any client mutates data, the relay message is emitted. All other connected clients receive it, invalidate the affected paths, and fire their listeners — all without any extra code on your part.

Setup with BroadcastChannel

For multi-tab synchronisation in the browser, the BroadcastChannel API works as a zero-dependency relay:
const channel = new BroadcastChannel("dragonjson-relay");

const [server] = dragonJSON("https://api.example.com/data", {
  liveInvalidation: {
    sendRelayMessage: (msg) => channel.postMessage(msg),
    onReceiveCallback: (cb) => channel.addEventListener("message", (e) => cb(e.data)),
  }
});
Any mutation in one tab is broadcast to all other tabs sharing the same channel name. Each tab invalidates its own cache and updates its UI independently.

Relay message shape

Every relay message is a plain JSON-serialisable object with four fields:
{
  "invalidate": ["posts.page1"],
  "initiator": "set",
  "key": "page1",
  "path": "posts"
}
FieldTypeDescription
invalidatestring[]Dot-separated paths to mark stale in the receiving client’s cache.
initiatorstringThe operation that triggered the message: "add", "set", or "remove".
keystringThe specific child key that was affected (e.g. "page1"), or "*" when the key isn’t specific.
pathstringThe dot-separated path of the node where the mutation originated (e.g. "posts").
You can inspect or filter relay messages in your 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 via onReceiveCallback, 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.
Do not manually call sendRelayMessage inside a $on listener that responds to relay-driven events — dragonJSON’s loop prevention already handles this, and a manual call would bypass it.

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:
// Server emits:
// { invalidate: ["relay.notification"], initiator: "set", key: "notification", path: "relay" }

server.relay.$on("set", "notification", (e) => {
  console.log("Server notification received");
});
The client treats the incoming relay message identically to a local mutation — it invalidates 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.

Build docs developers (and LLMs) love