Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions docs/docs/00100-intro/00200-quickstarts/00155-nuxt.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export default defineEventHandler(async () => {
<Step title="Set up the SpacetimeDB provider">
<StepText>
The root `app.vue` wraps your app in a `SpacetimeDBProvider` that manages the WebSocket connection. The provider is wrapped in `ClientOnly` so it only runs in the browser, while SSR uses the server API route for initial data.

With no token, SpacetimeDB issues a server-issued identity and a non-expiring token; persist it and pass it back on reconnect to keep the same identity. A lost token can't be recovered, so self-issued identities are for development. For production, authenticate with an OIDC provider such as SpacetimeAuth, which handles token lifecycle. See [Authentication](../../00200-core-concepts/00500-authentication.md).
</StepText>
<StepCode>
```vue
Expand Down
6 changes: 5 additions & 1 deletion docs/docs/00100-intro/00200-quickstarts/00180-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ npm run build
:::tip
The browser IIFE bundle also exposes the generated `tables` query builders, so you can use query-builder subscriptions here too.
:::

:::warning
With no token, SpacetimeDB issues a server-issued identity and a non-expiring token; persist it and pass it back on reconnect to keep the same identity. A lost token can't be recovered, so self-issued identities are for development. For production, authenticate with an OIDC provider such as SpacetimeAuth, which handles token lifecycle. See [Authentication](../../00200-core-concepts/00500-authentication.md).
:::
</StepText>
<StepCode>
```html
Expand Down Expand Up @@ -97,7 +101,7 @@ npm run build
<StepCode>
```javascript
// Call a reducer with named arguments
conn.reducers.add({ name: 'Alice' });
conn.reducers.add({ name: 'Alice' }).catch(console.error);
```
</StepCode>
</Step>
Expand Down
10 changes: 7 additions & 3 deletions docs/docs/00100-intro/00300-tutorials/00100-chat-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,10 @@ Here we are configuring our SpacetimeDB connection by specifying the server URI,

We are also using `localStorage` to store our SpacetimeDB credentials. This way, we can reconnect to SpacetimeDB with the same `Identity` and token if we refresh the page. The first time we connect, we won't have any credentials stored, so we pass `undefined` to the `withToken` method. This will cause SpacetimeDB to generate new credentials for us.

:::warning
With no token, SpacetimeDB issues a server-issued identity and a non-expiring token; persist it and pass it back on reconnect to keep the same identity. A lost token can't be recovered, so self-issued identities are for development. For production, authenticate with an OIDC provider such as SpacetimeAuth, which handles token lifecycle. See [Authentication](../../00200-core-concepts/00500-authentication.md).
:::

If you chose a different name for your database, replace `quickstart-chat` with that name, or republish your module as `quickstart-chat`.

Our React hooks will subscribe to the data in SpacetimeDB. When we subscribe, SpacetimeDB will run our subscription queries and store the result in a local "client cache". This cache will be updated in real-time as the data in the table changes on the server.
Expand Down Expand Up @@ -1532,7 +1536,7 @@ Modify the `onSubmitNewName` callback by adding a call to the `setName` reducer:
const onSubmitNewName = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSettingName(false);
setName({ name: newName });
setName({ name: newName }).catch(console.error);
};
```

Expand All @@ -1542,11 +1546,11 @@ Next, modify the `onSubmitMessage` callback by adding a call to the `sendMessage
const onSubmitMessage = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setNewMessage('');
sendMessage({ text: newMessage });
sendMessage({ text: newMessage }).catch(console.error);
};
```

SpacetimeDB generated these functions for us based on the type information provided by our module. Calling these functions will invoke our reducers in our module.
SpacetimeDB generated these functions for us based on the type information provided by our module. Calling these functions will invoke our reducers in our module. They return a `Promise` that rejects if the reducer fails, so we log any error to the console.

Let's try out our app to see the result of these changes.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,9 @@ LOG_DEBUG("Debug: " + msg);
7. **Use `spacetimedb/server`** — For modules, use `spacetimedb/server` (builder API), not `spacetimedb-sdk` (client decorators).
8. **`t.object` not `t.struct`** — Use `t.object('Name', {...})` for product types.
9. **`autoInc` not `autoIncrement`** — Use `.autoInc()` on column builders.
10. **Row fields are camelCase** — Generated client row fields convert snake_case column names: `trip_id` → `row.tripId`.
11. **Await reducer calls** — Client reducer calls return `Promise<void>` that rejects with `SenderError` on failure; `await` or `.catch()` them.
12. **Throw `SenderError` in reducers** — A plain `Error` surfaces as an internal error, not a reducer error.

</TabItem>
<TabItem value="csharp" label="C#">
Expand Down
18 changes: 10 additions & 8 deletions docs/docs/00200-core-concepts/00300-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ The table `name` in your schema is used **verbatim** in SQL queries and subscrip
<Tabs groupId="server-language" queryString>
<TabItem value="typescript" label="TypeScript">

The accessor name is converted from snake_case to camelCase:
The accessor is the key passed to `schema({...})`, verbatim. By convention the key matches the table `name`:

```typescript
// Table definition
Expand All @@ -229,15 +229,17 @@ const player_scores = table(
{ /* columns */ }
);

