Skip to main content

SDK overview

NodalMerge provides JavaScript-first SDK surfaces for building local-first collaborative applications on top of deterministic room history. This page helps you choose an SDK surface and adopt a safe baseline integration pattern.

SDK surfaces

NodalMerge currently exposes two practical JavaScript entry styles:
  • Document-style API (createDoc) for map/text/list-centric collaboration
  • Runtime-oriented SDK (createNodalMergeSdk) for room/sync/replay/offline/topology control
Both sit on top of the same core convergence model and wire protocol.

Which surface should you use?

Use document-style API when:
  • You want ergonomic map/text/list primitives
  • You are building app-level collaborative UX quickly
  • You prefer high-level handles and event hooks
Use runtime-oriented SDK when:
  • You need explicit control over sync/replay/query/projection flows
  • You are building host/runtime integrations
  • You want direct access to room, transport, and topology sub-APIs
You can start with the higher-level surface and move deeper only where needed.

Core integration flow

Most apps follow this sequence:
  1. Initialize SDK with room and transport options
  2. Connect room/session
  3. Apply local writes
  4. Push/flush updates
  5. Subscribe to change/runtime events
  6. Reconcile speculative intent with canonical outcomes
This sequence supports both local-first UX and policy-governed canonical correctness.

Data-model alignment

Use SDK data primitives according to merge semantics:
  • Map for key/value winner semantics
  • Text for collaborative text editing
  • List for ordered item identity workflows
  • Blob references for large binary payloads
Choosing the right primitive is more important than convenience wrappers.

Local-first and canonical refinement

SDK integrations should explicitly separate:
  • Fast speculative user intent
  • Authoritative canonical outcomes
A common namespace pattern:
  • intent/** for optimistic writes
  • world/** for canonical accepted state
This prevents mixing transient intent with durable truth.

Connection, transport, and reconnect

The SDK handles transport lifecycle concerns including:
  • WebSocket session management
  • Reconnect/backoff behavior
  • Optional mesh/direct transport enhancements where available
Build UI states that tolerate reconnect and eventual refinement rather than assuming continuous connectivity.

Presence and ephemeral state

Presence APIs are for real-time ephemeral collaboration state:
  • Cursor positions
  • Live participant context
  • Typing/attention hints
Do not model durable business state in presence channels.

Blob and file handling in SDK integrations

Blob/file transfer is not only a protocol concern. SDK integrations should model binary payload workflows explicitly. Use the SDK CAS surface when your app needs file-like payload behavior:
  • sdk.cas.setBlob(key, bytes) to attach content-addressed bytes and receive a hash pointer
  • sdk.cas.getBlob(hashHex) to resolve bytes when available locally
  • sdk.cas.requestMissingBlobs() to trigger retrieval of missing blob payloads
Keep app state as hash references (for example in world/files/**) rather than embedding raw bytes in normal sync keys. For wire-level transfer mechanics and direct I/O fallback behavior, see protocol/blob-flow.

Persistence strategy

For production local-first behavior, define and test persistence ownership explicitly:
  • What state is cached locally
  • When it is flushed
  • How hydration runs on app start
Validate reconnect and hydration behavior with real offline/restart drills, not only unit tests.

Host-exposed commands (SDK perspective)

The server runtime exposes a set of graph-level introspection and promotion commands beyond normal sync. As an SDK developer, you encounter these in two ways: the SDK wraps some automatically (e.g., sdk.query.readReplayRange), and others are available via direct host-core embedding for custom server builds.
CommandWhat it doesWhen an SDK developer cares
GetFrontierReturns the CRDT frontier (leaf node IDs)Building sync-status UIs, detecting convergence between peers
GetCausalParentsReturns a node’s causal parent IDsGraph visualizers, debugging change history
GetCanonicalResolutionReturns the conflict-resolved map state at the current canonical checkpointConflict inspector UIs, verifying canonical state
ComputeSyncDiffSet difference between the room’s nodes and a peer’s claimed setCustom sync progress displays, diagnosing gaps
InspectPackDecodes pack bytes to extract causal metadata without applying themDebugging packs before import, validation tooling
PromoteCheckpointToGraphPromotes a canonical checkpoint snapshot into the CRDT graph as a synthetic originControls where replay begins; affects getTextCanonical and projection behavior
See api-reference/websocket-commands#graph-introspection-host-core-direct for full request/response schemas.

Checkpoint promotion and the canonical plane

NodalMerge maintains two views of room state:
  • Speculative (intent lane): the local optimistic view, including uncommitted ops
  • Canonical (authoritative lane): the settled, policy-verified, conflict-resolved view
The canonical lane is managed through the Canonical Checkpoint plane — a separate compute path that resolves conflicts and produces a deterministic canonical_hash. PromoteCheckpointToGraph bridges these two planes: it takes a canonical checkpoint (identified by seq, hash, or frontier) and materializes it as a synthetic origin node in the CRDT graph. After promotion:
  • Replay always begins from the promoted node, not original genesis
  • sdk.sync.getTextCanonical() reflects post-promotion state
  • sdk.query.buildProjection() with { selector: "latest" } sees the promoted baseline
As an SDK developer this matters when you are building history panes, undo stacks, or audit views that must distinguish pre- and post-promotion state. Promotion is idempotent — re-promoting the same checkpoint returns the existing node.

Rejections, conflicts, and guardrails

SDK surfaces expose rejection/conflict signals that should feed user-visible and operator-visible handling. Recommended baseline:
  • Handle typed rejection classes where available
  • Record recent rejection/conflict context for diagnostics
  • Avoid silent rollback behavior in UX
Start with:
  1. Single room end-to-end connect/write/read flow
  2. Deterministic primitive modeling (map/text/list)
  3. Reconnect and offline persistence validation
  4. Intent vs canonical refinement flow
  5. Capability-scoped auth integration (if needed)
Then layer advanced query/projection/topology behavior.

Common mistakes

  • Treating SDK transport success as canonical-state success
  • Storing durable domain data in ephemeral presence channels
  • Skipping local persistence drills until late
  • Mixing intent and canonical writes in the same namespace
  • Choosing map/list/text primitives based on convenience instead of merge semantics
  • Treating large binary payloads as normal string/map state instead of using blob references + CAS