Any HTTP server that implements this protocol is compatible with the dragonJSON client. The spec is transport-agnostic — implement it in any language or framework you like. The client handles caching, event dispatching, and relay; the server only needs to handle data and mutations.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.
Base URL
All requests are sent to the single URL passed todragonJSON(url, options). There is no routing by path beyond GET vs POST — the operation type is encoded in the query string or request body.
Reading Data
Single-path read
The client issues aGET request with a path query parameter. The value is a dot-separated string that maps to the nested structure of your data store. An empty or absent path means the root.
Batched read
WhenenableBatching: true (the default), the client coalesces rapid parallel requests and sends them together using the paths parameter — a JSON-encoded array of path strings.
Freeform query ($get)
When the client calls .$get(command), the command object is JSON-encoded and appended as a command query parameter alongside the path. The result is returned directly to the caller and is never stored in the client cache.
command is entirely up to your implementation. Return any valid JSON.
Special Response Fields
Object responses may include the following reserved fields to control how the client loads children:Signals that this object has children that have not been sent yet. The client
stores a placeholder and re-fetches the real data the first time a child is
accessed. Use this to defer loading of expensive sub-trees.
Signals that this object has unknown or dynamic children — keys the client
does not know in advance. The client will fetch any key accessed under this
node on demand rather than treating it as not found. Use this for open-ended
collections such as user-generated content or dynamic routes.
A hierarchical batch envelope. Allows the server to return multiple paths in a
single response, saving round-trips. See the
Hierarchical batch section below for the full
shape.
Hierarchical Batch (Optional)
WhenenableHierarchicalBatch: true is set in client options, the client adds two extra query parameters to reads:
target tells the server which path the client is ultimately trying to reach. The server may respond with a __batch envelope containing multiple paths at once, saving the client extra round-trips:
If you do not want to implement this optimisation, simply respond with the
normal value for
path and ignore target and hierarchical. The client
detects the absence of __batch and wraps the response automatically.Mutations
All mutations arePOST requests. The path being mutated is encoded in the query string, exactly like a read. The Content-Type must be application/json.
$set
The body is the new value at path. It can be any JSON type — no __op field is used.
$add
The body carries __op: "add" and a value. The client may suggest a key; the server may accept or override it. If key is omitted, the server assigns one.
$remove
The path being deleted is in the query string. The body is always { "__op": "remove" }.
Mutation response contract
The client marks each listed path as stale and re-fetches it on next access. It also fires any.$on() listeners registered on those paths.
Authentication
If the client is initialised withauth: "Bearer my-token", every request includes:
403 to deny access.
Custom headers supplied via headers: {} in client options are also forwarded verbatim on every request, so you can use any header-based authentication or metadata scheme your stack requires.
Error Handling
Return a non-2xx HTTP status code for errors. The client rejects the corresponding promise with a message containing the status code and path. Error response bodies are not parsed by the client — they are available for your own logging.Live Invalidation Relay
If the client is configured withliveInvalidation, it sends a relay message to the relay channel after every mutation:
.$on() listeners locally — exactly as if the mutation had happened on their own instance.
You can also use the relay channel for server-initiated messages — for example, push notifications — by emitting relay-shaped messages from the server to any path your clients are listening on.
Path Conventions
- Paths use
.as a separator:posts.page1.title - An empty string or absent
pathrefers to the root of the data store - Path segments must not contain
.(dots within a segment are not supported) - Path segments must not start with
$(reserved for client methods) - The segment names
thenandexistsare reserved and must not be used as data keys
Compliance Checklist
A compliant dragonJSON server must implement all of the following:- Accept
GET ?path=and return the JSON value at that path - Accept
GET ?paths=(JSON-encoded array) and return a flat{ "path": value }object - Accept
POST ?path=with a JSON body and return{ "invalidate": [...] } - Distinguish
$add(__op: "add") and$remove(__op: "remove") from plain$set(no__opfield) - Accept
GET ?path=&command=and return any JSON for freeform queries - Return
null(not omit) for paths that do not exist in batch responses - Always return
{ "invalidate": [...] }from mutations, even when the array is empty
- Support
__next: trueon object responses to defer expensive sub-trees - Support
__more: trueon object responses with dynamic key sets - Support
GET ?path=&hierarchical=true&target=with a__batchresponse envelope - Implement a relay channel for live invalidation across clients
Quick Reference
| Client call | Method | Query params | Body |
|---|---|---|---|
await server.posts.page1 | GET | path=posts.page1 | — |
await Promise.all([server.posts.page1, server.meta]) | GET | paths=["posts.page1","meta"] | — |
server.posts.$get({ action: "paginate" }) | GET | path=posts&command={...} | — |
server.posts.page1.$set({ title: "Hi" }) | POST | path=posts.page1 | {"title":"Hi"} |
server.posts.$add({ title: "New" }) | POST | path=posts | {"__op":"add","value":{...}} |
server.posts.page1.$remove() | POST | path=posts.page1 | {"__op":"remove"} |