Skip to main content

JavaScript SDK

The JavaScript SDK gives you a practical API for room connection, synchronized state updates, replay-aware reads, and collaboration signals. Use this page as your implementation baseline for browser or JS runtime clients.

Install

Minimal setup

Read and write state

Call sdk.sync.push() after local mutations to flush updates. push() returns a booleantrue if a pack was actually sent, false if there was nothing pending. It sends only the delta accumulated since the last successful push (tracked internally), not a full state export. sdk.sync.get() returns the speculative/intent-lane view (including your own unconfirmed writes); sdk.sync.getCanonical() returns the authoritative canonical-lane view — the same intent-vs-canonical distinction described for text reads below. Push acknowledgement and retry. The server responds to each push() with a pack-ack message reporting accepted/rejected counts. The SDK handles this automatically: if any nodes are rejected, it reverts the corresponding local optimistic state and resends. You don’t need to handle pack-ack directly unless you’re building custom sync-status UI — see protocol/websocket-messages and api-reference/websocket-commands#pack-ack-server-client. Pack size limit. A single pushed pack is capped at 60 KiB of base64 (61,440 characters). Pushes exceeding this are rejected client-side — the SDK reverts the pending marks and emits an "error" event rather than sending an oversized frame. Batch large sets of writes into multiple smaller push() calls if you hit this.

Text operations

Use positional helpers for collaborative text edits:

Text range anchors

When your editor needs stable positions under concurrent edits, use anchor-based helpers. Anchors identify where to insert or delete relative to the document structure rather than a raw integer offset that may shift under concurrent changes. Insert anchors (TextInsertAnchor): Delete anchors (TextDeleteAnchor):
Prefer anchor-based APIs in editors that maintain cursor/selection as logical positions rather than integer offsets; they survive concurrent insertions at the same position without extra rebasing logic.

Text reads and attribution

For attribution UIs (per-glyph author), read the visible RGA sequence:
getTextAtLamport / getTextSequenceAtLamport replay only DAG nodes with transaction.lamport <= maxLamport (tombstones included). The speculative read (getText) returns the intent-lane view; getTextCanonical returns the canonical lane view. Try the interactive surface: Collab text playground (http://127.0.0.1:4177, default room collab-text).

Presence and ephemeral collaboration

Presence is for live ephemeral state, not durable business data:
Use presence for cursor/typing/view state. Persist durable facts via sync state instead.

Blob and file storage patterns

For file-like payloads, use the SDK CAS APIs and keep sync state as references:
Later, resolve bytes by hash:
When a referenced blob is missing locally, request retrieval:
This keeps large binary payloads out of normal key/value state while preserving deterministic references.

Runtime events

Subscribe to runtime messages for observability and integration hooks:
Use this stream for diagnostics and advanced UX instrumentation.

Offline and persistence

SDK can queue and flush writes across reconnect boundaries. For peer-local durability, enable persistence explicitly:
Validate restart and reconnect behavior with realistic offline drills before shipping.

Intent vs canonical pattern

Model local optimism and authoritative outcomes in separate namespaces:
  • intent/** for speculative writes
  • world/** for canonical accepted state
This keeps your UI responsive while preserving deterministic refinement behavior.

Replay range read

Retrieve a paginated history of key mutations from the server, useful for activity feeds, audit trails, and undo stacks:
For local-only history (offline/unsent ops), use sdk.sync.readLocalReplayRange which returns synchronously from the WASM store.

Query and projection helpers

Runtime query APIs support canonical-lane analytics and read-model workflows. Use sdk.query when you need structured projections over room state rather than direct key access. Register a query spec (once per descriptor version):
Build a projection at a specific checkpoint:
Read paginated results:
Invalidate or list projections:

Transport options

transport.mode commonly uses:
  • "auto": negotiate a WebRTC data channel alongside the WebSocket connection ("ws+webrtc"), falling back to WebSocket-only when WebRTC signaling isn’t available
  • "ws-only": force WebSocket-only behavior
The SDK default is "ws-only", not "auto" — every example on this page sets transport: { mode: "auto" } explicitly rather than relying on the default, since auto is the better choice for most browser apps. Omit the option (or pass "ws-only" explicitly) only when environment constraints require strict WebSocket-only transport.

Error and rejection handling

Treat rejection/error signals as first-class UX and ops inputs:
  • Surface user-meaningful failure states
  • Log structured rejection context
  • Avoid silent rollback paths
In policy-governed systems, rejection handling is part of normal operation.

Configuration reference

All options passed to createNodalMergeSdk: offline.persistenceKey controls outbox persistence (localStorage) and is independent of persistence.* which controls graph/node durability (IndexedDB).

Production checklist

  • Durable server storage enabled
  • Reconnect policy configured and tested
  • Local persistence strategy validated
  • Intent/canonical namespaces separated
  • Presence restricted to ephemeral signals
  • Runtime event/rejection telemetry wired

Common mistakes

  • Forgetting sdk.sync.push() after local writes
  • Using presence for durable domain state
  • Storing file/blob bytes directly in sync string keys instead of hash references
  • Mixing optimistic and canonical writes in one keyspace
  • Assuming online-only behavior during QA
  • Ignoring rejection pathways until late integration