cpp-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SpacetimeDB C++ SDK Reference

SpacetimeDB C++ SDK 参考

Imports

导入

cpp
#include <spacetimedb.h>
using namespace SpacetimeDB;
cpp
#include <spacetimedb.h>
using namespace SpacetimeDB;

Tables

表

Register structs with macros, then declare as tables:
cpp
struct Entity {
    uint64_t id;
    Identity owner;
    std::string name;
    bool active;
};
SPACETIMEDB_STRUCT(Entity, id, owner, name, active)
SPACETIMEDB_TABLE(Entity, entity, Public)
FIELD_PrimaryKeyAutoInc(entity, id)
FIELD_Index(entity, name)
Options:
  • SPACETIMEDB_TABLE(Type, accessor, Public|Private)
    : regular table
  • SPACETIMEDB_TABLE(Type, accessor, Public|Private, true)
    : event table
Field constraints:
  • FIELD_PrimaryKey(accessor, field)
    : primary key
  • FIELD_PrimaryKeyAutoInc(accessor, field)
    : primary key with auto-increment (use 0 on insert)
  • FIELD_Unique(accessor, field)
    : unique constraint
  • FIELD_Index(accessor, field)
    : btree index (enables
    .filter()
    )
使用宏注册结构体,然后将其声明为表:
cpp
struct Entity {
    uint64_t id;
    Identity owner;
    std::string name;
    bool active;
};
SPACETIMEDB_STRUCT(Entity, id, owner, name, active)
SPACETIMEDB_TABLE(Entity, entity, Public)
FIELD_PrimaryKeyAutoInc(entity, id)
FIELD_Index(entity, name)
选项:
  • SPACETIMEDB_TABLE(Type, accessor, Public|Private)
    :普通表
  • SPACETIMEDB_TABLE(Type, accessor, Public|Private, true)
    :事件表
字段约束:
  • FIELD_PrimaryKey(accessor, field)
    :主键
  • FIELD_PrimaryKeyAutoInc(accessor, field)
    :自动递增主键(插入时使用 0)
  • FIELD_Unique(accessor, field)
    :唯一约束
  • FIELD_Index(accessor, field)
    :btree 索引(支持
    .filter()
    )

Column Types

列类型

C++ typeNotes
uint8_t
/
uint16_t
/
uint32_t
/
uint64_t
unsigned integers
SpacetimeDB::u128
/
SpacetimeDB::u256
large unsigned integers
int8_t
/
int16_t
/
int32_t
/
int64_t
signed integers
SpacetimeDB::i128
/
SpacetimeDB::i256
large signed integers
float
/
double
floats
bool
boolean
std::string
text
std::vector<T>
list/array
std::optional<T>
nullable column
Identity
user identity
ConnectionId
connection handle
Timestamp
server timestamp (microseconds since epoch)
TimeDuration
duration in microseconds
ScheduleAt
for scheduled tables
C++ 类型说明
uint8_t
/
uint16_t
/
uint32_t
/
uint64_t
无符号整数
SpacetimeDB::u128
/
SpacetimeDB::u256
大型无符号整数
int8_t
/
int16_t
/
int32_t
/
int64_t
有符号整数
SpacetimeDB::i128
/
SpacetimeDB::i256
大型有符号整数
float
/
double
浮点数
bool
布尔值
std::string
文本
std::vector<T>
列表/数组
std::optional<T>
可空列
Identity
用户身份
ConnectionId
连接句柄
Timestamp
服务端时间戳(自 epoch 起的微秒数)
TimeDuration
以微秒为单位的时长
ScheduleAt
用于调度表

Indexes

索引

cpp
// Single-column:
FIELD_Index(entity, name)
// Access: ctx.db[entity_name].filter("Alice")

// Multi-column:
FIELD_NamedMultiColumnIndex(score, by_player_and_level, player_id, level)
Range queries (included by
spacetimedb.h
; include
<spacetimedb/range_queries.h>
directly only when not using the umbrella header):
cpp
ctx.db[user_age].filter(range_inclusive(uint8_t(18), uint8_t(65)));
ctx.db[user_age].filter(range_from(uint8_t(18)));
cpp
// Single-column:
FIELD_Index(entity, name)
// Access: ctx.db[entity_name].filter("Alice")

// Multi-column:
FIELD_NamedMultiColumnIndex(score, by_player_and_level, player_id, level)
范围查询(
spacetimedb.h
已包含相关功能;只有在不使用聚合头文件时,才直接包含
<spacetimedb/range_queries.h>
):
cpp
ctx.db[user_age].filter(range_inclusive(uint8_t(18), uint8_t(65)));
ctx.db[user_age].filter(range_from(uint8_t(18)));