// Accessor uses camelCase
ctx.db.playerScores.insert({ /* ... */ });
const spacetimedb = schema({ player_scores });

// Accessor is the schema key, verbatim
ctx.db.player_scores.insert({ /* ... */ });
```

| Table Name | Accessor |
| Schema Key | Accessor |
|------------|----------|
| `'user'` | `ctx.db.user` |
| `'player_scores'` | `ctx.db.playerScores` |
| `'game_session'` | `ctx.db.gameSession` |
| `user` | `ctx.db.user` |
| `player_scores` | `ctx.db.player_scores` |
| `game_session` | `ctx.db.game_session` |

</TabItem>
<TabItem value="csharp" label="C#">
Expand Down Expand Up @@ -319,7 +321,7 @@ Use idiomatic naming conventions for each language:

| Language | Convention | Example Table | Example Accessor |
|----------|------------|---------------|------------------|
| **TypeScript** | snake_case | `'player_score'` | `ctx.db.playerScore` |
| **TypeScript** | snake_case | `'player_score'` | `ctx.db.player_score` |
| **C#** | PascalCase | `Accessor = "PlayerScore"` | `ctx.Db.PlayerScore` |
| **Rust** | lower_snake_case | `name = player_score` | `ctx.db.player_score()` |
| **C++** | lower_snake_case | `player_score` | `ctx.db[player_score]` |
Expand Down
12 changes: 7 additions & 5 deletions docs/docs/00200-core-concepts/00600-clients/00200-codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ For example, a `user` table becomes:
export default __t.row({
id: __t.u64(),
name: __t.string(),
email: __t.string(),
emailAddress: __t.string(),
});

// Access via DbConnection
Expand All @@ -123,7 +123,7 @@ public partial class User
{
public ulong Id;
public string Name;
public string Email;
public string EmailAddress;
}

// Access via DbConnection
Expand All @@ -138,7 +138,7 @@ conn.Db.User
pub struct User {
pub id: u64,
pub name: String,
pub email: String,
pub email_address: String,
}

// Access via DbConnection
Expand All @@ -162,7 +162,7 @@ struct FUser
FString Name;

UPROPERTY(BlueprintReadWrite)
FString Email;
FString EmailAddress;
};

// Access via DbConnection
Expand All @@ -172,6 +172,8 @@ Context.Db->User
</TabItem>
</Tabs>

Note that generated names follow each language's conventions, including field names: a column declared as `email_address` in your module becomes `emailAddress` in TypeScript and `EmailAddress` in C#.

See the [Tables](../00300-tables.md) documentation for details on defining tables in your module.

### Reducers
Expand All @@ -189,7 +191,7 @@ For example, a `create_user` reducer becomes:

```typescript
// Call the reducer
conn.reducers.createUser({ name, email });
await conn.reducers.createUser({ name, email });

