From 8f20c966c5cc6752c6b717f32314057cd78e8379 Mon Sep 17 00:00:00 2001 From: Leo Voon Date: Sun, 22 Mar 2026 01:01:35 +0800 Subject: [PATCH 1/2] Add Astro TypeScript template --- .../00200-quickstarts/00152-astro.md | 181 ++++++++++++++++++ .../00100-databases/00200-spacetime-dev.md | 1 + templates/astro-ts/.gitignore | 28 +++ templates/astro-ts/.template.json | 13 ++ templates/astro-ts/LICENSE | 1 + templates/astro-ts/README.md | 137 +++++++++++++ templates/astro-ts/astro.config.mjs | 12 ++ templates/astro-ts/package.json | 30 +++ templates/astro-ts/public/favicon.svg | 9 + templates/astro-ts/spacetimedb/package.json | 18 ++ templates/astro-ts/spacetimedb/src/index.ts | 38 ++++ templates/astro-ts/spacetimedb/tsconfig.json | 12 ++ .../components/DeferredPeopleSnapshot.astro | 31 +++ .../astro-ts/src/components/PersonList.tsx | 86 +++++++++ .../astro-ts/src/components/SpacetimeApp.tsx | 56 ++++++ templates/astro-ts/src/env.d.ts | 10 + templates/astro-ts/src/layouts/Layout.astro | 28 +++ .../astro-ts/src/lib/spacetimedb-server.ts | 54 ++++++ .../src/module_bindings/add_reducer.ts | 15 ++ .../astro-ts/src/module_bindings/index.ts | 93 +++++++++ .../src/module_bindings/person_table.ts | 15 ++ .../src/module_bindings/say_hello_reducer.ts | 13 ++ .../astro-ts/src/module_bindings/types.ts | 16 ++ templates/astro-ts/src/pages/index.astro | 54 ++++++ templates/astro-ts/src/styles/global.css | 98 ++++++++++ templates/astro-ts/tsconfig.json | 9 + 26 files changed, 1058 insertions(+) create mode 100644 docs/docs/00100-intro/00200-quickstarts/00152-astro.md create mode 100644 templates/astro-ts/.gitignore create mode 100644 templates/astro-ts/.template.json create mode 100644 templates/astro-ts/LICENSE create mode 100644 templates/astro-ts/README.md create mode 100644 templates/astro-ts/astro.config.mjs create mode 100644 templates/astro-ts/package.json create mode 100644 templates/astro-ts/public/favicon.svg create mode 100644 templates/astro-ts/spacetimedb/package.json create mode 100644 templates/astro-ts/spacetimedb/src/index.ts create mode 100644 templates/astro-ts/spacetimedb/tsconfig.json create mode 100644 templates/astro-ts/src/components/DeferredPeopleSnapshot.astro create mode 100644 templates/astro-ts/src/components/PersonList.tsx create mode 100644 templates/astro-ts/src/components/SpacetimeApp.tsx create mode 100644 templates/astro-ts/src/env.d.ts create mode 100644 templates/astro-ts/src/layouts/Layout.astro create mode 100644 templates/astro-ts/src/lib/spacetimedb-server.ts create mode 100644 templates/astro-ts/src/module_bindings/add_reducer.ts create mode 100644 templates/astro-ts/src/module_bindings/index.ts create mode 100644 templates/astro-ts/src/module_bindings/person_table.ts create mode 100644 templates/astro-ts/src/module_bindings/say_hello_reducer.ts create mode 100644 templates/astro-ts/src/module_bindings/types.ts create mode 100644 templates/astro-ts/src/pages/index.astro create mode 100644 templates/astro-ts/src/styles/global.css create mode 100644 templates/astro-ts/tsconfig.json diff --git a/docs/docs/00100-intro/00200-quickstarts/00152-astro.md b/docs/docs/00100-intro/00200-quickstarts/00152-astro.md new file mode 100644 index 00000000000..0cf2f0daf4b --- /dev/null +++ b/docs/docs/00100-intro/00200-quickstarts/00152-astro.md @@ -0,0 +1,181 @@ +--- +title: Astro Quickstart +sidebar_label: Astro +slug: /quickstarts/astro +hide_table_of_contents: true +--- + +import { InstallCardLink } from "@site/src/components/InstallCardLink"; +import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps"; + + +Get a SpacetimeDB Astro app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + + + +--- + + + + + Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Astro client. + + This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Astro development server. + + +```bash +spacetime dev --template astro-ts +``` + + + + + + Navigate to [http://localhost:4321](http://localhost:4321) to see your app running. + + The Astro app reads `SPACETIMEDB_*` variables on the server and `PUBLIC_SPACETIMEDB_*` variables in the client, so `.env.local` can configure both sides of the app. + + + + + + Your project contains both server and client code using Astro SSR and a live React island for real-time updates. + + Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/pages/index.astro` and `src/components/PersonList.tsx` to build your UI. + + +```text +my-astro-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # SpacetimeDB module logic +├── src/ +│ ├── components/ +│ │ ├── PersonList.tsx +│ │ ├── SpacetimeApp.tsx +│ │ └── DeferredPeopleSnapshot.astro +│ ├── lib/ +│ │ └── spacetimedb-server.ts +│ ├── module_bindings/ # Auto-generated types +│ ├── layouts/ +│ │ └── Layout.astro +│ ├── pages/ +│ │ └── index.astro +│ └── styles/ +│ └── global.css +└── package.json +``` + + + + + + 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 and are 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!'); +}); +``` + + + + + + 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! +``` + + + + + + The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach: + + - **Astro page** (`src/pages/index.astro`): Fetches initial data on the server for a fast first render + - **React island** (`src/components/SpacetimeApp.tsx`): Hydrates on the client and provides the SpacetimeDB connection + - **Live table UI** (`src/components/PersonList.tsx`): Uses `useTable()` and `useReducer()` for real-time updates + + +```astro +--- +import Layout from '../layouts/Layout.astro'; +import SpacetimeApp from '../components/SpacetimeApp'; +import { fetchPeople } from '../lib/spacetimedb-server'; + +const initialPeople = await fetchPeople(); +--- + + +

