From c32ad6351a929783fcddbdebde3d6964ba8783ea Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:06:52 -0400 Subject: [PATCH 01/11] generated readmes --- templates/angular-ts/README.md | 117 +++++++++++++++ templates/basic-cpp/README.md | 118 +++++++++++++++ templates/basic-cs/README.md | 117 +++++++++++++++ templates/basic-rs/README.md | 112 +++++++++++++-- templates/basic-ts/README.md | 107 ++++++++++++++ templates/browser-ts/README.md | 106 ++++++++++++++ templates/bun-ts/README.md | 232 ++++++++++++++++++++++++++++++ templates/deno-ts/README.md | 245 ++++++++++++++++++++++++++++++++ templates/nextjs-ts/README.md | 195 +++++++++++++++++++++++++ templates/nodejs-ts/README.md | 199 ++++++++++++++++++++++++++ templates/nuxt-ts/README.md | 227 +++++++++++++++++++++++++++++ templates/react-ts/README.md | 116 +++++++++++++++ templates/remix-ts/README.md | 190 +++++++++++++++++++++++++ templates/svelte-ts/README.md | 114 +++++++++++++++ templates/tanstack-ts/README.md | 144 +++++++++++++++++++ templates/vue-ts/README.md | 114 +++++++++++++++ 16 files changed, 2441 insertions(+), 12 deletions(-) create mode 100644 templates/angular-ts/README.md create mode 100644 templates/basic-cpp/README.md create mode 100644 templates/basic-cs/README.md create mode 100644 templates/basic-ts/README.md create mode 100644 templates/browser-ts/README.md create mode 100644 templates/bun-ts/README.md create mode 100644 templates/deno-ts/README.md create mode 100644 templates/nextjs-ts/README.md create mode 100644 templates/nodejs-ts/README.md create mode 100644 templates/nuxt-ts/README.md create mode 100644 templates/react-ts/README.md create mode 100644 templates/remix-ts/README.md create mode 100644 templates/svelte-ts/README.md create mode 100644 templates/tanstack-ts/README.md create mode 100644 templates/vue-ts/README.md diff --git a/templates/angular-ts/README.md b/templates/angular-ts/README.md new file mode 100644 index 00000000000..de5dbae93cd --- /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/README.md b/templates/basic-cpp/README.md new file mode 100644 index 00000000000..8f67916a74e --- /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/README.md b/templates/basic-cs/README.md new file mode 100644 index 00000000000..547dd74da37 --- /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/README.md b/templates/basic-rs/README.md index 968af7be799..550c3e4f19c 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/README.md b/templates/basic-ts/README.md new file mode 100644 index 00000000000..29c3a8cce07 --- /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/README.md b/templates/browser-ts/README.md new file mode 100644 index 00000000000..41eaa2d87d6 --- /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/README.md b/templates/bun-ts/README.md new file mode 100644 index 00000000000..a04dc8f337b --- /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/deno-ts/README.md b/templates/deno-ts/README.md new file mode 100644 index 00000000000..716e0eb5a70 --- /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/README.md b/templates/nextjs-ts/README.md new file mode 100644 index 00000000000..25223dd1625 --- /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/README.md b/templates/nodejs-ts/README.md new file mode 100644 index 00000000000..940ea021392 --- /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/README.md b/templates/nuxt-ts/README.md new file mode 100644 index 00000000000..2ffe36a6804 --- /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/README.md b/templates/react-ts/README.md new file mode 100644 index 00000000000..cd4c7488af3 --- /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/README.md b/templates/remix-ts/README.md new file mode 100644 index 00000000000..19ce625b7ef --- /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/README.md b/templates/svelte-ts/README.md new file mode 100644 index 00000000000..75c5994dc7d --- /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/README.md b/templates/tanstack-ts/README.md new file mode 100644 index 00000000000..13ee03ed0ca --- /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/README.md b/templates/vue-ts/README.md new file mode 100644 index 00000000000..4f4e41fe915 --- /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 From 9119caf292524a8b3f932df1ec8d303305c59172 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:22:24 -0400 Subject: [PATCH 02/11] templates and readmes --- templates/angular-ts/.template.json | 6 +++++- templates/basic-cpp/.template.json | 5 ++++- templates/basic-cs/.template.json | 5 ++++- templates/basic-rs/.template.json | 5 ++++- templates/basic-ts/.template.json | 5 ++++- templates/browser-ts/.template.json | 5 ++++- templates/bun-ts/.template.json | 6 +++++- templates/chat-console-cs/.template.json | 5 ++++- templates/chat-console-rs/.template.json | 5 ++++- templates/chat-react-ts/.template.json | 6 +++++- templates/deno-ts/.template.json | 6 +++++- templates/nextjs-ts/.template.json | 6 +++++- templates/nodejs-ts/.template.json | 6 +++++- templates/nuxt-ts/.template.json | 6 +++++- templates/react-ts/.template.json | 6 +++++- templates/remix-ts/.template.json | 6 +++++- templates/svelte-ts/.template.json | 6 +++++- templates/tanstack-ts/.template.json | 6 +++++- templates/vue-ts/.template.json | 6 +++++- 19 files changed, 88 insertions(+), 19 deletions(-) diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json index afc15dbf0e5..d5f08cb3eb1 100644 --- a/templates/angular-ts/.template.json +++ b/templates/angular-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Angular web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "angular", + "spacetimedb" + ] } diff --git a/templates/basic-cpp/.template.json b/templates/basic-cpp/.template.json index 983a1b065ff..2d23d5e36e8 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" + ] } diff --git a/templates/basic-cs/.template.json b/templates/basic-cs/.template.json index b85112b2e07..49a481812ad 100644 --- a/templates/basic-cs/.template.json +++ b/templates/basic-cs/.template.json @@ -1,5 +1,8 @@ { "description": "A basic C# client and server template with only stubs for code", "client_lang": "csharp", - "server_lang": "csharp" + "server_lang": "csharp", + "builtWith": [ + "spacetimedb" + ] } diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json index 85ebfd88dc7..c2d67cfba37 100644 --- a/templates/basic-rs/.template.json +++ b/templates/basic-rs/.template.json @@ -1,5 +1,8 @@ { "description": "A basic Rust client and server template with only stubs for code", "client_lang": "rust", - "server_lang": "rust" + "server_lang": "rust", + "builtWith": [ + "spacetimedb" + ] } diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index f81df6ce52c..63349429477 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -1,5 +1,8 @@ { "description": "A basic TypeScript client and server template with only stubs for code", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "spacetimedb" + ] } diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json index 8cc8855138b..c2150d05574 100644 --- a/templates/browser-ts/.template.json +++ b/templates/browser-ts/.template.json @@ -1,5 +1,8 @@ { "description": "Browser web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "spacetimedb" + ] } diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index f6a2ab3b67e..78a924e32ab 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Bun TypeScript client and server template", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "bun", + "spacetimedb" + ] } diff --git a/templates/chat-console-cs/.template.json b/templates/chat-console-cs/.template.json index 0a476ac93f0..5a88a797792 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" + ] } diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json index 07ad0e5291a..0cfd462f950 100644 --- a/templates/chat-console-rs/.template.json +++ b/templates/chat-console-rs/.template.json @@ -1,5 +1,8 @@ { "description": "Rust server/client implementing quickstart chat", "client_lang": "rust", - "server_lang": "rust" + "server_lang": "rust", + "builtWith": [ + "spacetimedb" + ] } diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index c4a20fefa4a..83088d82eed 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -1,5 +1,9 @@ { "description": "TypeScript server/client implementing quickstart chat", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "react", + "spacetimedb" + ] } diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json index 9b815e8c464..2ad7af9aa73 100644 --- a/templates/deno-ts/.template.json +++ b/templates/deno-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Deno TypeScript client and server template", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "deno", + "spacetimedb" + ] } diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json index 19b5fb62be2..6814434698d 100644 --- a/templates/nextjs-ts/.template.json +++ b/templates/nextjs-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Next.js App Router with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "nextjs", + "spacetimedb" + ] } diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index af38af2c866..616d1be4172 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Node.js TypeScript client and server template", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "nodejs", + "spacetimedb" + ] } diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json index 812ba61c574..31444e9dbdd 100644 --- a/templates/nuxt-ts/.template.json +++ b/templates/nuxt-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Nuxt web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "nuxt", + "spacetimedb" + ] } diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index a11db218e11..de8fe80a051 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -1,5 +1,9 @@ { "description": "React web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "react", + "spacetimedb" + ] } diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 924245a1bc9..16a2f1eadfd 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Remix with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "remix", + "spacetimedb" + ] } diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json index 2f7d4081e89..434e3eede24 100644 --- a/templates/svelte-ts/.template.json +++ b/templates/svelte-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Svelte web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "svelte", + "spacetimedb" + ] } diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index d8cc9052acb..19fb5c2334d 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -1,5 +1,9 @@ { "description": "TanStack Start (React + TanStack Query/Router) with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "tanstack", + "spacetimedb" + ] } diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json index ea14a0674ad..8213c071a08 100644 --- a/templates/vue-ts/.template.json +++ b/templates/vue-ts/.template.json @@ -1,5 +1,9 @@ { "description": "Vue.js web app with TypeScript server", "client_lang": "typescript", - "server_lang": "typescript" + "server_lang": "typescript", + "builtWith": [ + "vue", + "spacetimedb" + ] } From 17b0834b8055d3158405623f8698213af946ea4e Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:11:27 -0400 Subject: [PATCH 03/11] readmes + scripts to generate --- templates/angular-ts/README.md | 32 +- templates/basic-cpp/README.md | 32 +- templates/basic-cs/README.md | 46 +-- templates/basic-rs/README.md | 16 +- templates/basic-ts/README.md | 32 +- templates/browser-ts/README.md | 48 +-- templates/bun-ts/README.md | 70 ++-- templates/deno-ts/README.md | 86 ++--- templates/nextjs-ts/README.md | 90 ++--- templates/nodejs-ts/README.md | 72 ++-- templates/nuxt-ts/README.md | 112 +++---- templates/react-ts/README.md | 32 +- templates/remix-ts/README.md | 88 ++--- templates/svelte-ts/README.md | 32 +- templates/tanstack-ts/README.md | 56 ++-- templates/vue-ts/README.md | 32 +- tools/templates/README.md | 29 ++ tools/templates/generate-template-readmes.ts | 180 ++++++++++ tools/templates/package.json | 14 + tools/templates/pnpm-lock.yaml | 332 +++++++++++++++++++ tools/templates/update-template-jsons.ts | 97 ++++++ 21 files changed, 1090 insertions(+), 438 deletions(-) create mode 100644 tools/templates/README.md create mode 100644 tools/templates/generate-template-readmes.ts create mode 100644 tools/templates/package.json create mode 100644 tools/templates/pnpm-lock.yaml create mode 100644 tools/templates/update-template-jsons.ts diff --git a/templates/angular-ts/README.md b/templates/angular-ts/README.md index de5dbae93cd..2dcdcc7241c 100644 --- a/templates/angular-ts/README.md +++ b/templates/angular-ts/README.md @@ -61,27 +61,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -99,9 +99,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello diff --git a/templates/basic-cpp/README.md b/templates/basic-cpp/README.md index 8f67916a74e..4e122dc0e94 100644 --- a/templates/basic-cpp/README.md +++ b/templates/basic-cpp/README.md @@ -39,17 +39,17 @@ Use the CLI-managed workflow with `spacetime build`, which wraps CMake + `emcc` ```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 @@ -58,7 +58,7 @@ my-spacetime-app/ │ └── lib.cpp # Server-side logic ├── Cargo.toml └── src/ -└── main.rs # Rust client application + └── main.rs # Rust client application ``` @@ -76,16 +76,16 @@ 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(); + 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(); + for (const auto& person : ctx.db[person]) { + LOG_INFO("Hello, " + person.name + "!"); + } + LOG_INFO("Hello, World!"); + return Ok(); } ``` diff --git a/templates/basic-cs/README.md b/templates/basic-cs/README.md index 547dd74da37..d693ce55ad2 100644 --- a/templates/basic-cs/README.md +++ b/templates/basic-cs/README.md @@ -60,27 +60,27 @@ 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!"); -} + [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!"); + } } ``` @@ -98,9 +98,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM Person" -name + name --------- -"Alice" + "Alice" # Call say_hello to greet everyone spacetime call say_hello diff --git a/templates/basic-rs/README.md b/templates/basic-rs/README.md index 550c3e4f19c..083102976ec 100644 --- a/templates/basic-rs/README.md +++ b/templates/basic-rs/README.md @@ -53,20 +53,20 @@ use spacetimedb::{ReducerContext, Table}; #[spacetimedb::table(accessor = person, public)] pub struct Person { -name: String, + name: String, } #[spacetimedb::reducer] pub fn add(ctx: &ReducerContext, name: String) { -ctx.db.person().insert(Person { name }); + 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!"); + for person in ctx.db.person().iter() { + log::info!("Hello, {}!", person.name); + } + log::info!("Hello, World!"); } ``` @@ -84,9 +84,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call say_hello to greet everyone spacetime call say_hello diff --git a/templates/basic-ts/README.md b/templates/basic-ts/README.md index 29c3a8cce07..a90b1813f62 100644 --- a/templates/basic-ts/README.md +++ b/templates/basic-ts/README.md @@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -88,9 +88,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello diff --git a/templates/browser-ts/README.md b/templates/browser-ts/README.md index 41eaa2d87d6..7e69847d072 100644 --- a/templates/browser-ts/README.md +++ b/templates/browser-ts/README.md @@ -48,28 +48,28 @@ The browser IIFE bundle also exposes the generated `tables` query builders, so y ``` @@ -92,11 +92,11 @@ Register callbacks to update your UI when data changes. ```javascript conn.db.person.onInsert((ctx, person) => { -console.log('New person:', person.name); + console.log('New person:', person.name); }); conn.db.person.onDelete((ctx, person) => { -console.log('Removed:', person.name); + console.log('Removed:', person.name); }); ``` diff --git a/templates/bun-ts/README.md b/templates/bun-ts/README.md index a04dc8f337b..21e5e33ef17 100644 --- a/templates/bun-ts/README.md +++ b/templates/bun-ts/README.md @@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -144,27 +144,27 @@ 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(); + .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(); ```` diff --git a/templates/deno-ts/README.md b/templates/deno-ts/README.md index 716e0eb5a70..3b72e75af58 100644 --- a/templates/deno-ts/README.md +++ b/templates/deno-ts/README.md @@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -144,27 +144,27 @@ 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(); + .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(); ```` @@ -228,14 +228,14 @@ deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-im # 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:*" -} + "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:*" + } } ```` diff --git a/templates/nextjs-ts/README.md b/templates/nextjs-ts/README.md index 25223dd1625..9fc4e60cca9 100644 --- a/templates/nextjs-ts/README.md +++ b/templates/nextjs-ts/README.md @@ -64,27 +64,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -102,9 +102,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello @@ -131,21 +131,21 @@ The `lib/spacetimedb-server.ts` file provides a utility for server-side data fet 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(); -}); + 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(); + }); } ``` @@ -161,8 +161,8 @@ import { PersonList } from './PersonList'; import { fetchPeople } from '../lib/spacetimedb-server'; export default async function Home() { -const initialPeople = await fetchPeople(); -return ; + const initialPeople = await fetchPeople(); + return ; } ``` @@ -174,18 +174,18 @@ 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}
  • )} -