Reducers

Reducer

All reducers return
ReducerResult
. Use
Ok()
or
Err(message)
:
cpp
SPACETIMEDB_REDUCER(create_entity, ReducerContext ctx, std::string name) {
    if (name.empty()) {
        return Err("Name cannot be empty");
    }
    ctx.db[entity].insert(Entity{0, ctx.sender(), name, true});
    return Ok();
}
所有 Reducer 都返回
ReducerResult
。使用
Ok()
或
Err(message)
:
cpp
SPACETIMEDB_REDUCER(create_entity, ReducerContext ctx, std::string name) {
    if (name.empty()) {
        return Err("Name cannot be empty");
    }
    ctx.db[entity].insert(Entity{0, ctx.sender(), name, true});
    return Ok();
}

DB Operations

数据库操作

cpp
ctx.db[entity].insert(Entity{0, owner, "Sample", true});     // Insert (0 for autoInc)
ctx.db[entity_id].find(entityId);                             // Find by PK → std::optional
ctx.db[entity_identity].find(ctx.sender());                   // Find by unique column
ctx.db[entity_name].filter("Alice");                          // Filter by index → iterable
ctx.db[entity];                                               // All rows → iterable (range-for)
ctx.db[entity].count();                                       // Count rows

// Update: find, mutate, update
if (auto e = ctx.db[entity_id].find(entityId)) {
    e->name = "New Name";
    ctx.db[entity_id].update(*e);
}

// Delete by primary key
ctx.db[entity_id].delete_by_key(entityId);
Note: Bracket notation
ctx.db[accessor]
is used for all table access. The accessor name comes from
SPACETIMEDB_TABLE
and
FIELD_*
macros.
cpp
ctx.db[entity].insert(Entity{0, owner, "Sample", true});     // Insert (0 for autoInc)
ctx.db[entity_id].find(entityId);                             // Find by PK → std::optional
ctx.db[entity_identity].find(ctx.sender());                   // Find by unique column
ctx.db[entity_name].filter("Alice");                          // Filter by index → iterable
ctx.db[entity];                                               // All rows → iterable (range-for)
ctx.db[entity].count();                                       // Count rows

// Update: find, mutate, update
if (auto e = ctx.db[entity_id].find(entityId)) {
    e->name = "New Name";
    ctx.db[entity_id].update(*e);
}

// Delete by primary key
ctx.db[entity_id].delete_by_key(entityId);
注意:所有表访问都使用方括号表示法
ctx.db[accessor]
。访问器名称来自
SPACETIMEDB_TABLE
和
FIELD_*
宏。

Lifecycle Hooks

生命周期钩子

cpp
SPACETIMEDB_INIT(init, ReducerContext ctx) {
    LOG_INFO("Database initializing...");
    return Ok();
}

SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
    LOG_INFO("Connected: " + ctx.sender().to_string());
    return Ok();
}

SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
    LOG_INFO("Disconnected: " + ctx.sender().to_string());
    return Ok();
}
cpp
SPACETIMEDB_INIT(init, ReducerContext ctx) {
    LOG_INFO("Database initializing...");
    return Ok();
}

SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
    LOG_INFO("Connected: " + ctx.sender().to_string());
    return Ok();
}

SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
    LOG_INFO("Disconnected: " + ctx.sender().to_string());
    return Ok();
}

Authentication & Timestamps

身份验证与时间戳

cpp
// Auth: ctx.sender() is the caller's Identity
if (row.owner != ctx.sender()) {
    return Err("unauthorized");
}

// Server timestamps
ctx.db[item].insert(Item{0, ctx.sender(), ctx.timestamp});

// Timestamp arithmetic
Timestamp later = ctx.timestamp + TimeDuration::from_seconds(10);
cpp
// Auth: ctx.sender() is the caller's Identity
if (row.owner != ctx.sender()) {
    return Err("unauthorized");
}

// Server timestamps
ctx.db[item].insert(Item{0, ctx.sender(), ctx.timestamp});

// Timestamp arithmetic
Timestamp later = ctx.timestamp + TimeDuration::from_seconds(10);

Reducer Context

Reducer 上下文

