diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json index 06c3ea9a227..5d54f7d2eff 100644 --- a/templates/angular-ts/.template.json +++ b/templates/angular-ts/.template.json @@ -2,5 +2,12 @@ "description": "Angular web app with TypeScript server", "client_framework": "Angular", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "angular", + "rxjs", + "tslib", + "typescript", + "spacetimedb" + ] } diff --git a/templates/angular-ts/README.md b/templates/angular-ts/README.md new file mode 100644 index 00000000000..2dcdcc7241c --- /dev/null +++ b/templates/angular-ts/README.md @@ -0,0 +1,117 @@ +Get a SpacetimeDB Angular app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Angular client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Angular development server. + +```bash +spacetime dev --template angular-ts +``` + + + +## Open your app + +Navigate to [http://localhost:4200](http://localhost:4200) to see your app running. + +The template includes a basic Angular app connected to SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/app/app.component.ts` to build your UI. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ # Angular frontend +│ └── app/ +│ ├── app.component.ts +│ ├── app.config.ts +│ └── module_bindings/ # Auto-generated types +├── angular.json +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/basic-cpp/.template.json b/templates/basic-cpp/.template.json index 983a1b065ff..cabcda719fe 100644 --- a/templates/basic-cpp/.template.json +++ b/templates/basic-cpp/.template.json @@ -1,5 +1,8 @@ { "description": "A basic C++ server template with only stubs for code", "server_lang": "cpp", - "client_lang": "rust" + "client_lang": "rust", + "builtWith": [ + "spacetimedb-sdk" + ] } diff --git a/templates/basic-cpp/README.md b/templates/basic-cpp/README.md new file mode 100644 index 00000000000..4e122dc0e94 --- /dev/null +++ b/templates/basic-cpp/README.md @@ -0,0 +1,118 @@ +Get a SpacetimeDB C++ app running in under 5 minutes. + +## Prerequisites + +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed +- [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) 4.0.21+ installed +- CMake 3.20+ and a make/ninja backend +- C++20 toolchain (host) — build targets WASM via Emscripten + +After installing the SDK, run the appropriate `emsdk_env` script (PowerShell or Bash) so `emcc` and the CMake toolchain file are available on `PATH`. + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Install Emscripten + +Use the official SDK (see [Emscripten downloads](https://emscripten.org/docs/getting_started/downloads.html)) and activate the environment so `emcc` and the CMake toolchain file are on PATH. We recommend Emscripten 4.0.21+. + +```bash +# From your emsdk directory (after downloading/cloning) +# Windows PowerShell +./emsdk install 4.0.21 +./emsdk activate 4.0.21 +./emsdk_env.ps1 + +# macOS/Linux +./emsdk install 4.0.21 +./emsdk activate 4.0.21 +source ./emsdk_env.sh +``` + + + +## Create your project + +Use the CLI-managed workflow with `spacetime build`, which wraps CMake + `emcc` for you, starts the local server, builds/publishes your module, and generates client bindings. + +```bash +spacetime dev --template basic-cpp +``` + + +Need manual control? You can still drive CMake+emcc directly (see `spacetimedb/CMakeLists.txt`), but the recommended path is `spacetime build`/`spacetime dev`. + + + + + +Server code lives in the `spacetimedb` folder; the template uses CMake and the SpacetimeDB C++ SDK. + + +``` +my-spacetime-app/ +├── spacetimedb/ # Your C++ module +│ ├── CMakeLists.txt +│ └── src/ +│ └── lib.cpp # Server-side logic +├── Cargo.toml +└── src/ + └── main.rs # Rust client application +``` + + + +## Understand tables and reducers + +The template includes a `Person` table and two reducers: `add` to insert, `say_hello` to iterate and log. + +```cpp +#include "spacetimedb.h" +using namespace SpacetimeDB; + +struct Person { std::string name; }; +SPACETIMEDB_STRUCT(Person, name) +SPACETIMEDB_TABLE(Person, person, Public) + +SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name) { + ctx.db[person].insert(Person{name}); + return Ok(); +} + +SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) { + for (const auto& person : ctx.db[person]) { + LOG_INFO("Hello, " + person.name + "!"); + } + LOG_INFO("Hello, World!"); + return Ok(); +} +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then call reducers and inspect data right from the CLI. + +```bash +cd my-spacetime-app + +# Insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + +# Call say_hello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +``` + +## Notes + +- To use a local SDK clone instead of the fetched archive, set `SPACETIMEDB_CPP_SDK_DIR` before running `spacetime dev`/`spacetime build`. +- The template builds to WebAssembly with exceptions disabled (`-fno-exceptions`). +- If `emcc` is not found, re-run the appropriate `emsdk_env` script to populate environment variables. diff --git a/templates/basic-cs/.template.json b/templates/basic-cs/.template.json index b85112b2e07..3acf693d379 100644 --- a/templates/basic-cs/.template.json +++ b/templates/basic-cs/.template.json @@ -1,5 +1,9 @@ { "description": "A basic C# client and server template with only stubs for code", "client_lang": "csharp", - "server_lang": "csharp" + "server_lang": "csharp", + "builtWith": [ + "SpacetimeDB.ClientSDK", + "SpacetimeDB.Runtime" + ] } diff --git a/templates/basic-cs/README.md b/templates/basic-cs/README.md new file mode 100644 index 00000000000..d693ce55ad2 --- /dev/null +++ b/templates/basic-cs/README.md @@ -0,0 +1,117 @@ +Get a SpacetimeDB C# app running in under 5 minutes. + +## Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Install .NET WASI workload + +SpacetimeDB C# modules compile to WebAssembly using the WASI experimental workload. + +```bash +dotnet workload install wasi-experimental +``` + + + +## Create your project + +Run the `spacetime dev` command to create a new project with a C# SpacetimeDB module. + +This will start the local SpacetimeDB server, compile and publish your module, and generate C# client bindings. + +```bash +spacetime dev --template basic-cs +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/Lib.cs` to add tables and reducers. Use the generated bindings in the client project. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ ├── StdbModule.csproj +│ └── Lib.cs # Server-side logic +├── client.csproj +├── Program.cs # Client application +└── module_bindings/ # Auto-generated types +``` + + + +## Understand tables and reducers + +Open `spacetimedb/Lib.cs` to see the module code. The template includes a `Person` table and two reducers: `Add` to insert a person, and `SayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```csharp +using SpacetimeDB; + +public static partial class Module +{ + [SpacetimeDB.Table(Accessor = "Person", Public = true)] + public partial struct Person + { + public string Name; + } + + [SpacetimeDB.Reducer] + public static void Add(ReducerContext ctx, string name) + { + ctx.Db.Person.Insert(new Person { Name = name }); + } + + [SpacetimeDB.Reducer] + public static void SayHello(ReducerContext ctx) + { + foreach (var person in ctx.Db.Person.Iter()) + { + Log.Info($"Hello, {person.Name}!"); + } + Log.Info("Hello, World!"); + } +} +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM Person" + name +--------- + "Alice" + +# Call say_hello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [C# SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/csharp-reference) for detailed API docs diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json index 85ebfd88dc7..ac616be16e3 100644 --- a/templates/basic-rs/.template.json +++ b/templates/basic-rs/.template.json @@ -1,5 +1,10 @@ { "description": "A basic Rust client and server template with only stubs for code", "client_lang": "rust", - "server_lang": "rust" + "server_lang": "rust", + "builtWith": [ + "log", + "spacetimedb-sdk", + "spacetimedb" + ] } diff --git a/templates/basic-rs/README.md b/templates/basic-rs/README.md index 968af7be799..083102976ec 100644 --- a/templates/basic-rs/README.md +++ b/templates/basic-rs/README.md @@ -1,15 +1,103 @@ -# SpacetimeDB Rust Client +Get a SpacetimeDB Rust app running in under 5 minutes. -A basic Rust client for SpacetimeDB. +## Prerequisites -## Setup +- [Rust](https://www.rust-lang.org/tools/install) installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed -1. Build and publish your server module -2. Generate bindings: - ``` - spacetime generate --lang rust --out-dir src/module_bindings - ``` -3. Run the client: - ``` - cargo run - ``` +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a Rust SpacetimeDB module. + +This will start the local SpacetimeDB server, compile and publish your module, and generate Rust client bindings. + +```bash +spacetime dev --template basic-rs +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/lib.rs` to add tables and reducers. Use the generated bindings in `src/module_bindings/` to build your client. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ ├── Cargo.toml +│ └── src/ +│ └── lib.rs # Server-side logic +├── Cargo.toml +├── src/ +│ ├── main.rs # Client application +│ └── module_bindings/ # Auto-generated types +└── README.md +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/lib.rs` to see the module code. The template includes a `Person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```rust +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, +} + +#[spacetimedb::reducer] +pub fn add(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} + +#[spacetimedb::reducer] +pub fn say_hello(ctx: &ReducerContext) { + for person in ctx.db.person().iter() { + log::info!("Hello, {}!", person.name); + } + log::info!("Hello, World!"); +} +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call say_hello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [Rust SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/rust-reference) for detailed API docs diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index f81df6ce52c..5039ff8aac2 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -1,5 +1,10 @@ { "description": "A basic TypeScript client and server template with only stubs for code", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "esbuild", + "typescript", + "spacetimedb" + ] } diff --git a/templates/basic-ts/README.md b/templates/basic-ts/README.md new file mode 100644 index 00000000000..a90b1813f62 --- /dev/null +++ b/templates/basic-ts/README.md @@ -0,0 +1,107 @@ +Get a SpacetimeDB TypeScript app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module. + +This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings. + +```bash +spacetime dev --template basic-ts +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Use the generated bindings in `src/module_bindings/` to build your client. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ +│ ├── main.ts # Client application +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json index 8cc8855138b..9cff3099637 100644 --- a/templates/browser-ts/.template.json +++ b/templates/browser-ts/.template.json @@ -1,5 +1,10 @@ { "description": "Browser web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "typescript", + "vite", + "spacetimedb" + ] } diff --git a/templates/browser-ts/README.md b/templates/browser-ts/README.md new file mode 100644 index 00000000000..7e69847d072 --- /dev/null +++ b/templates/browser-ts/README.md @@ -0,0 +1,106 @@ +Get a SpacetimeDB app running in the browser with inline JavaScript. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module. + +This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings. + +```bash +spacetime dev --template browser-ts +``` + + + +## Build the client bindings + +The generated TypeScript bindings need to be bundled into a JavaScript file that can be loaded in the browser via a script tag. + +```bash +cd my-spacetime-app +npm install +npm run build +``` + + + +## Open in browser + +Open `index.html` directly in your browser. The app connects to SpacetimeDB and displays data in real-time. + +The JavaScript code runs inline in a script tag, using the bundled `DbConnection` class. + +:::tip +The browser IIFE bundle also exposes the generated `tables` query builders, so you can use query-builder subscriptions here too. +::: + +```html + + + + +``` + + + +## Call reducers + +Reducers are functions that modify data — they're the only way to write to the database. + +```javascript +// Call a reducer with named arguments +conn.reducers.add({ name: 'Alice' }); +``` + + + +## React to changes + +Register callbacks to update your UI when data changes. + +```javascript +conn.db.person.onInsert((ctx, person) => { + console.log('New person:', person.name); +}); + +conn.db.person.onDelete((ctx, person) => { + console.log('Removed:', person.name); +}); +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index 2d523dc53b8..08fdc2fa863 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -2,5 +2,10 @@ "description": "Bun TypeScript client and server template", "client_framework": "Bun", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "bun", + "typescript", + "spacetimedb" + ] } diff --git a/templates/bun-ts/README.md b/templates/bun-ts/README.md new file mode 100644 index 00000000000..21e5e33ef17 --- /dev/null +++ b/templates/bun-ts/README.md @@ -0,0 +1,232 @@ +Get a SpacetimeDB Bun app running in under 5 minutes. + +## Prerequisites + +- [Bun](https://bun.sh/) installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Bun client. + +This will start the local SpacetimeDB server, publish your module, and generate TypeScript bindings. + +```bash +spacetime dev --template bun-ts +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Bun client. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ +│ ├── main.ts # Bun client script +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Run the client + +In a new terminal, run the Bun client. It will connect to SpacetimeDB and start an interactive CLI where you can add people and query the database. + +```bash +# Run with auto-reload during development +bun run dev + +# Or run once + +bun run start + +``` + + + +## Use the interactive CLI + +The client provides a command-line interface to interact with your SpacetimeDB module. Type a name to add a person, or use the built-in commands. + +``` + +Connecting to SpacetimeDB... +URI: ws://localhost:3000 +Module: bun-ts + +Connected to SpacetimeDB! +Identity: abc123def456... + +Current people (0): +(none yet) + +Commands: + - Add a person with that name +list - Show all people +hello - Greet everyone (check server logs) +Ctrl+C - Quit + +> Alice +> [Added] Alice + +> Bob +> [Added] Bob + +> list +> People in database: + +- Alice +- Bob + +> hello +> Called sayHello reducer (check server logs) + +```` + + + +## Understand the client code + +Open `src/main.ts` to see the Bun client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and sets up the interactive CLI using Bun's native APIs. + +Unlike browser apps, Bun stores the authentication token in a file using `Bun.file()` and `Bun.write()`. + +```typescript +import { DbConnection } from './module_bindings/index.js'; + +// Build and establish connection +DbConnection.builder() + .withUri(HOST) + .withDatabaseName(DB_NAME) + .withToken(await loadToken()) // Load saved token from file + .onConnect((conn, identity, token) => { + console.log('Connected! Identity:', identity.toHexString()); + saveToken(token); // Save token for future connections + + // Subscribe to all tables + conn.subscriptionBuilder() + .onApplied((ctx) => { + // Show current data, start CLI + setupCLI(conn); + }) + .subscribeToAllTables(); + + // Listen for table changes + conn.db.person.onInsert((ctx, person) => { + console.log(`[Added] ${person.name}`); + }); + }) + .build(); +```` + + + +## Test with the SpacetimeDB CLI + +You can also use the SpacetimeDB CLI to call reducers and query your data directly. Changes made via the CLI will appear in your Bun client in real-time. + +```bash +# Call the add reducer to insert a person +spacetime call add Charlie + +# Query the person table + +spacetime sql "SELECT \* FROM person" +name + +--- + +"Alice" +"Bob" +"Charlie" + +# Call sayHello to greet everyone + +spacetime call say_hello + +# View the module logs + +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, Bob! +2025-01-13T12:00:00.000000Z INFO: Hello, Charlie! +2025-01-13T12:00:00.000000Z INFO: Hello, World! + +```` + + + +## Bun-specific features + +**Native WebSocket:** Bun has built-in WebSocket support, so no additional packages like `undici` are needed. + +**Built-in TypeScript:** Bun runs TypeScript directly without transpilation, making startup faster and eliminating the need for `tsx` or `ts-node`. + +**Environment variables:** Bun automatically loads `.env` files. Configure the connection using `SPACETIMEDB_HOST` and `SPACETIMEDB_DB_NAME` environment variables. + +**File APIs:** The template uses `Bun.file()` and `Bun.write()` for token persistence, which are faster than Node.js `fs` operations. + +```bash +# Configure via environment variables +SPACETIMEDB_HOST=ws://localhost:3000 \ +SPACETIMEDB_DB_NAME=my-app \ +bun run start + +# Or create a .env file (Bun loads it automatically) +echo "SPACETIMEDB_HOST=ws://localhost:3000" > .env +echo "SPACETIMEDB_DB_NAME=my-app" >> .env +bun run start +```` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/chat-console-cs/.template.json b/templates/chat-console-cs/.template.json index 0a476ac93f0..e73f0e97f61 100644 --- a/templates/chat-console-cs/.template.json +++ b/templates/chat-console-cs/.template.json @@ -1,5 +1,8 @@ { "description": "C# server/client implementing quickstart chat", "client_lang": "csharp", - "server_lang": "csharp" + "server_lang": "csharp", + "builtWith": [ + "SpacetimeDB.Runtime" + ] } diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json index 07ad0e5291a..e51990cfd62 100644 --- a/templates/chat-console-rs/.template.json +++ b/templates/chat-console-rs/.template.json @@ -1,5 +1,10 @@ { "description": "Rust server/client implementing quickstart chat", "client_lang": "rust", - "server_lang": "rust" + "server_lang": "rust", + "builtWith": [ + "log.workspace", + "spacetimedb-sdk", + "spacetimedb" + ] } diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index c4a20fefa4a..6ca582e36b5 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -1,5 +1,22 @@ { "description": "TypeScript server/client implementing quickstart chat", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "react", + "react-dom", + "eslint", + "testing-library", + "vitejs", + "eslint-plugin-react-hooks", + "eslint-plugin-react-refresh", + "globals", + "jsdom", + "prettier", + "typescript", + "typescript-eslint", + "vite", + "vitest", + "spacetimedb" + ] } diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json index 9b815e8c464..0bc1e4b7f36 100644 --- a/templates/deno-ts/.template.json +++ b/templates/deno-ts/.template.json @@ -1,5 +1,10 @@ { "description": "Deno TypeScript client and server template", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "deno", + "typescript", + "spacetimedb" + ] } diff --git a/templates/deno-ts/README.md b/templates/deno-ts/README.md new file mode 100644 index 00000000000..3b72e75af58 --- /dev/null +++ b/templates/deno-ts/README.md @@ -0,0 +1,245 @@ +Get a SpacetimeDB Deno app running in under 5 minutes. + +## Prerequisites + +- [Deno](https://deno.land/) installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Deno client. + +This will start the local SpacetimeDB server, publish your module, and generate TypeScript bindings. + +```bash +spacetime dev --template deno-ts +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Deno client. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ +│ ├── main.ts # Deno client script +│ └── module_bindings/ # Auto-generated types +└── package.json # Scripts and dependencies (dev, start, spacetime:generate) +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Run the client + +In a new terminal, run the Deno client. It will connect to SpacetimeDB and start an interactive CLI where you can add people and query the database. + +```bash +# Run with auto-reload during development +pnpm run dev +# or: npm run dev + +# Or run once +pnpm run start +# or: npm run start +``` + + + +## Use the interactive CLI + +The client provides a command-line interface to interact with your SpacetimeDB module. Type a name to add a person, or use the built-in commands. + +``` + +Connecting to SpacetimeDB... +URI: ws://localhost:3000 +Module: deno-ts + +Connected to SpacetimeDB! +Identity: abc123def456... + +Current people (0): +(none yet) + +Commands: + - Add a person with that name +list - Show all people +hello - Greet everyone (check server logs) +Ctrl+C - Quit + +> Alice +> [Added] Alice + +> Bob +> [Added] Bob + +> list +> People in database: + +- Alice +- Bob + +> hello +> Called sayHello reducer (check server logs) + +```` + + + +## Understand the client code + +Open `src/main.ts` to see the Deno client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and sets up the interactive CLI using Deno's native APIs. + +Unlike browser apps, Deno stores the authentication token in a file using `Deno.readTextFile()` and `Deno.writeTextFile()`. + +```typescript +import { DbConnection } from './module_bindings/index.ts'; + +// Build and establish connection +DbConnection.builder() + .withUri(HOST) + .withDatabaseName(DB_NAME) + .withToken(await loadToken()) // Load saved token from file + .onConnect((conn, identity, token) => { + console.log('Connected! Identity:', identity.toHexString()); + saveToken(token); // Save token for future connections + + // Subscribe to all tables + conn.subscriptionBuilder() + .onApplied((ctx) => { + // Show current data, start CLI + setupCLI(conn); + }) + .subscribeToAllTables(); + + // Listen for table changes + conn.db.person.onInsert((ctx, person) => { + console.log(`[Added] ${person.name}`); + }); + }) + .build(); +```` + + + +## Test with the SpacetimeDB CLI + +You can also use the SpacetimeDB CLI to call reducers and query your data directly. Changes made via the CLI will appear in your Deno client in real-time. + +```bash +# Call the add reducer to insert a person +spacetime call add Charlie + +# Query the person table + +spacetime sql "SELECT \* FROM person" +name + +--- + +"Alice" +"Bob" +"Charlie" + +# Call sayHello to greet everyone + +spacetime call say_hello + +# View the module logs + +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, Bob! +2025-01-13T12:00:00.000000Z INFO: Hello, Charlie! +2025-01-13T12:00:00.000000Z INFO: Hello, World! + +```` + + + +## Deno-specific features + +**Permissions:** Deno requires explicit permissions. The template uses `--allow-net` for WebSocket connections, `--allow-read` and `--allow-write` for token persistence, and `--allow-env` for configuration. + +**Sloppy imports:** The `--unstable-sloppy-imports` flag is required because the generated module bindings use extensionless imports (Node.js convention), while Deno requires explicit file extensions. This flag enables Node.js-style module resolution. + +**Built-in TypeScript:** Deno runs TypeScript directly without transpilation, making startup faster and eliminating the need for build tools. + +**Dependencies:** The `package.json` file declares the `spacetimedb` dependency; Deno resolves it from there (and from `node_modules` after `pnpm install` when developing in the SpacetimeDB repo). + +**node_modules:** When using the template from the repo workspace, run `pnpm install` so `spacetimedb` is linked. For a project created with `spacetime init`, Deno resolves the versioned dependency from package.json. + +```bash +# Configure via environment variables +SPACETIMEDB_HOST=ws://localhost:3000 \ +SPACETIMEDB_DB_NAME=my-app \ +pnpm run start + +# Or run with explicit permissions +deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts + +# package.json defines scripts and the spacetimedb dependency +cat package.json +{ + "scripts": { + "dev": "deno run --watch --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts", + "start": "deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb" + }, + "dependencies": { + "spacetimedb": "workspace:*" + } +} +```` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json index 85589e54e1c..ad1d03c3b99 100644 --- a/templates/nextjs-ts/.template.json +++ b/templates/nextjs-ts/.template.json @@ -2,5 +2,12 @@ "description": "Next.js App Router with TypeScript server", "client_framework": "Next.js", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "next", + "react", + "react-dom", + "typescript", + "spacetimedb" + ] } diff --git a/templates/nextjs-ts/README.md b/templates/nextjs-ts/README.md new file mode 100644 index 00000000000..9fc4e60cca9 --- /dev/null +++ b/templates/nextjs-ts/README.md @@ -0,0 +1,195 @@ +Get a SpacetimeDB Next.js app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Next.js client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Next.js development server. + +```bash +spacetime dev --template nextjs-ts +``` + + + +## Open your app + +Navigate to [http://localhost:3000](http://localhost:3000) to see your app running. + +The `spacetime dev` command automatically configures your app to connect to SpacetimeDB via environment variables in `.env.local`. + + + +## Explore the project structure + +Your project contains both server and client code using the Next.js App Router. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `app/page.tsx` and `app/PersonList.tsx` to build your UI. + +``` +my-nextjs-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # SpacetimeDB module logic +├── app/ # Next.js App Router +│ ├── layout.tsx # Root layout with providers +│ ├── page.tsx # Server Component (fetches initial data) +│ ├── PersonList.tsx # Client Component (real-time updates) +│ └── providers.tsx # SpacetimeDB provider for real-time +├── lib/ +│ └── spacetimedb-server.ts # Server-side data fetching +├── src/ +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + +## Understand server-side rendering + +The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach: + +- **Server Component** (`page.tsx`): Fetches initial data during SSR for fast page loads +- **Client Component** (`PersonList.tsx`): Maintains a real-time WebSocket connection for live updates + +The `lib/spacetimedb-server.ts` file provides a utility for server-side data fetching. + +```tsx +// lib/spacetimedb-server.ts +import { DbConnection, tables } from '../src/module_bindings'; + +export async function fetchPeople() { + return new Promise((resolve, reject) => { + const connection = DbConnection.builder() + .withUri(process.env.SPACETIMEDB_HOST!) + .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!) + .onConnect(conn => { + conn.subscriptionBuilder() + .onApplied(() => { + const people = Array.from(conn.db.person.iter()); + conn.disconnect(); + resolve(people); + }) + .subscribe(tables.person); + }) + .build(); + }); +} +``` + + + +## Use React hooks for real-time data + +In client components, use `useTable` to subscribe to table data and `useReducer` to call reducers. The Server Component passes initial data as props for instant rendering. + +```tsx +// app/page.tsx (Server Component) +import { PersonList } from './PersonList'; +import { fetchPeople } from '../lib/spacetimedb-server'; + +export default async function Home() { + const initialPeople = await fetchPeople(); + return ; +} +``` + +```tsx +// app/PersonList.tsx (Client Component) +'use client'; + +import { tables, reducers } from '../src/module_bindings'; +import { useTable, useReducer } from 'spacetimedb/react'; + +export function PersonList({ initialPeople }) { + // Real-time data from WebSocket subscription + const [people, isLoading] = useTable(tables.person); + const addPerson = useReducer(reducers.add); + + // Use server data until client is connected + const displayPeople = isLoading ? initialPeople : people; + + return ( +
    + {displayPeople.map((person, i) =>
  • {person.name}
  • )} +
