Skip to main content

rust-server

SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.

Quellinformationen

Repository
clockworklabs/SpacetimeDB
Letzte Quellaktivität
4. November 2026 um 18:46
Erkannte Sprache von SKILL.md
Englisch
Sterne
25.521
Forks
1.112

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
rust-server
description
SpacetimeDB Rust server module SDK reference. Use when writing tables, reducers, or module logic in Rust.
license
Apache-2.0
metadata
{"author":"clockworklabs","version":"2.0","role":"server","language":"rust","cursor_globs":"**/*.rs","cursor_always_apply":true}
# SpacetimeDB Rust SDK Reference ## Imports ```rust use spacetimedb::{ procedure, reducer, table, Filter, Identity, ProcedureContext, Query, ReducerContext, SpacetimeType, Table, ConnectionId, ScheduleAt, TimeDuration, Timestamp, Uuid, }; ``` **`Table` is required.** Without it, `ctx.db.*.insert()`, `.iter()`, `.find()` etc. won't compile (`no method named 'insert' found`). ## Tables `#[spacetimedb::table(...)]` on a `pub struct`. `accessor` must be snake_case: ```rust #[spacetimedb::table(accessor = entity, public)] pub struct Entity { #[primary_key] #[auto_inc] pub id: u64, pub owner: Identity, pub name: String, #[index(btree)] pub active: bool, } ``` Options: `accessor = snake_case` (required), `public`, `scheduled(reducer_fn)`, `index(...)` `ctx.db` accessors use the `accessor` name (snake_case). ## Column Types | Rust type | Notes | |-----------|-------| | `u8` / `u16` / `u32` / `u64` / `u128` | unsigned integers | | `i8` / `i16` / `i32` / `i64` / `i128` | signed integers | | `spacetimedb::sats::u256` / `spacetimedb::sats::i256` | 256-bit integers | | `f32` / `f64` | floats | | `bool` | boolean | | `String` | text | | `Vec<T>` | list/array | | `Identity` | user identity | | `ConnectionId` | connection handle | | `Timestamp` | server timestamp (microseconds since epoch) | | `TimeDuration` | duration in microseconds | | `Uuid` | UUID | | `Option<T>` | nullable column | ## Column Attributes ```rust #[primary_key] // primary key #[auto_inc] // auto-increment (use 0 as placeholder on insert) #[unique] // unique constraint #[index(btree)] // btree index (enables .filter() on this column) #[default(true)] // migration-safe default for a newly appended column ``` Defaults support compatible addition of a newly appended field. Do not place `#[default(...)]` on primary-key, unique, or auto-increment columns. ## Indexes Prefer `#[index(btree)]` inline for single-column. Multi-column uses table-level: ```rust // Inline (preferred for single-column): #[index(btree)] pub author_id: u64, // Access: ctx.db.post().author_id().filter(author_id) // Multi-column (table-level): #[spacetimedb::table(accessor = membership, public, index(accessor = by_group_user, btree(columns = [group_id, user_id])) )] pub struct Membership { pub group_id: u64, pub user_id: Identity, ... } // Access: ctx.db.membership().by_group_user().filter((group_id, &user_id)) ``` When you frequently look up rows by multiple columns, prefer a multi-column index over filtering by one column and looping over the results. ## Reducers ```rust #[spacetimedb::reducer] pub fn create_entity(ctx: &ReducerContext, name: String) { ctx.db.entity().insert(Entity { id: 0, owner: ctx.sender(), name, active: true }); } // Reducers can return Result<(), String> or Result<(), E> where E: Display #[spacetimedb::reducer] pub fn validate_entity(ctx: &ReducerContext, name: String) -> Result<(), String> { if name.is_empty() { return Err("Name cannot be empty".to_string()); } ctx.db.entity().try_insert(Entity { id: 0, owner: ctx.sender(), name, active: true })?; Ok(()) } ``` Note: `insert()` panics on constraint violations. Use `try_insert()` with `?` when returning `Result`. ## DB Operations ```rust ctx.db.entity().insert(Entity { id: 0, name: "Sample".into() }); // Insert (0 for autoInc) ctx.db.entity().id().find(entity_id); // Find by PK → Option<Entity> ctx.db.entity().identity().find(ctx.sender()); // Find by unique column → Option<Entity> ctx.db.item().author_id().filter(author_id); // Filter by index → iterator ctx.db.entity().iter(); // All rows → iterator ctx.db.entity().count(); // Count rows ctx.db.entity().id().update(Entity { name: new_name, ..existing }); // Update (override + spread) ctx.db.entity().id().delete(entity_id); // Delete by PK ctx.db.entity().name().delete("Alice".to_string()); // Delete by indexed String column ``` Note: `iter()` and `filter()` return iterators. Collect to Vec if you need `.sort()`, `.filter()`, `.map()`. Range queries on btree indexes: `filter(18..=65)`, `filter(18..)`, `filter(..18)`. String column accessors operate on the column's owned `String` type, not `&str`. Pass a `String` or `&String` to `find` and `delete`; index filters borrow the key, as in `ctx.db.product().category().filter(&"hardware".to_string())`. ## Lifecycle Hooks ```rust #[spacetimedb::reducer(init)] pub fn init(ctx: &ReducerContext) { ... } #[spacetimedb::reducer(client_connected)] pub fn on_connect(ctx: &ReducerContext) { ... } #[spacetimedb::reducer(client_disconnected)] pub fn on_disconnect(ctx: &ReducerContext) { ... } ``` The current connection ID is available through `ctx.connection_id()` (not a public field) and may be absent outside connection-scoped calls. ## Views ```rust // Anonymous view (same result for all clients): use spacetimedb::{view, AnonymousViewContext}; #[view(accessor = active_users, public)] fn active_users(ctx: &AnonymousViewContext) -> Vec<Entity> { ctx.db.entity().active().filter(true).collect() } // Per-user view (result varies by sender): use spacetimedb::{view, ViewContext}; #[view(accessor = my_profile, public)] fn my_profile(ctx: &ViewContext) -> Option<Entity> { ctx.db.entity().identity().find(ctx.sender()) } ``` Procedural-view table handles support indexed `find` and `filter` access, but not full-table `iter()`. Start a procedural view from an appropriate table index. Declare a procedural view primary key in the view attribute: ```rust #[view(accessor = catalog_entry, public, primary_key = sku)] fn catalog_entry(ctx: &AnonymousViewContext) -> Vec<CatalogEntry> { ... } ``` Procedural-view primary keys are explicit schema metadata. Add one only when the view itself is required to expose a primary key; a source table's primary key is not inherited by the view. Query-builder views use `ViewContext`, `ctx.from`, and return `impl Query<Row>`. Use `filter` for predicates and `right_semijoin` when the result should contain right-side rows that have a matching left-side row: ```rust ctx.from.article().filter(|article| article.published.eq(true)) ctx.from.subscription().right_semijoin( ctx.from.account(), |subscription, account| subscription.account_id.eq(account.id), ) ``` Query-builder comparisons against a `String` column take an owned `String`, for example `row.label.eq("active".to_string())`. Views can also accept arguments (2.13+), after the context: ```rust #[spacetimedb::view(accessor = entity_by_id, public)] pub fn entity_by_id(ctx: &spacetimedb::AnonymousViewContext, id: u64) -> Option<Entity> { ctx.db.entity().id().find(id) } ``` Floating-point arguments and primary keys are rejected on argument-bearing views. See client skills for SQL subscription syntax and restrictions. ## Client Visibility Filters ```rust use spacetimedb::Filter; #[spacetimedb::client_visibility_filter] const PRIVATE_NOTE_FILTER: Filter = Filter::Sql("SELECT * FROM owned_row WHERE owner = :sender"); ``` ## Reducer Context API `ReducerContext` (`ctx`) is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules. ```rust // Auth: ctx.sender() is the caller's Identity if row.owner != ctx.sender() { panic!("unauthorized"); // or: return Err(anyhow::anyhow!("unauthorized")); } // Server timestamp (deterministic per reducer call) ctx.db.item().insert(Item { id: 0, owner: ctx.sender(), created_at: ctx.timestamp, .. }); // Timestamp arithmetic let expiry = ctx.timestamp + TimeDuration::from_micros(delay_micros); // Deterministic RNG: ctx.random() for a single value, ctx.rng() for the rand::Rng trait use spacetimedb::rand::Rng; let n: u32 = ctx.random(); // random u32 let roll: u32 = ctx.rng().gen_range(1..=6); // any rand::Rng method // Client: Timestamp → milliseconds since epoch timestamp.to_micros_since_unix_epoch() / 1000 ``` ## Environment Variables ```rust #[spacetimedb::env] pub struct Env { pub API_KEY: String, pub LOG_LEVEL: Option<String>, } // Inside a root entry point: // ctx.env.API_KEY() -> String // ctx.env.LOG_LEVEL() -> Option<String> // ctx.env.get("LOG_LEVEL") -> Option<String> ``` For constrained values, derive `spacetimedb::EnvironmentValue` on a payload-free enum and use it as a field type (or `Option<Enum>`). `#[env(value = "production")]` on a variant supplies its exact accepted string; without it, the Rust variant name is used. `#[env(values("info", "debug"))]` also constrains `String`/`Option<String>` fields. See the concepts skill for shared environment and secret-handling rules. ## Scheduled Tables ```rust #[spacetimedb::table(accessor = tick_timer, scheduled(tick), public)] pub struct TickTimer { #[primary_key] #[auto_inc] scheduled_id: u64, scheduled_at: spacetimedb::ScheduleAt, } #[spacetimedb::reducer] pub fn tick(ctx: &ReducerContext, timer: TickTimer) { // timer row is auto-deleted after this reducer runs } // One-time: fires once at a specific time let at = ScheduleAt::Time(ctx.timestamp + std::time::Duration::from_secs(10)); // Repeating: fires on an interval let at = ScheduleAt::Interval(std::time::Duration::from_secs(5).into()); ctx.db.tick_timer().insert(TickTimer { scheduled_id: 0, scheduled_at: at }); ``` Construct a connection ID from a numeric representation with `ConnectionId::from_u128(value)`. ## Inter-Database Calls (Async IDC) A reducer calls a reducer in another database by inserting a row into an outbox table. The call is sent only if the transaction commits, runs exactly once, and runs in order with other calls through the same outbox to the same database. The sending reducer doesn't wait for it. Both databases must be on the same server. Generate receiver bindings by listing the receiving module under `dependencies` in the sending database's `spacetime.json`, then running `spacetime generate <sending-database>`. Rust bindings are written to `src/remote_bindings/`. ```json { "database": "lobby", "module-path": "./lobby", "dependencies": [{ "name": "game_world", "database": "game-world" }], "children": [{ "database": "game-world", "module-path": "./game_world" }] } ``` ```rust
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen