Specimen Report
· Zig
spacetimedb-zig
phiat/spacetimedb-zig
Zig SDK for SpacetimeDB with real-time subscriptions, local cache, reducer calls, HTTP REST client, and code generation
- Stars
- ★ 1
- Forks
- ⑂ 0
- Language
- Zig
- Size
- 183 kB
- Last Push
- 2mo ago
- Forged
- 2mo ago
databaserealtimesdkspacetimedbwebsocketzig
# spacetimedb-zig
[](https://ziglang.org)
[](LICENSE)
[](https://github.com/phiat/zigcheck)
SpacetimeDB client library for Zig.
Connects to [SpacetimeDB](https://spacetimedb.com) via the v2 BSATN binary WebSocket protocol, providing real-time subscriptions, reducer calls, a local in-memory client cache, an HTTP REST client, and code generation.
## Features
| Module | Description |
|--------|-------------|
| `bsatn.zig` | Binary codec — encoder, decoder |
| `protocol.zig` | v2 client/server message encoding and decoding |
| `websocket.zig` | WebSocket connection state machine |
| `schema.zig` | Schema fetcher and parser (tables, reducers, typespace) |
| `client_cache.zig` | In-memory local mirror of subscribed tables |
| `client.zig` | High-level client with callbacks and event dispatch |
| `http_client.zig` | HTTP REST client for all v1 API endpoints |
| `table.zig` | Comptime typed table access (BSATN ↔ Zig structs) |
| `codegen.zig` | Code generation from schema |
| `codegen_cli.zig` | CLI entry point (`zig build codegen`) |
## Dependencies
| Dependency | Type | Purpose |
|------------|------|---------|
| [websocket.zig](https://github.com/karlseguin/websocket.zig) | Zig package (required) | WebSocket client transport |
| [zigcheck](https://github.com/phiat/zigcheck) | Zig package (test-only) | Property-based testing for BSATN codec and typed table roundtrips |
| [libbrotlidec](https://github.com/google/brotli) | System library (optional) | Brotli decompression, enabled with `-Denable-brotli=true` |
Everything else uses the Zig standard library: `std.http.Client` for HTTP REST, `std.compress.flate` for gzip, `std.json` for schema parsing.
## Installation
Add to your `build.zig.zon` dependencies:
```zig
.spacetimedb_zig = .{
.url = "https://github.com/phiat/spacetimedb-zig/archive/refs/tags/v0.1.0.tar.gz",
// Run `zig build` once; the compiler will provide the correct .hash value
},
```
Then in `build.zig`:
```zig
const spacetimedb = b.dependency("spacetimedb_zig", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("spacetimedb", spacetimedb.module("spacetimedb"));
```
[GitHub](https://github.com/phiat/spacetimedb-zig)
## Quick Start
### High-Level Client (recommended)
Create a client with event callbacks:
```zig
const stdb = @import("spacetimedb");
const handler = stdb.client.EventHandler{
.ptr = @ptrCast(my_state),
.vtable = &.{
.onConnect = myOnConnect,
.onInsert = myOnInsert,
.onDelete = myOnDelete,
.onUpdate = myOnUpdate,
.onTransaction = myOnTransaction,
.onReducerResult = myOnReducerResult,
},
};
var client = stdb.client.SpacetimeClient.init(allocator, .{
.host = "localhost:3000",
.database = "my_db",
.token = "my-jwt-token",
.subscriptions = &.{"SELECT * FROM users"},
}, handler);
defer client.deinit();
```
Connect and interact:
```zig
// Connect via WebSocket transport
client.connect(transport);
// Subscribe to tables
const qs_id = try client.subscribe(&.{"SELECT * FROM users"});
// Call a reducer (BSATN-encoded args)
const req_id = try client.callReducerRaw("create_user", bsatn_args);
// Query the local cache (untyped)
const row_count = client.count("users");
const rows = try client.getAll("users");
// Query the local cache (typed via comptime)
const users = try client.getTyped(User, "users");
defer allocator.free(users);
// Unsubscribe from a query set
try client.unsubscribe(qs_id);
```
### Client Callbacks
All callbacks are optional:
| Callback | When it fires |
|----------|---------------|
| `onConnect(identity, conn_id, token)` | Initial connection established |
| `onSubscribeApplied(table_name, row_count)` | Subscription data arrives |
| `onUnsubscribeApplied(query_set_id, rows)` | Unsubscription confirmed (rows optionally returned) |
| `onInsert(table_name, row)` | Row inserted |
| `onDelete(table_name, row)` | Row deleted |
| `onUpdate(table_name, old_row, new_row)` | Row replaced (same PK deleted + inserted) |
| `onTransaction(changes)` | Full transaction — return `true` to suppress per-row callbacks |
| `onReducerResult(request_id, result)` | Reducer completes |
| `onQueryResult(request_id, result)` | One-off SQL query completes |
| `onProcedureResult(request_id, status)` | Procedure call completes |
| `onError(message)` | Protocol or transport error |
| `onDisconnect(reason)` | Disconnected |
### Typed Table Access
The `table` module provides comptime-powered direct BSATN-to-struct decoding, bypassing the intermediate `AlgebraicValue` layer:
```zig
const stdb = @import("spacetimedb");
const User = struct {
id: u64,
name: []const u8,
email: ?[]const u8,
};
// Decode a single row from BSATN bytes (allocates — caller must free)
var user = try stdb.table.decodeRow(User, allocator, bsatn_bytes);
defer stdb.table.freeTypedRow(User, allocator, &user);
// Encode back to BSATN
const bytes = try stdb.table.encodeRow(User, allocator, user);
defer allocator.free(bytes);
```
### Code Generation
Generate typed structs, reducer functions, and decode/encode methods from a live database:
```bash
# From a live server
zig build codegen -- --host localhost:3000 --database mydb --output src/generated.zig
# From a JSON schema file
cat schema.json | zig build codegen -- --stdin --output src/generated.zig
```
Produces:
- `pub const TableName = struct { ... }` with `decode`, `encode`, `free` methods
- `pub const Reducers = struct { ... }` with typed call functions
### HTTP REST Client
For operations that don't need a persistent WebSocket (identity management, database admin, ad-hoc SQL):
```zig
const stdb = @import("spacetimedb");
var http_transport = stdb.http_client.StdHttpTransport{};
var client = stdb.http_client.Client.init(allocator, .{
.host = "localhost:3000",
.database = "my_db",
.token = "my-jwt",
}, http_transport.transport());
// Identity
const identity_json = try client.createIdentity();
// SQL query
const sql_resp = try client.executeSql("SELECT * FROM users");
// Call a reducer over HTTP
const resp = try client.callReducer("create_user", args_json);
// Database management
const db_info = try client.getDatabase("my_db");
const names = try client.getDatabaseNames("my_db");
```
## 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
- 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`.
### Layer Diagram
```
┌─────────────────────────────────────────┐
│ client.zig — SpacetimeClient │
│ (connect, subscribe, call, query) │
├─────────────┬─────────────┬─────────────┤
│ websocket │ client │ http_client │
│ .zig │ _cache.zig │ .zig │
├─────────────┴──────┬──────┴─────────────┤
│ table.zig │ codegen.zig │
├────────────────────┴────────────────────┤
│ protocol.zig │ schema.zig │
├────────────────────┴────────────────────┤
│ row_decoder.zig │ value_encoder.zig │
├────────────────────┴────────────────────┤
│ bsatn.zig │ types.zig │
└─────────────────────────────────────────┘
```
## Development
```bash
zig build # Build the library
zig build test # Unit tests (no server needed)
zig build integration-test # All tests (requires SpacetimeDB on :3000)
zig build check # Type-check only (fast)
```
With optional brotli decompression:
```bash
zig build -Denable-brotli=true # Build with brotli
zig build test -Denable-brotli=true # Test with brotli
```
Or using [just](https://github.com/casey/just):
```bash
just build # Build
just test # Unit tests
just integration-test # Integration tests
just check # Type-check only
just fmt # Format source
```
See [`justfile`](https://github.com/casey/just) for all available commands.
## License
MIT