ReducerContext
is the single source of sender identity, deterministic time, and deterministic randomness inside a reducer. Always go through
ctx
for these. Standard library clocks and random sources are not available in modules.
cpp
ctx.db[table]          // Table access (bracket notation)
ctx.sender()           // Caller's Identity
ctx.timestamp          // Invocation timestamp
ctx.connection_id      // std::optional<ConnectionId>
ctx.identity()         // Module's own identity
ctx.rng()              // Deterministic RNG
ctx.sender_auth()      // AuthCtx with JWT claims
ReducerContext
是 Reducer 内获取发送者身份、确定性时间和确定性随机数的唯一来源。始终通过
ctx
访问这些内容。模块中不可使用标准库时钟和随机源。
cpp
ctx.db[table]          // Table access (bracket notation)
ctx.sender()           // Caller's Identity
ctx.timestamp          // Invocation timestamp
ctx.connection_id      // std::optional<ConnectionId>
ctx.identity()         // Module's own identity
ctx.rng()              // Deterministic RNG
ctx.sender_auth()      // AuthCtx with JWT claims

Environment Variables

环境变量

Declare the environment in a dedicated
environment.h
:
cpp
#pragma once
#include <spacetimedb/environment_declaration.h>

SPACETIMEDB_ENV(
    (API_KEY, std::string),
    (MODE, std::string, ("development", "production")),
    (LOG_LEVEL, std::optional<std::string>)
)
// In a root entry point: ctx.env.API_KEY(), ctx.env.LOG_LEVEL()
// String-key lookup: ctx.env.get("LOG_LEVEL")
Set
SPACETIMEDB_ENV_HEADER
to this header's full path in CMake before adding the SpacetimeDB module library directory, so the library and module see the same context declaration. Including it manually in only one source file is insufficient.
std::string
is required;
std::optional<std::string>
permits absence. The optional third tuple element restricts allowed strings.
See the concepts skill for shared environment and secret-handling rules.
在专用的
environment.h
中声明环境变量:
cpp
#pragma once
#include <spacetimedb/environment_declaration.h>

SPACETIMEDB_ENV(
    (API_KEY, std::string),
    (MODE, std::string, ("development", "production")),
    (LOG_LEVEL, std::optional<std::string>)
)
// In a root entry point: ctx.env.API_KEY(), ctx.env.LOG_LEVEL()
// String-key lookup: ctx.env.get("LOG_LEVEL")
将
SPACETIMEDB_ENV_HEADER
设置为该头文件的完整路径,并且必须在 CMake 中添加 SpacetimeDB 模块库目录之前完成设置,这样库和模块才能使用相同的上下文声明。仅在一个源文件中手动包含该头文件是不够的。必须使用
std::string
;使用
std::optional<std::string>
则允许不存在值。可选的第三个元组元素用于限制允许的字符串值。
有关共享环境配置和 secret 处理规则,请参阅 concepts skill。

Scheduled Tables

调度表

cpp
struct Reminder {
    uint64_t scheduled_id;
    ScheduleAt scheduled_at;
    std::string message;
};
SPACETIMEDB_STRUCT(Reminder, scheduled_id, scheduled_at, message)
SPACETIMEDB_TABLE(Reminder, reminder, Public)
FIELD_PrimaryKeyAutoInc(reminder, scheduled_id)
SPACETIMEDB_SCHEDULE(reminder, 1, send_reminder)  // 1 = scheduled_at field index (0-based)

SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, Reminder arg) {
    LOG_INFO("Reminder: " + arg.message);
    return Ok();
}

// One-time: fires at a specific time
ctx.db[reminder].insert(Reminder{0, ScheduleAt::time(ctx.timestamp + TimeDuration::from_seconds(10)), "msg"});
// Repeating: fires on an interval
ctx.db[reminder].insert(Reminder{0, ScheduleAt::interval(TimeDuration::from_seconds(5)), "msg"});
cpp
struct Reminder {
    uint64_t scheduled_id;
    ScheduleAt scheduled_at;
    std::string message;
};
SPACETIMEDB_STRUCT(Reminder, scheduled_id, scheduled_at, message)
SPACETIMEDB_TABLE(Reminder, reminder, Public)
FIELD_PrimaryKeyAutoInc(reminder, scheduled_id)
SPACETIMEDB_SCHEDULE(reminder, 1, send_reminder)  // 1 = scheduled_at field index (0-based)

SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, Reminder arg) {
    LOG_INFO("Reminder: " + arg.message);
    return Ok();
}

// One-time: fires at a specific time
ctx.db[reminder].insert(Reminder{0, ScheduleAt::time(ctx.timestamp + TimeDuration::from_seconds(10)), "msg"});
// Repeating: fires on an interval
ctx.db[reminder].insert(Reminder{0, ScheduleAt::interval(TimeDuration::from_seconds(5)), "msg"});

Custom Types

自定义类型

cpp
// Struct (product type):
struct Point { float x; float y; };
SPACETIMEDB_STRUCT(Point, x, y)

