Skip to content

phiat/spacetimedbex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spacetimedbex

Hex.pm Hex Docs Elixir License: MIT

SpacetimeDB client library for Elixir.

Connects to SpacetimeDB via the v2 BSATN binary WebSocket protocol, providing real-time subscriptions, reducer calls, a local ETS-backed client cache, an HTTP REST client, Phoenix PubSub integration, and code generation.

Features

Module Description
Spacetimedbex.BSATN Binary codec — encoder, decoder, value encoder
Spacetimedbex.Protocol v2 client/server message encoding and decoding
Spacetimedbex.Connection WebSocket connection with auto-reconnect and backoff
Spacetimedbex.Schema Schema fetcher and parser (tables, reducers, typespace)
Spacetimedbex.ClientCache ETS-backed local mirror of subscribed tables
Spacetimedbex.Client High-level client with callbacks and auto-encoding
Spacetimedbex.Http HTTP REST client for all v1 API endpoints
Spacetimedbex.Phoenix Phoenix PubSub adapter for broadcasting events
Spacetimedbex.Codegen Code generation from schema
mix spacetimedb.gen Mix task to generate structs, reducers, and client

Installation

# mix.exs
def deps do
  [
    {:spacetimedbex, "~> 0.1.1"}
  ]
end

Documentation | Hex | GitHub

Quick Start

High-Level Client (recommended)

Define a client module with callbacks:

defmodule MyApp.SpaceClient do
  use Spacetimedbex.Client

  def config do
    %{
      host: "localhost:3000",
      database: "my_db",
      subscriptions: ["SELECT * FROM users"]
    }
  end

  def on_connect(_identity, _conn_id, token, state) do
    {:ok, Map.put(state, :token, token)}
  end

  def on_insert("users", row, state) do
    IO.puts("New user: #{inspect(row)}")
    {:ok, state}
  end

  def on_update("users", old_row, new_row, state) do
    IO.puts("Updated: #{inspect(old_row)}#{inspect(new_row)}")
    {:ok, state}
  end

  def on_delete("users", row, state) do
    IO.puts("Removed: #{inspect(row)}")
    {:ok, state}
  end
end

Start it and interact:

{:ok, pid} = Spacetimedbex.Client.start_link(MyApp.SpaceClient, %{})

# Call a reducer (auto-encodes args via schema)
Spacetimedbex.Client.call_reducer(pid, "create_user", %{"name" => "Alice", "age" => 30})

# Query the local cache
Spacetimedbex.Client.get_all(pid, "users")
Spacetimedbex.Client.find(pid, "users", 1)

# One-off SQL query via WebSocket
Spacetimedbex.Client.query(pid, "SELECT * FROM users WHERE age > 25")

# Unsubscribe from a query set
Spacetimedbex.Client.unsubscribe(pid, query_set_id)

Client Callbacks

All callbacks are optional except config/0:

Callback When it fires
on_connect(identity, conn_id, token, state) Initial connection established
on_subscribe_applied(table, rows, state) Subscription data arrives
on_insert(table, row, state) Row inserted
on_delete(table, row, state) Row deleted
on_update(table, old_row, new_row, state) Row replaced (same PK deleted + inserted)
on_transaction(changes, state) Full transaction — return {:ok, state, :skip_row_callbacks} to suppress per-row callbacks
on_reducer_result(request_id, result, state) Reducer completes
on_unsubscribe_applied(query_set_id, rows, state) Unsubscribe completes
on_query_result(request_id, result, state) One-off query result arrives
on_disconnect(reason, state) Disconnected

Code Generation

Generate typed structs, reducer functions, and a client skeleton from a live database:

mix spacetimedb.gen \
  --host localhost:3000 \
  --database my_db \
  --module MyApp.SpacetimeDB \
  --output lib

Produces:

  • MyApp.SpacetimeDB.Tables.TableNamedefstruct + @type t + from_row/1
  • MyApp.SpacetimeDB.Reducers — typed functions with @spec
  • MyApp.SpacetimeDB.Clientuse Spacetimedbex.Client skeleton with config

HTTP REST Client

For operations that don't need a persistent WebSocket (identity management, database admin, ad-hoc SQL):

alias Spacetimedbex.Http

# Identity
{:ok, %{"identity" => id, "token" => token}} = Http.create_identity("localhost:3000")

# SQL query
{:ok, results} = Http.sql("localhost:3000", "my_db", "SELECT * FROM users", token)

# Call a reducer over HTTP
:ok = Http.call_reducer("localhost:3000", "my_db", "create_user", ["Alice", 30], token)

# Database management
{:ok, _} = Http.publish_database("localhost:3000", "my_db", wasm_binary, token)
{:ok, info} = Http.get_database("localhost:3000", "my_db")

Low-Level Connection

For full control over the WebSocket connection:

{:ok, conn} = Spacetimedbex.Connection.start_link(
  host: "localhost:3000",
  database: "my_db",
  handler: self()
)

# Messages arrive as {:spacetimedb, msg} tuples
receive do
  {:spacetimedb, {:identity, identity, conn_id, token}} -> :connected
end

Spacetimedbex.Connection.subscribe(conn, ["SELECT * FROM users"])
Spacetimedbex.Connection.call_reducer(conn, "create_user", bsatn_args)

Architecture

BSATN Codec

Binary SpacetimeDB Algebraic Type Notation — a compact little-endian binary format:

  • Integers: u8..u256, i8..i256 (little-endian)
  • Floats: f32, f64 (IEEE 754, little-endian)
  • Strings/Bytes: u32 length prefix + raw data (UTF-8 validated)
  • Arrays: u32 count prefix + concatenated elements
  • Products (structs): fields concatenated in order
  • Sums (enums): u8 variant tag + payload

Protocol (v2)

Client sends: Subscribe, Unsubscribe, OneOffQuery, CallReducer, CallProcedure.

Server sends (with 1-byte compression envelope): InitialConnection, SubscribeApplied, UnsubscribeApplied, SubscriptionError, TransactionUpdate, OneOffQueryResult, ReducerResult, ProcedureResult.

OTP Design

Application
├── Connection (WebSockex) — WebSocket with auto-reconnect
├── ClientCache (GenServer) — ETS-backed row storage
├── Schema — HTTP schema fetch + parse
└── Client (GenServer) — ties it all together with callbacks

Development

mix deps.get          # Install dependencies
just test             # Unit tests (no server needed)
just test-all         # All tests (requires SpacetimeDB on :3000)
just check            # Compile (strict) + test + credo
just shell            # iex -S mix

See justfile for all available commands.

License

MIT

About

Elixir SDK for SpacetimeDB with real-time subscriptions, local ETS cache, reducer calls, HTTP REST client, and code generation

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages