spacetimedb-concepts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SpacetimeDB Core Concepts

SpacetimeDB核心概念

SpacetimeDB is a relational database that is also a server. It lets you upload application logic directly into the database via WebAssembly modules, eliminating the traditional web/game server layer entirely.

SpacetimeDB是一款兼具服务器功能的关系型数据库。它允许你通过WebAssembly模块将应用逻辑直接上传至数据库中,彻底省去了传统的Web/游戏服务器层。

Critical Rules (Read First)

重要规则(请先阅读)

These five rules prevent the most common SpacetimeDB mistakes:
  1. Reducers are transactional — they do not return data to callers. Use subscriptions to read data.
  2. Reducers must be deterministic — no filesystem, network, timers, or random. All state must come from tables.
  3. Read data via tables/subscriptions — not reducer return values. Clients get data through subscribed queries.
  4. Auto-increment IDs are not sequential — gaps are normal, do not use for ordering. Use timestamps or explicit sequence columns.
  5. ctx.sender()
    is the authenticated principal
    — never trust identity passed as arguments. Always use
    ctx.sender()
    for authorization.

以下五条规则可避免最常见的SpacetimeDB使用错误:
  1. Reducer是事务性的 —— 它们不会向调用者返回数据,请通过订阅读取数据。
  2. Reducer必须是确定性的 —— 禁止访问文件系统、网络、定时器或生成随机数。所有状态必须来自表。
  3. 通过表/订阅读取数据 —— 不要依赖Reducer的返回值。客户端通过订阅的查询获取数据。
  4. 自增ID并非连续的 —— 存在间隙是正常现象,请勿将其用于排序。请使用时间戳或显式的序列列。
  5. ctx.sender()
    是已认证的主体
    —— 永远不要信任作为参数传递的身份信息。授权时请始终使用
    ctx.sender()

Feature Implementation Checklist

功能实现检查清单

When implementing a feature that spans backend and client:
  1. Backend: Define table(s) to store the data
  2. Backend: Define reducer(s) to mutate the data
  3. Client: Subscribe to the table(s)
  4. Client: Call the reducer(s) from UI — do not skip this step
  5. Client: Render the data from the table(s)
Common mistake: Building backend tables/reducers but forgetting to wire up the client to call them.

当实现跨后端与客户端的功能时:
  1. 后端:定义用于存储数据的表
  2. 后端:定义用于修改数据的Reducer
  3. 客户端:订阅相关表
  4. 客户端:从UI调用Reducer —— 请勿跳过此步骤
  5. 客户端:渲染来自表的数据
常见错误:创建了后端表/Reducer,但忘记在客户端中调用它们。

Debugging Checklist

调试检查清单

When things are not working:
  1. Is SpacetimeDB server running? (
    spacetime start
    )
  2. Is the module published? (
    spacetime publish
    )
  3. Are client bindings generated? (
    spacetime generate
    )
  4. Check server logs for errors (
    spacetime logs <db-name>
    )
  5. Is the reducer actually being called from the client?

