SpacetimeDB Development
SpacetimeDB is a relational database system that runs your entire application server logic inside the database as WebAssembly (Wasm). Clients connect directly to the database via WebSocket, subscribing to live query updates and invoking server-side transaction-atomic reducers.
When to Use This Skill
- Creating, writing, or refactoring SpacetimeDB server-side database modules (in Rust, TypeScript, C#, or C++).
- Defining table schemas, indexes, constraints, event tables, and schedule tables.
- Implementing transaction-atomic reducers and read-only views.
- Developing client-side integration using TypeScript, Rust, C#, Unreal Engine, Godot, or Unity.
- Working with the
spacetime command-line interface (CLI) to develop, run, and publish modules.
1. Core Architecture
In SpacetimeDB, the database is the server. You do not write a separate API gateway.
graph TD
Client[Client SDK / WebSockets] -->|Invoke Reducer / Subscribe| DB[SpacetimeDB Host]
DB -->|Execute in Wasm| Module[Database Module: Rust/TS/C#]
Module -->|Read/Write In-Memory Tables| DB
DB -->|WAL Persistence| Commitlog[(Commitlog on Disk)]
Key Pillars
- In-Memory Speed & Durability: All database data is stored in memory for microsecond-latency access. Transactions are written to a write-ahead log (Commitlog) on disk for durability and crash recovery.
- Serverless Wasm Execution: All server-side logic (reducers, views) runs sandboxed inside the database engine via WebAssembly.
- Real-Time Subscriptions: Instead of polling or building polling API endpoints, clients subscribe to SQL queries. SpacetimeDB pushes incremental diffs over WebSockets whenever matching data changes.
- Transaction Atomicity: Every reducer execution is an isolated database transaction. If a reducer returns an error or panics, the entire transaction is rolled back.
2. Table Semantics
Data in SpacetimeDB is stored in tables. You declare tables using macros/decorators in your language of choice.
Table Configurations & Attributes
- Public vs. Private: Tables are private by default - queryable only by server-side code (reducers and views). Add the
public attribute (#[table(accessor = <name>, public)] in Rust) to make a table readable by any connected client.
- Primary Keys: Used to uniquely identify rows (
#[primary_key]).
- Unique Constraints: Prevent duplicate values in specified columns (
#[unique]).
- Auto-Increment: Automatically generates unique integer sequences for new rows (
#[auto_inc]).
- Indexes: Accelerate lookups on specific fields (
#[index(btree)]).
Special-Purpose Tables
- Event Tables: Transient tables designed for notifying clients of instantaneous actions (e.g., "entity took 50 damage") without storing the data permanently on disk. They do not participate in WAL storage.
- Schedule Tables: Tables that trigger reducers or procedures at specific times by including a special scheduling column. Perfect for cron tasks, delayed actions, or timeout triggers.
3. Reducers, Procedures, and Views
Modules expose functions to the outside world. They are classified as follows:
| Feature / Property |
Reducers |
Procedures |
Views |
| State Mutation |
Yes (Read/Write) |
No (Read-Only) |
No (Read-Only) |
| Transaction Boundary |
Starts a new transaction |
Runs outside or inherits |
Read-Only transaction |
| Client Invocation |
Yes (via WS/HTTP) |
Yes (via WS/HTTP) |
Yes (via WS/HTTP) |
| Typical Use |
Modifying state / actions |
Complex read workflows |
Aggregating & filtering |
Reducer Context (ReducerContext)
Every reducer receives a context object as its first argument containing:
sender: The Identity of the client calling the reducer.
db: The database interface to query/mutate tables.
timestamp: The execution time of the transaction.
rng: A deterministic random number generator.
Lifecycle Reducers
Special reducers invoked by the database host during system lifecycle events:
__init__: Runs once when the database is first initialized.
__connect__: Runs when a client connects.
__disconnect__: Runs when a client disconnects.
__update__: Runs when a new version of the module is published.
4. Rust Module Example
Below is a complete, minimal Rust module defining tables and a reducer.
use spacetimedb::{reducer, table, Identity, ReducerContext, Table};
// Define a public table for user profiles
#[table(accessor = user_profile, public)]
pub struct UserProfile {
#[primary_key]
pub identity: Identity,
pub username: String,
pub online: bool,
}
// Define a private table for handling timeouts (tables are private by default)
#[table(accessor = heartbeat_timeout)]
pub struct HeartbeatTimeout {
#[primary_key]
pub identity: Identity,
pub scheduled_time: u64,
}
// Reducer called by clients to register or update their username
#[reducer]
pub fn register_user(ctx: &ReducerContext, username: String) -> Result<(), String> {
if username.trim().is_empty() {
return Err("Username cannot be empty".to_string());
}
if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender()) {
profile.username = username;
ctx.db.user_profile().identity().update(profile);
} else {
ctx.db.user_profile().try_insert(UserProfile {
identity: ctx.sender(),
username,
online: true,
})?;
}
Ok(())
}
// Connection lifecycle hook
#[reducer(client_connected)]
pub fn identity_connected(ctx: &ReducerContext) {
if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender()) {
profile.online = true;
ctx.db.user_profile().identity().update(profile);
}
}
5. CLI & Deployment commands
Use the spacetime CLI to build, test, and host your modules.
| Command |
Description |
spacetime start |
Starts a local SpacetimeDB standalone database instance. |
spacetime dev |
Starts interactive development mode, auto-publishing changes on save. |
spacetime publish <name> |
Compiles your module to Wasm and deploys it to the database. |
spacetime generate --lang <lang> <db-name> --out-dir <dir> |
Generates type-safe client bindings (TypeScript, Rust, C#, C++). |
spacetime logs <db-name> |
Streams server-side execution logs. |
6. Best Practices
- Do: Keep reducers deterministic. Never fetch external web APIs or read system time directly inside a reducer; always use the values provided in
ReducerContext (e.g. ctx.timestamp, ctx.rng).
- Do: Use event tables for ephemeral real-time updates (e.g., chat messages, position updates in game loops) to save disk I/O and prevent database bloat.
- Do: Secure private tables. If a client should not see the data, make the table private.
- Don't: Perform long, blocking CPU tasks inside a reducer. Since reducers run in transactions, blocking them halts database throughput.
- Don't: Run manual migration scripts for simple additions. SpacetimeDB supports automatic schema migrations for backwards-compatible modifications.
1---2name: spacetimedb3description: Expert guidance for developing, publishing, and debugging SpacetimeDB database modules (in Rust, C#, TypeScript, C++) and connecting real-time clients. Use when asked to write SpacetimeDB table schemas, reducers, views, schedule/event tables, or when using the spacetime CLI, generating client SDK bindings, or implementing WebSocket subscriptions.4---56# SpacetimeDB Development78SpacetimeDB is a relational database system that runs your entire application server logic inside the database as WebAssembly (Wasm). Clients connect directly to the database via WebSocket, subscribing to live query updates and invoking server-side transaction-atomic reducers.910## When to Use This Skill1112- Creating, writing, or refactoring SpacetimeDB server-side database modules (in Rust, TypeScript, C#, or C++).13- Defining table schemas, indexes, constraints, event tables, and schedule tables.14- Implementing transaction-atomic reducers and read-only views.15- Developing client-side integration using TypeScript, Rust, C#, Unreal Engine, Godot, or Unity.16- Working with the `spacetime` command-line interface (CLI) to develop, run, and publish modules.1718---1920## 1. Core Architecture2122In SpacetimeDB, the database _is_ the server. You do not write a separate API gateway.2324```mermaid25graph TD26 Client[Client SDK / WebSockets] -->|Invoke Reducer / Subscribe| DB[SpacetimeDB Host]27 DB -->|Execute in Wasm| Module[Database Module: Rust/TS/C#]28 Module -->|Read/Write In-Memory Tables| DB29 DB -->|WAL Persistence| Commitlog[(Commitlog on Disk)]30```3132### Key Pillars33341. **In-Memory Speed & Durability**: All database data is stored in memory for microsecond-latency access. Transactions are written to a write-ahead log (Commitlog) on disk for durability and crash recovery.352. **Serverless Wasm Execution**: All server-side logic (reducers, views) runs sandboxed inside the database engine via WebAssembly.363. **Real-Time Subscriptions**: Instead of polling or building polling API endpoints, clients subscribe to SQL queries. SpacetimeDB pushes incremental diffs over WebSockets whenever matching data changes.374. **Transaction Atomicity**: Every reducer execution is an isolated database transaction. If a reducer returns an error or panics, the entire transaction is rolled back.3839---4041## 2. Table Semantics4243Data in SpacetimeDB is stored in tables. You declare tables using macros/decorators in your language of choice.4445### Table Configurations & Attributes4647- **Public vs. Private**: Tables are private by default - queryable only by server-side code (reducers and views). Add the `public` attribute (`#[table(accessor = <name>, public)]` in Rust) to make a table readable by any connected client.48- **Primary Keys**: Used to uniquely identify rows (`#[primary_key]`).49- **Unique Constraints**: Prevent duplicate values in specified columns (`#[unique]`).50- **Auto-Increment**: Automatically generates unique integer sequences for new rows (`#[auto_inc]`).51- **Indexes**: Accelerate lookups on specific fields (`#[index(btree)]`).5253### Special-Purpose Tables5455- **Event Tables**: Transient tables designed for notifying clients of instantaneous actions (e.g., "entity took 50 damage") without storing the data permanently on disk. They do not participate in WAL storage.56- **Schedule Tables**: Tables that trigger reducers or procedures at specific times by including a special scheduling column. Perfect for cron tasks, delayed actions, or timeout triggers.5758---5960## 3. Reducers, Procedures, and Views6162Modules expose functions to the outside world. They are classified as follows:6364| Feature / Property | Reducers | Procedures | Views |65| :----------------------- | :------------------------ | :----------------------- | :---------------------- |66| **State Mutation** | Yes (Read/Write) | No (Read-Only) | No (Read-Only) |67| **Transaction Boundary** | Starts a new transaction | Runs outside or inherits | Read-Only transaction |68| **Client Invocation** | Yes (via WS/HTTP) | Yes (via WS/HTTP) | Yes (via WS/HTTP) |69| **Typical Use** | Modifying state / actions | Complex read workflows | Aggregating & filtering |7071### Reducer Context (`ReducerContext`)7273Every reducer receives a context object as its first argument containing:7475- `sender`: The `Identity` of the client calling the reducer.76- `db`: The database interface to query/mutate tables.77- `timestamp`: The execution time of the transaction.78- `rng`: A deterministic random number generator.7980### Lifecycle Reducers8182Special reducers invoked by the database host during system lifecycle events:8384- `__init__`: Runs once when the database is first initialized.85- `__connect__`: Runs when a client connects.86- `__disconnect__`: Runs when a client disconnects.87- `__update__`: Runs when a new version of the module is published.8889---9091## 4. Rust Module Example9293Below is a complete, minimal Rust module defining tables and a reducer.9495```rust96use spacetimedb::{reducer, table, Identity, ReducerContext, Table};9798// Define a public table for user profiles99#[table(accessor = user_profile, public)]100pub struct UserProfile {101 #[primary_key]102 pub identity: Identity,103 pub username: String,104 pub online: bool,105}106107// Define a private table for handling timeouts (tables are private by default)108#[table(accessor = heartbeat_timeout)]109pub struct HeartbeatTimeout {110 #[primary_key]111 pub identity: Identity,112 pub scheduled_time: u64,113}114115// Reducer called by clients to register or update their username116#[reducer]117pub fn register_user(ctx: &ReducerContext, username: String) -> Result<(), String> {118 if username.trim().is_empty() {119 return Err("Username cannot be empty".to_string());120 }121122 if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender()) {123 profile.username = username;124 ctx.db.user_profile().identity().update(profile);125 } else {126 ctx.db.user_profile().try_insert(UserProfile {127 identity: ctx.sender(),128 username,129 online: true,130 })?;131 }132133 Ok(())134}135136// Connection lifecycle hook137#[reducer(client_connected)]138pub fn identity_connected(ctx: &ReducerContext) {139 if let Some(mut profile) = ctx.db.user_profile().identity().find(ctx.sender()) {140 profile.online = true;141 ctx.db.user_profile().identity().update(profile);142 }143}144```145146---147148## 5. CLI & Deployment commands149150Use the `spacetime` CLI to build, test, and host your modules.151152| Command | Description |153| :----------------------------------------------------------- | :-------------------------------------------------------------------- |154| `spacetime start` | Starts a local SpacetimeDB standalone database instance. |155| `spacetime dev` | Starts interactive development mode, auto-publishing changes on save. |156| `spacetime publish <name>` | Compiles your module to Wasm and deploys it to the database. |157| `spacetime generate --lang <lang> <db-name> --out-dir <dir>` | Generates type-safe client bindings (TypeScript, Rust, C#, C++). |158| `spacetime logs <db-name>` | Streams server-side execution logs. |159160---161162## 6. Best Practices163164- **Do:** Keep reducers deterministic. Never fetch external web APIs or read system time directly inside a reducer; always use the values provided in `ReducerContext` (e.g. `ctx.timestamp`, `ctx.rng`).165- **Do:** Use event tables for ephemeral real-time updates (e.g., chat messages, position updates in game loops) to save disk I/O and prevent database bloat.166- **Do:** Secure private tables. If a client should not see the data, make the table private.167- **Don't:** Perform long, blocking CPU tasks inside a reducer. Since reducers run in transactions, blocking them halts database throughput.168- **Don't:** Run manual migration scripts for simple additions. SpacetimeDB supports automatic schema migrations for backwards-compatible modifications.