SpacetimeDB — Elixir Client
An Elixir client for SpacetimeDB using the
v1.json.spacetimedb WebSocket subprotocol.
Features
- Full WebSocket lifecycle management via Mint.WebSocket
-
All client→server messages:
Subscribe,SubscribeSingle,SubscribeMulti,Unsubscribe,CallReducer,OneOffQuery - All server→client messages decoded into typed structs
- Automatic reconnection with exponential backoff
- Handler behaviour for clean separation of connection and business logic
- Works as a supervised child in OTP applications
Installation
def deps do
[
{:spacetimedb_ex, "~> 0.1"}
]
endQuick start
{:ok, conn} = SpacetimeDB.start_link(
host: "localhost",
database: "my_module",
handler: %{
on_identity_token: fn token, _ ->
IO.puts("Connected as #{token.identity}")
end,
on_transaction_update: fn update, _ ->
Enum.each(update.tables, fn t ->
IO.puts("#{t.table_name}: +#{length(t.inserts)} -#{length(t.deletes)}")
end)
end
}
)
# Subscribe to a SQL query
SpacetimeDB.subscribe(conn, ["SELECT * FROM Player"])
# Call a reducer
{:ok, request_id} = SpacetimeDB.call_reducer(conn, "CreatePlayer", ["Alice"])
# One-off query (no subscription)
{:ok, msg_id} = SpacetimeDB.one_off_query(conn, "SELECT * FROM Player WHERE name = 'Alice'")Handler behaviour
For production use, implement SpacetimeDB.Handler in a module:
defmodule MyApp.SpacetimeHandler do
@behaviour SpacetimeDB.Handler
@impl true
def on_identity_token(%SpacetimeDB.Types.IdentityToken{} = token, _arg) do
MyApp.Auth.store_token(token.token)
end
@impl true
def on_initial_subscription(%SpacetimeDB.Types.InitialSubscription{} = sub, _arg) do
Enum.each(sub.tables, &MyApp.Cache.seed_table/1)
end
@impl true
def on_transaction_update(%SpacetimeDB.Types.TransactionUpdate{} = update, _arg) do
Enum.each(update.tables, fn t ->
MyApp.Cache.apply_diff(t.table_name, t.inserts, t.deletes)
end)
end
@impl true
def on_disconnect(reason, _arg) do
MyApp.Metrics.record_disconnect(reason)
end
endThen connect with:
{:ok, conn} = SpacetimeDB.start_link(
host: "prod.example.com",
port: 443,
tls: true,
database: "game-prod",
token: System.get_env("SPACETIMEDB_TOKEN"),
handler: MyApp.SpacetimeHandler
)Supervised usage
Add to your application's supervision tree:
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
{SpacetimeDB,
host: "localhost",
database: "my_module",
handler: MyApp.SpacetimeHandler,
name: MyApp.SpacetimeDB}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endOptions
| Option | Type | Default | Description |
|---|---|---|---|
:host | String.t() | required | SpacetimeDB host |
:port | non_neg_integer() | 3000 | Port |
:tls | boolean() | false |
Use TLS (wss://) |
:database | String.t() | required | Database name or identity hex |
:token | String.t() | nil | nil | Auth token (persisted on reconnect) |
:handler | module | {module, term} | map | required |
Callback module, {module, arg} tuple, or map of anonymous functions |
:reconnect | boolean() | true | Auto-reconnect on disconnect |
:reconnect_delay_ms | non_neg_integer() | 500 | Initial reconnect backoff delay |
:max_reconnect_delay_ms | non_neg_integer() | 30_000 | Maximum reconnect backoff delay |
:name | GenServer.name() | — | Registered process name |
Types
All decoded server messages are plain Elixir structs:
| Struct | Description |
|---|---|
SpacetimeDB.Types.IdentityToken | First message — identity + auth token |
SpacetimeDB.Types.InitialSubscription |
Initial rows for a Subscribe call |
SpacetimeDB.Types.SubscribeApplied |
Confirmation for SubscribeSingle / SubscribeMulti |
SpacetimeDB.Types.UnsubscribeApplied |
Confirmation for Unsubscribe |
SpacetimeDB.Types.SubscriptionError | Server rejected a subscription |
SpacetimeDB.Types.TransactionUpdate | Row changes from a committed reducer call |
SpacetimeDB.Types.OneOffQueryResponse | Result of a one-off query |
SpacetimeDB.Types.TableUpdate |
Per-table diff (inserts, deletes) |
SpacetimeDB.Types.ReducerCallInfo |
Metadata in a TransactionUpdate |
SpacetimeDB.Types.Timestamp | Microseconds since Unix epoch |
Architecture
SpacetimeDB (public API)
│
└── SpacetimeDB.Connection (GenServer)
│ WebSocket frames via Mint.WebSocket
│
├── SpacetimeDB.Protocol encode/decode JSON
├── SpacetimeDB.Types typed structs
└── SpacetimeDB.Handler callback behaviour
The Connection GenServer owns a single Mint.HTTP connection upgraded to
WebSocket. Received frames are decoded by SpacetimeDB.Protocol.decode/1 and dispatched to
the handler callbacks. On disconnect the process waits reconnect_delay_ms
(doubling each attempt, capped at max_reconnect_delay_ms) before reconnecting.
Protocol
SpacetimeDB WebSocket URL format:
ws[s]://{host}:{port}/database/{name_or_identity}/subscribe
Subprotocol: v1.json.spacetimedb (text frames, JSON-encoded messages)
The library currently implements the JSON protocol only. BSATN binary protocol
(v1.bsatn.spacetimedb) support is planned for a future release.
License
MIT