Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a802e33
docs: update delete-data flag examples
Jul 10, 2026
c849ef6
docs: keep delete-data examples concise
Jul 10, 2026
eb538e1
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 11, 2026
4794bd4
docs: clarify C# reducer result callbacks
Jul 11, 2026
7a9dc22
docs: align TypeScript skill table naming guidance
Jul 12, 2026
6173dd9
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 14, 2026
45f7424
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 15, 2026
47b823c
docs: align TypeScript table accessor examples
Jul 15, 2026
ff9429e
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 16, 2026
7f66bc4
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 17, 2026
9dc58c7
docs: align scheduled function examples
Jul 17, 2026
dd24d4e
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 18, 2026
2caabd6
docs: update procedure language support overview
Jul 18, 2026
a129e9c
docs: clarify Unreal uproject-dir path
Jul 19, 2026
fab18a5
docs: update Rust and C++ sender examples
Jul 20, 2026
03cc8c5
Fix Rust codegen bindings path in docs
Jul 21, 2026
9fc83bc
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 21, 2026
f73fb9a
docs: address audit PR review comments
Jul 21, 2026
077a8c6
docs: align reducer context docs with SDK
Jul 22, 2026
63c0437
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 23, 2026
e52c15f
docs: update C# reducer failure pattern
Jul 23, 2026
c6c5606
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 24, 2026
4157226
Merge remote-tracking branch 'origin/master' into bot/docs-audit
Jul 25, 2026
879a028
docs: fix HTTP handler route placeholder
Jul 25, 2026
e03a492
docs: avoid ambient clock in file storage examples
Jul 26, 2026
4e49028
docs: correct TypeScript connectionId nullability
Jul 27, 2026
3e8e25d
Merge remote-tracking branch 'origin/master' into bot/docs-audit
clockwork-labs-bot Jul 28, 2026
bb43070
docs: clarify TypeScript provider reconnect behavior
clockwork-labs-bot Jul 28, 2026
77554f5
Merge remote-tracking branch 'origin/master' into bot/docs-audit
clockwork-labs-bot Jul 29, 2026
fe79243
Merge remote-tracking branch 'origin/master' into bot/docs-audit
clockwork-labs-bot Jul 30, 2026
e6f1e64
docs: clarify event table subscribe-all behavior
clockwork-labs-bot Jul 30, 2026
3b71177
docs: update C# reducer callback tutorial wording
clockwork-labs-bot Jul 31, 2026
ce0bf17
Merge remote-tracking branch 'origin/master' into bot/docs-audit
clockwork-labs-bot Aug 1, 2026
fc626dc
docs: align TypeScript table accessor examples
clockwork-labs-bot Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,18 @@ ctx.procedures.makeRequest().then(
</TabItem>
<TabItem value="csharp" label="C#">

C# modules currently cannot define procedures. Support for defining procedures in C# modules will be released shortly.
C# modules can define procedures:

A C# [client](#client) can call a procedure defined by a Rust or TypeScript module:
```csharp
[SpacetimeDB.Procedure]
public static string MakeRequest(ProcedureContext ctx)
{
// ...
return "result";
}
```

A C# [client](#client) can call a procedure defined by a module:

```csharp
void Main()
Expand Down Expand Up @@ -384,7 +393,7 @@ Because procedures are unstable, Rust modules that define them must opt in to th

```toml
[dependencies]
spacetimedb = { version = "1.x", features = ["unstable"] }
spacetimedb = { version = "2.*", features = ["unstable"] }
```

Then, that module can define a procedure:
Expand Down Expand Up @@ -436,7 +445,7 @@ Use the other tabs (TypeScript/C#/Rust/Unreal C++/Blueprint) for client call exa
</TabItem>
<TabItem value="cpp-unreal" label="Unreal C++">

An Unreal C++ [client](#client) can call a procedure defined by a Rust or TypeScript module:
An Unreal C++ [client](#client) can call a procedure defined by a module:

```cpp
{
Expand Down Expand Up @@ -468,7 +477,7 @@ void AGameManager::OnMakeRequestComplete(const FProcedureEventContext& Context,
</TabItem>
<TabItem value="blueprint" label="Unreal Blueprint">

An Unreal [client](#client) can call a procedure defined by a Rust or TypeScript module:
An Unreal [client](#client) can call a procedure defined by a module:

![MakeRequest without callback](/images/unreal/intro/ue-blueprint-makerequest-nocallback.png)

Expand Down
11 changes: 9 additions & 2 deletions docs/docs/00100-intro/00300-tutorials/00100-chat-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,10 @@ void PrintMessage(RemoteTables tables, Message message)

#### Warn if our name was rejected

We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `OnReducerEvent` method of the `Reducer` namespace, which is automatically implemented for each reducer by `spacetime generate`.
We can also register callbacks for reducer results. We register these callbacks
using generated events on `conn.Reducers`, such as `conn.Reducers.OnSetName`
and `conn.Reducers.OnSendMessage`, which are automatically implemented for
each reducer by `spacetime generate`.

Each reducer callback takes one fixed argument:

Expand All @@ -2061,7 +2064,11 @@ These callbacks will be invoked in one of two cases:

Note that a status of `Failed` or `OutOfEnergy` implies that the caller identity is our own identity.

We already handle successful `SetName` invocations using our `User.OnUpdate` callback, but if the module rejects a user's chosen name, we'd like that user's client to let them know. We define a function `Reducer_OnSetNameEvent` as a `Reducer.OnSetNameEvent` callback which checks if the reducer failed, and if it did, prints an error message including the rejected name.
We already handle successful `SetName` invocations using our `User.OnUpdate`
callback, but if the module rejects a user's chosen name, we'd like that user's
client to let them know. We define a function `Reducer_OnSetNameEvent` and
register it with `conn.Reducers.OnSetName`; the callback checks if the reducer
failed, and if it did, prints an error message including the rejected name.

We'll test both that our identity matches the sender and that the status is `Failed`, even though the latter implies the former, for demonstration purposes.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ const START_PLAYER_MASS: i32 = 15;
#[spacetimedb::reducer]
pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> {
log::info!("Creating player with name {}", name);
let mut player: Player = ctx.db.player().identity().find(ctx.sender).ok_or("")?;
let mut player: Player = ctx.db.player().identity().find(ctx.sender()).ok_or("")?;
let player_id = player.player_id;
player.name = name;
ctx.db.player().identity().update(player);
Expand Down Expand Up @@ -786,11 +786,11 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
.db
.player()
.identity()
.find(&ctx.sender)
.find(&ctx.sender())
.ok_or("Player not found")?;
let player_id = player.player_id;
ctx.db.logged_out_player().insert(player);
ctx.db.player().identity().delete(&ctx.sender);
ctx.db.player().identity().delete(&ctx.sender());

// Remove any circles from the arena
for circle in ctx.db.circle().player_id().filter(&player_id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ spacetime generate --lang unrealcpp --uproject-dir .. --unreal-module-name black
This will generate a set of files in the `blackholio/Source/blackholio/Private/ModuleBindings` and `blackholio/Source/blackholio/Public/ModuleBindings` directories which contain the code generated types and reducer functions that are defined in your module, but usable on the client.

:::note
`--uproject-dir` is straightforward as the path to the .uproject file. `--unreal-module-name` is the name of the Unreal module which in most projects is the name of the project, in this case `blackholio`.
`--uproject-dir` is the path to the Unreal project directory that contains the `.uproject` file. `--unreal-module-name` is the name of the Unreal module, which in most projects is the name of the project, in this case `blackholio`.
:::

:::warning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ const START_PLAYER_MASS: i32 = 15;
#[spacetimedb::reducer]
pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> {
log::info!("Creating player with name {}", name);
let mut player: Player = ctx.db.player().identity().find(ctx.sender).ok_or("")?;
let mut player: Player = ctx.db.player().identity().find(ctx.sender()).ok_or("")?;
let player_id = player.player_id;
player.name = name;
ctx.db.player().identity().update(player);
Expand Down Expand Up @@ -786,11 +786,11 @@ pub fn disconnect(ctx: &ReducerContext) -> Result<(), String> {
.db
.player()
.identity()
.find(&ctx.sender)
.find(&ctx.sender())
.ok_or("Player not found")?;
let player_id = player.player_id;
ctx.db.logged_out_player().insert(player);
ctx.db.player().identity().delete(&ctx.sender);
ctx.db.player().identity().delete(&ctx.sender());

// Remove any circles from the arena
for circle in ctx.db.circle().player_id().filter(&player_id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ SPACETIMEDB_VIEW(std::optional<PlayerCount>, player_count, Public, AnonymousView
```typescript
ctx.db // Database access
ctx.sender // Identity of caller
ctx.connectionId // ConnectionId | undefined
ctx.connectionId // ConnectionId | null
ctx.timestamp // Timestamp
ctx.databaseIdentity // Module's identity
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ import { ScheduleAt } from 'spacetimedb';
import { schema, t, table } from 'spacetimedb/server';

// Define a schedule table for the procedure
const fetchSchedule = table(
const fetch_schedule = table(
{ name: 'fetch_schedule', scheduled: (): any => fetch_external_data },
{
scheduled_id: t.u64().primaryKey().autoInc(),
Expand All @@ -555,12 +555,12 @@ const fetchSchedule = table(
}
);

const spacetimedb = schema({ fetchSchedule });
const spacetimedb = schema({ fetch_schedule });
export default spacetimedb;

// The procedure to be scheduled
export const fetch_external_data = spacetimedb.procedure(
{ arg: fetchSchedule.rowType },
{ arg: fetch_schedule.rowType },
t.unit(),
(ctx, { arg }) => {
const response = ctx.http.fetch(arg.url);
Expand All @@ -571,7 +571,7 @@ export const fetch_external_data = spacetimedb.procedure(

// From a reducer, schedule the procedure by inserting into the schedule table
export const queueFetch = spacetimedb.reducer({ url: t.string() }, (ctx, { url }) => {
ctx.db.fetchSchedule.insert({
ctx.db.fetch_schedule.insert({
scheduled_id: 0n,
scheduled_at: ScheduleAt.interval(0n), // Run immediately
url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Every reducer invocation has an associated caller identity.
<TabItem value="typescript" label="TypeScript">

```typescript
import { schema, table, t, type Identity } from 'spacetimedb/server';
import { schema, table, t } from 'spacetimedb/server';

const player = table(
{ name: 'player', public: true },
Expand Down Expand Up @@ -257,7 +257,7 @@ SPACETIMEDB_REDUCER(update_score, ReducerContext ctx, uint32_t new_score) {
The connection ID identifies the specific client connection that invoked the reducer. This is useful for tracking sessions or implementing per-connection state.

:::note
The connection ID may be `None`/`null`/`undefined` for reducers invoked by the system (such as scheduled reducers or lifecycle reducers) or when called via the CLI without specifying a connection.
The connection ID may be absent for reducers invoked by the system (such as scheduled reducers or lifecycle reducers) or when called via the CLI without specifying a connection. In TypeScript modules, `ctx.connectionId` is `ConnectionId | null`.
:::

### Timestamp
Expand Down Expand Up @@ -324,7 +324,7 @@ Scheduled reducers and procedures are private by default in SpacetimeDB 2.x, so
```typescript
import { schema, table, t } from 'spacetimedb/server';

const scheduledTask = table(
const scheduled_task = table(
{ name: 'scheduled_task', scheduled: (): any => send_reminder },
{
taskId: t.u64().primaryKey().autoInc(),
Expand All @@ -333,10 +333,10 @@ const scheduledTask = table(
}
);

const spacetimedb = schema({ scheduledTask });
const spacetimedb = schema({ scheduled_task });
export default spacetimedb;

export const send_reminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (_ctx, { arg }) => {
export const send_reminder = spacetimedb.reducer({ arg: scheduled_task.rowType }, (_ctx, { arg }) => {
console.log(`Reminder: ${arg.message}`);
});
```
Expand All @@ -349,20 +349,20 @@ using SpacetimeDB;

public static partial class Module
{
[SpacetimeDB.Table(Accessor = "ScheduledTask", Scheduled = nameof(SendReminder))]
[SpacetimeDB.Table(Accessor = "ScheduledTask", Scheduled = nameof(SendReminder), ScheduledAt = nameof(ScheduledAt))]
public partial struct ScheduledTask
{
[SpacetimeDB.PrimaryKey]
[SpacetimeDB.AutoInc]
public ulong taskId;
public ScheduleAt scheduledAt;
public string message;
public ulong TaskId;
public ScheduleAt ScheduledAt;
public string Message;
}

[SpacetimeDB.Reducer]
public static void SendReminder(ReducerContext _ctx, ScheduledTask task)
{
Log.Info($"Reminder: {task.message}");
Log.Info($"Reminder: {task.Message}");
}
}
```
Expand Down Expand Up @@ -426,7 +426,7 @@ SPACETIMEDB_REDUCER(send_reminder, ReducerContext _ctx, ScheduledTask task) {
| `db` | `DbView` | Access to the module's database tables |
| `sender` | `Identity` | Identity of the caller |
| `senderAuth` | `AuthCtx` | Authorization context for the caller (includes JWT claims and internal call detection) |
| `connectionId` | `ConnectionId \| undefined`| Connection ID of the caller, if available |
| `connectionId` | `ConnectionId \| null` | Connection ID of the caller, if available |
| `timestamp` | `Timestamp` | Time when the reducer was invoked |
| `random` | `Random` | Random number generator (deterministic, seeded by SpacetimeDB) |
</TabItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,5 @@ Reducers can be triggered at specific times using schedule tables. See [Schedule
:::info Scheduled Reducer Context
Scheduled reducer calls originate from SpacetimeDB itself, not from a client. Therefore:
- `ctx.sender()` will be the module's own identity
- `ctx.connection_id()` will be `None`/`null`/`undefined`
- The connection ID will be absent (`null` in TypeScript, `null` in C#, `None` in Rust, and `std::nullopt` in C++)
:::
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,20 @@ This means there's no `ctx.db` field to access the database.
Instead, procedure code must manage transactions explicitly with `ProcedureCtx.withTx`.

```typescript
const myTable = table(
const my_table = table(
{ name: "my_table" },
{
a: t.u32(),
b: t.string(),
},
)

const spacetimedb = schema({ myTable });
const spacetimedb = schema({ my_table });
export default spacetimedb;

export const insert_a_value = spacetimedb.procedure({ a: t.u32(), b: t.u32() }, t.unit(), (ctx, { a, b }) => {
ctx.withTx(ctx => {
ctx.db.myTable.insert({ a, b });
ctx.db.my_table.insert({ a, b });
});
return {};
})
Expand Down Expand Up @@ -328,7 +328,7 @@ export const maybe_insert_a_value = spacetimedb.procedure({ a: t.u32(), b: t.str
if (a < 10) {
throw new SenderError("a is less than 10!");
}
ctx.db.myTable.insert({ a, b });
ctx.db.my_table.insert({ a, b });
});
})
```
Expand Down Expand Up @@ -1190,7 +1190,7 @@ A common use case for procedures is integrating with external APIs like OpenAI's
import { schema, t, table, SenderError } from 'spacetimedb/server';
import { TimeDuration } from 'spacetimedb';

const aiMessage = table(
const ai_message = table(
{ name: 'ai_message', public: true },
{
user: t.identity(),
Expand All @@ -1200,7 +1200,7 @@ const aiMessage = table(
}
);

const spacetimedb = schema({ aiMessage });
const spacetimedb = schema({ ai_message });
export default spacetimedb;

export const ask_ai = spacetimedb.procedure(
Expand Down Expand Up @@ -1235,7 +1235,7 @@ export const ask_ai = spacetimedb.procedure(

// Store the conversation in the database
ctx.withTx(txCtx => {
txCtx.db.aiMessage.insert({
txCtx.db.ai_message.insert({
user: txCtx.sender,
prompt,
response: aiResponse,
Expand Down
Loading
Loading