+ ); +} +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index bb21899d82f..ea32f10847b 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -2,5 +2,12 @@ "description": "Node.js TypeScript client and server template", "client_framework": "Node.js", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "nodejs", + "esbuild", + "typescript", + "undici", + "spacetimedb" + ] } diff --git a/templates/nodejs-ts/README.md b/templates/nodejs-ts/README.md new file mode 100644 index 00000000000..66a81387d26 --- /dev/null +++ b/templates/nodejs-ts/README.md @@ -0,0 +1,199 @@ +Get a SpacetimeDB Node.js app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Node.js client. + +This starts the local SpacetimeDB server, publishes your module, generates TypeScript bindings, and runs the Node.js client. + +```bash +spacetime dev --template nodejs-ts +``` + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Node.js client. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ +│ ├── main.ts # Node.js client script +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Run the client + +`spacetime dev` starts both the server and the Node.js client. The client connects to SpacetimeDB, subscribes to tables, and displays people as they are added or removed. Press Ctrl+C to exit. + +```bash +spacetime dev --template nodejs-ts +``` + + + +## Call reducers from the SpacetimeDB CLI + +Use the SpacetimeDB CLI to add people and invoke reducers. Changes appear in your Node.js client in real time. + +```bash +# Add a person +spacetime call add Alice +spacetime call add Bob + +# Greet everyone (check server logs) + +spacetime call say_hello + +# Query the database + +spacetime sql "SELECT * FROM person" + +```` + + + +## Understand the client code + +Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and registers callbacks for insert/delete events. Unlike browser apps, Node.js stores the authentication token in a file instead of localStorage. + +```typescript +import { DbConnection } from './module_bindings/index.js'; + +DbConnection.builder() + .withUri(HOST) + .withDatabaseName(DB_NAME) + .withToken(loadToken()) // Load saved token from file + .onConnect((conn, identity, token) => { + console.log('Connected! Identity:', identity.toHexString()); + saveToken(token); // Save token for future connections + + // Subscribe to all tables + conn.subscriptionBuilder() + .onApplied((ctx) => { + // Show current people + const people = [...ctx.db.person.iter()]; + console.log('Current people:', people.length); + }) + .subscribeToAllTables(); + + // Listen for table changes + conn.db.person.onInsert((ctx, person) => { + console.log(`[Added] ${person.name}`); + }); + }) + .build(); +```` + + + +## More CLI examples + +The SpacetimeDB CLI can call reducers and query your data. Changes appear in your Node.js client in real time. + +```bash +# Call the add reducer to insert a person +spacetime call add Charlie + +# Query the person table + +spacetime sql "SELECT * FROM person" +name + +--- + +"Alice" +"Bob" +"Charlie" + +# Call sayHello to greet everyone + +spacetime call say_hello + +# View the module logs + +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, Bob! +2025-01-13T12:00:00.000000Z INFO: Hello, Charlie! +2025-01-13T12:00:00.000000Z INFO: Hello, World! + +```` + + + +## Node.js considerations + +**WebSocket support:** Node.js 22+ has native WebSocket support. For Node.js 18-21, the SDK automatically uses the `undici` package (included in devDependencies). + +**Environment variables:** Configure the connection using `SPACETIMEDB_HOST` and `SPACETIMEDB_DB_NAME` environment variables. + +**Exiting:** Press Ctrl+C to stop the client. + +```bash +# Configure via environment variables +SPACETIMEDB_HOST=ws://localhost:3000 \ +SPACETIMEDB_DB_NAME=my-app \ +npm run start + +# Or use a .env file with dotenv +```` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json index ea34667b01e..3eefe3bf2a3 100644 --- a/templates/nuxt-ts/.template.json +++ b/templates/nuxt-ts/.template.json @@ -2,5 +2,11 @@ "description": "Nuxt web app with TypeScript server", "client_framework": "Nuxt", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "nuxt", + "vue", + "typescript", + "spacetimedb" + ] } diff --git a/templates/nuxt-ts/README.md b/templates/nuxt-ts/README.md new file mode 100644 index 00000000000..7df666f1010 --- /dev/null +++ b/templates/nuxt-ts/README.md @@ -0,0 +1,227 @@ +Get a SpacetimeDB Nuxt app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Nuxt client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Nuxt development server. + +```bash +spacetime dev --template nuxt-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The template includes a basic Nuxt app connected to SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `components/AppContent.vue` to build your UI, and `app.vue` to configure the SpacetimeDB connection. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # SpacetimeDB module logic +├── app.vue # Root component with provider +├── components/ +│ └── AppContent.vue # Main UI component +├── server/ +│ └── api/ +│ └── people.get.ts # Server-side data fetching +├── module_bindings/ # Auto-generated types +├── nuxt.config.ts # Nuxt configuration +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + +## Understand server-side rendering + +The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach: + +- **Server API route** (`server/api/people.get.ts`): Fetches initial data during SSR for fast page loads +- **Client composables**: Maintain a real-time WebSocket connection for live updates + +The server API route connects to SpacetimeDB, subscribes, fetches data, and disconnects. + +```typescript +// server/api/people.get.ts +import { DbConnection, tables } from '../../module_bindings'; + +export default defineEventHandler(async () => { + return new Promise((resolve, reject) => { + DbConnection.builder() + .withUri(process.env.SPACETIMEDB_HOST!) + .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!) + .onConnect((conn) => { + conn.subscriptionBuilder() + .onApplied(() => { + const people = Array.from(conn.db.person.iter()); + conn.disconnect(); + resolve(people); + }) + .subscribe(tables.person); + }) + .build(); + }); +}); +``` + + + +## Set up the SpacetimeDB provider + +The root `app.vue` wraps your app in a `SpacetimeDBProvider` that manages the WebSocket connection. The provider is wrapped in `ClientOnly` so it only runs in the browser, while SSR uses the server API route for initial data. + +```vue + + + + +``` + + + +## Use composables and SSR data together + +Use `useFetch` to load initial data server-side, then Vue composables for real-time updates on the client. The component displays server-fetched data immediately while the WebSocket connection establishes. + +```vue + + +``` + +## Next steps + +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index 6dc635a7ccf..e60c63dabc6 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -2,5 +2,13 @@ "description": "React web app with TypeScript server", "client_framework": "React", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "react", + "react-dom", + "vitejs", + "typescript", + "vite", + "spacetimedb" + ] } diff --git a/templates/react-ts/README.md b/templates/react-ts/README.md new file mode 100644 index 00000000000..8db4516126e --- /dev/null +++ b/templates/react-ts/README.md @@ -0,0 +1,116 @@ +Get a SpacetimeDB React app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and React client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the React development server. + +```bash +spacetime dev --template react-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The template includes a basic React app connected to SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `client/src/App.tsx` to build your UI. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── client/ # React frontend +│ └── src/ +│ ├── App.tsx +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 6a2c68072c3..1259fb05a92 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -2,5 +2,14 @@ "description": "Remix with TypeScript server", "client_framework": "Remix", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "remix-run", + "isbot", + "react", + "react-dom", + "typescript", + "vite", + "spacetimedb" + ] } diff --git a/templates/remix-ts/README.md b/templates/remix-ts/README.md new file mode 100644 index 00000000000..7a5c0504866 --- /dev/null +++ b/templates/remix-ts/README.md @@ -0,0 +1,190 @@ +Get a SpacetimeDB Remix app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Remix client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Remix development server. + +```bash +spacetime dev --template remix-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The `spacetime dev` command automatically configures your app to connect to SpacetimeDB via environment variables. + + + +## Explore the project structure + +Your project contains both server and client code using Remix with Vite. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `app/routes/_index.tsx` to build your UI. + +``` +my-remix-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # SpacetimeDB module logic +├── app/ # Remix app +│ ├── root.tsx # Root layout with SpacetimeDB provider +│ ├── lib/ +│ │ └── spacetimedb.server.ts # Server-side data fetching +│ └── routes/ +│ └── _index.tsx # Home page with loader +├── src/ +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + +## Understand server-side rendering + +The SpacetimeDB SDK works both server-side and client-side. The template uses Remix loaders for SSR: + +- **Loader**: Fetches initial data from SpacetimeDB during server rendering +- **Client**: Maintains a real-time WebSocket connection for live updates + +The `app/lib/spacetimedb.server.ts` file provides a utility for server-side data fetching. + +```tsx +// app/lib/spacetimedb.server.ts +import { DbConnection, tables } from '../../src/module_bindings'; + +export async function fetchPeople() { + return new Promise((resolve, reject) => { + const connection = DbConnection.builder() + .withUri(process.env.SPACETIMEDB_HOST!) + .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!) + .onConnect(conn => { + conn.subscriptionBuilder() + .onApplied(() => { + const people = Array.from(conn.db.person.iter()); + conn.disconnect(); + resolve(people); + }) + .subscribe(tables.person); + }) + .build(); + }); +} +``` + + + +## Use loaders and hooks for data + +Use Remix loaders to fetch initial data server-side, then React hooks for real-time updates. The loader data is passed to the component and displayed immediately while the client connects. + +```tsx +// app/routes/_index.tsx +import { useLoaderData } from '@remix-run/react'; +import { tables, reducers } from '../../src/module_bindings'; +import { useTable, useReducer } from 'spacetimedb/react'; +import { fetchPeople } from '../lib/spacetimedb.server'; + +export async function loader() { + const people = await fetchPeople(); + return { initialPeople: people }; +} + +export default function Index() { + const { initialPeople } = useLoaderData(); + + // Real-time data from WebSocket subscription + const [people, isLoading] = useTable(tables.person); + const addPerson = useReducer(reducers.add); + + // Use server data until client is connected + const displayPeople = isLoading ? initialPeople : people; + + return ( +
    + {displayPeople.map((person, i) =>
  • {person.name}
  • )} +
