mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-08-09 18:50:42 -04:00
Add doc versioning (#4381)
# Description of Changes
This change cuts a version of the docs from 1.12.0
(e45cf891c2), and saves it under
`docs/versioned_docs/version-1.12.0`.
If you go to the docs page, it will redirect to the 1.12.0 docs, which
has a banner at the top for trying the prerelease docs:
<img width="1027" height="283" alt="Screenshot 2026-02-20 at 12 07
39 PM"
src="https://github.com/user-attachments/assets/8ff3d622-b693-469a-980a-01c34d0506b8"
/>
If you select the prelease docs, there is a warning banner at the top:
<img width="886" height="299" alt="Screenshot 2026-02-20 at 12 08 52 PM"
src="https://github.com/user-attachments/assets/5508a635-765e-40cc-ad2a-cbbed7f779dd"
/>
# Expected complexity level and risk
This only changes docs.
# Testing
I've testing by running this locally.
This commit is contained in:
@@ -58,6 +58,71 @@ For more information on how to use Docusaurus, see the
|
||||
4. Run the development server: `pnpm dev`, which will start a local server and open a browser window.
|
||||
All changes you make to the markdown files will be reflected live in the browser.
|
||||
|
||||
### Cutting Docs Versions
|
||||
|
||||
Use Docusaurus versioning to snapshot the current docs into `versioned_docs`.
|
||||
|
||||
1. From `docs/`, cut a version:
|
||||
|
||||
```bash
|
||||
pnpm docusaurus docs:version <version-name>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
pnpm docusaurus docs:version 1.12.0
|
||||
```
|
||||
|
||||
This updates:
|
||||
|
||||
- `docs/versions.json`
|
||||
- `docs/versioned_docs/version-<version-name>/`
|
||||
- `docs/versioned_sidebars/version-<version-name>-sidebars.json`
|
||||
|
||||
After cutting, update `docs/docusaurus.config.ts` as needed:
|
||||
|
||||
- `lastVersion` for the default version at `/docs`
|
||||
- `versions.current` label/path for prerelease docs
|
||||
- `versions['<version-name>']` label/banner for the stable snapshot
|
||||
|
||||
### Re-cutting a Version From an Older Commit
|
||||
|
||||
If you need a version snapshot from an old commit (instead of current `docs/docs`), use:
|
||||
|
||||
```bash
|
||||
./docs/scripts/get-old-docs.sh <commit> <version-name>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
./docs/scripts/get-old-docs.sh e45cf891c20d87b11976e1d54c04c0e4639dbe81 1.12.0
|
||||
```
|
||||
|
||||
The script creates a temporary worktree, snapshots docs from that commit, and copies the generated `versioned_docs` artifacts back into your current branch.
|
||||
|
||||
### Rewriting Absolute Links to Version-Safe Relative Links
|
||||
|
||||
Absolute links like `/quickstarts/react` can resolve to the default docs version. To keep links inside the current version, rewrite internal links to relative paths.
|
||||
|
||||
Dry run:
|
||||
|
||||
```bash
|
||||
pnpm --dir docs rewrite-links
|
||||
```
|
||||
|
||||
Apply changes:
|
||||
|
||||
```bash
|
||||
pnpm --dir docs rewrite-links:write
|
||||
```
|
||||
|
||||
This script rewrites internal absolute links in:
|
||||
|
||||
- `docs/docs` (current/prerelease docs)
|
||||
- `docs/versioned_docs/version-*` (all version snapshots)
|
||||
|
||||
### Adding new pages
|
||||
|
||||
All of our directory and file names are prefixed with a five-digit number which determines how they're sorted.
|
||||
|
||||
@@ -50,8 +50,8 @@ After completing a quickstart guide, explore these core concepts to deepen your
|
||||
|
||||
### Core Concepts
|
||||
|
||||
- **[Databases](/databases)** - Understand database lifecycle, publishing, and management
|
||||
- **[Tables](/tables)** - Define your data structure with tables, columns, and indexes
|
||||
- **[Functions](/functions)** - Write reducers, procedures, and views to implement your server logic
|
||||
- **[Subscriptions](/clients/subscriptions)** - Enable real-time data synchronization with clients
|
||||
- **[Client SDKs](/clients)** - Connect your client applications to SpacetimeDB
|
||||
- **[Databases](../../00200-core-concepts/00100-databases.md)** - Understand database lifecycle, publishing, and management
|
||||
- **[Tables](../../00200-core-concepts/00300-tables.md)** - Define your data structure with tables, columns, and indexes
|
||||
- **[Functions](../../00200-core-concepts/00200-functions.md)** - Write reducers, procedures, and views to implement your server logic
|
||||
- **[Subscriptions](../../00200-core-concepts/00400-subscriptions.md)** - Enable real-time data synchronization with clients
|
||||
- **[Client SDKs](../../00200-core-concepts/00600-clients.md)** - Connect your client applications to SpacetimeDB
|
||||
|
||||
@@ -6,7 +6,7 @@ slug: /intro/what-is-spacetimedb
|
||||
|
||||
SpacetimeDB is a database that is also a server.
|
||||
|
||||
SpacetimeDB is a full-featured relational database system that lets you run your application logic **inside** the database. You no longer need to deploy a separate web or game server. [Several programming languages](/intro/language-support) are supported, including C# and Rust. You can still write authorization logic, just like you would in a traditional server.
|
||||
SpacetimeDB is a full-featured relational database system that lets you run your application logic **inside** the database. You no longer need to deploy a separate web or game server. [Several programming languages](./00300-language-support.md) are supported, including C# and Rust. You can still write authorization logic, just like you would in a traditional server.
|
||||
|
||||
This means that you can write your entire application in a single language and deploy it as a single binary. No more microservices, no more containers, no more Kubernetes, no more Docker, no more VMs, no more DevOps, no more infrastructure, no more ops, no more servers.
|
||||
|
||||
@@ -56,6 +56,6 @@ The above illustrates the workflow when using SpacetimeDB.
|
||||
|
||||
## State Mirroring
|
||||
|
||||
SpacetimeDB can generate client code in a [variety of languages](/intro/language-support). This creates a client library custom-designed to talk to your database. It provides easy-to-use interfaces for connecting to the database and submitting requests. It can also **automatically mirror state** from your database to client applications.
|
||||
SpacetimeDB can generate client code in a [variety of languages](./00300-language-support.md). This creates a client library custom-designed to talk to your database. It provides easy-to-use interfaces for connecting to the database and submitting requests. It can also **automatically mirror state** from your database to client applications.
|
||||
|
||||
You define subscriptions specifying what information a client is interested in, typically with the type-safe query builder (or raw SQL for advanced cases) -- for instance, the terrain and items near a player's avatar. SpacetimeDB will generate types in your client language for the relevant tables, and feed clients a stream of live updates whenever the database state changes. Note that this is a **read-only** mirror -- the only way to change the database is to submit requests, which are validated on the server.
|
||||
|
||||
@@ -11,7 +11,7 @@ Your entire application state lives in tables. Users, messages, game entities, s
|
||||
|
||||
This simplifies your mental model dramatically and it makes the impossible possible. SpacetimeDB can hot-swap server code without disconnecting clients!
|
||||
|
||||
When you need to store something, you define a table. When you need to query something, you query a table. When you need to update something, you update a table. When you want to restrict who can read data, you create [a table](/functions/views).
|
||||
When you need to store something, you define a table. When you need to query something, you query a table. When you need to update something, you update a table. When you want to restrict who can read data, you create [a table](../../00200-core-concepts/00200-functions/00500-views.md).
|
||||
|
||||
```
|
||||
Traditional stack: SpacetimeDB:
|
||||
|
||||
@@ -8,19 +8,19 @@ slug: /intro/language-support
|
||||
|
||||
SpacetimeDB modules define your database schema and server-side business logic. Modules can be written in three languages:
|
||||
|
||||
- **[Rust](/databases)** - High performance, compiled to WebAssembly [(Quickstart)](/quickstarts/rust)
|
||||
- **[C#](/databases)** - Great for Unity developers, compiled to WebAssembly [(Quickstart)](/quickstarts/c-sharp)
|
||||
- **[TypeScript](/databases)** - Ideal for web developers, runs on V8 [(Quickstart)](/quickstarts/typescript)
|
||||
- **[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)
|
||||
|
||||
## Client SDKs
|
||||
|
||||
**Clients** are applications that connect to SpacetimeDB databases. The `spacetime` CLI tool can automatically generate type-safe client code for your database.
|
||||
|
||||
- **[Rust](/clients/rust)** - [(Quickstart)](/quickstarts/rust)
|
||||
- **[C#](/clients/c-sharp)** - [(Quickstart)](/quickstarts/c-sharp)
|
||||
- **[TypeScript](/clients/typescript)** - [(Quickstart)](/quickstarts/typescript)
|
||||
- **[Unreal Engine](/clients/unreal)** - C++ and Blueprint support [(Tutorial)](/tutorials/unreal/part-1)
|
||||
- **[Rust](../../00200-core-concepts/00600-clients/00500-rust-reference.md)** - [(Quickstart)](../00200-quickstarts/00500-rust.md)
|
||||
- **[C#](../../00200-core-concepts/00600-clients/00600-csharp-reference.md)** - [(Quickstart)](../00200-quickstarts/00600-c-sharp.md)
|
||||
- **[TypeScript](../../00200-core-concepts/00600-clients/00700-typescript-reference.md)** - [(Quickstart)](../00200-quickstarts/00400-typescript.md)
|
||||
- **[Unreal Engine](../../00200-core-concepts/00600-clients/00800-unreal-reference.md)** - C++ and Blueprint support [(Tutorial)](../00300-tutorials/00400-unreal-tutorial/00200-part-1.md)
|
||||
|
||||
### Unity
|
||||
|
||||
SpacetimeDB was designed first and foremost as the backend for multiplayer Unity games. The C# SDK integrates seamlessly with Unity projects. Learn more in the [SpacetimeDB Unity Tutorial](/tutorials/unity/part-1).
|
||||
SpacetimeDB was designed first and foremost as the backend for multiplayer Unity games. The C# SDK integrates seamlessly with Unity projects. Learn more in the [SpacetimeDB Unity Tutorial](../00300-tutorials/00300-unity-tutorial/00200-part-1.md).
|
||||
|
||||
@@ -19,7 +19,7 @@ A database exports [tables](#table), which store data, and [reducers](#reducer),
|
||||
|
||||
A database's schema and business logic is specified by a piece of software called a **module**. Modules can be written in C#, C++, Rust or TypeScript.
|
||||
|
||||
(Technically, a SpacetimeDB module is a [WebAssembly module](https://developer.mozilla.org/en-US/docs/WebAssembly) or JavaScript bundle, that imports a specific low-level [WebAssembly ABI](/webassembly-abi) and exports a small number of special functions. However, the SpacetimeDB [server-side libraries](/databases) hide these low-level details. As a developer, writing a module is mostly like writing any other application, except for the fact that a [special CLI tool](https://spacetimedb.com/install) is used to deploy the application.)
|
||||
(Technically, a SpacetimeDB module is a [WebAssembly module](https://developer.mozilla.org/en-US/docs/WebAssembly) or JavaScript bundle, that imports a specific low-level [WebAssembly ABI](../../00300-resources/00200-reference/00300-internals/00100-module-abi-reference.md) and exports a small number of special functions. However, the SpacetimeDB [server-side libraries](../../00200-core-concepts/00100-databases.md) hide these low-level details. As a developer, writing a module is mostly like writing any other application, except for the fact that a [special CLI tool](https://spacetimedb.com/install) is used to deploy the application.)
|
||||
|
||||
## Table
|
||||
|
||||
@@ -99,7 +99,7 @@ Tables marked `public` can also be read by [clients](#client).
|
||||
## Reducer
|
||||
|
||||
A **reducer** is a function exported by a [database](#database).
|
||||
Connected [clients](/clients) can call reducers to interact with the database.
|
||||
Connected [clients](../../00200-core-concepts/00600-clients.md) can call reducers to interact with the database.
|
||||
This is a form of [remote procedure call](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
@@ -232,7 +232,7 @@ export const world = spacetimedb.reducer((ctx) => {
|
||||
```
|
||||
|
||||
While SpacetimeDB doesn't support nested transactions,
|
||||
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
|
||||
a reducer can [schedule another reducer](../../00200-core-concepts/00300-tables/00500-schedule-tables.md) to run at an interval,
|
||||
or at a specific time.
|
||||
|
||||
</TabItem>
|
||||
@@ -257,7 +257,7 @@ public static void World(ReducerContext ctx)
|
||||
```
|
||||
|
||||
While SpacetimeDB doesn't support nested transactions,
|
||||
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
|
||||
a reducer can [schedule another reducer](../../00200-core-concepts/00300-tables/00500-schedule-tables.md) to run at an interval,
|
||||
or at a specific time.
|
||||
|
||||
</TabItem>
|
||||
@@ -300,13 +300,13 @@ SPACETIMEDB_REDUCER(hello, ReducerContext ctx) {
|
||||
```
|
||||
|
||||
While SpacetimeDB doesn't support nested transactions,
|
||||
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
|
||||
a reducer can [schedule another reducer](../../00200-core-concepts/00300-tables/00500-schedule-tables.md) to run at an interval,
|
||||
or at a specific time.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See [Reducers](/functions/reducers) for more details about reducers.
|
||||
See [Reducers](../../00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md) for more details about reducers.
|
||||
|
||||
## Procedure
|
||||
|
||||
@@ -481,7 +481,7 @@ An Unreal [client](#client) can also register a callback to run when a procedure
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See [Procedures](/functions/procedures) for more details about procedures.
|
||||
See [Procedures](../../00200-core-concepts/00200-functions/00400-procedures.md) for more details about procedures.
|
||||
|
||||
## View
|
||||
|
||||
@@ -550,13 +550,13 @@ Views can be queried and subscribed to using SQL:
|
||||
SELECT * FROM my_player;
|
||||
```
|
||||
|
||||
See [Views](/functions/views) for more details about views.
|
||||
See [Views](../../00200-core-concepts/00200-functions/00500-views.md) for more details about views.
|
||||
|
||||
## Client
|
||||
|
||||
A **client** is an application that connects to a [database](#database). A client logs in using an [identity](#identity) and receives an [connection id](#connectionid) to identify the connection. After that, it can call [reducers](#reducer) and query public [tables](#table).
|
||||
|
||||
Clients are written using the [client-side SDKs](/clients). The `spacetime` CLI tool allows automatically generating code that works with the client-side SDKs to talk to a particular database.
|
||||
Clients are written using the [client-side SDKs](../../00200-core-concepts/00600-clients.md). The `spacetime` CLI tool allows automatically generating code that works with the client-side SDKs to talk to a particular database.
|
||||
|
||||
Clients are regular software applications that developers can choose how to deploy (through Steam, app stores, package managers, or any other software deployment method, depending on the needs of the application.)
|
||||
|
||||
|
||||
@@ -86,11 +86,11 @@ Reducers are functions that modify your database state. They run inside a databa
|
||||
|
||||
### What are views?
|
||||
|
||||
Views are read-only functions that compute derived data from your tables. They are like SQL views but written in your module's language. Clients can subscribe to views just like tables, and they update automatically when the underlying data changes. See [Views](/functions/views) for details.
|
||||
Views are read-only functions that compute derived data from your tables. They are like SQL views but written in your module's language. Clients can subscribe to views just like tables, and they update automatically when the underlying data changes. See [Views](../../00200-core-concepts/00200-functions/00500-views.md) for details.
|
||||
|
||||
### What are procedures?
|
||||
|
||||
Procedures are similar to reducers but with additional capabilities. They can make HTTP requests to external services and manually manage transactions. Clients can also call procedures over HTTP. Procedures are currently in beta. Use reducers for most cases; use procedures when you need to interact with the outside world. In the future, procedures will be configurable as HTTP endpoints. See [Procedures](/functions/procedures) for details.
|
||||
Procedures are similar to reducers but with additional capabilities. They can make HTTP requests to external services and manually manage transactions. Clients can also call procedures over HTTP. Procedures are currently in beta. Use reducers for most cases; use procedures when you need to interact with the outside world. In the future, procedures will be configurable as HTTP endpoints. See [Procedures](../../00200-core-concepts/00200-functions/00400-procedures.md) for details.
|
||||
|
||||
---
|
||||
|
||||
@@ -154,7 +154,7 @@ SpacetimeDB 2.0 also includes a **type-safe query builder** for client-side subs
|
||||
5. Generate client bindings: `spacetime generate --lang typescript --out-dir src/module_bindings`
|
||||
6. Connect from your client using the generated code
|
||||
|
||||
See the [quickstart guides](/quickstarts/react) for step-by-step tutorials.
|
||||
See the [quickstart guides](../00200-quickstarts/00100-react.md) for step-by-step tutorials.
|
||||
|
||||
### What is `spacetime dev`?
|
||||
|
||||
@@ -166,11 +166,11 @@ No. Your server-side module code uses native language APIs to query and modify t
|
||||
|
||||
### Can I use SpacetimeDB with Unity?
|
||||
|
||||
Yes. SpacetimeDB has a C# client SDK that works with Unity. The SDK maintains a local cache of your subscribed data and provides callbacks for changes. See the [Unity tutorial](/tutorials/unity) for a complete walkthrough.
|
||||
Yes. SpacetimeDB has a C# client SDK that works with Unity. The SDK maintains a local cache of your subscribed data and provides callbacks for changes. See the [Unity tutorial](../00300-tutorials/00300-unity-tutorial/index.md) for a complete walkthrough.
|
||||
|
||||
### Can I use SpacetimeDB with Unreal Engine?
|
||||
|
||||
Yes. SpacetimeDB has a C++ client SDK for Unreal Engine with Blueprint support. See the [Unreal quickstart](/quickstarts/c-plus-plus) for details.
|
||||
Yes. SpacetimeDB has a C++ client SDK for Unreal Engine with Blueprint support. See the [Unreal quickstart](../00200-quickstarts/00700-cpp.md) for details.
|
||||
|
||||
### Can I use SpacetimeDB with React / Vue / Angular / Svelte?
|
||||
|
||||
@@ -184,10 +184,10 @@ Yes. The TypeScript SDK includes framework-specific integrations for React, Vue,
|
||||
|
||||
SpacetimeDB uses [OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) for authentication. Every reducer call includes the caller's `Identity`, which you can use for authorization logic in your module. You have several options for identity providers:
|
||||
|
||||
- **[SpacetimeAuth](/core-concepts/authentication/spacetimeauth)**: A fully managed OIDC provider built specifically for SpacetimeDB. The easiest way to get started.
|
||||
- **[SpacetimeAuth](../../00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md)**: A fully managed OIDC provider built specifically for SpacetimeDB. The easiest way to get started.
|
||||
- **Third-party providers**: Any OIDC-compliant provider works, including [Auth0](https://auth0.com/), [Clerk](https://clerk.com/), [Keycloak](https://www.keycloak.org/), Google, GitHub, and others.
|
||||
|
||||
See [Authentication](/core-concepts/authentication) for full details.
|
||||
See [Authentication](../../00200-core-concepts/00500-authentication.md) for full details.
|
||||
|
||||
### Can I use `Identity` as a 1-to-1 mapping with users?
|
||||
|
||||
@@ -195,11 +195,11 @@ Yes, and this is the recommended approach. Each authenticated user receives a st
|
||||
|
||||
### Can I store passwords in a private table and have users log in by calling a reducer?
|
||||
|
||||
This is not recommended. While SpacetimeDB 2.0 no longer exposes reducer arguments to subscribers (the old reducer callback system has been replaced by [Event Tables](/tables/event-tables)), storing and verifying passwords in your module means implementing your own authentication logic, which is error-prone and unnecessary. Use OIDC-based authentication instead, where the identity provider handles credential verification and issues tokens that SpacetimeDB validates.
|
||||
This is not recommended. While SpacetimeDB 2.0 no longer exposes reducer arguments to subscribers (the old reducer callback system has been replaced by [Event Tables](../../00200-core-concepts/00300-tables/00550-event-tables.md)), storing and verifying passwords in your module means implementing your own authentication logic, which is error-prone and unnecessary. Use OIDC-based authentication instead, where the identity provider handles credential verification and issues tokens that SpacetimeDB validates.
|
||||
|
||||
### How do I create a new localhost identity?
|
||||
|
||||
Send a `POST /v1/identity` request to your SpacetimeDB instance. The response includes a new identity and token. See the [HTTP API reference](/http/identity#post-v1identity) for details.
|
||||
Send a `POST /v1/identity` request to your SpacetimeDB instance. The response includes a new identity and token. See the [HTTP API reference](../../00300-resources/00200-reference/00200-http-api/00200-identity.md#post-v1identity) for details.
|
||||
|
||||
:::warning
|
||||
Identities issued by SpacetimeDB acting as its own identity provider are tied to a single token that does not expire. If that token is lost, there is no way to recover or re-authenticate as that identity. This approach is recommended for **development only**. For production applications, use an external OIDC provider (SpacetimeAuth, Auth0, Clerk, etc.) which provides proper token lifecycle management.
|
||||
@@ -221,15 +221,15 @@ conn.subscriptionBuilder().subscribe(
|
||||
|
||||
### How do I handle schema migrations?
|
||||
|
||||
When you publish an updated module, SpacetimeDB compares the new schema with the existing one and performs automatic migrations for compatible changes (adding tables, adding columns with defaults, etc.). For breaking changes, you may need to publish with `--delete-data` during development. See [Automatic Migrations](/databases/automatic-migrations) for details on what changes are supported.
|
||||
When you publish an updated module, SpacetimeDB compares the new schema with the existing one and performs automatic migrations for compatible changes (adding tables, adding columns with defaults, etc.). For breaking changes, you may need to publish with `--delete-data` during development. See [Automatic Migrations](../../00200-core-concepts/00100-databases/00500-migrations/00200-automatic-migrations.md) for details on what changes are supported.
|
||||
|
||||
### How do I add a column to an existing table?
|
||||
|
||||
SpacetimeDB supports adding new columns to the end of a table, provided the new columns have [default values](/tables/default-values). This is handled automatically when you republish your module. For more complex changes (reordering columns, changing types), use the [incremental migration pattern](/databases/incremental-migrations): create a new table with the desired schema and lazily migrate rows from the old table as they are accessed.
|
||||
SpacetimeDB supports adding new columns to the end of a table, provided the new columns have [default values](../../00200-core-concepts/00300-tables/00250-default-values.md). This is handled automatically when you republish your module. For more complex changes (reordering columns, changing types), use the [incremental migration pattern](../../00200-core-concepts/00100-databases/00500-migrations/00300-incremental-migrations.md): create a new table with the desired schema and lazily migrate rows from the old table as they are accessed.
|
||||
|
||||
### How do I remove a column from an existing table?
|
||||
|
||||
Removing columns is not supported through automatic migration. Use [incremental migrations](/databases/incremental-migrations) instead: create a new table without the column and migrate data incrementally.
|
||||
Removing columns is not supported through automatic migration. Use [incremental migrations](../../00200-core-concepts/00100-databases/00500-migrations/00300-incremental-migrations.md) instead: create a new table without the column and migrate data incrementally.
|
||||
|
||||
### How do I add a new table?
|
||||
|
||||
|
||||
@@ -129,5 +129,5 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -208,5 +208,5 @@ export function PersonList({ initialPeople }) {
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -127,4 +127,4 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -242,4 +242,4 @@ const displayPeople = computed(() => {
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -127,4 +127,4 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -129,4 +129,4 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -164,5 +164,5 @@ function App() {
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -203,5 +203,5 @@ export default function Index() {
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -122,5 +122,5 @@ conn.db.person.onDelete((ctx, person) => {
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -259,5 +259,5 @@ bun run start
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -272,5 +272,5 @@ cat package.json
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -254,5 +254,5 @@ npm run start
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -120,5 +120,5 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [TypeScript SDK Reference](/clients/typescript) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-clients/00700-typescript-reference.md) for detailed API docs
|
||||
|
||||
@@ -119,5 +119,5 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [Rust SDK Reference](/clients/rust) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [Rust SDK Reference](../../00200-core-concepts/00600-clients/00500-rust-reference.md) for detailed API docs
|
||||
|
||||
@@ -135,5 +135,5 @@ spacetime logs
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example
|
||||
- Read the [C# SDK Reference](/clients/c-sharp) for detailed API docs
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [C# SDK Reference](../../00200-core-concepts/00600-clients/00600-csharp-reference.md) for detailed API docs
|
||||
|
||||
@@ -1669,13 +1669,13 @@ At this point you've learned how to create a basic TypeScript client for your Sp
|
||||
|
||||
Next, we'll show you how to get up and running with a simple SpacetimeDB app with a client written in C#.
|
||||
|
||||
We'll implement a command-line client for the module created in our [Rust](/docs/quickstarts/rust) or [C# Module](/docs/quickstarts/c-sharp) Quickstart guides. Ensure you followed one of these guides before continuing.
|
||||
We'll implement a command-line client for the module created in our [Rust](../00200-quickstarts/00500-rust.md) or [C# Module](../00200-quickstarts/00600-c-sharp.md) Quickstart guides. Ensure you followed one of these guides before continuing.
|
||||
|
||||
If you've not already installed .NET 8, the [C# Module](/docs/quickstarts/c-sharp) Quickstart guide will show you how to install it, which we will need to run the client.
|
||||
If you've not already installed .NET 8, the [C# Module](../00200-quickstarts/00600-c-sharp.md) Quickstart guide will show you how to install it, which we will need to run the client.
|
||||
|
||||
### Project structure
|
||||
|
||||
Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/quickstarts/rust) or [C# Module Quickstart](/docs/quickstarts/c-sharp) guides:
|
||||
Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](../00200-quickstarts/00500-rust.md) or [C# Module Quickstart](../00200-quickstarts/00600-c-sharp.md) guides:
|
||||
|
||||
```bash
|
||||
cd quickstart-chat
|
||||
@@ -2258,7 +2258,7 @@ We'll implement a command-line client for the module created in our Rust or C# M
|
||||
|
||||
### Project structure
|
||||
|
||||
Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/quickstarts/rust) or [C# Module Quickstart](/docs/quickstarts/c-sharp) guides:
|
||||
Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](../00200-quickstarts/00500-rust.md) or [C# Module Quickstart](../00200-quickstarts/00600-c-sharp.md) guides:
|
||||
|
||||
```bash
|
||||
cd quickstart-chat
|
||||
@@ -2733,6 +2733,6 @@ User <my-name> connected.
|
||||
|
||||
Congratulations! You've built a chat app with SpacetimeDB.
|
||||
|
||||
- Check out the [SDK Reference documentation](/clients) for more advanced usage
|
||||
- Explore the [Unity Tutorial](/docs/tutorials/unity) or [Unreal Tutorial](/docs/tutorials/unreal) for game development
|
||||
- Learn about [Procedures](/functions/procedures) for making external API calls
|
||||
- Check out the [SDK Reference documentation](../../00200-core-concepts/00600-clients.md) for more advanced usage
|
||||
- Explore the [Unity Tutorial](./00300-unity-tutorial/index.md) or [Unreal Tutorial](./00400-unreal-tutorial/index.md) for game development
|
||||
- Learn about [Procedures](../../00200-core-concepts/00200-functions/00400-procedures.md) for making external API calls
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 1](/docs/tutorials/unity/part-1).
|
||||
This progressive tutorial is continued from [part 1](./00200-part-1.md).
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -31,7 +31,7 @@ Your `module_bindings` directory can go wherever you want as long as it is insid
|
||||
|
||||
## Create a Server Module
|
||||
|
||||
If you have not already installed the `spacetime` CLI, check out our [Getting Started](/) guide for instructions on how to install.
|
||||
If you have not already installed the `spacetime` CLI, check out our [Getting Started](../../00100-getting-started/00100-getting-started.md) guide for instructions on how to install.
|
||||
|
||||
In the same directory that contains your `blackholio` project, run the following command to initialize the SpacetimeDB server module project with your desired language:
|
||||
|
||||
@@ -211,7 +211,7 @@ The `FIELD_PrimaryKey` macro specifies that the `id` field should be used as the
|
||||
|
||||
> NOTE: The primary key of a row defines the "identity" of the row. A change to a row which doesn't modify the primary key is considered an update, but if you change the primary key, then you have deleted the old row and inserted a new one.
|
||||
|
||||
Learn more about defining tables, including indexes, constraints, and column types, in our [Tables documentation](/tables).
|
||||
Learn more about defining tables, including indexes, constraints, and column types, in our [Tables documentation](../../../00200-core-concepts/00300-tables.md).
|
||||
|
||||
### Creating Entities
|
||||
|
||||
@@ -832,4 +832,4 @@ spacetime logs --server local blackholio
|
||||
|
||||
You've learned how to setup a Unity project with the SpacetimeDB SDK, write a basic SpacetimeDB server module, and how to connect your Unity client to SpacetimeDB. That's pretty much all there is to the setup. You're now ready to start building the game.
|
||||
|
||||
In the [next part](/docs/tutorials/unity/part-3), we'll build out the functionality of the game and you'll learn how to access your table data and call reducers in Unity.
|
||||
In the [next part](./00400-part-3.md), we'll build out the functionality of the game and you'll learn how to access your table data and call reducers in Unity.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 2](/docs/tutorials/unity/part-2).
|
||||
This progressive tutorial is continued from [part 2](./00300-part-2.md).
|
||||
|
||||
### Spawning Food
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 3](/docs/tutorials/unity/part-3).
|
||||
This progressive tutorial is continued from [part 3](./00400-part-3.md).
|
||||
|
||||
### Moving the player
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ Please file an issue [here](https://github.com/clockworklabs/SpacetimeDB/issues)
|
||||
|
||||
First you'll get started with the core client/server setup. For part 2, you'll be able to choose between **Rust** or **C#** for your server module language:
|
||||
|
||||
- [Part 1 - Setup](/docs/tutorials/unity/part-1)
|
||||
- [Part 2 - Connecting to SpacetimeDB](/docs/tutorials/unity/part-2)
|
||||
- [Part 3 - Gameplay](/docs/tutorials/unity/part-3)
|
||||
- [Part 4 - Moving and Colliding](/docs/tutorials/unity/part-4)
|
||||
- [Part 1 - Setup](./00200-part-1.md)
|
||||
- [Part 2 - Connecting to SpacetimeDB](./00300-part-2.md)
|
||||
- [Part 3 - Gameplay](./00400-part-3.md)
|
||||
- [Part 4 - Moving and Colliding](./00500-part-4.md)
|
||||
|
||||
## Blackhol.io Tutorial - Advanced
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 1](/docs/tutorials/unreal/part-1).
|
||||
This progressive tutorial is continued from [part 1](./00200-part-1.md).
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -35,7 +35,7 @@ blackholio/ # Unreal project root
|
||||
Ensure you have SpacetimeDB version >=1.4.0 installed to enable Unreal Engine code generation support. You can use `spacetime --version` to check your version and you can use `spacetime version upgrade` to install the latest version.
|
||||
:::
|
||||
|
||||
If you have not already installed the `spacetime` CLI, check out our [Getting Started](/) guide for instructions on how to install.
|
||||
If you have not already installed the `spacetime` CLI, check out our [Getting Started](../../00100-getting-started/00100-getting-started.md) guide for instructions on how to install.
|
||||
|
||||
In the same directory that contains your `blackholio` project, run the following command to initialize the SpacetimeDB server module project with your desired language:
|
||||
|
||||
@@ -211,7 +211,7 @@ The `FIELD_PrimaryKey` macro specifies that the `id` field should be used as the
|
||||
The primary key of a row defines the "identity" of the row. A change to a row which doesn't modify the primary key is considered an update, but if you change the primary key, then you have deleted the old row and inserted a new one.
|
||||
:::
|
||||
|
||||
Learn more about defining tables, including indexes, constraints, and column types, in our [Tables documentation](/tables).
|
||||
Learn more about defining tables, including indexes, constraints, and column types, in our [Tables documentation](../../../00200-core-concepts/00300-tables.md).
|
||||
|
||||
### Creating Entities
|
||||
|
||||
@@ -871,7 +871,7 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context)
|
||||
|
||||
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 are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](/reference/sql) enumerates the operations that are accepted in our SQL syntax.
|
||||
In our `HandleConnect` callback we build a subscription and are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](../../../00300-resources/00200-reference/00400-sql-reference.md) enumerates the operations that are accepted in our SQL syntax.
|
||||
</TabItem>
|
||||
<TabItem value="blueprint" label="Blueprint">
|
||||
|
||||
@@ -953,7 +953,7 @@ Update the **OnConnect_Event**:
|
||||
|
||||
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 `OnConnect_Event` method, allowing us to start up the game.
|
||||
|
||||
In our `OnConnect_Event` callback we build a subscription and are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](/reference/sql) enumerates the operations that are accepted in our SQL syntax.
|
||||
In our `OnConnect_Event` callback we build a subscription and are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](../../../00300-resources/00200-reference/00400-sql-reference.md) enumerates the operations that are accepted in our SQL syntax.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
@@ -990,4 +990,4 @@ spacetime logs --server local blackholio
|
||||
|
||||
You've learned how to setup a Unreal project with the SpacetimeDB SDK, write a basic SpacetimeDB server module, and how to connect your Unreal client to SpacetimeDB. That's pretty much all there is to the setup. You're now ready to start building the game.
|
||||
|
||||
In the [next part](/docs/tutorials/unreal/part-3), we'll build out the functionality of the game and you'll learn how to access your table data and call reducers in Unreal.
|
||||
In the [next part](./00400-part-3.md), we'll build out the functionality of the game and you'll learn how to access your table data and call reducers in Unreal.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 2](/docs/tutorials/unreal/part-2).
|
||||
This progressive tutorial is continued from [part 2](./00300-part-2.md).
|
||||
|
||||
### Spawning Food
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 3](/docs/tutorials/unreal/part-3).
|
||||
This progressive tutorial is continued from [part 3](./00400-part-3.md).
|
||||
|
||||
### Moving the player
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@ Please file an issue [here](https://github.com/clockworklabs/SpacetimeDB/issues)
|
||||
|
||||
First you'll get started with the core client/server setup. For part 2, you'll be able to choose between **Rust** or **C#** for your server module language:
|
||||
|
||||
- [Part 1 - Setup](/docs/tutorials/unreal/part-1)
|
||||
- [Part 2 - Connecting to SpacetimeDB](/docs/tutorials/unreal/part-2)
|
||||
- [Part 3 - Gameplay](/docs/tutorials/unreal/part-3)
|
||||
- [Part 4 - Moving and Colliding](/docs/tutorials/unreal/part-4)
|
||||
- [Part 1 - Setup](./00200-part-1.md)
|
||||
- [Part 2 - Connecting to SpacetimeDB](./00300-part-2.md)
|
||||
- [Part 3 - Gameplay](./00400-part-3.md)
|
||||
- [Part 4 - Moving and Colliding](./00500-part-4.md)
|
||||
|
||||
## Blackhol.io Tutorial - Advanced
|
||||
|
||||
|
||||
@@ -9,28 +9,28 @@ This section covers the fundamental concepts you need to understand to build app
|
||||
|
||||
Learn how SpacetimeDB databases work, including modules, publishing, and transactions.
|
||||
|
||||
- [What is a Database?](/databases) - Understanding SpacetimeDB databases and modules
|
||||
- [Building & Publishing](/databases/building-publishing) - Deploy your module to SpacetimeDB
|
||||
- [Transactions](/databases/transactions-atomicity) - How atomicity and rollback work
|
||||
- [Migrations](/databases/automatic-migrations) - Evolving your schema over time
|
||||
- [What is a Database?](./00100-databases.md) - Understanding SpacetimeDB databases and modules
|
||||
- [Building & Publishing](./00100-databases/00300-spacetime-publish.md) - Deploy your module to SpacetimeDB
|
||||
- [Transactions](./00100-databases/00100-transactions-atomicity.md) - How atomicity and rollback work
|
||||
- [Migrations](./00100-databases/00500-migrations/00200-automatic-migrations.md) - Evolving your schema over time
|
||||
|
||||
## Tables
|
||||
|
||||
Define your data model with tables, columns, and indexes.
|
||||
|
||||
- [Tables Overview](/tables) - Declaring and using tables
|
||||
- [Column Types](/tables/column-types) - Supported column types
|
||||
- [Indexes](/tables/indexes) - Optimizing queries with indexes
|
||||
- [Access Permissions](/tables/access-permissions) - Public vs private tables
|
||||
- [Schedule Tables](/tables/schedule-tables) - Time-based operations
|
||||
- [Tables Overview](./00300-tables.md) - Declaring and using tables
|
||||
- [Column Types](./00300-tables/00200-column-types.md) - Supported column types
|
||||
- [Indexes](./00300-tables/00300-indexes.md) - Optimizing queries with indexes
|
||||
- [Access Permissions](./00300-tables/00400-access-permissions.md) - Public vs private tables
|
||||
- [Schedule Tables](./00300-tables/00500-schedule-tables.md) - Time-based operations
|
||||
|
||||
## Functions
|
||||
|
||||
Implement your application logic with reducers, procedures, and views.
|
||||
|
||||
- [Reducers](/functions/reducers) - Transactional functions that modify state
|
||||
- [Procedures](/functions/procedures) - Functions that can make external HTTP calls
|
||||
- [Views](/functions/views) - Read-only computed queries
|
||||
- [Reducers](./00200-functions/00300-reducers/00300-reducers.md) - Transactional functions that modify state
|
||||
- [Procedures](./00200-functions/00400-procedures.md) - Functions that can make external HTTP calls
|
||||
- [Views](./00200-functions/00500-views.md) - Read-only computed queries
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -44,10 +44,10 @@ Secure your application with SpacetimeAuth.
|
||||
|
||||
Connect your frontend to SpacetimeDB.
|
||||
|
||||
- [SDK Overview](/clients) - Available client SDKs
|
||||
- [Code Generation](/clients/codegen) - Generate type-safe bindings
|
||||
- [Connecting to SpacetimeDB](/clients/connection) - Establish and manage client connections
|
||||
- [SDK API Overview](/clients/api) - Core API concepts shared across SDKs
|
||||
- [Subscriptions](/clients/subscriptions) - Subscribe to data and keep a local cache in sync
|
||||
- [Subscription Semantics](/clients/subscriptions/semantics) - Understand subscription consistency and ordering guarantees
|
||||
- [TypeScript](/clients/typescript), [Rust](/clients/rust), [C#](/clients/c-sharp), [Unreal](/clients/unreal) - Language-specific references
|
||||
- [SDK Overview](./00600-clients.md) - Available client SDKs
|
||||
- [Code Generation](./00600-clients/00200-codegen.md) - Generate type-safe bindings
|
||||
- [Connecting to SpacetimeDB](./00600-clients/00300-connection.md) - Establish and manage client connections
|
||||
- [SDK API Overview](./00600-clients/00400-sdk-api.md) - Core API concepts shared across SDKs
|
||||
- [Subscriptions](./00400-subscriptions.md) - Subscribe to data and keep a local cache in sync
|
||||
- [Subscription Semantics](./00400-subscriptions/00200-subscription-semantics.md) - Understand subscription consistency and ordering guarantees
|
||||
- [TypeScript](./00600-clients/00700-typescript-reference.md), [Rust](./00600-clients/00500-rust-reference.md), [C#](./00600-clients/00600-csharp-reference.md), [Unreal](./00600-clients/00800-unreal-reference.md) - Language-specific references
|
||||
|
||||
@@ -25,10 +25,10 @@ You can deploy the same module to multiple databases (e.g. separate environments
|
||||
|
||||
A module contains:
|
||||
|
||||
- **[Tables](/tables)** - Define your data structure and storage.
|
||||
- **[Reducers](/functions/reducers)** - Server-side functions that modify your data transactionally.
|
||||
- **[Procedures](/functions/procedures)** - Functions that can perform external operations like HTTP requests and return results.
|
||||
- **[Views](/functions/views)** - Read-only computed queries over your data.
|
||||
- **[Tables](./00300-tables.md)** - Define your data structure and storage.
|
||||
- **[Reducers](./00200-functions/00300-reducers/00300-reducers.md)** - Server-side functions that modify your data transactionally.
|
||||
- **[Procedures](./00200-functions/00400-procedures.md)** - Functions that can perform external operations like HTTP requests and return results.
|
||||
- **[Views](./00200-functions/00500-views.md)** - Read-only computed queries over your data.
|
||||
|
||||
The logic is contained within these three categories of server-side functions: reducers (transactional state changes), procedures (functions with external capabilities), and views (read-only queries).
|
||||
|
||||
@@ -41,14 +41,14 @@ SpacetimeDB modules can be written in multiple languages:
|
||||
|
||||
TypeScript is fully supported for server modules. TypeScript is ideal for developers familiar with JavaScript/Node.js.
|
||||
|
||||
- [TypeScript Quickstart Guide](/quickstarts/typescript)
|
||||
- [TypeScript Quickstart Guide](../00100-intro/00200-quickstarts/00400-typescript.md)
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
C# is fully supported for server modules. C# is an excellent choice for developers using Unity or .NET.
|
||||
|
||||
- [C# Quickstart Guide](/quickstarts/c-sharp)
|
||||
- [C# Quickstart Guide](../00100-intro/00200-quickstarts/00600-c-sharp.md)
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
@@ -56,7 +56,7 @@ C# is fully supported for server modules. C# is an excellent choice for develope
|
||||
Rust is fully supported for server modules. Rust is a great choice for performance-critical applications.
|
||||
|
||||
- The Rust Module SDK docs are [hosted on docs.rs](https://docs.rs/spacetimedb/latest/spacetimedb/).
|
||||
- [Rust Quickstart Guide](/quickstarts/rust)
|
||||
- [Rust Quickstart Guide](../00100-intro/00200-quickstarts/00500-rust.md)
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="cpp" label="C++">
|
||||
@@ -65,7 +65,7 @@ Rust is fully supported for server modules. Rust is a great choice for performan
|
||||
|
||||
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)
|
||||
- [C++ Quickstart Guide](../00100-intro/00200-quickstarts/00700-cpp.md)
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -94,15 +94,15 @@ Create or update a database by publishing your module:
|
||||
spacetime publish <DATABASE_NAME>
|
||||
```
|
||||
|
||||
See [`spacetime publish`](/databases/building-publishing) for details on the publishing workflow.
|
||||
See [`spacetime publish`](./00100-databases/00300-spacetime-publish.md) for details on the publishing workflow.
|
||||
|
||||
When you republish to an existing database, SpacetimeDB attempts to automatically migrate the schema. For details on what changes are supported and migration strategies:
|
||||
|
||||
- [1.x to 2.0 Upgrade Notes](/upgrade) - Required reading before major-version upgrades.
|
||||
- [Automatic Migrations](/databases/automatic-migrations) - Learn which schema changes are safe, breaking, or forbidden.
|
||||
- [Incremental Migrations](/databases/incremental-migrations) - Advanced pattern for complex schema changes.
|
||||
- [1.x to 2.0 Upgrade Notes](./00100-databases/00500-migrations/00100-upgrade-notes-2-0.md) - Required reading before major-version upgrades.
|
||||
- [Automatic Migrations](./00100-databases/00500-migrations/00200-automatic-migrations.md) - Learn which schema changes are safe, breaking, or forbidden.
|
||||
- [Incremental Migrations](./00100-databases/00500-migrations/00300-incremental-migrations.md) - Advanced pattern for complex schema changes.
|
||||
|
||||
For all available publish options, see the [`spacetime publish` CLI reference](/cli-reference#spacetime-publish).
|
||||
For all available publish options, see the [`spacetime publish` CLI reference](../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-publish).
|
||||
|
||||
### Deleting a Database
|
||||
|
||||
@@ -118,7 +118,7 @@ You'll be prompted to confirm the deletion. Use `--yes` to skip the confirmation
|
||||
Deleting a database is permanent and cannot be undone. All data will be lost.
|
||||
:::
|
||||
|
||||
For more options, see the [`spacetime delete` CLI reference](/cli-reference#spacetime-delete).
|
||||
For more options, see the [`spacetime delete` CLI reference](../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-delete).
|
||||
|
||||
### Querying with SQL
|
||||
|
||||
@@ -140,7 +140,7 @@ spacetime sql --anonymous <DATABASE_NAME> "SELECT * FROM user"
|
||||
|
||||
This executes the query as an anonymous client, respecting table visibility rules.
|
||||
|
||||
For more SQL options, see the [`spacetime sql` CLI reference](/cli-reference#spacetime-sql).
|
||||
For more SQL options, see the [`spacetime sql` CLI reference](../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-sql).
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
@@ -166,7 +166,7 @@ To view only the last N lines:
|
||||
spacetime logs --num-lines 100 <DATABASE_NAME>
|
||||
```
|
||||
|
||||
For more logging options, see the [`spacetime logs` CLI reference](/cli-reference#spacetime-logs).
|
||||
For more logging options, see the [`spacetime logs` CLI reference](../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-logs).
|
||||
|
||||
### Listing Your Databases
|
||||
|
||||
@@ -206,41 +206,41 @@ SpacetimeDB supports organizing databases into projects and managing team access
|
||||
|
||||
If you're new to SpacetimeDB, follow this recommended learning path:
|
||||
|
||||
1. **[Create Your First Database Module](/databases/developing)** - Set up a new module project with `spacetime init` or `spacetime dev`
|
||||
2. **[Build and Publish](/databases/building-publishing)** - Learn how to compile and deploy your module
|
||||
3. **[Define Tables](/tables)** - Structure your data with tables, columns, and indexes
|
||||
4. **[Write Reducers](/functions/reducers)** - Create transactional functions that modify your database
|
||||
5. **[Connect a Client](/clients)** - Build a client application that connects to your database
|
||||
1. **[Create Your First Database Module](./00100-databases/00200-spacetime-dev.md)** - Set up a new module project with `spacetime init` or `spacetime dev`
|
||||
2. **[Build and Publish](./00100-databases/00300-spacetime-publish.md)** - Learn how to compile and deploy your module
|
||||
3. **[Define Tables](./00300-tables.md)** - Structure your data with tables, columns, and indexes
|
||||
4. **[Write Reducers](./00200-functions/00300-reducers/00300-reducers.md)** - Create transactional functions that modify your database
|
||||
5. **[Connect a Client](./00600-clients.md)** - Build a client application that connects to your database
|
||||
|
||||
### Core Concepts
|
||||
|
||||
Once you have the basics down, explore these essential topics:
|
||||
|
||||
- **[Error Handling](/functions/reducers/error-handling)** - Handle errors gracefully in reducers
|
||||
- **[Lifecycle Reducers](/functions/reducers/lifecycle)** - Respond to system events like initialization and client connections
|
||||
- **[Automatic Migrations](/databases/automatic-migrations)** - Understand how schema changes work
|
||||
- **[Logging](/how-to/logging)** - Debug and monitor your module with logging
|
||||
- **[Error Handling](./00200-functions/00300-reducers/00600-error-handling.md)** - Handle errors gracefully in reducers
|
||||
- **[Lifecycle Reducers](./00200-functions/00300-reducers/00500-lifecycle.md)** - Respond to system events like initialization and client connections
|
||||
- **[Automatic Migrations](./00100-databases/00500-migrations/00200-automatic-migrations.md)** - Understand how schema changes work
|
||||
- **[Logging](../00300-resources/00100-how-to/00300-logging.md)** - Debug and monitor your module with logging
|
||||
|
||||
### Advanced Features
|
||||
|
||||
Ready to level up? Dive into these advanced capabilities:
|
||||
|
||||
- **[Procedures](/functions/procedures)** - Make HTTP requests and interact with external services
|
||||
- **[Views](/functions/views)** - Create computed, subscribable queries
|
||||
- **[Schedule Tables](/tables/schedule-tables)** - Schedule reducers to run at specific times
|
||||
- **[Incremental Migrations](/databases/incremental-migrations)** - Handle complex schema changes
|
||||
- **[SQL Queries](/reference/sql)** - Query your database with SQL
|
||||
- **[Procedures](./00200-functions/00400-procedures.md)** - Make HTTP requests and interact with external services
|
||||
- **[Views](./00200-functions/00500-views.md)** - Create computed, subscribable queries
|
||||
- **[Schedule Tables](./00300-tables/00500-schedule-tables.md)** - Schedule reducers to run at specific times
|
||||
- **[Incremental Migrations](./00100-databases/00500-migrations/00300-incremental-migrations.md)** - Handle complex schema changes
|
||||
- **[SQL Queries](../00300-resources/00200-reference/00400-sql-reference.md)** - Query your database with SQL
|
||||
|
||||
### Deployment
|
||||
|
||||
When you're ready to go live:
|
||||
|
||||
- **[Deploy to MainCloud](/how-to/deploy/maincloud)** - Host your database on SpacetimeDB's managed service
|
||||
- **[Self-Hosting](/how-to/deploy/self-hosting)** - Run your own SpacetimeDB instance
|
||||
- **[Deploy to MainCloud](../00300-resources/00100-how-to/00100-deploy/00100-maincloud.md)** - Host your database on SpacetimeDB's managed service
|
||||
- **[Self-Hosting](../00300-resources/00100-how-to/00100-deploy/00200-self-hosting.md)** - Run your own SpacetimeDB instance
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Tables](/tables) to define your database schema
|
||||
- Create [Reducers](/functions/reducers) to modify database state
|
||||
- Understand [Subscriptions](/clients/subscriptions) for real-time data sync
|
||||
- Review the [CLI Reference](/cli-reference) for all available commands
|
||||
- Learn about [Tables](./00300-tables.md) to define your database schema
|
||||
- Create [Reducers](./00200-functions/00300-reducers/00300-reducers.md) to modify database state
|
||||
- Understand [Subscriptions](./00400-subscriptions.md) for real-time data sync
|
||||
- Review the [CLI Reference](../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md) for all available commands
|
||||
|
||||
@@ -8,7 +8,7 @@ import TabItem from '@theme/TabItem';
|
||||
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";
|
||||
|
||||
|
||||
SpacetimeDB provides strong transactional guarantees for all database operations. Every [reducer](/functions/reducers) runs inside a database transaction, ensuring your data remains consistent and reliable even under concurrent load.
|
||||
SpacetimeDB provides strong transactional guarantees for all database operations. Every [reducer](../00200-functions/00300-reducers/00300-reducers.md) runs inside a database transaction, ensuring your data remains consistent and reliable even under concurrent load.
|
||||
|
||||
## What is a Transaction?
|
||||
|
||||
@@ -27,7 +27,7 @@ SpacetimeDB transactions provide the following guarantees:
|
||||
- It's not possible to keep some changes and discard others from a single reducer execution
|
||||
|
||||
:::note
|
||||
This atomicity guarantee applies to **reducers**, which run in a single transaction. [Procedures](/functions/procedures) can manually open multiple separate transactions, where each transaction is individually atomic, but the procedure as a whole is not. See [Procedures: Manual Transactions](#procedures-manual-transactions) below.
|
||||
This atomicity guarantee applies to **reducers**, which run in a single transaction. [Procedures](../00200-functions/00400-procedures.md) can manually open multiple separate transactions, where each transaction is individually atomic, but the procedure as a whole is not. See [Procedures: Manual Transactions](#procedures-manual-transactions) below.
|
||||
:::
|
||||
|
||||
### Consistency
|
||||
@@ -195,25 +195,25 @@ SPACETIMEDB_REDUCER(child_reducer, ReducerContext ctx, bool some_condition) {
|
||||
</Tabs>
|
||||
|
||||
:::important
|
||||
SpacetimeDB does **not** support nested transactions. Nested reducer calls execute in the same transaction as their parent. If you need separate transactions, use [scheduled reducers](/tables/schedule-tables) instead.
|
||||
SpacetimeDB does **not** support nested transactions. Nested reducer calls execute in the same transaction as their parent. If you need separate transactions, use [scheduled reducers](../00300-tables/00500-schedule-tables.md) instead.
|
||||
:::
|
||||
|
||||
### Procedures: Manual Transactions
|
||||
|
||||
Unlike reducers, [procedures](/functions/procedures) don't automatically run in transactions. Procedures can run transactions, but must manually open them using `with_tx` (Rust) or `withTx` (TypeScript). This gives procedures more flexibility:
|
||||
Unlike reducers, [procedures](../00200-functions/00400-procedures.md) don't automatically run in transactions. Procedures can run transactions, but must manually open them using `with_tx` (Rust) or `withTx` (TypeScript). This gives procedures more flexibility:
|
||||
|
||||
- Procedures can perform operations **outside** transactions (like HTTP requests)
|
||||
- Procedures can open **multiple separate transactions** if needed
|
||||
- Each `with_tx`/`withTx` call creates a new transaction that commits independently
|
||||
|
||||
See [Procedures](/functions/procedures) for more details on manual transaction management.
|
||||
See [Procedures](../00200-functions/00400-procedures.md) for more details on manual transaction management.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Keep Transactions Short
|
||||
|
||||
- Perform only necessary database operations within reducers
|
||||
- Move external I/O (HTTP requests, etc.) to [procedures](/functions/procedures)
|
||||
- Move external I/O (HTTP requests, etc.) to [procedures](../00200-functions/00400-procedures.md)
|
||||
- Shorter transactions reduce contention and improve throughput
|
||||
|
||||
### Handle Errors Gracefully
|
||||
@@ -226,18 +226,18 @@ See [Procedures](/functions/procedures) for more details on manual transaction m
|
||||
|
||||
### No Nested Transactions
|
||||
|
||||
SpacetimeDB does not support nested transactions. When one reducer calls another, they share the same transaction. If you need separate transactions, use [scheduled reducers](/tables/schedule-tables) to trigger the second reducer asynchronously.
|
||||
SpacetimeDB does not support nested transactions. When one reducer calls another, they share the same transaction. If you need separate transactions, use [scheduled reducers](../00300-tables/00500-schedule-tables.md) to trigger the second reducer asynchronously.
|
||||
|
||||
### Auto-Increment is Not Transactional
|
||||
|
||||
The `#[auto_inc]` sequence generator is not transactional:
|
||||
- Sequence numbers are allocated even if a transaction rolls back
|
||||
- This can create gaps in your sequence
|
||||
- See [Auto-Increment](/tables/auto-increment#crash-recovery) for details
|
||||
- See [Auto-Increment](../00300-tables/00230-auto-increment.md#crash-recovery) for details
|
||||
|
||||
## Related Topics
|
||||
|
||||
- **[Reducers](/functions/reducers)** - Functions that modify database state transactionally
|
||||
- **[Procedures](/functions/procedures)** - Functions with manual transaction control
|
||||
- **[Schedule Tables](/tables/schedule-tables)** - Schedule reducers for separate transactions
|
||||
- **[Subscriptions](/clients/subscriptions)** - How clients receive transactional updates
|
||||
- **[Reducers](../00200-functions/00300-reducers/00300-reducers.md)** - Functions that modify database state transactionally
|
||||
- **[Procedures](../00200-functions/00400-procedures.md)** - Functions with manual transaction control
|
||||
- **[Schedule Tables](../00300-tables/00500-schedule-tables.md)** - Schedule reducers for separate transactions
|
||||
- **[Subscriptions](../00400-subscriptions.md)** - How clients receive transactional updates
|
||||
|
||||
@@ -260,5 +260,5 @@ This creates a new C++ project with:
|
||||
|
||||
After creating your database module:
|
||||
|
||||
- Learn about [Tables](/tables), [Reducers](/functions/reducers), and [Procedures](/functions/procedures)
|
||||
- [Build and publish your module](/databases/building-publishing)
|
||||
- Learn about [Tables](../00300-tables.md), [Reducers](../00200-functions/00300-reducers/00300-reducers.md), and [Procedures](../00200-functions/00400-procedures.md)
|
||||
- [Build and publish your module](./00300-spacetime-publish.md)
|
||||
|
||||
@@ -29,7 +29,7 @@ This compiles your module and validates its structure.
|
||||
If you're publishing your module, you don't need to run `spacetime build` separately - `spacetime publish` will automatically build your module if needed.
|
||||
:::
|
||||
|
||||
For all build options, see the [`spacetime build` CLI reference](/cli-reference#spacetime-build).
|
||||
For all build options, see the [`spacetime build` CLI reference](../../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-build).
|
||||
|
||||
## Publishing Modules
|
||||
|
||||
@@ -88,7 +88,7 @@ spacetime publish --break-clients <DATABASE_NAME>
|
||||
|
||||
⚠️ **Warning:** This will break existing clients that haven't been updated to match your new schema.
|
||||
|
||||
If this publish is a major upgrade from 1.x to 2.0, read [1.x to 2.0 Upgrade Notes](/upgrade) first.
|
||||
If this publish is a major upgrade from 1.x to 2.0, read [1.x to 2.0 Upgrade Notes](./00500-migrations/00100-upgrade-notes-2-0.md) first.
|
||||
|
||||
#### Clearing Data
|
||||
|
||||
@@ -102,11 +102,11 @@ spacetime publish --delete-data <DATABASE_NAME>
|
||||
|
||||
### Publishing Options
|
||||
|
||||
For all available publishing options and flags, see the [`spacetime publish` CLI reference](/cli-reference#spacetime-publish).
|
||||
For all available publishing options and flags, see the [`spacetime publish` CLI reference](../../00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-publish).
|
||||
|
||||
## Next Steps
|
||||
|
||||
After publishing:
|
||||
|
||||
- Learn about [connecting a client](/clients) to your database
|
||||
- Learn about [Tables](/tables), [Reducers](/functions/reducers), and [Procedures](/functions/procedures)
|
||||
- Learn about [connecting a client](../00600-clients.md) to your database
|
||||
- Learn about [Tables](../00300-tables.md), [Reducers](../00200-functions/00300-reducers/00300-reducers.md), and [Procedures](../00200-functions/00400-procedures.md)
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ The "schema" refers to the collection of tables, reducers, procedures, views, an
|
||||
:::
|
||||
|
||||
:::warning
|
||||
If you are upgrading an existing 1.x database to 2.0, review [1.x to 2.0 Upgrade Notes](/upgrade) before publishing.
|
||||
If you are upgrading an existing 1.x database to 2.0, review [1.x to 2.0 Upgrade Notes](./00100-upgrade-notes-2-0.md) before publishing.
|
||||
:::
|
||||
|
||||
## ✅ Safe Changes (Always Allowed)
|
||||
@@ -57,7 +57,7 @@ The following changes cannot be performed with automatic migration and will caus
|
||||
|
||||
## Working with Forbidden Changes
|
||||
|
||||
If you need to make changes that aren't supported by automatic migration, see [Incremental Migrations](/databases/incremental-migrations) for a production-ready pattern that allows complex schema changes without downtime or data loss.
|
||||
If you need to make changes that aren't supported by automatic migration, see [Incremental Migrations](./00300-incremental-migrations.md) for a production-ready pattern that allows complex schema changes without downtime or data loss.
|
||||
|
||||
For development and testing, you can use `spacetime publish --delete-data` to completely reset your database, but this should **not** be used in production as it permanently deletes all data.
|
||||
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ $ spacetime sql incr-migration-demo 'SELECT * FROM character'
|
||||
<snip> | "Gefjon" | 2 | (Fighter = ())
|
||||
```
|
||||
|
||||
See [the SATS JSON reference](/sats-json) for more on the encoding of arguments to `spacetime call`.
|
||||
See [the SATS JSON reference](../../../00300-resources/00200-reference/00300-internals/00200-sats-json.md) for more on the encoding of arguments to `spacetime call`.
|
||||
|
||||
Now we want to add a new feature: each player should be able to align themselves with the forces of good or evil, so we can get some healthy competition going between our players. We'll start each character off with `Alliance::Neutral`, and then offer them a reducer `choose_alliance` to set it to either `Alliance::Good` or `Alliance::Evil`. Our first attempt will be to add a new column to the type `Character`:
|
||||
|
||||
|
||||
@@ -20,19 +20,19 @@ SpacetimeDB modules can export three types of functions that clients can interac
|
||||
|
||||
## Reducers
|
||||
|
||||
**[Reducers](/functions/reducers)** are functions that modify database state in response to client requests or system events. They are the primary way to mutate tables in SpacetimeDB. Reducers run inside database transactions, providing isolation, atomicity, and consistency guarantees. If a reducer fails, all changes are automatically rolled back.
|
||||
**[Reducers](./00200-functions/00300-reducers/00300-reducers.md)** are functions that modify database state in response to client requests or system events. They are the primary way to mutate tables in SpacetimeDB. Reducers run inside database transactions, providing isolation, atomicity, and consistency guarantees. If a reducer fails, all changes are automatically rolled back.
|
||||
|
||||
Reducers are isolated and cannot interact with the outside world - they can only perform database operations. Use reducers for all state-changing operations in your module.
|
||||
|
||||
## Procedures
|
||||
|
||||
**[Procedures](/functions/procedures)** are functions similar to reducers, but with the ability to perform operations beyond the database. Unlike reducers, procedures can make HTTP requests to external services. However, procedures don't automatically run in database transactions - they must manually open and commit transactions to read from or modify database state.
|
||||
**[Procedures](./00200-functions/00400-procedures.md)** are functions similar to reducers, but with the ability to perform operations beyond the database. Unlike reducers, procedures can make HTTP requests to external services. However, procedures don't automatically run in database transactions - they must manually open and commit transactions to read from or modify database state.
|
||||
|
||||
Procedures are currently in beta and should only be used when you need their special capabilities, such as making HTTP requests. For standard database operations, prefer using reducers.
|
||||
|
||||
## Views
|
||||
|
||||
**[Views](/functions/views)** are read-only functions that compute and return results from your tables. Unlike reducers and procedures, views do not modify database state - they only query and return data. Views are useful for computing derived data, aggregations, or joining multiple tables server-side before sending results to clients.
|
||||
**[Views](./00200-functions/00500-views.md)** are read-only functions that compute and return results from your tables. Unlike reducers and procedures, views do not modify database state - they only query and return data. Views are useful for computing derived data, aggregations, or joining multiple tables server-side before sending results to clients.
|
||||
|
||||
Views run within a transaction to ensure isolation: the database state remains consistent for the entire duration of the view's execution. This means a view will never see partial updates from concurrent reducers.
|
||||
|
||||
|
||||
@@ -493,7 +493,7 @@ LOG_INFO("Total users: " + std::to_string(total));
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For more details on querying with indexes, including range queries and multi-column indexes, see [Indexes](/tables/indexes).
|
||||
For more details on querying with indexes, including range queries and multi-column indexes, see [Indexes](../../00300-tables/00300-indexes.md).
|
||||
|
||||
## Reducer Isolation
|
||||
|
||||
@@ -504,7 +504,7 @@ Reducers run in an isolated environment and **cannot** interact with the outside
|
||||
- ❌ No system calls
|
||||
- ✅ Only database operations
|
||||
|
||||
If you need to interact with external systems, use [Procedures](/functions/procedures) instead. Procedures can make network calls and perform other side effects, but they have different execution semantics and limitations.
|
||||
If you need to interact with external systems, use [Procedures](../00400-procedures.md) instead. Procedures can make network calls and perform other side effects, but they have different execution semantics and limitations.
|
||||
|
||||
:::warning Global and Static Variables Are Undefined Behavior
|
||||
Relying on global variables, static variables, or module-level state to persist across reducer calls is **undefined behavior**. SpacetimeDB does not guarantee that values stored in these locations will be available in subsequent reducer invocations.
|
||||
@@ -536,7 +536,7 @@ pub struct Counter {
|
||||
|
||||
## Scheduling Procedures
|
||||
|
||||
Reducers cannot call procedures directly (procedures may have side effects incompatible with transactional execution). Instead, schedule a procedure to run by inserting into a [schedule table](/tables/schedule-tables):
|
||||
Reducers cannot call procedures directly (procedures may have side effects incompatible with transactional execution). Instead, schedule a procedure to run by inserting into a [schedule table](../../00300-tables/00500-schedule-tables.md):
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
@@ -701,10 +701,10 @@ SPACETIMEDB_REDUCER(queue_fetch, ReducerContext ctx, std::string url) {
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See [Schedule Tables](/tables/schedule-tables) for more scheduling options.
|
||||
See [Schedule Tables](../../00300-tables/00500-schedule-tables.md) for more scheduling options.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Tables](/tables) to understand data storage
|
||||
- Explore [Procedures](/functions/procedures) for side effects beyond the database
|
||||
- Review [Subscriptions](/clients/subscriptions) for real-time client updates
|
||||
- Learn about [Tables](../../00300-tables.md) to understand data storage
|
||||
- Explore [Procedures](../00400-procedures.md) for side effects beyond the database
|
||||
- Review [Subscriptions](../../00400-subscriptions.md) for real-time client updates
|
||||
|
||||
+1
-1
@@ -266,7 +266,7 @@ Never use external random number generators (like `Math.random()` in TypeScript
|
||||
|
||||
The context provides access to the module's own identity, which is useful for distinguishing between user-initiated and system-initiated reducer calls.
|
||||
|
||||
This is particularly important for [scheduled reducers](/functions/reducers) that should only be invoked by the system, not by external clients.
|
||||
This is particularly important for [scheduled reducers](./00300-reducers.md) that should only be invoked by the system, not by external clients.
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
@@ -316,7 +316,7 @@ The `client_disconnected` reducer:
|
||||
|
||||
## Scheduled Reducers
|
||||
|
||||
Reducers can be triggered at specific times using schedule tables. See [Schedule Tables](/tables/schedule-tables) for details on:
|
||||
Reducers can be triggered at specific times using schedule tables. See [Schedule Tables](../../00300-tables/00500-schedule-tables.md) for details on:
|
||||
|
||||
- Defining schedule tables
|
||||
- Triggering reducers at specific timestamps
|
||||
|
||||
@@ -8,8 +8,8 @@ import TabItem from '@theme/TabItem';
|
||||
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";
|
||||
|
||||
|
||||
A **procedure** is a function exported by a [database](/databases), similar to a [reducer](/functions/reducers).
|
||||
Connected [clients](/clients) can call procedures.
|
||||
A **procedure** is a function exported by a [database](../00100-databases.md), similar to a [reducer](./00300-reducers/00300-reducers.md).
|
||||
Connected [clients](../00600-clients.md) can call procedures.
|
||||
Procedures can perform additional operations not possible in reducers, including making HTTP requests to external services.
|
||||
However, procedures don't automatically run in database transactions,
|
||||
and must manually open and commit a transaction in order to read from or modify the database state.
|
||||
@@ -153,7 +153,7 @@ export const insert_a_value = spacetimedb.procedure({ a: t.u32(), b: t.u32() },
|
||||
|
||||
`ProcedureCtx.withTx` takes a function of `(ctx: TransactionCtx) => T`.
|
||||
Within that function, the `TransactionCtx` can be used to access the database
|
||||
[in all the same ways as a `ReducerCtx`](/functions/reducers/reducer-context)
|
||||
[in all the same ways as a `ReducerCtx`](./00300-reducers/00400-reducer-context.md)
|
||||
When the function returns, the transaction will be committed,
|
||||
and its changes to the database state will become permanent and be broadcast to clients.
|
||||
If the function throws an error, the transaction will be rolled back, and its changes will be discarded.
|
||||
@@ -205,7 +205,7 @@ public static partial class Module
|
||||
|
||||
`ProcedureContext.WithTx` takes a function of type `Func<ProcedureTxContext, T>`.
|
||||
Within that function, the `TransactionContext` can be used to access the database
|
||||
[in all the same ways as a `ReducerContext`](/functions/reducers/reducer-context).
|
||||
[in all the same ways as a `ReducerContext`](./00300-reducers/00400-reducer-context.md).
|
||||
When the function returns, the transaction will be committed,
|
||||
and its changes to the database state will become permanent and be broadcast to clients.
|
||||
If the function throws an exception, the transaction will be rolled back, and its changes will be discarded.
|
||||
@@ -296,7 +296,7 @@ SPACETIMEDB_PROCEDURE(Unit, insert_a_value, ProcedureContext ctx, uint32_t a, st
|
||||
|
||||
`ctx.with_tx` takes a lambda function with signature `[](TxContext& tx) -> T`.
|
||||
Within that function, the `TxContext` can be used to access the database
|
||||
[in all the same ways as a `ReducerContext`](/functions/reducers/reducer-context).
|
||||
[in all the same ways as a `ReducerContext`](./00300-reducers/00400-reducer-context.md).
|
||||
When the function returns, the transaction will be committed,
|
||||
and its changes to the database state will become permanent and be broadcast to clients.
|
||||
If the function throws an exception, the transaction will be rolled back, and its changes will be discarded.
|
||||
|
||||
@@ -8,7 +8,7 @@ import TabItem from '@theme/TabItem';
|
||||
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";
|
||||
|
||||
|
||||
Views are read-only functions that compute and return results from your tables. Unlike [reducers](/functions/reducers), views do not modify database state - they only query and return data. Views are useful for computing derived data, aggregations, or joining multiple tables before sending results to clients.
|
||||
Views are read-only functions that compute and return results from your tables. Unlike [reducers](./00300-reducers/00300-reducers.md), views do not modify database state - they only query and return data. Views are useful for computing derived data, aggregations, or joining multiple tables before sending results to clients.
|
||||
|
||||
## Why Use Views?
|
||||
|
||||
@@ -1228,4 +1228,4 @@ fn levels_for_high_scorers(ctx: &AnonymousViewContext) -> impl Query<PlayerLevel
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Review [Subscriptions](/clients/subscriptions) for real-time client data access
|
||||
- Review [Subscriptions](../00400-subscriptions.md) for real-time client data access
|
||||
|
||||
@@ -36,7 +36,7 @@ The central principle of data-oriented design holds that **the purpose of any pr
|
||||
- **Flexible queries** through relational operations
|
||||
- **Real-time synchronization** through subscriptions
|
||||
|
||||
For further discussion of this philosophy, see [The Zen of SpacetimeDB](/intro/zen).
|
||||
For further discussion of this philosophy, see [The Zen of SpacetimeDB](../00100-intro/00100-getting-started/00250-zen-of-spacetimedb.md).
|
||||
|
||||
### Physical and Logical Independence
|
||||
|
||||
@@ -303,8 +303,8 @@ These conventions align with each language's standard style guides and make your
|
||||
|
||||
Tables can be **private** (default) or **public**:
|
||||
|
||||
- **Private tables**: Visible only to [reducers](/functions/reducers) and the database owner. Clients cannot access them.
|
||||
- **Public tables**: Exposed for client read access through [subscriptions](/clients/subscriptions). Writes still occur only through reducers.
|
||||
- **Private tables**: Visible only to [reducers](./00200-functions/00300-reducers/00300-reducers.md) and the database owner. Clients cannot access them.
|
||||
- **Public tables**: Exposed for client read access through [subscriptions](./00400-subscriptions.md). Writes still occur only through reducers.
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
@@ -358,9 +358,9 @@ FIELD_PrimaryKeyAutoInc(secret, id)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For more fine-grained access control, you can use [view functions](/functions/views) to expose computed subsets of your data to clients. Views allow you to filter rows, select specific columns, or join data from multiple tables before exposing it.
|
||||
For more fine-grained access control, you can use [view functions](./00200-functions/00500-views.md) to expose computed subsets of your data to clients. Views allow you to filter rows, select specific columns, or join data from multiple tables before exposing it.
|
||||
|
||||
See [Access Permissions](/tables/access-permissions) for complete details on table visibility and access patterns.
|
||||
See [Access Permissions](./00300-tables/00400-access-permissions.md) for complete details on table visibility and access patterns.
|
||||
|
||||
## Multiple Tables for the Same Type
|
||||
|
||||
@@ -499,27 +499,27 @@ Tables support several constraints to enforce data integrity:
|
||||
- **Primary keys** uniquely identify each row and define how updates and deletes work
|
||||
- **Unique constraints** ensure no two rows share the same value for a column
|
||||
|
||||
See [Constraints](/tables/constraints) for details.
|
||||
See [Constraints](./00300-tables/00240-constraints.md) for details.
|
||||
|
||||
## Auto-Increment
|
||||
|
||||
Auto-increment columns automatically generate unique integer values for new rows. SpacetimeDB implements auto-increment using sequences, which provide crash-safe value generation with configurable parameters.
|
||||
|
||||
See [Auto-Increment](/tables/auto-increment) for details.
|
||||
See [Auto-Increment](./00300-tables/00230-auto-increment.md) for details.
|
||||
|
||||
## Schedule Tables
|
||||
|
||||
Tables can trigger reducers at specific times by including a scheduling column. This allows you to schedule future actions like sending reminders, expiring content, or running periodic maintenance.
|
||||
|
||||
See [Schedule Tables](/tables/schedule-tables) for details.
|
||||
See [Schedule Tables](./00300-tables/00500-schedule-tables.md) for details.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Column Types](/tables/column-types) - Supported column types and performance considerations
|
||||
- [Constraints](/tables/constraints) - Primary keys and unique constraints
|
||||
- [Auto-Increment](/tables/auto-increment) - Automatic ID generation with sequences
|
||||
- [Default Values](/tables/default-values) - Schema evolution with column defaults
|
||||
- [Indexes](/tables/indexes) - Speed up queries with single and multi-column indexes
|
||||
- [Access Permissions](/tables/access-permissions) - Public vs private tables
|
||||
- [Schedule Tables](/tables/schedule-tables) - Time-based reducer execution
|
||||
- [Performance](/tables/performance) - Best practices for table design
|
||||
- [Column Types](./00300-tables/00200-column-types.md) - Supported column types and performance considerations
|
||||
- [Constraints](./00300-tables/00240-constraints.md) - Primary keys and unique constraints
|
||||
- [Auto-Increment](./00300-tables/00230-auto-increment.md) - Automatic ID generation with sequences
|
||||
- [Default Values](./00300-tables/00250-default-values.md) - Schema evolution with column defaults
|
||||
- [Indexes](./00300-tables/00300-indexes.md) - Speed up queries with single and multi-column indexes
|
||||
- [Access Permissions](./00300-tables/00400-access-permissions.md) - Public vs private tables
|
||||
- [Schedule Tables](./00300-tables/00500-schedule-tables.md) - Time-based reducer execution
|
||||
- [Performance](./00300-tables/00600-performance.md) - Best practices for table design
|
||||
|
||||
@@ -371,7 +371,7 @@ This pattern keeps large files out of SpacetimeDB while maintaining metadata in
|
||||
|
||||
### Example: Uploading to S3 from a Procedure
|
||||
|
||||
[Procedures](/functions/procedures) can make HTTP requests, enabling direct uploads to external storage services like S3. This example shows uploading a file to S3 and storing the metadata in SpacetimeDB:
|
||||
[Procedures](../00200-functions/00400-procedures.md) can make HTTP requests, enabling direct uploads to external storage services like S3. This example shows uploading a file to S3 and storing the metadata in SpacetimeDB:
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
@@ -8,7 +8,7 @@ import TabItem from '@theme/TabItem';
|
||||
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";
|
||||
|
||||
|
||||
Default values allow you to add new columns to existing tables during [automatic migrations](/databases/automatic-migrations). When you republish a module with a new column that has a default value, existing rows are automatically populated with that default.
|
||||
Default values allow you to add new columns to existing tables during [automatic migrations](../00100-databases/00500-migrations/00200-automatic-migrations.md). When you republish a module with a new column that has a default value, existing rows are automatically populated with that default.
|
||||
|
||||
:::note
|
||||
New columns with default values must be added at the **end** of the table definition. Adding columns in the middle of a table is not supported.
|
||||
@@ -117,7 +117,7 @@ Use `FIELD_Default(table, field, value)` after table registration to specify def
|
||||
Default values **cannot** be combined with:
|
||||
- Primary keys
|
||||
- Unique constraints
|
||||
- [Auto-increment](/tables/auto-increment)
|
||||
- [Auto-increment](./00230-auto-increment.md)
|
||||
|
||||
This restriction exists because these attributes require the database to manage the column values, which conflicts with providing a static default.
|
||||
|
||||
|
||||
@@ -571,5 +571,5 @@ log::info!("Deleted {} minor(s)", deleted);
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Constraints](/tables/constraints) for primary keys and unique indexes
|
||||
- See [Access Permissions](/tables/access-permissions) for querying tables from reducers
|
||||
- Learn about [Constraints](./00240-constraints.md) for primary keys and unique indexes
|
||||
- See [Access Permissions](./00400-access-permissions.md) for querying tables from reducers
|
||||
|
||||
@@ -342,11 +342,11 @@ SPACETIMEDB_PROCEDURE(Unit, update_user_procedure, ProcedureContext ctx, uint64_
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See the [Procedures documentation](/functions/procedures) for more details on using procedures, including making HTTP requests to external services.
|
||||
See the [Procedures documentation](../00200-functions/00400-procedures.md) for more details on using procedures, including making HTTP requests to external services.
|
||||
|
||||
## Views - Read-Only Access
|
||||
|
||||
[Views](/functions/views) receive a `ViewContext` or `AnonymousViewContext` which provides read-only access to all tables (both public and private). They can query and iterate tables, but cannot insert, update, or delete rows.
|
||||
[Views](../00200-functions/00500-views.md) receive a `ViewContext` or `AnonymousViewContext` which provides read-only access to all tables (both public and private). They can query and iterate tables, but cannot insert, update, or delete rows.
|
||||
|
||||
<Tabs groupId="server-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
@@ -407,14 +407,14 @@ SPACETIMEDB_VIEW(std::vector<User>, find_users_by_name, Public, ViewContext ctx)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See the [Views documentation](/functions/views) for more details on defining and querying views.
|
||||
See the [Views documentation](../00200-functions/00500-views.md) for more details on defining and querying views.
|
||||
|
||||
## Using Views for Fine-Grained Access Control
|
||||
|
||||
While table visibility controls whether clients can access a table at all, views provide fine-grained control over which rows and columns clients can see. Views can read from private tables and expose only the data appropriate for each client.
|
||||
|
||||
:::note
|
||||
Views can only access table data through indexed lookups, not by scanning all rows. This restriction ensures views remain performant. See the [Views documentation](/functions/views) for details.
|
||||
Views can only access table data through indexed lookups, not by scanning all rows. This restriction ensures views remain performant. See the [Views documentation](../00200-functions/00500-views.md) for details.
|
||||
:::
|
||||
|
||||
### Filtering Rows by Caller
|
||||
@@ -957,4 +957,4 @@ SPACETIMEDB_VIEW(std::vector<Colleague>, my_colleagues, Public, ViewContext ctx)
|
||||
|
||||
## Client Access - Read-Only Access
|
||||
|
||||
Clients connect to databases and can access public tables and views through subscriptions and queries. They cannot access private tables directly. See the [Subscriptions documentation](/clients/subscriptions) for details on client-side table access.
|
||||
Clients connect to databases and can access public tables and views through subscriptions and queries. They cannot access private tables directly. See the [Subscriptions documentation](../00400-subscriptions.md) for details on client-side table access.
|
||||
|
||||
@@ -8,10 +8,10 @@ import TabItem from '@theme/TabItem';
|
||||
import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNotice";
|
||||
|
||||
|
||||
Tables can trigger [reducers](/functions/reducers) or [procedures](/functions/procedures) at specific times by including a special scheduling column. This allows you to schedule future actions like sending reminders, expiring items, or running periodic maintenance tasks.
|
||||
Tables can trigger [reducers](../00200-functions/00300-reducers/00300-reducers.md) or [procedures](../00200-functions/00400-procedures.md) at specific times by including a special scheduling column. This allows you to schedule future actions like sending reminders, expiring items, or running periodic maintenance tasks.
|
||||
|
||||
:::tip Scheduling Procedures
|
||||
Procedures use the same scheduling pattern as reducers. Simply reference the procedure name in the `scheduled` attribute. This is particularly useful when you need scheduled tasks that make HTTP requests or perform other side effects. See [Scheduling Procedures](/functions/reducers#scheduling-procedures) for an example.
|
||||
Procedures use the same scheduling pattern as reducers. Simply reference the procedure name in the `scheduled` attribute. This is particularly useful when you need scheduled tasks that make HTTP requests or perform other side effects. See [Scheduling Procedures](../00200-functions/00300-reducers/00300-reducers.md#scheduling-procedures) for an example.
|
||||
:::
|
||||
|
||||
## Defining a Schedule Table
|
||||
@@ -424,5 +424,5 @@ SPACETIMEDB_REDUCER(send_reminder, ReducerContext ctx, Reminder arg)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Reducers](/functions/reducers) to handle scheduled actions
|
||||
- Explore [Procedures](/functions/procedures) for scheduled execution patterns
|
||||
- Learn about [Reducers](../00200-functions/00300-reducers/00300-reducers.md) to handle scheduled actions
|
||||
- Explore [Procedures](../00200-functions/00400-procedures.md) for scheduled execution patterns
|
||||
|
||||
@@ -183,7 +183,7 @@ Conceptually, every insert into an event table is a **noop**: an insert paired w
|
||||
**Wire format.** Event tables require the v2 WebSocket protocol. Clients connected via the v1 protocol that attempt to subscribe to an event table will receive an error message directing them to upgrade.
|
||||
|
||||
:::tip Migrating from reducer callbacks
|
||||
If you previously used `ctx.reducers.on_<reducer_name>()` callbacks to receive transient data, event tables are the recommended replacement. Define an event table with the fields you want to publish, insert a row in your reducer, and register an `on_insert` callback on the client via `ctx.db.<event_table>().on_insert(...)`. See the [migration guide](/how-to/migrating-to-2-0) for details.
|
||||
If you previously used `ctx.reducers.on_<reducer_name>()` callbacks to receive transient data, event tables are the recommended replacement. Define an event table with the fields you want to publish, insert a row in your reducer, and register an `on_insert` callback on the client via `ctx.db.<event_table>().on_insert(...)`. See the [migration guide](../../00300-resources/00100-how-to/00600-migrating-to-2.0.md) for details.
|
||||
:::
|
||||
|
||||
## Row-Level Security
|
||||
@@ -209,6 +209,6 @@ Event tables are well-suited to any situation where the module needs to notify c
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Tables](/tables) for persistent data storage
|
||||
- Explore [Schedule Tables](/tables/schedule-tables) for time-triggered actions
|
||||
- See [Row-Level Security](/tables/access-permissions) for controlling data visibility
|
||||
- Learn about [Tables](../00300-tables.md) for persistent data storage
|
||||
- Explore [Schedule Tables](./00500-schedule-tables.md) for time-triggered actions
|
||||
- See [Row-Level Security](./00400-access-permissions.md) for controlling data visibility
|
||||
|
||||
@@ -99,7 +99,7 @@ for (const auto& p : ctx.db[player]) {
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add indexes to columns you frequently filter or join on. See [Indexes](/tables/indexes) for details.
|
||||
Add indexes to columns you frequently filter or join on. See [Indexes](./00300-indexes.md) for details.
|
||||
|
||||
## Keep Tables Focused
|
||||
|
||||
@@ -637,6 +637,6 @@ Be mindful of unbounded table growth:
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Indexes](/tables/indexes) to optimize queries
|
||||
- Explore [Subscriptions](/clients/subscriptions) for efficient client data sync
|
||||
- Review [Reducers](/functions/reducers) for efficient data modification patterns
|
||||
- Learn about [Indexes](./00300-indexes.md) to optimize queries
|
||||
- Explore [Subscriptions](../00400-subscriptions.md) for efficient client data sync
|
||||
- Review [Reducers](../00200-functions/00300-reducers/00300-reducers.md) for efficient data modification patterns
|
||||
|
||||
@@ -143,7 +143,7 @@ conn.db().user().on_update(|ctx, old_user, new_user| {
|
||||
</Tabs>
|
||||
|
||||
:::tip Typed Query Builders
|
||||
Type-safe query builders are available in TypeScript, C#, and Rust and are the recommended default. They provide auto-completion and compile-time type checking. For complete API details, see [TypeScript](/clients/typescript#query-builder-api), [C#](/clients/c-sharp#query-builder-api), and [Rust](/clients/rust#query-builder-api) references.
|
||||
Type-safe query builders are available in TypeScript, C#, and Rust and are the recommended default. They provide auto-completion and compile-time type checking. For complete API details, see [TypeScript](./00600-clients/00700-typescript-reference.md#query-builder-api), [C#](./00600-clients/00600-csharp-reference.md#query-builder-api), and [Rust](./00600-clients/00500-rust-reference.md#query-builder-api) references.
|
||||
:::
|
||||
|
||||
## How Subscriptions Work
|
||||
@@ -155,7 +155,7 @@ Type-safe query builders are available in TypeScript, C#, and Rust and are the r
|
||||
|
||||
The client maintains a local cache of subscribed data. Reading from the cache is instant since it's local memory.
|
||||
|
||||
For advanced raw SQL subscription syntax, see the [SQL docs](/reference/sql#subscriptions).
|
||||
For advanced raw SQL subscription syntax, see the [SQL docs](../00300-resources/00200-reference/00400-sql-reference.md#subscriptions).
|
||||
|
||||
## Common API Concepts
|
||||
|
||||
@@ -191,19 +191,19 @@ Subscribing returns a handle that manages an individual subscription lifecycle.
|
||||
|
||||
### API References
|
||||
|
||||
- [TypeScript subscription API](/clients/typescript#subscribe-to-queries)
|
||||
- [TypeScript query builder API](/clients/typescript#query-builder-api)
|
||||
- [C# subscription API](/clients/c-sharp#subscribe-to-queries)
|
||||
- [C# query builder API](/clients/c-sharp#query-builder-api)
|
||||
- [Rust subscription API](/clients/rust#subscribe-to-queries)
|
||||
- [Rust query builder API](/clients/rust#query-builder-api)
|
||||
- [Unreal subscription API](/clients/unreal#subscriptions)
|
||||
- [TypeScript subscription API](./00600-clients/00700-typescript-reference.md#subscribe-to-queries)
|
||||
- [TypeScript query builder API](./00600-clients/00700-typescript-reference.md#query-builder-api)
|
||||
- [C# subscription API](./00600-clients/00600-csharp-reference.md#subscribe-to-queries)
|
||||
- [C# query builder API](./00600-clients/00600-csharp-reference.md#query-builder-api)
|
||||
- [Rust subscription API](./00600-clients/00500-rust-reference.md#subscribe-to-queries)
|
||||
- [Rust query builder API](./00600-clients/00500-rust-reference.md#query-builder-api)
|
||||
- [Unreal subscription API](./00600-clients/00800-unreal-reference.md#subscriptions)
|
||||
|
||||
## Best Practices for Optimizing Server Compute and Reducing Serialization Overhead
|
||||
|
||||
### 1. Writing Efficient Subscription Queries
|
||||
|
||||
Use the typed query builder to express precise filters and keep subscriptions small. If you use raw SQL subscriptions, see [SQL Best Practices](/reference/sql#best-practices-for-performance-and-scalability).
|
||||
Use the typed query builder to express precise filters and keep subscriptions small. If you use raw SQL subscriptions, see [SQL Best Practices](../00300-resources/00200-reference/00400-sql-reference.md#best-practices-for-performance-and-scalability).
|
||||
|
||||
### 2. Group Subscriptions with the Same Lifetime Together
|
||||
|
||||
|
||||
@@ -3,24 +3,24 @@ title: Clients
|
||||
slug: /clients
|
||||
---
|
||||
|
||||
The SpacetimeDB Client SDKs provide a comprehensive interface for building applications that connect to SpacetimeDB [databases](/databases). Client applications can query data, invoke server-side functions, and receive real-time updates as the database state changes.
|
||||
The SpacetimeDB Client SDKs provide a comprehensive interface for building applications that connect to SpacetimeDB [databases](./00100-databases.md). Client applications can query data, invoke server-side functions, and receive real-time updates as the database state changes.
|
||||
|
||||
## Available SDKs
|
||||
|
||||
SpacetimeDB provides client SDKs for multiple languages:
|
||||
|
||||
- [Rust](/clients/rust) - [(Quickstart)](/docs/quickstarts/rust)
|
||||
- [C#](/clients/c-sharp) - [(Quickstart)](/docs/quickstarts/c-sharp)
|
||||
- [TypeScript](/clients/typescript) - [(Quickstart)](/docs/quickstarts/typescript)
|
||||
- [Unreal](/clients/unreal) - [(Tutorial)](/tutorials/unreal)
|
||||
- [Rust](./00600-clients/00500-rust-reference.md) - [(Quickstart)](../00100-intro/00200-quickstarts/00500-rust.md)
|
||||
- [C#](./00600-clients/00600-csharp-reference.md) - [(Quickstart)](../00100-intro/00200-quickstarts/00600-c-sharp.md)
|
||||
- [TypeScript](./00600-clients/00700-typescript-reference.md) - [(Quickstart)](../00100-intro/00200-quickstarts/00400-typescript.md)
|
||||
- [Unreal](./00600-clients/00800-unreal-reference.md) - [(Tutorial)](../00100-intro/00300-tutorials/00400-unreal-tutorial/index.md)
|
||||
|
||||
## Getting Started
|
||||
|
||||
To build a client application with SpacetimeDB:
|
||||
|
||||
1. **[Generate client bindings](/clients/codegen)** - Use `spacetime generate` to create type-safe bindings for your [database](/databases)
|
||||
2. **[Connect to your database](/clients/connection)** - Establish a WebSocket connection to SpacetimeDB
|
||||
3. **[Use the SDK API](/clients/api)** - Subscribe to data, invoke functions, and register callbacks
|
||||
1. **[Generate client bindings](./00600-clients/00200-codegen.md)** - Use `spacetime generate` to create type-safe bindings for your [database](./00100-databases.md)
|
||||
2. **[Connect to your database](./00600-clients/00300-connection.md)** - Establish a WebSocket connection to SpacetimeDB
|
||||
3. **[Use the SDK API](./00600-clients/00400-sdk-api.md)** - Subscribe to data, invoke functions, and register callbacks
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
@@ -28,11 +28,11 @@ To build a client application with SpacetimeDB:
|
||||
|
||||
The SDKs handle establishing and maintaining WebSocket connections to SpacetimeDB servers. Connections support authentication via tokens (for example, from [SpacetimeAuth](./00500-authentication/00100-spacetimeauth/index.md)) and provide lifecycle callbacks for connect, disconnect, and error events.
|
||||
|
||||
See [Connecting to SpacetimeDB](/clients/connection) for details.
|
||||
See [Connecting to SpacetimeDB](./00600-clients/00300-connection.md) for details.
|
||||
|
||||
### Client-Side Data Cache
|
||||
|
||||
Each client maintains a local cache of database rows through [subscriptions](/clients/subscriptions). Clients define which data they need using typed query builders (or raw SQL when needed), and SpacetimeDB automatically synchronizes changes to the subscribed data. The local cache can be queried without network round-trips, providing fast access to frequently-read data.
|
||||
Each client maintains a local cache of database rows through [subscriptions](./00400-subscriptions.md). Clients define which data they need using typed query builders (or raw SQL when needed), and SpacetimeDB automatically synchronizes changes to the subscribed data. The local cache can be queried without network round-trips, providing fast access to frequently-read data.
|
||||
|
||||
### Real-Time Updates
|
||||
|
||||
@@ -40,19 +40,19 @@ Clients receive automatic updates when subscribed data changes. The SDKs provide
|
||||
|
||||
- **Subscription updates** - When subscription queries are applied or fail
|
||||
- **Row changes** - When rows are inserted, updated, or deleted in the local cache
|
||||
- **Reducer invocations** - When [reducers](/functions/reducers) run on the server
|
||||
- **Procedure results** - When [procedures](/functions/procedures) are called the results are returned via a callback
|
||||
- **Reducer invocations** - When [reducers](./00200-functions/00300-reducers/00300-reducers.md) run on the server
|
||||
- **Procedure results** - When [procedures](./00200-functions/00400-procedures.md) are called the results are returned via a callback
|
||||
|
||||
### Invoking Server Functions
|
||||
|
||||
Clients can invoke server-side functions to modify data or perform operations:
|
||||
|
||||
- **[Reducers](/functions/reducers)** - Transactional functions that modify database state
|
||||
- **[Procedures](/functions/procedures)** - Functions that can perform external operations like HTTP requests (beta)
|
||||
- **[Reducers](./00200-functions/00300-reducers/00300-reducers.md)** - Transactional functions that modify database state
|
||||
- **[Procedures](./00200-functions/00400-procedures.md)** - Functions that can perform external operations like HTTP requests (beta)
|
||||
|
||||
### Type Safety
|
||||
|
||||
The [generated client bindings](/clients/codegen) provide compile-time type safety between your client and server code. Table schemas, function signatures, and return types are all reflected in the generated code, catching errors before runtime.
|
||||
The [generated client bindings](./00600-clients/00200-codegen.md) provide compile-time type safety between your client and server code. Table schemas, function signatures, and return types are all reflected in the generated code, catching errors before runtime.
|
||||
|
||||
## Choosing a Language
|
||||
|
||||
@@ -80,14 +80,14 @@ The functionality of the SDKs remains consistent across languages, so transition
|
||||
|
||||
New to SpacetimeDB client development? Follow this progression:
|
||||
|
||||
1. **[Generate Client Bindings](/clients/codegen)** - Create type-safe interfaces from your module
|
||||
2. **[Connect to SpacetimeDB](/clients/connection)** - Establish a connection and understand the lifecycle
|
||||
3. **[Use the SDK API](/clients/api)** - Learn about subscriptions, reducers, and callbacks
|
||||
4. **Language Reference** - Dive into language-specific details: [Rust](/clients/rust), [C#](/clients/c-sharp), [TypeScript](/clients/typescript)
|
||||
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)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Follow a **Quickstart guide** [Rust](/quickstarts/rust), [C#](/quickstarts/c-sharp), or [TypeScript](/quickstarts/typescript) to build your first client
|
||||
- Learn about [Databases](/databases) to understand what you're connecting to
|
||||
- Explore [Subscriptions](/clients/subscriptions) for efficient data synchronization
|
||||
- Review [Reducers](/functions/reducers) to understand server-side state changes
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -7,7 +7,7 @@ import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
Before you can interact with a SpacetimeDB [database](/databases) from a client application, you must generate client bindings for your **module**. These bindings create type-safe interfaces that allow your client to query [tables](/tables), invoke [reducers](/functions/reducers), call [procedures](/functions/procedures), and subscribe to [tables](/tables), and/or [views](/functions/views).
|
||||
Before you can interact with a SpacetimeDB [database](../00100-databases.md) from a client application, you must generate client bindings for your **module**. These bindings create type-safe interfaces that allow your client to query [tables](../00300-tables.md), invoke [reducers](../00200-functions/00300-reducers/00300-reducers.md), call [procedures](../00200-functions/00400-procedures.md), and subscribe to [tables](../00300-tables.md), and/or [views](../00200-functions/00500-views.md).
|
||||
|
||||
## What Are Module Bindings?
|
||||
|
||||
@@ -91,7 +91,7 @@ The `spacetime generate` command creates client-side representations of your mod
|
||||
|
||||
### Tables
|
||||
|
||||
For each [table](/tables) in your module, codegen generates:
|
||||
For each [table](../00300-tables.md) in your module, codegen generates:
|
||||
|
||||
- **Type/class definitions** with properties for each column
|
||||
- **Table accessor** on the `DbConnection` for querying the client cache
|
||||
@@ -172,11 +172,11 @@ Context.Db->User
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See the [Tables](/tables) documentation for details on defining tables in your module.
|
||||
See the [Tables](../00300-tables.md) documentation for details on defining tables in your module.
|
||||
|
||||
### Reducers
|
||||
|
||||
For each [reducer](/functions/reducers) in your module, codegen generates:
|
||||
For each [reducer](../00200-functions/00300-reducers/00300-reducers.md) in your module, codegen generates:
|
||||
|
||||
- **Client-callable function** that sends a reducer invocation request to the server
|
||||
- **Callback registration** method for observing when the reducer runs
|
||||
@@ -247,11 +247,11 @@ void OnCreateUser(const FReducerEventContext& Ctx, const FString& Name, const FS
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See the [Reducers](/functions/reducers) documentation for details on defining reducers in your module.
|
||||
See the [Reducers](../00200-functions/00300-reducers/00300-reducers.md) documentation for details on defining reducers in your module.
|
||||
|
||||
### Procedures
|
||||
|
||||
For each [procedure](/functions/procedures) in your module, codegen generates:
|
||||
For each [procedure](../00200-functions/00400-procedures.md) in your module, codegen generates:
|
||||
|
||||
- **Client-callable function** that invokes the procedure
|
||||
- **Return value handling** for procedures that return results
|
||||
@@ -337,11 +337,11 @@ void OnFetchComplete(const FProcedureEventContext& Ctx, const FString& Result, b
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See the [Procedures](/functions/procedures) documentation for details on defining procedures in your module.
|
||||
See the [Procedures](../00200-functions/00400-procedures.md) documentation for details on defining procedures in your module.
|
||||
|
||||
### Views
|
||||
|
||||
For each [view](/functions/views) in your module, codegen generates:
|
||||
For each [view](../00200-functions/00500-views.md) in your module, codegen generates:
|
||||
|
||||
- **Type definitions** for the view's return type
|
||||
- **Subscription interfaces** for subscribing to view results
|
||||
@@ -349,7 +349,7 @@ For each [view](/functions/views) in your module, codegen generates:
|
||||
|
||||
Views provide subscribable, computed queries over your data.
|
||||
|
||||
See the [Views](/functions/views) documentation for details on defining views in your module.
|
||||
See the [Views](../00200-functions/00500-views.md) documentation for details on defining views in your module.
|
||||
|
||||
## Regenerating Bindings
|
||||
|
||||
@@ -361,9 +361,9 @@ If you're actively developing and testing changes, consider adding `spacetime ge
|
||||
|
||||
Once you've generated the bindings, you're ready to connect to your database and start interacting with it. See:
|
||||
|
||||
- [Connecting to SpacetimeDB](/clients/connection) for establishing a connection
|
||||
- [SDK API Reference](/clients/api) for using the generated bindings
|
||||
- Language-specific references: [Rust](/clients/rust), [C#](/clients/c-sharp), [TypeScript](/clients/typescript), [Unreal](/clients/unreal)
|
||||
- [Connecting to SpacetimeDB](./00300-connection.md) for establishing a connection
|
||||
- [SDK API Reference](./00400-sdk-api.md) for using the generated bindings
|
||||
- Language-specific references: [Rust](./00500-rust-reference.md), [C#](./00600-csharp-reference.md), [TypeScript](./00700-typescript-reference.md), [Unreal](./00800-unreal-reference.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ slug: /clients/connection
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
After [generating client bindings](/clients/codegen) for your module, you can establish a connection to your SpacetimeDB [database](/databases) from your client application. The `DbConnection` type provides a persistent WebSocket connection that enables real-time communication with the server.
|
||||
After [generating client bindings](./00200-codegen.md) for your module, you can establish a connection to your SpacetimeDB [database](../00100-databases.md) from your client application. The `DbConnection` type provides a persistent WebSocket connection that enables real-time communication with the server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before connecting, ensure you have:
|
||||
|
||||
1. [Generated client bindings](/clients/codegen) for your module
|
||||
2. A published database running on SpacetimeDB (local or on [MainCloud](/how-to/deploy/maincloud))
|
||||
1. [Generated client bindings](./00200-codegen.md) for your module
|
||||
2. A published database running on SpacetimeDB (local or on [MainCloud](../../00300-resources/00100-how-to/00100-deploy/00100-maincloud.md))
|
||||
3. The database's URI and name or identity
|
||||
|
||||
## Basic Connection
|
||||
@@ -400,7 +400,7 @@ We recommend implementing reconnection logic in your application if reliable con
|
||||
|
||||
## Connection Identity
|
||||
|
||||
Every connection receives a unique [identity](/intro/key-architecture#identity) from the server. Access it through the `on_connect` callback:
|
||||
Every connection receives a unique [identity](../../00100-intro/00100-getting-started/00400-key-architecture.md#identity) from the server. Access it through the `on_connect` callback:
|
||||
|
||||
<Tabs groupId="client-language" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
@@ -448,19 +448,19 @@ void OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The [identity](/intro/key-architecture#identity) persists across connections and represents the user, while the [connection ID](/intro/key-architecture#connectionid) is unique to each connection session.
|
||||
The [identity](../../00100-intro/00100-getting-started/00400-key-architecture.md#identity) persists across connections and represents the user, while the [connection ID](../../00100-intro/00100-getting-started/00400-key-architecture.md#connectionid) is unique to each connection session.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a connection established, you can:
|
||||
|
||||
- [Use the SDK API](/clients/api) to interact with tables, invoke reducers, and subscribe to data
|
||||
- [Use the SDK API](./00400-sdk-api.md) to interact with tables, invoke reducers, and subscribe to data
|
||||
- Register callbacks for observing database changes
|
||||
- Call reducers and procedures on the server
|
||||
|
||||
For language-specific details, see:
|
||||
|
||||
- [Rust SDK Reference](/clients/rust)
|
||||
- [C# SDK Reference](/clients/c-sharp)
|
||||
- [TypeScript SDK Reference](/clients/typescript)
|
||||
- [Unreal SDK Reference](/clients/unreal)
|
||||
- [Rust SDK Reference](./00500-rust-reference.md)
|
||||
- [C# SDK Reference](./00600-csharp-reference.md)
|
||||
- [TypeScript SDK Reference](./00700-typescript-reference.md)
|
||||
- [Unreal SDK Reference](./00800-unreal-reference.md)
|
||||
|
||||
@@ -7,16 +7,16 @@ import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
The SpacetimeDB client SDKs provide a comprehensive API for interacting with your [database](/databases). After [generating client bindings](/clients/codegen) and [establishing a connection](/clients/connection), you can query data, invoke server functions, and observe real-time changes.
|
||||
The SpacetimeDB client SDKs provide a comprehensive API for interacting with your [database](../00100-databases.md). After [generating client bindings](./00200-codegen.md) and [establishing a connection](./00300-connection.md), you can query data, invoke server functions, and observe real-time changes.
|
||||
|
||||
This page describes the core concepts and patterns that apply across all client SDKs. For language-specific details and complete API documentation, see the reference pages for [Rust](/clients/rust), [C#](/clients/c-sharp), [TypeScript](/clients/typescript), or [Unreal Engine](/clients/unreal).
|
||||
This page describes the core concepts and patterns that apply across all client SDKs. For language-specific details and complete API documentation, see the reference pages for [Rust](./00500-rust-reference.md), [C#](./00600-csharp-reference.md), [TypeScript](./00700-typescript-reference.md), or [Unreal Engine](./00800-unreal-reference.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using the SDK API, you must:
|
||||
|
||||
1. [Generate client bindings](/clients/codegen) using `spacetime generate`
|
||||
2. [Create a connection](/clients/connection) to your database
|
||||
1. [Generate client bindings](./00200-codegen.md) using `spacetime generate`
|
||||
2. [Create a connection](./00300-connection.md) to your database
|
||||
|
||||
## Subscriptions
|
||||
|
||||
@@ -29,7 +29,7 @@ Typical flow:
|
||||
3. Read from the local cache and register callbacks
|
||||
4. Unsubscribe when the data is no longer needed
|
||||
|
||||
For lifecycle guarantees and semantics, see [Subscriptions](/clients/subscriptions) and [Subscription Semantics](/clients/subscriptions/semantics).
|
||||
For lifecycle guarantees and semantics, see [Subscriptions](../00400-subscriptions.md) and [Subscription Semantics](../00400-subscriptions/00200-subscription-semantics.md).
|
||||
|
||||
### Example
|
||||
|
||||
@@ -183,11 +183,11 @@ Conn->Db->User->OnDelete.AddDynamic(this, &AMyActor::OnUserDelete);
|
||||
|
||||
## Canonical API References
|
||||
|
||||
- [Subscriptions](/clients/subscriptions) - Lifecycle, usage patterns, and semantics
|
||||
- [Subscription Semantics](/clients/subscriptions/semantics) - Detailed consistency and ordering behavior
|
||||
- [TypeScript Reference](/clients/typescript#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`, query builder API
|
||||
- [C# Reference](/clients/c-sharp#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`
|
||||
- [C# Query Builder API](/clients/c-sharp#query-builder-api) - Typed subscription query builder
|
||||
- [Rust Reference](/clients/rust#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`
|
||||
- [Rust Query Builder API](/clients/rust#query-builder-api) - Typed subscription query builder
|
||||
- [Unreal Reference](/clients/unreal#subscriptions) - Unreal subscription APIs
|
||||
- [Subscriptions](../00400-subscriptions.md) - Lifecycle, usage patterns, and semantics
|
||||
- [Subscription Semantics](../00400-subscriptions/00200-subscription-semantics.md) - Detailed consistency and ordering behavior
|
||||
- [TypeScript Reference](./00700-typescript-reference.md#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`, query builder API
|
||||
- [C# Reference](./00600-csharp-reference.md#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`
|
||||
- [C# Query Builder API](./00600-csharp-reference.md#query-builder-api) - Typed subscription query builder
|
||||
- [Rust Reference](./00500-rust-reference.md#subscribe-to-queries) - `SubscriptionBuilder`, `SubscriptionHandle`
|
||||
- [Rust Query Builder API](./00500-rust-reference.md#query-builder-api) - Typed subscription query builder
|
||||
- [Unreal Reference](./00800-unreal-reference.md#subscriptions) - Unreal subscription APIs
|
||||
|
||||
@@ -9,9 +9,9 @@ The SpacetimeDB client SDK for Rust contains all the tools you need to build nat
|
||||
|
||||
Before diving into the reference, you may want to review:
|
||||
|
||||
- [Generating Client Bindings](/clients/codegen) - How to generate Rust bindings from your module
|
||||
- [Connecting to SpacetimeDB](/clients/connection) - Establishing and managing connections
|
||||
- [SDK API Reference](/clients/api) - Core concepts that apply across all SDKs
|
||||
- [Generating Client Bindings](./00200-codegen.md) - How to generate Rust bindings from your module
|
||||
- [Connecting to SpacetimeDB](./00300-connection.md) - Establishing and managing connections
|
||||
- [SDK API Reference](./00400-sdk-api.md) - Core concepts that apply across all SDKs
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -389,7 +389,7 @@ impl SubscriptionBuilder {
|
||||
|
||||
Subscribe to a set of queries. `queries` should be a string or an array, vec or slice of strings.
|
||||
|
||||
See [the SpacetimeDB SQL Reference](/reference/sql#subscriptions) for information on the queries SpacetimeDB supports as subscriptions.
|
||||
See [the SpacetimeDB SQL Reference](../../00300-resources/00200-reference/00400-sql-reference.md#subscriptions) for information on the queries SpacetimeDB supports as subscriptions.
|
||||
|
||||
For typed query subscriptions, use [`add_query`](#method-add_query).
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ The SpacetimeDB client for C# contains all the tools you need to build native cl
|
||||
|
||||
Before diving into the reference, you may want to review:
|
||||
|
||||
- [Generating Client Bindings](/clients/codegen) - How to generate C# bindings from your module
|
||||
- [Connecting to SpacetimeDB](/clients/connection) - Establishing and managing connections (important: C# requires manual connection advancement!)
|
||||
- [SDK API Reference](/clients/api) - Core concepts that apply across all SDKs
|
||||
- [Generating Client Bindings](./00200-codegen.md) - How to generate C# bindings from your module
|
||||
- [Connecting to SpacetimeDB](./00300-connection.md) - Establishing and managing connections (important: C# requires manual connection advancement!)
|
||||
- [SDK API Reference](./00400-sdk-api.md) - Core concepts that apply across all SDKs
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
@@ -38,7 +38,7 @@ If you would like to create a console application using .NET, you can create a n
|
||||
dotnet add package SpacetimeDB.ClientSDK
|
||||
```
|
||||
|
||||
(See also the [CSharp Quickstart](/docs/quickstarts/c-sharp) for an in-depth example of such a console application.)
|
||||
(See also the [CSharp Quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for an in-depth example of such a console application.)
|
||||
|
||||
### Using Unity
|
||||
|
||||
@@ -48,7 +48,7 @@ Add the SpacetimeDB Unity Package using the Package Manager. Open the Package Ma
|
||||
https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git
|
||||
```
|
||||
|
||||
(See also the [Unity Tutorial](/docs/tutorials/unity/part-1))
|
||||
(See also the [Unity Tutorial](../../00100-intro/00300-tutorials/00300-unity-tutorial/00200-part-1.md))
|
||||
|
||||
## Generate module bindings
|
||||
|
||||
@@ -376,7 +376,7 @@ class SubscriptionBuilder
|
||||
|
||||
Subscribe to a set of queries. `queries` should be an array of SQL query strings.
|
||||
|
||||
See [the SpacetimeDB SQL Reference](/reference/sql#subscriptions) for information on the queries SpacetimeDB supports as subscriptions.
|
||||
See [the SpacetimeDB SQL Reference](../../00300-resources/00200-reference/00400-sql-reference.md#subscriptions) for information on the queries SpacetimeDB supports as subscriptions.
|
||||
|
||||
For typed query subscriptions, use [`AddQuery`](#method-addquery).
|
||||
|
||||
@@ -986,7 +986,7 @@ class RemoteTableHandle
|
||||
|
||||
The `OnInsert` callback runs whenever a new row is inserted into the client cache, either when applying a subscription or being notified of a transaction. The passed [`EventContext`](#type-eventcontext) contains an [`Event`](#record-event) which can identify the change which caused the insertion, and also allows the callback to interact with the connection, inspect the client cache and invoke reducers. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
See [the quickstart](/quickstarts/c-sharp) for examples of registering and unregistering row callbacks.
|
||||
See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.
|
||||
|
||||
#### Callback `OnDelete`
|
||||
|
||||
@@ -1000,7 +1000,7 @@ class RemoteTableHandle
|
||||
|
||||
The `OnDelete` callback runs whenever a previously-resident row is deleted from the client cache. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
See [the quickstart](/quickstarts/c-sharp) for examples of registering and unregistering row callbacks.
|
||||
See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.
|
||||
|
||||
#### Callback `OnUpdate`
|
||||
|
||||
@@ -1014,7 +1014,7 @@ class RemoteTableHandle
|
||||
|
||||
The `OnUpdate` callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key. The table must have a primary key for callbacks to be triggered. Newly registered or canceled callbacks do not take effect until the following event.
|
||||
|
||||
See [the quickstart](/quickstarts/c-sharp) for examples of registering and unregistering row callbacks.
|
||||
See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.
|
||||
|
||||
### Unique constraint index access
|
||||
|
||||
@@ -1082,19 +1082,19 @@ Each reducer defined by the module has three methods on the `.Reducers`:
|
||||
### Type `Identity`
|
||||
|
||||
A unique public identifier for a client connected to a database.
|
||||
See the [module docs](/intro/key-architecture#identity) for more details.
|
||||
See the [module docs](../../00100-intro/00100-getting-started/00400-key-architecture.md#identity) for more details.
|
||||
|
||||
### Type `ConnectionId`
|
||||
|
||||
An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity).
|
||||
See the [module docs](/intro/key-architecture#connectionid) for more details.
|
||||
See the [module docs](../../00100-intro/00100-getting-started/00400-key-architecture.md#connectionid) for more details.
|
||||
|
||||
### Type `Timestamp`
|
||||
|
||||
A point in time, measured in microseconds since the Unix epoch.
|
||||
See the [module docs](/docs/tables/column-types) for more details.
|
||||
See the [module docs](../00300-tables/00200-column-types.md) for more details.
|
||||
|
||||
### Type `TaggedEnum`
|
||||
|
||||
A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) type.
|
||||
See the [module docs](/docs/tables/column-types) for more details.
|
||||
See the [module docs](../00300-tables/00200-column-types.md) for more details.
|
||||
|
||||
@@ -8,9 +8,9 @@ The SpacetimeDB client SDK for TypeScript contains all the tools you need to bui
|
||||
|
||||
Before diving into the reference, you may want to review:
|
||||
|
||||
- [Generating Client Bindings](/clients/codegen) - How to generate TypeScript bindings from your module
|
||||
- [Connecting to SpacetimeDB](/clients/connection) - Establishing and managing connections
|
||||
- [SDK API Reference](/clients/api) - Core concepts that apply across all SDKs
|
||||
- [Generating Client Bindings](./00200-codegen.md) - How to generate TypeScript bindings from your module
|
||||
- [Connecting to SpacetimeDB](./00300-connection.md) - Establishing and managing connections
|
||||
- [SDK API Reference](./00400-sdk-api.md) - Core concepts that apply across all SDKs
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -374,7 +374,7 @@ conn.subscriptionBuilder().subscribe(
|
||||
);
|
||||
```
|
||||
|
||||
For raw SQL subscription syntax, see [the SpacetimeDB SQL Reference](/reference/sql#subscriptions).
|
||||
For raw SQL subscription syntax, see [the SpacetimeDB SQL Reference](../../00300-resources/00200-reference/00400-sql-reference.md#subscriptions).
|
||||
|
||||
##### Method `subscribeToAllTables`
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ The SpacetimeDB client for Unreal Engine contains all the tools you need to buil
|
||||
|
||||
Before diving into the reference, you may want to review:
|
||||
|
||||
- [Generating Client Bindings](/clients/codegen) - How to generate Unreal bindings from your module
|
||||
- [Connecting to SpacetimeDB](/clients/connection) - Establishing and managing connections
|
||||
- [SDK API Reference](/clients/api) - Core concepts that apply across all SDKs
|
||||
- [Generating Client Bindings](./00200-codegen.md) - How to generate Unreal bindings from your module
|
||||
- [Connecting to SpacetimeDB](./00300-connection.md) - Establishing and managing connections
|
||||
- [SDK API Reference](./00400-sdk-api.md) - Core concepts that apply across all SDKs
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -11,32 +11,32 @@ Guides, references, and tools to help you build with SpacetimeDB.
|
||||
Step-by-step guides for common tasks.
|
||||
|
||||
- **Deployment**
|
||||
- [Deploy to MainCloud](/how-to/deploy/maincloud) - Deploy to SpacetimeDB's managed cloud
|
||||
- [Self-Hosting](/how-to/deploy/self-hosting) - Run SpacetimeDB on your own infrastructure
|
||||
- [Deploy to MainCloud](./00100-how-to/00100-deploy/00100-maincloud.md) - Deploy to SpacetimeDB's managed cloud
|
||||
- [Self-Hosting](./00100-how-to/00100-deploy/00200-self-hosting.md) - Run SpacetimeDB on your own infrastructure
|
||||
|
||||
- **Database Features**
|
||||
- [PostgreSQL Compatibility](/how-to/pg-wire) - Connect with PostgreSQL clients
|
||||
- [Logging](/how-to/logging) - Set up logging for debugging
|
||||
- [Row-Level Security](/how-to/rls) - Fine-grained access control
|
||||
- [Reject Connections](/how-to/reject-client-connections) - Control client access
|
||||
- [PostgreSQL Compatibility](./00100-how-to/00200-pg-wire.md) - Connect with PostgreSQL clients
|
||||
- [Logging](./00100-how-to/00300-logging.md) - Set up logging for debugging
|
||||
- [Row-Level Security](./00100-how-to/00400-row-level-security.md) - Fine-grained access control
|
||||
- [Reject Connections](./00100-how-to/00500-reject-client-connections.md) - Control client access
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
Detailed technical references.
|
||||
|
||||
- **CLI**
|
||||
- [CLI Reference](/cli-reference) - All `spacetime` commands
|
||||
- [Standalone Config](/cli-reference/standalone-config) - Configuration options
|
||||
- [CLI Reference](./00200-reference/00100-cli-reference/00100-cli-reference.md) - All `spacetime` commands
|
||||
- [Standalone Config](./00200-reference/00100-cli-reference/00200-standalone-config.md) - Configuration options
|
||||
|
||||
- **HTTP API**
|
||||
- [Authorization](/http/authorization) - API authentication
|
||||
- [Identity](/http/identity) - Identity management endpoints
|
||||
- [Database](/http/database) - Database operations
|
||||
- [Authorization](./00200-reference/00200-http-api/00100-authorization.md) - API authentication
|
||||
- [Identity](./00200-reference/00200-http-api/00200-identity.md) - Identity management endpoints
|
||||
- [Database](./00200-reference/00200-http-api/00300-database.md) - Database operations
|
||||
|
||||
- **SQL**
|
||||
- [SQL Reference](/reference/sql) - Supported SQL syntax
|
||||
- [SQL Reference](./00200-reference/00400-sql-reference.md) - Supported SQL syntax
|
||||
|
||||
- **Internals**
|
||||
- [Module ABI](/webassembly-abi) - WebAssembly module interface
|
||||
- [SATS JSON](/sats-json) - JSON serialization format
|
||||
- [BSATN](/bsatn) - Binary serialization format
|
||||
- [Module ABI](./00200-reference/00300-internals/00100-module-abi-reference.md) - WebAssembly module interface
|
||||
- [SATS JSON](./00200-reference/00300-internals/00200-sats-json.md) - JSON serialization format
|
||||
- [BSATN](./00200-reference/00300-internals/00300-bsatn.md) - Binary serialization format
|
||||
|
||||
@@ -28,7 +28,7 @@ If you previously published a database without logging in first, your CLI identi
|
||||
|
||||
## Publishing to Maincloud
|
||||
|
||||
After creating your module (see [Getting Started](/)), publish it to Maincloud:
|
||||
After creating your module (see [Getting Started](../../../00100-intro/00100-getting-started/00100-getting-started.md)), publish it to Maincloud:
|
||||
|
||||
```bash
|
||||
spacetime publish my-database --server maincloud
|
||||
@@ -36,7 +36,7 @@ spacetime publish my-database --server maincloud
|
||||
|
||||
SpacetimeDB compiles your module, uploads it, runs your `init` reducer (if defined), and outputs the database identity. Save this identity for administrative tasks.
|
||||
|
||||
To update an existing module, run the same command. SpacetimeDB hot-swaps the module code without disconnecting clients. See [Automatic Migrations](/databases/automatic-migrations) for details on schema changes during updates.
|
||||
To update an existing module, run the same command. SpacetimeDB hot-swaps the module code without disconnecting clients. See [Automatic Migrations](../../../00200-core-concepts/00100-databases/00500-migrations/00200-automatic-migrations.md) for details on schema changes during updates.
|
||||
|
||||
To clear all data and start fresh:
|
||||
|
||||
@@ -113,7 +113,7 @@ The database dashboard gives you access to:
|
||||
- **Usage breakdown**: See this month's energy consumption broken down by bytes scanned, bytes written, index seeks, CPU instructions, bandwidth, and table storage.
|
||||
- **Logs**: View your module's log output in real time.
|
||||
- **SQL console**: Run ad-hoc SQL queries against your database.
|
||||
- **SpacetimeAuth**: Enable and configure the built-in authentication provider (see [SpacetimeAuth](/core-concepts/authentication/spacetimeauth)).
|
||||
- **SpacetimeAuth**: Enable and configure the built-in authentication provider (see [SpacetimeAuth](../../../00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md)).
|
||||
|
||||
## Database Lifecycle
|
||||
|
||||
@@ -151,6 +151,6 @@ This action cannot be undone.
|
||||
## Next Steps
|
||||
|
||||
- **Explore the dashboard**: Visit [spacetimedb.com](https://spacetimedb.com) to view your database, check logs, and run queries.
|
||||
- **Set up authentication**: Enable [SpacetimeAuth](/core-concepts/authentication/spacetimeauth) or connect a third-party [OIDC provider](/core-concepts/authentication) to authenticate your users.
|
||||
- **Connect a client**: Follow a [quickstart guide](/quickstarts/react) to build a client that connects to your Maincloud database.
|
||||
- **Set up authentication**: Enable [SpacetimeAuth](../../../00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md) or connect a third-party [OIDC provider](../../../00200-core-concepts/00500-authentication.md) to authenticate your users.
|
||||
- **Connect a client**: Follow a [quickstart guide](../../../00100-intro/00200-quickstarts/00100-react.md) to build a client that connects to your Maincloud database.
|
||||
- **Monitor your usage**: Check your energy consumption and plan limits on the [pricing page](https://spacetimedb.com/pricing).
|
||||
|
||||
@@ -24,7 +24,7 @@ implemented, or behave differently:
|
||||
|
||||
- **Protocol Version**: Only _PGWire_ protocol _version 3.0_ is supported, and only the _Simple Query Protocol_, and
|
||||
without parameterized queries.
|
||||
- **SQL Features**: Only the subset of SQL features documented in the [SQL documentation](/reference/sql) are
|
||||
- **SQL Features**: Only the subset of SQL features documented in the [SQL documentation](../00200-reference/00400-sql-reference.md) are
|
||||
supported. Subscription queries do not update in real time.
|
||||
- **Authentication**: SpacetimeDB does not implement database users or roles. The connection string
|
||||
`user_name@database_name` ignores `user_name`; only `database_name` is used. Authentication is based on the _auth
|
||||
@@ -102,7 +102,7 @@ spacetime start --pg-port 5432 [ARGS]
|
||||
## Examples
|
||||
|
||||
In the following example, we assume you are using the `quickstart-chat` database created in
|
||||
the [Rust Module Quickstart](/docs/quickstarts/rust) or [C# Module Quickstart](/docs/quickstarts/c-sharp),
|
||||
the [Rust Module Quickstart](../../00100-intro/00200-quickstarts/00500-rust.md) or [C# Module Quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md),
|
||||
and have set the `auth token` as shown above.
|
||||
|
||||
### Using `psql`
|
||||
|
||||
@@ -185,7 +185,7 @@ spacetime logs --level warn <DATABASE_NAME>
|
||||
spacetime logs --since "2023-01-01 00:00:00" <DATABASE_NAME>
|
||||
```
|
||||
|
||||
For all log viewing options, see the [`spacetime logs` CLI reference](/cli-reference#spacetime-logs).
|
||||
For all log viewing options, see the [`spacetime logs` CLI reference](../00200-reference/00100-cli-reference/00100-cli-reference.md#spacetime-logs).
|
||||
|
||||
## Best Practices
|
||||
|
||||
@@ -291,6 +291,6 @@ SPACETIMEDB_REDUCER(transfer_credits, ReducerContext ctx, uint64_t to_user, uint
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [Error Handling](/functions/reducers/error-handling) in reducers
|
||||
- Explore the [CLI Reference](/cli-reference) for more logging options
|
||||
- Learn about [Error Handling](../../00200-core-concepts/00200-functions/00300-reducers/00600-error-handling.md) in reducers
|
||||
- Explore the [CLI Reference](../00200-reference/00100-cli-reference/00100-cli-reference.md) for more logging options
|
||||
- Set up monitoring and alerting for your production databases
|
||||
|
||||
@@ -10,13 +10,13 @@ import TabItem from '@theme/TabItem';
|
||||
:::danger Experimental Feature - Use Views Instead
|
||||
**Row Level Security is an experimental, unstable feature.** The API may change or be removed in future releases.
|
||||
|
||||
For access control, **use [Views](/functions/views) instead**. Views provide:
|
||||
For access control, **use [Views](../../00200-core-concepts/00200-functions/00500-views.md) instead**. Views provide:
|
||||
- A simpler, more flexible approach to controlling data visibility
|
||||
- Better performance characteristics
|
||||
- Full control over which rows and columns clients can access
|
||||
- The ability to filter by caller identity using `ViewContext`
|
||||
|
||||
See [Using Views for Fine-Grained Access Control](/tables/access-permissions#using-views-for-fine-grained-access-control) for examples of implementing row and column filtering with views.
|
||||
See [Using Views for Fine-Grained Access Control](../../00200-core-concepts/00300-tables/00400-access-permissions.md#using-views-for-fine-grained-access-control) for examples of implementing row and column filtering with views.
|
||||
|
||||
Only use RLS if you have a specific use case that views cannot address.
|
||||
:::
|
||||
|
||||
@@ -8,13 +8,13 @@ slug: /http/authorization
|
||||
|
||||
SpacetimeDB can derive an identity from the `sub` and `iss` claims of any [OpenID Connect](https://openid.net/developers/how-connect-works/) compliant [JSON Web Token](https://jwt.io/).
|
||||
|
||||
Clients can request a new identity and token signed by the SpacetimeDB host via [the `POST /v1/identity` HTTP endpoint](/http/identity#post-v1identity). Such a token will not be portable to other SpacetimeDB clusters.
|
||||
Clients can request a new identity and token signed by the SpacetimeDB host via [the `POST /v1/identity` HTTP endpoint](./00200-identity.md#post-v1identity). Such a token will not be portable to other SpacetimeDB clusters.
|
||||
|
||||
Alternately, a new identity and token will be generated during an anonymous connection via the WebSocket API, and passed to the client as an `IdentityToken` message.
|
||||
|
||||
### `Authorization` headers
|
||||
|
||||
Many SpacetimeDB HTTP endpoints either require or optionally accept a token in the `Authorization` header. SpacetimeDB authorization headers are of the form `Authorization: Bearer ${token}`, where `token` is an [OpenID Connect](https://openid.net/developers/how-connect-works/) compliant [JSON Web Token](https://jwt.io/), such as the one returned from [the `POST /v1/identity` HTTP endpoint](/http/identity#post-v1identity).
|
||||
Many SpacetimeDB HTTP endpoints either require or optionally accept a token in the `Authorization` header. SpacetimeDB authorization headers are of the form `Authorization: Bearer ${token}`, where `token` is an [OpenID Connect](https://openid.net/developers/how-connect-works/) compliant [JSON Web Token](https://jwt.io/), such as the one returned from [the `POST /v1/identity` HTTP endpoint](./00200-identity.md#post-v1identity).
|
||||
|
||||
# Top level routes
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Generate a short-lived access token which can be used in untrusted contexts, e.g
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](/http/authorization). |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](./00100-authorization.md). |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -81,7 +81,7 @@ Associate an email with a Spacetime identity.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](/http/authorization). |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](./00100-authorization.md). |
|
||||
|
||||
## `GET /v1/identity/:identity/databases`
|
||||
|
||||
@@ -119,7 +119,7 @@ Verify the validity of an identity/token pair.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](/http/authorization). |
|
||||
| `Authorization` | A Spacetime token [encoded as Basic authorization](./00100-authorization.md). |
|
||||
|
||||
#### Returns
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Accessible through the CLI as `spacetime publish`.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -67,7 +67,7 @@ Accessible through the CLI as `spacetime publish`.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -127,7 +127,7 @@ Accessible through the CLI as `spacetime delete <identity>`.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
## `GET /v1/database/:name_or_identity/names`
|
||||
|
||||
@@ -151,7 +151,7 @@ Add a new name for this database.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -184,7 +184,7 @@ Set the list of names for this database.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -228,16 +228,16 @@ For more information about WebSocket headers, see [RFC 6455](https://datatracker
|
||||
| `Sec-WebSocket-Version` | `13` |
|
||||
| `Sec-WebSocket-Key` | A 16-byte value, generated randomly by the client, encoded as Base64. |
|
||||
|
||||
The SpacetimeDB binary WebSocket protocol, `v1.bsatn.spacetimedb`, encodes messages as well as reducer and row data using [BSATN](/bsatn).
|
||||
The SpacetimeDB binary WebSocket protocol, `v1.bsatn.spacetimedb`, encodes messages as well as reducer and row data using [BSATN](../00300-internals/00300-bsatn.md).
|
||||
Its messages are defined [here](https://github.com/clockworklabs/SpacetimeDB/blob/master/crates/client-api-messages/src/websocket.rs).
|
||||
|
||||
The SpacetimeDB text WebSocket protocol, `v1.json.spacetimedb`, encodes messages according to the [SATS-JSON format](/sats-json).
|
||||
The SpacetimeDB text WebSocket protocol, `v1.json.spacetimedb`, encodes messages according to the [SATS-JSON format](../00300-internals/00200-sats-json.md).
|
||||
|
||||
#### Optional Headers
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
## `POST /v1/database/:name_or_identity/call/:reducer`
|
||||
|
||||
@@ -253,7 +253,7 @@ Invoke a reducer in a database.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -413,7 +413,7 @@ Accessible through the CLI as `spacetime logs <name_or_identity>`.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -429,7 +429,7 @@ Accessible through the CLI as `spacetime sql <name_or_identity> <query>`.
|
||||
|
||||
| Name | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------- |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](/http/authorization#authorization-headers). |
|
||||
| `Authorization` | A Spacetime token [as Bearer auth](./00100-authorization.md#authorization-headers). |
|
||||
|
||||
#### Data
|
||||
|
||||
@@ -446,6 +446,6 @@ Returns a JSON array of statement results, each of which takes the form:
|
||||
}
|
||||
```
|
||||
|
||||
The `schema` will be a [JSON-encoded `ProductType`](/sats-json) describing the type of the returned rows.
|
||||
The `schema` will be a [JSON-encoded `ProductType`](../00300-internals/00200-sats-json.md) describing the type of the returned rows.
|
||||
|
||||
The `rows` will be an array of [JSON-encoded `ProductValue`s](/sats-json), each of which conforms to the `schema`.
|
||||
The `rows` will be an array of [JSON-encoded `ProductValue`s](../00300-internals/00200-sats-json.md), each of which conforms to the `schema`.
|
||||
|
||||
@@ -4,7 +4,7 @@ slug: /sats-json
|
||||
|
||||
# SATS-JSON Data Format
|
||||
|
||||
The Spacetime Algebraic Type System JSON format defines how Spacetime `AlgebraicType`s and `AlgebraicValue`s are encoded as JSON. Algebraic types and values are JSON-encoded for transport via the [HTTP Databases API](/http/database) and the WebSocket text protocol. Note that SATS-JSON is not self-describing, and so a SATS value represented in JSON requires knowing the value's schema to meaningfully understand it - for example, it's not possible to tell whether a JSON object with a single field is a `ProductValue` with one element or a `SumValue`.
|
||||
The Spacetime Algebraic Type System JSON format defines how Spacetime `AlgebraicType`s and `AlgebraicValue`s are encoded as JSON. Algebraic types and values are JSON-encoded for transport via the [HTTP Databases API](../00200-http-api/00300-database.md) and the WebSocket text protocol. Note that SATS-JSON is not self-describing, and so a SATS value represented in JSON requires knowing the value's schema to meaningfully understand it - for example, it's not possible to tell whether a JSON object with a single field is a `ProductValue` with one element or a `SumValue`.
|
||||
|
||||
## Values
|
||||
|
||||
@@ -170,4 +170,4 @@ SATS array and map types are homogeneous, meaning that each array has a single e
|
||||
|
||||
### `AlgebraicTypeRef`
|
||||
|
||||
`AlgebraicTypeRef`s are JSON-encoded as non-negative integers. These are indices into a typespace, like the one returned by the [`GET /v1/database/:name_or_identity/schema` HTTP endpoint](/http/database#get-v1databasename_or_identityschema).
|
||||
`AlgebraicTypeRef`s are JSON-encoded as non-negative integers. These are indices into a typespace, like the one returned by the [`GET /v1/database/:name_or_identity/schema` HTTP endpoint](../00200-http-api/00300-database.md#get-v1databasename_or_identityschema).
|
||||
|
||||
@@ -135,6 +135,6 @@ Where
|
||||
All SATS types are BSATN-encoded by converting them to an `AlgebraicValue`,
|
||||
then BSATN-encoding that meta-value.
|
||||
|
||||
See [the SATN JSON Format](/sats-json)
|
||||
See [the SATN JSON Format](./00200-sats-json.md)
|
||||
for more details of the conversion to meta values.
|
||||
Note that these meta values are converted to BSATN and _not JSON_.
|
||||
|
||||
@@ -103,6 +103,19 @@ const config: Config = {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: './sidebars.ts',
|
||||
sidebarCollapsed: false,
|
||||
includeCurrentVersion: true,
|
||||
lastVersion: '1.12.0',
|
||||
versions: {
|
||||
current: {
|
||||
label: '2.0.0-rc1',
|
||||
path: '2.0.0-rc1',
|
||||
banner: 'unreleased',
|
||||
},
|
||||
'1.12.0': {
|
||||
label: '1.12.0',
|
||||
banner: 'none',
|
||||
},
|
||||
},
|
||||
beforeDefaultRehypePlugins: [
|
||||
[
|
||||
rehypeShiki,
|
||||
@@ -150,6 +163,7 @@ const config: Config = {
|
||||
hideOnScroll: false,
|
||||
items: [
|
||||
{ type: 'search', position: 'left' },
|
||||
{ type: 'docsVersionDropdown', position: 'left' },
|
||||
{
|
||||
href: 'https://spacetimedb.com/install',
|
||||
label: 'Install',
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"generate-cli-docs": "node scripts/generate-cli-docs.mjs",
|
||||
"rewrite-links": "node scripts/rewrite-doc-links.mjs",
|
||||
"rewrite-links:write": "node scripts/rewrite-doc-links.mjs --write",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
|
||||
if [[ -z "$REPO_ROOT" ]]; then
|
||||
echo "Could not determine git repository root from $SCRIPT_DIR." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
DEFAULT_COMMIT="e45cf891c20d87b11976e1d54c04c0e4639dbe81"
|
||||
COMMIT="${1:-$DEFAULT_COMMIT}"
|
||||
VERSION="${2:-1.12.0}"
|
||||
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "git is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "node is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "pnpm is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
|
||||
if [[ "$NODE_MAJOR" -lt 20 ]]; then
|
||||
echo "Node >= 20 is required. Current: $(node -v)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d docs ]]; then
|
||||
echo "Run this from the repo root (expected ./docs)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify "${COMMIT}^{commit}" >/dev/null 2>&1; then
|
||||
echo "Commit not found: $COMMIT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/spacetimedb-docs-recut.XXXXXX")"
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
|
||||
git -C "$REPO_ROOT" worktree remove --force "$TMP_DIR" >/dev/null 2>&1 || true
|
||||
rm -rf "$TMP_DIR" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "Creating temp worktree at $TMP_DIR"
|
||||
git worktree add --detach "$TMP_DIR" HEAD >/dev/null
|
||||
|
||||
echo "Restoring docs source from commit $COMMIT"
|
||||
git -C "$TMP_DIR" restore --source "$COMMIT" --worktree -- docs/docs docs/sidebars.ts
|
||||
|
||||
TMP_CUT_VERSION="__recut_${VERSION}_$(date +%s)"
|
||||
TMP_CUT_VERSIONED_DOCS="$TMP_DIR/docs/versioned_docs/version-$TMP_CUT_VERSION"
|
||||
TMP_CUT_VERSIONED_SIDEBAR="$TMP_DIR/docs/versioned_sidebars/version-$TMP_CUT_VERSION-sidebars.json"
|
||||
|
||||
echo "Preparing temp cut version: $TMP_CUT_VERSION"
|
||||
rm -rf "$TMP_CUT_VERSIONED_DOCS" "$TMP_CUT_VERSIONED_SIDEBAR"
|
||||
|
||||
echo "Recutting docs version: $VERSION"
|
||||
echo "Installing docs dependencies in temp worktree"
|
||||
pnpm --dir "$TMP_DIR/docs" install --frozen-lockfile
|
||||
pnpm --dir "$TMP_DIR/docs" docusaurus docs:version "$TMP_CUT_VERSION"
|
||||
|
||||
DEST_VERSIONED_DOCS="$REPO_ROOT/docs/versioned_docs/version-$VERSION"
|
||||
DEST_VERSIONED_SIDEBAR="$REPO_ROOT/docs/versioned_sidebars/version-$VERSION-sidebars.json"
|
||||
DEST_VERSIONS_JSON="$REPO_ROOT/docs/versions.json"
|
||||
|
||||
echo "Copying regenerated artifacts into current branch working tree"
|
||||
rm -rf "$DEST_VERSIONED_DOCS" "$DEST_VERSIONED_SIDEBAR"
|
||||
cp -R "$TMP_CUT_VERSIONED_DOCS" "$REPO_ROOT/docs/versioned_docs/version-$VERSION"
|
||||
cp "$TMP_CUT_VERSIONED_SIDEBAR" "$DEST_VERSIONED_SIDEBAR"
|
||||
|
||||
node -e '
|
||||
const fs = require("fs");
|
||||
const path = process.argv[1];
|
||||
const version = process.argv[2];
|
||||
let versions = [];
|
||||
try {
|
||||
versions = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
if (!Array.isArray(versions)) versions = [];
|
||||
} catch {
|
||||
versions = [];
|
||||
}
|
||||
versions = versions.filter((v) => v !== version);
|
||||
versions.unshift(version);
|
||||
fs.writeFileSync(path, JSON.stringify(versions, null, 2) + "\n");
|
||||
' "$DEST_VERSIONS_JSON" "$VERSION"
|
||||
|
||||
echo
|
||||
echo "Done."
|
||||
echo "Updated:"
|
||||
echo " docs/versioned_docs/version-$VERSION"
|
||||
echo " docs/versioned_sidebars/version-$VERSION-sidebars.json"
|
||||
echo " docs/versions.json"
|
||||
echo
|
||||
echo "Run: git status"
|
||||
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = path.basename(cwd) === 'docs' ? path.dirname(cwd) : cwd;
|
||||
const docsDir = path.basename(cwd) === 'docs' ? cwd : path.join(repoRoot, 'docs');
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const write = args.has('--write');
|
||||
const verbose = args.has('--verbose');
|
||||
|
||||
const isDocFile = (p) => p.endsWith('.md') || p.endsWith('.mdx');
|
||||
|
||||
async function listFilesRecursive(dir) {
|
||||
const out = [];
|
||||
async function walk(current) {
|
||||
const entries = await fs.readdir(current, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await walk(full);
|
||||
} else if (entry.isFile() && isDocFile(full)) {
|
||||
out.push(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
await walk(dir);
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseFrontMatter(content) {
|
||||
if (!content.startsWith('---\n')) return {};
|
||||
const end = content.indexOf('\n---\n', 4);
|
||||
if (end === -1) return {};
|
||||
const body = content.slice(4, end);
|
||||
const out = {};
|
||||
for (const line of body.split('\n')) {
|
||||
const m = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.+)\s*$/);
|
||||
if (!m) continue;
|
||||
const key = m[1];
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
out[key] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function stripOrderingPrefix(segment) {
|
||||
return segment.replace(/^\d{3,6}-/, '');
|
||||
}
|
||||
|
||||
function normalizeRoute(route) {
|
||||
if (!route) return '/';
|
||||
let out = route.trim();
|
||||
if (!out.startsWith('/')) out = `/${out}`;
|
||||
out = out.replace(/\/{2,}/g, '/');
|
||||
if (out.length > 1) out = out.replace(/\/+$/, '');
|
||||
return out;
|
||||
}
|
||||
|
||||
function defaultRouteFromFile(namespaceRoot, fullPath) {
|
||||
const rel = path.posix.normalize(path.relative(namespaceRoot, fullPath)).replace(/\\/g, '/');
|
||||
const parsed = path.posix.parse(rel);
|
||||
const parts = parsed.dir === '.' ? [] : parsed.dir.split('/').map(stripOrderingPrefix);
|
||||
const base = stripOrderingPrefix(parsed.name);
|
||||
if (base !== 'index') parts.push(base);
|
||||
return normalizeRoute(parts.join('/'));
|
||||
}
|
||||
|
||||
function splitPathSuffix(target) {
|
||||
const m = target.match(/^([^?#]*)([?#].*)?$/);
|
||||
return { pathPart: m?.[1] ?? target, suffix: m?.[2] ?? '' };
|
||||
}
|
||||
|
||||
function buildLookupCandidates(rawPath, knownVersionNames) {
|
||||
const pathPart = normalizeRoute(rawPath);
|
||||
const candidates = new Set([pathPart]);
|
||||
|
||||
if (pathPart.startsWith('/docs/')) {
|
||||
candidates.add(normalizeRoute(pathPart.slice('/docs'.length)));
|
||||
} else if (pathPart === '/docs') {
|
||||
candidates.add('/');
|
||||
}
|
||||
|
||||
for (const versionName of knownVersionNames) {
|
||||
const prefix = `/${versionName}/`;
|
||||
if (pathPart.startsWith(prefix)) {
|
||||
candidates.add(normalizeRoute(pathPart.slice(versionName.length + 1)));
|
||||
}
|
||||
const docsPrefix = `/docs/${versionName}/`;
|
||||
if (pathPart.startsWith(docsPrefix)) {
|
||||
candidates.add(normalizeRoute(pathPart.slice(versionName.length + '/docs'.length + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return [...candidates];
|
||||
}
|
||||
|
||||
function toRelativeLink(fromFile, toFile, suffix) {
|
||||
const fromDir = path.posix.dirname(fromFile.replace(/\\/g, '/'));
|
||||
const toPosix = toFile.replace(/\\/g, '/');
|
||||
let rel = path.posix.relative(fromDir, toPosix);
|
||||
if (!rel) {
|
||||
return suffix || '#';
|
||||
}
|
||||
if (!rel.startsWith('.')) rel = `./${rel}`;
|
||||
return `${rel}${suffix}`;
|
||||
}
|
||||
|
||||
async function detectNamespaces() {
|
||||
const namespaces = [];
|
||||
const currentDocs = path.join(docsDir, 'docs');
|
||||
try {
|
||||
const stat = await fs.stat(currentDocs);
|
||||
if (stat.isDirectory()) namespaces.push(currentDocs);
|
||||
} catch {}
|
||||
|
||||
const versionedRoot = path.join(docsDir, 'versioned_docs');
|
||||
try {
|
||||
const entries = await fs.readdir(versionedRoot, { withFileTypes: true });
|
||||
for (const e of entries) {
|
||||
if (e.isDirectory() && e.name.startsWith('version-')) {
|
||||
namespaces.push(path.join(versionedRoot, e.name));
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
function markdownLinkRewriter(content, rewriter) {
|
||||
const pattern = /\[[^\]]*?\]\((\/[^)\s]*?)\)/g;
|
||||
return content.replace(pattern, (full, target, offset) => {
|
||||
if (offset > 0 && content[offset - 1] === '!') return full;
|
||||
const replaced = rewriter(target);
|
||||
if (!replaced || replaced === target) return full;
|
||||
return full.replace(`(${target})`, `(${replaced})`);
|
||||
});
|
||||
}
|
||||
|
||||
function mdxLinkRewriter(content, rewriter) {
|
||||
const pattern = /<Link\b([^>]*?\bto=)(["'])(\/[^"'{}]+)\2([^>]*)>/g;
|
||||
return content.replace(pattern, (full, before, quote, target, after) => {
|
||||
const replaced = rewriter(target);
|
||||
if (!replaced || replaced === target) return full;
|
||||
return `<Link${before}${quote}${replaced}${quote}${after}>`;
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const namespaces = await detectNamespaces();
|
||||
if (namespaces.length === 0) {
|
||||
console.error('No docs namespaces found.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const versionsPath = path.join(docsDir, 'versions.json');
|
||||
let knownVersionNames = ['v1', 'prerelease'];
|
||||
try {
|
||||
const raw = await fs.readFile(versionsPath, 'utf8');
|
||||
const versions = JSON.parse(raw);
|
||||
if (Array.isArray(versions)) {
|
||||
knownVersionNames = [...new Set([...knownVersionNames, ...versions])];
|
||||
}
|
||||
} catch {}
|
||||
knownVersionNames.push('2.0.0-rc1');
|
||||
knownVersionNames = [...new Set(knownVersionNames)];
|
||||
|
||||
let changedFiles = 0;
|
||||
let rewrittenLinks = 0;
|
||||
const unresolved = [];
|
||||
|
||||
for (const namespace of namespaces) {
|
||||
const files = await listFilesRecursive(namespace);
|
||||
const routeToFile = new Map();
|
||||
const collisions = new Map();
|
||||
|
||||
for (const file of files) {
|
||||
const content = await fs.readFile(file, 'utf8');
|
||||
const fm = parseFrontMatter(content);
|
||||
|
||||
const candidates = new Set([defaultRouteFromFile(namespace, file)]);
|
||||
if (typeof fm.slug === 'string' && fm.slug) {
|
||||
candidates.add(normalizeRoute(fm.slug));
|
||||
}
|
||||
|
||||
for (const route of candidates) {
|
||||
if (routeToFile.has(route) && routeToFile.get(route) !== file) {
|
||||
collisions.set(route, true);
|
||||
continue;
|
||||
}
|
||||
routeToFile.set(route, file);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const relFile = path.relative(repoRoot, file).replace(/\\/g, '/');
|
||||
const original = await fs.readFile(file, 'utf8');
|
||||
let linkChanges = 0;
|
||||
|
||||
const rewriteTarget = (target) => {
|
||||
if (!target.startsWith('/') || target.startsWith('//')) return target;
|
||||
const { pathPart, suffix } = splitPathSuffix(target);
|
||||
const lookupPaths = buildLookupCandidates(pathPart, knownVersionNames);
|
||||
|
||||
let resolvedFile;
|
||||
for (const candidate of lookupPaths) {
|
||||
if (collisions.has(candidate)) continue;
|
||||
const hit = routeToFile.get(candidate);
|
||||
if (hit) {
|
||||
resolvedFile = hit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolvedFile) {
|
||||
unresolved.push({ file: relFile, target });
|
||||
return target;
|
||||
}
|
||||
|
||||
const relative = toRelativeLink(file, resolvedFile, suffix);
|
||||
if (relative !== target) {
|
||||
linkChanges += 1;
|
||||
}
|
||||
return relative;
|
||||
};
|
||||
|
||||
let updated = markdownLinkRewriter(original, rewriteTarget);
|
||||
updated = mdxLinkRewriter(updated, rewriteTarget);
|
||||
|
||||
if (updated !== original) {
|
||||
changedFiles += 1;
|
||||
rewrittenLinks += linkChanges;
|
||||
if (write) {
|
||||
await fs.writeFile(file, updated, 'utf8');
|
||||
}
|
||||
if (verbose) {
|
||||
console.log(`${write ? 'rewrote' : 'would rewrite'} ${relFile} (${linkChanges} links)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${write ? 'Rewrote' : 'Would rewrite'} ${rewrittenLinks} link(s) in ${changedFiles} file(s).`,
|
||||
);
|
||||
|
||||
if (unresolved.length > 0) {
|
||||
const unique = new Map();
|
||||
for (const item of unresolved) {
|
||||
const key = `${item.file} -> ${item.target}`;
|
||||
unique.set(key, item);
|
||||
}
|
||||
const list = [...unique.values()];
|
||||
console.log(`Skipped ${list.length} unresolved absolute link(s).`);
|
||||
for (const item of list.slice(0, 50)) {
|
||||
console.log(` ${item.file}: ${item.target}`);
|
||||
}
|
||||
if (list.length > 50) {
|
||||
console.log(` ...and ${list.length - 50} more`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!write) {
|
||||
console.log('Dry run only. Re-run with --write to apply changes.');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -17,26 +17,26 @@ export function QuickstartLinks() {
|
||||
items={[
|
||||
{
|
||||
icon: <TypeScriptLogo height={40} />,
|
||||
href: "/quickstarts/typescript",
|
||||
href: "quickstarts/typescript",
|
||||
docId: "intro/quickstarts/typescript",
|
||||
label: "TypeScript",
|
||||
},
|
||||
{
|
||||
icon: <CSharpLogo height={40} />,
|
||||
href: "/quickstarts/c-sharp",
|
||||
href: "quickstarts/c-sharp",
|
||||
docId: "intro/quickstarts/c-sharp",
|
||||
label: "C#",
|
||||
},
|
||||
{
|
||||
icon: <RustLogo height={40} />,
|
||||
href: "/quickstarts/rust",
|
||||
href: "quickstarts/rust",
|
||||
docId: "intro/quickstarts/rust",
|
||||
invertIcon: true,
|
||||
label: "Rust",
|
||||
},
|
||||
{
|
||||
icon: <ReactLogo height={40} />,
|
||||
href: "/quickstarts/react",
|
||||
href: "quickstarts/react",
|
||||
docId: "intro/quickstarts/react",
|
||||
label: "React",
|
||||
},
|
||||
@@ -91,4 +91,4 @@ export function QuickstartLinks() {
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import Link from '@docusaurus/Link';
|
||||
import { useDocsVersion } from '@docusaurus/plugin-content-docs/client';
|
||||
import DocVersionBanner from '@theme-original/DocVersionBanner';
|
||||
|
||||
export default function DocVersionBannerWrapper(
|
||||
props: React.ComponentProps<typeof DocVersionBanner>,
|
||||
): JSX.Element {
|
||||
const version = useDocsVersion();
|
||||
|
||||
if (version.version === '1.12.0') {
|
||||
return (
|
||||
<div className="alert alert--info margin-bottom--md" role="alert">
|
||||
Want to try the upcoming v2 features? Try the {' '}
|
||||
<Link to="/2.0.0-rc1">Prerelease docs</Link>.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <DocVersionBanner {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: 'Ask AI Chat'
|
||||
pagination_next: null
|
||||
pagination_prev: null
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { InkeepEmbeddedChat } from '@inkeep/cxkit-react';
|
||||
|
||||
<!-- This API key is public, it's okay to have it in client code, https://docs.inkeep.com/cloud/ui-components/public-api-keys#public-clients -->
|
||||
|
||||
<InkeepEmbeddedChat
|
||||
shouldAutoFocusInput={true}
|
||||
baseSettings={{
|
||||
apiKey: '13504c49fb56b7c09a5ea0bcd68c2b55857661be4d6d311b',
|
||||
organizationDisplayName: 'SpacetimeDB',
|
||||
primaryBrandColor: '#4cf490',
|
||||
colorMode: {
|
||||
forcedColorMode: 'dark',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"label": "\u200B",
|
||||
"collapsed": false,
|
||||
"className": "sidebar-ghost"
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Getting Started
|
||||
slug: /
|
||||
---
|
||||
|
||||
import { InstallCardLink } from "@site/src/components/InstallCardLink";
|
||||
import { QuickstartLinks } from "@site/src/components/QuickstartLinks";
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
You can get started by first installing the `spacetime` CLI tool. The `spacetime` CLI tool makes it extremely easy to manage your databases and deployments.
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
## Log in to SpacetimeDB
|
||||
|
||||
SpacetimeDB authenticates users using a GitHub login, to prevent unauthorized access (e.g. somebody else publishing over your module). Log in to SpacetimeDB using:
|
||||
|
||||
```bash
|
||||
spacetime login
|
||||
```
|
||||
|
||||
This will open a browser and ask you to log in via GitHub. If you forget this step, any commands that require login (like `spacetime publish`) will ask you to log in when you run them.
|
||||
|
||||
## Quickstart Guides
|
||||
|
||||
You are now ready to start developing SpacetimeDB modules. Choose your favorite language and follow one of our quickstart guides to get started building your first app with SpacetimeDB.
|
||||
|
||||
<QuickstartLinks />
|
||||
|
||||
## Running SpacetimeDB Locally
|
||||
|
||||
To develop SpacetimeDB databases locally, you will need to run the Standalone version of the server.
|
||||
|
||||
After installing the SpacetimeDB CLI, run the start command:
|
||||
|
||||
```bash
|
||||
spacetime start
|
||||
```
|
||||
|
||||
The server listens on port `3000` by default, customized via `--listen-addr`.
|
||||
|
||||
💡 Standalone mode will run in the foreground.
|
||||
⚠️ SSL is not supported in standalone mode.
|
||||
|
||||
## Next Steps: Learn SpacetimeDB
|
||||
|
||||
After completing a quickstart guide, explore these core concepts to deepen your understanding:
|
||||
|
||||
### Core Concepts
|
||||
|
||||
- **[Databases](../../00200-core-concepts/00100-databases.md)** - Understand database lifecycle, publishing, and management
|
||||
- **[Tables](../../00200-core-concepts/00300-tables.md)** - Define your data structure with tables, columns, and indexes
|
||||
- **[Functions](../../00200-core-concepts/00200-functions.md)** - Write reducers, procedures, and views to implement your server logic
|
||||
- **[Subscriptions](../../00200-core-concepts/00400-subscriptions.md)** - Enable real-time data synchronization with clients
|
||||
- **[Client SDKs](../../00200-core-concepts/00600-client-sdk-languages.md)** - Connect your client applications to SpacetimeDB
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: What is SpacetimeDB?
|
||||
slug: /intro/what-is-spacetimedb
|
||||
---
|
||||
|
||||
|
||||
SpacetimeDB is a database that is also a server.
|
||||
|
||||
SpacetimeDB is a full-featured relational database system that lets you run your application logic **inside** the database. You no longer need to deploy a separate web or game server. [Several programming languages](./00300-language-support.md) are supported, including C# and Rust. You can still write authorization logic, just like you would in a traditional server.
|
||||
|
||||
This means that you can write your entire application in a single language and deploy it as a single binary. No more microservices, no more containers, no more Kubernetes, no more Docker, no more VMs, no more DevOps, no more infrastructure, no more ops, no more servers.
|
||||
|
||||
<figure>
|
||||
<img
|
||||
src="/docs/images/basic-architecture-diagram.png"
|
||||
alt="SpacetimeDB Architecture"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<figcaption style={{ marginTop: '10px', textAlign: 'center' }} align="center">
|
||||
<b align="center">SpacetimeDB application architecture</b>
|
||||
<span style={{ fontSize: '14px' }}>
|
||||
{' '}
|
||||
(elements in white are provided by SpacetimeDB)
|
||||
</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
In fact, it's so fast that we've been able to write the entire backend of our MMORPG [BitCraft Online](https://bitcraftonline.com) as a single SpacetimeDB database. Everything in the game -- chat messages, items, resources, terrain, and player locations -- is stored and processed by the database. SpacetimeDB [automatically mirrors](#state-mirroring) relevant state to connected players in real-time.
|
||||
|
||||
SpacetimeDB is optimized for maximum speed and minimum latency, rather than batch processing or analytical workloads. It is designed for real-time applications like games, chat, and collaboration tools.
|
||||
|
||||
Speed and latency is achieved by holding all of your application state in memory, while persisting data to a commit log which is used to recover data after restarts and system crashes.
|
||||
|
||||
## Application Workflow Preview
|
||||
|
||||
<figure>
|
||||
<img
|
||||
src="/docs/images/workflow-preview-diagram.png"
|
||||
alt="SpacetimeDB Application Workflow Preview"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<figcaption style={{ marginTop: '10px', textAlign: 'center' }} align="center">
|
||||
<b align="center">SpacetimeDB Application Workflow Preview</b>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
The above illustrates the workflow when using SpacetimeDB.
|
||||
|
||||
- All client-side reads happen with the data view that is cached locally.
|
||||
|
||||
- Client-side subscriptions tell the server what data client cares about and wants to be synced within its data view. Changes to data will be pushed by the server to the client cache.
|
||||
|
||||
- RLS filters restrict the data view server-side before subscriptions are evaluated. These filters can be used for access control or client scoping.
|
||||
|
||||
- Reducers are effectively async RPC's. The request is sent off and if the results of that reducer makes changes to data, it will be written to the database directly. As a result of that, if those changes make it through the two layers above, then the client will see the result when it queries its local cache.
|
||||
|
||||
## State Mirroring
|
||||
|
||||
SpacetimeDB can generate client code in a [variety of languages](./00300-language-support.md). This creates a client library custom-designed to talk to your database. It provides easy-to-use interfaces for connecting to the database and submitting requests. It can also **automatically mirror state** from your database to client applications.
|
||||
|
||||
You write SQL queries specifying what information a client is interested in -- for instance, the terrain and items near a player's avatar. SpacetimeDB will generate types in your client language for the relevant tables, and feed clients a stream of live updates whenever the database state changes. Note that this is a **read-only** mirror -- the only way to change the database is to submit requests, which are validated on the server.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: The Zen of SpacetimeDB
|
||||
slug: /intro/zen
|
||||
---
|
||||
|
||||
SpacetimeDB is built on 5 core principles. As you embrace these simple principles, you will find **your troubles simply melt away**. These principles guide both how we develop SpacetimeDB and how you should think about building applications with it.
|
||||
|
||||
## Everything is a Table
|
||||
|
||||
Your entire application state lives in tables. Users, messages, game entities, sessions—all tables. There's no separate cache layer, no Redis, no in-memory state that needs to be synchronized with a database. The database *is* your state. All of your state.
|
||||
|
||||
This simplifies your mental model dramatically and it makes the impossible possible. SpacetimeDB can hot-swap server code without disconnecting clients!
|
||||
|
||||
When you need to store something, you define a table. When you need to query something, you query a table. When you need to update something, you update a table. When you want to restrict who can read data, you create [a table](../../00200-core-concepts/00200-functions/00500-views.md).
|
||||
|
||||
```
|
||||
Traditional stack: SpacetimeDB:
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Application │ │ │
|
||||
├─────────────────┤ │ │
|
||||
│ Cache │ → │ Tables │
|
||||
├─────────────────┤ │ │
|
||||
│ Database │ │ │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Everything is Persistent
|
||||
|
||||
SpacetimeDB persists everything by default, including the full history of any rows that have ever changed.
|
||||
|
||||
You will ask, does everything need to be persistent? Won't that be a lot of data? Well, you would be surprised! For example, updating 1 million player transforms 10 times per second for a year uses roughly 10 petabytes of data, uncompressed. SpacetimeDB can compress that sort of data by about 5-10x, meaning that keeping every position for every player for a game with a million concurrent players uses only about 1-2 petabytes per year. Storing that much data in Amazon S3 would only cost you between $2,300 and $5,600 per month. A fraction of the cost of a single engineer or data scientist!
|
||||
|
||||
You can of course choose to delete the historical data, but it should be **your choice** to delete data, not the database's. SpacetimeDB gives you that choice.
|
||||
|
||||
Won't it be slow to persist everything? No. SpacetimeDB is designed so that persistence guarantees only ever increase latency and never decrease throughput! Modern SSDs can write upwards of 15 GB/s of data to disk. DRAM can only do about 4x more. Let's actually use that Samsung-given bandwidth.
|
||||
|
||||
SpacetimeDB holds all your data in memory for blazing-fast access, but automatically persists everything to disk. You get the speed of in-memory computing with the durability of a traditional database.
|
||||
|
||||
You will be tempted to ask for "ephemeral state". This is a mistake. Persistent everything allows your app to recover to the *exact* state it was in. In principle, you could even debug your production app in the state it was in in the past with a time-traveling debugger.
|
||||
|
||||
Write your code as if memory were infinite and permanent. Insert rows freely. Query without fear. SpacetimeDB handles the persistence, you handle the logic.
|
||||
|
||||
## Everything is Real-Time
|
||||
|
||||
Think of your client as a **replica** of your server. When you subscribe to data, SpacetimeDB mirrors that data to your client and keeps it synchronized automatically. You don't poll. You don't fetch. You subscribe, and the data flows.
|
||||
|
||||
> "The data must flow." - Tyler
|
||||
|
||||
```typescript
|
||||
// Subscribe once
|
||||
const [messages] = useTable(tables.message);
|
||||
|
||||
// messages updates automatically when the server state changes
|
||||
// No polling. No refetching. Just reactive data.
|
||||
```
|
||||
|
||||
This changes how you think about client-server communication. Stop thinking in terms of requests and responses. Think in terms of **synchronized state** updating in real-time.
|
||||
|
||||
Your users should never click a refresh button.
|
||||
|
||||
## Everything is Transactional
|
||||
|
||||
Every reducer runs in a transaction. They are atomic. They either fully complete or don't run at all. If something goes wrong, just throw an error (or return `Err`). All your changes roll back automatically. No partial updates. No corrupted state. No cleanup code.
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
fn transfer_funds(ctx: &ReducerContext, from: u64, to: u64, amount: u64) -> Result<(), String> {
|
||||
let sender = ctx.db.account().id().find(from).ok_or("Sender not found")?;
|
||||
if sender.balance < amount {
|
||||
return Err("Insufficient funds".to_string()); // Everything rolls back
|
||||
}
|
||||
// ... rest of transfer
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
This means you can write your business logic boldly. Try things. If they fail, the database remains consistent.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Even access control is programmable. While SpacetimeDB provides sensible defaults (public vs. private tables), you can implement any access pattern you can express in code.
|
||||
|
||||
Including the meta permissions to manage and control the application's deployment itself.
|
||||
|
||||
> "Enterprise clients require increasingly granular permissions, fractal-like in nature." - Tyler
|
||||
|
||||
All programmable means all powerful.
|
||||
|
||||
Never settle for less than Turing complete.
|
||||
|
||||
---
|
||||
|
||||
## The Result
|
||||
|
||||
When you embrace these principles, building real-time applications becomes remarkably simple:
|
||||
|
||||
- **No backend servers to deploy** - your logic runs in the database
|
||||
- **No caching layer to manage** - the database is already in memory
|
||||
- **No sync code to write** - subscriptions handle it automatically
|
||||
- **No rollback logic to maintain** - transactions handle it automatically
|
||||
- **No limitations on your logic** - it's just code
|
||||
|
||||
This is the Zen of SpacetimeDB: a simpler way to build and live.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Language Support
|
||||
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:
|
||||
|
||||
- **[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)
|
||||
|
||||
## Client SDKs
|
||||
|
||||
**Clients** are applications that connect to SpacetimeDB databases. The `spacetime` CLI tool can automatically generate type-safe client code for your database.
|
||||
|
||||
- **[Rust](../../00200-core-concepts/00600-client-sdk-languages/00500-rust-reference.md)** - [(Quickstart)](../00200-quickstarts/00500-rust.md)
|
||||
- **[C#](../../00200-core-concepts/00600-client-sdk-languages/00600-csharp-reference.md)** - [(Quickstart)](../00200-quickstarts/00600-c-sharp.md)
|
||||
- **[TypeScript](../../00200-core-concepts/00600-client-sdk-languages/00700-typescript-reference.md)** - [(Quickstart)](../00200-quickstarts/00400-typescript.md)
|
||||
- **[Unreal Engine](../../00200-core-concepts/00600-client-sdk-languages/00800-unreal-reference.md)** - C++ and Blueprint support [(Tutorial)](../00300-tutorials/00400-unreal-tutorial/00200-part-1.md)
|
||||
|
||||
### Unity
|
||||
|
||||
SpacetimeDB was designed first and foremost as the backend for multiplayer Unity games. The C# SDK integrates seamlessly with Unity projects. Learn more in the [SpacetimeDB Unity Tutorial](../00300-tutorials/00300-unity-tutorial/00200-part-1.md).
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
---
|
||||
title: Key Architecture
|
||||
slug: /intro/key-architecture
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Host
|
||||
|
||||
A SpacetimeDB **host** is a server that hosts [databases](#database). You can run your own host, or use the SpacetimeDB maincloud. Many databases can run on a single host.
|
||||
|
||||
## Database
|
||||
|
||||
A SpacetimeDB **database** is an application that runs on a [host](#host).
|
||||
|
||||
A database exports [tables](#table), which store data, and [reducers](#reducer), which allow [clients](#client) to make requests.
|
||||
|
||||
A database's schema and business logic is specified by a piece of software called a **module**. Modules can be written in C#, Rust or TypeScript.
|
||||
|
||||
(Technically, a SpacetimeDB module is a [WebAssembly module](https://developer.mozilla.org/en-US/docs/WebAssembly) or JavaScript bundle, that imports a specific low-level [WebAssembly ABI](../../00300-resources/00200-reference/00300-internals/00100-module-abi-reference.md) and exports a small number of special functions. However, the SpacetimeDB [server-side libraries](../../00200-core-concepts/00100-databases.md) hide these low-level details. As a developer, writing a module is mostly like writing any other C# or Rust application, except for the fact that a [special CLI tool](https://spacetimedb.com/install) is used to deploy the application.)
|
||||
|
||||
## Table
|
||||
|
||||
A SpacetimeDB **table** is a SQL database table. Tables are declared in a module's native language. For instance, in C#, a table is declared like so:
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```typescript
|
||||
import { table, t } from 'spacetimedb/server';
|
||||
|
||||
const players = table(
|
||||
{ name: 'players', public: true },
|
||||
{
|
||||
id: t.u64().primaryKey(),
|
||||
name: t.string(),
|
||||
age: t.u32(),
|
||||
user: t.identity(),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.Table(Name = "Player", Public = true)]
|
||||
public partial struct Player
|
||||
{
|
||||
[SpacetimeDB.PrimaryKey]
|
||||
uint playerId;
|
||||
string name;
|
||||
uint age;
|
||||
Identity user;
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
```rust
|
||||
#[spacetimedb::table(name = players, public)]
|
||||
pub struct Player {
|
||||
#[primary_key]
|
||||
id: u64,
|
||||
name: String,
|
||||
age: u32,
|
||||
user: Identity,
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
The contents of a table can be read and updated by [reducers](#reducer).
|
||||
Tables marked `public` can also be read by [clients](#client).
|
||||
|
||||
## Reducer
|
||||
|
||||
A **reducer** is a function exported by a [database](#database).
|
||||
Connected [clients](../../00200-core-concepts/00600-client-sdk-languages.md) can call reducers to interact with the database.
|
||||
This is a form of [remote procedure call](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
A reducer can be written in a TypeScript module like so:
|
||||
|
||||
```typescript
|
||||
spacetimedb.reducer('set_player_name', { id: t.u64(), name: t.string() }, (ctx, { id, name }) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
And a TypeScript [client](#client) can call that reducer:
|
||||
|
||||
```typescript
|
||||
function main() {
|
||||
// ...setup code, then...
|
||||
ctx.reducers.setPlayerName(57n, "Marceline");
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
A reducer can be written in C# like so:
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.Reducer]
|
||||
public static void SetPlayerName(ReducerContext ctx, uint playerId, string name)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
And a C# [client](#client) can call that reducer:
|
||||
|
||||
```cs
|
||||
void Main() {
|
||||
// ...setup code, then...
|
||||
Connection.Reducer.SetPlayerName(57, "Marceline");
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
A reducer can be written in Rust like so:
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn set_player_name(ctx: &spacetimedb::ReducerContext, id: u64, name: String) -> Result<(), String> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
And a Rust [client](#client) can call that reducer:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// ...setup code, then...
|
||||
ctx.reducers.set_player_name(57, "Marceline".into());
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
These look mostly like regular function calls, but under the hood,
|
||||
the client sends a request over the internet, which the database processes and responds to.
|
||||
|
||||
The `ReducerContext` is a reducer's only mandatory parameter
|
||||
and includes information about the caller's [identity](#identity).
|
||||
This can be used to authenticate the caller.
|
||||
|
||||
Reducers are run in their own separate and atomic [database transactions](https://en.wikipedia.org/wiki/Database_transaction).
|
||||
When a reducer completes successfully, the changes the reducer has made,
|
||||
such as inserting a table row, are _committed_ to the database.
|
||||
However, if the reducer instead returns an error, or throws an exception,
|
||||
the database will instead reject the request and _revert_ all those changes.
|
||||
That is, reducers and transactions are all-or-nothing requests.
|
||||
It's not possible to keep the first half of a reducer's changes and discard the last.
|
||||
|
||||
Transactions are only started by requests from outside the database.
|
||||
When a reducer calls another reducer directly, as in the example below,
|
||||
the changes in the called reducer does not happen in its own child transaction.
|
||||
Instead, when the nested reducer gracefully errors,
|
||||
and the overall reducer completes successfully,
|
||||
the changes in the nested one are still persisted.
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
```typescript
|
||||
spacetimedb.reducer('hello', (ctx) => {
|
||||
try {
|
||||
world(ctx);
|
||||
} catch {
|
||||
otherChanges(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
const world = spacetimedb.reducer('world', (ctx) => {
|
||||
clearAllTables(ctx);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
While SpacetimeDB doesn't support nested transactions,
|
||||
a reducer can [schedule another reducer](../../00200-core-concepts/00300-tables/00500-schedule-tables.md) to run at an interval,
|
||||
or at a specific time.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.Reducer]
|
||||
public static void Hello(ReducerContext ctx)
|
||||
{
|
||||
if(!World(ctx))
|
||||
{
|
||||
OtherChanges(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
[SpacetimeDB.Reducer]
|
||||
public static void World(ReducerContext ctx)
|
||||
{
|
||||
ClearAllTables(ctx);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
While SpacetimeDB doesn't support nested transactions,
|
||||
a reducer can [schedule another reducer](../../00200-core-concepts/00300-tables/00500-schedule-tables.md) to run at an interval,
|
||||
or at a specific time.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn hello(ctx: &spacetimedb::ReducerContext) -> Result<(), String> {
|
||||
if world(ctx).is_err() {
|
||||
other_changes(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn world(ctx: &spacetimedb::ReducerContext) -> Result<(), String> {
|
||||
clear_all_tables(ctx);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See [Reducers](../../00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md) for more details about reducers.
|
||||
|
||||
## Procedure
|
||||
|
||||
A **procedure** is a function exported by a [database](#database), similar to a [reducer](#reducer).
|
||||
Connected [clients](#client) can call procedures.
|
||||
Procedures can perform additional operations not possible in reducers, including making HTTP requests to external services.
|
||||
However, procedures don't automatically run in database transactions,
|
||||
and must manually open and commit a transaction in order to read from or modify the database state.
|
||||
|
||||
Procedures are currently in beta, and their API may change in upcoming SpacetimeDB releases.
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
A procedure can be defined in a TypeScript module:
|
||||
|
||||
```typescript
|
||||
spacetimedb.procedure("make_request", t.string(), ctx => {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
And a TypeScript [client](#client) can call that procedure:
|
||||
|
||||
```typescript
|
||||
ctx.procedures.makeRequest();
|
||||
```
|
||||
|
||||
A TypeScript [client](#client) can also register a callback to run when a procedure call finishes, which will be invoked with that procedure's return value:
|
||||
|
||||
```typescript
|
||||
ctx.procedures.makeRequest().then(
|
||||
res => console.log(`Procedure make_request returned ${res}`),
|
||||
err => console.error(`Procedure make_request failed! ${err}`),
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
C# modules currently cannot define procedures. Support for defining procedures in C# modules will be released shortly.
|
||||
|
||||
A C# [client](#client) can call a procedure defined by a Rust or TypeScript module:
|
||||
|
||||
```csharp
|
||||
void Main()
|
||||
{
|
||||
// ...setup code, then...
|
||||
ctx.Procedures.MakeRequest();
|
||||
}
|
||||
```
|
||||
|
||||
A C# [client](#client) can also register a callback to run when a procedure call finishes, which will be invoked with that procedure's return value:
|
||||
|
||||
```csharp
|
||||
void Main()
|
||||
{
|
||||
// ...setup code, then...
|
||||
ctx.Procedures.MakeRequestThen((ctx, res) =>
|
||||
{
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
Log.Debug($"Procedure `make_request` returned {res.Value!}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Procedure `make_request` failed: {res.Error!}");
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
Because procedures are unstable, Rust modules that define them must opt in to the `unstable` feature in their `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
spacetimedb = { version = "1.x", features = ["unstable"] }
|
||||
```
|
||||
|
||||
Then, that module can define a procedure:
|
||||
|
||||
```rust
|
||||
#[spacetimedb::procedure]
|
||||
pub fn make_request(ctx: &mut spacetimedb::ProcedureContext) -> String {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
And a Rust [client](#client) can call that procedure:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// ...setup code, then...
|
||||
ctx.procedures.make_request();
|
||||
}
|
||||
```
|
||||
|
||||
A Rust [client](#client) can also register a callback to run when a procedure call finishes, which will be invoked with that procedure's return value:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// ...setup code, then...
|
||||
ctx.procedures.make_request_then(|ctx, res| {
|
||||
match res {
|
||||
Ok(string) => log::info!("Procedure `make_request` returned {string}"),
|
||||
Err(e) => log::error!("Procedure `make_request` failed! {e:?}"),
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="cpp" label="Unreal C++">
|
||||
|
||||
An Unreal C++ [client](#client) can call a procedure defined by a Rust or TypeScript module:
|
||||
|
||||
```cpp
|
||||
{
|
||||
...
|
||||
// Call the procedure without a callback
|
||||
Context.Procedures->MakeRequest({});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
An Unreal C++ [client](#client) can also register a callback to run when a procedure call finishes, which will be invoked with that procedure's return value:
|
||||
|
||||
```cpp
|
||||
{
|
||||
...
|
||||
FOnMakeRequestComplete Callback;
|
||||
BIND_DELEGATE_SAFE(Callback, this, AGameManager, OnMakeRequestComplete);
|
||||
Context.Procedures->MakeRequest(Callback);
|
||||
}
|
||||
|
||||
// Make sure to mark any callback functions as UFUNCTION() or they will not be executed
|
||||
void AGameManager::OnMakeRequestComplete(const FProcedureEventContext& Context, const FString& Result, bool bSuccess)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Procedure `MakeRequest` returned %s"), *Result);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</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 also register a callback to run when a procedure call finishes, which will be invoked with that procedure's return value:
|
||||
|
||||

|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
See [Procedures](../../00200-core-concepts/00200-functions/00400-procedures.md) for more details about procedures.
|
||||
|
||||
## View
|
||||
|
||||
A **view** is a read-only function exported by a [database](#database) that computes and returns results from tables. Unlike [reducers](#reducer), views do not modify database state - they only query and return data. Views are useful for computing derived data, aggregations, or joining multiple tables before sending results to clients.
|
||||
|
||||
Views must be declared as `public` and accept only a context parameter. They can return either a single row or multiple rows. Like tables, views can be subscribed to and automatically update when their underlying data changes.
|
||||
|
||||
<Tabs groupId="syntax" queryString>
|
||||
<TabItem value="typescript" label="TypeScript">
|
||||
|
||||
A view can be written in a TypeScript module like so:
|
||||
|
||||
```typescript
|
||||
spacetimedb.view(
|
||||
{ name: 'my_player', public: true },
|
||||
t.option(players.row()),
|
||||
(ctx) => {
|
||||
const row = ctx.db.players.identity.find(ctx.sender);
|
||||
return row ?? null;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
A view can be written in C# like so:
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.View(Name = "MyPlayer", Public = true)]
|
||||
public static Player? MyPlayer(ViewContext ctx)
|
||||
{
|
||||
return ctx.Db.Player.Identity.Find(ctx.Sender) as Player;
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
A view can be written in Rust like so:
|
||||
|
||||
```rust
|
||||
#[spacetimedb::view(name = my_player, public)]
|
||||
fn my_player(ctx: &spacetimedb::ViewContext) -> Option<Player> {
|
||||
ctx.db.player().identity().find(ctx.sender)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Views can be queried and subscribed to using SQL:
|
||||
|
||||
```sql
|
||||
SELECT * FROM my_player;
|
||||
```
|
||||
|
||||
See [Views](../../00200-core-concepts/00200-functions/00500-views.md) for more details about views.
|
||||
|
||||
## Client
|
||||
|
||||
A **client** is an application that connects to a [database](#database). A client logs in using an [identity](#identity) and receives an [connection id](#connectionid) to identify the connection. After that, it can call [reducers](#reducer) and query public [tables](#table).
|
||||
|
||||
Clients are written using the [client-side SDKs](../../00200-core-concepts/00600-client-sdk-languages.md). The `spacetime` CLI tool allows automatically generating code that works with the client-side SDKs to talk to a particular database.
|
||||
|
||||
Clients are regular software applications that developers can choose how to deploy (through Steam, app stores, package managers, or any other software deployment method, depending on the needs of the application.)
|
||||
|
||||
## Identity
|
||||
|
||||
A SpacetimeDB `Identity` identifies someone interacting with a database. It is a long lived, public, globally valid identifier that will always refer to the same end user, even across different connections.
|
||||
|
||||
A user's `Identity` is attached to every [reducer call](#reducer) they make, and you can use this to decide what they are allowed to do.
|
||||
|
||||
Modules themselves also have Identities. When you `spacetime publish` a module, it will automatically be issued an `Identity` to distinguish it from other modules. Your client application will need to provide this `Identity` when connecting to the [host](#host).
|
||||
|
||||
Identities are issued using the [OpenID Connect](https://openid.net/developers/how-connect-works/) specification. Database developers are responsible for issuing Identities to their end users. OpenID Connect lets users log in to these accounts through standard services like Google and Facebook.
|
||||
|
||||
Specifically, an identity is derived from the issuer and subject fields of a [JSON Web Token (JWT)](https://jwt.io/) hashed together. The psuedocode for this is as follows:
|
||||
|
||||
```python
|
||||
def identity_from_claims(issuer: str, subject: str) -> [u8; 32]:
|
||||
hash1: [u8; 32] = blake3_hash(issuer + "|" + subject)
|
||||
id_hash: [u8; 26] = hash1[:26]
|
||||
checksum_hash: [u8; 32] = blake3_hash([
|
||||
0xC2,
|
||||
0x00,
|
||||
*id_hash
|
||||
])
|
||||
identity_big_endian_bytes: [u8; 32] = [
|
||||
0xC2,
|
||||
0x00,
|
||||
*checksum_hash[:4],
|
||||
*id_hash
|
||||
]
|
||||
return identity_big_endian_bytes
|
||||
```
|
||||
|
||||
You can obtain a JWT from our turnkey identity provider [SpacetimeAuth](../../00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md), or you can get one from any OpenID Connect compliant identity provider.
|
||||
|
||||
## ConnectionId
|
||||
|
||||
A `ConnectionId` identifies client connections to a SpacetimeDB database.
|
||||
|
||||
A user has a single [`Identity`](#identity), but may open multiple connections to your database. Each of these will receive a unique `ConnectionId`.
|
||||
|
||||
## Energy
|
||||
|
||||
**Energy** is the currency used to pay for data storage and compute operations in a SpacetimeDB host.
|
||||
|
||||
<!-- TODO(1.0): Rewrite this section after finalizing energy SKUs. -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: FAQ
|
||||
slug: /intro/faq
|
||||
---
|
||||
|
||||
|
||||
1. What is SpacetimeDB?
|
||||
It's a cloud platform within a database that's fast enough to run real-time applications.
|
||||
|
||||
1. How do I use SpacetimeDB?
|
||||
Install the `spacetime` command line tool, choose your favorite language, import the SpacetimeDB library, write your module, compile it to WebAssembly, and upload it to the SpacetimeDB cloud platform. Once it's uploaded you can call functions directly on your application and subscribe to changes in application state.
|
||||
|
||||
1. How do I get/install SpacetimeDB?
|
||||
Just install our command line tool and then upload your application to the cloud.
|
||||
|
||||
1. How do I create a new database with SpacetimeDB?
|
||||
Follow our [Quick Start](./00100-getting-started.md) guide!
|
||||
|
||||
1. How do I create a Unity game with SpacetimeDB?
|
||||
Follow our [Unity Tutorial](../00300-tutorials/00300-unity-tutorial/index.md) guide!
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Getting Started",
|
||||
"collapsed": true
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: React Quickstart
|
||||
sidebar_label: React
|
||||
slug: /quickstarts/react
|
||||
hide_table_of_contents: true
|
||||
pagination_next: intro/quickstarts/typescript
|
||||
---
|
||||
|
||||
import { InstallCardLink } from "@site/src/components/InstallCardLink";
|
||||
import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps";
|
||||
|
||||
|
||||
Get a SpacetimeDB React app running in under 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+ installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a SpacetimeDB module and React client.
|
||||
|
||||
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the React development server.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template react-ts my-spacetime-app
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Open your app">
|
||||
<StepText>
|
||||
Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
|
||||
|
||||
The template includes a basic React app connected to SpacetimeDB.
|
||||
</StepText>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `client/src/App.tsx` to build your UI.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ └── src/
|
||||
│ └── index.ts # Server-side logic
|
||||
├── client/ # React frontend
|
||||
│ └── src/
|
||||
│ ├── App.tsx
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── package.json
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```typescript
|
||||
import { schema, table, t } from 'spacetimedb/server';
|
||||
|
||||
export const spacetimedb = schema(
|
||||
table(
|
||||
{ name: 'person', public: true },
|
||||
{
|
||||
name: t.string(),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => {
|
||||
ctx.db.person.insert({ name });
|
||||
});
|
||||
|
||||
spacetimedb.reducer('say_hello', (ctx) => {
|
||||
for (const person of ctx.db.person.iter()) {
|
||||
console.info(`Hello, ${person.name}!`);
|
||||
}
|
||||
console.info('Hello, World!');
|
||||
});
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call <database-name> add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql <database-name> "SELECT * FROM person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call <database-name> say_hello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs <database-name>
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00700-typescript-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Vue Quickstart
|
||||
sidebar_label: Vue
|
||||
slug: /quickstarts/vue
|
||||
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 Vue app running in under 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+ installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Vue client.
|
||||
|
||||
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Vue development server.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template vue-ts
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Open your app">
|
||||
<StepText>
|
||||
Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
|
||||
|
||||
The template includes a basic Vue app connected to SpacetimeDB.
|
||||
</StepText>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.vue` to build your UI.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ └── src/
|
||||
│ └── index.ts # Server-side logic
|
||||
├── src/ # Vue frontend
|
||||
│ ├── App.vue
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── package.json
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```typescript
|
||||
import { schema, table, t } from 'spacetimedb/server';
|
||||
|
||||
export const spacetimedb = schema(
|
||||
table(
|
||||
{ name: 'person', public: true },
|
||||
{
|
||||
name: t.string(),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => {
|
||||
ctx.db.person.insert({ name });
|
||||
});
|
||||
|
||||
spacetimedb.reducer('say_hello', (ctx) => {
|
||||
for (const person of ctx.db.person.iter()) {
|
||||
console.info(`Hello, ${person.name}!`);
|
||||
}
|
||||
console.info('Hello, World!');
|
||||
});
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call <database-name> add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql <database-name> "SELECT * FROM person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call <database-name> say_hello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs <database-name>
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00700-typescript-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Svelte Quickstart
|
||||
sidebar_label: Svelte
|
||||
slug: /quickstarts/svelte
|
||||
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 Svelte app running in under 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+ installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Svelte client.
|
||||
|
||||
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Svelte development server.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template svelte-ts
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Open your app">
|
||||
<StepText>
|
||||
Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
|
||||
|
||||
The template includes a basic Svelte app connected to SpacetimeDB.
|
||||
</StepText>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.svelte` to build your UI.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ └── src/
|
||||
│ └── index.ts # Server-side logic
|
||||
├── src/ # Svelte frontend
|
||||
│ ├── App.svelte
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── package.json
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```typescript
|
||||
import { schema, table, t } from 'spacetimedb/server';
|
||||
|
||||
export const spacetimedb = schema(
|
||||
table(
|
||||
{ name: 'person', public: true },
|
||||
{
|
||||
name: t.string(),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => {
|
||||
ctx.db.person.insert({ name });
|
||||
});
|
||||
|
||||
spacetimedb.reducer('say_hello', (ctx) => {
|
||||
for (const person of ctx.db.person.iter()) {
|
||||
console.info(`Hello, ${person.name}!`);
|
||||
}
|
||||
console.info('Hello, World!');
|
||||
});
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call <database-name> add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql <database-name> "SELECT * FROM person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call <database-name> say_hello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs <database-name>
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00700-typescript-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: TypeScript Quickstart
|
||||
sidebar_label: TypeScript
|
||||
slug: /quickstarts/typescript
|
||||
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 TypeScript app running in under 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+ installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module.
|
||||
|
||||
This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template basic-ts my-spacetime-app
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/src/index.ts` to add tables and reducers. Use the generated bindings in `client/src/module_bindings/` to build your client.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ └── src/
|
||||
│ └── index.ts # Server-side logic
|
||||
├── client/ # Client application
|
||||
│ └── src/
|
||||
│ ├── index.ts
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── package.json
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```typescript
|
||||
import { schema, table, t } from 'spacetimedb/server';
|
||||
|
||||
export const spacetimedb = schema(
|
||||
table(
|
||||
{ name: 'person' },
|
||||
{
|
||||
name: t.string(),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => {
|
||||
ctx.db.person.insert({ name });
|
||||
});
|
||||
|
||||
spacetimedb.reducer('say_hello', (ctx) => {
|
||||
for (const person of ctx.db.person.iter()) {
|
||||
console.info(`Hello, ${person.name}!`);
|
||||
}
|
||||
console.info('Hello, World!');
|
||||
});
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call <database-name> add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql <database-name> "SELECT * FROM person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call <database-name> say_hello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs <database-name>
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [TypeScript SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00700-typescript-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Rust Quickstart
|
||||
sidebar_label: Rust
|
||||
slug: /quickstarts/rust
|
||||
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 Rust app running in under 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Rust](https://www.rust-lang.org/tools/install) installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a Rust SpacetimeDB module.
|
||||
|
||||
This will start the local SpacetimeDB server, compile and publish your module, and generate Rust client bindings.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template basic-rs my-spacetime-app
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/src/lib.rs` to add tables and reducers. Use the generated bindings in `client/src/module_bindings/` to build your client.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ ├── Cargo.toml
|
||||
│ └── src/
|
||||
│ └── lib.rs # Server-side logic
|
||||
├── client/ # Client application
|
||||
│ ├── Cargo.toml
|
||||
│ └── src/
|
||||
│ ├── main.rs
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── README.md
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/src/lib.rs` to see the module code. The template includes a `Person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```rust
|
||||
use spacetimedb::{ReducerContext, Table};
|
||||
|
||||
#[spacetimedb::table(name = person, public)]
|
||||
pub struct Person {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn add(ctx: &ReducerContext, name: String) {
|
||||
ctx.db.person().insert(Person { name });
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn say_hello(ctx: &ReducerContext) {
|
||||
for person in ctx.db.person().iter() {
|
||||
log::info!("Hello, {}!", person.name);
|
||||
}
|
||||
log::info!("Hello, World!");
|
||||
}
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call my-spacetime-app add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql my-spacetime-app "SELECT * FROM person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call my-spacetime-app say_hello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs my-spacetime-app
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [Rust SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00500-rust-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: C# Quickstart
|
||||
sidebar_label: C#
|
||||
slug: /quickstarts/c-sharp
|
||||
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
|
||||
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) installed
|
||||
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
|
||||
|
||||
<InstallCardLink />
|
||||
|
||||
---
|
||||
|
||||
<StepByStep>
|
||||
<Step title="Install .NET WASI workload">
|
||||
<StepText>
|
||||
SpacetimeDB C# modules compile to WebAssembly using the WASI experimental workload.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
dotnet workload install wasi-experimental
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Create your project">
|
||||
<StepText>
|
||||
Run the `spacetime dev` command to create a new project with a C# SpacetimeDB module.
|
||||
|
||||
This will start the local SpacetimeDB server, compile and publish your module, and generate C# client bindings.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
spacetime dev --template basic-cs my-spacetime-app
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Explore the project structure">
|
||||
<StepText>
|
||||
Your project contains both server and client code.
|
||||
|
||||
Edit `spacetimedb/Lib.cs` to add tables and reducers. Use the generated bindings in the client project.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```
|
||||
my-spacetime-app/
|
||||
├── spacetimedb/ # Your SpacetimeDB module
|
||||
│ ├── StdbModule.csproj
|
||||
│ └── Lib.cs # Server-side logic
|
||||
├── client/ # Client application
|
||||
│ ├── Client.csproj
|
||||
│ └── Program.cs
|
||||
│ └── module_bindings/ # Auto-generated types
|
||||
└── README.md
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Understand tables and reducers">
|
||||
<StepText>
|
||||
Open `spacetimedb/Lib.cs` to see the module code. The template includes a `Person` table and two reducers: `Add` to insert a person, and `SayHello` to greet everyone.
|
||||
|
||||
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```csharp
|
||||
using SpacetimeDB;
|
||||
|
||||
public static partial class Module
|
||||
{
|
||||
[SpacetimeDB.Table(Name = "Person", Public = true)]
|
||||
public partial struct Person
|
||||
{
|
||||
public string Name;
|
||||
}
|
||||
|
||||
[SpacetimeDB.Reducer]
|
||||
public static void Add(ReducerContext ctx, string name)
|
||||
{
|
||||
ctx.Db.Person.Insert(new Person { Name = name });
|
||||
}
|
||||
|
||||
[SpacetimeDB.Reducer]
|
||||
public static void SayHello(ReducerContext ctx)
|
||||
{
|
||||
foreach (var person in ctx.Db.Person.Iter())
|
||||
{
|
||||
Log.Info($"Hello, {person.Name}!");
|
||||
}
|
||||
Log.Info("Hello, World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
|
||||
<Step title="Test with the CLI">
|
||||
<StepText>
|
||||
Use the SpacetimeDB CLI to call reducers and query your data directly.
|
||||
</StepText>
|
||||
<StepCode>
|
||||
```bash
|
||||
# Call the add reducer to insert a person
|
||||
spacetime call <database-name> Add Alice
|
||||
|
||||
# Query the person table
|
||||
spacetime sql <database-name> "SELECT * FROM Person"
|
||||
name
|
||||
---------
|
||||
"Alice"
|
||||
|
||||
# Call say_hello to greet everyone
|
||||
spacetime call <database-name> SayHello
|
||||
|
||||
# View the module logs
|
||||
spacetime logs <database-name>
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
|
||||
2025-01-13T12:00:00.000000Z INFO: Hello, World!
|
||||
```
|
||||
</StepCode>
|
||||
</Step>
|
||||
</StepByStep>
|
||||
|
||||
## Next steps
|
||||
|
||||
- See the [Chat App Tutorial](../00300-tutorials/00100-chat-app.md) for a complete example
|
||||
- Read the [C# SDK Reference](../../00200-core-concepts/00600-client-sdk-languages/00600-csharp-reference.md) for detailed API docs
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Quickstarts",
|
||||
"collapsed": false
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: 1 - Setup
|
||||
slug: /tutorials/unity/part-1
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||

|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
> A completed version of the game we'll create in this tutorial is available at:
|
||||
>
|
||||
> [https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio](https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio)
|
||||
|
||||
## Setting up the Tutorial Unity Project
|
||||
|
||||
In this section, we will guide you through the process of setting up a Unity Project that will serve as the starting point for our tutorial. By the end of this section, you will have a basic Unity project and be ready to implement the server functionality.
|
||||
|
||||
### Step 1: Create a Blank Unity Project
|
||||
|
||||
SpacetimeDB supports Unity version `2022.3.32f1` or later. See [the overview](.) for more information on specific supported versions.
|
||||
|
||||
Open Unity and create a new project by selecting "New" from the Unity Hub or going to **File -> New Project**.
|
||||
|
||||

|
||||
|
||||
:::warning
|
||||
**Make sure to choose the `Universal 2D` template for your new project.**
|
||||
:::
|
||||
|
||||
For `Project Name` use `blackholio`. For `Project Location` select a directory that you can navigate to via the CLI because we will need to do so in part 2.
|
||||
|
||||

|
||||
|
||||
Click "Create" to generate the blank project.
|
||||
|
||||
### Import the SpacetimeDB Unity SDK
|
||||
|
||||
Add the SpacetimeDB Unity Package using the Package Manager. Open the Package Manager window by clicking on Window -> Package Manager. Click on the + button in the top left corner of the window and select "Add package from git URL". Enter the following URL and click Add.
|
||||
|
||||
```bash
|
||||
https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git
|
||||
```
|
||||
|
||||
The SpacetimeDB Unity SDK provides helpful tools for integrating SpacetimeDB into Unity, including a network manager which will synchronize your Unity client's state with your SpacetimeDB database in accordance with your subscription queries.
|
||||
|
||||
### Create the GameManager Script
|
||||
|
||||
1. In the Unity **Project** window, go to the folder where you want to keep your scripts (e.g., `Scripts` folder).
|
||||
2. **Right-click** in the folder, then select `Create > C# Script` or in Unity 6 `MonoBehavior Script`.
|
||||
3. Name the script `GameManager`.
|
||||
|
||||
The `GameManager` script will be where we will put the high level initialization and coordination logic for our game.
|
||||
|
||||
### Add the GameManager to the Scene
|
||||
|
||||
1. **Create an Empty GameObject**:
|
||||
- Go to the top menu and select **GameObject > Create Empty**.
|
||||
- Alternatively, right-click in the **Hierarchy** window and select **Create Empty**.
|
||||
|
||||
2. **Rename the GameObject**:
|
||||
- In the **Inspector**, click on the GameObject’s name at the top and rename it to `GameManager`.
|
||||
|
||||
3. **Attach the GameManager Script**:
|
||||
- Drag and drop the `GameManager` script from the **Project** window onto the `GameManager` GameObject in the **Hierarchy** window.
|
||||
- Alternatively, in the **Inspector**, click **Add Component**, search for `GameManager`, and select it.
|
||||
|
||||
### Add the SpacetimeDB Network Manager
|
||||
|
||||
The `SpacetimeDBNetworkManager` is a simple script which hooks into the Unity `Update` loop in order to drive the sending and processing of messages between your client and SpacetimeDB. You don't have to interact with this script, but it must be present on a single GameObject which is in the scene in order for it to facilitate the processing of messages.
|
||||
|
||||
When you build a new connection to SpacetimeDB, that connection will be added to and managed by the `SpacetimeDBNetworkManager` automatically.
|
||||
|
||||
Click on the `GameManager` object in the scene and click **Add Component**. Search for and select the `SpacetimeDBNetworkManager` to add it to your `GameManager` object.
|
||||
|
||||
Our Unity project is all set up! If you press play, it will show a blank screen, but it should start the game without any errors. Now we're ready to get started on our SpacetimeDB server module, so we have something to connect to!
|
||||
|
||||
### Create the Server Module
|
||||
|
||||
We've now got the very basics set up. In [part 2](part-2) you'll learn the basics of how to create a SpacetimeDB server module and how to connect to it from your client.
|
||||
+682
@@ -0,0 +1,682 @@
|
||||
---
|
||||
title: 2 - Connecting to SpacetimeDB
|
||||
slug: /tutorials/unity/part-2
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 1](./00200-part-1.md).
|
||||
|
||||
## Project Structure
|
||||
|
||||
Now that we have our client project setup we can configure the module directory. Regardless of what language you choose, your module will always go into a `spacetimedb` directory within your client directory like this:
|
||||
|
||||
```
|
||||
blackholio/ # This is the directory for your Unity project lives
|
||||
├── Assembly-CSharp.csproj
|
||||
├── Assets/
|
||||
│ └── module_bindings/ # This directory contains the client logic to communicate with the module
|
||||
├── Library/
|
||||
├── ... # rest of the Unity files
|
||||
└── spacetimedb/ # This is where your server module lives
|
||||
```
|
||||
|
||||
Your `module_bindings` directory can go wherever you want as long as it is inside of `Assets/` in your Unity project. We'll configure this in a later step. For now we will create a new module in the `blackholio` directory which will generate the `spacetimedb` directory for us.
|
||||
|
||||
|
||||
## Create a Server Module
|
||||
|
||||
If you have not already installed the `spacetime` CLI, check out our [Getting Started](../../00100-getting-started/00100-getting-started.md) guide for instructions on how to install.
|
||||
|
||||
In the same directory that contains your `blackholio` project, run the following command to initialize the SpacetimeDB server module project with your desired language:
|
||||
|
||||
:::warning
|
||||
The `blackholio` directory specified here is the same `blackholio` directory you created during part 1.
|
||||
:::
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Run the following command to initialize the SpacetimeDB server module project with C# as the language:
|
||||
|
||||
```bash
|
||||
spacetime init --lang csharp --server-only blackholio
|
||||
```
|
||||
|
||||
This command creates a new folder named `spacetimedb` inside of your Unity project `blackholio` directory and sets up the SpacetimeDB server project with C# as the programming language.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
Run the following command to initialize the SpacetimeDB server module project with Rust as the language:
|
||||
|
||||
```bash
|
||||
spacetime init --lang rust --server-only blackholio
|
||||
```
|
||||
|
||||
This command creates a new folder named `spacetimedb` inside of your Unity project `blackholio` directory and sets up the SpacetimeDB server project with Rust as the programming language.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### SpacetimeDB Tables
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
In this section we'll be making some edits to the file `blackholio/spacetimedb/Lib.cs`. We recommend you open up this file in an IDE like VSCode or Rider.
|
||||
|
||||
**Important: Open the `blackholio/spacetimedb/Lib.cs` file and delete its contents. We will be writing it from scratch here.**
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
In this section we'll be making some edits to the file `blackholio/spacetimedb/src/lib.rs`. We recommend you open up this file in an IDE like VSCode or RustRover.
|
||||
|
||||
**Important: Open the `blackholio/spacetimedb/src/lib.rs` file and delete its contents. We will be writing it from scratch here.**
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
First we need to add some imports at the top of the file. Some will remain unused for now.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
**Copy and paste into Lib.cs:**
|
||||
|
||||
```csharp
|
||||
using SpacetimeDB;
|
||||
|
||||
public static partial class Module
|
||||
{
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
**Copy and paste into lib.rs:**
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
use spacetimedb::{rand::Rng, Identity, SpacetimeType, ReducerContext, ScheduleAt, Table, Timestamp};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
We are going to start by defining a SpacetimeDB _table_. A _table_ in SpacetimeDB is a relational database table which stores rows, similar to something you might find in SQL. SpacetimeDB tables differ from normal relational database tables in that they are stored fully in memory, are blazing fast to access, and are defined in your module code, rather than in SQL.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Each row in a SpacetimeDB table is associated with a `struct` type in C#.
|
||||
|
||||
Let's start by defining the `Config` table. This is a simple table which will store some metadata about our game's state. Add the following code inside the `Module` class in `Lib.cs`.
|
||||
|
||||
```csharp
|
||||
// We're using this table as a singleton, so in this table
|
||||
// there will only be one element where the `id` is 0.
|
||||
[Table(Name = "config", Public = true)]
|
||||
public partial struct Config
|
||||
{
|
||||
[PrimaryKey]
|
||||
public int id;
|
||||
public long world_size;
|
||||
}
|
||||
```
|
||||
|
||||
Let's break down this code. This defines a normal C# `struct` with two fields: `id` and `world_size`. We have added the `[Table(Name = "config", Public = true)]` attribute the struct. This attribute signals to SpacetimeDB that it should create a new SpacetimeDB table with the row type defined by the `Config` type's fields.
|
||||
|
||||
> Although we're using `lower_snake_case` for our column names to have consistent column names across languages in this tutorial, you can also use `camelCase` or `PascalCase` if you prefer. See [#2168](https://github.com/clockworklabs/SpacetimeDB/issues/2168) for more information.
|
||||
|
||||
The `Table` attribute takes two parameters, a `Name` which is the name of the table and what you will use to query the table in SQL, and a `Public` visibility modifier which ensures that the rows of this table are visible to everyone.
|
||||
|
||||
The `[PrimaryKey]` attribute, specifies that the `id` field should be used as the primary key of the table.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
Each row in a SpacetimeDB table is associated with a `struct` type in Rust.
|
||||
|
||||
Let's start by defining the `Config` table. This is a simple table which will store some metadata about our game's state. Add the following code to `lib.rs`.
|
||||
|
||||
```rust
|
||||
// We're using this table as a singleton, so in this table
|
||||
// there only be one element where the `id` is 0.
|
||||
#[spacetimedb::table(name = config, public)]
|
||||
pub struct Config {
|
||||
#[primary_key]
|
||||
pub id: i32,
|
||||
pub world_size: i64,
|
||||
}
|
||||
```
|
||||
|
||||
Let's break down this code. This defines a normal Rust `struct` with two fields: `id` and `world_size`. We have decorated the struct with the `spacetimedb::table` macro. This procedural Rust macro signals to SpacetimeDB that it should create a new SpacetimeDB table with the row type defined by the `Config` type's fields.
|
||||
|
||||
The `spacetimedb::table` macro takes two parameters, a `name` which is the name of the table and what you will use to query the table in SQL, and a `public` visibility modifier which ensures that the rows of this table are visible to everyone.
|
||||
|
||||
The `#[primary_key]` attribute, specifies that the `id` field should be used as the primary key of the table.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
> NOTE: The primary key of a row defines the "identity" of the row. A change to a row which doesn't modify the primary key is considered an update, but if you change the primary key, then you have deleted the old row and inserted a new one.
|
||||
|
||||
Learn more about defining tables, including indexes, constraints, and column types, in our [Tables documentation](../../../00200-core-concepts/00300-tables.md).
|
||||
|
||||
### Creating Entities
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Next, we're going to define a new `SpacetimeType` called `DbVector2` which we're going to use to store positions. The difference between a `[SpacetimeDB.Type]` and a `[SpacetimeDB.Table]` is that tables actually store data, whereas the deriving `SpacetimeType` just allows you to create a new column of that type in a SpacetimeDB table. Therefore, `DbVector2` is only a type, and does not define a table.
|
||||
|
||||
**Append to the bottom of Lib.cs:**
|
||||
|
||||
```csharp
|
||||
// This allows us to store 2D points in tables.
|
||||
[SpacetimeDB.Type]
|
||||
public partial struct DbVector2
|
||||
{
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public DbVector2(float x, float y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's create a few tables to represent entities in our game by adding the following to the end of the `Module` class.
|
||||
|
||||
```csharp
|
||||
[Table(Name = "entity", Public = true)]
|
||||
public partial struct Entity
|
||||
{
|
||||
[PrimaryKey, AutoInc]
|
||||
public int entity_id;
|
||||
public DbVector2 position;
|
||||
public int mass;
|
||||
}
|
||||
|
||||
[Table(Name = "circle", Public = true)]
|
||||
public partial struct Circle
|
||||
{
|
||||
[PrimaryKey]
|
||||
public int entity_id;
|
||||
[SpacetimeDB.Index.BTree]
|
||||
public int player_id;
|
||||
public DbVector2 direction;
|
||||
public float speed;
|
||||
public SpacetimeDB.Timestamp last_split_time;
|
||||
}
|
||||
|
||||
[Table(Name = "food", Public = true)]
|
||||
public partial struct Food
|
||||
{
|
||||
[PrimaryKey]
|
||||
public int entity_id;
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
Next, we're going to define a new `SpacetimeType` called `DbVector2` which we're going to use to store positions. The difference between a `#[derive(SpacetimeType)]` and a `#[spacetimedb(table)]` is that tables actually store data, whereas the deriving `SpacetimeType` just allows you to create a new column of that type in a SpacetimeDB table. Therefore, `DbVector2` is only a type, and does not define a table.
|
||||
|
||||
**Append to the bottom of lib.rs:**
|
||||
|
||||
```rust
|
||||
// This allows us to store 2D points in tables.
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub struct DbVector2 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
```
|
||||
|
||||
Let's create a few tables to represent entities in our game.
|
||||
|
||||
```rust
|
||||
#[spacetimedb::table(name = entity, public)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Entity {
|
||||
// The `auto_inc` attribute indicates to SpacetimeDB that
|
||||
// this value should be determined by SpacetimeDB on insert.
|
||||
#[auto_inc]
|
||||
#[primary_key]
|
||||
pub entity_id: i32,
|
||||
pub position: DbVector2,
|
||||
pub mass: i32,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(name = circle, public)]
|
||||
pub struct Circle {
|
||||
#[primary_key]
|
||||
pub entity_id: i32,
|
||||
#[index(btree)]
|
||||
pub player_id: i32,
|
||||
pub direction: DbVector2,
|
||||
pub speed: f32,
|
||||
pub last_split_time: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(name = food, public)]
|
||||
pub struct Food {
|
||||
#[primary_key]
|
||||
pub entity_id: i32,
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The first table we defined is the `entity` table. An entity represents an object in our game world. We have decided, for convenience, that all entities in our game should share some common fields, namely `position` and `mass`.
|
||||
|
||||
We can create different types of entities with additional data by creating new tables with additional fields that have an `entity_id` which references a row in the `entity` table.
|
||||
|
||||
We've created two types of entities in our game world: `Food`s and `Circle`s. `Food` does not have any additional fields beyond the attributes in the `entity` table, so the `food` table simply represents the set of `entity_id`s that we want to recognize as food.
|
||||
|
||||
The `Circle` table, however, represents an entity that is controlled by a player. We've added a few additional fields to a `Circle` like `player_id` so that we know which player that circle belongs to.
|
||||
|
||||
### Representing Players
|
||||
|
||||
Next, let's create a table to store our player data.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
```csharp
|
||||
[Table(Name = "player", Public = true)]
|
||||
public partial struct Player
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Identity identity;
|
||||
[Unique, AutoInc]
|
||||
public int player_id;
|
||||
public string name;
|
||||
}
|
||||
```
|
||||
|
||||
There are a few new concepts we should touch on. First of all, we are using the `[Unique]` attribute on the `player_id` field. This attribute adds a constraint to the table that ensures that only one row in the player table has a particular `player_id`. We are also using the `[AutoInc]` attribute on the `player_id` field, which indicates "this field should get automatically assigned an auto-incremented value".
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
```rust
|
||||
#[spacetimedb::table(name = player, public)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Player {
|
||||
#[primary_key]
|
||||
identity: Identity,
|
||||
#[unique]
|
||||
#[auto_inc]
|
||||
player_id: i32,
|
||||
name: String,
|
||||
}
|
||||
```
|
||||
|
||||
There's a few new concepts we should touch on. First of all, we are using the `#[unique]` attribute on the `player_id` field. This attribute adds a constraint to the table that ensures that only one row in the player table has a particular `player_id`.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
We also have an `identity` field which uses the `Identity` type. The `Identity` type is an identifier that SpacetimeDB uses to uniquely assign and authenticate SpacetimeDB users.
|
||||
|
||||
### Writing a Reducer
|
||||
|
||||
Next, we write our very first reducer. A reducer is a module function which can be called by clients. Let's write a simple debug reducer to see how they work.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
|
||||
Add this function to the `Module` class in `Lib.cs`:
|
||||
|
||||
```csharp
|
||||
[Reducer]
|
||||
public static void Debug(ReducerContext ctx)
|
||||
{
|
||||
Log.Info($"This reducer was called by {ctx.Sender}");
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn debug(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("This reducer was called by {}.", ctx.sender);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This reducer doesn't update any tables, it just prints out the `Identity` of the client that called it.
|
||||
|
||||
---
|
||||
|
||||
**SpacetimeDB Reducers**
|
||||
|
||||
"Reducer" is a term coined by Clockwork Labs that refers to a function which when executed "reduces" a set of inserts and deletes into the database state. The term derives from functional programming and is closely related to [similarly named concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#reducers) in other frameworks like React Redux. Reducers can be called remotely using the CLI, client SDK or can be scheduled to be called at some future time from another reducer call.
|
||||
|
||||
All reducers execute _transactionally_ and _atomically_, meaning that from within the reducer it will appear as though all changes are being applied to the database immediately, however from the outside changes made in a reducer will only be applied to the database once the reducer completes successfully. If you return an error from a reducer or panic within a reducer, all changes made to the database will be rolled back, as if the function had never been called. If you're unfamiliar with atomic transactions, it may not be obvious yet just how useful and important this feature is, but once you build a somewhat complex application it will become clear just how invaluable this feature is.
|
||||
|
||||
---
|
||||
|
||||
### Publishing the Module
|
||||
|
||||
Now that we have some basic functionality, let's publish the module to SpacetimeDB and call our debug reducer.
|
||||
|
||||
In a new terminal window, run a local version of SpacetimeDB with the command:
|
||||
|
||||
```sh
|
||||
spacetime start
|
||||
```
|
||||
|
||||
This following log output indicates that SpacetimeDB is successfully running on your machine.
|
||||
|
||||
```
|
||||
Starting SpacetimeDB listening on 127.0.0.1:3000
|
||||
```
|
||||
|
||||
Now that SpacetimeDB is running we can publish our module to the SpacetimeDB host. In a separate terminal window, navigate to the `blackholio/spacetimedb` directory.
|
||||
|
||||
If you are not already logged in to the `spacetime` CLI, run the `spacetime login` command to log in to your SpacetimeDB website account. Once you are logged in, run `spacetime publish --server local blackholio`. This will publish our Blackholio server logic to SpacetimeDB.
|
||||
|
||||
If the publish completed successfully, you will see something like the following in the logs:
|
||||
|
||||
```
|
||||
Build finished successfully.
|
||||
Uploading to local => http://127.0.0.1:3000
|
||||
Publishing module...
|
||||
Created new database with name: blackholio, identity: c200d2c69b4524292b91822afac8ab016c15968ac993c28711f68c6bc40b89d5
|
||||
```
|
||||
|
||||
> If you sign into `spacetime login` via GitHub, the token you get will be issued by `auth.spacetimedb.com`. This will also ensure that you can recover your identity in case you lose it. On the other hand, if you do `spacetime login --server-issued-login local`, you will get an identity which is issued directly by your local server. Do note, however, that `--server-issued-login` tokens are not recoverable if lost, and are only recognized by the server that issued them.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Next, use the `spacetime` command to call our newly defined `Debug` reducer:
|
||||
|
||||
```sh
|
||||
spacetime call --server local blackholio Debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
|
||||
Next, use the `spacetime` command to call our newly defined `debug` reducer:
|
||||
|
||||
```sh
|
||||
spacetime call --server local blackholio debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If the call completed successfully, that command will have no output, but we can see the debug logs by running:
|
||||
|
||||
```sh
|
||||
spacetime logs --server local blackholio
|
||||
```
|
||||
|
||||
You should see something like the following output:
|
||||
|
||||
```sh
|
||||
2025-01-09T16:08:38.144299Z INFO: spacetimedb: Creating table `circle`
|
||||
2025-01-09T16:08:38.144438Z INFO: spacetimedb: Creating table `config`
|
||||
2025-01-09T16:08:38.144451Z INFO: spacetimedb: Creating table `entity`
|
||||
2025-01-09T16:08:38.144470Z INFO: spacetimedb: Creating table `food`
|
||||
2025-01-09T16:08:38.144479Z INFO: spacetimedb: Creating table `player`
|
||||
2025-01-09T16:08:38.144841Z INFO: spacetimedb: Database initialized
|
||||
2025-01-09T16:08:47.306823Z INFO: src/lib.rs:68: This reducer was called by c200e1a6494dbeeb0bbf49590b8778abf94fae4ea26faf9769c9a8d69a3ec348.
|
||||
```
|
||||
|
||||
### Connecting our Client
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Next let's connect our client to our database. Let's start by modifying our `Debug` reducer. Rename the reducer to be called `Connect` and add `ReducerKind.ClientConnected` in parentheses after `SpacetimeDB.Reducer`. The end result should look like this:
|
||||
|
||||
```csharp
|
||||
[Reducer(ReducerKind.ClientConnected)]
|
||||
public static void Connect(ReducerContext ctx)
|
||||
{
|
||||
Log.Info($"{ctx.Sender} just connected.");
|
||||
}
|
||||
```
|
||||
|
||||
The `ReducerKind.ClientConnected` argument to the `SpacetimeDB.Reducer` attribute indicates to SpacetimeDB that this is a special reducer. This reducer is only ever called by SpacetimeDB itself when a client connects to your database.
|
||||
|
||||
> SpacetimeDB gives you the ability to define custom reducers that automatically trigger when certain events occur.
|
||||
>
|
||||
> - `ReducerKind.Init` - Called the first time you publish your module and anytime you clear the database with `spacetime publish --server local <name> --delete-data`.
|
||||
> - `ReducerKind.ClientConnected` - Called when a user connects to the SpacetimeDB database. Their identity can be found in the `Sender` value of the `ReducerContext`.
|
||||
> - `ReducerKind.ClientDisconnected` - Called when a user disconnects from the SpacetimeDB database.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
Next let's connect our client to our database. Let's start by modifying our `debug` reducer. Rename the reducer to be called `connect` and add `client_connected` in parentheses after `spacetimedb::reducer`. The end result should look like this:
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer(client_connected)]
|
||||
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("{} just connected.", ctx.sender);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
The `client_connected` argument to the `spacetimedb::reducer` macro indicates to SpacetimeDB that this is a special reducer. This reducer is only ever called by SpacetimeDB itself when a client connects to your database.
|
||||
|
||||
> SpacetimeDB gives you the ability to define custom reducers that automatically trigger when certain events occur.
|
||||
>
|
||||
> - `init` - Called the first time you publish your module and anytime you clear the database with `spacetime publish --server local <name> --delete-data`.
|
||||
> - `client_connected` - Called when a user connects to the SpacetimeDB database. Their identity can be found in the `sender` value of the `ReducerContext`.
|
||||
> - `client_disconnected` - Called when a user disconnects from the SpacetimeDB database.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Publish your module again by running:
|
||||
|
||||
```sh
|
||||
spacetime publish --server local blackholio
|
||||
```
|
||||
|
||||
### Generating the Client
|
||||
|
||||
The `spacetime` CLI has built in functionality to let us generate C# types that correspond to our tables, types, and reducers that we can use from our Unity client.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
Let's generate our types for our module. In the `blackholio/spacetimedb`
|
||||
directory run the following command:
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
Let's generate our types for our module. In the `blackholio/spacetimedb`
|
||||
directory run the following command:
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```sh
|
||||
spacetime generate --lang csharp --out-dir ../Assets/autogen
|
||||
```
|
||||
|
||||
This will generate a set of files in the `Assets/autogen` directory which contain the code generated types and reducer functions that are defined in your module, but usable on the client.
|
||||
|
||||
```
|
||||
├── Reducers
|
||||
│ └── Connect.g.cs
|
||||
├── Tables
|
||||
│ ├── Circle.g.cs
|
||||
│ ├── Config.g.cs
|
||||
│ ├── Entity.g.cs
|
||||
│ ├── Food.g.cs
|
||||
│ └── Player.g.cs
|
||||
├── Types
|
||||
│ ├── Circle.g.cs
|
||||
│ ├── Config.g.cs
|
||||
│ ├── DbVector2.g.cs
|
||||
│ ├── Entity.g.cs
|
||||
│ ├── Food.g.cs
|
||||
│ └── Player.g.cs
|
||||
└── SpacetimeDBClient.g.cs
|
||||
```
|
||||
|
||||
This will also generate a file in the `Assets/autogen/SpacetimeDBClient.g.cs` directory with a type aware `DbConnection` class. We will use this class to connect to your database from Unity.
|
||||
|
||||
> IMPORTANT! At this point there will be an error in your Unity project. Due to a [known issue](https://docs.unity3d.com/6000.0/Documentation/Manual/csharp-compiler.html) with Unity and C# 9 you need to insert the following code into your Unity project.
|
||||
>
|
||||
> ```csharp
|
||||
> namespace System.Runtime.CompilerServices
|
||||
> {
|
||||
> internal static class IsExternalInit { }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Add this snippet to the bottom of your `GameManager.cs` file in your Unity project. This will hopefully be resolved in Unity soon.
|
||||
|
||||
### Connecting to the Database
|
||||
|
||||
At this point we can set up Unity to connect your Unity client to the server. Replace your imports at the top of the `GameManager.cs` file with:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using SpacetimeDB;
|
||||
using SpacetimeDB.Types;
|
||||
using UnityEngine;
|
||||
```
|
||||
|
||||
Replace the implementation of the `GameManager` class with the following.
|
||||
|
||||
```cs
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
const string SERVER_URL = "http://127.0.0.1:3000";
|
||||
const string MODULE_NAME = "blackholio";
|
||||
|
||||
public static event Action OnConnected;
|
||||
public static event Action OnSubscriptionApplied;
|
||||
|
||||
public float borderThickness = 2;
|
||||
public Material borderMaterial;
|
||||
|
||||
public static GameManager Instance { get; private set; }
|
||||
public static Identity LocalIdentity { get; private set; }
|
||||
public static DbConnection Conn { get; private set; }
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Instance = this;
|
||||
Application.targetFrameRate = 60;
|
||||
|
||||
// In order to build a connection to SpacetimeDB we need to register
|
||||
// our callbacks and specify a SpacetimeDB server URI and module name.
|
||||
var builder = DbConnection.Builder()
|
||||
.OnConnect(HandleConnect)
|
||||
.OnConnectError(HandleConnectError)
|
||||
.OnDisconnect(HandleDisconnect)
|
||||
.WithUri(SERVER_URL)
|
||||
.WithModuleName(MODULE_NAME);
|
||||
|
||||
// If the user has a SpacetimeDB auth token stored in the Unity PlayerPrefs,
|
||||
// we can use it to authenticate the connection.
|
||||
if (AuthToken.Token != "")
|
||||
{
|
||||
builder = builder.WithToken(AuthToken.Token);
|
||||
}
|
||||
|
||||
// Building the connection will establish a connection to the SpacetimeDB
|
||||
// server.
|
||||
Conn = builder.Build();
|
||||
}
|
||||
|
||||
// Called when we connect to SpacetimeDB and receive our client identity
|
||||
void HandleConnect(DbConnection _conn, Identity identity, string token)
|
||||
{
|
||||
Debug.Log("Connected.");
|
||||
AuthToken.SaveToken(token);
|
||||
LocalIdentity = identity;
|
||||
|
||||
OnConnected?.Invoke();
|
||||
|
||||
// Request all tables
|
||||
Conn.SubscriptionBuilder()
|
||||
.OnApplied(HandleSubscriptionApplied)
|
||||
.SubscribeToAllTables();
|
||||
}
|
||||
|
||||
void HandleConnectError(Exception ex)
|
||||
{
|
||||
Debug.LogError($"Connection error: {ex}");
|
||||
}
|
||||
|
||||
void HandleDisconnect(DbConnection _conn, Exception ex)
|
||||
{
|
||||
Debug.Log("Disconnected.");
|
||||
if (ex != null)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSubscriptionApplied(SubscriptionEventContext ctx)
|
||||
{
|
||||
Debug.Log("Subscription applied!");
|
||||
OnSubscriptionApplied?.Invoke();
|
||||
}
|
||||
|
||||
public static bool IsConnected()
|
||||
{
|
||||
return Conn != null && Conn.IsActive;
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Conn.Disconnect();
|
||||
Conn = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unity client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](../../../00300-resources/00200-reference/00400-sql-reference.md) enumerates the operations that are accepted in our SQL syntax.
|
||||
|
||||
---
|
||||
|
||||
**SDK Client Cache**
|
||||
|
||||
The "SDK client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. SpacetimeDB ensures that the results of subscription queries are automatically updated and pushed to the client cache as they change which allows efficient access without unnecessary server queries.
|
||||
|
||||
---
|
||||
|
||||
Now we're ready to connect the client and server. Press the play button in Unity.
|
||||
|
||||
If all went well you should see the below output in your Unity logs.
|
||||
|
||||
```
|
||||
SpacetimeDBClient: Connecting to ws://127.0.0.1:3000 blackholio
|
||||
Connected.
|
||||
Subscription applied!
|
||||
```
|
||||
|
||||
Subscription applied indicates that the SpacetimeDB SDK has evaluated your subscription queries and synchronized your local cache with your database's tables.
|
||||
|
||||
We can also see that the server has logged the connection as well.
|
||||
|
||||
```sh
|
||||
spacetime logs --server local blackholio
|
||||
...
|
||||
2025-01-10T03:51:02.078700Z DEBUG: src/lib.rs:63: c200fb5be9524bfb8289c351516a1d9ea800f70a17a9a6937f11c0ed3854087d just connected.
|
||||
```
|
||||
|
||||
### Next Steps
|
||||
|
||||
You've learned how to setup a Unity project with the SpacetimeDB SDK, write a basic SpacetimeDB server module, and how to connect your Unity client to SpacetimeDB. That's pretty much all there is to the setup. You're now ready to start building the game.
|
||||
|
||||
In the [next part](./00400-part-3.md), we'll build out the functionality of the game and you'll learn how to access your table data and call reducers in Unity.
|
||||
+1281
File diff suppressed because it is too large
Load Diff
+674
@@ -0,0 +1,674 @@
|
||||
---
|
||||
title: 4 - Moving and Colliding
|
||||
slug: /tutorials/unity/part-4
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
This progressive tutorial is continued from [part 3](./00400-part-3.md).
|
||||
|
||||
### Moving the player
|
||||
|
||||
At this point, we're very close to having a working game. All we have to do is modify our server to allow the player to move around, and to simulate the physics and collisions of the game.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#" >
|
||||
Let's start by building out a simple math library to help us do collision calculations. Create a new `Math.cs` file in the `csharp-server` directory and add the following contents. Let's also remove the `DbVector2` type from `Lib.cs`.
|
||||
|
||||
```csharp
|
||||
[SpacetimeDB.Type]
|
||||
public partial struct DbVector2
|
||||
{
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public DbVector2(float x, float y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public float SqrMagnitude => x * x + y * y;
|
||||
public float Magnitude => MathF.Sqrt(SqrMagnitude);
|
||||
public DbVector2 Normalized => this / Magnitude;
|
||||
|
||||
public static DbVector2 operator +(DbVector2 a, DbVector2 b) => new DbVector2(a.x + b.x, a.y + b.y);
|
||||
public static DbVector2 operator -(DbVector2 a, DbVector2 b) => new DbVector2(a.x - b.x, a.y - b.y);
|
||||
public static DbVector2 operator *(DbVector2 a, float b) => new DbVector2(a.x * b, a.y * b);
|
||||
public static DbVector2 operator /(DbVector2 a, float b) => new DbVector2(a.x / b, a.y / b);
|
||||
}
|
||||
```
|
||||
|
||||
Next, add the following reducer to the `Module` class of your `Lib.cs` file.
|
||||
|
||||
```csharp
|
||||
[Reducer]
|
||||
public static void UpdatePlayerInput(ReducerContext ctx, DbVector2 direction)
|
||||
{
|
||||
var player = ctx.Db.player.identity.Find(ctx.Sender) ?? throw new Exception("Player not found");
|
||||
foreach (var c in ctx.Db.circle.player_id.Filter(player.player_id))
|
||||
{
|
||||
var circle = c;
|
||||
circle.direction = direction.Normalized;
|
||||
circle.speed = Math.Clamp(direction.Magnitude, 0f, 1f);
|
||||
ctx.Db.circle.entity_id.Update(circle);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.Sender` value is not set by the client. Instead `ctx.Sender` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust" >
|
||||
Let's start by building out a simple math library to help us do collision calculations. Create a new `math.rs` file in the `spacetimedb/src` directory and add the following contents. Let's also move the `DbVector2` type from `lib.rs` into this file.
|
||||
|
||||
```rust
|
||||
use spacetimedb::SpacetimeType;
|
||||
|
||||
// This allows us to store 2D points in tables.
|
||||
#[derive(SpacetimeType, Debug, Clone, Copy)]
|
||||
pub struct DbVector2 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl std::ops::Add<&DbVector2> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn add(self, other: &DbVector2) -> DbVector2 {
|
||||
DbVector2 {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<DbVector2> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn add(self, other: DbVector2) -> DbVector2 {
|
||||
DbVector2 {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<DbVector2> for DbVector2 {
|
||||
fn add_assign(&mut self, rhs: DbVector2) {
|
||||
self.x += rhs.x;
|
||||
self.y += rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::Sum<DbVector2> for DbVector2 {
|
||||
fn sum<I: Iterator<Item = DbVector2>>(iter: I) -> Self {
|
||||
let mut r = DbVector2::new(0.0, 0.0);
|
||||
for val in iter {
|
||||
r += val;
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&DbVector2> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn sub(self, other: &DbVector2) -> DbVector2 {
|
||||
DbVector2 {
|
||||
x: self.x - other.x,
|
||||
y: self.y - other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<DbVector2> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn sub(self, other: DbVector2) -> DbVector2 {
|
||||
DbVector2 {
|
||||
x: self.x - other.x,
|
||||
y: self.y - other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<DbVector2> for DbVector2 {
|
||||
fn sub_assign(&mut self, rhs: DbVector2) {
|
||||
self.x -= rhs.x;
|
||||
self.y -= rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn mul(self, other: f32) -> DbVector2 {
|
||||
DbVector2 {
|
||||
x: self.x * other,
|
||||
y: self.y * other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f32> for DbVector2 {
|
||||
type Output = DbVector2;
|
||||
|
||||
fn div(self, other: f32) -> DbVector2 {
|
||||
if other != 0.0 {
|
||||
DbVector2 {
|
||||
x: self.x / other,
|
||||
y: self.y / other,
|
||||
}
|
||||
} else {
|
||||
DbVector2 { x: 0.0, y: 0.0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DbVector2 {
|
||||
pub fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn sqr_magnitude(&self) -> f32 {
|
||||
self.x * self.x + self.y * self.y
|
||||
}
|
||||
|
||||
pub fn magnitude(&self) -> f32 {
|
||||
(self.x * self.x + self.y * self.y).sqrt()
|
||||
}
|
||||
|
||||
pub fn normalized(self) -> DbVector2 {
|
||||
self / self.magnitude()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
At the very top of `lib.rs` add the following lines to import the moved `DbVector2` from the `math` module.
|
||||
|
||||
```rust
|
||||
pub mod math;
|
||||
|
||||
use math::DbVector2;
|
||||
// ...
|
||||
```
|
||||
|
||||
Next, add the following reducer to your `lib.rs` file.
|
||||
|
||||
```rust
|
||||
#[spacetimedb::reducer]
|
||||
pub fn update_player_input(ctx: &ReducerContext, direction: DbVector2) -> Result<(), String> {
|
||||
let player = ctx
|
||||
.db
|
||||
.player()
|
||||
.identity()
|
||||
.find(&ctx.sender)
|
||||
.ok_or("Player not found")?;
|
||||
for mut circle in ctx.db.circle().player_id().filter(&player.player_id) {
|
||||
circle.direction = direction.normalized();
|
||||
circle.speed = direction.magnitude().clamp(0.0, 1.0);
|
||||
ctx.db.circle().entity_id().update(circle);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
This is a simple reducer that takes the movement input from the client and applies them to all circles that that player controls. Note that it is not possible for a player to move another player's circles using this reducer, because the `ctx.sender` value is not set by the client. Instead `ctx.sender` is set by SpacetimeDB after it has authenticated that sender. You can rest assured that the caller has been authenticated as that player by the time this reducer is called.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Finally, let's schedule a reducer to run every 50 milliseconds to move the player's circles around based on the most recently set player input.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#" >
|
||||
|
||||
```csharp
|
||||
[Table(Name = "move_all_players_timer", Scheduled = nameof(MoveAllPlayers), ScheduledAt = nameof(scheduled_at))]
|
||||
public partial struct MoveAllPlayersTimer
|
||||
{
|
||||
[PrimaryKey, AutoInc]
|
||||
public ulong scheduled_id;
|
||||
public ScheduleAt scheduled_at;
|
||||
}
|
||||
|
||||
const int START_PLAYER_SPEED = 10;
|
||||
|
||||
public static float MassToMaxMoveSpeed(int mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS));
|
||||
|
||||
[Reducer]
|
||||
public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer)
|
||||
{
|
||||
var world_size = (ctx.Db.config.id.Find(0) ?? throw new Exception("Config not found")).world_size;
|
||||
|
||||
var circle_directions = ctx.Db.circle.Iter().Select(c => (c.entity_id, c.direction * c.speed)).ToDictionary();
|
||||
|
||||
// Handle player input
|
||||
foreach (var circle in ctx.Db.circle.Iter())
|
||||
{
|
||||
var check_entity = ctx.Db.entity.entity_id.Find(circle.entity_id);
|
||||
if (check_entity == null)
|
||||
{
|
||||
// This can happen if the circle has been eaten by another circle.
|
||||
continue;
|
||||
}
|
||||
var circle_entity = check_entity.Value;
|
||||
var circle_radius = MassToRadius(circle_entity.mass);
|
||||
var direction = circle_directions[circle.entity_id];
|
||||
var new_pos = circle_entity.position + direction * MassToMaxMoveSpeed(circle_entity.mass);
|
||||
circle_entity.position.x = Math.Clamp(new_pos.x, circle_radius, world_size - circle_radius);
|
||||
circle_entity.position.y = Math.Clamp(new_pos.y, circle_radius, world_size - circle_radius);
|
||||
ctx.Db.entity.entity_id.Update(circle_entity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust" >
|
||||
|
||||
```rust
|
||||
#[spacetimedb::table(name = move_all_players_timer, scheduled(move_all_players))]
|
||||
pub struct MoveAllPlayersTimer {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
scheduled_id: u64,
|
||||
scheduled_at: spacetimedb::ScheduleAt,
|
||||
}
|
||||
|
||||
const START_PLAYER_SPEED: i32 = 10;
|
||||
|
||||
fn mass_to_max_move_speed(mass: i32) -> f32 {
|
||||
2.0 * START_PLAYER_SPEED as f32 / (1.0 + (mass as f32 / START_PLAYER_MASS as f32).sqrt())
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Result<(), String> {
|
||||
let world_size = ctx
|
||||
.db
|
||||
.config()
|
||||
.id()
|
||||
.find(0)
|
||||
.ok_or("Config not found")?
|
||||
.world_size;
|
||||
|
||||
// Handle player input
|
||||
for circle in ctx.db.circle().iter() {
|
||||
let circle_entity = ctx.db.entity().entity_id().find(&circle.entity_id);
|
||||
if !circle_entity.is_some() {
|
||||
// This can happen if a circle is eaten by another circle
|
||||
continue;
|
||||
}
|
||||
let mut circle_entity = circle_entity.unwrap();
|
||||
let circle_radius = mass_to_radius(circle_entity.mass);
|
||||
let direction = circle.direction * circle.speed;
|
||||
let new_pos =
|
||||
circle_entity.position + direction * mass_to_max_move_speed(circle_entity.mass);
|
||||
let min = circle_radius;
|
||||
let max = world_size as f32 - circle_radius;
|
||||
circle_entity.position.x = new_pos.x.clamp(min, max);
|
||||
circle_entity.position.y = new_pos.y.clamp(min, max);
|
||||
ctx.db.entity().entity_id().update(circle_entity);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This reducer is very similar to a standard game "tick" or "frame" that you might find in an ordinary game server or similar to something like the `Update` loop in a game engine like Unity. We've scheduled it every 50 milliseconds and we can use it to step forward our simulation by moving all the circles a little bit further in the direction they're moving.
|
||||
|
||||
In this reducer, we're just looping through all the circles in the game and updating their position based on their direction, speed, and mass. Just basic physics.
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#" >
|
||||
Add the following to your `Init` reducer to schedule the `MoveAllPlayers` reducer to run every 50 milliseconds.
|
||||
|
||||
```csharp
|
||||
ctx.Db.move_all_players_timer.Insert(new MoveAllPlayersTimer
|
||||
{
|
||||
scheduled_at = new ScheduleAt.Interval(TimeSpan.FromMilliseconds(50))
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust" >
|
||||
Add the following to your `init` reducer to schedule the `move_all_players` reducer to run every 50 milliseconds.
|
||||
|
||||
```rust
|
||||
ctx.db
|
||||
.move_all_players_timer()
|
||||
.try_insert(MoveAllPlayersTimer {
|
||||
scheduled_id: 0,
|
||||
scheduled_at: ScheduleAt::Interval(Duration::from_millis(50).into()),
|
||||
})?;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Republish your module with:
|
||||
|
||||
```sh
|
||||
spacetime publish --server local blackholio --delete-data
|
||||
```
|
||||
|
||||
Regenerate your server bindings with:
|
||||
|
||||
```sh
|
||||
spacetime generate --lang csharp --out-dir ../Assets/module_bindings
|
||||
```
|
||||
|
||||
### Moving on the Client
|
||||
|
||||
All that's left is to modify our `PlayerController` on the client to call the `update_player_input` reducer. Open `PlayerController.cs` and add an `Update` function:
|
||||
|
||||
```cs
|
||||
public void Update()
|
||||
{
|
||||
if (!IsLocalPlayer || NumberOfOwnedCircles == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Q))
|
||||
{
|
||||
if (LockInputPosition.HasValue)
|
||||
{
|
||||
LockInputPosition = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
LockInputPosition = (Vector2)Input.mousePosition;
|
||||
}
|
||||
}
|
||||
|
||||
// Throttled input requests
|
||||
if (Time.time - LastMovementSendTimestamp >= SEND_UPDATES_FREQUENCY)
|
||||
{
|
||||
LastMovementSendTimestamp = Time.time;
|
||||
|
||||
var mousePosition = LockInputPosition ?? (Vector2)Input.mousePosition;
|
||||
var screenSize = new Vector2
|
||||
{
|
||||
x = Screen.width,
|
||||
y = Screen.height,
|
||||
};
|
||||
var centerOfScreen = screenSize / 2;
|
||||
|
||||
var direction = (mousePosition - centerOfScreen) / (screenSize.y / 3);
|
||||
if (testInputEnabled) { direction = testInput; }
|
||||
GameManager.Conn.Reducers.UpdatePlayerInput(direction);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's try it out! Press play and roam freely around the arena! Now we're cooking with gas.
|
||||
|
||||
### Collisions and Eating Food
|
||||
|
||||
Well this is pretty fun, but wouldn't it be better if we could eat food and grow our circle? Surely, that's going to be a pain, right?
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#" >
|
||||
Wrong. With SpacetimeDB it's extremely easy. All we have to do is add an `IsOverlapping` helper function which does some basic math based on mass radii, and modify our `MoveAllPlayers` reducer to loop through every entity in the arena for every circle, checking each for overlaps. This may not be the most efficient way to do collision checking (building a quad tree or doing [spatial hashing](https://conkerjo.wordpress.com/2009/06/13/spatial-hashing-implementation-for-fast-2d-collisions/) might be better), but SpacetimeDB is very fast so for this number of entities it'll be a breeze for SpacetimeDB.
|
||||
|
||||
Sometimes simple is best! Add the following code to the `Module` class of your `Lib.cs` file and make sure to replace the existing `MoveAllPlayers` reducer.
|
||||
|
||||
```csharp
|
||||
const float MINIMUM_SAFE_MASS_RATIO = 0.85f;
|
||||
|
||||
public static bool IsOverlapping(Entity a, Entity b)
|
||||
{
|
||||
var dx = a.position.x - b.position.x;
|
||||
var dy = a.position.y - b.position.y;
|
||||
var distance_sq = dx * dx + dy * dy;
|
||||
|
||||
var radius_a = MassToRadius(a.mass);
|
||||
var radius_b = MassToRadius(b.mass);
|
||||
|
||||
// If the distance between the two circle centers is less than the
|
||||
// maximum radius, then the center of the smaller circle is inside
|
||||
// the larger circle. This gives some leeway for the circles to overlap
|
||||
// before being eaten.
|
||||
var max_radius = radius_a > radius_b ? radius_a: radius_b;
|
||||
return distance_sq <= max_radius * max_radius;
|
||||
}
|
||||
|
||||
[Reducer]
|
||||
public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer)
|
||||
{
|
||||
var world_size = (ctx.Db.config.id.Find(0) ?? throw new Exception("Config not found")).world_size;
|
||||
|
||||
// Handle player input
|
||||
foreach (var circle in ctx.Db.circle.Iter())
|
||||
{
|
||||
var check_entity = ctx.Db.entity.entity_id.Find(circle.entity_id);
|
||||
if (check_entity == null)
|
||||
{
|
||||
// This can happen if the circle has been eaten by another circle.
|
||||
continue;
|
||||
}
|
||||
var circle_entity = check_entity.Value;
|
||||
var circle_radius = MassToRadius(circle_entity.mass);
|
||||
var direction = circle.direction * circle.speed;
|
||||
var new_pos = circle_entity.position + direction * MassToMaxMoveSpeed(circle_entity.mass);
|
||||
circle_entity.position.x = Math.Clamp(new_pos.x, circle_radius, world_size - circle_radius);
|
||||
circle_entity.position.y = Math.Clamp(new_pos.y, circle_radius, world_size - circle_radius);
|
||||
|
||||
// Check collisions
|
||||
foreach (var entity in ctx.Db.entity.Iter())
|
||||
{
|
||||
if (entity.entity_id == circle_entity.entity_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (IsOverlapping(circle_entity, entity))
|
||||
{
|
||||
// Check to see if we're overlapping with food
|
||||
if (ctx.Db.food.entity_id.Find(entity.entity_id).HasValue) {
|
||||
ctx.Db.entity.entity_id.Delete(entity.entity_id);
|
||||
ctx.Db.food.entity_id.Delete(entity.entity_id);
|
||||
circle_entity.mass += entity.mass;
|
||||
}
|
||||
|
||||
// Check to see if we're overlapping with another circle owned by another player
|
||||
var other_circle = ctx.Db.circle.entity_id.Find(entity.entity_id);
|
||||
if (other_circle.HasValue &&
|
||||
other_circle.Value.player_id != circle.player_id)
|
||||
{
|
||||
var mass_ratio = (float)entity.mass / circle_entity.mass;
|
||||
if (mass_ratio < MINIMUM_SAFE_MASS_RATIO)
|
||||
{
|
||||
ctx.Db.entity.entity_id.Delete(entity.entity_id);
|
||||
ctx.Db.circle.entity_id.Delete(entity.entity_id);
|
||||
circle_entity.mass += entity.mass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Db.entity.entity_id.Update(circle_entity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust" >
|
||||
Wrong. With SpacetimeDB it's extremely easy. All we have to do is add an `is_overlapping` helper function which does some basic math based on mass radii, and modify our `move_all_player` reducer to loop through every entity in the arena for every circle, checking each for overlaps. This may not be the most efficient way to do collision checking (building a quad tree or doing [spatial hashing](https://conkerjo.wordpress.com/2009/06/13/spatial-hashing-implementation-for-fast-2d-collisions/) might be better), but SpacetimeDB is very fast so for this number of entities it'll be a breeze for SpacetimeDB.
|
||||
|
||||
Sometimes simple is best! Add the following code to your `lib.rs` file and make sure to replace the existing `move_all_players` reducer.
|
||||
|
||||
```rust
|
||||
const MINIMUM_SAFE_MASS_RATIO: f32 = 0.85;
|
||||
|
||||
fn is_overlapping(a: &Entity, b: &Entity) -> bool {
|
||||
let dx = a.position.x - b.position.x;
|
||||
let dy = a.position.y - b.position.y;
|
||||
let distance_sq = dx * dx + dy * dy;
|
||||
|
||||
let radius_a = mass_to_radius(a.mass);
|
||||
let radius_b = mass_to_radius(b.mass);
|
||||
|
||||
// If the distance between the two circle centers is less than the
|
||||
// maximum radius, then the center of the smaller circle is inside
|
||||
// the larger circle. This gives some leeway for the circles to overlap
|
||||
// before being eaten.
|
||||
let max_radius = f32::max(radius_a, radius_b);
|
||||
distance_sq <= max_radius * max_radius
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Result<(), String> {
|
||||
let world_size = ctx
|
||||
.db
|
||||
.config()
|
||||
.id()
|
||||
.find(0)
|
||||
.ok_or("Config not found")?
|
||||
.world_size;
|
||||
|
||||
// Handle player input
|
||||
for circle in ctx.db.circle().iter() {
|
||||
let circle_entity = ctx.db.entity().entity_id().find(&circle.entity_id);
|
||||
if !circle_entity.is_some() {
|
||||
// This can happen if a circle is eaten by another circle
|
||||
continue;
|
||||
}
|
||||
let mut circle_entity = circle_entity.unwrap();
|
||||
let circle_radius = mass_to_radius(circle_entity.mass);
|
||||
let direction = circle.direction * circle.speed;
|
||||
let new_pos =
|
||||
circle_entity.position + direction * mass_to_max_move_speed(circle_entity.mass);
|
||||
let min = circle_radius;
|
||||
let max = world_size as f32 - circle_radius;
|
||||
circle_entity.position.x = new_pos.x.clamp(min, max);
|
||||
circle_entity.position.y = new_pos.y.clamp(min, max);
|
||||
|
||||
// Check collisions
|
||||
for entity in ctx.db.entity().iter() {
|
||||
if entity.entity_id == circle_entity.entity_id {
|
||||
continue;
|
||||
}
|
||||
if is_overlapping(&circle_entity, &entity) {
|
||||
// Check to see if we're overlapping with food
|
||||
if ctx.db.food().entity_id().find(&entity.entity_id).is_some() {
|
||||
ctx.db.entity().entity_id().delete(&entity.entity_id);
|
||||
ctx.db.food().entity_id().delete(&entity.entity_id);
|
||||
circle_entity.mass += entity.mass;
|
||||
}
|
||||
|
||||
// Check to see if we're overlapping with another circle owned by another player
|
||||
let other_circle = ctx.db.circle().entity_id().find(&entity.entity_id);
|
||||
if let Some(other_circle) = other_circle {
|
||||
if other_circle.player_id != circle.player_id {
|
||||
let mass_ratio = entity.mass as f32 / circle_entity.mass as f32;
|
||||
if mass_ratio < MINIMUM_SAFE_MASS_RATIO {
|
||||
ctx.db.entity().entity_id().delete(&entity.entity_id);
|
||||
ctx.db.circle().entity_id().delete(&entity.entity_id);
|
||||
circle_entity.mass += entity.mass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.db.entity().entity_id().update(circle_entity);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For every circle, we look at all other entities. If they are overlapping then for food, we add the mass of the food to the circle and delete the food, otherwise if it's a circle we delete the smaller circle and add the mass to the bigger circle.
|
||||
|
||||
That's it. We don't even have to do anything on the client.
|
||||
|
||||
```sh
|
||||
spacetime publish --server local blackholio
|
||||
```
|
||||
|
||||
Just update your module by publishing and you're on your way eating food! Try to see how big you can get!
|
||||
|
||||
We didn't even have to update the client, because our client's `OnDelete` callbacks already handled deleting entities from the scene when they're deleted on the server. SpacetimeDB just synchronizes the state with your client automatically.
|
||||
|
||||
Notice that the food automatically respawns as you vaccuum them up. This is because our scheduled reducer is automatically replacing the food 2 times per second, to ensure that there is always 600 food on the map.
|
||||
|
||||
## Connecting to Maincloud
|
||||
|
||||
- Publish to Maincloud `spacetime publish --server maincloud <your database name> --delete-data`
|
||||
- `<your database name>` This name should be unique and cannot contain any special characters other than internal hyphens (`-`).
|
||||
- Update the URL in the Unity project to: `https://maincloud.spacetimedb.com`
|
||||
- Update the module name in the Unity project to `<your database name>`.
|
||||
- Clear the PlayerPrefs in Start() within `GameManager.cs`
|
||||
- Your `GameManager.cs` should look something like this:
|
||||
|
||||
```csharp
|
||||
const string SERVER_URL = "https://maincloud.spacetimedb.com";
|
||||
const string MODULE_NAME = "<your module name>";
|
||||
|
||||
...
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Clear cached connection data to ensure proper connection
|
||||
PlayerPrefs.DeleteAll();
|
||||
|
||||
// Continue with initialization
|
||||
}
|
||||
```
|
||||
|
||||
To delete your Maincloud database, you can run: `spacetime delete --server maincloud <your database name>`
|
||||
|
||||
# Conclusion
|
||||
|
||||
<Tabs groupId="server-language" defaultValue="rust">
|
||||
<TabItem value="csharp" label="C#">
|
||||
So far you've learned how to configure a new Unity project to work with
|
||||
SpacetimeDB, how to develop, build, and publish a SpacetimeDB server module.
|
||||
Within the module, you've learned how to create tables, update tables, and
|
||||
write reducers. You've learned about special reducers like `ClientConnected`
|
||||
and `Init` and how to created scheduled reducers. You learned how we can
|
||||
used scheduled reducers to implement a physics simulation right within your
|
||||
module.
|
||||
</TabItem>
|
||||
<TabItem value="rust" label="Rust">
|
||||
So far you've learned how to configure a new Unity project to work with
|
||||
SpacetimeDB, how to develop, build, and publish a SpacetimeDB server module.
|
||||
Within the module, you've learned how to create tables, update tables, and
|
||||
write reducers. You've learned about special reducers like
|
||||
`client_connected` and `init` and how to created scheduled reducers. You
|
||||
learned how we can used scheduled reducers to implement a physics simulation
|
||||
right within your module.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You've also learned how view module logs and connect your client to your database server, call reducers from the client and synchronize the data with client. Finally you learned how to use that synchronized data to draw game objects on the screen, so that we can interact with them and play a game!
|
||||
|
||||
And all of that completely from scratch!
|
||||
|
||||
Our game is still pretty limited in some important ways. The biggest limitation is that the client assumes your username is "3Blave" and doesn't give you a menu or a window to set your username before joining the game. Notably, we do not have a unique constraint on the `name` column, so that does not prevent us from connecting multiple clients to the same server.
|
||||
|
||||
In fact, if you build what we have and run multiple clients you already have a (very simple) MMO! You can connect hundreds of players to this arena with SpacetimeDB.
|
||||
|
||||
There's still plenty more we can do to build this into a proper game though. For example, you might want to also add
|
||||
|
||||
- Username chooser
|
||||
- Chat
|
||||
- Leaderboards
|
||||
- Nice animations
|
||||
- Nice shaders
|
||||
- Space theme!
|
||||
|
||||
Fortunately, we've done that for you! If you'd like to check out the completed tutorial game, with these additional features, you can download it on GitHub:
|
||||
|
||||
[https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio](https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio)
|
||||
|
||||
If you have any suggestions or comments on the tutorial, either [open an issue](https://github.com/clockworklabs/SpacetimeDB/issues/new), or join our Discord ([https://discord.gg/SpacetimeDB](https://discord.gg/SpacetimeDB)) and chat with us!
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Unity Tutorial",
|
||||
"collapsed": true,
|
||||
"link": {
|
||||
"type": "doc",
|
||||
"id": "intro/tutorials/unity-tutorial/index"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Unity Tutorial
|
||||
slug: /tutorials/unity
|
||||
---
|
||||
|
||||
|
||||
Need help with the tutorial or CLI commands? [Join our Discord server](https://discord.gg/spacetimedb)!
|
||||
|
||||
In this tutorial you'll learn how to build a small-scoped MMORPG in Unity, from scratch, using SpacetimeDB. Although, the game we're going to build is small in scope, it'll scale to hundreds of players and will help you get acquainted with all the features and best practices of SpacetimeDB, while building [a fun little game](https://github.com/ClockworkLabs/Blackholio).
|
||||
|
||||
By the end, you should have a basic understanding of what SpacetimeDB offers for developers making multiplayer games.
|
||||
|
||||
The game is inspired by [agar.io](https://agar.io), but SpacetimeDB themed with some fun twists. If you're not familiar [agar.io](https://agar.io), it's a web game in which you and hundreds of other players compete to cultivate mass to become the largest cell in the Petri dish.
|
||||
|
||||
Our game, called [Blackhol.io](https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio), will be similar but space themed. It should give you a great idea of the types of games you can develop easily with SpacetimeDB.
|
||||
|
||||
This tutorial assumes that you have a basic understanding of the Unity Editor, using a command line terminal and programming. We'll give you some CLI commands to execute. If you are using Windows, we recommend using Git Bash or PowerShell. For Mac, we recommend Terminal.
|
||||
|
||||
SpacetimeDB supports Unity version `2022.3.32f1` or later, and this tutorial has been tested with the following Unity versions:
|
||||
|
||||
- `2022.3.32f1 LTS`
|
||||
- `6000.0.33f1`
|
||||
|
||||
Please file an issue [here](https://github.com/clockworklabs/SpacetimeDB/issues) if you encounter an issue with a specific Unity version, but please be aware that the SpacetimeDB team is unable to offer support for issues related to versions of Unity prior to `2022.3.32f1 LTS`.
|
||||
|
||||
## Blackhol.io Tutorial - Basic Multiplayer
|
||||
|
||||
First you'll get started with the core client/server setup. For part 2, you'll be able to choose between **Rust** or **C#** for your server module language:
|
||||
|
||||
- [Part 1 - Setup](./00200-part-1.md)
|
||||
- [Part 2 - Connecting to SpacetimeDB](./00300-part-2.md)
|
||||
- [Part 3 - Gameplay](./00400-part-3.md)
|
||||
- [Part 4 - Moving and Colliding](./00500-part-4.md)
|
||||
|
||||
## Blackhol.io Tutorial - Advanced
|
||||
|
||||
If you already have a good understanding of the SpacetimeDB client and server, check out our completed tutorial project!
|
||||
|
||||
[https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio](https://github.com/clockworklabs/SpacetimeDB/tree/master/demo/Blackholio)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user