astro-ts

+ +
+``` +
+
+ + + + The template also includes a deferred Astro-only section to demonstrate `server:defer`. + + `src/components/DeferredPeopleSnapshot.astro` fetches its own server-rendered snapshot, and `src/pages/index.astro` renders it as a server island without changing the main real-time client flow. + + +```astro + +
Loading deferred people snapshot…
+
+``` +
+
+
diff --git a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md index f8384e5bc6e..f556450be04 100644 --- a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md +++ b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md @@ -70,6 +70,7 @@ Choose from several built-in templates: - `basic-rs` - Basic Rust client and server stubs - `basic-cpp` - Basic C++ server stubs - `react-ts` - React web app with TypeScript server +- `astro-ts` - Astro app with TypeScript server - `chat-console-rs` - Complete Rust chat implementation - `chat-console-cs` - Complete C# chat implementation - `chat-react-ts` - Complete TypeScript chat implementation diff --git a/templates/astro-ts/.gitignore b/templates/astro-ts/.gitignore new file mode 100644 index 00000000000..9e7cfe93106 --- /dev/null +++ b/templates/astro-ts/.gitignore @@ -0,0 +1,28 @@ +# build output +dist/ +spacetimedb/dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.local +.env.*.local +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ + diff --git a/templates/astro-ts/.template.json b/templates/astro-ts/.template.json new file mode 100644 index 00000000000..795b0c40260 --- /dev/null +++ b/templates/astro-ts/.template.json @@ -0,0 +1,13 @@ +{ + "description": "Astro app with a TypeScript server module", + "client_framework": "Astro", + "client_lang": "typescript", + "server_lang": "typescript", + "builtWith": [ + "astro", + "react", + "react-dom", + "typescript", + "spacetimedb" + ] +} diff --git a/templates/astro-ts/LICENSE b/templates/astro-ts/LICENSE new file mode 100644 index 00000000000..18d511e85da --- /dev/null +++ b/templates/astro-ts/LICENSE @@ -0,0 +1 @@ +../../licenses/apache2.txt diff --git a/templates/astro-ts/README.md b/templates/astro-ts/README.md new file mode 100644 index 00000000000..cc728b753a8 --- /dev/null +++ b/templates/astro-ts/README.md @@ -0,0 +1,137 @@ +Get a SpacetimeDB Astro 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 Astro client. + +This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Astro development server. + +```bash +spacetime dev --template astro-ts +``` + +## Open your app + +Navigate to [http://localhost:4321](http://localhost:4321) to see your app running. + +The Astro app reads `SPACETIMEDB_*` variables on the server and `PUBLIC_SPACETIMEDB_*` variables in the client, so `.env.local` can configure both sides of the app. + +## Explore the project structure + +Your project contains both server and client code using Astro SSR and a live interactive client for real-time updates. + +Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/pages/index.astro` and `src/components/PersonList.tsx` to build your UI. + +```text +my-astro-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # SpacetimeDB module logic +├── src/ +│ ├── components/ +│ │ ├── PersonList.tsx +│ │ ├── SpacetimeApp.tsx +│ │ └── DeferredPeopleSnapshot.astro +│ ├── lib/ +│ │ └── spacetimedb-server.ts +│ ├── module_bindings/ # Auto-generated types +│ ├── layouts/ +│ │ └── Layout.astro +│ ├── pages/ +│ │ └── index.astro +│ └── styles/ +│ └── global.css +└── 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 and are 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!'); +}); +``` + +## Understand Astro SSR plus real-time hydration + +The template uses a hybrid rendering model: + +- `src/pages/index.astro` fetches the initial list of people on the server for a fast first paint. +- `src/components/SpacetimeApp.tsx` hydrates with `client:load` and provides the SpacetimeDB connection. +- `src/components/PersonList.tsx` subscribes to the `person` table with `useTable()` and calls reducers with `useReducer()`. + +This gives you server-rendered HTML on the first request and a live WebSocket-backed UI after hydration. + +## Understand Astro server islands + +The template also includes a deferred Astro-only section to demonstrate `server:defer`. + +- `src/components/DeferredPeopleSnapshot.astro` fetches its own server-rendered snapshot. +- `src/pages/index.astro` renders it with `server:defer`, so it loads after the main page shell. + +This demonstrates an Astro-specific pattern without affecting the main real-time client flow. + +## 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/astro-ts/astro.config.mjs b/templates/astro-ts/astro.config.mjs new file mode 100644 index 00000000000..d1243fa519d --- /dev/null +++ b/templates/astro-ts/astro.config.mjs @@ -0,0 +1,12 @@ +// @ts-check +import node from '@astrojs/node'; +import react from '@astrojs/react'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + adapter: node({ + mode: 'standalone', + }), + integrations: [react()], +}); diff --git a/templates/astro-ts/package.json b/templates/astro-ts/package.json new file mode 100644 index 00000000000..1e194115bb8 --- /dev/null +++ b/templates/astro-ts/package.json @@ -0,0 +1,30 @@ +{ + "name": "@clockworklabs/astro-ts", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "start": "node ./dist/server/entry.mjs", + "astro": "astro", + "generate": "pnpm --dir spacetimedb install && cargo run -p gen-bindings -- --out-dir src/module_bindings --module-path spacetimedb && prettier --write src/module_bindings", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb", + "spacetime:publish:local": "spacetime publish --module-path spacetimedb --server local", + "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud" + }, + "dependencies": { + "@astrojs/node": "^10.0.3", + "@astrojs/react": "^5.0.1", + "astro": "^6.0.8", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "spacetimedb": "workspace:*" + }, + "devDependencies": { + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "typescript": "~5.6.2" + } +} diff --git a/templates/astro-ts/public/favicon.svg b/templates/astro-ts/public/favicon.svg new file mode 100644 index 00000000000..f157bd1c5e2 --- /dev/null +++ b/templates/astro-ts/public/favicon.svg @@ -0,0 +1,9 @@ + + + + diff --git a/templates/astro-ts/spacetimedb/package.json b/templates/astro-ts/spacetimedb/package.json new file mode 100644 index 00000000000..8a263dc97a6 --- /dev/null +++ b/templates/astro-ts/spacetimedb/package.json @@ -0,0 +1,18 @@ +{ + "name": "spacetime-module", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "spacetime build", + "publish": "spacetime publish" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "spacetimedb": "workspace:*" + }, + "devDependencies": { + "typescript": "~5.6.2" + } +} diff --git a/templates/astro-ts/spacetimedb/src/index.ts b/templates/astro-ts/spacetimedb/src/index.ts new file mode 100644 index 00000000000..3f4738743b1 --- /dev/null +++ b/templates/astro-ts/spacetimedb/src/index.ts @@ -0,0 +1,38 @@ +import { schema, table, t } from 'spacetimedb/server'; + +const spacetimedb = schema({ + person: table( + { public: true }, + { + name: t.string(), + } + ), +}); +export default spacetimedb; + +export const init = spacetimedb.init(_ctx => { + // Called when the module is initially published. +}); + +export const onConnect = spacetimedb.clientConnected(_ctx => { + // Called every time a new client connects. +}); + +export const onDisconnect = spacetimedb.clientDisconnected(_ctx => { + // Called every time a client disconnects. +}); + +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!'); +}); diff --git a/templates/astro-ts/spacetimedb/tsconfig.json b/templates/astro-ts/spacetimedb/tsconfig.json new file mode 100644 index 00000000000..0bede6a959b --- /dev/null +++ b/templates/astro-ts/spacetimedb/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} diff --git a/templates/astro-ts/src/components/DeferredPeopleSnapshot.astro b/templates/astro-ts/src/components/DeferredPeopleSnapshot.astro new file mode 100644 index 00000000000..d58aed195f7 --- /dev/null +++ b/templates/astro-ts/src/components/DeferredPeopleSnapshot.astro @@ -0,0 +1,31 @@ +--- +import { fetchPeople } from '../lib/spacetimedb-server'; + +const people = await fetchPeople(); +const renderedAt = new Intl.DateTimeFormat('en', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}).format(new Date()); +--- + +
+