-); + // 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}
  • )} +
+ ); } ``` diff --git a/templates/nodejs-ts/README.md b/templates/nodejs-ts/README.md index 940ea021392..66a81387d26 100644 --- a/templates/nodejs-ts/README.md +++ b/templates/nodejs-ts/README.md @@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -115,28 +115,28 @@ Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` t 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(); + .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(); ```` diff --git a/templates/nuxt-ts/README.md b/templates/nuxt-ts/README.md index 2ffe36a6804..7df666f1010 100644 --- a/templates/nuxt-ts/README.md +++ b/templates/nuxt-ts/README.md @@ -63,27 +63,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -101,9 +101,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello @@ -130,21 +130,21 @@ The server API route connects to SpacetimeDB, subscribes, fetches data, and disc 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(); -}); + 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(); + }); }); ``` @@ -157,14 +157,14 @@ The root `app.vue` wraps your app in a `SpacetimeDBProvider` that manages the We ```vue ``` @@ -208,16 +208,16 @@ const { data: initialPeople } = await useFetch('/api/people'); // On the client, use real-time composables let conn, people, addReducer; if (import.meta.client) { -const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue'); -conn = useSpacetimeDB(); -[people] = useTable(tables.person); -addReducer = useReducer(reducers.add); + const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue'); + conn = useSpacetimeDB(); + [people] = useTable(tables.person); + addReducer = useReducer(reducers.add); } // Use real-time data once connected, fall back to SSR data const displayPeople = computed(() => { -if (conn?.isActive && people?.value) return people.value; -return initialPeople.value ?? []; + if (conn?.isActive && people?.value) return people.value; + return initialPeople.value ?? []; }); ``` diff --git a/templates/react-ts/README.md b/templates/react-ts/README.md index cd4c7488af3..8db4516126e 100644 --- a/templates/react-ts/README.md +++ b/templates/react-ts/README.md @@ -59,27 +59,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -97,9 +97,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello diff --git a/templates/remix-ts/README.md b/templates/remix-ts/README.md index 19ce625b7ef..7a5c0504866 100644 --- a/templates/remix-ts/README.md +++ b/templates/remix-ts/README.md @@ -63,27 +63,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -101,9 +101,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello @@ -130,21 +130,21 @@ The `app/lib/spacetimedb.server.ts` file provides a utility for server-side data 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(); -}); + 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(); + }); } ``` @@ -162,25 +162,25 @@ import { useTable, useReducer } from 'spacetimedb/react'; import { fetchPeople } from '../lib/spacetimedb.server'; export async function loader() { -const people = await fetchPeople(); -return { initialPeople: people }; + const people = await fetchPeople(); + return { initialPeople: people }; } export default function Index() { -const { initialPeople } = useLoaderData(); + const { initialPeople } = useLoaderData(); -// Real-time data from WebSocket subscription -const [people, isLoading] = useTable(tables.person); -const addPerson = useReducer(reducers.add); + // 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; + // Use server data until client is connected + const displayPeople = isLoading ? initialPeople : people; -return ( -
    -{displayPeople.map((person, i) =>
  • {person.name}
  • )} -
-); + return ( +
    + {displayPeople.map((person, i) =>
  • {person.name}
  • )} +
+ ); } ``` diff --git a/templates/svelte-ts/README.md b/templates/svelte-ts/README.md index 75c5994dc7d..3ef596dd66e 100644 --- a/templates/svelte-ts/README.md +++ b/templates/svelte-ts/README.md @@ -58,27 +58,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -96,9 +96,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello diff --git a/templates/tanstack-ts/README.md b/templates/tanstack-ts/README.md index 13ee03ed0ca..090e5bf244e 100644 --- a/templates/tanstack-ts/README.md +++ b/templates/tanstack-ts/README.md @@ -61,27 +61,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -99,9 +99,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello @@ -123,18 +123,18 @@ 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}
  • -))} -
