slap_yjs

slap_yjs durably stores Yjs documents, and keeps each document in sync across the nodes of an Elixir cluster. Yjs is a CRDT library: its shared data types merge concurrent edits from many clients without conflicts, and its protocol exchanges those edits between them. Each document is stored as a snapshot log, using slap_snapshot_log. Clients connected to different nodes edit the same document and see each other's cursors and other Yjs awareness state.

Use slap_yjs when your application uses y_ex, the Elixir bindings for Yjs. Your application is responsible for providing the client connection, forwarding sync and awareness messages, and handling authentication; this package does not start a WebSocket or HTTP server.

About Slap {: .info}

This package is part of Slap.

Slap provides Elixir bindings for SlateDB (an embedded key-value engine optimized for storing data in object storage), as well as services built on top of these bindings. The services run inside your application, on one node or several.

Services:

Building blocks:

Standalone server:

Installation

Add slap_yjs to the dependencies in your application's mix.exs:

defp deps do
  [
    {:slap_yjs, "~> 0.2.0"}
  ]
end

Run mix deps.get. API documentation is on HexDocs. This package requires OTP 26.2 or later (see Requirements).

Example

In an application that depends on slap_yjs, add these children to its supervision tree, in this order:

children = [
  {Slap.Streams.Cluster, store: {:local, "/tmp/slap-yjs"}, shards: 8},
  Slap.Yjs.Docs
]

For an application-owned Streams cluster, use the pattern in the slap_streams README and pass cluster: MyApp.StreamsCluster when starting Slap.Yjs.Docs and calling Slap.Yjs.Store. Run one Streams cluster per VM.

Define a document server module:

defmodule MyApp.DocServer do
  use Slap.Yjs.DocServer
end

With the supervision tree running, join the document from IEx, which acts as the client process:

iex> {:ok, server} = Slap.Yjs.Docs.join(MyApp.DocServer, {"my-service", "doc-1"}); :ok
:ok
iex> is_reference(Process.monitor(server))
true

iex> :ok = Slap.Yjs.DocServer.sync(server, 5_000)
:ok
iex> {:ok, snapshots} = Slap.Yjs.Store.snapshots({"my-service", "doc-1"}); is_list(snapshots)
true

Pass each Yjs sync or awareness message from the client to MyApp.DocServer.process_message_v1(server, message, self()). Encode each {:slap_yjs_update, _, _} and {:slap_yjs_awareness, _, _} message the client process receives with Slap.Yjs.DocServer.encode_message/1, and send it to the client. sync/2 waits for local updates to be stored and for the server to catch up with other nodes.

To read or edit a document from server code, get its Yex.Doc with Slap.Yjs.DocServer.doc/2 and use y_ex's functions on it. To pass settings to your document server module, use the :assigns option of Slap.Yjs.Docs.join/3.

Client behavior

How it works

Requirements

OTP 26.2 or later (mix.exs checks it): the document servers monitor their :pg group, and before 26.2 a process that both monitors and joins a group crashes the :pg scope when it exits.

Tests

mix test

Set SLAP_TEST_S3_ENDPOINT and SLAP_TEST_S3_BUCKET to include the RustFS tests. The multi-node convergence test uses :peer and needs epmd; SLAP_YJS_PROP_SEED replays its edit sequence.

License

Slap is released under the terms of the Apache License 2.0.

Copyright (c) 2026, Michael Russo.