Server-rendered snapshot

+

Rendered at {renderedAt}

+

+ This section is a plain Astro component rendered with server:defer. It fetches a + new SpacetimeDB snapshot on its own request, separately from the live client section above. +

+ + {people.length === 0 ? ( +

No people are in the table yet for this deferred snapshot.

+ ) : ( +
    + {people.map(person => ( +
  • + {person.name} +
  • + ))} +
+ )} +
diff --git a/templates/astro-ts/src/components/PersonList.tsx b/templates/astro-ts/src/components/PersonList.tsx new file mode 100644 index 00000000000..cc8117447c4 --- /dev/null +++ b/templates/astro-ts/src/components/PersonList.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState, type ComponentProps } from 'react'; +import { reducers, tables } from '../module_bindings'; +import type { PersonData } from '../lib/spacetimedb-server'; +import { useReducer, useSpacetimeDB, useTable } from 'spacetimedb/react'; + +interface PersonListProps { + initialPeople: PersonData[]; +} + +export function PersonList({ initialPeople }: PersonListProps) { + const [name, setName] = useState(''); + const [isHydrated, setIsHydrated] = useState(false); + + const connection = useSpacetimeDB(); + const { isActive: connected } = connection; + + const [people, isLoading] = useTable(tables.person); + const addPersonReducer = useReducer(reducers.add); + + useEffect(() => { + if (connected && !isLoading) { + setIsHydrated(true); + } + }, [connected, isLoading]); + + const displayPeople = isHydrated ? people : initialPeople; + + const handleSubmit: NonNullable['onSubmit']> = event => { + event.preventDefault(); + + const trimmedName = name.trim(); + if (!trimmedName || !connected) { + return; + } + + addPersonReducer({ name: trimmedName }); + setName(''); + }; + + return ( +
+
+ Status:{' '} + + {connected ? 'Connected' : 'Connecting...'} + + + {isHydrated ? 'Live subscription active' : 'Showing server snapshot'} + +
+ +
+ + setName(event.target.value)} + disabled={!connected} + /> + +
+ +

People ({displayPeople.length})

+ + {displayPeople.length === 0 ? ( +

No people yet. Add someone to seed the table.

+ ) : ( +
    + {displayPeople.map((person, index) => ( +
  • + {person.name} +
  • + ))} +
+ )} +
+ ); +} diff --git a/templates/astro-ts/src/components/SpacetimeApp.tsx b/templates/astro-ts/src/components/SpacetimeApp.tsx new file mode 100644 index 00000000000..2a720012872 --- /dev/null +++ b/templates/astro-ts/src/components/SpacetimeApp.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react'; +import type { Identity } from 'spacetimedb'; +import { SpacetimeDBProvider } from 'spacetimedb/react'; +import { DbConnection, type ErrorContext } from '../module_bindings'; +import type { PersonData } from '../lib/spacetimedb-server'; +import { PersonList } from './PersonList'; + +interface SpacetimeAppProps { + initialPeople: PersonData[]; +} + +const HOST = import.meta.env.PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; +const DB_NAME = import.meta.env.PUBLIC_SPACETIMEDB_DB_NAME ?? 'astro-ts'; +const TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`; + +function getStoredToken() { + if (typeof window === 'undefined') { + return undefined; + } + + return localStorage.getItem(TOKEN_KEY) ?? undefined; +} + +const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { + if (typeof window !== 'undefined') { + localStorage.setItem(TOKEN_KEY, token); + } + + console.info('Connected to SpacetimeDB with identity:', identity.toHexString()); +}; + +const onDisconnect = () => { + console.info('Disconnected from SpacetimeDB'); +}; + +const onConnectError = (_ctx: ErrorContext, error: Error) => { + console.error('Error connecting to SpacetimeDB:', error); +}; + +export function SpacetimeApp({ initialPeople }: SpacetimeAppProps) { + const [connectionBuilder] = useState(() => + DbConnection.builder() + .withUri(HOST) + .withDatabaseName(DB_NAME) + .withToken(getStoredToken()) + .onConnect(onConnect) + .onDisconnect(onDisconnect) + .onConnectError(onConnectError) + ); + + return ( + + + + ); +} diff --git a/templates/astro-ts/src/env.d.ts b/templates/astro-ts/src/env.d.ts new file mode 100644 index 00000000000..dc1e5ef1e07 --- /dev/null +++ b/templates/astro-ts/src/env.d.ts @@ -0,0 +1,10 @@ +interface ImportMetaEnv { + readonly SPACETIMEDB_HOST?: string; + readonly SPACETIMEDB_DB_NAME?: string; + readonly PUBLIC_SPACETIMEDB_HOST?: string; + readonly PUBLIC_SPACETIMEDB_DB_NAME?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/templates/astro-ts/src/layouts/Layout.astro b/templates/astro-ts/src/layouts/Layout.astro new file mode 100644 index 00000000000..75c239ae396 --- /dev/null +++ b/templates/astro-ts/src/layouts/Layout.astro @@ -0,0 +1,28 @@ +--- +import '../styles/global.css'; + +interface Props { + title?: string; + description?: string; +} + +const { + title = 'SpacetimeDB Astro App', + description = 'An Astro app powered by SpacetimeDB with server-rendered data and a live interactive client.', +} = Astro.props; +--- + + + + + + + + + + {title} + + + + + diff --git a/templates/astro-ts/src/lib/spacetimedb-server.ts b/templates/astro-ts/src/lib/spacetimedb-server.ts new file mode 100644 index 00000000000..5aeedaed982 --- /dev/null +++ b/templates/astro-ts/src/lib/spacetimedb-server.ts @@ -0,0 +1,54 @@ +import { DbConnection, tables } from '../module_bindings'; +import type { Person } from '../module_bindings/types'; + +const HOST = + import.meta.env.SPACETIMEDB_HOST ?? + import.meta.env.PUBLIC_SPACETIMEDB_HOST ?? + 'ws://localhost:3000'; +const DB_NAME = + import.meta.env.SPACETIMEDB_DB_NAME ?? + import.meta.env.PUBLIC_SPACETIMEDB_DB_NAME ?? + 'astro-ts'; + +export type PersonData = Person; + +/** + * Fetches the initial list of people from SpacetimeDB during Astro SSR. + * The page renders this snapshot first, then the interactive client hydrates + * into a live subscription. + */ +export async function fetchPeople(): Promise { + return new Promise((resolve, reject) => { + let connection: DbConnection | undefined; + + const timeoutId = setTimeout(() => { + connection?.disconnect(); + reject(new Error('SpacetimeDB connection timeout')); + }, 10_000); + + connection = DbConnection.builder() + .withUri(HOST) + .withDatabaseName(DB_NAME) + .onConnect(conn => { + conn + .subscriptionBuilder() + .onApplied(() => { + clearTimeout(timeoutId); + const people = Array.from(conn.db.person.iter()); + conn.disconnect(); + resolve(people); + }) + .onError(ctx => { + clearTimeout(timeoutId); + conn.disconnect(); + reject(ctx.event ?? new Error('Subscription error')); + }) + .subscribe(tables.person); + }) + .onConnectError((_ctx, error) => { + clearTimeout(timeoutId); + reject(error); + }) + .build(); + }); +} diff --git a/templates/astro-ts/src/module_bindings/add_reducer.ts b/templates/astro-ts/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..ce493ee8574 --- /dev/null +++ b/templates/astro-ts/src/module_bindings/add_reducer.ts @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default { + name: __t.string(), +}; diff --git a/templates/astro-ts/src/module_bindings/index.ts b/templates/astro-ts/src/module_bindings/index.ts new file mode 100644 index 00000000000..dc377229927 --- /dev/null +++ b/templates/astro-ts/src/module_bindings/index.ts @@ -0,0 +1,93 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 2.0.3 (commit 5836c268a7307f864b33636357b0869ecc40bc0a). + +/* eslint-disable */ +/* tslint:disable */ +import { + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TypeBuilder as __TypeBuilder, + Uuid as __Uuid, + convertToAccessorMap as __convertToAccessorMap, + makeQueryBuilder as __makeQueryBuilder, + procedureSchema as __procedureSchema, + procedures as __procedures, + reducerSchema as __reducerSchema, + reducers as __reducers, + schema as __schema, + t as __t, + table as __table, + type AlgebraicTypeType as __AlgebraicTypeType, + type DbConnectionConfig as __DbConnectionConfig, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type Infer as __Infer, + type QueryBuilder as __QueryBuilder, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type RemoteModule as __RemoteModule, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type SubscriptionHandleImpl as __SubscriptionHandleImpl, +} from "spacetimedb"; + +import AddReducer from "./add_reducer"; +import SayHelloReducer from "./say_hello_reducer"; +import PersonRow from "./person_table"; + +const tablesSchema = __schema({ + person: __table({ + name: 'person', + indexes: [ + ], + constraints: [ + ], + }, PersonRow), +}); + +const reducersSchema = __reducers( + __reducerSchema("add", AddReducer), + __reducerSchema("say_hello", SayHelloReducer), +); + +const proceduresSchema = __procedures( +); + +const REMOTE_MODULE = { + versionInfo: { + cliVersion: "2.0.3" as const, + }, + tables: tablesSchema.schemaType.tables, + reducers: reducersSchema.reducersType.reducers, + ...proceduresSchema, +} satisfies __RemoteModule< + typeof tablesSchema.schemaType, + typeof reducersSchema.reducersType, + typeof proceduresSchema +>; + +export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType); + +export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); + +export type EventContext = __EventContextInterface; +export type ReducerEventContext = __ReducerEventContextInterface; +export type SubscriptionEventContext = __SubscriptionEventContextInterface; +export type ErrorContext = __ErrorContextInterface; +export type SubscriptionHandle = __SubscriptionHandleImpl; + +export class SubscriptionBuilder extends __SubscriptionBuilderImpl {} + +export class DbConnectionBuilder extends __DbConnectionBuilder {} + +export class DbConnection extends __DbConnectionImpl { + static builder = (): DbConnectionBuilder => { + return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); + }; + + override subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} diff --git a/templates/astro-ts/src/module_bindings/person_table.ts b/templates/astro-ts/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..4dc4a822cc3 --- /dev/null +++ b/templates/astro-ts/src/module_bindings/person_table.ts @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default __t.row({ + name: __t.string(), +}); diff --git a/templates/astro-ts/src/module_bindings/say_hello_reducer.ts b/templates/astro-ts/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..e18fbc0a086 --- /dev/null +++ b/templates/astro-ts/src/module_bindings/say_hello_reducer.ts @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default {}; diff --git a/templates/astro-ts/src/module_bindings/types.ts b/templates/astro-ts/src/module_bindings/types.ts new file mode 100644 index 00000000000..d39b06139bc --- /dev/null +++ b/templates/astro-ts/src/module_bindings/types.ts @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export const Person = __t.object("Person", { + name: __t.string(), +}); +export type Person = __Infer; diff --git a/templates/astro-ts/src/pages/index.astro b/templates/astro-ts/src/pages/index.astro new file mode 100644 index 00000000000..efcc8fedf68 --- /dev/null +++ b/templates/astro-ts/src/pages/index.astro @@ -0,0 +1,54 @@ +--- +import DeferredPeopleSnapshot from '../components/DeferredPeopleSnapshot.astro'; +import { SpacetimeApp } from '../components/SpacetimeApp'; +import Layout from '../layouts/Layout.astro'; +import { fetchPeople } from '../lib/spacetimedb-server'; + +let initialPeople: Awaited> = []; +let initialLoadNotice: string | null = null; + +try { + initialPeople = await fetchPeople(); +} catch (error) { + console.error('Failed to fetch initial data from SpacetimeDB:', error); + initialLoadNotice = + 'The first server-rendered snapshot is unavailable right now. The live client will still connect when SpacetimeDB is ready.'; +} +--- + + +
+