// Register a callback to observe reducer invocations
conn.reducers.onCreateUser((ctx, { name, email }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ The `onUpdate` callback runs whenever an already-resident row in the client cach

Only handles with a declared primary key expose `onUpdate` callbacks. Handles for tables or views without a primary key will not have `onUpdate` or `removeOnUpdate` methods. TypeScript view handles expose `onUpdate` callbacks when the returned row type has exactly one column marked with `.primaryKey()`.

The `Row` type will be an autogenerated type which matches the row type defined by the module.
The `Row` type will be an autogenerated type which matches the row type defined by the module. Its field names are the module's column names converted to camelCase, so a column declared `trip_id` appears as `tripId`.

`removeOnUpdate` may be used to un-register a previously-registered `onUpdate` callback.

Expand All @@ -1010,10 +1010,24 @@ All [`DbContext`](#interface-dbcontext) implementors, including [`DbConnection`]

Each reducer defined by the module has three methods on the `.reducers`:

- An invoke method, whose name is the reducer's name converted to camel case, like `setName`. This requests that the module run the reducer.
- An invoke method, whose name is the reducer's name converted to camel case, like `setName`. This requests that the module run the reducer. It returns a `Promise<void>` which rejects with `SenderError` if the reducer fails, so `await` it in a `try`/`catch` to handle errors.
- A callback registation method, whose name is prefixed with `on`, like `onSetName`. This registers a callback to run whenever we are notified that the reducer ran, including successfully committed runs and runs we requested which failed. This method returns a callback id, which can be passed to the callback remove method.
- A callback remove method, whose name is prefixed with `removeOn`, like `removeOnSetName`. This cancels a callback previously registered via the callback registration method.

For example:

```typescript
try {
await conn.reducers.setName({ name: newName });
} catch (err) {
if (err instanceof SenderError) {
console.log(`You made an error: ${err}`);
} else if (err instanceof InternalError) {
console.log(`The server had an error: ${err}`);
}
}
```

## React Integration

The SpacetimeDB TypeScript SDK includes React bindings under the `spacetimedb/react` subpath. These bindings provide a `SpacetimeDBProvider` component and hooks for easily integrating SpacetimeDB into React applications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ These access rules are expressed in SQL and evaluated automatically for queries
RLS is **experimental and unstable**. It must be explicitly enabled in your module, and the API may change in future releases.

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">
No explicit opt-in is required in TypeScript; the feature is still experimental and the API may change.

</TabItem>
<TabItem value="csharp" label="C#">
To enable RLS, include the following preprocessor directive at the top of your module files:

Expand All @@ -50,6 +54,17 @@ spacetimedb = { version = "...", features = ["unstable"] }
## How It Works

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">
RLS rules are expressed in SQL and declared as exported filters on your schema.

```typescript
/** A client can only see their account */
export const accountFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT * FROM account WHERE account.identity = :sender'
);
```

</TabItem>
<TabItem value="csharp" label="C#">
RLS rules are expressed in SQL and declared as public static readonly fields of type `Filter`.

Expand Down Expand Up @@ -112,6 +127,21 @@ This means clients will be able to see to any row that matches at least one of t
#### Example

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">

```typescript
/** A client can only see their account */
export const accountFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT * FROM account WHERE account.identity = :sender'
);

/** An admin can see all accounts */
export const accountFilterForAdmins = spacetimedb.clientVisibilityFilter.sql(
'SELECT account.* FROM account JOIN admin WHERE admin.identity = :sender'
);
```

</TabItem>
<TabItem value="csharp" label="C#">

```cs
Expand Down Expand Up @@ -169,6 +199,30 @@ This ensures that data is never leaked through indirect access patterns.
#### Example

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">

```typescript
/** A client can only see their account */
export const accountFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT * FROM account WHERE account.identity = :sender'
);

/** An admin can see all accounts */
export const accountFilterForAdmins = spacetimedb.clientVisibilityFilter.sql(
'SELECT account.* FROM account JOIN admin WHERE admin.identity = :sender'
);

/**
* Explicitly filtering by client identity in this rule is not necessary,
* since the above RLS rules on `account` will be applied automatically.
* Hence a client can only see their player, but an admin can see all players.
*/
export const playerFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT p.* FROM account a JOIN player p ON a.id = p.id'
);
```

</TabItem>
<TabItem value="csharp" label="C#">

```cs
Expand Down Expand Up @@ -240,6 +294,20 @@ as this would result in infinite recursion.
#### Example: Self-Join

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">

```typescript
/** A client can only see players on their same level */
export const playerFilter = spacetimedb.clientVisibilityFilter.sql(`
SELECT q.*
FROM account a
JOIN player p ON a.id = p.id
JOIN player q on p.level = q.level
WHERE a.identity = :sender
`);
```

</TabItem>
<TabItem value="csharp" label="C#">

```cs
Expand Down Expand Up @@ -283,9 +351,24 @@ const PLAYER_FILTER: Filter = Filter::Sql("

#### Example: Recursive Rules

This module will fail to publish because each rule depends on the other one.
This module will publish, but because each rule depends on the other one, client queries and subscriptions on these tables will fail with a cyclic dependency error.

<Tabs groupId="server-language" defaultValue="rust">
<TabItem value="typescript" label="TypeScript">

```typescript
/** An account must have a corresponding player */
export const accountFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT a.* FROM account a JOIN player p ON a.id = p.id WHERE a.identity = :sender'
);

