From e24bf42f1862ad2beaa71576e6c545d4adc73c31 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 11 Oct 2023 22:22:57 -0700 Subject: [PATCH 01/80] Update index.md --- docs/Overview/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Overview/index.md b/docs/Overview/index.md index 2464e6e3..264b5861 100644 --- a/docs/Overview/index.md +++ b/docs/Overview/index.md @@ -1,5 +1,3 @@ -# SpacetimeDB Documentation - ## Installation You can run SpacetimeDB as a standalone database server via the `spacetime` CLI tool. From 176c19d7983bfbb6563f4cdf03dff08134b00a98 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 11 Oct 2023 23:07:24 -0700 Subject: [PATCH 02/80] Update index.md --- docs/Overview/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Overview/index.md b/docs/Overview/index.md index 264b5861..2464e6e3 100644 --- a/docs/Overview/index.md +++ b/docs/Overview/index.md @@ -1,3 +1,5 @@ +# SpacetimeDB Documentation + ## Installation You can run SpacetimeDB as a standalone database server via the `spacetime` CLI tool. From 9d7d5c71441640bca2a7860cff2a2711c676d472 Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 08:22:23 -0500 Subject: [PATCH 03/80] Including this line for completeness --- LICENSE.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSE.txt b/LICENSE.txt index dd5b3a58..d9a10c0d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -172,3 +172,5 @@ defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS From ec418f1d54e5d6426a2cd464119362dcec77e3fd Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 09:39:03 -0500 Subject: [PATCH 04/80] C# Quickstart is working --- docs/Client SDK Languages/C#/index.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/Client SDK Languages/C#/index.md b/docs/Client SDK Languages/C#/index.md index b64ca13d..eb7829a6 100644 --- a/docs/Client SDK Languages/C#/index.md +++ b/docs/Client SDK Languages/C#/index.md @@ -150,7 +150,7 @@ This second case means that, even though the module only ever inserts online use Whenever we want to print a user, if they have set a name, we'll use that. If they haven't set a name, we'll instead print the first 8 bytes of their identity, encoded as hexadecimal. We'll define a function `UserNameOrIdentity` to handle this. ```csharp -string UserNameOrIdentity(User user) => user.Name ?? Identity.From(user.Identity).ToString()!.Substring(0, 8); +string UserNameOrIdentity(User user) => user.Name ?? user.Identity.ToString()!.Substring(0, 8); void User_OnInsert(User insertedValue, ReducerEvent? dbEvent) { @@ -291,7 +291,7 @@ void OnConnect() This callback is executed when we receive our credentials from the SpacetimeDB module. We'll use the `AuthToken` module to save our token to local storage, so that we can re-authenticate as the same user the next time we connect. We'll also store the identity in a global variable `local_identity` so that we can use it to check if we are the sender of a message or name change. ```csharp -void OnIdentityReceived(string authToken, Identity identity) +void OnIdentityReceived(string authToken, Identity identity, Address _address) { local_identity = identity; AuthToken.SaveToken(authToken); @@ -333,13 +333,12 @@ Since the input loop will be blocking, we'll run our processing code in a separa 3. Finally, Close the connection to the module. ```csharp -const string HOST = "localhost:3000"; -const string DBNAME = "chat"; -const bool SSL_ENABLED = false; - +const string HOST = "http://localhost:3000"; +const string DBNAME = "module"; + void ProcessThread() { - SpacetimeDBClient.instance.Connect(AuthToken.Token, HOST, DBNAME, SSL_ENABLED); + SpacetimeDBClient.instance.Connect(AuthToken.Token, HOST, DBNAME); // loop until cancellation token while (!cancel_token.IsCancellationRequested) From e40ce9282f13c4cbb77d16bde0c582f3b023da4b Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 11:16:06 -0500 Subject: [PATCH 05/80] Python quickstart updated --- docs/Client SDK Languages/Python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Client SDK Languages/Python/index.md b/docs/Client SDK Languages/Python/index.md index 52630452..6cdac567 100644 --- a/docs/Client SDK Languages/Python/index.md +++ b/docs/Client SDK Languages/Python/index.md @@ -53,7 +53,7 @@ In your `client` directory, run: ```bash mkdir -p module_bindings -spacetime generate --lang python --out-dir src/module_bindings --project_path ../server +spacetime generate --lang python --out-dir module_bindings --project-path ../server ``` Take a look inside `client/module_bindings`. The CLI should have generated five files: From d6de290fcb86e5677a1c181388f4b956dd02e178 Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 11:48:08 -0500 Subject: [PATCH 06/80] Another small python quickstart fix --- docs/Client SDK Languages/Python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Client SDK Languages/Python/index.md b/docs/Client SDK Languages/Python/index.md index 6cdac567..29552c32 100644 --- a/docs/Client SDK Languages/Python/index.md +++ b/docs/Client SDK Languages/Python/index.md @@ -250,7 +250,7 @@ We handle warnings on rejected messages the same way as rejected names, though t Add this function before the `register_callbacks` function: ```python -def on_send_message_reducer(sender, status, message, msg): +def on_send_message_reducer(sender, _addr, status, message, msg): if sender == local_identity: if status == "failed": print(f"Failed to send message: {message}") From 60fee240bdb90e9251c581aca447304f4b336379 Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 14:10:57 -0500 Subject: [PATCH 07/80] Fix Nuget command --- docs/Client SDK Languages/C#/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Client SDK Languages/C#/index.md b/docs/Client SDK Languages/C#/index.md index eb7829a6..66342bce 100644 --- a/docs/Client SDK Languages/C#/index.md +++ b/docs/Client SDK Languages/C#/index.md @@ -22,10 +22,10 @@ Open the project in your IDE of choice. ## Add the NuGet package for the C# SpacetimeDB SDK -Add the `spacetimedbsdk` [NuGet package](https://www.nuget.org/packages/spacetimedbsdk) using Visual Studio NuGet package manager or via the .NET CLI +Add the `SpacetimeDB.ClientSDK` [NuGet package](https://www.nuget.org/packages/spacetimedbsdk) using Visual Studio NuGet package manager or via the .NET CLI ```bash -dotnet add package spacetimedbsdk +dotnet add package SpacetimeDB.ClientSDK ``` ## Generate your module types From 54935143294935729283cc64c77d69a49ed27de5 Mon Sep 17 00:00:00 2001 From: John Detter Date: Thu, 12 Oct 2023 15:16:44 -0500 Subject: [PATCH 08/80] Applied Phoebe's patch --- docs/Client SDK Languages/C#/SDK Reference.md | 49 ++++--- docs/Client SDK Languages/C#/index.md | 2 +- .../Python/SDK Reference.md | 73 ++++++---- docs/Client SDK Languages/Python/index.md | 22 +-- .../Rust/SDK Reference.md | 72 +++++++--- docs/Client SDK Languages/Rust/index.md | 18 ++- .../Typescript/SDK Reference.md | 127 +++++++++++++++--- docs/Client SDK Languages/Typescript/index.md | 8 +- docs/Overview/index.md | 12 +- .../C#/ModuleReference.md | 9 +- docs/Server Module Languages/C#/index.md | 5 +- docs/Server Module Languages/Rust/index.md | 6 +- .../Part 1 - Basic Multiplayer.md | 6 +- 13 files changed, 295 insertions(+), 114 deletions(-) diff --git a/docs/Client SDK Languages/C#/SDK Reference.md b/docs/Client SDK Languages/C#/SDK Reference.md index 3284e6fe..ad4c8c48 100644 --- a/docs/Client SDK Languages/C#/SDK Reference.md +++ b/docs/Client SDK Languages/C#/SDK Reference.md @@ -44,6 +44,7 @@ The SpacetimeDB client C# for Rust contains all the tools you need to build nati - [Static Property `AuthToken.Token`](#static-property-authtokentoken) - [Static Method `AuthToken.SaveToken`](#static-method-authtokensavetoken) - [Class `Identity`](#class-identity) + - [Class `Address`](#class-address) - [Customizing logging](#customizing-logging) - [Interface `ISpacetimeDBLogger`](#interface-ispacetimedblogger) - [Class `ConsoleLogger`](#class-consolelogger) @@ -178,7 +179,7 @@ SpacetimeDBClient.instance.Connect(null, "dev.spacetimedb.net", DBNAME, true); AuthToken.Init(); Identity localIdentity; SpacetimeDBClient.instance.Connect(AuthToken.Token, "dev.spacetimedb.net", DBNAME, true); -SpacetimeDBClient.instance.onIdentityReceived += (string authToken, Identity identity) { +SpacetimeDBClient.instance.onIdentityReceived += (string authToken, Identity identity, Address address) { AuthToken.SaveToken(authToken); localIdentity = identity; } @@ -192,13 +193,13 @@ SpacetimeDBClient.instance.onIdentityReceived += (string authToken, Identity ide namespace SpacetimeDB { class SpacetimeDBClient { - public event Action onIdentityReceived; + public event Action onIdentityReceived; } } ``` -Called when we receive an auth token and [`Identity`](#class-identity) from the server. The [`Identity`](#class-identity) serves as a unique public identifier for a client connected to the database. It can be for several purposes, such as filtering rows in a database for the rows created by a particular user. The auth token is a private access token that allows us to assume an identity. ++Called when we receive an auth token, [`Identity`](#class-identity) and [`Address`](#class-address) from the server. The [`Identity`](#class-identity) serves as a unique public identifier for a user of the database. It can be for several purposes, such as filtering rows in a database for the rows created by a particular user. The auth token is a private access token that allows us to assume an identity. The [`Address`](#class-address) is opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#class-identity). To store the auth token to the filesystem, use the static method [`AuthToken.SaveToken`](#static-method-authtokensavetoken). You may also want to store the returned [`Identity`](#class-identity) in a local variable. @@ -209,7 +210,7 @@ If an existing auth token is used to connect to the database, the same auth toke AuthToken.Init(); Identity localIdentity; SpacetimeDBClient.instance.Connect(AuthToken.Token, "dev.spacetimedb.net", DBNAME, true); -SpacetimeDBClient.instance.onIdentityReceived += (string authToken, Identity identity) { +SpacetimeDBClient.instance.onIdentityReceived += (string authToken, Identity identity, Address address) { AuthToken.SaveToken(authToken); localIdentity = identity; } @@ -856,24 +857,42 @@ Save a token to the filesystem. ### Class `Identity` ```cs -namespace SpacetimeDB { - -public struct Identity : IEquatable +namespace SpacetimeDB { - public byte[] Bytes { get; } - public static Identity From(byte[] bytes); - public bool Equals(Identity other); - public static bool operator ==(Identity a, Identity b); - public static bool operator !=(Identity a, Identity b); -} - + public struct Identity : IEquatable + { + public byte[] Bytes { get; } + public static Identity From(byte[] bytes); + public bool Equals(Identity other); + public static bool operator ==(Identity a, Identity b); + public static bool operator !=(Identity a, Identity b); + } } ``` -A unique public identifier for a client connected to a database. +A unique public identifier for a user of a database. + + Columns of type `Identity` inside a module will be represented in the C# SDK as properties of type `byte[]`. `Identity` is essentially just a wrapper around `byte[]`, and you can use the `Bytes` property to get a `byte[]` that can be used to filter tables and so on. +### Class `Identity` +```cs +namespace SpacetimeDB +{ + public struct Address : IEquatable
+ { + public byte[] Bytes { get; } + public static Address? From(byte[] bytes); + public bool Equals(Address other); + public static bool operator ==(Address a, Address b); + public static bool operator !=(Address a, Address b); + } +} +``` + +An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#class-identity). + ## Customizing logging The SpacetimeDB C# SDK performs internal logging. Instances of [`ISpacetimeDBLogger`](#interface-ispacetimedblogger) can be passed to [`SpacetimeDBClient.CreateInstance`](#static-method-spacetimedbclientcreateinstance) to customize how SDK logs are delivered to your application. diff --git a/docs/Client SDK Languages/C#/index.md b/docs/Client SDK Languages/C#/index.md index 66342bce..f4d8b7ee 100644 --- a/docs/Client SDK Languages/C#/index.md +++ b/docs/Client SDK Languages/C#/index.md @@ -288,7 +288,7 @@ void OnConnect() ## OnIdentityReceived callback -This callback is executed when we receive our credentials from the SpacetimeDB module. We'll use the `AuthToken` module to save our token to local storage, so that we can re-authenticate as the same user the next time we connect. We'll also store the identity in a global variable `local_identity` so that we can use it to check if we are the sender of a message or name change. +This callback is executed when we receive our credentials from the SpacetimeDB module. We'll use the `AuthToken` module to save our token to local storage, so that we can re-authenticate as the same user the next time we connect. We'll also store the identity in a global variable `local_identity` so that we can use it to check if we are the sender of a message or name change. This callback also notifies us of our client's `Address`, an opaque identifier SpacetimeDB modules can use to distinguish connections by the same `Identity`, but we won't use it in our app. ```csharp void OnIdentityReceived(string authToken, Identity identity, Address _address) diff --git a/docs/Client SDK Languages/Python/SDK Reference.md b/docs/Client SDK Languages/Python/SDK Reference.md index 8cd4b4ca..276d59df 100644 --- a/docs/Client SDK Languages/Python/SDK Reference.md +++ b/docs/Client SDK Languages/Python/SDK Reference.md @@ -44,8 +44,9 @@ The following functions and types are used in both the Basic and Async clients. ### API at a glance | Definition | Description | -| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +|---------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| | Type [`Identity`](#type-identity) | A unique public identifier for a client. | +| Type [`Address`](#type-address) | An opaque identifier for differentiating connections by the same `Identity`. | | Type [`ReducerEvent`](#type-reducerevent) | `class` containing information about the reducer that triggered a row update event. | | Type [`module_bindings::{TABLE}`](#type-table) | Autogenerated `class` type for a table, holding one row. | | Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over or seek subscribed rows where a column matches a value. | @@ -76,7 +77,31 @@ class Identity: | `__str__` | `None` | Convert the Identity to a hex string | | `__eq__` | `Identity` | Compare two Identities for equality | -A unique public identifier for a client connected to a database. +A unique public identifier for a user of a database. + +### Type `Address` + +```python +class Address: + @staticmethod + def from_string(string) + + @staticmethod + def from_bytes(data) + + def __str__(self) + + def __eq__(self, other) +``` + +| Member | Type | Meaning | +|---------------|-----------|-------------------------------------| +| `from_string` | `str` | Create an Address from a hex string | +| `from_bytes` | `bytes` | Create an Address from raw bytes | +| `__str__` | `None` | Convert the Address to a hex string | +| `__eq__` | `Address` | Compare two Identities for equality | + +An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). ### Type `ReducerEvent` @@ -90,13 +115,14 @@ class ReducerEvent: self.args = args ``` -| Member | Args | Meaning | -| ----------------- | ----------- | --------------------------------------------------------------------------- | -| `caller_identity` | `Identity` | The identity of the user who invoked the reducer | -| `reducer_name` | `str` | The name of the reducer that was invoked | -| `status` | `str` | The status of the reducer invocation ("committed", "failed", "outofenergy") | -| `message` | `str` | The message returned by the reducer if it fails | -| `args` | `List[str]` | The arguments passed to the reducer | +| Member | Type | Meaning | +|-------------------|---------------------|------------------------------------------------------------------------------------| +| `caller_identity` | `Identity` | The identity of the user who invoked the reducer | +| `caller_address` | `Optional[Address]` | The address of the user who invoked the reducer, or `None` for scheduled reducers. | +| `reducer_name` | `str` | The name of the reducer that was invoked | +| `status` | `str` | The status of the reducer invocation ("committed", "failed", "outofenergy") | +| `message` | `str` | The message returned by the reducer if it fails | +| `args` | `List[str]` | The arguments passed to the reducer | This class contains the information about a reducer event to be passed to row update callbacks. @@ -173,7 +199,7 @@ This function is autogenerated for each reducer in your module. It is used to in ### Function `register_on_{REDUCER_NAME}` ```python -def register_on_{REDUCER_NAME}(callback: Callable[[Identity, str, str, ARG1_TYPE, ARG1_TYPE], None]) +def register_on_{REDUCER_NAME}(callback: Callable[[Identity, Optional[Address], str, str, ARG1_TYPE, ARG1_TYPE], None]) ``` | Argument | Type | Meaning | @@ -183,6 +209,7 @@ def register_on_{REDUCER_NAME}(callback: Callable[[Identity, str, str, ARG1_TYPE Register a callback function to be executed when the reducer is invoked. Callback arguments are: - `caller_identity`: The identity of the user who invoked the reducer. +- `caller_address`: The address of the user who invoked the reducer, or `None` for scheduled reducers. - `status`: The status of the reducer invocation ("committed", "failed", "outofenergy"). - `message`: The message returned by the reducer if it fails. - `args`: Variable number of arguments passed to the reducer. @@ -326,7 +353,7 @@ spacetime_client.schedule_event(0.1, application_tick) ### API at a glance | Definition | Description | -| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +|------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| | Function [`SpacetimeDBClient::init`](#function-init) | Create a network manager instance. | | Function [`SpacetimeDBClient::subscribe`](#function-subscribe) | Subscribe to receive data and transaction updates for the provided queries. | | Function [`SpacetimeDBClient::register_on_event`](#function-register_on_event) | Register a callback function to handle transaction update events. | @@ -349,24 +376,24 @@ def init( autogen_package: module, on_connect: Callable[[], NoneType] = None, on_disconnect: Callable[[str], NoneType] = None, - on_identity: Callable[[str, Identity], NoneType] = None, + on_identity: Callable[[str, Identity, Address], NoneType] = None, on_error: Callable[[str], NoneType] = None ) ``` Create a network manager instance. -| Argument | Type | Meaning | -| ----------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `auth_token` | `str` | This is the token generated by SpacetimeDB that matches the user's identity. If None, token will be generated | -| `host` | `str` | Hostname:port for SpacetimeDB connection | -| `address_or_name` | `str` | The name or address of the database to connect to | -| `ssl_enabled` | `bool` | Whether to use SSL when connecting to the server. | -| `autogen_package` | `ModuleType` | Python package where SpacetimeDB module generated files are located. | -| `on_connect` | `Callable[[], None]` | Optional callback called when a connection is made to the SpacetimeDB module. | -| `on_disconnect` | `Callable[[str], None]` | Optional callback called when the Python client is disconnected from the SpacetimeDB module. The argument is the close message. | -| `on_identity` | `Callable[[str, Identity], None]` | Called when the user identity is recieved from SpacetimeDB. First argument is the auth token used to login in future sessions. | -| `on_error` | `Callable[[str], None]` | Optional callback called when the Python client connection encounters an error. The argument is the error message. | +| Argument | Type | Meaning | +|-------------------|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `auth_token` | `str` | This is the token generated by SpacetimeDB that matches the user's identity. If None, token will be generated | +| `host` | `str` | Hostname:port for SpacetimeDB connection | +| `address_or_name` | `str` | The name or address of the database to connect to | +| `ssl_enabled` | `bool` | Whether to use SSL when connecting to the server. | +| `autogen_package` | `ModuleType` | Python package where SpacetimeDB module generated files are located. | +| `on_connect` | `Callable[[], None]` | Optional callback called when a connection is made to the SpacetimeDB module. | +| `on_disconnect` | `Callable[[str], None]` | Optional callback called when the Python client is disconnected from the SpacetimeDB module. The argument is the close message. | +| `on_identity` | `Callable[[str, Identity, Address], None]` | Called when the user identity is recieved from SpacetimeDB. First argument is the auth token used to login in future sessions. Third argument is the client connection's [`Address`](#type-address). | +| `on_error` | `Callable[[str], None]` | Optional callback called when the Python client connection encounters an error. The argument is the error message. | This function creates a new SpacetimeDBClient instance. It should be called before any other functions in the SpacetimeDBClient class. This init will call connect for you. diff --git a/docs/Client SDK Languages/Python/index.md b/docs/Client SDK Languages/Python/index.md index 29552c32..25723fcc 100644 --- a/docs/Client SDK Languages/Python/index.md +++ b/docs/Client SDK Languages/Python/index.md @@ -215,11 +215,12 @@ def print_message(message): We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `register_on_` method, which is automatically implemented for each reducer by `spacetime generate`. -Each reducer callback takes three fixed arguments: - +Each reducer callback takes four fixed arguments: + 1. The `Identity` of the client who requested the reducer invocation. -2. The `Status` of the reducer run, one of `committed`, `failed` or `outofenergy`. -3. The `Message` returned by the reducer in error cases, or `None` if the reducer succeeded. +2. The `Address` of the client who requested the reducer invocation, or `None` for scheduled reducers. +3. The `Status` of the reducer run, one of `committed`, `failed` or `outofenergy`. +4. The `Message` returned by the reducer in error cases, or `None` if the reducer succeeded. It also takes a variable number of arguments which match the calling arguments of the reducer. @@ -237,8 +238,8 @@ We'll test both that our identity matches the sender and that the status is `fai Add this function before the `register_callbacks` function: ```python -def on_set_name_reducer(sender, status, message, name): - if sender == local_identity: +def on_set_name_reducer(sender_id, sender_address, status, message, name): + if sender_id == local_identity: if status == "failed": print(f"Failed to set name: {message}") ``` @@ -250,10 +251,10 @@ We handle warnings on rejected messages the same way as rejected names, though t Add this function before the `register_callbacks` function: ```python -def on_send_message_reducer(sender, _addr, status, message, msg): - if sender == local_identity: +def on_send_message_reducer(sender_id, sender_address, status, message, msg): + if sender_id == local_identity: if status == "failed": - print(f"Failed to send message: {message}") + print(f"Failed to send message: {message}") ``` ### OnSubscriptionApplied callback @@ -301,10 +302,11 @@ def check_commands(): This callback fires after the client connects to the server. We'll use it to save our credentials to a file so that we can re-authenticate as the same user next time we connect. -The `on_connect` callback takes two arguments: +The `on_connect` callback takes three arguments: 1. The `Auth Token` is the equivalent of your private key. This is the only way to authenticate with the SpacetimeDB module as this user. 2. The `Identity` is the equivalent of your public key. This is used to uniquely identify this user and will be sent to other clients. We store this in a global variable so we can use it to identify that a given message or transaction was sent by us. +3. The `Address` is an opaque identifier modules can use to distinguish multiple concurrent connections by the same `Identity`. We don't need to know our `Address`, so we'll ignore that argument. To store our auth token, we use the optional component `local_config`, which provides a simple interface for storing and retrieving a single `Identity` from a file. We'll use the `local_config::set_string` method to store the auth token. Other projects might want to associate this token with some other identifier such as an email address or Steam ID. diff --git a/docs/Client SDK Languages/Rust/SDK Reference.md b/docs/Client SDK Languages/Rust/SDK Reference.md index c61a06f3..bd914b00 100644 --- a/docs/Client SDK Languages/Rust/SDK Reference.md +++ b/docs/Client SDK Languages/Rust/SDK Reference.md @@ -46,9 +46,11 @@ mod module_bindings; | Type [`spacetimedb_sdk::identity::Identity`](#type-identity) | A unique public identifier for a client. | | Type [`spacetimedb_sdk::identity::Token`](#type-token) | A private authentication token corresponding to an `Identity`. | | Type [`spacetimedb_sdk::identity::Credentials`](#type-credentials) | An `Identity` paired with its `Token`. | +| Type [`spacetimedb_sdk::Address`](#type-address) | An opaque identifier for differentiating connections by the same `Identity`. | | Function [`spacetimedb_sdk::identity::identity`](#function-identity) | Return the current connection's `Identity`. | | Function [`spacetimedb_sdk::identity::token`](#function-token) | Return the current connection's `Token`. | | Function [`spacetimedb_sdk::identity::credentials`](#function-credentials) | Return the current connection's [`Credentials`](#type-credentials). | +| Function [`spacetimedb_sdk::identity::address`](#function-address) | Return the current connection's [`Address`](#type-address). | | Function [`spacetimedb_sdk::identity::on_connect`](#function-on-connect) | Register a `FnMut` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::once_on_connect`](#function-once_on_connect) | Register a `FnOnce` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::remove_on_connect`](#function-remove_on_connect) | Cancel an `on_connect` or `once_on_connect` callback. | @@ -419,6 +421,14 @@ Credentials, including a private access token, sufficient to authenticate a clie | `identity` | [`Identity`](#type-identity) | | `token` | [`Token`](#type-token) | +### Type `Address` + +```rust +spacetimedb_sdk::Address +``` + +An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). + ### Function `identity` ```rust @@ -494,21 +504,40 @@ println!("My credentials are {:?}", credentials()); // })" ``` +### Function `address` + +```rust +spacetimedb_sdk::identity::address() -> Result
+``` + +Read the current connection's [`Address`](#type-address). + +Returns an error if [`connect`](#function-connect) has not yet been called. + +```rust +connect(SPACETIMEDB_URI, DB_NAME, None) + .expect("Failed to connect"); + +sleep(Duration::from_secs(1)); + +println!("My address is {:?}", address()); +``` + ### Function `on_connect` ```rust spacetimedb_sdk::identity::on_connect( - callback: impl FnMut(&Credentials) + Send + 'static, + callback: impl FnMut(&Credentials, Address) + Send + 'static, ) -> ConnectCallbackId ``` Register a callback to be invoked upon authentication with the database. -| Argument | Type | Meaning | -| ---------- | ----------------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut(&Credentials) + Send + 'sync` | Callback to be invoked upon successful authentication. | - -The callback will be invoked with the [`Credentials`](#type-credentials) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. +| Argument | Type | Meaning | +|------------|----------------------------------------------------|--------------------------------------------------------| +| `callback` | `impl FnMut(&Credentials, Address) + Send + 'sync` | Callback to be invoked upon successful authentication. | + +The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. The [`Credentials`](#type-credentials) passed to the callback can be saved and used to authenticate the same user in future connections. @@ -516,7 +545,8 @@ The returned `ConnectCallbackId` can be passed to [`remove_on_connect`](#functio ```rust on_connect( - |creds| println!("Successfully connected! My credentials are: {:?}", creds) + |creds, addr| + println!("Successfully connected! My credentials are: {:?} and my address is: {:?}", creds, addr) ); connect(SPACETIMEDB_URI, DB_NAME, None) @@ -532,17 +562,17 @@ sleep(Duration::from_secs(1)); ```rust spacetimedb_sdk::identity::once_on_connect( - callback: impl FnOnce(&Credentials) + Send + 'static, + callback: impl FnOnce(&Credentials, Address) + Send + 'static, ) -> ConnectCallbackId ``` Register a callback to be invoked once upon authentication with the database. -| Argument | Type | Meaning | -| ---------- | ------------------------------------------ | ---------------------------------------------------------------- | -| `callback` | `impl FnOnce(&Credentials) + Send + 'sync` | Callback to be invoked once upon next successful authentication. | +| Argument | Type | Meaning | +|------------|-----------------------------------------------------|------------------------------------------------------------------| +| `callback` | `impl FnOnce(&Credentials, Address) + Send + 'sync` | Callback to be invoked once upon next successful authentication. | -The callback will be invoked with the [`Credentials`](#type-credentials) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. +The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. The [`Credentials`](#type-credentials) passed to the callback can be saved and used to authenticate the same user in future connections. @@ -565,7 +595,7 @@ Unregister a previously-registered [`on_connect`](#function-on_connect) or [`onc If `id` does not refer to a currently-registered callback, this operation does nothing. ```rust -let id = on_connect(|_creds| unreachable!()); +let id = on_connect(|_creds, _addr| unreachable!()); remove_on_connect(id); @@ -631,7 +661,7 @@ const CREDENTIALS_DIR = ".my-module"; let creds = load_credentials(CREDENTIALS_DIRectory) .expect("Error while loading credentials"); -on_connect(|creds| { +on_connect(|creds, _addr| { if let Err(e) = save_credentials(CREDENTIALS_DIR, creds) { eprintln!("Error while saving credentials: {:?}", e); } @@ -1068,7 +1098,7 @@ For reducers which accept a `ReducerContext` as their first argument, the `Reduc ```rust module_bindings::on_{REDUCER}( - callback: impl FnMut(&Identity, Status, {&ARGS...}) + Send + 'static, + callback: impl FnMut(&Identity, Option
, Status, {&ARGS...}) + Send + 'static, ) -> ReducerCallbackId<{REDUCER}Args> ``` @@ -1076,12 +1106,12 @@ For each reducer defined by a module, `spacetime generate` generates a function | Argument | Type | Meaning | | ---------- | ------------------------------------------------------------- | ------------------------------------------------ | -| `callback` | `impl FnMut(&Identity, &Status, {&ARGS...}) + Send + 'static` | Callback to run whenever the reducer is invoked. | +| `callback` | `impl FnMut(&Identity, Option
&Status, {&ARGS...}) + Send + 'static` | Callback to run whenever the reducer is invoked. | -The callback always accepts two arguments: +The callback always accepts three arguments: -- `caller: &Identity`, the [`Identity`](#type-identity) of the client which invoked the reducer. -- `status: &Status`, the termination [`Status`](#type-status) of the reducer run. +- `caller_id: &Identity`, the [`Identity`](#type-identity) of the client which invoked the reducer. +- `caller_address: Option
`, the [`Address`](#type-address) of the client which invoked the reducer. This may be `None` for scheduled reducers. In addition, the callback accepts a reference to each of the reducer's arguments. @@ -1096,7 +1126,7 @@ The `on_{REDUCER}` function returns a `ReducerCallbackId<{REDUCER}Args>`, where ```rust module_bindings::once_on_{REDUCER}( - callback: impl FnOnce(&Identity, &Status, {&ARGS...}) + Send + 'static, + callback: impl FnOnce(&Identity, Option
, &Status, {&ARGS...}) + Send + 'static, ) -> ReducerCallbackId<{REDUCER}Args> ``` @@ -1104,7 +1134,7 @@ For each reducer defined by a module, `spacetime generate` generates a function | Argument | Type | Meaning | | ---------- | -------------------------------------------------------------- | ----------------------------------------------------- | -| `callback` | `impl FnOnce(&Identity, &Status, {&ARGS...}) + Send + 'static` | Callback to run the next time the reducer is invoked. | +| `callback` | `impl FnOnce(&Identity, Option
, &Status, {&ARGS...}) + Send + 'static` | Callback to run the next time the reducer is invoked. | The callback accepts the same arguments as an [on-reducer callback](#function-on_reducer), but may be a `FnOnce` rather than a `FnMut`. diff --git a/docs/Client SDK Languages/Rust/index.md b/docs/Client SDK Languages/Rust/index.md index c44ab49d..f35f0829 100644 --- a/docs/Client SDK Languages/Rust/index.md +++ b/docs/Client SDK Languages/Rust/index.md @@ -28,7 +28,7 @@ cargo new client Below the `[dependencies]` line in `client/Cargo.toml`, add: ```toml -spacetimedb-sdk = "0.6" +spacetimedb-sdk = "0.7" hex = "0.4" ``` @@ -84,6 +84,7 @@ To `client/src/main.rs`, add: ```rust use spacetimedb_sdk::{ + Address, disconnect, identity::{load_credentials, once_on_connect, save_credentials, Credentials, Identity}, on_disconnect, on_subscription_applied, @@ -160,18 +161,20 @@ fn register_callbacks() { ### Save credentials -Each client has a `Credentials`, which consists of two parts: +Each user has a `Credentials`, which consists of two parts: - An `Identity`, a unique public identifier. We're using these to identify `User` rows. - A `Token`, a private key which SpacetimeDB uses to authenticate the client. `Credentials` are generated by SpacetimeDB each time a new client connects, and sent to the client so they can be saved, in order to re-connect with the same identity. The Rust SDK provides a pair of functions, `save_credentials` and `load_credentials`, for storing these credentials in a file. We'll save our credentials into a file in the directory `~/.spacetime_chat`, which should be unintrusive. If saving our credentials fails, we'll print a message to standard error, but otherwise continue normally; even though the user won't be able to reconnect with the same identity, they can still chat normally. +Each client also has an `Address`, which modules can use to distinguish multiple concurrent connections by the same `Identity`. We don't need to know our `Address`, so we'll ignore that argument. + To `client/src/main.rs`, add: ```rust /// Our `on_connect` callback: save our credentials to a file. -fn on_connected(creds: &Credentials) { +fn on_connected(creds: &Credentials, _client_address: Address) { if let Err(e) = save_credentials(CREDS_DIR, creds) { eprintln!("Failed to save credentials: {:?}", e); } @@ -303,10 +306,11 @@ fn on_sub_applied() { We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `on_reducer` method of the `Reducer` trait, which is automatically implemented for each reducer by `spacetime generate`. -Each reducer callback takes at least two arguments: +Each reducer callback takes at least three arguments: 1. The `Identity` of the client who requested the reducer invocation. -2. The `Status` of the reducer run, one of `Committed`, `Failed` or `OutOfEnergy`. `Status::Failed` holds the error which caused the reducer to fail, as a `String`. +2. The `Address` of the client who requested the reducer invocation, which may be `None` for scheduled reducers. +3. The `Status` of the reducer run, one of `Committed`, `Failed` or `OutOfEnergy`. `Status::Failed` holds the error which caused the reducer to fail, as a `String`. In addition, it takes a reference to each of the arguments passed to the reducer itself. @@ -323,7 +327,7 @@ To `client/src/main.rs`, add: ```rust /// Our `on_set_name` callback: print a warning if the reducer failed. -fn on_name_set(_sender: &Identity, status: &Status, name: &String) { +fn on_name_set(_sender_id: &Identity, _sender_address: Option
, status: &Status, name: &String) { if let Status::Failed(err) = status { eprintln!("Failed to change name to {:?}: {}", name, err); } @@ -338,7 +342,7 @@ To `client/src/main.rs`, add: ```rust /// Our `on_send_message` callback: print a warning if the reducer failed. -fn on_message_sent(_sender: &Identity, status: &Status, text: &String) { +fn on_message_sent(_sender_id: &Identity, _sender_address: Option
, status: &Status, text: &String) { if let Status::Failed(err) = status { eprintln!("Failed to send message {:?}: {}", text, err); } diff --git a/docs/Client SDK Languages/Typescript/SDK Reference.md b/docs/Client SDK Languages/Typescript/SDK Reference.md index 657115d7..fb7d5be6 100644 --- a/docs/Client SDK Languages/Typescript/SDK Reference.md +++ b/docs/Client SDK Languages/Typescript/SDK Reference.md @@ -91,12 +91,13 @@ console.log(Person, AddReducer, SayHelloReducer); ### Classes -| Class | Description | -| ----------------------------------------------- | ---------------------------------------------------------------- | -| [`SpacetimeDBClient`](#class-spacetimedbclient) | The database client connection to a SpacetimeDB server. | -| [`Identity`](#class-identity) | The user's public identity. | -| [`{Table}`](#class-table) | `{Table}` is a placeholder for each of the generated tables. | -| [`{Reducer}`](#class-reducer) | `{Reducer}` is a placeholder for each of the generated reducers. | +| Class | Description | +|-------------------------------------------------|------------------------------------------------------------------------------| +| [`SpacetimeDBClient`](#class-spacetimedbclient) | The database client connection to a SpacetimeDB server. | +| [`Identity`](#class-identity) | The user's public identity. | +| [`Address`](#class-address) | An opaque identifier for differentiating connections by the same `Identity`. | +| [`{Table}`](#class-table) | `{Table}` is a placeholder for each of the generated tables. | +| [`{Reducer}`](#class-reducer) | `{Reducer}` is a placeholder for each of the generated reducers. | ### Class `SpacetimeDBClient` @@ -288,23 +289,24 @@ Register a callback to be invoked upon authentication with the database. onConnect(callback: (token: string, identity: Identity) => void): void ``` -The callback will be invoked with the public [Identity](#class-identity) and private authentication token provided by the database to identify this connection. If credentials were supplied to [connect](#spacetimedbclient-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of credentials will be generated by the database to identify this user. +The callback will be invoked with the public user [Identity](#class-identity), private authentication token and connection [`Address`](#class-address) provided by the database. If credentials were supplied to [connect](#spacetimedbclient-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of credentials will be generated by the database to identify this user. The credentials passed to the callback can be saved and used to authenticate the same user in future connections. #### Parameters -| Name | Type | -| :--------- | :----------------------------------------------------------------------- | -| `callback` | (`token`: `string`, `identity`: [`Identity`](#class-identity)) => `void` | +| Name | Type | +|:-----------|:-----------------------------------------------------------------------------------------------------------------| +| `callback` | (`token`: `string`, `identity`: [`Identity`](#class-identity), `address`: [`Address`](#class-address)) => `void` | #### Example ```ts -spacetimeDBClient.onConnect((token, identity) => { - console.log("Connected to SpacetimeDB"); - console.log("Token", token); - console.log("Identity", identity); +spacetimeDBClient.onConnect((token, identity, address) => { + console.log("Connected to SpacetimeDB"); + console.log("Token", token); + console.log("Identity", identity); + console.log("Address", address); }); ``` @@ -334,7 +336,7 @@ spacetimeDBClient.onError((...args: any[]) => { ### Class `Identity` -A unique public identifier for a client connected to a database. +A unique public identifier for a user of a database. Defined in [spacetimedb-sdk.identity](https://github.com/clockworklabs/spacetimedb-typescript-sdk/blob/main/src/identity.ts): @@ -415,6 +417,89 @@ Identity.fromString(str: string): Identity [`Identity`](#class-identity) +### Class `Address` + +An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). + +Defined in [spacetimedb-sdk.address](https://github.com/clockworklabs/spacetimedb-typescript-sdk/blob/main/src/address.ts): + +| Constructors | Description | +| ----------------------------------------------- | -------------------------------------------- | +| [`Address.constructor`](#address-constructor) | Creates a new `Address`. | +| Methods | | +| [`Address.isEqual`](#address-isequal) | Compare two identities for equality. | +| [`Address.toHexString`](#address-tohexstring) | Print the address as a hexadecimal string. | +| Static methods | | +| [`Address.fromString`](#address-fromstring) | Parse an Address from a hexadecimal string. | + +## Constructors + +### `Address` constructor + +```ts +new Address(data: Uint8Array) +``` + +#### Parameters + +| Name | Type | +| :----- | :----------- | +| `data` | `Uint8Array` | + +## Methods + +### `Address` isEqual + +Compare two addresses for equality. + +```ts +isEqual(other: Address): boolean +``` + +#### Parameters + +| Name | Type | +| :------ | :---------------------------- | +| `other` | [`Address`](#class-address) | + +#### Returns + +`boolean` + +___ + +### `Address` toHexString + +Print an `Address` as a hexadecimal string. + +```ts +toHexString(): string +``` + +#### Returns + +`string` + +___ + +### `Address` fromString + +Static method; parse an Address from a hexadecimal string. + +```ts +Address.fromString(str: string): Address +``` + +#### Parameters + +| Name | Type | +| :---- | :------- | +| `str` | `string` | + +#### Returns + +[`Address`](#class-address) + ### Class `{Table}` For each table defined by a module, `spacetime generate` generates a `class` in the `module_bindings` folder whose name is that table's name converted to `PascalCase`. @@ -475,7 +560,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); setTimeout(() => { @@ -506,7 +591,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); setTimeout(() => { @@ -545,7 +630,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); setTimeout(() => { @@ -613,7 +698,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "ws://localhost:3000", "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); }); @@ -667,7 +752,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "ws://localhost:3000", "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); }); @@ -715,7 +800,7 @@ var spacetimeDBClient = new SpacetimeDBClient( "ws://localhost:3000", "database_name" ); -spacetimeDBClient.onConnect((token, identity) => { +spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(["SELECT * FROM Person"]); }); diff --git a/docs/Client SDK Languages/Typescript/index.md b/docs/Client SDK Languages/Typescript/index.md index ae893af5..8baed6fb 100644 --- a/docs/Client SDK Languages/Typescript/index.md +++ b/docs/Client SDK Languages/Typescript/index.md @@ -170,7 +170,7 @@ We need to import these types into our `client/src/App.tsx`. While we are at it, > There is a known issue where if you do not use every type in your file, it will not pull them into the published build. To fix this, we are using `console.log` to force them to get pulled in. ```typescript -import { SpacetimeDBClient, Identity } from "@clockworklabs/spacetimedb-sdk"; +import { SpacetimeDBClient, Identity, Address } from "@clockworklabs/spacetimedb-sdk"; import Message from "./module_bindings/message"; import User from "./module_bindings/user"; @@ -224,7 +224,7 @@ We will add callbacks for each of these items in the following sections. All of On connect SpacetimeDB will provide us with our client credentials. -Each client has a credentials which consists of two parts: +Each user has a set of credentials, which consists of two parts: - An `Identity`, a unique public identifier. We're using these to identify `User` rows. - A `Token`, a private key which SpacetimeDB uses to authenticate the client. @@ -233,12 +233,14 @@ These credentials are generated by SpacetimeDB each time a new client connects, We want to store our local client identity in a stateful variable and also save our `token` to local storage for future connections. +Each client also has an `Address`, which modules can use to distinguish multiple concurrent connections by the same `Identity`. We don't need to know our `Address`, so we'll ignore that argument. + Once we are connected, we can send our subscription to the SpacetimeDB module. SpacetimeDB is set up so that each client subscribes via SQL queries to some subset of the database, and is notified about changes only to that subset. For complex apps with large databases, judicious subscriptions can save each client significant network bandwidth, memory and computation compared. For example, in [BitCraft](https://bitcraftonline.com), each player's client subscribes only to the entities in the "chunk" of the world where that player currently resides, rather than the entire game world. Our app is much simpler than BitCraft, so we'll just subscribe to the whole database. To the body of `App`, add: ```typescript -client.current.onConnect((token, identity) => { +client.current.onConnect((token, identity, address) => { console.log("Connected to SpacetimeDB"); local_identity.current = identity; diff --git a/docs/Overview/index.md b/docs/Overview/index.md index 2464e6e3..0e1a6394 100644 --- a/docs/Overview/index.md +++ b/docs/Overview/index.md @@ -44,9 +44,9 @@ SpacetimeDB syncs client and server state for you so that you can just write you ## Identities -An important concept in SpacetimeDB is that of an `Identity`. An `Identity` represents who someone is. It is a unique identifier that is used to authenticate and authorize access to the database. Importantly, while it represents who someone is, does NOT represent what they can do. Your application's logic will determine what a given identity is able to do by allowing or disallowing a transaction based on the `Identity`. +A SpacetimeDB `Identity` is a unique identifier that is used to authenticate and authorize access to the database. Importantly, while it represents who someone is, does NOT represent what they can do. Your application's logic will determine what a given identity is able to do by allowing or disallowing a transaction based on the caller's `Identity` along with any module-defined data and logic. -SpacetimeDB associates each client with a 256-bit (32-byte) integer `Identity`. These identities are usually formatted as 64-digit hexadecimal strings. Identities are public information, and applications can use them to identify users. Identities are a global resource, so a user can use the same identity with multiple applications, so long as they're hosted by the same SpacetimeDB instance. +SpacetimeDB associates each user with a 256-bit (32-byte) integer `Identity`. These identities are usually formatted as 64-digit hexadecimal strings. Identities are public information, and applications can use them to identify users. Identities are a global resource, so a user can use the same identity with multiple applications, so long as they're hosted by the same SpacetimeDB instance. Each identity has a corresponding authentication token. The authentication token is private, and should never be shared with anyone. Specifically, authentication tokens are [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519) signed by a secret unique to the SpacetimeDB instance. @@ -54,6 +54,14 @@ Additionally, each database has an owner `Identity`. Many database maintenance o SpacetimeDB provides tools in the CLI and the [client SDKs](/docs/client-languages/client-sdk-overview) for managing credentials. +## Addresses + +A SpacetimeDB `Address` is an opaque identifier for a database or a client connection. An `Address` is a 128-bit integer, usually formatted as a 32-character (16-byte) hexadecimal string. + +Each SpacetimeDB database has an `Address`, generated by the SpacetimeDB host, which can be used to connect to the database or to request information about it. Databases may also have human-readable names, which are mapped to addresses internally. + +Each client connection has an `Address`. These addresses are opaque, and do not correspond to any metadata about the client. They are notably not IP addresses or device identifiers. A client connection can be uniquely identified by its `(Identity, Address)` pair, but client addresses may not be globally unique; it is possible for multiple connections with the same `Address` but different identities to co-exist. SpacetimeDB modules should treat `Identity` as differentiating users, and `Address` as differentiating connections by the same user. + ## Language Support ### Server-side Libraries diff --git a/docs/Server Module Languages/C#/ModuleReference.md b/docs/Server Module Languages/C#/ModuleReference.md index 305ea211..d655ea6d 100644 --- a/docs/Server Module Languages/C#/ModuleReference.md +++ b/docs/Server Module Languages/C#/ModuleReference.md @@ -116,7 +116,9 @@ The following types are supported out of the box and can be stored in the databa And a couple of special custom types: - `SpacetimeDB.SATS.Unit` - semantically equivalent to an empty struct, sometimes useful in generic contexts where C# doesn't permit `void`. -- `Identity` (`SpacetimeDB.Runtime.Identity`) - a unique identifier for each connected client; internally a byte blob but can be printed, hashed and compared for equality. +- `Identity` (`SpacetimeDB.Runtime.Identity`) - a unique identifier for each user; internally a byte blob but can be printed, hashed and compared for equality. +- `Address` (`SpacetimeDB.Runtime.Address`) - an identifier which disamgibuates connections by the same `Identity`; internally a byte blob but can be printed, hashed and compared for equality. + #### Custom types @@ -245,13 +247,14 @@ public static void Add(string name, int age) } ``` -If a reducer has an argument with a type `DbEventArgs` (`SpacetimeDB.Runtime.DbEventArgs`), it will be provided with event details such as the sender identity (`SpacetimeDB.Runtime.Identity`) and the time (`DateTimeOffset`) of the invocation: +If a reducer has an argument with a type `DbEventArgs` (`SpacetimeDB.Runtime.DbEventArgs`), it will be provided with event details such as the sender identity (`SpacetimeDB.Runtime.Identity`), sender address (`SpacetimeDB.Runtime.Address?`) and the time (`DateTimeOffset`) of the invocation: ```csharp [SpacetimeDB.Reducer] public static void PrintInfo(DbEventArgs e) { - Log($"Sender: {e.Sender}"); + Log($"Sender identity: {e.Sender}"); + Log($"Sender address: {e.Address}"); Log($"Time: {e.Time}"); } ``` diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index e849002f..6893a089 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -61,7 +61,8 @@ static partial class Module To get our chat server running, we'll need to store two kinds of data: information about each user, and records of all the messages that have been sent. -For each `User`, we'll store the `Identity` of their client connection, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the `Identity` as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates. +For each `User`, we'll store their `Identity`, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the `Identity` as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates. + In `server/Lib.cs`, add the definition of the table `User` to the `Module` class: @@ -94,7 +95,7 @@ In `server/Lib.cs`, add the definition of the table `Message` to the `Module` cl We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `SetName` which clients can invoke to set their `User.Name`. It will validate the caller's chosen name, using a function `ValidateName` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -Each reducer may accept as its first argument a `DbEventArgs`, which includes the `Identity` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `dbEvent.Sender`. ++Each reducer may accept as its first argument a `DbEventArgs`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `dbEvent.Sender`. It's also possible to call `SetName` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. diff --git a/docs/Server Module Languages/Rust/index.md b/docs/Server Module Languages/Rust/index.md index ed59d8dd..9f0a6636 100644 --- a/docs/Server Module Languages/Rust/index.md +++ b/docs/Server Module Languages/Rust/index.md @@ -55,14 +55,14 @@ From `spacetimedb`, we import: - `spacetimedb`, an attribute macro we'll use to define tables and reducers. - `ReducerContext`, a special argument passed to each reducer. -- `Identity`, a unique identifier for each connected client. +- `Identity`, a unique identifier for each user. - `Timestamp`, a point in time. Specifically, an unsigned 64-bit count of milliseconds since the UNIX epoch. ## Define tables To get our chat server running, we'll need to store two kinds of data: information about each user, and records of all the messages that have been sent. -For each `User`, we'll store the `Identity` of their client connection, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the `Identity` as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates. +For each `User`, we'll store their `Identity`, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the `Identity` as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates. To `server/src/lib.rs`, add the definition of the table `User`: @@ -93,7 +93,7 @@ pub struct Message { We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `set_name` which clients can invoke to set their `User.name`. It will validate the caller's chosen name, using a function `validate_name` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.sender`. +Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.sender`. It's also possible to call `set_name` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. diff --git a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md index 92f1a04c..4d51790f 100644 --- a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md +++ b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md @@ -345,14 +345,14 @@ We use the `connect` and `disconnect` reducers to update the logged in state of ```rust #[spacetimedb(connect)] -pub fn identity_connected(ctx: ReducerContext) { +pub fn client_connected(ctx: ReducerContext) { // called when the client connects, we update the logged_in state to true update_player_login_state(ctx, true); } #[spacetimedb(disconnect)] -pub fn identity_disconnected(ctx: ReducerContext) { +pub fn client_disconnected(ctx: ReducerContext) { // Called when the client disconnects, we update the logged_in state to false update_player_login_state(ctx, false); } @@ -545,7 +545,7 @@ The "local client cache" is a client-side view of the database, defined by the s // called when we receive the client identity from SpacetimeDB - SpacetimeDBClient.instance.onIdentityReceived += (token, identity) => { + SpacetimeDBClient.instance.onIdentityReceived += (token, identity, address) => { AuthToken.SaveToken(token); local_identity = identity; }; From f8a7b2761fca91d9ba4ae331f4c078455e8c51cf Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Thu, 12 Oct 2023 21:01:39 -0500 Subject: [PATCH 09/80] This was an error in applying a patch (#6) Co-authored-by: John Detter --- docs/Server Module Languages/C#/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index 6893a089..473a8ac6 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -95,7 +95,7 @@ In `server/Lib.cs`, add the definition of the table `Message` to the `Module` cl We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `SetName` which clients can invoke to set their `User.Name`. It will validate the caller's chosen name, using a function `ValidateName` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -+Each reducer may accept as its first argument a `DbEventArgs`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `dbEvent.Sender`. +Each reducer may accept as its first argument a `DbEventArgs`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `dbEvent.Sender`. It's also possible to call `SetName` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. From 91ace6a6a8ed7213b60d2456eb1be8d3c0e2d2f1 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Sat, 14 Oct 2023 03:23:44 -0500 Subject: [PATCH 10/80] Unity Tutorial Updates (#7) * Updated server side module * More simplification * More progress * More updates * Ready to start testing again * Got through tutorial with some issues, going again * Added warnings * Small fix, this is ready to be released --------- Co-authored-by: John Detter --- .../Part 1 - Basic Multiplayer.md | 861 ++++++++---------- .../Part 2 - Resources And Scheduling.md | 2 + docs/Unity Tutorial/Part 3 - BitCraft Mini.md | 2 + 3 files changed, 405 insertions(+), 460 deletions(-) diff --git a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md index 4d51790f..915fd444 100644 --- a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md +++ b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md @@ -2,27 +2,42 @@ ![UnityTutorial-HeroImage](/images/unity-tutorial/UnityTutorial-HeroImage.JPG) +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! + The objective of this tutorial is to help you become acquainted with the basic features of SpacetimeDB. By the end of this tutorial you should have a basic understanding of what SpacetimeDB offers for developers making multiplayer games. It assumes that you have a basic understanding of the Unity Editor, using a command line terminal, and coding. +In this tutorial we'll be giving you some CLI commands to execute. If you are using Windows we recommend using Git Bash or powershell. If you're on mac we recommend you use the Terminal application. If you encouter issues with any of the commands in this guide, please reach out to us through our discord server and we would be happy to help assist you. + +This tutorial has been tested against UnityEngine version 2022.3.4f1. This tutorial may work on newer versions as well. + +## Prepare Project Structure + +This project is separated into two sub-projects, one for the server (module) code and one for the client code. First we'll create the main directory, this directory name doesn't matter but we'll give you an example: + +```bash +mkdir SpacetimeDBUnityTutorial +cd SpacetimeDBUnityTutorial +``` + +In the following sections we'll be adding a client directory and a server directory, which will contain the client files and the module (server) files respectively. We'll start by populating the client directory. + ## Setting up the Tutorial Unity Project -In this section, we will guide you through the process of setting up the 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 ready to integrate SpacetimeDB functionality. +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 -1. Open Unity and create a new project by selecting "New" from the Unity Hub or going to **File -> New Project**. +Open Unity and create a new project by selecting "New" from the Unity Hub or going to **File -> New Project**. ![UnityHub-NewProject](/images/unity-tutorial/UnityHub-NewProject.JPG) -2. Choose a suitable project name and location. For this tutorial, we recommend creating an empty folder for your tutorial project and selecting that as the project location, with the project being named "Client". - -This allows you to have a single subfolder that contains both the Unity project in a folder called "Client" and the SpacetimeDB server module in a folder called "Server" which we will create later in this tutorial. +For Project Name use `client`. For Project Location make sure that you use your `SpacetimeDBUnityTutorial` directory. This is the directory that we created in a previous step. -Ensure that you have selected the **3D (URP)** template for this project. +**Important: Ensure that you have selected the 3D (URP) template for this project.** If you forget to do this then Unity won't be able to properly render the materials in the scene! ![UnityHub-3DURP](/images/unity-tutorial/UnityHub-3DURP.JPG) -3. Click "Create" to generate the blank project. +Click "Create" to generate the blank project. ### Step 2: Adding Required Packages @@ -50,6 +65,7 @@ In this step, we will import the provided Unity tutorial package that contains t 3. Browse and select the downloaded tutorial package file. 4. Unity will prompt you with an import settings dialog. Ensure that all the files are selected and click "Import" to import the package into your project. +5. At this point in the project, you shouldn't have any errors. ![Unity-ImportCustomPackage2](/images/unity-tutorial/Unity-ImportCustomPackage2.JPG) @@ -77,221 +93,145 @@ Congratulations! You have successfully set up the basic single-player game proje ## Writing our SpacetimeDB Server Module -### Step 1: Create the Module - -1. It is important that you already have SpacetimeDB [installed](/install). - -2. Run the SpacetimeDB standalone using the installed CLI. In your terminal or command window, run the following command: - -```bash -spacetime start -``` +At this point you should have the single player game working. In your CLI, your current working directory should be within your `SpacetimeDBUnityTutorial` directory that we created in a previous step. -3. Make sure your CLI is pointed to your local instance of SpacetimeDB. You can do this by running the following command: +### Create the Module -```bash -spacetime server set http://localhost:3000 -``` +1. It is important that you already have the SpacetimeDB CLI tool [installed](/install). -4. Open a new command prompt or terminal and navigate to the folder where your Unity project is located using the cd command. For example: +2. Run SpacetimeDB locally using the installed CLI. In a **new** terminal or command window, run the following command: ```bash -cd path/to/tutorial_project_folder +spacetime start ``` -5. Run the following command to initialize the SpacetimeDB server project with Rust as the language: +3. Run the following command to initialize the SpacetimeDB server project with Rust as the language: ```bash -spacetime init --lang=rust ./Server +spacetime init --lang=rust server ``` -This command creates a new folder named "Server" within your Unity project directory and sets up the SpacetimeDB server project with Rust as the programming language. - -### Step 2: SpacetimeDB Tables - -1. Using your favorite code editor (we recommend VS Code) open the newly created lib.rs file in the Server folder. -2. Erase everything in the file as we are going to be writing our module from scratch. - ---- +This command creates a new folder named "server" within your Unity project directory and sets up the SpacetimeDB server project with Rust as the programming language. -**Understanding ECS** +### Understanding Entity Component Systems -ECS is a game development architecture that separates game objects into components for better flexibility and performance. You can read more about the ECS design pattern [here](https://en.wikipedia.org/wiki/Entity_component_system). +Entity Component System (ECS) is a game development architecture that separates game objects into components for better flexibility and performance. You can read more about the ECS design pattern [here](https://en.wikipedia.org/wiki/Entity_component_system). We chose ECS for this example project because it promotes scalability, modularity, and efficient data management, making it ideal for building multiplayer games with SpacetimeDB. ---- - -3. Add the following code to lib.rs. +### SpacetimeDB Tables -We are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. +In this section we'll be making some edits to the file `server/src/lib.rs`. We recommend you open up this file in an IDE like VSCode or RustRover. -You'll notice we have a custom `spacetimedb(table)` attribute that tells SpacetimeDB that this is a SpacetimeDB table. SpacetimeDB automatically generates several functions for us for inserting, updating and querying the table created as a result of this attribute. +**Important: Open the `server/src/lib.rs` file and delete its contents. We will be writing it from scratch here.** -The `primarykey` attribute on the version not only ensures uniqueness, preventing duplicate values for the column, but also guides the client to determine whether an operation should be an insert or an update. NOTE: Our `version` column in this `Config` table is always 0. This is a trick we use to store -global variables that can be accessed from anywhere. +First we need to add some imports at the top of the file. -We also use the built in rust `derive(Clone)` function to automatically generate a clone function for this struct that we use when updating the row. +**Copy and paste into lib.rs:** ```rust -use spacetimedb::{spacetimedb, Identity, SpacetimeType, Timestamp, ReducerContext}; +use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; use log; +``` + +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our rust module reference. Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +**Append to the bottom of lib.rs:** + +```rust +// We're using this table as a singleton, so there should typically only be one element where the version is 0. #[spacetimedb(table)] #[derive(Clone)] pub struct Config { - // Config is a global table with a single row. This table will be used to - // store configuration or global variables - #[primarykey] - // always 0 - // having a table with a primarykey field which is always zero is a way to store singleton global state pub version: u32, - pub message_of_the_day: String, } - ``` -The next few tables are all components in the ECS system for our spawnable entities. Spawnable Entities are any objects in the game simulation that can have a world location. In this tutorial we will have only one type of spawnable entity, the Player. +Next we're going to define a new `SpacetimeType` called `StdbVector3` 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. So therefore, `StdbVector3` is not itself a table. -The first component is the `SpawnableEntityComponent` that allows us to access any spawnable entity in the world by its entity_id. The `autoinc` attribute designates an auto-incrementing column in SpacetimeDB, generating sequential values for new entries. When inserting 0 with this attribute, it gets replaced by the next value in the sequence. +**Append to the bottom of lib.rs:** ```rust -#[spacetimedb(table)] -pub struct SpawnableEntityComponent { - // All entities that can be spawned in the world will have this component. - // This allows us to find all objects in the world by iterating through - // this table. It also ensures that all world objects have a unique - // entity_id. +// This allows us to store 3D points in tables. +#[derive(SpacetimeType, Clone)] +pub struct StdbVector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} +``` +Now we're going to create a table which actually uses the `StdbVector3` that we just defined. The `EntityComponent` is associated with all entities in the world, including players. + +```rust +// This stores information related to all entities in our game. In this tutorial +// all entities must at least have an entity_id, a position, a direction and they +// must specify whether or not they are moving. +#[spacetimedb(table)] +#[derive(Clone)] +pub struct EntityComponent { #[primarykey] + // The autoinc macro here just means every time we insert into this table + // we will receive a new row where this value will be increased by one. This + // allows us to easily get rows where `entity_id` is unique. #[autoinc] pub entity_id: u64, + pub position: StdbVector3, + pub direction: f32, + pub moving: bool, } ``` -The `PlayerComponent` table connects this entity to a SpacetimeDB identity - a user's "public key." In the context of this tutorial, each user is permitted to have just one Player entity. To guarantee this, we apply the `unique` attribute to the `owner_id` column. If a uniqueness constraint is required on a column aside from the `primarykey`, we make use of the `unique` attribute. This mechanism makes certain that no duplicate values exist within the designated column. +Next we will define the `PlayerComponent` table. The `PlayerComponent` table is used to store information related to players. Each player will have a row in this table, and will also have a row in the `EntityComponent` table with a matching `entity_id`. You'll see how this works later in the `create_player` reducer. + +**Append to the bottom of lib.rs:** ```rust +// All players have this component and it associates an entity with the user's +// Identity. It also stores their username and whether or not they're logged in. #[derive(Clone)] #[spacetimedb(table)] pub struct PlayerComponent { - // All players have this component and it associates the spawnable entity - // with the user's identity. It also stores their username. - + // An entity_id that matches an entity_id in the `EntityComponent` table. #[primarykey] pub entity_id: u64, + // The user's identity, which is unique to each player #[unique] pub owner_id: Identity, - - // username is provided to the create_player reducer pub username: String, - // this value is updated when the user logs in and out pub logged_in: bool, } ``` -The next component, `MobileLocationComponent`, is used to store the last known location and movement direction for spawnable entities that can move smoothly through the world. - -Using the `derive(SpacetimeType)` attribute, we define a custom SpacetimeType, StdbVector2, that stores 2D positions. Marking it a `SpacetimeType` allows it to be used in SpacetimeDB columns and reducer calls. - -We are also making use of the SpacetimeDB `Timestamp` type for the `move_start_timestamp` column. Timestamps represent the elapsed time since the Unix epoch (January 1, 1970, at 00:00:00 UTC) and are not dependent on any specific timezone. - -```rust -#[derive(SpacetimeType, Clone)] -pub struct StdbVector2 { - // A spacetime type which can be used in tables and reducers to represent - // a 2d position. - pub x: f32, - pub z: f32, -} - -impl StdbVector2 { - // this allows us to use StdbVector2::ZERO in reducers - pub const ZERO: StdbVector2 = StdbVector2 { x: 0.0, z: 0.0 }; -} - -#[spacetimedb(table)] -#[derive(Clone)] -pub struct MobileLocationComponent { - // This component will be created for all world objects that can move - // smoothly throughout the world. It keeps track of the position the last - // time the component was updated and the direction the mobile object is - // currently moving. - - #[primarykey] - pub entity_id: u64, - - // The last known location of this entity - pub location: StdbVector2, - // Movement direction, {0,0} if not moving at all. - pub direction: StdbVector2, - // Timestamp when movement started. Timestamp::UNIX_EPOCH if not moving. - pub move_start_timestamp: Timestamp, -} -``` - -Next we write our very first reducer, `create_player`. This reducer is called by the client after the user enters a username. - ---- - -**SpacetimeDB Reducers** - -"Reducer" is a term coined by SpacetimeDB that "reduces" a single function call into one or more database updates performed within a single transaction. Reducers can be called remotely using a client SDK or they can be scheduled to be called at some future time from another reducer call. - ---- - -The first argument to all reducers is the `ReducerContext`. This struct contains: `sender` the identity of the user that called the reducer and `timestamp` which is the `Timestamp` when the reducer was called. - -Before we begin creating the components for the player entity, we pass the sender identity to the auto-generated function `filter_by_owner_id` to see if there is already a player entity associated with this user's identity. Because the `owner_id` column is unique, the `filter_by_owner_id` function returns a `Option` that we can check to see if a matching row exists. - ---- - -**Rust Options** - -Rust programs use Option in a similar way to how C#/Unity programs use nullable types. Rust's Option is an enumeration type that represents the possibility of a value being either present (Some) or absent (None), providing a way to handle optional values and avoid null-related errors. For more information, refer to the official Rust documentation: [Rust Option](https://doc.rust-lang.org/std/option/). - ---- - -The first component we create and insert, `SpawnableEntityComponent`, automatically increments the `entity_id` property. When we use the insert function, it returns a result that includes the newly generated `entity_id`. We will utilize this generated `entity_id` in all other components associated with the player entity. +Next we write our very first reducer, `create_player`. From the client we will call this reducer when we create a new player: -Note the Result that the insert function returns can fail with a "DuplicateRow" error if we insert two rows with the same unique column value. In this example we just use the rust `expect` function to check for this. - ---- - -**Rust Results** - -A Result is like an Option where the None is augmented with a value describing the error. Rust programs use Result and return Err in situations where Unity/C# programs would signal an exception. For more information, refer to the official Rust documentation: [Rust Result](https://doc.rust-lang.org/std/result/). - ---- - -We then create and insert our `PlayerComponent` and `MobileLocationComponent` using the same `entity_id`. - -We use the log crate to write to the module log. This can be viewed using the CLI command `spacetime logs `. If you add the -f switch it will continuously tail the log. +**Append to the bottom of lib.rs:** ```rust +// This reducer is called when the user logs in for the first time and +// enters a username #[spacetimedb(reducer)] pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String> { - // This reducer is called when the user logs in for the first time and - // enters a username - + // Get the Identity of the client who called this reducer let owner_id = ctx.sender; - // We check to see if there is already a PlayerComponent with this identity. - // this should never happen because the client only calls it if no player - // is found. + + // Make sure we don't already have a player with this identity if PlayerComponent::filter_by_owner_id(&owner_id).is_some() { log::info!("Player already exists"); return Err("Player already exists".to_string()); } - // Next we create the SpawnableEntityComponent. The entity_id for this - // component automatically increments and we get it back from the result - // of the insert call and use it for all components. + // Create a new entity for this player and get a unique `entity_id`. + let entity_id = EntityComponent::insert(EntityComponent + { + entity_id: 0, + position: StdbVector3 { x: 0.0, y: 0.0, z: 0.0 }, + direction: 0.0, + moving: false, + }).expect("Failed to create a unique PlayerComponent.").entity_id; - let entity_id = SpawnableEntityComponent::insert(SpawnableEntityComponent { entity_id: 0 }) - .expect("Failed to create player spawnable entity component.") - .entity_id; // The PlayerComponent uses the same entity_id and stores the identity of // the owner, username, and whether or not they are logged in. PlayerComponent::insert(PlayerComponent { @@ -299,18 +239,7 @@ pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String owner_id, username: username.clone(), logged_in: true, - }) - .expect("Failed to insert player component."); - // The MobileLocationComponent is used to calculate the current position - // of an entity that can move smoothly in the world. We are using 2d - // positions and the client will use the terrain height for the y value. - MobileLocationComponent::insert(MobileLocationComponent { - entity_id, - location: StdbVector2::ZERO, - direction: StdbVector2::ZERO, - move_start_timestamp: Timestamp::UNIX_EPOCH, - }) - .expect("Failed to insert player mobile entity component."); + }).expect("Failed to insert player component."); log::info!("Player created: {}({})", username, entity_id); @@ -318,32 +247,41 @@ pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String } ``` -SpacetimeDB also gives you the ability to define custom reducers that automatically trigger when certain events occur. +--- + +**SpacetimeDB Reducers** + +"Reducer" is a term coined by Clockwork Labs that refers to a function which when executed "reduces" into a list of inserts and deletes, which is then packed into a single database transaction. Reducers can be called remotely using the CLI or a client SDK or they can be scheduled to be called at some future time from another reducer call. + +--- -- `init` - Called the very first time you publish your module and anytime you clear the database. We'll learn about publishing a little later. -- `connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `sender` member of the `ReducerContext`. +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. We'll learn about publishing later. +- `connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `sender` value of the `ReducerContext`. - `disconnect` - Called when a user disconnects from the SpacetimeDB module. Next we are going to write a custom `init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config::filter_by_version(0)`. +**Append to the bottom of lib.rs:** + ```rust +// Called when the module is initially published #[spacetimedb(init)] pub fn init() { - // Called when the module is initially published - - - // Create our global config table. Config::insert(Config { version: 0, message_of_the_day: "Hello, World!".to_string(), - }) - .expect("Failed to insert config."); + }).expect("Failed to insert config."); } ``` We use the `connect` and `disconnect` reducers to update the logged in state of the player. The `update_player_login_state` helper function looks up the `PlayerComponent` row using the user's identity and if it exists, it updates the `logged_in` variable and calls the auto-generated `update` function on `PlayerComponent` to update the row. +**Append to the bottom of lib.rs:** + ```rust +// Called when the client connects, we update the logged_in state to true #[spacetimedb(connect)] pub fn client_connected(ctx: ReducerContext) { // called when the client connects, we update the logged_in state to true @@ -351,109 +289,82 @@ pub fn client_connected(ctx: ReducerContext) { } +// Called when the client disconnects, we update the logged_in state to false #[spacetimedb(disconnect)] pub fn client_disconnected(ctx: ReducerContext) { // Called when the client disconnects, we update the logged_in state to false update_player_login_state(ctx, false); } - +// This helper function gets the PlayerComponent, sets the logged +// in variable and updates the PlayerComponent table row. pub fn update_player_login_state(ctx: ReducerContext, logged_in: bool) { - // This helper function gets the PlayerComponent, sets the logged - // in variable and updates the SpacetimeDB table row. if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { - let entity_id = player.entity_id; // We clone the PlayerComponent so we can edit it and pass it back. let mut player = player.clone(); player.logged_in = logged_in; - PlayerComponent::update_by_entity_id(&entity_id, player); + PlayerComponent::update_by_entity_id(&player.entity_id.clone(), player); } } ``` -Our final two reducers handle player movement. In `move_player` we look up the `PlayerComponent` using the user identity. If we don't find one, we return an error because the client should not be sending moves without creating a player entity first. - -Using the `entity_id` in the `PlayerComponent` we retrieved, we can lookup the `MobileLocationComponent` that stores the entity's locations in the world. We update the values passed in from the client and call the auto-generated `update` function. +Our final reducer handles player movement. In `update_player_position` we look up the `PlayerComponent` using the user's Identity. If we don't find one, we return an error because the client should not be sending moves without calling `create_player` first. ---- - -**Server Validation** - -In a fully developed game, the server would typically perform server-side validation on player movements to ensure they comply with game boundaries, rules, and mechanics. This validation, which we omit for simplicity in this tutorial, is essential for maintaining game integrity, preventing cheating, and ensuring a fair gaming experience. Remember to incorporate appropriate server-side validation in your game's development to ensure a secure and fair gameplay environment. +Using the `entity_id` in the `PlayerComponent` we retrieved, we can lookup the `EntityComponent` that stores the entity's locations in the world. We update the values passed in from the client and call the auto-generated `update` function. ---- +**Append to the bottom of lib.rs:** ```rust +// Updates the position of a player. This is also called when the player stops moving. #[spacetimedb(reducer)] -pub fn move_player( +pub fn update_player_position( ctx: ReducerContext, - start: StdbVector2, - direction: StdbVector2, + position: StdbVector3, + direction: f32, + moving: bool, ) -> Result<(), String> { - // Update the MobileLocationComponent with the current movement - // values. The client will call this regularly as the direction of movement - // changes. A fully developed game should validate these moves on the server - // before committing them, but that is beyond the scope of this tutorial. - - let owner_id = ctx.sender; // First, look up the player using the sender identity, then use that - // entity_id to retrieve and update the MobileLocationComponent - if let Some(player) = PlayerComponent::filter_by_owner_id(&owner_id) { - if let Some(mut mobile) = MobileLocationComponent::filter_by_entity_id(&player.entity_id) { - mobile.location = start; - mobile.direction = direction; - mobile.move_start_timestamp = ctx.timestamp; - MobileLocationComponent::update_by_entity_id(&player.entity_id, mobile); - - + // entity_id to retrieve and update the EntityComponent + if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + if let Some(mut entity) = EntityComponent::filter_by_entity_id(&player.entity_id) { + entity.position = position; + entity.direction = direction; + entity.moving = moving; + EntityComponent::update_by_entity_id(&player.entity_id, entity); return Ok(()); } } - - // If we can not find the PlayerComponent for this user something went wrong. - // This should never happen. + // If we can not find the PlayerComponent or EntityComponent for + // this player then something went wrong. return Err("Player not found".to_string()); } +``` +--- -#[spacetimedb(reducer)] -pub fn stop_player(ctx: ReducerContext, location: StdbVector2) -> Result<(), String> { - // Update the MobileLocationComponent when a player comes to a stop. We set - // the location to the current location and the direction to {0,0} - let owner_id = ctx.sender; - if let Some(player) = PlayerComponent::filter_by_owner_id(&owner_id) { - if let Some(mut mobile) = MobileLocationComponent::filter_by_entity_id(&player.entity_id) { - mobile.location = location; - mobile.direction = StdbVector2::ZERO; - mobile.move_start_timestamp = Timestamp::UNIX_EPOCH; - MobileLocationComponent::update_by_entity_id(&player.entity_id, mobile); - +**Server Validation** - return Ok(()); - } - } +In a fully developed game, the server would typically perform server-side validation on player movements to ensure they comply with game boundaries, rules, and mechanics. This validation, which we omit for simplicity in this tutorial, is essential for maintaining game integrity, preventing cheating, and ensuring a fair gaming experience. Remember to incorporate appropriate server-side validation in your game's development to ensure a secure and fair gameplay environment. +--- - return Err("Player not found".to_string()); -} -``` +### Publishing a Module to SpacetimeDB -4. Now that we've written the code for our server module, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. Make sure your domain name is unique. You will get an error if someone has already created a database with that name. In your terminal or command window, run the following commands. +Now that we've written the code for our server module, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. ```bash -cd Server - -spacetime publish -c yourname-bitcraftmini +cd server +spacetime publish unity-tutorial ``` -If you get any errors from this command, double check that you correctly entered everything into lib.rs. You can also look at the Troubleshooting section at the end of this tutorial. +If you get any errors from this command, double check that you correctly entered everything into `lib.rs`. You can also look at the Troubleshooting section at the end of this tutorial. ## Updating our Unity Project to use SpacetimeDB Now we are ready to connect our bitcraft mini project to SpacetimeDB. -### Step 1: Import the SDK and Generate Module Files +### Import the SDK and Generate Module Files 1. 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. @@ -466,100 +377,106 @@ https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git 3. The next step is to generate the module specific client files using the SpacetimeDB CLI. The files created by this command provide an interface for retrieving values from the local client cache of the database and for registering for callbacks to events. In your terminal or command window, run the following commands. ```bash -mkdir -p ../Client/Assets/module_bindings - -spacetime generate --out-dir ../Client/Assets/module_bindings --lang=csharp +mkdir -p ../client/Assets/module_bindings +spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp ``` -### Step 2: Connect to the SpacetimeDB Module +### Connect to Your SpacetimeDB Module -1. The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in the scene. Click on the GameManager object in the scene, and in the inspector, add the `NetworkManager` component. +The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in the scene. Click on the GameManager object in the scene, and in the inspector, add the `NetworkManager` component. ![Unity-AddNetworkManager](/images/unity-tutorial/Unity-AddNetworkManager.JPG) -2. Next we are going to connect to our SpacetimeDB module. Open BitcraftMiniGameManager.cs in your editor of choice and add the following code at the top of the file: +Next we are going to connect to our SpacetimeDB module. Open `TutorialGameManager.cs` in your editor of choice and add the following code at the top of the file: -`SpacetimeDB.Types` is the namespace that your generated code is in. You can change this by specifying a namespace in the generate command using `--namespace`. +**Append to the top of TutorialGameManager.cs** ```csharp using SpacetimeDB; using SpacetimeDB.Types; +using System.Linq; ``` -3. Inside the class definition add the following members: +At the top of the class definition add the following members: + +**Append to the top of TutorialGameManager class inside of TutorialGameManager.cs** ```csharp - // These are connection variables that are exposed on the GameManager - // inspector. The cloud version of SpacetimeDB needs sslEnabled = true - [SerializeField] private string moduleAddress = "YOUR_MODULE_DOMAIN_OR_ADDRESS"; - [SerializeField] private string hostName = "localhost:3000"; - [SerializeField] private bool sslEnabled = false; +// These are connection variables that are exposed on the GameManager +// inspector. +[SerializeField] private string moduleAddress = "unity-tutorial"; +[SerializeField] private string hostName = "localhost:3000"; - // This is the identity for this player that is automatically generated - // the first time you log in. We set this variable when the - // onIdentityReceived callback is triggered by the SDK after connecting - private Identity local_identity; +// This is the identity for this player that is automatically generated +// the first time you log in. We set this variable when the +// onIdentityReceived callback is triggered by the SDK after connecting +private Identity local_identity; ``` -The first three fields will appear in your Inspector so you can update your connection details without editing the code. The `moduleAddress` should be set to the domain you used in the publish command. You should not need to change `hostName` or `sslEnabled` if you are using the standalone version of SpacetimeDB. +The first three fields will appear in your Inspector so you can update your connection details without editing the code. The `moduleAddress` should be set to the domain you used in the publish command. You should not need to change `hostName` if you are using SpacetimeDB locally. -4. Add the following code to the `Start` function. **Be sure to remove the line `UIUsernameChooser.instance.Show();`** since we will call this after we get the local state and find that the player for us. +Now add the following code to the `Start()` function. For clarity, replace your entire `Start()` function with the function below. -In our `onConnect` callback we are calling `Subscribe` with a list of queries. This tells SpacetimeDB what rows we want in our local client cache. We will also not get row update callbacks or event callbacks for any reducer that does not modify a row that matches these queries. +**REPLACE the Start() function in TutorialGameManager.cs** ---- +```csharp +// Start is called before the first frame update +void Start() +{ + instance = this; -**Local Client Cache** + SpacetimeDBClient.instance.onConnect += () => + { + Debug.Log("Connected."); -The "local client cache" is a client-side view of the database, defined by the supplied queries to the Subscribe function. It contains relevant data, allowing efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated iter and filter_by functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. + // Request all tables + SpacetimeDBClient.instance.Subscribe(new List() + { + "SELECT * FROM *", + }); + }; ---- + // Called when we have an error connecting to SpacetimeDB + SpacetimeDBClient.instance.onConnectError += (error, message) => + { + Debug.LogError($"Connection error: " + message); + }; -```csharp - // When we connect to SpacetimeDB we send our subscription queries - // to tell SpacetimeDB which tables we want to get updates for. - SpacetimeDBClient.instance.onConnect += () => - { - Debug.Log("Connected."); + // Called when we are disconnected from SpacetimeDB + SpacetimeDBClient.instance.onDisconnect += (closeStatus, error) => + { + Debug.Log("Disconnected."); + }; - SpacetimeDBClient.instance.Subscribe(new List() - { - "SELECT * FROM Config", - "SELECT * FROM SpawnableEntityComponent", - "SELECT * FROM PlayerComponent", - "SELECT * FROM MobileLocationComponent", - }); - }; - - // called when we have an error connecting to SpacetimeDB - SpacetimeDBClient.instance.onConnectError += (error, message) => - { - Debug.LogError($"Connection error: " + message); - }; + // Called when we receive the client identity from SpacetimeDB + SpacetimeDBClient.instance.onIdentityReceived += (token, identity, address) => { + AuthToken.SaveToken(token); + local_identity = identity; + }; - // called when we are disconnected from SpacetimeDB - SpacetimeDBClient.instance.onDisconnect += (closeStatus, error) => - { - Debug.Log("Disconnected."); - }; + // Called after our local cache is populated from a Subscribe call + SpacetimeDBClient.instance.onSubscriptionApplied += OnSubscriptionApplied; + + // Now that we’ve registered all our callbacks, lets connect to spacetimedb + SpacetimeDBClient.instance.Connect(AuthToken.Token, hostName, moduleAddress); +} +``` +In our `onConnect` callback we are calling `Subscribe` and subscribing to all data in the database. You can also subscribe to specific tables using SQL syntax like `SELECT * FROM MyTable`. Our SQL documentation enumerates the operations that are accepted in our SQL syntax. - // called when we receive the client identity from SpacetimeDB - SpacetimeDBClient.instance.onIdentityReceived += (token, identity, address) => { - AuthToken.SaveToken(token); - local_identity = identity; - }; +Subscribing to tables tells SpacetimeDB what rows we want in our local client cache. We will also not get row update callbacks or event callbacks for any reducer that does not modify a row that matches at least one of our queries. This means that events can happen on the server and the client won't be notified unless they are subscribed to at least 1 row in the change. +--- - // called after our local cache is populated from a Subscribe call - SpacetimeDBClient.instance.onSubscriptionApplied += OnSubscriptionApplied; +**Local Client Cache** - // now that we’ve registered all our callbacks, lets connect to - // spacetimedb - SpacetimeDBClient.instance.Connect(AuthToken.Token, hostName, moduleAddress, sslEnabled); -``` +The "local client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. It contains the requested data which allows efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated iter and filter_by functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. -5. Next we write the `OnSubscriptionUpdate` callback. When this event occurs for the first time, it signifies that our local client cache is fully populated. At this point, we can verify if a player entity already exists for the corresponding user. If we do not have a player entity, we need to show the `UserNameChooser` dialog so the user can enter a username. We also put the message of the day into the chat window. Finally we unsubscribe from the callback since we only need to do this once. +--- + +Next we write the `OnSubscriptionApplied` callback. When this event occurs for the first time, it signifies that our local client cache is fully populated. At this point, we can verify if a player entity already exists for the corresponding user. If we do not have a player entity, we need to show the `UserNameChooser` dialog so the user can enter a username. We also put the message of the day into the chat window. Finally we unsubscribe from the callback since we only need to do this once. + +**Append after the Start() function in TutorialGameManager.cs** ```csharp void OnSubscriptionApplied() @@ -582,25 +499,46 @@ void OnSubscriptionApplied() } ``` -### Step 3: Adding the Multiplayer Functionality +### Adding the Multiplayer Functionality + +Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `UIUsernameChooser.cs`. -1. Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `UIUsernameChooser`, **add `using SpacetimeDB.Types;`** at the top of the file, and replace: +**Append to the top of UIUsernameChooser.cs** ```csharp - LocalPlayer.instance.username = _usernameField.text; - BitcraftMiniGameManager.instance.StartGame(); +using SpacetimeDB.Types; ``` -with: +Then we're doing a modification to the `ButtonPressed()` function: + +**Modify the ButtonPressed function in UIUsernameChooser.cs** ```csharp +public void ButtonPressed() +{ + CameraController.RemoveDisabler(GetHashCode()); + _panel.SetActive(false); + // Call the SpacetimeDB CreatePlayer reducer Reducer.CreatePlayer(_usernameField.text); +} ``` -2. We need to create a `RemotePlayer` component that we attach to remote player objects. In the same folder as `LocalPlayer`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `MobileLocationComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. +We need to create a `RemotePlayer` script that we attach to remote player objects. In the same folder as `LocalPlayer.cs`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `EntityComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. + +First append this using to the top of `RemotePlayer.cs` + +**Create file RemotePlayer.cs, then replace its contents:** ```csharp +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using SpacetimeDB.Types; +using TMPro; + +public class RemotePlayer : MonoBehaviour +{ public ulong EntityId; public TMP_Text UsernameElement; @@ -609,191 +547,202 @@ with: void Start() { - // initialize overhead name + // Initialize overhead name UsernameElement = GetComponentInChildren(); var canvas = GetComponentInChildren(); canvas.worldCamera = Camera.main; - // get the username from the PlayerComponent for this object and set it in the UI + // Get the username from the PlayerComponent for this object and set it in the UI PlayerComponent playerComp = PlayerComponent.FilterByEntityId(EntityId); Username = playerComp.Username; - // get the last location for this player and set the initial - // position - MobileLocationComponent mobPos = MobileLocationComponent.FilterByEntityId(EntityId); - Vector3 playerPos = new Vector3(mobPos.Location.X, 0.0f, mobPos.Location.Z); - transform.position = new Vector3(playerPos.x, MathUtil.GetTerrainHeight(playerPos), playerPos.z); + // Get the last location for this player and set the initial position + EntityComponent entity = EntityComponent.FilterByEntityId(EntityId); + transform.position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); - // register for a callback that is called when the client gets an - // update for a row in the MobileLocationComponent table - MobileLocationComponent.OnUpdate += MobileLocationComponent_OnUpdate; + // Register for a callback that is called when the client gets an + // update for a row in the EntityComponent table + EntityComponent.OnUpdate += EntityComponent_OnUpdate; } +} ``` -3. We now write the `MobileLocationComponent_OnUpdate` callback which sets the movement direction in the `MovementController` for this player. We also set the position to the current location when we stop moving (`DirectionVec` is zero) +We now write the `EntityComponent_OnUpdate` callback which sets the movement direction in the `MovementController` for this player. We also set the target position to the current location in the latest update. + +**Append to bottom of RemotePlayer class in RemotePlayer.cs:** ```csharp - private void MobileLocationComponent_OnUpdate(MobileLocationComponent oldObj, MobileLocationComponent obj, ReducerEvent callInfo) +private void EntityComponent_OnUpdate(EntityComponent oldObj, EntityComponent obj, ReducerEvent callInfo) +{ + // If the update was made to this object + if(obj.EntityId == EntityId) { - // if the update was made to this object - if(obj.EntityId == EntityId) - { - // update the DirectionVec in the PlayerMovementController component with the updated values - var movementController = GetComponent(); - movementController.DirectionVec = new Vector3(obj.Direction.X, 0.0f, obj.Direction.Z); - // if DirectionVec is {0,0,0} then we came to a stop so correct our position to match the server - if (movementController.DirectionVec == Vector3.zero) - { - Vector3 playerPos = new Vector3(obj.Location.X, 0.0f, obj.Location.Z); - transform.position = new Vector3(playerPos.x, MathUtil.GetTerrainHeight(playerPos), playerPos.z); - } - } + var movementController = GetComponent(); + + // Update target position, rotation, etc. + movementController.RemoteTargetPosition = new Vector3(obj.Position.X, obj.Position.Y, obj.Position.Z); + movementController.RemoteTargetRotation = obj.Direction; + movementController.SetMoving(obj.Moving); } +} ``` -4. Next we need to handle what happens when a `PlayerComponent` is added to our local cache. We will handle it differently based on if it’s our local player entity or a remote player. We are going to register for the `OnInsert` event for our `PlayerComponent` table. Add the following code to the `Start` function in `BitcraftMiniGameManager`. +Next we need to handle what happens when a `PlayerComponent` is added to our local cache. We will handle it differently based on if it’s our local player entity or a remote player. We are going to register for the `OnInsert` event for our `PlayerComponent` table. Add the following code to the `Start` function in `TutorialGameManager`. + +**Append to bottom of Start() function in TutorialGameManager.cs:** ```csharp - PlayerComponent.OnInsert += PlayerComponent_OnInsert; +PlayerComponent.OnInsert += PlayerComponent_OnInsert; ``` -5. Create the `PlayerComponent_OnInsert` function which does something different depending on if it's the component for the local player or a remote player. If it's the local player, we set the local player object's initial position and call `StartGame`. If it's a remote player, we instantiate a `PlayerPrefab` with the `RemotePlayer` component. The start function of `RemotePlayer` handles initializing the player position. +Create the `PlayerComponent_OnInsert` function which does something different depending on if it's the component for the local player or a remote player. If it's the local player, we set the local player object's initial position and call `StartGame`. If it's a remote player, we instantiate a `PlayerPrefab` with the `RemotePlayer` component. The start function of `RemotePlayer` handles initializing the player position. + +**Append to bottom of TutorialGameManager class in TutorialGameManager.cs:** ```csharp - private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo) +private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo) +{ + // If the identity of the PlayerComponent matches our user identity then this is the local player + if(obj.OwnerId == local_identity) { - // if the identity of the PlayerComponent matches our user identity then this is the local player - if(obj.OwnerId == local_identity) - { - // Set the local player username - LocalPlayer.instance.Username = obj.Username; - - // Get the MobileLocationComponent for this object and update the position to match the server - MobileLocationComponent mobPos = MobileLocationComponent.FilterByEntityId(obj.EntityId); - Vector3 playerPos = new Vector3(mobPos.Location.X, 0.0f, mobPos.Location.Z); - LocalPlayer.instance.transform.position = new Vector3(playerPos.x, MathUtil.GetTerrainHeight(playerPos), playerPos.z); - - // Now that we have our initial position we can start the game - StartGame(); - } - // otherwise this is a remote player - else - { - // spawn the player object and attach the RemotePlayer component - var remotePlayer = Instantiate(PlayerPrefab); - remotePlayer.AddComponent().EntityId = obj.EntityId; - } + // Now that we have our initial position we can start the game + StartGame(); + } + else + { + // Spawn the player object and attach the RemotePlayer component + var remotePlayer = Instantiate(PlayerPrefab); + // Lookup and apply the position for this new player + var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); + remotePlayer.transform.position = position; + var movementController = remotePlayer.GetComponent(); + movementController.RemoteTargetPosition = position; + movementController.RemoteTargetRotation = entity.Direction; + remotePlayer.AddComponent().EntityId = obj.EntityId; } +} ``` -6. Next, we need to update the `FixedUpdate` function in `LocalPlayer` to call the `move_player` and `stop_player` reducers using the auto-generated functions. **Don’t forget to add `using SpacetimeDB.Types;`** to LocalPlayer.cs +Next, we will add a `FixedUpdate()` function to the `LocalPlayer` class so that we can send the local player's position to SpacetimeDB. We will do this by calling the auto-generated reducer function `Reducer.UpdatePlayerPosition(...)`. When we invoke this reducer from the client, a request is sent to SpacetimeDB and the reducer `update_player_position(...)` is executed on the server and a transaction is produced. All clients connected to SpacetimeDB will start receiving the results of these transactions. + +**Append to the top of LocalPlayer.cs** ```csharp - private Vector3? lastUpdateDirection; +using SpacetimeDB.Types; +using SpacetimeDB; +``` + +**Append to the bottom of LocalPlayer class in LocalPlayer.cs** - private void FixedUpdate() +```csharp +private float? lastUpdateTime; +private void FixedUpdate() +{ + if ((lastUpdateTime.HasValue && Time.time - lastUpdateTime.Value > 1.0f / movementUpdateSpeed) || !SpacetimeDBClient.instance.IsConnected()) { - var directionVec = GetDirectionVec(); - PlayerMovementController.Local.DirectionVec = directionVec; + return; + } - // first get the position of the player - var ourPos = PlayerMovementController.Local.GetModelTransform().position; - // if we are moving , and we haven't updated our destination yet, or we've moved more than .1 units, update our destination - if (directionVec.sqrMagnitude != 0 && (!lastUpdateDirection.HasValue || (directionVec - lastUpdateDirection.Value).sqrMagnitude > .1f)) - { - Reducer.MovePlayer(new StdbVector2() { X = ourPos.x, Z = ourPos.z }, new StdbVector2() { X = directionVec.x, Z = directionVec.z }); - lastUpdateDirection = directionVec; - } - // if we stopped moving, send the update - else if(directionVec.sqrMagnitude == 0 && lastUpdateDirection != null) + lastUpdateTime = Time.time; + var p = PlayerMovementController.Local.GetModelPosition(); + Reducer.UpdatePlayerPosition(new StdbVector3 { - Reducer.StopPlayer(new StdbVector2() { X = ourPos.x, Z = ourPos.z }); - lastUpdateDirection = null; - } - } + X = p.x, + Y = p.y, + Z = p.z, + }, + PlayerMovementController.Local.GetModelRotation(), + PlayerMovementController.Local.IsMoving()); +} ``` -7. Finally, we need to update our connection settings in the inspector for our GameManager object in the scene. Click on the GameManager in the Hierarchy tab. The the inspector tab you should now see fields for `Module Address`, `Host Name` and `SSL Enabled`. Set the `Module Address` to the name you used when you ran `spacetime publish`. If you don't remember, you can go back to your terminal and run `spacetime publish` again from the `Server` folder. +Finally, we need to update our connection settings in the inspector for our GameManager object in the scene. Click on the GameManager in the Hierarchy tab. The the inspector tab you should now see fields for `Module Address` and `Host Name`. Set the `Module Address` to the name you used when you ran `spacetime publish`. This is likely `unity-tutorial`. If you don't remember, you can go back to your terminal and run `spacetime publish` again from the `server` folder. ![GameManager-Inspector2](/images/unity-tutorial/GameManager-Inspector2.JPG) -### Step 4: Play the Game! +### Play the Game! -1. Go to File -> Build Settings... Replace the SampleScene with the Main scene we have been working in. +Go to File -> Build Settings... Replace the SampleScene with the Main scene we have been working in. ![Unity-AddOpenScenes](/images/unity-tutorial/Unity-AddOpenScenes.JPG) When you hit the `Build` button, it will kick off a build of the game which will use a different identity than the Unity Editor. Create your character in the build and in the Unity Editor by entering a name and clicking `Continue`. Now you can see each other in game running around the map. -### Step 5: Implement Player Logout +### Implement Player Logout So far we have not handled the `logged_in` variable of the `PlayerComponent`. This means that remote players will not despawn on your screen when they disconnect. To fix this we need to handle the `OnUpdate` event for the `PlayerComponent` table in addition to `OnInsert`. We are going to use a common function that handles any time the `PlayerComponent` changes. -1. Open `BitcraftMiniGameManager.cs` and add the following code to the `Start` function: - +**Append to the bottom of Start() function in TutorialGameManager.cs** ```csharp - PlayerComponent.OnUpdate += PlayerComponent_OnUpdate; +PlayerComponent.OnUpdate += PlayerComponent_OnUpdate; ``` -2. We are going to add a check to determine if the player is logged for remote players. If the player is not logged in, we search for the RemotePlayer object with the corresponding `EntityId` and destroy it. Add `using System.Linq;` to the top of the file and replace the `PlayerComponent_OnInsert` function with the following code. +We are going to add a check to determine if the player is logged for remote players. If the player is not logged in, we search for the `RemotePlayer` object with the corresponding `EntityId` and destroy it. +Next we'll be updating some of the code in `PlayerComponent_OnInsert`. For simplicity, just replace the entire function. + +**REPLACE PlayerComponent_OnInsert in TutorialGameManager.cs** ```csharp - private void PlayerComponent_OnUpdate(PlayerComponent oldValue, PlayerComponent newValue, ReducerEvent dbEvent) - { - OnPlayerComponentChanged(newValue); - } +private void PlayerComponent_OnUpdate(PlayerComponent oldValue, PlayerComponent newValue, ReducerEvent dbEvent) +{ + OnPlayerComponentChanged(newValue); +} + +private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent dbEvent) +{ + OnPlayerComponentChanged(obj); +} - private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent dbEvent) +private void OnPlayerComponentChanged(PlayerComponent obj) +{ + // If the identity of the PlayerComponent matches our user identity then this is the local player + if(obj.OwnerId == local_identity) { - OnPlayerComponentChanged(obj); + // Now that we have our initial position we can start the game + StartGame(); } - - private void OnPlayerComponentChanged(PlayerComponent obj) + else { - // if the identity of the PlayerComponent matches our user identity then this is the local player - if (obj.OwnerId == local_identity) - { - // Set the local player username - LocalPlayer.instance.Username = obj.Username; - - // Get the MobileLocationComponent for this object and update the position to match the server - MobileLocationComponent mobPos = MobileLocationComponent.FilterByEntityId(obj.EntityId); - Vector3 playerPos = new Vector3(mobPos.Location.X, 0.0f, mobPos.Location.Z); - LocalPlayer.instance.transform.position = new Vector3(playerPos.x, MathUtil.GetTerrainHeight(playerPos), playerPos.z); - - // Now that we have our initial position we can start the game - StartGame(); - } - // otherwise this is a remote player - else + // otherwise we need to look for the remote player object in the scene (if it exists) and destroy it + var existingPlayer = FindObjectsOfType().FirstOrDefault(item => item.EntityId == obj.EntityId); + if (obj.LoggedIn) { - // if the remote player is logged in, spawn it - if (obj.LoggedIn) + // Only spawn remote players who aren't already spawned + if (existingPlayer == null) { - // spawn the player object and attach the RemotePlayer component + // Spawn the player object and attach the RemotePlayer component var remotePlayer = Instantiate(PlayerPrefab); + // Lookup and apply the position for this new player + var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); + remotePlayer.transform.position = position; + var movementController = remotePlayer.GetComponent(); + movementController.RemoteTargetPosition = position; + movementController.RemoteTargetRotation = entity.Direction; remotePlayer.AddComponent().EntityId = obj.EntityId; } - // otherwise we need to look for the remote player object in the scene (if it exists) and destroy it - else + } + else + { + if (existingPlayer != null) { - var remotePlayer = FindObjectsOfType().FirstOrDefault(item => item.EntityId == obj.EntityId); - if (remotePlayer != null) - { - Destroy(remotePlayer.gameObject); - } + Destroy(existingPlayer.gameObject); } } } +} ``` -3. Now you when you play the game you should see remote players disappear when they log out. +Now you when you play the game you should see remote players disappear when they log out. -### Step 6: Add Chat Support +### Finally, Add Chat Support The project has a chat window but so far all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. -1. First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to lib.rs. +First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.rs``. + +**Append to the bottom of server/src/lib.rs:** ```rust #[spacetimedb(table)] @@ -801,34 +750,30 @@ pub struct ChatMessage { // The primary key for this table will be auto-incremented #[primarykey] #[autoinc] - pub chat_entity_id: u64, + pub message_id: u64, - // The entity id of the player (or NPC) that sent the message - pub source_entity_id: u64, + // The entity id of the player that sent the message + pub sender_id: u64, // Message contents - pub chat_text: String, - // Timestamp of when the message was sent - pub timestamp: Timestamp, + pub text: String, } ``` -2. Now we need to add a reducer to handle inserting new chat messages. Add the following code to lib.rs. +Now we need to add a reducer to handle inserting new chat messages. + +**Append to the bottom of server/src/lib.rs:** ```rust +// Adds a chat entry to the ChatMessage table #[spacetimedb(reducer)] -pub fn chat_message(ctx: ReducerContext, message: String) -> Result<(), String> { - // Add a chat entry to the ChatMessage table - - // Get the player component based on the sender identity - let owner_id = ctx.sender; - if let Some(player) = PlayerComponent::filter_by_owner_id(&owner_id) { +pub fn send_chat_message(ctx: ReducerContext, text: String) -> Result<(), String> { + if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { // Now that we have the player we can insert the chat message using the player entity id. ChatMessage::insert(ChatMessage { // this column auto-increments so we can set it to 0 - chat_entity_id: 0, - source_entity_id: player.entity_id, - chat_text: message, - timestamp: ctx.timestamp, + message_id: 0, + sender_id: player.entity_id, + text, }) .unwrap(); @@ -839,57 +784,53 @@ pub fn chat_message(ctx: ReducerContext, message: String) -> Result<(), String> } ``` -3. Before updating the client, let's generate the client files and publish our module. +Before updating the client, let's generate the client files and update publish our module. +**Execute commands in the server/ directory** ```bash -spacetime generate --out-dir ../Client/Assets/module_bindings --lang=csharp +spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp +spacetime publish -c unity-tutorial +``` + +On the client, let's add code to send the message when the chat button or enter is pressed. Update the `OnChatButtonPress` function in `UIChatController.cs`. -spacetime publish -c yourname-bitcraftmini +**Append to the top of UIChatController.cs:** +```csharp +using SpacetimeDB.Types; ``` -4. On the client, let's add code to send the message when the chat button or enter is pressed. Update the `OnChatButtonPress` function in `UIChatController.cs`. +**REPLACE the OnChatButtonPress function in UIChatController.cs:** ```csharp public void OnChatButtonPress() { - Reducer.ChatMessage(_chatInput.text); + Reducer.SendChatMessage(_chatInput.text); _chatInput.text = ""; } ``` -5. Next let's add the `ChatMessage` table to our list of subscriptions. +Now we need to add a reducer to handle inserting new chat messages. First register for the ChatMessage reducer in the `Start()` function using the auto-generated function: +**Append to the bottom of the Start() function in TutorialGameManager.cs:** ```csharp - SpacetimeDBClient.instance.Subscribe(new List() - { - "SELECT * FROM Config", - "SELECT * FROM SpawnableEntityComponent", - "SELECT * FROM PlayerComponent", - "SELECT * FROM MobileLocationComponent", - "SELECT * FROM ChatMessage", - }); +Reducer.OnSendChatMessageEvent += OnSendChatMessageEvent; ``` -6. Now we need to add a reducer to handle inserting new chat messages. First register for the ChatMessage reducer in the `Start` function using the auto-generated function: +Now we write the `OnSendChatMessageEvent` function. We can find the `PlayerComponent` for the player who sent the message using the `Identity` of the sender. Then we get the `Username` and prepend it to the message before sending it to the chat window. +**Append after the Start() function in TutorialGameManager.cs** ```csharp - Reducer.OnChatMessageEvent += OnChatMessageEvent; -``` - -Then we write the `OnChatMessageEvent` function. We can find the `PlayerComponent` for the player who sent the message using the `Identity` of the sender. Then we get the `Username` and prepend it to the message before sending it to the chat window. - -```csharp - private void OnChatMessageEvent(ReducerEvent dbEvent, string message) +private void OnSendChatMessageEvent(ReducerEvent dbEvent, string message) +{ + var player = PlayerComponent.FilterByOwnerId(dbEvent.Identity); + if (player != null) { - var player = PlayerComponent.FilterByOwnerId(dbEvent.Identity); - if (player != null) - { - UIChatController.instance.OnChatMessageReceived(player.Username + ": " + message); - } + UIChatController.instance.OnChatMessageReceived(player.Username + ": " + message); } +} ``` -7. Now when you run the game you should be able to send chat messages to other players. Be sure to make a new Unity client build and run it in a separate window so you can test chat between two clients. +Now when you run the game you should be able to send chat messages to other players. Be sure to make a new Unity client build and run it in a separate window so you can test chat between two clients. ## Conclusion @@ -905,7 +846,7 @@ This concludes the first part of the tutorial. We've learned about the basics of ``` NullReferenceException: Object reference not set to an instance of an object -BitcraftMiniGameManager.Start () (at Assets/_Project/Game/BitcraftMiniGameManager.cs:26) +TutorialGameManager.Start () (at Assets/_Project/Game/TutorialGameManager.cs:26) ``` Check to see if your GameManager object in the Scene has the NetworkManager component attached. diff --git a/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md b/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md index 5cd205ef..537edd44 100644 --- a/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md +++ b/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md @@ -1,5 +1,7 @@ # Part 2 - Resources and Scheduling +**Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** + In this second part of the lesson, we'll add resource nodes to our project and learn about scheduled reducers. Then we will spawn the nodes on the client so they are visible to the player. ## Add Resource Node Spawner diff --git a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md b/docs/Unity Tutorial/Part 3 - BitCraft Mini.md index e1f5e3eb..16816dd6 100644 --- a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md +++ b/docs/Unity Tutorial/Part 3 - BitCraft Mini.md @@ -1,5 +1,7 @@ # Part 3 - BitCraft Mini +**Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** + BitCraft Mini is a game that we developed which extends the code you've already developed in this tutorial. It is inspired by our game [BitCraft](https://bitcraftonline.com) and illustrates how you could build a more complex game from just the components we've discussed. Right now you can walk around, mine ore, and manage your inventory. ## 1. Download From f2e1fbd22bddb7ac7be5921a796e7defed234e69 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:17:33 -0500 Subject: [PATCH 11/80] Addressing Chippy's feedback (#8) Co-authored-by: John Detter --- docs/Server Module Languages/C#/index.md | 2 +- docs/Unity Tutorial/Part 1 - Basic Multiplayer.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index 473a8ac6..dd818b07 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -184,7 +184,7 @@ You could extend the validation in `ValidateMessage` in similar ways to `Validat In C# modules, you can register for OnConnect and OnDisconnect events in a special initializer function that uses the attribute `ModuleInitializer`. We'll use the `OnConnect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User.FilterByOwnerIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByOwnerIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FilterByOwnerIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByOwnerIdentity`. +We'll use `User.FilterByIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FilterByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: diff --git a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md index 915fd444..30bd3137 100644 --- a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md +++ b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md @@ -355,7 +355,7 @@ Now that we've written the code for our server module, we need to publish it to ```bash cd server -spacetime publish unity-tutorial +spacetime publish -c unity-tutorial ``` If you get any errors from this command, double check that you correctly entered everything into `lib.rs`. You can also look at the Troubleshooting section at the end of this tutorial. From 2968a4e9c2e61681ac52966f5cb25a68db621212 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 25 Oct 2023 11:53:25 +0100 Subject: [PATCH 12/80] Update C# docs for connect/disconnect (#9) Update after the change in https://github.com/clockworklabs/SpacetimeDB/pull/309. --- .../C#/ModuleReference.md | 16 +--- docs/Server Module Languages/C#/index.md | 80 +++++++++---------- 2 files changed, 42 insertions(+), 54 deletions(-) diff --git a/docs/Server Module Languages/C#/ModuleReference.md b/docs/Server Module Languages/C#/ModuleReference.md index d655ea6d..36a9618a 100644 --- a/docs/Server Module Languages/C#/ModuleReference.md +++ b/docs/Server Module Languages/C#/ModuleReference.md @@ -289,6 +289,9 @@ These are two special kinds of reducers that can be used to respond to module li - `ReducerKind.Init` - this reducer will be invoked when the module is first published. - `ReducerKind.Update` - this reducer will be invoked when the module is updated. +- `ReducerKind.Connect` - this reducer will be invoked when a client connects to the database. +- `ReducerKind.Disconnect` - this reducer will be invoked when a client disconnects from the database. + Example: @@ -299,16 +302,3 @@ public static void Init() Log("...and we're live!"); } ``` - -### Connection events - -`OnConnect` and `OnDisconnect` `SpacetimeDB.Runtime` events are triggered when a client connects or disconnects from the database. They can be used to initialize per-client state or to clean up after the client disconnects. They get passed an instance of the earlier mentioned `DbEventArgs` which can be used to distinguish clients via its `Sender` field. - -```csharp -[SpacetimeDB.Reducer(ReducerKind.Init)] -public static void Init() -{ - OnConnect += (e) => Log($"Client {e.Sender} connected!"); - OnDisconnect += (e) => Log($"Client {e.Sender} disconnected!"); -} -``` diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index dd818b07..03937466 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -182,64 +182,62 @@ You could extend the validation in `ValidateMessage` in similar ways to `Validat ## Set users' online status -In C# modules, you can register for OnConnect and OnDisconnect events in a special initializer function that uses the attribute `ModuleInitializer`. We'll use the `OnConnect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. +In C# modules, you can register for `Connect` and `Disconnect` events by using a special `ReducerKind`. We'll use the `Connect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. We'll use `User.FilterByIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FilterByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: ```C# - [ModuleInitializer] - public static void Init() + [SpacetimeDB.Reducer(ReducerKind.Connect)] + public static void OnConnect(DbEventArgs dbEventArgs) { - OnConnect += (dbEventArgs) => - { - Log($"Connect {dbEventArgs.Sender}"); - var user = User.FindByIdentity(dbEventArgs.Sender); + Log($"Connect {dbEventArgs.Sender}"); + var user = User.FindByIdentity(dbEventArgs.Sender); - if (user is not null) - { - // If this is a returning user, i.e., we already have a `User` with this `Identity`, - // set `Online: true`, but leave `Name` and `Identity` unchanged. - user.Online = true; - User.UpdateByIdentity(dbEventArgs.Sender, user); - } - else + if (user is not null) + { + // If this is a returning user, i.e., we already have a `User` with this `Identity`, + // set `Online: true`, but leave `Name` and `Identity` unchanged. + user.Online = true; + User.UpdateByIdentity(dbEventArgs.Sender, user); + } + else + { + // If this is a new user, create a `User` object for the `Identity`, + // which is online, but hasn't set a name. + new User { - // If this is a new user, create a `User` object for the `Identity`, - // which is online, but hasn't set a name. - new User - { - Name = null, - Identity = dbEventArgs.Sender, - Online = true, - }.Insert(); - } - }; + Name = null, + Identity = dbEventArgs.Sender, + Online = true, + }.Insert(); + } } ``` -Similarly, whenever a client disconnects, the module will execute the `OnDisconnect` event if it's registered. We'll use it to un-set the `Online` status of the `User` for the disconnected client. +Similarly, whenever a client disconnects, the module will execute the `OnDisconnect` event if it's registered with `ReducerKind.Disconnect`. We'll use it to un-set the `Online` status of the `User` for the disconnected client. Add the following code after the `OnConnect` lambda: ```C# - OnDisconnect += (dbEventArgs) => - { - var user = User.FindByIdentity(dbEventArgs.Sender); + [SpacetimeDB.Reducer(ReducerKind.Disconnect)] + public static void OnDisconnect(DbEventArgs dbEventArgs) + { + var user = User.FindByIdentity(dbEventArgs.Sender); - if (user is not null) - { - // This user should exist, so set `Online: false`. - user.Online = false; - User.UpdateByIdentity(dbEventArgs.Sender, user); - } - else - { - // User does not exist, log warning - Log($"Warning: No user found for disconnected client."); - } - }; + if (user is not null) + { + // This user should exist, so set `Online: false`. + user.Online = false; + User.UpdateByIdentity(dbEventArgs.Sender, user); + } + else + { + // User does not exist, log warning + Log($"Warning: No user found for disconnected client."); + } + } ``` ## Publish the module From 1cafac1f8a7e9c603dc4b458c111071f0e5243e4 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:53:49 -0500 Subject: [PATCH 13/80] Fix syntax highlighting (#10) Co-authored-by: John Detter --- docs/Server Module Languages/C#/index.md | 192 +++++++++++------------ 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index 03937466..0346157f 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -39,7 +39,7 @@ spacetime init --lang csharp server To the top of `server/Lib.cs`, add some imports we'll be using: -```C# +```csharp using System.Runtime.CompilerServices; using SpacetimeDB.Module; using static SpacetimeDB.Runtime; @@ -66,29 +66,29 @@ For each `User`, we'll store their `Identity`, an optional name they can set to In `server/Lib.cs`, add the definition of the table `User` to the `Module` class: -```C# - [SpacetimeDB.Table] - public partial class User - { - [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] - public Identity Identity; - public string? Name; - public bool Online; - } +```csharp +[SpacetimeDB.Table] +public partial class User +{ + [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] + public Identity Identity; + public string? Name; + public bool Online; +} ``` For each `Message`, we'll store the `Identity` of the user who sent it, the `Timestamp` when it was sent, and the text of the message. In `server/Lib.cs`, add the definition of the table `Message` to the `Module` class: -```C# - [SpacetimeDB.Table] - public partial class Message - { - public Identity Sender; - public long Sent; - public string Text = ""; - } +```csharp +[SpacetimeDB.Table] +public partial class Message +{ + public Identity Sender; + public long Sent; + public string Text = ""; +} ``` ## Set users' names @@ -101,19 +101,19 @@ It's also possible to call `SetName` via the SpacetimeDB CLI's `spacetime call` In `server/Lib.cs`, add to the `Module` class: -```C# - [SpacetimeDB.Reducer] - public static void SetName(DbEventArgs dbEvent, string name) - { - name = ValidateName(name); +```csharp +[SpacetimeDB.Reducer] +public static void SetName(DbEventArgs dbEvent, string name) +{ + name = ValidateName(name); - var user = User.FindByIdentity(dbEvent.Sender); - if (user is not null) - { - user.Name = name; - User.UpdateByIdentity(dbEvent.Sender, user); - } + var user = User.FindByIdentity(dbEvent.Sender); + if (user is not null) + { + user.Name = name; + User.UpdateByIdentity(dbEvent.Sender, user); } +} ``` For now, we'll just do a bare minimum of validation, rejecting the empty name. You could extend this in various ways, like: @@ -126,16 +126,16 @@ For now, we'll just do a bare minimum of validation, rejecting the empty name. Y In `server/Lib.cs`, add to the `Module` class: -```C# - /// Takes a name and checks if it's acceptable as a user's name. - public static string ValidateName(string name) +```csharp +/// Takes a name and checks if it's acceptable as a user's name. +public static string ValidateName(string name) +{ + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new Exception("Names must not be empty"); - } - return name; + throw new Exception("Names must not be empty"); } + return name; +} ``` ## Send messages @@ -144,35 +144,35 @@ We define a reducer `SendMessage`, which clients will call to send messages. It In `server/Lib.cs`, add to the `Module` class: -```C# - [SpacetimeDB.Reducer] - public static void SendMessage(DbEventArgs dbEvent, string text) +```csharp +[SpacetimeDB.Reducer] +public static void SendMessage(DbEventArgs dbEvent, string text) +{ + text = ValidateMessage(text); + Log(text); + new Message { - text = ValidateMessage(text); - Log(text); - new Message - { - Sender = dbEvent.Sender, - Text = text, - Sent = dbEvent.Time.ToUnixTimeMilliseconds(), - }.Insert(); - } + Sender = dbEvent.Sender, + Text = text, + Sent = dbEvent.Time.ToUnixTimeMilliseconds(), + }.Insert(); +} ``` We'll want to validate messages' texts in much the same way we validate users' chosen names. As above, we'll do the bare minimum, rejecting only empty messages. In `server/Lib.cs`, add to the `Module` class: -```C# - /// Takes a message's text and checks if it's acceptable to send. - public static string ValidateMessage(string text) +```csharp +/// Takes a message's text and checks if it's acceptable to send. +public static string ValidateMessage(string text) +{ + if (string.IsNullOrEmpty(text)) { - if (string.IsNullOrEmpty(text)) - { - throw new ArgumentException("Messages must not be empty"); - } - return text; + throw new ArgumentException("Messages must not be empty"); } + return text; +} ``` You could extend the validation in `ValidateMessage` in similar ways to `ValidateName`, or add additional checks to `SendMessage`, like: @@ -188,56 +188,56 @@ We'll use `User.FilterByIdentity` to look up a `User` row for `dbEvent.Sender`, In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: -```C# - [SpacetimeDB.Reducer(ReducerKind.Connect)] - public static void OnConnect(DbEventArgs dbEventArgs) - { - Log($"Connect {dbEventArgs.Sender}"); - var user = User.FindByIdentity(dbEventArgs.Sender); +```csharp +[SpacetimeDB.Reducer(ReducerKind.Connect)] +public static void OnConnect(DbEventArgs dbEventArgs) +{ + Log($"Connect {dbEventArgs.Sender}"); + var user = User.FindByIdentity(dbEventArgs.Sender); - if (user is not null) - { - // If this is a returning user, i.e., we already have a `User` with this `Identity`, - // set `Online: true`, but leave `Name` and `Identity` unchanged. - user.Online = true; - User.UpdateByIdentity(dbEventArgs.Sender, user); - } - else + if (user is not null) + { + // If this is a returning user, i.e., we already have a `User` with this `Identity`, + // set `Online: true`, but leave `Name` and `Identity` unchanged. + user.Online = true; + User.UpdateByIdentity(dbEventArgs.Sender, user); + } + else + { + // If this is a new user, create a `User` object for the `Identity`, + // which is online, but hasn't set a name. + new User { - // If this is a new user, create a `User` object for the `Identity`, - // which is online, but hasn't set a name. - new User - { - Name = null, - Identity = dbEventArgs.Sender, - Online = true, - }.Insert(); - } + Name = null, + Identity = dbEventArgs.Sender, + Online = true, + }.Insert(); } +} ``` Similarly, whenever a client disconnects, the module will execute the `OnDisconnect` event if it's registered with `ReducerKind.Disconnect`. We'll use it to un-set the `Online` status of the `User` for the disconnected client. Add the following code after the `OnConnect` lambda: -```C# - [SpacetimeDB.Reducer(ReducerKind.Disconnect)] - public static void OnDisconnect(DbEventArgs dbEventArgs) - { - var user = User.FindByIdentity(dbEventArgs.Sender); +```csharp +[SpacetimeDB.Reducer(ReducerKind.Disconnect)] +public static void OnDisconnect(DbEventArgs dbEventArgs) +{ + var user = User.FindByIdentity(dbEventArgs.Sender); - if (user is not null) - { - // This user should exist, so set `Online: false`. - user.Online = false; - User.UpdateByIdentity(dbEventArgs.Sender, user); - } - else - { - // User does not exist, log warning - Log($"Warning: No user found for disconnected client."); - } + if (user is not null) + { + // This user should exist, so set `Online: false`. + user.Online = false; + User.UpdateByIdentity(dbEventArgs.Sender, user); } + else + { + // User does not exist, log warning + Log($"Warning: No user found for disconnected client."); + } +} ``` ## Publish the module From dae1640c938ac64f5e39ce97f9f37141c55a69a6 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Mon, 20 Nov 2023 17:49:34 -0800 Subject: [PATCH 14/80] Update index.md Fixes a margin for the figure that kinda breaks it on mobile. --- docs/Overview/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Overview/index.md b/docs/Overview/index.md index 0e1a6394..35ebbcb7 100644 --- a/docs/Overview/index.md +++ b/docs/Overview/index.md @@ -24,7 +24,7 @@ This means that you can write your entire application in a single language, Rust
SpacetimeDB Architecture -
+
SpacetimeDB application architecture (elements in white are provided by SpacetimeDB)
From ba8a6f7bc19e022a95c3333dff8a092905787f35 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 21 Nov 2023 19:34:27 -0800 Subject: [PATCH 15/80] Update index.md --- docs/Client SDK Languages/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Client SDK Languages/index.md b/docs/Client SDK Languages/index.md index 27c9284f..2e3e0740 100644 --- a/docs/Client SDK Languages/index.md +++ b/docs/Client SDK Languages/index.md @@ -1,4 +1,4 @@ -# Welcome to Client SDK Languages# SpacetimeDB Client SDKs Overview + SpacetimeDB Client SDKs Overview The SpacetimeDB Client SDKs provide a comprehensive interface to interact with the SpacetimeDB server engine from various programming languages. Currently, SDKs are available for From 0c5cc3d5890b22cee1ddb59ce911e54f7bf44252 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 21 Nov 2023 22:39:35 -0800 Subject: [PATCH 16/80] Added nav.ts --- docs/nav.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/nav.ts diff --git a/docs/nav.ts b/docs/nav.ts new file mode 100644 index 00000000..caccf9d1 --- /dev/null +++ b/docs/nav.ts @@ -0,0 +1,75 @@ +export type Nav = { + items: NavItem[]; +}; +export type NavItem = NavPage | NavSection; +export type NavPage = { + type: "page"; + path: string; + title: string; + disabled?: boolean; + href?: string; +}; +type NavSection = { + type: "section"; + title: string; +}; + +function page(path: string, title: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { + return { type: "page", path: path, title, ...props }; +} +function section(title: string): NavSection { + return { type: "section", title }; +} + +export default { + items: [ + section("Intro"), + page("Overview/index.md", "Overview"), + page("Getting Started/index.md", "Getting Started"), + + section("Deploying"), + page("Cloud Testnet/index.md", "Testnet"), + + section("Unity Tutorial"), + page("Unity Tutorial/Part 1 - Basic Multiplayer.md", "Part 1 - Basic Multiplayer"), + page("Unity Tutorial/Part 2 - Resources And Scheduling.md", "Part 2 - Resources And Scheduling"), + page("Unity Tutorial/Part 3 - BitCraft Mini.md", "Part 3 - BitCraft Mini"), + + section("Server Module Languages"), + page("Server Module Languages/index.md", "Overview"), + page("Server Module Languages/Rust/index.md", "Rust Quickstart"), + page("Server Module Languages/Rust/ModuleReference.md", "Rust Reference"), + page("Server Module Languages/C#/index.md", "C# Quickstart"), + page("Server Module Languages/C#/ModuleReference.md", "C# Reference"), + + section("Client SDK Languages"), + page("Client SDK Languages/index.md", "Overview"), + page("Client SDK Languages/Typescript/index.md", "Typescript Quickstart"), + page("Client SDK Languages/Typescript/SDK Reference.md", "Typescript Reference"), + page("Client SDK Languages/Rust/index.md", "Rust Quickstart"), + page("Client SDK Languages/Rust/SDK Reference.md", "Rust Reference"), + page("Client SDK Languages/Python/index.md", "Python Quickstart"), + page("Client SDK Languages/Python/SDK Reference.md", "Python Reference"), + page("Client SDK Languages/C#/index.md", "C# Quickstart"), + page("Client SDK Languages/C#/SDK Reference.md", "C# Reference"), + + section("WebAssembly ABI"), + page("Module ABI Reference/index.md", "Module ABI Reference"), + + section("HTTP API"), + page("HTTP API Reference/index.md", "HTTP"), + page("HTTP API Reference/Identities.md", "`/identity`"), + page("HTTP API Reference/Databases.md", "`/database`"), + page("HTTP API Reference/Energy.md", "`/energy`"), + + section("WebSocket API Reference"), + page("WebSocket API Reference/index.md", "WebSocket"), + + section("Data Format"), + page("SATN Reference/index.md", "SATN"), + page("SATN Reference/Binary Format.md", "BSATN"), + + section("SQL"), + page("SQL Reference/index.md", "SQL Reference"), + ], +} satisfies Nav; From 98810a9f82e697481dc72a11c6a41017be9fe355 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 21 Nov 2023 22:52:36 -0800 Subject: [PATCH 17/80] Removed satisfies keyword for better Typescript compat --- docs/nav.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/nav.ts b/docs/nav.ts index caccf9d1..7a1a032f 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -21,7 +21,7 @@ function section(title: string): NavSection { return { type: "section", title }; } -export default { +const nav: Nav = { items: [ section("Intro"), page("Overview/index.md", "Overview"), @@ -72,4 +72,6 @@ export default { section("SQL"), page("SQL Reference/index.md", "SQL Reference"), ], -} satisfies Nav; +}; + +export default nav; From 02184d9f5c2cd8e536ba8789fba79683fda0cef2 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 22 Nov 2023 20:14:10 -0800 Subject: [PATCH 18/80] Added slugs to nav --- docs/nav.ts | 63 +++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/nav.ts b/docs/nav.ts index 7a1a032f..f7681728 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -5,6 +5,7 @@ export type NavItem = NavPage | NavSection; export type NavPage = { type: "page"; path: string; + slug: string; title: string; disabled?: boolean; href?: string; @@ -14,8 +15,8 @@ type NavSection = { title: string; }; -function page(path: string, title: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { - return { type: "page", path: path, title, ...props }; +function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { + return { type: "page", path, slug, title, ...props }; } function section(title: string): NavSection { return { type: "section", title }; @@ -24,53 +25,53 @@ function section(title: string): NavSection { const nav: Nav = { items: [ section("Intro"), - page("Overview/index.md", "Overview"), - page("Getting Started/index.md", "Getting Started"), + page("Overview", "index", "Overview/index.md"), + page("Getting Started", "getting-started", "Getting Started/index.md"), section("Deploying"), - page("Cloud Testnet/index.md", "Testnet"), + page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), section("Unity Tutorial"), - page("Unity Tutorial/Part 1 - Basic Multiplayer.md", "Part 1 - Basic Multiplayer"), - page("Unity Tutorial/Part 2 - Resources And Scheduling.md", "Part 2 - Resources And Scheduling"), - page("Unity Tutorial/Part 3 - BitCraft Mini.md", "Part 3 - BitCraft Mini"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), section("Server Module Languages"), - page("Server Module Languages/index.md", "Overview"), - page("Server Module Languages/Rust/index.md", "Rust Quickstart"), - page("Server Module Languages/Rust/ModuleReference.md", "Rust Reference"), - page("Server Module Languages/C#/index.md", "C# Quickstart"), - page("Server Module Languages/C#/ModuleReference.md", "C# Reference"), + page("Overview", "modules", "Server Module Languages/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), + page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), + page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), section("Client SDK Languages"), - page("Client SDK Languages/index.md", "Overview"), - page("Client SDK Languages/Typescript/index.md", "Typescript Quickstart"), - page("Client SDK Languages/Typescript/SDK Reference.md", "Typescript Reference"), - page("Client SDK Languages/Rust/index.md", "Rust Quickstart"), - page("Client SDK Languages/Rust/SDK Reference.md", "Rust Reference"), - page("Client SDK Languages/Python/index.md", "Python Quickstart"), - page("Client SDK Languages/Python/SDK Reference.md", "Python Reference"), - page("Client SDK Languages/C#/index.md", "C# Quickstart"), - page("Client SDK Languages/C#/SDK Reference.md", "C# Reference"), + page("Overview", "sdks", "Client SDK Languages/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), + page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), + page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), + page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), + page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), + page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), section("WebAssembly ABI"), - page("Module ABI Reference/index.md", "Module ABI Reference"), + page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), section("HTTP API"), - page("HTTP API Reference/index.md", "HTTP"), - page("HTTP API Reference/Identities.md", "`/identity`"), - page("HTTP API Reference/Databases.md", "`/database`"), - page("HTTP API Reference/Energy.md", "`/energy`"), + page("HTTP", "http", "HTTP API Reference/index.md"), + page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), + page("`/database`", "http/database", "HTTP API Reference/Databases.md"), + page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), section("WebSocket API Reference"), - page("WebSocket API Reference/index.md", "WebSocket"), + page("WebSocket", "ws", "WebSocket API Reference/index.md"), section("Data Format"), - page("SATN Reference/index.md", "SATN"), - page("SATN Reference/Binary Format.md", "BSATN"), + page("SATN", "satn", "SATN Reference/index.md"), + page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), section("SQL"), - page("SQL Reference/index.md", "SQL Reference"), + page("SQL Reference", "sql", "SQL Reference/index.md"), ], }; From adfd936dd7c8fe86ac85a2471f221e92260c90a5 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Nov 2023 20:41:01 +0000 Subject: [PATCH 19/80] Ask users to install .NET 8 --- docs/Server Module Languages/C#/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index 0346157f..d5638423 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -14,9 +14,9 @@ A reducer is a function which traverses and updates the database. Each reducer c If you haven't already, start by [installing SpacetimeDB](/install). This will install the `spacetime` command line interface (CLI), which contains all the functionality for interacting with SpacetimeDB. -## Install .NET +## Install .NET 8 -Next we need to [install .NET](https://dotnet.microsoft.com/en-us/download/dotnet) so that we can build and publish our module. +Next we need to [install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) so that we can build and publish our module. .NET 8.0 is the earliest to have the `wasi-experimental` workload that we rely on. ## Project structure From f43e54971655c429b782f9362632a7e589abbb02 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Mon, 27 Nov 2023 23:58:02 -0800 Subject: [PATCH 20/80] Fix most (but possibly not all) links in the docs --- docs/Client SDK Languages/C#/SDK Reference.md | 6 ++-- docs/Client SDK Languages/C#/index.md | 2 +- .../Python/SDK Reference.md | 2 +- docs/Client SDK Languages/Python/index.md | 2 +- docs/Client SDK Languages/Rust/index.md | 4 +-- docs/Client SDK Languages/Typescript/index.md | 2 +- docs/Client SDK Languages/index.md | 8 ++--- docs/Getting Started/index.md | 14 ++++---- docs/HTTP API Reference/Databases.md | 34 +++++++++---------- docs/HTTP API Reference/Energy.md | 2 +- docs/HTTP API Reference/Identities.md | 6 ++-- docs/HTTP API Reference/index.md | 4 +-- docs/Overview/index.md | 18 +++++----- docs/SATN Reference/index.md | 4 +-- docs/SQL Reference/index.md | 2 +- docs/Server Module Languages/C#/index.md | 2 +- docs/Server Module Languages/Rust/index.md | 4 +-- docs/Server Module Languages/index.md | 8 ++--- docs/Unity Tutorial/Part 3 - BitCraft Mini.md | 2 +- docs/WebSocket API Reference/index.md | 12 +++---- 20 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/Client SDK Languages/C#/SDK Reference.md b/docs/Client SDK Languages/C#/SDK Reference.md index ad4c8c48..473ca1ba 100644 --- a/docs/Client SDK Languages/C#/SDK Reference.md +++ b/docs/Client SDK Languages/C#/SDK Reference.md @@ -44,7 +44,7 @@ The SpacetimeDB client C# for Rust contains all the tools you need to build nati - [Static Property `AuthToken.Token`](#static-property-authtokentoken) - [Static Method `AuthToken.SaveToken`](#static-method-authtokensavetoken) - [Class `Identity`](#class-identity) - - [Class `Address`](#class-address) + - [Class `Identity`](#class-identity-1) - [Customizing logging](#customizing-logging) - [Interface `ISpacetimeDBLogger`](#interface-ispacetimedblogger) - [Class `ConsoleLogger`](#class-consolelogger) @@ -60,7 +60,7 @@ If you would like to create a console application using .NET, you can create a n dotnet add package spacetimedbsdk ``` -(See also the [CSharp Quickstart](./CSharpSDKQuickStart) for an in-depth example of such a console application.) +(See also the [CSharp Quickstart](/docs/modules/c-sharp/quickstart) for an in-depth example of such a console application.) ### Using Unity @@ -70,7 +70,7 @@ https://sdk.spacetimedb.com/SpacetimeDBUnitySDK.unitypackage In Unity navigate to the `Assets > Import Package > Custom Package...` menu in the menu bar. Select your `SpacetimeDBUnitySDK.unitypackage` file and leave all folders checked. -(See also the [Unity Quickstart](./UnityQuickStart) and [Unity Tutorial](./UnityTutorialPart1).) +(See also the [Unity Tutorial](/docs/unity/part-1).) ## Generate module bindings diff --git a/docs/Client SDK Languages/C#/index.md b/docs/Client SDK Languages/C#/index.md index f4d8b7ee..f7565019 100644 --- a/docs/Client SDK Languages/C#/index.md +++ b/docs/Client SDK Languages/C#/index.md @@ -6,7 +6,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/server-languages/rust/rust-module-quickstart-guide) or [C# Module Quickstart](/docs/server-languages/csharp/csharp-module-quickstart-guide) guides: +Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-sharp/quickstart) guides: ```bash cd quickstart-chat diff --git a/docs/Client SDK Languages/Python/SDK Reference.md b/docs/Client SDK Languages/Python/SDK Reference.md index 276d59df..8b1ceb8b 100644 --- a/docs/Client SDK Languages/Python/SDK Reference.md +++ b/docs/Client SDK Languages/Python/SDK Reference.md @@ -253,7 +253,7 @@ Run the client. This function will not return until the client is closed. If `auth_token` is not None, they will be passed to the new connection to identify and authenticate the user. Otherwise, a new Identity and auth token will be generated by the server. An optional [local_config](#local_config) module can be used to store the user's auth token to local storage. -If you are connecting to SpacetimeDB Cloud `testnet` the host should be `testnet.spacetimedb.com` and `ssl_enabled` should be `True`. If you are connecting to SpacetimeDB Standalone locally, the host should be `localhost:3000` and `ssl_enabled` should be `False`. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/DeploymentOverview.md) +If you are connecting to SpacetimeDB Cloud `testnet` the host should be `testnet.spacetimedb.com` and `ssl_enabled` should be `True`. If you are connecting to SpacetimeDB Standalone locally, the host should be `localhost:3000` and `ssl_enabled` should be `False`. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/deploying/testnet) ```python asyncio.run( diff --git a/docs/Client SDK Languages/Python/index.md b/docs/Client SDK Languages/Python/index.md index 25723fcc..2b9d7aa1 100644 --- a/docs/Client SDK Languages/Python/index.md +++ b/docs/Client SDK Languages/Python/index.md @@ -2,7 +2,7 @@ In this guide, we'll show you how to get up and running with a simple SpacetimDB app with a client written in Python. -We'll implement a command-line client for the module created in our [Rust Module Quickstart](/docs/languages/rust/rust-module-quickstart-guide) or [C# Module Quickstart](/docs/languages/csharp/csharp-module-reference) guides. Make sure you follow one of these guides before you start on this one. +We'll implement a command-line client for the module created in our [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-charp/quickstart) guides. Make sure you follow one of these guides before you start on this one. ## Install the SpacetimeDB SDK Python Package diff --git a/docs/Client SDK Languages/Rust/index.md b/docs/Client SDK Languages/Rust/index.md index f35f0829..d1969fc3 100644 --- a/docs/Client SDK Languages/Rust/index.md +++ b/docs/Client SDK Languages/Rust/index.md @@ -6,7 +6,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/server-languages/rust/rust-module-quickstart-guide) or [C# Module Quickstart](/docs/server-languages/csharp/csharp-module-reference) guides: +Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-sharp/quickstart) guides: ```bash cd quickstart-chat @@ -471,7 +471,7 @@ User connected. You can find the full code for this client [in the Rust SDK's examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/sdk/examples/quickstart-chat). -Check out the [Rust SDK Reference](/docs/client-languages/rust/rust-sdk-reference) for a more comprehensive view of the SpacetimeDB Rust SDK. +Check out the [Rust SDK Reference](/docs/sdks/rust) for a more comprehensive view of the SpacetimeDB Rust SDK. Our bare-bones terminal interface has some quirks. Incoming messages can appear while the user is typing and be spliced into the middle of user input, which is less than ideal. Also, the user's input is interspersed with the program's output, so messages the user sends will seem to appear twice. Why not try building a better interface using [Rustyline](https://crates.io/crates/rustyline), [Cursive](https://crates.io/crates/cursive), or even a full-fledged GUI? We went for the Cursive route, and you can check out what we came up with [in the Rust SDK's examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/sdk/examples/cursive-chat). diff --git a/docs/Client SDK Languages/Typescript/index.md b/docs/Client SDK Languages/Typescript/index.md index 8baed6fb..ab7cfe89 100644 --- a/docs/Client SDK Languages/Typescript/index.md +++ b/docs/Client SDK Languages/Typescript/index.md @@ -6,7 +6,7 @@ We'll implement a basic single page web app for the module created in our Rust o ## Project structure -Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/server-languages/rust/rust-module-quickstart-guide) or [C# Module Quickstart](/docs/server-languages/csharp/csharp-module-reference) guides: +Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-sharp/quickstart) guides: ```bash cd quickstart-chat diff --git a/docs/Client SDK Languages/index.md b/docs/Client SDK Languages/index.md index 2e3e0740..6357e653 100644 --- a/docs/Client SDK Languages/index.md +++ b/docs/Client SDK Languages/index.md @@ -2,10 +2,10 @@ The SpacetimeDB Client SDKs provide a comprehensive interface to interact with the SpacetimeDB server engine from various programming languages. Currently, SDKs are available for -- [Rust](/docs/client-languages/rust/rust-sdk-reference) - [(Quickstart)](/docs/client-languages/rust/rust-sdk-quickstart-guide) -- [C#](/docs/client-languages/csharp/csharp-sdk-reference) - [(Quickstart)](/docs/client-languages/csharp/csharp-sdk-quickstart-guide) -- [TypeScript](/docs/client-languages/typescript/typescript-sdk-reference) - [(Quickstart)](client-languages/typescript/typescript-sdk-quickstart-guide) -- [Python](/docs/client-languages/python/python-sdk-reference) - [(Quickstart)](/docs/python/python-sdk-quickstart-guide) +- [Rust](/docs/sdks/rust) - [(Quickstart)](/docs/sdks/rust/quickstart) +- [C#](/docs/sdks/c-sharp) - [(Quickstart)](/docs/sdks/c-sharp/quickstart) +- [TypeScript](/docs/sdks/typescript) - [(Quickstart)](/docs/sdks/typescript/quickstart) +- [Python](/docs/sdks/python) - [(Quickstart)](/docs/sdks/python/quickstart) ## Key Features diff --git a/docs/Getting Started/index.md b/docs/Getting Started/index.md index 854d227c..54337d08 100644 --- a/docs/Getting Started/index.md +++ b/docs/Getting Started/index.md @@ -23,14 +23,14 @@ spacetime server set "http://localhost:3000" You are ready to start developing SpacetimeDB modules. We have a quickstart guide for each supported server-side language: -- [Rust](/docs/server-languages/rust/rust-module-quickstart-guide) -- [C#](/docs/server-languages/csharp/csharp-module-quickstart-guide) +- [Rust](/docs/modules/rust/quickstart) +- [C#](/docs/modules/c-sharp/quickstart) Then you can write your client application. We have a quickstart guide for each supported client-side language: -- [Rust](/docs/client-languages/rust/rust-sdk-quickstart-guide) -- [C#](/docs/client-languages/csharp/csharp-sdk-quickstart-guide) -- [Typescript](/docs/client-languages/typescript/typescript-sdk-quickstart-guide) -- [Python](/docs/client-languages/python/python-sdk-quickstart-guide) +- [Rust](/docs/sdks/rust/quickstart) +- [C#](/docs/sdks/c-sharp/quickstart) +- [Typescript](/docs/sdks/typescript/quickstart) +- [Python](/docs/sdks/python/quickstart) -We also have a [step-by-step tutorial](/docs/unity-tutorial/unity-tutorial-part-1) for building a multiplayer game in Unity3d. +We also have a [step-by-step tutorial](/docs/unity/part-1) for building a multiplayer game in Unity3d. diff --git a/docs/HTTP API Reference/Databases.md b/docs/HTTP API Reference/Databases.md index 91e7d0a9..2d55188a 100644 --- a/docs/HTTP API Reference/Databases.md +++ b/docs/HTTP API Reference/Databases.md @@ -15,7 +15,7 @@ The HTTP endpoints in `/database` allow clients to interact with Spacetime datab | [`/database/confirm_recovery_code GET`](#databaseconfirm_recovery_code-get) | Recover a login token from a recovery code. | | [`/database/publish POST`](#databasepublish-post) | Publish a database given its module code. | | [`/database/delete/:address POST`](#databasedeleteaddress-post) | Delete a database. | -| [`/database/subscribe/:name_or_address GET`](#databasesubscribename_or_address-get) | Begin a [WebSocket connection](/docs/websocket-api-reference). | +| [`/database/subscribe/:name_or_address GET`](#databasesubscribename_or_address-get) | Begin a [WebSocket connection](/docs/ws). | | [`/database/call/:name_or_address/:reducer POST`](#databasecallname_or_addressreducer-post) | Invoke a reducer in a database. | | [`/database/schema/:name_or_address GET`](#databaseschemaname_or_address-get) | Get the schema for a database. | | [`/database/schema/:name_or_address/:entity_type/:entity GET`](#databaseschemaname_or_addressentity_typeentity-get) | Get a schema for a particular table or reducer. | @@ -94,7 +94,7 @@ Accessible through the CLI as `spacetime dns set-name
`. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -147,7 +147,7 @@ Accessible through the CLI as `spacetime dns register-tld `. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -186,7 +186,7 @@ Accessible through the CLI as `spacetime identity recover `. | Name | Value | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identity` | The identity whose token should be recovered. | -| `email` | The email to send the recovery code or link to. This email must be associated with the identity, either during creation via [`/identity`](/docs/http-api-reference/identities#identity-post) or afterwards via [`/identity/:identity/set-email`](/docs/http-api-reference/identities#identityidentityset_email-post). | +| `email` | The email to send the recovery code or link to. This email must be associated with the identity, either during creation via [`/identity`](/docs/http/identity#identity-post) or afterwards via [`/identity/:identity/set-email`](/docs/http/identity#identityidentityset_email-post). | | `link` | A boolean; whether to send a clickable link rather than a recovery code. | ## `/database/confirm_recovery_code GET` @@ -231,7 +231,7 @@ Accessible through the CLI as `spacetime publish`. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data @@ -283,11 +283,11 @@ Accessible through the CLI as `spacetime delete
`. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/database/subscribe/:name_or_address GET` -Begin a [WebSocket connection](/docs/websocket-api-reference) with a database. +Begin a [WebSocket connection](/docs/ws) with a database. #### Parameters @@ -301,7 +301,7 @@ For more information about WebSocket headers, see [RFC 6455](https://datatracker | Name | Value | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `Sec-WebSocket-Protocol` | [`v1.bin.spacetimedb`](/docs/websocket-api-reference#binary-protocol) or [`v1.text.spacetimedb`](/docs/websocket-api-reference#text-protocol). | +| `Sec-WebSocket-Protocol` | [`v1.bin.spacetimedb`](/docs/ws#binary-protocol) or [`v1.text.spacetimedb`](/docs/ws#text-protocol). | | `Connection` | `Updgrade` | | `Upgrade` | `websocket` | | `Sec-WebSocket-Version` | `13` | @@ -311,7 +311,7 @@ For more information about WebSocket headers, see [RFC 6455](https://datatracker | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/database/call/:name_or_address/:reducer POST` @@ -328,7 +328,7 @@ Invoke a reducer in a database. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data @@ -448,9 +448,9 @@ The `"entities"` will be an object whose keys are table and reducer names, and w | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `arity` | For tables, the number of colums; for reducers, the number of arguments. | | `type` | For tables, `"table"`; for reducers, `"reducer"`. | -| `schema` | A [JSON-encoded `ProductType`](/docs/satn-reference/satn-reference-json-format); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | +| `schema` | A [JSON-encoded `ProductType`](/docs/satn); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | -The `"typespace"` will be a JSON array of [`AlgebraicType`s](/docs/satn-reference/satn-reference-json-format) referenced by the module. This can be used to resolve `Ref` types within the schema; the type `{ "Ref": n }` refers to `response["typespace"][n]`. +The `"typespace"` will be a JSON array of [`AlgebraicType`s](/docs/satn) referenced by the module. This can be used to resolve `Ref` types within the schema; the type `{ "Ref": n }` refers to `response["typespace"][n]`. ## `/database/schema/:name_or_address/:entity_type/:entity GET` @@ -488,7 +488,7 @@ Returns a single entity in the same format as in the `"entities"` returned by [t | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `arity` | For tables, the number of colums; for reducers, the number of arguments. | | `type` | For tables, `"table"`; for reducers, `"reducer"`. | -| `schema` | A [JSON-encoded `ProductType`](/docs/satn-reference/satn-reference-json-format); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | +| `schema` | A [JSON-encoded `ProductType`](/docs/satn); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | ## `/database/info/:name_or_address GET` @@ -545,7 +545,7 @@ Accessible through the CLI as `spacetime logs `. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -567,7 +567,7 @@ Accessible through the CLI as `spacetime sql `. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data @@ -584,6 +584,6 @@ Returns a JSON array of statement results, each of which takes the form: } ``` -The `schema` will be a [JSON-encoded `ProductType`](/docs/satn-reference/satn-reference-json-format) describing the type of the returned rows. +The `schema` will be a [JSON-encoded `ProductType`](/docs/satn) describing the type of the returned rows. -The `rows` will be an array of [JSON-encoded `ProductValue`s](/docs/satn-reference/satn-reference-json-format), each of which conforms to the `schema`. +The `rows` will be an array of [JSON-encoded `ProductValue`s](/docs/satn), each of which conforms to the `schema`. diff --git a/docs/HTTP API Reference/Energy.md b/docs/HTTP API Reference/Energy.md index a7b6d05a..b49a1ee7 100644 --- a/docs/HTTP API Reference/Energy.md +++ b/docs/HTTP API Reference/Energy.md @@ -59,7 +59,7 @@ Accessible through the CLI as `spacetime energy set-balance | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns diff --git a/docs/HTTP API Reference/Identities.md b/docs/HTTP API Reference/Identities.md index 87411759..5fb45867 100644 --- a/docs/HTTP API Reference/Identities.md +++ b/docs/HTTP API Reference/Identities.md @@ -73,7 +73,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](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -109,7 +109,7 @@ Accessible through the CLI as `spacetime identity set-email `. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/identity/:identity/databases GET` @@ -147,7 +147,7 @@ Verify the validity of an identity/token pair. | Name | Value | | --------------- | ------------------------------------------------------------------------------------------- | -| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http-api-reference/authorization). | +| `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns diff --git a/docs/HTTP API Reference/index.md b/docs/HTTP API Reference/index.md index 224aaf77..a4e885b1 100644 --- a/docs/HTTP API Reference/index.md +++ b/docs/HTTP API Reference/index.md @@ -6,9 +6,9 @@ Rather than a password, each Spacetime identity is associated with a private tok ### Generating identities and tokens -Clients can request a new identity and token via [the `/identity POST` HTTP endpoint](/docs/http-api-reference/identities#identity-post). +Clients can request a new identity and token via [the `/identity POST` HTTP endpoint](/docs/http/identity#identity-post). -Alternately, a new identity and token will be generated during an anonymous connection via the [WebSocket API](/docs/websocket-api-reference), and passed to the client as [an `IdentityToken` message](/docs/websocket-api-reference#identitytoken). +Alternately, a new identity and token will be generated during an anonymous connection via the [WebSocket API](/docs/ws), and passed to the client as [an `IdentityToken` message](/docs/ws#identitytoken). ### Encoding `Authorization` headers diff --git a/docs/Overview/index.md b/docs/Overview/index.md index 35ebbcb7..7a95f4f8 100644 --- a/docs/Overview/index.md +++ b/docs/Overview/index.md @@ -52,7 +52,7 @@ Each identity has a corresponding authentication token. The authentication token Additionally, each database has an owner `Identity`. Many database maintenance operations, like publishing a new version or evaluating arbitrary SQL queries, are restricted to only authenticated connections by the owner. -SpacetimeDB provides tools in the CLI and the [client SDKs](/docs/client-languages/client-sdk-overview) for managing credentials. +SpacetimeDB provides tools in the CLI and the [client SDKs](/docs/sdks) for managing credentials. ## Addresses @@ -68,8 +68,8 @@ Each client connection has an `Address`. These addresses are opaque, and do not Currently, Rust is the best-supported language for writing SpacetimeDB modules. Support for lots of other languages is in the works! -- [Rust](/docs/server-languages/rust/rust-module-reference) - [(Quickstart)](/docs/server-languages/rust/rust-module-quickstart-guide) -- [C#](/docs/server-languages/csharp/csharp-module-reference) - [(Quickstart)](/docs/server-languages/csharp/csharp-module-quickstart-guide) +- [Rust](/docs/modules/rust) - [(Quickstart)](/docs/modules/rust/quickstart) +- [C#](/docs/modules/c-sharp) - [(Quickstart)](/docs/modules/c-sharp/quickstart) - Python (Coming soon) - C# (Coming soon) - Typescript (Coming soon) @@ -78,16 +78,16 @@ Currently, Rust is the best-supported language for writing SpacetimeDB modules. ### Client-side SDKs -- [Rust](/docs/client-languages/rust/rust-sdk-reference) - [(Quickstart)](/docs/client-languages/rust/rust-sdk-quickstart-guide) -- [C#](/docs/client-languages/csharp/csharp-sdk-reference) - [(Quickstart)](/docs/client-languages/csharp/csharp-sdk-quickstart-guide) -- [TypeScript](/docs/client-languages/typescript/typescript-sdk-reference) - [(Quickstart)](client-languages/typescript/typescript-sdk-quickstart-guide) -- [Python](/docs/client-languages/python/python-sdk-reference) - [(Quickstart)](/docs/python/python-sdk-quickstart-guide) +- [Rust](/docs/sdks/rust) - [(Quickstart)](/docs/sdks/rust/quickstart) +- [C#](/docs/sdks/c-sharp) - [(Quickstart)](/docs/sdks/c-sharp/quickstart) +- [TypeScript](/docs/sdks/typescript) - [(Quickstart)](/docs/sdks/typescript/quickstart) +- [Python](/docs/sdks/python) - [(Quickstart)](/docs/sdks/python/quickstart) - C++ (Planned) - Lua (Planned) ### Unity -SpacetimeDB was designed first and foremost as the backend for multiplayer Unity games. To learn more about using SpacetimeDB with Unity, jump on over to the [SpacetimeDB Unity Tutorial](/docs/unity-tutorial/unity-tutorial-part-1). +SpacetimeDB was designed first and foremost as the backend for multiplayer Unity games. To learn more about using SpacetimeDB with Unity, jump on over to the [SpacetimeDB Unity Tutorial](/docs/unity/part-1). ## FAQ @@ -101,7 +101,7 @@ SpacetimeDB was designed first and foremost as the backend for multiplayer Unity 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](/docs/quick-start) guide! + Follow our [Quick Start](/docs/getting-started) guide! TL;DR in an empty directory: diff --git a/docs/SATN Reference/index.md b/docs/SATN Reference/index.md index cedc496a..f21e9b30 100644 --- a/docs/SATN Reference/index.md +++ b/docs/SATN Reference/index.md @@ -1,6 +1,6 @@ # SATN JSON Format -The Spacetime Algebraic Type Notation 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](/docs/http-api-reference/databases) and the [WebSocket text protocol](/docs/websocket-api-reference#text-protocol). +The Spacetime Algebraic Type Notation 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](/docs/http/database) and the [WebSocket text protocol](/docs/ws#text-protocol). ## Values @@ -160,4 +160,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 [`/database/schema/:name_or_address GET` HTTP endpoint](/docs/http-api-reference/databases#databaseschemaname_or_address-get). +`AlgebraicTypeRef`s are JSON-encoded as non-negative integers. These are indices into a typespace, like the one returned by the [`/database/schema/:name_or_address GET` HTTP endpoint](/docs/http/database#databaseschemaname_or_address-get). diff --git a/docs/SQL Reference/index.md b/docs/SQL Reference/index.md index 08f9536a..66097209 100644 --- a/docs/SQL Reference/index.md +++ b/docs/SQL Reference/index.md @@ -1,6 +1,6 @@ # SQL Support -SpacetimeDB supports a subset of SQL as a query language. Developers can evaluate SQL queries against a Spacetime database via the `spacetime sql` command-line tool and the [`/database/sql/:name_or_address POST` HTTP endpoint](/docs/http-api-reference/databases#databasesqlname_or_address-post). Client developers also write SQL queries when subscribing to events in the [WebSocket API](/docs/websocket-api-reference#subscribe) or via an SDK `subscribe` function. +SpacetimeDB supports a subset of SQL as a query language. Developers can evaluate SQL queries against a Spacetime database via the `spacetime sql` command-line tool and the [`/database/sql/:name_or_address POST` HTTP endpoint](/docs/http/database#databasesqlname_or_address-post). Client developers also write SQL queries when subscribing to events in the [WebSocket API](/docs/ws#subscribe) or via an SDK `subscribe` function. SpacetimeDB aims to support much of the [SQL 2016 standard](https://www.iso.org/standard/63555.html), and in particular aims to be compatible with [PostgreSQL](https://www.postgresql.org/). diff --git a/docs/Server Module Languages/C#/index.md b/docs/Server Module Languages/C#/index.md index 0346157f..3d543f4a 100644 --- a/docs/Server Module Languages/C#/index.md +++ b/docs/Server Module Languages/C#/index.md @@ -288,4 +288,4 @@ spacetime sql "SELECT * FROM Message" You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/languages/rust/rust-sdk-quickstart-guide), [C#](/docs/languages/csharp/csharp-sdk-quickstart-guide), [TypeScript](/docs/languages/typescript/typescript-sdk-quickstart-guide) or [Python](/docs/languages/python/python-sdk-quickstart-guide). -If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/game-dev/unity-tutorial) or check out our example game, [BitcraftMini](/docs/game-dev/unity-tutorial-bitcraft-mini). +If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/Server Module Languages/Rust/index.md b/docs/Server Module Languages/Rust/index.md index 9f0a6636..6e0f1747 100644 --- a/docs/Server Module Languages/Rust/index.md +++ b/docs/Server Module Languages/Rust/index.md @@ -267,6 +267,6 @@ spacetime sql "SELECT * FROM Message" You can find the full code for this module [in the SpacetimeDB module examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/modules/quickstart-chat). -You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: [Rust](/docs/client-languages/rust/rust-sdk-quickstart-guide), [C#](/docs/client-languages/csharp/csharp-sdk-quickstart-guide), [TypeScript](/docs/client-languages/typescript/typescript-sdk-quickstart-guide) or [Python](/docs/client-languages/python/python-sdk-quickstart-guide). +You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: [Rust](/docs/sdks/rust/quickstart), [C#](/docs/sdks/c-sharp/quickstart), [TypeScript](/docs/sdks/typescript/quickstart) or [Python](/docs/sdks/python/quickstart). -If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/game-dev/unity-tutorial) or check out our example game, [BitcraftMini](/docs/game-dev/unity-tutorial-bitcraft-mini). +If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/Server Module Languages/index.md b/docs/Server Module Languages/index.md index d6668131..d7d13685 100644 --- a/docs/Server Module Languages/index.md +++ b/docs/Server Module Languages/index.md @@ -10,15 +10,15 @@ In the following sections, we'll cover the basics of server modules and how to c As of SpacetimeDB 0.6, Rust is the only fully supported language for server modules. Rust is a great option for server modules because it is fast, safe, and has a small runtime. -- [Rust Module Reference](/docs/server-languages/rust/rust-module-reference) -- [Rust Module Quickstart Guide](/docs/server-languages/rust/rust-module-quickstart-guide) +- [Rust Module Reference](/docs/modules/rust) +- [Rust Module Quickstart Guide](/docs/modules/rust/quickstart) ### C# We have C# support available in experimental status. C# can be a good choice for developers who are already using Unity or .net for their client applications. -- [C# Module Reference](/docs/server-languages/csharp/csharp-module-reference) -- [C# Module Quickstart Guide](/docs/server-languages/csharp/csharp-module-quickstart-guide) +- [C# Module Reference](/docs/modules/c-sharp) +- [C# Module Quickstart Guide](/docs/modules/c-sharp/quickstart) ### Coming Soon diff --git a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md b/docs/Unity Tutorial/Part 3 - BitCraft Mini.md index 16816dd6..b49b5a5d 100644 --- a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md +++ b/docs/Unity Tutorial/Part 3 - BitCraft Mini.md @@ -79,7 +79,7 @@ Open the Main scene in Unity and click on the `GameManager` object in the heirar ![GameManager-Inspector](/images/unity-tutorial/GameManager-Inspector.JPG) -Update the module address with the address you got from the `spacetime publish` command. If you are using SpacetimeDB Cloud `testnet`, the host name should be `testnet.spacetimedb.com` and SSL Enabled should be checked. If you are running SpacetimeDB Standalone locally, the host name should be `localhost:3000` and SSL Enabled should be unchecked. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/DeploymentOverview.md) +Update the module address with the address you got from the `spacetime publish` command. If you are using SpacetimeDB Cloud `testnet`, the host name should be `testnet.spacetimedb.com` and SSL Enabled should be checked. If you are running SpacetimeDB Standalone locally, the host name should be `localhost:3000` and SSL Enabled should be unchecked. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/deploying/testnet) ## 4. Play Mode diff --git a/docs/WebSocket API Reference/index.md b/docs/WebSocket API Reference/index.md index dd8fbc39..76240163 100644 --- a/docs/WebSocket API Reference/index.md +++ b/docs/WebSocket API Reference/index.md @@ -6,9 +6,9 @@ The SpacetimeDB SDKs comminicate with their corresponding database using the Web ## Connecting -To initiate a WebSocket connection, send a `GET` request to the [`/database/subscribe/:name_or_address` endpoint](/docs/http-api-reference/databases#databasesubscribename_or_address-get) with headers appropriate to upgrade to a WebSocket connection as per [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455). +To initiate a WebSocket connection, send a `GET` request to the [`/database/subscribe/:name_or_address` endpoint](/docs/http/database#databasesubscribename_or_address-get) with headers appropriate to upgrade to a WebSocket connection as per [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455). -To re-connect with an existing identity, include its token in a [SpacetimeDB Authorization header](/docs/http-api-reference/authorization). Otherwise, a new identity and token will be generated for the client. +To re-connect with an existing identity, include its token in a [SpacetimeDB Authorization header](/docs/http). Otherwise, a new identity and token will be generated for the client. ## Protocols @@ -21,13 +21,13 @@ Clients connecting via WebSocket can choose between two protocols, [`v1.bin.spac ### Binary Protocol -The SpacetimeDB binary WebSocket protocol, `v1.bin.spacetimedb`, encodes messages using [ProtoBuf 3](https://protobuf.dev), and reducer and row data using [BSATN](/docs/satn-reference/satn-reference-binary-format). +The SpacetimeDB binary WebSocket protocol, `v1.bin.spacetimedb`, encodes messages using [ProtoBuf 3](https://protobuf.dev), and reducer and row data using [BSATN](/docs/bsatn). The binary protocol's messages are defined in [`client_api.proto`](https://github.com/clockworklabs/SpacetimeDB/blob/master/crates/client-api-messages/protobuf/client_api.proto). ### Text Protocol -The SpacetimeDB text WebSocket protocol, `v1.text.spacetimedb`, encodes messages, reducer and row data as JSON. Reducer arguments and table rows are JSON-encoded according to the [SATN JSON format](/docs/satn-reference/satn-reference-json-format). +The SpacetimeDB text WebSocket protocol, `v1.text.spacetimedb`, encodes messages, reducer and row data as JSON. Reducer arguments and table rows are JSON-encoded according to the [SATN JSON format](/docs/satn). ## Messages @@ -82,7 +82,7 @@ SpacetimeDB responds to each `Subscribe` message with a [`SubscriptionUpdate` me Each `Subscribe` message establishes a new set of subscriptions, replacing all previous subscriptions. Clients which want to add a query to an existing subscription must send a `Subscribe` message containing all the previous queries in addition to the new query. In this case, the returned [`SubscriptionUpdate`](#subscriptionupdate) will contain all previously-subscribed rows in addition to the newly-subscribed rows. -Each query must be a SQL `SELECT * FROM` statement on a single table with an optional `WHERE` clause. See the [SQL Reference](/docs/sql-reference) for the subset of SQL supported by SpacetimeDB. +Each query must be a SQL `SELECT * FROM` statement on a single table with an optional `WHERE` clause. See the [SQL Reference](/docs/sql) for the subset of SQL supported by SpacetimeDB. ##### Binary: ProtoBuf definition @@ -120,7 +120,7 @@ message Subscribe { #### `IdentityToken` -Upon establishing a WebSocket connection, the server will send an `IdentityToken` message containing the client's identity and token. If the client included a [SpacetimeDB Authorization header](/docs/http-api-reference/authorization) in their connection request, the `IdentityToken` message will contain the same token used to connect, and its corresponding identity. If the client connected anonymously, SpacetimeDB will generate a new identity and token for the client. +Upon establishing a WebSocket connection, the server will send an `IdentityToken` message containing the client's identity and token. If the client included a [SpacetimeDB Authorization header](/docs/http) in their connection request, the `IdentityToken` message will contain the same token used to connect, and its corresponding identity. If the client connected anonymously, SpacetimeDB will generate a new identity and token for the client. ##### Binary: ProtoBuf definition From a7ca89ccd09a655e57d9e9f5cc2c62853be0228a Mon Sep 17 00:00:00 2001 From: Nathaniel Richards <46858886+NateTheDev1@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:57:48 -0500 Subject: [PATCH 21/80] Update nav.ts --- docs/nav.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/nav.ts b/docs/nav.ts index f7681728..b9a64ee0 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -1,8 +1,8 @@ -export type Nav = { +type Nav = { items: NavItem[]; }; -export type NavItem = NavPage | NavSection; -export type NavPage = { +type NavItem = NavPage | NavSection; +type NavPage = { type: "page"; path: string; slug: string; From 0f86be29c1d827b1263c1b3e88b737d4c48d7646 Mon Sep 17 00:00:00 2001 From: Nathaniel Richards Date: Tue, 28 Nov 2023 15:01:39 -0500 Subject: [PATCH 22/80] Created buildeR --- .gitignore | 3 +-- docs/nav.js | 52 +++++++++++++++++++++++++++++++++++++++++++ docs/nav.ts => nav.ts | 0 package.json | 15 +++++++++++++ tsconfig.json | 8 +++++++ yarn.lock | 8 +++++++ 6 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 docs/nav.js rename docs/nav.ts => nav.ts (100%) create mode 100644 package.json create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 55f71abd..589c396e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ **/.vscode .idea *.log -node_modules -dist \ No newline at end of file +node_modules \ No newline at end of file diff --git a/docs/nav.js b/docs/nav.js new file mode 100644 index 00000000..b62e9b7f --- /dev/null +++ b/docs/nav.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function page(title, slug, path, props) { + return { type: "page", path, slug, title, ...props }; +} +function section(title) { + return { type: "section", title }; +} +const nav = { + items: [ + section("Intro"), + page("Overview", "index", "Overview/index.md"), + page("Getting Started", "getting-started", "Getting Started/index.md"), + section("Deploying"), + page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), + section("Unity Tutorial"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), + section("Server Module Languages"), + page("Overview", "modules", "Server Module Languages/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), + page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), + page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), + section("Client SDK Languages"), + page("Overview", "sdks", "Client SDK Languages/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), + page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), + page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), + page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), + page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), + page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), + section("WebAssembly ABI"), + page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), + section("HTTP API"), + page("HTTP", "http", "HTTP API Reference/index.md"), + page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), + page("`/database`", "http/database", "HTTP API Reference/Databases.md"), + page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), + section("WebSocket API Reference"), + page("WebSocket", "ws", "WebSocket API Reference/index.md"), + section("Data Format"), + page("SATN", "satn", "SATN Reference/index.md"), + page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), + section("SQL"), + page("SQL Reference", "sql", "SQL Reference/index.md"), + ], +}; +exports.default = nav; diff --git a/docs/nav.ts b/nav.ts similarity index 100% rename from docs/nav.ts rename to nav.ts diff --git a/package.json b/package.json new file mode 100644 index 00000000..a56ea4e8 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "spacetime-docs", + "version": "1.0.0", + "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "typescript": "^5.3.2" + }, + "scripts": { + "build": "tsc" + }, + "author": "Clockwork Labs", + "license": "ISC" +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..2a5ee7d2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "commonjs", + "outDir": "./docs", + "skipLibCheck": true + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fce89544 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== From 182284bcf0808889da8f3a26762def4d90c7830a Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 28 Nov 2023 17:21:24 -0800 Subject: [PATCH 23/80] Reorganized the doc paths to match slugs and removed _category.json files --- docs/Client SDK Languages/C#/_category.json | 5 -- .../Python/_category.json | 5 -- docs/Client SDK Languages/Rust/_category.json | 5 -- .../Typescript/_category.json | 5 -- docs/Client SDK Languages/_category.json | 1 - docs/Cloud Testnet/_category.json | 1 - docs/Getting Started/_category.json | 1 - docs/HTTP API Reference/_category.json | 1 - docs/Module ABI Reference/_category.json | 1 - docs/Overview/_category.json | 1 - docs/SATN Reference/_category.json | 1 - docs/SQL Reference/_category.json | 1 - .../Server Module Languages/C#/_category.json | 6 -- .../Rust/_category.json | 5 -- docs/Server Module Languages/_category.json | 1 - docs/Unity Tutorial/_category.json | 5 -- docs/WebSocket API Reference/_category.json | 1 - .../Binary Format.md => bsatn.md} | 0 .../index.md => deploying/testnet.md} | 0 .../index.md => getting-started.md} | 0 .../Databases.md => http/database.md} | 0 .../Energy.md => http/energy.md} | 0 .../Identities.md => http/identity.md} | 0 docs/{HTTP API Reference => http}/index.md | 0 docs/{Overview => }/index.md | 0 .../c-sharp/index.md} | 0 .../c-sharp/quickstart.md} | 0 .../index.md | 0 .../rust/index.md} | 0 .../index.md => modules/rust/quickstart.md} | 0 docs/nav.js | 58 +++++++++---------- docs/{SATN Reference/index.md => satn.md} | 0 .../c-sharp/index.md} | 0 .../index.md => sdks/c-sharp/quickstart.md} | 0 docs/{Client SDK Languages => sdks}/index.md | 0 .../SDK Reference.md => sdks/python/index.md} | 0 .../index.md => sdks/python/quickstart.md} | 0 .../SDK Reference.md => sdks/rust/index.md} | 0 .../Rust/index.md => sdks/rust/quickstart.md} | 0 .../typescript/index.md} | 0 .../typescript/quickstart.md} | 0 docs/{SQL Reference => sql}/index.md | 0 .../part-1.md} | 0 .../part-2.md} | 0 .../part-3.md} | 0 .../index.md | 0 docs/{WebSocket API Reference => ws}/index.md | 0 nav.ts | 58 +++++++++---------- 48 files changed, 58 insertions(+), 104 deletions(-) delete mode 100644 docs/Client SDK Languages/C#/_category.json delete mode 100644 docs/Client SDK Languages/Python/_category.json delete mode 100644 docs/Client SDK Languages/Rust/_category.json delete mode 100644 docs/Client SDK Languages/Typescript/_category.json delete mode 100644 docs/Client SDK Languages/_category.json delete mode 100644 docs/Cloud Testnet/_category.json delete mode 100644 docs/Getting Started/_category.json delete mode 100644 docs/HTTP API Reference/_category.json delete mode 100644 docs/Module ABI Reference/_category.json delete mode 100644 docs/Overview/_category.json delete mode 100644 docs/SATN Reference/_category.json delete mode 100644 docs/SQL Reference/_category.json delete mode 100644 docs/Server Module Languages/C#/_category.json delete mode 100644 docs/Server Module Languages/Rust/_category.json delete mode 100644 docs/Server Module Languages/_category.json delete mode 100644 docs/Unity Tutorial/_category.json delete mode 100644 docs/WebSocket API Reference/_category.json rename docs/{SATN Reference/Binary Format.md => bsatn.md} (100%) rename docs/{Cloud Testnet/index.md => deploying/testnet.md} (100%) rename docs/{Getting Started/index.md => getting-started.md} (100%) rename docs/{HTTP API Reference/Databases.md => http/database.md} (100%) rename docs/{HTTP API Reference/Energy.md => http/energy.md} (100%) rename docs/{HTTP API Reference/Identities.md => http/identity.md} (100%) rename docs/{HTTP API Reference => http}/index.md (100%) rename docs/{Overview => }/index.md (100%) rename docs/{Server Module Languages/C#/ModuleReference.md => modules/c-sharp/index.md} (100%) rename docs/{Server Module Languages/C#/index.md => modules/c-sharp/quickstart.md} (100%) rename docs/{Server Module Languages => modules}/index.md (100%) rename docs/{Server Module Languages/Rust/ModuleReference.md => modules/rust/index.md} (100%) rename docs/{Server Module Languages/Rust/index.md => modules/rust/quickstart.md} (100%) rename docs/{SATN Reference/index.md => satn.md} (100%) rename docs/{Client SDK Languages/C#/SDK Reference.md => sdks/c-sharp/index.md} (100%) rename docs/{Client SDK Languages/C#/index.md => sdks/c-sharp/quickstart.md} (100%) rename docs/{Client SDK Languages => sdks}/index.md (100%) rename docs/{Client SDK Languages/Python/SDK Reference.md => sdks/python/index.md} (100%) rename docs/{Client SDK Languages/Python/index.md => sdks/python/quickstart.md} (100%) rename docs/{Client SDK Languages/Rust/SDK Reference.md => sdks/rust/index.md} (100%) rename docs/{Client SDK Languages/Rust/index.md => sdks/rust/quickstart.md} (100%) rename docs/{Client SDK Languages/Typescript/SDK Reference.md => sdks/typescript/index.md} (100%) rename docs/{Client SDK Languages/Typescript/index.md => sdks/typescript/quickstart.md} (100%) rename docs/{SQL Reference => sql}/index.md (100%) rename docs/{Unity Tutorial/Part 1 - Basic Multiplayer.md => unity/part-1.md} (100%) rename docs/{Unity Tutorial/Part 2 - Resources And Scheduling.md => unity/part-2.md} (100%) rename docs/{Unity Tutorial/Part 3 - BitCraft Mini.md => unity/part-3.md} (100%) rename docs/{Module ABI Reference => webassembly-abi}/index.md (100%) rename docs/{WebSocket API Reference => ws}/index.md (100%) diff --git a/docs/Client SDK Languages/C#/_category.json b/docs/Client SDK Languages/C#/_category.json deleted file mode 100644 index 60238f8e..00000000 --- a/docs/Client SDK Languages/C#/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "C#", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Python/_category.json b/docs/Client SDK Languages/Python/_category.json deleted file mode 100644 index 4e08cfa1..00000000 --- a/docs/Client SDK Languages/Python/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Python", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Rust/_category.json b/docs/Client SDK Languages/Rust/_category.json deleted file mode 100644 index 6280366c..00000000 --- a/docs/Client SDK Languages/Rust/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Rust", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Typescript/_category.json b/docs/Client SDK Languages/Typescript/_category.json deleted file mode 100644 index 590d44a2..00000000 --- a/docs/Client SDK Languages/Typescript/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Typescript", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/_category.json b/docs/Client SDK Languages/_category.json deleted file mode 100644 index 530c17aa..00000000 --- a/docs/Client SDK Languages/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Client SDK Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Cloud Testnet/_category.json b/docs/Cloud Testnet/_category.json deleted file mode 100644 index e6fa11b9..00000000 --- a/docs/Cloud Testnet/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Cloud Testnet","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Getting Started/_category.json b/docs/Getting Started/_category.json deleted file mode 100644 index a68dc36c..00000000 --- a/docs/Getting Started/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Getting Started","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/HTTP API Reference/_category.json b/docs/HTTP API Reference/_category.json deleted file mode 100644 index c8ad821b..00000000 --- a/docs/HTTP API Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"HTTP API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Module ABI Reference/_category.json b/docs/Module ABI Reference/_category.json deleted file mode 100644 index 7583598d..00000000 --- a/docs/Module ABI Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Module ABI Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Overview/_category.json b/docs/Overview/_category.json deleted file mode 100644 index 35164a50..00000000 --- a/docs/Overview/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Overview","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SATN Reference/_category.json b/docs/SATN Reference/_category.json deleted file mode 100644 index e26b2f05..00000000 --- a/docs/SATN Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"SATN Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SQL Reference/_category.json b/docs/SQL Reference/_category.json deleted file mode 100644 index 73d7df23..00000000 --- a/docs/SQL Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"SQL Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Server Module Languages/C#/_category.json b/docs/Server Module Languages/C#/_category.json deleted file mode 100644 index 71ae9015..00000000 --- a/docs/Server Module Languages/C#/_category.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "C#", - "disabled": false, - "index": "index.md", - "tag": "Expiremental" -} \ No newline at end of file diff --git a/docs/Server Module Languages/Rust/_category.json b/docs/Server Module Languages/Rust/_category.json deleted file mode 100644 index 6280366c..00000000 --- a/docs/Server Module Languages/Rust/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Rust", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Server Module Languages/_category.json b/docs/Server Module Languages/_category.json deleted file mode 100644 index 3bfa0e87..00000000 --- a/docs/Server Module Languages/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Server Module Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Unity Tutorial/_category.json b/docs/Unity Tutorial/_category.json deleted file mode 100644 index a3c837ad..00000000 --- a/docs/Unity Tutorial/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Unity Tutorial", - "disabled": false, - "index": "Part 1 - Basic Multiplayer.md" -} \ No newline at end of file diff --git a/docs/WebSocket API Reference/_category.json b/docs/WebSocket API Reference/_category.json deleted file mode 100644 index d2797306..00000000 --- a/docs/WebSocket API Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"WebSocket API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SATN Reference/Binary Format.md b/docs/bsatn.md similarity index 100% rename from docs/SATN Reference/Binary Format.md rename to docs/bsatn.md diff --git a/docs/Cloud Testnet/index.md b/docs/deploying/testnet.md similarity index 100% rename from docs/Cloud Testnet/index.md rename to docs/deploying/testnet.md diff --git a/docs/Getting Started/index.md b/docs/getting-started.md similarity index 100% rename from docs/Getting Started/index.md rename to docs/getting-started.md diff --git a/docs/HTTP API Reference/Databases.md b/docs/http/database.md similarity index 100% rename from docs/HTTP API Reference/Databases.md rename to docs/http/database.md diff --git a/docs/HTTP API Reference/Energy.md b/docs/http/energy.md similarity index 100% rename from docs/HTTP API Reference/Energy.md rename to docs/http/energy.md diff --git a/docs/HTTP API Reference/Identities.md b/docs/http/identity.md similarity index 100% rename from docs/HTTP API Reference/Identities.md rename to docs/http/identity.md diff --git a/docs/HTTP API Reference/index.md b/docs/http/index.md similarity index 100% rename from docs/HTTP API Reference/index.md rename to docs/http/index.md diff --git a/docs/Overview/index.md b/docs/index.md similarity index 100% rename from docs/Overview/index.md rename to docs/index.md diff --git a/docs/Server Module Languages/C#/ModuleReference.md b/docs/modules/c-sharp/index.md similarity index 100% rename from docs/Server Module Languages/C#/ModuleReference.md rename to docs/modules/c-sharp/index.md diff --git a/docs/Server Module Languages/C#/index.md b/docs/modules/c-sharp/quickstart.md similarity index 100% rename from docs/Server Module Languages/C#/index.md rename to docs/modules/c-sharp/quickstart.md diff --git a/docs/Server Module Languages/index.md b/docs/modules/index.md similarity index 100% rename from docs/Server Module Languages/index.md rename to docs/modules/index.md diff --git a/docs/Server Module Languages/Rust/ModuleReference.md b/docs/modules/rust/index.md similarity index 100% rename from docs/Server Module Languages/Rust/ModuleReference.md rename to docs/modules/rust/index.md diff --git a/docs/Server Module Languages/Rust/index.md b/docs/modules/rust/quickstart.md similarity index 100% rename from docs/Server Module Languages/Rust/index.md rename to docs/modules/rust/quickstart.md diff --git a/docs/nav.js b/docs/nav.js index b62e9b7f..cb8d22f1 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -9,44 +9,44 @@ function section(title) { const nav = { items: [ section("Intro"), - page("Overview", "index", "Overview/index.md"), - page("Getting Started", "getting-started", "Getting Started/index.md"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), section("Server Module Languages"), - page("Overview", "modules", "Server Module Languages/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), - page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), - page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), section("Client SDK Languages"), - page("Overview", "sdks", "Client SDK Languages/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), - page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), - page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), - page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), - page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), - page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), section("HTTP API"), - page("HTTP", "http", "HTTP API Reference/index.md"), - page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), - page("`/database`", "http/database", "HTTP API Reference/Databases.md"), - page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "WebSocket API Reference/index.md"), + page("WebSocket", "ws", "ws/index.md"), section("Data Format"), - page("SATN", "satn", "SATN Reference/index.md"), - page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), section("SQL"), - page("SQL Reference", "sql", "SQL Reference/index.md"), + page("SQL Reference", "sql", "sql/index.md"), ], }; exports.default = nav; diff --git a/docs/SATN Reference/index.md b/docs/satn.md similarity index 100% rename from docs/SATN Reference/index.md rename to docs/satn.md diff --git a/docs/Client SDK Languages/C#/SDK Reference.md b/docs/sdks/c-sharp/index.md similarity index 100% rename from docs/Client SDK Languages/C#/SDK Reference.md rename to docs/sdks/c-sharp/index.md diff --git a/docs/Client SDK Languages/C#/index.md b/docs/sdks/c-sharp/quickstart.md similarity index 100% rename from docs/Client SDK Languages/C#/index.md rename to docs/sdks/c-sharp/quickstart.md diff --git a/docs/Client SDK Languages/index.md b/docs/sdks/index.md similarity index 100% rename from docs/Client SDK Languages/index.md rename to docs/sdks/index.md diff --git a/docs/Client SDK Languages/Python/SDK Reference.md b/docs/sdks/python/index.md similarity index 100% rename from docs/Client SDK Languages/Python/SDK Reference.md rename to docs/sdks/python/index.md diff --git a/docs/Client SDK Languages/Python/index.md b/docs/sdks/python/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Python/index.md rename to docs/sdks/python/quickstart.md diff --git a/docs/Client SDK Languages/Rust/SDK Reference.md b/docs/sdks/rust/index.md similarity index 100% rename from docs/Client SDK Languages/Rust/SDK Reference.md rename to docs/sdks/rust/index.md diff --git a/docs/Client SDK Languages/Rust/index.md b/docs/sdks/rust/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Rust/index.md rename to docs/sdks/rust/quickstart.md diff --git a/docs/Client SDK Languages/Typescript/SDK Reference.md b/docs/sdks/typescript/index.md similarity index 100% rename from docs/Client SDK Languages/Typescript/SDK Reference.md rename to docs/sdks/typescript/index.md diff --git a/docs/Client SDK Languages/Typescript/index.md b/docs/sdks/typescript/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Typescript/index.md rename to docs/sdks/typescript/quickstart.md diff --git a/docs/SQL Reference/index.md b/docs/sql/index.md similarity index 100% rename from docs/SQL Reference/index.md rename to docs/sql/index.md diff --git a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md b/docs/unity/part-1.md similarity index 100% rename from docs/Unity Tutorial/Part 1 - Basic Multiplayer.md rename to docs/unity/part-1.md diff --git a/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md b/docs/unity/part-2.md similarity index 100% rename from docs/Unity Tutorial/Part 2 - Resources And Scheduling.md rename to docs/unity/part-2.md diff --git a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md b/docs/unity/part-3.md similarity index 100% rename from docs/Unity Tutorial/Part 3 - BitCraft Mini.md rename to docs/unity/part-3.md diff --git a/docs/Module ABI Reference/index.md b/docs/webassembly-abi/index.md similarity index 100% rename from docs/Module ABI Reference/index.md rename to docs/webassembly-abi/index.md diff --git a/docs/WebSocket API Reference/index.md b/docs/ws/index.md similarity index 100% rename from docs/WebSocket API Reference/index.md rename to docs/ws/index.md diff --git a/nav.ts b/nav.ts index b9a64ee0..6d5a304b 100644 --- a/nav.ts +++ b/nav.ts @@ -25,53 +25,53 @@ function section(title: string): NavSection { const nav: Nav = { items: [ section("Intro"), - page("Overview", "index", "Overview/index.md"), - page("Getting Started", "getting-started", "Getting Started/index.md"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), section("Server Module Languages"), - page("Overview", "modules", "Server Module Languages/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), - page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), - page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), section("Client SDK Languages"), - page("Overview", "sdks", "Client SDK Languages/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), - page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), - page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), - page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), - page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), - page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), section("HTTP API"), - page("HTTP", "http", "HTTP API Reference/index.md"), - page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), - page("`/database`", "http/database", "HTTP API Reference/Databases.md"), - page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "WebSocket API Reference/index.md"), + page("WebSocket", "ws", "ws/index.md"), section("Data Format"), - page("SATN", "satn", "SATN Reference/index.md"), - page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), section("SQL"), - page("SQL Reference", "sql", "SQL Reference/index.md"), + page("SQL Reference", "sql", "sql/index.md"), ], }; From 1fb1e0fba655e08914c5f3d4243ac840424ba640 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 28 Nov 2023 20:03:42 -0800 Subject: [PATCH 24/80] Revert "Reorganized the doc paths to match slugs and removed _category.json files" --- .../C#/SDK Reference.md} | 0 docs/Client SDK Languages/C#/_category.json | 5 ++ .../C#/index.md} | 0 .../Python/SDK Reference.md} | 0 .../Python/_category.json | 5 ++ .../Python/index.md} | 0 .../Rust/SDK Reference.md} | 0 docs/Client SDK Languages/Rust/_category.json | 5 ++ .../Rust/index.md} | 0 .../Typescript/SDK Reference.md} | 0 .../Typescript/_category.json | 5 ++ .../Typescript/index.md} | 0 docs/Client SDK Languages/_category.json | 1 + docs/{sdks => Client SDK Languages}/index.md | 0 docs/Cloud Testnet/_category.json | 1 + .../testnet.md => Cloud Testnet/index.md} | 0 docs/Getting Started/_category.json | 1 + .../index.md} | 0 .../Databases.md} | 0 .../Energy.md} | 0 .../Identities.md} | 0 docs/HTTP API Reference/_category.json | 1 + docs/{http => HTTP API Reference}/index.md | 0 docs/Module ABI Reference/_category.json | 1 + .../index.md | 0 docs/Overview/_category.json | 1 + docs/{ => Overview}/index.md | 0 .../Binary Format.md} | 0 docs/SATN Reference/_category.json | 1 + docs/{satn.md => SATN Reference/index.md} | 0 docs/SQL Reference/_category.json | 1 + docs/{sql => SQL Reference}/index.md | 0 .../C#/ModuleReference.md} | 0 .../Server Module Languages/C#/_category.json | 6 ++ .../C#/index.md} | 0 .../Rust/ModuleReference.md} | 0 .../Rust/_category.json | 5 ++ .../Rust/index.md} | 0 docs/Server Module Languages/_category.json | 1 + .../index.md | 0 .../Part 1 - Basic Multiplayer.md} | 0 .../Part 2 - Resources And Scheduling.md} | 0 .../Part 3 - BitCraft Mini.md} | 0 docs/Unity Tutorial/_category.json | 5 ++ docs/WebSocket API Reference/_category.json | 1 + docs/{ws => WebSocket API Reference}/index.md | 0 docs/nav.js | 58 +++++++++---------- nav.ts | 58 +++++++++---------- 48 files changed, 104 insertions(+), 58 deletions(-) rename docs/{sdks/c-sharp/index.md => Client SDK Languages/C#/SDK Reference.md} (100%) create mode 100644 docs/Client SDK Languages/C#/_category.json rename docs/{sdks/c-sharp/quickstart.md => Client SDK Languages/C#/index.md} (100%) rename docs/{sdks/python/index.md => Client SDK Languages/Python/SDK Reference.md} (100%) create mode 100644 docs/Client SDK Languages/Python/_category.json rename docs/{sdks/python/quickstart.md => Client SDK Languages/Python/index.md} (100%) rename docs/{sdks/rust/index.md => Client SDK Languages/Rust/SDK Reference.md} (100%) create mode 100644 docs/Client SDK Languages/Rust/_category.json rename docs/{sdks/rust/quickstart.md => Client SDK Languages/Rust/index.md} (100%) rename docs/{sdks/typescript/index.md => Client SDK Languages/Typescript/SDK Reference.md} (100%) create mode 100644 docs/Client SDK Languages/Typescript/_category.json rename docs/{sdks/typescript/quickstart.md => Client SDK Languages/Typescript/index.md} (100%) create mode 100644 docs/Client SDK Languages/_category.json rename docs/{sdks => Client SDK Languages}/index.md (100%) create mode 100644 docs/Cloud Testnet/_category.json rename docs/{deploying/testnet.md => Cloud Testnet/index.md} (100%) create mode 100644 docs/Getting Started/_category.json rename docs/{getting-started.md => Getting Started/index.md} (100%) rename docs/{http/database.md => HTTP API Reference/Databases.md} (100%) rename docs/{http/energy.md => HTTP API Reference/Energy.md} (100%) rename docs/{http/identity.md => HTTP API Reference/Identities.md} (100%) create mode 100644 docs/HTTP API Reference/_category.json rename docs/{http => HTTP API Reference}/index.md (100%) create mode 100644 docs/Module ABI Reference/_category.json rename docs/{webassembly-abi => Module ABI Reference}/index.md (100%) create mode 100644 docs/Overview/_category.json rename docs/{ => Overview}/index.md (100%) rename docs/{bsatn.md => SATN Reference/Binary Format.md} (100%) create mode 100644 docs/SATN Reference/_category.json rename docs/{satn.md => SATN Reference/index.md} (100%) create mode 100644 docs/SQL Reference/_category.json rename docs/{sql => SQL Reference}/index.md (100%) rename docs/{modules/c-sharp/index.md => Server Module Languages/C#/ModuleReference.md} (100%) create mode 100644 docs/Server Module Languages/C#/_category.json rename docs/{modules/c-sharp/quickstart.md => Server Module Languages/C#/index.md} (100%) rename docs/{modules/rust/index.md => Server Module Languages/Rust/ModuleReference.md} (100%) create mode 100644 docs/Server Module Languages/Rust/_category.json rename docs/{modules/rust/quickstart.md => Server Module Languages/Rust/index.md} (100%) create mode 100644 docs/Server Module Languages/_category.json rename docs/{modules => Server Module Languages}/index.md (100%) rename docs/{unity/part-1.md => Unity Tutorial/Part 1 - Basic Multiplayer.md} (100%) rename docs/{unity/part-2.md => Unity Tutorial/Part 2 - Resources And Scheduling.md} (100%) rename docs/{unity/part-3.md => Unity Tutorial/Part 3 - BitCraft Mini.md} (100%) create mode 100644 docs/Unity Tutorial/_category.json create mode 100644 docs/WebSocket API Reference/_category.json rename docs/{ws => WebSocket API Reference}/index.md (100%) diff --git a/docs/sdks/c-sharp/index.md b/docs/Client SDK Languages/C#/SDK Reference.md similarity index 100% rename from docs/sdks/c-sharp/index.md rename to docs/Client SDK Languages/C#/SDK Reference.md diff --git a/docs/Client SDK Languages/C#/_category.json b/docs/Client SDK Languages/C#/_category.json new file mode 100644 index 00000000..60238f8e --- /dev/null +++ b/docs/Client SDK Languages/C#/_category.json @@ -0,0 +1,5 @@ +{ + "title": "C#", + "disabled": false, + "index": "index.md" +} \ No newline at end of file diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/Client SDK Languages/C#/index.md similarity index 100% rename from docs/sdks/c-sharp/quickstart.md rename to docs/Client SDK Languages/C#/index.md diff --git a/docs/sdks/python/index.md b/docs/Client SDK Languages/Python/SDK Reference.md similarity index 100% rename from docs/sdks/python/index.md rename to docs/Client SDK Languages/Python/SDK Reference.md diff --git a/docs/Client SDK Languages/Python/_category.json b/docs/Client SDK Languages/Python/_category.json new file mode 100644 index 00000000..4e08cfa1 --- /dev/null +++ b/docs/Client SDK Languages/Python/_category.json @@ -0,0 +1,5 @@ +{ + "title": "Python", + "disabled": false, + "index": "index.md" +} \ No newline at end of file diff --git a/docs/sdks/python/quickstart.md b/docs/Client SDK Languages/Python/index.md similarity index 100% rename from docs/sdks/python/quickstart.md rename to docs/Client SDK Languages/Python/index.md diff --git a/docs/sdks/rust/index.md b/docs/Client SDK Languages/Rust/SDK Reference.md similarity index 100% rename from docs/sdks/rust/index.md rename to docs/Client SDK Languages/Rust/SDK Reference.md diff --git a/docs/Client SDK Languages/Rust/_category.json b/docs/Client SDK Languages/Rust/_category.json new file mode 100644 index 00000000..6280366c --- /dev/null +++ b/docs/Client SDK Languages/Rust/_category.json @@ -0,0 +1,5 @@ +{ + "title": "Rust", + "disabled": false, + "index": "index.md" +} \ No newline at end of file diff --git a/docs/sdks/rust/quickstart.md b/docs/Client SDK Languages/Rust/index.md similarity index 100% rename from docs/sdks/rust/quickstart.md rename to docs/Client SDK Languages/Rust/index.md diff --git a/docs/sdks/typescript/index.md b/docs/Client SDK Languages/Typescript/SDK Reference.md similarity index 100% rename from docs/sdks/typescript/index.md rename to docs/Client SDK Languages/Typescript/SDK Reference.md diff --git a/docs/Client SDK Languages/Typescript/_category.json b/docs/Client SDK Languages/Typescript/_category.json new file mode 100644 index 00000000..590d44a2 --- /dev/null +++ b/docs/Client SDK Languages/Typescript/_category.json @@ -0,0 +1,5 @@ +{ + "title": "Typescript", + "disabled": false, + "index": "index.md" +} \ No newline at end of file diff --git a/docs/sdks/typescript/quickstart.md b/docs/Client SDK Languages/Typescript/index.md similarity index 100% rename from docs/sdks/typescript/quickstart.md rename to docs/Client SDK Languages/Typescript/index.md diff --git a/docs/Client SDK Languages/_category.json b/docs/Client SDK Languages/_category.json new file mode 100644 index 00000000..530c17aa --- /dev/null +++ b/docs/Client SDK Languages/_category.json @@ -0,0 +1 @@ +{"title":"Client SDK Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/sdks/index.md b/docs/Client SDK Languages/index.md similarity index 100% rename from docs/sdks/index.md rename to docs/Client SDK Languages/index.md diff --git a/docs/Cloud Testnet/_category.json b/docs/Cloud Testnet/_category.json new file mode 100644 index 00000000..e6fa11b9 --- /dev/null +++ b/docs/Cloud Testnet/_category.json @@ -0,0 +1 @@ +{"title":"Cloud Testnet","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/deploying/testnet.md b/docs/Cloud Testnet/index.md similarity index 100% rename from docs/deploying/testnet.md rename to docs/Cloud Testnet/index.md diff --git a/docs/Getting Started/_category.json b/docs/Getting Started/_category.json new file mode 100644 index 00000000..a68dc36c --- /dev/null +++ b/docs/Getting Started/_category.json @@ -0,0 +1 @@ +{"title":"Getting Started","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/getting-started.md b/docs/Getting Started/index.md similarity index 100% rename from docs/getting-started.md rename to docs/Getting Started/index.md diff --git a/docs/http/database.md b/docs/HTTP API Reference/Databases.md similarity index 100% rename from docs/http/database.md rename to docs/HTTP API Reference/Databases.md diff --git a/docs/http/energy.md b/docs/HTTP API Reference/Energy.md similarity index 100% rename from docs/http/energy.md rename to docs/HTTP API Reference/Energy.md diff --git a/docs/http/identity.md b/docs/HTTP API Reference/Identities.md similarity index 100% rename from docs/http/identity.md rename to docs/HTTP API Reference/Identities.md diff --git a/docs/HTTP API Reference/_category.json b/docs/HTTP API Reference/_category.json new file mode 100644 index 00000000..c8ad821b --- /dev/null +++ b/docs/HTTP API Reference/_category.json @@ -0,0 +1 @@ +{"title":"HTTP API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/http/index.md b/docs/HTTP API Reference/index.md similarity index 100% rename from docs/http/index.md rename to docs/HTTP API Reference/index.md diff --git a/docs/Module ABI Reference/_category.json b/docs/Module ABI Reference/_category.json new file mode 100644 index 00000000..7583598d --- /dev/null +++ b/docs/Module ABI Reference/_category.json @@ -0,0 +1 @@ +{"title":"Module ABI Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/webassembly-abi/index.md b/docs/Module ABI Reference/index.md similarity index 100% rename from docs/webassembly-abi/index.md rename to docs/Module ABI Reference/index.md diff --git a/docs/Overview/_category.json b/docs/Overview/_category.json new file mode 100644 index 00000000..35164a50 --- /dev/null +++ b/docs/Overview/_category.json @@ -0,0 +1 @@ +{"title":"Overview","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/index.md b/docs/Overview/index.md similarity index 100% rename from docs/index.md rename to docs/Overview/index.md diff --git a/docs/bsatn.md b/docs/SATN Reference/Binary Format.md similarity index 100% rename from docs/bsatn.md rename to docs/SATN Reference/Binary Format.md diff --git a/docs/SATN Reference/_category.json b/docs/SATN Reference/_category.json new file mode 100644 index 00000000..e26b2f05 --- /dev/null +++ b/docs/SATN Reference/_category.json @@ -0,0 +1 @@ +{"title":"SATN Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/satn.md b/docs/SATN Reference/index.md similarity index 100% rename from docs/satn.md rename to docs/SATN Reference/index.md diff --git a/docs/SQL Reference/_category.json b/docs/SQL Reference/_category.json new file mode 100644 index 00000000..73d7df23 --- /dev/null +++ b/docs/SQL Reference/_category.json @@ -0,0 +1 @@ +{"title":"SQL Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/sql/index.md b/docs/SQL Reference/index.md similarity index 100% rename from docs/sql/index.md rename to docs/SQL Reference/index.md diff --git a/docs/modules/c-sharp/index.md b/docs/Server Module Languages/C#/ModuleReference.md similarity index 100% rename from docs/modules/c-sharp/index.md rename to docs/Server Module Languages/C#/ModuleReference.md diff --git a/docs/Server Module Languages/C#/_category.json b/docs/Server Module Languages/C#/_category.json new file mode 100644 index 00000000..71ae9015 --- /dev/null +++ b/docs/Server Module Languages/C#/_category.json @@ -0,0 +1,6 @@ +{ + "title": "C#", + "disabled": false, + "index": "index.md", + "tag": "Expiremental" +} \ No newline at end of file diff --git a/docs/modules/c-sharp/quickstart.md b/docs/Server Module Languages/C#/index.md similarity index 100% rename from docs/modules/c-sharp/quickstart.md rename to docs/Server Module Languages/C#/index.md diff --git a/docs/modules/rust/index.md b/docs/Server Module Languages/Rust/ModuleReference.md similarity index 100% rename from docs/modules/rust/index.md rename to docs/Server Module Languages/Rust/ModuleReference.md diff --git a/docs/Server Module Languages/Rust/_category.json b/docs/Server Module Languages/Rust/_category.json new file mode 100644 index 00000000..6280366c --- /dev/null +++ b/docs/Server Module Languages/Rust/_category.json @@ -0,0 +1,5 @@ +{ + "title": "Rust", + "disabled": false, + "index": "index.md" +} \ No newline at end of file diff --git a/docs/modules/rust/quickstart.md b/docs/Server Module Languages/Rust/index.md similarity index 100% rename from docs/modules/rust/quickstart.md rename to docs/Server Module Languages/Rust/index.md diff --git a/docs/Server Module Languages/_category.json b/docs/Server Module Languages/_category.json new file mode 100644 index 00000000..3bfa0e87 --- /dev/null +++ b/docs/Server Module Languages/_category.json @@ -0,0 +1 @@ +{"title":"Server Module Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/modules/index.md b/docs/Server Module Languages/index.md similarity index 100% rename from docs/modules/index.md rename to docs/Server Module Languages/index.md diff --git a/docs/unity/part-1.md b/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md similarity index 100% rename from docs/unity/part-1.md rename to docs/Unity Tutorial/Part 1 - Basic Multiplayer.md diff --git a/docs/unity/part-2.md b/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md similarity index 100% rename from docs/unity/part-2.md rename to docs/Unity Tutorial/Part 2 - Resources And Scheduling.md diff --git a/docs/unity/part-3.md b/docs/Unity Tutorial/Part 3 - BitCraft Mini.md similarity index 100% rename from docs/unity/part-3.md rename to docs/Unity Tutorial/Part 3 - BitCraft Mini.md diff --git a/docs/Unity Tutorial/_category.json b/docs/Unity Tutorial/_category.json new file mode 100644 index 00000000..a3c837ad --- /dev/null +++ b/docs/Unity Tutorial/_category.json @@ -0,0 +1,5 @@ +{ + "title": "Unity Tutorial", + "disabled": false, + "index": "Part 1 - Basic Multiplayer.md" +} \ No newline at end of file diff --git a/docs/WebSocket API Reference/_category.json b/docs/WebSocket API Reference/_category.json new file mode 100644 index 00000000..d2797306 --- /dev/null +++ b/docs/WebSocket API Reference/_category.json @@ -0,0 +1 @@ +{"title":"WebSocket API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/ws/index.md b/docs/WebSocket API Reference/index.md similarity index 100% rename from docs/ws/index.md rename to docs/WebSocket API Reference/index.md diff --git a/docs/nav.js b/docs/nav.js index cb8d22f1..b62e9b7f 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -9,44 +9,44 @@ function section(title) { const nav = { items: [ section("Intro"), - page("Overview", "index", "index.md"), - page("Getting Started", "getting-started", "getting-started.md"), + page("Overview", "index", "Overview/index.md"), + page("Getting Started", "getting-started", "Getting Started/index.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), + page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + page("Overview", "modules", "Server Module Languages/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), + page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), + page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + page("Overview", "sdks", "Client SDK Languages/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), + page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), + page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), + page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), + page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), + page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), + page("HTTP", "http", "HTTP API Reference/index.md"), + page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), + page("`/database`", "http/database", "HTTP API Reference/Databases.md"), + page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), + page("WebSocket", "ws", "WebSocket API Reference/index.md"), section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), + page("SATN", "satn", "SATN Reference/index.md"), + page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), + page("SQL Reference", "sql", "SQL Reference/index.md"), ], }; exports.default = nav; diff --git a/nav.ts b/nav.ts index 6d5a304b..b9a64ee0 100644 --- a/nav.ts +++ b/nav.ts @@ -25,53 +25,53 @@ function section(title: string): NavSection { const nav: Nav = { items: [ section("Intro"), - page("Overview", "index", "index.md"), - page("Getting Started", "getting-started", "getting-started.md"), + page("Overview", "index", "Overview/index.md"), + page("Getting Started", "getting-started", "Getting Started/index.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), + page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + page("Overview", "modules", "Server Module Languages/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), + page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), + page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + page("Overview", "sdks", "Client SDK Languages/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), + page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), + page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), + page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), + page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), + page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), + page("HTTP", "http", "HTTP API Reference/index.md"), + page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), + page("`/database`", "http/database", "HTTP API Reference/Databases.md"), + page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), + page("WebSocket", "ws", "WebSocket API Reference/index.md"), section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), + page("SATN", "satn", "SATN Reference/index.md"), + page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), + page("SQL Reference", "sql", "SQL Reference/index.md"), ], }; From 5e79903267f23362d3b31afe3bb0036b2c62065b Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Thu, 7 Dec 2023 18:30:11 -0800 Subject: [PATCH 25/80] Update index.md --- docs/Server Module Languages/Rust/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Server Module Languages/Rust/index.md b/docs/Server Module Languages/Rust/index.md index 6e0f1747..e0ff0f5f 100644 --- a/docs/Server Module Languages/Rust/index.md +++ b/docs/Server Module Languages/Rust/index.md @@ -234,7 +234,7 @@ spacetime publish --project-path server You can use the CLI (command line interface) to run reducers. The arguments to the reducer are passed in JSON format. ```bash -spacetime call send_message '["Hello, World!"]' +spacetime call send_message 'Hello, World!' ``` Once we've called our `send_message` reducer, we can check to make sure it ran by running the `logs` command. From 8b32302e6976d3dbac9082eb3b6f0ca7cdb59556 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Thu, 7 Dec 2023 18:34:55 -0800 Subject: [PATCH 26/80] Update index.md --- docs/Getting Started/index.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/Getting Started/index.md b/docs/Getting Started/index.md index 54337d08..5a0c6041 100644 --- a/docs/Getting Started/index.md +++ b/docs/Getting Started/index.md @@ -13,12 +13,6 @@ The server listens on port `3000` by default. You can change this by using the ` SSL is not supported in standalone mode. -To set up your CLI to connect to the server, you can run the `spacetime server` command. - -```bash -spacetime server set "http://localhost:3000" -``` - ## What's Next? You are ready to start developing SpacetimeDB modules. We have a quickstart guide for each supported server-side language: From bc9a334c16efe6ae56aa6e1ecf13675908e64ff7 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 15 Dec 2023 16:22:38 -0800 Subject: [PATCH 27/80] Update index.md --- docs/Client SDK Languages/Typescript/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Client SDK Languages/Typescript/index.md b/docs/Client SDK Languages/Typescript/index.md index ab7cfe89..0ec6b0eb 100644 --- a/docs/Client SDK Languages/Typescript/index.md +++ b/docs/Client SDK Languages/Typescript/index.md @@ -152,7 +152,7 @@ In your `quickstart-chat` directory, run: ```bash mkdir -p client/src/module_bindings -spacetime generate --lang typescript --out-dir client/src/module_bindings --project_path server +spacetime generate --lang typescript --out-dir client/src/module_bindings --project-path server ``` Take a look inside `client/src/module_bindings`. The CLI should have generated four files: From ab103824c73b79dc8f1ff0ef95d8a45b4084bf91 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 15 Dec 2023 16:24:16 -0800 Subject: [PATCH 28/80] Update index.md --- docs/Cloud Testnet/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Cloud Testnet/index.md b/docs/Cloud Testnet/index.md index abb90fb8..ce648043 100644 --- a/docs/Cloud Testnet/index.md +++ b/docs/Cloud Testnet/index.md @@ -10,7 +10,7 @@ Currently only the `testnet` is available for SpacetimeDB cloud which is subject 1. Configure your CLI to use the SpacetimeDB Cloud. To do this, run the `spacetime server` command: ```bash -spacetime server set "https://testnet.spacetimedb.com" +spacetime server add --default "https://testnet.spacetimedb.com" testnet ``` ## Connecting your Identity to the Web Dashboard From 059d87b15d75ec5782c83018adbfc9edff43f017 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 15 Dec 2023 23:19:22 -0800 Subject: [PATCH 29/80] Revert "Revert "Reorganized the doc paths to match slugs and removed _category.json files"" (#23) This reverts commit 1fb1e0fba655e08914c5f3d4243ac840424ba640. --- docs/Client SDK Languages/C#/_category.json | 5 -- .../Python/_category.json | 5 -- docs/Client SDK Languages/Rust/_category.json | 5 -- .../Typescript/_category.json | 5 -- docs/Client SDK Languages/_category.json | 1 - docs/Cloud Testnet/_category.json | 1 - docs/Getting Started/_category.json | 1 - docs/HTTP API Reference/_category.json | 1 - docs/Module ABI Reference/_category.json | 1 - docs/Overview/_category.json | 1 - docs/SATN Reference/_category.json | 1 - docs/SQL Reference/_category.json | 1 - .../Server Module Languages/C#/_category.json | 6 -- .../Rust/_category.json | 5 -- docs/Server Module Languages/_category.json | 1 - docs/Unity Tutorial/_category.json | 5 -- docs/WebSocket API Reference/_category.json | 1 - .../Binary Format.md => bsatn.md} | 0 .../index.md => deploying/testnet.md} | 0 .../index.md => getting-started.md} | 0 .../Databases.md => http/database.md} | 0 .../Energy.md => http/energy.md} | 0 .../Identities.md => http/identity.md} | 0 docs/{HTTP API Reference => http}/index.md | 0 docs/{Overview => }/index.md | 0 .../c-sharp/index.md} | 0 .../c-sharp/quickstart.md} | 0 .../index.md | 0 .../rust/index.md} | 0 .../index.md => modules/rust/quickstart.md} | 0 docs/nav.js | 58 +++++++++---------- docs/{SATN Reference/index.md => satn.md} | 0 .../c-sharp/index.md} | 0 .../index.md => sdks/c-sharp/quickstart.md} | 0 docs/{Client SDK Languages => sdks}/index.md | 0 .../SDK Reference.md => sdks/python/index.md} | 0 .../index.md => sdks/python/quickstart.md} | 0 .../SDK Reference.md => sdks/rust/index.md} | 0 .../Rust/index.md => sdks/rust/quickstart.md} | 0 .../typescript/index.md} | 0 .../typescript/quickstart.md} | 0 docs/{SQL Reference => sql}/index.md | 0 .../part-1.md} | 0 .../part-2.md} | 0 .../part-3.md} | 0 .../index.md | 0 docs/{WebSocket API Reference => ws}/index.md | 0 nav.ts | 58 +++++++++---------- 48 files changed, 58 insertions(+), 104 deletions(-) delete mode 100644 docs/Client SDK Languages/C#/_category.json delete mode 100644 docs/Client SDK Languages/Python/_category.json delete mode 100644 docs/Client SDK Languages/Rust/_category.json delete mode 100644 docs/Client SDK Languages/Typescript/_category.json delete mode 100644 docs/Client SDK Languages/_category.json delete mode 100644 docs/Cloud Testnet/_category.json delete mode 100644 docs/Getting Started/_category.json delete mode 100644 docs/HTTP API Reference/_category.json delete mode 100644 docs/Module ABI Reference/_category.json delete mode 100644 docs/Overview/_category.json delete mode 100644 docs/SATN Reference/_category.json delete mode 100644 docs/SQL Reference/_category.json delete mode 100644 docs/Server Module Languages/C#/_category.json delete mode 100644 docs/Server Module Languages/Rust/_category.json delete mode 100644 docs/Server Module Languages/_category.json delete mode 100644 docs/Unity Tutorial/_category.json delete mode 100644 docs/WebSocket API Reference/_category.json rename docs/{SATN Reference/Binary Format.md => bsatn.md} (100%) rename docs/{Cloud Testnet/index.md => deploying/testnet.md} (100%) rename docs/{Getting Started/index.md => getting-started.md} (100%) rename docs/{HTTP API Reference/Databases.md => http/database.md} (100%) rename docs/{HTTP API Reference/Energy.md => http/energy.md} (100%) rename docs/{HTTP API Reference/Identities.md => http/identity.md} (100%) rename docs/{HTTP API Reference => http}/index.md (100%) rename docs/{Overview => }/index.md (100%) rename docs/{Server Module Languages/C#/ModuleReference.md => modules/c-sharp/index.md} (100%) rename docs/{Server Module Languages/C#/index.md => modules/c-sharp/quickstart.md} (100%) rename docs/{Server Module Languages => modules}/index.md (100%) rename docs/{Server Module Languages/Rust/ModuleReference.md => modules/rust/index.md} (100%) rename docs/{Server Module Languages/Rust/index.md => modules/rust/quickstart.md} (100%) rename docs/{SATN Reference/index.md => satn.md} (100%) rename docs/{Client SDK Languages/C#/SDK Reference.md => sdks/c-sharp/index.md} (100%) rename docs/{Client SDK Languages/C#/index.md => sdks/c-sharp/quickstart.md} (100%) rename docs/{Client SDK Languages => sdks}/index.md (100%) rename docs/{Client SDK Languages/Python/SDK Reference.md => sdks/python/index.md} (100%) rename docs/{Client SDK Languages/Python/index.md => sdks/python/quickstart.md} (100%) rename docs/{Client SDK Languages/Rust/SDK Reference.md => sdks/rust/index.md} (100%) rename docs/{Client SDK Languages/Rust/index.md => sdks/rust/quickstart.md} (100%) rename docs/{Client SDK Languages/Typescript/SDK Reference.md => sdks/typescript/index.md} (100%) rename docs/{Client SDK Languages/Typescript/index.md => sdks/typescript/quickstart.md} (100%) rename docs/{SQL Reference => sql}/index.md (100%) rename docs/{Unity Tutorial/Part 1 - Basic Multiplayer.md => unity/part-1.md} (100%) rename docs/{Unity Tutorial/Part 2 - Resources And Scheduling.md => unity/part-2.md} (100%) rename docs/{Unity Tutorial/Part 3 - BitCraft Mini.md => unity/part-3.md} (100%) rename docs/{Module ABI Reference => webassembly-abi}/index.md (100%) rename docs/{WebSocket API Reference => ws}/index.md (100%) diff --git a/docs/Client SDK Languages/C#/_category.json b/docs/Client SDK Languages/C#/_category.json deleted file mode 100644 index 60238f8e..00000000 --- a/docs/Client SDK Languages/C#/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "C#", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Python/_category.json b/docs/Client SDK Languages/Python/_category.json deleted file mode 100644 index 4e08cfa1..00000000 --- a/docs/Client SDK Languages/Python/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Python", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Rust/_category.json b/docs/Client SDK Languages/Rust/_category.json deleted file mode 100644 index 6280366c..00000000 --- a/docs/Client SDK Languages/Rust/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Rust", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/Typescript/_category.json b/docs/Client SDK Languages/Typescript/_category.json deleted file mode 100644 index 590d44a2..00000000 --- a/docs/Client SDK Languages/Typescript/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Typescript", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Client SDK Languages/_category.json b/docs/Client SDK Languages/_category.json deleted file mode 100644 index 530c17aa..00000000 --- a/docs/Client SDK Languages/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Client SDK Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Cloud Testnet/_category.json b/docs/Cloud Testnet/_category.json deleted file mode 100644 index e6fa11b9..00000000 --- a/docs/Cloud Testnet/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Cloud Testnet","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Getting Started/_category.json b/docs/Getting Started/_category.json deleted file mode 100644 index a68dc36c..00000000 --- a/docs/Getting Started/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Getting Started","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/HTTP API Reference/_category.json b/docs/HTTP API Reference/_category.json deleted file mode 100644 index c8ad821b..00000000 --- a/docs/HTTP API Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"HTTP API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Module ABI Reference/_category.json b/docs/Module ABI Reference/_category.json deleted file mode 100644 index 7583598d..00000000 --- a/docs/Module ABI Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Module ABI Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Overview/_category.json b/docs/Overview/_category.json deleted file mode 100644 index 35164a50..00000000 --- a/docs/Overview/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Overview","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SATN Reference/_category.json b/docs/SATN Reference/_category.json deleted file mode 100644 index e26b2f05..00000000 --- a/docs/SATN Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"SATN Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SQL Reference/_category.json b/docs/SQL Reference/_category.json deleted file mode 100644 index 73d7df23..00000000 --- a/docs/SQL Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"SQL Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Server Module Languages/C#/_category.json b/docs/Server Module Languages/C#/_category.json deleted file mode 100644 index 71ae9015..00000000 --- a/docs/Server Module Languages/C#/_category.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "C#", - "disabled": false, - "index": "index.md", - "tag": "Expiremental" -} \ No newline at end of file diff --git a/docs/Server Module Languages/Rust/_category.json b/docs/Server Module Languages/Rust/_category.json deleted file mode 100644 index 6280366c..00000000 --- a/docs/Server Module Languages/Rust/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Rust", - "disabled": false, - "index": "index.md" -} \ No newline at end of file diff --git a/docs/Server Module Languages/_category.json b/docs/Server Module Languages/_category.json deleted file mode 100644 index 3bfa0e87..00000000 --- a/docs/Server Module Languages/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"Server Module Languages","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/Unity Tutorial/_category.json b/docs/Unity Tutorial/_category.json deleted file mode 100644 index a3c837ad..00000000 --- a/docs/Unity Tutorial/_category.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Unity Tutorial", - "disabled": false, - "index": "Part 1 - Basic Multiplayer.md" -} \ No newline at end of file diff --git a/docs/WebSocket API Reference/_category.json b/docs/WebSocket API Reference/_category.json deleted file mode 100644 index d2797306..00000000 --- a/docs/WebSocket API Reference/_category.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"WebSocket API Reference","disabled":false,"index":"index.md"} \ No newline at end of file diff --git a/docs/SATN Reference/Binary Format.md b/docs/bsatn.md similarity index 100% rename from docs/SATN Reference/Binary Format.md rename to docs/bsatn.md diff --git a/docs/Cloud Testnet/index.md b/docs/deploying/testnet.md similarity index 100% rename from docs/Cloud Testnet/index.md rename to docs/deploying/testnet.md diff --git a/docs/Getting Started/index.md b/docs/getting-started.md similarity index 100% rename from docs/Getting Started/index.md rename to docs/getting-started.md diff --git a/docs/HTTP API Reference/Databases.md b/docs/http/database.md similarity index 100% rename from docs/HTTP API Reference/Databases.md rename to docs/http/database.md diff --git a/docs/HTTP API Reference/Energy.md b/docs/http/energy.md similarity index 100% rename from docs/HTTP API Reference/Energy.md rename to docs/http/energy.md diff --git a/docs/HTTP API Reference/Identities.md b/docs/http/identity.md similarity index 100% rename from docs/HTTP API Reference/Identities.md rename to docs/http/identity.md diff --git a/docs/HTTP API Reference/index.md b/docs/http/index.md similarity index 100% rename from docs/HTTP API Reference/index.md rename to docs/http/index.md diff --git a/docs/Overview/index.md b/docs/index.md similarity index 100% rename from docs/Overview/index.md rename to docs/index.md diff --git a/docs/Server Module Languages/C#/ModuleReference.md b/docs/modules/c-sharp/index.md similarity index 100% rename from docs/Server Module Languages/C#/ModuleReference.md rename to docs/modules/c-sharp/index.md diff --git a/docs/Server Module Languages/C#/index.md b/docs/modules/c-sharp/quickstart.md similarity index 100% rename from docs/Server Module Languages/C#/index.md rename to docs/modules/c-sharp/quickstart.md diff --git a/docs/Server Module Languages/index.md b/docs/modules/index.md similarity index 100% rename from docs/Server Module Languages/index.md rename to docs/modules/index.md diff --git a/docs/Server Module Languages/Rust/ModuleReference.md b/docs/modules/rust/index.md similarity index 100% rename from docs/Server Module Languages/Rust/ModuleReference.md rename to docs/modules/rust/index.md diff --git a/docs/Server Module Languages/Rust/index.md b/docs/modules/rust/quickstart.md similarity index 100% rename from docs/Server Module Languages/Rust/index.md rename to docs/modules/rust/quickstart.md diff --git a/docs/nav.js b/docs/nav.js index b62e9b7f..cb8d22f1 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -9,44 +9,44 @@ function section(title) { const nav = { items: [ section("Intro"), - page("Overview", "index", "Overview/index.md"), - page("Getting Started", "getting-started", "Getting Started/index.md"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), section("Server Module Languages"), - page("Overview", "modules", "Server Module Languages/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), - page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), - page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), section("Client SDK Languages"), - page("Overview", "sdks", "Client SDK Languages/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), - page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), - page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), - page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), - page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), - page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), section("HTTP API"), - page("HTTP", "http", "HTTP API Reference/index.md"), - page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), - page("`/database`", "http/database", "HTTP API Reference/Databases.md"), - page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "WebSocket API Reference/index.md"), + page("WebSocket", "ws", "ws/index.md"), section("Data Format"), - page("SATN", "satn", "SATN Reference/index.md"), - page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), section("SQL"), - page("SQL Reference", "sql", "SQL Reference/index.md"), + page("SQL Reference", "sql", "sql/index.md"), ], }; exports.default = nav; diff --git a/docs/SATN Reference/index.md b/docs/satn.md similarity index 100% rename from docs/SATN Reference/index.md rename to docs/satn.md diff --git a/docs/Client SDK Languages/C#/SDK Reference.md b/docs/sdks/c-sharp/index.md similarity index 100% rename from docs/Client SDK Languages/C#/SDK Reference.md rename to docs/sdks/c-sharp/index.md diff --git a/docs/Client SDK Languages/C#/index.md b/docs/sdks/c-sharp/quickstart.md similarity index 100% rename from docs/Client SDK Languages/C#/index.md rename to docs/sdks/c-sharp/quickstart.md diff --git a/docs/Client SDK Languages/index.md b/docs/sdks/index.md similarity index 100% rename from docs/Client SDK Languages/index.md rename to docs/sdks/index.md diff --git a/docs/Client SDK Languages/Python/SDK Reference.md b/docs/sdks/python/index.md similarity index 100% rename from docs/Client SDK Languages/Python/SDK Reference.md rename to docs/sdks/python/index.md diff --git a/docs/Client SDK Languages/Python/index.md b/docs/sdks/python/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Python/index.md rename to docs/sdks/python/quickstart.md diff --git a/docs/Client SDK Languages/Rust/SDK Reference.md b/docs/sdks/rust/index.md similarity index 100% rename from docs/Client SDK Languages/Rust/SDK Reference.md rename to docs/sdks/rust/index.md diff --git a/docs/Client SDK Languages/Rust/index.md b/docs/sdks/rust/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Rust/index.md rename to docs/sdks/rust/quickstart.md diff --git a/docs/Client SDK Languages/Typescript/SDK Reference.md b/docs/sdks/typescript/index.md similarity index 100% rename from docs/Client SDK Languages/Typescript/SDK Reference.md rename to docs/sdks/typescript/index.md diff --git a/docs/Client SDK Languages/Typescript/index.md b/docs/sdks/typescript/quickstart.md similarity index 100% rename from docs/Client SDK Languages/Typescript/index.md rename to docs/sdks/typescript/quickstart.md diff --git a/docs/SQL Reference/index.md b/docs/sql/index.md similarity index 100% rename from docs/SQL Reference/index.md rename to docs/sql/index.md diff --git a/docs/Unity Tutorial/Part 1 - Basic Multiplayer.md b/docs/unity/part-1.md similarity index 100% rename from docs/Unity Tutorial/Part 1 - Basic Multiplayer.md rename to docs/unity/part-1.md diff --git a/docs/Unity Tutorial/Part 2 - Resources And Scheduling.md b/docs/unity/part-2.md similarity index 100% rename from docs/Unity Tutorial/Part 2 - Resources And Scheduling.md rename to docs/unity/part-2.md diff --git a/docs/Unity Tutorial/Part 3 - BitCraft Mini.md b/docs/unity/part-3.md similarity index 100% rename from docs/Unity Tutorial/Part 3 - BitCraft Mini.md rename to docs/unity/part-3.md diff --git a/docs/Module ABI Reference/index.md b/docs/webassembly-abi/index.md similarity index 100% rename from docs/Module ABI Reference/index.md rename to docs/webassembly-abi/index.md diff --git a/docs/WebSocket API Reference/index.md b/docs/ws/index.md similarity index 100% rename from docs/WebSocket API Reference/index.md rename to docs/ws/index.md diff --git a/nav.ts b/nav.ts index b9a64ee0..6d5a304b 100644 --- a/nav.ts +++ b/nav.ts @@ -25,53 +25,53 @@ function section(title: string): NavSection { const nav: Nav = { items: [ section("Intro"), - page("Overview", "index", "Overview/index.md"), - page("Getting Started", "getting-started", "Getting Started/index.md"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), - page("Testnet", "deploying/testnet", "Cloud Testnet/index.md"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "Unity Tutorial/Part 1 - Basic Multiplayer.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "Unity Tutorial/Part 2 - Resources And Scheduling.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "Unity Tutorial/Part 3 - BitCraft Mini.md"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), section("Server Module Languages"), - page("Overview", "modules", "Server Module Languages/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "Server Module Languages/Rust/index.md"), - page("Rust Reference", "modules/rust", "Server Module Languages/Rust/ModuleReference.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "Server Module Languages/C#/index.md"), - page("C# Reference", "modules/c-sharp", "Server Module Languages/C#/ModuleReference.md"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), section("Client SDK Languages"), - page("Overview", "sdks", "Client SDK Languages/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "Client SDK Languages/Typescript/index.md"), - page("Typescript Reference", "sdks/typescript", "Client SDK Languages/Typescript/SDK Reference.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "Client SDK Languages/Rust/index.md"), - page("Rust Reference", "sdks/rust", "Client SDK Languages/Rust/SDK Reference.md"), - page("Python Quickstart", "sdks/python/quickstart", "Client SDK Languages/Python/index.md"), - page("Python Reference", "sdks/python", "Client SDK Languages/Python/SDK Reference.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "Client SDK Languages/C#/index.md"), - page("C# Reference", "sdks/c-sharp", "Client SDK Languages/C#/SDK Reference.md"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "Module ABI Reference/index.md"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), section("HTTP API"), - page("HTTP", "http", "HTTP API Reference/index.md"), - page("`/identity`", "http/identity", "HTTP API Reference/Identities.md"), - page("`/database`", "http/database", "HTTP API Reference/Databases.md"), - page("`/energy`", "http/energy", "HTTP API Reference/Energy.md"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), section("WebSocket API Reference"), - page("WebSocket", "ws", "WebSocket API Reference/index.md"), + page("WebSocket", "ws", "ws/index.md"), section("Data Format"), - page("SATN", "satn", "SATN Reference/index.md"), - page("BSATN", "bsatn", "SATN Reference/Binary Format.md"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), section("SQL"), - page("SQL Reference", "sql", "SQL Reference/index.md"), + page("SQL Reference", "sql", "sql/index.md"), ], }; From 4dd6a93a70395ef0c8efa86dcb40a888bd516a83 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 18 Dec 2023 20:07:19 +0100 Subject: [PATCH 30/80] Typescript SDK 0.8 changes (#21) * Empty push to trigger a webhook * Update TypeScript docs to 0.8 --- docs/sdks/typescript/index.md | 58 ++++++++++++++++++++++++++++-- docs/sdks/typescript/quickstart.md | 18 +++++----- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/docs/sdks/typescript/index.md b/docs/sdks/typescript/index.md index fb7d5be6..fd7c9e91 100644 --- a/docs/sdks/typescript/index.md +++ b/docs/sdks/typescript/index.md @@ -155,6 +155,58 @@ var spacetimeDBClient = new SpacetimeDBClient( ); ``` +## Class methods + +### `SpacetimeDBClient.registerReducers` + +Registers reducer classes for use with a SpacetimeDBClient + +```ts +registerReducers(...reducerClasses: ReducerClass[]) +``` + +#### Parameters + +| Name | Type | Description | +| :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| `reducerClasses` | `ReducerClass` | A list of classes to register | + +#### Example + +```ts +import SayHelloReducer from './types/say_hello_reducer'; +import AddReducer from './types/add_reducer'; + +SpacetimeDBClient.registerReducers(SayHelloReducer, AddReducer); +``` + +--- + +### `SpacetimeDBClient.registerTables` + +Registers table classes for use with a SpacetimeDBClient + +```ts +registerTables(...reducerClasses: TableClass[]) +``` + +#### Parameters + +| Name | Type | Description | +| :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| `tableClasses` | `TableClass` | A list of classes to register | + +#### Example + +```ts +import User from './types/user'; +import Player from './types/player'; + +SpacetimeDBClient.registerTables(User, Player); +``` + +--- + ## Properties ### `SpacetimeDBClient` identity @@ -867,7 +919,7 @@ SayHelloReducer.call(); Register a callback to run each time the reducer is invoked. ```ts -{Reducer}.on(callback: (reducerEvent: ReducerEvent, reducerArgs: any[]) => void): void +{Reducer}.on(callback: (reducerEvent: ReducerEvent, ...reducerArgs: any[]) => void): void ``` Clients will only be notified of reducer runs if either of two criteria is met: @@ -879,12 +931,12 @@ Clients will only be notified of reducer runs if either of two criteria is met: | Name | Type | | :--------- | :---------------------------------------------------------- | -| `callback` | `(reducerEvent: ReducerEvent, reducerArgs: any[]) => void)` | +| `callback` | `(reducerEvent: ReducerEvent, ...reducerArgs: any[]) => void)` | #### Example ```ts -SayHelloReducer.on((reducerEvent, reducerArgs) => { +SayHelloReducer.on((reducerEvent, ...reducerArgs) => { console.log("SayHelloReducer called", reducerEvent, reducerArgs); }); ``` diff --git a/docs/sdks/typescript/quickstart.md b/docs/sdks/typescript/quickstart.md index 0ec6b0eb..ca8abff9 100644 --- a/docs/sdks/typescript/quickstart.md +++ b/docs/sdks/typescript/quickstart.md @@ -165,9 +165,7 @@ module_bindings └── user.ts ``` -We need to import these types into our `client/src/App.tsx`. While we are at it, we will also import the SpacetimeDBClient class from our SDK. - -> There is a known issue where if you do not use every type in your file, it will not pull them into the published build. To fix this, we are using `console.log` to force them to get pulled in. +We need to import these types into our `client/src/App.tsx`. While we are at it, we will also import the SpacetimeDBClient class from our SDK. In order to let the SDK know what tables and reducers we will be using we need to also register them. ```typescript import { SpacetimeDBClient, Identity, Address } from "@clockworklabs/spacetimedb-sdk"; @@ -176,7 +174,9 @@ import Message from "./module_bindings/message"; import User from "./module_bindings/user"; import SendMessageReducer from "./module_bindings/send_message_reducer"; import SetNameReducer from "./module_bindings/set_name_reducer"; -console.log(Message, User, SendMessageReducer, SetNameReducer); + +SpacetimeDBClient.registerReducers(SendMessageReducer, SetNameReducer); +SpacetimeDBClient.registerTables(Message, User); ``` ## Create your SpacetimeDB client @@ -385,7 +385,7 @@ User.onUpdate((oldUser, user, reducerEvent) => { We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `OnReducer` method which is automatically implemented for each reducer by `spacetime generate`. -Each reducer callback takes two arguments: +Each reducer callback takes a number of parameters: 1. `ReducerEvent` that contains information about the reducer that triggered this event. It contains several fields. The ones we care about are: @@ -393,7 +393,7 @@ Each reducer callback takes two arguments: - `status`: The `Status` of the reducer run, one of `"Committed"`, `"Failed"` or `"OutOfEnergy"`. - `message`: The error message, if any, that the reducer returned. -2. `ReducerArgs` which is an array containing the arguments with which the reducer was invoked. +2. The rest of the parameters are arguments passed to the reducer. These callbacks will be invoked in one of two cases: @@ -411,7 +411,7 @@ If the reducer status comes back as `committed`, we'll update the name in our ap To the body of `App`, add: ```typescript -SetNameReducer.on((reducerEvent, reducerArgs) => { +SetNameReducer.on((reducerEvent, newName) => { if ( local_identity.current && reducerEvent.callerIdentity.isEqual(local_identity.current) @@ -419,7 +419,7 @@ SetNameReducer.on((reducerEvent, reducerArgs) => { if (reducerEvent.status === "failed") { appendToSystemMessage(`Error setting name: ${reducerEvent.message} `); } else if (reducerEvent.status === "committed") { - setName(reducerArgs[0]); + setName(newName); } } }); @@ -432,7 +432,7 @@ We handle warnings on rejected messages the same way as rejected names, though t To the body of `App`, add: ```typescript -SendMessageReducer.on((reducerEvent, reducerArgs) => { +SendMessageReducer.on((reducerEvent, newMessage) => { if ( local_identity.current && reducerEvent.callerIdentity.isEqual(local_identity.current) From f6d1353a113dfb10c4b625bee939df8dc3994b1a Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 24 Jan 2024 12:26:38 -0800 Subject: [PATCH 31/80] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cfe1e0af..af34b88a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ git push -u origin a-branch-name-that-describes-my-change 6. Go to our GitHub and open a PR that references your branch in your fork on your GitHub +> NOTE! If you make a change to `nav.ts` you will have to run `npm run build` to generate a new `docs/nav.js` file. + ## License This documentation repository is licensed under Apache 2.0. See LICENSE.txt for more details. From 4a50e1c29a59df437c7b3f464d29761ea3e4f63a Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Wed, 28 Feb 2024 14:12:58 -0500 Subject: [PATCH 32/80] WebSocket API ref: remove `row_pk`. (#29) Re https://github.com/clockworklabs/SpacetimeDB/pull/840 . We're removing the `row_pk` from the WebSocket API `TableRowOperation`, as computing it has a major performance impact on the server. This commit removes references to it from the WebSocket API reference. --- docs/ws/index.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/ws/index.md b/docs/ws/index.md index 76240163..b00bfa56 100644 --- a/docs/ws/index.md +++ b/docs/ws/index.md @@ -175,7 +175,6 @@ message TableRowOperation { INSERT = 1; } OperationType op = 1; - bytes row_pk = 2; bytes row = 3; } ``` @@ -189,9 +188,8 @@ Each `SubscriptionUpdate` contains a `TableUpdate` for each table with subscribe | `tableRowOperations` | A `TableRowOperation` for each inserted or deleted row. | | `TableRowOperation` field | Value | -| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `op` | `INSERT` for inserted rows during a [`TransactionUpdate`](#transactionupdate) or rows resident upon applying a subscription; `DELETE` for deleted rows during a [`TransactionUpdate`](#transactionupdate). | -| `row_pk` | An opaque hash of the row computed by SpacetimeDB. Clients can use this hash to identify a previously `INSERT`ed row during a `DELETE`. | | `row` | The altered row, encoded as a BSATN `ProductValue`. | ##### Text: JSON encoding @@ -214,7 +212,6 @@ Each `SubscriptionUpdate` contains a `TableUpdate` for each table with subscribe // TableRowOperation: { "op": "insert" | "delete", - "row_pk": string, "row": array } ``` @@ -228,9 +225,8 @@ Each `SubscriptionUpdate` contains a `TableUpdate` for each table with subscribe | `table_row_operations` | A `TableRowOperation` for each inserted or deleted row. | | `TableRowOperation` field | Value | -| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `op` | `"insert"` for inserted rows during a [`TransactionUpdate`](#transactionupdate) or rows resident upon applying a subscription; `"delete"` for deleted rows during a [`TransactionUpdate`](#transactionupdate). | -| `row_pk` | An opaque hash of the row computed by SpacetimeDB. Clients can use this hash to identify a previously inserted row during a delete. | | `row` | The altered row, encoded as a JSON array. | #### `TransactionUpdate` From ab6b6b486561adb295eb0f305372665a860a5be4 Mon Sep 17 00:00:00 2001 From: Dylan Hunt Date: Fri, 29 Mar 2024 17:16:35 +0800 Subject: [PATCH 33/80] Dylan/onboarding-upgrades (#28) * doc: Onboarding impr, fixes, consistency, cleanup refactor: Whats next cleanup, +unity, -bloat Removed redundant text while there refactor: Unity quickstart fixes, impr, prettify refactor: Unity pt1 fixes, impr, prettify fix(README): Rm "see test edits below" ref * !exists refactor(minor): General onboarding cleanup * Shorter, prettier, consistent fix(sdks/c#): Broken unitypackage url feat(sdks/c#): Add OneTimeQuery api ref * doc: Onboarding impr, fixes, consistency, cleanup * fix: Rm redundant 'module_bindings' mention * fix: Floating period, "arbitrary", "important": - PR review change requests - Additionally: hasUpdatedRecently fix and reformatting * fix: Mentioned FilterBy, used FindBy - Used FindBy since that was what the tutorial used, and also looking for a single Identity. - Note: There may be a similar rust discrepancy in the Unity pt1 tutorial. It'll work with Filter, but just simply less consistent. Holding off on that since my Rust syntax knowledge !exists. * fix(Unity-pt1): Rm copy+paste redundant comments * Duplicate comments found both above and within funcs * fix(unity): Rm unused using statement +merged info * Removed `System.Runtime.CompilerServices` * SpacetimeDB.Module seems to already include this (merged the info) * refactor(minor): Code spacing for grouping/clarity * feat: 'Standalone mode runs in foreground' memo * At general quickstart for `spacetime start` * refactor(unity-pt1): Standalone mode foreground memo * Also, removed the "speed" loss mention of C# * fix(syntaxErr): Fix err, keep FilterBy, handle null - After a verbose discussion, we will eventually swap to FindBy for single-result queries, but not in this PR. - For now, the syntax err is fixed by making the var nullable and suffixing a LINQ FirstOrDefault(). Approved by Tyler in Discord. - We never *actually* created a player in the tutorial. This creates the player. Approved by Tyler in Discord. * fix: Remote player `is null` check removal --- README.md | 2 +- docs/getting-started.md | 24 ++++++----- docs/modules/c-sharp/index.md | 3 ++ docs/modules/c-sharp/quickstart.md | 37 +++++++++++++---- docs/modules/rust/quickstart.md | 2 +- docs/sdks/c-sharp/index.md | 20 ++++++--- docs/sdks/c-sharp/quickstart.md | 66 +++++++++++++++++------------ docs/unity/part-1.md | 67 +++++++++++++++++++----------- 8 files changed, 145 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index af34b88a..0f9998b0 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ To make changes to our docs, you can open a pull request in this repository. You git clone ssh://git@github.com//spacetime-docs ``` -3. Make your edits to the docs that you want to make + test them locally (see Testing Your Edits below) +3. Make your edits to the docs that you want to make + test them locally 4. Commit your changes: ```bash diff --git a/docs/getting-started.md b/docs/getting-started.md index 5a0c6041..177a0d25 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,29 +2,33 @@ To develop SpacetimeDB applications locally, you will need to run the Standalone version of the server. -1. [Install](/install) the SpacetimeDB CLI (Command Line Interface). -2. Run the start command +1. [Install](/install) the SpacetimeDB CLI (Command Line Interface) +2. Run the start command: ```bash spacetime start ``` -The server listens on port `3000` by default. You can change this by using the `--listen-addr` option described below. +The server listens on port `3000` by default, customized via `--listen-addr`. -SSL is not supported in standalone mode. +💡 Standalone mode will run in the foreground. +⚠️ SSL is not supported in standalone mode. ## What's Next? -You are ready to start developing SpacetimeDB modules. We have a quickstart guide for each supported server-side language: +You are ready to start developing SpacetimeDB modules. See below for a quickstart guide for both client and server (module) languages/frameworks. + +### Server (Module) - [Rust](/docs/modules/rust/quickstart) - [C#](/docs/modules/c-sharp/quickstart) -Then you can write your client application. We have a quickstart guide for each supported client-side language: +⚡**Note:** Rust is [roughly 2x faster](https://faun.dev/c/links/faun/c-vs-rust-vs-go-a-performance-benchmarking-in-kubernetes/) than C# + +### Client - [Rust](/docs/sdks/rust/quickstart) -- [C#](/docs/sdks/c-sharp/quickstart) +- [C# (Standalone)](/docs/sdks/c-sharp/quickstart) +- [C# (Unity)](/docs/unity/part-1) - [Typescript](/docs/sdks/typescript/quickstart) -- [Python](/docs/sdks/python/quickstart) - -We also have a [step-by-step tutorial](/docs/unity/part-1) for building a multiplayer game in Unity3d. +- [Python](/docs/sdks/python/quickstart) \ No newline at end of file diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 36a9618a..31ebd1d4 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -42,6 +42,7 @@ static partial class Module { // We can skip (or explicitly set to zero) auto-incremented fields when creating new rows. var person = new Person { Name = name, Age = age }; + // `Insert()` method is auto-generated and will insert the given row into the table. person.Insert(); // After insertion, the auto-incremented fields will be populated with their actual values. @@ -211,8 +212,10 @@ public partial struct Person // Finds a row in the table with the given value in the `Id` column and returns it, or `null` if no such row exists. public static Person? FindById(int id); + // Deletes a row in the table with the given value in the `Id` column and returns `true` if the row was found and deleted, or `false` if no such row exists. public static bool DeleteById(int id); + // Updates a row in the table with the given value in the `Id` column and returns `true` if the row was found and updated, or `false` if no such row exists. public static bool UpdateById(int oldId, Person newValue); } diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index fb97c316..f5f73401 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -16,7 +16,18 @@ If you haven't already, start by [installing SpacetimeDB](/install). This will i ## Install .NET 8 -Next we need to [install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) so that we can build and publish our module. .NET 8.0 is the earliest to have the `wasi-experimental` workload that we rely on. +Next we need to [install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) so that we can build and publish our module. + +You may already have .NET 8 and can be checked: +```bash +dotnet --list-sdks +``` + +.NET 8.0 is the earliest to have the `wasi-experimental` workload that we rely on, but requires manual activation: + +```bash +dotnet workload install wasi-experimental +``` ## Project structure @@ -35,7 +46,11 @@ spacetime init --lang csharp server ## Declare imports -`spacetime init` should have pre-populated `server/Lib.cs` with a trivial module. Clear it out, so we can write a module that's still pretty simple: a bare-bones chat server. +`spacetime init` generated a few files: + +1. Open `server/StdbModule.csproj` to generate a .sln file for intellisense/validation support. +2. Open `server/Lib.cs`, a trivial module. +3. Clear it out, so we can write a new module that's still pretty simple: a bare-bones chat server. To the top of `server/Lib.cs`, add some imports we'll be using: @@ -45,8 +60,10 @@ using SpacetimeDB.Module; using static SpacetimeDB.Runtime; ``` -- `System.Runtime.CompilerServices` allows us to use the `ModuleInitializer` attribute, which we'll use to register our `OnConnect` and `OnDisconnect` callbacks. -- `SpacetimeDB.Module` contains the special attributes we'll use to define our module. +- `System.Runtime.CompilerServices` +- `SpacetimeDB.Module` + - Contains the special attributes we'll use to define our module. + - Allows us to use the `ModuleInitializer` attribute, which we'll use to register our `OnConnect` and `OnDisconnect` callbacks. - `SpacetimeDB.Runtime` contains the raw API bindings SpacetimeDB uses to communicate with the database. We also need to create our static module class which all of the module code will live in. In `server/Lib.cs`, add: @@ -184,7 +201,7 @@ You could extend the validation in `ValidateMessage` in similar ways to `Validat In C# modules, you can register for `Connect` and `Disconnect` events by using a special `ReducerKind`. We'll use the `Connect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User.FilterByIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FilterByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. +We'll use `User.FindByIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FindByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: @@ -235,7 +252,7 @@ public static void OnDisconnect(DbEventArgs dbEventArgs) else { // User does not exist, log warning - Log($"Warning: No user found for disconnected client."); + Log("Warning: No user found for disconnected client."); } } ``` @@ -250,12 +267,16 @@ From the `quickstart-chat` directory, run: spacetime publish --project-path server ``` +```bash +npm i wasm-opt -g +``` + ## Call Reducers You can use the CLI (command line interface) to run reducers. The arguments to the reducer are passed in JSON format. ```bash -spacetime call send_message '["Hello, World!"]' +spacetime call send_message "Hello, World!" ``` Once we've called our `send_message` reducer, we can check to make sure it ran by running the `logs` command. @@ -288,4 +309,4 @@ spacetime sql "SELECT * FROM Message" You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/languages/rust/rust-sdk-quickstart-guide), [C#](/docs/languages/csharp/csharp-sdk-quickstart-guide), [TypeScript](/docs/languages/typescript/typescript-sdk-quickstart-guide) or [Python](/docs/languages/python/python-sdk-quickstart-guide). -If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). +If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index e0ff0f5f..e015b881 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -269,4 +269,4 @@ You can find the full code for this module [in the SpacetimeDB module examples]( You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: [Rust](/docs/sdks/rust/quickstart), [C#](/docs/sdks/c-sharp/quickstart), [TypeScript](/docs/sdks/typescript/quickstart) or [Python](/docs/sdks/python/quickstart). -If you are planning to use SpacetimeDB with the Unity3d game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). +If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/sdks/c-sharp/index.md b/docs/sdks/c-sharp/index.md index 473ca1ba..7c920cf5 100644 --- a/docs/sdks/c-sharp/index.md +++ b/docs/sdks/c-sharp/index.md @@ -17,9 +17,10 @@ The SpacetimeDB client C# for Rust contains all the tools you need to build nati - [Method `SpacetimeDBClient.Connect`](#method-spacetimedbclientconnect) - [Event `SpacetimeDBClient.onIdentityReceived`](#event-spacetimedbclientonidentityreceived) - [Event `SpacetimeDBClient.onConnect`](#event-spacetimedbclientonconnect) - - [Subscribe to queries](#subscribe-to-queries) + - [Query subscriptions & one-time actions](#subscribe-to-queries) - [Method `SpacetimeDBClient.Subscribe`](#method-spacetimedbclientsubscribe) - [Event `SpacetimeDBClient.onSubscriptionApplied`](#event-spacetimedbclientonsubscriptionapplied) + - [Method `SpacetimeDBClient.OneOffQuery`](#event-spacetimedbclientoneoffquery) - [View rows of subscribed tables](#view-rows-of-subscribed-tables) - [Class `{TABLE}`](#class-table) - [Static Method `{TABLE}.Iter`](#static-method-tableiter) @@ -64,13 +65,11 @@ dotnet add package spacetimedbsdk ### Using Unity -To install the SpacetimeDB SDK into a Unity project, download the SpacetimeDB SDK from the following link. +To install the SpacetimeDB SDK into a Unity project, [download the SpacetimeDB SDK](https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/releases/latest), packaged as a `.unitypackage`. -https://sdk.spacetimedb.com/SpacetimeDBUnitySDK.unitypackage +In Unity navigate to the `Assets > Import Package > Custom Package` menu in the menu bar. Select your `SpacetimeDB.Unity.Comprehensive.Tutorial.unitypackage` file and leave all folders checked. -In Unity navigate to the `Assets > Import Package > Custom Package...` menu in the menu bar. Select your `SpacetimeDBUnitySDK.unitypackage` file and leave all folders checked. - -(See also the [Unity Tutorial](/docs/unity/part-1).) +(See also the [Unity Tutorial](/docs/unity/part-1)) ## Generate module bindings @@ -319,6 +318,15 @@ void Main() } ``` +### Method [`OneTimeQuery`](#method-spacetimedbclientsubscribe) + +You may not want to subscribe to a query, but instead want to run a query once and receive the results immediately via a `Task` result: + +```csharp +// Query all Messages from the sender "bob" +SpacetimeDBClient.instance.OneOffQuery("WHERE sender = \"bob\""); +``` + ## View rows of subscribed tables The SDK maintains a local view of the database called the "client cache". This cache contains whatever rows are selected via a call to [`SpacetimeDBClient.Subscribe`](#method-spacetimedbclientsubscribe). These rows are represented in the SpacetimeDB .Net SDK as instances of [`SpacetimeDB.Types.{TABLE}`](#class-table). diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/sdks/c-sharp/quickstart.md index f7565019..07aa6cf6 100644 --- a/docs/sdks/c-sharp/quickstart.md +++ b/docs/sdks/c-sharp/quickstart.md @@ -1,8 +1,8 @@ # C# Client SDK Quick Start -In this guide we'll show you how to get up and running with a simple SpacetimDB app with a client written in C#. +In this guide 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 or C# Module Quickstart guides. Make sure you follow one of these guides before you start on this one. +We'll implement a command-line client for the module created in our [Rust](../../modules/rust/quickstart.md) or [C# Module](../../modules/c-sharp/quickstart.md) Quickstart guides. Ensure you followed one of these guides before continuing. ## Project structure @@ -12,7 +12,7 @@ Enter the directory `quickstart-chat` you created in the [Rust Module Quickstart cd quickstart-chat ``` -Within it, create a new C# console application project called `client` using either Visual Studio or the .NET CLI: +Within it, create a new C# console application project called `client` using either Visual Studio, Rider or the .NET CLI: ```bash dotnet new console -o client @@ -22,7 +22,7 @@ Open the project in your IDE of choice. ## Add the NuGet package for the C# SpacetimeDB SDK -Add the `SpacetimeDB.ClientSDK` [NuGet package](https://www.nuget.org/packages/spacetimedbsdk) using Visual Studio NuGet package manager or via the .NET CLI +Add the `SpacetimeDB.ClientSDK` [NuGet package](https://www.nuget.org/packages/spacetimedbsdk) using Visual Studio or Rider _NuGet Package Manager_ or via the .NET CLI: ```bash dotnet add package SpacetimeDB.ClientSDK @@ -65,8 +65,10 @@ We will also need to create some global variables that will be explained when we ```csharp // our local client SpacetimeDB identity Identity? local_identity = null; + // declare a thread safe queue to store commands in format (command, args) ConcurrentQueue<(string,string)> input_queue = new ConcurrentQueue<(string, string)>(); + // declare a threadsafe cancel token to cancel the process loop CancellationTokenSource cancel_token = new CancellationTokenSource(); ``` @@ -75,10 +77,10 @@ CancellationTokenSource cancel_token = new CancellationTokenSource(); We'll work outside-in, first defining our `Main` function at a high level, then implementing each behavior it needs. We need `Main` to do several things: -1. Initialize the AuthToken module, which loads and stores our authentication token to/from local storage. -2. Create the SpacetimeDBClient instance. +1. Initialize the `AuthToken` module, which loads and stores our authentication token to/from local storage. +2. Create the `SpacetimeDBClient` instance. 3. Register callbacks on any events we want to handle. These will print to standard output messages received from the database and updates about users' names and online statuses. -4. Start our processing thread, which connects to the SpacetimeDB module, updates the SpacetimeDB client and processes commands that come in from the input loop running in the main thread. +4. Start our processing thread which connects to the SpacetimeDB module, updates the SpacetimeDB client and processes commands that come in from the input loop running in the main thread. 5. Start the input loop, which reads commands from standard input and sends them to the processing thread. 6. When the input loop exits, stop the processing thread and wait for it to exit. @@ -154,7 +156,7 @@ string UserNameOrIdentity(User user) => user.Name ?? user.Identity.ToString()!.S void User_OnInsert(User insertedValue, ReducerEvent? dbEvent) { - if(insertedValue.Online) + if (insertedValue.Online) { Console.WriteLine($"{UserNameOrIdentity(insertedValue)} is online"); } @@ -178,20 +180,21 @@ We'll print an appropriate message in each of these cases. ```csharp void User_OnUpdate(User oldValue, User newValue, ReducerEvent dbEvent) { - if(oldValue.Name != newValue.Name) + if (oldValue.Name != newValue.Name) { Console.WriteLine($"{UserNameOrIdentity(oldValue)} renamed to {newValue.Name}"); } - if(oldValue.Online != newValue.Online) + + if (oldValue.Online == newValue.Online) + return; + + if (newValue.Online) { - if(newValue.Online) - { - Console.WriteLine($"{UserNameOrIdentity(newValue)} connected."); - } - else - { - Console.WriteLine($"{UserNameOrIdentity(newValue)} disconnected."); - } + Console.WriteLine($"{UserNameOrIdentity(newValue)} connected."); + } + else + { + Console.WriteLine($"{UserNameOrIdentity(newValue)} disconnected."); } } ``` @@ -209,7 +212,7 @@ void PrintMessage(Message message) { var sender = User.FilterByIdentity(message.Sender); var senderName = "unknown"; - if(sender != null) + if (sender != null) { senderName = UserNameOrIdentity(sender); } @@ -219,7 +222,7 @@ void PrintMessage(Message message) void Message_OnInsert(Message insertedValue, ReducerEvent? dbEvent) { - if(dbEvent != null) + if (dbEvent != null) { PrintMessage(insertedValue); } @@ -254,7 +257,11 @@ We'll test both that our identity matches the sender and that the status is `Fai ```csharp void Reducer_OnSetNameEvent(ReducerEvent reducerEvent, string name) { - if(reducerEvent.Identity == local_identity && reducerEvent.Status == ClientApi.Event.Types.Status.Failed) + bool localIdentityFailedToChangeName = + reducerEvent.Identity == local_identity && + reducerEvent.Status == ClientApi.Event.Types.Status.Failed; + + if (localIdentityFailedToChangeName) { Console.Write($"Failed to change name to {name}"); } @@ -268,7 +275,11 @@ We handle warnings on rejected messages the same way as rejected names, though t ```csharp void Reducer_OnSendMessageEvent(ReducerEvent reducerEvent, string text) { - if (reducerEvent.Identity == local_identity && reducerEvent.Status == ClientApi.Event.Types.Status.Failed) + bool localIdentityFailedToSendMessage = + reducerEvent.Identity == local_identity && + reducerEvent.Status == ClientApi.Event.Types.Status.Failed; + + if (localIdentityFailedToSendMessage) { Console.Write($"Failed to send message {text}"); } @@ -282,7 +293,10 @@ Once we are connected, we can send our subscription to the SpacetimeDB module. S ```csharp void OnConnect() { - SpacetimeDBClient.instance.Subscribe(new List { "SELECT * FROM User", "SELECT * FROM Message" }); + SpacetimeDBClient.instance.Subscribe(new List + { + "SELECT * FROM User", "SELECT * FROM Message" + }); } ``` @@ -370,12 +384,12 @@ void InputLoop() while (true) { var input = Console.ReadLine(); - if(input == null) + if (input == null) { break; } - if(input.StartsWith("/name ")) + if (input.StartsWith("/name ")) { input_queue.Enqueue(("name", input.Substring(6))); continue; @@ -421,4 +435,4 @@ dotnet run --project client ## What's next? -Congratulations! You've built a simple chat app using SpacetimeDB. You can look at the C# SDK Reference for more information about the client SDK. If you are interested in developing in the Unity3d game engine, check out our Unity3d Comprehensive Tutorial and BitcraftMini game example. +Congratulations! You've built a simple chat app using SpacetimeDB. You can look at the C# SDK Reference for more information about the client SDK. If you are interested in developing in the Unity game engine, check out our Unity3d Comprehensive Tutorial and BitcraftMini game example. diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index 30bd3137..0e899750 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -12,14 +12,19 @@ This tutorial has been tested against UnityEngine version 2022.3.4f1. This tutor ## Prepare Project Structure -This project is separated into two sub-projects, one for the server (module) code and one for the client code. First we'll create the main directory, this directory name doesn't matter but we'll give you an example: +This project is separated into two sub-projects; + +1. Server (module) code +2. Client code + +First, we'll create a project root directory (you can choose the name): ```bash mkdir SpacetimeDBUnityTutorial cd SpacetimeDBUnityTutorial ``` -In the following sections we'll be adding a client directory and a server directory, which will contain the client files and the module (server) files respectively. We'll start by populating the client directory. +We'll start by populating the client directory. ## Setting up the Tutorial Unity Project @@ -31,9 +36,9 @@ Open Unity and create a new project by selecting "New" from the Unity Hub or goi ![UnityHub-NewProject](/images/unity-tutorial/UnityHub-NewProject.JPG) -For Project Name use `client`. For Project Location make sure that you use your `SpacetimeDBUnityTutorial` directory. This is the directory that we created in a previous step. +**⚠️ Important: Ensure `3D (URP)` is selected** to properly render the materials in the scene! -**Important: Ensure that you have selected the 3D (URP) template for this project.** If you forget to do this then Unity won't be able to properly render the materials in the scene! +For Project Name use `client`. For Project Location make sure that you use your `SpacetimeDBUnityTutorial` directory. This is the directory that we created in a previous step. ![UnityHub-3DURP](/images/unity-tutorial/UnityHub-3DURP.JPG) @@ -77,7 +82,9 @@ Now that we have everything set up, let's run the project and see it in action: ![Unity-OpenSceneMain](/images/unity-tutorial/Unity-OpenSceneMain.JPG) -NOTE: When you open the scene you may get a message saying you need to import TMP Essentials. When it appears, click the "Import TMP Essentials" button. +**NOTE:** When you open the scene you may get a message saying you need to import TMP Essentials. When it appears, click the "Import TMP Essentials" button. + +🧹 Clear any false-positive TMPro errors that may show. ![Unity Import TMP Essentials](/images/unity-tutorial/Unity-ImportTMPEssentials.JPG) @@ -105,6 +112,9 @@ At this point you should have the single player game working. In your CLI, your spacetime start ``` +💡 Standalone mode will run in the foreground. +💡 Below examples Rust language, [but you may also use C#](../modules/c-sharp/index.md). + 3. Run the following command to initialize the SpacetimeDB server project with Rust as the language: ```bash @@ -284,7 +294,6 @@ We use the `connect` and `disconnect` reducers to update the logged in state of // Called when the client connects, we update the logged_in state to true #[spacetimedb(connect)] pub fn client_connected(ctx: ReducerContext) { - // called when the client connects, we update the logged_in state to true update_player_login_state(ctx, true); } @@ -292,7 +301,6 @@ pub fn client_connected(ctx: ReducerContext) { // Called when the client disconnects, we update the logged_in state to false #[spacetimedb(disconnect)] pub fn client_disconnected(ctx: ReducerContext) { - // Called when the client disconnects, we update the logged_in state to false update_player_login_state(ctx, false); } @@ -553,8 +561,8 @@ public class RemotePlayer : MonoBehaviour canvas.worldCamera = Camera.main; // Get the username from the PlayerComponent for this object and set it in the UI - PlayerComponent playerComp = PlayerComponent.FilterByEntityId(EntityId); - Username = playerComp.Username; + // FilterByEntityId is normally nullable, but we'll assume not null for simplicity + PlayerComponent playerComp = PlayerComponent.FilterByEntityId(EntityId).First(); // Get the last location for this player and set the initial position EntityComponent entity = EntityComponent.FilterByEntityId(EntityId); @@ -612,13 +620,16 @@ private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo { // Spawn the player object and attach the RemotePlayer component var remotePlayer = Instantiate(PlayerPrefab); + // Lookup and apply the position for this new player var entity = EntityComponent.FilterByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; + var movementController = remotePlayer.GetComponent(); movementController.RemoteTargetPosition = position; movementController.RemoteTargetRotation = entity.Direction; + remotePlayer.AddComponent().EntityId = obj.EntityId; } } @@ -639,21 +650,26 @@ using SpacetimeDB; private float? lastUpdateTime; private void FixedUpdate() { - if ((lastUpdateTime.HasValue && Time.time - lastUpdateTime.Value > 1.0f / movementUpdateSpeed) || !SpacetimeDBClient.instance.IsConnected()) - { - return; - } - - lastUpdateTime = Time.time; - var p = PlayerMovementController.Local.GetModelPosition(); - Reducer.UpdatePlayerPosition(new StdbVector3 - { - X = p.x, - Y = p.y, - Z = p.z, - }, - PlayerMovementController.Local.GetModelRotation(), - PlayerMovementController.Local.IsMoving()); + float? deltaTime = Time.time - lastUpdateTime; + bool hasUpdatedRecently = deltaTime.HasValue && deltaTime.Value < 1.0f / movementUpdateSpeed; + bool isConnected = SpacetimeDBClient.instance.IsConnected(); + + if (hasUpdatedRecently || !isConnected) + { + return; + } + + lastUpdateTime = Time.time; + var p = PlayerMovementController.Local.GetModelPosition(); + + Reducer.UpdatePlayerPosition(new StdbVector3 + { + X = p.x, + Y = p.y, + Z = p.z, + }, + PlayerMovementController.Local.GetModelRotation(), + PlayerMovementController.Local.IsMoving()); } ``` @@ -713,13 +729,16 @@ private void OnPlayerComponentChanged(PlayerComponent obj) { // Spawn the player object and attach the RemotePlayer component var remotePlayer = Instantiate(PlayerPrefab); + // Lookup and apply the position for this new player var entity = EntityComponent.FilterByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; + var movementController = remotePlayer.GetComponent(); movementController.RemoteTargetPosition = position; movementController.RemoteTargetRotation = entity.Direction; + remotePlayer.AddComponent().EntityId = obj.EntityId; } } From 6ed040b8345e8ffa6ed97669a34b9b0b09b2a621 Mon Sep 17 00:00:00 2001 From: Dylan Hunt Date: Fri, 26 Apr 2024 03:51:37 +0800 Subject: [PATCH 34/80] Unity tutorial - C# parity (#31) * doc: Onboarding impr, fixes, consistency, cleanup refactor: Whats next cleanup, +unity, -bloat Removed redundant text while there refactor: Unity quickstart fixes, impr, prettify refactor: Unity pt1 fixes, impr, prettify fix(README): Rm "see test edits below" ref * !exists refactor(minor): General onboarding cleanup * Shorter, prettier, consistent fix(sdks/c#): Broken unitypackage url feat(sdks/c#): Add OneTimeQuery api ref * doc: Onboarding impr, fixes, consistency, cleanup * fix: Rm redundant 'module_bindings' mention * fix: Floating period, "arbitrary", "important": - PR review change requests - Additionally: hasUpdatedRecently fix and reformatting * fix: Mentioned FilterBy, used FindBy - Used FindBy since that was what the tutorial used, and also looking for a single Identity. - Note: There may be a similar rust discrepancy in the Unity pt1 tutorial. It'll work with Filter, but just simply less consistent. Holding off on that since my Rust syntax knowledge !exists. * fix(Unity-pt1): Rm copy+paste redundant comments * Duplicate comments found both above and within funcs * fix(unity): Rm unused using statement +merged info * Removed `System.Runtime.CompilerServices` * SpacetimeDB.Module seems to already include this (merged the info) * refactor(minor): Code spacing for grouping/clarity * feat: 'Standalone mode runs in foreground' memo * At general quickstart for `spacetime start` * refactor(unity-pt1): Standalone mode foreground memo * Also, removed the "speed" loss mention of C# * fix(syntaxErr): Fix err, keep FilterBy, handle null - After a verbose discussion, we will eventually swap to FindBy for single-result queries, but not in this PR. - For now, the syntax err is fixed by making the var nullable and suffixing a LINQ FirstOrDefault(). Approved by Tyler in Discord. - We never *actually* created a player in the tutorial. This creates the player. Approved by Tyler in Discord. * doc!(unity-tutorial): Add C# module parity + split - Why? - Despite being a Unity tutorial (we 100% know the user knows C#), the server example used Rust. - This creates friction when the user is already learning multiple new things: The SpacetimeDB architecture, the CLI, the client SDK and server SDK. If they previously did not know Rust, this could add some weight to the onboarding friction. - The Unity tutorial could use an overview since it's quite lengthy and progressive. - Part1 should be split, anyway - it covers way too much for a single section to handle (especially since it jumps between client and server). Splitting between basic multiplayer + advanced makes things more-manageable and less intimidating. - Before: - UNITY TUTORIAL - Part1 (Client + Rust Server) - Part2 (Resources and Scheduling) - Part3 (BitCraft Mini) - After: - UNITY TUTORIAL - BASIC MULTIPLAYER - Overview - Part1 (Setup) - Part2a (Rust Server) - Part2b (C# Server) - Part3 (Client) - UNITY TUTORIAL - ADVANCED - Part4 (Resources and Scheduling) - Part5 (BitCraft Mini) * Update docs/unity/part-2b-c-sharp.md Rust -> C# Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> * Update docs/unity/part-2b-c-sharp.md - `--lang=rust` to `=csharp` Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> * Update docs/unity/part-2b-c-sharp.md - Rm RustRover mention Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> * Update docs/unity/part-2b-c-sharp.md - Rust -> C# Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> * fix: "Next tutorial" mixups * fix: Bad troubleshooting links - Server issues shouldn't link to Client troubleshooting that has no answer * Update docs/unity/part-2b-c-sharp.md Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> * Update docs/unity/part-2a-rust.md Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> --------- Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> --- docs/sdks/c-sharp/quickstart.md | 2 +- docs/unity/index.md | 23 + docs/unity/part-1.md | 769 +--------------------------- docs/unity/part-2a-rust.md | 312 +++++++++++ docs/unity/part-2b-c-sharp.md | 344 +++++++++++++ docs/unity/part-3.md | 487 ++++++++++++++++-- docs/unity/{part-2.md => part-4.md} | 6 +- docs/unity/part-5.md | 108 ++++ nav.ts | 16 +- 9 files changed, 1242 insertions(+), 825 deletions(-) create mode 100644 docs/unity/index.md create mode 100644 docs/unity/part-2a-rust.md create mode 100644 docs/unity/part-2b-c-sharp.md rename docs/unity/{part-2.md => part-4.md} (97%) create mode 100644 docs/unity/part-5.md diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/sdks/c-sharp/quickstart.md index 07aa6cf6..92980f42 100644 --- a/docs/sdks/c-sharp/quickstart.md +++ b/docs/sdks/c-sharp/quickstart.md @@ -427,7 +427,7 @@ Finally we just need to add a call to `Main` in `Program.cs`: Main(); ``` -Now we can run the client, by hitting start in Visual Studio or running the following command in the `client` directory: +Now, we can run the client by hitting start in Visual Studio or Rider; or by running the following command in the `client` directory: ```bash dotnet run --project client diff --git a/docs/unity/index.md b/docs/unity/index.md new file mode 100644 index 00000000..2b8e6d67 --- /dev/null +++ b/docs/unity/index.md @@ -0,0 +1,23 @@ +# Unity Tutorial Overview + +Need help with the tutorial or CLI commands? [Join our Discord server](https://discord.gg/spacetimedb)! + +The objective of this progressive tutorial is to help you become acquainted with the basic features of SpacetimeDB. By the end, you should have a basic understanding of what SpacetimeDB offers for developers making multiplayer games. It assumes that you have a basic understanding of the Unity Editor, using a command line terminal and coding. + +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. + +Tested with UnityEngine `2022.3.20f1 LTS` (and may also work on newer versions). + +## Unity Tutorial - Basic Multiplayer +Get started with the core client-server setup. For part 2, you may choose your server module preference of [Rust](/docs/modules/rust) or [C#](/docs/modules/c-sharp): + +- [Part 1 - Setup](/docs/unity/part-1.md) +- [Part 2a - Server (Rust)](/docs/unity/part-2a-rust.md) +- [Part 2b - Server (C#)](/docs/unity/part-2b-csharp.md) +- [Part 3 - Client](/docs/unity/part-3.md) + +## Unity Tutorial - Advanced +By this point, you should already have a basic understanding of SpacetimeDB client, server and CLI: + +- [Part 4 - Resources & Scheduling](/docs/unity/part-4.md) +- [Part 5 - BitCraft Mini](/docs/unity/part-5.md) diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index 0e899750..b8b8c3c0 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -1,15 +1,9 @@ -# Part 1 - Basic Multiplayer +# Unity Tutorial - Basic Multiplayer - Part 1 - Setup ![UnityTutorial-HeroImage](/images/unity-tutorial/UnityTutorial-HeroImage.JPG) Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -The objective of this tutorial is to help you become acquainted with the basic features of SpacetimeDB. By the end of this tutorial you should have a basic understanding of what SpacetimeDB offers for developers making multiplayer games. It assumes that you have a basic understanding of the Unity Editor, using a command line terminal, and coding. - -In this tutorial we'll be giving you some CLI commands to execute. If you are using Windows we recommend using Git Bash or powershell. If you're on mac we recommend you use the Terminal application. If you encouter issues with any of the commands in this guide, please reach out to us through our discord server and we would be happy to help assist you. - -This tutorial has been tested against UnityEngine version 2022.3.4f1. This tutorial may work on newer versions as well. - ## Prepare Project Structure This project is separated into two sub-projects; @@ -115,763 +109,14 @@ spacetime start 💡 Standalone mode will run in the foreground. 💡 Below examples Rust language, [but you may also use C#](../modules/c-sharp/index.md). -3. Run the following command to initialize the SpacetimeDB server project with Rust as the language: - -```bash -spacetime init --lang=rust server -``` - -This command creates a new folder named "server" within your Unity project directory and sets up the SpacetimeDB server project with Rust as the programming language. - -### Understanding Entity Component Systems +### The Entity Component Systems (ECS) -Entity Component System (ECS) is a game development architecture that separates game objects into components for better flexibility and performance. You can read more about the ECS design pattern [here](https://en.wikipedia.org/wiki/Entity_component_system). +Before we continue to creating the server module, it's important to understand the basics of the ECS. This is a game development architecture that separates game objects into components for better flexibility and performance. You can read more about the ECS design pattern [here](https://en.wikipedia.org/wiki/Entity_component_system). We chose ECS for this example project because it promotes scalability, modularity, and efficient data management, making it ideal for building multiplayer games with SpacetimeDB. -### SpacetimeDB Tables - -In this section we'll be making some edits to the file `server/src/lib.rs`. We recommend you open up this file in an IDE like VSCode or RustRover. - -**Important: Open the `server/src/lib.rs` file and delete its contents. We will be writing it from scratch here.** - -First we need to add some imports at the top of the file. - -**Copy and paste into lib.rs:** - -```rust -use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; -use log; -``` - -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our rust module reference. Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. - -**Append to the bottom of lib.rs:** - -```rust -// We're using this table as a singleton, so there should typically only be one element where the version is 0. -#[spacetimedb(table)] -#[derive(Clone)] -pub struct Config { - #[primarykey] - pub version: u32, - pub message_of_the_day: String, -} -``` - -Next we're going to define a new `SpacetimeType` called `StdbVector3` 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. So therefore, `StdbVector3` is not itself a table. - -**Append to the bottom of lib.rs:** - -```rust -// This allows us to store 3D points in tables. -#[derive(SpacetimeType, Clone)] -pub struct StdbVector3 { - pub x: f32, - pub y: f32, - pub z: f32, -} -``` - -Now we're going to create a table which actually uses the `StdbVector3` that we just defined. The `EntityComponent` is associated with all entities in the world, including players. - -```rust -// This stores information related to all entities in our game. In this tutorial -// all entities must at least have an entity_id, a position, a direction and they -// must specify whether or not they are moving. -#[spacetimedb(table)] -#[derive(Clone)] -pub struct EntityComponent { - #[primarykey] - // The autoinc macro here just means every time we insert into this table - // we will receive a new row where this value will be increased by one. This - // allows us to easily get rows where `entity_id` is unique. - #[autoinc] - pub entity_id: u64, - pub position: StdbVector3, - pub direction: f32, - pub moving: bool, -} -``` - -Next we will define the `PlayerComponent` table. The `PlayerComponent` table is used to store information related to players. Each player will have a row in this table, and will also have a row in the `EntityComponent` table with a matching `entity_id`. You'll see how this works later in the `create_player` reducer. - -**Append to the bottom of lib.rs:** - -```rust -// All players have this component and it associates an entity with the user's -// Identity. It also stores their username and whether or not they're logged in. -#[derive(Clone)] -#[spacetimedb(table)] -pub struct PlayerComponent { - // An entity_id that matches an entity_id in the `EntityComponent` table. - #[primarykey] - pub entity_id: u64, - // The user's identity, which is unique to each player - #[unique] - pub owner_id: Identity, - pub username: String, - pub logged_in: bool, -} -``` - -Next we write our very first reducer, `create_player`. From the client we will call this reducer when we create a new player: - -**Append to the bottom of lib.rs:** - -```rust -// This reducer is called when the user logs in for the first time and -// enters a username -#[spacetimedb(reducer)] -pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String> { - // Get the Identity of the client who called this reducer - let owner_id = ctx.sender; - - // Make sure we don't already have a player with this identity - if PlayerComponent::filter_by_owner_id(&owner_id).is_some() { - log::info!("Player already exists"); - return Err("Player already exists".to_string()); - } - - // Create a new entity for this player and get a unique `entity_id`. - let entity_id = EntityComponent::insert(EntityComponent - { - entity_id: 0, - position: StdbVector3 { x: 0.0, y: 0.0, z: 0.0 }, - direction: 0.0, - moving: false, - }).expect("Failed to create a unique PlayerComponent.").entity_id; - - // The PlayerComponent uses the same entity_id and stores the identity of - // the owner, username, and whether or not they are logged in. - PlayerComponent::insert(PlayerComponent { - entity_id, - owner_id, - username: username.clone(), - logged_in: true, - }).expect("Failed to insert player component."); - - log::info!("Player created: {}({})", username, entity_id); - - Ok(()) -} -``` - ---- - -**SpacetimeDB Reducers** - -"Reducer" is a term coined by Clockwork Labs that refers to a function which when executed "reduces" into a list of inserts and deletes, which is then packed into a single database transaction. Reducers can be called remotely using the CLI or a client SDK or they can be scheduled to be called at some future time from another reducer call. - ---- - -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. We'll learn about publishing later. -- `connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `sender` value of the `ReducerContext`. -- `disconnect` - Called when a user disconnects from the SpacetimeDB module. - -Next we are going to write a custom `init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config::filter_by_version(0)`. - -**Append to the bottom of lib.rs:** - -```rust -// Called when the module is initially published -#[spacetimedb(init)] -pub fn init() { - Config::insert(Config { - version: 0, - message_of_the_day: "Hello, World!".to_string(), - }).expect("Failed to insert config."); -} -``` - -We use the `connect` and `disconnect` reducers to update the logged in state of the player. The `update_player_login_state` helper function looks up the `PlayerComponent` row using the user's identity and if it exists, it updates the `logged_in` variable and calls the auto-generated `update` function on `PlayerComponent` to update the row. - -**Append to the bottom of lib.rs:** - -```rust -// Called when the client connects, we update the logged_in state to true -#[spacetimedb(connect)] -pub fn client_connected(ctx: ReducerContext) { - update_player_login_state(ctx, true); -} - - -// Called when the client disconnects, we update the logged_in state to false -#[spacetimedb(disconnect)] -pub fn client_disconnected(ctx: ReducerContext) { - update_player_login_state(ctx, false); -} - -// This helper function gets the PlayerComponent, sets the logged -// in variable and updates the PlayerComponent table row. -pub fn update_player_login_state(ctx: ReducerContext, logged_in: bool) { - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { - // We clone the PlayerComponent so we can edit it and pass it back. - let mut player = player.clone(); - player.logged_in = logged_in; - PlayerComponent::update_by_entity_id(&player.entity_id.clone(), player); - } -} -``` - -Our final reducer handles player movement. In `update_player_position` we look up the `PlayerComponent` using the user's Identity. If we don't find one, we return an error because the client should not be sending moves without calling `create_player` first. - -Using the `entity_id` in the `PlayerComponent` we retrieved, we can lookup the `EntityComponent` that stores the entity's locations in the world. We update the values passed in from the client and call the auto-generated `update` function. - -**Append to the bottom of lib.rs:** - -```rust -// Updates the position of a player. This is also called when the player stops moving. -#[spacetimedb(reducer)] -pub fn update_player_position( - ctx: ReducerContext, - position: StdbVector3, - direction: f32, - moving: bool, -) -> Result<(), String> { - // First, look up the player using the sender identity, then use that - // entity_id to retrieve and update the EntityComponent - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { - if let Some(mut entity) = EntityComponent::filter_by_entity_id(&player.entity_id) { - entity.position = position; - entity.direction = direction; - entity.moving = moving; - EntityComponent::update_by_entity_id(&player.entity_id, entity); - return Ok(()); - } - } - - // If we can not find the PlayerComponent or EntityComponent for - // this player then something went wrong. - return Err("Player not found".to_string()); -} -``` - ---- - -**Server Validation** - -In a fully developed game, the server would typically perform server-side validation on player movements to ensure they comply with game boundaries, rules, and mechanics. This validation, which we omit for simplicity in this tutorial, is essential for maintaining game integrity, preventing cheating, and ensuring a fair gaming experience. Remember to incorporate appropriate server-side validation in your game's development to ensure a secure and fair gameplay environment. - ---- - -### Publishing a Module to SpacetimeDB - -Now that we've written the code for our server module, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. - -```bash -cd server -spacetime publish -c unity-tutorial -``` - -If you get any errors from this command, double check that you correctly entered everything into `lib.rs`. You can also look at the Troubleshooting section at the end of this tutorial. - -## Updating our Unity Project to use SpacetimeDB - -Now we are ready to connect our bitcraft mini project to SpacetimeDB. - -### Import the SDK and Generate Module Files - -1. 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 -``` - -![Unity-PackageManager](/images/unity-tutorial/Unity-PackageManager.JPG) - -3. The next step is to generate the module specific client files using the SpacetimeDB CLI. The files created by this command provide an interface for retrieving values from the local client cache of the database and for registering for callbacks to events. In your terminal or command window, run the following commands. - -```bash -mkdir -p ../client/Assets/module_bindings -spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp -``` - -### Connect to Your SpacetimeDB Module - -The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in the scene. Click on the GameManager object in the scene, and in the inspector, add the `NetworkManager` component. - -![Unity-AddNetworkManager](/images/unity-tutorial/Unity-AddNetworkManager.JPG) - -Next we are going to connect to our SpacetimeDB module. Open `TutorialGameManager.cs` in your editor of choice and add the following code at the top of the file: - -**Append to the top of TutorialGameManager.cs** - -```csharp -using SpacetimeDB; -using SpacetimeDB.Types; -using System.Linq; -``` - -At the top of the class definition add the following members: - -**Append to the top of TutorialGameManager class inside of TutorialGameManager.cs** - -```csharp -// These are connection variables that are exposed on the GameManager -// inspector. -[SerializeField] private string moduleAddress = "unity-tutorial"; -[SerializeField] private string hostName = "localhost:3000"; - -// This is the identity for this player that is automatically generated -// the first time you log in. We set this variable when the -// onIdentityReceived callback is triggered by the SDK after connecting -private Identity local_identity; -``` - -The first three fields will appear in your Inspector so you can update your connection details without editing the code. The `moduleAddress` should be set to the domain you used in the publish command. You should not need to change `hostName` if you are using SpacetimeDB locally. - -Now add the following code to the `Start()` function. For clarity, replace your entire `Start()` function with the function below. - -**REPLACE the Start() function in TutorialGameManager.cs** - -```csharp -// Start is called before the first frame update -void Start() -{ - instance = this; - - SpacetimeDBClient.instance.onConnect += () => - { - Debug.Log("Connected."); - - // Request all tables - SpacetimeDBClient.instance.Subscribe(new List() - { - "SELECT * FROM *", - }); - }; - - // Called when we have an error connecting to SpacetimeDB - SpacetimeDBClient.instance.onConnectError += (error, message) => - { - Debug.LogError($"Connection error: " + message); - }; - - // Called when we are disconnected from SpacetimeDB - SpacetimeDBClient.instance.onDisconnect += (closeStatus, error) => - { - Debug.Log("Disconnected."); - }; - - // Called when we receive the client identity from SpacetimeDB - SpacetimeDBClient.instance.onIdentityReceived += (token, identity, address) => { - AuthToken.SaveToken(token); - local_identity = identity; - }; - - // Called after our local cache is populated from a Subscribe call - SpacetimeDBClient.instance.onSubscriptionApplied += OnSubscriptionApplied; - - // Now that we’ve registered all our callbacks, lets connect to spacetimedb - SpacetimeDBClient.instance.Connect(AuthToken.Token, hostName, moduleAddress); -} -``` - -In our `onConnect` callback we are calling `Subscribe` and subscribing to all data in the database. You can also subscribe to specific tables using SQL syntax like `SELECT * FROM MyTable`. Our SQL documentation enumerates the operations that are accepted in our SQL syntax. - -Subscribing to tables tells SpacetimeDB what rows we want in our local client cache. We will also not get row update callbacks or event callbacks for any reducer that does not modify a row that matches at least one of our queries. This means that events can happen on the server and the client won't be notified unless they are subscribed to at least 1 row in the change. - ---- - -**Local Client Cache** - -The "local client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. It contains the requested data which allows efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated iter and filter_by functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. - ---- - -Next we write the `OnSubscriptionApplied` callback. When this event occurs for the first time, it signifies that our local client cache is fully populated. At this point, we can verify if a player entity already exists for the corresponding user. If we do not have a player entity, we need to show the `UserNameChooser` dialog so the user can enter a username. We also put the message of the day into the chat window. Finally we unsubscribe from the callback since we only need to do this once. - -**Append after the Start() function in TutorialGameManager.cs** - -```csharp -void OnSubscriptionApplied() -{ - // If we don't have any data for our player, then we are creating a - // new one. Let's show the username dialog, which will then call the - // create player reducer - var player = PlayerComponent.FilterByOwnerId(local_identity); - if (player == null) - { - // Show username selection - UIUsernameChooser.instance.Show(); - } - - // Show the Message of the Day in our Config table of the Client Cache - UIChatController.instance.OnChatMessageReceived("Message of the Day: " + Config.FilterByVersion(0).MessageOfTheDay); - - // Now that we've done this work we can unregister this callback - SpacetimeDBClient.instance.onSubscriptionApplied -= OnSubscriptionApplied; -} -``` - -### Adding the Multiplayer Functionality - -Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `UIUsernameChooser.cs`. - -**Append to the top of UIUsernameChooser.cs** - -```csharp -using SpacetimeDB.Types; -``` - -Then we're doing a modification to the `ButtonPressed()` function: - -**Modify the ButtonPressed function in UIUsernameChooser.cs** - -```csharp -public void ButtonPressed() -{ - CameraController.RemoveDisabler(GetHashCode()); - _panel.SetActive(false); - - // Call the SpacetimeDB CreatePlayer reducer - Reducer.CreatePlayer(_usernameField.text); -} -``` - -We need to create a `RemotePlayer` script that we attach to remote player objects. In the same folder as `LocalPlayer.cs`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `EntityComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. - -First append this using to the top of `RemotePlayer.cs` - -**Create file RemotePlayer.cs, then replace its contents:** - -```csharp -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using SpacetimeDB.Types; -using TMPro; - -public class RemotePlayer : MonoBehaviour -{ - public ulong EntityId; - - public TMP_Text UsernameElement; - - public string Username { set { UsernameElement.text = value; } } - - void Start() - { - // Initialize overhead name - UsernameElement = GetComponentInChildren(); - var canvas = GetComponentInChildren(); - canvas.worldCamera = Camera.main; - - // Get the username from the PlayerComponent for this object and set it in the UI - // FilterByEntityId is normally nullable, but we'll assume not null for simplicity - PlayerComponent playerComp = PlayerComponent.FilterByEntityId(EntityId).First(); - - // Get the last location for this player and set the initial position - EntityComponent entity = EntityComponent.FilterByEntityId(EntityId); - transform.position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); - - // Register for a callback that is called when the client gets an - // update for a row in the EntityComponent table - EntityComponent.OnUpdate += EntityComponent_OnUpdate; - } -} -``` - -We now write the `EntityComponent_OnUpdate` callback which sets the movement direction in the `MovementController` for this player. We also set the target position to the current location in the latest update. - -**Append to bottom of RemotePlayer class in RemotePlayer.cs:** - -```csharp -private void EntityComponent_OnUpdate(EntityComponent oldObj, EntityComponent obj, ReducerEvent callInfo) -{ - // If the update was made to this object - if(obj.EntityId == EntityId) - { - var movementController = GetComponent(); - - // Update target position, rotation, etc. - movementController.RemoteTargetPosition = new Vector3(obj.Position.X, obj.Position.Y, obj.Position.Z); - movementController.RemoteTargetRotation = obj.Direction; - movementController.SetMoving(obj.Moving); - } -} -``` - -Next we need to handle what happens when a `PlayerComponent` is added to our local cache. We will handle it differently based on if it’s our local player entity or a remote player. We are going to register for the `OnInsert` event for our `PlayerComponent` table. Add the following code to the `Start` function in `TutorialGameManager`. - -**Append to bottom of Start() function in TutorialGameManager.cs:** - -```csharp -PlayerComponent.OnInsert += PlayerComponent_OnInsert; -``` - -Create the `PlayerComponent_OnInsert` function which does something different depending on if it's the component for the local player or a remote player. If it's the local player, we set the local player object's initial position and call `StartGame`. If it's a remote player, we instantiate a `PlayerPrefab` with the `RemotePlayer` component. The start function of `RemotePlayer` handles initializing the player position. - -**Append to bottom of TutorialGameManager class in TutorialGameManager.cs:** - -```csharp -private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo) -{ - // If the identity of the PlayerComponent matches our user identity then this is the local player - if(obj.OwnerId == local_identity) - { - // Now that we have our initial position we can start the game - StartGame(); - } - else - { - // Spawn the player object and attach the RemotePlayer component - var remotePlayer = Instantiate(PlayerPrefab); - - // Lookup and apply the position for this new player - var entity = EntityComponent.FilterByEntityId(obj.EntityId); - var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); - remotePlayer.transform.position = position; - - var movementController = remotePlayer.GetComponent(); - movementController.RemoteTargetPosition = position; - movementController.RemoteTargetRotation = entity.Direction; - - remotePlayer.AddComponent().EntityId = obj.EntityId; - } -} -``` - -Next, we will add a `FixedUpdate()` function to the `LocalPlayer` class so that we can send the local player's position to SpacetimeDB. We will do this by calling the auto-generated reducer function `Reducer.UpdatePlayerPosition(...)`. When we invoke this reducer from the client, a request is sent to SpacetimeDB and the reducer `update_player_position(...)` is executed on the server and a transaction is produced. All clients connected to SpacetimeDB will start receiving the results of these transactions. - -**Append to the top of LocalPlayer.cs** - -```csharp -using SpacetimeDB.Types; -using SpacetimeDB; -``` - -**Append to the bottom of LocalPlayer class in LocalPlayer.cs** - -```csharp -private float? lastUpdateTime; -private void FixedUpdate() -{ - float? deltaTime = Time.time - lastUpdateTime; - bool hasUpdatedRecently = deltaTime.HasValue && deltaTime.Value < 1.0f / movementUpdateSpeed; - bool isConnected = SpacetimeDBClient.instance.IsConnected(); - - if (hasUpdatedRecently || !isConnected) - { - return; - } - - lastUpdateTime = Time.time; - var p = PlayerMovementController.Local.GetModelPosition(); - - Reducer.UpdatePlayerPosition(new StdbVector3 - { - X = p.x, - Y = p.y, - Z = p.z, - }, - PlayerMovementController.Local.GetModelRotation(), - PlayerMovementController.Local.IsMoving()); -} -``` - -Finally, we need to update our connection settings in the inspector for our GameManager object in the scene. Click on the GameManager in the Hierarchy tab. The the inspector tab you should now see fields for `Module Address` and `Host Name`. Set the `Module Address` to the name you used when you ran `spacetime publish`. This is likely `unity-tutorial`. If you don't remember, you can go back to your terminal and run `spacetime publish` again from the `server` folder. - -![GameManager-Inspector2](/images/unity-tutorial/GameManager-Inspector2.JPG) - -### Play the Game! - -Go to File -> Build Settings... Replace the SampleScene with the Main scene we have been working in. - -![Unity-AddOpenScenes](/images/unity-tutorial/Unity-AddOpenScenes.JPG) - -When you hit the `Build` button, it will kick off a build of the game which will use a different identity than the Unity Editor. Create your character in the build and in the Unity Editor by entering a name and clicking `Continue`. Now you can see each other in game running around the map. +### Create the Server Module -### Implement Player Logout - -So far we have not handled the `logged_in` variable of the `PlayerComponent`. This means that remote players will not despawn on your screen when they disconnect. To fix this we need to handle the `OnUpdate` event for the `PlayerComponent` table in addition to `OnInsert`. We are going to use a common function that handles any time the `PlayerComponent` changes. - -**Append to the bottom of Start() function in TutorialGameManager.cs** -```csharp -PlayerComponent.OnUpdate += PlayerComponent_OnUpdate; -``` - -We are going to add a check to determine if the player is logged for remote players. If the player is not logged in, we search for the `RemotePlayer` object with the corresponding `EntityId` and destroy it. - -Next we'll be updating some of the code in `PlayerComponent_OnInsert`. For simplicity, just replace the entire function. - -**REPLACE PlayerComponent_OnInsert in TutorialGameManager.cs** -```csharp -private void PlayerComponent_OnUpdate(PlayerComponent oldValue, PlayerComponent newValue, ReducerEvent dbEvent) -{ - OnPlayerComponentChanged(newValue); -} - -private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent dbEvent) -{ - OnPlayerComponentChanged(obj); -} - -private void OnPlayerComponentChanged(PlayerComponent obj) -{ - // If the identity of the PlayerComponent matches our user identity then this is the local player - if(obj.OwnerId == local_identity) - { - // Now that we have our initial position we can start the game - StartGame(); - } - else - { - // otherwise we need to look for the remote player object in the scene (if it exists) and destroy it - var existingPlayer = FindObjectsOfType().FirstOrDefault(item => item.EntityId == obj.EntityId); - if (obj.LoggedIn) - { - // Only spawn remote players who aren't already spawned - if (existingPlayer == null) - { - // Spawn the player object and attach the RemotePlayer component - var remotePlayer = Instantiate(PlayerPrefab); - - // Lookup and apply the position for this new player - var entity = EntityComponent.FilterByEntityId(obj.EntityId); - var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); - remotePlayer.transform.position = position; - - var movementController = remotePlayer.GetComponent(); - movementController.RemoteTargetPosition = position; - movementController.RemoteTargetRotation = entity.Direction; - - remotePlayer.AddComponent().EntityId = obj.EntityId; - } - } - else - { - if (existingPlayer != null) - { - Destroy(existingPlayer.gameObject); - } - } - } -} -``` - -Now you when you play the game you should see remote players disappear when they log out. - -### Finally, Add Chat Support - -The project has a chat window but so far all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. - -First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.rs``. - -**Append to the bottom of server/src/lib.rs:** - -```rust -#[spacetimedb(table)] -pub struct ChatMessage { - // The primary key for this table will be auto-incremented - #[primarykey] - #[autoinc] - pub message_id: u64, - - // The entity id of the player that sent the message - pub sender_id: u64, - // Message contents - pub text: String, -} -``` - -Now we need to add a reducer to handle inserting new chat messages. - -**Append to the bottom of server/src/lib.rs:** - -```rust -// Adds a chat entry to the ChatMessage table -#[spacetimedb(reducer)] -pub fn send_chat_message(ctx: ReducerContext, text: String) -> Result<(), String> { - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { - // Now that we have the player we can insert the chat message using the player entity id. - ChatMessage::insert(ChatMessage { - // this column auto-increments so we can set it to 0 - message_id: 0, - sender_id: player.entity_id, - text, - }) - .unwrap(); - - return Ok(()); - } - - Err("Player not found".into()) -} -``` - -Before updating the client, let's generate the client files and update publish our module. - -**Execute commands in the server/ directory** -```bash -spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp -spacetime publish -c unity-tutorial -``` - -On the client, let's add code to send the message when the chat button or enter is pressed. Update the `OnChatButtonPress` function in `UIChatController.cs`. - -**Append to the top of UIChatController.cs:** -```csharp -using SpacetimeDB.Types; -``` - -**REPLACE the OnChatButtonPress function in UIChatController.cs:** - -```csharp -public void OnChatButtonPress() -{ - Reducer.SendChatMessage(_chatInput.text); - _chatInput.text = ""; -} -``` - -Now we need to add a reducer to handle inserting new chat messages. First register for the ChatMessage reducer in the `Start()` function using the auto-generated function: - -**Append to the bottom of the Start() function in TutorialGameManager.cs:** -```csharp -Reducer.OnSendChatMessageEvent += OnSendChatMessageEvent; -``` - -Now we write the `OnSendChatMessageEvent` function. We can find the `PlayerComponent` for the player who sent the message using the `Identity` of the sender. Then we get the `Username` and prepend it to the message before sending it to the chat window. - -**Append after the Start() function in TutorialGameManager.cs** -```csharp -private void OnSendChatMessageEvent(ReducerEvent dbEvent, string message) -{ - var player = PlayerComponent.FilterByOwnerId(dbEvent.Identity); - if (player != null) - { - UIChatController.instance.OnChatMessageReceived(player.Username + ": " + message); - } -} -``` - -Now when you run the game you should be able to send chat messages to other players. Be sure to make a new Unity client build and run it in a separate window so you can test chat between two clients. - -## Conclusion - -This concludes the first part of the tutorial. We've learned about the basics of SpacetimeDB and how to use it to create a multiplayer game. In the next part of the tutorial we will add resource nodes to the game and learn about scheduled reducers. - ---- - -### Troubleshooting - -- If you get an error when running the generate command, make sure you have an empty subfolder in your Unity project Assets folder called `module_bindings` - -- If you get this exception when running the project: - -``` -NullReferenceException: Object reference not set to an instance of an object -TutorialGameManager.Start () (at Assets/_Project/Game/TutorialGameManager.cs:26) -``` - -Check to see if your GameManager object in the Scene has the NetworkManager component attached. - -- If you get an error in your Unity console when starting the game, double check your connection settings in the Inspector for the `GameManager` object in the scene. - -``` -Connection error: Unable to connect to the remote server -``` +From here, the tutorial continues with your favorite server module language of choice: + - [Rust](part-2a-rust.md) + - [C#](part-2b-csharp.md) diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md new file mode 100644 index 00000000..9b12de47 --- /dev/null +++ b/docs/unity/part-2a-rust.md @@ -0,0 +1,312 @@ +# Unity Tutorial - Basic Multiplayer - Part 2a - Server Module (Rust) + +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! + +This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1.md) + +## Create a Server Module + +Run the following command to initialize the SpacetimeDB server module project with Rust as the language: + +```bash +spacetime init --lang=rust server +``` + +This command creates a new folder named "server" within your Unity project directory and sets up the SpacetimeDB server project with Rust as the programming language. + +### SpacetimeDB Tables + +In this section we'll be making some edits to the file `server/src/lib.rs`. We recommend you open up this file in an IDE like VSCode or RustRover. + +**Important: Open the `server/src/lib.rs` file and delete its contents. We will be writing it from scratch here.** + +First we need to add some imports at the top of the file. + +**Copy and paste into lib.rs:** + +```rust +use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; +use log; +``` + +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [Rust module reference](/docs/modules/rust). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. + +**Append to the bottom of lib.rs:** + +```rust +// We're using this table as a singleton, so there should typically only be one element where the version is 0. +#[spacetimedb(table)] +#[derive(Clone)] +pub struct Config { + #[primarykey] + pub version: u32, + pub message_of_the_day: String, +} +``` + +Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. + +**Append to the bottom of lib.rs:** + +```rust +// This allows us to store 3D points in tables. +#[derive(SpacetimeType, Clone)] +pub struct StdbVector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} +``` + +Now we're going to create a table which actually uses the `StdbVector3` that we just defined. The `EntityComponent` is associated with all entities in the world, including players. + +```rust +// This stores information related to all entities in our game. In this tutorial +// all entities must at least have an entity_id, a position, a direction and they +// must specify whether or not they are moving. +#[spacetimedb(table)] +#[derive(Clone)] +pub struct EntityComponent { + #[primarykey] + // The autoinc macro here just means every time we insert into this table + // we will receive a new row where this value will be increased by one. This + // allows us to easily get rows where `entity_id` is unique. + #[autoinc] + pub entity_id: u64, + pub position: StdbVector3, + pub direction: f32, + pub moving: bool, +} +``` + +Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is used to store information related to players. Each player will have a row in this table, and will also have a row in the `EntityComponent` table with a matching `entity_id`. You'll see how this works later in the `create_player` reducer. + +**Append to the bottom of lib.rs:** + +```rust +// All players have this component and it associates an entity with the user's +// Identity. It also stores their username and whether or not they're logged in. +#[derive(Clone)] +#[spacetimedb(table)] +pub struct PlayerComponent { + // An entity_id that matches an entity_id in the `EntityComponent` table. + #[primarykey] + pub entity_id: u64, + + // The user's identity, which is unique to each player + #[unique] + pub owner_id: Identity, + pub username: String, + pub logged_in: bool, +} +``` + +Next, we write our very first reducer, `create_player`. From the client we will call this reducer when we create a new player: + +**Append to the bottom of lib.rs:** + +```rust +// This reducer is called when the user logs in for the first time and +// enters a username +#[spacetimedb(reducer)] +pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String> { + // Get the Identity of the client who called this reducer + let owner_id = ctx.sender; + + // Make sure we don't already have a player with this identity + if PlayerComponent::filter_by_owner_id(&owner_id).is_some() { + log::info!("Player already exists"); + return Err("Player already exists".to_string()); + } + + // Create a new entity for this player and get a unique `entity_id`. + let entity_id = EntityComponent::insert(EntityComponent + { + entity_id: 0, + position: StdbVector3 { x: 0.0, y: 0.0, z: 0.0 }, + direction: 0.0, + moving: false, + }).expect("Failed to create a unique PlayerComponent.").entity_id; + + // The PlayerComponent uses the same entity_id and stores the identity of + // the owner, username, and whether or not they are logged in. + PlayerComponent::insert(PlayerComponent { + entity_id, + owner_id, + username: username.clone(), + logged_in: true, + }).expect("Failed to insert player component."); + + log::info!("Player created: {}({})", username, entity_id); + + Ok(()) +} +``` + +--- + +**SpacetimeDB Reducers** + +"Reducer" is a term coined by Clockwork Labs that refers to a function which when executed "reduces" into a list of inserts and deletes, which is then packed into a single database transaction. 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. + +--- + +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. We'll learn about publishing later. +- `connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `sender` value of the `ReducerContext`. +- `disconnect` - Called when a user disconnects from the SpacetimeDB module. + +Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config.FilterByVersion(0)`. + +**Append to the bottom of lib.rs:** + +```rust +// Called when the module is initially published +#[spacetimedb(init)] +pub fn init() { + Config::insert(Config { + version: 0, + message_of_the_day: "Hello, World!".to_string(), + }).expect("Failed to insert config."); +} +``` + +We use the `connect` and `disconnect` reducers to update the logged in state of the player. The `update_player_login_state` helper function looks up the `PlayerComponent` row using the user's identity and if it exists, it updates the `logged_in` variable and calls the auto-generated `update` function on `PlayerComponent` to update the row. + +**Append to the bottom of lib.rs:** + +```rust +// Called when the client connects, we update the logged_in state to true +#[spacetimedb(connect)] +pub fn client_connected(ctx: ReducerContext) { + update_player_login_state(ctx, true); +} +``` +```rust +// Called when the client disconnects, we update the logged_in state to false +#[spacetimedb(disconnect)] +pub fn client_disconnected(ctx: ReducerContext) { + update_player_login_state(ctx, false); +} +``` +```rust +// This helper function gets the PlayerComponent, sets the logged +// in variable and updates the PlayerComponent table row. +pub fn update_player_login_state(ctx: ReducerContext, logged_in: bool) { + if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + // We clone the PlayerComponent so we can edit it and pass it back. + let mut player = player.clone(); + player.logged_in = logged_in; + PlayerComponent::update_by_entity_id(&player.entity_id.clone(), player); + } +} +``` + +Our final reducer handles player movement. In `update_player_position` we look up the `PlayerComponent` using the user's Identity. If we don't find one, we return an error because the client should not be sending moves without calling `create_player` first. + +Using the `entity_id` in the `PlayerComponent` we retrieved, we can lookup the `EntityComponent` that stores the entity's locations in the world. We update the values passed in from the client and call the auto-generated `update` function. + +**Append to the bottom of lib.rs:** + +```rust +// Updates the position of a player. This is also called when the player stops moving. +#[spacetimedb(reducer)] +pub fn update_player_position( + ctx: ReducerContext, + position: StdbVector3, + direction: f32, + moving: bool, +) -> Result<(), String> { + // First, look up the player using the sender identity, then use that + // entity_id to retrieve and update the EntityComponent + if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + if let Some(mut entity) = EntityComponent::filter_by_entity_id(&player.entity_id) { + entity.position = position; + entity.direction = direction; + entity.moving = moving; + EntityComponent::update_by_entity_id(&player.entity_id, entity); + return Ok(()); + } + } + + // If we can not find the PlayerComponent or EntityComponent for + // this player then something went wrong. + return Err("Player not found".to_string()); +} +``` + +--- + +**Server Validation** + +In a fully developed game, the server would typically perform server-side validation on player movements to ensure they comply with game boundaries, rules, and mechanics. This validation, which we omit for simplicity in this tutorial, is essential for maintaining game integrity, preventing cheating, and ensuring a fair gaming experience. Remember to incorporate appropriate server-side validation in your game's development to ensure a secure and fair gameplay environment. + +--- + +### Publishing a Module to SpacetimeDB + +Now that we've written the code for our server module and reached a clean checkpoint, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. + +```bash +cd server +spacetime publish -c unity-tutorial +``` + +### Finally, Add Chat Support + +The client project has a chat window, but so far, all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. + +First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.rs``. + +**Append to the bottom of server/src/lib.rs:** + +```rust +#[spacetimedb(table)] +pub struct ChatMessage { + // The primary key for this table will be auto-incremented + #[primarykey] + #[autoinc] + pub message_id: u64, + + // The entity id of the player that sent the message + pub sender_id: u64, + // Message contents + pub text: String, +} +``` + +Now we need to add a reducer to handle inserting new chat messages. + +**Append to the bottom of server/src/lib.rs:** + +```rust +// Adds a chat entry to the ChatMessage table +#[spacetimedb(reducer)] +pub fn send_chat_message(ctx: ReducerContext, text: String) -> Result<(), String> { + if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + // Now that we have the player we can insert the chat message using the player entity id. + ChatMessage::insert(ChatMessage { + // this column auto-increments so we can set it to 0 + message_id: 0, + sender_id: player.entity_id, + text, + }) + .unwrap(); + + return Ok(()); + } + + Err("Player not found".into()) +} +``` + +## Wrapping Up + +Now that we added chat support, let's publish the latest module version to SpacetimeDB, assuming we're still in the `server` dir: + +```bash +spacetime publish -c unity-tutorial +``` + +From here, the [next tutorial](/docs/unity/part-3.md) continues with a Client (Unity) focus. diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md new file mode 100644 index 00000000..f324a36d --- /dev/null +++ b/docs/unity/part-2b-c-sharp.md @@ -0,0 +1,344 @@ +# Unity Tutorial - Basic Multiplayer - Part 2a - Server Module (C#) + +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! + +This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1.md) + +## Create a Server Module + +Run the following command to initialize the SpacetimeDB server module project with C# as the language: + +```bash +spacetime init --lang=csharp server +``` + +This command creates a new folder named "server" within your Unity project directory and sets up the SpacetimeDB server project with C# as the programming language. + +### SpacetimeDB Tables + +In this section we'll be making some edits to the file `server/src/lib.cs`. We recommend you open up this file in an IDE like VSCode. + +**Important: Open the `server/src/lib.cs` file and delete its contents. We will be writing it from scratch here.** + +First we need to add some imports at the top of the file. + +**Copy and paste into lib.cs:** + +```csharp +// using SpacetimeDB; // Uncomment to omit `SpacetimeDB` attribute prefixes +using SpacetimeDB.Module; +using static SpacetimeDB.Runtime; +``` + +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. + +**Append to the bottom of lib.cs:** + +```csharp +/// We're using this table as a singleton, +/// so there should typically only be one element where the version is 0. +[SpacetimeDB.Table] +public partial class Config +{ + [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] + public Identity Version; + public string? MessageOfTheDay; +} +``` + +Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. + +**Append to the bottom of lib.cs:** + +```csharp +/// This allows us to store 3D points in tables. +[SpacetimeDB.Type] +public partial class StdbVector3 +{ + public float X; + public float Y; + public float Z; +} +``` + +Now we're going to create a table which actually uses the `StdbVector3` that we just defined. The `EntityComponent` is associated with all entities in the world, including players. + +```csharp +/// This stores information related to all entities in our game. In this tutorial +/// all entities must at least have an entity_id, a position, a direction and they +/// must specify whether or not they are moving. +[SpacetimeDB.Table] +public partial class EntityComponent +{ + [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] + public ulong EntityId; + public StdbVector3 Position; + public float Direction; + public bool Moving; +} +``` + +Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is used to store information related to players. Each player will have a row in this table, and will also have a row in the `EntityComponent` table with a matching `EntityId`. You'll see how this works later in the `CreatePlayer` reducer. + +**Append to the bottom of lib.cs:** + +```csharp +/// All players have this component and it associates an entity with the user's +/// Identity. It also stores their username and whether or not they're logged in. +[SpacetimeDB.Table] +public partial class PlayerComponent +{ + // An EntityId that matches an EntityId in the `EntityComponent` table. + [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] + public ulong EntityId; + + // The user's identity, which is unique to each player + [SpacetimeDB.Column(ColumnAttrs.Unique)] + public Identity Identity; + public string? Username; + public bool LoggedIn; +} +``` + +Next, we write our very first reducer, `CreatePlayer`. From the client we will call this reducer when we create a new player: + +**Append to the bottom of lib.cs:** + +```csharp +/// This reducer is called when the user logs in for the first time and +/// enters a username. +[SpacetimeDB.Reducer] +public static void CreatePlayer(DbEventArgs dbEvent, string username) +{ + // Get the Identity of the client who called this reducer + Identity sender = dbEvent.Sender; + + // Make sure we don't already have a player with this identity + PlayerComponent? user = PlayerComponent.FindByIdentity(sender); + if (user is null) + { + throw new ArgumentException("Player already exists"); + } + + // Create a new entity for this player + try + { + new EntityComponent + { + // EntityId = 0, // 0 is the same as leaving null to get a new, unique Id + Position = new StdbVector3 { X = 0, Y = 0, Z = 0 }, + Direction = 0, + Moving = false, + }.Insert(); + } + catch + { + Log("Error: Failed to create a unique PlayerComponent", LogLevel.Error); + Throw; + } + + // The PlayerComponent uses the same entity_id and stores the identity of + // the owner, username, and whether or not they are logged in. + try + { + new PlayerComponent + { + // EntityId = 0, // 0 is the same as leaving null to get a new, unique Id + Identity = dbEvent.Sender, + Username = username, + LoggedIn = true, + }.Insert(); + } + catch + { + Log("Error: Failed to insert PlayerComponent", LogLevel.Error); + throw; + } + Log($"Player created: {username}"); +} +``` + +--- + +**SpacetimeDB Reducers** + +"Reducer" is a term coined by Clockwork Labs that refers to a function which when executed "reduces" into a list of inserts and deletes, which is then packed into a single database transaction. 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. + +--- + +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. We'll learn about publishing later. +- `Connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `Sender` value of the `ReducerContext`. +- `Disconnect` - Called when a user disconnects from the SpacetimeDB module. + +Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config.FilterByVersion(0)`. + +**Append to the bottom of lib.cs:** + +```csharp +/// Called when the module is initially published +[SpacetimeDB.Reducer(ReducerKind.Init)] +public static void OnInit() +{ + try + { + new Config + { + Version = 0, + MessageOfTheDay = "Hello, World!", + }.Insert(); + } + catch + { + Log("Error: Failed to insert Config", LogLevel.Error); + throw; + } +} +``` + +We use the `Connect` and `Disconnect` reducers to update the logged in state of the player. The `UpdatePlayerLoginState` helper function we are about to define looks up the `PlayerComponent` row using the user's identity and if it exists, it updates the `LoggedIn` variable and calls the auto-generated `Update` function on `PlayerComponent` to update the row. + +**Append to the bottom of lib.cs:** + +```csharp +/// Called when the client connects, we update the LoggedIn state to true +[SpacetimeDB.Reducer(ReducerKind.Init)] +public static void ClientConnected(DbEventArgs dbEvent) => + UpdatePlayerLoginState(dbEvent, loggedIn:true); +``` +```csharp +/// Called when the client disconnects, we update the logged_in state to false +[SpacetimeDB.Reducer(ReducerKind.Disconnect)] +public static void ClientDisonnected(DbEventArgs dbEvent) => + UpdatePlayerLoginState(dbEvent, loggedIn:false); +``` +```csharp +/// This helper function gets the PlayerComponent, sets the LoggedIn +/// variable and updates the PlayerComponent table row. +private static void UpdatePlayerLoginState(DbEventArgs dbEvent, bool loggedIn) +{ + PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + if (player is null) + { + throw new ArgumentException("Player not found"); + } + + player.LoggedIn = loggedIn; + PlayerComponent.UpdateByIdentity(dbEvent.Sender, player); +} +``` + +Our final reducer handles player movement. In `UpdatePlayerPosition` we look up the `PlayerComponent` using the user's Identity. If we don't find one, we return an error because the client should not be sending moves without calling `CreatePlayer` first. + +Using the `EntityId` in the `PlayerComponent` we retrieved, we can lookup the `EntityComponent` that stores the entity's locations in the world. We update the values passed in from the client and call the auto-generated `Update` function. + +**Append to the bottom of lib.cs:** + +```csharp +/// Updates the position of a player. This is also called when the player stops moving. +[SpacetimeDB.Reducer] +private static void UpdatePlayerPosition( + DbEventArgs dbEvent, + StdbVector3 position, + float direction, + bool moving) +{ + // First, look up the player using the sender identity + PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + if (player is null) + { + throw new ArgumentException("Player not found"); + } + // Use the Player's EntityId to retrieve and update the EntityComponent + ulong playerEntityId = player.EntityId; + EntityComponent? entity = EntityComponent.FindByEntityId(playerEntityId); + if (entity is null) + { + throw new ArgumentException($"Player Entity '{playerEntityId}' not found"); + } + + entity.Position = position; + entity.Direction = direction; + entity.Moving = moving; + EntityComponent.UpdateByEntityId(playerEntityId, entity); +} +``` + +--- + +**Server Validation** + +In a fully developed game, the server would typically perform server-side validation on player movements to ensure they comply with game boundaries, rules, and mechanics. This validation, which we omit for simplicity in this tutorial, is essential for maintaining game integrity, preventing cheating, and ensuring a fair gaming experience. Remember to incorporate appropriate server-side validation in your game's development to ensure a secure and fair gameplay environment. + +--- + +### Publishing a Module to SpacetimeDB + +Now that we've written the code for our server module and reached a clean checkpoint, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. + +```bash +cd server +spacetime publish -c unity-tutorial +``` + +### Finally, Add Chat Support + +The client project has a chat window, but so far, all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. + +First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.cs``. + +**Append to the bottom of server/src/lib.cs:** + +```csharp +[SpacetimeDB.Table] +public partial class ChatMessage +{ + // The primary key for this table will be auto-incremented + [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] + + // The entity id of the player that sent the message + public ulong SenderId; + + // Message contents + public string? Text; +} +``` + +Now we need to add a reducer to handle inserting new chat messages. + +**Append to the bottom of server/src/lib.cs:** + +```csharp +/// Adds a chat entry to the ChatMessage table +[SpacetimeDB.Reducer] +public static void SendChatMessage(DbEventArgs dbEvent, string text) +{ + // Get the player's entity id + PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + if (player is null) + { + throw new ArgumentException("Player not found"); + } + + + // Insert the chat message + new ChatMessage + { + SenderId = player.EntityId, + Text = text, + }.Insert(); +} +``` + +## Wrapping Up + +💡View the [entire lib.cs file](https://gist.github.com/dylanh724/68067b4e843ea6e99fbd297fe1a87c49) + +Now that we added chat support, let's publish the latest module version to SpacetimeDB, assuming we're still in the `server` dir: + +```bash +spacetime publish -c unity-tutorial +``` + +From here, the [next tutorial](/docs/unity/part-3.md) continues with a Client (Unity) focus. \ No newline at end of file diff --git a/docs/unity/part-3.md b/docs/unity/part-3.md index b49b5a5d..c80000e1 100644 --- a/docs/unity/part-3.md +++ b/docs/unity/part-3.md @@ -1,104 +1,479 @@ -# Part 3 - BitCraft Mini +# Unity Tutorial - Basic Multiplayer - Part 3 - Client -**Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -BitCraft Mini is a game that we developed which extends the code you've already developed in this tutorial. It is inspired by our game [BitCraft](https://bitcraftonline.com) and illustrates how you could build a more complex game from just the components we've discussed. Right now you can walk around, mine ore, and manage your inventory. +This progressive tutorial is continued from one of the Part 2 tutorials: +- [Rust Server Module](/docs/unity/part-2a-rust.md) +- [C# Server Module](/docs/unity/part-2b-c-sharp.md) -## 1. Download +## Updating our Unity Project Client to use SpacetimeDB -You can git-clone BitCraftMini from here: +Now we are ready to connect our _BitCraft Mini_ project to SpacetimeDB. -```plaintext -git clone ssh://git@github.com/clockworklabs/BitCraftMini +### Import the SDK and Generate Module Files + +1. 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 ``` -Once you have downloaded BitCraftMini, you will need to compile the spacetime module. +![Unity-PackageManager](/images/unity-tutorial/Unity-PackageManager.JPG) -## 2. Compile the Spacetime Module +3. The next step is to generate the module specific client files using the SpacetimeDB CLI. The files created by this command provide an interface for retrieving values from the local client cache of the database and for registering for callbacks to events. In your terminal or command window, run the following commands. -In order to compile the BitCraftMini module, you will need to install cargo. You can install cargo from here: +```bash +mkdir -p ../client/Assets/module_bindings +spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp +``` -> https://www.rust-lang.org/tools/install +### Connect to Your SpacetimeDB Module -Once you have cargo installed, you can compile and publish the module with these commands: +The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in the scene. Click on the GameManager object in the scene, and in the inspector, add the `NetworkManager` component. -```bash -cd BitCraftMini/Server -spacetime publish +![Unity-AddNetworkManager](/images/unity-tutorial/Unity-AddNetworkManager.JPG) + +Next we are going to connect to our SpacetimeDB module. Open `TutorialGameManager.cs` in your editor of choice and add the following code at the top of the file: + +**Append to the top of TutorialGameManager.cs** + +```csharp +using SpacetimeDB; +using SpacetimeDB.Types; +using System.Linq; ``` -`spacetime publish` will output an address where your module has been deployed to. You will want to copy/save this address because you will need it in step 3. Here is an example of what it should look like: +At the top of the class definition add the following members: -```plaintext -$ spacetime publish -info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date - Finished release [optimized] target(s) in 0.03s -Publish finished successfully. -Created new database with address: c91c17ecdcea8a05302be2bad9dd59b3 +**Append to the top of TutorialGameManager class inside of TutorialGameManager.cs** + +```csharp +// These are connection variables that are exposed on the GameManager +// inspector. +[SerializeField] private string moduleAddress = "unity-tutorial"; +[SerializeField] private string hostName = "localhost:3000"; + +// This is the identity for this player that is automatically generated +// the first time you log in. We set this variable when the +// onIdentityReceived callback is triggered by the SDK after connecting +private Identity local_identity; ``` -Optionally, you can specify a name when you publish the module: +The first three fields will appear in your Inspector so you can update your connection details without editing the code. The `moduleAddress` should be set to the domain you used in the publish command. You should not need to change `hostName` if you are using SpacetimeDB locally. + +Now add the following code to the `Start()` function. For clarity, replace your entire `Start()` function with the function below. + +**REPLACE the Start() function in TutorialGameManager.cs** + +```csharp +// Start is called before the first frame update +void Start() +{ + instance = this; + + SpacetimeDBClient.instance.onConnect += () => + { + Debug.Log("Connected."); + + // Request all tables + SpacetimeDBClient.instance.Subscribe(new List() + { + "SELECT * FROM *", + }); + }; + + // Called when we have an error connecting to SpacetimeDB + SpacetimeDBClient.instance.onConnectError += (error, message) => + { + Debug.LogError($"Connection error: " + message); + }; + + // Called when we are disconnected from SpacetimeDB + SpacetimeDBClient.instance.onDisconnect += (closeStatus, error) => + { + Debug.Log("Disconnected."); + }; + + // Called when we receive the client identity from SpacetimeDB + SpacetimeDBClient.instance.onIdentityReceived += (token, identity, address) => { + AuthToken.SaveToken(token); + local_identity = identity; + }; + + // Called after our local cache is populated from a Subscribe call + SpacetimeDBClient.instance.onSubscriptionApplied += OnSubscriptionApplied; + + // Now that we’ve registered all our callbacks, lets connect to spacetimedb + SpacetimeDBClient.instance.Connect(AuthToken.Token, hostName, moduleAddress); +} +``` -```bash -spacetime publish "unique-module-name" +In our `onConnect` callback we are calling `Subscribe` and subscribing to all data in the database. You can also subscribe to specific tables using SQL syntax like `SELECT * FROM MyTable`. Our SQL documentation enumerates the operations that are accepted in our SQL syntax. + +Subscribing to tables tells SpacetimeDB what rows we want in our local client cache. We will also not get row update callbacks or event callbacks for any reducer that does not modify a row that matches at least one of our queries. This means that events can happen on the server and the client won't be notified unless they are subscribed to at least 1 row in the change. + +--- + +**Local Client Cache** + +The "local client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. It contains the requested data which allows efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated iter and filter_by functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. + +--- + +Next we write the `OnSubscriptionApplied` callback. When this event occurs for the first time, it signifies that our local client cache is fully populated. At this point, we can verify if a player entity already exists for the corresponding user. If we do not have a player entity, we need to show the `UserNameChooser` dialog so the user can enter a username. We also put the message of the day into the chat window. Finally we unsubscribe from the callback since we only need to do this once. + +**Append after the Start() function in TutorialGameManager.cs** + +```csharp +void OnSubscriptionApplied() +{ + // If we don't have any data for our player, then we are creating a + // new one. Let's show the username dialog, which will then call the + // create player reducer + var player = PlayerComponent.FilterByOwnerId(local_identity); + if (player == null) + { + // Show username selection + UIUsernameChooser.instance.Show(); + } + + // Show the Message of the Day in our Config table of the Client Cache + UIChatController.instance.OnChatMessageReceived("Message of the Day: " + Config.FilterByVersion(0).MessageOfTheDay); + + // Now that we've done this work we can unregister this callback + SpacetimeDBClient.instance.onSubscriptionApplied -= OnSubscriptionApplied; +} ``` -Currently, all the named modules exist in the same namespace so if you get a message saying that database is not owned by you, it means that someone else has already published a module with that name. You can either choose a different name or you can use the address instead. If you specify a name when you publish, you can use that name in place of the autogenerated address in both the CLI and in the Unity client. +### Adding the Multiplayer Functionality -In the BitCraftMini module we have a function called `initialize()`. This function should be called immediately after publishing the module to spacetimedb. This function is in charge of generating some initial settings that are required for the server to operate. You can call this function like so: +Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `UIUsernameChooser.cs`. -```bash -spacetime call "" "initialize" "[]" +**Append to the top of UIUsernameChooser.cs** + +```csharp +using SpacetimeDB.Types; ``` -Here we are telling spacetime to invoke the `initialize()` function on our module "bitcraftmini". If the function had some arguments, we would json encode them and put them into the "[]". Since `initialize()` requires no parameters, we just leave it empty. +Then we're doing a modification to the `ButtonPressed()` function: -After you have called `initialize()` on the spacetime module you shouldgenerate the client files: +**Modify the ButtonPressed function in UIUsernameChooser.cs** -```bash -spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang=cs +```csharp +public void ButtonPressed() +{ + CameraController.RemoveDisabler(GetHashCode()); + _panel.SetActive(false); + + // Call the SpacetimeDB CreatePlayer reducer + Reducer.CreatePlayer(_usernameField.text); +} +``` + +We need to create a `RemotePlayer` script that we attach to remote player objects. In the same folder as `LocalPlayer.cs`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `EntityComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. + +First append this using to the top of `RemotePlayer.cs` + +**Create file RemotePlayer.cs, then replace its contents:** + +```csharp +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using SpacetimeDB.Types; +using TMPro; + +public class RemotePlayer : MonoBehaviour +{ + public ulong EntityId; + + public TMP_Text UsernameElement; + + public string Username { set { UsernameElement.text = value; } } + + void Start() + { + // Initialize overhead name + UsernameElement = GetComponentInChildren(); + var canvas = GetComponentInChildren(); + canvas.worldCamera = Camera.main; + + // Get the username from the PlayerComponent for this object and set it in the UI + PlayerComponent? playerComp = PlayerComponent.FilterByEntityId(EntityId).FirstOrDefault(); + if (playerComp is null) + { + string inputUsername = UsernameElement.Text; + Debug.Log($"PlayerComponent not found - Creating a new player ({inputUsername})"); + Reducer.CreatePlayer(inputUsername); + + // Try again, optimistically assuming success for simplicity + PlayerComponent? playerComp = PlayerComponent.FilterByEntityId(EntityId).FirstOrDefault(); + } + + Username = playerComp.Username; + + // Get the last location for this player and set the initial position + EntityComponent entity = EntityComponent.FilterByEntityId(EntityId); + transform.position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); + + // Register for a callback that is called when the client gets an + // update for a row in the EntityComponent table + EntityComponent.OnUpdate += EntityComponent_OnUpdate; + } +} +``` + +We now write the `EntityComponent_OnUpdate` callback which sets the movement direction in the `MovementController` for this player. We also set the target position to the current location in the latest update. + +**Append to bottom of RemotePlayer class in RemotePlayer.cs:** + +```csharp +private void EntityComponent_OnUpdate(EntityComponent oldObj, EntityComponent obj, ReducerEvent callInfo) +{ + // If the update was made to this object + if(obj.EntityId == EntityId) + { + var movementController = GetComponent(); + + // Update target position, rotation, etc. + movementController.RemoteTargetPosition = new Vector3(obj.Position.X, obj.Position.Y, obj.Position.Z); + movementController.RemoteTargetRotation = obj.Direction; + movementController.SetMoving(obj.Moving); + } +} +``` + +Next we need to handle what happens when a `PlayerComponent` is added to our local cache. We will handle it differently based on if it’s our local player entity or a remote player. We are going to register for the `OnInsert` event for our `PlayerComponent` table. Add the following code to the `Start` function in `TutorialGameManager`. + +**Append to bottom of Start() function in TutorialGameManager.cs:** + +```csharp +PlayerComponent.OnInsert += PlayerComponent_OnInsert; +``` + +Create the `PlayerComponent_OnInsert` function which does something different depending on if it's the component for the local player or a remote player. If it's the local player, we set the local player object's initial position and call `StartGame`. If it's a remote player, we instantiate a `PlayerPrefab` with the `RemotePlayer` component. The start function of `RemotePlayer` handles initializing the player position. + +**Append to bottom of TutorialGameManager class in TutorialGameManager.cs:** + +```csharp +private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo) +{ + // If the identity of the PlayerComponent matches our user identity then this is the local player + if(obj.OwnerId == local_identity) + { + // Now that we have our initial position we can start the game + StartGame(); + } + else + { + // Spawn the player object and attach the RemotePlayer component + var remotePlayer = Instantiate(PlayerPrefab); + + // Lookup and apply the position for this new player + var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); + remotePlayer.transform.position = position; + + var movementController = remotePlayer.GetComponent(); + movementController.RemoteTargetPosition = position; + movementController.RemoteTargetRotation = entity.Direction; + + remotePlayer.AddComponent().EntityId = obj.EntityId; + } +} ``` -Here is some sample output: +Next, we will add a `FixedUpdate()` function to the `LocalPlayer` class so that we can send the local player's position to SpacetimeDB. We will do this by calling the auto-generated reducer function `Reducer.UpdatePlayerPosition(...)`. When we invoke this reducer from the client, a request is sent to SpacetimeDB and the reducer `update_player_position(...)` (Rust) or `UpdatePlayerPosition(...)` (C#) is executed on the server and a transaction is produced. All clients connected to SpacetimeDB will start receiving the results of these transactions. -```plaintext -$ spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang cs -info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date - Finished release [optimized] target(s) in 0.03s -compilation took 234.613518ms -Generate finished successfully. +**Append to the top of LocalPlayer.cs** + +```csharp +using SpacetimeDB.Types; +using SpacetimeDB; ``` -If you've gotten this message then everything should be working properly so far. +**Append to the bottom of LocalPlayer class in LocalPlayer.cs** + +```csharp +private float? lastUpdateTime; +private void FixedUpdate() +{ + float? deltaTime = Time.time - lastUpdateTime; + bool hasUpdatedRecently = deltaTime.HasValue && deltaTime.Value < 1.0f / movementUpdateSpeed; + bool isConnected = SpacetimeDBClient.instance.IsConnected(); + + if (hasUpdatedRecently || !isConnected) + { + return; + } + + lastUpdateTime = Time.time; + var p = PlayerMovementController.Local.GetModelPosition(); + + Reducer.UpdatePlayerPosition(new StdbVector3 + { + X = p.x, + Y = p.y, + Z = p.z, + }, + PlayerMovementController.Local.GetModelRotation(), + PlayerMovementController.Local.IsMoving()); +} +``` -## 3. Replace address in BitCraftMiniGameManager +Finally, we need to update our connection settings in the inspector for our GameManager object in the scene. Click on the GameManager in the Hierarchy tab. The the inspector tab you should now see fields for `Module Address` and `Host Name`. Set the `Module Address` to the name you used when you ran `spacetime publish`. This is likely `unity-tutorial`. If you don't remember, you can go back to your terminal and run `spacetime publish` again from the `server` folder. -The following settings are exposed in the `BitCraftMiniGameManager` inspector: Module Address, Host Name, and SSL Enabled. +![GameManager-Inspector2](/images/unity-tutorial/GameManager-Inspector2.JPG) -Open the Main scene in Unity and click on the `GameManager` object in the heirarchy. The inspector window will look like this: +### Play the Game! -![GameManager-Inspector](/images/unity-tutorial/GameManager-Inspector.JPG) +Go to File -> Build Settings... Replace the SampleScene with the Main scene we have been working in. -Update the module address with the address you got from the `spacetime publish` command. If you are using SpacetimeDB Cloud `testnet`, the host name should be `testnet.spacetimedb.com` and SSL Enabled should be checked. If you are running SpacetimeDB Standalone locally, the host name should be `localhost:3000` and SSL Enabled should be unchecked. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/deploying/testnet) +![Unity-AddOpenScenes](/images/unity-tutorial/Unity-AddOpenScenes.JPG) -## 4. Play Mode +When you hit the `Build` button, it will kick off a build of the game which will use a different identity than the Unity Editor. Create your character in the build and in the Unity Editor by entering a name and clicking `Continue`. Now you can see each other in game running around the map. -You should now be able to enter play mode and walk around! You can mine some rocks, cut down some trees and if you connect more clients you can trade with other players. +### Implement Player Logout -## 5. Editing the Module +So far we have not handled the `logged_in` variable of the `PlayerComponent`. This means that remote players will not despawn on your screen when they disconnect. To fix this we need to handle the `OnUpdate` event for the `PlayerComponent` table in addition to `OnInsert`. We are going to use a common function that handles any time the `PlayerComponent` changes. -If you want to make further updates to the module, make sure to use this publish command instead: +**Append to the bottom of Start() function in TutorialGameManager.cs** +```csharp +PlayerComponent.OnUpdate += PlayerComponent_OnUpdate; +``` -```bash -spacetime publish +We are going to add a check to determine if the player is logged for remote players. If the player is not logged in, we search for the `RemotePlayer` object with the corresponding `EntityId` and destroy it. + +Next we'll be updating some of the code in `PlayerComponent_OnInsert`. For simplicity, just replace the entire function. + +**REPLACE PlayerComponent_OnInsert in TutorialGameManager.cs** +```csharp +private void PlayerComponent_OnUpdate(PlayerComponent oldValue, PlayerComponent newValue, ReducerEvent dbEvent) +{ + OnPlayerComponentChanged(newValue); +} + +private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent dbEvent) +{ + OnPlayerComponentChanged(obj); +} + +private void OnPlayerComponentChanged(PlayerComponent obj) +{ + // If the identity of the PlayerComponent matches our user identity then this is the local player + if(obj.OwnerId == local_identity) + { + // Now that we have our initial position we can start the game + StartGame(); + } + else + { + // otherwise we need to look for the remote player object in the scene (if it exists) and destroy it + var existingPlayer = FindObjectsOfType().FirstOrDefault(item => item.EntityId == obj.EntityId); + if (obj.LoggedIn) + { + // Only spawn remote players who aren't already spawned + if (existingPlayer == null) + { + // Spawn the player object and attach the RemotePlayer component + var remotePlayer = Instantiate(PlayerPrefab); + + // Lookup and apply the position for this new player + var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); + remotePlayer.transform.position = position; + + var movementController = remotePlayer.GetComponent(); + movementController.RemoteTargetPosition = position; + movementController.RemoteTargetRotation = entity.Direction; + + remotePlayer.AddComponent().EntityId = obj.EntityId; + } + } + else + { + if (existingPlayer != null) + { + Destroy(existingPlayer.gameObject); + } + } + } +} ``` -Where `` is your own address. If you do this instead then you won't have to change the address inside of `BitCraftMiniGameManager.cs` +Now you when you play the game you should see remote players disappear when they log out. -When you change the server module you should also regenerate the client files as well: +Before updating the client, let's generate the client files and update publish our module. +**Execute commands in the server/ directory** ```bash -spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang=cs +spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp +spacetime publish -c unity-tutorial +``` + +On the client, let's add code to send the message when the chat button or enter is pressed. Update the `OnChatButtonPress` function in `UIChatController.cs`. + +**Append to the top of UIChatController.cs:** +```csharp +using SpacetimeDB.Types; +``` + +**REPLACE the OnChatButtonPress function in UIChatController.cs:** + +```csharp +public void OnChatButtonPress() +{ + Reducer.SendChatMessage(_chatInput.text); + _chatInput.text = ""; +} +``` + +Now we need to add a reducer to handle inserting new chat messages. First register for the ChatMessage reducer in the `Start()` function using the auto-generated function: + +**Append to the bottom of the Start() function in TutorialGameManager.cs:** +```csharp +Reducer.OnSendChatMessageEvent += OnSendChatMessageEvent; ``` -You may want to consider putting these 2 commands into a simple shell script to make the process a bit cleaner. +Now we write the `OnSendChatMessageEvent` function. We can find the `PlayerComponent` for the player who sent the message using the `Identity` of the sender. Then we get the `Username` and prepend it to the message before sending it to the chat window. + +**Append after the Start() function in TutorialGameManager.cs** +```csharp +private void OnSendChatMessageEvent(ReducerEvent dbEvent, string message) +{ + var player = PlayerComponent.FilterByOwnerId(dbEvent.Identity); + if (player != null) + { + UIChatController.instance.OnChatMessageReceived(player.Username + ": " + message); + } +} +``` + +Now when you run the game you should be able to send chat messages to other players. Be sure to make a new Unity client build and run it in a separate window so you can test chat between two clients. + +## Conclusion + +This concludes the SpacetimeDB basic multiplayer tutorial, where we learned how to create a multiplayer game. In the next Unity tutorial, we will add resource nodes to the game and learn about _scheduled_ reducers: + +From here, the tutorial continues with more-advanced topics: The [next tutorial](/docs/unity/part-4.md) introduces Resources & Scheduling. + +--- + +### Troubleshooting + +- If you get an error when running the generate command, make sure you have an empty subfolder in your Unity project Assets folder called `module_bindings` + +- If you get this exception when running the project: + +``` +NullReferenceException: Object reference not set to an instance of an object +TutorialGameManager.Start () (at Assets/_Project/Game/TutorialGameManager.cs:26) +``` + +Check to see if your GameManager object in the Scene has the NetworkManager component attached. + +- If you get an error in your Unity console when starting the game, double check your connection settings in the Inspector for the `GameManager` object in the scene. + +``` +Connection error: Unable to connect to the remote server +``` diff --git a/docs/unity/part-2.md b/docs/unity/part-4.md similarity index 97% rename from docs/unity/part-2.md rename to docs/unity/part-4.md index 537edd44..a87f27a2 100644 --- a/docs/unity/part-2.md +++ b/docs/unity/part-4.md @@ -1,4 +1,8 @@ -# Part 2 - Resources and Scheduling +# Unity Tutorial - Advanced - Part 4 - Resources and Scheduling + +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! + +This progressive tutorial is continued from the [Part 3](/docs/unity/part-3.md) Tutorial. **Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** diff --git a/docs/unity/part-5.md b/docs/unity/part-5.md new file mode 100644 index 00000000..6ebce1c0 --- /dev/null +++ b/docs/unity/part-5.md @@ -0,0 +1,108 @@ +# Unity Tutorial - Advanced - Part 5 - BitCraft Mini + +Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! + +This progressive tutorial is continued from the [Part 4](/docs/unity/part-3.md) Tutorial. + +**Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** + +BitCraft Mini is a game that we developed which extends the code you've already developed in this tutorial. It is inspired by our game [BitCraft](https://bitcraftonline.com) and illustrates how you could build a more complex game from just the components we've discussed. Right now you can walk around, mine ore, and manage your inventory. + +## 1. Download + +You can git-clone BitCraftMini from here: + +```plaintext +git clone ssh://git@github.com/clockworklabs/BitCraftMini +``` + +Once you have downloaded BitCraftMini, you will need to compile the spacetime module. + +## 2. Compile the Spacetime Module + +In order to compile the BitCraftMini module, you will need to install cargo. You can install cargo from here: + +> https://www.rust-lang.org/tools/install + +Once you have cargo installed, you can compile and publish the module with these commands: + +```bash +cd BitCraftMini/Server +spacetime publish +``` + +`spacetime publish` will output an address where your module has been deployed to. You will want to copy/save this address because you will need it in step 3. Here is an example of what it should look like: + +```plaintext +$ spacetime publish +info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date + Finished release [optimized] target(s) in 0.03s +Publish finished successfully. +Created new database with address: c91c17ecdcea8a05302be2bad9dd59b3 +``` + +Optionally, you can specify a name when you publish the module: + +```bash +spacetime publish "unique-module-name" +``` + +Currently, all the named modules exist in the same namespace so if you get a message saying that database is not owned by you, it means that someone else has already published a module with that name. You can either choose a different name or you can use the address instead. If you specify a name when you publish, you can use that name in place of the autogenerated address in both the CLI and in the Unity client. + +In the BitCraftMini module we have a function called `initialize()`. This function should be called immediately after publishing the module to spacetimedb. This function is in charge of generating some initial settings that are required for the server to operate. You can call this function like so: + +```bash +spacetime call "" "initialize" "[]" +``` + +Here we are telling spacetime to invoke the `initialize()` function on our module "bitcraftmini". If the function had some arguments, we would json encode them and put them into the "[]". Since `initialize()` requires no parameters, we just leave it empty. + +After you have called `initialize()` on the spacetime module you shouldgenerate the client files: + +```bash +spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang=cs +``` + +Here is some sample output: + +```plaintext +$ spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang cs +info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date + Finished release [optimized] target(s) in 0.03s +compilation took 234.613518ms +Generate finished successfully. +``` + +If you've gotten this message then everything should be working properly so far. + +## 3. Replace address in BitCraftMiniGameManager + +The following settings are exposed in the `BitCraftMiniGameManager` inspector: Module Address, Host Name, and SSL Enabled. + +Open the Main scene in Unity and click on the `GameManager` object in the heirarchy. The inspector window will look like this: + +![GameManager-Inspector](/images/unity-tutorial/GameManager-Inspector.JPG) + +Update the module address with the address you got from the `spacetime publish` command. If you are using SpacetimeDB Cloud `testnet`, the host name should be `testnet.spacetimedb.com` and SSL Enabled should be checked. If you are running SpacetimeDB Standalone locally, the host name should be `localhost:3000` and SSL Enabled should be unchecked. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/deploying/testnet) + +## 4. Play Mode + +You should now be able to enter play mode and walk around! You can mine some rocks, cut down some trees and if you connect more clients you can trade with other players. + +## 5. Editing the Module + +If you want to make further updates to the module, make sure to use this publish command instead: + +```bash +spacetime publish +``` + +Where `` is your own address. If you do this instead then you won't have to change the address inside of `BitCraftMiniGameManager.cs` + +When you change the server module you should also regenerate the client files as well: + +```bash +spacetime generate --out-dir ../Client/Assets/_Project/autogen --lang=cs +``` + +You may want to consider putting these 2 commands into a simple shell script to make the process a bit cleaner. diff --git a/nav.ts b/nav.ts index 6d5a304b..8f463ad7 100644 --- a/nav.ts +++ b/nav.ts @@ -25,16 +25,22 @@ function section(title: string): NavSection { const nav: Nav = { items: [ section("Intro"), - page("Overview", "index", "index.md"), + page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + section("Unity Tutorial - Basic Multiplayer"), + page("Overview", "unity-tutorial", "unity/index.md"), + page("1 - Setup", "unity/part-1", "unity/part-1.md"), + page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), + page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), + page("3 - Client", "unity/part-3", "unity/part-3.md"), + + section("Unity Tutorial - Advanced"), + page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), + page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), section("Server Module Languages"), page("Overview", "modules", "modules/index.md"), From 4537457478327f97f2ddd689500e539d783401ab Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Wed, 1 May 2024 22:20:11 +0530 Subject: [PATCH 35/80] fix: Docs build, pnpm, vm evaluate (#46) * Push * prettierrc * Use cjs cuz current api require's it * Prettier override for md * fix 2b-c-sharp --- .prettierrc | 19 + build.js | 29 ++ docs/nav.js | 298 ++++++++++-- nav.ts | 163 ++++--- package.json | 29 +- pnpm-lock.yaml | 1261 ++++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 8 - 7 files changed, 1674 insertions(+), 133 deletions(-) create mode 100644 .prettierrc create mode 100644 build.js create mode 100644 pnpm-lock.yaml delete mode 100644 yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..81d845ca --- /dev/null +++ b/.prettierrc @@ -0,0 +1,19 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "jsxSingleQuote": false, + "trailingComma": "none", + "endOfLine": "auto", + "printWidth": 80, + "overrides": [ + { + "files": "*.md", + "options": { + "tabWidth": 2 + } + } + ] +} diff --git a/build.js b/build.js new file mode 100644 index 00000000..5f7414c0 --- /dev/null +++ b/build.js @@ -0,0 +1,29 @@ +// @ts-check +import { build } from 'tsup'; +import { createContext, Script } from 'node:vm'; +import { readFile, writeFile, rm } from 'node:fs/promises'; +import { inspect } from 'node:util'; + +await build({ entry: { out: 'nav.ts' }, clean: true, format: 'esm' }); + +// Read the file +const nav = await readFile('dist/out.js', 'utf8'); + +// Remove this line +// export { +// nav +// }; +const final = nav.replace(/export {[^}]*};/, '') + '\nnav;'; + +// Execute the code +const context = createContext(); +const script = new Script(final); +const out = script.runInContext(context); + +await writeFile( + 'docs/nav.js', + 'module.exports = ' + + inspect(out, { depth: null, compact: false, breakLength: 120 }) +); + +await rm('dist/out.js', { recursive: true }); diff --git a/docs/nav.js b/docs/nav.js index cb8d22f1..c4346d75 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,52 +1,246 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function page(title, slug, path, props) { - return { type: "page", path, slug, title, ...props }; -} -function section(title) { - return { type: "section", title }; -} -const nav = { - items: [ - section("Intro"), - page("Overview", "index", "index.md"), - page("Getting Started", "getting-started", "getting-started.md"), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), - ], -}; -exports.default = nav; +module.exports = { + items: [ + { + type: 'section', + title: 'Intro' + }, + { + type: 'page', + path: 'index.md', + slug: 'index', + title: 'Overview' + }, + { + type: 'page', + path: 'getting-started.md', + slug: 'getting-started', + title: 'Getting Started' + }, + { + type: 'section', + title: 'Deploying' + }, + { + type: 'page', + path: 'deploying/testnet.md', + slug: 'deploying/testnet', + title: 'Testnet' + }, + { + type: 'section', + title: 'Unity Tutorial - Basic Multiplayer' + }, + { + type: 'page', + path: 'unity/index.md', + slug: 'unity-tutorial', + title: 'Overview' + }, + { + type: 'page', + path: 'unity/part-1.md', + slug: 'unity/part-1', + title: '1 - Setup' + }, + { + type: 'page', + path: 'unity/part-2a-rust.md', + slug: 'unity/part-2a-rust', + title: '2a - Server (Rust)' + }, + { + type: 'page', + path: 'unity/part-2b-c-sharp.md', + slug: 'unity/part-2b-c-sharp', + title: '2b - Server (C#)' + }, + { + type: 'page', + path: 'unity/part-3.md', + slug: 'unity/part-3', + title: '3 - Client' + }, + { + type: 'section', + title: 'Unity Tutorial - Advanced' + }, + { + type: 'page', + path: 'unity/part-4.md', + slug: 'unity/part-4', + title: '4 - Resources And Scheduling' + }, + { + type: 'page', + path: 'unity/part-5.md', + slug: 'unity/part-5', + title: '5 - BitCraft Mini' + }, + { + type: 'section', + title: 'Server Module Languages' + }, + { + type: 'page', + path: 'modules/index.md', + slug: 'modules', + title: 'Overview' + }, + { + type: 'page', + path: 'modules/rust/quickstart.md', + slug: 'modules/rust/quickstart', + title: 'Rust Quickstart' + }, + { + type: 'page', + path: 'modules/rust/index.md', + slug: 'modules/rust', + title: 'Rust Reference' + }, + { + type: 'page', + path: 'modules/c-sharp/quickstart.md', + slug: 'modules/c-sharp/quickstart', + title: 'C# Quickstart' + }, + { + type: 'page', + path: 'modules/c-sharp/index.md', + slug: 'modules/c-sharp', + title: 'C# Reference' + }, + { + type: 'section', + title: 'Client SDK Languages' + }, + { + type: 'page', + path: 'sdks/index.md', + slug: 'sdks', + title: 'Overview' + }, + { + type: 'page', + path: 'sdks/typescript/quickstart.md', + slug: 'sdks/typescript/quickstart', + title: 'Typescript Quickstart' + }, + { + type: 'page', + path: 'sdks/typescript/index.md', + slug: 'sdks/typescript', + title: 'Typescript Reference' + }, + { + type: 'page', + path: 'sdks/rust/quickstart.md', + slug: 'sdks/rust/quickstart', + title: 'Rust Quickstart' + }, + { + type: 'page', + path: 'sdks/rust/index.md', + slug: 'sdks/rust', + title: 'Rust Reference' + }, + { + type: 'page', + path: 'sdks/python/quickstart.md', + slug: 'sdks/python/quickstart', + title: 'Python Quickstart' + }, + { + type: 'page', + path: 'sdks/python/index.md', + slug: 'sdks/python', + title: 'Python Reference' + }, + { + type: 'page', + path: 'sdks/c-sharp/quickstart.md', + slug: 'sdks/c-sharp/quickstart', + title: 'C# Quickstart' + }, + { + type: 'page', + path: 'sdks/c-sharp/index.md', + slug: 'sdks/c-sharp', + title: 'C# Reference' + }, + { + type: 'section', + title: 'WebAssembly ABI' + }, + { + type: 'page', + path: 'webassembly-abi/index.md', + slug: 'webassembly-abi', + title: 'Module ABI Reference' + }, + { + type: 'section', + title: 'HTTP API' + }, + { + type: 'page', + path: 'http/index.md', + slug: 'http', + title: 'HTTP' + }, + { + type: 'page', + path: 'http/identity.md', + slug: 'http/identity', + title: '`/identity`' + }, + { + type: 'page', + path: 'http/database.md', + slug: 'http/database', + title: '`/database`' + }, + { + type: 'page', + path: 'http/energy.md', + slug: 'http/energy', + title: '`/energy`' + }, + { + type: 'section', + title: 'WebSocket API Reference' + }, + { + type: 'page', + path: 'ws/index.md', + slug: 'ws', + title: 'WebSocket' + }, + { + type: 'section', + title: 'Data Format' + }, + { + type: 'page', + path: 'satn.md', + slug: 'satn', + title: 'SATN' + }, + { + type: 'page', + path: 'bsatn.md', + slug: 'bsatn', + title: 'BSATN' + }, + { + type: 'section', + title: 'SQL' + }, + { + type: 'page', + path: 'sql/index.md', + slug: 'sql', + title: 'SQL Reference' + } + ] +} \ No newline at end of file diff --git a/nav.ts b/nav.ts index 8f463ad7..b6eea77a 100644 --- a/nav.ts +++ b/nav.ts @@ -1,84 +1,129 @@ type Nav = { - items: NavItem[]; + items: NavItem[]; }; type NavItem = NavPage | NavSection; type NavPage = { - type: "page"; - path: string; - slug: string; - title: string; - disabled?: boolean; - href?: string; + type: 'page'; + path: string; + slug: string; + title: string; + disabled?: boolean; + href?: string; }; type NavSection = { - type: "section"; - title: string; + type: 'section'; + title: string; }; -function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { - return { type: "page", path, slug, title, ...props }; +function page( + title: string, + slug: string, + path: string, + props?: { disabled?: boolean; href?: string; description?: string } +): NavPage { + return { type: 'page', path, slug, title, ...props }; } function section(title: string): NavSection { - return { type: "section", title }; + return { type: 'section', title }; } const nav: Nav = { - items: [ - section("Intro"), - page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page("Getting Started", "getting-started", "getting-started.md"), + items: [ + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - section("Unity Tutorial - Basic Multiplayer"), - page("Overview", "unity-tutorial", "unity/index.md"), - page("1 - Setup", "unity/part-1", "unity/part-1.md"), - page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), - page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), - page("3 - Client", "unity/part-3", "unity/part-3.md"), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page( + '2a - Server (Rust)', + 'unity/part-2a-rust', + 'unity/part-2a-rust.md' + ), + page( + '2b - Server (C#)', + 'unity/part-2b-c-sharp', + 'unity/part-2b-c-sharp.md' + ), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), - section("Unity Tutorial - Advanced"), - page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), - page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page( + 'Rust Quickstart', + 'modules/rust/quickstart', + 'modules/rust/quickstart.md' + ), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page( + 'C# Quickstart', + 'modules/c-sharp/quickstart', + 'modules/c-sharp/quickstart.md' + ), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page( + 'Typescript Quickstart', + 'sdks/typescript/quickstart', + 'sdks/typescript/quickstart.md' + ), + page( + 'Typescript Reference', + 'sdks/typescript', + 'sdks/typescript/index.md' + ), + page( + 'Rust Quickstart', + 'sdks/rust/quickstart', + 'sdks/rust/quickstart.md' + ), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page( + 'Python Quickstart', + 'sdks/python/quickstart', + 'sdks/python/quickstart.md' + ), + page('Python Reference', 'sdks/python', 'sdks/python/index.md'), + page( + 'C# Quickstart', + 'sdks/c-sharp/quickstart', + 'sdks/c-sharp/quickstart.md' + ), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + section('WebAssembly ABI'), + page( + 'Module ABI Reference', + 'webassembly-abi', + 'webassembly-abi/index.md' + ), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), - ], + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md') + ] }; -export default nav; +export { nav }; diff --git a/package.json b/package.json index a56ea4e8..0a764ee6 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { - "name": "spacetime-docs", - "version": "1.0.0", - "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", - "main": "index.js", - "dependencies": {}, - "devDependencies": { - "typescript": "^5.3.2" - }, - "scripts": { - "build": "tsc" - }, - "author": "Clockwork Labs", - "license": "ISC" -} \ No newline at end of file + "name": "spacetime-docs", + "version": "1.0.0", + "type": "module", + "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "tsup": "^8.0.2" + }, + "scripts": { + "build": "node build.js" + }, + "author": "Clockwork Labs", + "license": "ISC" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..bec77ca8 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1261 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + tsup: + specifier: ^8.0.2 + version: 8.0.2 + +packages: + + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.17.2': + resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.17.2': + resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.17.2': + resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.17.2': + resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.17.2': + resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.17.2': + resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.17.2': + resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.17.2': + resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': + resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.17.2': + resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.17.2': + resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.17.2': + resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.17.2': + resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.17.2': + resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.17.2': + resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.17.2': + resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + bundle-require@4.0.3: + resolution: {integrity: sha512-2iscZ3fcthP2vka4Y7j277YJevwmsby/FpFDwjgw34Nl7dtCpt7zz/4TexmHMzY6KZEih7En9ImlbbgUNNQGtA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} + engines: {node: '>=16 || 14 >=14.17'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.17.2: + resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.0.2: + resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yaml@2.4.2: + resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} + engines: {node: '>= 14'} + hasBin: true + +snapshots: + + '@esbuild/aix-ppc64@0.19.12': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.17.2': + optional: true + + '@rollup/rollup-android-arm64@4.17.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.17.2': + optional: true + + '@rollup/rollup-darwin-x64@4.17.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.17.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.17.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.17.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.17.2': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.17.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.17.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.17.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.17.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.17.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.17.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.17.2': + optional: true + + '@types/estree@1.0.5': {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-union@2.1.0: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.2: + dependencies: + fill-range: 7.0.1 + + bundle-require@4.0.3(esbuild@0.19.12): + dependencies: + esbuild: 0.19.12 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.3.4: + dependencies: + ms: 2.1.2 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.0.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.1.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.3.12: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.0.4 + path-scurry: 1.10.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + human-signals@2.1.0: {} + + ignore@5.3.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + lilconfig@3.1.1: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + lodash.sortby@4.7.0: {} + + lru-cache@10.2.2: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.5: + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.0.4: {} + + ms@2.1.2: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + path-key@3.1.1: {} + + path-scurry@1.10.2: + dependencies: + lru-cache: 10.2.2 + minipass: 7.0.4 + + path-type@4.0.0: {} + + picomatch@2.3.1: {} + + pirates@4.0.6: {} + + postcss-load-config@4.0.2: + dependencies: + lilconfig: 3.1.1 + yaml: 2.4.2 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve-from@5.0.0: {} + + reusify@1.0.4: {} + + rollup@4.17.2: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.17.2 + '@rollup/rollup-android-arm64': 4.17.2 + '@rollup/rollup-darwin-arm64': 4.17.2 + '@rollup/rollup-darwin-x64': 4.17.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 + '@rollup/rollup-linux-arm-musleabihf': 4.17.2 + '@rollup/rollup-linux-arm64-gnu': 4.17.2 + '@rollup/rollup-linux-arm64-musl': 4.17.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 + '@rollup/rollup-linux-riscv64-gnu': 4.17.2 + '@rollup/rollup-linux-s390x-gnu': 4.17.2 + '@rollup/rollup-linux-x64-gnu': 4.17.2 + '@rollup/rollup-linux-x64-musl': 4.17.2 + '@rollup/rollup-win32-arm64-msvc': 4.17.2 + '@rollup/rollup-win32-ia32-msvc': 4.17.2 + '@rollup/rollup-win32-x64-msvc': 4.17.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-final-newline@2.0.0: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.3.12 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsup@8.0.2: + dependencies: + bundle-require: 4.0.3(esbuild@0.19.12) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.3.4 + esbuild: 0.19.12 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.2 + resolve-from: 5.0.0 + rollup: 4.17.2 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + transitivePeerDependencies: + - supports-color + - ts-node + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yaml@2.4.2: {} diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fce89544..00000000 --- a/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -typescript@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== From 222b4d04de48d99748e7d1550aec7a61776b6414 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 1 May 2024 23:57:58 -0400 Subject: [PATCH 36/80] Revert "fix: Docs build, pnpm, vm evaluate (#46)" (#48) This reverts commit 4537457478327f97f2ddd689500e539d783401ab. --- .prettierrc | 19 - build.js | 29 -- docs/nav.js | 298 ++---------- nav.ts | 163 +++---- package.json | 29 +- pnpm-lock.yaml | 1261 ------------------------------------------------ yarn.lock | 8 + 7 files changed, 133 insertions(+), 1674 deletions(-) delete mode 100644 .prettierrc delete mode 100644 build.js delete mode 100644 pnpm-lock.yaml create mode 100644 yarn.lock diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 81d845ca..00000000 --- a/.prettierrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "tabWidth": 4, - "useTabs": false, - "semi": true, - "singleQuote": true, - "arrowParens": "avoid", - "jsxSingleQuote": false, - "trailingComma": "none", - "endOfLine": "auto", - "printWidth": 80, - "overrides": [ - { - "files": "*.md", - "options": { - "tabWidth": 2 - } - } - ] -} diff --git a/build.js b/build.js deleted file mode 100644 index 5f7414c0..00000000 --- a/build.js +++ /dev/null @@ -1,29 +0,0 @@ -// @ts-check -import { build } from 'tsup'; -import { createContext, Script } from 'node:vm'; -import { readFile, writeFile, rm } from 'node:fs/promises'; -import { inspect } from 'node:util'; - -await build({ entry: { out: 'nav.ts' }, clean: true, format: 'esm' }); - -// Read the file -const nav = await readFile('dist/out.js', 'utf8'); - -// Remove this line -// export { -// nav -// }; -const final = nav.replace(/export {[^}]*};/, '') + '\nnav;'; - -// Execute the code -const context = createContext(); -const script = new Script(final); -const out = script.runInContext(context); - -await writeFile( - 'docs/nav.js', - 'module.exports = ' + - inspect(out, { depth: null, compact: false, breakLength: 120 }) -); - -await rm('dist/out.js', { recursive: true }); diff --git a/docs/nav.js b/docs/nav.js index c4346d75..cb8d22f1 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,246 +1,52 @@ -module.exports = { - items: [ - { - type: 'section', - title: 'Intro' - }, - { - type: 'page', - path: 'index.md', - slug: 'index', - title: 'Overview' - }, - { - type: 'page', - path: 'getting-started.md', - slug: 'getting-started', - title: 'Getting Started' - }, - { - type: 'section', - title: 'Deploying' - }, - { - type: 'page', - path: 'deploying/testnet.md', - slug: 'deploying/testnet', - title: 'Testnet' - }, - { - type: 'section', - title: 'Unity Tutorial - Basic Multiplayer' - }, - { - type: 'page', - path: 'unity/index.md', - slug: 'unity-tutorial', - title: 'Overview' - }, - { - type: 'page', - path: 'unity/part-1.md', - slug: 'unity/part-1', - title: '1 - Setup' - }, - { - type: 'page', - path: 'unity/part-2a-rust.md', - slug: 'unity/part-2a-rust', - title: '2a - Server (Rust)' - }, - { - type: 'page', - path: 'unity/part-2b-c-sharp.md', - slug: 'unity/part-2b-c-sharp', - title: '2b - Server (C#)' - }, - { - type: 'page', - path: 'unity/part-3.md', - slug: 'unity/part-3', - title: '3 - Client' - }, - { - type: 'section', - title: 'Unity Tutorial - Advanced' - }, - { - type: 'page', - path: 'unity/part-4.md', - slug: 'unity/part-4', - title: '4 - Resources And Scheduling' - }, - { - type: 'page', - path: 'unity/part-5.md', - slug: 'unity/part-5', - title: '5 - BitCraft Mini' - }, - { - type: 'section', - title: 'Server Module Languages' - }, - { - type: 'page', - path: 'modules/index.md', - slug: 'modules', - title: 'Overview' - }, - { - type: 'page', - path: 'modules/rust/quickstart.md', - slug: 'modules/rust/quickstart', - title: 'Rust Quickstart' - }, - { - type: 'page', - path: 'modules/rust/index.md', - slug: 'modules/rust', - title: 'Rust Reference' - }, - { - type: 'page', - path: 'modules/c-sharp/quickstart.md', - slug: 'modules/c-sharp/quickstart', - title: 'C# Quickstart' - }, - { - type: 'page', - path: 'modules/c-sharp/index.md', - slug: 'modules/c-sharp', - title: 'C# Reference' - }, - { - type: 'section', - title: 'Client SDK Languages' - }, - { - type: 'page', - path: 'sdks/index.md', - slug: 'sdks', - title: 'Overview' - }, - { - type: 'page', - path: 'sdks/typescript/quickstart.md', - slug: 'sdks/typescript/quickstart', - title: 'Typescript Quickstart' - }, - { - type: 'page', - path: 'sdks/typescript/index.md', - slug: 'sdks/typescript', - title: 'Typescript Reference' - }, - { - type: 'page', - path: 'sdks/rust/quickstart.md', - slug: 'sdks/rust/quickstart', - title: 'Rust Quickstart' - }, - { - type: 'page', - path: 'sdks/rust/index.md', - slug: 'sdks/rust', - title: 'Rust Reference' - }, - { - type: 'page', - path: 'sdks/python/quickstart.md', - slug: 'sdks/python/quickstart', - title: 'Python Quickstart' - }, - { - type: 'page', - path: 'sdks/python/index.md', - slug: 'sdks/python', - title: 'Python Reference' - }, - { - type: 'page', - path: 'sdks/c-sharp/quickstart.md', - slug: 'sdks/c-sharp/quickstart', - title: 'C# Quickstart' - }, - { - type: 'page', - path: 'sdks/c-sharp/index.md', - slug: 'sdks/c-sharp', - title: 'C# Reference' - }, - { - type: 'section', - title: 'WebAssembly ABI' - }, - { - type: 'page', - path: 'webassembly-abi/index.md', - slug: 'webassembly-abi', - title: 'Module ABI Reference' - }, - { - type: 'section', - title: 'HTTP API' - }, - { - type: 'page', - path: 'http/index.md', - slug: 'http', - title: 'HTTP' - }, - { - type: 'page', - path: 'http/identity.md', - slug: 'http/identity', - title: '`/identity`' - }, - { - type: 'page', - path: 'http/database.md', - slug: 'http/database', - title: '`/database`' - }, - { - type: 'page', - path: 'http/energy.md', - slug: 'http/energy', - title: '`/energy`' - }, - { - type: 'section', - title: 'WebSocket API Reference' - }, - { - type: 'page', - path: 'ws/index.md', - slug: 'ws', - title: 'WebSocket' - }, - { - type: 'section', - title: 'Data Format' - }, - { - type: 'page', - path: 'satn.md', - slug: 'satn', - title: 'SATN' - }, - { - type: 'page', - path: 'bsatn.md', - slug: 'bsatn', - title: 'BSATN' - }, - { - type: 'section', - title: 'SQL' - }, - { - type: 'page', - path: 'sql/index.md', - slug: 'sql', - title: 'SQL Reference' - } - ] -} \ No newline at end of file +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function page(title, slug, path, props) { + return { type: "page", path, slug, title, ...props }; +} +function section(title) { + return { type: "section", title }; +} +const nav = { + items: [ + section("Intro"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), + section("Deploying"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), + section("Unity Tutorial"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + section("Server Module Languages"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + section("Client SDK Languages"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + section("WebAssembly ABI"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + section("HTTP API"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), + section("WebSocket API Reference"), + page("WebSocket", "ws", "ws/index.md"), + section("Data Format"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), + section("SQL"), + page("SQL Reference", "sql", "sql/index.md"), + ], +}; +exports.default = nav; diff --git a/nav.ts b/nav.ts index b6eea77a..8f463ad7 100644 --- a/nav.ts +++ b/nav.ts @@ -1,129 +1,84 @@ type Nav = { - items: NavItem[]; + items: NavItem[]; }; type NavItem = NavPage | NavSection; type NavPage = { - type: 'page'; - path: string; - slug: string; - title: string; - disabled?: boolean; - href?: string; + type: "page"; + path: string; + slug: string; + title: string; + disabled?: boolean; + href?: string; }; type NavSection = { - type: 'section'; - title: string; + type: "section"; + title: string; }; -function page( - title: string, - slug: string, - path: string, - props?: { disabled?: boolean; href?: string; description?: string } -): NavPage { - return { type: 'page', path, slug, title, ...props }; +function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { + return { type: "page", path, slug, title, ...props }; } function section(title: string): NavSection { - return { type: 'section', title }; + return { type: "section", title }; } const nav: Nav = { - items: [ - section('Intro'), - page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page('Getting Started', 'getting-started', 'getting-started.md'), + items: [ + section("Intro"), + page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page("Getting Started", "getting-started", "getting-started.md"), - section('Deploying'), - page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + section("Deploying"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section('Unity Tutorial - Basic Multiplayer'), - page('Overview', 'unity-tutorial', 'unity/index.md'), - page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), - page( - '2a - Server (Rust)', - 'unity/part-2a-rust', - 'unity/part-2a-rust.md' - ), - page( - '2b - Server (C#)', - 'unity/part-2b-c-sharp', - 'unity/part-2b-c-sharp.md' - ), - page('3 - Client', 'unity/part-3', 'unity/part-3.md'), + section("Unity Tutorial - Basic Multiplayer"), + page("Overview", "unity-tutorial", "unity/index.md"), + page("1 - Setup", "unity/part-1", "unity/part-1.md"), + page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), + page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), + page("3 - Client", "unity/part-3", "unity/part-3.md"), - section('Unity Tutorial - Advanced'), - page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), - page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), + section("Unity Tutorial - Advanced"), + page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), + page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), - section('Server Module Languages'), - page('Overview', 'modules', 'modules/index.md'), - page( - 'Rust Quickstart', - 'modules/rust/quickstart', - 'modules/rust/quickstart.md' - ), - page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), - page( - 'C# Quickstart', - 'modules/c-sharp/quickstart', - 'modules/c-sharp/quickstart.md' - ), - page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), + section("Server Module Languages"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), - section('Client SDK Languages'), - page('Overview', 'sdks', 'sdks/index.md'), - page( - 'Typescript Quickstart', - 'sdks/typescript/quickstart', - 'sdks/typescript/quickstart.md' - ), - page( - 'Typescript Reference', - 'sdks/typescript', - 'sdks/typescript/index.md' - ), - page( - 'Rust Quickstart', - 'sdks/rust/quickstart', - 'sdks/rust/quickstart.md' - ), - page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), - page( - 'Python Quickstart', - 'sdks/python/quickstart', - 'sdks/python/quickstart.md' - ), - page('Python Reference', 'sdks/python', 'sdks/python/index.md'), - page( - 'C# Quickstart', - 'sdks/c-sharp/quickstart', - 'sdks/c-sharp/quickstart.md' - ), - page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), + section("Client SDK Languages"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), - section('WebAssembly ABI'), - page( - 'Module ABI Reference', - 'webassembly-abi', - 'webassembly-abi/index.md' - ), + section("WebAssembly ABI"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), - section('HTTP API'), - page('HTTP', 'http', 'http/index.md'), - page('`/identity`', 'http/identity', 'http/identity.md'), - page('`/database`', 'http/database', 'http/database.md'), - page('`/energy`', 'http/energy', 'http/energy.md'), + section("HTTP API"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), - section('WebSocket API Reference'), - page('WebSocket', 'ws', 'ws/index.md'), + section("WebSocket API Reference"), + page("WebSocket", "ws", "ws/index.md"), - section('Data Format'), - page('SATN', 'satn', 'satn.md'), - page('BSATN', 'bsatn', 'bsatn.md'), + section("Data Format"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), - section('SQL'), - page('SQL Reference', 'sql', 'sql/index.md') - ] + section("SQL"), + page("SQL Reference", "sql", "sql/index.md"), + ], }; -export { nav }; +export default nav; diff --git a/package.json b/package.json index 0a764ee6..a56ea4e8 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,15 @@ { - "name": "spacetime-docs", - "version": "1.0.0", - "type": "module", - "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", - "main": "index.js", - "dependencies": {}, - "devDependencies": { - "tsup": "^8.0.2" - }, - "scripts": { - "build": "node build.js" - }, - "author": "Clockwork Labs", - "license": "ISC" -} + "name": "spacetime-docs", + "version": "1.0.0", + "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "typescript": "^5.3.2" + }, + "scripts": { + "build": "tsc" + }, + "author": "Clockwork Labs", + "license": "ISC" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index bec77ca8..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,1261 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - tsup: - specifier: ^8.0.2 - version: 8.0.2 - -packages: - - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@rollup/rollup-android-arm-eabi@4.17.2': - resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.17.2': - resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.17.2': - resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.17.2': - resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.17.2': - resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.17.2': - resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.17.2': - resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.17.2': - resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} - cpu: [x64] - os: [win32] - - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - - bundle-require@4.0.3: - resolution: {integrity: sha512-2iscZ3fcthP2vka4Y7j277YJevwmsby/FpFDwjgw34Nl7dtCpt7zz/4TexmHMzY6KZEih7En9ImlbbgUNNQGtA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.17' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@10.3.12: - resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - lilconfig@3.1.1: - resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@1.10.2: - resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} - engines: {node: '>=16 || 14 >=14.17'} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup@4.17.2: - resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tsup@8.0.2: - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - yaml@2.4.2: - resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} - engines: {node: '>= 14'} - hasBin: true - -snapshots: - - '@esbuild/aix-ppc64@0.19.12': - optional: true - - '@esbuild/android-arm64@0.19.12': - optional: true - - '@esbuild/android-arm@0.19.12': - optional: true - - '@esbuild/android-x64@0.19.12': - optional: true - - '@esbuild/darwin-arm64@0.19.12': - optional: true - - '@esbuild/darwin-x64@0.19.12': - optional: true - - '@esbuild/freebsd-arm64@0.19.12': - optional: true - - '@esbuild/freebsd-x64@0.19.12': - optional: true - - '@esbuild/linux-arm64@0.19.12': - optional: true - - '@esbuild/linux-arm@0.19.12': - optional: true - - '@esbuild/linux-ia32@0.19.12': - optional: true - - '@esbuild/linux-loong64@0.19.12': - optional: true - - '@esbuild/linux-mips64el@0.19.12': - optional: true - - '@esbuild/linux-ppc64@0.19.12': - optional: true - - '@esbuild/linux-riscv64@0.19.12': - optional: true - - '@esbuild/linux-s390x@0.19.12': - optional: true - - '@esbuild/linux-x64@0.19.12': - optional: true - - '@esbuild/netbsd-x64@0.19.12': - optional: true - - '@esbuild/openbsd-x64@0.19.12': - optional: true - - '@esbuild/sunos-x64@0.19.12': - optional: true - - '@esbuild/win32-arm64@0.19.12': - optional: true - - '@esbuild/win32-ia32@0.19.12': - optional: true - - '@esbuild/win32-x64@0.19.12': - optional: true - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@rollup/rollup-android-arm-eabi@4.17.2': - optional: true - - '@rollup/rollup-android-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-x64@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.17.2': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.17.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.17.2': - optional: true - - '@types/estree@1.0.5': {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - array-union@2.1.0: {} - - balanced-match@1.0.2: {} - - binary-extensions@2.3.0: {} - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.2: - dependencies: - fill-range: 7.0.1 - - bundle-require@4.0.3(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - commander@4.1.1: {} - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.3.4: - dependencies: - ms: 2.1.2 - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - esbuild@0.19.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fill-range@7.0.1: - dependencies: - to-regex-range: 5.0.1 - - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - fsevents@2.3.3: - optional: true - - get-stream@6.0.1: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob@10.3.12: - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.4 - minipass: 7.0.4 - path-scurry: 1.10.2 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - - human-signals@2.1.0: {} - - ignore@5.3.1: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-stream@2.0.1: {} - - isexe@2.0.0: {} - - jackspeak@2.3.6: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - joycon@3.1.1: {} - - lilconfig@3.1.1: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - - lodash.sortby@4.7.0: {} - - lru-cache@10.2.2: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.5: - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - - mimic-fn@2.1.0: {} - - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - - minipass@7.0.4: {} - - ms@2.1.2: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - object-assign@4.1.1: {} - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - path-key@3.1.1: {} - - path-scurry@1.10.2: - dependencies: - lru-cache: 10.2.2 - minipass: 7.0.4 - - path-type@4.0.0: {} - - picomatch@2.3.1: {} - - pirates@4.0.6: {} - - postcss-load-config@4.0.2: - dependencies: - lilconfig: 3.1.1 - yaml: 2.4.2 - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - resolve-from@5.0.0: {} - - reusify@1.0.4: {} - - rollup@4.17.2: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.17.2 - '@rollup/rollup-android-arm64': 4.17.2 - '@rollup/rollup-darwin-arm64': 4.17.2 - '@rollup/rollup-darwin-x64': 4.17.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 - '@rollup/rollup-linux-arm-musleabihf': 4.17.2 - '@rollup/rollup-linux-arm64-gnu': 4.17.2 - '@rollup/rollup-linux-arm64-musl': 4.17.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 - '@rollup/rollup-linux-riscv64-gnu': 4.17.2 - '@rollup/rollup-linux-s390x-gnu': 4.17.2 - '@rollup/rollup-linux-x64-gnu': 4.17.2 - '@rollup/rollup-linux-x64-musl': 4.17.2 - '@rollup/rollup-win32-arm64-msvc': 4.17.2 - '@rollup/rollup-win32-ia32-msvc': 4.17.2 - '@rollup/rollup-win32-x64-msvc': 4.17.2 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - slash@3.0.0: {} - - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.0.1 - - strip-final-newline@2.0.0: {} - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.3.12 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - - ts-interface-checker@0.1.13: {} - - tsup@8.0.2: - dependencies: - bundle-require: 4.0.3(esbuild@0.19.12) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.4 - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.2 - resolve-from: 5.0.0 - rollup: 4.17.2 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - transitivePeerDependencies: - - supports-color - - ts-node - - webidl-conversions@4.0.2: {} - - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - yaml@2.4.2: {} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fce89544 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== From 93cb9d6b2fe8a6be6d636174b1d5821852ff22c8 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Thu, 2 May 2024 10:02:46 -0400 Subject: [PATCH 37/80] fix: Docs build, pnpm, vm evaluate (#46) (#50) * Push * prettierrc * Use cjs cuz current api require's it * Prettier override for md * fix 2b-c-sharp Hopefully fixed the break introduced by pnpm Fix to nav.js generation Now just using tsc to build the file type = commonjs Co-authored-by: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> --- .prettierrc | 19 ++++++ docs/nav.js | 92 +++++++++++++++------------- nav.ts | 163 +++++++++++++++++++++++++++++++------------------ package.json | 27 ++++---- pnpm-lock.yaml | 18 ++++++ yarn.lock | 8 --- 6 files changed, 203 insertions(+), 124 deletions(-) create mode 100644 .prettierrc create mode 100644 pnpm-lock.yaml delete mode 100644 yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..81d845ca --- /dev/null +++ b/.prettierrc @@ -0,0 +1,19 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "jsxSingleQuote": false, + "trailingComma": "none", + "endOfLine": "auto", + "printWidth": 80, + "overrides": [ + { + "files": "*.md", + "options": { + "tabWidth": 2 + } + } + ] +} diff --git a/docs/nav.js b/docs/nav.js index cb8d22f1..ec6d9d66 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,52 +1,58 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.nav = void 0; function page(title, slug, path, props) { - return { type: "page", path, slug, title, ...props }; + return { type: 'page', path, slug, title, ...props }; } function section(title) { - return { type: "section", title }; + return { type: 'section', title }; } const nav = { items: [ - section("Intro"), - page("Overview", "index", "index.md"), - page("Getting Started", "getting-started", "getting-started.md"), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), - ], + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), + page('2b - Server (C#)', 'unity/part-2b-c-sharp', 'unity/part-2b-c-sharp.md'), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page('Rust Quickstart', 'modules/rust/quickstart', 'modules/rust/quickstart.md'), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page('C# Quickstart', 'modules/c-sharp/quickstart', 'modules/c-sharp/quickstart.md'), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page('Typescript Quickstart', 'sdks/typescript/quickstart', 'sdks/typescript/quickstart.md'), + page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), + page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page('Python Quickstart', 'sdks/python/quickstart', 'sdks/python/quickstart.md'), + page('Python Reference', 'sdks/python', 'sdks/python/index.md'), + page('C# Quickstart', 'sdks/c-sharp/quickstart', 'sdks/c-sharp/quickstart.md'), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), + section('WebAssembly ABI'), + page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md') + ] }; -exports.default = nav; +exports.nav = nav; diff --git a/nav.ts b/nav.ts index 8f463ad7..b6eea77a 100644 --- a/nav.ts +++ b/nav.ts @@ -1,84 +1,129 @@ type Nav = { - items: NavItem[]; + items: NavItem[]; }; type NavItem = NavPage | NavSection; type NavPage = { - type: "page"; - path: string; - slug: string; - title: string; - disabled?: boolean; - href?: string; + type: 'page'; + path: string; + slug: string; + title: string; + disabled?: boolean; + href?: string; }; type NavSection = { - type: "section"; - title: string; + type: 'section'; + title: string; }; -function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { - return { type: "page", path, slug, title, ...props }; +function page( + title: string, + slug: string, + path: string, + props?: { disabled?: boolean; href?: string; description?: string } +): NavPage { + return { type: 'page', path, slug, title, ...props }; } function section(title: string): NavSection { - return { type: "section", title }; + return { type: 'section', title }; } const nav: Nav = { - items: [ - section("Intro"), - page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page("Getting Started", "getting-started", "getting-started.md"), + items: [ + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - section("Unity Tutorial - Basic Multiplayer"), - page("Overview", "unity-tutorial", "unity/index.md"), - page("1 - Setup", "unity/part-1", "unity/part-1.md"), - page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), - page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), - page("3 - Client", "unity/part-3", "unity/part-3.md"), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page( + '2a - Server (Rust)', + 'unity/part-2a-rust', + 'unity/part-2a-rust.md' + ), + page( + '2b - Server (C#)', + 'unity/part-2b-c-sharp', + 'unity/part-2b-c-sharp.md' + ), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), - section("Unity Tutorial - Advanced"), - page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), - page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page( + 'Rust Quickstart', + 'modules/rust/quickstart', + 'modules/rust/quickstart.md' + ), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page( + 'C# Quickstart', + 'modules/c-sharp/quickstart', + 'modules/c-sharp/quickstart.md' + ), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page( + 'Typescript Quickstart', + 'sdks/typescript/quickstart', + 'sdks/typescript/quickstart.md' + ), + page( + 'Typescript Reference', + 'sdks/typescript', + 'sdks/typescript/index.md' + ), + page( + 'Rust Quickstart', + 'sdks/rust/quickstart', + 'sdks/rust/quickstart.md' + ), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page( + 'Python Quickstart', + 'sdks/python/quickstart', + 'sdks/python/quickstart.md' + ), + page('Python Reference', 'sdks/python', 'sdks/python/index.md'), + page( + 'C# Quickstart', + 'sdks/c-sharp/quickstart', + 'sdks/c-sharp/quickstart.md' + ), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + section('WebAssembly ABI'), + page( + 'Module ABI Reference', + 'webassembly-abi', + 'webassembly-abi/index.md' + ), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), - ], + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md') + ] }; -export default nav; +export { nav }; diff --git a/package.json b/package.json index a56ea4e8..4b23519c 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,14 @@ { - "name": "spacetime-docs", - "version": "1.0.0", - "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", - "main": "index.js", - "dependencies": {}, - "devDependencies": { - "typescript": "^5.3.2" - }, - "scripts": { - "build": "tsc" - }, - "author": "Clockwork Labs", - "license": "ISC" -} \ No newline at end of file + "name": "spacetime-docs", + "version": "1.0.0", + "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", + "main": "index.js", + "devDependencies": { + "typescript": "^5.4.5" + }, + "scripts": { + "build": "tsc" + }, + "author": "Clockwork Labs", + "license": "ISC" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..8cffafc8 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,18 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + typescript: + specifier: ^5.4.5 + version: 5.4.5 + +packages: + + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fce89544..00000000 --- a/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -typescript@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== From 09ece428977c5eccfc3addbf5a4ae72758e96afc Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Thu, 2 May 2024 10:11:54 -0400 Subject: [PATCH 38/80] Revert "fix: Docs build, pnpm, vm evaluate (#46) (#50)" (#52) This reverts commit 93cb9d6b2fe8a6be6d636174b1d5821852ff22c8. --- .prettierrc | 19 ------ docs/nav.js | 92 +++++++++++++--------------- nav.ts | 163 ++++++++++++++++++------------------------------- package.json | 27 ++++---- pnpm-lock.yaml | 18 ------ yarn.lock | 8 +++ 6 files changed, 124 insertions(+), 203 deletions(-) delete mode 100644 .prettierrc delete mode 100644 pnpm-lock.yaml create mode 100644 yarn.lock diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 81d845ca..00000000 --- a/.prettierrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "tabWidth": 4, - "useTabs": false, - "semi": true, - "singleQuote": true, - "arrowParens": "avoid", - "jsxSingleQuote": false, - "trailingComma": "none", - "endOfLine": "auto", - "printWidth": 80, - "overrides": [ - { - "files": "*.md", - "options": { - "tabWidth": 2 - } - } - ] -} diff --git a/docs/nav.js b/docs/nav.js index ec6d9d66..cb8d22f1 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,58 +1,52 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.nav = void 0; function page(title, slug, path, props) { - return { type: 'page', path, slug, title, ...props }; + return { type: "page", path, slug, title, ...props }; } function section(title) { - return { type: 'section', title }; + return { type: "section", title }; } const nav = { items: [ - section('Intro'), - page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page('Getting Started', 'getting-started', 'getting-started.md'), - section('Deploying'), - page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - section('Unity Tutorial - Basic Multiplayer'), - page('Overview', 'unity-tutorial', 'unity/index.md'), - page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), - page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), - page('2b - Server (C#)', 'unity/part-2b-c-sharp', 'unity/part-2b-c-sharp.md'), - page('3 - Client', 'unity/part-3', 'unity/part-3.md'), - section('Unity Tutorial - Advanced'), - page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), - page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), - section('Server Module Languages'), - page('Overview', 'modules', 'modules/index.md'), - page('Rust Quickstart', 'modules/rust/quickstart', 'modules/rust/quickstart.md'), - page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), - page('C# Quickstart', 'modules/c-sharp/quickstart', 'modules/c-sharp/quickstart.md'), - page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), - section('Client SDK Languages'), - page('Overview', 'sdks', 'sdks/index.md'), - page('Typescript Quickstart', 'sdks/typescript/quickstart', 'sdks/typescript/quickstart.md'), - page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), - page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), - page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), - page('Python Quickstart', 'sdks/python/quickstart', 'sdks/python/quickstart.md'), - page('Python Reference', 'sdks/python', 'sdks/python/index.md'), - page('C# Quickstart', 'sdks/c-sharp/quickstart', 'sdks/c-sharp/quickstart.md'), - page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), - section('WebAssembly ABI'), - page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), - section('HTTP API'), - page('HTTP', 'http', 'http/index.md'), - page('`/identity`', 'http/identity', 'http/identity.md'), - page('`/database`', 'http/database', 'http/database.md'), - page('`/energy`', 'http/energy', 'http/energy.md'), - section('WebSocket API Reference'), - page('WebSocket', 'ws', 'ws/index.md'), - section('Data Format'), - page('SATN', 'satn', 'satn.md'), - page('BSATN', 'bsatn', 'bsatn.md'), - section('SQL'), - page('SQL Reference', 'sql', 'sql/index.md') - ] + section("Intro"), + page("Overview", "index", "index.md"), + page("Getting Started", "getting-started", "getting-started.md"), + section("Deploying"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), + section("Unity Tutorial"), + page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), + page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), + page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + section("Server Module Languages"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + section("Client SDK Languages"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + section("WebAssembly ABI"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + section("HTTP API"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), + section("WebSocket API Reference"), + page("WebSocket", "ws", "ws/index.md"), + section("Data Format"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), + section("SQL"), + page("SQL Reference", "sql", "sql/index.md"), + ], }; -exports.nav = nav; +exports.default = nav; diff --git a/nav.ts b/nav.ts index b6eea77a..8f463ad7 100644 --- a/nav.ts +++ b/nav.ts @@ -1,129 +1,84 @@ type Nav = { - items: NavItem[]; + items: NavItem[]; }; type NavItem = NavPage | NavSection; type NavPage = { - type: 'page'; - path: string; - slug: string; - title: string; - disabled?: boolean; - href?: string; + type: "page"; + path: string; + slug: string; + title: string; + disabled?: boolean; + href?: string; }; type NavSection = { - type: 'section'; - title: string; + type: "section"; + title: string; }; -function page( - title: string, - slug: string, - path: string, - props?: { disabled?: boolean; href?: string; description?: string } -): NavPage { - return { type: 'page', path, slug, title, ...props }; +function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { + return { type: "page", path, slug, title, ...props }; } function section(title: string): NavSection { - return { type: 'section', title }; + return { type: "section", title }; } const nav: Nav = { - items: [ - section('Intro'), - page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page('Getting Started', 'getting-started', 'getting-started.md'), + items: [ + section("Intro"), + page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page("Getting Started", "getting-started", "getting-started.md"), - section('Deploying'), - page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + section("Deploying"), + page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section('Unity Tutorial - Basic Multiplayer'), - page('Overview', 'unity-tutorial', 'unity/index.md'), - page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), - page( - '2a - Server (Rust)', - 'unity/part-2a-rust', - 'unity/part-2a-rust.md' - ), - page( - '2b - Server (C#)', - 'unity/part-2b-c-sharp', - 'unity/part-2b-c-sharp.md' - ), - page('3 - Client', 'unity/part-3', 'unity/part-3.md'), + section("Unity Tutorial - Basic Multiplayer"), + page("Overview", "unity-tutorial", "unity/index.md"), + page("1 - Setup", "unity/part-1", "unity/part-1.md"), + page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), + page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), + page("3 - Client", "unity/part-3", "unity/part-3.md"), - section('Unity Tutorial - Advanced'), - page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), - page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), + section("Unity Tutorial - Advanced"), + page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), + page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), - section('Server Module Languages'), - page('Overview', 'modules', 'modules/index.md'), - page( - 'Rust Quickstart', - 'modules/rust/quickstart', - 'modules/rust/quickstart.md' - ), - page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), - page( - 'C# Quickstart', - 'modules/c-sharp/quickstart', - 'modules/c-sharp/quickstart.md' - ), - page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), + section("Server Module Languages"), + page("Overview", "modules", "modules/index.md"), + page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), + page("Rust Reference", "modules/rust", "modules/rust/index.md"), + page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), + page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), - section('Client SDK Languages'), - page('Overview', 'sdks', 'sdks/index.md'), - page( - 'Typescript Quickstart', - 'sdks/typescript/quickstart', - 'sdks/typescript/quickstart.md' - ), - page( - 'Typescript Reference', - 'sdks/typescript', - 'sdks/typescript/index.md' - ), - page( - 'Rust Quickstart', - 'sdks/rust/quickstart', - 'sdks/rust/quickstart.md' - ), - page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), - page( - 'Python Quickstart', - 'sdks/python/quickstart', - 'sdks/python/quickstart.md' - ), - page('Python Reference', 'sdks/python', 'sdks/python/index.md'), - page( - 'C# Quickstart', - 'sdks/c-sharp/quickstart', - 'sdks/c-sharp/quickstart.md' - ), - page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), + section("Client SDK Languages"), + page("Overview", "sdks", "sdks/index.md"), + page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), + page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), + page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), + page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), + page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), + page("Python Reference", "sdks/python", "sdks/python/index.md"), + page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), + page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), - section('WebAssembly ABI'), - page( - 'Module ABI Reference', - 'webassembly-abi', - 'webassembly-abi/index.md' - ), + section("WebAssembly ABI"), + page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), - section('HTTP API'), - page('HTTP', 'http', 'http/index.md'), - page('`/identity`', 'http/identity', 'http/identity.md'), - page('`/database`', 'http/database', 'http/database.md'), - page('`/energy`', 'http/energy', 'http/energy.md'), + section("HTTP API"), + page("HTTP", "http", "http/index.md"), + page("`/identity`", "http/identity", "http/identity.md"), + page("`/database`", "http/database", "http/database.md"), + page("`/energy`", "http/energy", "http/energy.md"), - section('WebSocket API Reference'), - page('WebSocket', 'ws', 'ws/index.md'), + section("WebSocket API Reference"), + page("WebSocket", "ws", "ws/index.md"), - section('Data Format'), - page('SATN', 'satn', 'satn.md'), - page('BSATN', 'bsatn', 'bsatn.md'), + section("Data Format"), + page("SATN", "satn", "satn.md"), + page("BSATN", "bsatn", "bsatn.md"), - section('SQL'), - page('SQL Reference', 'sql', 'sql/index.md') - ] + section("SQL"), + page("SQL Reference", "sql", "sql/index.md"), + ], }; -export { nav }; +export default nav; diff --git a/package.json b/package.json index 4b23519c..a56ea4e8 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { - "name": "spacetime-docs", - "version": "1.0.0", - "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", - "main": "index.js", - "devDependencies": { - "typescript": "^5.4.5" - }, - "scripts": { - "build": "tsc" - }, - "author": "Clockwork Labs", - "license": "ISC" -} + "name": "spacetime-docs", + "version": "1.0.0", + "description": "This repository contains the markdown files which are used to display documentation on our [website](https://spacetimedb.com/docs).", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "typescript": "^5.3.2" + }, + "scripts": { + "build": "tsc" + }, + "author": "Clockwork Labs", + "license": "ISC" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 8cffafc8..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,18 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -devDependencies: - typescript: - specifier: ^5.4.5 - version: 5.4.5 - -packages: - - /typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - dev: true diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fce89544 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== From d1902a7e2afc47b5df00863c290e3c592f59f9df Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Thu, 2 May 2024 20:51:12 +0530 Subject: [PATCH 39/80] fix: Unity tutorial slugs (#51) --- docs/nav.js | 15 ++++++++++----- nav.ts | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/nav.js b/docs/nav.js index cb8d22f1..4413888e 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -9,14 +9,19 @@ function section(title) { const nav = { items: [ section("Intro"), - page("Overview", "index", "index.md"), + page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? page("Getting Started", "getting-started", "getting-started.md"), section("Deploying"), page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section("Unity Tutorial"), - page("Part 1 - Basic Multiplayer", "unity/part-1", "unity/part-1.md"), - page("Part 2 - Resources And Scheduling", "unity/part-2", "unity/part-2.md"), - page("Part 3 - BitCraft Mini", "unity/part-3", "unity/part-3.md"), + section("Unity Tutorial - Basic Multiplayer"), + page("Overview", "unity-tutorial", "unity/index.md"), + page("1 - Setup", "unity/part-1", "unity/part-1.md"), + page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), + page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2b-c-sharp.md"), + page("3 - Client", "unity/part-3", "unity/part-3.md"), + section("Unity Tutorial - Advanced"), + page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), + page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), section("Server Module Languages"), page("Overview", "modules", "modules/index.md"), page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), diff --git a/nav.ts b/nav.ts index 8f463ad7..26a83f4c 100644 --- a/nav.ts +++ b/nav.ts @@ -35,7 +35,7 @@ const nav: Nav = { page("Overview", "unity-tutorial", "unity/index.md"), page("1 - Setup", "unity/part-1", "unity/part-1.md"), page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), - page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2a-c-sharp.md"), + page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2b-c-sharp.md"), page("3 - Client", "unity/part-3", "unity/part-3.md"), section("Unity Tutorial - Advanced"), From 801ab78d65e397ba35633b20130dcce8ffa3ed83 Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Thu, 23 May 2024 05:19:14 +0530 Subject: [PATCH 40/80] fix: Broken docs links (#53) --- docs/sdks/c-sharp/quickstart.md | 24 ++-- docs/sdks/typescript/index.md | 191 +++++++++++++------------------- docs/unity/index.md | 14 ++- docs/unity/part-1.md | 9 +- docs/unity/part-2a-rust.md | 20 ++-- docs/unity/part-2b-c-sharp.md | 18 +-- docs/unity/part-3.md | 35 +++--- docs/unity/part-4.md | 2 +- docs/unity/part-5.md | 2 +- 9 files changed, 148 insertions(+), 167 deletions(-) diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/sdks/c-sharp/quickstart.md index 92980f42..28f3c2e1 100644 --- a/docs/sdks/c-sharp/quickstart.md +++ b/docs/sdks/c-sharp/quickstart.md @@ -2,7 +2,7 @@ In this guide 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](../../modules/rust/quickstart.md) or [C# Module](../../modules/c-sharp/quickstart.md) 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](../../modules/rust/quickstart) or [C# Module](../../modules/c-sharp/quickstart) Quickstart guides. Ensure you followed one of these guides before continuing. ## Project structure @@ -184,10 +184,10 @@ void User_OnUpdate(User oldValue, User newValue, ReducerEvent dbEvent) { Console.WriteLine($"{UserNameOrIdentity(oldValue)} renamed to {newValue.Name}"); } - + if (oldValue.Online == newValue.Online) return; - + if (newValue.Online) { Console.WriteLine($"{UserNameOrIdentity(newValue)} connected."); @@ -257,10 +257,10 @@ We'll test both that our identity matches the sender and that the status is `Fai ```csharp void Reducer_OnSetNameEvent(ReducerEvent reducerEvent, string name) { - bool localIdentityFailedToChangeName = - reducerEvent.Identity == local_identity && + bool localIdentityFailedToChangeName = + reducerEvent.Identity == local_identity && reducerEvent.Status == ClientApi.Event.Types.Status.Failed; - + if (localIdentityFailedToChangeName) { Console.Write($"Failed to change name to {name}"); @@ -275,8 +275,8 @@ We handle warnings on rejected messages the same way as rejected names, though t ```csharp void Reducer_OnSendMessageEvent(ReducerEvent reducerEvent, string text) { - bool localIdentityFailedToSendMessage = - reducerEvent.Identity == local_identity && + bool localIdentityFailedToSendMessage = + reducerEvent.Identity == local_identity && reducerEvent.Status == ClientApi.Event.Types.Status.Failed; if (localIdentityFailedToSendMessage) @@ -293,9 +293,9 @@ Once we are connected, we can send our subscription to the SpacetimeDB module. S ```csharp void OnConnect() { - SpacetimeDBClient.instance.Subscribe(new List - { - "SELECT * FROM User", "SELECT * FROM Message" + SpacetimeDBClient.instance.Subscribe(new List + { + "SELECT * FROM User", "SELECT * FROM Message" }); } ``` @@ -349,7 +349,7 @@ Since the input loop will be blocking, we'll run our processing code in a separa ```csharp const string HOST = "http://localhost:3000"; const string DBNAME = "module"; - + void ProcessThread() { SpacetimeDBClient.instance.Connect(AuthToken.Token, HOST, DBNAME); diff --git a/docs/sdks/typescript/index.md b/docs/sdks/typescript/index.md index fd7c9e91..166c1575 100644 --- a/docs/sdks/typescript/index.md +++ b/docs/sdks/typescript/index.md @@ -10,11 +10,11 @@ First, create a new client project, and add the following to your `tsconfig.json ```json { - "compilerOptions": { - //You can use any target higher than this one - //https://www.typescriptlang.org/tsconfig#target - "target": "es2015" - } + "compilerOptions": { + //You can use any target higher than this one + //https://www.typescriptlang.org/tsconfig#target + "target": "es2015" + } } ``` @@ -77,11 +77,11 @@ quickstart-chat Import the `module_bindings` in your client's _main_ file: ```typescript -import { SpacetimeDBClient, Identity } from "@clockworklabs/spacetimedb-sdk"; +import { SpacetimeDBClient, Identity } from '@clockworklabs/spacetimedb-sdk'; -import Person from "./module_bindings/person"; -import AddReducer from "./module_bindings/add_reducer"; -import SayHelloReducer from "./module_bindings/say_hello_reducer"; +import Person from './module_bindings/person'; +import AddReducer from './module_bindings/add_reducer'; +import SayHelloReducer from './module_bindings/say_hello_reducer'; console.log(Person, AddReducer, SayHelloReducer); ``` @@ -92,7 +92,7 @@ console.log(Person, AddReducer, SayHelloReducer); ### Classes | Class | Description | -|-------------------------------------------------|------------------------------------------------------------------------------| +| ----------------------------------------------- | ---------------------------------------------------------------------------- | | [`SpacetimeDBClient`](#class-spacetimedbclient) | The database client connection to a SpacetimeDB server. | | [`Identity`](#class-identity) | The user's public identity. | | [`Address`](#class-address) | An opaque identifier for differentiating connections by the same `Identity`. | @@ -142,17 +142,12 @@ new SpacetimeDBClient(host: string, name_or_address: string, auth_token?: string #### Example ```ts -const host = "ws://localhost:3000"; -const name_or_address = "database_name"; +const host = 'ws://localhost:3000'; +const name_or_address = 'database_name'; const auth_token = undefined; -const protocol = "binary"; +const protocol = 'binary'; -var spacetimeDBClient = new SpacetimeDBClient( - host, - name_or_address, - auth_token, - protocol -); +var spacetimeDBClient = new SpacetimeDBClient(host, name_or_address, auth_token, protocol); ``` ## Class methods @@ -167,9 +162,9 @@ registerReducers(...reducerClasses: ReducerClass[]) #### Parameters -| Name | Type | Description | -| :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `reducerClasses` | `ReducerClass` | A list of classes to register | +| Name | Type | Description | +| :--------------- | :------------- | :---------------------------- | +| `reducerClasses` | `ReducerClass` | A list of classes to register | #### Example @@ -192,9 +187,9 @@ registerTables(...reducerClasses: TableClass[]) #### Parameters -| Name | Type | Description | -| :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `tableClasses` | `TableClass` | A list of classes to register | +| Name | Type | Description | +| :------------- | :----------- | :---------------------------- | +| `tableClasses` | `TableClass` | A list of classes to register | #### Example @@ -239,10 +234,10 @@ token: string | undefined #### Parameters -| Name | Type | Description | -| :------------ | :----------------------------------------------------- | :------------------------------ | -| `reducerName` | `string` | The name of the reducer to call | -| `serializer` | [`Serializer`](../interfaces/serializer.Serializer.md) | - | +| Name | Type | Description | +| :------------ | :----------- | :------------------------------ | +| `reducerName` | `string` | The name of the reducer to call | +| `serializer` | `Serializer` | - | --- @@ -269,15 +264,11 @@ connect(host: string?, name_or_address: string?, auth_token: string?): Promise `void` | #### Example ```ts spacetimeDBClient.onConnect((token, identity, address) => { - console.log("Connected to SpacetimeDB"); - console.log("Token", token); - console.log("Identity", identity); - console.log("Address", address); + console.log('Connected to SpacetimeDB'); + console.log('Token', token); + console.log('Identity', identity); + console.log('Address', address); }); ``` @@ -382,7 +370,7 @@ onError(callback: (...args: any[]) => void): void ```ts spacetimeDBClient.onError((...args: any[]) => { - console.error("ERROR", args); + console.error('ERROR', args); }); ``` @@ -475,13 +463,13 @@ An opaque identifier for a client connection to a database, intended to differen Defined in [spacetimedb-sdk.address](https://github.com/clockworklabs/spacetimedb-typescript-sdk/blob/main/src/address.ts): -| Constructors | Description | -| ----------------------------------------------- | -------------------------------------------- | +| Constructors | Description | +| --------------------------------------------- | ------------------------------------------- | | [`Address.constructor`](#address-constructor) | Creates a new `Address`. | -| Methods | | -| [`Address.isEqual`](#address-isequal) | Compare two identities for equality. | +| Methods | | +| [`Address.isEqual`](#address-isequal) | Compare two identities for equality. | | [`Address.toHexString`](#address-tohexstring) | Print the address as a hexadecimal string. | -| Static methods | | +| Static methods | | | [`Address.fromString`](#address-fromstring) | Parse an Address from a hexadecimal string. | ## Constructors @@ -510,15 +498,15 @@ isEqual(other: Address): boolean #### Parameters -| Name | Type | -| :------ | :---------------------------- | +| Name | Type | +| :------ | :-------------------------- | | `other` | [`Address`](#class-address) | #### Returns `boolean` -___ +--- ### `Address` toHexString @@ -532,7 +520,7 @@ toHexString(): string `string` -___ +--- ### `Address` fromString @@ -607,17 +595,14 @@ Return all the subscribed rows in the table. #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.all()); // Prints all the `Person` rows in the database. - }, 5000); + setTimeout(() => { + console.log(Person.all()); // Prints all the `Person` rows in the database. + }, 5000); }); ``` @@ -638,17 +623,14 @@ Return the number of subscribed rows in the table, or 0 if there is no active co #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.count()); - }, 5000); + setTimeout(() => { + console.log(Person.count()); + }, 5000); }); ``` @@ -677,17 +659,14 @@ These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.filterByName("John")); // prints all the `Person` rows named John. - }, 5000); + setTimeout(() => { + console.log(Person.filterByName('John')); // prints all the `Person` rows named John. + }, 5000); }); ``` @@ -746,20 +725,17 @@ Register an `onInsert` callback for when a subscribed row is newly inserted into #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onInsert((person, reducerEvent) => { - if (reducerEvent) { - console.log("New person inserted by reducer", reducerEvent, person); - } else { - console.log("New person received during subscription update", person); - } + if (reducerEvent) { + console.log('New person inserted by reducer', reducerEvent, person); + } else { + console.log('New person received during subscription update', person); + } }); ``` @@ -800,16 +776,13 @@ Register an `onUpdate` callback to run when an existing row is modified by prima #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onUpdate((oldPerson, newPerson, reducerEvent) => { - console.log("Person updated by reducer", reducerEvent, oldPerson, newPerson); + console.log('Person updated by reducer', reducerEvent, oldPerson, newPerson); }); ``` @@ -848,23 +821,17 @@ Register an `onDelete` callback for when a subscribed row is removed from the da #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "database_name" -); +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(["SELECT * FROM Person"]); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onDelete((person, reducerEvent) => { - if (reducerEvent) { - console.log("Person deleted by reducer", reducerEvent, person); - } else { - console.log( - "Person no longer subscribed during subscription update", - person - ); - } + if (reducerEvent) { + console.log('Person deleted by reducer', reducerEvent, person); + } else { + console.log('Person no longer subscribed during subscription update', person); + } }); ``` @@ -929,14 +896,14 @@ Clients will only be notified of reducer runs if either of two criteria is met: #### Parameters -| Name | Type | -| :--------- | :---------------------------------------------------------- | +| Name | Type | +| :--------- | :------------------------------------------------------------- | | `callback` | `(reducerEvent: ReducerEvent, ...reducerArgs: any[]) => void)` | #### Example ```ts SayHelloReducer.on((reducerEvent, ...reducerArgs) => { - console.log("SayHelloReducer called", reducerEvent, reducerArgs); + console.log('SayHelloReducer called', reducerEvent, reducerArgs); }); ``` diff --git a/docs/unity/index.md b/docs/unity/index.md index 2b8e6d67..76970748 100644 --- a/docs/unity/index.md +++ b/docs/unity/index.md @@ -9,15 +9,17 @@ We'll give you some CLI commands to execute. If you are using Windows, we recomm Tested with UnityEngine `2022.3.20f1 LTS` (and may also work on newer versions). ## Unity Tutorial - Basic Multiplayer + Get started with the core client-server setup. For part 2, you may choose your server module preference of [Rust](/docs/modules/rust) or [C#](/docs/modules/c-sharp): -- [Part 1 - Setup](/docs/unity/part-1.md) -- [Part 2a - Server (Rust)](/docs/unity/part-2a-rust.md) -- [Part 2b - Server (C#)](/docs/unity/part-2b-csharp.md) -- [Part 3 - Client](/docs/unity/part-3.md) +- [Part 1 - Setup](/docs/unity/part-1) +- [Part 2a - Server (Rust)](/docs/unity/part-2a-rust) +- [Part 2b - Server (C#)](/docs/unity/part-2b-csharp) +- [Part 3 - Client](/docs/unity/part-3) ## Unity Tutorial - Advanced + By this point, you should already have a basic understanding of SpacetimeDB client, server and CLI: -- [Part 4 - Resources & Scheduling](/docs/unity/part-4.md) -- [Part 5 - BitCraft Mini](/docs/unity/part-5.md) +- [Part 4 - Resources & Scheduling](/docs/unity/part-4) +- [Part 5 - BitCraft Mini](/docs/unity/part-5) diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index b8b8c3c0..0db2f5aa 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -8,7 +8,7 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacet This project is separated into two sub-projects; -1. Server (module) code +1. Server (module) code 2. Client code First, we'll create a project root directory (you can choose the name): @@ -107,7 +107,7 @@ spacetime start ``` 💡 Standalone mode will run in the foreground. -💡 Below examples Rust language, [but you may also use C#](../modules/c-sharp/index.md). +💡 Below examples Rust language, [but you may also use C#](../modules/c-sharp). ### The Entity Component Systems (ECS) @@ -118,5 +118,6 @@ We chose ECS for this example project because it promotes scalability, modularit ### Create the Server Module From here, the tutorial continues with your favorite server module language of choice: - - [Rust](part-2a-rust.md) - - [C#](part-2b-csharp.md) + +- [Rust](part-2a-rust) +- [C#](part-2b-csharp) diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md index 9b12de47..fd9361f2 100644 --- a/docs/unity/part-2a-rust.md +++ b/docs/unity/part-2a-rust.md @@ -2,7 +2,7 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1.md) +This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1) ## Create a Server Module @@ -84,7 +84,7 @@ Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is **Append to the bottom of lib.rs:** ```rust -// All players have this component and it associates an entity with the user's +// All players have this component and it associates an entity with the user's // Identity. It also stores their username and whether or not they're logged in. #[derive(Clone)] #[spacetimedb(table)] @@ -92,7 +92,7 @@ pub struct PlayerComponent { // An entity_id that matches an entity_id in the `EntityComponent` table. #[primarykey] pub entity_id: u64, - + // The user's identity, which is unique to each player #[unique] pub owner_id: Identity, @@ -120,9 +120,9 @@ pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String } // Create a new entity for this player and get a unique `entity_id`. - let entity_id = EntityComponent::insert(EntityComponent - { - entity_id: 0, + let entity_id = EntityComponent::insert(EntityComponent + { + entity_id: 0, position: StdbVector3 { x: 0.0, y: 0.0, z: 0.0 }, direction: 0.0, moving: false, @@ -183,6 +183,7 @@ pub fn client_connected(ctx: ReducerContext) { update_player_login_state(ctx, true); } ``` + ```rust // Called when the client disconnects, we update the logged_in state to false #[spacetimedb(disconnect)] @@ -190,6 +191,7 @@ pub fn client_disconnected(ctx: ReducerContext) { update_player_login_state(ctx, false); } ``` + ```rust // This helper function gets the PlayerComponent, sets the logged // in variable and updates the PlayerComponent table row. @@ -230,7 +232,7 @@ pub fn update_player_position( } } - // If we can not find the PlayerComponent or EntityComponent for + // If we can not find the PlayerComponent or EntityComponent for // this player then something went wrong. return Err("Player not found".to_string()); } @@ -257,7 +259,7 @@ spacetime publish -c unity-tutorial The client project has a chat window, but so far, all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. -First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.rs``. +First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to `lib.rs`. **Append to the bottom of server/src/lib.rs:** @@ -309,4 +311,4 @@ Now that we added chat support, let's publish the latest module version to Space spacetime publish -c unity-tutorial ``` -From here, the [next tutorial](/docs/unity/part-3.md) continues with a Client (Unity) focus. +From here, the [next tutorial](/docs/unity/part-3) continues with a Client (Unity) focus. diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index f324a36d..ee6c0028 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -2,7 +2,7 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1.md) +This progressive tutorial is continued from the [Part 1 Tutorial](/docs/unity/part-1) ## Create a Server Module @@ -91,7 +91,7 @@ public partial class PlayerComponent // An EntityId that matches an EntityId in the `EntityComponent` table. [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] public ulong EntityId; - + // The user's identity, which is unique to each player [SpacetimeDB.Column(ColumnAttrs.Unique)] public Identity Identity; @@ -136,7 +136,7 @@ public static void CreatePlayer(DbEventArgs dbEvent, string username) Log("Error: Failed to create a unique PlayerComponent", LogLevel.Error); Throw; } - + // The PlayerComponent uses the same entity_id and stores the identity of // the owner, username, and whether or not they are logged in. try @@ -207,12 +207,14 @@ We use the `Connect` and `Disconnect` reducers to update the logged in state of public static void ClientConnected(DbEventArgs dbEvent) => UpdatePlayerLoginState(dbEvent, loggedIn:true); ``` + ```csharp /// Called when the client disconnects, we update the logged_in state to false [SpacetimeDB.Reducer(ReducerKind.Disconnect)] public static void ClientDisonnected(DbEventArgs dbEvent) => UpdatePlayerLoginState(dbEvent, loggedIn:false); ``` + ```csharp /// This helper function gets the PlayerComponent, sets the LoggedIn /// variable and updates the PlayerComponent table row. @@ -257,7 +259,7 @@ private static void UpdatePlayerPosition( { throw new ArgumentException($"Player Entity '{playerEntityId}' not found"); } - + entity.Position = position; entity.Direction = direction; entity.Moving = moving; @@ -286,7 +288,7 @@ spacetime publish -c unity-tutorial The client project has a chat window, but so far, all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. -First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to ``lib.cs``. +First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the following code to `lib.cs`. **Append to the bottom of server/src/lib.cs:** @@ -296,10 +298,10 @@ public partial class ChatMessage { // The primary key for this table will be auto-incremented [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] - + // The entity id of the player that sent the message public ulong SenderId; - + // Message contents public string? Text; } @@ -341,4 +343,4 @@ Now that we added chat support, let's publish the latest module version to Space spacetime publish -c unity-tutorial ``` -From here, the [next tutorial](/docs/unity/part-3.md) continues with a Client (Unity) focus. \ No newline at end of file +From here, the [next tutorial](/docs/unity/part-3) continues with a Client (Unity) focus. diff --git a/docs/unity/part-3.md b/docs/unity/part-3.md index c80000e1..d1db4dbb 100644 --- a/docs/unity/part-3.md +++ b/docs/unity/part-3.md @@ -2,9 +2,10 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -This progressive tutorial is continued from one of the Part 2 tutorials: -- [Rust Server Module](/docs/unity/part-2a-rust.md) -- [C# Server Module](/docs/unity/part-2b-c-sharp.md) +This progressive tutorial is continued from one of the Part 2 tutorials: + +- [Rust Server Module](/docs/unity/part-2a-rust) +- [C# Server Module](/docs/unity/part-2b-c-sharp) ## Updating our Unity Project Client to use SpacetimeDB @@ -161,7 +162,7 @@ Then we're doing a modification to the `ButtonPressed()` function: ```csharp public void ButtonPressed() -{ +{ CameraController.RemoveDisabler(GetHashCode()); _panel.SetActive(false); @@ -205,11 +206,11 @@ public class RemotePlayer : MonoBehaviour string inputUsername = UsernameElement.Text; Debug.Log($"PlayerComponent not found - Creating a new player ({inputUsername})"); Reducer.CreatePlayer(inputUsername); - + // Try again, optimistically assuming success for simplicity PlayerComponent? playerComp = PlayerComponent.FilterByEntityId(EntityId).FirstOrDefault(); } - + Username = playerComp.Username; // Get the last location for this player and set the initial position @@ -268,16 +269,16 @@ private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo { // Spawn the player object and attach the RemotePlayer component var remotePlayer = Instantiate(PlayerPrefab); - + // Lookup and apply the position for this new player var entity = EntityComponent.FilterByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; - + var movementController = remotePlayer.GetComponent(); movementController.RemoteTargetPosition = position; movementController.RemoteTargetRotation = entity.Direction; - + remotePlayer.AddComponent().EntityId = obj.EntityId; } } @@ -309,7 +310,7 @@ private void FixedUpdate() lastUpdateTime = Time.time; var p = PlayerMovementController.Local.GetModelPosition(); - + Reducer.UpdatePlayerPosition(new StdbVector3 { X = p.x, @@ -338,6 +339,7 @@ When you hit the `Build` button, it will kick off a build of the game which will So far we have not handled the `logged_in` variable of the `PlayerComponent`. This means that remote players will not despawn on your screen when they disconnect. To fix this we need to handle the `OnUpdate` event for the `PlayerComponent` table in addition to `OnInsert`. We are going to use a common function that handles any time the `PlayerComponent` changes. **Append to the bottom of Start() function in TutorialGameManager.cs** + ```csharp PlayerComponent.OnUpdate += PlayerComponent_OnUpdate; ``` @@ -347,6 +349,7 @@ We are going to add a check to determine if the player is logged for remote play Next we'll be updating some of the code in `PlayerComponent_OnInsert`. For simplicity, just replace the entire function. **REPLACE PlayerComponent_OnInsert in TutorialGameManager.cs** + ```csharp private void PlayerComponent_OnUpdate(PlayerComponent oldValue, PlayerComponent newValue, ReducerEvent dbEvent) { @@ -377,16 +380,16 @@ private void OnPlayerComponentChanged(PlayerComponent obj) { // Spawn the player object and attach the RemotePlayer component var remotePlayer = Instantiate(PlayerPrefab); - + // Lookup and apply the position for this new player var entity = EntityComponent.FilterByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; - + var movementController = remotePlayer.GetComponent(); movementController.RemoteTargetPosition = position; movementController.RemoteTargetRotation = entity.Direction; - + remotePlayer.AddComponent().EntityId = obj.EntityId; } } @@ -406,6 +409,7 @@ Now you when you play the game you should see remote players disappear when they Before updating the client, let's generate the client files and update publish our module. **Execute commands in the server/ directory** + ```bash spacetime generate --out-dir ../client/Assets/module_bindings --lang=csharp spacetime publish -c unity-tutorial @@ -414,6 +418,7 @@ spacetime publish -c unity-tutorial On the client, let's add code to send the message when the chat button or enter is pressed. Update the `OnChatButtonPress` function in `UIChatController.cs`. **Append to the top of UIChatController.cs:** + ```csharp using SpacetimeDB.Types; ``` @@ -431,6 +436,7 @@ public void OnChatButtonPress() Now we need to add a reducer to handle inserting new chat messages. First register for the ChatMessage reducer in the `Start()` function using the auto-generated function: **Append to the bottom of the Start() function in TutorialGameManager.cs:** + ```csharp Reducer.OnSendChatMessageEvent += OnSendChatMessageEvent; ``` @@ -438,6 +444,7 @@ Reducer.OnSendChatMessageEvent += OnSendChatMessageEvent; Now we write the `OnSendChatMessageEvent` function. We can find the `PlayerComponent` for the player who sent the message using the `Identity` of the sender. Then we get the `Username` and prepend it to the message before sending it to the chat window. **Append after the Start() function in TutorialGameManager.cs** + ```csharp private void OnSendChatMessageEvent(ReducerEvent dbEvent, string message) { @@ -455,7 +462,7 @@ Now when you run the game you should be able to send chat messages to other play This concludes the SpacetimeDB basic multiplayer tutorial, where we learned how to create a multiplayer game. In the next Unity tutorial, we will add resource nodes to the game and learn about _scheduled_ reducers: -From here, the tutorial continues with more-advanced topics: The [next tutorial](/docs/unity/part-4.md) introduces Resources & Scheduling. +From here, the tutorial continues with more-advanced topics: The [next tutorial](/docs/unity/part-4) introduces Resources & Scheduling. --- diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index a87f27a2..b8af1018 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -2,7 +2,7 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -This progressive tutorial is continued from the [Part 3](/docs/unity/part-3.md) Tutorial. +This progressive tutorial is continued from the [Part 3](/docs/unity/part-3) Tutorial. **Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** diff --git a/docs/unity/part-5.md b/docs/unity/part-5.md index 6ebce1c0..2c59c73b 100644 --- a/docs/unity/part-5.md +++ b/docs/unity/part-5.md @@ -2,7 +2,7 @@ Need help with the tutorial? [Join our Discord server](https://discord.gg/spacetimedb)! -This progressive tutorial is continued from the [Part 4](/docs/unity/part-3.md) Tutorial. +This progressive tutorial is continued from the [Part 4](/docs/unity/part-3) Tutorial. **Oct 14th, 2023: This tutorial has not yet been updated for the recent 0.7.0 release, it will be updated asap!** From bb0efc66be695b461dbe4a9968379d9e2756c83c Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Wed, 22 May 2024 17:01:27 -0700 Subject: [PATCH 41/80] Fix the C# module link in overview (#54) * [bfops/docs]: C# fix * [bfops/docs]: empty --------- Co-authored-by: Zeke Foppa --- docs/unity/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/unity/index.md b/docs/unity/index.md index 76970748..0fa181c6 100644 --- a/docs/unity/index.md +++ b/docs/unity/index.md @@ -14,7 +14,7 @@ Get started with the core client-server setup. For part 2, you may choose your s - [Part 1 - Setup](/docs/unity/part-1) - [Part 2a - Server (Rust)](/docs/unity/part-2a-rust) -- [Part 2b - Server (C#)](/docs/unity/part-2b-csharp) +- [Part 2b - Server (C#)](/docs/unity/part-2b-c-sharp) - [Part 3 - Client](/docs/unity/part-3) ## Unity Tutorial - Advanced From 38e769c0ae1a7fa222717316d0ffd66b5dbaa136 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Wed, 22 May 2024 19:23:32 -0700 Subject: [PATCH 42/80] Fix the C# module link in part 1 (#55) * [bfops/docs]: empty * [bfops/docs]: one more fix --------- Co-authored-by: Zeke Foppa --- docs/unity/part-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index 0db2f5aa..c53814d1 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -120,4 +120,4 @@ We chose ECS for this example project because it promotes scalability, modularit From here, the tutorial continues with your favorite server module language of choice: - [Rust](part-2a-rust) -- [C#](part-2b-csharp) +- [C#](part-2b-c-sharp) From 0ddd4eb3dadd5b5979301bfe4c2cd190859563a0 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:41:59 -0700 Subject: [PATCH 43/80] Update docs for `#[spacetimedb(table)]` (#61) * [bfops/public-tables]: update docs for #[spacetimedb(table)] * [bfops/public-tables]: review --------- Co-authored-by: Zeke Foppa --- docs/modules/rust/index.md | 24 +++++++++++++----------- docs/modules/rust/quickstart.md | 12 ++++++++---- docs/unity/part-2a-rust.md | 12 ++++++------ docs/unity/part-2b-c-sharp.md | 2 +- docs/unity/part-4.md | 6 +++--- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 05d62bdc..5e64051d 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -31,7 +31,7 @@ use spacetimedb::{spacetimedb, println}; // This macro lets us interact with a SpacetimeDB table of Person rows. // We can insert and delete into, and query, this table by the collection // of functions generated by the macro. -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Person { name: String, } @@ -88,10 +88,12 @@ Now we'll get into details on all the macro APIs SpacetimeDB provides, starting ### Defining tables -`#[spacetimedb(table)]` takes no further arguments, and is applied to a Rust struct with named fields: +The `#[spacetimedb(table)]` is applied to a Rust struct with named fields. +By default, tables are considered **private**. This means that they are only readable by the table owner, and by server module code. +The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Table { field1: String, field2: u32, @@ -116,10 +118,10 @@ And common data structures: - `Option where T: SpacetimeType` - `Vec where T: SpacetimeType` -All `#[spacetimedb(table)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. +All `#[spacetimedb(table(...))]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct AnotherTable { // Fine, some builtin types. id: u64, @@ -151,7 +153,7 @@ enum Serial { Once the table is created via the macro, other attributes described below can control more aspects of the table. For instance, a particular column can be indexed, or take on values of an automatically incremented counter. These are described in detail below. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Person { #[unique] id: u64, @@ -269,7 +271,7 @@ We'll work off these structs to see what functions SpacetimeDB generates: This table has a plain old column. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Ordinary { ordinary_field: u64, } @@ -278,7 +280,7 @@ struct Ordinary { This table has a unique column. Every row in the `Person` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Unique { // A unique column: #[unique] @@ -291,7 +293,7 @@ This table has an automatically incrementing column. SpacetimeDB automatically p Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Autoinc { #[autoinc] autoinc_field: u64, @@ -301,7 +303,7 @@ struct Autoinc { These attributes can be combined, to create an automatically assigned ID usable for filtering. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Identity { #[autoinc] #[unique] @@ -375,7 +377,7 @@ fn insert_id() { Given a table, we can iterate over all the rows in it. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Person { #[unique] id: u64, diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index e015b881..062aa163 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -6,7 +6,11 @@ A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded t Each SpacetimeDB module defines a set of tables and a set of reducers. -Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table)]`, where an instance represents a row, and each field represents a column. +Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table(...))]`, where an instance represents a row, and each field represents a column. +By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. +The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. + +_Coming soon: We plan to add much more robust access controls than just `public` or `private`. Stay tuned!_ A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In Rust, reducers are defined as functions annotated with `#[spacetimedb(reducer)]`, and may return a `Result<()>`, with an `Err` return aborting the transaction. @@ -67,7 +71,7 @@ For each `User`, we'll store their `Identity`, an optional name they can set to To `server/src/lib.rs`, add the definition of the table `User`: ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct User { #[primarykey] identity: Identity, @@ -81,7 +85,7 @@ For each `Message`, we'll store the `Identity` of the user who sent it, the `Tim To `server/src/lib.rs`, add the definition of the table `Message`: ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Message { sender: Identity, sent: Timestamp, @@ -179,7 +183,7 @@ You could extend the validation in `validate_message` in similar ways to `valida Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` attribute, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table(...))]` attribute, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. To `server/src/lib.rs`, add the definition of the connect reducer: diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md index fd9361f2..0acac51c 100644 --- a/docs/unity/part-2a-rust.md +++ b/docs/unity/part-2a-rust.md @@ -29,13 +29,13 @@ use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; use log; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [Rust module reference](/docs/modules/rust). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table(...))]` which you can learn more about in our [Rust module reference](/docs/modules/rust) (including making your tables `private`!). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.rs:** ```rust // We're using this table as a singleton, so there should typically only be one element where the version is 0. -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct Config { #[primarykey] @@ -44,7 +44,7 @@ pub struct Config { } ``` -Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. +Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. **Append to the bottom of lib.rs:** @@ -64,7 +64,7 @@ Now we're going to create a table which actually uses the `StdbVector3` that we // This stores information related to all entities in our game. In this tutorial // all entities must at least have an entity_id, a position, a direction and they // must specify whether or not they are moving. -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct EntityComponent { #[primarykey] @@ -87,7 +87,7 @@ Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is // All players have this component and it associates an entity with the user's // Identity. It also stores their username and whether or not they're logged in. #[derive(Clone)] -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct PlayerComponent { // An entity_id that matches an entity_id in the `EntityComponent` table. #[primarykey] @@ -264,7 +264,7 @@ First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the foll **Append to the bottom of server/src/lib.rs:** ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct ChatMessage { // The primary key for this table will be auto-incremented #[primarykey] diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index ee6c0028..8cdb0947 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -30,7 +30,7 @@ using SpacetimeDB.Module; using static SpacetimeDB.Runtime; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of attributes, like `[SpacetimeDB.Table]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.cs:** diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index b8af1018..b3a17439 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -34,7 +34,7 @@ pub enum ResourceNodeType { Iron, } -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct ResourceNodeComponent { #[primarykey] @@ -48,7 +48,7 @@ pub struct ResourceNodeComponent { Because resource nodes never move, the `MobileEntityComponent` is overkill. Instead, we will add a new entity component named `StaticLocationComponent` that only stores the position and rotation. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct StaticLocationComponent { #[primarykey] @@ -62,7 +62,7 @@ pub struct StaticLocationComponent { 3. We are also going to add a couple of additional column to our Config table. `map_extents` let's our spawner know where it can spawn the nodes. `num_resource_nodes` is the maximum number of nodes to spawn on the map. Update the config table in lib.rs. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Config { // Config is a global table with a single row. This table will be used to // store configuration or global variables From 93ac70933bdb02911e2f917a043d743298a8d771 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:48:05 -0700 Subject: [PATCH 44/80] Revert "Update docs for `#[spacetimedb(table)]` (#61)" (#62) This reverts commit 0ddd4eb3dadd5b5979301bfe4c2cd190859563a0. --- docs/modules/rust/index.md | 24 +++++++++++------------- docs/modules/rust/quickstart.md | 12 ++++-------- docs/unity/part-2a-rust.md | 12 ++++++------ docs/unity/part-2b-c-sharp.md | 2 +- docs/unity/part-4.md | 6 +++--- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 5e64051d..05d62bdc 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -31,7 +31,7 @@ use spacetimedb::{spacetimedb, println}; // This macro lets us interact with a SpacetimeDB table of Person rows. // We can insert and delete into, and query, this table by the collection // of functions generated by the macro. -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct Person { name: String, } @@ -88,12 +88,10 @@ Now we'll get into details on all the macro APIs SpacetimeDB provides, starting ### Defining tables -The `#[spacetimedb(table)]` is applied to a Rust struct with named fields. -By default, tables are considered **private**. This means that they are only readable by the table owner, and by server module code. -The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. +`#[spacetimedb(table)]` takes no further arguments, and is applied to a Rust struct with named fields: ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Table { field1: String, field2: u32, @@ -118,10 +116,10 @@ And common data structures: - `Option where T: SpacetimeType` - `Vec where T: SpacetimeType` -All `#[spacetimedb(table(...))]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. +All `#[spacetimedb(table)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct AnotherTable { // Fine, some builtin types. id: u64, @@ -153,7 +151,7 @@ enum Serial { Once the table is created via the macro, other attributes described below can control more aspects of the table. For instance, a particular column can be indexed, or take on values of an automatically incremented counter. These are described in detail below. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Person { #[unique] id: u64, @@ -271,7 +269,7 @@ We'll work off these structs to see what functions SpacetimeDB generates: This table has a plain old column. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Ordinary { ordinary_field: u64, } @@ -280,7 +278,7 @@ struct Ordinary { This table has a unique column. Every row in the `Person` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Unique { // A unique column: #[unique] @@ -293,7 +291,7 @@ This table has an automatically incrementing column. SpacetimeDB automatically p Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Autoinc { #[autoinc] autoinc_field: u64, @@ -303,7 +301,7 @@ struct Autoinc { These attributes can be combined, to create an automatically assigned ID usable for filtering. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Identity { #[autoinc] #[unique] @@ -377,7 +375,7 @@ fn insert_id() { Given a table, we can iterate over all the rows in it. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] struct Person { #[unique] id: u64, diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index 062aa163..e015b881 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -6,11 +6,7 @@ A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded t Each SpacetimeDB module defines a set of tables and a set of reducers. -Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table(...))]`, where an instance represents a row, and each field represents a column. -By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. -The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. - -_Coming soon: We plan to add much more robust access controls than just `public` or `private`. Stay tuned!_ +Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table)]`, where an instance represents a row, and each field represents a column. A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In Rust, reducers are defined as functions annotated with `#[spacetimedb(reducer)]`, and may return a `Result<()>`, with an `Err` return aborting the transaction. @@ -71,7 +67,7 @@ For each `User`, we'll store their `Identity`, an optional name they can set to To `server/src/lib.rs`, add the definition of the table `User`: ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct User { #[primarykey] identity: Identity, @@ -85,7 +81,7 @@ For each `Message`, we'll store the `Identity` of the user who sent it, the `Tim To `server/src/lib.rs`, add the definition of the table `Message`: ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct Message { sender: Identity, sent: Timestamp, @@ -183,7 +179,7 @@ You could extend the validation in `validate_message` in similar ways to `valida Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table(...))]` attribute, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` attribute, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. To `server/src/lib.rs`, add the definition of the connect reducer: diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md index 0acac51c..fd9361f2 100644 --- a/docs/unity/part-2a-rust.md +++ b/docs/unity/part-2a-rust.md @@ -29,13 +29,13 @@ use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; use log; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table(...))]` which you can learn more about in our [Rust module reference](/docs/modules/rust) (including making your tables `private`!). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [Rust module reference](/docs/modules/rust). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.rs:** ```rust // We're using this table as a singleton, so there should typically only be one element where the version is 0. -#[spacetimedb(table(public))] +#[spacetimedb(table)] #[derive(Clone)] pub struct Config { #[primarykey] @@ -44,7 +44,7 @@ pub struct Config { } ``` -Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. +Next, we're going to define a new `SpacetimeType` called `StdbVector3` 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, `StdbVector3` is not, itself, a table. **Append to the bottom of lib.rs:** @@ -64,7 +64,7 @@ Now we're going to create a table which actually uses the `StdbVector3` that we // This stores information related to all entities in our game. In this tutorial // all entities must at least have an entity_id, a position, a direction and they // must specify whether or not they are moving. -#[spacetimedb(table(public))] +#[spacetimedb(table)] #[derive(Clone)] pub struct EntityComponent { #[primarykey] @@ -87,7 +87,7 @@ Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is // All players have this component and it associates an entity with the user's // Identity. It also stores their username and whether or not they're logged in. #[derive(Clone)] -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct PlayerComponent { // An entity_id that matches an entity_id in the `EntityComponent` table. #[primarykey] @@ -264,7 +264,7 @@ First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the foll **Append to the bottom of server/src/lib.rs:** ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct ChatMessage { // The primary key for this table will be auto-incremented #[primarykey] diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index 8cdb0947..ee6c0028 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -30,7 +30,7 @@ using SpacetimeDB.Module; using static SpacetimeDB.Runtime; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of attributes, like `[SpacetimeDB.Table]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.cs:** diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index b3a17439..b8af1018 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -34,7 +34,7 @@ pub enum ResourceNodeType { Iron, } -#[spacetimedb(table(public))] +#[spacetimedb(table)] #[derive(Clone)] pub struct ResourceNodeComponent { #[primarykey] @@ -48,7 +48,7 @@ pub struct ResourceNodeComponent { Because resource nodes never move, the `MobileEntityComponent` is overkill. Instead, we will add a new entity component named `StaticLocationComponent` that only stores the position and rotation. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] #[derive(Clone)] pub struct StaticLocationComponent { #[primarykey] @@ -62,7 +62,7 @@ pub struct StaticLocationComponent { 3. We are also going to add a couple of additional column to our Config table. `map_extents` let's our spawner know where it can spawn the nodes. `num_resource_nodes` is the maximum number of nodes to spawn on the map. Update the config table in lib.rs. ```rust -#[spacetimedb(table(public))] +#[spacetimedb(table)] pub struct Config { // Config is a global table with a single row. This table will be used to // store configuration or global variables From 78a67b263f0c0153bb2b38c92f6a7d5a97586919 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Fri, 14 Jun 2024 16:29:25 +0200 Subject: [PATCH 45/80] Update response of `/database/info` (#64) To match changes in clockworklabs/spacetimedb#1305 --- docs/http/database.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/http/database.md b/docs/http/database.md index 2d55188a..16ee729c 100644 --- a/docs/http/database.md +++ b/docs/http/database.md @@ -507,20 +507,18 @@ Returns JSON in the form: ```typescript { "address": string, - "identity": string, - "host_type": "wasmer", - "num_replicas": number, - "program_bytes_address": string + "owner_identity": string, + "host_type": "wasm", + "initial_program": string } ``` -| Field | Type | Meaning | -| ------------------------- | ------ | ----------------------------------------------------------- | -| `"address"` | String | The address of the database. | -| `"identity"` | String | The Spacetime identity of the database's owner. | -| `"host_type"` | String | The module host type; currently always `"wasmer"`. | -| `"num_replicas"` | Number | The number of replicas of the database. Currently always 1. | -| `"program_bytes_address"` | String | Hash of the WASM module for the database. | +| Field | Type | Meaning | +| --------------------| ------ | ---------------------------------------------------------------- | +| `"address"` | String | The address of the database. | +| `"owner_identity"` | String | The Spacetime identity of the database's owner. | +| `"host_type"` | String | The module host type; currently always `"wasm"`. | +| `"initial_program"` | String | Hash of the WASM module with which the database was initialized. | ## `/database/logs/:name_or_address GET` From a12354b919adb988d9abdb91d18e14a2a2134e7f Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:59:41 -0700 Subject: [PATCH 46/80] Update docs for making tables public/private (#63) * [bfops/revert-premature-merge]: do revert * [bfops/redo-61]: Redo #61 * [bfops/redo-61]: redo * [bfops/redo-61]: add C# * [bfops/redo-61]: review * [bfops/redo-61]: review --------- Co-authored-by: Zeke Foppa --- docs/modules/c-sharp/index.md | 8 ++++++-- docs/modules/c-sharp/quickstart.md | 8 ++++++-- docs/modules/rust/index.md | 24 ++++++++++++++---------- docs/modules/rust/quickstart.md | 10 +++++++--- docs/unity/part-2a-rust.md | 10 +++++----- docs/unity/part-2b-c-sharp.md | 10 +++++----- docs/unity/part-4.md | 6 +++--- 7 files changed, 46 insertions(+), 30 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 31ebd1d4..1af76f84 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -22,7 +22,7 @@ static partial class Module // `[SpacetimeDB.Table]` registers a struct or a class as a SpacetimeDB table. // // It generates methods to insert, filter, update, and delete rows of the given type in the table. - [SpacetimeDB.Table] + [SpacetimeDB.Table(Public = true)] public partial struct Person { // `[SpacetimeDB.Column]` allows to specify column attributes / constraints such as @@ -172,11 +172,15 @@ if (option.IsSome) ### Tables `[SpacetimeDB.Table]` attribute can be used on any `struct` or `class` to mark it as a SpacetimeDB table. It will register a table in the database with the given name and fields as well as will generate C# methods to insert, filter, update, and delete rows of the given type. +By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. +Adding `[SpacetimeDB.Table(Public = true))]` annotation makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. + +_Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ It implies `[SpacetimeDB.Type]`, so you must not specify both attributes on the same type. ```csharp -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial struct Person { [SpacetimeDB.Column(ColumnAttrs.Unique | ColumnAttrs.AutoInc)] diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index f5f73401..747f4260 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -7,6 +7,10 @@ A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded t Each SpacetimeDB module defines a set of tables and a set of reducers. Each table is defined as a C# `class` annotated with `[SpacetimeDB.Table]`, where an instance represents a row, and each field represents a column. +By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. +The `[SpacetimeDB.Table(Public = true))]` annotation makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. + +_Coming soon: We plan to add much more robust access controls than just public or private tables. Stay tuned!_ A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In C#, reducers are defined as functions annotated with `[SpacetimeDB.Reducer]`. If an exception is thrown, the reducer call fails, the database is not updated, and a failed message is reported to the client. @@ -84,7 +88,7 @@ For each `User`, we'll store their `Identity`, an optional name they can set to In `server/Lib.cs`, add the definition of the table `User` to the `Module` class: ```csharp -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class User { [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] @@ -99,7 +103,7 @@ For each `Message`, we'll store the `Identity` of the user who sent it, the `Tim In `server/Lib.cs`, add the definition of the table `Message` to the `Module` class: ```csharp -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class Message { public Identity Sender; diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 05d62bdc..b08075a0 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -31,7 +31,7 @@ use spacetimedb::{spacetimedb, println}; // This macro lets us interact with a SpacetimeDB table of Person rows. // We can insert and delete into, and query, this table by the collection // of functions generated by the macro. -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Person { name: String, } @@ -88,10 +88,14 @@ Now we'll get into details on all the macro APIs SpacetimeDB provides, starting ### Defining tables -`#[spacetimedb(table)]` takes no further arguments, and is applied to a Rust struct with named fields: +The `#[spacetimedb(table)]` is applied to a Rust struct with named fields. +By default, tables are considered **private**. This means that they are only readable by the table owner, and by server module code. +The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. + +_Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Table { field1: String, field2: u32, @@ -119,7 +123,7 @@ And common data structures: All `#[spacetimedb(table)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct AnotherTable { // Fine, some builtin types. id: u64, @@ -151,7 +155,7 @@ enum Serial { Once the table is created via the macro, other attributes described below can control more aspects of the table. For instance, a particular column can be indexed, or take on values of an automatically incremented counter. These are described in detail below. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Person { #[unique] id: u64, @@ -269,7 +273,7 @@ We'll work off these structs to see what functions SpacetimeDB generates: This table has a plain old column. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Ordinary { ordinary_field: u64, } @@ -278,7 +282,7 @@ struct Ordinary { This table has a unique column. Every row in the `Person` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Unique { // A unique column: #[unique] @@ -291,7 +295,7 @@ This table has an automatically incrementing column. SpacetimeDB automatically p Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Autoinc { #[autoinc] autoinc_field: u64, @@ -301,7 +305,7 @@ struct Autoinc { These attributes can be combined, to create an automatically assigned ID usable for filtering. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Identity { #[autoinc] #[unique] @@ -375,7 +379,7 @@ fn insert_id() { Given a table, we can iterate over all the rows in it. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] struct Person { #[unique] id: u64, diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index e015b881..ed9fc376 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -7,6 +7,10 @@ A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded t Each SpacetimeDB module defines a set of tables and a set of reducers. Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table)]`, where an instance represents a row, and each field represents a column. +By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. +The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. + +_Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In Rust, reducers are defined as functions annotated with `#[spacetimedb(reducer)]`, and may return a `Result<()>`, with an `Err` return aborting the transaction. @@ -67,7 +71,7 @@ For each `User`, we'll store their `Identity`, an optional name they can set to To `server/src/lib.rs`, add the definition of the table `User`: ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct User { #[primarykey] identity: Identity, @@ -81,7 +85,7 @@ For each `Message`, we'll store the `Identity` of the user who sent it, the `Tim To `server/src/lib.rs`, add the definition of the table `Message`: ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Message { sender: Identity, sent: Timestamp, @@ -179,7 +183,7 @@ You could extend the validation in `validate_message` in similar ways to `valida Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` attribute, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. To `server/src/lib.rs`, add the definition of the connect reducer: diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md index fd9361f2..dbfdc888 100644 --- a/docs/unity/part-2a-rust.md +++ b/docs/unity/part-2a-rust.md @@ -29,13 +29,13 @@ use spacetimedb::{spacetimedb, Identity, SpacetimeType, ReducerContext}; use log; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [Rust module reference](/docs/modules/rust). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [Rust module reference](/docs/modules/rust) (including making your tables `private`!). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.rs:** ```rust // We're using this table as a singleton, so there should typically only be one element where the version is 0. -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct Config { #[primarykey] @@ -64,7 +64,7 @@ Now we're going to create a table which actually uses the `StdbVector3` that we // This stores information related to all entities in our game. In this tutorial // all entities must at least have an entity_id, a position, a direction and they // must specify whether or not they are moving. -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct EntityComponent { #[primarykey] @@ -87,7 +87,7 @@ Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is // All players have this component and it associates an entity with the user's // Identity. It also stores their username and whether or not they're logged in. #[derive(Clone)] -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct PlayerComponent { // An entity_id that matches an entity_id in the `EntityComponent` table. #[primarykey] @@ -264,7 +264,7 @@ First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the foll **Append to the bottom of server/src/lib.rs:** ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct ChatMessage { // The primary key for this table will be auto-incremented #[primarykey] diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index ee6c0028..f1956b70 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -30,14 +30,14 @@ using SpacetimeDB.Module; using static SpacetimeDB.Runtime; ``` -Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of macros, like `#[spacetimedb(table)]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. +Then we are going to start by adding the global `Config` table. Right now it only contains the "message of the day" but it can be extended to store other configuration variables. This also uses a couple of attributes, like `[SpacetimeDB.Table]` which you can learn more about in our [C# module reference](/docs/modules/c-sharp). Simply put, this just tells SpacetimeDB to create a table which uses this struct as the schema for the table. **Append to the bottom of lib.cs:** ```csharp /// We're using this table as a singleton, /// so there should typically only be one element where the version is 0. -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class Config { [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] @@ -67,7 +67,7 @@ Now we're going to create a table which actually uses the `StdbVector3` that we /// This stores information related to all entities in our game. In this tutorial /// all entities must at least have an entity_id, a position, a direction and they /// must specify whether or not they are moving. -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class EntityComponent { [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] @@ -85,7 +85,7 @@ Next, we will define the `PlayerComponent` table. The `PlayerComponent` table is ```csharp /// All players have this component and it associates an entity with the user's /// Identity. It also stores their username and whether or not they're logged in. -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class PlayerComponent { // An EntityId that matches an EntityId in the `EntityComponent` table. @@ -293,7 +293,7 @@ First lets add a new `ChatMessage` table to the SpacetimeDB module. Add the foll **Append to the bottom of server/src/lib.cs:** ```csharp -[SpacetimeDB.Table] +[SpacetimeDB.Table(Public = true)] public partial class ChatMessage { // The primary key for this table will be auto-incremented diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index b8af1018..b3a17439 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -34,7 +34,7 @@ pub enum ResourceNodeType { Iron, } -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct ResourceNodeComponent { #[primarykey] @@ -48,7 +48,7 @@ pub struct ResourceNodeComponent { Because resource nodes never move, the `MobileEntityComponent` is overkill. Instead, we will add a new entity component named `StaticLocationComponent` that only stores the position and rotation. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] #[derive(Clone)] pub struct StaticLocationComponent { #[primarykey] @@ -62,7 +62,7 @@ pub struct StaticLocationComponent { 3. We are also going to add a couple of additional column to our Config table. `map_extents` let's our spawner know where it can spawn the nodes. `num_resource_nodes` is the maximum number of nodes to spawn on the map. Update the config table in lib.rs. ```rust -#[spacetimedb(table)] +#[spacetimedb(table(public))] pub struct Config { // Config is a global table with a single row. This table will be used to // store configuration or global variables From 6983d70a47b5c498d490f4b6084d4dbff347ce34 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 18 Jun 2024 16:01:53 +0100 Subject: [PATCH 47/80] Remove CreateInstance() from C# client docs (#69) This method no longer exists - an instance with a default logger is set up automatically. --- docs/sdks/c-sharp/index.md | 42 ++++----------------------------- docs/sdks/c-sharp/quickstart.md | 3 --- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/docs/sdks/c-sharp/index.md b/docs/sdks/c-sharp/index.md index 7c920cf5..d0d15237 100644 --- a/docs/sdks/c-sharp/index.md +++ b/docs/sdks/c-sharp/index.md @@ -11,7 +11,6 @@ The SpacetimeDB client C# for Rust contains all the tools you need to build nati - [Using Unity](#using-unity) - [Generate module bindings](#generate-module-bindings) - [Initialization](#initialization) - - [Static Method `SpacetimeDBClient.CreateInstance`](#static-method-spacetimedbclientcreateinstance) - [Property `SpacetimeDBClient.instance`](#property-spacetimedbclientinstance) - [Class `NetworkManager`](#class-networkmanager) - [Method `SpacetimeDBClient.Connect`](#method-spacetimedbclientconnect) @@ -84,32 +83,6 @@ Replace `PATH-TO-MODULE-DIRECTORY` with the path to your SpacetimeDB module. ## Initialization -### Static Method `SpacetimeDBClient.CreateInstance` - -```cs -namespace SpacetimeDB { - -public class SpacetimeDBClient { - public static void CreateInstance(ISpacetimeDBLogger loggerToUse); -} - -} -``` - -Create a global SpacetimeDBClient instance, accessible via [`SpacetimeDBClient.instance`](#property-spacetimedbclientinstance) - -| Argument | Type | Meaning | -| ------------- | ----------------------------------------------------- | --------------------------------- | -| `loggerToUse` | [`ISpacetimeDBLogger`](#interface-ispacetimedblogger) | The logger to use to log messages | - -There is a provided logger called [`ConsoleLogger`](#class-consolelogger) which logs to `System.Console`, and can be used as follows: - -```cs -using SpacetimeDB; -using SpacetimeDB.Types; -SpacetimeDBClient.CreateInstance(new ConsoleLogger()); -``` - ### Property `SpacetimeDBClient.instance` ```cs @@ -130,7 +103,7 @@ The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in ![Unity-AddNetworkManager](/images/unity-tutorial/Unity-AddNetworkManager.JPG) -This component will handle calling [`SpacetimeDBClient.CreateInstance`](#static-method-spacetimedbclientcreateinstance) for you, but will not call [`SpacetimeDBClient.Connect`](#method-spacetimedbclientconnect), you still need to handle that yourself. See the [Unity Quickstart](./UnityQuickStart) and [Unity Tutorial](./UnityTutorialPart1) for more information. +This component will handle updating and closing the [`SpacetimeDBClient.instance`](#property-spacetimedbclientinstance) for you, but will not call [`SpacetimeDBClient.Connect`](#method-spacetimedbclientconnect), you still need to handle that yourself. See the [Unity Quickstart](./UnityQuickStart) and [Unity Tutorial](./UnityTutorialPart1) for more information. ### Method `SpacetimeDBClient.Connect` @@ -264,7 +237,6 @@ using SpacetimeDB.Types; void Main() { AuthToken.Init(); - SpacetimeDBClient.CreateInstance(new ConsoleLogger()); SpacetimeDBClient.instance.onConnect += OnConnect; @@ -903,17 +875,11 @@ An opaque identifier for a client connection to a database, intended to differen ## Customizing logging -The SpacetimeDB C# SDK performs internal logging. Instances of [`ISpacetimeDBLogger`](#interface-ispacetimedblogger) can be passed to [`SpacetimeDBClient.CreateInstance`](#static-method-spacetimedbclientcreateinstance) to customize how SDK logs are delivered to your application. +The SpacetimeDB C# SDK performs internal logging. -This is set up automatically for you if you use Unity-- adding a [`NetworkManager`](#class-networkmanager) component to your unity scene will automatically initialize the `SpacetimeDBClient` with a [`UnityDebugLogger`](#class-unitydebuglogger). +A default logger is set up automatically for you - a [`ConsoleLogger`](#class-consolelogger) for C# projects and [`UnityDebugLogger`](#class-unitydebuglogger) for Unity projects. -Outside of unity, all you need to do is the following: - -```cs -using SpacetimeDB; -using SpacetimeDB.Types; -SpacetimeDBClient.CreateInstance(new ConsoleLogger()); -``` +If you want to redirect SDK logs elsewhere, you can inherit from the [`ISpacetimeDBLogger`](#interface-ispacetimedblogger) and assign an instance of your class to the `SpacetimeDB.Logger.Current` static property. ### Interface `ISpacetimeDBLogger` diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/sdks/c-sharp/quickstart.md index 28f3c2e1..122465d3 100644 --- a/docs/sdks/c-sharp/quickstart.md +++ b/docs/sdks/c-sharp/quickstart.md @@ -89,9 +89,6 @@ void Main() { AuthToken.Init(".spacetime_csharp_quickstart"); - // create the client, pass in a logger to see debug messages - SpacetimeDBClient.CreateInstance(new ConsoleLogger()); - RegisterCallbacks(); // spawn a thread to call process updates and process commands From b1af7b19dc4f024667349b3e079c381c93a1645c Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 18 Jun 2024 16:12:13 +0100 Subject: [PATCH 48/80] DbEventArgs -> ReducerContext in C# API docs (#66) This API was recently renamed. --- docs/modules/c-sharp/index.md | 8 +++---- docs/modules/c-sharp/quickstart.md | 34 +++++++++++++++--------------- docs/unity/part-2b-c-sharp.md | 28 ++++++++++++------------ 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 1af76f84..7037a2a8 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -254,11 +254,11 @@ public static void Add(string name, int age) } ``` -If a reducer has an argument with a type `DbEventArgs` (`SpacetimeDB.Runtime.DbEventArgs`), it will be provided with event details such as the sender identity (`SpacetimeDB.Runtime.Identity`), sender address (`SpacetimeDB.Runtime.Address?`) and the time (`DateTimeOffset`) of the invocation: +If a reducer has an argument with a type `ReducerContext` (`SpacetimeDB.Runtime.ReducerContext`), it will be provided with event details such as the sender identity (`SpacetimeDB.Runtime.Identity`), sender address (`SpacetimeDB.Runtime.Address?`) and the time (`DateTimeOffset`) of the invocation: ```csharp [SpacetimeDB.Reducer] -public static void PrintInfo(DbEventArgs e) +public static void PrintInfo(ReducerContext e) { Log($"Sender identity: {e.Sender}"); Log($"Sender address: {e.Address}"); @@ -268,7 +268,7 @@ public static void PrintInfo(DbEventArgs e) `[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer in the future. -Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `DbEventArgs`. +Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `ReducerContext`. ```csharp // Example reducer: @@ -280,7 +280,7 @@ public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... // Usage from another reducer: [SpacetimeDB.Reducer] -public static void AddIn5Minutes(DbEventArgs e, string name, int age) +public static void AddIn5Minutes(ReducerContext e, string name, int age) { // Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules. var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age); diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 747f4260..66c6e5cb 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -116,7 +116,7 @@ public partial class Message We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `SetName` which clients can invoke to set their `User.Name`. It will validate the caller's chosen name, using a function `ValidateName` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -Each reducer may accept as its first argument a `DbEventArgs`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `dbEvent.Sender`. +Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.Sender`. It's also possible to call `SetName` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. @@ -124,15 +124,15 @@ In `server/Lib.cs`, add to the `Module` class: ```csharp [SpacetimeDB.Reducer] -public static void SetName(DbEventArgs dbEvent, string name) +public static void SetName(ReducerContext ctx, string name) { name = ValidateName(name); - var user = User.FindByIdentity(dbEvent.Sender); + var user = User.FindByIdentity(ctx.Sender); if (user is not null) { user.Name = name; - User.UpdateByIdentity(dbEvent.Sender, user); + User.UpdateByIdentity(ctx.Sender, user); } } ``` @@ -161,21 +161,21 @@ public static string ValidateName(string name) ## Send messages -We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Message.Insert`, with the `Sender` identity and `Time` timestamp taken from the `DbEventArgs`. +We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Message.Insert`, with the `Sender` identity and `Time` timestamp taken from the `ReducerContext`. In `server/Lib.cs`, add to the `Module` class: ```csharp [SpacetimeDB.Reducer] -public static void SendMessage(DbEventArgs dbEvent, string text) +public static void SendMessage(ReducerContext ctx, string text) { text = ValidateMessage(text); Log(text); new Message { - Sender = dbEvent.Sender, + Sender = ctx.Sender, Text = text, - Sent = dbEvent.Time.ToUnixTimeMilliseconds(), + Sent = ctx.Time.ToUnixTimeMilliseconds(), }.Insert(); } ``` @@ -205,23 +205,23 @@ You could extend the validation in `ValidateMessage` in similar ways to `Validat In C# modules, you can register for `Connect` and `Disconnect` events by using a special `ReducerKind`. We'll use the `Connect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User.FindByIdentity` to look up a `User` row for `dbEvent.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FindByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. +We'll use `User.FindByIdentity` to look up a `User` row for `ctx.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FindByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: ```csharp [SpacetimeDB.Reducer(ReducerKind.Connect)] -public static void OnConnect(DbEventArgs dbEventArgs) +public static void OnConnect(ReducerContext ReducerContext) { - Log($"Connect {dbEventArgs.Sender}"); - var user = User.FindByIdentity(dbEventArgs.Sender); + Log($"Connect {ReducerContext.Sender}"); + var user = User.FindByIdentity(ReducerContext.Sender); if (user is not null) { // If this is a returning user, i.e., we already have a `User` with this `Identity`, // set `Online: true`, but leave `Name` and `Identity` unchanged. user.Online = true; - User.UpdateByIdentity(dbEventArgs.Sender, user); + User.UpdateByIdentity(ReducerContext.Sender, user); } else { @@ -230,7 +230,7 @@ public static void OnConnect(DbEventArgs dbEventArgs) new User { Name = null, - Identity = dbEventArgs.Sender, + Identity = ReducerContext.Sender, Online = true, }.Insert(); } @@ -243,15 +243,15 @@ Add the following code after the `OnConnect` lambda: ```csharp [SpacetimeDB.Reducer(ReducerKind.Disconnect)] -public static void OnDisconnect(DbEventArgs dbEventArgs) +public static void OnDisconnect(ReducerContext ReducerContext) { - var user = User.FindByIdentity(dbEventArgs.Sender); + var user = User.FindByIdentity(ReducerContext.Sender); if (user is not null) { // This user should exist, so set `Online: false`. user.Online = false; - User.UpdateByIdentity(dbEventArgs.Sender, user); + User.UpdateByIdentity(ReducerContext.Sender, user); } else { diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index f1956b70..e311714a 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -108,10 +108,10 @@ Next, we write our very first reducer, `CreatePlayer`. From the client we will c /// This reducer is called when the user logs in for the first time and /// enters a username. [SpacetimeDB.Reducer] -public static void CreatePlayer(DbEventArgs dbEvent, string username) +public static void CreatePlayer(ReducerContext ctx, string username) { // Get the Identity of the client who called this reducer - Identity sender = dbEvent.Sender; + Identity sender = ctx.Sender; // Make sure we don't already have a player with this identity PlayerComponent? user = PlayerComponent.FindByIdentity(sender); @@ -144,7 +144,7 @@ public static void CreatePlayer(DbEventArgs dbEvent, string username) new PlayerComponent { // EntityId = 0, // 0 is the same as leaving null to get a new, unique Id - Identity = dbEvent.Sender, + Identity = ctx.Sender, Username = username, LoggedIn = true, }.Insert(); @@ -204,30 +204,30 @@ We use the `Connect` and `Disconnect` reducers to update the logged in state of ```csharp /// Called when the client connects, we update the LoggedIn state to true [SpacetimeDB.Reducer(ReducerKind.Init)] -public static void ClientConnected(DbEventArgs dbEvent) => - UpdatePlayerLoginState(dbEvent, loggedIn:true); +public static void ClientConnected(ReducerContext ctx) => + UpdatePlayerLoginState(ctx, loggedIn:true); ``` ```csharp /// Called when the client disconnects, we update the logged_in state to false [SpacetimeDB.Reducer(ReducerKind.Disconnect)] -public static void ClientDisonnected(DbEventArgs dbEvent) => - UpdatePlayerLoginState(dbEvent, loggedIn:false); +public static void ClientDisonnected(ReducerContext ctx) => + UpdatePlayerLoginState(ctx, loggedIn:false); ``` ```csharp /// This helper function gets the PlayerComponent, sets the LoggedIn /// variable and updates the PlayerComponent table row. -private static void UpdatePlayerLoginState(DbEventArgs dbEvent, bool loggedIn) +private static void UpdatePlayerLoginState(ReducerContext ctx, bool loggedIn) { - PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + PlayerComponent? player = PlayerComponent.FindByIdentity(ctx.Sender); if (player is null) { throw new ArgumentException("Player not found"); } player.LoggedIn = loggedIn; - PlayerComponent.UpdateByIdentity(dbEvent.Sender, player); + PlayerComponent.UpdateByIdentity(ctx.Sender, player); } ``` @@ -241,13 +241,13 @@ Using the `EntityId` in the `PlayerComponent` we retrieved, we can lookup the `E /// Updates the position of a player. This is also called when the player stops moving. [SpacetimeDB.Reducer] private static void UpdatePlayerPosition( - DbEventArgs dbEvent, + ReducerContext ctx, StdbVector3 position, float direction, bool moving) { // First, look up the player using the sender identity - PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + PlayerComponent? player = PlayerComponent.FindByIdentity(ctx.Sender); if (player is null) { throw new ArgumentException("Player not found"); @@ -314,10 +314,10 @@ Now we need to add a reducer to handle inserting new chat messages. ```csharp /// Adds a chat entry to the ChatMessage table [SpacetimeDB.Reducer] -public static void SendChatMessage(DbEventArgs dbEvent, string text) +public static void SendChatMessage(ReducerContext ctx, string text) { // Get the player's entity id - PlayerComponent? player = PlayerComponent.FindByIdentity(dbEvent.Sender); + PlayerComponent? player = PlayerComponent.FindByIdentity(ctx.Sender); if (player is null) { throw new ArgumentException("Player not found"); From 7a54f2655e526b0ef4c71c20c1fb541b49114402 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 18 Jun 2024 16:12:24 +0100 Subject: [PATCH 49/80] Remove obsolete C# module imports (#67) --- docs/modules/c-sharp/quickstart.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 66c6e5cb..559dca92 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -64,10 +64,7 @@ using SpacetimeDB.Module; using static SpacetimeDB.Runtime; ``` -- `System.Runtime.CompilerServices` -- `SpacetimeDB.Module` - - Contains the special attributes we'll use to define our module. - - Allows us to use the `ModuleInitializer` attribute, which we'll use to register our `OnConnect` and `OnDisconnect` callbacks. +- `SpacetimeDB.Module` contains the special attributes we'll use to define tables and reducers in our module. - `SpacetimeDB.Runtime` contains the raw API bindings SpacetimeDB uses to communicate with the database. We also need to create our static module class which all of the module code will live in. In `server/Lib.cs`, add: @@ -239,7 +236,7 @@ public static void OnConnect(ReducerContext ReducerContext) Similarly, whenever a client disconnects, the module will execute the `OnDisconnect` event if it's registered with `ReducerKind.Disconnect`. We'll use it to un-set the `Online` status of the `User` for the disconnected client. -Add the following code after the `OnConnect` lambda: +Add the following code after the `OnConnect` handler: ```csharp [SpacetimeDB.Reducer(ReducerKind.Disconnect)] From 6a7ecbf9492fb59c0af0ec6c3a74f9d7fa39a563 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 18 Jun 2024 16:12:58 +0100 Subject: [PATCH 50/80] Update docs for Consistent Filtering proposal (#68) * Update docs for Consistent Filtering proposal Updating docs for FilterBy methods across SDKs, adding docs for FindBy methods, and a couple of drive-by fixes for broken links. * Update docs/sdks/c-sharp/index.md Co-authored-by: Phoebe Goldman --------- Co-authored-by: Phoebe Goldman --- docs/index.md | 2 +- docs/modules/rust/index.md | 6 ++-- docs/modules/rust/quickstart.md | 8 ++--- docs/sdks/c-sharp/index.md | 43 +++++++++++++++++---------- docs/sdks/c-sharp/quickstart.md | 4 +-- docs/sdks/rust/index.md | 26 ++++++++++++----- docs/sdks/rust/quickstart.md | 4 +-- docs/sdks/typescript/index.md | 47 ++++++++++++++++++++++++++---- docs/sdks/typescript/quickstart.md | 6 ++-- docs/unity/part-2a-rust.md | 12 ++++---- docs/unity/part-2b-c-sharp.md | 2 +- docs/unity/part-3.md | 18 ++++++------ docs/unity/part-4.md | 4 +-- 13 files changed, 120 insertions(+), 62 deletions(-) diff --git a/docs/index.md b/docs/index.md index 7a95f4f8..eaee2c83 100644 --- a/docs/index.md +++ b/docs/index.md @@ -111,7 +111,7 @@ spacetime publish ``` 5. How do I create a Unity game with SpacetimeDB? - Follow our [Unity Project](/docs/unity-project) guide! + Follow our [Unity Project](/docs/unity-tutorial) guide! TL;DR in an empty directory: diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index b08075a0..f4d02490 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -176,7 +176,7 @@ struct Person { fn give_player_item(player_id: u64, item_id: u64) -> Result<(), GameErr> { // Notice how the exact name of the filter function derives from // the name of the field of the struct. - let mut item = Item::filter_by_item_id(id).ok_or(GameErr::InvalidId)?; + let mut item = Item::find_by_item_id(id).ok_or(GameErr::InvalidId)?; item.owner = Some(player_id); Item::update_by_id(id, item); Ok(()) @@ -424,7 +424,7 @@ The name of the filter method just corresponds to the column name. ```rust #[spacetimedb(reducer)] fn filtering(id: u64) { - match Person::filter_by_id(&id) { + match Person::find_by_id(&id) { Some(person) => println!("Found {person}"), None => println!("No person with id {id}"), } @@ -436,7 +436,7 @@ Our `Person` table also has a column for age. Unlike IDs, ages aren't unique. Fi ```rust #[spacetimedb(reducer)] fn filtering_non_unique() { - for person in Person::filter_by_age(&21) { + for person in Person::find_by_age(&21) { println!("{person} has turned 21"); } } diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index ed9fc376..346810d7 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -108,7 +108,7 @@ To `server/src/lib.rs`, add: /// Clientss invoke this reducer to set their user names. pub fn set_name(ctx: ReducerContext, name: String) -> Result<(), String> { let name = validate_name(name)?; - if let Some(user) = User::filter_by_identity(&ctx.sender) { + if let Some(user) = User::find_by_identity(&ctx.sender) { User::update_by_identity(&ctx.sender, User { name: Some(name), ..user }); Ok(()) } else { @@ -183,7 +183,7 @@ You could extend the validation in `validate_message` in similar ways to `valida Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `User::find_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `find_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. To `server/src/lib.rs`, add the definition of the connect reducer: @@ -191,7 +191,7 @@ To `server/src/lib.rs`, add the definition of the connect reducer: #[spacetimedb(connect)] // Called when a client connects to the SpacetimeDB pub fn identity_connected(ctx: ReducerContext) { - if let Some(user) = User::filter_by_identity(&ctx.sender) { + if let Some(user) = User::find_by_identity(&ctx.sender) { // If this is a returning user, i.e. we already have a `User` with this `Identity`, // set `online: true`, but leave `name` and `identity` unchanged. User::update_by_identity(&ctx.sender, User { online: true, ..user }); @@ -213,7 +213,7 @@ Similarly, whenever a client disconnects, the module will run the `#[spacetimedb #[spacetimedb(disconnect)] // Called when a client disconnects from SpacetimeDB pub fn identity_disconnected(ctx: ReducerContext) { - if let Some(user) = User::filter_by_identity(&ctx.sender) { + if let Some(user) = User::find_by_identity(&ctx.sender) { User::update_by_identity(&ctx.sender, User { online: false, ..user }); } else { // This branch should be unreachable, diff --git a/docs/sdks/c-sharp/index.md b/docs/sdks/c-sharp/index.md index d0d15237..e8a3d01a 100644 --- a/docs/sdks/c-sharp/index.md +++ b/docs/sdks/c-sharp/index.md @@ -19,11 +19,12 @@ The SpacetimeDB client C# for Rust contains all the tools you need to build nati - [Query subscriptions & one-time actions](#subscribe-to-queries) - [Method `SpacetimeDBClient.Subscribe`](#method-spacetimedbclientsubscribe) - [Event `SpacetimeDBClient.onSubscriptionApplied`](#event-spacetimedbclientonsubscriptionapplied) - - [Method `SpacetimeDBClient.OneOffQuery`](#event-spacetimedbclientoneoffquery) + - [Method `SpacetimeDBClient.OneOffQuery`](#method-spacetimedbclientoneoffquery) - [View rows of subscribed tables](#view-rows-of-subscribed-tables) - [Class `{TABLE}`](#class-table) - [Static Method `{TABLE}.Iter`](#static-method-tableiter) - [Static Method `{TABLE}.FilterBy{COLUMN}`](#static-method-tablefilterbycolumn) + - [Static Method `{TABLE}.FindBy{COLUMN}`](#static-method-tablefindbycolumn) - [Static Method `{TABLE}.Count`](#static-method-tablecount) - [Static Event `{TABLE}.OnInsert`](#static-event-tableoninsert) - [Static Event `{TABLE}.OnBeforeDelete`](#static-event-tableonbeforedelete) @@ -171,7 +172,7 @@ class SpacetimeDBClient { } ``` -+Called when we receive an auth token, [`Identity`](#class-identity) and [`Address`](#class-address) from the server. The [`Identity`](#class-identity) serves as a unique public identifier for a user of the database. It can be for several purposes, such as filtering rows in a database for the rows created by a particular user. The auth token is a private access token that allows us to assume an identity. The [`Address`](#class-address) is opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#class-identity). +Called when we receive an auth token, [`Identity`](#class-identity) and [`Address`](#class-address) from the server. The [`Identity`](#class-identity) serves as a unique public identifier for a user of the database. It can be for several purposes, such as filtering rows in a database for the rows created by a particular user. The auth token is a private access token that allows us to assume an identity. The [`Address`](#class-address) is opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#class-identity). To store the auth token to the filesystem, use the static method [`AuthToken.SaveToken`](#static-method-authtokensavetoken). You may also want to store the returned [`Identity`](#class-identity) in a local variable. @@ -224,11 +225,11 @@ class SpacetimeDBClient { Subscribe to a set of queries, to be notified when rows which match those queries are altered. -`Subscribe` will return an error if called before establishing a connection with the [`SpacetimeDBClient.Connect`](#method-connect) function. In that case, the queries are not registered. +`Subscribe` will return an error if called before establishing a connection with the [`SpacetimeDBClient.Connect`](#method-spacetimedbclientconnect) function. In that case, the queries are not registered. The `Subscribe` method does not return data directly. `spacetime generate` will generate classes [`SpacetimeDB.Types.{TABLE}`](#class-table) for each table in your module. These classes are used to reecive information from the database. See the section [View Rows of Subscribed Tables](#view-rows-of-subscribed-tables) for more information. -A new call to `Subscribe` will remove all previous subscriptions and replace them with the new `queries`. If any rows matched the previous subscribed queries but do not match the new queries, those rows will be removed from the client cache, and [`{TABLE}.OnDelete`](#event-tableondelete) callbacks will be invoked for them. +A new call to `Subscribe` will remove all previous subscriptions and replace them with the new `queries`. If any rows matched the previous subscribed queries but do not match the new queries, those rows will be removed from the client cache, and [`{TABLE}.OnDelete`](#static-event-tableoninsert) callbacks will be invoked for them. ```cs using SpacetimeDB; @@ -290,7 +291,7 @@ void Main() } ``` -### Method [`OneTimeQuery`](#method-spacetimedbclientsubscribe) +### Method [`SpacetimeDBClient.OneOffQuery`] You may not want to subscribe to a query, but instead want to run a query once and receive the results immediately via a `Task` result: @@ -317,6 +318,7 @@ Static Methods: - [`{TABLE}.Iter()`](#static-method-tableiter) iterates all subscribed rows in the client cache. - [`{TABLE}.FilterBy{COLUMN}(value)`](#static-method-tablefilterbycolumn) filters subscribed rows in the client cache by a column value. +- [`{TABLE}.FindBy{COLUMN}(value)`](#static-method-tablefindbycolumn) finds a subscribed row in the client cache by a unique column value. - [`{TABLE}.Count()`](#static-method-tablecount) counts the number of subscribed rows in the client cache. Static Events: @@ -334,7 +336,7 @@ Note that it is not possible to directly insert into the database from the clien namespace SpacetimeDB.Types { class TABLE { - public static System.Collections.Generic.IEnumerable Iter(); + public static IEnumerable
Iter(); } } @@ -342,7 +344,7 @@ class TABLE { Iterate over all the subscribed rows in the table. This method is only available after [`SpacetimeDBClient.onSubscriptionApplied`](#event-spacetimedbclientonsubscriptionapplied) has occurred. -When iterating over rows and filtering for those containing a particular column, [`TableType::filter`](#method-filter) will be more efficient, so prefer it when possible. +When iterating over rows and filtering for those containing a particular column, [`{TABLE}.FilterBy{COLUMN}`](#static-method-tablefilterbycolumn) and [`{TABLE}.FindBy{COLUMN}`](#static-method-tablefindbycolumn) will be more efficient, so prefer those when possible. ```cs using SpacetimeDB; @@ -366,22 +368,32 @@ SpacetimeDBClient.instance.connect(/* ... */); namespace SpacetimeDB.Types { class TABLE { - // If the column has no #[unique] or #[primarykey] constraint - public static System.Collections.Generic.IEnumerable
FilterBySender(COLUMNTYPE value); + public static IEnumerable
FilterBySender(COLUMNTYPE value); +} + +} +``` + +For each column of a table, `spacetime generate` generates a static method on the [table class](#class-table) to filter subscribed rows where that column matches a requested value. + +These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name converted to `PascalCase`. The method's return type is an `IEnumerable` over the [table class](#class-table). + +#### Static Method `{TABLE}.FindBy{COLUMN}` +```cs +namespace SpacetimeDB.Types { + +class TABLE { // If the column has a #[unique] or #[primarykey] constraint - public static TABLE? FilterBySender(COLUMNTYPE value); + public static TABLE? FindBySender(COLUMNTYPE value); } } ``` -For each column of a table, `spacetime generate` generates a static method on the [table class](#class-table) to filter or seek subscribed rows where that column matches a requested value. These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name converted to `PascalCase`. - -The method's return type depends on the column's attributes: +For each unique column of a table (those annotated `#[unique]` or `#[primarykey]`), `spacetime generate` generates a static method on the [table class](#class-table) to seek a subscribed row where that column matches a requested value. -- For unique columns, including those annotated `#[unique]` and `#[primarykey]`, the `filterBy{COLUMN}` method returns a `{TABLE}?`, where `{TABLE}` is the [table class](#class-table). -- For non-unique columns, the `filter_by` method returns an `IEnumerator<{TABLE}>`. +These methods are named `findBy{COLUMN}`, where `{COLUMN}` is the column name converted to `PascalCase`. Those methods return a single instance of the [table class](#class-table) if a row is found, or `null` if no row matches the query. #### Static Method `{TABLE}.Count` @@ -856,7 +868,6 @@ A unique public identifier for a user of a database. Columns of type `Identity` inside a module will be represented in the C# SDK as properties of type `byte[]`. `Identity` is essentially just a wrapper around `byte[]`, and you can use the `Bytes` property to get a `byte[]` that can be used to filter tables and so on. -### Class `Identity` ```cs namespace SpacetimeDB { diff --git a/docs/sdks/c-sharp/quickstart.md b/docs/sdks/c-sharp/quickstart.md index 122465d3..db06d9a4 100644 --- a/docs/sdks/c-sharp/quickstart.md +++ b/docs/sdks/c-sharp/quickstart.md @@ -200,14 +200,14 @@ void User_OnUpdate(User oldValue, User newValue, ReducerEvent dbEvent) When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `SendMessage` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `OnInsert` callback will check if its `ReducerEvent` argument is not `null`, and only print in that case. -To find the `User` based on the message's `Sender` identity, we'll use `User::FilterByIdentity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `FilterByIdentity` accepts a `byte[]`, rather than an `Identity`. The `Sender` identity stored in the message is also a `byte[]`, not an `Identity`, so we can just pass it to the filter method. +To find the `User` based on the message's `Sender` identity, we'll use `User::FindByIdentity`, which behaves like the same function on the server. We'll print the user's name or identity in the same way as we did when notifying about `User` table events, but here we have to handle the case where we don't find a matching `User` row. This can happen when the module owner sends a message using the CLI's `spacetime call`. In this case, we'll print `unknown`. ```csharp void PrintMessage(Message message) { - var sender = User.FilterByIdentity(message.Sender); + var sender = User.FindByIdentity(message.Sender); var senderName = "unknown"; if (sender != null) { diff --git a/docs/sdks/rust/index.md b/docs/sdks/rust/index.md index bd914b00..dbc23112 100644 --- a/docs/sdks/rust/index.md +++ b/docs/sdks/rust/index.md @@ -51,13 +51,14 @@ mod module_bindings; | Function [`spacetimedb_sdk::identity::token`](#function-token) | Return the current connection's `Token`. | | Function [`spacetimedb_sdk::identity::credentials`](#function-credentials) | Return the current connection's [`Credentials`](#type-credentials). | | Function [`spacetimedb_sdk::identity::address`](#function-address) | Return the current connection's [`Address`](#type-address). | -| Function [`spacetimedb_sdk::identity::on_connect`](#function-on-connect) | Register a `FnMut` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | +| Function [`spacetimedb_sdk::identity::on_connect`](#function-on_connect) | Register a `FnMut` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::once_on_connect`](#function-once_on_connect) | Register a `FnOnce` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::remove_on_connect`](#function-remove_on_connect) | Cancel an `on_connect` or `once_on_connect` callback. | | Function [`spacetimedb_sdk::identity::load_credentials`](#function-load_credentials) | Load a saved [`Credentials`](#type-credentials) from a file. | | Function [`spacetimedb_sdk::identity::save_credentials`](#function-save_credentials) | Save a [`Credentials`](#type-credentials) to a file. | | Type [`module_bindings::{TABLE}`](#type-table) | Autogenerated `struct` type for a table, holding one row. | -| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over or seek subscribed rows where a column matches a value. | +| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over subscribed rows where a column matches a value. | +| Method [`module_bindings::{TABLE}::find_by_{COLUMN}`](#method-find_by_column) | Autogenerated method to seek a subscribed row where a unique column matches a value. | | Trait [`spacetimedb_sdk::table::TableType`](#trait-tabletype) | Automatically implemented for all tables defined by a module. | | Method [`spacetimedb_sdk::table::TableType::count`](#method-count) | Count the number of subscribed rows in a table. | | Method [`spacetimedb_sdk::table::TableType::iter`](#method-iter) | Iterate over all subscribed rows. | @@ -686,15 +687,24 @@ For each table defined by a module, `spacetime generate` generates a struct in t ```rust module_bindings::{TABLE}::filter_by_{COLUMN}( value: {COLUMN_TYPE}, -) -> {FILTER_RESULT}<{TABLE}> +) -> impl Iterator ``` -For each column of a table, `spacetime generate` generates a static method on the [table struct](#type-table) to filter or seek subscribed rows where that column matches a requested value. These methods are named `filter_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. +For each column of a table, `spacetime generate` generates a static method on the [table struct](#type-table) to filter subscribed rows where that column matches a requested value. + +These methods are named `filter_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. The method's return type is an `Iterator` over the `{TABLE}` rows which match the requested value. + +### Method `find_by_{COLUMN}` + +```rust +module_bindings::{TABLE}::find_by_{COLUMN}( + value: {COLUMN_TYPE}, +) -> {FILTER_RESULT}<{TABLE}> +``` -The method's return type depends on the column's attributes: +For each unique column of a table (those annotated `#[unique]` and `#[primarykey]`), `spacetime generate` generates a static method on the [table struct](#type-table) to seek a subscribed row where that column matches a requested value. -- For unique columns, including those annotated `#[unique]` and `#[primarykey]`, the `filter_by` method returns an `Option<{TABLE}>`, where `{TABLE}` is the [table struct](#type-table). -- For non-unique columns, the `filter_by` method returns an `impl Iterator`. +These methods are named `find_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. The method's return type is `Option<{TABLE}>`. ### Trait `TableType` @@ -816,7 +826,7 @@ This method acquires a global lock. If multiple subscribed rows match `predicate`, one is chosen arbitrarily. The choice may not be stable across different calls to `find` with the same `predicate`. -Client authors should prefer calling [tables' generated `filter_by_{COLUMN}` methods](#method-filter_by_column) when possible rather than calling `TableType::find`. +Client authors should prefer calling [tables' generated `find_by_{COLUMN}` methods](#method-find_by_column) when possible rather than calling `TableType::find`. ```rust connect(SPACETIMEDB_URI, DB_NAME, None) diff --git a/docs/sdks/rust/quickstart.md b/docs/sdks/rust/quickstart.md index d1969fc3..6df255e8 100644 --- a/docs/sdks/rust/quickstart.md +++ b/docs/sdks/rust/quickstart.md @@ -260,7 +260,7 @@ fn on_user_updated(old: &User, new: &User, _: Option<&ReducerEvent>) { When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `print_new_message` callback will check if its `reducer_event` argument is `Some`, and only print in that case. -To find the `User` based on the message's `sender` identity, we'll use `User::filter_by_identity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `filter_by_identity` accepts an owned `Identity`, rather than a reference. We can `clone` the identity held in `message.sender`. +To find the `User` based on the message's `sender` identity, we'll use `User::find_by_identity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `find_by_identity` accepts an owned `Identity`, rather than a reference. We can `clone` the identity held in `message.sender`. We'll print the user's name or identity in the same way as we did when notifying about `User` table events, but here we have to handle the case where we don't find a matching `User` row. This can happen when the module owner sends a message using the CLI's `spacetime call`. In this case, we'll print `unknown`. @@ -275,7 +275,7 @@ fn on_message_inserted(message: &Message, reducer_event: Option<&ReducerEvent>) } fn print_message(message: &Message) { - let sender = User::filter_by_identity(message.sender.clone()) + let sender = User::find_by_identity(message.sender.clone()) .map(|u| user_name_or_identity(&u)) .unwrap_or_else(|| "unknown".to_string()); println!("{}: {}", sender, message.text); diff --git a/docs/sdks/typescript/index.md b/docs/sdks/typescript/index.md index 166c1575..00917813 100644 --- a/docs/sdks/typescript/index.md +++ b/docs/sdks/typescript/index.md @@ -553,7 +553,8 @@ The generated class has a field for each of the table's columns, whose names are | Methods | | | [`Table.isEqual`](#table-isequal) | Method to compare two identities. | | [`Table.all`](#table-all) | Return all the subscribed rows in the table. | -| [`Table.filterBy{COLUMN}`](#table-filterbycolumn) | Autogenerated; returned subscribed rows with a given value in a particular column. `{COLUMN}` is a placeholder for a column name. | +| [`Table.filterBy{COLUMN}`](#table-filterbycolumn) | Autogenerated; return subscribed rows with a given value in a particular column. `{COLUMN}` is a placeholder for a column name. | +| [`Table.findBy{COLUMN}`](#table-findbycolumn) | Autogenerated; return a subscribed row with a given value in a particular unique column. `{COLUMN}` is a placeholder for a column name. | | Events | | | [`Table.onInsert`](#table-oninsert) | Register an `onInsert` callback for when a subscribed row is newly inserted into the database. | | [`Table.removeOnInsert`](#table-removeoninsert) | Unregister a previously-registered [`onInsert`](#table-oninsert) callback. | @@ -638,12 +639,12 @@ spacetimeDBClient.onConnect((token, identity, address) => { ### {Table} filterBy{COLUMN} -For each column of a table, `spacetime generate` generates a static method on the `Class` to filter or seek subscribed rows where that column matches a requested value. +For each column of a table, `spacetime generate` generates a static method on the `Class` to filter subscribed rows where that column matches a requested value. These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name converted to `camelCase`. ```ts -{Table}.filterBy{COLUMN}(value): {Table}[] +{Table}.filterBy{COLUMN}(value): Iterable<{Table}> ``` #### Parameters @@ -654,7 +655,43 @@ These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name #### Returns -`{Table}[]` +`Iterable<{Table}>` + +#### Example + +```ts +var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); + +spacetimeDBClient.onConnect((token, identity, address) => { + spacetimeDBClient.subscribe(['SELECT * FROM Person']); + + setTimeout(() => { + console.log(...Person.filterByName('John')); // prints all the `Person` rows named John. + }, 5000); +}); +``` + +--- + +### {Table} findBy{COLUMN} + +For each unique column of a table, `spacetime generate` generates a static method on the `Class` to find the subscribed row where that column matches a requested value. + +These methods are named `findBy{COLUMN}`, where `{COLUMN}` is the column name converted to `camelCase`. + +```ts +{Table}.findBy{COLUMN}(value): {Table} | undefined +``` + +#### Parameters + +| Name | Type | +| :------ | :-------------------------- | +| `value` | The type of the `{COLUMN}`. | + +#### Returns + +`{Table} | undefined` #### Example @@ -665,7 +702,7 @@ spacetimeDBClient.onConnect((token, identity, address) => { spacetimeDBClient.subscribe(['SELECT * FROM Person']); setTimeout(() => { - console.log(Person.filterByName('John')); // prints all the `Person` rows named John. + console.log(Person.findById(0)); // prints a `Person` row with id 0. }, 5000); }); ``` diff --git a/docs/sdks/typescript/quickstart.md b/docs/sdks/typescript/quickstart.md index ca8abff9..46b758ea 100644 --- a/docs/sdks/typescript/quickstart.md +++ b/docs/sdks/typescript/quickstart.md @@ -257,7 +257,7 @@ This callback fires when our local client cache of the database is populated. Th We'll define a helper function, `setAllMessagesInOrder`, to supply the `MessageType` class for our React application. It will call the autogenerated `Message.all` function to get an array of `Message` rows, then sort them and convert them to `MessageType`. -To find the `User` based on the message's `sender` identity, we'll use `User::filterByIdentity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `filterByIdentity` accepts a `UInt8Array`, rather than an `Identity`. The `sender` identity stored in the message is also a `UInt8Array`, not an `Identity`, so we can just pass it to the filter method. +To find the `User` based on the message's `sender` identity, we'll use `User::findByIdentity`, which behaves like the same function on the server. Whenever we want to display a user name, if they have set a name, we'll use that. If they haven't set a name, we'll instead use the first 8 bytes of their identity, encoded as hexadecimal. We'll define the function `userNameOrIdentity` to handle this. @@ -282,7 +282,7 @@ function setAllMessagesInOrder() { messages.sort((a, b) => (a.sent > b.sent ? 1 : a.sent < b.sent ? -1 : 0)); let messagesType: MessageType[] = messages.map((message) => { - let sender_identity = User.filterByIdentity(message.sender); + let sender_identity = User.findByIdentity(message.sender); let display_name = sender_identity ? userNameOrIdentity(sender_identity) : "unknown"; @@ -298,7 +298,7 @@ function setAllMessagesInOrder() { client.current.on("initialStateSync", () => { setAllMessagesInOrder(); - var user = User.filterByIdentity(local_identity?.current?.toUint8Array()!); + var user = User.findByIdentity(local_identity?.current?.toUint8Array()!); setName(userNameOrIdentity(user!)); }); ``` diff --git a/docs/unity/part-2a-rust.md b/docs/unity/part-2a-rust.md index dbfdc888..58523f57 100644 --- a/docs/unity/part-2a-rust.md +++ b/docs/unity/part-2a-rust.md @@ -114,7 +114,7 @@ pub fn create_player(ctx: ReducerContext, username: String) -> Result<(), String let owner_id = ctx.sender; // Make sure we don't already have a player with this identity - if PlayerComponent::filter_by_owner_id(&owner_id).is_some() { + if PlayerComponent::find_by_owner_id(&owner_id).is_some() { log::info!("Player already exists"); return Err("Player already exists".to_string()); } @@ -157,7 +157,7 @@ SpacetimeDB gives you the ability to define custom reducers that automatically t - `connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `sender` value of the `ReducerContext`. - `disconnect` - Called when a user disconnects from the SpacetimeDB module. -Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config.FilterByVersion(0)`. +Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. **Append to the bottom of lib.rs:** @@ -196,7 +196,7 @@ pub fn client_disconnected(ctx: ReducerContext) { // This helper function gets the PlayerComponent, sets the logged // in variable and updates the PlayerComponent table row. pub fn update_player_login_state(ctx: ReducerContext, logged_in: bool) { - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + if let Some(player) = PlayerComponent::find_by_owner_id(&ctx.sender) { // We clone the PlayerComponent so we can edit it and pass it back. let mut player = player.clone(); player.logged_in = logged_in; @@ -222,8 +222,8 @@ pub fn update_player_position( ) -> Result<(), String> { // First, look up the player using the sender identity, then use that // entity_id to retrieve and update the EntityComponent - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { - if let Some(mut entity) = EntityComponent::filter_by_entity_id(&player.entity_id) { + if let Some(player) = PlayerComponent::find_by_owner_id(&ctx.sender) { + if let Some(mut entity) = EntityComponent::find_by_entity_id(&player.entity_id) { entity.position = position; entity.direction = direction; entity.moving = moving; @@ -286,7 +286,7 @@ Now we need to add a reducer to handle inserting new chat messages. // Adds a chat entry to the ChatMessage table #[spacetimedb(reducer)] pub fn send_chat_message(ctx: ReducerContext, text: String) -> Result<(), String> { - if let Some(player) = PlayerComponent::filter_by_owner_id(&ctx.sender) { + if let Some(player) = PlayerComponent::find_by_owner_id(&ctx.sender) { // Now that we have the player we can insert the chat message using the player entity id. ChatMessage::insert(ChatMessage { // this column auto-increments so we can set it to 0 diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index e311714a..e4dcac7a 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -172,7 +172,7 @@ SpacetimeDB gives you the ability to define custom reducers that automatically t - `Connect` - Called when a user connects to the SpacetimeDB module. Their identity can be found in the `Sender` value of the `ReducerContext`. - `Disconnect` - Called when a user disconnects from the SpacetimeDB module. -Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. The `Config` table only ever contains a single row with version 0, which we retrieve using `Config.FilterByVersion(0)`. +Next, we are going to write a custom `Init` reducer that inserts the default message of the day into our `Config` table. **Append to the bottom of lib.cs:** diff --git a/docs/unity/part-3.md b/docs/unity/part-3.md index d1db4dbb..5c47cdc8 100644 --- a/docs/unity/part-3.md +++ b/docs/unity/part-3.md @@ -117,7 +117,7 @@ Subscribing to tables tells SpacetimeDB what rows we want in our local client ca **Local Client Cache** -The "local client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. It contains the requested data which allows efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated iter and filter_by functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. +The "local client cache" is a client-side view of the database defined by the supplied queries to the `Subscribe` function. It contains the requested data which allows efficient access without unnecessary server queries. Accessing data from the client cache is done using the auto-generated `Iter`, `FilterBy`, and `FindBy` functions for each table, and it ensures that update and event callbacks are limited to the subscribed rows. --- @@ -131,7 +131,7 @@ void OnSubscriptionApplied() // If we don't have any data for our player, then we are creating a // new one. Let's show the username dialog, which will then call the // create player reducer - var player = PlayerComponent.FilterByOwnerId(local_identity); + var player = PlayerComponent.FindByOwnerId(local_identity); if (player == null) { // Show username selection @@ -139,7 +139,7 @@ void OnSubscriptionApplied() } // Show the Message of the Day in our Config table of the Client Cache - UIChatController.instance.OnChatMessageReceived("Message of the Day: " + Config.FilterByVersion(0).MessageOfTheDay); + UIChatController.instance.OnChatMessageReceived("Message of the Day: " + Config.FindByVersion(0).MessageOfTheDay); // Now that we've done this work we can unregister this callback SpacetimeDBClient.instance.onSubscriptionApplied -= OnSubscriptionApplied; @@ -200,7 +200,7 @@ public class RemotePlayer : MonoBehaviour canvas.worldCamera = Camera.main; // Get the username from the PlayerComponent for this object and set it in the UI - PlayerComponent? playerComp = PlayerComponent.FilterByEntityId(EntityId).FirstOrDefault(); + PlayerComponent? playerComp = PlayerComponent.FindByEntityId(EntityId); if (playerComp is null) { string inputUsername = UsernameElement.Text; @@ -208,13 +208,13 @@ public class RemotePlayer : MonoBehaviour Reducer.CreatePlayer(inputUsername); // Try again, optimistically assuming success for simplicity - PlayerComponent? playerComp = PlayerComponent.FilterByEntityId(EntityId).FirstOrDefault(); + PlayerComponent? playerComp = PlayerComponent.FindByEntityId(EntityId); } Username = playerComp.Username; // Get the last location for this player and set the initial position - EntityComponent entity = EntityComponent.FilterByEntityId(EntityId); + EntityComponent entity = EntityComponent.FindByEntityId(EntityId); transform.position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); // Register for a callback that is called when the client gets an @@ -271,7 +271,7 @@ private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo var remotePlayer = Instantiate(PlayerPrefab); // Lookup and apply the position for this new player - var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var entity = EntityComponent.FindByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; @@ -382,7 +382,7 @@ private void OnPlayerComponentChanged(PlayerComponent obj) var remotePlayer = Instantiate(PlayerPrefab); // Lookup and apply the position for this new player - var entity = EntityComponent.FilterByEntityId(obj.EntityId); + var entity = EntityComponent.FindByEntityId(obj.EntityId); var position = new Vector3(entity.Position.X, entity.Position.Y, entity.Position.Z); remotePlayer.transform.position = position; @@ -448,7 +448,7 @@ Now we write the `OnSendChatMessageEvent` function. We can find the `PlayerCompo ```csharp private void OnSendChatMessageEvent(ReducerEvent dbEvent, string message) { - var player = PlayerComponent.FilterByOwnerId(dbEvent.Identity); + var player = PlayerComponent.FindByOwnerId(dbEvent.Identity); if (player != null) { UIChatController.instance.OnChatMessageReceived(player.Username + ": " + message); diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index b3a17439..10738e84 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -103,7 +103,7 @@ pub struct Config { ```rust #[spacetimedb(reducer, repeat = 1000ms)] pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Result<(), String> { - let config = Config::filter_by_version(&0).unwrap(); + let config = Config::find_by_version(&0).unwrap(); // Retrieve the maximum number of nodes we want to spawn from the Config table let num_resource_nodes = config.num_resource_nodes as usize; @@ -247,7 +247,7 @@ To get the position and the rotation of the node, we look up the `StaticLocation { case ResourceNodeType.Iron: var iron = Instantiate(IronPrefab); - StaticLocationComponent loc = StaticLocationComponent.FilterByEntityId(insertedValue.EntityId); + StaticLocationComponent loc = StaticLocationComponent.FindByEntityId(insertedValue.EntityId); Vector3 nodePos = new Vector3(loc.Location.X, 0.0f, loc.Location.Z); iron.transform.position = new Vector3(nodePos.x, MathUtil.GetTerrainHeight(nodePos), nodePos.z); iron.transform.rotation = Quaternion.Euler(0.0f, loc.Rotation, 0.0f); From fed397e37acd7ae58965bda372462fbfe48f007a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 18 Jun 2024 16:13:16 +0100 Subject: [PATCH 51/80] Update C# tagged enum docs (#65) * Update C# tagged enum docs * Apply suggestions from code review Co-authored-by: Phoebe Goldman * Reword --------- Co-authored-by: Phoebe Goldman --- docs/modules/c-sharp/index.md | 47 ++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 7037a2a8..6fdc84be 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -152,21 +152,50 @@ public enum Color SpacetimeDB has support for tagged enums which can be found in languages like Rust, but not C#. -To bridge the gap, a special marker interface `SpacetimeDB.TaggedEnum` can be used on any `SpacetimeDB.Type`-marked `struct` or `class` to mark it as a SpacetimeDB tagged enum. It accepts a tuple of 2 or more named items and will generate methods to check which variant is currently active, as well as accessors for each variant. +We provide a tagged enum support for C# modules via a special `record SpacetimeDB.TaggedEnum<(...types and names of the variants as a tuple...)>`. -It is expected that you will use the `Is*` methods to check which variant is active before accessing the corresponding field, as the accessor will throw an exception on a state mismatch. +When you inherit from the `SpacetimeDB.TaggedEnum` marker, it will generate variants as subclasses of the annotated type, so you can use regular C# pattern matching operators like `is` or `switch` to determine which variant a given tagged enum holds at any time. + +For unit variants (those without any data payload) you can use a built-in `SpacetimeDB.Unit` as the variant type. + +Example: ```csharp -// Example declaration: +// Define a tagged enum named `MyEnum` with three variants, +// `MyEnum.String`, `MyEnum.Int` and `MyEnum.None`. [SpacetimeDB.Type] -partial struct Option : SpacetimeDB.TaggedEnum<(T Some, Unit None)> { } - -// Usage: -var option = new Option { Some = 42 }; -if (option.IsSome) +public partial record MyEnum : SpacetimeDB.TaggedEnum<( + string String, + int Int, + SpacetimeDB.Unit None +)>; + +// Print an instance of `MyEnum`, using `switch`/`case` to determine the active variant. +void PrintEnum(MyEnum e) { - Log($"Value: {option.Some}"); + switch (e) + { + case MyEnum.String(var s): + Console.WriteLine(s); + break; + + case MyEnum.Int(var i): + Console.WriteLine(i); + break; + + case MyEnum.None: + Console.WriteLine("(none)"); + break; + } } + +// Test whether an instance of `MyEnum` holds some value (either a string or an int one). +bool IsSome(MyEnum e) => e is not MyEnum.None; + +// Construct an instance of `MyEnum` with the `String` variant active. +var myEnum = new MyEnum.String("Hello, world!"); +Console.WriteLine($"IsSome: {IsSome(myEnum)}"); +PrintEnum(myEnum); ``` ### Tables From 2f308933d4572acb75975389e1eec7e95f32bbe9 Mon Sep 17 00:00:00 2001 From: Chip <36650721+Lethalchip@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:39:46 -0700 Subject: [PATCH 52/80] CSharp Module tweak & Unity Tutorial part 1, 2b, 3 tweaks (#56) * expanded on taggedenums and added examples for each special ReducerKind Fixed a few typos/mistakes here and there also. * fixed part2 hyperlinks * fixed config version type from Identity to uint * update Throw => throw * update log typo * fix type on connect reducerkind from init=>connect * private=>public for UpdatePlayerLoginState reducer * remove double "publish" condenses it into one publish at the end after chat * fixed name of GameManager file, tweaks to instructions kept application.runInBackground (it wasn't included) renamed many instances of "TutorialGameManager.cs" to "BitcraftMiniGameManager.cs" to represent accurate filename * fixed onConnectError * more TutorialGameManager renames to BitcraftMiniGameManager.cs and also a FilterByX fix * added clarity to UIUsernameChooser.cs and LocalPlayer.cs -- Also fixed RemotePlayer.cs errors * some small tweaks again to GameManager name * updated tagged enums to reflect record usage and pattern matching * filter -> find fixes * expanded on taggedenums and added examples for each special ReducerKind Fixed a few typos/mistakes here and there also. * fixed config version type from Identity to uint * update Throw => throw * update log typo * fix type on connect reducerkind from init=>connect * private=>public for UpdatePlayerLoginState reducer * remove double "publish" condenses it into one publish at the end after chat * fixed name of GameManager file, tweaks to instructions kept application.runInBackground (it wasn't included) renamed many instances of "TutorialGameManager.cs" to "BitcraftMiniGameManager.cs" to represent accurate filename * fixed onConnectError * more TutorialGameManager renames to BitcraftMiniGameManager.cs and also a FilterByX fix * added clarity to UIUsernameChooser.cs and LocalPlayer.cs -- Also fixed RemotePlayer.cs errors * some small tweaks again to GameManager name * updated tagged enums to reflect record usage and pattern matching * filter -> find fixes * updated based on feedback --- docs/modules/c-sharp/index.md | 21 +++++++++++++++++++-- docs/unity/part-1.md | 4 ++-- docs/unity/part-2b-c-sharp.md | 19 ++++++------------- docs/unity/part-3.md | 26 ++++++++++++++------------ 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 6fdc84be..ad1446fb 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -321,7 +321,7 @@ public static void AddIn5Minutes(ReducerContext e, string name, int age) #### Special reducers -These are two special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute: +These are four special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute: - `ReducerKind.Init` - this reducer will be invoked when the module is first published. - `ReducerKind.Update` - this reducer will be invoked when the module is updated. @@ -337,4 +337,21 @@ public static void Init() { Log("...and we're live!"); } -``` + +[SpacetimeDB.Reducer(ReducerKind.Update)] +public static void Update() +{ + Log("Update get!"); +} + +[SpacetimeDB.Reducer(ReducerKind.Connect)] +public static void OnConnect(DbEventArgs ctx) +{ + Log($"{ctx.Sender} has connected from {ctx.Address}!"); +} + +[SpacetimeDB.Reducer(ReducerKind.Disconnect)] +public static void OnDisconnect(DbEventArgs ctx) +{ + Log($"{ctx.Sender} has disconnected."); +}``` diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index c53814d1..5643a285 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -119,5 +119,5 @@ We chose ECS for this example project because it promotes scalability, modularit From here, the tutorial continues with your favorite server module language of choice: -- [Rust](part-2a-rust) -- [C#](part-2b-c-sharp) + - [Rust](part-2a-rust.md) + - [C#](part-2b-csharp.md) diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index e4dcac7a..5be1c7cb 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -41,7 +41,7 @@ Then we are going to start by adding the global `Config` table. Right now it onl public partial class Config { [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] - public Identity Version; + public uint Version; public string? MessageOfTheDay; } ``` @@ -133,8 +133,8 @@ public static void CreatePlayer(ReducerContext ctx, string username) } catch { - Log("Error: Failed to create a unique PlayerComponent", LogLevel.Error); - Throw; + Log("Error: Failed to create a unique EntityComponent", LogLevel.Error); + throw; } // The PlayerComponent uses the same entity_id and stores the identity of @@ -275,15 +275,6 @@ In a fully developed game, the server would typically perform server-side valida --- -### Publishing a Module to SpacetimeDB - -Now that we've written the code for our server module and reached a clean checkpoint, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. - -```bash -cd server -spacetime publish -c unity-tutorial -``` - ### Finally, Add Chat Support The client project has a chat window, but so far, all it's used for is the message of the day. We are going to add the ability for players to send chat messages to each other. @@ -335,11 +326,13 @@ public static void SendChatMessage(ReducerContext ctx, string text) ## Wrapping Up +### Publishing a Module to SpacetimeDB 💡View the [entire lib.cs file](https://gist.github.com/dylanh724/68067b4e843ea6e99fbd297fe1a87c49) -Now that we added chat support, let's publish the latest module version to SpacetimeDB, assuming we're still in the `server` dir: +Now that we've written the code for our server module and reached a clean checkpoint, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. ```bash +cd server spacetime publish -c unity-tutorial ``` diff --git a/docs/unity/part-3.md b/docs/unity/part-3.md index 5c47cdc8..d3eeec8c 100644 --- a/docs/unity/part-3.md +++ b/docs/unity/part-3.md @@ -34,9 +34,9 @@ The Unity SpacetimeDB SDK relies on there being a `NetworkManager` somewhere in ![Unity-AddNetworkManager](/images/unity-tutorial/Unity-AddNetworkManager.JPG) -Next we are going to connect to our SpacetimeDB module. Open `TutorialGameManager.cs` in your editor of choice and add the following code at the top of the file: +Next we are going to connect to our SpacetimeDB module. Open `Assets/_Project/Game/BitcraftMiniGameManager.cs` in your editor of choice and add the following code at the top of the file: -**Append to the top of TutorialGameManager.cs** +**Append to the top of BitcraftMiniGameManager.cs** ```csharp using SpacetimeDB; @@ -46,7 +46,7 @@ using System.Linq; At the top of the class definition add the following members: -**Append to the top of TutorialGameManager class inside of TutorialGameManager.cs** +**Append to the top of BitcraftMiniGameManager class inside of BitcraftMiniGameManager.cs** ```csharp // These are connection variables that are exposed on the GameManager @@ -64,7 +64,7 @@ The first three fields will appear in your Inspector so you can update your conn Now add the following code to the `Start()` function. For clarity, replace your entire `Start()` function with the function below. -**REPLACE the Start() function in TutorialGameManager.cs** +**REPLACE the Start() function in BitcraftMiniGameManager.cs** ```csharp // Start is called before the first frame update @@ -72,6 +72,8 @@ void Start() { instance = this; + Application.runInBackground = true; + SpacetimeDBClient.instance.onConnect += () => { Debug.Log("Connected."); @@ -86,7 +88,7 @@ void Start() // Called when we have an error connecting to SpacetimeDB SpacetimeDBClient.instance.onConnectError += (error, message) => { - Debug.LogError($"Connection error: " + message); + Debug.LogError($"Connection error: {error} - {message}"); }; // Called when we are disconnected from SpacetimeDB @@ -123,7 +125,7 @@ The "local client cache" is a client-side view of the database defined by the su Next we write the `OnSubscriptionApplied` callback. When this event occurs for the first time, it signifies that our local client cache is fully populated. At this point, we can verify if a player entity already exists for the corresponding user. If we do not have a player entity, we need to show the `UserNameChooser` dialog so the user can enter a username. We also put the message of the day into the chat window. Finally we unsubscribe from the callback since we only need to do this once. -**Append after the Start() function in TutorialGameManager.cs** +**Append after the Start() function in BitcraftMiniGameManager.cs** ```csharp void OnSubscriptionApplied() @@ -148,7 +150,7 @@ void OnSubscriptionApplied() ### Adding the Multiplayer Functionality -Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `UIUsernameChooser.cs`. +Now we have to change what happens when you press the "Continue" button in the name dialog window. Instead of calling start game like we did in the single player version, we call the `create_player` reducer on the SpacetimeDB module using the auto-generated code. Open `Assets/_Project/Username/UIUsernameChooser.cs`. **Append to the top of UIUsernameChooser.cs** @@ -171,7 +173,7 @@ public void ButtonPressed() } ``` -We need to create a `RemotePlayer` script that we attach to remote player objects. In the same folder as `LocalPlayer.cs`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `EntityComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. +We need to create a `RemotePlayer` script that we attach to remote player objects. In the same folder as `Assets/_Project/Player/LocalPlayer.cs`, create a new C# script called `RemotePlayer`. In the start function, we will register an OnUpdate callback for the `EntityComponent` and query the local cache to get the player’s initial position. **Make sure you include a `using SpacetimeDB.Types;`** at the top of the file. First append this using to the top of `RemotePlayer.cs` @@ -203,7 +205,7 @@ public class RemotePlayer : MonoBehaviour PlayerComponent? playerComp = PlayerComponent.FindByEntityId(EntityId); if (playerComp is null) { - string inputUsername = UsernameElement.Text; + string inputUsername = UsernameElement.text; Debug.Log($"PlayerComponent not found - Creating a new player ({inputUsername})"); Reducer.CreatePlayer(inputUsername); @@ -246,7 +248,7 @@ private void EntityComponent_OnUpdate(EntityComponent oldObj, EntityComponent ob Next we need to handle what happens when a `PlayerComponent` is added to our local cache. We will handle it differently based on if it’s our local player entity or a remote player. We are going to register for the `OnInsert` event for our `PlayerComponent` table. Add the following code to the `Start` function in `TutorialGameManager`. -**Append to bottom of Start() function in TutorialGameManager.cs:** +**Append to bottom of Start() function in BitcraftMiniGameManager.cs:** ```csharp PlayerComponent.OnInsert += PlayerComponent_OnInsert; @@ -254,13 +256,13 @@ PlayerComponent.OnInsert += PlayerComponent_OnInsert; Create the `PlayerComponent_OnInsert` function which does something different depending on if it's the component for the local player or a remote player. If it's the local player, we set the local player object's initial position and call `StartGame`. If it's a remote player, we instantiate a `PlayerPrefab` with the `RemotePlayer` component. The start function of `RemotePlayer` handles initializing the player position. -**Append to bottom of TutorialGameManager class in TutorialGameManager.cs:** +**Append to bottom of TutorialGameManager class in BitcraftMiniGameManager.cs:** ```csharp private void PlayerComponent_OnInsert(PlayerComponent obj, ReducerEvent callInfo) { // If the identity of the PlayerComponent matches our user identity then this is the local player - if(obj.OwnerId == local_identity) + if(obj.Identity == local_identity) { // Now that we have our initial position we can start the game StartGame(); From 70cca46c76a476b4485b9b0e7ecf44d50330abf7 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:18:43 +0000 Subject: [PATCH 53/80] Remove Python & update "coming soon" languages (#72) * [bfops/remove-python]: do thing * [bfops/remove-python]: empty --------- Co-authored-by: Zeke Foppa --- docs/getting-started.md | 1 - docs/http/index.md | 9 - docs/index.md | 3 +- docs/modules/c-sharp/quickstart.md | 2 +- docs/modules/rust/quickstart.md | 2 +- docs/nav.js | 2 - docs/sdks/index.md | 5 +- docs/sdks/python/index.md | 552 ----------------------------- docs/sdks/python/quickstart.md | 379 -------------------- nav.ts | 2 - 10 files changed, 5 insertions(+), 952 deletions(-) delete mode 100644 docs/sdks/python/index.md delete mode 100644 docs/sdks/python/quickstart.md diff --git a/docs/getting-started.md b/docs/getting-started.md index 177a0d25..4b0cddae 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -31,4 +31,3 @@ You are ready to start developing SpacetimeDB modules. See below for a quickstar - [C# (Standalone)](/docs/sdks/c-sharp/quickstart) - [C# (Unity)](/docs/unity/part-1) - [Typescript](/docs/sdks/typescript/quickstart) -- [Python](/docs/sdks/python/quickstart) \ No newline at end of file diff --git a/docs/http/index.md b/docs/http/index.md index a4e885b1..a59408e4 100644 --- a/docs/http/index.md +++ b/docs/http/index.md @@ -20,15 +20,6 @@ To construct an appropriate `Authorization` header value for a `token`: 2. Base64-encode. 3. Prepend the string `Basic `. -#### Python - -```python -def auth_header_value(token): - username_and_password = f"token:{token}".encode("utf-8") - base64_encoded = base64.b64encode(username_and_password).decode("utf-8") - return f"Basic {base64_encoded}" -``` - #### Rust ```rust diff --git a/docs/index.md b/docs/index.md index eaee2c83..700c2bfc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -71,7 +71,6 @@ Currently, Rust is the best-supported language for writing SpacetimeDB modules. - [Rust](/docs/modules/rust) - [(Quickstart)](/docs/modules/rust/quickstart) - [C#](/docs/modules/c-sharp) - [(Quickstart)](/docs/modules/c-sharp/quickstart) - Python (Coming soon) -- C# (Coming soon) - Typescript (Coming soon) - C++ (Planned) - Lua (Planned) @@ -81,7 +80,7 @@ Currently, Rust is the best-supported language for writing SpacetimeDB modules. - [Rust](/docs/sdks/rust) - [(Quickstart)](/docs/sdks/rust/quickstart) - [C#](/docs/sdks/c-sharp) - [(Quickstart)](/docs/sdks/c-sharp/quickstart) - [TypeScript](/docs/sdks/typescript) - [(Quickstart)](/docs/sdks/typescript/quickstart) -- [Python](/docs/sdks/python) - [(Quickstart)](/docs/sdks/python/quickstart) +- Python (Planned) - C++ (Planned) - Lua (Planned) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 559dca92..027b7ef9 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -308,6 +308,6 @@ spacetime sql "SELECT * FROM Message" ## What's next? -You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/languages/rust/rust-sdk-quickstart-guide), [C#](/docs/languages/csharp/csharp-sdk-quickstart-guide), [TypeScript](/docs/languages/typescript/typescript-sdk-quickstart-guide) or [Python](/docs/languages/python/python-sdk-quickstart-guide). +You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/languages/rust/rust-sdk-quickstart-guide), [C#](/docs/languages/csharp/csharp-sdk-quickstart-guide), or [TypeScript](/docs/languages/typescript/typescript-sdk-quickstart-guide). If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index 346810d7..e115ac97 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -271,6 +271,6 @@ spacetime sql "SELECT * FROM Message" You can find the full code for this module [in the SpacetimeDB module examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/modules/quickstart-chat). -You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: [Rust](/docs/sdks/rust/quickstart), [C#](/docs/sdks/c-sharp/quickstart), [TypeScript](/docs/sdks/typescript/quickstart) or [Python](/docs/sdks/python/quickstart). +You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quickstart guide for your client language of choice: [Rust](/docs/sdks/rust/quickstart), [C#](/docs/sdks/c-sharp/quickstart), or [TypeScript](/docs/sdks/typescript/quickstart). If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). diff --git a/docs/nav.js b/docs/nav.js index 4413888e..6949c4f7 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -34,8 +34,6 @@ const nav = { page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), section("WebAssembly ABI"), diff --git a/docs/sdks/index.md b/docs/sdks/index.md index 6357e653..940f06ac 100644 --- a/docs/sdks/index.md +++ b/docs/sdks/index.md @@ -5,7 +5,6 @@ The SpacetimeDB Client SDKs provide a comprehensive interface to interact with t - [Rust](/docs/sdks/rust) - [(Quickstart)](/docs/sdks/rust/quickstart) - [C#](/docs/sdks/c-sharp) - [(Quickstart)](/docs/sdks/c-sharp/quickstart) - [TypeScript](/docs/sdks/typescript) - [(Quickstart)](/docs/sdks/typescript/quickstart) -- [Python](/docs/sdks/python) - [(Quickstart)](/docs/sdks/python/quickstart) ## Key Features @@ -55,7 +54,7 @@ The familiarity of your development team with a particular language can greatly ### Application Type -Different languages are often better suited to different types of applications. For instance, if you are developing a web-based application, you might opt for TypeScript due to its seamless integration with web technologies. On the other hand, if you're developing a desktop application, you might choose C# or Python, depending on your requirements and platform. Python is also very useful for utility scripts and tools. +Different languages are often better suited to different types of applications. For instance, if you are developing a web-based application, you might opt for TypeScript due to its seamless integration with web technologies. On the other hand, if you're developing a desktop application, you might choose C#, depending on your requirements and platform. ### Performance @@ -71,4 +70,4 @@ Each language has its own ecosystem of libraries and tools that can help in deve Remember, the best language to use is the one that best fits your use case and the one you and your team are most comfortable with. It's worth noting that due to the consistent functionality across different SDKs, transitioning from one language to another should you need to in the future will primarily involve syntax changes rather than changes in the application's logic. -You may want to use multiple languages in your application. For instance, you might want to use C# in Unity for your game logic, TypeScript for a web-based administration panel, and Python for utility scripts. This is perfectly fine, as the SpacetimeDB server is completely client-agnostic. +You may want to use multiple languages in your application. For instance, you might want to use C# in Unity for your game logic and TypeScript for a web-based administration panel. This is perfectly fine, as the SpacetimeDB server is completely client-agnostic. diff --git a/docs/sdks/python/index.md b/docs/sdks/python/index.md deleted file mode 100644 index 8b1ceb8b..00000000 --- a/docs/sdks/python/index.md +++ /dev/null @@ -1,552 +0,0 @@ -# The SpacetimeDB Python client SDK - -The SpacetimeDB client SDK for Python contains all the tools you need to build native clients for SpacetimeDB modules using Python. - -## Install the SDK - -Use pip to install the SDK: - -```bash -pip install spacetimedb-sdk -``` - -## Generate module bindings - -Each SpacetimeDB client depends on some bindings specific to your module. Create a `module_bindings` directory in your project's directory and generate the Python interface files using the Spacetime CLI. From your project directory, run: - -```bash -mkdir -p module_bindings -spacetime generate --lang python \ - --out-dir module_bindings \ - --project-path PATH-TO-MODULE-DIRECTORY -``` - -Replace `PATH-TO-MODULE-DIRECTORY` with the path to your SpacetimeDB module. - -Import your bindings in your client's code: - -```python -import module_bindings -``` - -## Basic vs Async SpacetimeDB Client - -This SDK provides two different client modules for interacting with your SpacetimeDB module. - -The Basic client allows you to have control of the main loop of your application and you are responsible for regularly calling the client's `update` function. This is useful in settings like PyGame where you want to have full control of the main loop. - -The Async client has a run function that you call after you set up all your callbacks and it will take over the main loop and handle updating the client for you. With the async client, you can have a regular "tick" function by using the `schedule_event` function. - -## Common Client Reference - -The following functions and types are used in both the Basic and Async clients. - -### API at a glance - -| Definition | Description | -|---------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| -| Type [`Identity`](#type-identity) | A unique public identifier for a client. | -| Type [`Address`](#type-address) | An opaque identifier for differentiating connections by the same `Identity`. | -| Type [`ReducerEvent`](#type-reducerevent) | `class` containing information about the reducer that triggered a row update event. | -| Type [`module_bindings::{TABLE}`](#type-table) | Autogenerated `class` type for a table, holding one row. | -| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over or seek subscribed rows where a column matches a value. | -| Method [`module_bindings::{TABLE}::iter`](#method-iter) | Autogenerated method to iterate over all subscribed rows. | -| Method [`module_bindings::{TABLE}::register_row_update`](#method-register_row_update) | Autogenerated method to register a callback that fires when a row changes. | -| Function [`module_bindings::{REDUCER_NAME}::{REDUCER_NAME}`](#function-reducer) | Autogenerated function to invoke a reducer. | -| Function [`module_bindings::{REDUCER_NAME}::register_on_{REDUCER_NAME}`](#function-register_on_reducer) | Autogenerated function to register a callback to run whenever the reducer is invoked. | - -### Type `Identity` - -```python -class Identity: - @staticmethod - def from_string(string) - - @staticmethod - def from_bytes(data) - - def __str__(self) - - def __eq__(self, other) -``` - -| Member | Args | Meaning | -| ------------- | ---------- | ------------------------------------ | -| `from_string` | `str` | Create an Identity from a hex string | -| `from_bytes` | `bytes` | Create an Identity from raw bytes | -| `__str__` | `None` | Convert the Identity to a hex string | -| `__eq__` | `Identity` | Compare two Identities for equality | - -A unique public identifier for a user of a database. - -### Type `Address` - -```python -class Address: - @staticmethod - def from_string(string) - - @staticmethod - def from_bytes(data) - - def __str__(self) - - def __eq__(self, other) -``` - -| Member | Type | Meaning | -|---------------|-----------|-------------------------------------| -| `from_string` | `str` | Create an Address from a hex string | -| `from_bytes` | `bytes` | Create an Address from raw bytes | -| `__str__` | `None` | Convert the Address to a hex string | -| `__eq__` | `Address` | Compare two Identities for equality | - -An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). - -### Type `ReducerEvent` - -```python -class ReducerEvent: - def __init__(self, caller_identity, reducer_name, status, message, args): - self.caller_identity = caller_identity - self.reducer_name = reducer_name - self.status = status - self.message = message - self.args = args -``` - -| Member | Type | Meaning | -|-------------------|---------------------|------------------------------------------------------------------------------------| -| `caller_identity` | `Identity` | The identity of the user who invoked the reducer | -| `caller_address` | `Optional[Address]` | The address of the user who invoked the reducer, or `None` for scheduled reducers. | -| `reducer_name` | `str` | The name of the reducer that was invoked | -| `status` | `str` | The status of the reducer invocation ("committed", "failed", "outofenergy") | -| `message` | `str` | The message returned by the reducer if it fails | -| `args` | `List[str]` | The arguments passed to the reducer | - -This class contains the information about a reducer event to be passed to row update callbacks. - -### Type `{TABLE}` - -```python -class TABLE: - is_table_class = True - - primary_key = "identity" - - @classmethod - def register_row_update(cls, callback: Callable[[str,TABLE,TABLE,ReducerEvent], None]) - - @classmethod - def iter(cls) -> Iterator[User] - - @classmethod - def filter_by_COLUMN_NAME(cls, COLUMN_VALUE) -> TABLE -``` - -This class is autogenerated for each table in your module. It contains methods for filtering and iterating over subscribed rows. - -### Method `filter_by_{COLUMN}` - -```python -def filter_by_COLUMN(self, COLUMN_VALUE) -> TABLE -``` - -| Argument | Type | Meaning | -| -------------- | ------------- | ---------------------- | -| `column_value` | `COLUMN_TYPE` | The value to filter by | - -For each column of a table, `spacetime generate` generates a `classmethod` on the [table class](#type-table) to filter or seek subscribed rows where that column matches a requested value. These methods are named `filter_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. - -The method's return type depends on the column's attributes: - -- For unique columns, including those annotated `#[unique]` and `#[primarykey]`, the `filter_by` method returns a `{TABLE}` or None, where `{TABLE}` is the [table struct](#type-table). -- For non-unique columns, the `filter_by` method returns an `Iterator` that can be used in a `for` loop. - -### Method `iter` - -```python -def iter(self) -> Iterator[TABLE] -``` - -Iterate over all the subscribed rows in the table. - -### Method `register_row_update` - -```python -def register_row_update(self, callback: Callable[[str,TABLE,TABLE,ReducerEvent], None]) -``` - -| Argument | Type | Meaning | -| ---------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | -| `callback` | `Callable[[str,TABLE,TABLE,ReducerEvent]` | Callback to be invoked when a row is updated (Args: row_op, old_value, new_value, reducer_event) | - -Register a callback function to be executed when a row is updated. Callback arguments are: - -- `row_op`: The type of row update event. One of `"insert"`, `"delete"`, or `"update"`. -- `old_value`: The previous value of the row, `None` if the row was inserted. -- `new_value`: The new value of the row, `None` if the row was deleted. -- `reducer_event`: The [`ReducerEvent`](#type-reducerevent) that caused the row update, or `None` if the row was updated as a result of a subscription change. - -### Function `{REDUCER_NAME}` - -```python -def {REDUCER_NAME}(arg1, arg2) -``` - -This function is autogenerated for each reducer in your module. It is used to invoke the reducer. The arguments match the arguments defined in the reducer's `#[reducer]` attribute. - -### Function `register_on_{REDUCER_NAME}` - -```python -def register_on_{REDUCER_NAME}(callback: Callable[[Identity, Optional[Address], str, str, ARG1_TYPE, ARG1_TYPE], None]) -``` - -| Argument | Type | Meaning | -| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -| `callback` | `Callable[[Identity, str, str, ARG1_TYPE, ARG1_TYPE], None]` | Callback to be invoked when the reducer is invoked (Args: caller_identity, status, message, args) | - -Register a callback function to be executed when the reducer is invoked. Callback arguments are: - -- `caller_identity`: The identity of the user who invoked the reducer. -- `caller_address`: The address of the user who invoked the reducer, or `None` for scheduled reducers. -- `status`: The status of the reducer invocation ("committed", "failed", "outofenergy"). -- `message`: The message returned by the reducer if it fails. -- `args`: Variable number of arguments passed to the reducer. - -## Async Client Reference - -### API at a glance - -| Definition | Description | -| ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| Function [`SpacetimeDBAsyncClient::run`](#function-run) | Run the client. This function will not return until the client is closed. | -| Function [`SpacetimeDBAsyncClient::subscribe`](#function-subscribe) | Subscribe to receive data and transaction updates for the provided queries. | -| Function [`SpacetimeDBAsyncClient::register_on_subscription_applied`](#function-register_on_subscription_applied) | Register a callback when the local cache is updated as a result of a change to the subscription queries. | -| Function [`SpacetimeDBAsyncClient::force_close`](#function-force_close) | Signal the client to stop processing events and close the connection to the server. | -| Function [`SpacetimeDBAsyncClient::schedule_event`](#function-schedule_event) | Schedule an event to be fired after a delay | - -### Function `run` - -```python -async def run( - self, - auth_token, - host, - address_or_name, - ssl_enabled, - on_connect, - subscription_queries=[], - ) -``` - -Run the client. This function will not return until the client is closed. - -| Argument | Type | Meaning | -| ---------------------- | --------------------------------- | -------------------------------------------------------------- | -| `auth_token` | `str` | Auth token to authenticate the user. (None if new user) | -| `host` | `str` | Hostname of SpacetimeDB server | -| `address_or_name` | `&str` | Name or address of the module. | -| `ssl_enabled` | `bool` | Whether to use SSL when connecting to the server. | -| `on_connect` | `Callable[[str, Identity], None]` | Callback to be invoked when the client connects to the server. | -| `subscription_queries` | `List[str]` | List of queries to subscribe to. | - -If `auth_token` is not None, they will be passed to the new connection to identify and authenticate the user. Otherwise, a new Identity and auth token will be generated by the server. An optional [local_config](#local_config) module can be used to store the user's auth token to local storage. - -If you are connecting to SpacetimeDB Cloud `testnet` the host should be `testnet.spacetimedb.com` and `ssl_enabled` should be `True`. If you are connecting to SpacetimeDB Standalone locally, the host should be `localhost:3000` and `ssl_enabled` should be `False`. For instructions on how to deploy to these environments, see the [Deployment Section](/docs/deploying/testnet) - -```python -asyncio.run( - spacetime_client.run( - AUTH_TOKEN, - "localhost:3000", - "my-module-name", - False, - on_connect, - ["SELECT * FROM User", "SELECT * FROM Message"], - ) -) -``` - -### Function `subscribe` - -```rust -def subscribe(self, queries: List[str]) -``` - -Subscribe to a set of queries, to be notified when rows which match those queries are altered. - -| Argument | Type | Meaning | -| --------- | ----------- | ---------------------------- | -| `queries` | `List[str]` | SQL queries to subscribe to. | - -The `queries` should be a slice of strings representing SQL queries. - -A new call to `subscribe` will remove all previous subscriptions and replace them with the new `queries`. If any rows matched the previous subscribed queries but do not match the new queries, those rows will be removed from the client cache. Row update events will be dispatched for any inserts and deletes that occur as a result of the new queries. For these events, the [`ReducerEvent`](#type-reducerevent) argument will be `None`. - -This should be called before the async client is started with [`run`](#function-run). - -```python -spacetime_client.subscribe(["SELECT * FROM User;", "SELECT * FROM Message;"]) -``` - -Subscribe to a set of queries, to be notified when rows which match those queries are altered. - -### Function `register_on_subscription_applied` - -```python -def register_on_subscription_applied(self, callback) -``` - -Register a callback function to be executed when the local cache is updated as a result of a change to the subscription queries. - -| Argument | Type | Meaning | -| ---------- | -------------------- | ------------------------------------------------------ | -| `callback` | `Callable[[], None]` | Callback to be invoked when subscriptions are applied. | - -The callback will be invoked after a successful [`subscribe`](#function-subscribe) call when the initial set of matching rows becomes available. - -```python -spacetime_client.register_on_subscription_applied(on_subscription_applied) -``` - -### Function `force_close` - -```python -def force_close(self) -) -``` - -Signal the client to stop processing events and close the connection to the server. - -```python -spacetime_client.force_close() -``` - -### Function `schedule_event` - -```python -def schedule_event(self, delay_secs, callback, *args) -``` - -Schedule an event to be fired after a delay - -To create a repeating event, call schedule_event() again from within the callback function. - -| Argument | Type | Meaning | -| ------------ | -------------------- | -------------------------------------------------------------- | -| `delay_secs` | `float` | number of seconds to wait before firing the event | -| `callback` | `Callable[[], None]` | Callback to be invoked when the event fires. | -| `args` | `*args` | Variable number of arguments to pass to the callback function. | - -```python -def application_tick(): - # ... do some work - - spacetime_client.schedule_event(0.1, application_tick) - -spacetime_client.schedule_event(0.1, application_tick) -``` - -## Basic Client Reference - -### API at a glance - -| Definition | Description | -|------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| Function [`SpacetimeDBClient::init`](#function-init) | Create a network manager instance. | -| Function [`SpacetimeDBClient::subscribe`](#function-subscribe) | Subscribe to receive data and transaction updates for the provided queries. | -| Function [`SpacetimeDBClient::register_on_event`](#function-register_on_event) | Register a callback function to handle transaction update events. | -| Function [`SpacetimeDBClient::unregister_on_event`](#function-unregister_on_event) | Unregister a callback function that was previously registered using `register_on_event`. | -| Function [`SpacetimeDBClient::register_on_subscription_applied`](#function-register_on_subscription_applied) | Register a callback function to be executed when the local cache is updated as a result of a change to the subscription queries. | -| Function [`SpacetimeDBClient::unregister_on_subscription_applied`](#function-unregister_on_subscription_applied) | Unregister a callback function from the subscription update event. | -| Function [`SpacetimeDBClient::update`](#function-update) | Process all pending incoming messages from the SpacetimeDB module. | -| Function [`SpacetimeDBClient::close`](#function-close) | Close the WebSocket connection. | -| Type [`TransactionUpdateMessage`](#type-transactionupdatemessage) | Represents a transaction update message. | - -### Function `init` - -```python -@classmethod -def init( - auth_token: str, - host: str, - address_or_name: str, - ssl_enabled: bool, - autogen_package: module, - on_connect: Callable[[], NoneType] = None, - on_disconnect: Callable[[str], NoneType] = None, - on_identity: Callable[[str, Identity, Address], NoneType] = None, - on_error: Callable[[str], NoneType] = None -) -``` - -Create a network manager instance. - -| Argument | Type | Meaning | -|-------------------|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `auth_token` | `str` | This is the token generated by SpacetimeDB that matches the user's identity. If None, token will be generated | -| `host` | `str` | Hostname:port for SpacetimeDB connection | -| `address_or_name` | `str` | The name or address of the database to connect to | -| `ssl_enabled` | `bool` | Whether to use SSL when connecting to the server. | -| `autogen_package` | `ModuleType` | Python package where SpacetimeDB module generated files are located. | -| `on_connect` | `Callable[[], None]` | Optional callback called when a connection is made to the SpacetimeDB module. | -| `on_disconnect` | `Callable[[str], None]` | Optional callback called when the Python client is disconnected from the SpacetimeDB module. The argument is the close message. | -| `on_identity` | `Callable[[str, Identity, Address], None]` | Called when the user identity is recieved from SpacetimeDB. First argument is the auth token used to login in future sessions. Third argument is the client connection's [`Address`](#type-address). | -| `on_error` | `Callable[[str], None]` | Optional callback called when the Python client connection encounters an error. The argument is the error message. | - -This function creates a new SpacetimeDBClient instance. It should be called before any other functions in the SpacetimeDBClient class. This init will call connect for you. - -```python -SpacetimeDBClient.init(autogen, on_connect=self.on_connect) -``` - -### Function `subscribe` - -```python -def subscribe(queries: List[str]) -``` - -Subscribe to receive data and transaction updates for the provided queries. - -| Argument | Type | Meaning | -| --------- | ----------- | -------------------------------------------------------------------------------------------------------- | -| `queries` | `List[str]` | A list of queries to subscribe to. Each query is a string representing an sql formatted query statement. | - -This function sends a subscription request to the SpacetimeDB module, indicating that the client wants to receive data and transaction updates related to the specified queries. - -```python -queries = ["SELECT * FROM table1", "SELECT * FROM table2 WHERE col2 = 0"] -SpacetimeDBClient.instance.subscribe(queries) -``` - -### Function `register_on_event` - -```python -def register_on_event(callback: Callable[[TransactionUpdateMessage], NoneType]) -``` - -Register a callback function to handle transaction update events. - -| Argument | Type | Meaning | -| ---------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `callback` | `Callable[[TransactionUpdateMessage], None]` | A callback function that takes a single argument of type `TransactionUpdateMessage`. This function will be invoked with a `TransactionUpdateMessage` instance containing information about the transaction update event. | - -This function registers a callback function that will be called when a reducer modifies a table matching any of the subscribed queries or if a reducer called by this Python client encounters a failure. - -```python -def handle_event(transaction_update): - # Code to handle the transaction update event - -SpacetimeDBClient.instance.register_on_event(handle_event) -``` - -### Function `unregister_on_event` - -```python -def unregister_on_event(callback: Callable[[TransactionUpdateMessage], NoneType]) -``` - -Unregister a callback function that was previously registered using `register_on_event`. - -| Argument | Type | Meaning | -| ---------- | -------------------------------------------- | ------------------------------------ | -| `callback` | `Callable[[TransactionUpdateMessage], None]` | The callback function to unregister. | - -```python -SpacetimeDBClient.instance.unregister_on_event(handle_event) -``` - -### Function `register_on_subscription_applied` - -```python -def register_on_subscription_applied(callback: Callable[[], NoneType]) -``` - -Register a callback function to be executed when the local cache is updated as a result of a change to the subscription queries. - -| Argument | Type | Meaning | -| ---------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `callback` | `Callable[[], None]` | A callback function that will be invoked on each subscription update. The callback function should not accept any arguments and should not return any value. | - -```python -def subscription_callback(): - # Code to be executed on each subscription update - -SpacetimeDBClient.instance.register_on_subscription_applied(subscription_callback) -``` - -### Function `unregister_on_subscription_applied` - -```python -def unregister_on_subscription_applied(callback: Callable[[], NoneType]) -``` - -Unregister a callback function from the subscription update event. - -| Argument | Type | Meaning | -| ---------- | -------------------- | -------------------------------------------------------------------------------------------------------- | -| `callback` | `Callable[[], None]` | A callback function that was previously registered with the `register_on_subscription_applied` function. | - -```python -def subscription_callback(): - # Code to be executed on each subscription update - -SpacetimeDBClient.instance.register_on_subscription_applied(subscription_callback) -``` - -### Function `update` - -```python -def update() -``` - -Process all pending incoming messages from the SpacetimeDB module. - -This function must be called on a regular interval in the main loop to process incoming messages. - -```python -while True: - SpacetimeDBClient.instance.update() # Call the update function in a loop to process incoming messages - # Additional logic or code can be added here -``` - -### Function `close` - -```python -def close() -``` - -Close the WebSocket connection. - -This function closes the WebSocket connection to the SpacetimeDB module. - -```python -SpacetimeDBClient.instance.close() -``` - -### Type `TransactionUpdateMessage` - -```python -class TransactionUpdateMessage: - def __init__( - self, - caller_identity: Identity, - status: str, - message: str, - reducer_name: str, - args: Dict - ) -``` - -| Member | Args | Meaning | -| ----------------- | ---------- | ------------------------------------------------- | -| `caller_identity` | `Identity` | The identity of the caller. | -| `status` | `str` | The status of the transaction. | -| `message` | `str` | A message associated with the transaction update. | -| `reducer_name` | `str` | The reducer used for the transaction. | -| `args` | `Dict` | Additional arguments for the transaction. | - -Represents a transaction update message. Used in on_event callbacks. - -For more details, see [`register_on_event`](#function-register_on_event). diff --git a/docs/sdks/python/quickstart.md b/docs/sdks/python/quickstart.md deleted file mode 100644 index 2b9d7aa1..00000000 --- a/docs/sdks/python/quickstart.md +++ /dev/null @@ -1,379 +0,0 @@ -# Python Client SDK Quick Start - -In this guide, we'll show you how to get up and running with a simple SpacetimDB app with a client written in Python. - -We'll implement a command-line client for the module created in our [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-charp/quickstart) guides. Make sure you follow one of these guides before you start on this one. - -## Install the SpacetimeDB SDK Python Package - -1. Run pip install - -```bash -pip install spacetimedb_sdk -``` - -## Project structure - -Enter the directory `quickstart-chat` you created in the Rust or C# Module Quickstart guides and create a `client` folder: - -```bash -cd quickstart-chat -mkdir client -``` - -## Create the Python main file - -Create a file called `main.py` in the `client` and open it in your favorite editor. We prefer [VS Code](https://code.visualstudio.com/). - -## Add imports - -We need to add several imports for this quickstart: - -- [`asyncio`](https://docs.python.org/3/library/asyncio.html) is required to run the async code in the SDK. -- [`multiprocessing.Queue`](https://docs.python.org/3/library/multiprocessing.html) allows us to pass our input to the async code, which we will run in a separate thread. -- [`threading`](https://docs.python.org/3/library/threading.html) allows us to spawn our async code in a separate thread so the main thread can run the input loop. - -- `spacetimedb_sdk.spacetimedb_async_client.SpacetimeDBAsyncClient` is the async wrapper around the SpacetimeDB client which we use to interact with our SpacetimeDB module. -- `spacetimedb_sdk.local_config` is an optional helper module to load the auth token from local storage. - -```python -import asyncio -from multiprocessing import Queue -import threading - -from spacetimedb_sdk.spacetimedb_async_client import SpacetimeDBAsyncClient -import spacetimedb_sdk.local_config as local_config -``` - -## Generate your module types - -The `spacetime` CLI's `generate` command will generate client-side interfaces for the tables, reducers and types defined in your server module. - -In your `client` directory, run: - -```bash -mkdir -p module_bindings -spacetime generate --lang python --out-dir module_bindings --project-path ../server -``` - -Take a look inside `client/module_bindings`. The CLI should have generated five files: - -``` -module_bindings -+-- message.py -+-- send_message_reducer.py -+-- set_name_reducer.py -+-- user.py -``` - -Now we import these types by adding the following lines to `main.py`: - -```python -import module_bindings -from module_bindings.user import User -from module_bindings.message import Message -import module_bindings.send_message_reducer as send_message_reducer -import module_bindings.set_name_reducer as set_name_reducer -``` - -## Global variables - -Next we will add our global `input_queue` and `local_identity` variables which we will explain later when they are used. - -```python -input_queue = Queue() -local_identity = None -``` - -## Define main function - -We'll work outside-in, first defining our `main` function at a high level, then implementing each behavior it needs. We need `main` to do four things: - -1. Init the optional local config module. The first parameter is the directory name to be created in the user home directory. -1. Create our async SpacetimeDB client. -1. Register our callbacks. -1. Start the async client in a thread. -1. Run a loop to read user input and send it to a repeating event in the async client. -1. When the user exits, stop the async client and exit the program. - -```python -if __name__ == "__main__": - local_config.init(".spacetimedb-python-quickstart") - - spacetime_client = SpacetimeDBAsyncClient(module_bindings) - - register_callbacks(spacetime_client) - - thread = threading.Thread(target=run_client, args=(spacetime_client,)) - thread.start() - - input_loop() - - spacetime_client.force_close() - thread.join() -``` - -## Register callbacks - -We need to handle several sorts of events: - -1. OnSubscriptionApplied is a special callback that is executed when the local client cache is populated. We will talk more about this later. -2. When a new user joins or a user is updated, we'll print an appropriate message. -3. When we receive a new message, we'll print it. -4. If the server rejects our attempt to set our name, we'll print an error. -5. If the server rejects a message we send, we'll print an error. -6. We use the `schedule_event` function to register a callback to be executed after 100ms. This callback will check the input queue for any user input and execute the appropriate command. - -Because python requires functions to be defined before they're used, the following code must be added to `main.py` before main block: - -```python -def register_callbacks(spacetime_client): - spacetime_client.client.register_on_subscription_applied(on_subscription_applied) - - User.register_row_update(on_user_row_update) - Message.register_row_update(on_message_row_update) - - set_name_reducer.register_on_set_name(on_set_name_reducer) - send_message_reducer.register_on_send_message(on_send_message_reducer) - - spacetime_client.schedule_event(0.1, check_commands) -``` - -### Handling User row updates - -For each table, we can register a row update callback to be run whenever a subscribed row is inserted, updated or deleted. We register these callbacks using the `register_row_update` methods that are generated automatically for each table by `spacetime generate`. - -These callbacks can fire in two contexts: - -- After a reducer runs, when the client's cache is updated about changes to subscribed rows. -- After calling `subscribe`, when the client's cache is initialized with all existing matching rows. - -This second case means that, even though the module only ever inserts online users, the client's `User::row_update` callbacks may be invoked with users who are offline. We'll only notify about online users. - -We are also going to check for updates to the user row. This can happen for three reasons: - -1. They've set their name using the `set_name` reducer. -2. They're an existing user re-connecting, so their `online` has been set to `true`. -3. They've disconnected, so their `online` has been set to `false`. - -We'll print an appropriate message in each of these cases. - -`row_update` callbacks take four arguments: the row operation ("insert", "update", or "delete"), the old row if it existed, the new or updated row, and a `ReducerEvent`. This will `None` for rows inserted when initializing the cache for a subscription. `ReducerEvent` is an class that contains information about the reducer that triggered this row update event. - -Whenever we want to print a user, if they have set a name, we'll use that. If they haven't set a name, we'll instead print the first 8 bytes of their identity, encoded as hexadecimal. We'll define a function `user_name_or_identity` handle this. - -Add these functions before the `register_callbacks` function: - -```python -def user_name_or_identity(user): - if user.name: - return user.name - else: - return (str(user.identity))[:8] - -def on_user_row_update(row_op, user_old, user, reducer_event): - if row_op == "insert": - if user.online: - print(f"User {user_name_or_identity(user)} connected.") - elif row_op == "update": - if user_old.online and not user.online: - print(f"User {user_name_or_identity(user)} disconnected.") - elif not user_old.online and user.online: - print(f"User {user_name_or_identity(user)} connected.") - - if user_old.name != user.name: - print( - f"User {user_name_or_identity(user_old)} renamed to {user_name_or_identity(user)}." - ) -``` - -### Print messages - -When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `on_message_row_update` callback will check if its `reducer_event` argument is not `None`, and only print in that case. - -To find the `User` based on the message's `sender` identity, we'll use `User::filter_by_identity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `filter_by_identity` accepts a `bytes`, rather than an `&Identity`. The `sender` identity stored in the message is also a `bytes`, not an `Identity`, so we can just pass it to the filter method. - -We'll print the user's name or identity in the same way as we did when notifying about `User` table events, but here we have to handle the case where we don't find a matching `User` row. This can happen when the module owner sends a message using the CLI's `spacetime call`. In this case, we'll print `unknown`. - -Add these functions before the `register_callbacks` function: - -```python -def on_message_row_update(row_op, message_old, message, reducer_event): - if reducer_event is not None and row_op == "insert": - print_message(message) - -def print_message(message): - user = User.filter_by_identity(message.sender) - user_name = "unknown" - if user is not None: - user_name = user_name_or_identity(user) - - print(f"{user_name}: {message.text}") -``` - -### Warn if our name was rejected - -We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `register_on_` method, which is automatically implemented for each reducer by `spacetime generate`. - -Each reducer callback takes four fixed arguments: - -1. The `Identity` of the client who requested the reducer invocation. -2. The `Address` of the client who requested the reducer invocation, or `None` for scheduled reducers. -3. The `Status` of the reducer run, one of `committed`, `failed` or `outofenergy`. -4. The `Message` returned by the reducer in error cases, or `None` if the reducer succeeded. - -It also takes a variable number of arguments which match the calling arguments of the reducer. - -These callbacks will be invoked in one of two cases: - -1. If the reducer was successful and altered any of our subscribed rows. -2. If we requested an invocation which failed. - -Note that a status of `failed` or `outofenergy` implies that the caller identity is our own identity. - -We already handle successful `set_name` invocations using our `User::on_update` callback, but if the module rejects a user's chosen name, we'd like that user's client to let them know. We define a function `on_set_name_reducer` as a callback which checks if the reducer failed, and if it did, prints an error message including the rejected name. - -We'll test both that our identity matches the sender and that the status is `failed`, even though the latter implies the former, for demonstration purposes. - -Add this function before the `register_callbacks` function: - -```python -def on_set_name_reducer(sender_id, sender_address, status, message, name): - if sender_id == local_identity: - if status == "failed": - print(f"Failed to set name: {message}") -``` - -### Warn if our message was rejected - -We handle warnings on rejected messages the same way as rejected names, though the types and the error message are different. - -Add this function before the `register_callbacks` function: - -```python -def on_send_message_reducer(sender_id, sender_address, status, message, msg): - if sender_id == local_identity: - if status == "failed": - print(f"Failed to send message: {message}") -``` - -### OnSubscriptionApplied callback - -This callback fires after the client cache is updated as a result in a change to the client subscription. This happens after connect and if after calling `subscribe` to modify the subscription. - -In this case, we want to print all the existing messages when the subscription is applied. `print_messages_in_order` iterates over all the `Message`s we've received, sorts them, and then prints them. `Message.iter()` is generated for all table types, and returns an iterator over all the messages in the client's cache. - -Add these functions before the `register_callbacks` function: - -```python -def print_messages_in_order(): - all_messages = sorted(Message.iter(), key=lambda x: x.sent) - for entry in all_messages: - print(f"{user_name_or_identity(User.filter_by_identity(entry.sender))}: {entry.text}") - -def on_subscription_applied(): - print(f"\nSYSTEM: Connected.") - print_messages_in_order() -``` - -### Check commands repeating event - -We'll use a repeating event to check the user input queue every 100ms. If there's a command in the queue, we'll execute it. If not, we'll just keep waiting. Notice that at the end of the function we call `schedule_event` again to so the event will repeat. - -If the command is to send a message, we'll call the `send_message` reducer. If the command is to set our name, we'll call the `set_name` reducer. - -Add these functions before the `register_callbacks` function: - -```python -def check_commands(): - global input_queue - - if not input_queue.empty(): - choice = input_queue.get() - if choice[0] == "name": - set_name_reducer.set_name(choice[1]) - else: - send_message_reducer.send_message(choice[1]) - - spacetime_client.schedule_event(0.1, check_commands) -``` - -### OnConnect callback - -This callback fires after the client connects to the server. We'll use it to save our credentials to a file so that we can re-authenticate as the same user next time we connect. - -The `on_connect` callback takes three arguments: - -1. The `Auth Token` is the equivalent of your private key. This is the only way to authenticate with the SpacetimeDB module as this user. -2. The `Identity` is the equivalent of your public key. This is used to uniquely identify this user and will be sent to other clients. We store this in a global variable so we can use it to identify that a given message or transaction was sent by us. -3. The `Address` is an opaque identifier modules can use to distinguish multiple concurrent connections by the same `Identity`. We don't need to know our `Address`, so we'll ignore that argument. - -To store our auth token, we use the optional component `local_config`, which provides a simple interface for storing and retrieving a single `Identity` from a file. We'll use the `local_config::set_string` method to store the auth token. Other projects might want to associate this token with some other identifier such as an email address or Steam ID. - -The `on_connect` callback is passed to the client connect function so it just needs to be defined before the `run_client` described next. - -```python -def on_connect(auth_token, identity): - global local_identity - local_identity = identity - - local_config.set_string("auth_token", auth_token) -``` - -## Async client thread - -We are going to write a function that starts the async client, which will be executed on a separate thread. - -```python -def run_client(spacetime_client): - asyncio.run( - spacetime_client.run( - local_config.get_string("auth_token"), - "localhost:3000", - "chat", - False, - on_connect, - ["SELECT * FROM User", "SELECT * FROM Message"], - ) - ) -``` - -## Input loop - -Finally, we need a function to be executed on the main loop which listens for user input and adds it to the queue. - -```python -def input_loop(): - global input_queue - - while True: - user_input = input() - if len(user_input) == 0: - return - elif user_input.startswith("/name "): - input_queue.put(("name", user_input[6:])) - else: - input_queue.put(("message", user_input)) -``` - -## Run the client - -Make sure your module from the Rust or C# module quickstart is published. If you used a different module name than `chat`, you will need to update the `connect` call in the `run_client` function. - -Run the client: - -```bash -python main.py -``` - -If you want to connect another client, you can use the --client command line option, which is built into the local_config module. This will create different settings file for the new client's auth token. - -```bash -python main.py --client 2 -``` - -## Next steps - -Congratulations! You've built a simple chat app with a Python client. You can now use this as a starting point for your own SpacetimeDB apps. - -For a more complex example of the Spacetime Python SDK, check out our [AI Agent](https://github.com/clockworklabs/spacetime-mud/tree/main/ai-agent-python-client) for the [Spacetime Multi-User Dungeon](https://github.com/clockworklabs/spacetime-mud). The AI Agent uses the OpenAI API to create dynamic content on command. diff --git a/nav.ts b/nav.ts index 26a83f4c..8b21cc91 100644 --- a/nav.ts +++ b/nav.ts @@ -55,8 +55,6 @@ const nav: Nav = { page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("Python Quickstart", "sdks/python/quickstart", "sdks/python/quickstart.md"), - page("Python Reference", "sdks/python", "sdks/python/index.md"), page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), From 698140bf2cf44d11188f51b637b767dfe337da6d Mon Sep 17 00:00:00 2001 From: Shubham Mishra Date: Fri, 2 Aug 2024 23:25:52 +0530 Subject: [PATCH 54/80] scheduler table doc update (#73) --- docs/modules/c-sharp/index.md | 77 ++++++++++++++++++++++++++++------- docs/modules/rust/index.md | 76 ++++++++++++++++++++++++---------- docs/unity/part-4.md | 21 +++++++--- 3 files changed, 133 insertions(+), 41 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index ad1446fb..7380467f 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -295,30 +295,77 @@ public static void PrintInfo(ReducerContext e) } ``` -`[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer in the future. -Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `ReducerContext`. +### Scheduler Tables +Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```csharp -// Example reducer: -[SpacetimeDB.Reducer] -public static void Add(string name, int age) { ... } +public static partial class Timers +{ + + // The `Scheduled` attribute links this table to a reducer. + [SpacetimeDB.Table(Scheduled = nameof(SendScheduledMessage))] + public partial struct SendMessageTimer + { + public string Text; + } -// Auto-generated by the codegen: -public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... } + + // Define the reducer that will be invoked by the scheduler table. + // The first parameter is always `ReducerContext`, and the second parameter is an instance of the linked table struct. + [SpacetimeDB.Reducer] + public static void SendScheduledMessage(ReducerContext ctx, SendMessageTimer arg) + { + // ... + } -// Usage from another reducer: -[SpacetimeDB.Reducer] -public static void AddIn5Minutes(ReducerContext e, string name, int age) -{ - // Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules. - var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age); - // We can cancel the scheduled reducer by calling `Cancel()` on the returned token. - scheduleToken.Cancel(); + // Scheduling reducers inside `init` reducer. + [SpacetimeDB.Reducer(ReducerKind.Init)] + public static void Init(ReducerContext ctx) + { + + // Schedule a one-time reducer call by inserting a row. + new SendMessageTimer + { + Text = "bot sending a message", + ScheduledAt = ctx.Time.AddSeconds(10), + ScheduledId = 1, + }.Insert(); + + + // Schedule a recurring reducer. + new SendMessageTimer + { + Text = "bot sending a message", + ScheduledAt = new TimeStamp(10), + ScheduledId = 2, + }.Insert(); + } } ``` +Annotating a struct with `Scheduled` automatically adds fields to support scheduling, It can be expanded as: + +```csharp +public static partial class Timers +{ + [SpacetimeDB.Table] + public partial struct SendMessageTimer + { + public string Text; // fields of original struct + + [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] + public ulong ScheduledId; // unique identifier to be used internally + + public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval) + } +} + +// `ScheduledAt` definition +public abstract partial record ScheduleAt: SpacetimeDB.TaggedEnum<(DateTimeOffset Time, TimeSpan Interval)> +``` + #### Special reducers These are four special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute: diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index f4d02490..c2acf5cb 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -167,8 +167,6 @@ struct Person { ### Defining reducers -`#[spacetimedb(reducer)]` optionally takes a single argument, which is a frequency at which the reducer will be automatically called by the database. - `#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. ```rust @@ -192,39 +190,75 @@ struct Item { Note that reducers can call non-reducer functions, including standard library functions. -Reducers that are called periodically take an additional macro argument specifying the frequency at which they will be invoked. Durations are parsed according to https://docs.rs/humantime/latest/humantime/fn.parse_duration.html and will usually be a number of milliseconds or seconds. -Both of these examples are invoked every second. +There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. -```rust -#[spacetimedb(reducer, repeat = 1s)] -fn every_second() {} +#[SpacetimeType] -#[spacetimedb(reducer, repeat = 1000ms)] -fn every_thousand_milliseconds() {} -``` +#[sats] -Finally, reducers can also receive a ReducerContext object, or the Timestamp at which they are invoked, just by taking parameters of those types first. +### Defining Scheduler Tables +Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```rust -#[spacetimedb(reducer, repeat = 1s)] -fn tick_timestamp(time: Timestamp) { - println!("tick at {time}"); +// The `scheduled` attribute links this table to a reducer. +#[spacetimedb(table, scheduled(send_message))] +struct SendMessageTimer { + text: String, } +``` -#[spacetimedb(reducer, repeat = 500ms)] -fn tick_ctx(ctx: ReducerContext) { - println!("tick at {}", ctx.timestamp) +The `scheduled` attribute adds a couple of default fields and expands as follows: +```rust +#[spacetimedb(table)] + struct SendMessageTimer { + text: String, // original field + #[primary] + #[autoinc] + scheduled_id: u64, // identifier for internal purpose + scheduled_at: ScheduleAt, //schedule details +} + +pub enum ScheduleAt { + /// A specific time at which the reducer is scheduled. + /// Value is a UNIX timestamp in microseconds. + Time(u64), + /// A regular interval at which the repeated reducer is scheduled. + /// Value is a duration in microseconds. + Interval(u64), } ``` -Note that each distinct time a repeating reducer is invoked, a seperate schedule is created for that reducer. So invoking `every_second` three times from the spacetimedb cli will result in the reducer being called times times each second. +Managing timers with scheduled table is as simple as inserting or deleting rows from table. +```rust +#[spacetimedb(reducer)] -There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. +// Reducers linked to the scheduler table should have their first argument as `ReducerContext` +// and the second as an instance of the table struct it is linked to. +fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { + // ... +} -#[SpacetimeType] +// Scheduling reducers inside `init` reducer +fn init() { + // Scheduling a reducer for a specific Timestamp + SendMessageTimer::insert(SendMessageTimer { + scheduled_id: 1, + text:"bot sending a message".to_string(), + //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. + scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into() + }); + + // Scheduling a reducer to be called at fixed interval of 100 milliseconds. + SendMessageTimer::insert(SendMessageTimer { + scheduled_id: 0, + text:"bot sending a message".to_string(), + //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. + scheduled_at: duration!(100ms).into(), + }); +} +``` -#[sats] ## Client API diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index 10738e84..d7c22280 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -98,11 +98,16 @@ pub struct Config { ### Step 2: Write our Resource Spawner Repeating Reducer -1. Add the following code to lib.rs. We are using a special attribute argument called repeat which will automatically schedule the reducer to run every 1000ms. +1. Add the following code to lib.rs. As we want to schedule `resource_spawn_agent` to run later, It will require to implement a scheduler table. ```rust -#[spacetimedb(reducer, repeat = 1000ms)] -pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Result<(), String> { +#[spacetimedb(table, scheduled(resource_spawner_agent))] +struct ResouceSpawnAgentSchedueler { + _prev_time: Timestamp, +} + +#[spacetimedb(reducer) +pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentScheduler) -> Result<(), String> { let config = Config::find_by_version(&0).unwrap(); // Retrieve the maximum number of nodes we want to spawn from the Config table @@ -157,18 +162,24 @@ pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Re } ``` + 2. Since this reducer uses `rand::Rng` we need add include it. Add this `use` statement to the top of lib.rs. ```rust use rand::Rng; ``` -3. Even though our reducer is set to repeat, we still need to schedule it the first time. Add the following code to the end of the `init` reducer. You can use this `schedule!` macro to schedule any reducer to run in the future after a certain amount of time. +3. Add the following code to the end of the `init` reducer to set the reducer to repeat at every regular interval. ```rust // Start our resource spawner repeating reducer - spacetimedb::schedule!("1000ms", resource_spawner_agent(_, Timestamp::now())); + ResouceSpawnAgentSchedueler::insert(ResouceSpawnAgentSchedueler { + _prev_time: TimeStamp::now(), + scheduled_id: 1, + scheduled_at: duration!(1000ms).into() + }).expect(); ``` +struct ResouceSpawnAgentSchedueler { 4. Next we need to generate our client code and publish the module. Since we changed the schema we need to make sure we include the `--clear-database` flag. Run the following commands from your Server directory: From 42c92ee40b7e011e302afcc38b89910df2000b77 Mon Sep 17 00:00:00 2001 From: Shubham Mishra Date: Sat, 3 Aug 2024 00:25:37 +0530 Subject: [PATCH 55/80] Shub/revert scheduler table doc (#76) Revert "scheduler table doc update (#73)" This reverts commit 698140bf2cf44d11188f51b637b767dfe337da6d. --- docs/modules/c-sharp/index.md | 77 +++++++---------------------------- docs/modules/rust/index.md | 76 ++++++++++------------------------ docs/unity/part-4.md | 21 +++------- 3 files changed, 41 insertions(+), 133 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 7380467f..ad1446fb 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -295,75 +295,28 @@ public static void PrintInfo(ReducerContext e) } ``` +`[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer in the future. -### Scheduler Tables -Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. +Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `ReducerContext`. ```csharp -public static partial class Timers -{ - - // The `Scheduled` attribute links this table to a reducer. - [SpacetimeDB.Table(Scheduled = nameof(SendScheduledMessage))] - public partial struct SendMessageTimer - { - public string Text; - } - - - // Define the reducer that will be invoked by the scheduler table. - // The first parameter is always `ReducerContext`, and the second parameter is an instance of the linked table struct. - [SpacetimeDB.Reducer] - public static void SendScheduledMessage(ReducerContext ctx, SendMessageTimer arg) - { - // ... - } - - - // Scheduling reducers inside `init` reducer. - [SpacetimeDB.Reducer(ReducerKind.Init)] - public static void Init(ReducerContext ctx) - { - - // Schedule a one-time reducer call by inserting a row. - new SendMessageTimer - { - Text = "bot sending a message", - ScheduledAt = ctx.Time.AddSeconds(10), - ScheduledId = 1, - }.Insert(); - - - // Schedule a recurring reducer. - new SendMessageTimer - { - Text = "bot sending a message", - ScheduledAt = new TimeStamp(10), - ScheduledId = 2, - }.Insert(); - } -} -``` +// Example reducer: +[SpacetimeDB.Reducer] +public static void Add(string name, int age) { ... } -Annotating a struct with `Scheduled` automatically adds fields to support scheduling, It can be expanded as: +// Auto-generated by the codegen: +public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... } -```csharp -public static partial class Timers +// Usage from another reducer: +[SpacetimeDB.Reducer] +public static void AddIn5Minutes(ReducerContext e, string name, int age) { - [SpacetimeDB.Table] - public partial struct SendMessageTimer - { - public string Text; // fields of original struct - - [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] - public ulong ScheduledId; // unique identifier to be used internally - - public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval) - } -} + // Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules. + var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age); -// `ScheduledAt` definition -public abstract partial record ScheduleAt: SpacetimeDB.TaggedEnum<(DateTimeOffset Time, TimeSpan Interval)> + // We can cancel the scheduled reducer by calling `Cancel()` on the returned token. + scheduleToken.Cancel(); +} ``` #### Special reducers diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index c2acf5cb..f4d02490 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -167,6 +167,8 @@ struct Person { ### Defining reducers +`#[spacetimedb(reducer)]` optionally takes a single argument, which is a frequency at which the reducer will be automatically called by the database. + `#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. ```rust @@ -190,75 +192,39 @@ struct Item { Note that reducers can call non-reducer functions, including standard library functions. +Reducers that are called periodically take an additional macro argument specifying the frequency at which they will be invoked. Durations are parsed according to https://docs.rs/humantime/latest/humantime/fn.parse_duration.html and will usually be a number of milliseconds or seconds. -There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. +Both of these examples are invoked every second. -#[SpacetimeType] +```rust +#[spacetimedb(reducer, repeat = 1s)] +fn every_second() {} -#[sats] +#[spacetimedb(reducer, repeat = 1000ms)] +fn every_thousand_milliseconds() {} +``` -### Defining Scheduler Tables -Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. +Finally, reducers can also receive a ReducerContext object, or the Timestamp at which they are invoked, just by taking parameters of those types first. ```rust -// The `scheduled` attribute links this table to a reducer. -#[spacetimedb(table, scheduled(send_message))] -struct SendMessageTimer { - text: String, +#[spacetimedb(reducer, repeat = 1s)] +fn tick_timestamp(time: Timestamp) { + println!("tick at {time}"); } -``` -The `scheduled` attribute adds a couple of default fields and expands as follows: -```rust -#[spacetimedb(table)] - struct SendMessageTimer { - text: String, // original field - #[primary] - #[autoinc] - scheduled_id: u64, // identifier for internal purpose - scheduled_at: ScheduleAt, //schedule details -} - -pub enum ScheduleAt { - /// A specific time at which the reducer is scheduled. - /// Value is a UNIX timestamp in microseconds. - Time(u64), - /// A regular interval at which the repeated reducer is scheduled. - /// Value is a duration in microseconds. - Interval(u64), +#[spacetimedb(reducer, repeat = 500ms)] +fn tick_ctx(ctx: ReducerContext) { + println!("tick at {}", ctx.timestamp) } ``` -Managing timers with scheduled table is as simple as inserting or deleting rows from table. -```rust -#[spacetimedb(reducer)] +Note that each distinct time a repeating reducer is invoked, a seperate schedule is created for that reducer. So invoking `every_second` three times from the spacetimedb cli will result in the reducer being called times times each second. -// Reducers linked to the scheduler table should have their first argument as `ReducerContext` -// and the second as an instance of the table struct it is linked to. -fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { - // ... -} +There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. -// Scheduling reducers inside `init` reducer -fn init() { - // Scheduling a reducer for a specific Timestamp - SendMessageTimer::insert(SendMessageTimer { - scheduled_id: 1, - text:"bot sending a message".to_string(), - //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. - scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into() - }); - - // Scheduling a reducer to be called at fixed interval of 100 milliseconds. - SendMessageTimer::insert(SendMessageTimer { - scheduled_id: 0, - text:"bot sending a message".to_string(), - //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. - scheduled_at: duration!(100ms).into(), - }); -} -``` +#[SpacetimeType] +#[sats] ## Client API diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index d7c22280..10738e84 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -98,16 +98,11 @@ pub struct Config { ### Step 2: Write our Resource Spawner Repeating Reducer -1. Add the following code to lib.rs. As we want to schedule `resource_spawn_agent` to run later, It will require to implement a scheduler table. +1. Add the following code to lib.rs. We are using a special attribute argument called repeat which will automatically schedule the reducer to run every 1000ms. ```rust -#[spacetimedb(table, scheduled(resource_spawner_agent))] -struct ResouceSpawnAgentSchedueler { - _prev_time: Timestamp, -} - -#[spacetimedb(reducer) -pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentScheduler) -> Result<(), String> { +#[spacetimedb(reducer, repeat = 1000ms)] +pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Result<(), String> { let config = Config::find_by_version(&0).unwrap(); // Retrieve the maximum number of nodes we want to spawn from the Config table @@ -162,24 +157,18 @@ pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentSche } ``` - 2. Since this reducer uses `rand::Rng` we need add include it. Add this `use` statement to the top of lib.rs. ```rust use rand::Rng; ``` -3. Add the following code to the end of the `init` reducer to set the reducer to repeat at every regular interval. +3. Even though our reducer is set to repeat, we still need to schedule it the first time. Add the following code to the end of the `init` reducer. You can use this `schedule!` macro to schedule any reducer to run in the future after a certain amount of time. ```rust // Start our resource spawner repeating reducer - ResouceSpawnAgentSchedueler::insert(ResouceSpawnAgentSchedueler { - _prev_time: TimeStamp::now(), - scheduled_id: 1, - scheduled_at: duration!(1000ms).into() - }).expect(); + spacetimedb::schedule!("1000ms", resource_spawner_agent(_, Timestamp::now())); ``` -struct ResouceSpawnAgentSchedueler { 4. Next we need to generate our client code and publish the module. Since we changed the schema we need to make sure we include the `--clear-database` flag. Run the following commands from your Server directory: From ef229dae37757c0bba2ea4fd30d4e9e02b76775c Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 9 Aug 2024 19:28:38 -0400 Subject: [PATCH 56/80] Update quickstart.md (#74) --- docs/sdks/rust/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdks/rust/quickstart.md b/docs/sdks/rust/quickstart.md index 6df255e8..af07e403 100644 --- a/docs/sdks/rust/quickstart.md +++ b/docs/sdks/rust/quickstart.md @@ -47,7 +47,7 @@ touch client/src/main.rs ## Generate your module types -The `spacetime` CLI's `generate` command will generate client-side interfaces for the tables, reducers and types defined in your server module. +The `spacetime` CLI's `generate` command will generate client-side interfaces for the tables, reducers and types referenced by tables or reducers defined in your server module. In your `quickstart-chat` directory, run: From c87ebba2200651d9b1a90e99b61d6816424779ed Mon Sep 17 00:00:00 2001 From: Shubham Mishra Date: Sat, 10 Aug 2024 04:58:49 +0530 Subject: [PATCH 57/80] scheduler table doc update (#77) --- docs/modules/c-sharp/index.md | 77 ++++++++++++++++++++++++++++------- docs/modules/rust/index.md | 76 ++++++++++++++++++++++++---------- docs/unity/part-4.md | 21 +++++++--- 3 files changed, 133 insertions(+), 41 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index ad1446fb..7380467f 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -295,30 +295,77 @@ public static void PrintInfo(ReducerContext e) } ``` -`[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer in the future. -Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `ReducerContext`. +### Scheduler Tables +Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```csharp -// Example reducer: -[SpacetimeDB.Reducer] -public static void Add(string name, int age) { ... } +public static partial class Timers +{ + + // The `Scheduled` attribute links this table to a reducer. + [SpacetimeDB.Table(Scheduled = nameof(SendScheduledMessage))] + public partial struct SendMessageTimer + { + public string Text; + } -// Auto-generated by the codegen: -public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... } + + // Define the reducer that will be invoked by the scheduler table. + // The first parameter is always `ReducerContext`, and the second parameter is an instance of the linked table struct. + [SpacetimeDB.Reducer] + public static void SendScheduledMessage(ReducerContext ctx, SendMessageTimer arg) + { + // ... + } -// Usage from another reducer: -[SpacetimeDB.Reducer] -public static void AddIn5Minutes(ReducerContext e, string name, int age) -{ - // Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules. - var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age); - // We can cancel the scheduled reducer by calling `Cancel()` on the returned token. - scheduleToken.Cancel(); + // Scheduling reducers inside `init` reducer. + [SpacetimeDB.Reducer(ReducerKind.Init)] + public static void Init(ReducerContext ctx) + { + + // Schedule a one-time reducer call by inserting a row. + new SendMessageTimer + { + Text = "bot sending a message", + ScheduledAt = ctx.Time.AddSeconds(10), + ScheduledId = 1, + }.Insert(); + + + // Schedule a recurring reducer. + new SendMessageTimer + { + Text = "bot sending a message", + ScheduledAt = new TimeStamp(10), + ScheduledId = 2, + }.Insert(); + } } ``` +Annotating a struct with `Scheduled` automatically adds fields to support scheduling, It can be expanded as: + +```csharp +public static partial class Timers +{ + [SpacetimeDB.Table] + public partial struct SendMessageTimer + { + public string Text; // fields of original struct + + [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] + public ulong ScheduledId; // unique identifier to be used internally + + public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval) + } +} + +// `ScheduledAt` definition +public abstract partial record ScheduleAt: SpacetimeDB.TaggedEnum<(DateTimeOffset Time, TimeSpan Interval)> +``` + #### Special reducers These are four special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute: diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index f4d02490..c2acf5cb 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -167,8 +167,6 @@ struct Person { ### Defining reducers -`#[spacetimedb(reducer)]` optionally takes a single argument, which is a frequency at which the reducer will be automatically called by the database. - `#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. ```rust @@ -192,39 +190,75 @@ struct Item { Note that reducers can call non-reducer functions, including standard library functions. -Reducers that are called periodically take an additional macro argument specifying the frequency at which they will be invoked. Durations are parsed according to https://docs.rs/humantime/latest/humantime/fn.parse_duration.html and will usually be a number of milliseconds or seconds. -Both of these examples are invoked every second. +There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. -```rust -#[spacetimedb(reducer, repeat = 1s)] -fn every_second() {} +#[SpacetimeType] -#[spacetimedb(reducer, repeat = 1000ms)] -fn every_thousand_milliseconds() {} -``` +#[sats] -Finally, reducers can also receive a ReducerContext object, or the Timestamp at which they are invoked, just by taking parameters of those types first. +### Defining Scheduler Tables +Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```rust -#[spacetimedb(reducer, repeat = 1s)] -fn tick_timestamp(time: Timestamp) { - println!("tick at {time}"); +// The `scheduled` attribute links this table to a reducer. +#[spacetimedb(table, scheduled(send_message))] +struct SendMessageTimer { + text: String, } +``` -#[spacetimedb(reducer, repeat = 500ms)] -fn tick_ctx(ctx: ReducerContext) { - println!("tick at {}", ctx.timestamp) +The `scheduled` attribute adds a couple of default fields and expands as follows: +```rust +#[spacetimedb(table)] + struct SendMessageTimer { + text: String, // original field + #[primary] + #[autoinc] + scheduled_id: u64, // identifier for internal purpose + scheduled_at: ScheduleAt, //schedule details +} + +pub enum ScheduleAt { + /// A specific time at which the reducer is scheduled. + /// Value is a UNIX timestamp in microseconds. + Time(u64), + /// A regular interval at which the repeated reducer is scheduled. + /// Value is a duration in microseconds. + Interval(u64), } ``` -Note that each distinct time a repeating reducer is invoked, a seperate schedule is created for that reducer. So invoking `every_second` three times from the spacetimedb cli will result in the reducer being called times times each second. +Managing timers with scheduled table is as simple as inserting or deleting rows from table. +```rust +#[spacetimedb(reducer)] -There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. +// Reducers linked to the scheduler table should have their first argument as `ReducerContext` +// and the second as an instance of the table struct it is linked to. +fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { + // ... +} -#[SpacetimeType] +// Scheduling reducers inside `init` reducer +fn init() { + // Scheduling a reducer for a specific Timestamp + SendMessageTimer::insert(SendMessageTimer { + scheduled_id: 1, + text:"bot sending a message".to_string(), + //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. + scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into() + }); + + // Scheduling a reducer to be called at fixed interval of 100 milliseconds. + SendMessageTimer::insert(SendMessageTimer { + scheduled_id: 0, + text:"bot sending a message".to_string(), + //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. + scheduled_at: duration!(100ms).into(), + }); +} +``` -#[sats] ## Client API diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index 10738e84..d7c22280 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -98,11 +98,16 @@ pub struct Config { ### Step 2: Write our Resource Spawner Repeating Reducer -1. Add the following code to lib.rs. We are using a special attribute argument called repeat which will automatically schedule the reducer to run every 1000ms. +1. Add the following code to lib.rs. As we want to schedule `resource_spawn_agent` to run later, It will require to implement a scheduler table. ```rust -#[spacetimedb(reducer, repeat = 1000ms)] -pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Result<(), String> { +#[spacetimedb(table, scheduled(resource_spawner_agent))] +struct ResouceSpawnAgentSchedueler { + _prev_time: Timestamp, +} + +#[spacetimedb(reducer) +pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentScheduler) -> Result<(), String> { let config = Config::find_by_version(&0).unwrap(); // Retrieve the maximum number of nodes we want to spawn from the Config table @@ -157,18 +162,24 @@ pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Re } ``` + 2. Since this reducer uses `rand::Rng` we need add include it. Add this `use` statement to the top of lib.rs. ```rust use rand::Rng; ``` -3. Even though our reducer is set to repeat, we still need to schedule it the first time. Add the following code to the end of the `init` reducer. You can use this `schedule!` macro to schedule any reducer to run in the future after a certain amount of time. +3. Add the following code to the end of the `init` reducer to set the reducer to repeat at every regular interval. ```rust // Start our resource spawner repeating reducer - spacetimedb::schedule!("1000ms", resource_spawner_agent(_, Timestamp::now())); + ResouceSpawnAgentSchedueler::insert(ResouceSpawnAgentSchedueler { + _prev_time: TimeStamp::now(), + scheduled_id: 1, + scheduled_at: duration!(1000ms).into() + }).expect(); ``` +struct ResouceSpawnAgentSchedueler { 4. Next we need to generate our client code and publish the module. Since we changed the schema we need to make sure we include the `--clear-database` flag. Run the following commands from your Server directory: From 33626389ffa56818ea3c7160506e5117cb1737fa Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Thu, 29 Aug 2024 17:52:53 -0400 Subject: [PATCH 58/80] Update quickstart.md (#81) Revert the find_by changes in rust which were never made. --- docs/modules/rust/quickstart.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index e115ac97..d3544f19 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -108,7 +108,7 @@ To `server/src/lib.rs`, add: /// Clientss invoke this reducer to set their user names. pub fn set_name(ctx: ReducerContext, name: String) -> Result<(), String> { let name = validate_name(name)?; - if let Some(user) = User::find_by_identity(&ctx.sender) { + if let Some(user) = User::filter_by_identity(&ctx.sender) { User::update_by_identity(&ctx.sender, User { name: Some(name), ..user }); Ok(()) } else { @@ -183,7 +183,7 @@ You could extend the validation in `validate_message` in similar ways to `valida Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::find_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `find_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. To `server/src/lib.rs`, add the definition of the connect reducer: @@ -191,7 +191,7 @@ To `server/src/lib.rs`, add the definition of the connect reducer: #[spacetimedb(connect)] // Called when a client connects to the SpacetimeDB pub fn identity_connected(ctx: ReducerContext) { - if let Some(user) = User::find_by_identity(&ctx.sender) { + if let Some(user) = User::filter_by_identity(&ctx.sender) { // If this is a returning user, i.e. we already have a `User` with this `Identity`, // set `online: true`, but leave `name` and `identity` unchanged. User::update_by_identity(&ctx.sender, User { online: true, ..user }); @@ -213,7 +213,7 @@ Similarly, whenever a client disconnects, the module will run the `#[spacetimedb #[spacetimedb(disconnect)] // Called when a client disconnects from SpacetimeDB pub fn identity_disconnected(ctx: ReducerContext) { - if let Some(user) = User::find_by_identity(&ctx.sender) { + if let Some(user) = User::filter_by_identity(&ctx.sender) { User::update_by_identity(&ctx.sender, User { online: false, ..user }); } else { // This branch should be unreachable, From 4b28be5316658baee50192b6b6f7373ce0b19533 Mon Sep 17 00:00:00 2001 From: Mats Bennervall <44610444+Savalige@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:54:13 +0200 Subject: [PATCH 59/80] Update Rust Quickstart to use correct function to find User (#80) Update quickstart.md From 12dcdfe1dff346cce7fc08f477e98f0886b99d98 Mon Sep 17 00:00:00 2001 From: ike709 Date: Thu, 29 Aug 2024 16:55:13 -0500 Subject: [PATCH 60/80] Explicitly remind the reader to start the server (#43) --- docs/modules/c-sharp/quickstart.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 027b7ef9..21e4fcd0 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -258,6 +258,10 @@ public static void OnDisconnect(ReducerContext ReducerContext) } ``` +## Start the Server + +If you haven't already started the SpacetimeDB server, run the `spacetime start` command in a _separate_ terminal and leave it running while you continue following along. + ## Publish the module And that's all of our module code! We'll run `spacetime publish` to compile our module and publish it on SpacetimeDB. `spacetime publish` takes an optional name which will map to the database's unique address. Clients can connect either by name or by address, but names are much more pleasant. Come up with a unique name, and fill it in where we've written ``. From 3132663b573583a66be6ee08088015200d1c3f2c Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:08:13 -0500 Subject: [PATCH 61/80] Fix broken tutorial package link (#86) --- docs/unity/part-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index 5643a285..14eb2405 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -57,7 +57,7 @@ To work with SpacetimeDB and ensure compatibility, we need to add some essential In this step, we will import the provided Unity tutorial package that contains the basic single-player game setup. Follow these instructions: -1. Download the tutorial package from the releases page on GitHub: [https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/releases/latest](https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk/releases/latest) +1. Download the tutorial package from the releases page on GitHub: [https://github.com/clockworklabs/SpacetimeDBUnityTutorial/releases/latest](https://github.com/clockworklabs/SpacetimeDBUnityTutorial/releases/latest) 2. In Unity, go to **Assets -> Import Package -> Custom Package**. ![Unity-ImportCustomPackageB](/images/unity-tutorial/Unity-ImportCustomPackageB.JPG) From 3b46a5d3aa5c68d87dfbd964822491023bc7dfe1 Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:38:41 +0530 Subject: [PATCH 62/80] prettier (#85) Push --- .prettierrc | 11 ++ README.md | 1 + docs/getting-started.md | 2 +- docs/http/database.md | 72 ++++++------ docs/http/energy.md | 4 +- docs/http/identity.md | 12 +- docs/modules/c-sharp/index.md | 21 ++-- docs/modules/c-sharp/quickstart.md | 4 +- docs/modules/rust/index.md | 13 ++- docs/nav.js | 116 ++++++++++++-------- docs/sdks/c-sharp/index.md | 6 +- docs/sdks/index.md | 2 +- docs/sdks/rust/index.md | 20 ++-- docs/sdks/typescript/index.md | 170 +++++++++++++++++------------ docs/sdks/typescript/quickstart.md | 42 +++---- docs/unity/part-1.md | 4 +- docs/unity/part-2b-c-sharp.md | 1 + docs/unity/part-4.md | 2 +- docs/ws/index.md | 4 +- nav.ts | 119 ++++++++++++-------- package.json | 2 +- 21 files changed, 363 insertions(+), 265 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..2921455b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "jsxSingleQuote": false, + "trailingComma": "es5", + "endOfLine": "auto", + "printWidth": 80 +} diff --git a/README.md b/README.md index 0f9998b0..c31b2c3f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ git clone ssh://git@github.com//spacetime-docs git add . git commit -m "A specific description of the changes I made and why" ``` + 5. Push your changes to your fork as a branch ```bash diff --git a/docs/getting-started.md b/docs/getting-started.md index 4b0cddae..33265dc2 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -28,6 +28,6 @@ You are ready to start developing SpacetimeDB modules. See below for a quickstar ### Client - [Rust](/docs/sdks/rust/quickstart) -- [C# (Standalone)](/docs/sdks/c-sharp/quickstart) +- [C# (Standalone)](/docs/sdks/c-sharp/quickstart) - [C# (Unity)](/docs/unity/part-1) - [Typescript](/docs/sdks/typescript/quickstart) diff --git a/docs/http/database.md b/docs/http/database.md index 16ee729c..9b6e0488 100644 --- a/docs/http/database.md +++ b/docs/http/database.md @@ -15,7 +15,7 @@ The HTTP endpoints in `/database` allow clients to interact with Spacetime datab | [`/database/confirm_recovery_code GET`](#databaseconfirm_recovery_code-get) | Recover a login token from a recovery code. | | [`/database/publish POST`](#databasepublish-post) | Publish a database given its module code. | | [`/database/delete/:address POST`](#databasedeleteaddress-post) | Delete a database. | -| [`/database/subscribe/:name_or_address GET`](#databasesubscribename_or_address-get) | Begin a [WebSocket connection](/docs/ws). | +| [`/database/subscribe/:name_or_address GET`](#databasesubscribename_or_address-get) | Begin a [WebSocket connection](/docs/ws). | | [`/database/call/:name_or_address/:reducer POST`](#databasecallname_or_addressreducer-post) | Invoke a reducer in a database. | | [`/database/schema/:name_or_address GET`](#databaseschemaname_or_address-get) | Get the schema for a database. | | [`/database/schema/:name_or_address/:entity_type/:entity GET`](#databaseschemaname_or_addressentity_typeentity-get) | Get a schema for a particular table or reducer. | @@ -92,8 +92,8 @@ Accessible through the CLI as `spacetime dns set-name
`. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -145,8 +145,8 @@ Accessible through the CLI as `spacetime dns register-tld `. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -183,11 +183,11 @@ Accessible through the CLI as `spacetime identity recover `. #### Query Parameters -| Name | Value | -| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `identity` | The identity whose token should be recovered. | +| Name | Value | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `identity` | The identity whose token should be recovered. | | `email` | The email to send the recovery code or link to. This email must be associated with the identity, either during creation via [`/identity`](/docs/http/identity#identity-post) or afterwards via [`/identity/:identity/set-email`](/docs/http/identity#identityidentityset_email-post). | -| `link` | A boolean; whether to send a clickable link rather than a recovery code. | +| `link` | A boolean; whether to send a clickable link rather than a recovery code. | ## `/database/confirm_recovery_code GET` @@ -229,8 +229,8 @@ Accessible through the CLI as `spacetime publish`. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data @@ -281,8 +281,8 @@ Accessible through the CLI as `spacetime delete
`. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/database/subscribe/:name_or_address GET` @@ -299,18 +299,18 @@ Begin a [WebSocket connection](/docs/ws) with a database. For more information about WebSocket headers, see [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455). -| Name | Value | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Name | Value | +| ------------------------ | ---------------------------------------------------------------------------------------------------- | | `Sec-WebSocket-Protocol` | [`v1.bin.spacetimedb`](/docs/ws#binary-protocol) or [`v1.text.spacetimedb`](/docs/ws#text-protocol). | -| `Connection` | `Updgrade` | -| `Upgrade` | `websocket` | -| `Sec-WebSocket-Version` | `13` | -| `Sec-WebSocket-Key` | A 16-byte value, generated randomly by the client, encoded as Base64. | +| `Connection` | `Updgrade` | +| `Upgrade` | `websocket` | +| `Sec-WebSocket-Version` | `13` | +| `Sec-WebSocket-Key` | A 16-byte value, generated randomly by the client, encoded as Base64. | #### Optional Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/database/call/:name_or_address/:reducer POST` @@ -326,8 +326,8 @@ Invoke a reducer in a database. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data @@ -444,10 +444,10 @@ The `"entities"` will be an object whose keys are table and reducer names, and w } ``` -| Entity field | Value | -| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `arity` | For tables, the number of colums; for reducers, the number of arguments. | -| `type` | For tables, `"table"`; for reducers, `"reducer"`. | +| Entity field | Value | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `arity` | For tables, the number of colums; for reducers, the number of arguments. | +| `type` | For tables, `"table"`; for reducers, `"reducer"`. | | `schema` | A [JSON-encoded `ProductType`](/docs/satn); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | The `"typespace"` will be a JSON array of [`AlgebraicType`s](/docs/satn) referenced by the module. This can be used to resolve `Ref` types within the schema; the type `{ "Ref": n }` refers to `response["typespace"][n]`. @@ -484,10 +484,10 @@ Returns a single entity in the same format as in the `"entities"` returned by [t } ``` -| Field | Value | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `arity` | For tables, the number of colums; for reducers, the number of arguments. | -| `type` | For tables, `"table"`; for reducers, `"reducer"`. | +| Field | Value | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `arity` | For tables, the number of colums; for reducers, the number of arguments. | +| `type` | For tables, `"table"`; for reducers, `"reducer"`. | | `schema` | A [JSON-encoded `ProductType`](/docs/satn); for tables, the table schema; for reducers, the argument schema. Only present if `expand` is supplied and true. | ## `/database/info/:name_or_address GET` @@ -514,7 +514,7 @@ Returns JSON in the form: ``` | Field | Type | Meaning | -| --------------------| ------ | ---------------------------------------------------------------- | +| ------------------- | ------ | ---------------------------------------------------------------- | | `"address"` | String | The address of the database. | | `"owner_identity"` | String | The Spacetime identity of the database's owner. | | `"host_type"` | String | The module host type; currently always `"wasm"`. | @@ -541,8 +541,8 @@ Accessible through the CLI as `spacetime logs `. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -563,8 +563,8 @@ Accessible through the CLI as `spacetime sql `. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Data diff --git a/docs/http/energy.md b/docs/http/energy.md index b49a1ee7..6f008314 100644 --- a/docs/http/energy.md +++ b/docs/http/energy.md @@ -57,8 +57,8 @@ Accessible through the CLI as `spacetime energy set-balance #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns diff --git a/docs/http/identity.md b/docs/http/identity.md index 5fb45867..6f1e22c9 100644 --- a/docs/http/identity.md +++ b/docs/http/identity.md @@ -71,8 +71,8 @@ Generate a short-lived access token which can be used in untrusted contexts, e.g #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns @@ -107,8 +107,8 @@ Accessible through the CLI as `spacetime identity set-email `. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | ## `/identity/:identity/databases GET` @@ -145,8 +145,8 @@ Verify the validity of an identity/token pair. #### Required Headers -| Name | Value | -| --------------- | ------------------------------------------------------------------------------------------- | +| Name | Value | +| --------------- | --------------------------------------------------------------- | | `Authorization` | A Spacetime token [encoded as Basic authorization](/docs/http). | #### Returns diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 7380467f..f6763fc7 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -42,7 +42,7 @@ static partial class Module { // We can skip (or explicitly set to zero) auto-incremented fields when creating new rows. var person = new Person { Name = name, Age = age }; - + // `Insert()` method is auto-generated and will insert the given row into the table. person.Insert(); // After insertion, the auto-incremented fields will be populated with their actual values. @@ -120,7 +120,6 @@ And a couple of special custom types: - `Identity` (`SpacetimeDB.Runtime.Identity`) - a unique identifier for each user; internally a byte blob but can be printed, hashed and compared for equality. - `Address` (`SpacetimeDB.Runtime.Address`) - an identifier which disamgibuates connections by the same `Identity`; internally a byte blob but can be printed, hashed and compared for equality. - #### Custom types `[SpacetimeDB.Type]` attribute can be used on any `struct`, `class` or an `enum` to mark it as a SpacetimeDB type. It will implement serialization and deserialization for values of this type so that they can be stored in the database. @@ -245,10 +244,10 @@ public partial struct Person // Finds a row in the table with the given value in the `Id` column and returns it, or `null` if no such row exists. public static Person? FindById(int id); - + // Deletes a row in the table with the given value in the `Id` column and returns `true` if the row was found and deleted, or `false` if no such row exists. public static bool DeleteById(int id); - + // Updates a row in the table with the given value in the `Id` column and returns `true` if the row was found and updated, or `false` if no such row exists. public static bool UpdateById(int oldId, Person newValue); } @@ -295,14 +294,14 @@ public static void PrintInfo(ReducerContext e) } ``` - ### Scheduler Tables + Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```csharp public static partial class Timers { - + // The `Scheduled` attribute links this table to a reducer. [SpacetimeDB.Table(Scheduled = nameof(SendScheduledMessage))] public partial struct SendMessageTimer @@ -310,7 +309,7 @@ public static partial class Timers public string Text; } - + // Define the reducer that will be invoked by the scheduler table. // The first parameter is always `ReducerContext`, and the second parameter is an instance of the linked table struct. [SpacetimeDB.Reducer] @@ -354,10 +353,10 @@ public static partial class Timers public partial struct SendMessageTimer { public string Text; // fields of original struct - + [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] public ulong ScheduledId; // unique identifier to be used internally - + public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval) } } @@ -375,10 +374,9 @@ These are four special kinds of reducers that can be used to respond to module l - `ReducerKind.Connect` - this reducer will be invoked when a client connects to the database. - `ReducerKind.Disconnect` - this reducer will be invoked when a client disconnects from the database. - Example: -```csharp +````csharp [SpacetimeDB.Reducer(ReducerKind.Init)] public static void Init() { @@ -402,3 +400,4 @@ public static void OnDisconnect(DbEventArgs ctx) { Log($"{ctx.Sender} has disconnected."); }``` +```` diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 21e4fcd0..768602e4 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -23,6 +23,7 @@ If you haven't already, start by [installing SpacetimeDB](/install). This will i Next we need to [install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) so that we can build and publish our module. You may already have .NET 8 and can be checked: + ```bash dotnet --list-sdks ``` @@ -50,7 +51,7 @@ spacetime init --lang csharp server ## Declare imports -`spacetime init` generated a few files: +`spacetime init` generated a few files: 1. Open `server/StdbModule.csproj` to generate a .sln file for intellisense/validation support. 2. Open `server/Lib.cs`, a trivial module. @@ -81,7 +82,6 @@ To get our chat server running, we'll need to store two kinds of data: informati For each `User`, we'll store their `Identity`, an optional name they can set to identify themselves to other users, and whether they're online or not. We'll designate the `Identity` as our primary key, which enforces that it must be unique, indexes it for faster lookup, and allows clients to track updates. - In `server/Lib.cs`, add the definition of the table `User` to the `Module` class: ```csharp diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index c2acf5cb..55ceec18 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -190,7 +190,6 @@ struct Item { Note that reducers can call non-reducer functions, including standard library functions. - There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. #[SpacetimeType] @@ -198,6 +197,7 @@ There are several macros which modify the semantics of a column, which are appli #[sats] ### Defining Scheduler Tables + Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. ```rust @@ -208,7 +208,8 @@ struct SendMessageTimer { } ``` -The `scheduled` attribute adds a couple of default fields and expands as follows: +The `scheduled` attribute adds a couple of default fields and expands as follows: + ```rust #[spacetimedb(table)] struct SendMessageTimer { @@ -230,10 +231,11 @@ pub enum ScheduleAt { ``` Managing timers with scheduled table is as simple as inserting or deleting rows from table. + ```rust #[spacetimedb(reducer)] -// Reducers linked to the scheduler table should have their first argument as `ReducerContext` +// Reducers linked to the scheduler table should have their first argument as `ReducerContext` // and the second as an instance of the table struct it is linked to. fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { // ... @@ -245,7 +247,7 @@ fn init() { SendMessageTimer::insert(SendMessageTimer { scheduled_id: 1, text:"bot sending a message".to_string(), - //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. + //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into() }); @@ -253,13 +255,12 @@ fn init() { SendMessageTimer::insert(SendMessageTimer { scheduled_id: 0, text:"bot sending a message".to_string(), - //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. + //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. scheduled_at: duration!(100ms).into(), }); } ``` - ## Client API Besides the macros for creating tables and reducers, there's two other parts of the Rust SpacetimeDB library. One is a collection of macros for logging, and the other is all the automatically generated functions for operating on those tables. diff --git a/docs/nav.js b/docs/nav.js index 6949c4f7..5a669500 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,55 +1,75 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); function page(title, slug, path, props) { - return { type: "page", path, slug, title, ...props }; + return { type: 'page', path, slug, title, ...props }; } function section(title) { - return { type: "section", title }; + return { type: 'section', title }; } const nav = { - items: [ - section("Intro"), - page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page("Getting Started", "getting-started", "getting-started.md"), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), - section("Unity Tutorial - Basic Multiplayer"), - page("Overview", "unity-tutorial", "unity/index.md"), - page("1 - Setup", "unity/part-1", "unity/part-1.md"), - page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), - page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2b-c-sharp.md"), - page("3 - Client", "unity/part-3", "unity/part-3.md"), - section("Unity Tutorial - Advanced"), - page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), - page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), - ], + items: [ + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), + page( + '2b - Server (C#)', + 'unity/part-2b-c-sharp', + 'unity/part-2b-c-sharp.md' + ), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page( + 'Rust Quickstart', + 'modules/rust/quickstart', + 'modules/rust/quickstart.md' + ), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page( + 'C# Quickstart', + 'modules/c-sharp/quickstart', + 'modules/c-sharp/quickstart.md' + ), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page( + 'Typescript Quickstart', + 'sdks/typescript/quickstart', + 'sdks/typescript/quickstart.md' + ), + page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), + page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page( + 'C# Quickstart', + 'sdks/c-sharp/quickstart', + 'sdks/c-sharp/quickstart.md' + ), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), + section('WebAssembly ABI'), + page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md'), + ], }; exports.default = nav; diff --git a/docs/sdks/c-sharp/index.md b/docs/sdks/c-sharp/index.md index e8a3d01a..d85f5702 100644 --- a/docs/sdks/c-sharp/index.md +++ b/docs/sdks/c-sharp/index.md @@ -849,7 +849,7 @@ Save a token to the filesystem. ### Class `Identity` ```cs -namespace SpacetimeDB +namespace SpacetimeDB { public struct Identity : IEquatable { @@ -869,7 +869,7 @@ A unique public identifier for a user of a database. Columns of type `Identity` inside a module will be represented in the C# SDK as properties of type `byte[]`. `Identity` is essentially just a wrapper around `byte[]`, and you can use the `Bytes` property to get a `byte[]` that can be used to filter tables and so on. ```cs -namespace SpacetimeDB +namespace SpacetimeDB { public struct Address : IEquatable
{ @@ -888,7 +888,7 @@ An opaque identifier for a client connection to a database, intended to differen The SpacetimeDB C# SDK performs internal logging. -A default logger is set up automatically for you - a [`ConsoleLogger`](#class-consolelogger) for C# projects and [`UnityDebugLogger`](#class-unitydebuglogger) for Unity projects. +A default logger is set up automatically for you - a [`ConsoleLogger`](#class-consolelogger) for C# projects and [`UnityDebugLogger`](#class-unitydebuglogger) for Unity projects. If you want to redirect SDK logs elsewhere, you can inherit from the [`ISpacetimeDBLogger`](#interface-ispacetimedblogger) and assign an instance of your class to the `SpacetimeDB.Logger.Current` static property. diff --git a/docs/sdks/index.md b/docs/sdks/index.md index 940f06ac..46078cb9 100644 --- a/docs/sdks/index.md +++ b/docs/sdks/index.md @@ -1,4 +1,4 @@ - SpacetimeDB Client SDKs Overview +SpacetimeDB Client SDKs Overview The SpacetimeDB Client SDKs provide a comprehensive interface to interact with the SpacetimeDB server engine from various programming languages. Currently, SDKs are available for diff --git a/docs/sdks/rust/index.md b/docs/sdks/rust/index.md index dbc23112..9c9e6f12 100644 --- a/docs/sdks/rust/index.md +++ b/docs/sdks/rust/index.md @@ -50,15 +50,15 @@ mod module_bindings; | Function [`spacetimedb_sdk::identity::identity`](#function-identity) | Return the current connection's `Identity`. | | Function [`spacetimedb_sdk::identity::token`](#function-token) | Return the current connection's `Token`. | | Function [`spacetimedb_sdk::identity::credentials`](#function-credentials) | Return the current connection's [`Credentials`](#type-credentials). | -| Function [`spacetimedb_sdk::identity::address`](#function-address) | Return the current connection's [`Address`](#type-address). | +| Function [`spacetimedb_sdk::identity::address`](#function-address) | Return the current connection's [`Address`](#type-address). | | Function [`spacetimedb_sdk::identity::on_connect`](#function-on_connect) | Register a `FnMut` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::once_on_connect`](#function-once_on_connect) | Register a `FnOnce` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | | Function [`spacetimedb_sdk::identity::remove_on_connect`](#function-remove_on_connect) | Cancel an `on_connect` or `once_on_connect` callback. | | Function [`spacetimedb_sdk::identity::load_credentials`](#function-load_credentials) | Load a saved [`Credentials`](#type-credentials) from a file. | | Function [`spacetimedb_sdk::identity::save_credentials`](#function-save_credentials) | Save a [`Credentials`](#type-credentials) to a file. | | Type [`module_bindings::{TABLE}`](#type-table) | Autogenerated `struct` type for a table, holding one row. | -| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over subscribed rows where a column matches a value. | -| Method [`module_bindings::{TABLE}::find_by_{COLUMN}`](#method-find_by_column) | Autogenerated method to seek a subscribed row where a unique column matches a value. | +| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over subscribed rows where a column matches a value. | +| Method [`module_bindings::{TABLE}::find_by_{COLUMN}`](#method-find_by_column) | Autogenerated method to seek a subscribed row where a unique column matches a value. | | Trait [`spacetimedb_sdk::table::TableType`](#trait-tabletype) | Automatically implemented for all tables defined by a module. | | Method [`spacetimedb_sdk::table::TableType::count`](#method-count) | Count the number of subscribed rows in a table. | | Method [`spacetimedb_sdk::table::TableType::iter`](#method-iter) | Iterate over all subscribed rows. | @@ -535,9 +535,9 @@ spacetimedb_sdk::identity::on_connect( Register a callback to be invoked upon authentication with the database. | Argument | Type | Meaning | -|------------|----------------------------------------------------|--------------------------------------------------------| +| ---------- | -------------------------------------------------- | ------------------------------------------------------ | | `callback` | `impl FnMut(&Credentials, Address) + Send + 'sync` | Callback to be invoked upon successful authentication. | - + The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. The [`Credentials`](#type-credentials) passed to the callback can be saved and used to authenticate the same user in future connections. @@ -570,7 +570,7 @@ spacetimedb_sdk::identity::once_on_connect( Register a callback to be invoked once upon authentication with the database. | Argument | Type | Meaning | -|------------|-----------------------------------------------------|------------------------------------------------------------------| +| ---------- | --------------------------------------------------- | ---------------------------------------------------------------- | | `callback` | `impl FnOnce(&Credentials, Address) + Send + 'sync` | Callback to be invoked once upon next successful authentication. | The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. @@ -1114,8 +1114,8 @@ module_bindings::on_{REDUCER}( For each reducer defined by a module, `spacetime generate` generates a function which registers a `FnMut` callback to run each time the reducer is invoked. The generated functions are named `on_{REDUCER}`, where `{REDUCER}` is the reducer's name converted to `snake_case`. -| Argument | Type | Meaning | -| ---------- | ------------------------------------------------------------- | ------------------------------------------------ | +| Argument | Type | Meaning | +| ---------- | ----------------------------------------------------------------------------- | ------------------------------------------------ | | `callback` | `impl FnMut(&Identity, Option
&Status, {&ARGS...}) + Send + 'static` | Callback to run whenever the reducer is invoked. | The callback always accepts three arguments: @@ -1142,8 +1142,8 @@ module_bindings::once_on_{REDUCER}( For each reducer defined by a module, `spacetime generate` generates a function which registers a `FnOnce` callback to run the next time the reducer is invoked. The generated functions are named `once_on_{REDUCER}`, where `{REDUCER}` is the reducer's name converted to `snake_case`. -| Argument | Type | Meaning | -| ---------- | -------------------------------------------------------------- | ----------------------------------------------------- | +| Argument | Type | Meaning | +| ---------- | ------------------------------------------------------------------------------- | ----------------------------------------------------- | | `callback` | `impl FnOnce(&Identity, Option
, &Status, {&ARGS...}) + Send + 'static` | Callback to run the next time the reducer is invoked. | The callback accepts the same arguments as an [on-reducer callback](#function-on_reducer), but may be a `FnOnce` rather than a `FnMut`. diff --git a/docs/sdks/typescript/index.md b/docs/sdks/typescript/index.md index 00917813..4f4e17da 100644 --- a/docs/sdks/typescript/index.md +++ b/docs/sdks/typescript/index.md @@ -10,11 +10,11 @@ First, create a new client project, and add the following to your `tsconfig.json ```json { - "compilerOptions": { - //You can use any target higher than this one - //https://www.typescriptlang.org/tsconfig#target - "target": "es2015" - } + "compilerOptions": { + //You can use any target higher than this one + //https://www.typescriptlang.org/tsconfig#target + "target": "es2015" + } } ``` @@ -147,7 +147,12 @@ const name_or_address = 'database_name'; const auth_token = undefined; const protocol = 'binary'; -var spacetimeDBClient = new SpacetimeDBClient(host, name_or_address, auth_token, protocol); +var spacetimeDBClient = new SpacetimeDBClient( + host, + name_or_address, + auth_token, + protocol +); ``` ## Class methods @@ -268,7 +273,11 @@ const host = 'ws://localhost:3000'; const name_or_address = 'database_name'; const auth_token = undefined; -var spacetimeDBClient = new SpacetimeDBClient(host, name_or_address, auth_token); +var spacetimeDBClient = new SpacetimeDBClient( + host, + name_or_address, + auth_token +); // Connect with the initial parameters spacetimeDBClient.connect(); //Set the `auth_token` @@ -288,7 +297,10 @@ disconnect(): void #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.disconnect(); ``` @@ -343,10 +355,10 @@ The credentials passed to the callback can be saved and used to authenticate the ```ts spacetimeDBClient.onConnect((token, identity, address) => { - console.log('Connected to SpacetimeDB'); - console.log('Token', token); - console.log('Identity', identity); - console.log('Address', address); + console.log('Connected to SpacetimeDB'); + console.log('Token', token); + console.log('Identity', identity); + console.log('Address', address); }); ``` @@ -370,7 +382,7 @@ onError(callback: (...args: any[]) => void): void ```ts spacetimeDBClient.onError((...args: any[]) => { - console.error('ERROR', args); + console.error('ERROR', args); }); ``` @@ -546,22 +558,22 @@ For each table defined by a module, `spacetime generate` generates a `class` in The generated class has a field for each of the table's columns, whose names are the column names converted to `snake_case`. -| Properties | Description | -| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| [`Table.name`](#table-name) | The name of the class. | -| [`Table.tableName`](#table-tableName) | The name of the table in the database. | -| Methods | | -| [`Table.isEqual`](#table-isequal) | Method to compare two identities. | -| [`Table.all`](#table-all) | Return all the subscribed rows in the table. | -| [`Table.filterBy{COLUMN}`](#table-filterbycolumn) | Autogenerated; return subscribed rows with a given value in a particular column. `{COLUMN}` is a placeholder for a column name. | -| [`Table.findBy{COLUMN}`](#table-findbycolumn) | Autogenerated; return a subscribed row with a given value in a particular unique column. `{COLUMN}` is a placeholder for a column name. | -| Events | | -| [`Table.onInsert`](#table-oninsert) | Register an `onInsert` callback for when a subscribed row is newly inserted into the database. | -| [`Table.removeOnInsert`](#table-removeoninsert) | Unregister a previously-registered [`onInsert`](#table-oninsert) callback. | -| [`Table.onUpdate`](#table-onupdate) | Register an `onUpdate` callback for when an existing row is modified. | -| [`Table.removeOnUpdate`](#table-removeonupdate) | Unregister a previously-registered [`onUpdate`](#table-onupdate) callback. | -| [`Table.onDelete`](#table-ondelete) | Register an `onDelete` callback for when a subscribed row is removed from the database. | -| [`Table.removeOnDelete`](#table-removeondelete) | Unregister a previously-registered [`onDelete`](#table-removeondelete) callback. | +| Properties | Description | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| [`Table.name`](#table-name) | The name of the class. | +| [`Table.tableName`](#table-tableName) | The name of the table in the database. | +| Methods | | +| [`Table.isEqual`](#table-isequal) | Method to compare two identities. | +| [`Table.all`](#table-all) | Return all the subscribed rows in the table. | +| [`Table.filterBy{COLUMN}`](#table-filterbycolumn) | Autogenerated; return subscribed rows with a given value in a particular column. `{COLUMN}` is a placeholder for a column name. | +| [`Table.findBy{COLUMN}`](#table-findbycolumn) | Autogenerated; return a subscribed row with a given value in a particular unique column. `{COLUMN}` is a placeholder for a column name. | +| Events | | +| [`Table.onInsert`](#table-oninsert) | Register an `onInsert` callback for when a subscribed row is newly inserted into the database. | +| [`Table.removeOnInsert`](#table-removeoninsert) | Unregister a previously-registered [`onInsert`](#table-oninsert) callback. | +| [`Table.onUpdate`](#table-onupdate) | Register an `onUpdate` callback for when an existing row is modified. | +| [`Table.removeOnUpdate`](#table-removeonupdate) | Unregister a previously-registered [`onUpdate`](#table-onupdate) callback. | +| [`Table.onDelete`](#table-ondelete) | Register an `onDelete` callback for when a subscribed row is removed from the database. | +| [`Table.removeOnDelete`](#table-removeondelete) | Unregister a previously-registered [`onDelete`](#table-removeondelete) callback. | ## Properties @@ -596,14 +608,17 @@ Return all the subscribed rows in the table. #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.all()); // Prints all the `Person` rows in the database. - }, 5000); + setTimeout(() => { + console.log(Person.all()); // Prints all the `Person` rows in the database. + }, 5000); }); ``` @@ -624,14 +639,17 @@ Return the number of subscribed rows in the table, or 0 if there is no active co #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.count()); - }, 5000); + setTimeout(() => { + console.log(Person.count()); + }, 5000); }); ``` @@ -660,14 +678,17 @@ These methods are named `filterBy{COLUMN}`, where `{COLUMN}` is the column name #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(...Person.filterByName('John')); // prints all the `Person` rows named John. - }, 5000); + setTimeout(() => { + console.log(...Person.filterByName('John')); // prints all the `Person` rows named John. + }, 5000); }); ``` @@ -696,14 +717,17 @@ These methods are named `findBy{COLUMN}`, where `{COLUMN}` is the column name co #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); - setTimeout(() => { - console.log(Person.findById(0)); // prints a `Person` row with id 0. - }, 5000); + setTimeout(() => { + console.log(Person.findById(0)); // prints a `Person` row with id 0. + }, 5000); }); ``` @@ -762,17 +786,20 @@ Register an `onInsert` callback for when a subscribed row is newly inserted into #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onInsert((person, reducerEvent) => { - if (reducerEvent) { - console.log('New person inserted by reducer', reducerEvent, person); - } else { - console.log('New person received during subscription update', person); - } + if (reducerEvent) { + console.log('New person inserted by reducer', reducerEvent, person); + } else { + console.log('New person received during subscription update', person); + } }); ``` @@ -813,13 +840,16 @@ Register an `onUpdate` callback to run when an existing row is modified by prima #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onUpdate((oldPerson, newPerson, reducerEvent) => { - console.log('Person updated by reducer', reducerEvent, oldPerson, newPerson); + console.log('Person updated by reducer', reducerEvent, oldPerson, newPerson); }); ``` @@ -858,17 +888,23 @@ Register an `onDelete` callback for when a subscribed row is removed from the da #### Example ```ts -var spacetimeDBClient = new SpacetimeDBClient('ws://localhost:3000', 'database_name'); +var spacetimeDBClient = new SpacetimeDBClient( + 'ws://localhost:3000', + 'database_name' +); spacetimeDBClient.onConnect((token, identity, address) => { - spacetimeDBClient.subscribe(['SELECT * FROM Person']); + spacetimeDBClient.subscribe(['SELECT * FROM Person']); }); Person.onDelete((person, reducerEvent) => { - if (reducerEvent) { - console.log('Person deleted by reducer', reducerEvent, person); - } else { - console.log('Person no longer subscribed during subscription update', person); - } + if (reducerEvent) { + console.log('Person deleted by reducer', reducerEvent, person); + } else { + console.log( + 'Person no longer subscribed during subscription update', + person + ); + } }); ``` @@ -941,6 +977,6 @@ Clients will only be notified of reducer runs if either of two criteria is met: ```ts SayHelloReducer.on((reducerEvent, ...reducerArgs) => { - console.log('SayHelloReducer called', reducerEvent, reducerArgs); + console.log('SayHelloReducer called', reducerEvent, reducerArgs); }); ``` diff --git a/docs/sdks/typescript/quickstart.md b/docs/sdks/typescript/quickstart.md index 46b758ea..96725cbd 100644 --- a/docs/sdks/typescript/quickstart.md +++ b/docs/sdks/typescript/quickstart.md @@ -168,12 +168,16 @@ module_bindings We need to import these types into our `client/src/App.tsx`. While we are at it, we will also import the SpacetimeDBClient class from our SDK. In order to let the SDK know what tables and reducers we will be using we need to also register them. ```typescript -import { SpacetimeDBClient, Identity, Address } from "@clockworklabs/spacetimedb-sdk"; +import { + SpacetimeDBClient, + Identity, + Address, +} from '@clockworklabs/spacetimedb-sdk'; -import Message from "./module_bindings/message"; -import User from "./module_bindings/user"; -import SendMessageReducer from "./module_bindings/send_message_reducer"; -import SetNameReducer from "./module_bindings/set_name_reducer"; +import Message from './module_bindings/message'; +import User from './module_bindings/user'; +import SendMessageReducer from './module_bindings/send_message_reducer'; +import SetNameReducer from './module_bindings/set_name_reducer'; SpacetimeDBClient.registerReducers(SendMessageReducer, SetNameReducer); SpacetimeDBClient.registerTables(Message, User); @@ -190,10 +194,10 @@ Replace `` with the name you chose when publishing your module duri Add this before the `App` function declaration: ```typescript -let token = localStorage.getItem("auth_token") || undefined; +let token = localStorage.getItem('auth_token') || undefined; var spacetimeDBClient = new SpacetimeDBClient( - "ws://localhost:3000", - "chat", + 'ws://localhost:3000', + 'chat', token ); ``` @@ -241,13 +245,13 @@ To the body of `App`, add: ```typescript client.current.onConnect((token, identity, address) => { - console.log("Connected to SpacetimeDB"); + console.log('Connected to SpacetimeDB'); local_identity.current = identity; - localStorage.setItem("auth_token", token); + localStorage.setItem('auth_token', token); - client.current.subscribe(["SELECT * FROM User", "SELECT * FROM Message"]); + client.current.subscribe(['SELECT * FROM User', 'SELECT * FROM Message']); }); ``` @@ -269,7 +273,7 @@ To the body of `App`, add: function userNameOrIdentity(user: User): string { console.log(`Name: ${user.name} `); if (user.name !== null) { - return user.name || ""; + return user.name || ''; } else { var identityStr = new Identity(user.identity).toHexString(); console.log(`Name: ${identityStr} `); @@ -281,11 +285,11 @@ function setAllMessagesInOrder() { let messages = Array.from(Message.all()); messages.sort((a, b) => (a.sent > b.sent ? 1 : a.sent < b.sent ? -1 : 0)); - let messagesType: MessageType[] = messages.map((message) => { + let messagesType: MessageType[] = messages.map(message => { let sender_identity = User.findByIdentity(message.sender); let display_name = sender_identity ? userNameOrIdentity(sender_identity) - : "unknown"; + : 'unknown'; return { name: display_name, @@ -296,7 +300,7 @@ function setAllMessagesInOrder() { setMessages(messagesType); } -client.current.on("initialStateSync", () => { +client.current.on('initialStateSync', () => { setAllMessagesInOrder(); var user = User.findByIdentity(local_identity?.current?.toUint8Array()!); setName(userNameOrIdentity(user!)); @@ -337,7 +341,7 @@ To the body of `App`, add: ```typescript // Helper function to append a line to the systemMessage state function appendToSystemMessage(line: String) { - setSystemMessage((prevMessage) => prevMessage + "\n" + line); + setSystemMessage(prevMessage => prevMessage + '\n' + line); } User.onInsert((user, reducerEvent) => { @@ -416,9 +420,9 @@ SetNameReducer.on((reducerEvent, newName) => { local_identity.current && reducerEvent.callerIdentity.isEqual(local_identity.current) ) { - if (reducerEvent.status === "failed") { + if (reducerEvent.status === 'failed') { appendToSystemMessage(`Error setting name: ${reducerEvent.message} `); - } else if (reducerEvent.status === "committed") { + } else if (reducerEvent.status === 'committed') { setName(newName); } } @@ -437,7 +441,7 @@ SendMessageReducer.on((reducerEvent, newMessage) => { local_identity.current && reducerEvent.callerIdentity.isEqual(local_identity.current) ) { - if (reducerEvent.status === "failed") { + if (reducerEvent.status === 'failed') { appendToSystemMessage(`Error sending message: ${reducerEvent.message} `); } } diff --git a/docs/unity/part-1.md b/docs/unity/part-1.md index 14eb2405..8e0a49e3 100644 --- a/docs/unity/part-1.md +++ b/docs/unity/part-1.md @@ -119,5 +119,5 @@ We chose ECS for this example project because it promotes scalability, modularit From here, the tutorial continues with your favorite server module language of choice: - - [Rust](part-2a-rust.md) - - [C#](part-2b-csharp.md) +- [Rust](part-2a-rust.md) +- [C#](part-2b-csharp.md) diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index 5be1c7cb..fa02d866 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -327,6 +327,7 @@ public static void SendChatMessage(ReducerContext ctx, string text) ## Wrapping Up ### Publishing a Module to SpacetimeDB + 💡View the [entire lib.cs file](https://gist.github.com/dylanh724/68067b4e843ea6e99fbd297fe1a87c49) Now that we've written the code for our server module and reached a clean checkpoint, we need to publish it to SpacetimeDB. This will create the database and call the init reducer. In your terminal or command window, run the following commands. diff --git a/docs/unity/part-4.md b/docs/unity/part-4.md index d7c22280..029fbe13 100644 --- a/docs/unity/part-4.md +++ b/docs/unity/part-4.md @@ -162,7 +162,6 @@ pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentSche } ``` - 2. Since this reducer uses `rand::Rng` we need add include it. Add this `use` statement to the top of lib.rs. ```rust @@ -179,6 +178,7 @@ use rand::Rng; scheduled_at: duration!(1000ms).into() }).expect(); ``` + struct ResouceSpawnAgentSchedueler { 4. Next we need to generate our client code and publish the module. Since we changed the schema we need to make sure we include the `--clear-database` flag. Run the following commands from your Server directory: diff --git a/docs/ws/index.md b/docs/ws/index.md index b00bfa56..587fbad0 100644 --- a/docs/ws/index.md +++ b/docs/ws/index.md @@ -188,7 +188,7 @@ Each `SubscriptionUpdate` contains a `TableUpdate` for each table with subscribe | `tableRowOperations` | A `TableRowOperation` for each inserted or deleted row. | | `TableRowOperation` field | Value | -|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | `INSERT` for inserted rows during a [`TransactionUpdate`](#transactionupdate) or rows resident upon applying a subscription; `DELETE` for deleted rows during a [`TransactionUpdate`](#transactionupdate). | | `row` | The altered row, encoded as a BSATN `ProductValue`. | @@ -225,7 +225,7 @@ Each `SubscriptionUpdate` contains a `TableUpdate` for each table with subscribe | `table_row_operations` | A `TableRowOperation` for each inserted or deleted row. | | `TableRowOperation` field | Value | -|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | `"insert"` for inserted rows during a [`TransactionUpdate`](#transactionupdate) or rows resident upon applying a subscription; `"delete"` for deleted rows during a [`TransactionUpdate`](#transactionupdate). | | `row` | The altered row, encoded as a JSON array. | diff --git a/nav.ts b/nav.ts index 8b21cc91..19e69c76 100644 --- a/nav.ts +++ b/nav.ts @@ -3,7 +3,7 @@ type Nav = { }; type NavItem = NavPage | NavSection; type NavPage = { - type: "page"; + type: 'page'; path: string; slug: string; title: string; @@ -11,71 +11,96 @@ type NavPage = { href?: string; }; type NavSection = { - type: "section"; + type: 'section'; title: string; }; -function page(title: string, slug: string, path: string, props?: { disabled?: boolean; href?: string; description?: string }): NavPage { - return { type: "page", path, slug, title, ...props }; +function page( + title: string, + slug: string, + path: string, + props?: { disabled?: boolean; href?: string; description?: string } +): NavPage { + return { type: 'page', path, slug, title, ...props }; } function section(title: string): NavSection { - return { type: "section", title }; + return { type: 'section', title }; } const nav: Nav = { items: [ - section("Intro"), - page("Overview", "index", "index.md"), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page("Getting Started", "getting-started", "getting-started.md"), + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), - section("Deploying"), - page("Testnet", "deploying/testnet", "deploying/testnet.md"), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - section("Unity Tutorial - Basic Multiplayer"), - page("Overview", "unity-tutorial", "unity/index.md"), - page("1 - Setup", "unity/part-1", "unity/part-1.md"), - page("2a - Server (Rust)", "unity/part-2a-rust", "unity/part-2a-rust.md"), - page("2b - Server (C#)", "unity/part-2b-c-sharp", "unity/part-2b-c-sharp.md"), - page("3 - Client", "unity/part-3", "unity/part-3.md"), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), + page( + '2b - Server (C#)', + 'unity/part-2b-c-sharp', + 'unity/part-2b-c-sharp.md' + ), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), - section("Unity Tutorial - Advanced"), - page("4 - Resources And Scheduling", "unity/part-4", "unity/part-4.md"), - page("5 - BitCraft Mini", "unity/part-5", "unity/part-5.md"), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), - section("Server Module Languages"), - page("Overview", "modules", "modules/index.md"), - page("Rust Quickstart", "modules/rust/quickstart", "modules/rust/quickstart.md"), - page("Rust Reference", "modules/rust", "modules/rust/index.md"), - page("C# Quickstart", "modules/c-sharp/quickstart", "modules/c-sharp/quickstart.md"), - page("C# Reference", "modules/c-sharp", "modules/c-sharp/index.md"), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page( + 'Rust Quickstart', + 'modules/rust/quickstart', + 'modules/rust/quickstart.md' + ), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page( + 'C# Quickstart', + 'modules/c-sharp/quickstart', + 'modules/c-sharp/quickstart.md' + ), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), - section("Client SDK Languages"), - page("Overview", "sdks", "sdks/index.md"), - page("Typescript Quickstart", "sdks/typescript/quickstart", "sdks/typescript/quickstart.md"), - page("Typescript Reference", "sdks/typescript", "sdks/typescript/index.md"), - page("Rust Quickstart", "sdks/rust/quickstart", "sdks/rust/quickstart.md"), - page("Rust Reference", "sdks/rust", "sdks/rust/index.md"), - page("C# Quickstart", "sdks/c-sharp/quickstart", "sdks/c-sharp/quickstart.md"), - page("C# Reference", "sdks/c-sharp", "sdks/c-sharp/index.md"), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page( + 'Typescript Quickstart', + 'sdks/typescript/quickstart', + 'sdks/typescript/quickstart.md' + ), + page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), + page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page( + 'C# Quickstart', + 'sdks/c-sharp/quickstart', + 'sdks/c-sharp/quickstart.md' + ), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), - section("WebAssembly ABI"), - page("Module ABI Reference", "webassembly-abi", "webassembly-abi/index.md"), + section('WebAssembly ABI'), + page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), - section("HTTP API"), - page("HTTP", "http", "http/index.md"), - page("`/identity`", "http/identity", "http/identity.md"), - page("`/database`", "http/database", "http/database.md"), - page("`/energy`", "http/energy", "http/energy.md"), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), - section("WebSocket API Reference"), - page("WebSocket", "ws", "ws/index.md"), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), - section("Data Format"), - page("SATN", "satn", "satn.md"), - page("BSATN", "bsatn", "bsatn.md"), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), - section("SQL"), - page("SQL Reference", "sql", "sql/index.md"), + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md'), ], }; diff --git a/package.json b/package.json index a56ea4e8..2c2b9445 100644 --- a/package.json +++ b/package.json @@ -12,4 +12,4 @@ }, "author": "Clockwork Labs", "license": "ISC" -} \ No newline at end of file +} From 5dce9de00656aae13581062656d50d2dbf286ac8 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 24 Sep 2024 11:33:31 -0400 Subject: [PATCH 63/80] Update quickstart.md (#84) --- docs/modules/c-sharp/quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 768602e4..5d8c873d 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -281,10 +281,10 @@ npm i wasm-opt -g You can use the CLI (command line interface) to run reducers. The arguments to the reducer are passed in JSON format. ```bash -spacetime call send_message "Hello, World!" +spacetime call SendMessage "Hello, World!" ``` -Once we've called our `send_message` reducer, we can check to make sure it ran by running the `logs` command. +Once we've called our `SendMessage` reducer, we can check to make sure it ran by running the `logs` command. ```bash spacetime logs From 0c0aa6dff110bc327165493b30ae335fcf7eea29 Mon Sep 17 00:00:00 2001 From: Egor Gavrilov Date: Tue, 24 Sep 2024 23:34:20 +0800 Subject: [PATCH 64/80] Fix typo in modules/rust/index.md (#83) Person -> Unique (because that belongs to `Unique` table) --- docs/modules/rust/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 55ceec18..28be1c83 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -314,7 +314,7 @@ struct Ordinary { } ``` -This table has a unique column. Every row in the `Person` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. +This table has a unique column. Every row in the `Unique` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust #[spacetimedb(table(public))] From 3385ec789a09b953d25827e14754c3624562f18d Mon Sep 17 00:00:00 2001 From: Arrel Neumiller Date: Tue, 24 Sep 2024 16:35:13 +0100 Subject: [PATCH 65/80] Update part-2b-c-sharp.md (#75) The intent is to throw an exception if the player already exists, not the other way 'round. --- docs/unity/part-2b-c-sharp.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/unity/part-2b-c-sharp.md b/docs/unity/part-2b-c-sharp.md index fa02d866..b1d50e8b 100644 --- a/docs/unity/part-2b-c-sharp.md +++ b/docs/unity/part-2b-c-sharp.md @@ -113,12 +113,11 @@ public static void CreatePlayer(ReducerContext ctx, string username) // Get the Identity of the client who called this reducer Identity sender = ctx.Sender; - // Make sure we don't already have a player with this identity - PlayerComponent? user = PlayerComponent.FindByIdentity(sender); - if (user is null) - { - throw new ArgumentException("Player already exists"); - } + PlayerComponent? existingPlayer = PlayerComponent.FindByIdentity(sender); + if (existingPlayer != null) + { + throw new InvalidOperationException($"Player already exists for identity: {sender}"); + } // Create a new entity for this player try From 082bfcb66eba4e3c0d6201e4ee2f80064ccd9fef Mon Sep 17 00:00:00 2001 From: Muthsera Date: Tue, 24 Sep 2024 17:54:25 +0200 Subject: [PATCH 66/80] Fixed code examples in rust reference regarding insertion (#42) --- docs/modules/rust/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 28be1c83..443f8171 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -372,10 +372,10 @@ If we insert two rows which have the same value of a unique column, the second w ```rust #[spacetimedb(reducer)] fn insert_unique(value: u64) { - let result = Ordinary::insert(Unique { unique_field: value }); + let result = Unique::insert(Unique { unique_field: value }); assert!(result.is_ok()); - let result = Ordinary::insert(Unique { unique_field: value }); + let result = Unique::insert(Unique { unique_field: value }); assert!(result.is_err()); } ``` @@ -404,7 +404,7 @@ fn insert_id() { // There's no collision and silent failure to insert, // because the value of the field is ignored and overwritten // with the automatically incremented value. - Identity::insert(Identity { autoinc_field: 23 }) + Identity::insert(Identity { id_field: 23 }) } } ``` From 2533e566fc8fb35e31eacdcde403046c172cbc02 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:59:09 -0500 Subject: [PATCH 67/80] Rust client quickstart updated for 0.12 (#92) * Rust client updated for 0.12 * Small update * More updates * Final pass --------- Co-authored-by: John Detter --- docs/sdks/rust/quickstart.md | 299 +++++++++++++++++------------------ 1 file changed, 148 insertions(+), 151 deletions(-) diff --git a/docs/sdks/rust/quickstart.md b/docs/sdks/rust/quickstart.md index af07e403..9cea42c3 100644 --- a/docs/sdks/rust/quickstart.md +++ b/docs/sdks/rust/quickstart.md @@ -28,7 +28,7 @@ cargo new client Below the `[dependencies]` line in `client/Cargo.toml`, add: ```toml -spacetimedb-sdk = "0.7" +spacetimedb-sdk = "0.12" hex = "0.4" ``` @@ -56,18 +56,20 @@ mkdir -p client/src/module_bindings spacetime generate --lang rust --out-dir client/src/module_bindings --project-path server ``` -Take a look inside `client/src/module_bindings`. The CLI should have generated five files: +Take a look inside `client/src/module_bindings`. The CLI should have generated a few files: ``` module_bindings -├── message.rs +├── message_table.rs +├── message_type.rs ├── mod.rs ├── send_message_reducer.rs ├── set_name_reducer.rs -└── user.rs +├── user_table.rs +└── user_type.rs ``` -We need to declare the module in our client crate, and we'll want to import its definitions. +To use these, we'll declare the module in our client crate and import its definitions. To `client/src/main.rs`, add: @@ -78,123 +80,133 @@ use module_bindings::*; ## Add more imports -We'll need a whole boatload of imports from `spacetimedb_sdk`, which we'll describe when we use them. +We'll need additional imports from `spacetimedb_sdk` for interacting with the database, handling credentials, and managing events. To `client/src/main.rs`, add: ```rust -use spacetimedb_sdk::{ - Address, - disconnect, - identity::{load_credentials, once_on_connect, save_credentials, Credentials, Identity}, - on_disconnect, on_subscription_applied, - reducer::Status, - subscribe, - table::{TableType, TableWithPrimaryKey}, -}; +use spacetimedb_sdk::{anyhow, DbContext, Event, Identity, Status, Table, TableWithPrimaryKey}; +use spacetimedb_sdk::credentials::File; ``` -## Define main function +## Define the main function -We'll work outside-in, first defining our `main` function at a high level, then implementing each behavior it needs. We need `main` to do five things: +Our `main` function will do the following: +1. Connect to the database. This will also start a new thread for handling network messages. +2. Handle user input from the command line. -1. Register callbacks on any events we want to handle. These will print to standard output messages received from the database and updates about users' names and online statuses. -2. Establish a connection to the database. This will involve authenticating with our credentials, if we're a returning user. -3. Subscribe to receive updates on tables. -4. Loop, processing user input from standard input. This will be how we enable users to set their names and send messages. -5. Close our connection. This one is easy; we just call `spacetimedb_sdk::disconnect`. - -To `client/src/main.rs`, add: +We'll see the implementation of these functions a bit later, but for now add to `client/src/main.rs`: ```rust fn main() { - register_callbacks(); - connect_to_db(); - subscribe_to_tables(); - user_input_loop(); + // Connect to the database + let conn = connect_to_db(); + // Handle CLI input + user_input_loop(&conn); } ``` + ## Register callbacks We need to handle several sorts of events: 1. When we connect and receive our credentials, we'll save them to a file so that the next time we connect, we can re-authenticate as the same user. -2. When a new user joins, we'll print a message introducing them. -3. When a user is updated, we'll print their new name, or declare their new online status. -4. When we receive a new message, we'll print it. -5. When we're informed of the backlog of past messages, we'll sort them and print them in order. -6. If the server rejects our attempt to set our name, we'll print an error. -7. If the server rejects a message we send, we'll print an error. +2. When a new user joins, we'll print a message introducing them. +3. When a user is updated, we'll print their new name, or declare their new online status. +4. When we receive a new message, we'll print it. +5. When we're informed of the backlog of past messages, we'll sort them and print them in order. +6. If the server rejects our attempt to set our name, we'll print an error. +7. If the server rejects a message we send, we'll print an error. 8. When our connection ends, we'll print a note, then exit the process. To `client/src/main.rs`, add: ```rust /// Register all the callbacks our app will use to respond to database events. -fn register_callbacks() { - // When we receive our `Credentials`, save them to a file. - once_on_connect(on_connected); - +fn register_callbacks(conn: &DbConnection) { // When a new user joins, print a notification. - User::on_insert(on_user_inserted); + conn.db.user().on_insert(on_user_inserted); // When a user's status changes, print a notification. - User::on_update(on_user_updated); + conn.db.user().on_update(on_user_updated); // When a new message is received, print it. - Message::on_insert(on_message_inserted); + conn.db.message().on_insert(on_message_inserted); // When we receive the message backlog, print it in timestamp order. - on_subscription_applied(on_sub_applied); + conn.subscription_builder().on_applied(on_sub_applied); // When we fail to set our name, print a warning. - on_set_name(on_name_set); + conn.reducers.on_set_name(on_name_set); // When we fail to send a message, print a warning. - on_send_message(on_message_sent); - - // When our connection closes, inform the user and exit. - on_disconnect(on_disconnected); + conn.reducers.on_send_message(on_message_sent); } ``` -### Save credentials +## Save credentials -Each user has a `Credentials`, which consists of two parts: +Each user has a `Credentials`, which consists of two parts: - An `Identity`, a unique public identifier. We're using these to identify `User` rows. -- A `Token`, a private key which SpacetimeDB uses to authenticate the client. - -`Credentials` are generated by SpacetimeDB each time a new client connects, and sent to the client so they can be saved, in order to re-connect with the same identity. The Rust SDK provides a pair of functions, `save_credentials` and `load_credentials`, for storing these credentials in a file. We'll save our credentials into a file in the directory `~/.spacetime_chat`, which should be unintrusive. If saving our credentials fails, we'll print a message to standard error, but otherwise continue normally; even though the user won't be able to reconnect with the same identity, they can still chat normally. +- A `Token`, a private key which SpacetimeDB uses to authenticate the client. -Each client also has an `Address`, which modules can use to distinguish multiple concurrent connections by the same `Identity`. We don't need to know our `Address`, so we'll ignore that argument. +`Credentials` are generated by SpacetimeDB each time a new client connects, and sent to the client so they can be saved, in order to re-connect with the same identity. The Rust SDK provides a pair of functions in `File`, `save` and `load`, for saving and storing these credentials in a file. By default the `save` and `load` will look for credentials in the `$HOME/.spacetimedb_client_credentials/` directory, which should be unintrusive. If saving our credentials fails, we'll print a message to standard error, but otherwise continue normally; even though the user won't be able to reconnect with the same identity, they can still chat normally. To `client/src/main.rs`, add: ```rust /// Our `on_connect` callback: save our credentials to a file. -fn on_connected(creds: &Credentials, _client_address: Address) { - if let Err(e) = save_credentials(CREDS_DIR, creds) { +fn on_connected(conn: &DbConnection, ident: Identity, token: &str) { + let file = File::new(CREDS_NAME); + if let Err(e) = file.save(ident, token) { eprintln!("Failed to save credentials: {:?}", e); } + + println!("Connected to SpacetimeDB."); + println!("Use /name to set your username, otherwise enter your message!"); + + // Subscribe to the data we care about + subscribe_to_tables(&conn); + // Register callbacks for reducers + register_callbacks(&conn); +} +``` + +You can see here that when we connect we're going to register our callbacks, which we defined above. + +## Handle errors and disconnections + +We need to handle connection errors and disconnections by printing appropriate messages and exiting the program. + +To `client/src/main.rs`, add: + +```rust +/// Our `on_connect_error` callback: print the error, then exit the process. +fn on_connect_error(err: &anyhow::Error) { + eprintln!("Connection error: {:?}", err); } -const CREDS_DIR: &str = ".spacetime_chat"; +/// Our `on_disconnect` callback: print a note, then exit the process. +fn on_disconnected(_conn: &DbConnection, _err: Option<&anyhow::Error>) { + eprintln!("Disconnected!"); + std::process::exit(0) +} ``` -### Notify about new users +## Notify about new users -For each table, we can register on-insert and on-delete callbacks to be run whenever a subscribed row is inserted or deleted. We register these callbacks using the `on_insert` and `on_delete` methods of the trait `TableType`, which is automatically implemented for each table by `spacetime generate`. +For each table, we can register on-insert and on-delete callbacks to be run whenever a subscribed row is inserted or deleted. We register these callbacks using the `on_insert` and `on_delete`, which is automatically implemented for each table by `spacetime generate`. These callbacks can fire in two contexts: - After a reducer runs, when the client's cache is updated about changes to subscribed rows. - After calling `subscribe`, when the client's cache is initialized with all existing matching rows. -This second case means that, even though the module only ever inserts online users, the client's `User::on_insert` callbacks may be invoked with users who are offline. We'll only notify about online users. +This second case means that, even though the module only ever inserts online users, the client's `conn.db.user().on_insert(..)` callbacks may be invoked with users who are offline. We'll only notify about online users. -`on_insert` and `on_delete` callbacks take two arguments: the altered row, and an `Option<&ReducerEvent>`. This will be `Some` for rows altered by a reducer run, and `None` for rows inserted when initializing the cache for a subscription. `ReducerEvent` is an enum autogenerated by `spacetime generate` with a variant for each reducer defined by the module. For now, we can ignore this argument. +`on_insert` and `on_delete` callbacks take two arguments: `&EventContext` and the row data (in the case of insert it's a new row and in the case of delete it's the row that was deleted). You can determine whether the insert/delete operation was caused by a reducer or subscription update by checking the type of `ctx.event`. If `ctx.event` is a `Event::Reducer` then the row was changed by a reducer call, otherwise it was modified by a subscription update. `Reducer` is an enum autogenerated by `spacetime generate` with a variant for each reducer defined by the module. For now, we can ignore this argument. Whenever we want to print a user, if they have set a name, we'll use that. If they haven't set a name, we'll instead print the first 8 bytes of their identity, encoded as hexadecimal. We'll define functions `user_name_or_identity` and `identity_leading_hex` to handle this. @@ -203,7 +215,7 @@ To `client/src/main.rs`, add: ```rust /// Our `User::on_insert` callback: /// if the user is online, print a notification. -fn on_user_inserted(user: &User, _: Option<&ReducerEvent>) { +fn on_user_inserted(_ctx: &EventContext, user: &User) { if user.online { println!("User {} connected.", user_name_or_identity(user)); } @@ -212,17 +224,13 @@ fn on_user_inserted(user: &User, _: Option<&ReducerEvent>) { fn user_name_or_identity(user: &User) -> String { user.name .clone() - .unwrap_or_else(|| identity_leading_hex(&user.identity)) -} - -fn identity_leading_hex(id: &Identity) -> String { - hex::encode(&id.bytes()[0..8]) + .unwrap_or_else(|| user.identity.to_hex().to_string()) } ``` ### Notify about updated users -Because we declared a `#[primarykey]` column in our `User` table, we can also register on-update callbacks. These run whenever a row is replaced by a row with the same primary key, like our module's `User::update_by_identity` calls. We register these callbacks using the `on_update` method of the trait `TableWithPrimaryKey`, which is automatically implemented by `spacetime generate` for any table with a `#[primarykey]` column. +Because we declared a `#[primary_key]` column in our `User` table, we can also register on-update callbacks. These run whenever a row is replaced by a row with the same primary key, like our module's `ctx.db.user().identity().update(..) calls. We register these callbacks using the `on_update` method of the trait `TableWithPrimaryKey`, which is automatically implemented by `spacetime generate` for any table with a `#[primary_key]` column. `on_update` callbacks take three arguments: the old row, the new row, and an `Option<&ReducerEvent>`. @@ -256,119 +264,96 @@ fn on_user_updated(old: &User, new: &User, _: Option<&ReducerEvent>) { } ``` -### Print messages +## Print messages -When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `print_new_message` callback will check if its `reducer_event` argument is `Some`, and only print in that case. +When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `on_message_inserted` callback will check if the ctx.event type is an `Event::Reducer`, and only print in that case. -To find the `User` based on the message's `sender` identity, we'll use `User::find_by_identity`, which behaves like the same function on the server. The key difference is that, unlike on the module side, the client's `find_by_identity` accepts an owned `Identity`, rather than a reference. We can `clone` the identity held in `message.sender`. +To find the `User` based on the message's `sender` identity, we'll use `ctx.db.user().identity().find(..)`, which behaves like the same function on the server. We'll print the user's name or identity in the same way as we did when notifying about `User` table events, but here we have to handle the case where we don't find a matching `User` row. This can happen when the module owner sends a message using the CLI's `spacetime call`. In this case, we'll print `unknown`. +We'll handle message-related events, such as receiving new messages or loading past messages. + To `client/src/main.rs`, add: ```rust /// Our `Message::on_insert` callback: print new messages. -fn on_message_inserted(message: &Message, reducer_event: Option<&ReducerEvent>) { - if reducer_event.is_some() { - print_message(message); +fn on_message_inserted(ctx: &EventContext, message: &Message) { + if let Event::Reducer(_) = ctx.event { + print_message(ctx, message) } } -fn print_message(message: &Message) { - let sender = User::find_by_identity(message.sender.clone()) +fn print_message(ctx: &EventContext, message: &Message) { + let sender = ctx.db.user().identity().find(&message.sender.clone()) .map(|u| user_name_or_identity(&u)) .unwrap_or_else(|| "unknown".to_string()); println!("{}: {}", sender, message.text); } -``` -### Print past messages in order +### Print past messages in order + +Messages we receive live will come in order, but when we connect, we'll receive all the past messages at once. We can't just print these in the order we receive them; the logs would be all shuffled around, and would make no sense. Instead, when we receive the log of past messages, we'll sort them by their sent timestamps and print them in order. -Messages we receive live will come in order, but when we connect, we'll receive all the past messages at once. We can't just print these in the order we receive them; the logs would be all shuffled around, and would make no sense. Instead, when we receive the log of past messages, we'll sort them by their sent timestamps and print them in order. -We'll handle this in our function `print_messages_in_order`, which we registered as an `on_subscription_applied` callback. `print_messages_in_order` iterates over all the `Message`s we've received, sorts them, and then prints them. `Message::iter()` is defined on the trait `TableType`, and returns an iterator over all the messages in the client's cache. Rust iterators can't be sorted in-place, so we'll collect it to a `Vec`, then use the `sort_by_key` method to sort by timestamp. +We'll handle this in our function `print_messages_in_order`, which we registered as an `on_subscription_applied` callback. `print_messages_in_order` iterates over all the `Message`s we've received, sorts them, and then prints them. `Message::iter()` is defined on the trait `TableType`, and returns an iterator over all the messages in the client's cache. Rust iterators can't be sorted in-place, so we'll collect it to a `Vec`, then use the `sort_by_key` method to sort by timestamp. To `client/src/main.rs`, add: ```rust /// Our `on_subscription_applied` callback: /// sort all past messages and print them in timestamp order. -fn on_sub_applied() { - let mut messages = Message::iter().collect::>(); +fn on_sub_applied(ctx: &EventContext) { + let mut messages = ctx.db.message().iter().collect::>(); messages.sort_by_key(|m| m.sent); for message in messages { - print_message(&message); + print_message(ctx, &message); } } ``` -### Warn if our name was rejected +## Handle reducer failures -We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `on_reducer` method of the `Reducer` trait, which is automatically implemented for each reducer by `spacetime generate`. +We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `on_reducer` method of the `Reducer` trait, which is automatically implemented for each reducer by `spacetime generate`. -Each reducer callback takes at least three arguments: - -1. The `Identity` of the client who requested the reducer invocation. -2. The `Address` of the client who requested the reducer invocation, which may be `None` for scheduled reducers. -3. The `Status` of the reducer run, one of `Committed`, `Failed` or `OutOfEnergy`. `Status::Failed` holds the error which caused the reducer to fail, as a `String`. - -In addition, it takes a reference to each of the arguments passed to the reducer itself. +Each reducer callback first takes an `&EventContext` which contains all of the information from the reducer call including the reducer arguments, the identity of the caller, and whether or not the reducer call suceeded. These callbacks will be invoked in one of two cases: 1. If the reducer was successful and altered any of our subscribed rows. -2. If we requested an invocation which failed. +2. If we requested an invocation which failed. -Note that a status of `Failed` or `OutOfEnergy` implies that the caller identity is our own identity. +Note that a status of `Failed` or `OutOfEnergy` implies that the caller identity is our own identity. + +We already handle successful `set_name` invocations using our `ctx.db.user().on_update(..)` callback, but if the module rejects a user's chosen name, we'd like that user's client to let them know. We define a function `on_set_name` as a `conn.reducers.on_set_name(..)` callback which checks if the reducer failed, and if it did, prints a message including the rejected name and the error. -We already handle successful `set_name` invocations using our `User::on_update` callback, but if the module rejects a user's chosen name, we'd like that user's client to let them know. We define a function `warn_if_name_rejected` as a `SetNameArgs::on_reducer` callback which checks if the reducer failed, and if it did, prints a message including the rejected name and the error. To `client/src/main.rs`, add: ```rust /// Our `on_set_name` callback: print a warning if the reducer failed. -fn on_name_set(_sender_id: &Identity, _sender_address: Option
, status: &Status, name: &String) { - if let Status::Failed(err) = status { - eprintln!("Failed to change name to {:?}: {}", name, err); +fn on_name_set(ctx: &EventContext, name: &String) { + if let Event::Reducer(reducer) = &ctx.event { + if let Status::Failed(err) = reducer.status.clone() { + eprintln!("Failed to change name to {:?}: {}", name, err); + } } } -``` - -### Warn if our message was rejected - -We handle warnings on rejected messages the same way as rejected names, though the types and the error message are different. -To `client/src/main.rs`, add: - -```rust /// Our `on_send_message` callback: print a warning if the reducer failed. -fn on_message_sent(_sender_id: &Identity, _sender_address: Option
, status: &Status, text: &String) { - if let Status::Failed(err) = status { - eprintln!("Failed to send message {:?}: {}", text, err); +fn on_message_sent(ctx: &EventContext, text: &String) { + if let Event::Reducer(reducer) = &ctx.event { + if let Status::Failed(err) = reducer.status.clone() { + eprintln!("Failed to send message {:?}: {}", text, err); + } } } ``` -### Exit on disconnect - -We can register callbacks to run when our connection ends using `on_disconnect`. These callbacks will run either when the client disconnects by calling `disconnect`, or when the server closes our connection. More involved apps might attempt to reconnect in this case, or do some sort of client-side cleanup, but we'll just print a note to the user and then exit the process. - -To `client/src/main.rs`, add: - -```rust -/// Our `on_disconnect` callback: print a note, then exit the process. -fn on_disconnected() { - eprintln!("Disconnected!"); - std::process::exit(0) -} -``` - ## Connect to the database Now that our callbacks are all set up, we can connect to the database. We'll store the URI of the SpacetimeDB instance and our module name in constants `SPACETIMEDB_URI` and `DB_NAME`. Replace `` with the name you chose when publishing your module during the module quickstart. -`connect` takes an `Option`, which is `None` for a new connection, or `Some` for a returning user. The Rust SDK defines `load_credentials`, the counterpart to the `save_credentials` we used in our `save_credentials_or_log_error`, to load `Credentials` from a file. `load_credentials` returns `Result>`, with `Ok(None)` meaning the credentials haven't been saved yet, and an `Err` meaning reading from disk failed. We can `expect` to handle the `Result`, and pass the `Option` directly to `connect`. - To `client/src/main.rs`, add: ```rust @@ -378,14 +363,22 @@ const SPACETIMEDB_URI: &str = "http://localhost:3000"; /// The module name we chose when we published our module. const DB_NAME: &str = ""; +/// You should change this value to a unique name based on your application. +const CREDS_NAME: &str = "rust-sdk-quickstart"; + /// Load credentials from a file and connect to the database. -fn connect_to_db() { - connect( - SPACETIMEDB_URI, - DB_NAME, - load_credentials(CREDS_DIR).expect("Error reading stored credentials"), - ) - .expect("Failed to connect"); +fn connect_to_db() -> DbConnection { + let credentials = File::new(CREDS_NAME); + let conn = DbConnection::builder() + .on_connect(on_connected) + .on_connect_error(on_connect_error) + .on_disconnect(on_disconnected) + .with_uri(SPACETIMEDB_URI) + .with_module_name(DB_NAME) + .with_credentials(credentials.load().unwrap()) + .build().expect("Failed to connect"); + conn.run_threaded(); + conn } ``` @@ -397,30 +390,33 @@ To `client/src/main.rs`, add: ```rust /// Register subscriptions for all rows of both tables. -fn subscribe_to_tables() { - subscribe(&["SELECT * FROM User;", "SELECT * FROM Message;"]).unwrap(); +fn subscribe_to_tables(conn: &DbConnection) { + conn.subscription_builder().subscribe([ + "SELECT * FROM user;", + "SELECT * FROM message;", + ]); } ``` ## Handle user input -A user should interact with our client by typing lines into their terminal. A line that starts with `/name ` will set the user's name to the rest of the line. Any other line will send a message. +Our app should allow the user to interact by typing lines into their terminal. If the line starts with `/name `, we'll change the user's name. Any other line will send a message. -`spacetime generate` defined two functions for us, `set_name` and `send_message`, which send a message to the database to invoke the corresponding reducer. The first argument, the `ReducerContext`, is supplied by the server, but we pass all other arguments ourselves. In our case, that means that both `set_name` and `send_message` take one argument, a `String`. +The functions `set_name` and `send_message` are generated from the server module via `spacetime generate`. We pass them a `String`, which gets sent to the server to execute the corresponding reducer. To `client/src/main.rs`, add: ```rust /// Read each line of standard input, and either set our name or send a message as appropriate. -fn user_input_loop() { +fn user_input_loop(conn: &DbConnection) { for line in std::io::stdin().lines() { let Ok(line) = line else { panic!("Failed to read from stdin."); }; if let Some(name) = line.strip_prefix("/name ") { - set_name(name.to_string()); + conn.reducers.set_name(name.to_string()).unwrap(); } else { - send_message(line); + conn.reducers.send_message(line).unwrap(); } } } @@ -428,7 +424,7 @@ fn user_input_loop() { ## Run it -Change your directory to the client app, then compile and run it. From the `quickstart-chat` directory, run: +After setting everything up, change your directory to the client app, then compile and run it. From the `quickstart-chat` directory, run: ```bash cd client @@ -441,25 +437,25 @@ You should see something like: User d9e25c51996dea2f connected. ``` -Now try sending a message. Type `Hello, world!` and press enter. You should see something like: +Now try sending a message by typing `Hello, world!` and pressing enter. You should see: ``` d9e25c51996dea2f: Hello, world! ``` -Next, set your name. Type `/name `, replacing `` with your name. You should see something like: +Next, set your name by typing `/name `, replacing `` with your desired username. You should see: ``` User d9e25c51996dea2f renamed to . ``` -Then send another message. Type `Hello after naming myself.` and press enter. You should see: +Then, send another message: ``` : Hello after naming myself. ``` -Now, close the app by hitting control-c, and start it again with `cargo run`. You should see yourself connecting, and your past messages in order: +Now, close the app by hitting `Ctrl+C`, and start it again with `cargo run`. You'll see yourself connecting, and your past messages will load in order: ``` User connected. @@ -473,15 +469,16 @@ You can find the full code for this client [in the Rust SDK's examples](https:// Check out the [Rust SDK Reference](/docs/sdks/rust) for a more comprehensive view of the SpacetimeDB Rust SDK. -Our bare-bones terminal interface has some quirks. Incoming messages can appear while the user is typing and be spliced into the middle of user input, which is less than ideal. Also, the user's input is interspersed with the program's output, so messages the user sends will seem to appear twice. Why not try building a better interface using [Rustyline](https://crates.io/crates/rustyline), [Cursive](https://crates.io/crates/cursive), or even a full-fledged GUI? We went for the Cursive route, and you can check out what we came up with [in the Rust SDK's examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/sdk/examples/cursive-chat). +Our basic terminal interface has some limitations. Incoming messages can appear while the user is typing, which is less than ideal. Additionally, the user's input gets mixed with the program's output, making messages the user sends appear twice. You might want to try improving the interface by using [Rustyline](https://crates.io/crates/rustyline), [Cursive](https://crates.io/crates/cursive), or even creating a full-fledged GUI. + +We've tried using Cursive for the interface, and you can check out our implementation in the [Rust SDK's examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/sdk/examples/cursive-chat). -Once our chat server runs for a while, messages will accumulate, and it will get frustrating to see the entire backlog each time you connect. Instead, you could refine your `Message` subscription query, subscribing only to messages newer than, say, half an hour before the user connected. +Once your chat server runs for a while, you might want to limit the messages your client loads by refining your `Message` subscription query, only subscribing to messages sent within the last half-hour. -You could also add support for styling messages, perhaps by interpreting HTML tags in the messages and printing appropriate [ANSI escapes](https://en.wikipedia.org/wiki/ANSI_escape_code). +You could also add features like: -Or, you could extend the module and the client together, perhaps: +- Styling messages by interpreting HTML tags and printing appropriate [ANSI escapes](https://en.wikipedia.org/wiki/ANSI_escape_code). +- Adding a `moderator` flag to the `User` table, allowing moderators to manage users (e.g., time-out, ban). +- Adding rooms or channels that users can join or leave. +- Supporting direct messages or displaying user statuses next to their usernames. -- Adding a `moderator: bool` flag to `User` and allowing moderators to time-out or ban naughty chatters. -- Adding a message of the day which gets shown to users whenever they connect, or some rules which get shown only to new users. -- Supporting separate rooms or channels which users can join or leave, and maybe even direct messages. -- Allowing users to set their status, which could be displayed alongside their username. From c3b7065a326ea3aa98cf2b0ffdf479561830b851 Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Wed, 2 Oct 2024 12:09:32 -0400 Subject: [PATCH 68/80] I didn't notice that auto-merge was enabled, so here's my review (#94) --- docs/sdks/rust/quickstart.md | 37 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/sdks/rust/quickstart.md b/docs/sdks/rust/quickstart.md index 9cea42c3..38d9dee7 100644 --- a/docs/sdks/rust/quickstart.md +++ b/docs/sdks/rust/quickstart.md @@ -112,12 +112,12 @@ fn main() { We need to handle several sorts of events: 1. When we connect and receive our credentials, we'll save them to a file so that the next time we connect, we can re-authenticate as the same user. -2. When a new user joins, we'll print a message introducing them. -3. When a user is updated, we'll print their new name, or declare their new online status. -4. When we receive a new message, we'll print it. -5. When we're informed of the backlog of past messages, we'll sort them and print them in order. -6. If the server rejects our attempt to set our name, we'll print an error. -7. If the server rejects a message we send, we'll print an error. +2. When a new user joins, we'll print a message introducing them. +3. When a user is updated, we'll print their new name, or declare their new online status. +4. When we receive a new message, we'll print it. +5. When we're informed of the backlog of past messages, we'll sort them and print them in order. +6. If the server rejects our attempt to set our name, we'll print an error. +7. If the server rejects a message we send, we'll print an error. 8. When our connection ends, we'll print a note, then exit the process. To `client/src/main.rs`, add: @@ -147,12 +147,12 @@ fn register_callbacks(conn: &DbConnection) { ## Save credentials -Each user has a `Credentials`, which consists of two parts: +Each user has a `Credentials`, which consists of two parts: - An `Identity`, a unique public identifier. We're using these to identify `User` rows. -- A `Token`, a private key which SpacetimeDB uses to authenticate the client. +- A `Token`, a private key which SpacetimeDB uses to authenticate the client. -`Credentials` are generated by SpacetimeDB each time a new client connects, and sent to the client so they can be saved, in order to re-connect with the same identity. The Rust SDK provides a pair of functions in `File`, `save` and `load`, for saving and storing these credentials in a file. By default the `save` and `load` will look for credentials in the `$HOME/.spacetimedb_client_credentials/` directory, which should be unintrusive. If saving our credentials fails, we'll print a message to standard error, but otherwise continue normally; even though the user won't be able to reconnect with the same identity, they can still chat normally. +`Credentials` are generated by SpacetimeDB each time a new client connects, and sent to the client so they can be saved, in order to re-connect with the same identity. The Rust SDK provides a pair of functions in `File`, `save` and `load`, for saving and storing these credentials in a file. By default the `save` and `load` will look for credentials in the `$HOME/.spacetimedb_client_credentials/` directory, which should be unintrusive. If saving our credentials fails, we'll print a message to standard error, but otherwise continue normally; even though the user won't be able to reconnect with the same identity, they can still chat normally. To `client/src/main.rs`, add: @@ -266,9 +266,9 @@ fn on_user_updated(old: &User, new: &User, _: Option<&ReducerEvent>) { ## Print messages -When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `on_message_inserted` callback will check if the ctx.event type is an `Event::Reducer`, and only print in that case. +When we receive a new message, we'll print it to standard output, along with the name of the user who sent it. Keep in mind that we only want to do this for new messages, i.e. those inserted by a `send_message` reducer invocation. We have to handle the backlog we receive when our subscription is initialized separately, to ensure they're printed in the correct order. To that effect, our `on_message_inserted` callback will check if the ctx.event type is an `Event::Reducer`, and only print in that case. -To find the `User` based on the message's `sender` identity, we'll use `ctx.db.user().identity().find(..)`, which behaves like the same function on the server. +To find the `User` based on the message's `sender` identity, we'll use `ctx.db.user().identity().find(..)`, which behaves like the same function on the server. We'll print the user's name or identity in the same way as we did when notifying about `User` table events, but here we have to handle the case where we don't find a matching `User` row. This can happen when the module owner sends a message using the CLI's `spacetime call`. In this case, we'll print `unknown`. @@ -291,12 +291,12 @@ fn print_message(ctx: &EventContext, message: &Message) { println!("{}: {}", sender, message.text); } -### Print past messages in order +### Print past messages in order -Messages we receive live will come in order, but when we connect, we'll receive all the past messages at once. We can't just print these in the order we receive them; the logs would be all shuffled around, and would make no sense. Instead, when we receive the log of past messages, we'll sort them by their sent timestamps and print them in order. +Messages we receive live will come in order, but when we connect, we'll receive all the past messages at once. We can't just print these in the order we receive them; the logs would be all shuffled around, and would make no sense. Instead, when we receive the log of past messages, we'll sort them by their sent timestamps and print them in order. -We'll handle this in our function `print_messages_in_order`, which we registered as an `on_subscription_applied` callback. `print_messages_in_order` iterates over all the `Message`s we've received, sorts them, and then prints them. `Message::iter()` is defined on the trait `TableType`, and returns an iterator over all the messages in the client's cache. Rust iterators can't be sorted in-place, so we'll collect it to a `Vec`, then use the `sort_by_key` method to sort by timestamp. +We'll handle this in our function `print_messages_in_order`, which we registered as an `on_subscription_applied` callback. `print_messages_in_order` iterates over all the `Message`s we've received, sorts them, and then prints them. `Message::iter()` is defined on the trait `TableType`, and returns an iterator over all the messages in the client's cache. Rust iterators can't be sorted in-place, so we'll collect it to a `Vec`, then use the `sort_by_key` method to sort by timestamp. To `client/src/main.rs`, add: @@ -314,16 +314,16 @@ fn on_sub_applied(ctx: &EventContext) { ## Handle reducer failures -We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `on_reducer` method of the `Reducer` trait, which is automatically implemented for each reducer by `spacetime generate`. +We can also register callbacks to run each time a reducer is invoked. We register these callbacks using the `on_reducer` method of the `Reducer` trait, which is automatically implemented for each reducer by `spacetime generate`. Each reducer callback first takes an `&EventContext` which contains all of the information from the reducer call including the reducer arguments, the identity of the caller, and whether or not the reducer call suceeded. These callbacks will be invoked in one of two cases: 1. If the reducer was successful and altered any of our subscribed rows. -2. If we requested an invocation which failed. +2. If we requested an invocation which failed. -Note that a status of `Failed` or `OutOfEnergy` implies that the caller identity is our own identity. +Note that a status of `Failed` or `OutOfEnergy` implies that the caller identity is our own identity. We already handle successful `set_name` invocations using our `ctx.db.user().on_update(..)` callback, but if the module rejects a user's chosen name, we'd like that user's client to let them know. We define a function `on_set_name` as a `conn.reducers.on_set_name(..)` callback which checks if the reducer failed, and if it did, prints a message including the rejected name and the error. @@ -471,8 +471,6 @@ Check out the [Rust SDK Reference](/docs/sdks/rust) for a more comprehensive vie Our basic terminal interface has some limitations. Incoming messages can appear while the user is typing, which is less than ideal. Additionally, the user's input gets mixed with the program's output, making messages the user sends appear twice. You might want to try improving the interface by using [Rustyline](https://crates.io/crates/rustyline), [Cursive](https://crates.io/crates/cursive), or even creating a full-fledged GUI. -We've tried using Cursive for the interface, and you can check out our implementation in the [Rust SDK's examples](https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/sdk/examples/cursive-chat). - Once your chat server runs for a while, you might want to limit the messages your client loads by refining your `Message` subscription query, only subscribing to messages sent within the last half-hour. You could also add features like: @@ -481,4 +479,3 @@ You could also add features like: - Adding a `moderator` flag to the `User` table, allowing moderators to manage users (e.g., time-out, ban). - Adding rooms or channels that users can join or leave. - Supporting direct messages or displaying user statuses next to their usernames. - From cb90180aa9cbd6ff676c2e97d4b1e6b195055b22 Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Wed, 2 Oct 2024 12:35:23 -0400 Subject: [PATCH 69/80] Update Rust SDK ref for the new SDK (#93) --- docs/sdks/rust/index.md | 1153 ++++++++------------------------------- 1 file changed, 221 insertions(+), 932 deletions(-) diff --git a/docs/sdks/rust/index.md b/docs/sdks/rust/index.md index 9c9e6f12..50e8aa9b 100644 --- a/docs/sdks/rust/index.md +++ b/docs/sdks/rust/index.md @@ -7,7 +7,7 @@ The SpacetimeDB client SDK for Rust contains all the tools you need to build nat First, create a new project using `cargo new` and add the SpacetimeDB SDK to your dependencies: ```bash -cargo add spacetimedb +cargo add spacetimedb_sdk ``` ## Generate module bindings @@ -29,1165 +29,454 @@ Declare a `mod` for the bindings in your client's `src/main.rs`: mod module_bindings; ``` -## API at a glance - -| Definition | Description | -| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | -| Function [`module_bindings::connect`](#function-connect) | Autogenerated function to connect to a database. | -| Function [`spacetimedb_sdk::disconnect`](#function-disconnect) | Close the active connection. | -| Function [`spacetimedb_sdk::on_disconnect`](#function-on_disconnect) | Register a `FnMut` callback to run when a connection ends. | -| Function [`spacetimedb_sdk::once_on_disconnect`](#function-once_on_disconnect) | Register a `FnOnce` callback to run the next time a connection ends. | -| Function [`spacetimedb_sdk::remove_on_disconnect`](#function-remove_on_disconnect) | Cancel an `on_disconnect` or `once_on_disconnect` callback. | -| Function [`spacetimedb_sdk::subscribe`](#function-subscribe) | Subscribe to queries with a `&[&str]`. | -| Function [`spacetimedb_sdk::subscribe_owned`](#function-subscribe_owned) | Subscribe to queries with a `Vec`. | -| Function [`spacetimedb_sdk::on_subscription_applied`](#function-on_subscription_applied) | Register a `FnMut` callback to run when a subscription's initial rows become available. | -| Function [`spacetimedb_sdk::once_on_subscription_applied`](#function-once_on_subscription_applied) | Register a `FnOnce` callback to run the next time a subscription's initial rows become available. | -| Function [`spacetimedb_sdk::remove_on_subscription_applied`](#function-remove_on_subscription_applied) | Cancel an `on_subscription_applied` or `once_on_subscription_applied` callback. | -| Type [`spacetimedb_sdk::identity::Identity`](#type-identity) | A unique public identifier for a client. | -| Type [`spacetimedb_sdk::identity::Token`](#type-token) | A private authentication token corresponding to an `Identity`. | -| Type [`spacetimedb_sdk::identity::Credentials`](#type-credentials) | An `Identity` paired with its `Token`. | -| Type [`spacetimedb_sdk::Address`](#type-address) | An opaque identifier for differentiating connections by the same `Identity`. | -| Function [`spacetimedb_sdk::identity::identity`](#function-identity) | Return the current connection's `Identity`. | -| Function [`spacetimedb_sdk::identity::token`](#function-token) | Return the current connection's `Token`. | -| Function [`spacetimedb_sdk::identity::credentials`](#function-credentials) | Return the current connection's [`Credentials`](#type-credentials). | -| Function [`spacetimedb_sdk::identity::address`](#function-address) | Return the current connection's [`Address`](#type-address). | -| Function [`spacetimedb_sdk::identity::on_connect`](#function-on_connect) | Register a `FnMut` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | -| Function [`spacetimedb_sdk::identity::once_on_connect`](#function-once_on_connect) | Register a `FnOnce` callback to run when the connection's [`Credentials`](#type-credentials) are verified with the database. | -| Function [`spacetimedb_sdk::identity::remove_on_connect`](#function-remove_on_connect) | Cancel an `on_connect` or `once_on_connect` callback. | -| Function [`spacetimedb_sdk::identity::load_credentials`](#function-load_credentials) | Load a saved [`Credentials`](#type-credentials) from a file. | -| Function [`spacetimedb_sdk::identity::save_credentials`](#function-save_credentials) | Save a [`Credentials`](#type-credentials) to a file. | -| Type [`module_bindings::{TABLE}`](#type-table) | Autogenerated `struct` type for a table, holding one row. | -| Method [`module_bindings::{TABLE}::filter_by_{COLUMN}`](#method-filter_by_column) | Autogenerated method to iterate over subscribed rows where a column matches a value. | -| Method [`module_bindings::{TABLE}::find_by_{COLUMN}`](#method-find_by_column) | Autogenerated method to seek a subscribed row where a unique column matches a value. | -| Trait [`spacetimedb_sdk::table::TableType`](#trait-tabletype) | Automatically implemented for all tables defined by a module. | -| Method [`spacetimedb_sdk::table::TableType::count`](#method-count) | Count the number of subscribed rows in a table. | -| Method [`spacetimedb_sdk::table::TableType::iter`](#method-iter) | Iterate over all subscribed rows. | -| Method [`spacetimedb_sdk::table::TableType::filter`](#method-filter) | Iterate over a subset of subscribed rows matching a predicate. | -| Method [`spacetimedb_sdk::table::TableType::find`](#method-find) | Return one subscribed row matching a predicate. | -| Method [`spacetimedb_sdk::table::TableType::on_insert`](#method-on_insert) | Register a `FnMut` callback to run whenever a new subscribed row is inserted. | -| Method [`spacetimedb_sdk::table::TableType::remove_on_insert`](#method-remove_on_insert) | Cancel an `on_insert` callback. | -| Method [`spacetimedb_sdk::table::TableType::on_delete`](#method-on_delete) | Register a `FnMut` callback to run whenever a subscribed row is deleted. | -| Method [`spacetimedb_sdk::table::TableType::remove_on_delete`](#method-remove_on_delete) | Cancel an `on_delete` callback. | -| Trait [`spacetimedb_sdk::table::TableWithPrimaryKey`](#trait-tablewithprimarykey) | Automatically implemented for tables with a column designated `#[primarykey]`. | -| Method [`spacetimedb_sdk::table::TableWithPrimaryKey::on_update`](#method-on_update) | Register a `FnMut` callback to run whenever an existing subscribed row is updated. | -| Method [`spacetimedb_sdk::table::TableWithPrimaryKey::remove_on_update`](#method-remove_on_update) | Cancel an `on_update` callback. | -| Type [`module_bindings::ReducerEvent`](#type-reducerevent) | Autogenerated enum with a variant for each reducer defined by the module. | -| Type [`module_bindings::{REDUCER}Args`](#type-reducerargs) | Autogenerated `struct` type for a reducer, holding its arguments. | -| Function [`module_bindings::{REDUCER}`](#function-reducer) | Autogenerated function to invoke a reducer. | -| Function [`module_bindings::on_{REDUCER}`](#function-on_reducer) | Autogenerated function to register a `FnMut` callback to run whenever the reducer is invoked. | -| Function [`module_bindings::once_on_{REDUCER}`](#function-once_on_reducer) | Autogenerated function to register a `FnOnce` callback to run the next time the reducer is invoked. | -| Function [`module_bindings::remove_on_{REDUCER}`](#function-remove_on_reducer) | Autogenerated function to cancel an `on_{REDUCER}` or `once_on_{REDUCER}` callback. | -| Type [`spacetimedb_sdk::reducer::Status`](#type-status) | Enum representing reducer completion statuses. | - -## Connect to a database - -### Function `connect` +## Type `DbConnection` ```rust -module_bindings::connect( - spacetimedb_uri: impl TryInto, - db_name: &str, - credentials: Option, -) -> anyhow::Result<()> +module_bindings::DbConnection ``` -Connect to a database named `db_name` accessible over the internet at the URI `spacetimedb_uri`. +A connection to a remote database is represented by the `module_bindings::DbConnection` type. This type is generated per-module, and contains information about the types, tables and reducers defined by your module. -| Argument | Type | Meaning | -| ----------------- | --------------------- | ------------------------------------------------------------ | -| `spacetimedb_uri` | `impl TryInto` | URI of the SpacetimeDB instance running the module. | -| `db_name` | `&str` | Name of the module. | -| `credentials` | `Option` | [`Credentials`](#type-credentials) to authenticate the user. | - -If `credentials` are supplied, they will be passed to the new connection to identify and authenticate the user. Otherwise, a set of [`Credentials`](#type-credentials) will be generated by the server. - -```rust -const MODULE_NAME: &str = "my-module-name"; - -// Connect to a local DB with a fresh identity -connect("http://localhost:3000", MODULE_NAME, None) - .expect("Connection failed"); - -// Connect to cloud with a fresh identity. -connect("https://testnet.spacetimedb.com", MODULE_NAME, None) - .expect("Connection failed"); - -// Connect with a saved identity -const CREDENTIALS_DIR: &str = ".my-module"; -connect( - "https://testnet.spacetimedb.com", - MODULE_NAME, - load_credentials(CREDENTIALS_DIR) - .expect("Error while loading credentials"), -).expect("Connection failed"); -``` - -### Function `disconnect` +### Connect to a module - `DbConnection::builder()` and `.build()` ```rust -spacetimedb_sdk::disconnect() +impl DbConnection { + fn builder() -> DbConnectionBuilder; +} ``` -Gracefully close the current WebSocket connection. +Construct a `DbConnection` by calling `DbConnection::builder()` and chaining configuration methods, then calling `.build()`. You must at least specify `with_uri`, to supply the URI of the SpacetimeDB to which you published your module, and `with_module_name`, to supply the human-readable SpacetimeDB domain name or the raw address which identifies the module. -If there is no active connection, this operation does nothing. +#### Method `with_uri` ```rust -connect(SPACETIMEDB_URI, MODULE_NAME, credentials) - .expect("Connection failed"); - -run_app(); - -disconnect(); +impl DbConnectionBuilder { + fn with_uri(self, uri: impl TryInto) -> Self; +} ``` -### Function `on_disconnect` - -```rust -spacetimedb_sdk::on_disconnect( - callback: impl FnMut() + Send + 'static, -) -> DisconnectCallbackId -``` - -Register a callback to be invoked when a connection ends. - -| Argument | Type | Meaning | -| ---------- | ------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut() + Send + 'static` | Callback to be invoked when subscriptions are applied. | - -The callback will be invoked after calling [`disconnect`](#function-disconnect), or when a connection is closed by the server. - -The returned `DisconnectCallbackId` can be passed to [`remove_on_disconnect`](#function-remove_on_disconnect) to unregister the callback. - -```rust -on_disconnect(|| println!("Disconnected!")); - -connect(SPACETIMEDB_URI, MODULE_NAME, credentials) - .expect("Connection failed"); - -disconnect(); - -// Will print "Disconnected!" -``` +Configure the URI of the SpacetimeDB instance or cluster which hosts the remote module. -### Function `once_on_disconnect` +#### Method `with_module_name` ```rust -spacetimedb_sdk::once_on_disconnect( - callback: impl FnOnce() + Send + 'static, -) -> DisconnectCallbackId +impl DbConnectionBuilder { + fn with_module_name(self, name_or_address: impl ToString) -> Self; +} ``` -Register a callback to be invoked the next time a connection ends. - -| Argument | Type | Meaning | -| ---------- | ------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut() + Send + 'static` | Callback to be invoked when subscriptions are applied. | - -The callback will be invoked after calling [`disconnect`](#function-disconnect), or when a connection is closed by the server. +Configure the SpacetimeDB domain name or address of the remote module which identifies it within the SpacetimeDB instance or cluster. -The callback will be unregistered after running. - -The returned `DisconnectCallbackId` can be passed to [`remove_on_disconnect`](#function-remove_on_disconnect) to unregister the callback. +#### Callback `on_connect` ```rust -once_on_disconnect(|| println!("Disconnected!")); - -connect(SPACETIMEDB_URI, MODULE_NAME, credentials) - .expect("Connection failed"); - -disconnect(); - -// Will print "Disconnected!" - -connect(SPACETIMEDB_URI, MODULE_NAME, credentials) - .expect("Connection failed"); - -disconnect(); - -// Nothing printed this time. +impl DbConnectionBuilder { + fn on_connect(self, callback: impl FnOnce(&DbConnection, Identity, &str)) -> DbConnectionBuilder; +} ``` -### Function `remove_on_disconnect` +Chain a call to `.on_connect(callback)` to your builder to register a callback to run when your new `DbConnection` successfully initiates its connection to the remote module. The callback accepts three arguments: a reference to the `DbConnection`, the `Identity` by which SpacetimeDB identifies this connection, and a private access token which can be saved and later passed to [`with_credentials`](#method-with_credentials) to authenticate the same user in future connections. -```rust -spacetimedb_sdk::remove_on_disconnect( - id: DisconnectCallbackId, -) -``` +This interface may change in an upcoming release as we rework SpacetimeDB's authentication model. -Unregister a previously-registered [`on_disconnect`](#function-on_disconnect) callback. +#### Callback `on_connect_error` -| Argument | Type | Meaning | -| -------- | ---------------------- | ------------------------------------------ | -| `id` | `DisconnectCallbackId` | Identifier for the callback to be removed. | +Currently unused. -If `id` does not refer to a currently-registered callback, this operation does nothing. +#### Callback `on_disconnect` ```rust -let id = on_disconnect(|| unreachable!()); - -remove_on_disconnect(id); - -disconnect(); - -// No `unreachable` panic. +impl DbConnectionBuilder { + fn on_disconnect(self, callback: impl FnOnce(&DbConnection, Option<&anyhow::Error>)) -> DbConnectionBuilder; +} ``` -## Subscribe to queries +Chain a call to `.on_connect(callback)` to your builder to register a callback to run when your `DbConnection` disconnects from the remote module, either as a result of a call to [`disconnect`](#method-disconnect) or due to an error. -### Function `subscribe` +#### Method `with_credentials` ```rust -spacetimedb_sdk::subscribe(queries: &[&str]) -> anyhow::Result<()> +impl DbConnectionBuilder { + fn with_credentials(self, credentials: Option<(Identity, String)>) -> Self; +} ``` -Subscribe to a set of queries, to be notified when rows which match those queries are altered. - -| Argument | Type | Meaning | -| --------- | --------- | ---------------------------- | -| `queries` | `&[&str]` | SQL queries to subscribe to. | - -The `queries` should be a slice of strings representing SQL queries. - -`subscribe` will return an error if called before establishing a connection with the autogenerated [`connect`](#function-connect) function. In that case, the queries are not registered. - -`subscribe` does not return data directly. The SDK will generate types [`module_bindings::{TABLE}`](#type-table) corresponding to each of the tables in your module. These types implement the trait [`spacetimedb_sdk::table_type::TableType`](#trait-tabletype), which contains methods such as [`TableType::on_insert`](#method-on_insert). Use these methods to receive data from the queries you subscribe to. +Chain a call to `.with_credentials(credentials)` to your builder to provide an `Identity` and private access token to authenticate with, or to explicitly select an anonymous connection. If this method is not called or `None` is passed, SpacetimeDB will generate a new `Identity` and sign a new private access token for the connection. -A new call to `subscribe` (or [`subscribe_owned`](#function-subscribe_owned)) will remove all previous subscriptions and replace them with the new `queries`. If any rows matched the previous subscribed queries but do not match the new queries, those rows will be removed from the client cache, and [`TableType::on_delete`](#method-on_delete) callbacks will be invoked for them. +This interface may change in an upcoming release as we rework SpacetimeDB's authentication model. -```rust -subscribe(&["SELECT * FROM User;", "SELECT * FROM Message;"]) - .expect("Called `subscribe` before `connect`"); -``` - -### Function `subscribe_owned` +#### Method `build` ```rust -spacetimedb_sdk::subscribe_owned(queries: Vec) -> anyhow::Result<()> +impl DbConnectionBuilder { + fn build(self) -> anyhow::Result; +} ``` -Subscribe to a set of queries, to be notified when rows which match those queries are altered. - -| Argument | Type | Meaning | -| --------- | ------------- | ---------------------------- | -| `queries` | `Vec` | SQL queries to subscribe to. | - -The `queries` should be a `Vec` of `String`s representing SQL queries. +After configuring the connection and registering callbacks, attempt to open the connection. -A new call to `subscribe_owned` (or [`subscribe`](#function-subscribe)) will remove all previous subscriptions and replace them with the new `queries`. -If any rows matched the previous subscribed queries but do not match the new queries, those rows will be removed from the client cache, and [`TableType::on_delete`](#method-on_delete) callbacks will be invoked for them. +### Advance the connection and process messages -`subscribe_owned` will return an error if called before establishing a connection with the autogenerated [`connect`](#function-connect) function. In that case, the queries are not registered. +In the interest of supporting a wide variety of client applications with different execution strategies, the SpacetimeDB SDK allows you to choose when the `DbConnection` spends compute time and processes messages. If you do not arrange for the connection to advance by calling one of these methods, the `DbConnection` will never advance, and no callbacks will ever be invoked. -```rust -let query = format!("SELECT * FROM User WHERE name = '{}';", compute_my_name()); - -subscribe_owned(vec![query]) - .expect("Called `subscribe_owned` before `connect`"); -``` - -### Function `on_subscription_applied` +#### Run in the background - method `run_threaded` ```rust -spacetimedb_sdk::on_subscription_applied( - callback: impl FnMut() + Send + 'static, -) -> SubscriptionCallbackId +impl DbConnection { + fn run_threaded(&self) -> std::thread::JoinHandle<()>; +} ``` -Register a callback to be invoked the first time a subscription's matching rows becoming available. - -| Argument | Type | Meaning | -| ---------- | ------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut() + Send + 'static` | Callback to be invoked when subscriptions are applied. | - -The callback will be invoked after a successful [`subscribe`](#function-subscribe) or [`subscribe_owned`](#function-subscribe_owned) call when the initial set of matching rows becomes available. - -The returned `SubscriptionCallbackId` can be passed to [`remove_on_subscription_applied`](#function-remove_on_subscription_applied) to unregister the callback. - -```rust -on_subscription_applied(|| println!("Subscription applied!")); - -subscribe(&["SELECT * FROM User;"]) - .expect("Called `subscribe` before `connect`"); - -sleep(Duration::from_secs(1)); - -// Will print "Subscription applied!" - -subscribe(&["SELECT * FROM User;", "SELECT * FROM Message;"]) - .expect("Called `subscribe` before `connect`"); - -// Will print again. -``` +`run_threaded` spawns a thread which will continuously advance the connection, sleeping when there is no work to do. The thread will panic if the connection disconnects erroneously, or return if it disconnects as a result of a call to [`disconnect`](#method-disconnect). -### Function `once_on_subscription_applied` +#### Run asynchronously - method `run_async` ```rust -spacetimedb_sdk::once_on_subscription_applied( - callback: impl FnOnce() + Send + 'static, -) -> SubscriptionCallbackId +impl DbConnection { + async fn run_async(&self) -> anyhow::Result<()>; +} ``` -Register a callback to be invoked the next time a subscription's matching rows become available. - -| Argument | Type | Meaning | -| ---------- | ------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut() + Send + 'static` | Callback to be invoked when subscriptions are applied. | - -The callback will be invoked after a successful [`subscribe`](#function-subscribe) or [`subscribe_owned`](#function-subscribe_owned) call when the initial set of matching rows becomes available. +`run_async` will continuously advance the connection, `await`-ing when there is no work to do. The task will return an `Err` if the connection disconnects erroneously, or return `Ok(())` if it disconnects as a result of a call to [`disconnect`](#method-disconnect). -The callback will be unregistered after running. - -The returned `SubscriptionCallbackId` can be passed to [`remove_on_subscription_applied`](#function-remove_on_subscription_applied) to unregister the callback. +#### Run on the main thread without blocking - method `frame_tick` ```rust -once_on_subscription_applied(|| println!("Subscription applied!")); - -subscribe(&["SELECT * FROM User;"]) - .expect("Called `subscribe` before `connect`"); - -sleep(Duration::from_secs(1)); - -// Will print "Subscription applied!" - -subscribe(&["SELECT * FROM User;", "SELECT * FROM Message;"]) - .expect("Called `subscribe` before `connect`"); - -// Nothing printed this time. +impl DbConnection { + fn frame_tick(&self) -> anyhow::Result<()>; +} ``` -### Function `remove_on_subscription_applied` - -```rust -spacetimedb_sdk::remove_on_subscription_applied( - id: SubscriptionCallbackId, -) -``` - -Unregister a previously-registered [`on_subscription_applied`](#function-on_subscription_applied) callback. - -| Argument | Type | Meaning | -| -------- | ------------------------ | ------------------------------------------ | -| `id` | `SubscriptionCallbackId` | Identifier for the callback to be removed. | - -If `id` does not refer to a currently-registered callback, this operation does nothing. - -```rust -let id = on_subscription_applied(|| println!("Subscription applied!")); - -subscribe(&["SELECT * FROM User;"]) - .expect("Called `subscribe` before `connect`"); - -sleep(Duration::from_secs(1)); +`frame_tick` will advance the connection until no work remains, then return rather than blocking or `await`-ing. Games might arrange for this message to be called every frame. `frame_tick` returns `Ok` if the connection remains active afterwards, or `Err` if the connection disconnected before or during the call. -// Will print "Subscription applied!" +## Trait `spacetimedb_sdk::DbContext` -remove_on_subscription_applied(id); +[`DbConnection`](#type-dbconnection) and [`EventContext`](#type-eventcontext) both implement `DbContext`, which allows -subscribe(&["SELECT * FROM User;", "SELECT * FROM Message;"]) - .expect("Called `subscribe` before `connect`"); - -// Nothing printed this time. -``` - -## Identify a client - -### Type `Identity` +### Method `disconnect` ```rust -spacetimedb_sdk::identity::Identity +trait DbContext { + fn disconnect(&self) -> anyhow::Result<()>; +} ``` -A unique public identifier for a client connected to a database. - -### Type `Token` +Gracefully close the `DbConnection`. Returns an `Err` if the connection is already disconnected. -```rust -spacetimedb_sdk::identity::Token -``` +### Subscribe to queries - `DbContext::subscription_builder` and `.subscribe()` -A private access token for a client connected to a database. +This interface is subject to change in an upcoming SpacetimeDB release. -### Type `Credentials` +A known issue in the SpacetimeDB Rust SDK causes inconsistent behaviors after re-subscribing. This will be fixed in an upcoming SpacetimeDB release. For now, Rust clients should issue only one subscription per `DbConnection`. ```rust -spacetimedb_sdk::identity::Credentials +trait DbContext { + fn subscription_builder(&self) -> SubscriptionBuilder; +} ``` -Credentials, including a private access token, sufficient to authenticate a client connected to a database. +Subscribe to queries by calling `ctx.subscription_builder()` and chaining configuration methods, then calling `.subscribe(queries)`. -| Field | Type | -| ---------- | ---------------------------- | -| `identity` | [`Identity`](#type-identity) | -| `token` | [`Token`](#type-token) | - -### Type `Address` +#### Callback `on_applied` ```rust -spacetimedb_sdk::Address +impl SubscriptionBuilder { + fn on_applied(self, callback: impl FnOnce(&EventContext)) -> Self; +} ``` -An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). +Register a callback to run when the subscription is applied and the matching rows are inserted into the client cache. The [`EventContext`](#type-module_bindings-eventcontext) passed to the callback will have `Event::SubscribeApplied` as its `event`. -### Function `identity` +#### Method `subscribe` ```rust -spacetimedb_sdk::identity::identity() -> Result +impl SubscriptionBuilder { + fn subscribe(self, queries: impl IntoQueries) -> SubscriptionHandle; +} ``` -Read the current connection's public [`Identity`](#type-identity). +Subscribe to a set of queries. `queries` should be an array or slice of strings. -Returns an error if: +The returned `SubscriptionHandle` is currently not useful, but will become significant in a future version of SpacetimeDB. -- [`connect`](#function-connect) has not yet been called. -- We connected anonymously, and we have not yet received our credentials. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -println!("My identity is {:?}", identity()); - -// Prints "My identity is Ok(Identity { bytes: [...several u8s...] })" -``` +### Identity a client -### Function `token` +#### Method `identity` ```rust -spacetimedb_sdk::identity::token() -> Result +trait DbContext { + fn identity(&self) -> Identity; +} ``` -Read the current connection's private [`Token`](#type-token). +Get the `Identity` with which SpacetimeDB identifies the connection. This method may panic if the connection was initiated anonymously and the newly-generated `Identity` has not yet been received, i.e. if called before the [`on_connect` callback](#callback-on_connect) is invoked. -Returns an error if: - -- [`connect`](#function-connect) has not yet been called. -- We connected anonymously, and we have not yet received our credentials. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -println!("My token is {:?}", token()); - -// Prints "My token is Ok(Token {string: "...several Base64 digits..." })" -``` - -### Function `credentials` +#### Method `try_identity` ```rust -spacetimedb_sdk::identity::credentials() -> Result +trait DbContext { + fn try_identity(&self) -> Option; +} ``` -Read the current connection's [`Credentials`](#type-credentials), including a public [`Identity`](#type-identity) and a private [`Token`](#type-token). +Like [`DbContext::identity`](#method-identity), but returns `None` instead of panicking if the `Identity` is not yet available. -Returns an error if: - -- [`connect`](#function-connect) has not yet been called. -- We connected anonymously, and we have not yet received our credentials. +#### Method `is_active` ```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -println!("My credentials are {:?}", credentials()); - -// Prints "My credentials are Ok(Credentials { -// identity: Identity { bytes: [...several u8s...] }, -// token: Token { string: "...several Base64 digits..."}, -// })" +trait DbContext { + fn is_active(&self) -> bool; +} ``` -### Function `address` - -```rust -spacetimedb_sdk::identity::address() -> Result
-``` - -Read the current connection's [`Address`](#type-address). - -Returns an error if [`connect`](#function-connect) has not yet been called. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -println!("My address is {:?}", address()); -``` +`true` if the connection has not yet disconnected. Note that a connection `is_active` when it is constructed, before its [`on_connect` callback](#callback-on_connect) is invoked. -### Function `on_connect` +## Type `EventContext` ```rust -spacetimedb_sdk::identity::on_connect( - callback: impl FnMut(&Credentials, Address) + Send + 'static, -) -> ConnectCallbackId +module_bindings::EventContext ``` -Register a callback to be invoked upon authentication with the database. - -| Argument | Type | Meaning | -| ---------- | -------------------------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut(&Credentials, Address) + Send + 'sync` | Callback to be invoked upon successful authentication. | - -The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. +An `EventContext` is a [`DbContext`](#trait-dbcontext) augmented with a field `event: Event`. -The [`Credentials`](#type-credentials) passed to the callback can be saved and used to authenticate the same user in future connections. - -The returned `ConnectCallbackId` can be passed to [`remove_on_connect`](#function-remove_on_connect) to unregister the callback. +### Enum `Event` ```rust -on_connect( - |creds, addr| - println!("Successfully connected! My credentials are: {:?} and my address is: {:?}", creds, addr) -); - -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -// Will print "Successfully connected! My credentials are: " -// followed by a printed representation of the client's `Credentials`. +spacetimedb_sdk::Event ``` -### Function `once_on_connect` - -```rust -spacetimedb_sdk::identity::once_on_connect( - callback: impl FnOnce(&Credentials, Address) + Send + 'static, -) -> ConnectCallbackId -``` - -Register a callback to be invoked once upon authentication with the database. - -| Argument | Type | Meaning | -| ---------- | --------------------------------------------------- | ---------------------------------------------------------------- | -| `callback` | `impl FnOnce(&Credentials, Address) + Send + 'sync` | Callback to be invoked once upon next successful authentication. | - -The callback will be invoked with the [`Credentials`](#type-credentials) and [`Address`](#type-address) provided by the database to identify this connection. If [`Credentials`](#type-credentials) were supplied to [`connect`](#function-connect), those passed to the callback will be equivalent to the ones used to connect. If the initial connection was anonymous, a new set of [`Credentials`](#type-credentials) will be generated by the database to identify this user. - -The [`Credentials`](#type-credentials) passed to the callback can be saved and used to authenticate the same user in future connections. - -The callback will be unregistered after running. - -The returned `ConnectCallbackId` can be passed to [`remove_on_connect`](#function-remove_on_connect) to unregister the callback. - -### Function `remove_on_connect` +#### Variant `Reducer` ```rust -spacetimedb_sdk::identity::remove_on_connect(id: ConnectCallbackId) +spacetimedb_sdk::Event::Reducer(spacetimedb_sdk::ReducerEvent) ``` -Unregister a previously-registered [`on_connect`](#function-on_connect) or [`once_on_connect`](#function-once_on_connect) callback. +Event when we are notified that a reducer ran in the remote module. The [`ReducerEvent`](#struct-reducerevent) contains metadata about the reducer run, including its arguments and termination [`Status`](#enum-status). -| Argument | Type | Meaning | -| -------- | ------------------- | ------------------------------------------ | -| `id` | `ConnectCallbackId` | Identifier for the callback to be removed. | +This event is passed to reducer callbacks, and to row callbacks resulting from modifications by the reducer. -If `id` does not refer to a currently-registered callback, this operation does nothing. +#### Variant `SubscribeApplied` ```rust -let id = on_connect(|_creds, _addr| unreachable!()); - -remove_on_connect(id); - -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -sleep(Duration::from_secs(1)); - -// No `unreachable` panic. +spacetimedb_sdk::Event::SubscribeApplied ``` -### Function `load_credentials` +Event when our subscription is applied and its rows are inserted into the client cache. -```rust -spacetimedb_sdk::identity::load_credentials( - dirname: &str, -) -> Result> -``` +This event is passed to [subscription `on_applied` callbacks](#callback-on_applied), and to [row `on_insert` callbacks](#callback-on_insert) resulting from the new subscription. -Load a saved [`Credentials`](#type-credentials) from a file within `~/dirname`, if one exists. +#### Variant `UnsubscribeApplied` -| Argument | Type | Meaning | -| --------- | ------ | ----------------------------------------------------- | -| `dirname` | `&str` | Name of a sub-directory in the user's home directory. | +Currently unused. -`dirname` is treated as a directory in the user's home directory. If it contains a file named `credentials`, that file is treated as a BSATN-encoded [`Credentials`](#type-credentials), deserialized and returned. These files are created by [`save_credentials`](#function-save_credentials) with the same `dirname` argument. +#### Variant `SubscribeError` -Returns `Ok(None)` if the directory or the credentials file does not exist. Returns `Err` when IO or deserialization fails. The returned `Result` may be unwrapped, and the contained `Option` passed to [`connect`](#function-connect). +Currently unused. -```rust -const CREDENTIALS_DIR = ".my-module"; +#### Variant `UnknownTransaction` -let creds = load_credentials(CREDENTIALS_DIR) - .expect("Error while loading credentials"); +Event when we are notified of a transaction in the remote module which we cannot associate with a known reducer. This may be an ad-hoc SQL query or a reducer for which we do not have bindings. -connect(SPACETIMEDB_URI, DB_NAME, creds) - .expect("Failed to connect"); -``` +This event is passed to row callbacks resulting from modifications by the transaction. -### Function `save_credentials` +### Struct `ReducerEvent` ```rust -spacetimedb_sdk::identity::save_credentials( - dirname: &str, - credentials: &Credentials, -) -> Result<()> +spacetimedb_sdk::ReducerEvent ``` -Store a [`Credentials`](#type-credentials) to a file within `~/dirname`, to be later loaded with [`load_credentials`](#function-load_credentials). - -| Argument | Type | Meaning | -| ------------- | -------------- | ----------------------------------------------------- | -| `dirname` | `&str` | Name of a sub-directory in the user's home directory. | -| `credentials` | `&Credentials` | [`Credentials`](#type-credentials) to store. | - -`dirname` is treated as a directory in the user's home directory. The directory is created if it does not already exists. A file within it named `credentials` is created or replaced, containing `creds` encoded as BSATN. The saved credentials can be retrieved by [`load_credentials`](#function-load_credentials) with the same `dirname` argument. - -Returns `Err` when IO or serialization fails. +A `ReducerEvent` contains metadata about a reducer run. ```rust -const CREDENTIALS_DIR = ".my-module"; +struct spacetimedb_sdk::ReducerEvent { + /// The time at which the reducer was invoked. + timestamp: SystemTime, -let creds = load_credentials(CREDENTIALS_DIRectory) - .expect("Error while loading credentials"); + /// Whether the reducer committed, was aborted due to insufficient energy, or failed with an error message. + status: Status, -on_connect(|creds, _addr| { - if let Err(e) = save_credentials(CREDENTIALS_DIR, creds) { - eprintln!("Error while saving credentials: {:?}", e); - } -}); + /// The `Identity` of the SpacetimeDB actor which invoked the reducer. + caller_identity: Identity, -connect(SPACETIMEDB_URI, DB_NAME, creds) - .expect("Failed to connect"); -``` + /// The `Address` of the SpacetimeDB actor which invoked the reducer, + /// or `None` if the actor did not supply an address. + caller_address: Option
, -## View subscribed rows of tables + /// The amount of energy consumed by the reducer run, in eV. + /// (Not literal eV, but our SpacetimeDB energy unit eV.) + /// + /// May be `None` if the module is configured not to broadcast energy consumed. + energy_consumed: Option, -### Type `{TABLE}` + /// The `Reducer` enum defined by the `module_bindings`, which encodes which reducer ran and its arguments. + reducer: R, -```rust -module_bindings::{TABLE} + // ...private fields +} ``` -For each table defined by a module, `spacetime generate` generates a struct in the `module_bindings` mod whose name is that table's name converted to `PascalCase`. The generated struct has a field for each of the table's columns, whose names are the column names converted to `snake_case`. - -### Method `filter_by_{COLUMN}` - -```rust -module_bindings::{TABLE}::filter_by_{COLUMN}( - value: {COLUMN_TYPE}, -) -> impl Iterator -``` - -For each column of a table, `spacetime generate` generates a static method on the [table struct](#type-table) to filter subscribed rows where that column matches a requested value. - -These methods are named `filter_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. The method's return type is an `Iterator` over the `{TABLE}` rows which match the requested value. - -### Method `find_by_{COLUMN}` +### Enum `Status` ```rust -module_bindings::{TABLE}::find_by_{COLUMN}( - value: {COLUMN_TYPE}, -) -> {FILTER_RESULT}<{TABLE}> +spacetimedb_sdk::Status ``` -For each unique column of a table (those annotated `#[unique]` and `#[primarykey]`), `spacetime generate` generates a static method on the [table struct](#type-table) to seek a subscribed row where that column matches a requested value. - -These methods are named `find_by_{COLUMN}`, where `{COLUMN}` is the column name converted to `snake_case`. The method's return type is `Option<{TABLE}>`. - -### Trait `TableType` +#### Variant `Committed` ```rust -spacetimedb_sdk::table::TableType +spacetimedb_sdk::Status::Committed ``` -Every [generated table struct](#type-table) implements the trait `TableType`. +The reducer returned successfully and its changes were committed into the database state. An [`Event::Reducer`](#variant-reducer) passed to a row callback must have this status in its [`ReducerEvent`](#struct-reducerevent). -#### Method `count` +#### Variant `Failed` ```rust -TableType::count() -> usize +spacetimedb_sdk::Status::Failed(Box) ``` -Return the number of subscribed rows in the table, or 0 if there is no active connection. - -This method acquires a global lock. +The reducer returned an error, panicked, or threw an exception. The enum payload is the stringified error message. Formatting of the error message is unstable and subject to change, so clients should use it only as a human-readable diagnostic, and in particular should not attempt to parse the message. -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -on_subscription_applied(|| println!("There are {} users", User::count())); +#### Variant `OutOfEnergy` -subscribe(&["SELECT * FROM User;"]) - .unwrap(); +The reducer was aborted due to insufficient energy balance of the module owner. -sleep(Duration::from_secs(1)); - -// Will the number of `User` rows in the database. -``` - -#### Method `iter` +### Enum `Reducer` ```rust -TableType::iter() -> impl Iterator +module_bindings::Reducer ``` -Iterate over all the subscribed rows in the table. - -This method acquires a global lock, but the iterator does not hold it. +The module bindings contains an enum `Reducer` with a variant for each reducer defined by the module. Each variant has a payload containing the arguments to the reducer. -This method must heap-allocate enough memory to hold all of the rows being iterated over. [`TableType::filter`](#method-filter) allocates significantly less, so prefer it when possible. +## Access the client cache -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); +Both [`DbConnection`](#type-dbconnection) and [`EventContext`](#type-eventcontext) have fields `.db`, which in turn has methods for accessing tables in the client cache. The trait method `DbContext::db(&self)` can also be used in contexts with an `impl DbContext` rather than a concrete-typed `EventContext` or `DbConnection`. -on_subscription_applied(|| for user in User::iter() { - println!("{:?}", user); -}); +Each table defined by a module has an accessor method, whose name is the table name converted to `snake_case`, on this `.db` field. The methods are defined via extension traits, which `rustc` or your IDE should help you identify and import where necessary. The table accessor methods return table handles, which implement [`Table`](#trait-table), may implement [`TableWithPrimaryKey`](#trait-tablewithprimarykey), and have methods for searching by unique index. -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -sleep(Duration::from_secs(1)); - -// Will print a line for each `User` row in the database. -``` - -#### Method `filter` +### Trait `Table` ```rust -TableType::filter( - predicate: impl FnMut(&Self) -> bool, -) -> impl Iterator +spacetimedb_sdk::Table ``` -Iterate over the subscribed rows in the table for which `predicate` returns `true`. - -| Argument | Type | Meaning | -| ----------- | --------------------------- | ------------------------------------------------------------------------------- | -| `predicate` | `impl FnMut(&Self) -> bool` | Test which returns `true` if a row should be included in the filtered iterator. | - -This method acquires a global lock, and the `predicate` runs while the lock is held. The returned iterator does not hold the lock. +Implemented by all table handles. -The `predicate` is called eagerly for each subscribed row in the table, even if the returned iterator is never consumed. - -This method must heap-allocate enough memory to hold all of the matching rows, but does not allocate space for subscribed rows which do not match the `predicate`. - -Client authors should prefer calling [tables' generated `filter_by_{COLUMN}` methods](#method-filter_by_column) when possible rather than calling `TableType::filter`. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -on_subscription_applied(|| { - for user in User::filter(|user| user.age >= 30 - && user.country == Country::USA) { - println!("{:?}", user); - } -}); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -sleep(Duration::from_secs(1)); - -// Will print a line for each `User` row in the database -// who is at least 30 years old and who lives in the United States. -``` - -#### Method `find` +#### Associated type `Row` ```rust -TableType::find( - predicate: impl FnMut(&Self) -> bool, -) -> Option +trait spacetimedb_sdk::Table { + type Table::Row; +} ``` -Locate a subscribed row for which `predicate` returns `true`, if one exists. - -| Argument | Type | Meaning | -| ----------- | --------------------------- | ------------------------------------------------------ | -| `predicate` | `impl FnMut(&Self) -> bool` | Test which returns `true` if a row should be returned. | - -This method acquires a global lock. - -If multiple subscribed rows match `predicate`, one is chosen arbitrarily. The choice may not be stable across different calls to `find` with the same `predicate`. - -Client authors should prefer calling [tables' generated `find_by_{COLUMN}` methods](#method-find_by_column) when possible rather than calling `TableType::find`. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -on_subscription_applied(|| { - if let Some(tyler) = User::find(|user| user.first_name == "Tyler" - && user.surname == "Cloutier") { - println!("Found Tyler: {:?}", tyler); - } else { - println!("Tyler isn't registered :("); - } -}); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -sleep(Duration::from_secs(1)); +The type of rows in the table. -// Will tell us whether Tyler Cloutier is registered in the database. -``` - -#### Method `on_insert` +#### Method `count` ```rust -TableType::on_insert( - callback: impl FnMut(&Self, Option<&ReducerEvent>) + Send + 'static, -) -> InsertCallbackId +trait spacetimedb_sdk::Table { + fn count(&self) -> u64; +} ``` -Register an `on_insert` callback for when a subscribed row is newly inserted into the database. - -| Argument | Type | Meaning | -| ---------- | ----------------------------------------------------------- | ------------------------------------------------------ | -| `callback` | `impl FnMut(&Self, Option<&ReducerEvent>) + Send + 'static` | Callback to run whenever a subscribed row is inserted. | - -The callback takes two arguments: - -- `row: &Self`, the newly-inserted row value. -- `reducer_event: Option<&ReducerEvent>`, the [`ReducerEvent`](#type-reducerevent) which caused this row to be inserted, or `None` if this row is being inserted while initializing a subscription. - -The returned `InsertCallbackId` can be passed to [`remove_on_insert`](#method-remove_on_insert) to remove the callback. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -User::on_insert(|user, reducer_event| { - if let Some(reducer_event) = reducer_event { - println!("New user inserted by reducer {:?}: {:?}", reducer_event, user); - } else { - println!("New user received during subscription update: {:?}", user); - } -}); +Returns the number of rows of this table resident in the client cache, i.e. the total number which match any subscribed query. -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -sleep(Duration::from_secs(1)); - -// Will print a note whenever a new `User` row is inserted. -``` - -#### Method `remove_on_insert` +#### Method `iter` ```rust -TableType::remove_on_insert(id: InsertCallbackId) +trait spacetimedb_sdk::Table { + fn iter(&self) -> impl Iterator; +} ``` -Unregister a previously-registered [`on_insert`](#method-on_insert) callback. +An iterator over all the subscribed rows in the client cache, i.e. those which match any subscribed query. -| Argument | Type | Meaning | -| -------- | ------------------------ | ----------------------------------------------------------------------- | -| `id` | `InsertCallbackId` | Identifier for the [`on_insert`](#method-on_insert) callback to remove. | - -If `id` does not refer to a currently-registered callback, this operation does nothing. +#### Callback `on_insert` ```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -let id = User::on_insert(|_, _| unreachable!()); - -User::remove_on_insert(id); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); +trait spacetimedb_sdk::Table { + type InsertCallbackId; + + fn on_insert(&self, callback: impl FnMut(&EventContext, &Self::Row)) -> Self::InsertCallbackId; -sleep(Duration::from_secs(1)); - -// No `unreachable` panic. -``` - -#### Method `on_delete` - -```rust -TableType::on_delete( - callback: impl FnMut(&Self, Option<&ReducerEvent>) + Send + 'static, -) -> DeleteCallbackId + fn remove_on_insert(&self, callback: Self::InsertCallbackId); +} ``` -Register an `on_delete` callback for when a subscribed row is removed from the database. +The `on_insert` 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`](#enum-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. -| Argument | Type | Meaning | -| ---------- | ----------------------------------------------------------- | ----------------------------------------------------- | -| `callback` | `impl FnMut(&Self, Option<&ReducerEvent>) + Send + 'static` | Callback to run whenever a subscribed row is deleted. | +Registering an `on_insert` callback returns a callback id, which can later be passed to `remove_on_insert` to cancel the callback. Newly registered or canceled callbacks do not take effect until the following event. -The callback takes two arguments: - -- `row: &Self`, the previously-present row which is no longer resident in the database. -- `reducer_event: Option<&ReducerEvent>`, the [`ReducerEvent`](#type-reducerevent) which caused this row to be deleted, or `None` if this row was previously subscribed but no longer matches the new queries while initializing a subscription. - -The returned `DeleteCallbackId` can be passed to [`remove_on_delete`](#method-remove_on_delete) to remove the callback. +#### Callback `on_delete` ```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -User::on_delete(|user, reducer_event| { - if let Some(reducer_event) = reducer_event { - println!("User deleted by reducer {:?}: {:?}", reducer_event, user); - } else { - println!("User no longer subscribed during subscription update: {:?}", user); - } -}); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -// Invoke a reducer which will delete a `User` row. -delete_user_by_name("Tyler Cloutier".to_string()); +trait spacetimedb_sdk::Table { + type DeleteCallbackId; + + fn on_delete(&self, callback: impl FnMut(&EventContext, &Self::Row)) -> Self::DeleteCallbackId; -sleep(Duration::from_secs(1)); - -// Will print a note whenever a `User` row is inserted, -// including "User deleted by reducer ReducerEvent::DeleteUserByName( -// DeleteUserByNameArgs { name: "Tyler Cloutier" } -// ): User { first_name: "Tyler", surname: "Cloutier" }" -``` - -#### Method `remove_on_delete` - -```rust -TableType::remove_on_delete(id: DeleteCallbackId) + fn remove_on_delete(&self, callback: Self::DeleteCallbackId); +} ``` -Unregister a previously-registered [`on_delete`](#method-on_delete) callback. - -| Argument | Type | Meaning | -| -------- | ------------------------ | ----------------------------------------------------------------------- | -| `id` | `DeleteCallbackId` | Identifier for the [`on_delete`](#method-on_delete) callback to remove. | - -If `id` does not refer to a currently-registered callback, this operation does nothing. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -let id = User::on_delete(|_, _| unreachable!()); - -User::remove_on_delete(id); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -// Invoke a reducer which will delete a `User` row. -delete_user_by_name("Tyler Cloutier".to_string()); - -sleep(Duration::from_secs(1)); - -// No `unreachable` panic. -``` +The `on_delete` callback runs whenever a previously-resident row is deleted from the client cache. Registering an `on_delete` callback returns a callback id, which can later be passed to `remove_on_delete` to cancel the callback. Newly registered or canceled callbacks do not take effect until the following event. ### Trait `TableWithPrimaryKey` ```rust -spacetimedb_sdk::table::TableWithPrimaryKey -``` - -[Generated table structs](#type-table) with a column designated `#[primarykey]` implement the trait `TableWithPrimaryKey`. - -#### Method `on_update` - -```rust -TableWithPrimaryKey::on_update( - callback: impl FnMut(&Self, &Self, Option<&Self::ReducerEvent>) + Send + 'static, -) -> UpdateCallbackId -``` - -Register an `on_update` callback for when an existing row is modified. - -| Argument | Type | Meaning | -| ---------- | ------------------------------------------------------------------ | ----------------------------------------------------- | -| `callback` | `impl FnMut(&Self, &Self, Option<&ReducerEvent>) + Send + 'static` | Callback to run whenever a subscribed row is updated. | - -The callback takes three arguments: - -- `old: &Self`, the previous row value which has been replaced in the database. -- `new: &Self`, the updated row value which is now resident in the database. -- `reducer_event: Option<&ReducerEvent>`, the [`ReducerEvent`](#type-reducerevent) which caused this row to be inserted. - -The returned `UpdateCallbackId` can be passed to [`remove_on_update`](#method-remove_on_update) to remove the callback. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -User::on_update(|old, new, reducer_event| { - println!("User updated by reducer {:?}: from {:?} to {:?}", reducer_event, old, new); -}); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -// Prints a line whenever a `User` row is updated by primary key. -``` - -#### Method `remove_on_update` - -```rust -TableWithPrimaryKey::remove_on_update(id: UpdateCallbackId) -``` - -| Argument | Type | Meaning | -| -------- | ------------------------ | ----------------------------------------------------------------------- | -| `id` | `UpdateCallbackId` | Identifier for the [`on_update`](#method-on_update) callback to remove. | - -Unregister a previously-registered [`on_update`](#method-on_update) callback. - -If `id` does not refer to a currently-registered callback, this operation does nothing. - -```rust -connect(SPACETIMEDB_URI, DB_NAME, None) - .expect("Failed to connect"); - -let id = User::on_update(|_, _, _| unreachable!); - -User::remove_on_update(id); - -subscribe(&["SELECT * FROM User;"]) - .unwrap(); - -// No `unreachable` panic. -``` - -## Observe and request reducer invocations - -### Type `ReducerEvent` - -```rust -module_bindings::ReducerEvent +spacetimedb_sdk::TableWithPrimaryKey ``` -`spacetime generate` defines an enum `ReducerEvent` with a variant for each reducer defined by a module. The variant's name will be the reducer's name converted to `PascalCase`, and the variant will hold an instance of [the autogenerated reducer arguments struct for that reducer](#type-reducerargs). - -[`on_insert`](#method-on_insert), [`on_delete`](#method-on_delete) and [`on_update`](#method-on_update) callbacks accept an `Option<&ReducerEvent>` which identifies the reducer which caused the row to be inserted, deleted or updated. +Implemented for table handles whose tables have a primary key. -### Type `{REDUCER}Args` +#### Callback `on_delete` ```rust -module_bindings::{REDUCER}Args -``` - -For each reducer defined by a module, `spacetime generate` generates a struct whose name is that reducer's name converted to `PascalCase`, suffixed with `Args`. The generated struct has a field for each of the reducer's arguments, whose names are the argument names converted to `snake_case`. - -For reducers which accept a `ReducerContext` as their first argument, the `ReducerContext` is not included in the arguments struct. +trait spacetimedb_sdk::TableWithPrimaryKey { + type UpdateCallbackId; + + fn on_update(&self, callback: impl FnMut(&EventContext, &Self::Row, &Self::Row)) -> Self::UpdateCallbackId; -### Function `{REDUCER}` - -```rust -module_bindings::{REDUCER}({ARGS...}) + fn remove_on_update(&self, callback: Self::UpdateCallbackId); +} ``` -For each reducer defined by a module, `spacetime generate` generates a function which sends a request to the database to invoke that reducer. The generated function's name is the reducer's name converted to `snake_case`. - -For reducers which accept a `ReducerContext` as their first argument, the `ReducerContext` is not included in the generated function's argument list. - -### Function `on_{REDUCER}` - -```rust -module_bindings::on_{REDUCER}( - callback: impl FnMut(&Identity, Option
, Status, {&ARGS...}) + Send + 'static, -) -> ReducerCallbackId<{REDUCER}Args> -``` - -For each reducer defined by a module, `spacetime generate` generates a function which registers a `FnMut` callback to run each time the reducer is invoked. The generated functions are named `on_{REDUCER}`, where `{REDUCER}` is the reducer's name converted to `snake_case`. - -| Argument | Type | Meaning | -| ---------- | ----------------------------------------------------------------------------- | ------------------------------------------------ | -| `callback` | `impl FnMut(&Identity, Option
&Status, {&ARGS...}) + Send + 'static` | Callback to run whenever the reducer is invoked. | - -The callback always accepts three arguments: - -- `caller_id: &Identity`, the [`Identity`](#type-identity) of the client which invoked the reducer. -- `caller_address: Option
`, the [`Address`](#type-address) of the client which invoked the reducer. This may be `None` for scheduled reducers. +The `on_update` 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. Registering an `on_update` callback returns a callback id, which can later be passed to `remove_on_update` to cancel the callback. Newly registered or canceled callbacks do not take effect until the following event. -In addition, the callback accepts a reference to each of the reducer's arguments. +### Unique constraint index access -Clients will only be notified of reducer runs if either of two criteria is met: +For each unique constraint on a table, its table handle has a method whose name is the unique column name which returns a unique index handle. The unique index handle has a method `.find(desired_val: &Col) -> Option`, where `Col` is the type of the column, and `Row` the type of rows. If a row with `desired_val` in the unique column is resident in the client cache, `.find` returns it. -- The reducer inserted, deleted or updated at least one row to which the client is subscribed. -- The reducer invocation was requested by this client, and the run failed. +### BTree index access -The `on_{REDUCER}` function returns a `ReducerCallbackId<{REDUCER}Args>`, where `{REDUCER}Args` is the [generated reducer arguments struct](#type-reducerargs). This `ReducerCallbackId` can be passed to the [generated `remove_on_{REDUCER}` function](#function-remove_on_reducer) to cancel the callback. +Not currently implemented in the Rust SDK. Coming soon! -### Function `once_on_{REDUCER}` +## Observe and invoke reducers -```rust -module_bindings::once_on_{REDUCER}( - callback: impl FnOnce(&Identity, Option
, &Status, {&ARGS...}) + Send + 'static, -) -> ReducerCallbackId<{REDUCER}Args> -``` - -For each reducer defined by a module, `spacetime generate` generates a function which registers a `FnOnce` callback to run the next time the reducer is invoked. The generated functions are named `once_on_{REDUCER}`, where `{REDUCER}` is the reducer's name converted to `snake_case`. +Both [`DbConnection`](#type-dbconnection) and [`EventContext`](#type-eventcontext) have fields `.reducers`, which in turn has methods for invoking reducers defined by the module and registering callbacks on it. The trait method `DbContext::reducers(&self)` can also be used in contexts with an `impl DbContext` rather than a concrete-typed `EventContext` or `DbConnection`. -| Argument | Type | Meaning | -| ---------- | ------------------------------------------------------------------------------- | ----------------------------------------------------- | -| `callback` | `impl FnOnce(&Identity, Option
, &Status, {&ARGS...}) + Send + 'static` | Callback to run the next time the reducer is invoked. | +Each reducer defined by the module has three methods on the `.reducers`: -The callback accepts the same arguments as an [on-reducer callback](#function-on_reducer), but may be a `FnOnce` rather than a `FnMut`. +- An invoke method, whose name is the reducer's name converted to snake case. This requests that the module run the reducer. +- A callback registation method, whose name is prefixed with `on_`. This registers a callback to run whenever we are notified that the reducer ran, including successfully committed runs and runs we requested which failed. This method returns a callback id, which can be passed to the callback remove method. +- A callback remove method, whose name is prefixed with `remove_`. This cancels a callback previously registered via the callback registration method. -The callback will be invoked in the same circumstances as an on-reducer callback. - -The `once_on_{REDUCER}` function returns a `ReducerCallbackId<{REDUCER}Args>`, where `{REDUCER}Args` is the [generated reducer arguments struct](#type-reducerargs). This `ReducerCallbackId` can be passed to the [generated `remove_on_{REDUCER}` function](#function-remove_on_reducer) to cancel the callback. +## Identify a client -### Function `remove_on_{REDUCER}` +### Type `Identity` ```rust -module_bindings::remove_on_{REDUCER}(id: ReducerCallbackId<{REDUCER}Args>) +spacetimedb_sdk::Identity ``` -For each reducer defined by a module, `spacetime generate` generates a function which unregisters a previously-registered [on-reducer](#function-on_reducer) or [once-on-reducer](#function-once_on_reducer) callback. - -| Argument | Type | Meaning | -| -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | -| `id` | `UpdateCallbackId` | Identifier for the [`on_{REDUCER}`](#function-on_reducer) or [`once_on_{REDUCER}`](#function-once_on_reducer) callback to remove. | - -If `id` does not refer to a currently-registered callback, this operation does nothing. +A unique public identifier for a client connected to a database. -### Type `Status` +### Type `Address` ```rust -spacetimedb_sdk::reducer::Status +spacetimedb_sdk::Address ``` -An enum whose variants represent possible reducer completion statuses. - -A `Status` is passed as the second argument to [`on_{REDUCER}`](#function-on_reducer) and [`once_on_{REDUCER}`](#function-once_on_reducer) callbacks. - -#### Variant `Status::Committed` - -The reducer finished successfully, and its row changes were committed to the database. - -#### Variant `Status::Failed(String)` - -The reducer failed, either by panicking or returning an `Err`. - -| Field | Type | Meaning | -| ----- | -------- | --------------------------------------------------- | -| 0 | `String` | The error message which caused the reducer to fail. | - -#### Variant `Status::OutOfEnergy` - -The reducer was canceled because the module owner had insufficient energy to allow it to run to completion. +An opaque identifier for a client connection to a database, intended to differentiate between connections from the same [`Identity`](#type-identity). This will be removed in a future SpacetimeDB version in favor of a connection or session ID. From e6b3f3d621148c0ae738d31bd0fb3f921d5a38a4 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Thu, 3 Oct 2024 02:32:27 -0500 Subject: [PATCH 70/80] Updated rust quickstart for 0.12 (#88) * Updated rust quickstart for 0.12 * Suggested tweaks --------- Co-authored-by: John Detter --- docs/modules/rust/quickstart.md | 91 +++++++++++++++++---------------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/docs/modules/rust/quickstart.md b/docs/modules/rust/quickstart.md index d3544f19..9fcfe30d 100644 --- a/docs/modules/rust/quickstart.md +++ b/docs/modules/rust/quickstart.md @@ -2,27 +2,28 @@ In this tutorial, we'll implement a simple chat server as a SpacetimeDB module. -A SpacetimeDB module is code that gets compiled to WebAssembly and is uploaded to SpacetimeDB. This code becomes server-side logic that interfaces directly with the Spacetime relational database. +A SpacetimeDB module is code that gets compiled to a WebAssembly binary and is uploaded to SpacetimeDB. This code becomes server-side logic that interfaces directly with the SpacetimeDB relational database. Each SpacetimeDB module defines a set of tables and a set of reducers. -Each table is defined as a Rust `struct` annotated with `#[spacetimedb(table)]`, where an instance represents a row, and each field represents a column. +Each table is defined as a Rust struct annotated with `#[table(name = table_name)]`. An instance of the struct represents a row, and each field represents a column. + By default, tables are **private**. This means that they are only readable by the table owner, and by server module code. -The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. +The `#[table(name = table_name, public)]` macro makes a table public. **Public** tables are readable by all users but can still only be modified by your server module code. _Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ -A reducer is a function which traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In Rust, reducers are defined as functions annotated with `#[spacetimedb(reducer)]`, and may return a `Result<()>`, with an `Err` return aborting the transaction. +A reducer is a function that traverses and updates the database. Each reducer call runs in its own transaction, and its updates to the database are only committed if the reducer returns successfully. In Rust, reducers are defined as functions annotated with `#[reducer]`, and may return a `Result<()>`, with an `Err` return aborting the transaction. ## Install SpacetimeDB -If you haven't already, start by [installing SpacetimeDB](/install). This will install the `spacetime` command line interface (CLI), which contains all the functionality for interacting with SpacetimeDB. +If you haven't already, start by [installing SpacetimeDB](/install). This will install the `spacetime` command line interface (CLI), which provides all the functionality needed to interact with SpacetimeDB. ## Install Rust Next we need to [install Rust](https://www.rust-lang.org/tools/install) so that we can create our database module. -On MacOS and Linux run this command to install the Rust compiler: +On macOS and Linux run this command to install the Rust compiler: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -47,17 +48,19 @@ spacetime init --lang rust server ## Declare imports -`spacetime init` should have pre-populated `server/src/lib.rs` with a trivial module. Clear it out, so we can write a module that's still pretty simple: a bare-bones chat server. +`spacetime init` should have pre-populated `server/src/lib.rs` with a trivial module. Clear it out so we can write a new, simple module: a bare-bones chat server. To the top of `server/src/lib.rs`, add some imports we'll be using: ```rust -use spacetimedb::{spacetimedb, ReducerContext, Identity, Timestamp}; +use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp}; ``` From `spacetimedb`, we import: -- `spacetimedb`, an attribute macro we'll use to define tables and reducers. +- `table`, a macro used to define SpacetimeDB tables. +- `reducer`, a macro used to define SpacetimeDB reducers. +- `Table`, a rust trait which allows us to interact with tables. - `ReducerContext`, a special argument passed to each reducer. - `Identity`, a unique identifier for each user. - `Timestamp`, a point in time. Specifically, an unsigned 64-bit count of milliseconds since the UNIX epoch. @@ -71,9 +74,9 @@ For each `User`, we'll store their `Identity`, an optional name they can set to To `server/src/lib.rs`, add the definition of the table `User`: ```rust -#[spacetimedb(table(public))] +#[table(name = user, public)] pub struct User { - #[primarykey] + #[primary_key] identity: Identity, name: Option, online: bool, @@ -85,7 +88,7 @@ For each `Message`, we'll store the `Identity` of the user who sent it, the `Tim To `server/src/lib.rs`, add the definition of the table `Message`: ```rust -#[spacetimedb(table(public))] +#[table(name = message, public)] pub struct Message { sender: Identity, sent: Timestamp, @@ -97,19 +100,19 @@ pub struct Message { We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `set_name` which clients can invoke to set their `User.name`. It will validate the caller's chosen name, using a function `validate_name` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.sender`. +Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. It also allows us access to the `db`, which is used to read and manipulate rows in our tables. For now, we only need the `db`, `Identity`, and `ctx.sender`. It's also possible to call `set_name` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. To `server/src/lib.rs`, add: ```rust -#[spacetimedb(reducer)] -/// Clientss invoke this reducer to set their user names. -pub fn set_name(ctx: ReducerContext, name: String) -> Result<(), String> { +#[reducer] +/// Clients invoke this reducer to set their user names. +pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> { let name = validate_name(name)?; - if let Some(user) = User::filter_by_identity(&ctx.sender) { - User::update_by_identity(&ctx.sender, User { name: Some(name), ..user }); + if let Some(user) = ctx.db.user().identity().find(ctx.sender) { + ctx.db.user().identity().update(User { name: Some(name), ..user }) Ok(()) } else { Err("Cannot set name for unknown user".to_string()) @@ -140,17 +143,17 @@ fn validate_name(name: String) -> Result { ## Send messages -We define a reducer `send_message`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Message::insert`, with the `sender` identity and `sent` timestamp taken from the `ReducerContext`. Because `Message` does not have any columns with unique constraints, `Message::insert` is infallible; it does not return a `Result`. +We define a reducer `send_message`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `ctx.db.message().insert(..)`, with the `sender` identity and `sent` timestamp taken from the `ReducerContext`. Because the `Message` table does not have any columns with a unique constraint, `ctx.db.message().insert()` is infallible and does not return a `Result`. To `server/src/lib.rs`, add: ```rust -#[spacetimedb(reducer)] +#[reducer] /// Clients invoke this reducer to send messages. -pub fn send_message(ctx: ReducerContext, text: String) -> Result<(), String> { +pub fn send_message(ctx: &ReducerContext, text: String) -> Result<(), String> { let text = validate_message(text)?; log::info!("{}", text); - Message::insert(Message { + ctx.db.message().insert(Message { sender: ctx.sender, text, sent: ctx.timestamp, @@ -181,40 +184,39 @@ You could extend the validation in `validate_message` in similar ways to `valida ## Set users' online status -Whenever a client connects, the module will run a special reducer, annotated with `#[spacetimedb(connect)]`, if it's defined. By convention, it's named `identity_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. +Whenever a client connects, the module will run a special reducer, annotated with `#[reducer(client_connected)]`, if it's defined. By convention, it's named `client_connected`. We'll use it to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User::filter_by_identity` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `User::update_by_identity` to overwrite it with a row that has `online: true`. If not, we'll use `User::insert` to insert a new row for our new user. All three of these methods are generated by the `#[spacetimedb(table)]` macro, with rows and behavior based on the row attributes. `filter_by_identity` returns an `Option`, because the unique constraint from the `#[primarykey]` attribute means there will be either zero or one matching rows. `insert` returns a `Result<(), UniqueConstraintViolation>` because of the same unique constraint; if we want to overwrite a `User` row, we need to do so explicitly using `update_by_identity`. +We'll use `ctx.db.user().identity().find(ctx.sender)` to look up a `User` row for `ctx.sender`, if one exists. If we find one, we'll use `ctx.db.user().identity().update(..)` to overwrite it with a row that has `online: true`. If not, we'll use `ctx.db.user().insert(..)` to insert a new row for our new user. All three of these methods are generated by the `#[table(..)]` macro, with rows and behavior based on the row attributes. `ctx.db.user().find(..)` returns an `Option`, because of the unique constraint from the `#[primary_key]` attribute. This means there will be either zero or one matching rows. If we used `try_insert` here it would return a `Result<(), UniqueConstraintViolation>` because of the same unique constraint. However, because we're already checking if there is a user with the given sender identity we know that inserting into this table will not fail. Therefore, we use `insert`, which automatically unwraps the result, simplifying the code. If we want to overwrite a `User` row, we need to do so explicitly using `ctx.db.user().identity().update(..)`. To `server/src/lib.rs`, add the definition of the connect reducer: ```rust -#[spacetimedb(connect)] +#[reducer(client_connected)] // Called when a client connects to the SpacetimeDB -pub fn identity_connected(ctx: ReducerContext) { - if let Some(user) = User::filter_by_identity(&ctx.sender) { +pub fn client_connected(ctx: &ReducerContext) { + if let Some(user) = ctx.db.user().identity().find(ctx.sender) { // If this is a returning user, i.e. we already have a `User` with this `Identity`, // set `online: true`, but leave `name` and `identity` unchanged. - User::update_by_identity(&ctx.sender, User { online: true, ..user }); + ctx.db.user().identity().update(User { online: true, ..user }); } else { // If this is a new user, create a `User` row for the `Identity`, // which is online, but hasn't set a name. - User::insert(User { + ctx.db.user().insert(User { name: None, identity: ctx.sender, online: true, - }).unwrap(); + }); } -} -``` +}``` -Similarly, whenever a client disconnects, the module will run the `#[spacetimedb(disconnect)]` reducer if it's defined. By convention, it's named `identity_disconnect`. We'll use it to un-set the `online` status of the `User` for the disconnected client. +Similarly, whenever a client disconnects, the module will run the `#[reducer(client_disconnected)]` reducer if it's defined. By convention, it's named `client_disconnected`. We'll use it to un-set the `online` status of the `User` for the disconnected client. ```rust -#[spacetimedb(disconnect)] +#[reducer(client_disconnected)] // Called when a client disconnects from SpacetimeDB -pub fn identity_disconnected(ctx: ReducerContext) { - if let Some(user) = User::filter_by_identity(&ctx.sender) { - User::update_by_identity(&ctx.sender, User { online: false, ..user }); +pub fn identity_disconnected(ctx: &ReducerContext) { + if let Some(user) = ctx.db.user().identity().find(ctx.sender) { + ctx.db.user().identity().update(User { online: false, ..user }); } else { // This branch should be unreachable, // as it doesn't make sense for a client to disconnect without connecting first. @@ -225,7 +227,7 @@ pub fn identity_disconnected(ctx: ReducerContext) { ## Publish the module -And that's all of our module code! We'll run `spacetime publish` to compile our module and publish it on SpacetimeDB. `spacetime publish` takes an optional name which will map to the database's unique address. Clients can connect either by name or by address, but names are much more pleasant. Come up with a unique name that contains only URL-safe characters (letters, numbers, hyphens and underscores), and fill it in where we've written ``. +And that's all of our module code! We'll run `spacetime publish` to compile our module and publish it on SpacetimeDB. `spacetime publish` takes an optional name which will map to the database's unique address. Clients can connect either by name or by address, but names are much more user-friendly. Come up with a unique name that contains only URL-safe characters (letters, numbers, hyphens and underscores), and fill it in where we've written ``. From the `quickstart-chat` directory, run: @@ -250,7 +252,10 @@ spacetime logs You should now see the output that your module printed in the database. ```bash -info: Hello, World! + INFO: spacetimedb: Creating table `message` + INFO: spacetimedb: Creating table `user` + INFO: spacetimedb: Database initialized + INFO: src/lib.rs:43: Hello, world! ``` ## SQL Queries @@ -258,13 +263,13 @@ info: Hello, World! SpacetimeDB supports a subset of the SQL syntax so that you can easily query the data of your database. We can run a query using the `sql` command. ```bash -spacetime sql "SELECT * FROM Message" +spacetime sql "SELECT * FROM message" ``` ```bash - text ---------- - "Hello, World!" + sender | sent | text +--------------------------------------------------------------------+------------------+----------------- + 0x93dda09db9a56d8fa6c024d843e805d8262191db3b4ba84c5efcd1ad451fed4e | 1727858455560802 | "Hello, world!" ``` ## What's next? From ed651d71958c516ce1b60b65f922f039a2e911b3 Mon Sep 17 00:00:00 2001 From: John Detter <4099508+jdetter@users.noreply.github.com> Date: Thu, 3 Oct 2024 02:32:41 -0500 Subject: [PATCH 71/80] Update rust index page for 0.12 (#89) * Updated rust quickstart for 0.12 * Suggested tweaks * Initial updates to the index file * More updates to index, rolled back changes from another PR I'm working on * Small improvements --------- Co-authored-by: John Detter --- docs/modules/rust/index.md | 150 ++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 443f8171..83a751be 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -23,7 +23,7 @@ struct Location { Let's start with a highly commented example, straight from the [demo]. This Rust package defines a SpacetimeDB module, with types we can operate on and functions we can run. ```rust -// In this small example, we have two rust imports: +// In this small example, we have two Rust imports: // |spacetimedb::spacetimedb| is the most important attribute we'll be using. // |spacetimedb::println| is like regular old |println|, but outputting to the module's logs. use spacetimedb::{spacetimedb, println}; @@ -31,7 +31,7 @@ use spacetimedb::{spacetimedb, println}; // This macro lets us interact with a SpacetimeDB table of Person rows. // We can insert and delete into, and query, this table by the collection // of functions generated by the macro. -#[spacetimedb(table(public))] +#[table(name = person, public)] pub struct Person { name: String, } @@ -39,26 +39,26 @@ pub struct Person { // This is the other key macro we will be using. A reducer is a // stored procedure that lives in the database, and which can // be invoked remotely. -#[spacetimedb(reducer)] -pub fn add(name: String) { +#[reducer] +pub fn add(ctx: &ReducerContext, name: String) { // |Person| is a totally ordinary Rust struct. We can construct // one from the given name as we typically would. let person = Person { name }; // Here's our first generated function! Given a |Person| object, // we can insert it into the table: - Person::insert(person) + ctx.db.person().insert(person); } // Here's another reducer. Notice that this one doesn't take any arguments, while // |add| did take one. Reducers can take any number of arguments, as long as -// SpacetimeDB knows about all their types. Reducers also have to be top level +// SpacetimeDB recognizes their types. Reducers also have to be top level // functions, not methods. -#[spacetimedb(reducer)] -pub fn say_hello() { +#[reducer] +pub fn say_hello(ctx: &ReducerContext) { // Here's the next of our generated functions: |iter()|. This // iterates over all the columns in the |Person| table in SpacetimeDB. - for person in Person::iter() { + for person in ctx.db.person().iter() { // Reducers run in a very constrained and sandboxed environment, // and in particular, can't do most I/O from the Rust standard library. // We provide an alternative |spacetimedb::println| which is just like @@ -72,13 +72,13 @@ pub fn say_hello() { // the reducer must have a return type of `Result<(), T>`, for any `T` that // implements `Debug`. Such errors returned from reducers will be formatted and // printed out to logs. -#[spacetimedb(reducer)] -pub fn add_person(name: String) -> Result<(), String> { +#[reducer] +pub fn add_person(ctx: &ReducerContext, name: String) -> Result<(), String> { if name.is_empty() { return Err("Name cannot be empty"); } - Person::insert(Person { name }) + ctx.db.person().insert(Person { name }) } ``` @@ -88,15 +88,15 @@ Now we'll get into details on all the macro APIs SpacetimeDB provides, starting ### Defining tables -The `#[spacetimedb(table)]` is applied to a Rust struct with named fields. +The `#[table(name = table_name)]` macro is applied to a Rust struct with named fields. By default, tables are considered **private**. This means that they are only readable by the table owner, and by server module code. -The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. +The `#[table(name = table_name, public)]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. _Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ ```rust -#[spacetimedb(table(public))] -struct Table { +#[table(name = my_table, public)] +struct MyTable { field1: String, field2: u32, } @@ -104,7 +104,7 @@ struct Table { This attribute is applied to Rust structs in order to create corresponding tables in SpacetimeDB. Fields of the Rust struct correspond to columns of the database table. -The fields of the struct have to be types that spacetimedb knows how to encode into the database. This is captured in Rust by the `SpacetimeType` trait. +The fields of the struct have to be types that SpacetimeDB knows how to encode into the database. This is captured in Rust by the `SpacetimeType` trait. This is automatically defined for built in numeric types: @@ -120,10 +120,10 @@ And common data structures: - `Option where T: SpacetimeType` - `Vec where T: SpacetimeType` -All `#[spacetimedb(table)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. +All `#[table(..)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. ```rust -#[spacetimedb(table(public))] +#[table(name = another_table, public)] struct AnotherTable { // Fine, some builtin types. id: u64, @@ -155,7 +155,7 @@ enum Serial { Once the table is created via the macro, other attributes described below can control more aspects of the table. For instance, a particular column can be indexed, or take on values of an automatically incremented counter. These are described in detail below. ```rust -#[spacetimedb(table(public))] +#[table(name = person, public)] struct Person { #[unique] id: u64, @@ -167,30 +167,30 @@ struct Person { ### Defining reducers -`#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. +`#[reducer]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. ```rust -#[spacetimedb(reducer)] -fn give_player_item(player_id: u64, item_id: u64) -> Result<(), GameErr> { +#[reducer] +fn give_player_item(ctx: &ReducerContext, player_id: u64, item_id: u64) -> Result<(), GameErr> { // Notice how the exact name of the filter function derives from // the name of the field of the struct. - let mut item = Item::find_by_item_id(id).ok_or(GameErr::InvalidId)?; + let mut item = ctx.db.item().item_id().find(id).ok_or(GameErr::InvalidId)?; item.owner = Some(player_id); - Item::update_by_id(id, item); + ctx.db.item().item_id().update(item); Ok(()) } +#[table(name = item, public)] struct Item { - #[unique] + #[primary_key] item_id: u64, - owner: Option, } ``` Note that reducers can call non-reducer functions, including standard library functions. -There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. +There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[primary_key]`, `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. #[SpacetimeType] @@ -202,7 +202,7 @@ Tables can be used to schedule a reducer calls either at a specific timestamp or ```rust // The `scheduled` attribute links this table to a reducer. -#[spacetimedb(table, scheduled(send_message))] +#[table(name = send_message_timer, scheduled(send_message)] struct SendMessageTimer { text: String, } @@ -211,10 +211,10 @@ struct SendMessageTimer { The `scheduled` attribute adds a couple of default fields and expands as follows: ```rust -#[spacetimedb(table)] +#[table(name = send_message_timer, scheduled(send_message)] struct SendMessageTimer { text: String, // original field - #[primary] + #[primary_key] #[autoinc] scheduled_id: u64, // identifier for internal purpose scheduled_at: ScheduleAt, //schedule details @@ -230,21 +230,21 @@ pub enum ScheduleAt { } ``` -Managing timers with scheduled table is as simple as inserting or deleting rows from table. +Managing timers with a scheduled table is as simple as inserting or deleting rows from the table. ```rust -#[spacetimedb(reducer)] - -// Reducers linked to the scheduler table should have their first argument as `ReducerContext` +#[reducer] +// Reducers linked to the scheduler table should have their first argument as `&ReducerContext` // and the second as an instance of the table struct it is linked to. -fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { +fn send_message(ctx: &ReducerContext, arg: SendMessageTimer) -> Result<(), String> { // ... } // Scheduling reducers inside `init` reducer -fn init() { +#[reducer(init)] +fn init(ctx: &ReducerContext) { // Scheduling a reducer for a specific Timestamp - SendMessageTimer::insert(SendMessageTimer { + ctx.db.send_message_timer().insert(SendMessageTimer { scheduled_id: 1, text:"bot sending a message".to_string(), //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. @@ -252,7 +252,7 @@ fn init() { }); // Scheduling a reducer to be called at fixed interval of 100 milliseconds. - SendMessageTimer::insert(SendMessageTimer { + ctx.db.send_message_timer().insert(SendMessageTimer { scheduled_id: 0, text:"bot sending a message".to_string(), //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. @@ -282,8 +282,8 @@ use spacetimedb::{ dbg, }; -#[spacetimedb(reducer)] -fn output(i: i32) { +#[reducer] +fn output(ctx: &ReducerContext, i: i32) { // These will be logged at log::Level::Info. println!("an int with a trailing newline: {i}"); print!("some more text...\n"); @@ -297,7 +297,7 @@ fn output(i: i32) { // before passing the value of |i| along to the calling function. // // The output is logged log::Level::Debug. - OutputtedNumbers::insert(dbg!(i)); + ctx.db.outputted_number().insert(dbg!(i)); } ``` @@ -308,7 +308,7 @@ We'll work off these structs to see what functions SpacetimeDB generates: This table has a plain old column. ```rust -#[spacetimedb(table(public))] +#[table(name = ordinary, public)] struct Ordinary { ordinary_field: u64, } @@ -317,7 +317,7 @@ struct Ordinary { This table has a unique column. Every row in the `Unique` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust -#[spacetimedb(table(public))] +#[table(name = unique, public)] struct Unique { // A unique column: #[unique] @@ -330,7 +330,7 @@ This table has an automatically incrementing column. SpacetimeDB automatically p Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. ```rust -#[spacetimedb(table(public))] +#[table(name = autoinc, public)] struct Autoinc { #[autoinc] autoinc_field: u64, @@ -340,7 +340,7 @@ struct Autoinc { These attributes can be combined, to create an automatically assigned ID usable for filtering. ```rust -#[spacetimedb(table(public))] +#[table(name = identity, public)] struct Identity { #[autoinc] #[unique] @@ -352,15 +352,15 @@ struct Identity { We'll talk about insertion first, as there a couple of special semantics to know about. -When we define |Ordinary| as a spacetimedb table, we get the ability to insert into it with the generated `Ordinary::insert` method. +When we define |Ordinary| as a SpacetimeDB table, we get the ability to insert into it with the generated `ctx.db.ordinary().insert(..)` method. Inserting takes a single argument, the row to insert. When there are no unique fields in the row, the return value is the inserted row. ```rust -#[spacetimedb(reducer)] -fn insert_ordinary(value: u64) { +#[reducer] +fn insert_ordinary(ctx: &ReducerContext, value: u64) { let ordinary = Ordinary { ordinary_field: value }; - let result = Ordinary::insert(ordinary); + let result = ctx.db.ordinary().insert(ordinary); assert_eq!(ordinary.ordinary_field, result.ordinary_field); } ``` @@ -370,12 +370,12 @@ When there is a unique column constraint on the table, insertion can fail if a u If we insert two rows which have the same value of a unique column, the second will fail. ```rust -#[spacetimedb(reducer)] -fn insert_unique(value: u64) { - let result = Unique::insert(Unique { unique_field: value }); +#[reducer] +fn insert_unique(ctx: &ReducerContext, value: u64) { + let result = ctx.db.unique().insert(Unique { unique_field: value }); assert!(result.is_ok()); - let result = Unique::insert(Unique { unique_field: value }); + let result = ctx.db.unique().insert(Unique { unique_field: value }); assert!(result.is_err()); } ``` @@ -385,26 +385,26 @@ When inserting a table with an `#[autoinc]` column, the database will automatica The returned row has the `autoinc` column set to the value that was actually written into the database. ```rust -#[spacetimedb(reducer)] -fn insert_autoinc() { +#[reducer] +fn insert_autoinc(ctx: &ReducerContext) { for i in 1..=10 { // These will have values of 1, 2, ..., 10 // at rest in the database, regardless of // what value is actually present in the // insert call. - let actual = Autoinc::insert(Autoinc { autoinc_field: 23 }) + let actual = ctx.db.autoinc().insert(Autoinc { autoinc_field: 23 }) assert_eq!(actual.autoinc_field, i); } } -#[spacetimedb(reducer)] -fn insert_id() { +#[reducer] +fn insert_id(ctx: &ReducerContext) { for _ in 0..10 { // These also will have values of 1, 2, ..., 10. // There's no collision and silent failure to insert, // because the value of the field is ignored and overwritten // with the automatically incremented value. - Identity::insert(Identity { id_field: 23 }) + ctx.db.identity().insert(Identity { id_field: 23 }) } } ``` @@ -414,7 +414,7 @@ fn insert_id() { Given a table, we can iterate over all the rows in it. ```rust -#[spacetimedb(table(public))] +#[table(name = person, public)] struct Person { #[unique] id: u64, @@ -425,20 +425,20 @@ struct Person { } ``` -// Every table structure an iter function, like: +// Every table structure has a generated iter function, like: ```rust -fn MyTable::iter() -> TableIter +ctx.db.my_table().iter() ``` `iter()` returns a regular old Rust iterator, giving us a sequence of `Person`. The database sends us over rows, one at a time, for each time through the loop. This means we get them by value, and own the contents of `String` fields and so on. ``` -#[spacetimedb(reducer)] -fn iteration() { +#[reducer] +fn iteration(ctx: &ReducerContext) { let mut addresses = HashSet::new(); - for person in Person::iter() { + for person in ctx.db.person().iter() { addresses.insert(person.address); } @@ -457,9 +457,9 @@ Our `Person` table has a unique id column, so we can filter for a row matching t The name of the filter method just corresponds to the column name. ```rust -#[spacetimedb(reducer)] -fn filtering(id: u64) { - match Person::find_by_id(&id) { +#[reducer] +fn filtering(ctx: &ReducerContext, id: u64) { + match ctx.db.person().id().find(id) { Some(person) => println!("Found {person}"), None => println!("No person with id {id}"), } @@ -469,9 +469,9 @@ fn filtering(id: u64) { Our `Person` table also has a column for age. Unlike IDs, ages aren't unique. Filtering for every person who is 21, then, gives us an `Iterator` rather than an `Option`. ```rust -#[spacetimedb(reducer)] -fn filtering_non_unique() { - for person in Person::find_by_age(&21) { +#[reducer] +fn filtering_non_unique(ctx: &ReducerContext) { + for person in ctx.db.person().age().find(21) { println!("{person} has turned 21"); } } @@ -482,9 +482,9 @@ fn filtering_non_unique() { Like filtering, we can delete by a unique column instead of the entire row. ```rust -#[spacetimedb(reducer)] -fn delete_id(id: u64) { - Person::delete_by_id(&id) +#[reducer] +fn delete_id(ctx: &ReducerContext, id: u64) { + ctx.db.person().id().delete(id) } ``` From 7de9e7b57a4e97a826f6457d6be44ecbee1ef68b Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 4 Oct 2024 02:39:05 -0400 Subject: [PATCH 72/80] Added migration guide for v0.12 (#95) * Added initial migration guide for v0.12 * My C# additions so far * [v0.12-migration-guide]: build and style fixes * Polished migration guide * [v0.12-migration-guide]: docs update * [v0.12-migration-guide]: C# TODOs * [v0.12-migration-guide]: review --------- Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com> Co-authored-by: Zeke Foppa --- docs/migration/v0.12.md | 341 ++++++++++++++++++++++++++++++++++++++++ docs/nav.js | 118 ++++++-------- nav.ts | 3 + 3 files changed, 394 insertions(+), 68 deletions(-) create mode 100644 docs/migration/v0.12.md diff --git a/docs/migration/v0.12.md b/docs/migration/v0.12.md new file mode 100644 index 00000000..9384407f --- /dev/null +++ b/docs/migration/v0.12.md @@ -0,0 +1,341 @@ +# Updating your app for SpacetimeDB v0.12 + +We're excited to release SpacetimeDB v0.12, which includes a major overhaul of our Rust, C# and TypeScript APIs for both modules and clients. In no particular order, our goals with this rewrite were: + +- Our APIs should be as similar as possible in all three languages we support, and in clients and modules, so that you don't have to go to a ton of work figuring out why something works in one place but not somewhere else. +- We should be very explicit about what operations interact with the database and how. In addition to good hygiene, this means that a client can now connect to multiple remote modules at the same time without getting confused. (Some day a module will be able to connect to remote modules too, but we're not there yet.) +- Our APIs should expose low level database operations so you can program your applications to have predictable performance characteristics. An indexed lookup should look different in your code from a full scan, and writing the indexed lookup should be easier. This will help you write your apps as efficiently as possible as we add features to SpacetimeDB. (In the future, as we get more sophisticated at optimizing and evaluating queries, we will offer a higher level logical query API which let's us implement very high performance optimizations and abstract away concerns like indices.) + +The new APIs are a significant improvement to the developer experience of SpacetimeDB and enable some amazing features in the future. They're completely new APIs, so if you run into any trouble, please [ask us for help or share your feedback on Discord!](https://discord.gg/spacetimedb) + +To start migrating, update your SpacetimeDB CLI, and bump the `spacetimedb` and `spacetimedb-sdk` dependency versions to 0.12 in your module and client respectively. + +## Modules + +### The reducer context + +All your reducers must now accept a reducer context as their first argument. In Rust, this is now taken by reference, as `&ReducerContext`. All access to tables now go through methods on the `db` or `Db` field of the `ReducerContext`. + +```rust +#[spacetimedb::reducer] +fn my_reducer(ctx: &ReducerContext) { + for row in ctx.db.my_table().iter() { + // Do something with the row... + } +} +``` + +```csharp +[SpacetimeDB.Reducer] +public static void MyReducer(ReducerContext ctx) { + foreach (var row in ctx.Db.MyTable.Iter()) { + // Do something with the row... + } +} +``` + +### Table names and access methods + +You now must specify a name for every table, distinct from the type name. In Rust, write this as `#[spacetimedb::table(name = my_table)]`. The name you specify here will be the method on `ctx.db` you use to access the table. + +```rust +#[spacetimedb::table(name = my_table)] +struct MyTable { + #[primary_key] + #[auto_inc] + id: u64, + other_column: u32, +} +``` + +```csharp +[SpacetimeDB.Table(Name = "MyTable")] +public partial struct MyTable +{ + [SpacetimeDB.PrimaryKey] + [SpacetimeDB.AutoInc] + public long Id; + public int OtherColumn; +} +``` + +One neat upside of this is that you can now have multiple tables with the same row type! + +```rust +#[spacetimedb::table(name = signed_in_user)] +#[spacetimedb::table(name = signed_out_user)] +struct User { + #[primary_key] + id: Identity, + #[unique] + username: String, +} +``` + +```csharp +[SpacetimeDB.Table(Name = "SignedInUser")] +[SpacetimeDB.Table(Name = "SignedOutUser")] +public partial struct User +{ + [SpacetimeDB.PrimaryKey] + public SpacetimeDB.Identity Id; + [SpacetimeDB.Unique] + public String Username; +} +``` + +### Iterating, counting, inserting, deleting + +Each "table handle" `ctx.db.my_table()` has methods: + +| Rust name | C# name | Behavior | +|-----------|----------|-----------------------------------------| +| `iter` | `Iter` | Iterate over all rows in the table. | +| `count` | `Count` | Return the number of rows in the table. | +| `insert` | `Insert` | Add a new row to the table. | +| `delete` | `Delete` | Delete a given row from the table. | + +### Index access + +Each table handle also has a method for each BTree index and/or unique constraint on the table, which allows you to filter, delete or update by that index. BTree indices' filter and delete methods accept both point and range queries. + +```rust +#[spacetimedb::table( + name = entity, + index(name = location, btree = [x, y]), +)] +struct Entity { + #[primary_key] + #[auto_inc] + id: u64, + x: u32, + y: u32, + #[index(btree)] + faction: String, +} + +#[spacetimedb::reducer] +fn move_entity(ctx: &ReducerContext, entity_id: u64, x: u32, y: u32) { + let entity = ctx.db.entity().id().find(entity_id).expect("No such entity"); + ctx.db.entity.id().update(Entity { x, y, ..entity }); +} + +#[spacetimedb::reducer] +fn log_entities_at_point(ctx: &ReducerContext, x: u32, y: u32) { + for entity in ctx.db.entity().location().filter((x, y)) { + log::info!("Entity {} is at ({}, {})", entity.id, x, y); + } +} + +#[spacetimedb::reducer] +fn delete_faction(ctx: &ReducerContext, faction: String) { + ctx.db.entity().faction().delete(&faction); +} +``` + +```csharp +[SpacetimeDB.Table(Name = "Entity")] +[SpacetimeDB.Table(Name = "SignedOutUser")] +[SpacetimeDB.Index(Name = "Location", BTree = ["X", "Y"])] +[SpacetimeDB.Index(Name = "Faction", BTree = ["Faction"])] +public partial struct Entity +{ + [SpacetimeDB.PrimaryKey] + [SpacetimeDB.AutoInc] + public long Id; + public int X; + public int Y; + public string Faction; +} + +[SpacetimeDB.Reducer] +public static void MoveEntity(SpacetimeDB.ReducerContext ctx, long entityId, int x, int y) { + var entity = ctx.Db.Entity.Id.Find(entityId); + ctx.Db.Entity.Id.Update(new Entity { + Id = entityId, + X = x, + Y = y, + Faction = entity.Faction, + }); +} + +[SpacetimeDB.Reducer] +public static void LogEntitiesAtPoint(SpacetimeDB.ReducerContext ctx, int x, int y) { + foreach(var entity in ctx.Db.Entity.Location.Filter((x, y))) { + SpacetimeDB.Log.Info($"Entity {entity.Id} is at ({x}, {y})"); + } +} + +[SpacetimeDB.Reducer] +public static void DeleteFaction(SpacetimeDB.ReducerContext ctx, string Faction) { + ctx.Db.Entity.Faction.Delete(Faction); +} +``` + +### `query` + +Note that the `query!` macro in Rust and the `.Query()` method in C# have been removed. We plan to replace them with something even better in the future, but for now, you should write your query explicitly, either by accessing an index or multi-column index by chaining `ctx.db.my_table().iter().filter(|row| predicate)`. + +### Built-in reducers + +The Rust syntax for declaring builtin lifecycles have changed. They are now: + +- `#[spacetimedb::reducer(client_connected)]` +- `#[spacetimedb::reducer(client_disconnected)]` +- `#[spacetimedb::reducer(init)]` + +In C# they are now: + +- `[SpacetimeDB.Reducer(SpacetimeDB.ReducerKind.ClientConnected)]` +- `[SpacetimeDB.Reducer(SpacetimeDB.ReducerKind.ClientDisconnected)]` +- `[SpacetimeDB.Reducer(SpacetimeDB.ReducerKind.Init)]` + +## Clients + +Make sure to run `spacetime generate` after updating your module! + +### The connection object + +Your connection to a remote module is now represented by a `DbConnection` object, which holds all state associated with the connection. We encourage you to name the variable that holds your connection `ctx`. + +Construct a `DbConnection` via the [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) with `DbConnection::builder()` or your language's equivalent. Register on-connect and on-disconnect callbacks while constructing the connection via the builder. + +> NOTE: The APIs for the the `DbConnection` and `ReducerContext` are quite similar, allowing you to write the same patterns on both the client and server. + +### Polling the `DbConnection` + +In Rust, you now must explicitly poll your `DbConnection` to advance, where previously it ran automatically in the background. This provides a much greater degree of flexibility to choose your own async runtime and to work under the variety of exciting constraints imposed by game development - for example, you can now arrange it so that all your callbacks run on the main thread if you want to make GUI calls. You can recreate the previous behavior by calling `ctx.run_threaded()` immediately after buidling your connection. You can also call `ctx.run_async()`, or manually call `ctx.frame_tick()` at an appropriate interval. + +In C# the existing API already required you explictly poll your `DbConnection`, so not much has changed there. The `Update()` method is now called `FrameTick()`. + +### Subscribing to queries + +We're planning a major overhaul of the API for subscribing to queries, but we're not quite there yet. This means that our subscription APIs are not yet as consistent as will soon be. + +#### Rust + +Subscribe to a set of queries by creating a subscription builder and calling `subscribe`. + +```rust +ctx.subscription_builder() + .on_applied(|ctx| { ... }) + .subscribe([ + "SELECT * FROM my_table", + "SELECT * FROM other_table WHERE some_column = 123" + ]); +``` + +The `on_applied` callback is optional. A temporarily limitation of this API is that you should add all your subscription queries at one time for any given connection. + +#### C# + +```csharp +ctx.SubscriptionBuilder() + .OnApplied(ctx => { ... }) + .Subscribe( + "SELECT * FROM MyTable", + "SELECT * FROM OtherTable WHERE SomeColumn = 123" + ); +``` + +#### TypeScript + +```ts +ctx.subscriptionBuilder() + .onApplied(ctx => { ... }) + .subscribe([ + "SELECT * FROM my_table", + "SELECT * FROM other_table WHERE some_column = 123" + ]); +``` + +### Accessing tables + +As in modules, all accesses to your connection's client cache now go through the `ctx.db`. Support for client-side indices is not yet consistent across all our SDKs, so for now you may find that you can't make some queries in clients which you could make in modules. The table handles also expose row callbacks. + +### Observing and invoking reducers + +Register reducer callbacks and request reducer invocations by going through `ctx.reducers`. You can also add functions to subscribe to reducer events that the server sends when a particular reducer is executed. + +#### Rust + +```rust +ctx.reducers.my_reducer(my_first_arg, my_second_arg, ...); + +// Add a callback for each reducer event for `my_reducer` +let callback_id = ctx.reducers.on_my_reducer(|ctx, first_arg, second_arg, ...| { + ... +}); + +// Unregister the callback +ctx.reducers.remove_my_reducer(callback_id); +``` + +#### C# + +```cs +ctx.Reducers.MyReducer(myFirstArg, mySecondArg, ...); + +// Add a callback for each reducer event for `MyReducer` +void OnMyReducerCallback(EventContext ctx) { + ... +} +ctx.Reducers.OnMyReducer += OnMyReducerCallback; + +// Unregister the callback +ctx.Reducers.OnMyReducer -= OnMyReducerCallback; +``` + +#### TypeScript + +```ts +ctx.reducers.myReducer(myFirstArg, mySecondArg, ...); + +// Add a callback for each reducer event for `my_reducer` +const callback = (ctx, firstArg, secondArg, ...) => { + ... +}; +ctx.reducers.onMyReducer(callback); + +// Unregister the callback +ctx.reducers.removeMyReducer(callback); +``` + +### The event context + +Most callbacks now take a first argument of type `&EventContext`. This is just like your `DbConnection`, but it has an additional field `event: Event`. `Event` is an enum, tagged union, or sum type which encodes all the different events the SDK can observe. This fills the same role as `ReducerEvent` used to, but `Event` is more specific and more accurate to what actually happened. + +```rust +ctx.reducers.on_my_reducer(|ctx, first_arg, second_arg, ...| { + match ctx.event { + Reducer(reducer_event) => { + ... + }, + _ => unreachable!(); + } +}); +``` + +#### C# + +```csharp +ctx.Reducers.OnMyReducer += (ctx, firstArg, secondArg, ...) => { + switch (ctx.Event) { + case Event.Reducer (var value): + var reducerEvent = value.Reducer; + ... + break; + } +}; +``` + +#### TypeScript + +```ts +ctx.reducers.onMyReducer((ctx, firstArg, secondArg, ...) => { + if (ctx.event.tag === 'Reducer') { + const reducerEvent = ctx.event.value; + ... + } +}); +``` diff --git a/docs/nav.js b/docs/nav.js index 5a669500..a43c2e29 100644 --- a/docs/nav.js +++ b/docs/nav.js @@ -1,75 +1,57 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); function page(title, slug, path, props) { - return { type: 'page', path, slug, title, ...props }; + return { type: 'page', path, slug, title, ...props }; } function section(title) { - return { type: 'section', title }; + return { type: 'section', title }; } const nav = { - items: [ - section('Intro'), - page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? - page('Getting Started', 'getting-started', 'getting-started.md'), - section('Deploying'), - page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - section('Unity Tutorial - Basic Multiplayer'), - page('Overview', 'unity-tutorial', 'unity/index.md'), - page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), - page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), - page( - '2b - Server (C#)', - 'unity/part-2b-c-sharp', - 'unity/part-2b-c-sharp.md' - ), - page('3 - Client', 'unity/part-3', 'unity/part-3.md'), - section('Unity Tutorial - Advanced'), - page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), - page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), - section('Server Module Languages'), - page('Overview', 'modules', 'modules/index.md'), - page( - 'Rust Quickstart', - 'modules/rust/quickstart', - 'modules/rust/quickstart.md' - ), - page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), - page( - 'C# Quickstart', - 'modules/c-sharp/quickstart', - 'modules/c-sharp/quickstart.md' - ), - page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), - section('Client SDK Languages'), - page('Overview', 'sdks', 'sdks/index.md'), - page( - 'Typescript Quickstart', - 'sdks/typescript/quickstart', - 'sdks/typescript/quickstart.md' - ), - page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), - page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), - page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), - page( - 'C# Quickstart', - 'sdks/c-sharp/quickstart', - 'sdks/c-sharp/quickstart.md' - ), - page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), - section('WebAssembly ABI'), - page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), - section('HTTP API'), - page('HTTP', 'http', 'http/index.md'), - page('`/identity`', 'http/identity', 'http/identity.md'), - page('`/database`', 'http/database', 'http/database.md'), - page('`/energy`', 'http/energy', 'http/energy.md'), - section('WebSocket API Reference'), - page('WebSocket', 'ws', 'ws/index.md'), - section('Data Format'), - page('SATN', 'satn', 'satn.md'), - page('BSATN', 'bsatn', 'bsatn.md'), - section('SQL'), - page('SQL Reference', 'sql', 'sql/index.md'), - ], + items: [ + section('Intro'), + page('Overview', 'index', 'index.md'), // TODO(BREAKING): For consistency & clarity, 'index' slug should be renamed 'intro'? + page('Getting Started', 'getting-started', 'getting-started.md'), + section('Deploying'), + page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + section('Migration Guides'), + page('v0.12', 'migration/v0.12', 'migration/v0.12.md'), + section('Unity Tutorial - Basic Multiplayer'), + page('Overview', 'unity-tutorial', 'unity/index.md'), + page('1 - Setup', 'unity/part-1', 'unity/part-1.md'), + page('2a - Server (Rust)', 'unity/part-2a-rust', 'unity/part-2a-rust.md'), + page('2b - Server (C#)', 'unity/part-2b-c-sharp', 'unity/part-2b-c-sharp.md'), + page('3 - Client', 'unity/part-3', 'unity/part-3.md'), + section('Unity Tutorial - Advanced'), + page('4 - Resources And Scheduling', 'unity/part-4', 'unity/part-4.md'), + page('5 - BitCraft Mini', 'unity/part-5', 'unity/part-5.md'), + section('Server Module Languages'), + page('Overview', 'modules', 'modules/index.md'), + page('Rust Quickstart', 'modules/rust/quickstart', 'modules/rust/quickstart.md'), + page('Rust Reference', 'modules/rust', 'modules/rust/index.md'), + page('C# Quickstart', 'modules/c-sharp/quickstart', 'modules/c-sharp/quickstart.md'), + page('C# Reference', 'modules/c-sharp', 'modules/c-sharp/index.md'), + section('Client SDK Languages'), + page('Overview', 'sdks', 'sdks/index.md'), + page('Typescript Quickstart', 'sdks/typescript/quickstart', 'sdks/typescript/quickstart.md'), + page('Typescript Reference', 'sdks/typescript', 'sdks/typescript/index.md'), + page('Rust Quickstart', 'sdks/rust/quickstart', 'sdks/rust/quickstart.md'), + page('Rust Reference', 'sdks/rust', 'sdks/rust/index.md'), + page('C# Quickstart', 'sdks/c-sharp/quickstart', 'sdks/c-sharp/quickstart.md'), + page('C# Reference', 'sdks/c-sharp', 'sdks/c-sharp/index.md'), + section('WebAssembly ABI'), + page('Module ABI Reference', 'webassembly-abi', 'webassembly-abi/index.md'), + section('HTTP API'), + page('HTTP', 'http', 'http/index.md'), + page('`/identity`', 'http/identity', 'http/identity.md'), + page('`/database`', 'http/database', 'http/database.md'), + page('`/energy`', 'http/energy', 'http/energy.md'), + section('WebSocket API Reference'), + page('WebSocket', 'ws', 'ws/index.md'), + section('Data Format'), + page('SATN', 'satn', 'satn.md'), + page('BSATN', 'bsatn', 'bsatn.md'), + section('SQL'), + page('SQL Reference', 'sql', 'sql/index.md'), + ], }; exports.default = nav; diff --git a/nav.ts b/nav.ts index 19e69c76..0d191439 100644 --- a/nav.ts +++ b/nav.ts @@ -35,6 +35,9 @@ const nav: Nav = { section('Deploying'), page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), + + section('Migration Guides'), + page('v0.12', 'migration/v0.12', 'migration/v0.12.md'), section('Unity Tutorial - Basic Multiplayer'), page('Overview', 'unity-tutorial', 'unity/index.md'), From 5ff556956cd3f4fe2b7279baa4f594ef86388885 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Fri, 4 Oct 2024 12:47:39 -0400 Subject: [PATCH 73/80] Whitespace (#98) --- nav.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nav.ts b/nav.ts index 0d191439..8ca41be7 100644 --- a/nav.ts +++ b/nav.ts @@ -35,7 +35,7 @@ const nav: Nav = { section('Deploying'), page('Testnet', 'deploying/testnet', 'deploying/testnet.md'), - + section('Migration Guides'), page('v0.12', 'migration/v0.12', 'migration/v0.12.md'), From 97be43cf04d58e79a24d4ea8ef6269706fdec37b Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Tue, 22 Oct 2024 10:49:41 -0400 Subject: [PATCH 74/80] Add note about integer literal type inference (#100) Companion to https://github.com/clockworklabs/SpacetimeDB/pull/1815 Also fix surrounding example code and text: you filter on indices, not columns. --- docs/modules/rust/index.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 83a751be..24fa82bf 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -419,6 +419,7 @@ struct Person { #[unique] id: u64, + #[index(btree)] age: u32, name: String, address: String, @@ -466,20 +467,40 @@ fn filtering(ctx: &ReducerContext, id: u64) { } ``` -Our `Person` table also has a column for age. Unlike IDs, ages aren't unique. Filtering for every person who is 21, then, gives us an `Iterator` rather than an `Option`. +Our `Person` table also has an index on its `age` column. Unlike IDs, ages aren't unique. Filtering for every person who is 21, then, gives us an `Iterator` rather than an `Option`. ```rust #[reducer] fn filtering_non_unique(ctx: &ReducerContext) { - for person in ctx.db.person().age().find(21) { - println!("{person} has turned 21"); + for person in ctx.db.person().age().filter(21u32) { + println!("{} has turned 21", person.name); } } ``` +> NOTE: An unfortunate interaction between Rust's trait solver and integer literal defaulting rules means that you must specify the types of integer literals passed to `filter` and `find` methods via the suffix syntax, like `21u32`. If you don't, you'll see a compiler error like: +> ``` +> error[E0271]: type mismatch resolving `::Column == u32` +> --> modules/rust-wasm-test/src/lib.rs:356:48 +> | +> 356 | for person in ctx.db.person().age().filter(21) { +> | ------ ^^ expected `u32`, found `i32` +> | | +> | required by a bound introduced by this call +> | +> = note: required for `i32` to implement `BTreeIndexBounds<(u32,), SingleBound>` +> note: required by a bound in `BTreeIndex::::filter` +> | +> 410 | pub fn filter(&self, b: B) -> impl Iterator +> | ------ required by a bound in this associated function +> 411 | where +> 412 | B: BTreeIndexBounds, +> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `BTreeIndex::::filter` +> ``` + ### Deleting -Like filtering, we can delete by a unique column instead of the entire row. +Like filtering, we can delete by an indexed or unique column instead of the entire row. ```rust #[reducer] From 158a98a03d1a47092bcd2b5b3984da88665f9a0d Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:43:15 -0700 Subject: [PATCH 75/80] Update quickstart.md --- docs/modules/c-sharp/quickstart.md | 65 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 5d8c873d..38061021 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -60,18 +60,15 @@ spacetime init --lang csharp server To the top of `server/Lib.cs`, add some imports we'll be using: ```csharp -using System.Runtime.CompilerServices; -using SpacetimeDB.Module; -using static SpacetimeDB.Runtime; +using SpacetimeDB; ``` -- `SpacetimeDB.Module` contains the special attributes we'll use to define tables and reducers in our module. -- `SpacetimeDB.Runtime` contains the raw API bindings SpacetimeDB uses to communicate with the database. +- `SpacetimeDB` contains the special attributes we'll use to define tables and reducers in our module and the raw API bindings SpacetimeDB uses to communicate with the database. We also need to create our static module class which all of the module code will live in. In `server/Lib.cs`, add: ```csharp -static partial class Module +public static partial class Module { } ``` @@ -88,7 +85,7 @@ In `server/Lib.cs`, add the definition of the table `User` to the `Module` class [SpacetimeDB.Table(Public = true)] public partial class User { - [SpacetimeDB.Column(ColumnAttrs.PrimaryKey)] + [SpacetimeDB.PrimaryKey] public Identity Identity; public string? Name; public bool Online; @@ -113,7 +110,7 @@ public partial class Message We want to allow users to set their names, because `Identity` is not a terribly user-friendly identifier. To that effect, we define a reducer `SetName` which clients can invoke to set their `User.Name`. It will validate the caller's chosen name, using a function `ValidateName` which we'll define next, then look up the `User` record for the caller and update it to store the validated name. If the name fails the validation, the reducer will fail. -Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.Sender`. +Each reducer may accept as its first argument a `ReducerContext`, which includes the `Identity` and `Address` of the client that called the reducer, and the `Timestamp` when it was invoked. For now, we only need the `Identity`, `ctx.CallerIdentity`. It's also possible to call `SetName` via the SpacetimeDB CLI's `spacetime call` command without a connection, in which case no `User` record will exist for the caller. We'll return an error in this case, but you could alter the reducer to insert a `User` row for the module owner. You'll have to decide whether the module owner is always online or always offline, though. @@ -125,11 +122,11 @@ public static void SetName(ReducerContext ctx, string name) { name = ValidateName(name); - var user = User.FindByIdentity(ctx.Sender); + var user = ctx.Db.User.Identity.Find(ctx.CallerIdentity); if (user is not null) { user.Name = name; - User.UpdateByIdentity(ctx.Sender, user); + ctx.Db.User.Identity.Update(user); } } ``` @@ -158,7 +155,7 @@ public static string ValidateName(string name) ## Send messages -We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Message.Insert`, with the `Sender` identity and `Time` timestamp taken from the `ReducerContext`. +We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Ctx.Db.Message.Insert`, with the `Sender` identity and `Time` timestamp taken from the `ReducerContext`. In `server/Lib.cs`, add to the `Module` class: @@ -167,13 +164,14 @@ In `server/Lib.cs`, add to the `Module` class: public static void SendMessage(ReducerContext ctx, string text) { text = ValidateMessage(text); - Log(text); - new Message + Log.info(text); + var msg = new Message { - Sender = ctx.Sender, + Sender = ctx.CallerIdentity, Text = text, Sent = ctx.Time.ToUnixTimeMilliseconds(), - }.Insert(); + }; + ctx.Db.Message.Insert(msg); } ``` @@ -200,60 +198,61 @@ You could extend the validation in `ValidateMessage` in similar ways to `Validat ## Set users' online status -In C# modules, you can register for `Connect` and `Disconnect` events by using a special `ReducerKind`. We'll use the `Connect` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. +In C# modules, you can register for `ClientConnected` and `ClientDisconnected` events by using a special `ReducerKind`. We'll use the `ClientConnected` event to create a `User` record for the client if it doesn't yet exist, and to set its online status. -We'll use `User.FindByIdentity` to look up a `User` row for `ctx.Sender`, if one exists. If we find one, we'll use `User.UpdateByIdentity` to overwrite it with a row that has `Online: true`. If not, we'll use `User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `FindByIdentity` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.Column(ColumnAttrs.PrimaryKey)]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `UpdateByIdentity`. +We'll use `ctx.Db.User.Identity.Find` to look up a `User` row for `ctx.CallerIdentity`, if one exists. If we find one, we'll use `ctx.Db.User.Identity.Update` to overwrite it with a row that has `Online: true`. If not, we'll use `ctx.Db.User.Insert` to insert a new row for our new user. All three of these methods are generated by the `[SpacetimeDB.Table]` attribute, with rows and behavior based on the row attributes. `Ctx.Db.User.Identity.Find` returns a nullable `User`, because the unique constraint from the `[SpacetimeDB.PrimaryKey]` attribute means there will be either zero or one matching rows. `Insert` will throw an exception if the insert violates this constraint; if we want to overwrite a `User` row, we need to do so explicitly using `ctx.Db.User.Identity.Update`. In `server/Lib.cs`, add the definition of the connect reducer to the `Module` class: ```csharp -[SpacetimeDB.Reducer(ReducerKind.Connect)] -public static void OnConnect(ReducerContext ReducerContext) +[SpacetimeDB.Reducer(ReducerKind.ClientConnected)] +public static void Connect(ReducerContext ctx) { - Log($"Connect {ReducerContext.Sender}"); - var user = User.FindByIdentity(ReducerContext.Sender); + Log.info($"Connect {ReducerContext.Sender}"); + var user = ctx.Db.User.Identity.Find(ctx.CallerIdentity); if (user is not null) { // If this is a returning user, i.e., we already have a `User` with this `Identity`, // set `Online: true`, but leave `Name` and `Identity` unchanged. user.Online = true; - User.UpdateByIdentity(ReducerContext.Sender, user); + ctx.Db.User.Identity.Update(user); } else { // If this is a new user, create a `User` object for the `Identity`, // which is online, but hasn't set a name. - new User + var user = new User { Name = null, Identity = ReducerContext.Sender, Online = true, - }.Insert(); + }; + ctx.Db.User.Insert(user); } } ``` -Similarly, whenever a client disconnects, the module will execute the `OnDisconnect` event if it's registered with `ReducerKind.Disconnect`. We'll use it to un-set the `Online` status of the `User` for the disconnected client. +Similarly, whenever a client disconnects, the module will execute the `Disconnect` event if it's registered with `ReducerKind.ClientDisconnected`. We'll use it to un-set the `Online` status of the `User` for the disconnected client. Add the following code after the `OnConnect` handler: ```csharp -[SpacetimeDB.Reducer(ReducerKind.Disconnect)] -public static void OnDisconnect(ReducerContext ReducerContext) +[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)] +public static void OnDisconnect(ReducerContext ctx) { - var user = User.FindByIdentity(ReducerContext.Sender); + var user = ctx.Db.User.Identity.Find(ctx.CallerIdentity); if (user is not null) { // This user should exist, so set `Online: false`. user.Online = false; - User.UpdateByIdentity(ReducerContext.Sender, user); + ctx.Db.User.Identity.Update(user); } else { // User does not exist, log warning - Log("Warning: No user found for disconnected client."); + Log.Warning("No user found for disconnected client."); } } ``` @@ -272,6 +271,8 @@ From the `quickstart-chat` directory, run: spacetime publish --project-path server ``` +Install wasm-opt for optimized builds either from NPM or from the [binaryen releases](https://github.com/WebAssembly/binaryen/releases). + ```bash npm i wasm-opt -g ``` @@ -312,6 +313,6 @@ spacetime sql "SELECT * FROM Message" ## What's next? -You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/languages/rust/rust-sdk-quickstart-guide), [C#](/docs/languages/csharp/csharp-sdk-quickstart-guide), or [TypeScript](/docs/languages/typescript/typescript-sdk-quickstart-guide). +You've just set up your first database in SpacetimeDB! The next step would be to create a client module that interacts with this module. You can use any of SpacetimDB's supported client languages to do this. Take a look at the quick start guide for your client language of choice: [Rust](/docs/sdks/rust/quickstart), [C#](/docs/sdks/c-sharp/quickstart), or [TypeScript](/docs/sdks/typescript/quickstart). -If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-3). +If you are planning to use SpacetimeDB with the Unity game engine, you can skip right to the [Unity Comprehensive Tutorial](/docs/unity/part-1) or check out our example game, [BitcraftMini](/docs/unity/part-5). From 7cf7ab176a410b5403703c4eaee16cd31142b5ca Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:47:11 -0700 Subject: [PATCH 76/80] Update quickstart.md --- docs/modules/c-sharp/quickstart.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/c-sharp/quickstart.md b/docs/modules/c-sharp/quickstart.md index 38061021..1882f9b0 100644 --- a/docs/modules/c-sharp/quickstart.md +++ b/docs/modules/c-sharp/quickstart.md @@ -155,7 +155,7 @@ public static string ValidateName(string name) ## Send messages -We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Ctx.Db.Message.Insert`, with the `Sender` identity and `Time` timestamp taken from the `ReducerContext`. +We define a reducer `SendMessage`, which clients will call to send messages. It will validate the message's text, then insert a new `Message` record using `Ctx.Db.Message.Insert`, with the `CallerIdentity` identity and `Time` timestamp taken from the `ReducerContext`. In `server/Lib.cs`, add to the `Module` class: @@ -208,7 +208,7 @@ In `server/Lib.cs`, add the definition of the connect reducer to the `Module` cl [SpacetimeDB.Reducer(ReducerKind.ClientConnected)] public static void Connect(ReducerContext ctx) { - Log.info($"Connect {ReducerContext.Sender}"); + Log.info($"Connect {ctx.CallerIdentity}"); var user = ctx.Db.User.Identity.Find(ctx.CallerIdentity); if (user is not null) @@ -225,7 +225,7 @@ public static void Connect(ReducerContext ctx) var user = new User { Name = null, - Identity = ReducerContext.Sender, + Identity = ctx.CallerIdentity, Online = true, }; ctx.Db.User.Insert(user); From 367198dbc32c7ff894fea58791a49efd7d693854 Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:06:04 -0700 Subject: [PATCH 77/80] Update index.md --- docs/modules/c-sharp/index.md | 165 ++++++++++++++++------------------ 1 file changed, 79 insertions(+), 86 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index f6763fc7..552ede24 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -10,14 +10,13 @@ Let's start with a heavily commented version of the default example from the lan ```csharp // These imports bring into the scope common APIs you'll need to expose items from your module and to interact with the database runtime. -using SpacetimeDB.Module; -using static SpacetimeDB.Runtime; +using SpacetimeDB; // Roslyn generators are statically generating extra code as-if they were part of the source tree, so, // in order to inject new methods, types they operate on as well as their parents have to be marked as `partial`. // // We start with the top-level `Module` class for the module itself. -static partial class Module +public static partial class Module { // `[SpacetimeDB.Table]` registers a struct or a class as a SpacetimeDB table. // @@ -25,9 +24,10 @@ static partial class Module [SpacetimeDB.Table(Public = true)] public partial struct Person { - // `[SpacetimeDB.Column]` allows to specify column attributes / constraints such as + // SpacetimeDB allows you to specify column attributes / constraints such as // "this field should be unique" or "this field should get automatically assigned auto-incremented value". - [SpacetimeDB.Column(ColumnAttrs.Unique | ColumnAttrs.AutoInc)] + [SpacetimeDB.Unique] + [SpacetimeDB.PrimaryKey] public int Id; public string Name; public int Age; @@ -38,29 +38,29 @@ static partial class Module // Reducers are functions that can be invoked from the database runtime. // They can't return values, but can throw errors that will be caught and reported back to the runtime. [SpacetimeDB.Reducer] - public static void Add(string name, int age) + public static void Add(ReducerContext ctx, string name, int age) { // We can skip (or explicitly set to zero) auto-incremented fields when creating new rows. var person = new Person { Name = name, Age = age }; // `Insert()` method is auto-generated and will insert the given row into the table. - person.Insert(); + ctx.Db.Person.Insert(person); // After insertion, the auto-incremented fields will be populated with their actual values. // - // `Log()` function is provided by the runtime and will print the message to the database log. + // `Log` class is provided by the runtime and will print messages to the database log. // It should be used instead of `Console.WriteLine()` or similar functions. - Log($"Inserted {person.Name} under #{person.Id}"); + Log.Info($"Inserted {person.Name} under #{person.Id}"); } [SpacetimeDB.Reducer] - public static void SayHello() + public static void SayHello(ReducerContext ctx) { // Each table type gets a static Iter() method that can be used to iterate over the entire table. - foreach (var person in Person.Iter()) + foreach (var person in ctx.Db.Person.Iter()) { - Log($"Hello, {person.Name}!"); + Log.Info($"Hello, {person.Name}!"); } - Log("Hello, World!"); + Log.Info("Hello, World!"); } } ``` @@ -73,28 +73,21 @@ Now we'll get into details on all the APIs SpacetimeDB provides for writing modu First of all, logging as we're likely going to use it a lot for debugging and reporting errors. -`SpacetimeDB.Runtime` provides a `Log` function that will print the given message to the database log, along with the source location and a log level it was provided. +`SpacetimeDB` provides a `Log` class that will print messages to the database log, along with the source location and a log level it was provided. -Supported log levels are provided by the `LogLevel` enum: +Supported log levels are provided by different methods on the `Log` class: ```csharp -public enum LogLevel -{ - Error, - Warn, - Info, - Debug, - Trace, - Panic + public static void Trace(string message); + public static void Debug(string message); + public static void Info(string message); + public static void Warn(string message); + public static void Error(string message); + public static void Exception(string message); } ``` -If omitted, the log level will default to `Info`, so these two forms are equivalent: - -```csharp -Log("Hello, World!"); -Log("Hello, World!", LogLevel.Info); -``` +You should use `Log.Info` by default. ### Supported types @@ -116,9 +109,9 @@ The following types are supported out of the box and can be stored in the databa And a couple of special custom types: -- `SpacetimeDB.SATS.Unit` - semantically equivalent to an empty struct, sometimes useful in generic contexts where C# doesn't permit `void`. -- `Identity` (`SpacetimeDB.Runtime.Identity`) - a unique identifier for each user; internally a byte blob but can be printed, hashed and compared for equality. -- `Address` (`SpacetimeDB.Runtime.Address`) - an identifier which disamgibuates connections by the same `Identity`; internally a byte blob but can be printed, hashed and compared for equality. +- `SpacetimeDB.Unit` - semantically equivalent to an empty struct, sometimes useful in generic contexts where C# doesn't permit `void`. +- `Identity` (`SpacetimeDB.Identity`) - a unique identifier for each user; internally a byte blob but can be printed, hashed and compared for equality. +- `Address` (`SpacetimeDB.Address`) - an identifier which disamgibuates connections by the same `Identity`; internally a byte blob but can be printed, hashed and compared for equality. #### Custom types @@ -193,7 +186,7 @@ bool IsSome(MyEnum e) => e is not MyEnum.None; // Construct an instance of `MyEnum` with the `String` variant active. var myEnum = new MyEnum.String("Hello, world!"); -Console.WriteLine($"IsSome: {IsSome(myEnum)}"); +Log.Info($"IsSome: {IsSome(myEnum)}"); PrintEnum(myEnum); ``` @@ -211,7 +204,8 @@ It implies `[SpacetimeDB.Type]`, so you must not specify both attributes on the [SpacetimeDB.Table(Public = true)] public partial struct Person { - [SpacetimeDB.Column(ColumnAttrs.Unique | ColumnAttrs.AutoInc)] + [SpacetimeDB.PrimaryKey] + [SpacetimeDB.AutoInc] public int Id; public string Name; public int Age; @@ -221,10 +215,10 @@ public partial struct Person The example above will generate the following extra methods: ```csharp -public partial struct Person +public partial class ReducerCtx.Db.Person { // Inserts current instance as a new row into the table. - public void Insert(); + public void Insert(Person row); // Returns an iterator over all rows in the table, e.g.: // `for (var person in Person.Iter()) { ... }` @@ -234,22 +228,31 @@ public partial struct Person // `for (var person in Person.Query(p => p.Age >= 18)) { ... }` public static IEnumerable Query(Expression> filter); - // Generated for each column: + // Generated for each column with the `Indexed` attribute: - // Returns an iterator over all rows in the table that have the given value in the `Name` column. - public static IEnumerable FilterByName(string name); + public static class Name { + // Returns an iterator over all rows in the table that have the given value in the `Name` column. + public static IEnumerable Filter(string name); + public static void Update(Person row); + public static Person Find(string name); + } + public static class Name { + // Returns an iterator over all rows in the table that have the given value in the `Name` column. + public static IEnumerable Filter(string name); + } public static IEnumerable FilterByAge(int age); // Generated for each unique column: + public static partial class Id { + // Find a `Person` based on `id == $key` + public static Person? Find(int key); - // Finds a row in the table with the given value in the `Id` column and returns it, or `null` if no such row exists. - public static Person? FindById(int id); - - // Deletes a row in the table with the given value in the `Id` column and returns `true` if the row was found and deleted, or `false` if no such row exists. - public static bool DeleteById(int id); + // Delete a row by `key` on the row + public static bool Delete(int key); - // Updates a row in the table with the given value in the `Id` column and returns `true` if the row was found and updated, or `false` if no such row exists. - public static bool UpdateById(int oldId, Person newValue); + // Update based on the `id` of the row + public static bool Update(Person row); + } } ``` @@ -259,38 +262,33 @@ Attribute `[SpacetimeDB.Column]` can be used on any field of a `SpacetimeDB.Tabl The supported column attributes are: -- `ColumnAttrs.AutoInc` - this column should be auto-incremented. -- `ColumnAttrs.Unique` - this column should be unique. -- `ColumnAttrs.PrimaryKey` - this column should be a primary key, it implies `ColumnAttrs.Unique` but also allows clients to subscribe to updates via `OnUpdate` which will use this field to match the old and the new version of the row with each other. - -These attributes are bitflags and can be combined together, but you can also use some predefined shortcut aliases: - -- `ColumnAttrs.Identity` - same as `ColumnAttrs.Unique | ColumnAttrs.AutoInc`. -- `ColumnAttrs.PrimaryKeyAuto` - same as `ColumnAttrs.PrimaryKey | ColumnAttrs.AutoInc`. - +- `SpacetimeDB.AutoInc` - this column should be auto-incremented. +- `SpacetimeDB.Unique` - this column should be unique. +- `SpacetimeDB.PrimaryKey` - this column should be a primary key, it implies `SpacetimeDB.Unique` but also allows clients to subscribe to updates via `OnUpdate` which will use this field to match the old and the new version of the row with each other. +- ### Reducers -Attribute `[SpacetimeDB.Reducer]` can be used on any `static void` method to register it as a SpacetimeDB reducer. The method must accept only supported types as arguments. If it throws an exception, those will be caught and reported back to the database runtime. +Attribute `[SpacetimeDB.Reducer]` can be used on any `public static void` method to register it as a SpacetimeDB reducer. The method must accept only supported types as arguments. If it throws an exception, those will be caught and reported back to the database runtime. ```csharp [SpacetimeDB.Reducer] -public static void Add(string name, int age) +public static void Add(ReducerContext ctx, string name, int age) { var person = new Person { Name = name, Age = age }; - person.Insert(); - Log($"Inserted {person.Name} under #{person.Id}"); + ctx.Db.Person.Insert(person); + Log.Info($"Inserted {person.Name} under #{person.Id}"); } ``` -If a reducer has an argument with a type `ReducerContext` (`SpacetimeDB.Runtime.ReducerContext`), it will be provided with event details such as the sender identity (`SpacetimeDB.Runtime.Identity`), sender address (`SpacetimeDB.Runtime.Address?`) and the time (`DateTimeOffset`) of the invocation: +If a reducer has an argument with a type `ReducerContext` (`SpacetimeDB.ReducerContext`), it will be provided with event details such as the sender identity (`SpacetimeDB.Identity`), sender address (`SpacetimeDB.Address?`) and the time (`DateTimeOffset`) of the invocation: ```csharp [SpacetimeDB.Reducer] -public static void PrintInfo(ReducerContext e) +public static void PrintInfo(ReducerContext ctx) { - Log($"Sender identity: {e.Sender}"); - Log($"Sender address: {e.Address}"); - Log($"Time: {e.Time}"); + Log.Info($"Sender identity: {ctx.CallerIdentity}"); + Log.Info($"Sender address: {ctx.CallerAddress}"); + Log.Info($"Time: {ctx.Timestamp}"); } ``` @@ -325,21 +323,23 @@ public static partial class Timers { // Schedule a one-time reducer call by inserting a row. - new SendMessageTimer + var timer = new SendMessageTimer { Text = "bot sending a message", ScheduledAt = ctx.Time.AddSeconds(10), ScheduledId = 1, - }.Insert(); + }; + ctx.Db.SendMessageTimer.Insert(timer); // Schedule a recurring reducer. - new SendMessageTimer + var timer = new SendMessageTimer { Text = "bot sending a message", ScheduledAt = new TimeStamp(10), ScheduledId = 2, - }.Insert(); + }; + ctx.Db.SendMessageTimer.Insert(timer); } } ``` @@ -354,7 +354,7 @@ public static partial class Timers { public string Text; // fields of original struct - [SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)] + [SpacetimeDB.PrimaryKey] public ulong ScheduledId; // unique identifier to be used internally public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval) @@ -370,34 +370,27 @@ public abstract partial record ScheduleAt: SpacetimeDB.TaggedEnum<(DateTimeOffse These are four special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute: - `ReducerKind.Init` - this reducer will be invoked when the module is first published. -- `ReducerKind.Update` - this reducer will be invoked when the module is updated. -- `ReducerKind.Connect` - this reducer will be invoked when a client connects to the database. -- `ReducerKind.Disconnect` - this reducer will be invoked when a client disconnects from the database. +- `ReducerKind.ClientConnected` - this reducer will be invoked when a client connects to the database. +- `ReducerKind.ClientDisconnected` - this reducer will be invoked when a client disconnects from the database. Example: ````csharp [SpacetimeDB.Reducer(ReducerKind.Init)] -public static void Init() -{ - Log("...and we're live!"); -} - -[SpacetimeDB.Reducer(ReducerKind.Update)] -public static void Update() +public static void Init(ReducerContext ctx) { - Log("Update get!"); + Log.Info("...and we're live!"); } -[SpacetimeDB.Reducer(ReducerKind.Connect)] -public static void OnConnect(DbEventArgs ctx) +[SpacetimeDB.Reducer(ReducerKind.ClientConnected)] +public static void OnConnect(ReducerContext ctx) { - Log($"{ctx.Sender} has connected from {ctx.Address}!"); + Log.Info($"{ctx.CallerIdentity} has connected from {ctx.CallerAddress}!"); } -[SpacetimeDB.Reducer(ReducerKind.Disconnect)] -public static void OnDisconnect(DbEventArgs ctx) +[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)] +public static void OnDisconnect(ReducerContext ctx) { - Log($"{ctx.Sender} has disconnected."); + Log.Info($"{ctx.CallerIdentity} has disconnected."); }``` ```` From 2c74622c37e1f7646be6d11882dd8d0d4ce1bbd8 Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:06:32 -0700 Subject: [PATCH 78/80] Update index.md --- docs/modules/c-sharp/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 552ede24..98014b0b 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -27,7 +27,7 @@ public static partial class Module // SpacetimeDB allows you to specify column attributes / constraints such as // "this field should be unique" or "this field should get automatically assigned auto-incremented value". [SpacetimeDB.Unique] - [SpacetimeDB.PrimaryKey] + [SpacetimeDB.AutoInc] public int Id; public string Name; public int Age; From e262eb4bd45bf53c07a1c4eaa23db11d6a94a1b2 Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:21 -0700 Subject: [PATCH 79/80] Update index.md --- docs/modules/c-sharp/index.md | 45 +++++++++++++---------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 98014b0b..90b90eda 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -78,13 +78,12 @@ First of all, logging as we're likely going to use it a lot for debugging and re Supported log levels are provided by different methods on the `Log` class: ```csharp - public static void Trace(string message); - public static void Debug(string message); - public static void Info(string message); - public static void Warn(string message); - public static void Error(string message); - public static void Exception(string message); -} +public static void Trace(string message); +public static void Debug(string message); +public static void Info(string message); +public static void Warn(string message); +public static void Error(string message); +public static void Exception(string message); ``` You should use `Log.Info` by default. @@ -220,38 +219,26 @@ public partial class ReducerCtx.Db.Person // Inserts current instance as a new row into the table. public void Insert(Person row); - // Returns an iterator over all rows in the table, e.g.: - // `for (var person in Person.Iter()) { ... }` - public static IEnumerable Iter(); - - // Returns an iterator over all rows in the table that match the given filter, e.g.: - // `for (var person in Person.Query(p => p.Age >= 18)) { ... }` - public static IEnumerable Query(Expression> filter); + // Deletes current instance from the table + public void Delete(Person row); - // Generated for each column with the `Indexed` attribute: + // Gets the number of rows in the table + public ulong Count { get { ... } }; - public static class Name { - // Returns an iterator over all rows in the table that have the given value in the `Name` column. - public static IEnumerable Filter(string name); - public static void Update(Person row); - public static Person Find(string name); - } - public static class Name { - // Returns an iterator over all rows in the table that have the given value in the `Name` column. - public static IEnumerable Filter(string name); - } - public static IEnumerable FilterByAge(int age); + // Returns an iterator over all rows in the table, e.g.: + // `for (var person in Person.Iter()) { ... }` + public IEnumerable Iter(); // Generated for each unique column: public static partial class Id { // Find a `Person` based on `id == $key` - public static Person? Find(int key); + public Person? Find(int key); // Delete a row by `key` on the row - public static bool Delete(int key); + public bool Delete(int key); // Update based on the `id` of the row - public static bool Update(Person row); + public bool Update(Person row); } } ``` From 138636d5457999a72e8f83fad5b884cbc66deb21 Mon Sep 17 00:00:00 2001 From: Dillon Shaffer <46535284+Molkars@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:58 -0700 Subject: [PATCH 80/80] Update index.md --- docs/modules/c-sharp/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/c-sharp/index.md b/docs/modules/c-sharp/index.md index 90b90eda..768170ac 100644 --- a/docs/modules/c-sharp/index.md +++ b/docs/modules/c-sharp/index.md @@ -370,13 +370,13 @@ public static void Init(ReducerContext ctx) } [SpacetimeDB.Reducer(ReducerKind.ClientConnected)] -public static void OnConnect(ReducerContext ctx) +public static void Connect(ReducerContext ctx) { Log.Info($"{ctx.CallerIdentity} has connected from {ctx.CallerAddress}!"); } [SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)] -public static void OnDisconnect(ReducerContext ctx) +public static void Disconnect(ReducerContext ctx) { Log.Info($"{ctx.CallerIdentity} has disconnected."); }```