SpacetimeDB Astro App

+

+ This Astro template keeps the UI simple while showing both a server rendered snapshot and a + live interactive client. +

+ +
+

Live People List

+

Server snapshot first, WebSocket updates after hydration.

+ + {initialLoadNotice &&

{initialLoadNotice}

} + + +
+ +
+

Deferred Server Island

+

This section uses server:defer and loads after the page shell.

+ + +
+

Fetching a fresh server snapshot.

+

+ Astro has already rendered the page shell. This placeholder will be replaced with the + server island response once the snapshot is ready. +

+
+
+
+
+
diff --git a/templates/astro-ts/src/styles/global.css b/templates/astro-ts/src/styles/global.css new file mode 100644 index 00000000000..afa858d0533 --- /dev/null +++ b/templates/astro-ts/src/styles/global.css @@ -0,0 +1,98 @@ +html, +body { + margin: 0; + width: 100%; + min-height: 100%; +} + +body { + font-family: Arial, Helvetica, sans-serif; + color: #111; +} + +* { + box-sizing: border-box; +} + +code, +input, +button { + font: inherit; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.page-shell { + padding: 2rem; +} + +.section-block { + margin-top: 2rem; +} + +.connection-note { + margin-bottom: 1rem; +} + +.live-app, +.server-island-card, +.server-island-fallback { + margin-top: 1rem; +} + +.status-row { + margin-bottom: 1rem; +} + +.status-live { + color: green; +} + +.status-pending { + color: red; +} + +.status-note { + margin-left: 0.5rem; +} + +.person-form { + margin-bottom: 2rem; +} + +.person-form input { + padding: 0.5rem; + margin-right: 0.5rem; +} + +.person-form button { + padding: 0.5rem 1rem; +} + +.empty-state { + margin: 0; +} + +.person-list, +.server-island-list { + padding-left: 1.25rem; +} + +.person-list-item, +.server-island-item { + margin-bottom: 0.25rem; +} + +.server-island-stamp { + margin: 0.5rem 0; +} diff --git a/templates/astro-ts/tsconfig.json b/templates/astro-ts/tsconfig.json new file mode 100644 index 00000000000..20b3fde6b0a --- /dev/null +++ b/templates/astro-ts/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist", "spacetimedb/dist"], + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react" + } +} From 853797fd3432962088fa2d0b512857720ed1278d Mon Sep 17 00:00:00 2001 From: Leo Voon Date: Sun, 22 Mar 2026 01:06:23 +0800 Subject: [PATCH 2/2] Remove Astro template list entry --- .../00200-core-concepts/00100-databases/00200-spacetime-dev.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md index f556450be04..f8384e5bc6e 100644 --- a/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md +++ b/docs/docs/00200-core-concepts/00100-databases/00200-spacetime-dev.md @@ -70,7 +70,6 @@ Choose from several built-in templates: - `basic-rs` - Basic Rust client and server stubs - `basic-cpp` - Basic C++ server stubs - `react-ts` - React web app with TypeScript server -- `astro-ts` - Astro app with TypeScript server - `chat-console-rs` - Complete Rust chat implementation - `chat-console-cs` - Complete C# chat implementation - `chat-react-ts` - Complete TypeScript chat implementation