Files
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

278 lines
11 KiB
Markdown

# 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<T>)
- 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](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
### Option 1: Using spacetime init (Recommended)
```bash
# 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:
```cpp
#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
```bash
# 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:
```bash
# 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
```cpp
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](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.