关于 cpp-server
面向开发 SpacetimeDB 服务端模块的 C++ SDK 参考技能,涵盖表宏、Reducer、数据库访问、索引和运行时上下文 API。
主要功能
- 01表与字段宏用法
- 02Reducer 和数据库操作
- 03索引与范围查询
- 04生命周期钩子和定时表
适用场景
- 01定义 C++ 表和约束
- 02实现 Reducer 与数据库访问
- 03添加索引查询或范围查询
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ clockworklabs/ spacetimedb --skill cpp-server安装后在 Claude Code 中输入/cpp-server确认已加载。
详细介绍
SpacetimeDB C++ SDK 参考
导入
#include <spacetimedb.h>
using namespace SpacetimeDB;
表
使用宏注册结构体,然后将其声明为表:
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())
列类型
| 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 | 服务端时间戳(自纪元起的微秒数) |
TimeDuration | 微秒级时长 |
ScheduleAt | 用于计划表 |
索引
// 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>):
ctx.db[user_age].filter(range_inclusive(uint8_t(18), uint8_t(65)));
ctx.db[user_age].filter(range_from(uint8_t(18)));
Reducer
所有 reducer 都返回 ReducerResult。使用 Ok() 或 Err(message):
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();
}
数据库操作
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_* 宏。
生命周期钩子
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();
}
身份验证与时间戳
// 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 上下文
ReducerContext 是 reducer 中发送者身份、确定性时间和确定性随机性的唯一来源。始终通过 ctx 获取这些信息。模块中无法使用标准库时钟和随机源。
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
计划表
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"});
自定义类型
// 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)
)
日志记录
LOG_INFO("Message: " + msg);
LOG_WARN("Warning: " + msg);
LOG_ERROR("Error: " + msg);
LOG_DEBUG("Debug: " + msg);
LOG_PANIC("Fatal: " + msg); // terminates reducer
完整示例
#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();
}
评论
支持 Markdown