diff --git a/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md b/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md
index d4fa03561bd..afef567efca 100644
--- a/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md
+++ b/docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md
@@ -71,6 +71,21 @@ pub struct Player {
}
```
+
+
+
+```cpp
+struct Player {
+ uint64_t id;
+ std::string name;
+ uint32_t age;
+ Identity user;
+};
+SPACETIMEDB_STRUCT(Player, id, name, age, user)
+SPACETIMEDB_TABLE(Player, players, Public)
+FIELD_PrimaryKey(players, id)
+```
+
@@ -147,6 +162,28 @@ fn main() {
}
```
+
+
+
+A reducer can be written in C++ like so:
+
+```cpp
+SPACETIMEDB_REDUCER(set_player_name, ReducerContext ctx, uint64_t id, std::string name) {
+ // ...
+ return Ok();
+}
+```
+
+And an Unreal C++ [client](#client) can call that reducer:
+
+```cpp
+void AMyGameManager::UpdatePlayerName()
+{
+ // ...setup code, then...
+ Conn->Reducers->SetPlayerName(57, "Marceline");
+}
+```
+
@@ -242,6 +279,27 @@ While SpacetimeDB doesn't support nested transactions,
a reducer can [schedule another reducer](https://docs.rs/spacetimedb/latest/spacetimedb/attr.reducer.html#scheduled-reducers) to run at an interval,
or at a specific time.
+
+
+
+```cpp
+SPACETIMEDB_REDUCER(world, ReducerContext ctx) {
+ clear_all_tables(ctx);
+ return Ok();
+}
+
+SPACETIMEDB_REDUCER(hello, ReducerContext ctx) {
+ if (world(ctx).is_err()) {
+ other_changes(ctx);
+ }
+ return Ok();
+}
+```
+
+While SpacetimeDB doesn't support nested transactions,
+a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
+or at a specific time.
+
@@ -361,7 +419,21 @@ fn main() {
```
-
+
+
+A procedure can be defined in a C++ module:
+
+```cpp
+SPACETIMEDB_PROCEDURE(std::string, make_request, ProcedureContext ctx) {
+ // ...
+ return std::string{"result"};
+}
+```
+
+Use the other tabs (TypeScript/C#/Rust/Unreal C++/Blueprint) for client call examples.
+
+
+
An Unreal C++ [client](#client) can call a procedure defined by a Rust or TypeScript module:
@@ -455,6 +527,17 @@ fn my_player(ctx: &spacetimedb::ViewContext) -> Option {
}
```
+
+
+
+A view can be written in C++ like so:
+
+```cpp
+SPACETIMEDB_VIEW(std::optional, my_player, Public, ViewContext ctx) {
+ return ctx.db[player_identity].find(ctx.sender);
+}
+```
+
diff --git a/docs/docs/00100-intro/00200-quickstarts/00700-cpp.md b/docs/docs/00100-intro/00200-quickstarts/00700-cpp.md
new file mode 100644
index 00000000000..3aca542944c
--- /dev/null
+++ b/docs/docs/00100-intro/00200-quickstarts/00700-cpp.md
@@ -0,0 +1,130 @@
+---
+title: C++ Quickstart
+sidebar_label: C++
+slug: /quickstarts/cpp
+hide_table_of_contents: true
+---
+
+import { InstallCardLink } from "@site/src/components/InstallCardLink";
+import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps";
+
+Get a SpacetimeDB C++ app running in under 5 minutes.
+
+## Prerequisites
+
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+- Emscripten SDK installed and on PATH (download from [emscripten.org/docs/getting_started/downloads.html](https://emscripten.org/docs/getting_started/downloads.html); use `emsdk_env.bat`/`emsdk_env.sh`)
+- CMake 3.20+ and a make/ninja backend
+- C++20 toolchain (host) — build targets WASM via Emscripten
+
+
+
+---
+
+
+
+
+ Use the official SDK (see [Emscripten downloads](https://emscripten.org/docs/getting_started/downloads.html)) and activate the environment so `emcc` and the CMake toolchain file are on PATH. We recommend Emscripten 4.0.21+.
+
+
+```bash
+# From your emsdk directory (after downloading/cloning)
+# Windows PowerShell
+./emsdk install 4.0.21
+./emsdk activate 4.0.21
+./emsdk_env.ps1
+
+# macOS/Linux
+./emsdk install 4.0.21
+./emsdk activate 4.0.21
+source ./emsdk_env.sh
+```
+
+
+
+
+
+ Prefer the CLI-managed build with `spacetime build` (wraps CMake+emcc for you). The basic C++ template starts the local server, builds/publishes your module, and generates client bindings.
+
+
+```bash
+spacetime dev --template basic-cpp my-spacetime-app
+```
+
+
+ Need manual control? You can still drive CMake+emcc directly (see `spacetimedb/CMakeLists.txt`), but the recommended path is `spacetime build`/`spacetime dev`.
+
+
+
+
+
+ Server code lives in the `spacetimedb` folder; the template uses CMake and the SpacetimeDB C++ SDK.
+
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your C++ module
+│ ├── CMakeLists.txt
+│ └── src/
+│ └── lib.cpp # Server-side logic
+└── client/ # Client bindings (generated by spacetime dev)
+```
+
+
+
+
+
+ The template includes a `Person` table and two reducers: `add` to insert, `say_hello` to iterate and log.
+
+
+```cpp
+#include "spacetimedb.h"
+using namespace SpacetimeDB;
+
+struct Person { std::string name; };
+SPACETIMEDB_STRUCT(Person, name)
+SPACETIMEDB_TABLE(Person, person, Public)
+
+SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name) {
+ ctx.db[person].insert(Person{name});
+ return Ok();
+}
+
+SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) {
+ for (const auto& person : ctx.db[person]) {
+ LOG_INFO("Hello, " + person.name + "!");
+ }
+ LOG_INFO("Hello, World!");
+ return Ok();
+}
+```
+
+
+
+
+
+ Call reducers and inspect data right from the CLI.
+
+
+```bash
+# Insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+
+# Greet everyone
+spacetime call say_hello
+
+# View module logs
+spacetime logs
+```
+
+
+
+
+## Notes
+
+- To use a local SDK clone instead of the fetched archive, set `SPACETIMEDB_CPP_SDK_DIR` before running `spacetime dev`/`spacetime build`.
+- The template builds to WebAssembly with exceptions disabled (`-fno-exceptions`).
+- If `emcc` is not found, re-run the appropriate `emsdk_env` script to populate environment variables.
diff --git a/docs/docs/00200-core-concepts/00100-databases.md b/docs/docs/00200-core-concepts/00100-databases.md
index 107cadc80cc..541ba44d9a4 100644
--- a/docs/docs/00200-core-concepts/00100-databases.md
+++ b/docs/docs/00200-core-concepts/00100-databases.md
@@ -7,7 +7,7 @@ slug: /databases
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-A **module** is a collection of functions and schema definitions, which can be written in TypeScript, C# or Rust. Modules define the structure of your database and the server-side logic that processes and handles client requests.
+A **module** is a collection of functions and schema definitions, which can be written in TypeScript, C#, Rust, or C++. Modules define the structure of your database and the server-side logic that processes and handles client requests.
A **database** is a running instance of a module. While a module is the code you write (schema and reducers), a database is the actual deployed entity running on a SpacetimeDB **host** with stored data and active connections.
@@ -15,7 +15,7 @@ A **database** is a running instance of a module. While a module is the code you
Understanding this distinction is important:
-- A **module** is the code you write; it defines your schema (tables) and business logic (reducers, procedures, and views). Modules are compiled and deployed to SpacetimeDB. Rust and C# modules compile to WebAssembly, while TypeScript modules run on V8.
+- A **module** is the code you write; it defines your schema (tables) and business logic (reducers, procedures, and views). Modules are compiled and deployed to SpacetimeDB. Rust, C#, and C++ modules compile to WebAssembly, while TypeScript modules run on V8.
- A **database** is a *running instance* of a module; it has the module's schema and logic, plus actual stored data.
You can deploy the same module to multiple databases (e.g. separate environments for testing, staging, production), each with its own independent data. When you update your module code and re-publish, SpacetimeDB will update the database's schema/logic — the existing data remains (though for complicated schema changes you may need to handle migrations carefully).
@@ -57,6 +57,13 @@ Rust is fully supported for server modules. Rust is a great choice for performan
- The Rust Module SDK docs are [hosted on docs.rs](https://docs.rs/spacetimedb/latest/spacetimedb/).
- [Rust Quickstart Guide](/quickstarts/rust)
+
+
+
+C++ is fully supported for server modules. C++ is an excellent choice for developers working with Unreal Engine or those who prefer to stay in the C++ ecosystem.
+
+- [C++ Quickstart Guide](/quickstarts/c-plus-plus)
+
diff --git a/docs/docs/00200-core-concepts/00100-databases/00100-transactions-atomicity.md b/docs/docs/00200-core-concepts/00100-databases/00100-transactions-atomicity.md
index f1acc2c3b47..a62f18a058f 100644
--- a/docs/docs/00200-core-concepts/00100-databases/00100-transactions-atomicity.md
+++ b/docs/docs/00200-core-concepts/00100-databases/00100-transactions-atomicity.md
@@ -152,6 +152,42 @@ pub fn child_reducer(ctx: &ReducerContext) -> Result<(), String> {
}
```
+
+
+
+```cpp
+using namespace SpacetimeDB;
+
+// Forward declare child reducer to allow calling it before its definition
+ReducerResult child_reducer(ReducerContext&, bool some_condition);
+
+SPACETIMEDB_REDUCER(parent_reducer, ReducerContext ctx, bool some_condition) {
+ ctx.db[table_a].insert(RowA{ /* ... */ });
+
+ // This runs in the SAME transaction
+ ReducerResult result = child_reducer(ctx, some_condition);
+ if (result.is_err()) {
+ return result;
+ }
+
+ ctx.db[table_b].insert(RowB{ /* ... */ });
+
+ // All changes from both parent and child commit together
+ return Ok();
+}
+
+SPACETIMEDB_REDUCER(child_reducer, ReducerContext ctx, bool some_condition) {
+ ctx.db[table_c].insert(RowC{ /* ... */ });
+
+ // If this returns Err, the parent's changes also roll back
+ if (some_condition) {
+ return Err("Child failed");
+ }
+
+ return Ok();
+}
+```
+
diff --git a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md
index 864d28bf4e2..1fa6c8eab67 100644
--- a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md
+++ b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md
@@ -67,6 +67,7 @@ Choose from several built-in templates:
- `basic-ts` - Basic TypeScript client and server stubs
- `basic-cs` - Basic C# client and server stubs
- `basic-rs` - Basic Rust client and server stubs
+- `basic-cpp` - Basic C++ server stubs
- `react-ts` - React web app with TypeScript server
- `chat-console-rs` - Complete Rust chat implementation
- `chat-console-cs` - Complete C# chat implementation
@@ -81,6 +82,7 @@ Creates a server module only, without any client code. You'll choose your server
- **TypeScript** - Server module in TypeScript
- **Rust** - Server module in Rust
- **C#** - Server module in C#
+- **C++** - Server module in C++
The server code will be created in a `spacetimedb/` subdirectory within your project.
@@ -147,6 +149,18 @@ my-project/
└── README.md
```
+
+
+
+```text
+my-project/
+├── spacetimedb/ # Server module code (C++)
+│ ├── CMakeLists.txt
+│ └── src/
+│ └── lib.cpp
+└── README.md
+```
+
@@ -198,6 +212,20 @@ This creates a new Rust project with:
- A `src/lib.rs` with a sample module
- Sample table and reducer definitions
+
+
+
+```bash
+spacetime init --lang cpp --project-path ./my-project my-project
+cd my-project
+```
+
+This creates a new C++ project with:
+
+- A `CMakeLists.txt` configured for SpacetimeDB
+- A `src/lib.cpp` with a sample module
+- Sample table and reducer definitions
+
diff --git a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md
index 529e79d6ec4..5655e689eba 100644
--- a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md
+++ b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md
@@ -41,6 +41,16 @@ spacetime login
spacetime publish
```
+
+
+
+```bash
+spacetime init --lang cpp --project-path my-project my-project
+cd my-project
+spacetime login
+spacetime publish
+```
+
@@ -154,6 +164,40 @@ pub enum Status {
}
```
+
+
+
+```cpp
+#include
+using namespace SpacetimeDB;
+
+// Basic table
+struct Player {
+ uint64_t id;
+ std::string username;
+ int32_t score;
+};
+SPACETIMEDB_STRUCT(Player, id, username, score);
+SPACETIMEDB_TABLE(Player, player, Public);
+FIELD_PrimaryKeyAutoInc(player, id);
+FIELD_Unique(player, username);
+FIELD_Index(player, score);
+
+// Multi-column index
+struct Score {
+ uint64_t player_id;
+ uint32_t level;
+};
+SPACETIMEDB_STRUCT(Score, player_id, level);
+SPACETIMEDB_TABLE(Score, score, Private);
+// Named multi-column btree index on (player_id, level)
+FIELD_NamedMultiColumnIndex(score, idx, player_id, level);
+
+// Custom types (enums)
+// Note: 'Status' conflicts with a built-in SDK type; use a distinct name
+SPACETIMEDB_ENUM(PlayerStatus, Active, Inactive);
+```
+
@@ -246,6 +290,38 @@ let all = ctx.db.player().iter(); // Iterate all
ctx.db.player().id().delete(123); // Delete by primary key
```
+
+
+
+```cpp
+#include
+using namespace SpacetimeDB;
+
+// Basic reducer
+SPACETIMEDB_REDUCER(create_player, ReducerContext ctx, std::string username) {
+ ctx.db[player].insert(Player{0, username, 0});
+ return Ok();
+}
+
+// With error handling
+SPACETIMEDB_REDUCER(update_score, ReducerContext ctx, uint64_t id, int32_t points) {
+ auto player_opt = ctx.db[player_id].find(id);
+ if (!player_opt) {
+ return Err("Player not found");
+ }
+ Player updated = *player_opt;
+ updated.score += points;
+ ctx.db[player_id].update(updated);
+ return Ok();
+}
+
+// Query examples
+auto player = ctx.db[player_id].find((uint64_t)123); // Find by primary key
+auto player_by_name = ctx.db[player_username].find(std::string("Alice")); // Filter by unique index
+for (const auto& p : ctx.db[player]) { /* iterate all */ } // Iterate all
+ctx.db[player_id].delete_by_key((uint64_t)123); // Delete by primary key
+```
+
@@ -290,6 +366,19 @@ pub fn on_connect(ctx: &ReducerContext) { /* ... */ }
pub fn on_disconnect(ctx: &ReducerContext) { /* ... */ }
```
+
+
+
+```cpp
+using namespace SpacetimeDB;
+
+SPACETIMEDB_INIT(init, ReducerContext ctx) { /* ... */ }
+
+SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) { /* ... */ }
+
+SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) { /* ... */ }
+```
+
@@ -353,6 +442,27 @@ fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
}
```
+
+
+
+```cpp
+struct Reminder {
+ uint64_t id;
+ std::string message;
+ ScheduleAt scheduled_at;
+};
+SPACETIMEDB_STRUCT(Reminder, id, message, scheduled_at)
+SPACETIMEDB_TABLE(Reminder, reminder, Private)
+FIELD_PrimaryKeyAutoInc(reminder, id)
+
+SPACETIMEDB_SCHEDULE(reminder, 2, send_reminder)
+
+SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, Reminder reminder) {
+ LOG_INFO("Reminder: " + reminder.message);
+ return Ok();
+}
+```
+
@@ -427,6 +537,41 @@ fn fetch_data(ctx: &mut ProcedureContext, url: String) -> String {
}
```
+
+
+
+```cpp
+// SPACETIMEDB_UNSTABLE_FEATURES is necessary to access Http + Transactions in C++ Procedures
+#define SPACETIMEDB_UNSTABLE_FEATURES
+#include
+using namespace SpacetimeDB;
+
+// Cache table for fetched data
+struct Cache {
+ std::string data;
+};
+SPACETIMEDB_STRUCT(Cache, data)
+SPACETIMEDB_TABLE(Cache, cache, Private)
+
+SPACETIMEDB_PROCEDURE(std::string, fetch_data, ProcedureContext ctx, std::string url) {
+ // Fetch from HTTP (outside transaction)
+ auto response = ctx.http.get(url);
+ if (!response.is_ok()) {
+ LOG_ERROR("HTTP request failed");
+ return std::string("");
+ }
+
+ std::string body = response.value().body.to_string_utf8_lossy();
+
+ // Insert into cache with transaction
+ ctx.with_tx([&body](TxContext& tx) {
+ tx.db[cache].insert(Cache{body});
+ });
+
+ return body;
+}
+```
+
@@ -489,6 +634,23 @@ fn top_players(ctx: &ViewContext) -> Vec {
}
```
+
+
+
+```cpp
+using namespace SpacetimeDB;
+
+// Return single row using unique indexed field
+SPACETIMEDB_VIEW(std::optional, my_player, Public, ViewContext ctx) {
+ return ctx.db[player_identity].find(ctx.sender);
+}
+
+// Return multiple rows using indexed field
+SPACETIMEDB_VIEW(std::vector, top_players, Public, ViewContext ctx) {
+ return ctx.db[player_score].filter(range_from(int32_t(1000))).collect();
+}
+```
+
@@ -529,6 +691,18 @@ ctx.identity() // Module's identity
ctx.rng() // Random number generator
```
+
+
+
+```cpp
+ctx.db // Database access (Table accessor)
+ctx.sender // Identity of caller (Identity type)
+ctx.connection_id // std::optional
+ctx.timestamp // Timestamp of current transaction (Timestamp type)
+ctx.identity() // Module's own identity (Identity type)
+ctx.rng() // Random number generator (for seeded randomness)
+```
+
@@ -564,6 +738,16 @@ log::info!("Info: {}", msg);
log::debug!("Debug: {}", msg);
```
+
+
+
+```cpp
+LOG_ERROR("Error: " + msg);
+LOG_WARN("Warning: " + msg);
+LOG_INFO("Info: " + msg);
+LOG_DEBUG("Debug: " + msg);
+```
+
@@ -648,5 +832,25 @@ Option, Vec
Identity, ConnectionId, Timestamp, Duration, ScheduleAt
```
+
+
+
+```cpp
+// Primitives
+bool, std::string, float, double
+int8_t, int16_t, int32_t, int64_t
+uint8_t, uint16_t, uint32_t, uint64_t
+
+// Large integers (SpacetimeDB types)
+SpacetimeDB::i128, SpacetimeDB::u128
+SpacetimeDB::i256, SpacetimeDB::u256
+
+// Collections
+std::optional, std::vector
+
+// SpacetimeDB types
+Identity, ConnectionId, Timestamp, TimeDuration, ScheduleAt
+```
+
diff --git a/docs/docs/00200-core-concepts/00500-authentication/00500-usage.md b/docs/docs/00200-core-concepts/00500-authentication/00500-usage.md
index d8f64d8e1b1..3f0a5455fb9 100644
--- a/docs/docs/00200-core-concepts/00500-authentication/00500-usage.md
+++ b/docs/docs/00200-core-concepts/00500-authentication/00500-usage.md
@@ -71,6 +71,25 @@ _Example output when using a Google-issued token:_
INFO: src\lib.rs:64: sub: 321321321321321, iss: https://accounts.google.com
```
+
+
+
+```cpp
+SPACETIMEDB_CLIENT_CONNECTED(auth_claims_connect, ReducerContext ctx) {
+ const auto& auth = ctx.sender_auth();
+ const auto& jwt_opt = auth.get_jwt();
+ if (!jwt_opt.has_value()) {
+ return Err("Client connected without JWT");
+ }
+
+ const JwtClaims& jwt = *jwt_opt;
+ const std::string subject = jwt.subject();
+ const std::string issuer = jwt.issuer();
+ LOG_INFO("sub: " + subject + ", iss: " + issuer);
+ return Ok();
+}
+```
+
@@ -146,6 +165,42 @@ pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
}
```
+
+
+
+```cpp
+const std::string SPACETIME_OIDC_ISSUER = "https://auth.spacetimedb.com/oidc";
+const std::string SPACETIME_OIDC_CLIENT_ID = "client_XXXXXXXXXXXXXXXXXXXXXX";
+
+SPACETIMEDB_CLIENT_CONNECTED(restrict_auth_provider_connect, ReducerContext ctx) {
+ const auto& auth = ctx.sender_auth();
+ const auto& jwt_opt = auth.get_jwt();
+ if (!jwt_opt.has_value()) {
+ return Err("Authentication required");
+ }
+
+ const JwtClaims& jwt = *jwt_opt;
+
+ if (jwt.issuer() != SPACETIME_OIDC_ISSUER) {
+ return Err("Invalid issuer");
+ }
+
+ const auto& audience = jwt.audience();
+ bool found = false;
+ for (const auto& aud : audience) {
+ if (aud == SPACETIME_OIDC_CLIENT_ID) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return Err("Invalid audience");
+ }
+
+ return Ok();
+}
+```
+
@@ -260,6 +315,52 @@ pub fn admin_only_reducer(ctx: &ReducerContext) -> Result<(), String> {
}
```
+
+
+
+```cpp
+// For robust JSON parsing, this example uses nlohmann/json (header-only).
+
+bool ensure_admin_access(const AuthCtx& auth) {
+ if (auth.is_internal()) {
+ // Scheduled reducers are trusted
+ return true;
+ }
+
+ const auto& jwt_opt = auth.get_jwt();
+ if (!jwt_opt.has_value()) {
+ return false;
+ }
+
+ const auto& payload = jwt_opt->raw_payload();
+ auto json = nlohmann::json::parse(payload, nullptr, false);
+ if (json.is_discarded()) {
+ return false;
+ }
+
+ if (!json.contains("roles") || !json["roles"].is_array()) {
+ return false;
+ }
+
+ for (const auto& role : json["roles"]) {
+ if (role.is_string() && role.get() == "admin") {
+ return true;
+ }
+ }
+ return false;
+}
+
+SPACETIMEDB_REDUCER(admin_only_reducer, ReducerContext ctx) {
+ if (!ensure_admin_access(ctx.sender_auth())) {
+ return Err("Admin role required");
+ }
+
+ // We can now safely perform admin-only actions.
+ LOG_INFO("Admin action performed");
+ return Ok();
+}
+```
+
diff --git a/docs/docs/00300-resources/00100-how-to/00300-logging.md b/docs/docs/00300-resources/00100-how-to/00300-logging.md
index 83dd011566f..2e66b8464de 100644
--- a/docs/docs/00300-resources/00100-how-to/00300-logging.md
+++ b/docs/docs/00300-resources/00100-how-to/00300-logging.md
@@ -115,6 +115,39 @@ Available log levels:
- `log::debug!()` - Debug messages
- `log::trace!()` - Trace messages
+
+
+
+Use the `LOG_*` macros to write logs from your reducers:
+
+```cpp
+using namespace SpacetimeDB;
+
+SPACETIMEDB_REDUCER(process_data, ReducerContext ctx, uint32_t value) {
+ LOG_INFO("Processing data with value: " + std::to_string(value));
+
+ if (value > 100) {
+ LOG_WARN("Value " + std::to_string(value) + " exceeds threshold");
+ }
+
+ if (value == 0) {
+ LOG_ERROR("Invalid value: 0");
+ return Err("Value cannot be zero");
+ }
+
+ LOG_DEBUG("Debug information: ctx.sender = " + ctx.sender.to_string());
+
+ return Ok();
+}
+```
+
+Available log macros:
+- `LOG_ERROR()` - Error messages
+- `LOG_WARN()` - Warning messages
+- `LOG_INFO()` - Informational messages
+- `LOG_DEBUG()` - Debug messages
+- `LOG_PANIC()` + `LOG_FATAL()` - Fatal errors (terminates the reducer)
+
@@ -231,6 +264,25 @@ pub fn transfer_credits(ctx: &ReducerContext, to_user: u64, amount: u32) -> Resu
}
```
+
+
+
+Include relevant context in your log messages:
+
+```cpp
+using namespace SpacetimeDB;
+
+SPACETIMEDB_REDUCER(transfer_credits, ReducerContext ctx, uint64_t to_user, uint32_t amount) {
+ LOG_INFO("Credit transfer: from=" + ctx.sender.to_string() +
+ ", to=" + std::to_string(to_user) +
+ ", amount=" + std::to_string(amount));
+
+ // ... transfer logic
+
+ return Ok();
+}
+```
+
diff --git a/docs/docs/00300-resources/00100-how-to/00500-reject-client-connections.md b/docs/docs/00300-resources/00100-how-to/00500-reject-client-connections.md
index bc468f67ec7..61e65968f54 100644
--- a/docs/docs/00300-resources/00100-how-to/00500-reject-client-connections.md
+++ b/docs/docs/00300-resources/00100-how-to/00500-reject-client-connections.md
@@ -66,4 +66,35 @@ Client behavior can vary by client type. For example:
Regardless of the client type, from the rust server's perspective, the client will be disconnected and the server module's logs will contain an entry reading:
`ERROR: : The client connection was rejected. With our current code logic, all clients will be rejected.`
+
+In C++, if we return an error during the `client_connected` reducer, the client will be disconnected.
+
+Here is a simple example where the server module returns an error for all incoming client connections.
+
+```cpp
+using namespace SpacetimeDB;
+
+SPACETIMEDB_CLIENT_CONNECTED(client_connected, ReducerContext ctx) {
+ bool client_is_rejected = true;
+ if (client_is_rejected) {
+ return Err("The client connection was rejected. With our current code logic, all clients will be rejected.");
+ } else {
+ return Ok();
+ }
+}
+```
+
+Client behavior can vary by client type. For example:
+
+- **C# clients**: Client disconnection behavior is currently undefined and will generate an error reading:
+ `Disconnected abnormally: System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake.`
+
+- **Rust clients**: Client disconnection behavior is currently undefined and will generate an error reading:
+ `Unable to send subscribe message: WS sender loop has dropped its recv channel: TrySendError { kind: Disconnected }`
+
+- **TypeScript clients**: Client will receive an `Error connecting to SpacetimeDB:` and a `CloseEvent` with a code of 1006.
+
+Regardless of the client type, from the C++ server's perspective, the client will be disconnected and the server module's logs will contain an entry reading:
+`ERROR: : The client connection was rejected. With our current code logic, all clients will be rejected.`
+