Skip to content

Commit c37b791

Browse files
bradleyshepcursoragentcloutiertylerjoshua-spacetime
authored
LLM Benchmark docs updates from testings (#4416)
# Description of Changes Documentation updates from llm-benchmark testing. Includes: - **Core concepts**: Tables, column types, auto-increment, indexes, access permissions, schedule tables, event tables, performance - **Client references**: C# and TypeScript reference updates - **Migration guide**: 2.0 migration docs - **AI rules**: `spacetimedb-csharp.mdc`, `spacetimedb-typescript.mdc`, new `spacetimedb-migration-2.0.mdc` - **Cheat sheet**: Database cheat sheet updates - **Reducers, procedures, reducer context**: Small fixes and clarifications - **llms.md**: LLM-related doc updates # API and ABI breaking changes None. Documentation only. # Expected complexity level and risk **Complexity: 2** — Broad docs changes but no code. Risk is limited to incorrect or misleading docs. # Testing - [ ] Build docs site and confirm it renders - [ ] Spot-check updated pages (tables, schedule tables, cheat sheet, client references) - [ ] Confirm C# tutorial examples use `nameof(...)` and snake_case --------- Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com> Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
1 parent b002158 commit c37b791

23 files changed

Lines changed: 630 additions & 187 deletions

docs/docs/00100-intro/00300-tutorials/00300-unity-tutorial/00400-part-3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ public static void Init(ReducerContext ctx)
350350

351351
:::note
352352

353-
You can use `ScheduleAt.Interval` to schedule a reducer call at an interval like we're doing here. SpacetimeDB will continue to call the reducer at this interval until you remove the row. You can also use `ScheduleAt.Time()` to specify a specific at which to call a reducer once. SpacetimeDB will remove that row automatically after the reducer has been called.
353+
You can use `ScheduleAt.Interval` to schedule a reducer call at an interval like we're doing here. SpacetimeDB will continue to call the reducer at this interval until you remove the row. You can also use `new ScheduleAt.Time(...)` to schedule a reducer once at a specific time. SpacetimeDB will remove that row automatically after the reducer has been called.
354354

355355
:::
356356

docs/docs/00100-intro/00300-tutorials/00400-unreal-tutorial/00400-part-3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public static void Init(ReducerContext ctx)
347347
```
348348

349349
:::note
350-
You can use `ScheduleAt.Interval` to schedule a reducer call at an interval like we're doing here. SpacetimeDB will continue to call the reducer at this interval until you remove the row. You can also use `ScheduleAt.Time()` to specify a specific at which to call a reducer once. SpacetimeDB will remove that row automatically after the reducer has been called.
350+
You can use `ScheduleAt.Interval` to schedule a reducer call at an interval like we're doing here. SpacetimeDB will continue to call the reducer at this interval until you remove the row. You can also use `new ScheduleAt.Time(...)` to schedule a reducer once at a specific time. SpacetimeDB will remove that row automatically after the reducer has been called.
351351
:::
352352

353353
</TabItem>

docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const status = t.enum('Status', ['Active', 'Inactive']);
102102
using SpacetimeDB;
103103

104104
// Basic table
105-
[SpacetimeDB.Table(Public = true)]
105+
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
106106
public partial struct Player
107107
{
108108
[SpacetimeDB.PrimaryKey]
@@ -116,27 +116,27 @@ public partial struct Player
116116
public int Score;
117117
}
118118

119-
// Multi-column index
120-
[SpacetimeDB.Table]
121-
[SpacetimeDB.Index.BTree(Accessor = "idx", Columns = ["PlayerId", "Level"])]
119+
// Multi-column index (use new[] for attribute params — collection expressions invalid in attributes)
120+
[SpacetimeDB.Table(Accessor = "Score")]
121+
[SpacetimeDB.Index.BTree(Accessor = "idx", Columns = new[] { "PlayerId", "Level" })]
122122
public partial struct Score
123123
{
124124
public ulong PlayerId;
125125
public uint Level;
126126
}
127127

128-
// Custom types
128+
// Sum types: TaggedEnum with partial record (not partial class)
129129
[SpacetimeDB.Type]
130-
public enum Status
131-
{
132-
Active,
133-
Inactive,
134-
}
130+
public partial record Status : TaggedEnum<(Unit Active, Unit Inactive)> { }
135131
```
136132

137133
</TabItem>
138134
<TabItem value="rust" label="Rust">
139135

136+
:::tip Table access
137+
Table accessors use **snake_case**. Use `ctx.db.player()` not `ctx.db.Player`.
138+
:::
139+
140140
```rust
141141
use spacetimedb::{table, SpacetimeType};
142142

@@ -145,21 +145,21 @@ use spacetimedb::{table, SpacetimeType};
145145
pub struct Player {
146146
#[primary_key]
147147
#[auto_inc]
148-
id: u64,
148+
pub id: u64,
149149
#[unique]
150-
username: String,
150+
pub username: String,
151151
#[index(btree)]
152-
score: i32,
152+
pub score: i32,
153153
}
154154

155155
// Multi-column index
156156
#[table(accessor = score, index(accessor = idx, btree(columns = [player_id, level])))]
157157
pub struct Score {
158-
player_id: u64,
159-
level: u32,
158+
pub player_id: u64,
159+
pub level: u32,
160160
}
161161

162-
// Custom types
162+
// Custom types (product types need #[derive(SpacetimeType)])
163163
#[derive(SpacetimeType)]
164164
pub enum Status {
165165
Active,
@@ -268,8 +268,12 @@ ctx.Db.Player.Id.Delete(123); // Delete by primary key
268268
</TabItem>
269269
<TabItem value="rust" label="Rust">
270270

271+
:::warning Required for Reducers
272+
Include `Table` in imports when using `ctx.db.*.insert()`, `.iter()`, `.get_by_id()`, etc. Without it: `no method named 'insert' found`. Use `use spacetimedb::{..., Table};`
273+
:::
274+
271275
```rust
272-
use spacetimedb::{reducer, ReducerContext};
276+
use spacetimedb::{reducer, ReducerContext, Table};
273277

274278
// Basic reducer
275279
#[reducer]
@@ -388,6 +392,10 @@ SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) { /* ... */ }
388392
389393
## Schedule Tables
390394
395+
:::important TypeScript: ScheduleAt import
396+
`ScheduleAt` is imported from `'spacetimedb'`, **not** from `'spacetimedb/server'`. Use: `import { ScheduleAt } from 'spacetimedb';`
397+
:::
398+
391399
<Tabs groupId="server-language" queryString>
392400
<TabItem value="typescript" label="TypeScript">
393401
@@ -435,15 +443,17 @@ public static void SendReminder(ReducerContext ctx, Reminder reminder)
435443
pub struct Reminder {
436444
#[primary_key]
437445
#[auto_inc]
438-
id: u64,
439-
message: String,
440-
scheduled_at: ScheduleAt,
446+
pub id: u64,
447+
pub message: String,
448+
pub scheduled_at: ScheduleAt,
441449
}
442450

