Jason Larabie 14f79910ee
Update C++ module bindings to RawModuleDefV10 (#4461)
# Description of Changes
- Migrated the C++ module-definition assembly path to V10-first
internals:
      - Added v10_builder and module_type_registration systems.
- Switched Module::__describe_module__ to serialize RawModuleDef with
V10 payload.
      - Updated macro registration pipeline to register through V10
- Added explicit naming support across macro surface (*_NAMED variants
for reducer/procedure/
        view and field/index macros).
- Reworked multi-column index macros (FIELD_MultiColumnIndex,
FIELD_MultiColumnIndex_NAMED) with
        migration alias.
- Added SPACETIMEDB_SETTING_CASE_CONVERSION(...) to support case
conversion policy
- Error-path hardening by adding explicit constraint-registration error
tracking and preinit validation
  - Codegen updates:
      - Updated C++ moduledef regen to V10 builder types.
- Adjusted C++ codegen duplicate-variant wrapper generation to emit
proper product-type
        wrappers.
  - Test/harness updates:
- type-isolation-test runner now defaults to focused V10 regression
checks; --v9 runs broader
        legacy/full suite.
      - Added focused modules for positive/negative V10 checks:
          - test_multicolumn_index_valid
          - error_multicolumn_missing_field
          - error_default_missing_field
- Re-enabled C++ paths in sdks/rust/tests/test.rs procedure/view/test
suites.

# API and ABI breaking changes

- Refactor of the underlying module definition
- New *_NAMED variant macros for explicit canonical naming
- FIELD_NamedMultiColumnIndex renamed to FIELD_MultiColumnIndex

# Expected complexity level and risk

3 - Large set of changes moving over to V10 with underlying changes to
make future updates a little easier

# Testing
- [x] Ran the type isolation test and expanded it
- [x] Ran the spacetimedb-sdk test framework to confirm no more drift
between C++ and other module languages
- [x] Ran Unreal test suite though not really applicable
- [x] New app creation with `spacetime init --template basic-cpp`
- [x] Ran describe module tests against Rust + C# matching with C++ on
the /modules/sdk-test* modules to find any possible mis-alignment

# Review
- [x] Another look at the new features with C++
- [x] Thoughts on *_NAMED macros, I couldn't come up with a better
solution with C++20
2026-02-28 07:05:50 +00:00
..
2026-02-07 04:26:45 +00:00
2026-02-07 04:26:45 +00:00
2026-02-07 04:26:45 +00:00
2026-02-07 04:26:45 +00:00
2026-02-07 04:26:45 +00:00

SpacetimeDB C++ Module Library

The SpacetimeDB C++ Module Library provides a modern C++20 API for building SpacetimeDB modules that run inside the database as WebAssembly.

Current State

This library provides a production-ready C++ bindings for SpacetimeDB with complete type system support:

Features

  • Module compilation and publishing to SpacetimeDB
  • All lifecycle reducers (init, client_connected, client_disconnected)
  • User-defined reducers with unlimited parameters
  • Table registration with constraints (PrimaryKey, Unique, AutoInc)
  • Insert, update and delete operations
  • All primitive types (u8-u256, i8-i256, bool, f32, f64, string)
  • All special types (Identity, ConnectionId, Timestamp, TimeDuration, Uuid, Result<>)
  • Vector types for all primitives and special types
  • Optional types (std::optional)
  • Custom struct serialization via BSATN
  • Complex enum support with proper variant names
  • Enhanced logging system with file/line info

🏗️ Architecture

  • Hybrid Compile-Time/Runtime System: C++20 concepts for compile-time validation with preinit runtime registration
  • V9 Type Registration System: Unified type registration with comprehensive error detection and circular reference prevention
  • Nominal Type System: Types identified by their declared names with explicit registration via SPACETIMEDB_STRUCT macros
  • Multi-Layer Validation: Static assertions, runtime constraint checking, and error module replacement strategy

See ARCHITECTURE.md for detailed technical documentation.

Advanced Features Available

  • Btree indexes: Full support with FIELD_Index macros and optimized queries
  • Range queries: Complete range query system with range_from(), range_to(), range_inclusive(), etc.
  • Client visibility filters: Row-level security with SPACETIMEDB_CLIENT_VISIBILITY_FILTER macro
  • Scheduled reducers: SPACETIMEDB_SCHEDULE macro for time-based execution
  • Procedures: Pure functions with return values using SPACETIMEDB_PROCEDURE macro
  • Views: Read-only query functions with SPACETIMEDB_VIEW macro
  • Field accessor patterns: Efficient indexed operations with ctx.db[table_field]

See the working examples in modules/*-cpp/src/lib.cpp for comprehensive feature usage.

Features

  • Modern C++20 API: Uses concepts, structured bindings, and other C++20 features
  • BSATN Serialization: Binary Serialization And Type Notation for efficient data transfer
  • Automatic Field Registration: Tables register their fields using SPACETIMEDB_STRUCT macro
  • Unified Reducer System: Single macro for all reducer types with automatic lifecycle detection
  • Type-Safe Database Access: Template-based table accessors with compile-time type checking
  • Memory Safety: WASI shims for safe memory operations in WebAssembly environment
  • Enhanced Logging: Multiple log levels with file/line information
  • Namespace Support: Clean namespace qualification for enums with just 2 lines of code

Prerequisites

  • Emscripten SDK (emsdk)
  • CMake 3.16+
  • C++20 compatible compiler

Quick Start

# Create a new C++ project
spacetime init --lang cpp my-project
cd my-project

# Build and publish
spacetime build -p ./spacetimedb
spacetime publish -p ./spacetimedb my-database

Option 2: Manual Setup

For existing projects, add the following to your C++ module:

#include <spacetimedb.h>

using namespace SpacetimeDB;

// Define a table structure
struct User {
    Identity identity;
    std::string name;
    std::string email;
};

// Register BSATN serialization
SPACETIMEDB_STRUCT(User, identity, name, email)

// Register as a table
SPACETIMEDB_TABLE(User, users, Public)

// Add constraints using FIELD_ macros
FIELD_PrimaryKey(users, identity);
FIELD_Unique(users, email);

// Define an enum with namespace qualification
SPACETIMEDB_ENUM(UserRole, Admin, Moderator, Member)
SPACETIMEDB_NAMESPACE(UserRole, "Auth")  // Will be "Auth.UserRole" in client code

// User-defined reducer
SPACETIMEDB_REDUCER(add_user, ReducerContext ctx, std::string name, std::string email) {
    User user{ctx.sender, name, email}; // id will be auto-generated
    ctx.db[users].insert(user);
    LOG_INFO("Added user: " + name);
    return Ok();
}

// Delete user by id (using primary key)
SPACETIMEDB_REDUCER(delete_user, ReducerContext ctx) {
    ctx.db[users_identity].delete_by_key(ctx.sender);
    return Ok();
}

// Lifecycle reducers (optional)
SPACETIMEDB_INIT(init, ReducerContext ctx) {
    LOG_INFO("Module initialized");
    return Ok();
}

SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
    LOG_INFO("Client connected: " + ctx.sender.to_hex_string());
    return Ok();
}

SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) {
    LOG_INFO("Client disconnected: " + ctx.sender.to_hex_string());
    return Ok();
}

// Define a view for querying data (finds the calling user)
SPACETIMEDB_VIEW(std::optional<User>, find_my_user, Public, ViewContext ctx) {
    // Use indexed field to find user by their identity
    return ctx.db[users_identity].find(ctx.sender);
}

// Define a procedure (pure function with return value)
SPACETIMEDB_PROCEDURE(uint32_t, add_numbers, ProcedureContext ctx, uint32_t a, uint32_t b) {
    return a + b;
}

Building Modules

Build Steps

# Navigate to your module directory
cd modules/your-module

# Build the project
spacetime build -p ./spacetimedb

# Publish to SpacetimeDB
spacetime publish --bin-path ./spacetimedb/build/lib.wasm your-database-name
# Or use the directory (auto-detects build/lib.wasm)
spacetime publish ./spacetimedb your-database-name

Custom Module Source

To build a different source file:

# Build a specific test module
emcmake cmake -B build -DMODULE_SOURCE=src/test_module.cpp -DOUTPUT_NAME=test_module .
cmake --build build
# This creates build/test_module.wasm

API Reference

Macros

Table Definition

  • SPACETIMEDB_TABLE(Type, table_name, Public/Private) - Register a table
  • SPACETIMEDB_STRUCT(Type, field1, field2, ...) - Register type for BSATN serialization

Enum Definition

  • SPACETIMEDB_ENUM(EnumName, Value1, Value2, ...) - Define a simple enum
  • SPACETIMEDB_ENUM(EnumName, (Variant1, Type1), (Variant2, Type2), ...) - Define an enum with payloads
  • SPACETIMEDB_NAMESPACE(EnumName, "Namespace") - Add namespace qualification to an enum

Reducers

  • SPACETIMEDB_REDUCER(name, ReducerContext ctx, ...) - User-defined reducer
    • Returns ReducerResult (alias for Outcome<void>)
    • Use return Ok(); for success or return Err("message"); for errors
    • Failed reducers (Err) trigger transaction rollback
  • SPACETIMEDB_INIT(name, ReducerContext ctx) - Module initialization reducer (optional)
  • SPACETIMEDB_CLIENT_CONNECTED(name, ReducerContext ctx) - Client connection reducer (optional)
  • SPACETIMEDB_CLIENT_DISCONNECTED(name, ReducerContext ctx) - Client disconnection reducer (optional)

Views

  • SPACETIMEDB_VIEW(return_type, name, Public/Private, ViewContext ctx) - Read-only query function
  • SPACETIMEDB_VIEW(return_type, name, Public/Private, AnonymousViewContext ctx) - Anonymous view (no sender identity)
  • Note: Views currently only support the context parameter (no additional parameters yet)

Procedures

  • SPACETIMEDB_PROCEDURE(return_type, name, ProcedureContext ctx, ...) - Pure function that returns a value
    • Returns the type directly (not wrapped in Outcome)
    • Can return any SpacetimeType (primitives, structs, enums, Unit, etc.)
    • Database access requires explicit transactions (use ctx.WithTx() or ctx.TryWithTx())
    • Always public (no access control)

Field Constraints (applied after table registration)

  • FIELD_PrimaryKey(table_name, field) - Primary key constraint
  • FIELD_PrimaryKeyAutoInc(table_name, field) - Auto-incrementing primary key
  • FIELD_Unique(table_name, field) - Unique constraint
  • FIELD_UniqueAutoInc(table_name, field) - Auto-incrementing unique field
  • FIELD_Index(table_name, field) - Index for faster queries
  • FIELD_IndexAutoInc(table_name, field) - Auto-incrementing indexed field
  • FIELD_AutoInc(table_name, field) - Auto-increment without other constraints

Logging

LOG_DEBUG("Debug message");
LOG_INFO("Info message");
LOG_WARN("Warning message");
LOG_ERROR("Error message");
LOG_PANIC("Fatal error message");

// With timing
{
    LogStopwatch timer("Operation name");
    // ... code to time ...
} // Automatically logs duration

Architecture

The library uses a sophisticated hybrid compile-time/runtime architecture:

  • Compile-Time Validation (table_with_constraints.h): C++20 concepts and static assertions for constraint validation
  • Module Type Registration System (internal/module_type_registration.h): Unified type registration with error detection and circular reference prevention
  • Priority-Ordered Initialization (internal/Module.cpp): preinit functions with numbered priorities ensure correct registration order
  • Error Detection System (internal/Module.cpp): Multi-layer validation with error module replacement for clear diagnostics
  • BSATN Serialization (bsatn/): Binary serialization system with algebraic type support for all data types
  • Database Interface (database.h, table_with_constraints.h): Type-safe table access with optimized field accessors
  • Reducer System (reducer_macros.h): Unified macro system for all reducer types with parameter type capture
  • Logging (logger.h): Comprehensive logging with source location tracking

For detailed technical documentation, see ARCHITECTURE.md.

Note on Architecture Documentation: ARCHITECTURE.md contains references to some legacy implementation details. The current implementation is streamlined and production-ready.

Limitations

  1. Type System

    • Very large type combinations may exceed WASM memory limits
    • Complex recursive type references require careful ordering
  2. Database Operations

    • Index-based operations use field accessors: ctx.db[table_field].delete_by_key(value)
    • Table constraints are declared and enforced by server
    • Supports insert, delete, and update operations through field accessors
  3. Advanced Features

    • Btree indexes: FIELD_Index creates btree indexes for efficient range queries
    • Range queries: Full support for range_from(), range_to(), range_inclusive(), etc.
    • Client visibility filters: Row-level security via SPACETIMEDB_CLIENT_VISIBILITY_FILTER
    • Limited migrations: Only adding tables supported automatically
    • SQL execution: Available via CLI (spacetime sql) but not within modules

Examples

See the modules/*-cpp/src/ directory for example modules:

  • lib.cpp - Comprehensive working module with all primitive types, tables, and reducers
  • Full equivalence with Rust and C# SDK test modules
  • Examples of all constraint types and database operations

Contributing

This library is part of the SpacetimeDB project. Please see the main repository for contribution guidelines.