当功能无法正常工作时:
  1. SpacetimeDB服务器是否正在运行?(执行
    spacetime start
  2. 模块是否已发布?(执行
    spacetime publish
  3. 是否生成了客户端绑定?(执行
    spacetime generate
  4. 检查服务器日志中的错误(执行
    spacetime logs <db-name>
  5. 客户端是否真的调用了Reducer?

CLI Commands

CLI命令

bash
spacetime start
spacetime publish <db-name> --module-path <module-path>
spacetime publish <db-name> --clear-database -y --module-path <module-path>
spacetime generate --lang <lang> --out-dir <out> --module-path <module-path>
spacetime logs <db-name>

bash
spacetime start
spacetime publish <db-name> --module-path <module-path>
spacetime publish <db-name> --clear-database -y --module-path <module-path>
spacetime generate --lang <lang> --out-dir <out> --module-path <module-path>
spacetime logs <db-name>

What SpacetimeDB Is

什么是SpacetimeDB

SpacetimeDB combines a database and application server into a single deployable unit. Clients connect directly to the database and execute application logic inside it. The system is optimized for real-time applications requiring maximum speed and minimum latency.
Key characteristics:
  • In-memory execution: Application state is served from memory for very low-latency access
  • Persistent storage: Data is automatically persisted to a write-ahead log (WAL) for durability
  • Real-time synchronization: Changes are automatically pushed to subscribed clients
  • Single deployment: No separate servers, containers, or infrastructure to manage
SpacetimeDB将数据库与应用服务器整合为一个可部署的独立单元。客户端直接连接到数据库,并在其中执行应用逻辑。该系统专为需要极致速度与最低延迟的实时应用优化。
核心特性:
  • 内存内执行:应用状态从内存中读取,实现极低延迟的访问
  • 持久化存储:数据自动持久化到预写日志(WAL)中,确保数据安全性
  • 实时同步:数据变更会自动推送给已订阅的客户端
  • 单一部署单元:无需管理单独的服务器、容器或基础设施

The Five Zen Principles

五大核心原则

  1. Everything is a Table: Your entire application state lives in tables. No separate cache layer, no Redis, no in-memory state to synchronize.
  2. Everything is Persistent: SpacetimeDB persists state by default (for example via WAL-backed durability).
  3. Everything is Real-Time: Clients are replicas of server state. Subscribe to data and it flows automatically.
  4. Everything is Transactional: Every reducer runs atomically. Either all changes succeed or all roll back.
  5. Everything is Programmable: Modules are real code (Rust, C#, TypeScript) running inside the database.
  1. 一切皆为表:你的整个应用状态都存储在表中。无需单独的缓存层、Redis或需要同步的内存状态。
  2. 一切皆持久化:SpacetimeDB默认持久化状态(例如通过WAL实现的持久性)。
  3. 一切皆实时:客户端是服务器状态的副本。订阅数据后,数据会自动同步至客户端。
  4. 一切皆事务性:每个Reducer都以原子方式运行。要么所有变更都成功,要么全部回滚。
  5. 一切皆可编程:模块是运行在数据库内部的真实代码(Rust、C#、TypeScript)。

Tables

Tables store all data in SpacetimeDB. They use the relational model and support SQL queries for subscriptions.
表是SpacetimeDB中所有数据的存储载体。它们采用关系模型,并支持通过SQL查询进行订阅。

Defining Tables

定义表

Tables are defined using language-specific attributes. In 2.0, use
accessor
(not
name
) for the API name:
Rust:
rust
#[spacetimedb::table(accessor = player, public)]
pub struct Player {
    #[primary_key]
    #[auto_inc]
    id: u32,
    #[index(btree)]
    name: String,
    #[unique]
    email: String,
}
C#:
csharp
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
public partial struct Player
{
    [SpacetimeDB.PrimaryKey]
    [SpacetimeDB.AutoInc]
    public uint Id;
    [SpacetimeDB.Index.BTree]
    public string Name;
    [SpacetimeDB.Unique]
    public string Email;
}
TypeScript:
typescript
const players = table(
  { name: 'players', public: true },
  {
    id: t.u32().primaryKey().autoInc(),
    name: t.string().index('btree'),
    email: t.string().unique(),
  }
);
表通过特定语言的特性进行定义。在2.0版本中,请使用
accessor
(而非
name
)作为API名称:
Rust:
rust
#[spacetimedb::table(accessor = player, public)]
pub struct Player {
    #[primary_key]
    #[auto_inc]
    id: u32,
    #[index(btree)]
    name: String,
    #[unique]
    email: String,
}
C#:
csharp
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
public partial struct Player
{
    [SpacetimeDB.PrimaryKey]
    [SpacetimeDB.AutoInc]
    public uint Id;
    [SpacetimeDB.Index.BTree]
    public string Name;
    [SpacetimeDB.Unique]
    public string Email;
}
TypeScript:
typescript
const players = table(
  { name: 'players', public: true },
  {
    id: t.u32().primaryKey().autoInc(),
    name: t.string().index('btree'),
    email: t.string().unique(),
  }
);

Table Visibility

表的可见性

  • Private tables (default): Only accessible by reducers and the database owner
  • Public tables: Exposed for client read access through subscriptions. Writes still require reducers.
  • 私有表(默认):仅Reducer和数据库所有者可访问
  • 公共表:开放给客户端通过订阅读取数据。写入操作仍需通过Reducer完成。

Table Design Principles

表设计原则

Organize data by access pattern, not by entity:
Decomposed approach (recommended):
Player          PlayerState         PlayerStats
id         <--  player_id           player_id
name            position_x          total_kills
                position_y          total_deaths
                velocity_x          play_time
Benefits: reduced bandwidth, cache efficiency, schema evolution, semantic clarity.
按访问模式组织数据,而非按实体:
推荐的分解方式:
Player          PlayerState         PlayerStats
id         <--  player_id           player_id
name            position_x          total_kills
                position_y          total_deaths
                velocity_x          play_time
优势:减少带宽占用、提升缓存效率、便于Schema演进、语义清晰。

Reducers

Reducer

Reducers are transactional functions that modify database state. They are the primary client-invoked mutation path; procedures can also mutate tables by running explicit transactions.
Reducer是用于修改数据库状态的事务性函数。它们是客户端触发状态变更的主要途径;过程(Procedures)也可通过显式事务修改表。

Key Properties

核心特性

  • Transactional: Run in isolated database transactions
  • Atomic: Either all changes succeed or all roll back
  • Isolated: Cannot interact with the outside world (no network, no filesystem)
  • Callable: Clients invoke reducers as remote procedure calls
  • 事务性:在独立的数据库事务中运行
  • 原子性:要么所有变更都成功,要么全部回滚
  • 隔离性:无法与外部交互(无网络、无文件系统访问)
  • 可调用:客户端通过远程过程调用(RPC)触发Reducer

Critical Reducer Rules

Reducer重要规则

  1. No global state: Relying on static variables is undefined behavior
  2. No side effects: Reducers cannot make network requests or access files
  3. Store state in tables: All persistent state must be in tables
  4. No return data: Reducers do not return data to callers — use subscriptions
  5. Must be deterministic: No random, no timers, no external I/O
  1. 无全局状态:依赖静态变量属于未定义行为
  2. 无副作用:Reducer无法发起网络请求或访问文件
  3. 状态存储于表中:所有持久化状态必须存储在表内
  4. 无返回数据:Reducer不会向调用者返回数据 —— 请使用订阅
  5. 必须是确定性的:不能包含随机操作、定时器或外部I/O

Defining Reducers

定义Reducer

Rust:
rust
#[spacetimedb::reducer]
pub fn create_user(ctx: &ReducerContext, name: String, email: String) -> Result<(), String> {
    if name.is_empty() {
        return Err("Name cannot be empty".to_string());
    }
    ctx.db.user().insert(User { id: 0, name, email });
    Ok(())
}
C#:
csharp
[SpacetimeDB.Reducer]
public static void CreateUser(ReducerContext ctx, string name, string email)
{
    if (string.IsNullOrEmpty(name))
        throw new ArgumentException("Name cannot be empty");
    ctx.Db.User.Insert(new User { Id = 0, Name = name, Email = email });
}
Rust:
rust
#[spacetimedb::reducer]
pub fn create_user(ctx: &ReducerContext, name: String, email: String) -> Result<(), String> {
    if name.is_empty() {
        return Err("Name cannot be empty".to_string());
    }
    ctx.db.user().insert(User { id: 0, name, email });
    Ok(())
}
C#:
csharp
[SpacetimeDB.Reducer]
public static void CreateUser(ReducerContext ctx, string name, string email)
{
    if (string.IsNullOrEmpty(name))
        throw new ArgumentException("Name cannot be empty");
    ctx.Db.User.Insert(new User { Id = 0, Name = name, Email = email });
}

ReducerContext

ReducerContext

Every reducer receives a
ReducerContext
providing:
  • Database:
    ctx.db
    (Rust field, TS property) /
    ctx.Db
    (C# property)
  • Sender:
    ctx.sender()
    (Rust method) /
    ctx.Sender
    (C# property) /
    ctx.sender
    (TS property)
  • Connection ID:
    ctx.connection_id()
    (Rust method) /
    ctx.ConnectionId
    (C# property) /
    ctx.connectionId
    (TS property)
  • Timestamp:
    ctx.timestamp
    (Rust field, TS property) /
    ctx.Timestamp
    (C# property)
每个Reducer都会接收一个
ReducerContext
,提供以下功能:
  • 数据库实例
    ctx.db
    (Rust字段、TS属性)/
    ctx.Db
    (C#属性)
  • 调用者身份
    ctx.sender()
    (Rust方法)/
    ctx.Sender
    (C#属性)/
    ctx.sender
    (TS属性)
  • 连接ID
    ctx.connection_id()
    (Rust方法)/
    ctx.ConnectionId
    (C#属性)/
    ctx.connectionId
    (TS属性)
  • 时间戳
    ctx.timestamp
    (Rust字段、TS属性)/
    ctx.Timestamp
    (C#属性)

Event Tables (2.0)

事件表(2.0版本)

Event tables are the preferred way to broadcast reducer-specific data to clients.
rust
#[table(accessor = damage_event, public, event)]
pub struct DamageEvent {
    pub target: Identity,
    pub amount: u32,
}

#[reducer]
fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) {
    ctx.db.damage_event().insert(DamageEvent { target, amount });
}
Clients subscribe to event tables and use
on_insert
callbacks. Event tables must be subscribed explicitly and are excluded from
subscribe_to_all_tables()
.
事件表是向客户端广播Reducer特定数据的首选方式。
rust
#[table(accessor = damage_event, public, event)]
pub struct DamageEvent {
    pub target: Identity,
    pub amount: u32,
}

#[reducer]
fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) {
    ctx.db.damage_event().insert(DamageEvent { target, amount });
}
客户端订阅事件表并使用
on_insert
回调。事件表必须显式订阅,且不会被
subscribe_to_all_tables()
包含。

Subscriptions

订阅

Subscriptions replicate database rows to clients in real-time.
订阅功能可将数据库中的行实时同步至客户端。

How Subscriptions Work

订阅的工作原理

  1. Subscribe: Register SQL queries describing needed data
  2. Receive initial data: All matching rows are sent immediately
  3. Receive updates: Real-time updates when subscribed rows change
  4. React to changes: Use callbacks (
    onInsert
    ,
    onDelete
    ,
    onUpdate
    )
  1. 订阅:注册描述所需数据的SQL查询
  2. 接收初始数据:所有匹配的行将立即发送至客户端
  3. 接收更新:当订阅的行发生变更时,实时推送更新
  4. 响应变更:使用回调函数(
    onInsert
    onDelete
    onUpdate

Subscription Best Practices

订阅最佳实践

  1. Group subscriptions by lifetime: Keep always-needed data separate from temporary subscriptions
  2. Subscribe before unsubscribing: When updating subscriptions, subscribe to new data first
  3. Avoid overlapping queries: Distinct queries returning overlapping data cause redundant processing
  4. Use indexes: Queries on indexed columns are efficient; full table scans are expensive
  1. 按生命周期分组订阅:将始终需要的数据与临时订阅的数据分开
  2. 先订阅再取消旧订阅:更新订阅时,先订阅新数据,再取消旧订阅
  3. 避免重叠查询:返回重叠数据的不同查询会导致冗余处理
  4. 使用索引:针对带索引列的查询效率更高;全表扫描开销较大

Modules

模块

Modules are WebAssembly bundles containing application logic that runs inside the database.
模块是包含应用逻辑的WebAssembly包,运行在数据库内部。

Module Components

模块组件

  • Tables: Define the data schema
  • Reducers: Define callable functions that modify state
  • Views: Define read-only computed queries
  • Event Tables: Broadcast reducer-specific data to clients (2.0)
  • Procedures: (Beta) Functions that can have side effects (HTTP requests)
  • :定义数据Schema
  • Reducer:定义可调用的状态变更函数
  • 视图:定义只读的计算查询
  • 事件表:向客户端广播Reducer特定数据(2.0版本)
  • 过程:(测试版)可产生副作用的函数(如HTTP请求)

Module Languages

模块支持的语言

Server-side modules can be written in: Rust, C#, TypeScript (beta)
服务器端模块可使用以下语言编写:Rust、C#、TypeScript(测试版)

Module Lifecycle

模块生命周期

  1. Write: Define tables and reducers in your chosen language
  2. Compile: Build to WebAssembly using the SpacetimeDB CLI
  3. Publish: Upload to a SpacetimeDB host with
    spacetime publish
  4. Hot-swap: Republish to update code without disconnecting clients
  1. 编写:使用你选择的语言定义表和Reducer
  2. 编译:使用SpacetimeDB CLI编译为WebAssembly
  3. 发布:通过
    spacetime publish
    命令上传至SpacetimeDB主机
  4. 热替换:重新发布即可更新代码,无需断开客户端连接

Identity

身份认证

Identity is SpacetimeDB's authentication system based on OpenID Connect (OIDC).
  • Identity: A long-lived, globally unique identifier for a user.
  • ConnectionId: Identifies a specific client connection.
rust
#[spacetimedb::reducer]
pub fn do_something(ctx: &ReducerContext) {
    let caller_identity = ctx.sender();  // Who is calling?
    // NEVER trust identity passed as a reducer argument
}
身份认证是SpacetimeDB基于OpenID Connect(OIDC)的认证系统。
  • Identity:用户的长期全局唯一标识符。
  • ConnectionId:标识特定的客户端连接。
rust
#[spacetimedb::reducer]
pub fn do_something(ctx: &ReducerContext) {
    let caller_identity = ctx.sender();  // 获取调用者身份
    // 永远不要信任作为Reducer参数传递的身份信息
}

Authentication Providers

认证提供商

SpacetimeDB works with many OIDC providers, including SpacetimeAuth (built-in), Auth0, Clerk, Keycloak, Google, and GitHub.
SpacetimeDB支持多种OIDC提供商,包括内置的SpacetimeAuth、Auth0、Clerk、Keycloak、Google以及GitHub。

When to Use SpacetimeDB

何时使用SpacetimeDB

Ideal Use Cases

理想适用场景

  • Real-time games: MMOs, multiplayer games, turn-based games
  • Collaborative applications: Document editing, whiteboards, design tools
  • Chat and messaging: Real-time communication with presence
  • Live dashboards: Streaming analytics and monitoring
  • 实时游戏:大型多人在线游戏(MMO)、多人协作游戏、回合制游戏
  • 协作类应用:文档编辑、白板、设计工具
  • 聊天与消息:带在线状态的实时通讯
  • 实时仪表盘:流分析与监控

Key Decision Factors

核心决策因素

Choose SpacetimeDB when you need:
  • Sub-10ms latency for reads and writes
  • Automatic real-time synchronization
  • Transactional guarantees for all operations
  • Simplified architecture (no separate cache, queue, or server)
当你需要以下特性时,选择SpacetimeDB:
  • 读写延迟低于10毫秒
  • 自动实时同步
  • 所有操作的事务性保障
  • 简化的架构(无需单独的缓存、队列或服务器)

Less Suitable For

不太适用的场景

  • Batch analytics: Optimized for OLTP, not OLAP
  • Large blob storage: Better suited for structured relational data
  • Stateless APIs: Traditional REST APIs do not need real-time sync
  • 批量分析:针对OLTP优化,而非OLAP
  • 大对象存储:更适合结构化的关系型数据
  • 无状态API:传统REST API无需实时同步

Common Patterns

常见模式

Authentication check in reducer:
rust
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
    let admin = ctx.db.admin().identity().find(&ctx.sender())
        .ok_or("Not an admin")?;
    Ok(())
}
Scheduled reducer:
rust
#[spacetimedb::table(accessor = reminder, scheduled(send_reminder))]
pub struct Reminder {
    #[primary_key]
    #[auto_inc]
    id: u64,
    scheduled_at: ScheduleAt,
    message: String,
}

#[spacetimedb::reducer]
fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
    log::info!("Reminder: {}", reminder.message);
}

Reducer中的身份认证检查:
rust
#[spacetimedb::reducer]
fn admin_action(ctx: &ReducerContext) -> Result<(), String> {
    let admin = ctx.db.admin().identity().find(&ctx.sender())
        .ok_or("Not an admin")?;
    Ok(())
}
定时Reducer:
rust
#[spacetimedb::table(accessor = reminder, scheduled(send_reminder))]
pub struct Reminder {
    #[primary_key]
    #[auto_inc]
    id: u64,
    scheduled_at: ScheduleAt,
    message: String,
}

#[spacetimedb::reducer]
fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
    log::info!("Reminder: {}", reminder.message);
}

Editing Behavior

编辑规范

When modifying SpacetimeDB code:
  • Make the smallest change necessary
  • Do NOT touch unrelated files, configs, or dependencies
  • Do NOT invent new SpacetimeDB APIs — use only what exists in docs or this repo
修改SpacetimeDB代码时:
  • 仅做必要的最小变更
  • 请勿修改无关文件、配置或依赖
  • 请勿自创SpacetimeDB API —— 仅使用文档或本仓库中已有的API