-); + const [people, loading] = useSpacetimeDBQuery(tables.person); + const addPerson = useReducer(reducers.add); + + if (loading) return

Loading...

; + + return ( +
    + {people.map((person, i) => ( +
  • {person.name}
  • + ))} +
+ ); } ```` diff --git a/templates/vue-ts/README.md b/templates/vue-ts/README.md index 4f4e41fe915..95e5b54e398 100644 --- a/templates/vue-ts/README.md +++ b/templates/vue-ts/README.md @@ -58,27 +58,27 @@ Tables store your data. Reducers are functions that modify data — they're the import { schema, table, t } from 'spacetimedb/server'; const spacetimedb = schema({ -person: table( -{ public: true }, -{ -name: t.string(), -} -), + 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 }); -} + { 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!'); + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); }); ``` @@ -96,9 +96,9 @@ spacetime call add Alice # Query the person table spacetime sql "SELECT * FROM person" -name + name --------- -"Alice" + "Alice" # Call sayHello to greet everyone spacetime call say_hello diff --git a/tools/templates/README.md b/tools/templates/README.md new file mode 100644 index 00000000000..99dc2f03acc --- /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` +- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from the slug +- **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..608e0aa5b92 --- /dev/null +++ b/tools/templates/generate-template-readmes.ts @@ -0,0 +1,180 @@ +/** + * 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, 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_TO_QUICKSTART: Record = { + 'react-ts': '00100-react.md', + 'nextjs-ts': '00150-nextjs.md', + 'vue-ts': '00150-vue.md', + 'nuxt-ts': '00155-nuxt.md', + 'svelte-ts': '00160-svelte.md', + 'angular-ts': '00165-angular.md', + 'tanstack-ts': '00170-tanstack.md', + 'remix-ts': '00175-remix.md', + 'browser-ts': '00180-browser.md', + 'bun-ts': '00250-bun.md', + 'deno-ts': '00275-deno.md', + 'nodejs-ts': '00300-nodejs.md', + 'basic-ts': '00400-typescript.md', + 'basic-rs': '00500-rust.md', + 'basic-cs': '00600-c-sharp.md', + 'basic-cpp': '00700-cpp.md', +}; + +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'; +} + +export async function generateTemplateReadmes(): Promise { + let generated = 0; + for (const [templateSlug, quickstartFile] of Object.entries(TEMPLATE_TO_QUICKSTART)) { + const 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 ${quickstartFile}`); + 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/pnpm-lock.yaml b/tools/templates/pnpm-lock.yaml new file mode 100644 index 00000000000..bf289c47f07 --- /dev/null +++ b/tools/templates/pnpm-lock.yaml @@ -0,0 +1,332 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.35 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + +packages: + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@types/node@20.19.35': + resolution: {integrity: sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + 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-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + +snapshots: + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@types/node@20.19.35': + dependencies: + undici-types: 6.21.0 + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + resolve-pkg-maps@1.0.0: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + + undici-types@6.21.0: {} diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts new file mode 100644 index 00000000000..1bfa34cf89e --- /dev/null +++ b/tools/templates/update-template-jsons.ts @@ -0,0 +1,97 @@ +/** + * Updates .template.json in each template folder with builtWith derived from + * the slug. 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'); + +/** Framework slugs that can be derived from template folder names. Must match spacetimedb.com BUILT_WITH keys. */ +const FRAMEWORK_SLUGS = new Set([ + 'react', 'nextjs', 'vue', 'nuxt', 'svelte', 'angular', 'tanstack', 'remix', + 'browser', 'bun', 'deno', 'nodejs', 'spacetimedb', 'tailwind', 'vite', +]); + +function deriveBuiltWith(slug: string): string[] { + const parts = slug.split('-'); + const result = new Set(); + + for (const part of parts) { + if (FRAMEWORK_SLUGS.has(part)) { + result.add(part); + } + } + + result.add('spacetimedb'); + return [...result]; +} + +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 jsonPath = path.join(TEMPLATES_DIR, slug, '.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 = Array.isArray(meta.builtWith) && meta.builtWith.length > 0 + ? meta.builtWith as string[] + : deriveBuiltWith(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); + }); +} From 864e5d5eaa3dd018c50c91c1c0c983a089d2e261 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:30:44 -0400 Subject: [PATCH 04/11] updates --- templates/angular-ts/.template.json | 5 +- templates/basic-cpp/.template.json | 2 +- templates/basic-cs/.template.json | 3 +- templates/basic-rs/.template.json | 4 +- templates/basic-ts/.template.json | 5 +- templates/browser-ts/.template.json | 4 +- templates/bun-ts/.template.json | 4 +- templates/chat-console-cs/.template.json | 2 +- templates/chat-console-rs/.template.json | 4 +- templates/chat-react-ts/.template.json | 16 ++- templates/deno-ts/.template.json | 3 +- templates/nextjs-ts/.template.json | 8 +- templates/nodejs-ts/.template.json | 7 +- templates/nuxt-ts/.template.json | 4 +- templates/react-ts/.template.json | 7 +- templates/remix-ts/.template.json | 10 +- templates/svelte-ts/.template.json | 6 +- templates/tanstack-ts/.template.json | 9 +- templates/vue-ts/.template.json | 6 +- tools/templates/README.md | 2 +- tools/templates/update-template-jsons.ts | 148 ++++++++++++++++++++--- 21 files changed, 218 insertions(+), 41 deletions(-) diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json index d5f08cb3eb1..01832430be1 100644 --- a/templates/angular-ts/.template.json +++ b/templates/angular-ts/.template.json @@ -4,6 +4,9 @@ "server_lang": "typescript", "builtWith": [ "angular", - "spacetimedb" + "rxjs", + "spacetimedb", + "tslib", + "typescript" ] } diff --git a/templates/basic-cpp/.template.json b/templates/basic-cpp/.template.json index 2d23d5e36e8..cabcda719fe 100644 --- a/templates/basic-cpp/.template.json +++ b/templates/basic-cpp/.template.json @@ -3,6 +3,6 @@ "server_lang": "cpp", "client_lang": "rust", "builtWith": [ - "spacetimedb" + "spacetimedb-sdk" ] } diff --git a/templates/basic-cs/.template.json b/templates/basic-cs/.template.json index 49a481812ad..3acf693d379 100644 --- a/templates/basic-cs/.template.json +++ b/templates/basic-cs/.template.json @@ -3,6 +3,7 @@ "client_lang": "csharp", "server_lang": "csharp", "builtWith": [ - "spacetimedb" + "SpacetimeDB.ClientSDK", + "SpacetimeDB.Runtime" ] } diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json index c2d67cfba37..2d968a5b88e 100644 --- a/templates/basic-rs/.template.json +++ b/templates/basic-rs/.template.json @@ -3,6 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ - "spacetimedb" + "log", + "spacetimedb", + "spacetimedb-sdk" ] } diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index 63349429477..744e1e3875f 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -3,6 +3,9 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb" + "esbuild", + "spacetimedb", + "types", + "typescript" ] } diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json index c2150d05574..975a5b5e4d0 100644 --- a/templates/browser-ts/.template.json +++ b/templates/browser-ts/.template.json @@ -3,6 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb" + "spacetimedb", + "typescript", + "vite" ] } diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index 78a924e32ab..4426d3c289c 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -4,6 +4,8 @@ "server_lang": "typescript", "builtWith": [ "bun", - "spacetimedb" + "spacetimedb", + "types", + "typescript" ] } diff --git a/templates/chat-console-cs/.template.json b/templates/chat-console-cs/.template.json index 5a88a797792..e73f0e97f61 100644 --- a/templates/chat-console-cs/.template.json +++ b/templates/chat-console-cs/.template.json @@ -3,6 +3,6 @@ "client_lang": "csharp", "server_lang": "csharp", "builtWith": [ - "spacetimedb" + "SpacetimeDB.Runtime" ] } diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json index 0cfd462f950..eb0e3bdc308 100644 --- a/templates/chat-console-rs/.template.json +++ b/templates/chat-console-rs/.template.json @@ -3,6 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ - "spacetimedb" + "log.workspace", + "spacetimedb", + "spacetimedb-sdk" ] } diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index 83088d82eed..fcdacd360ae 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -3,7 +3,21 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "eslint", + "eslint-plugin-react-hooks", + "eslint-plugin-react-refresh", + "globals", + "jsdom", + "prettier", "react", - "spacetimedb" + "react-dom", + "spacetimedb", + "testing-library", + "types", + "typescript", + "typescript-eslint", + "vite", + "vitejs", + "vitest" ] } diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json index 2ad7af9aa73..103a46b45ad 100644 --- a/templates/deno-ts/.template.json +++ b/templates/deno-ts/.template.json @@ -4,6 +4,7 @@ "server_lang": "typescript", "builtWith": [ "deno", - "spacetimedb" + "spacetimedb", + "typescript" ] } diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json index 6814434698d..c13284ad276 100644 --- a/templates/nextjs-ts/.template.json +++ b/templates/nextjs-ts/.template.json @@ -3,7 +3,11 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "nextjs", - "spacetimedb" + "next", + "react", + "react-dom", + "spacetimedb", + "types", + "typescript" ] } diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index 616d1be4172..f9011801551 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -3,7 +3,10 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "nodejs", - "spacetimedb" + "esbuild", + "spacetimedb", + "types", + "typescript", + "undici" ] } diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json index 31444e9dbdd..e95c0d8da64 100644 --- a/templates/nuxt-ts/.template.json +++ b/templates/nuxt-ts/.template.json @@ -4,6 +4,8 @@ "server_lang": "typescript", "builtWith": [ "nuxt", - "spacetimedb" + "spacetimedb", + "typescript", + "vue" ] } diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index de8fe80a051..321a2d5c653 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -4,6 +4,11 @@ "server_lang": "typescript", "builtWith": [ "react", - "spacetimedb" + "react-dom", + "spacetimedb", + "types", + "typescript", + "vite", + "vitejs" ] } diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 16a2f1eadfd..438fe5b08c4 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -3,7 +3,13 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "remix", - "spacetimedb" + "isbot", + "react", + "react-dom", + "remix-run", + "spacetimedb", + "types", + "typescript", + "vite" ] } diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json index 434e3eede24..aabee6e1c7b 100644 --- a/templates/svelte-ts/.template.json +++ b/templates/svelte-ts/.template.json @@ -3,7 +3,11 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "spacetimedb", "svelte", - "spacetimedb" + "svelte-check", + "sveltejs", + "typescript", + "vite" ] } diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index 19fb5c2334d..581c38931fe 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -3,7 +3,14 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "react", + "react-dom", + "spacetimedb", "tanstack", - "spacetimedb" + "types", + "typescript", + "vite", + "vite-tsconfig-paths", + "vitejs" ] } diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json index 8213c071a08..3c5c05599c7 100644 --- a/templates/vue-ts/.template.json +++ b/templates/vue-ts/.template.json @@ -3,7 +3,11 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "spacetimedb", + "typescript", + "vite", + "vitejs", "vue", - "spacetimedb" + "vue-tsc" ] } diff --git a/tools/templates/README.md b/tools/templates/README.md index 99dc2f03acc..358ae5aebea 100644 --- a/tools/templates/README.md +++ b/tools/templates/README.md @@ -5,7 +5,7 @@ Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. O ## Scripts - **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md` -- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from the slug +- **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 diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index 1bfa34cf89e..ba93e63b93d 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -1,6 +1,6 @@ /** * Updates .template.json in each template folder with builtWith derived from - * the slug. Run from SpacetimeDB repo root. + * 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. @@ -16,24 +16,137 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = path.resolve(__dirname, '../..'); const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates'); -/** Framework slugs that can be derived from template folder names. Must match spacetimedb.com BUILT_WITH keys. */ -const FRAMEWORK_SLUGS = new Set([ - 'react', 'nextjs', 'vue', 'nuxt', 'svelte', 'angular', 'tanstack', 'remix', - 'browser', 'bun', 'deno', 'nodejs', 'spacetimedb', 'tailwind', 'vite', -]); +const PACKAGE_REFERENCE_RE = /PackageReference\s+Include="([^"]+)"/g; -function deriveBuiltWith(slug: string): string[] { - const parts = slug.split('-'); - const result = new Set(); +/** 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; +} + +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)) { + result.push(normalizeNpmPackageName(name)); + } + } + } + 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[] = []; - for (const part of parts) { - if (FRAMEWORK_SLUGS.has(part)) { - result.add(part); + 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 }; +} + +async function collectDepsFromManifests(templateDir: string): Promise { + const seen = new Set(); + const { packageJson, cargoToml, csproj } = await findManifests(templateDir); + + for (const filePath of packageJson) { + try { + const content = await readFile(filePath, 'utf-8'); + for (const dep of parsePackageJson(content)) { + seen.add(dep); + } + } catch { + // skip + } + } + + for (const filePath of cargoToml) { + try { + const content = await readFile(filePath, 'utf-8'); + for (const dep of parseCargoToml(content)) { + seen.add(dep); + } + } catch { + // skip + } + } + + for (const filePath of csproj) { + try { + const content = await readFile(filePath, 'utf-8'); + for (const dep of parseCsproj(content)) { + seen.add(dep); + } + } catch { + // skip } } - result.add('spacetimedb'); - return [...result]; + return [...seen].sort(); } export async function updateTemplateJsons(): Promise { @@ -51,7 +164,8 @@ export async function updateTemplateJsons(): Promise { let updated = 0; for (const slug of dirs) { - const jsonPath = path.join(TEMPLATES_DIR, slug, '.template.json'); + const templateDir = path.join(TEMPLATES_DIR, slug); + const jsonPath = path.join(templateDir, '.template.json'); let jsonRaw: string; try { jsonRaw = await readFile(jsonPath, 'utf-8'); @@ -67,9 +181,7 @@ export async function updateTemplateJsons(): Promise { continue; } - const builtWith = Array.isArray(meta.builtWith) && meta.builtWith.length > 0 - ? meta.builtWith as string[] - : deriveBuiltWith(slug); + const builtWith = await collectDepsFromManifests(templateDir); const { image: _image, ...rest } = meta; const updatedMeta = { ...rest, builtWith }; From 637f5443bcbae65c81be88f1c9ed5bc26373a5ca Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:25:06 -0400 Subject: [PATCH 05/11] templates --- templates/basic-rs/.template.json | 4 ++-- templates/basic-ts/.template.json | 2 +- templates/bun-ts/.template.json | 2 +- templates/chat-console-rs/.template.json | 4 ++-- templates/chat-react-ts/.template.json | 12 ++++++------ templates/chat-react-ts/package.json | 4 ++-- templates/deno-ts/.template.json | 2 +- templates/nodejs-ts/.template.json | 2 +- templates/nuxt-ts/.template.json | 4 ++-- templates/nuxt-ts/package.json | 4 ++-- templates/react-ts/.template.json | 4 ++-- templates/react-ts/package.json | 4 ++-- templates/remix-ts/.template.json | 4 ++-- templates/remix-ts/package.json | 2 +- templates/svelte-ts/.template.json | 4 ++-- templates/svelte-ts/package.json | 2 +- templates/tanstack-ts/.template.json | 6 +++--- templates/vue-ts/.template.json | 4 ++-- templates/vue-ts/package.json | 4 ++-- tools/templates/update-template-jsons.ts | 13 +++++++++---- 20 files changed, 46 insertions(+), 41 deletions(-) diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json index 2d968a5b88e..40a60b180ca 100644 --- a/templates/basic-rs/.template.json +++ b/templates/basic-rs/.template.json @@ -3,8 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ - "log", + "spacetimedb-sdk", "spacetimedb", - "spacetimedb-sdk" + "log" ] } diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index 744e1e3875f..4d1fbbdad84 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -3,9 +3,9 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "esbuild", "spacetimedb", "types", + "esbuild", "typescript" ] } diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index 4426d3c289c..1d451e11df5 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -3,9 +3,9 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "bun", "spacetimedb", "types", + "bun", "typescript" ] } diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json index eb0e3bdc308..3965208ce95 100644 --- a/templates/chat-console-rs/.template.json +++ b/templates/chat-console-rs/.template.json @@ -3,8 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ - "log.workspace", + "spacetimedb-sdk", "spacetimedb", - "spacetimedb-sdk" + "log.workspace" ] } diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index fcdacd360ae..4249f27c1bf 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -3,21 +3,21 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "react", + "react-dom", + "spacetimedb", "eslint", + "testing-library", + "types", + "vitejs", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals", "jsdom", "prettier", - "react", - "react-dom", - "spacetimedb", - "testing-library", - "types", "typescript", "typescript-eslint", "vite", - "vitejs", "vitest" ] } diff --git a/templates/chat-react-ts/package.json b/templates/chat-react-ts/package.json index a5a081caed2..50bba45390a 100644 --- a/templates/chat-react-ts/package.json +++ b/templates/chat-react-ts/package.json @@ -16,9 +16,9 @@ "spacetime:publish": "spacetime publish --module-path server --server maincloud" }, "dependencies": { - "spacetimedb": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "spacetimedb": "workspace:*" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json index 103a46b45ad..49fbea31e1f 100644 --- a/templates/deno-ts/.template.json +++ b/templates/deno-ts/.template.json @@ -3,8 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "deno", "spacetimedb", + "deno", "typescript" ] } diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index f9011801551..3ba16bd9954 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -3,9 +3,9 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "esbuild", "spacetimedb", "types", + "esbuild", "typescript", "undici" ] diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json index e95c0d8da64..c480499f5fd 100644 --- a/templates/nuxt-ts/.template.json +++ b/templates/nuxt-ts/.template.json @@ -4,8 +4,8 @@ "server_lang": "typescript", "builtWith": [ "nuxt", + "vue", "spacetimedb", - "typescript", - "vue" + "typescript" ] } diff --git a/templates/nuxt-ts/package.json b/templates/nuxt-ts/package.json index 5c220f3bcc8..f226f6b257c 100644 --- a/templates/nuxt-ts/package.json +++ b/templates/nuxt-ts/package.json @@ -13,9 +13,9 @@ "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud" }, "dependencies": { - "spacetimedb": "workspace:*", "nuxt": "~3.16.0", - "vue": "^3.5.13" + "vue": "^3.5.13", + "spacetimedb": "workspace:*" }, "devDependencies": { "typescript": "~5.6.2" diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index 321a2d5c653..21cae666fe3 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -7,8 +7,8 @@ "react-dom", "spacetimedb", "types", + "vitejs", "typescript", - "vite", - "vitejs" + "vite" ] } diff --git a/templates/react-ts/package.json b/templates/react-ts/package.json index bd51ae129ae..73c1f27c0c5 100644 --- a/templates/react-ts/package.json +++ b/templates/react-ts/package.json @@ -13,9 +13,9 @@ "spacetime:publish": "spacetime publish --project-path server --server maincloud" }, "dependencies": { - "spacetimedb": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "spacetimedb": "workspace:*" }, "devDependencies": { "@types/react": "^18.3.18", diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 438fe5b08c4..9dfb3aca4a6 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -3,10 +3,10 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "isbot", + "remix-run", "react", "react-dom", - "remix-run", + "isbot", "spacetimedb", "types", "typescript", diff --git a/templates/remix-ts/package.json b/templates/remix-ts/package.json index 155dd879d04..a7bae8c37cc 100644 --- a/templates/remix-ts/package.json +++ b/templates/remix-ts/package.json @@ -16,9 +16,9 @@ "@remix-run/node": "^2.16.0", "@remix-run/react": "^2.16.0", "@remix-run/serve": "^2.16.0", - "isbot": "^5.1.17", "react": "^18.3.1", "react-dom": "^18.3.1", + "isbot": "^5.1.17", "spacetimedb": "workspace:*" }, "devDependencies": { diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json index aabee6e1c7b..d8281c96442 100644 --- a/templates/svelte-ts/.template.json +++ b/templates/svelte-ts/.template.json @@ -3,10 +3,10 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb", "svelte", - "svelte-check", + "spacetimedb", "sveltejs", + "svelte-check", "typescript", "vite" ] diff --git a/templates/svelte-ts/package.json b/templates/svelte-ts/package.json index 98915fd9d95..4bded297466 100644 --- a/templates/svelte-ts/package.json +++ b/templates/svelte-ts/package.json @@ -13,11 +13,11 @@ "spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud" }, "dependencies": { + "svelte": "^5.0.0", "spacetimedb": "workspace:*" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.1.1", - "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", "vite": "^6.4.1" diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index 581c38931fe..566adb72427 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -3,14 +3,14 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "tanstack", "react", "react-dom", "spacetimedb", - "tanstack", "types", + "vitejs", "typescript", "vite", - "vite-tsconfig-paths", - "vitejs" + "vite-tsconfig-paths" ] } diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json index 3c5c05599c7..419f8a17214 100644 --- a/templates/vue-ts/.template.json +++ b/templates/vue-ts/.template.json @@ -3,11 +3,11 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "vue", "spacetimedb", + "vitejs", "typescript", "vite", - "vitejs", - "vue", "vue-tsc" ] } diff --git a/templates/vue-ts/package.json b/templates/vue-ts/package.json index c41aaac13b3..028fa395ab9 100644 --- a/templates/vue-ts/package.json +++ b/templates/vue-ts/package.json @@ -13,8 +13,8 @@ "spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud" }, "dependencies": { - "spacetimedb": "workspace:*", - "vue": "^3.5.13" + "vue": "^3.5.13", + "spacetimedb": "workspace:*" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index ba93e63b93d..762ef4e685f 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -109,11 +109,16 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg 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): Promise { const seen = new Set(); const { packageJson, cargoToml, csproj } = await findManifests(templateDir); - for (const filePath of packageJson) { + for (const filePath of sortRootFirst(packageJson)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parsePackageJson(content)) { @@ -124,7 +129,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of cargoToml) { + for (const filePath of sortRootFirst(cargoToml)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCargoToml(content)) { @@ -135,7 +140,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of csproj) { + for (const filePath of sortRootFirst(csproj)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCsproj(content)) { @@ -146,7 +151,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - return [...seen].sort(); + return [...seen]; } export async function updateTemplateJsons(): Promise { From 532ad01eb3b91f779e38a9fbd1e92e54e7f61c02 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:29:24 -0400 Subject: [PATCH 06/11] Update update-template-jsons.ts --- tools/templates/update-template-jsons.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index 762ef4e685f..ba93e63b93d 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -109,16 +109,11 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg 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): Promise { const seen = new Set(); const { packageJson, cargoToml, csproj } = await findManifests(templateDir); - for (const filePath of sortRootFirst(packageJson)) { + for (const filePath of packageJson) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parsePackageJson(content)) { @@ -129,7 +124,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of sortRootFirst(cargoToml)) { + for (const filePath of cargoToml) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCargoToml(content)) { @@ -140,7 +135,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of sortRootFirst(csproj)) { + for (const filePath of csproj) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCsproj(content)) { @@ -151,7 +146,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - return [...seen]; + return [...seen].sort(); } export async function updateTemplateJsons(): Promise { From 334c2d8b8c25d5620938173c4ef831c48b317fae Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:06:51 -0400 Subject: [PATCH 07/11] Update update-template-jsons.ts --- tools/templates/update-template-jsons.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index ba93e63b93d..762ef4e685f 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -109,11 +109,16 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg 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): Promise { const seen = new Set(); const { packageJson, cargoToml, csproj } = await findManifests(templateDir); - for (const filePath of packageJson) { + for (const filePath of sortRootFirst(packageJson)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parsePackageJson(content)) { @@ -124,7 +129,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of cargoToml) { + for (const filePath of sortRootFirst(cargoToml)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCargoToml(content)) { @@ -135,7 +140,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - for (const filePath of csproj) { + for (const filePath of sortRootFirst(csproj)) { try { const content = await readFile(filePath, 'utf-8'); for (const dep of parseCsproj(content)) { @@ -146,7 +151,7 @@ async function collectDepsFromManifests(templateDir: string): Promise } } - return [...seen].sort(); + return [...seen]; } export async function updateTemplateJsons(): Promise { From dcd816cbdbc437915288455d41937affae520fc6 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:11:48 -0400 Subject: [PATCH 08/11] templates --- templates/basic-ts/.template.json | 1 - templates/bun-ts/.template.json | 1 - templates/chat-react-ts/.template.json | 1 - templates/nextjs-ts/.template.json | 1 - templates/nodejs-ts/.template.json | 2 +- templates/react-ts/.template.json | 1 - templates/remix-ts/.template.json | 1 - templates/tanstack-ts/.template.json | 1 - tools/templates/update-template-jsons.ts | 24 +++++++++++++++++++++--- 9 files changed, 22 insertions(+), 11 deletions(-) diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index 4d1fbbdad84..e46d072d3ce 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -4,7 +4,6 @@ "server_lang": "typescript", "builtWith": [ "spacetimedb", - "types", "esbuild", "typescript" ] diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index 1d451e11df5..52bb8316796 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -4,7 +4,6 @@ "server_lang": "typescript", "builtWith": [ "spacetimedb", - "types", "bun", "typescript" ] diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index 4249f27c1bf..ce27697cc7f 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -8,7 +8,6 @@ "spacetimedb", "eslint", "testing-library", - "types", "vitejs", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json index c13284ad276..8f74dbc30e1 100644 --- a/templates/nextjs-ts/.template.json +++ b/templates/nextjs-ts/.template.json @@ -7,7 +7,6 @@ "react", "react-dom", "spacetimedb", - "types", "typescript" ] } diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index 3ba16bd9954..c066ac463f3 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -3,8 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ + "nodejs", "spacetimedb", - "types", "esbuild", "typescript", "undici" diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index 21cae666fe3..588b144ed62 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -6,7 +6,6 @@ "react", "react-dom", "spacetimedb", - "types", "vitejs", "typescript", "vite" diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 9dfb3aca4a6..916f18bbaff 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -8,7 +8,6 @@ "react-dom", "isbot", "spacetimedb", - "types", "typescript", "vite" ] diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index 566adb72427..2d8a787a99c 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -7,7 +7,6 @@ "react", "react-dom", "spacetimedb", - "types", "vitejs", "typescript", "vite", diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index 762ef4e685f..cec556e4dc1 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -27,6 +27,11 @@ function normalizeNpmPackageName(name: string): string { 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 }; @@ -38,7 +43,8 @@ function parsePackageJson(content: string): string[] { for (const deps of [pkg.dependencies, pkg.devDependencies]) { if (deps && typeof deps === 'object') { for (const name of Object.keys(deps)) { - result.push(normalizeNpmPackageName(name)); + const normalized = normalizeNpmPackageName(name); + if (!shouldSkipPackage(normalized)) result.push(normalized); } } } @@ -114,13 +120,25 @@ 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): Promise { +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); } @@ -186,7 +204,7 @@ export async function updateTemplateJsons(): Promise { continue; } - const builtWith = await collectDepsFromManifests(templateDir); + const builtWith = await collectDepsFromManifests(templateDir, slug); const { image: _image, ...rest } = meta; const updatedMeta = { ...rest, builtWith }; From 96fca6a824fdadf894d3f67bee2d8d17da5150b6 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:49:36 -0400 Subject: [PATCH 09/11] dynamic readme generation --- tools/templates/README.md | 2 +- tools/templates/generate-template-readmes.ts | 102 +++++++++++++++---- 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/tools/templates/README.md b/tools/templates/README.md index 358ae5aebea..151bb130dcc 100644 --- a/tools/templates/README.md +++ b/tools/templates/README.md @@ -4,7 +4,7 @@ Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. O ## Scripts -- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md` +- **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) diff --git a/tools/templates/generate-template-readmes.ts b/tools/templates/generate-template-readmes.ts index 608e0aa5b92..2740d4ad001 100644 --- a/tools/templates/generate-template-readmes.ts +++ b/tools/templates/generate-template-readmes.ts @@ -8,7 +8,7 @@ * Usage: pnpm run generate-readmes (from tools/templates/) */ -import { readFile, writeFile } from 'node:fs/promises'; +import { readFile, readdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -18,24 +18,40 @@ 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_TO_QUICKSTART: Record = { - 'react-ts': '00100-react.md', - 'nextjs-ts': '00150-nextjs.md', - 'vue-ts': '00150-vue.md', - 'nuxt-ts': '00155-nuxt.md', - 'svelte-ts': '00160-svelte.md', - 'angular-ts': '00165-angular.md', - 'tanstack-ts': '00170-tanstack.md', - 'remix-ts': '00175-remix.md', - 'browser-ts': '00180-browser.md', - 'bun-ts': '00250-bun.md', - 'deno-ts': '00275-deno.md', - 'nodejs-ts': '00300-nodejs.md', - 'basic-ts': '00400-typescript.md', - 'basic-rs': '00500-rust.md', - 'basic-cs': '00600-c-sharp.md', - 'basic-cpp': '00700-cpp.md', -}; +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'; @@ -141,17 +157,59 @@ function quickstartMdxToMarkdown( 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, quickstartFile] of Object.entries(TEMPLATE_TO_QUICKSTART)) { - const quickstartFullPath = path.join(QUICKSTARTS_DIR, quickstartFile); + 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 ${quickstartFile}`); + console.warn(`Skipping ${templateSlug}: could not read ${quickstartFullPath}`); continue; } From fbf3b7fa871345679139d7b573e9ab4430110f1e Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:41:31 -0400 Subject: [PATCH 10/11] prettier --- tools/templates/generate-template-readmes.ts | 325 +++++++++--------- tools/templates/update-template-jsons.ts | 331 ++++++++++--------- 2 files changed, 338 insertions(+), 318 deletions(-) diff --git a/tools/templates/generate-template-readmes.ts b/tools/templates/generate-template-readmes.ts index 2740d4ad001..9eacb14eef7 100644 --- a/tools/templates/generate-template-readmes.ts +++ b/tools/templates/generate-template-readmes.ts @@ -15,224 +15,231 @@ 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 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; + 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[]; + 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 { - entries = await readdir(QUICKSTARTS_DIR, { withFileTypes: true }); + 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 { - 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 - } + // skip } - return map; + } + 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(); + 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.' - ); + 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'); + 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; - }); + 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'); + 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; + 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 + 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})`; - } - ); + 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'); + 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 + 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'; + 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); + 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[]; + 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 { - entries = await readdir(TEMPLATES_DIR, { withFileTypes: true }); - } catch (err) { - console.warn(`Could not read templates dir: ${TEMPLATES_DIR}`, err); - return; + 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'); - 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++; + let mdx: string; + try { + mdx = await readFile(quickstartFullPath, 'utf-8'); + } catch (err) { + console.warn( + `Skipping ${templateSlug}: could not read ${quickstartFullPath}` + ); + continue; } - console.log(`Generated ${generated} template READMEs`); + 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]); + 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); - }); + generateTemplateReadmes().catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index cec556e4dc1..44c107823f7 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -20,213 +20,226 @@ 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; + 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'; + 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); - } - } - } + 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); - } - } + 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; } - return result; + 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; + 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; +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); } - 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); - } - } + } 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 }; + 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); + 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 - } +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(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 - } + 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 } + } - return [...seen]; + return [...seen]; } export async function updateTemplateJsons(): Promise { - let entries: import('node:fs').Dirent[]; + 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 { - entries = await readdir(TEMPLATES_DIR, { withFileTypes: true }); - } catch (err) { - console.warn(`Could not read templates dir: ${TEMPLATES_DIR}`, err); - return; + jsonRaw = await readFile(jsonPath, 'utf-8'); + } catch { + continue; } - 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; - } + let meta: Record; + try { + meta = JSON.parse(jsonRaw); + } catch { + console.warn(`Skipping ${slug}: invalid JSON`); + continue; + } - const builtWith = await collectDepsFromManifests(templateDir, slug); + const builtWith = await collectDepsFromManifests(templateDir, slug); - const { image: _image, ...rest } = meta; - const updatedMeta = { ...rest, builtWith }; + 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++; - } + 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)`); + 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]); + 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); - }); + updateTemplateJsons().catch(err => { + console.error(err); + process.exit(1); + }); } From eaa35a42fa6f8df6e8bbbf4b9fb7887156dc0fab Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:08:43 -0400 Subject: [PATCH 11/11] Built with ordering without changing packages... --- templates/angular-ts/.template.json | 4 +- templates/basic-rs/.template.json | 4 +- templates/basic-ts/.template.json | 4 +- templates/browser-ts/.template.json | 4 +- templates/bun-ts/.template.json | 4 +- templates/chat-console-rs/.template.json | 4 +- templates/chat-react-ts/.template.json | 4 +- templates/chat-react-ts/package.json | 4 +- templates/deno-ts/.template.json | 4 +- templates/nextjs-ts/.template.json | 4 +- templates/nodejs-ts/.template.json | 4 +- templates/nuxt-ts/.template.json | 4 +- templates/nuxt-ts/package.json | 4 +- templates/react-ts/.template.json | 4 +- templates/react-ts/package.json | 4 +- templates/remix-ts/.template.json | 6 +- templates/remix-ts/package.json | 2 +- templates/svelte-ts/.template.json | 6 +- templates/svelte-ts/package.json | 2 +- templates/tanstack-ts/.template.json | 4 +- templates/vue-ts/.template.json | 4 +- templates/vue-ts/package.json | 4 +- tools/templates/pnpm-lock.yaml | 332 ----------------------- tools/templates/update-template-jsons.ts | 12 +- 24 files changed, 55 insertions(+), 377 deletions(-) delete mode 100644 tools/templates/pnpm-lock.yaml diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json index f533c2c13b4..5d54f7d2eff 100644 --- a/templates/angular-ts/.template.json +++ b/templates/angular-ts/.template.json @@ -6,8 +6,8 @@ "builtWith": [ "angular", "rxjs", - "spacetimedb", "tslib", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json index 40a60b180ca..ac616be16e3 100644 --- a/templates/basic-rs/.template.json +++ b/templates/basic-rs/.template.json @@ -3,8 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ + "log", "spacetimedb-sdk", - "spacetimedb", - "log" + "spacetimedb" ] } diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json index e46d072d3ce..5039ff8aac2 100644 --- a/templates/basic-ts/.template.json +++ b/templates/basic-ts/.template.json @@ -3,8 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb", "esbuild", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json index 975a5b5e4d0..9cff3099637 100644 --- a/templates/browser-ts/.template.json +++ b/templates/browser-ts/.template.json @@ -3,8 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb", "typescript", - "vite" + "vite", + "spacetimedb" ] } diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json index 82af44827bb..08fdc2fa863 100644 --- a/templates/bun-ts/.template.json +++ b/templates/bun-ts/.template.json @@ -4,8 +4,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb", "bun", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json index 3965208ce95..e51990cfd62 100644 --- a/templates/chat-console-rs/.template.json +++ b/templates/chat-console-rs/.template.json @@ -3,8 +3,8 @@ "client_lang": "rust", "server_lang": "rust", "builtWith": [ + "log.workspace", "spacetimedb-sdk", - "spacetimedb", - "log.workspace" + "spacetimedb" ] } diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json index ce27697cc7f..6ca582e36b5 100644 --- a/templates/chat-react-ts/.template.json +++ b/templates/chat-react-ts/.template.json @@ -5,7 +5,6 @@ "builtWith": [ "react", "react-dom", - "spacetimedb", "eslint", "testing-library", "vitejs", @@ -17,6 +16,7 @@ "typescript", "typescript-eslint", "vite", - "vitest" + "vitest", + "spacetimedb" ] } diff --git a/templates/chat-react-ts/package.json b/templates/chat-react-ts/package.json index 50bba45390a..a5a081caed2 100644 --- a/templates/chat-react-ts/package.json +++ b/templates/chat-react-ts/package.json @@ -16,9 +16,9 @@ "spacetime:publish": "spacetime publish --module-path server --server maincloud" }, "dependencies": { + "spacetimedb": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1", - "spacetimedb": "workspace:*" + "react-dom": "^18.3.1" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json index 49fbea31e1f..0bc1e4b7f36 100644 --- a/templates/deno-ts/.template.json +++ b/templates/deno-ts/.template.json @@ -3,8 +3,8 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "spacetimedb", "deno", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json index a34ef4b4c69..ad1d03c3b99 100644 --- a/templates/nextjs-ts/.template.json +++ b/templates/nextjs-ts/.template.json @@ -7,7 +7,7 @@ "next", "react", "react-dom", - "spacetimedb", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json index f9db989ce70..ea32f10847b 100644 --- a/templates/nodejs-ts/.template.json +++ b/templates/nodejs-ts/.template.json @@ -5,9 +5,9 @@ "server_lang": "typescript", "builtWith": [ "nodejs", - "spacetimedb", "esbuild", "typescript", - "undici" + "undici", + "spacetimedb" ] } diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json index 19959407964..3eefe3bf2a3 100644 --- a/templates/nuxt-ts/.template.json +++ b/templates/nuxt-ts/.template.json @@ -6,7 +6,7 @@ "builtWith": [ "nuxt", "vue", - "spacetimedb", - "typescript" + "typescript", + "spacetimedb" ] } diff --git a/templates/nuxt-ts/package.json b/templates/nuxt-ts/package.json index f226f6b257c..5c220f3bcc8 100644 --- a/templates/nuxt-ts/package.json +++ b/templates/nuxt-ts/package.json @@ -13,9 +13,9 @@ "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud" }, "dependencies": { + "spacetimedb": "workspace:*", "nuxt": "~3.16.0", - "vue": "^3.5.13", - "spacetimedb": "workspace:*" + "vue": "^3.5.13" }, "devDependencies": { "typescript": "~5.6.2" diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json index 9d0fa315408..e60c63dabc6 100644 --- a/templates/react-ts/.template.json +++ b/templates/react-ts/.template.json @@ -6,9 +6,9 @@ "builtWith": [ "react", "react-dom", - "spacetimedb", "vitejs", "typescript", - "vite" + "vite", + "spacetimedb" ] } diff --git a/templates/react-ts/package.json b/templates/react-ts/package.json index 801748f41af..00765c9e27a 100644 --- a/templates/react-ts/package.json +++ b/templates/react-ts/package.json @@ -13,9 +13,9 @@ "spacetime:publish": "spacetime publish --module-path server --server maincloud" }, "dependencies": { + "spacetimedb": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1", - "spacetimedb": "workspace:*" + "react-dom": "^18.3.1" }, "devDependencies": { "@types/react": "^18.3.18", diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json index 74618cb26ba..1259fb05a92 100644 --- a/templates/remix-ts/.template.json +++ b/templates/remix-ts/.template.json @@ -5,11 +5,11 @@ "server_lang": "typescript", "builtWith": [ "remix-run", + "isbot", "react", "react-dom", - "isbot", - "spacetimedb", "typescript", - "vite" + "vite", + "spacetimedb" ] } diff --git a/templates/remix-ts/package.json b/templates/remix-ts/package.json index a7bae8c37cc..155dd879d04 100644 --- a/templates/remix-ts/package.json +++ b/templates/remix-ts/package.json @@ -16,9 +16,9 @@ "@remix-run/node": "^2.16.0", "@remix-run/react": "^2.16.0", "@remix-run/serve": "^2.16.0", + "isbot": "^5.1.17", "react": "^18.3.1", "react-dom": "^18.3.1", - "isbot": "^5.1.17", "spacetimedb": "workspace:*" }, "devDependencies": { diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json index 6ab4d8a5c57..e6db7ed45e2 100644 --- a/templates/svelte-ts/.template.json +++ b/templates/svelte-ts/.template.json @@ -4,11 +4,11 @@ "client_lang": "typescript", "server_lang": "typescript", "builtWith": [ - "svelte", - "spacetimedb", "sveltejs", + "svelte", "svelte-check", "typescript", - "vite" + "vite", + "spacetimedb" ] } diff --git a/templates/svelte-ts/package.json b/templates/svelte-ts/package.json index 56eb77870bc..fb909102b0e 100644 --- a/templates/svelte-ts/package.json +++ b/templates/svelte-ts/package.json @@ -13,11 +13,11 @@ "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud" }, "dependencies": { - "svelte": "^5.0.0", "spacetimedb": "workspace:*" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.1.1", + "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", "vite": "^6.4.1" diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json index 31a860d08ca..2c7333fa606 100644 --- a/templates/tanstack-ts/.template.json +++ b/templates/tanstack-ts/.template.json @@ -7,10 +7,10 @@ "tanstack", "react", "react-dom", - "spacetimedb", "vitejs", "typescript", "vite", - "vite-tsconfig-paths" + "vite-tsconfig-paths", + "spacetimedb" ] } diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json index 04c4630d86b..73fa04a4814 100644 --- a/templates/vue-ts/.template.json +++ b/templates/vue-ts/.template.json @@ -5,10 +5,10 @@ "server_lang": "typescript", "builtWith": [ "vue", - "spacetimedb", "vitejs", "typescript", "vite", - "vue-tsc" + "vue-tsc", + "spacetimedb" ] } diff --git a/templates/vue-ts/package.json b/templates/vue-ts/package.json index 55a1edd91b9..8a41c1f0ef3 100644 --- a/templates/vue-ts/package.json +++ b/templates/vue-ts/package.json @@ -13,8 +13,8 @@ "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud" }, "dependencies": { - "vue": "^3.5.13", - "spacetimedb": "workspace:*" + "spacetimedb": "workspace:*", + "vue": "^3.5.13" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", diff --git a/tools/templates/pnpm-lock.yaml b/tools/templates/pnpm-lock.yaml deleted file mode 100644 index bf289c47f07..00000000000 --- a/tools/templates/pnpm-lock.yaml +++ /dev/null @@ -1,332 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@types/node': - specifier: ^20.0.0 - version: 20.19.35 - tsx: - specifier: ^4.7.0 - version: 4.21.0 - -packages: - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@types/node@20.19.35': - resolution: {integrity: sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==} - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - - 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-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} - engines: {node: '>=18.0.0'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - -snapshots: - - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - - '@types/node@20.19.35': - dependencies: - undici-types: 6.21.0 - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - - fsevents@2.3.3: - optional: true - - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - - resolve-pkg-maps@1.0.0: {} - - tsx@4.21.0: - dependencies: - esbuild: 0.27.3 - get-tsconfig: 4.13.6 - optionalDependencies: - fsevents: 2.3.3 - - undici-types@6.21.0: {} diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts index 44c107823f7..4cff064ce4c 100644 --- a/tools/templates/update-template-jsons.ts +++ b/tools/templates/update-template-jsons.ts @@ -179,7 +179,17 @@ async function collectDepsFromManifests( } } - return [...seen]; + 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 {