Skip to content
Open
2 changes: 1 addition & 1 deletion crates/cli/src/subcommands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn cli() -> Command {
Arg::new("client-lang")
.long("client-lang")
.value_parser(clap::value_parser!(Language))
.help("The programming language for the generated client module bindings (e.g., typescript, csharp, python). If not specified, it will be detected from the project."),
.help("The programming language for the generated client module bindings (e.g., typescript, csharp, rust, unrealcpp). If not specified, it will be detected from the project."),
)
.arg(common_args::server().help("The nickname, host name or URL of the server to publish to"))
.arg(common_args::yes())
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/subcommands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ fn get_filtered_generate_configs<'a>(
pub fn cli() -> clap::Command {
clap::Command::new("generate")
.about("Generate client files for a spacetime module.")
.override_usage("generate [DATABASE] --lang <LANG> --out-dir <DIR> [--module-path <DIR> | --bin-path <PATH> | --unreal-module-name <MODULE_NAME> | --uproject-dir <DIR> | --include-private]")
.override_usage("generate [DATABASE] --lang <LANG> [--out-dir <DIR> | --uproject-dir <DIR>] [--module-path <DIR> | --bin-path <PATH> | --js-path <PATH>] [OPTIONS]")
.arg(
Arg::new("database")
.help("Database name or glob pattern to filter which databases to generate for"),
Expand Down
2 changes: 1 addition & 1 deletion crates/update/src/cli/self_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ following line to your shell configuration and open a new shell session:
eprintln!(
"\
The install process is complete; check out our quickstart guide to get started!
<https://spacetimedb.com/docs/getting-started>"
<https://spacetimedb.com/docs/>"
);

Ok(ExitCode::SUCCESS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Perfect consistency, always.

## Everything is Programmable

SpacetimeDB doesn't limit you to declarative rules or configuration files. Your module is real code (Rust, C#, or TypeScript) running inside the database. You have the full power of a procedural, normal programming language at your disposal.
SpacetimeDB doesn't limit you to declarative rules or configuration files. Your module is real code (Rust, C#, TypeScript, or C++) running inside the database. You have the full power of a procedural, normal programming language at your disposal.

Need custom authorization logic? Write a function. Need to validate complex business rules? Write a function. Need to transform data before storing it? Write a function.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ slug: /intro/language-support

## Server Database Modules

SpacetimeDB modules define your database schema and server-side business logic. Modules can be written in three languages:
SpacetimeDB modules define your database schema and server-side business logic. Modules can be written in four languages:

- **[Rust](../../00200-core-concepts/00100-databases.md)** - High performance, compiled to WebAssembly [(Quickstart)](../00200-quickstarts/00500-rust.md)
- **[C#](../../00200-core-concepts/00100-databases.md)** - Great for Unity developers, compiled to WebAssembly [(Quickstart)](../00200-quickstarts/00600-c-sharp.md)
- **[TypeScript](../../00200-core-concepts/00100-databases.md)** - Ideal for web developers, runs on V8 [(Quickstart)](../00200-quickstarts/00400-typescript.md)
- **[C++](../../00200-core-concepts/00100-databases.md)** - Fits Unreal and C++ workflows, compiled to WebAssembly [(Quickstart)](../00200-quickstarts/00700-cpp.md)

## Client SDKs

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/00100-intro/00100-getting-started/00500-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ SpacetimeDB replaces the entire server. Your game state lives in tables, your ga

Firebase and Supabase are Backend-as-a-Service platforms. They give you a database with an API layer on top, but your application logic still runs elsewhere (cloud functions, edge functions, or your own server). Complex business logic is awkward to express as database triggers or serverless functions.

SpacetimeDB lets you write your entire application as a module in a real programming language (Rust, C#, TypeScript) that runs inside the database. You get full transactional guarantees, direct table access, and real-time subscriptions without the cold starts, execution limits, or awkward abstractions of serverless functions.
SpacetimeDB lets you write your entire application as a module in a real programming language (Rust, C#, TypeScript, or C++) that runs inside the database. You get full transactional guarantees, direct table access, and real-time subscriptions without the cold starts, execution limits, or awkward abstractions of serverless functions.

### How is SpacetimeDB different from a regular database (PostgreSQL, MySQL)?

Expand Down Expand Up @@ -149,7 +149,7 @@ SpacetimeDB 2.0 also includes a **type-safe query builder** for client-side subs

1. Install the CLI: `curl -sSf https://install.spacetimedb.com | sh`
2. Start a local instance: `spacetime start`
3. Create a project: `spacetime init --lang rust` (or `csharp`, `typescript`)
3. Create a project: `spacetime init --lang rust` (or `csharp`, `typescript`, `cpp`)
4. Write your module, publish it: `spacetime publish my-app`
5. Generate client bindings: `spacetime generate --lang typescript --out-dir src/module_bindings`
6. Connect from your client using the generated code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,21 @@ public class GameManager : MonoBehaviour
void HandleConnect(DbConnection _conn, Identity identity, string token)
{
Debug.Log("Connected.");
// Only the WebGL player has the browser WebSocket header limitation.
// The Unity Editor uses the normal desktop transport even when the
// selected build target is WebGL, so keep the normal behavior there.
#if UNITY_WEBGL && !UNITY_EDITOR
if (AuthToken.Token == "")
{
// No token was supplied to the connection, so this is the
// long-lived server-issued token for the new identity. Save it.
// If a token already exists, this connect may have used a
// short-lived WebSocket token, which should not overwrite it.
AuthToken.SaveToken(token);
}
#else
AuthToken.SaveToken(token);
#endif
LocalIdentity = identity;

OnConnected?.Invoke();
Expand Down Expand Up @@ -787,6 +801,8 @@ public class GameManager : MonoBehaviour
}
```

> Unity WebGL needs one extra precaution here. Browser WebSocket APIs cannot set an `Authorization` header, so reconnecting with a saved server-issued token may yield a short-lived WebSocket token in `HandleConnect`. The `#if UNITY_WEBGL` guard keeps the original saved token instead of overwriting it during reconnect.

Here we configure the connection to the database, by passing it some callbacks in addition to providing the `SERVER_URI` and `MODULE_NAME` to the connection. When the client connects, the SpacetimeDB SDK will call the `HandleConnect` method, allowing us to start up the game.

In our `HandleConnect` callback we build a subscription and call `Subscribe`, subscribing to all data in the database. This causes SpacetimeDB to synchronize the state of all your tables with your Unity client's SDK client cache.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,21 @@ Next lets add some callbacks when rows change in the database. Modify the `Handl
void HandleConnect(DbConnection conn, Identity identity, string token)
{
Debug.Log("Connected.");
// Only the WebGL player has the browser WebSocket header limitation.
// The Unity Editor uses the normal desktop transport even when the
// selected build target is WebGL, so keep the normal behavior there.
#if UNITY_WEBGL && !UNITY_EDITOR
if (AuthToken.Token == "")
{
// No token was supplied to the connection, so this is the
// long-lived server-issued token for the new identity. Save it.
// If a token already exists, this connect may have used a
// short-lived WebSocket token, which should not overwrite it.
AuthToken.SaveToken(token);
}
#else
AuthToken.SaveToken(token);
#endif
LocalIdentity = identity;

conn.Db.Circle.OnInsert += CircleOnInsert;
Expand All @@ -1370,6 +1384,8 @@ void HandleConnect(DbConnection conn, Identity identity, string token)
}
```

Keep the same WebGL guard from Part 2 here as well. On Unity WebGL, a reconnect can surface a short-lived WebSocket token in `HandleConnect`, so you should not overwrite an already-saved long-lived server-issued token.

Next add the following implementations for those callbacks to the `GameManager` class.

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Quick reference for SpacetimeDB module syntax across Rust, C#, and TypeScript.
Quick reference for SpacetimeDB module syntax across Rust, C#, TypeScript, and C++.

## Project Setup

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Consider a game inventory with ordered pockets. A `Vec<Item>` preserves pocket o

## Binary Data and Files

SpacetimeDB includes optimizations for storing binary data as `Vec<u8>` (Rust), `List<byte>` (C#), or `t.array(t.u8())` (TypeScript). You can store files, images, serialized data, or other binary blobs directly in table columns.
SpacetimeDB includes optimizations for storing binary data as `Vec<u8>` (Rust), `List<byte>` (C#), `t.array(t.u8())` (TypeScript), or `std::vector<uint8_t>` (C++). You can store files, images, serialized data, or other binary blobs directly in table columns.

This approach works well when:
- The binary data is associated with a specific row (e.g., a user's avatar image)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const schedule_periodic_tasks = spacetimedb.reducer((ctx) => {
<TabItem value="csharp" label="C#">

```csharp
public partial class Module
public static partial class Module
{
[SpacetimeDB.Reducer]
public static void SchedulePeriodicTasks(ReducerContext ctx)
Expand Down Expand Up @@ -291,10 +291,11 @@ public static partial class Module
public static void ScheduleTimedTasks(ReducerContext ctx)
{
// Schedule for 10 seconds from now
var tenSecondsFromNow = ctx.Timestamp + new TimeDuration(10_000_000);
ctx.Db.Reminder.Insert(new Reminder
{
Message = "Your auction has ended",
ScheduledAt = new ScheduleAt.Time(DateTimeOffset.UtcNow.AddSeconds(10))
ScheduledAt = new ScheduleAt.Time(tenSecondsFromNow)
});

// Schedule for a specific time
Expand Down
31 changes: 31 additions & 0 deletions docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ slug: /tables/event-tables

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";

In many applications, particularly games and real-time systems, modules need to notify clients about things that happened without storing that information permanently. A combat system might need to tell clients "entity X took 50 damage" so they can display a floating damage number, but there is no reason to keep that record in the database after the moment has passed.

Expand Down Expand Up @@ -60,6 +61,21 @@ pub struct DamageEvent {
}
```

</TabItem>
<TabItem value="cpp" label="C++">

<CppModuleVersionNotice />

```cpp
struct DamageEvent {
Identity entity_id;
uint32_t damage;
std::string source;
};
SPACETIMEDB_STRUCT(DamageEvent, entity_id, damage, source)
SPACETIMEDB_TABLE(DamageEvent, damage_event, Public, true)
```

</TabItem>
</Tabs>

Expand Down Expand Up @@ -128,6 +144,21 @@ fn attack(ctx: &ReducerContext, target_id: Identity, damage: u32) {
}
```

</TabItem>
<TabItem value="cpp" label="C++">

<CppModuleVersionNotice />

```cpp
SPACETIMEDB_REDUCER(attack, ReducerContext ctx, Identity target_id, uint32_t damage) {
// Game logic...

// Publish the event
ctx.db[damage_event].insert(DamageEvent{target_id, damage, "melee_attack"});
return Ok();
}
```

</TabItem>
</Tabs>

Expand Down
22 changes: 22 additions & 0 deletions docs/docs/00200-core-concepts/00500-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,28 @@ needs, or even implement your own.
If you're new to OIDC, check out our [blog post about OIDC](https://spacetimedb.com/blog/who-are-you)
to learn more about how OIDC works and why it's a great choice for authentication.

## Server-issued tokens and reconnects

If a client connects without supplying a token, SpacetimeDB creates a new
identity and returns a server-issued token for that identity. If you want later
connections to keep using that same identity, persist that returned token and
pass it back into the SDK on reconnect.

On platforms that can send an `Authorization` header during the WebSocket
handshake, the SDK can reuse that saved token directly. On platforms that cannot
set custom WebSocket headers, the SDK first exchanges the saved token for a
short-lived WebSocket token through
[`POST /v1/identity/websocket-token`](/docs/http/identity#post-v1identitywebsocket-token),
then sends only that short-lived token in the WebSocket URL.

This matters when you persist tokens on the client:

- Save the long-lived server-issued token that establishes the user's identity.
- Do not overwrite an already-saved long-lived token with a short-lived
WebSocket token received during reconnect.
- Expect this distinction on browser-style transports where WebSocket headers
are unavailable, such as Unity WebGL builds.

## SpacetimeAuth

To make it easier to get started with authentication, SpacetimeDB offers
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/00200-core-concepts/00600-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ New to SpacetimeDB client development? Follow this progression:
1. **[Generate Client Bindings](./00600-clients/00200-codegen.md)** - Create type-safe interfaces from your module
2. **[Connect to SpacetimeDB](./00600-clients/00300-connection.md)** - Establish a connection and understand the lifecycle
3. **[Use the SDK API](./00600-clients/00400-sdk-api.md)** - Learn about subscriptions, reducers, and callbacks
4. **Language Reference** - Dive into language-specific details: [Rust](./00600-clients/00500-rust-reference.md), [C#](./00600-clients/00600-csharp-reference.md), [TypeScript](./00600-clients/00700-typescript-reference.md)
4. **Language Reference** - Dive into language-specific details: [Rust](./00600-clients/00500-rust-reference.md), [C#](./00600-clients/00600-csharp-reference.md), [TypeScript](./00600-clients/00700-typescript-reference.md), and [Unreal Engine](./00600-clients/00800-unreal-reference.md)

## Next Steps

- Follow a **Quickstart guide** [Rust](../00100-intro/00200-quickstarts/00500-rust.md), [C#](../00100-intro/00200-quickstarts/00600-c-sharp.md), or [TypeScript](../00100-intro/00200-quickstarts/00400-typescript.md) to build your first client
- To build your first client, follow a **Quickstart guide** for [Rust](../00100-intro/00200-quickstarts/00500-rust.md), [C#](../00100-intro/00200-quickstarts/00600-c-sharp.md), or [TypeScript](../00100-intro/00200-quickstarts/00400-typescript.md), or use the [Unreal tutorial](../00100-intro/00300-tutorials/00400-unreal-tutorial/index.md)
- Learn about [Databases](./00100-databases.md) to understand what you're connecting to
- Explore [Subscriptions](./00400-subscriptions.md) for efficient data synchronization
- Review [Reducers](./00200-functions/00300-reducers/00300-reducers.md) to understand server-side state changes
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Replace **PATH-TO-MODULE-DIRECTORY** with the path to your module's directory, w

```bash
mkdir -p module_bindings
spacetime generate --lang cs --out-dir module_bindings --module-path PATH-TO-MODULE-DIRECTORY
spacetime generate --lang csharp --out-dir module_bindings --module-path PATH-TO-MODULE-DIRECTORY
```

This generates C# files in `module_bindings/`. The generated files are automatically included in your project.
Expand Down
18 changes: 11 additions & 7 deletions docs/docs/00200-core-concepts/00600-clients/00300-connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const conn = new DbConnection.builder()
using SpacetimeDB;

var conn = DbConnection.Builder()
.WithUri(new Uri("https://maincloud.spacetimedb.com"))
.WithUri("https://maincloud.spacetimedb.com")
.WithDatabaseName("my_database")
.Build();
```
Expand Down Expand Up @@ -90,7 +90,7 @@ const conn = new DbConnection.builder()

```csharp
var conn = DbConnection.Builder()
.WithUri(new Uri("https://maincloud.spacetimedb.com"))
.WithUri("https://maincloud.spacetimedb.com")
.WithDatabaseName("my_database")
.Build();
```
Expand Down Expand Up @@ -137,7 +137,7 @@ const conn = new DbConnection.builder()

```csharp
var conn = DbConnection.Builder()
.WithUri(new Uri("https://maincloud.spacetimedb.com"))
.WithUri("https://maincloud.spacetimedb.com")
.WithDatabaseName("my_database")
.WithToken("your_auth_token_here")
.Build();
Expand Down Expand Up @@ -174,9 +174,9 @@ The token is sent to the server during connection and validates your identity. S

:::danger[Critical: C#, Unity, and Unreal Users]

In C# (including Unity) and Unreal Engine, you **must** manually advance the connection to process incoming messages. The connection does not process messages automatically!
In C# (including Unity), you **must** manually advance the connection to process incoming messages. In Unreal Engine, you must either manually advance the connection or enable automatic ticking. If the connection is not advanced, it will not process messages.

Call `DbConnection.FrameTick()` in your game loop or update method:
Call `FrameTick()` in your game loop or update method:

<Tabs groupId="client-language" queryString>
<TabItem value="csharp" label="C#">
Expand All @@ -200,7 +200,7 @@ while (running)
<TabItem value="unreal" label="Unreal">

```cpp
// In your Actor's Tick() method
// Option 1: call FrameTick() from your Actor's Tick() method
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
Expand All @@ -210,6 +210,10 @@ void AMyActor::Tick(float DeltaTime)
Conn->FrameTick();
}
}

// Option 2: enable automatic ticking once after building the connection
Conn = Builder->Build();
Conn->SetAutoTicking(true);
```

</TabItem>
Expand Down Expand Up @@ -256,7 +260,7 @@ const conn = DbConnection.builder()

```csharp
var conn = DbConnection.Builder()
.WithUri(new Uri("https://maincloud.spacetimedb.com"))
.WithUri("https://maincloud.spacetimedb.com")
.WithDatabaseName("my_database")
.OnConnect((conn, identity, token) =>
{
Expand Down
Loading
Loading