/** A player must have a corresponding account */
export const playerFilter = spacetimedb.clientVisibilityFilter.sql(
'SELECT p.* FROM account a JOIN player p ON a.id = p.id WHERE a.identity = :sender'
);
```

</TabItem>
<TabItem value="csharp" label="C#">

```cs
Expand Down
4 changes: 3 additions & 1 deletion docs/static/llms.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ This section covers the fundamental concepts you need to understand to build app

- [Core Concepts](/docs/core-concepts): This section covers the fundamental concepts you need to understand to build applications with SpacetimeDB.
- [Authentication](/docs/core-concepts/authentication): SpacetimeDB modules are exposed to the open internet and anyone can connect to
- [Auth0](/docs/core-concepts/authentication/Auth0): This guilde will walk you through integrating Auth0 authentication with your SpacetimeDB application.
- [Auth0](/docs/core-concepts/authentication/Auth0): This guide will walk you through integrating Auth0 authentication with your SpacetimeDB application.
- [Better Auth](/docs/core-concepts/authentication/BetterAuth): Better Auth is a TypeScript authentication
- [Clerk](/docs/core-concepts/authentication/Clerk): This guide will walk you through integrating Clerk authentication with your SpacetimeDB React application. You will configure a Clerk application, obtain a JWT from Clerk, and pass it to your SpacetimeDB connection as the authentication token.
- [Overview](/docs/core-concepts/authentication/spacetimeauth/): SpacetimeAuth is currently in beta, some features may not be available yet or
- [Configuring your project](/docs/core-concepts/authentication/spacetimeauth/configuring-a-project): SpacetimeAuth is currently in beta, some features may not be available yet or may change in the future. You might encounter bugs or issues while using the service. Please report any problems you encounter to help us improve SpacetimeAuth.
Expand Down Expand Up @@ -119,6 +120,7 @@ A module is a collection of functions and schema definitions, which can be writt
- [React Quickstart](/docs/quickstarts/react): Get a SpacetimeDB React app running in under 5 minutes.
- [Remix Quickstart](/docs/quickstarts/remix): Get a SpacetimeDB Remix app running in under 5 minutes.
- [Rust Quickstart](/docs/quickstarts/rust): Get a SpacetimeDB Rust app running in under 5 minutes.
- [SolidJS Quickstart](/docs/quickstarts/solid): Get a SpacetimeDB SolidJS app running in under 5 minutes.
- [Svelte Quickstart](/docs/quickstarts/svelte): Get a SpacetimeDB Svelte app running in under 5 minutes.
- [TanStack Start Quickstart](/docs/quickstarts/tanstack): Get a SpacetimeDB app with TanStack Start running in under 5 minutes.
- [TypeScript Quickstart](/docs/quickstarts/typescript): Get a SpacetimeDB TypeScript app running in under 5 minutes.
Expand Down
2 changes: 1 addition & 1 deletion skills/concepts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ SpacetimeDB is a relational database that is also a server. It lets you upload a
## Critical Rules

1. **Reducers are transactional.** They do not return data to callers. Use subscriptions to read data.
2. **Reducers must be deterministic.** Do not use filesystem, network, external clocks, or external random sources in reducers. Use `ReducerContext` for SpacetimeDB-provided timestamp and deterministic random values.
2. **Reducers must be deterministic.** Do not use filesystem, network, external clocks, or external random sources in reducers. Use the reducer context (`ctx`) for SpacetimeDB-provided timestamp and deterministic random values.
3. **Read data via tables/subscriptions**, not reducer return values. Clients get data through subscribed queries.
4. **Auto-increment IDs are not sequential.** Gaps are normal, do not use for ordering. Use timestamps or explicit sequence columns.
5. **`ctx.sender` is the authenticated principal.** Never trust identity passed as arguments.
Expand Down
4 changes: 4 additions & 0 deletions skills/csharp-client/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ metadata:

Install: `dotnet add package SpacetimeDB.ClientSDK`

Generated bindings convert snake_case names to PascalCase, including row fields: a server column `trip_id` is `TripId` on client rows.

## Connection

```csharp
Expand Down Expand Up @@ -137,6 +139,8 @@ conn.Reducers.MovePlayer(10.0f, 20.0f);
conn.Reducers.SendMessage("Hello!");
```

Reducer calls return `void`; observe failures via the reducer callbacks below (`Status.Failed`).

## Reducer Callbacks

```csharp
Expand Down
Loading
Loading