Files
Jason Larabie 52b6c66fa1 Add C++ Bindings (#3544)
# Description of Changes

This adds C++ server bindings (/crate/bindings-cpp) to allow writing C++
20 modules.

- Emscripten WASM build system integration with CMake
- Macro-based code generation (SPACETIMEDB_TABLE, SPACETIMEDB_REDUCER,
etc)
- All SpacetimeDB types supported (primitives, Timestamp, Identity,
Uuid, etc)
- Product types via SPACETIMEDB_STRUCT
- Sum types via SPACETIMEDB_ENUM
- Constraints marked with FIELD* macros

# API and ABI breaking changes

None

# Expected complexity level and risk

2 - Doesn't heavily impact any other areas but is complex macro C++
structure to support a similar developer experience, did have a small
impact on init command

# Testing

- [x] modules/module-test-cpp - heavily tested every reducer
- [x] modules/benchmarks-cpp - tested through the standalone (~6x faster
than C#, ~6x slower than Rust)
- [x] modules/sdk-test-cpp
- [x] modules/sdk-test-procedure-cpp
- [x] modules/sdk-test-view-cpp  
- [x] Wrote several test modules myself
- [x] Quickstart smoketest [Currently in progress]
- [ ] Write Blackholio C++ server module

---------

Signed-off-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
2026-02-07 04:26:45 +00:00

118 lines
3.9 KiB
C++

#ifndef SPACETIMEDB_TX_CONTEXT_H
#define SPACETIMEDB_TX_CONTEXT_H
#include <spacetimedb/reducer_context.h>
namespace SpacetimeDB {
/**
* @brief Transaction context for procedures
*
* TxContext wraps a ReducerContext to provide transactional database access.
* It's analogous to Rust's TxContext which is passed to closures in
* `ctx.with_tx()` and `ctx.try_with_tx()`.
*
* Design: Mimics Rust's Deref trait for consistent API
* =====================================================
* In Rust, TxContext implements Deref<Target=ReducerContext>, which means:
* - Reducers use: ctx.db.table()
* - Transactions use: tx.db.table() (Deref auto-dereferences)
*
* C++ doesn't support operator. overloading, so we explicitly expose
* ReducerContext fields as public references to achieve the same ergonomics:
* - Reducers use: ctx.db[table]
* - Transactions use: tx.db[table] (same syntax!)
*
* Tradeoff: TxContext is 40 bytes instead of 8 bytes (storing 5 references),
* but this is negligible as TxContext is stack-allocated and short-lived.
* The consistent API is worth the minor memory cost.
*
* All database operations are part of an anonymous transaction:
* - Transaction commits when the callback returns successfully
* - Transaction rolls back if the callback throws or returns error
*
* Example usage:
* @code
* SPACETIMEDB_PROCEDURE(void, insert_user, ProcedureContext ctx, std::string name) {
* ctx.with_tx([&](TxContext& tx) {
* // Access authentication (same as in reducers)
* if (tx.sender_auth().has_jwt()) {
* auto jwt = tx.sender_auth().get_jwt();
* // ...
* }
* // Database operations here are transactional (same syntax as reducers)
* tx.db[users].insert(User{name});
* });
* }
* @endcode
*/
struct TxContext {
private:
ReducerContext& ctx_;
public:
// Public references to ReducerContext fields for consistent API with Rust
// In Rust, Deref makes tx.db work the same as ctx.db
// In C++, we explicitly expose references to achieve the same ergonomics
DatabaseContext& db;
const Identity& sender;
const Timestamp& timestamp;
const std::optional<ConnectionId>& connection_id;
// Constructor - initializes all reference members
explicit TxContext(ReducerContext& ctx)
: ctx_(ctx),
db(ctx.db),
sender(ctx.sender),
timestamp(ctx.timestamp),
connection_id(ctx.connection_id) {}
// Access to ReducerContext methods
const AuthCtx& sender_auth() const { return ctx_.sender_auth(); }
Identity identity() const { return ctx_.identity(); }
StdbRng& rng() const { return ctx_.rng(); }
/**
* Generate a new random UUID v4.
*
* Creates a random UUID using the transaction's deterministic RNG.
*
* Example:
* @code
* SPACETIMEDB_PROCEDURE(void, create_session, ProcedureContext ctx) {
* ctx.with_tx([&](TxContext& tx) {
* Uuid session_id = tx.new_uuid_v4();
* tx.db[sessions].insert(Session{session_id});
* });
* }
* @endcode
*
* @return A new UUID v4
*/
Uuid new_uuid_v4() const { return ctx_.new_uuid_v4(); }
/**
* Generate a new UUID v7.
*
* Creates a time-ordered UUID with the transaction's timestamp, a monotonic counter,
* and random bytes from the transaction's deterministic RNG.
*
* Example:
* @code
* SPACETIMEDB_PROCEDURE(void, create_user, ProcedureContext ctx, std::string name) {
* ctx.with_tx([&](TxContext& tx) {
* Uuid user_id = tx.new_uuid_v7();
* tx.db[users].insert(User{user_id, name});
* });
* }
* @endcode
*
* @return A new UUID v7
*/
Uuid new_uuid_v7() const { return ctx_.new_uuid_v7(); }
};
} // namespace SpacetimeDB
#endif // SPACETIMEDB_TX_CONTEXT_H