443451
#[reducer]
444-
fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
452+
pub fn send_reminder(ctx: &ReducerContext, reminder: Reminder) {
445453
log::info!("Reminder: {}", reminder.message);
446454
}
455+
456+
// To schedule: Duration::from_millis(50).into() for ScheduleAt
447457
```
448458

449459
</TabItem>
@@ -521,6 +531,7 @@ public static string FetchData(ProcedureContext ctx, string url)
521531

522532
```rust
523533
// In Cargo.toml: spacetimedb = { version = "1.*", features = ["unstable"] }
534+
use spacetimedb::Table;
524535

525536
#[spacetimedb::procedure]
526537
fn fetch_data(ctx: &mut ProcedureContext, url: String) -> String {
@@ -773,6 +784,44 @@ LOG_DEBUG("Debug: " + msg);
773784
</TabItem>
774785
</Tabs>
775786

787+
## Common Mistakes
788+
789+
<Tabs groupId="server-language" queryString>
790+
<TabItem value="typescript" label="TypeScript">
791+
792+
1. **ScheduleAt import** — Import `ScheduleAt` from `'spacetimedb'`, not `'spacetimedb/server'`.
793+
2. **Schema-first** — Create `const spacetimedb = schema({...})` before reducers/init/clientConnected.
794+
3. **`schema()` shape** — Use `schema({ table })` or `schema({ a, b })`, not `schema(table)` or `schema(a, b)`.
795+
4. **Reducer naming** — Reducer names come from exports: `export const doThing = spacetimedb.reducer(...)`. Do not pass a string name.
796+
5. **No `ReducerContext` import** — Use the `ctx` parameter; do not import `ReducerContext`.
797+
6. **Export rules** — Only export schema, reducers, init, clientConnected, clientDisconnected, views. Keep helpers local.
798+
7. **Use `spacetimedb/server`** — For modules, use `spacetimedb/server` (builder API), not `spacetimedb-sdk` (client decorators).
799+
8. **`t.object` not `t.struct`** — Use `t.object('Name', {...})` for product types.
800+
9. **`autoInc` not `autoIncrement`** — Use `.autoInc()` on column builders.
801+
802+
</TabItem>
803+
<TabItem value="csharp" label="C#">
804+
805+
_See Rust tab for module-specific tips._
806+
807+
</TabItem>
808+
<TabItem value="rust" label="Rust">
809+
810+
1. **Missing Table import** — Add `use spacetimedb::Table` when using `insert`, `iter`, `get_by_id`, or `update`.
811+
2. **Wrong table access** — Use `ctx.db.user()` not `ctx.db.User` (accessor is snake_case).
812+
3. **Wrong scheduled table syntax** — Use `#[table(..., scheduled(reducer_name))]` (reducer name in parentheses). Not `schedule(reducer = ..., column = ...)` or `scheduled = tick`.
813+
4. **Missing `pub` on fields** — Struct and enum fields in tables must be `pub`.
814+
5. **Wrong reducer attribute** — Use `#[reducer]` or `#[spacetimedb::reducer]`, not `#[spacetime::reducer]`.
815+
6. **Product types** — Structs used in table columns need `#[derive(SpacetimeType)]`.
816+
817+
</TabItem>
818+
<TabItem value="cpp" label="C++">
819+
820+
_See Rust tab for module-specific tips._
821+
822+
</TabItem>
823+
</Tabs>
824+
776825
## Common CLI Commands
777826

778827
```bash

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,8 @@ Reducers cannot call procedures directly (procedures may have side effects incom
542542
<TabItem value="typescript" label="TypeScript">
543543

544544
```typescript
545-
import { schema, t, table, SenderError } from 'spacetimedb/server';
545+
import { ScheduleAt } from 'spacetimedb';
546+
import { schema, t, table } from 'spacetimedb/server';
546547

547548
// Define a schedule table for the procedure
548549
const fetchSchedule = table(
@@ -558,8 +559,7 @@ const spacetimedb = schema({ fetchSchedule });
558559
export default spacetimedb;
559560

560561
// The procedure to be scheduled
561-
const fetchExternalData = spacetimedb.procedure(
562-
'fetch_external_data',
562+
export const fetch_external_data = spacetimedb.procedure(
563563
{ arg: fetchSchedule.rowType },
564564
t.unit(),
565565
(ctx, { arg }) => {
@@ -570,7 +570,7 @@ const fetchExternalData = spacetimedb.procedure(
570570
);
571571

572572
// From a reducer, schedule the procedure by inserting into the schedule table
573-
const queueFetch = spacetimedb.reducer('queue_fetch', { url: t.string() }, (ctx, { url }) => {
573+
export const queueFetch = spacetimedb.reducer({ url: t.string() }, (ctx, { url }) => {
574574
ctx.db.fetchSchedule.insert({
575575
scheduled_id: 0n,
576576
scheduled_at: ScheduleAt.interval(0n), // Run immediately

docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,21 @@ public static partial class Module
6363
}
6464
```
6565

66+
:::note C# table accessors
67+
Table accessors use PascalCase: `ctx.Db.User`, `ctx.Db.Player`. The codegen derives these from table names.
68+
:::
69+
6670
</TabItem>
6771
<TabItem value="rust" label="Rust">
6872

73+
:::warning Required for Reducers
74+
Every reducer that uses `ctx.db.*.insert()`, `.iter()`, `.get_by_id()`, etc. **must** include `Table` in its imports:
75+
`use spacetimedb::{..., Table};`
76+
Without it you get: `no method named 'insert' found`.
77+
:::
78+
6979
```rust
70-
use spacetimedb::{table, reducer, ReducerContext};
80+
use spacetimedb::{table, reducer, ReducerContext, Table};
7181

7282
#[table(accessor = user)]
7383
pub struct User {
@@ -187,7 +197,7 @@ public static partial class Module
187197
<TabItem value="rust" label="Rust">
188198

189199
```rust
190-
use spacetimedb::{table, reducer, ReducerContext, Identity};
200+
use spacetimedb::{table, reducer, ReducerContext, Identity, Table};
191201

192202
#[table(accessor = player)]
193203
pub struct Player {

docs/docs/00200-core-concepts/00200-functions/00400-procedures.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ Procedures can call reducers by invoking them within a transaction block. The re
891891

892892
```typescript
893893
// Define a reducer and save the reference
894-
const processItem = spacetimedb.reducer('process_item', { itemId: t.u64() }, (ctx, { itemId }) => {
894+
export const processItem = spacetimedb.reducer({ itemId: t.u64() }, (ctx, { itemId }) => {
895895
// ... reducer logic
896896
});
897897

docs/docs/00200-core-concepts/00300-tables.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Tables are defined in your module code with a name, columns, and optional config
106106
Use the `table` function to declare a new table:
107107

108108
```typescript
109-
import { table, t } from 'spacetimedb/server';
109+
import { schema, table, t } from 'spacetimedb/server';
110110

111111
const people = table(
112112
{ name: 'people', public: true },
@@ -116,9 +116,12 @@ const people = table(
116116
email: t.string().unique(),
117117
}
118118
);
119+
120+
const spacetimedb = schema({ people });
121+
export default spacetimedb;
119122
```
120123

121-
The first argument defines table options, and the second defines columns.
124+
The first argument to `table()` defines table options, and the second defines columns. Pass your tables to `schema()` as an object: `schema({ people })` or `schema({ table1, table2 })`. Never use `schema(table)` or `schema(t1, t2, t3)`.
122125

123126
</TabItem>
124127
<TabItem value="csharp" label="C#">
@@ -188,9 +191,29 @@ FIELD_Unique(person, email)
188191
</TabItem>
189192
</Tabs>
190193
194+
### Creating the schema (TypeScript)
195+
196+
After defining tables, pass them to `schema()` as a single object. The object keys become the accessor names in `ctx.db`:
197+
198+
```typescript
199+
const people = table({ name: 'people', public: true }, { /* columns */ });
200+
const products = table({ name: 'products', public: true }, { /* columns */ });
201+
202+
const spacetimedb = schema({ people, products });
203+
export default spacetimedb;
204+
```
205+
206+
Use `schema({ table1 })` or `schema({ t1, t2 })`. Never use `schema(table)` or `schema(t1, t2, t3)`.
207+
208+
For a compact list of TypeScript gotchas, see the [cheat sheet](./00100-databases/00500-cheat-sheet.md#common-mistakes).
209+
191210
## Table Naming and Accessors
192211

193-
The table name you specify determines how you access the table in your code. Understanding this relationship is essential for writing correct SpacetimeDB modules.
212+
The table name you specify determines how you access the table in your code and in SQL. Understanding this relationship is essential for writing correct SpacetimeDB modules.
213+
214+
:::note Table names in SQL
215+
The table `name` in your schema is used **verbatim** in SQL queries and subscriptions. There is no automatic pluralization or case conversion. If you define `name: 'position'`, SQL uses `position`; if you define `name: 'positions'`, SQL uses `positions`. Ensure your schema names match what your SQL queries expect.
216+
:::
194217

195218
### How Accessor Names Are Derived
196219

@@ -219,25 +242,29 @@ ctx.db.playerScores.insert({ /* ... */ });
219242
</TabItem>
220243
<TabItem value="csharp" label="C#">
221244

222-
The accessor name **exactly matches** the `Name` attribute value:
245+
The accessor name **exactly matches** the `Accessor` attribute value:
223246

224247
```csharp
225248
// Table definition
226249
[SpacetimeDB.Table(Accessor = "Player", Public = true)]
227250
public partial struct Player { /* columns */ }
228251

229-
// Accessor matches Name exactly
252+
// Accessor matches Accessor value exactly
230253
ctx.Db.Player.Insert(new Player { /* ... */ });
231254
```
232255

233-
| Name Attribute | Accessor |
234-
|----------------|----------|
235-
| `Name = "User"` | `ctx.Db.User` |
236-
| `Name = "Player"` | `ctx.Db.Player` |
237-
| `Name = "GameSession"` | `ctx.Db.GameSession` |
256+
| Accessor | API Accessor | SQL Table Name (when Name omitted) |
257+
|----------|---------------|-------------------------------------|
258+
| `"User"` | `ctx.Db.User` | `User` |
259+
| `"Player"` | `ctx.Db.Player` | `Player` |
260+
| `"GameSession"` | `ctx.Db.GameSession` | `GameSession` |
238261

239262
:::warning Case Sensitivity
240-
The accessor is case-sensitive and must match the `Name` value exactly. `Name = "user"` produces `ctx.Db.user`, not `ctx.Db.User`.
263+
The accessor is case-sensitive and must match the `Accessor` value exactly. `Accessor = "user"` produces `ctx.Db.user`, not `ctx.Db.User`.
264+
:::
265+
266+
:::tip C# Convention
267+
Pick a stable accessor style for your project and use it consistently (for example, `User`, `user`, or `player_score`). Accessor names are case-sensitive.
241268
:::
242269

243270
</TabItem>

docs/docs/00200-core-concepts/00300-tables/00200-column-types.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ These optimizations apply across all supported languages.
7979
| Primitive | `t.u128()` | `bigint` | Unsigned 128-bit integer |
8080
| Primitive | `t.i256()` | `bigint` | Signed 256-bit integer |
8181
| Primitive | `t.u256()` | `bigint` | Unsigned 256-bit integer |
82-
| Composite | `t.object(name, obj)` | `{ [K in keyof Obj]: T<Obj[K]> }` | Product/object type for nested data |
82+
| Composite | `t.object(name, obj)` | `{ [K in keyof Obj]: T<Obj[K]> }` | Product/object type for nested data. Use `t.object`, not `t.struct` (which does not exist). |
8383
| Composite | `t.enum(name, variants)` | `{ tag: 'variant' } \| { tag: 'variant', value: T }` | Sum/enum type (tagged union) |
8484
| Composite | `t.array(element)` | `T<Element>[]` | Array of elements |
8585
| Composite | `t.option(value)` | `Value \| undefined` | Optional value |
@@ -229,7 +229,7 @@ public static partial class Module
229229
public double Z;
230230
}
231231

232-
// Define an enum for status
232+
// Define an enum for status (must be partial record, not partial class)
233233
[SpacetimeDB.Type]
234234
public partial record Status : TaggedEnum<(
235235
Unit Active,

docs/docs/00200-core-concepts/00300-tables/00230-auto-increment.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const add_post = spacetimedb.reducer({ title: t.string() }, (ctx, { title
3535
});
3636
```
3737

38-
Use the `.autoInc()` method on a column builder.
38+
Use the `.autoInc()` method on a column builder (not `autoIncrement`, which does not exist).
3939

4040
Auto-increment columns must be integer types: `t.i8()`, `t.u8()`, `t.i16()`, `t.u16()`, `t.i32()`, `t.u32()`, `t.i64()`, `t.u64()`, `t.i128()`, `t.u128()`, `t.i256()`, or `t.u256()`.
4141

@@ -198,7 +198,7 @@ export const insert_user = spacetimedb.reducer({ name: t.string() }, (ctx, { nam
198198
```csharp
199199
public partial class Module
200200
{
201-
[SpacetimeDB.Table(Accessor = "user", Public = true)]
201+
[SpacetimeDB.Table(Accessor = "User", Public = true)]
202202
public partial struct User
203203
{
204204
[SpacetimeDB.AutoInc]
@@ -217,6 +217,8 @@ public partial class Module
217217
<TabItem value="rust" label="Rust">
218218

219219
```rust
220+
use spacetimedb::{ReducerContext, Table};
221+
220222
#[spacetimedb::table(accessor = user, public)]
221223
pub struct User {
222224
#[auto_inc]

0 commit comments

Comments
 (0)