mcpField
C
cpp-server

cpp-server

github.com/clockworklabsDevelopment25.4k45 installsUpdated today
Repository

About cpp-server

A C++ SDK reference skill for developers creating SpacetimeDB server modules. It documents table macros, reducers, database access, indexes, and runtime context APIs.

Key features

  1. 01Table and field macro patterns
  2. 02Reducer and database operations
  3. 03Indexes and range queries
  4. 04Lifecycle hooks and scheduled tables

Use cases

  1. 01Defining C++ tables and constraints
  2. 02Implementing reducers and database access
  3. 03Adding indexed or range-based queries

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/clockworklabs/spacetimedb --skill cpp-server

After installing, type/cpp-serverin Claude Code to confirm it loaded.

Details

From the project README

SpacetimeDB C++ SDK Reference

Imports

#include <spacetimedb.h>
using namespace SpacetimeDB;

Tables

Register structs with macros, then declare as tables:

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())

Column Types

C++ typeNotes
uint8_t / uint16_t / uint32_t / uint64_tunsigned integers
SpacetimeDB::u128 / SpacetimeDB::u256large unsigned integers
int8_t / int16_t / int32_t / int64_tsigned integers
SpacetimeDB::i128 / SpacetimeDB::i256large signed integers
float / doublefloats
boolboolean
std::stringtext
std::vector<T>list/array
std::optional<T>nullable column
Identityuser identity
ConnectionIdconnection handle
Timestampserver timestamp (microseconds since epoch)
TimeDurationduration in microseconds
ScheduleAtfor scheduled tables

Indexes

// 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):

ctx.db[user_age].filter(range_inclusive(uint8_t(18), uint8_t(65)));
ctx.db[user_age].filter(range_from(uint8_t(18)));

Reducers

All reducers return ReducerResult. Use Ok() or 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();
}

DB Operations

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.

Lifecycle Hooks

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

// 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

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.

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

Scheduled Tables

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

// 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

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

Complete Example

#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();
}

Comments

Markdown supported