douglance/spacetimedb/spacetimedb-rust

Claude Code and agent skill by douglance/spacetimedb

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Develop SpacetimeDB server modules in Rust. Use when writing reducers, tables, or module logic.

Install

npx vskill@latest install douglance/spacetimedb/spacetimedb-rust

SKILL.md

SpacetimeDB Rust Module Development

SpacetimeDB modules are WebAssembly applications that run inside the database. They define tables to store data and reducers to modify data. Clients connect directly to the database and execute application logic inside it.

Tested with: SpacetimeDB runtime 1.11.x, spacetimedb crate 1.1.x

HALLUCINATED APIs — DO NOT USE

These APIs DO NOT EXIST. LLMs frequently hallucinate them.

rust
// WRONG — these macros/attributes don't exist
#[spacetimedb::table] // Use #[table] after importing
#[spacetimedb::reducer] // Use #[reducer] after importing
#[derive(Table)] // Tables use #[table] attribute, not derive
#[derive(Reducer)] // Reducers use #[reducer] attribute
// WRONG — SpacetimeType on tables
#[derive(SpacetimeType)] // DO NOT use on #[table] structs!
#[table(name = my_table)]
pub struct MyTable { ... }
// WRONG — mutable context
pub fn my_reducer(ctx: &mut ReducerContext, ...) { } // Should be &ReducerContext
// WRONG — table access without parentheses
ctx.db.player // Should be ctx.db.player()
ctx.db.player.find(id) // Should be ctx.db.player().id().find(&id)
CORRECT PATTERNS:
rust
// CORRECT IMPORTS
use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp};
use spacetimedb::SpacetimeType; // Only for custom types, NOT tables
// CORRECT TABLE — no SpacetimeType derive!
#[table(name = player, public)]
pub struct Player {
#[primary_key]
pub id: u64,
pub name: String,
}
// CORRECT REDUCER — immutable context reference
#[reducer]
pub fn create_player(ctx: &ReducerContext, name: String) {
ctx.db.player().insert(Player { id: 0, name });
}
// CORRECT TABLE ACCESS — methods with parentheses
let player = ctx.db.player().id().find(&player_id);
DO NOT:
  • Derive `SpacetimeType` on `#[table]` structs — the macro handles this
  • Use mutable context — &ReducerContext, not &mut ReducerContext
  • Forget `Table` trait import — required for table operations
  • Use field access for tables — ctx.db.player() not ctx.db.player

Common Mistakes Table

Server-side errors
WrongRightError
#[derive(SpacetimeType)] on #[table]Remove it — macro handles thisConflicting derive macros
ctx.db.player (field access)ctx.db.player() (method)"no field player on type"
ctx.db.player().find(id)ctx.db.player().id().find(&id)Must access via index
&mut ReducerContext&ReducerContextWrong context type
Missing use spacetimedb::Table;Add import"no method named insert"
#[table(name = "my_table")]#[table(name = my_table)]String literals not allowed
Missing public on tableAdd public flagClients can't subscribe
#[spacetimedb::reducer]#[reducer] after importWrong attribute path
Network/filesystem in reducerUse procedures insteadSandbox violation
Panic for expected errorsReturn Result<(), String>WASM instance destroyed
Client-side errors
WrongRightError
Wrong crate namespacetimedb-sdkDependency not found
Manual event loopUse tokio runtimeAsync issues

Hard Requirements

  1. DO NOT derive `SpacetimeType` on `#[table]` structs — the macro handles this
  2. Import `Table` trait — required for all table operations
  3. Use `&ReducerContext` — not &mut ReducerContext
  4. Tables are methods — ctx.db.table() not ctx.db.table
  5. Reducers must be deterministic — no filesystem, network, timers, or external RNG
  6. Use `ctx.random()` or `ctx.rng` — not rand crate for random numbers
  7. Add `public` flag — if clients need to subscribe to a table

Project Setup

Cargo.toml Requirements
toml
[package]
name = "my-module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
spacetimedb = "1.0"
log = "0.4"

The crate-type = ["cdylib"] is required for WebAssembly compilation.

Essential Imports
rust
use spacetimedb::{ReducerContext, Table};

Additional imports as needed:

rust
use spacetimedb::{Identity, Timestamp, ConnectionId, ScheduleAt};
use spacetimedb::sats::{i256, u256}; // For 256-bit integers

Table Definitions

Tables store data in SpacetimeDB. Define tables using the #[spacetimedb::table] macro on a struct.

Basic Table
rust
#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
id: u64,
name: String,
score: u32,
}
Table Attributes
AttributeDescription
name = identifierRequired. The table name used in ctx.db.{name}()
publicMakes table visible to clients via subscriptions
scheduled(reducer_name)Creates a schedule table that triggers the named reducer
index(name = idx, btree(columns = [a, b]))Creates a multi-column index
Column Attributes
AttributeDescription
#[primary_key]Unique identifier for the row (one per table max)
#[unique]Enforces uniqueness, enables find() method
#[auto_inc]Auto-generates unique integer values when inserting 0
#[index(btree)]Creates a B-tree index for efficient lookups
#[default(value)]Default value for migrations (must be const-evaluable)
Supported Column Types

Primitives: u8, u16, u32, u64, u128, u256, i8, i16, i32, i64, i128, i256, f32, f64, bool, String

SpacetimeDB Types: Identity, ConnectionId, Timestamp, Uuid, ScheduleAt

Collections: Vec<T>, Option<T>, Result<T, E> where inner types are also supported

Custom Types: Any struct/enum with #[derive(SpacetimeType)]