+ ); +} +``` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json index 31bbd725c6a..e6db7ed45e2 100644 --- a/templates/svelte-ts/.template.json +++ b/templates/svelte-ts/.template.json @@ -2,5 +2,13 @@ "description": "Svelte web app with TypeScript server", "client_framework": "Svelte", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "sveltejs", + "svelte", + "svelte-check", + "typescript", + "vite", + "spacetimedb" + ] } diff --git a/templates/svelte-ts/README.md b/templates/svelte-ts/README.md new file mode 100644 index 00000000000..3ef596dd66e --- /dev/null +++ b/templates/svelte-ts/README.md @@ -0,0 +1,114 @@ +Get a SpacetimeDB Svelte app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Svelte client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Svelte development server. + +```bash +spacetime dev --template svelte-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The template includes a basic Svelte app connected to SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.svelte` to build your UI. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ # Svelte frontend +│ ├── App.svelte +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index 76ab7261836..2c7333fa606 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -2,5 +2,15 @@ "description": "TanStack Start (React + TanStack Query/Router) with TypeScript server", "client_framework": "TanStack", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "tanstack", + "react", + "react-dom", + "vitejs", + "typescript", + "vite", + "vite-tsconfig-paths", + "spacetimedb" + ] } diff --git a/templates/tanstack-ts/README.md b/templates/tanstack-ts/README.md new file mode 100644 index 00000000000..090e5bf244e --- /dev/null +++ b/templates/tanstack-ts/README.md @@ -0,0 +1,144 @@ +Get a SpacetimeDB app with TanStack Start running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and TanStack Start. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the development server. + +```bash +spacetime dev --template tanstack-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The template includes a TanStack Start app with TanStack Query integration with SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/routes/index.tsx` to build your UI. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ # TanStack Start frontend +│ ├── router.tsx # QueryClient + SpacetimeDB setup +│ ├── routes/ +│ │ ├── __root.tsx # Root layout +│ │ └── index.tsx # Main app component +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + +## Query and update data + +Use `useSpacetimeDBQuery()` to subscribe to tables with TanStack Query — it returns `[data, loading, query]`. SpacetimeDB React hooks also work with TanStack Start. + +```typescript +import { useSpacetimeDBQuery, useReducer } from 'spacetimedb/tanstack'; +import { tables, reducers } from '../module_bindings'; + +function App() { + const [people, loading] = useSpacetimeDBQuery(tables.person); + const addPerson = useReducer(reducers.add); + + if (loading) return

Loading...

; + + return ( +
    + {people.map((person, i) => ( +
  • {person.name}
  • + ))} +
+ ); +} +```` + +## Next steps + +- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json index e66e510a0b0..73fa04a4814 100644 --- a/templates/vue-ts/.template.json +++ b/templates/vue-ts/.template.json @@ -2,5 +2,13 @@ "description": "Vue.js web app with TypeScript server", "client_framework": "Vue.js", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "vue", + "vitejs", + "typescript", + "vite", + "vue-tsc", + "spacetimedb" + ] } diff --git a/templates/vue-ts/README.md b/templates/vue-ts/README.md new file mode 100644 index 00000000000..95e5b54e398 --- /dev/null +++ b/templates/vue-ts/README.md @@ -0,0 +1,114 @@ +Get a SpacetimeDB Vue app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + +Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing. + +--- + +## Create your project + +Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Vue client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Vue development server. + +```bash +spacetime dev --template vue-ts +``` + + + +## Open your app + +Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + +The template includes a basic Vue app connected to SpacetimeDB. + + + +## Explore the project structure + +Your project contains both server and client code. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.vue` to build your UI. + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── src/ # Vue frontend +│ ├── App.vue +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + +## Understand tables and reducers + +Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone. + +Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const add = spacetimedb.reducer( + { name: t.string() }, + (ctx, { name }) => { + ctx.db.person.insert({ name }); + } +); + +export const sayHello = spacetimedb.reducer(ctx => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + +## Test with the CLI + +Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly. + +```bash +cd my-spacetime-app + +# Call the add reducer to insert a person +spacetime call add Alice + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + +# Call sayHello to greet everyone +spacetime call say_hello + +# View the module logs +spacetime logs +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + +## Next steps + +- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs diff --git a/tools/templates/README.md b/tools/templates/README.md new file mode 100644 index 00000000000..151bb130dcc --- /dev/null +++ b/tools/templates/README.md @@ -0,0 +1,29 @@ +# Template Tools + +Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. Output is consumed by [spacetimedb.com](https://github.com/clockworklabs/spacetime-web) for the templates page. + +## Scripts + +- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md`. Discovers mappings by parsing `--template X` from quickstart files. Templates can override with a `quickstart` field in `.template.json` (must point to a file in the quickstarts dir). +- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from package.json, Cargo.toml, and .csproj manifests +- **generate** – Runs both (readmes first, then jsons) + +## Usage + +From this directory: + +```bash +pnpm install +pnpm run generate +``` + +Or individually: + +```bash +pnpm run generate-readmes +pnpm run update-jsons +``` + +## When to run + +Run after changing quickstart docs (`docs/docs/00100-intro/00200-quickstarts/`) or adding/renaming templates. Commit the generated READMEs and updated `.template.json` files. diff --git a/tools/templates/generate-template-readmes.ts b/tools/templates/generate-template-readmes.ts new file mode 100644 index 00000000000..9eacb14eef7 --- /dev/null +++ b/tools/templates/generate-template-readmes.ts @@ -0,0 +1,245 @@ +/** + * Reads SpacetimeDB quickstart MDX docs, converts them to plain Markdown, + * and writes README.md into each template folder. These READMEs are consumed + * by spacetimedb.com's process-templates to generate the templates page. + * + * Run from SpacetimeDB repo root. Writes to templates//README.md. + * + * Usage: pnpm run generate-readmes (from tools/templates/) + */ + +import { readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../..'); +const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates'); +const QUICKSTARTS_DIR = path.join( + REPO_ROOT, + 'docs/docs/00100-intro/00200-quickstarts' +); +const DOCS_ROOT = path.join(REPO_ROOT, 'docs/docs'); + +const TEMPLATE_FROM_QUICKSTART_RE = /--template\s+(\S+)/; + +/** Parse --template X from quickstart content. Returns template slug or null. */ +function parseTemplateFromQuickstart(content: string): string | null { + const match = content.match(TEMPLATE_FROM_QUICKSTART_RE); + return match ? match[1] : null; +} + +/** Discover template -> quickstart mapping by parsing --template from each quickstart file. */ +async function discoverQuickstartMapping(): Promise> { + const map = new Map(); + let entries: import('node:fs').Dirent[]; + try { + entries = await readdir(QUICKSTARTS_DIR, { withFileTypes: true }); + } catch { + return map; + } + const files = entries + .filter(e => e.isFile() && e.name.endsWith('.md')) + .map(e => e.name) + .sort(); + for (const file of files) { + try { + const content = await readFile(path.join(QUICKSTARTS_DIR, file), 'utf-8'); + const template = parseTemplateFromQuickstart(content); + if (template && !map.has(template)) { + map.set(template, file); + } + } catch { + // skip + } + } + return map; +} + +const DOCS_BASE = 'https://spacetimedb.com/docs'; + +function stripFrontmatterAndImports(content: string): string { + let out = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, ''); + out = out.replace(/^import .+ from ["'][^"']*@site[^"']*["'];\r?\n/gm, ''); + return out.trim(); +} + +function replaceInstallCardLink(content: string): string { + return content.replace( + //g, + 'Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.' + ); +} + +function normalizeStepText(text: string): string { + return text + .trim() + .split('\n') + .map(line => line.replace(/^\s+/, '')) + .join('\n'); +} + +function convertStepByStepToMarkdown(content: string): string { + const stepRegex = + /\s*\s*([\s\S]*?)\s*<\/StepText>\s*(?:\s*([\s\S]*?)\s*<\/StepCode>)?\s*<\/Step>/g; + + return content.replace(stepRegex, (_, title, stepText, stepCode) => { + const normalizedText = normalizeStepText(stepText); + let block = `## ${title}\n\n${normalizedText}\n\n`; + if (stepCode && stepCode.trim()) { + block += stepCode.trim() + '\n\n'; + } + return block; + }); +} + +function removeStepByStepWrapper(content: string): string { + return content.replace(/\s*([\s\S]*?)\s*<\/StepByStep>/g, '$1'); +} + +function stripRemainingStepTags(content: string): string { + let out = content + .replace(/([\s\S]*?)<\/StepText>/g, '$1') + .replace(/([\s\S]*?)<\/StepCode>/g, '$1') + .replace(/]*>/g, '') + .replace(/<\/Step>/g, '') + .replace(/<\/StepCode>/g, '') + .replace(/<\/StepText>/g, ''); + return out; +} + +function rewriteDocLinks( + content: string, + quickstartDir: string, + docsRoot: string +): string { + return content.replace( + /\[([^\]]+)\]\((\.\.\/)*(.+?\.md)(#[\w-]+)?\)/g, + (_, linkText, parentRefs, docPath, hash) => { + const relPath = (parentRefs || '') + docPath; + const resolved = path.resolve(quickstartDir, relPath); + const relativeToDocs = path + .relative(docsRoot, resolved) + .replace(/\\/g, '/'); + const withoutExt = relativeToDocs.replace(/\.md$/, ''); + const slug = withoutExt + .split('/') + .map(seg => seg.replace(/^\d+-/, '')) + .join('/'); + const url = `${DOCS_BASE}/${slug}${hash || ''}`; + return `[${linkText}](${url})`; + } + ); +} + +function stripLineIndent(md: string): string { + let inCodeBlock = false; + return md + .split('\n') + .map(line => { + if (line.startsWith('```')) { + inCodeBlock = !inCodeBlock; + return line; + } + if (inCodeBlock) return line; + return line.replace(/^\s+/, ''); + }) + .join('\n'); +} + +function quickstartMdxToMarkdown( + mdx: string, + quickstartDir: string, + docsRoot: string +): string { + let md = stripFrontmatterAndImports(mdx); + md = replaceInstallCardLink(md); + md = convertStepByStepToMarkdown(md); + md = removeStepByStepWrapper(md); + md = stripRemainingStepTags(md); + md = stripLineIndent(md); + md = rewriteDocLinks(md, quickstartDir, docsRoot); + return md.trim() + '\n'; +} + +/** Resolve quickstart path: override with "/" is relative to DOCS_ROOT, else relative to QUICKSTARTS_DIR. */ +function resolveQuickstartPath(override: string): string { + if (override.includes('/')) { + return path.join(DOCS_ROOT, override); + } + return path.join(QUICKSTARTS_DIR, override); +} + +export async function generateTemplateReadmes(): Promise { + const discovered = await discoverQuickstartMapping(); + let entries: import('node:fs').Dirent[]; + try { + entries = await readdir(TEMPLATES_DIR, { withFileTypes: true }); + } catch (err) { + console.warn(`Could not read templates dir: ${TEMPLATES_DIR}`, err); + return; + } + + const templateDirs = entries + .filter(e => e.isDirectory() && !e.name.startsWith('.')) + .map(e => e.name); + + let generated = 0; + for (const templateSlug of templateDirs) { + let quickstartOverride: string | undefined; + try { + const metaPath = path.join(TEMPLATES_DIR, templateSlug, '.template.json'); + const metaRaw = await readFile(metaPath, 'utf-8'); + const meta = JSON.parse(metaRaw) as { quickstart?: string }; + quickstartOverride = meta.quickstart; + } catch { + // no .template.json or no quickstart field + } + + let quickstartFullPath: string; + if (quickstartOverride) { + const resolved = resolveQuickstartPath(quickstartOverride); + if (!resolved.startsWith(QUICKSTARTS_DIR)) { + continue; + } + quickstartFullPath = resolved; + } else { + const quickstartFile = discovered.get(templateSlug); + if (!quickstartFile) continue; + quickstartFullPath = path.join(QUICKSTARTS_DIR, quickstartFile); + } + const readmePath = path.join(TEMPLATES_DIR, templateSlug, 'README.md'); + + let mdx: string; + try { + mdx = await readFile(quickstartFullPath, 'utf-8'); + } catch (err) { + console.warn( + `Skipping ${templateSlug}: could not read ${quickstartFullPath}` + ); + continue; + } + + const md = quickstartMdxToMarkdown( + mdx, + path.dirname(quickstartFullPath), + DOCS_ROOT + ); + await writeFile(readmePath, md); + console.log(`Generated README for ${templateSlug}`); + generated++; + } + + console.log(`Generated ${generated} template READMEs`); +} + +const isMain = + import.meta.url === `file://${process.argv[1]}` || + fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isMain) { + generateTemplateReadmes().catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/tools/templates/package.json b/tools/templates/package.json new file mode 100644 index 00000000000..bb82a7cad00 --- /dev/null +++ b/tools/templates/package.json @@ -0,0 +1,14 @@ +{ + "name": "templates-tools", + "private": true, + "type": "module", + "scripts": { + "generate-readmes": "tsx generate-template-readmes.ts", + "update-jsons": "tsx update-template-jsons.ts", + "generate": "pnpm run generate-readmes && pnpm run update-jsons" + }, + "devDependencies": { + "tsx": "^4.7.0", + "@types/node": "^20.0.0" + } +} diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts new file mode 100644 index 00000000000..4cff064ce4c --- /dev/null +++ b/tools/templates/update-template-jsons.ts @@ -0,0 +1,255 @@ +/** + * Updates .template.json in each template folder with builtWith derived from + * package.json, Cargo.toml, and .csproj manifests. Run from SpacetimeDB repo root. + * + * Writes to templates//.template.json. Commit those changes to keep + * template metadata in sync with spacetimedb.com. + * + * Usage: pnpm run update-jsons (from tools/templates/) + */ + +import { readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../..'); +const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates'); + +const PACKAGE_REFERENCE_RE = /PackageReference\s+Include="([^"]+)"/g; + +/** Normalize npm package name: @scope/pkg → scope, else use as-is */ +function normalizeNpmPackageName(name: string): string { + if (name.startsWith('@')) { + const slash = name.indexOf('/'); + return slash > 0 ? name.slice(1, slash) : name.slice(1); + } + return name; +} + +/** Skip @types/* packages - typings, not frameworks */ +function shouldSkipPackage(normalized: string): boolean { + return normalized === 'types'; +} + +function parsePackageJson(content: string): string[] { + const result: string[] = []; + let pkg: { + dependencies?: Record; + devDependencies?: Record; + }; + try { + pkg = JSON.parse(content); + } catch { + return result; + } + for (const deps of [pkg.dependencies, pkg.devDependencies]) { + if (deps && typeof deps === 'object') { + for (const name of Object.keys(deps)) { + const normalized = normalizeNpmPackageName(name); + if (!shouldSkipPackage(normalized)) result.push(normalized); + } + } + } + return result; +} + +/** Parse [dependencies] section from Cargo.toml. Keys only, no external deps. */ +function parseCargoToml(content: string): string[] { + const result: string[] = []; + const lines = content.split(/\r?\n/); + let inDependencies = false; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('[')) { + inDependencies = trimmed === '[dependencies]'; + continue; + } + if (inDependencies && trimmed && !trimmed.startsWith('#')) { + const eq = trimmed.indexOf('='); + if (eq > 0) { + const key = trimmed.slice(0, eq).trim(); + if (key) result.push(key); + } + } + } + return result; +} + +function parseCsproj(content: string): string[] { + const result: string[] = []; + for (const match of content.matchAll(PACKAGE_REFERENCE_RE)) { + result.push(match[1]); + } + return result; +} + +async function findManifests( + dir: string +): Promise<{ packageJson: string[]; cargoToml: string[]; csproj: string[] }> { + const packageJson: string[] = []; + const cargoToml: string[] = []; + const csproj: string[] = []; + + async function walk(currentDir: string): Promise { + let entries: import('node:fs').Dirent[]; + try { + entries = await readdir(currentDir, { withFileTypes: true }); + } catch { + return; + } + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + if (entry.isDirectory()) { + if (!entry.name.startsWith('.') && entry.name !== 'node_modules') { + await walk(fullPath); + } + } else if (entry.isFile()) { + if (entry.name === 'package.json') { + packageJson.push(fullPath); + } else if (entry.name === 'Cargo.toml') { + cargoToml.push(fullPath); + } else if (entry.name.endsWith('.csproj')) { + csproj.push(fullPath); + } + } + } + } + + await walk(dir); + return { packageJson, cargoToml, csproj }; +} + +/** Sort paths so root manifests come before subdirs (e.g. root package.json before spacetimedb/package.json) */ +function sortRootFirst(paths: string[]): string[] { + return [...paths].sort( + (a, b) => a.split(path.sep).length - b.split(path.sep).length + ); +} + +async function collectDepsFromManifests( + templateDir: string, + slug: string +): Promise { + const seen = new Set(); + const { packageJson, cargoToml, csproj } = await findManifests(templateDir); + const isNodeTemplate = slug.includes('nodejs'); + + for (const filePath of sortRootFirst(packageJson)) { + try { + const content = await readFile(filePath, 'utf-8'); + const pkg = JSON.parse(content) as { + dependencies?: Record; + devDependencies?: Record; + }; + if ( + isNodeTemplate && + ((pkg.dependencies && '@types/node' in pkg.dependencies) || + (pkg.devDependencies && '@types/node' in pkg.devDependencies)) + ) { + seen.add('nodejs'); + } + for (const dep of parsePackageJson(content)) { + seen.add(dep); + } + } catch { + // skip + } + } + + for (const filePath of sortRootFirst(cargoToml)) { + try { + const content = await readFile(filePath, 'utf-8'); + for (const dep of parseCargoToml(content)) { + seen.add(dep); + } + } catch { + // skip + } + } + + for (const filePath of sortRootFirst(csproj)) { + try { + const content = await readFile(filePath, 'utf-8'); + for (const dep of parseCsproj(content)) { + seen.add(dep); + } + } catch { + // skip + } + } + + const deps = [...seen]; + const stdb: string[] = []; + const rest: string[] = []; + for (const d of deps) { + if (d.startsWith('spacetimedb') || d.startsWith('SpacetimeDB')) { + stdb.push(d); + } else { + rest.push(d); + } + } + return [...rest, ...stdb]; +} + +export async function updateTemplateJsons(): Promise { + let entries: import('node:fs').Dirent[]; + try { + entries = await readdir(TEMPLATES_DIR, { withFileTypes: true }); + } catch (err) { + console.warn(`Could not read templates dir: ${TEMPLATES_DIR}`, err); + return; + } + + const dirs = entries + .filter( + (e): e is import('node:fs').Dirent => + e.isDirectory() && !e.name.startsWith('.') + ) + .map(e => e.name); + + let updated = 0; + for (const slug of dirs) { + const templateDir = path.join(TEMPLATES_DIR, slug); + const jsonPath = path.join(templateDir, '.template.json'); + let jsonRaw: string; + try { + jsonRaw = await readFile(jsonPath, 'utf-8'); + } catch { + continue; + } + + let meta: Record; + try { + meta = JSON.parse(jsonRaw); + } catch { + console.warn(`Skipping ${slug}: invalid JSON`); + continue; + } + + const builtWith = await collectDepsFromManifests(templateDir, slug); + + const { image: _image, ...rest } = meta; + const updatedMeta = { ...rest, builtWith }; + + const updatedJson = JSON.stringify(updatedMeta, null, 2) + '\n'; + if (updatedJson !== jsonRaw) { + await writeFile(jsonPath, updatedJson); + console.log(`Updated ${slug}/.template.json`); + updated++; + } + } + + console.log(`Updated ${updated} template JSON(s)`); +} + +const isMain = + import.meta.url === `file://${process.argv[1]}` || + fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isMain) { + updateTemplateJsons().catch(err => { + console.error(err); + process.exit(1); + }); +}