// Enum (sum type):
SPACETIMEDB_UNIT_TYPE(Active)
SPACETIMEDB_UNIT_TYPE(Inactive)
SPACETIMEDB_ENUM(PlayerStatus,
    (Active, Active),
    (Inactive, Inactive),
    (Suspended, std::string)
)
cpp
// Struct (product type):
struct Point { float x; float y; };
SPACETIMEDB_STRUCT(Point, x, y)

// Enum (sum type):
SPACETIMEDB_UNIT_TYPE(Active)
SPACETIMEDB_UNIT_TYPE(Inactive)
SPACETIMEDB_ENUM(PlayerStatus,
    (Active, Active),
    (Inactive, Inactive),
    (Suspended, std::string)
)

Logging

日志

cpp
LOG_INFO("Message: " + msg);
LOG_WARN("Warning: " + msg);
LOG_ERROR("Error: " + msg);
LOG_DEBUG("Debug: " + msg);
LOG_PANIC("Fatal: " + msg);   // terminates reducer
cpp
LOG_INFO("Message: " + msg);
LOG_WARN("Warning: " + msg);
LOG_ERROR("Error: " + msg);
LOG_DEBUG("Debug: " + msg);
LOG_PANIC("Fatal: " + msg);   // terminates reducer

Complete Example

完整示例

cpp
#include <spacetimedb.h>
using namespace SpacetimeDB;

struct Entity {
    Identity identity;
    std::string name;
    bool active;
};
SPACETIMEDB_STRUCT(Entity, identity, name, active)
SPACETIMEDB_TABLE(Entity, entity, Public)
FIELD_PrimaryKey(entity, identity)

struct Record {
    uint64_t id;
    Identity owner;
    uint32_t value;
    Timestamp created_at;
};
SPACETIMEDB_STRUCT(Record, id, owner, value, created_at)
SPACETIMEDB_TABLE(Record, record, Public)
FIELD_PrimaryKeyAutoInc(record, id)

SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
    if (auto existing = ctx.db[entity_identity].find(ctx.sender())) {
        existing->active = true;
        ctx.db[entity_identity].update(*existing);
    }
    return Ok();
}

SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
    if (auto existing = ctx.db[entity_identity].find(ctx.sender())) {
        existing->active = false;
        ctx.db[entity_identity].update(*existing);
    }
    return Ok();
}

SPACETIMEDB_REDUCER(create_entity, ReducerContext ctx, std::string name) {
    if (ctx.db[entity_identity].find(ctx.sender())) {
        return Err("already exists");
    }
    ctx.db[entity].insert(Entity{ctx.sender(), name, true});
    return Ok();
}

SPACETIMEDB_REDUCER(add_record, ReducerContext ctx, uint32_t value) {
    if (!ctx.db[entity_identity].find(ctx.sender())) {
        return Err("not found");
    }
    ctx.db[record].insert(Record{0, ctx.sender(), value, ctx.timestamp});
    return Ok();
}
cpp
#include <spacetimedb.h>
using namespace SpacetimeDB;

struct Entity {
    Identity identity;
    std::string name;
    bool active;
};
SPACETIMEDB_STRUCT(Entity, identity, name, active)
SPACETIMEDB_TABLE(Entity, entity, Public)
FIELD_PrimaryKey(entity, identity)

struct Record {
    uint64_t id;
    Identity owner;
    uint32_t value;
    Timestamp created_at;
};
SPACETIMEDB_STRUCT(Record, id, owner, value, created_at)
SPACETIMEDB_TABLE(Record, record, Public)
FIELD_PrimaryKeyAutoInc(record, id)

SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
    if (auto existing = ctx.db[entity_identity].find(ctx.sender())) {
        existing->active = true;
        ctx.db[entity_identity].update(*existing);
    }
    return Ok();
}

SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
    if (auto existing = ctx.db[entity_identity].find(ctx.sender())) {
        existing->active = false;
        ctx.db[entity_identity].update(*existing);
    }
    return Ok();
}

SPACETIMEDB_REDUCER(create_entity, ReducerContext ctx, std::string name) {
    if (ctx.db[entity_identity].find(ctx.sender())) {
        return Err("already exists");
    }
    ctx.db[entity].insert(Entity{ctx.sender(), name, true});
    return Ok();
}

SPACETIMEDB_REDUCER(add_record, ReducerContext ctx, uint32_t value) {
    if (!ctx.db[entity_identity].find(ctx.sender())) {
        return Err("not found");
    }
    ctx.db[record].insert(Record{0, ctx.sender(), value, ctx.timestamp});
    return Ok();
}