Insert Returns the Row
rust
// Insert and get the auto-generated ID
let row = ctx.db.task().insert(Task {
id: 0, // Placeholder for auto_inc
owner_id: ctx.sender,
title: "New task".to_string(),
created_at: ctx.timestamp,
});
let new_id = row.id; // Get the actual ID

Data Visibility and Row-Level Security

`public` flag exposes ALL rows to ALL clients.

ScenarioPattern
Everyone sees all rows#[table(name = x, public)]
Users see only their dataPrivate table + row-level security
Private Table (default)
rust
// No public flag — only server can read
#[table(name = secret_data)]
pub struct SecretData { ... }
Row-Level Security (RLS)

Use RLS to filter which rows each client can see:

rust
// Use row-level security for per-user visibility
#[table(name = player_data, public)]
#[rls(filter = |ctx, row| row.owner_id == ctx.sender)]
pub struct PlayerData {
#[primary_key]
pub id: u64,
pub owner_id: Identity,
pub data: String,
}

With RLS, clients can subscribe to the table but only see rows where the filter returns true for their identity.


Reducers

Reducers are transactional functions that modify database state. They run inside the database and are the only way to mutate tables.

Basic Reducer
rust
#[spacetimedb::reducer]
pub fn create_player(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
ctx.db.player().insert(Player {
id: 0, // auto_inc assigns the value
name,
score: 0,
});
Ok(())
}
Reducer Rules
  1. First parameter must be &ReducerContext
  2. Additional parameters must implement SpacetimeType
  3. Return (), Result<(), String>, or Result<(), E> where E: Display
  4. All changes roll back on panic or Err return
  5. Reducers run in isolation from concurrent reducers
  6. Cannot make network requests or access filesystem
  7. Must import Table trait for table operations: use spacetimedb::Table;

ReducerContext

The ReducerContext provides access to the database and caller information.

Properties
rust
#[spacetimedb::reducer]
pub fn example(ctx: &ReducerContext) {
// Database access
let _table = ctx.db.player();
// Caller identity (always present)
let caller: Identity = ctx.sender;
// Connection ID (None for scheduled/system reducers)
let conn: Option<ConnectionId> = ctx.connection_id;
// Invocation timestamp
let when: Timestamp = ctx.timestamp;
// Module's own identity
let module_id: Identity = ctx.identity();
// Random number generation (deterministic)
let random_val: u32 = ctx.random();
// UUID generation
let uuid = ctx.new_uuid_v4().unwrap(); // Random UUID
let uuid = ctx.new_uuid_v7().unwrap(); // Timestamp-based UUID
// Check if caller is internal (scheduled reducer)
if ctx.sender_auth().is_internal() {
// Called by scheduler, not external client
}
}

Table Operations

Insert
rust
// Insert returns the row with auto_inc values populated
let player = ctx.db.player().insert(Player {
id: 0, // auto_inc fills this
name: "Alice".to_string(),
score: 100,
});
log::info!("Created player with id: {}", player.id);
Find by Unique/Primary Key
rust
// find() returns Option<RowType>
if let Some(player) = ctx.db.player().id().find(123) {
log::info!("Found: {}", player.name);
}
Filter by Indexed Column
rust
// filter() returns an iterator
for player in ctx.db.player().name().filter("Alice") {
log::info!("Player {}: score {}", player.id, player.score);
}
// Range queries (Rust range syntax)
for player in ctx.db.player().score().filter(50..=100) {
log::info!("{} has score {}", player.name, player.score);
}
Update

Updates require a unique column. Find the row, modify it, then call update():

rust
if let Some(mut player) = ctx.db.player().id().find(123) {
player.score += 10;
ctx.db.player().id().update(player);
}
Delete
rust
// Delete by unique key
ctx.db.player().id().delete(&123);
// Delete by indexed column (returns count)
let deleted = ctx.db.player().name().delete("Alice");
log::info!("Deleted {} rows", deleted);
// Delete by range
ctx.db.player().score().delete(..50); // Delete all with score < 50
Iterate All Rows
rust
for player in ctx.db.player().iter() {
log::info!("{}: {}", player.name, player.score);
}
// Count rows
let total = ctx.db.player().count();

Indexes

Single-Column Index
rust
#[spacetimedb::table(name = player, public)]
pub struct Player {
#[primary_key]
id: u64,
#[index(btree)]
level: u32,
name: String,
}
Multi-Column Index
rust
#[spacetimedb::table(
name = score,
public,
index(name = by_player_level, btree(columns = [player_id, level]))
)]
pub struct Score {
player_id: u32,
level: u32,
points: i64,
}
Querying Multi-Column Indexes
rust
// Prefix match (first column only)
for score in ctx.db.score().by_player_level().filter(&123u32) {
log::info!("Level {}: {} points", score.level, score.points);
}
// Full match
for score in ctx.db.score().by_player_level().filter((123u32, 5u32)) {
log::info!("Points: {}", score.points);
}
// Prefix with range on second column
for score in ctx.db.score().by_player_level().filter((123u32, 1u32..=10u32)) {
log::info!("Level {}: {} points", score.level, score.points);
}

Identity and Authentication

Storing User Identity
rust
#[spacetimedb::table(name = user_profile, public)]
pub struct UserProfile {
#[primary_key]
identity: Identity,
display_name: String,
created_at: Timestamp,
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Refreshed 27 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/douglance/spacetimedb/spacetimedb-rust/badge)](https://verified-skill.com/skills/douglance/spacetimedb/spacetimedb-rust)

Something wrong with this skill or its page? Tell us.