Skip to main content

rust-server

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

Informações da origem

Repositório
clockworklabs/SpacetimeDB
Última atividade na origem
4 de novembro de 2026 às 18:46
Idioma detectado do SKILL.md
inglês
Estrelas
25.521
Forks
1.112

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub