From 0136d60cabdc8ddc7e4a9a6258efc70eed854e35 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:45:38 -0500 Subject: [PATCH 01/12] init --- .../00200-quickstarts/00150-nextjs.md | 105 +++++++++++++++ templates/nextjs-ts/.template.json | 5 + templates/nextjs-ts/LICENSE | 1 + templates/nextjs-ts/app/globals.css | 28 ++++ templates/nextjs-ts/app/layout.tsx | 22 ++++ templates/nextjs-ts/app/page.tsx | 71 ++++++++++ templates/nextjs-ts/app/providers.tsx | 56 ++++++++ templates/nextjs-ts/next.config.ts | 8 ++ templates/nextjs-ts/package.json | 28 ++++ templates/nextjs-ts/spacetimedb/package.json | 15 +++ templates/nextjs-ts/spacetimedb/src/index.ts | 33 +++++ templates/nextjs-ts/spacetimedb/tsconfig.json | 12 ++ .../src/module_bindings/add_reducer.ts | 15 +++ .../nextjs-ts/src/module_bindings/index.ts | 123 ++++++++++++++++++ .../src/module_bindings/on_connect_reducer.ts | 13 ++ .../module_bindings/on_disconnect_reducer.ts | 13 ++ .../src/module_bindings/person_table.ts | 15 +++ .../src/module_bindings/person_type.ts | 15 +++ .../src/module_bindings/say_hello_reducer.ts | 13 ++ templates/nextjs-ts/tsconfig.json | 27 ++++ 20 files changed, 618 insertions(+) create mode 100644 docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md create mode 100644 templates/nextjs-ts/.template.json create mode 100644 templates/nextjs-ts/LICENSE create mode 100644 templates/nextjs-ts/app/globals.css create mode 100644 templates/nextjs-ts/app/layout.tsx create mode 100644 templates/nextjs-ts/app/page.tsx create mode 100644 templates/nextjs-ts/app/providers.tsx create mode 100644 templates/nextjs-ts/next.config.ts create mode 100644 templates/nextjs-ts/package.json create mode 100644 templates/nextjs-ts/spacetimedb/package.json create mode 100644 templates/nextjs-ts/spacetimedb/src/index.ts create mode 100644 templates/nextjs-ts/spacetimedb/tsconfig.json create mode 100644 templates/nextjs-ts/src/module_bindings/add_reducer.ts create mode 100644 templates/nextjs-ts/src/module_bindings/index.ts create mode 100644 templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts create mode 100644 templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts create mode 100644 templates/nextjs-ts/src/module_bindings/person_table.ts create mode 100644 templates/nextjs-ts/src/module_bindings/person_type.ts create mode 100644 templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts create mode 100644 templates/nextjs-ts/tsconfig.json diff --git a/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md b/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md new file mode 100644 index 00000000000..9b9619330de --- /dev/null +++ b/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md @@ -0,0 +1,105 @@ +--- +title: Next.js Quickstart +sidebar_label: Next.js +slug: /quickstarts/nextjs +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 Next.js 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 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 my-nextjs-app +``` + + + + + + Navigate to [http://localhost:3001](http://localhost:3001) to see your app running. + + Note: The Next.js dev server runs on port 3001 to avoid conflict with SpacetimeDB on port 3000. + + + + + + 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` to build your UI. + + +``` +my-nextjs-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── app/ # Next.js App Router +│ ├── layout.tsx # Root layout with providers +│ ├── page.tsx # Home page +│ └── providers.tsx # SpacetimeDB provider (client component) +├── src/ +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + + + + SpacetimeDB requires a client-side connection. In Next.js App Router, this is handled by a client component wrapper. + + The `app/providers.tsx` file uses the `"use client"` directive and wraps your app with `SpacetimeDBProvider`. + + +```tsx +// app/providers.tsx +'use client'; + +import { useMemo } from 'react'; +import { SpacetimeDBProvider } from 'spacetimedb/react'; +import { DbConnection } from '../src/module_bindings'; + +export function Providers({ children }) { + const connectionBuilder = useMemo(() => + DbConnection.builder() + .withUri('ws://localhost:3000') + .withModuleName('my-nextjs-app'), + [] + ); + + return ( + + {children} + + ); +} +``` + + + + +## Next steps + +- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](/sdks/typescript) for detailed API docs diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json new file mode 100644 index 00000000000..19b5fb62be2 --- /dev/null +++ b/templates/nextjs-ts/.template.json @@ -0,0 +1,5 @@ +{ + "description": "Next.js App Router with TypeScript server", + "client_lang": "typescript", + "server_lang": "typescript" +} diff --git a/templates/nextjs-ts/LICENSE b/templates/nextjs-ts/LICENSE new file mode 100644 index 00000000000..039e117dde2 --- /dev/null +++ b/templates/nextjs-ts/LICENSE @@ -0,0 +1 @@ +../../licenses/apache2.txt \ No newline at end of file diff --git a/templates/nextjs-ts/app/globals.css b/templates/nextjs-ts/app/globals.css new file mode 100644 index 00000000000..7eec9bcc880 --- /dev/null +++ b/templates/nextjs-ts/app/globals.css @@ -0,0 +1,28 @@ +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: #333; + background: #fafafa; +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + body { + color: #eee; + background: #111; + } +} diff --git a/templates/nextjs-ts/app/layout.tsx b/templates/nextjs-ts/app/layout.tsx new file mode 100644 index 00000000000..200d42f58d3 --- /dev/null +++ b/templates/nextjs-ts/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from 'next'; +import { Providers } from './providers'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'SpacetimeDB Next.js App', + description: 'A Next.js app powered by SpacetimeDB', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/templates/nextjs-ts/app/page.tsx b/templates/nextjs-ts/app/page.tsx new file mode 100644 index 00000000000..3f1951648f5 --- /dev/null +++ b/templates/nextjs-ts/app/page.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { useState } from 'react'; +import { tables, reducers } from '../src/module_bindings'; +import { useSpacetimeDB, useTable, useReducer } from 'spacetimedb/react'; + +export default function Home() { + const [name, setName] = useState(''); + + const conn = useSpacetimeDB(); + const { isActive: connected } = conn; + + // Subscribe to all people in the database + // useTable returns [rows, isLoading] tuple + const [people] = useTable(tables.person); + + const addReducer = useReducer(reducers.add); + + const addPerson = (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim() || !connected) return; + + // Call the add reducer with object syntax + addReducer({ name: name }); + setName(''); + }; + + return ( +
+

SpacetimeDB Next.js App

+ +
+ Status:{' '} + + {connected ? 'Connected' : 'Disconnected'} + +
+ +
+ setName(e.target.value)} + style={{ padding: '0.5rem', marginRight: '0.5rem' }} + disabled={!connected} + /> + +
+ +
+

People ({people.length})

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

No people yet. Add someone above!

+ ) : ( +
    + {people.map((person, index) => ( +
  • {person.name}
  • + ))} +
+ )} +
+
+ ); +} diff --git a/templates/nextjs-ts/app/providers.tsx b/templates/nextjs-ts/app/providers.tsx new file mode 100644 index 00000000000..372e422f9ea --- /dev/null +++ b/templates/nextjs-ts/app/providers.tsx @@ -0,0 +1,56 @@ +'use client'; + +import { useMemo } from 'react'; +import { SpacetimeDBProvider } from 'spacetimedb/react'; +import { DbConnection, ErrorContext } from '../src/module_bindings'; +import { Identity } from 'spacetimedb'; + +const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; +const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'nextjs-ts'; + +const onConnect = ( + _conn: DbConnection, + identity: Identity, + token: string +) => { + if (typeof window !== 'undefined') { + localStorage.setItem('auth_token', token); + } + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); +}; + +const onDisconnect = () => { + console.log('Disconnected from SpacetimeDB'); +}; + +const onConnectError = (_ctx: ErrorContext, err: Error) => { + console.log('Error connecting to SpacetimeDB:', err); +}; + +export function Providers({ children }: { children: React.ReactNode }) { + // CRITICAL: Memoize connectionBuilder to prevent reconnects on re-render + const connectionBuilder = useMemo( + () => + DbConnection.builder() + .withUri(HOST) + .withModuleName(DB_NAME) + .withToken( + typeof window !== 'undefined' + ? localStorage.getItem('auth_token') || undefined + : undefined + ) + .onConnect(onConnect) + .onDisconnect(onDisconnect) + .onConnectError(onConnectError), + [] + ); + + return ( + + {children} + + ); +} diff --git a/templates/nextjs-ts/next.config.ts b/templates/nextjs-ts/next.config.ts new file mode 100644 index 00000000000..577a42a673d --- /dev/null +++ b/templates/nextjs-ts/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + // Next.js configuration + // Note: Use port 3001 (via npm scripts) to avoid conflict with SpacetimeDB on port 3000 +}; + +export default nextConfig; diff --git a/templates/nextjs-ts/package.json b/templates/nextjs-ts/package.json new file mode 100644 index 00000000000..448bc449e1a --- /dev/null +++ b/templates/nextjs-ts/package.json @@ -0,0 +1,28 @@ +{ + "name": "@clockworklabs/nextjs-ts", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "start": "next start -p 3001", + "lint": "next lint", + "generate": "pnpm --dir spacetimedb install --ignore-workspace && cargo run -p gen-bindings -- --out-dir src/module_bindings --project-path spacetimedb && prettier --write src/module_bindings", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb", + "spacetime:publish:local": "spacetime publish --project-path spacetimedb --server local", + "spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud" + }, + "dependencies": { + "next": "^15.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "spacetimedb": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "typescript": "~5.6.2" + } +} diff --git a/templates/nextjs-ts/spacetimedb/package.json b/templates/nextjs-ts/spacetimedb/package.json new file mode 100644 index 00000000000..214ccc569bf --- /dev/null +++ b/templates/nextjs-ts/spacetimedb/package.json @@ -0,0 +1,15 @@ +{ + "name": "spacetime-module", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "spacetime build", + "publish": "spacetime publish" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "spacetimedb": "1.*" + } +} diff --git a/templates/nextjs-ts/spacetimedb/src/index.ts b/templates/nextjs-ts/spacetimedb/src/index.ts new file mode 100644 index 00000000000..900cb1bf2e9 --- /dev/null +++ b/templates/nextjs-ts/spacetimedb/src/index.ts @@ -0,0 +1,33 @@ +import { schema, table, t } from 'spacetimedb/server'; + +export const spacetimedb = schema( + table( + { name: 'person', public: true }, + { + name: t.string(), + } + ) +); + +spacetimedb.init((_ctx) => { + // Called when the module is initially published +}); + +spacetimedb.clientConnected((_ctx) => { + // Called every time a new client connects +}); + +spacetimedb.clientDisconnected((_ctx) => { + // Called every time a client disconnects +}); + +spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { + ctx.db.person.insert({ name }); +}); + +spacetimedb.reducer('say_hello', (ctx) => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); diff --git a/templates/nextjs-ts/spacetimedb/tsconfig.json b/templates/nextjs-ts/spacetimedb/tsconfig.json new file mode 100644 index 00000000000..812c3b98cb1 --- /dev/null +++ b/templates/nextjs-ts/spacetimedb/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} diff --git a/templates/nextjs-ts/src/module_bindings/add_reducer.ts b/templates/nextjs-ts/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..ce493ee8574 --- /dev/null +++ b/templates/nextjs-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/nextjs-ts/src/module_bindings/index.ts b/templates/nextjs-ts/src/module_bindings/index.ts new file mode 100644 index 00000000000..d300a723a33 --- /dev/null +++ b/templates/nextjs-ts/src/module_bindings/index.ts @@ -0,0 +1,123 @@ +// 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 1.11.2 (commit dc5997d48f7c472faf2756b07c012bcf28edc50b). + +/* eslint-disable */ +/* tslint:disable */ +import { + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TypeBuilder as __TypeBuilder, + Uuid as __Uuid, + convertToAccessorMap as __convertToAccessorMap, + 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 ReducerEventContextInterface as __ReducerEventContextInterface, + type RemoteModule as __RemoteModule, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type SubscriptionHandleImpl as __SubscriptionHandleImpl, +} from "spacetimedb"; + +// Import and reexport all reducer arg types +import OnConnectReducer from "./on_connect_reducer"; +export { OnConnectReducer }; +import OnDisconnectReducer from "./on_disconnect_reducer"; +export { OnDisconnectReducer }; +import AddReducer from "./add_reducer"; +export { AddReducer }; +import SayHelloReducer from "./say_hello_reducer"; +export { SayHelloReducer }; + +// Import and reexport all procedure arg types + +// Import and reexport all table handle types +import PersonRow from "./person_table"; +export { PersonRow }; + +// Import and reexport all types +import Person from "./person_type"; +export { Person }; + +/** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ +const tablesSchema = __schema( + __table({ + name: 'person', + indexes: [ + ], + constraints: [ + ], + }, PersonRow), +); + +/** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ +const reducersSchema = __reducers( + __reducerSchema("add", AddReducer), + __reducerSchema("say_hello", SayHelloReducer), +); + +/** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ +const proceduresSchema = __procedures( +); + +/** The remote SpacetimeDB module schema, both runtime and type information. */ +const REMOTE_MODULE = { + versionInfo: { + cliVersion: "1.11.2" as const, + }, + tables: tablesSchema.schemaType.tables, + reducers: reducersSchema.reducersType.reducers, + ...proceduresSchema, +} satisfies __RemoteModule< + typeof tablesSchema.schemaType, + typeof reducersSchema.reducersType, + typeof proceduresSchema +>; + +/** The tables available in this remote SpacetimeDB module. */ +export const tables = __convertToAccessorMap(tablesSchema.schemaType.tables); + +/** The reducers available in this remote SpacetimeDB module. */ +export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); + +/** The context type returned in callbacks for all possible events. */ +export type EventContext = __EventContextInterface; +/** The context type returned in callbacks for reducer events. */ +export type ReducerEventContext = __ReducerEventContextInterface; +/** The context type returned in callbacks for subscription events. */ +export type SubscriptionEventContext = __SubscriptionEventContextInterface; +/** The context type returned in callbacks for error events. */ +export type ErrorContext = __ErrorContextInterface; +/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ +export type SubscriptionHandle = __SubscriptionHandleImpl; + +/** Builder class to configure a new subscription to the remote SpacetimeDB instance. */ +export class SubscriptionBuilder extends __SubscriptionBuilderImpl {} + +/** Builder class to configure a new database connection to the remote SpacetimeDB instance. */ +export class DbConnectionBuilder extends __DbConnectionBuilder {} + +/** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ +export class DbConnection extends __DbConnectionImpl { + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ + static builder = (): DbConnectionBuilder => { + return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); + }; + + /** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */ + override subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} diff --git a/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts b/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts new file mode 100644 index 00000000000..e18fbc0a086 --- /dev/null +++ b/templates/nextjs-ts/src/module_bindings/on_connect_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/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts b/templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts new file mode 100644 index 00000000000..e18fbc0a086 --- /dev/null +++ b/templates/nextjs-ts/src/module_bindings/on_disconnect_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/nextjs-ts/src/module_bindings/person_table.ts b/templates/nextjs-ts/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..4dc4a822cc3 --- /dev/null +++ b/templates/nextjs-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/nextjs-ts/src/module_bindings/person_type.ts b/templates/nextjs-ts/src/module_bindings/person_type.ts new file mode 100644 index 00000000000..0f29efdf2fe --- /dev/null +++ b/templates/nextjs-ts/src/module_bindings/person_type.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.object("Person", { + name: __t.string(), +}); diff --git a/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts b/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..e18fbc0a086 --- /dev/null +++ b/templates/nextjs-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/nextjs-ts/tsconfig.json b/templates/nextjs-ts/tsconfig.json new file mode 100644 index 00000000000..2c145a2d166 --- /dev/null +++ b/templates/nextjs-ts/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 534a1c68ed32318f6f0dfe0042140a13bf85a119 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:50:05 -0500 Subject: [PATCH 02/12] prettier --- .../{00150-nextjs.md => 00200-nextjs.md} | 61 ++++++++++++++++++- templates/nextjs-ts/app/providers.tsx | 6 +- templates/nextjs-ts/spacetimedb/src/index.ts | 8 +-- .../src/module_bindings/add_reducer.ts | 2 +- .../nextjs-ts/src/module_bindings/index.ts | 60 ++++++++++-------- .../src/module_bindings/on_connect_reducer.ts | 2 +- .../module_bindings/on_disconnect_reducer.ts | 2 +- .../src/module_bindings/person_table.ts | 2 +- .../src/module_bindings/person_type.ts | 4 +- .../src/module_bindings/say_hello_reducer.ts | 2 +- 10 files changed, 108 insertions(+), 41 deletions(-) rename docs/docs/00100-intro/00200-quickstarts/{00150-nextjs.md => 00200-nextjs.md} (65%) diff --git a/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md similarity index 65% rename from docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md rename to docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md index 9b9619330de..a9ff4ca0331 100644 --- a/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md +++ b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md @@ -65,6 +65,65 @@ my-nextjs-app/ + + + Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone. + + Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +export const spacetimedb = schema( + table( + { name: 'person', public: true }, + { + name: t.string(), + } + ) +); + +spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { + ctx.db.person.insert({ name }); +}); + +spacetimedb.reducer('say_hello', (ctx) => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + + + + Use the SpacetimeDB CLI to call reducers and query your data directly. + + +```bash +# Call the add reducer to insert a person +spacetime call my-nextjs-app add Alice + +# Query the person table +spacetime sql my-nextjs-app "SELECT * FROM person" + name +--------- + "Alice" + +# Call say_hello to greet everyone +spacetime call my-nextjs-app say_hello + +# View the module logs +spacetime logs my-nextjs-app +2025-01-13T12:00:00.000000Z INFO: Hello, Alice! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + SpacetimeDB requires a client-side connection. In Next.js App Router, this is handled by a client component wrapper. @@ -80,7 +139,7 @@ import { useMemo } from 'react'; import { SpacetimeDBProvider } from 'spacetimedb/react'; import { DbConnection } from '../src/module_bindings'; -export function Providers({ children }) { +export function Providers({ children }: { children: React.ReactNode }) { const connectionBuilder = useMemo(() => DbConnection.builder() .withUri('ws://localhost:3000') diff --git a/templates/nextjs-ts/app/providers.tsx b/templates/nextjs-ts/app/providers.tsx index 372e422f9ea..6478eb75c74 100644 --- a/templates/nextjs-ts/app/providers.tsx +++ b/templates/nextjs-ts/app/providers.tsx @@ -8,11 +8,7 @@ import { Identity } from 'spacetimedb'; const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'nextjs-ts'; -const onConnect = ( - _conn: DbConnection, - identity: Identity, - token: string -) => { +const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { if (typeof window !== 'undefined') { localStorage.setItem('auth_token', token); } diff --git a/templates/nextjs-ts/spacetimedb/src/index.ts b/templates/nextjs-ts/spacetimedb/src/index.ts index 900cb1bf2e9..3a5ddbc8257 100644 --- a/templates/nextjs-ts/spacetimedb/src/index.ts +++ b/templates/nextjs-ts/spacetimedb/src/index.ts @@ -9,15 +9,15 @@ export const spacetimedb = schema( ) ); -spacetimedb.init((_ctx) => { +spacetimedb.init(_ctx => { // Called when the module is initially published }); -spacetimedb.clientConnected((_ctx) => { +spacetimedb.clientConnected(_ctx => { // Called every time a new client connects }); -spacetimedb.clientDisconnected((_ctx) => { +spacetimedb.clientDisconnected(_ctx => { // Called every time a client disconnects }); @@ -25,7 +25,7 @@ spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { ctx.db.person.insert({ name }); }); -spacetimedb.reducer('say_hello', (ctx) => { +spacetimedb.reducer('say_hello', ctx => { for (const person of ctx.db.person.iter()) { console.info(`Hello, ${person.name}!`); } diff --git a/templates/nextjs-ts/src/module_bindings/add_reducer.ts b/templates/nextjs-ts/src/module_bindings/add_reducer.ts index ce493ee8574..85081559c7d 100644 --- a/templates/nextjs-ts/src/module_bindings/add_reducer.ts +++ b/templates/nextjs-ts/src/module_bindings/add_reducer.ts @@ -8,7 +8,7 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; export default { name: __t.string(), diff --git a/templates/nextjs-ts/src/module_bindings/index.ts b/templates/nextjs-ts/src/module_bindings/index.ts index d300a723a33..bc073933509 100644 --- a/templates/nextjs-ts/src/module_bindings/index.ts +++ b/templates/nextjs-ts/src/module_bindings/index.ts @@ -29,53 +29,53 @@ import { type RemoteModule as __RemoteModule, type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, type SubscriptionHandleImpl as __SubscriptionHandleImpl, -} from "spacetimedb"; +} from 'spacetimedb'; // Import and reexport all reducer arg types -import OnConnectReducer from "./on_connect_reducer"; +import OnConnectReducer from './on_connect_reducer'; export { OnConnectReducer }; -import OnDisconnectReducer from "./on_disconnect_reducer"; +import OnDisconnectReducer from './on_disconnect_reducer'; export { OnDisconnectReducer }; -import AddReducer from "./add_reducer"; +import AddReducer from './add_reducer'; export { AddReducer }; -import SayHelloReducer from "./say_hello_reducer"; +import SayHelloReducer from './say_hello_reducer'; export { SayHelloReducer }; // Import and reexport all procedure arg types // Import and reexport all table handle types -import PersonRow from "./person_table"; +import PersonRow from './person_table'; export { PersonRow }; // Import and reexport all types -import Person from "./person_type"; +import Person from './person_type'; export { Person }; /** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ const tablesSchema = __schema( - __table({ - name: 'person', - indexes: [ - ], - constraints: [ - ], - }, PersonRow), + __table( + { + name: 'person', + indexes: [], + constraints: [], + }, + PersonRow + ) ); /** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ const reducersSchema = __reducers( - __reducerSchema("add", AddReducer), - __reducerSchema("say_hello", SayHelloReducer), + __reducerSchema('add', AddReducer), + __reducerSchema('say_hello', SayHelloReducer) ); /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ -const proceduresSchema = __procedures( -); +const proceduresSchema = __procedures(); /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { - cliVersion: "1.11.2" as const, + cliVersion: '1.11.2' as const, }, tables: tablesSchema.schemaType.tables, reducers: reducersSchema.reducersType.reducers, @@ -90,21 +90,29 @@ const REMOTE_MODULE = { export const tables = __convertToAccessorMap(tablesSchema.schemaType.tables); /** The reducers available in this remote SpacetimeDB module. */ -export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); +export const reducers = __convertToAccessorMap( + reducersSchema.reducersType.reducers +); /** The context type returned in callbacks for all possible events. */ export type EventContext = __EventContextInterface; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface; +export type ReducerEventContext = __ReducerEventContextInterface< + typeof REMOTE_MODULE +>; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface; +export type SubscriptionEventContext = __SubscriptionEventContextInterface< + typeof REMOTE_MODULE +>; /** The context type returned in callbacks for error events. */ export type ErrorContext = __ErrorContextInterface; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; /** Builder class to configure a new subscription to the remote SpacetimeDB instance. */ -export class SubscriptionBuilder extends __SubscriptionBuilderImpl {} +export class SubscriptionBuilder extends __SubscriptionBuilderImpl< + typeof REMOTE_MODULE +> {} /** Builder class to configure a new database connection to the remote SpacetimeDB instance. */ export class DbConnectionBuilder extends __DbConnectionBuilder {} @@ -113,7 +121,11 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} export class DbConnection extends __DbConnectionImpl { /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { - return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); + return new DbConnectionBuilder( + REMOTE_MODULE, + (config: __DbConnectionConfig) => + new DbConnection(config) + ); }; /** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */ diff --git a/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts b/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts index e18fbc0a086..2ca99c88fea 100644 --- a/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts +++ b/templates/nextjs-ts/src/module_bindings/on_connect_reducer.ts @@ -8,6 +8,6 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; export default {}; diff --git a/templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts b/templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts index e18fbc0a086..2ca99c88fea 100644 --- a/templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts +++ b/templates/nextjs-ts/src/module_bindings/on_disconnect_reducer.ts @@ -8,6 +8,6 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; export default {}; diff --git a/templates/nextjs-ts/src/module_bindings/person_table.ts b/templates/nextjs-ts/src/module_bindings/person_table.ts index 4dc4a822cc3..0f70f74f617 100644 --- a/templates/nextjs-ts/src/module_bindings/person_table.ts +++ b/templates/nextjs-ts/src/module_bindings/person_table.ts @@ -8,7 +8,7 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; export default __t.row({ name: __t.string(), diff --git a/templates/nextjs-ts/src/module_bindings/person_type.ts b/templates/nextjs-ts/src/module_bindings/person_type.ts index 0f29efdf2fe..1156775a3cf 100644 --- a/templates/nextjs-ts/src/module_bindings/person_type.ts +++ b/templates/nextjs-ts/src/module_bindings/person_type.ts @@ -8,8 +8,8 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; -export default __t.object("Person", { +export default __t.object('Person', { name: __t.string(), }); diff --git a/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts b/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts index e18fbc0a086..2ca99c88fea 100644 --- a/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts +++ b/templates/nextjs-ts/src/module_bindings/say_hello_reducer.ts @@ -8,6 +8,6 @@ import { t as __t, type AlgebraicTypeType as __AlgebraicTypeType, type Infer as __Infer, -} from "spacetimedb"; +} from 'spacetimedb'; export default {}; From f0807d696c1958579d6c971a4d29edd62775c32c Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:50:13 -0500 Subject: [PATCH 03/12] Update templates-list.json --- templates/templates-list.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/templates/templates-list.json b/templates/templates-list.json index 05f8c035e5c..3860f1bcd6e 100644 --- a/templates/templates-list.json +++ b/templates/templates-list.json @@ -1,6 +1,7 @@ { "highlights": [ - { "name": "React", "template_id": "basic-react" } + { "name": "React", "template_id": "basic-react" }, + { "name": "Next.js", "template_id": "nextjs-ts" } ], "templates": [ { @@ -35,6 +36,14 @@ "server_lang": "typescript", "client_lang": "typescript" }, + { + "id": "nextjs-ts", + "description": "Next.js App Router with TypeScript server", + "server_source": "nextjs-ts/spacetimedb", + "client_source": "nextjs-ts", + "server_lang": "typescript", + "client_lang": "typescript" + }, { "id": "quickstart-chat-rust", "description": "Rust server/client implementing quickstart chat", From 8fcb0fa6c78f81363c567cc4e795d3eabd87663d Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:10:14 -0500 Subject: [PATCH 04/12] Update 00200-nextjs.md --- .../00200-quickstarts/00200-nextjs.md | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md index a9ff4ca0331..af1663d266e 100644 --- a/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md +++ b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md @@ -126,9 +126,9 @@ spacetime logs my-nextjs-app - SpacetimeDB requires a client-side connection. In Next.js App Router, this is handled by a client component wrapper. + SpacetimeDB is client-side only — it cannot run during server-side rendering. The `app/providers.tsx` file uses the `"use client"` directive and wraps your app with `SpacetimeDBProvider`. - The `app/providers.tsx` file uses the `"use client"` directive and wraps your app with `SpacetimeDBProvider`. + The template uses environment variables for configuration. Set `NEXT_PUBLIC_SPACETIMEDB_HOST` and `NEXT_PUBLIC_SPACETIMEDB_DB_NAME` to override defaults. ```tsx @@ -139,11 +139,14 @@ import { useMemo } from 'react'; import { SpacetimeDBProvider } from 'spacetimedb/react'; import { DbConnection } from '../src/module_bindings'; +const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; +const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'my-nextjs-app'; + export function Providers({ children }: { children: React.ReactNode }) { const connectionBuilder = useMemo(() => DbConnection.builder() - .withUri('ws://localhost:3000') - .withModuleName('my-nextjs-app'), + .withUri(HOST) + .withModuleName(DB_NAME), [] ); @@ -153,6 +156,40 @@ export function Providers({ children }: { children: React.ReactNode }) { ); } +``` + + + + + + In your page components, use `useTable` to subscribe to table data and `useReducer` to call reducers. All components using these hooks must have the `"use client"` directive. + + +```tsx +// app/page.tsx +'use client'; + +import { tables, reducers } from '../src/module_bindings'; +import { useTable, useReducer } from 'spacetimedb/react'; + +export default function Home() { + // Subscribe to table data - returns [rows, isLoading] + const [people] = useTable(tables.person); + + // Get a function to call a reducer + const addPerson = useReducer(reducers.add); + + const handleAdd = () => { + // Call reducer with object syntax + addPerson({ name: 'Alice' }); + }; + + return ( +
    + {people.map((person, i) =>
  • {person.name}
  • )} +
+ ); +} ```
From bca0e36f1e69f3ee67c19b2ddb86d8853942cffa Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:13:12 -0500 Subject: [PATCH 05/12] refinements --- .../docs/00100-intro/00200-quickstarts/00200-nextjs.md | 10 +++++----- templates/nextjs-ts/app/providers.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md index af1663d266e..762dbc2f93a 100644 --- a/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md +++ b/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md @@ -128,7 +128,7 @@ spacetime logs my-nextjs-app SpacetimeDB is client-side only — it cannot run during server-side rendering. The `app/providers.tsx` file uses the `"use client"` directive and wraps your app with `SpacetimeDBProvider`. - The template uses environment variables for configuration. Set `NEXT_PUBLIC_SPACETIMEDB_HOST` and `NEXT_PUBLIC_SPACETIMEDB_DB_NAME` to override defaults. + The template uses environment variables for configuration. Set `NEXT_PUBLIC_SPACETIMEDB_URI` and `NEXT_PUBLIC_SPACETIMEDB_MODULE` to override defaults. ```tsx @@ -139,14 +139,14 @@ import { useMemo } from 'react'; import { SpacetimeDBProvider } from 'spacetimedb/react'; import { DbConnection } from '../src/module_bindings'; -const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; -const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'my-nextjs-app'; +const URI = process.env.NEXT_PUBLIC_SPACETIMEDB_URI ?? 'ws://localhost:3000'; +const MODULE = process.env.NEXT_PUBLIC_SPACETIMEDB_MODULE ?? 'my-nextjs-app'; export function Providers({ children }: { children: React.ReactNode }) { const connectionBuilder = useMemo(() => DbConnection.builder() - .withUri(HOST) - .withModuleName(DB_NAME), + .withUri(URI) + .withModuleName(MODULE), [] ); diff --git a/templates/nextjs-ts/app/providers.tsx b/templates/nextjs-ts/app/providers.tsx index 6478eb75c74..2661d88807f 100644 --- a/templates/nextjs-ts/app/providers.tsx +++ b/templates/nextjs-ts/app/providers.tsx @@ -5,8 +5,8 @@ import { SpacetimeDBProvider } from 'spacetimedb/react'; import { DbConnection, ErrorContext } from '../src/module_bindings'; import { Identity } from 'spacetimedb'; -const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; -const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'nextjs-ts'; +const URI = process.env.NEXT_PUBLIC_SPACETIMEDB_URI ?? 'ws://localhost:3000'; +const MODULE = process.env.NEXT_PUBLIC_SPACETIMEDB_MODULE ?? 'nextjs-ts'; const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { if (typeof window !== 'undefined') { @@ -31,8 +31,8 @@ export function Providers({ children }: { children: React.ReactNode }) { const connectionBuilder = useMemo( () => DbConnection.builder() - .withUri(HOST) - .withModuleName(DB_NAME) + .withUri(URI) + .withModuleName(MODULE) .withToken( typeof window !== 'undefined' ? localStorage.getItem('auth_token') || undefined From 82900408dfe761aaf1576e65939cfc7c89d0a34a Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:50:18 -0500 Subject: [PATCH 06/12] Update providers.tsx --- templates/nextjs-ts/app/providers.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/nextjs-ts/app/providers.tsx b/templates/nextjs-ts/app/providers.tsx index 2661d88807f..ba2d618d188 100644 --- a/templates/nextjs-ts/app/providers.tsx +++ b/templates/nextjs-ts/app/providers.tsx @@ -27,7 +27,6 @@ const onConnectError = (_ctx: ErrorContext, err: Error) => { }; export function Providers({ children }: { children: React.ReactNode }) { - // CRITICAL: Memoize connectionBuilder to prevent reconnects on re-render const connectionBuilder = useMemo( () => DbConnection.builder() From 8adab5883e9ff75cfce53c6a90c9ed30acbbf8db Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:09:44 -0500 Subject: [PATCH 07/12] rename --- .../00200-quickstarts/{00200-nextjs.md => 00150-nextjs.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/docs/00100-intro/00200-quickstarts/{00200-nextjs.md => 00150-nextjs.md} (100%) diff --git a/docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md b/docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md similarity index 100% rename from docs/docs/00100-intro/00200-quickstarts/00200-nextjs.md rename to docs/docs/00100-intro/00200-quickstarts/00150-nextjs.md From 0af3de91d7cc8f7743c791dc71dd1be481f66b5e Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:10:44 -0500 Subject: [PATCH 08/12] init --- .../00200-quickstarts/00300-nodejs.md | 201 ++++++++++++++++++ templates/nodejs-ts/.template.json | 5 + templates/nodejs-ts/LICENSE | 1 + templates/nodejs-ts/package.json | 21 ++ templates/nodejs-ts/spacetimedb/package.json | 15 ++ templates/nodejs-ts/spacetimedb/src/index.ts | 33 +++ templates/nodejs-ts/spacetimedb/tsconfig.json | 24 +++ templates/nodejs-ts/src/main.ts | 135 ++++++++++++ .../src/module_bindings/add_reducer.ts | 15 ++ .../nodejs-ts/src/module_bindings/add_type.ts | 15 ++ .../nodejs-ts/src/module_bindings/index.ts | 151 +++++++++++++ .../src/module_bindings/init_type.ts | 13 ++ .../src/module_bindings/on_connect_reducer.ts | 13 ++ .../src/module_bindings/on_connect_type.ts | 13 ++ .../module_bindings/on_disconnect_reducer.ts | 13 ++ .../src/module_bindings/on_disconnect_type.ts | 13 ++ .../src/module_bindings/person_table.ts | 15 ++ .../src/module_bindings/person_type.ts | 15 ++ .../src/module_bindings/say_hello_reducer.ts | 13 ++ .../src/module_bindings/say_hello_type.ts | 13 ++ templates/nodejs-ts/tsconfig.json | 22 ++ templates/templates-list.json | 8 + 22 files changed, 767 insertions(+) create mode 100644 docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md create mode 100644 templates/nodejs-ts/.template.json create mode 100644 templates/nodejs-ts/LICENSE create mode 100644 templates/nodejs-ts/package.json create mode 100644 templates/nodejs-ts/spacetimedb/package.json create mode 100644 templates/nodejs-ts/spacetimedb/src/index.ts create mode 100644 templates/nodejs-ts/spacetimedb/tsconfig.json create mode 100644 templates/nodejs-ts/src/main.ts create mode 100644 templates/nodejs-ts/src/module_bindings/add_reducer.ts create mode 100644 templates/nodejs-ts/src/module_bindings/add_type.ts create mode 100644 templates/nodejs-ts/src/module_bindings/index.ts create mode 100644 templates/nodejs-ts/src/module_bindings/init_type.ts create mode 100644 templates/nodejs-ts/src/module_bindings/on_connect_reducer.ts create mode 100644 templates/nodejs-ts/src/module_bindings/on_connect_type.ts create mode 100644 templates/nodejs-ts/src/module_bindings/on_disconnect_reducer.ts create mode 100644 templates/nodejs-ts/src/module_bindings/on_disconnect_type.ts create mode 100644 templates/nodejs-ts/src/module_bindings/person_table.ts create mode 100644 templates/nodejs-ts/src/module_bindings/person_type.ts create mode 100644 templates/nodejs-ts/src/module_bindings/say_hello_reducer.ts create mode 100644 templates/nodejs-ts/src/module_bindings/say_hello_type.ts create mode 100644 templates/nodejs-ts/tsconfig.json diff --git a/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md new file mode 100644 index 00000000000..1d995206130 --- /dev/null +++ b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md @@ -0,0 +1,201 @@ +--- +title: Node.js Quickstart +sidebar_label: Node.js +slug: /quickstarts/nodejs +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 Node.js 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 Node.js client. + + This will start the local SpacetimeDB server, publish your module, and generate TypeScript bindings. + + +```bash +spacetime dev --template nodejs-ts +``` + + + + + + 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 +``` + + + + + + Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone. + + Tables store your data. Reducers are functions that modify data — they're the only way to write to the database. + + +```typescript +import { schema, table, t } from 'spacetimedb/server'; + +export const spacetimedb = schema( + table( + { name: 'person', public: true }, + { + name: t.string(), + } + ) +); + +spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { + ctx.db.person.insert({ name }); +}); + +spacetimedb.reducer('say_hello', (ctx) => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); +``` + + + + + + Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and handles real-time updates. + + Unlike browser apps, Node.js stores the authentication token in a file instead of localStorage. + + +```typescript +import { Identity } from 'spacetimedb'; +import { DbConnection, tables, reducers } from './module_bindings/index.js'; + +const SPACETIMEDB_URI = process.env.SPACETIMEDB_URI ?? 'ws://localhost:3000'; +const MODULE_NAME = process.env.SPACETIMEDB_MODULE ?? 'nodejs-ts'; + +// Build and establish connection +const conn = DbConnection.builder() + .withUri(SPACETIMEDB_URI) + .withModuleName(MODULE_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) => { + console.log('Subscription ready!'); + // Call a reducer + conn.reducers.add({ name: 'Alice' }); + }) + .subscribeToAllTables(); + + // Listen for table changes + conn.db.person.onInsert((ctx, person) => { + console.log('New person:', person.name); + }); + }) + .build(); +``` + + + + + + In a new terminal, run the Node.js client. It will connect to SpacetimeDB, subscribe to tables, and log updates. + + The client runs as a long-lived process, receiving real-time updates whenever data changes. + + +```bash +# Run with auto-reload during development +npm run dev + +# Or run once +npm run start +``` + + + + + + Use the SpacetimeDB CLI to call reducers and query your data directly. + + +```bash +# Call the add reducer to insert a person +spacetime call add Bob + +# Query the person table +spacetime sql "SELECT * FROM person" + name +--------- + "Alice" + "Bob" + +# 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, Bob! +2025-01-13T12:00:00.000000Z INFO: Hello, World! +``` + + + + + + **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_URI` and `SPACETIMEDB_MODULE` environment variables. + + **Graceful shutdown:** The template includes signal handlers for `SIGINT` and `SIGTERM` to cleanly disconnect when stopping the process. + + +```bash +# Configure via environment variables +SPACETIMEDB_URI=ws://localhost:3000 \ +SPACETIMEDB_MODULE=my-app \ +npm run start + +# Or use a .env file with dotenv +``` + + + + +## Next steps + +- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](/sdks/typescript) for detailed API docs diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json new file mode 100644 index 00000000000..af38af2c866 --- /dev/null +++ b/templates/nodejs-ts/.template.json @@ -0,0 +1,5 @@ +{ + "description": "Node.js TypeScript client and server template", + "client_lang": "typescript", + "server_lang": "typescript" +} diff --git a/templates/nodejs-ts/LICENSE b/templates/nodejs-ts/LICENSE new file mode 100644 index 00000000000..039e117dde2 --- /dev/null +++ b/templates/nodejs-ts/LICENSE @@ -0,0 +1 @@ +../../licenses/apache2.txt \ No newline at end of file diff --git a/templates/nodejs-ts/package.json b/templates/nodejs-ts/package.json new file mode 100644 index 00000000000..9b87a1c7750 --- /dev/null +++ b/templates/nodejs-ts/package.json @@ -0,0 +1,21 @@ +{ + "name": "@clockworklabs/nodejs-ts", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "tsx watch src/main.ts", + "start": "tsx src/main.ts", + "build": "tsc", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb" + }, + "dependencies": { + "spacetimedb": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.19.0", + "typescript": "~5.6.2", + "undici": "^6.19.2" + } +} diff --git a/templates/nodejs-ts/spacetimedb/package.json b/templates/nodejs-ts/spacetimedb/package.json new file mode 100644 index 00000000000..214ccc569bf --- /dev/null +++ b/templates/nodejs-ts/spacetimedb/package.json @@ -0,0 +1,15 @@ +{ + "name": "spacetime-module", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "spacetime build", + "publish": "spacetime publish" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "spacetimedb": "1.*" + } +} diff --git a/templates/nodejs-ts/spacetimedb/src/index.ts b/templates/nodejs-ts/spacetimedb/src/index.ts new file mode 100644 index 00000000000..900cb1bf2e9 --- /dev/null +++ b/templates/nodejs-ts/spacetimedb/src/index.ts @@ -0,0 +1,33 @@ +import { schema, table, t } from 'spacetimedb/server'; + +export const spacetimedb = schema( + table( + { name: 'person', public: true }, + { + name: t.string(), + } + ) +); + +spacetimedb.init((_ctx) => { + // Called when the module is initially published +}); + +spacetimedb.clientConnected((_ctx) => { + // Called every time a new client connects +}); + +spacetimedb.clientDisconnected((_ctx) => { + // Called every time a client disconnects +}); + +spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { + ctx.db.person.insert({ name }); +}); + +spacetimedb.reducer('say_hello', (ctx) => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); diff --git a/templates/nodejs-ts/spacetimedb/tsconfig.json b/templates/nodejs-ts/spacetimedb/tsconfig.json new file mode 100644 index 00000000000..6283107337e --- /dev/null +++ b/templates/nodejs-ts/spacetimedb/tsconfig.json @@ -0,0 +1,24 @@ + +/* + * This tsconfig is used for TypeScript projects created with `spacetimedb init + * --lang typescript`. You can modify it as needed for your project, although + * some options are required by SpacetimeDB. + */ +{ + "compilerOptions": { + "strict": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + "jsx": "react-jsx", + + /* The following options are required by SpacetimeDB + * and should not be modified + */ + "target": "ESNext", + "lib": ["ES2021", "dom"], + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"] +} diff --git a/templates/nodejs-ts/src/main.ts b/templates/nodejs-ts/src/main.ts new file mode 100644 index 00000000000..c2065c5f9b8 --- /dev/null +++ b/templates/nodejs-ts/src/main.ts @@ -0,0 +1,135 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import { Identity } from 'spacetimedb'; +import { + DbConnection, + ErrorContext, + EventContext, + tables, + reducers, +} from './module_bindings/index.js'; + +// Configuration +const SPACETIMEDB_URI = process.env.SPACETIMEDB_URI ?? 'ws://localhost:3000'; +const MODULE_NAME = process.env.SPACETIMEDB_MODULE ?? 'nodejs-ts'; + +// Token persistence (file-based for Node.js instead of localStorage) +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const TOKEN_FILE = path.join(__dirname, '..', '.spacetimedb-token'); + +function loadToken(): string | undefined { + try { + if (fs.existsSync(TOKEN_FILE)) { + return fs.readFileSync(TOKEN_FILE, 'utf-8').trim(); + } + } catch (err) { + console.warn('Could not load token:', err); + } + return undefined; +} + +function saveToken(token: string): void { + try { + fs.writeFileSync(TOKEN_FILE, token, 'utf-8'); + } catch (err) { + console.warn('Could not save token:', err); + } +} + +// Connection state +let conn: DbConnection | null = null; + +// Connection callbacks +function onConnect(_conn: DbConnection, identity: Identity, token: string): void { + console.log('Connected to SpacetimeDB!'); + console.log('Identity:', identity.toHexString()); + + // Save token for future connections + saveToken(token); + + // Subscribe to all tables + _conn + .subscriptionBuilder() + .onApplied((ctx) => { + console.log('Subscription applied, initial data received'); + + // Log all existing people + const people = ctx.db.person.iter(); + console.log('Current people in database:'); + for (const person of people) { + console.log(` - ${person.name}`); + } + + // Example: Add a person after subscription is ready + console.log('\nAdding a new person...'); + _conn.reducers.add({ name: `Node-User-${Date.now()}` }); + }) + .onError((ctx, err) => { + console.error('Subscription error:', err); + }) + .subscribeToAllTables(); + + // Register callbacks for table changes + _conn.db.person.onInsert((ctx: EventContext, person) => { + console.log(`[INSERT] New person added: ${person.name}`); + }); + + _conn.db.person.onDelete((ctx: EventContext, person) => { + console.log(`[DELETE] Person removed: ${person.name}`); + }); +} + +function onDisconnect(_ctx: ErrorContext, error?: Error): void { + if (error) { + console.error('Disconnected with error:', error); + } else { + console.log('Disconnected from SpacetimeDB'); + } +} + +function onConnectError(_ctx: ErrorContext, error: Error): void { + console.error('Connection error:', error); + process.exit(1); +} + +// Main entry point +async function main(): Promise { + console.log(`Connecting to SpacetimeDB at ${SPACETIMEDB_URI}...`); + console.log(`Module: ${MODULE_NAME}`); + + const token = loadToken(); + if (token) { + console.log('Using saved authentication token'); + } + + // Build and establish connection + conn = DbConnection.builder() + .withUri(SPACETIMEDB_URI) + .withModuleName(MODULE_NAME) + .withToken(token) + .onConnect(onConnect) + .onDisconnect(onDisconnect) + .onConnectError(onConnectError) + .build(); + + console.log('Connection initiated, waiting for callbacks...'); +} + +// Graceful shutdown +function shutdown(): void { + console.log('\nShutting down...'); + if (conn) { + conn.disconnect(); + } + process.exit(0); +} + +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); + +// Run the main function +main().catch((err) => { + console.error('Fatal error:', err); + process.exit(1); +}); diff --git a/templates/nodejs-ts/src/module_bindings/add_reducer.ts b/templates/nodejs-ts/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..85081559c7d --- /dev/null +++ b/templates/nodejs-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/nodejs-ts/src/module_bindings/add_type.ts b/templates/nodejs-ts/src/module_bindings/add_type.ts new file mode 100644 index 00000000000..638f62cea39 --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/add_type.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.object('Add', { + name: __t.string(), +}); diff --git a/templates/nodejs-ts/src/module_bindings/index.ts b/templates/nodejs-ts/src/module_bindings/index.ts new file mode 100644 index 00000000000..5e83bc75b20 --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/index.ts @@ -0,0 +1,151 @@ +// 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 1.11.3 (commit f9bca6a8df856d950360b40cbce744fcbffc9a63). + +/* 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 and reexport all reducer arg types +import OnConnectReducer from './on_connect_reducer.js'; +export { OnConnectReducer }; +import OnDisconnectReducer from './on_disconnect_reducer.js'; +export { OnDisconnectReducer }; +import AddReducer from './add_reducer.js'; +export { AddReducer }; +import SayHelloReducer from './say_hello_reducer.js'; +export { SayHelloReducer }; + +// Import and reexport all procedure arg types + +// Import and reexport all table handle types +import PersonRow from './person_table.js'; +export { PersonRow }; + +// Import and reexport all types +import Add from './add_type.js'; +export { Add }; +import Init from './init_type.js'; +export { Init }; +import OnConnect from './on_connect_type.js'; +export { OnConnect }; +import OnDisconnect from './on_disconnect_type.js'; +export { OnDisconnect }; +import Person from './person_type.js'; +export { Person }; +import SayHello from './say_hello_type.js'; +export { SayHello }; + +/** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ +const tablesSchema = __schema( + __table( + { + name: 'person', + indexes: [], + constraints: [], + }, + PersonRow + ) +); + +/** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ +const reducersSchema = __reducers( + __reducerSchema('add', AddReducer), + __reducerSchema('say_hello', SayHelloReducer) +); + +/** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ +const proceduresSchema = __procedures(); + +/** The remote SpacetimeDB module schema, both runtime and type information. */ +const REMOTE_MODULE = { + versionInfo: { + cliVersion: '1.11.3' as const, + }, + tables: tablesSchema.schemaType.tables, + reducers: reducersSchema.reducersType.reducers, + ...proceduresSchema, +} satisfies __RemoteModule< + typeof tablesSchema.schemaType, + typeof reducersSchema.reducersType, + typeof proceduresSchema +>; + +/** The tables available in this remote SpacetimeDB module. */ +export const tables = __convertToAccessorMap(tablesSchema.schemaType.tables); + +/** A typed query builder for this remote SpacetimeDB module. */ +export const query: __QueryBuilder = + __makeQueryBuilder(tablesSchema.schemaType); + +/** The reducers available in this remote SpacetimeDB module. */ +export const reducers = __convertToAccessorMap( + reducersSchema.reducersType.reducers +); + +/** The context type returned in callbacks for all possible events. */ +export type EventContext = __EventContextInterface; +/** The context type returned in callbacks for reducer events. */ +export type ReducerEventContext = __ReducerEventContextInterface< + typeof REMOTE_MODULE +>; +/** The context type returned in callbacks for subscription events. */ +export type SubscriptionEventContext = __SubscriptionEventContextInterface< + typeof REMOTE_MODULE +>; +/** The context type returned in callbacks for error events. */ +export type ErrorContext = __ErrorContextInterface; +/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ +export type SubscriptionHandle = __SubscriptionHandleImpl; + +/** Builder class to configure a new subscription to the remote SpacetimeDB instance. */ +export class SubscriptionBuilder extends __SubscriptionBuilderImpl< + typeof REMOTE_MODULE +> {} + +/** Builder class to configure a new database connection to the remote SpacetimeDB instance. */ +export class DbConnectionBuilder extends __DbConnectionBuilder {} + +/** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ +export class DbConnection extends __DbConnectionImpl { + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ + static builder = (): DbConnectionBuilder => { + return new DbConnectionBuilder( + REMOTE_MODULE, + (config: __DbConnectionConfig) => + new DbConnection(config) + ); + }; + + /** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */ + override subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} diff --git a/templates/nodejs-ts/src/module_bindings/init_type.ts b/templates/nodejs-ts/src/module_bindings/init_type.ts new file mode 100644 index 00000000000..52ed691ed94 --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/init_type.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 __t.object('Init', {}); diff --git a/templates/nodejs-ts/src/module_bindings/on_connect_reducer.ts b/templates/nodejs-ts/src/module_bindings/on_connect_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/on_connect_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/nodejs-ts/src/module_bindings/on_connect_type.ts b/templates/nodejs-ts/src/module_bindings/on_connect_type.ts new file mode 100644 index 00000000000..d36362515de --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/on_connect_type.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 __t.object('OnConnect', {}); diff --git a/templates/nodejs-ts/src/module_bindings/on_disconnect_reducer.ts b/templates/nodejs-ts/src/module_bindings/on_disconnect_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/on_disconnect_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/nodejs-ts/src/module_bindings/on_disconnect_type.ts b/templates/nodejs-ts/src/module_bindings/on_disconnect_type.ts new file mode 100644 index 00000000000..efda71ebcfd --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/on_disconnect_type.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 __t.object('OnDisconnect', {}); diff --git a/templates/nodejs-ts/src/module_bindings/person_table.ts b/templates/nodejs-ts/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..0f70f74f617 --- /dev/null +++ b/templates/nodejs-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/nodejs-ts/src/module_bindings/person_type.ts b/templates/nodejs-ts/src/module_bindings/person_type.ts new file mode 100644 index 00000000000..1156775a3cf --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/person_type.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.object('Person', { + name: __t.string(), +}); diff --git a/templates/nodejs-ts/src/module_bindings/say_hello_reducer.ts b/templates/nodejs-ts/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/nodejs-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/nodejs-ts/src/module_bindings/say_hello_type.ts b/templates/nodejs-ts/src/module_bindings/say_hello_type.ts new file mode 100644 index 00000000000..6293ca6bd09 --- /dev/null +++ b/templates/nodejs-ts/src/module_bindings/say_hello_type.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 __t.object('SayHello', {}); diff --git a/templates/nodejs-ts/tsconfig.json b/templates/nodejs-ts/tsconfig.json new file mode 100644 index 00000000000..2992457d9d5 --- /dev/null +++ b/templates/nodejs-ts/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/templates/templates-list.json b/templates/templates-list.json index 3860f1bcd6e..b499254c394 100644 --- a/templates/templates-list.json +++ b/templates/templates-list.json @@ -44,6 +44,14 @@ "server_lang": "typescript", "client_lang": "typescript" }, + { + "id": "nodejs-ts", + "description": "Node.js TypeScript client and server template", + "server_source": "nodejs-ts/spacetimedb", + "client_source": "nodejs-ts", + "server_lang": "typescript", + "client_lang": "typescript" + }, { "id": "quickstart-chat-rust", "description": "Rust server/client implementing quickstart chat", From 6406c65d97b517e8511cb583342c57c66b72c3a7 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:39:19 -0500 Subject: [PATCH 09/12] basic cli --- .../00200-quickstarts/00300-nodejs.md | 94 +++++++++++++------ templates/nodejs-ts/src/main.ts | 90 +++++++++++++----- 2 files changed, 128 insertions(+), 56 deletions(-) diff --git a/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md index 1d995206130..4fda3a24b7e 100644 --- a/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md +++ b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md @@ -87,19 +87,69 @@ spacetimedb.reducer('say_hello', (ctx) => {
+ + + In a new terminal, run the Node.js 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 +npm run dev + +# Or run once +npm run start +``` + + + + + + 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: nodejs-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 say_hello reducer (check server logs) +``` + + + - Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and handles real-time updates. + Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and sets up the interactive CLI using Node's `readline` module. Unlike browser apps, Node.js stores the authentication token in a file instead of localStorage. ```typescript -import { Identity } from 'spacetimedb'; -import { DbConnection, tables, reducers } from './module_bindings/index.js'; - -const SPACETIMEDB_URI = process.env.SPACETIMEDB_URI ?? 'ws://localhost:3000'; -const MODULE_NAME = process.env.SPACETIMEDB_MODULE ?? 'nodejs-ts'; +import { DbConnection } from './module_bindings/index.js'; // Build and establish connection const conn = DbConnection.builder() @@ -113,15 +163,14 @@ const conn = DbConnection.builder() // Subscribe to all tables conn.subscriptionBuilder() .onApplied((ctx) => { - console.log('Subscription ready!'); - // Call a reducer - conn.reducers.add({ name: 'Alice' }); + // Show current data, start CLI + setupCLI(); }) .subscribeToAllTables(); // Listen for table changes conn.db.person.onInsert((ctx, person) => { - console.log('New person:', person.name); + console.log(`[Added] ${person.name}`); }); }) .build(); @@ -129,31 +178,14 @@ const conn = DbConnection.builder() - - - In a new terminal, run the Node.js client. It will connect to SpacetimeDB, subscribe to tables, and log updates. - - The client runs as a long-lived process, receiving real-time updates whenever data changes. - - -```bash -# Run with auto-reload during development -npm run dev - -# Or run once -npm run start -``` - - - - + - Use the SpacetimeDB CLI to call reducers and query your data directly. + You can also use the SpacetimeDB CLI to call reducers and query your data directly. Changes made via the CLI will appear in your Node.js client in real-time. ```bash # Call the add reducer to insert a person -spacetime call add Bob +spacetime call add Charlie # Query the person table spacetime sql "SELECT * FROM person" @@ -161,6 +193,7 @@ spacetime sql "SELECT * FROM person" --------- "Alice" "Bob" + "Charlie" # Call say_hello to greet everyone spacetime call say_hello @@ -169,6 +202,7 @@ spacetime call say_hello 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! ``` diff --git a/templates/nodejs-ts/src/main.ts b/templates/nodejs-ts/src/main.ts index c2065c5f9b8..e4b1bdd0733 100644 --- a/templates/nodejs-ts/src/main.ts +++ b/templates/nodejs-ts/src/main.ts @@ -1,13 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as readline from 'readline'; import { fileURLToPath } from 'url'; import { Identity } from 'spacetimedb'; import { DbConnection, ErrorContext, EventContext, - tables, - reducers, } from './module_bindings/index.js'; // Configuration @@ -39,11 +38,51 @@ function saveToken(token: string): void { // Connection state let conn: DbConnection | null = null; +let isReady = false; + +// Setup interactive CLI +function setupCLI(): void { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + console.log('\nCommands:'); + console.log(' - Add a person with that name'); + console.log(' list - Show all people'); + console.log(' hello - Greet everyone (check server logs)'); + console.log(' Ctrl+C - Quit\n'); + + rl.on('line', (input) => { + const text = input.trim(); + if (!text || !conn || !isReady) return; + + if (text.toLowerCase() === 'list') { + console.log('\nPeople in database:'); + let count = 0; + for (const person of conn.db.person.iter()) { + console.log(` - ${person.name}`); + count++; + } + if (count === 0) { + console.log(' (none)'); + } + console.log(); + } else if (text.toLowerCase() === 'hello') { + conn.reducers.sayHello({}); + console.log('Called say_hello reducer (check server logs)\n'); + } else { + conn.reducers.add({ name: text }); + } + }); + + rl.on('close', shutdown); +} // Connection callbacks function onConnect(_conn: DbConnection, identity: Identity, token: string): void { - console.log('Connected to SpacetimeDB!'); - console.log('Identity:', identity.toHexString()); + console.log('\nConnected to SpacetimeDB!'); + console.log(`Identity: ${identity.toHexString().slice(0, 16)}...`); // Save token for future connections saveToken(token); @@ -52,35 +91,38 @@ function onConnect(_conn: DbConnection, identity: Identity, token: string): void _conn .subscriptionBuilder() .onApplied((ctx) => { - console.log('Subscription applied, initial data received'); - - // Log all existing people - const people = ctx.db.person.iter(); - console.log('Current people in database:'); - for (const person of people) { - console.log(` - ${person.name}`); + isReady = true; + + // Show current people + const people = [...ctx.db.person.iter()]; + console.log(`\nCurrent people (${people.length}):`); + if (people.length === 0) { + console.log(' (none yet)'); + } else { + for (const person of people) { + console.log(` - ${person.name}`); + } } - // Example: Add a person after subscription is ready - console.log('\nAdding a new person...'); - _conn.reducers.add({ name: `Node-User-${Date.now()}` }); + setupCLI(); }) - .onError((ctx, err) => { + .onError((_ctx, err) => { console.error('Subscription error:', err); }) .subscribeToAllTables(); // Register callbacks for table changes - _conn.db.person.onInsert((ctx: EventContext, person) => { - console.log(`[INSERT] New person added: ${person.name}`); + _conn.db.person.onInsert((_ctx: EventContext, person) => { + console.log(`[Added] ${person.name}`); }); - _conn.db.person.onDelete((ctx: EventContext, person) => { - console.log(`[DELETE] Person removed: ${person.name}`); + _conn.db.person.onDelete((_ctx: EventContext, person) => { + console.log(`[Removed] ${person.name}`); }); } function onDisconnect(_ctx: ErrorContext, error?: Error): void { + isReady = false; if (error) { console.error('Disconnected with error:', error); } else { @@ -95,13 +137,11 @@ function onConnectError(_ctx: ErrorContext, error: Error): void { // Main entry point async function main(): Promise { - console.log(`Connecting to SpacetimeDB at ${SPACETIMEDB_URI}...`); - console.log(`Module: ${MODULE_NAME}`); + console.log(`Connecting to SpacetimeDB...`); + console.log(` URI: ${SPACETIMEDB_URI}`); + console.log(` Module: ${MODULE_NAME}`); const token = loadToken(); - if (token) { - console.log('Using saved authentication token'); - } // Build and establish connection conn = DbConnection.builder() @@ -112,8 +152,6 @@ async function main(): Promise { .onDisconnect(onDisconnect) .onConnectError(onConnectError) .build(); - - console.log('Connection initiated, waiting for callbacks...'); } // Graceful shutdown From eaf70b14066578c6d329f77f6b71ad2d770c299b Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:32:15 -0500 Subject: [PATCH 10/12] Update providers.tsx --- templates/nextjs-ts/app/providers.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/nextjs-ts/app/providers.tsx b/templates/nextjs-ts/app/providers.tsx index ba2d618d188..bc4ec9bf028 100644 --- a/templates/nextjs-ts/app/providers.tsx +++ b/templates/nextjs-ts/app/providers.tsx @@ -5,8 +5,8 @@ import { SpacetimeDBProvider } from 'spacetimedb/react'; import { DbConnection, ErrorContext } from '../src/module_bindings'; import { Identity } from 'spacetimedb'; -const URI = process.env.NEXT_PUBLIC_SPACETIMEDB_URI ?? 'ws://localhost:3000'; -const MODULE = process.env.NEXT_PUBLIC_SPACETIMEDB_MODULE ?? 'nextjs-ts'; +const HOST = process.env.NEXT_PUBLIC_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; +const DB_NAME = process.env.NEXT_PUBLIC_SPACETIMEDB_DB_NAME ?? 'nextjs-ts'; const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { if (typeof window !== 'undefined') { @@ -30,8 +30,8 @@ export function Providers({ children }: { children: React.ReactNode }) { const connectionBuilder = useMemo( () => DbConnection.builder() - .withUri(URI) - .withModuleName(MODULE) + .withUri(HOST) + .withModuleName(DB_NAME) .withToken( typeof window !== 'undefined' ? localStorage.getItem('auth_token') || undefined From 669ec7e0ac806d6c8e1d2334d24c48377075cb53 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:47:08 -0500 Subject: [PATCH 11/12] env name consistency --- .../00100-intro/00200-quickstarts/00300-nodejs.md | 10 +++++----- templates/nodejs-ts/src/main.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md index 4fda3a24b7e..83c7b54b9ac 100644 --- a/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md +++ b/docs/docs/00100-intro/00200-quickstarts/00300-nodejs.md @@ -153,8 +153,8 @@ import { DbConnection } from './module_bindings/index.js'; // Build and establish connection const conn = DbConnection.builder() - .withUri(SPACETIMEDB_URI) - .withModuleName(MODULE_NAME) + .withUri(HOST) + .withModuleName(DB_NAME) .withToken(loadToken()) // Load saved token from file .onConnect((conn, identity, token) => { console.log('Connected! Identity:', identity.toHexString()); @@ -212,15 +212,15 @@ spacetime logs **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_URI` and `SPACETIMEDB_MODULE` environment variables. + **Environment variables:** Configure the connection using `SPACETIMEDB_HOST` and `SPACETIMEDB_DB_NAME` environment variables. **Graceful shutdown:** The template includes signal handlers for `SIGINT` and `SIGTERM` to cleanly disconnect when stopping the process. ```bash # Configure via environment variables -SPACETIMEDB_URI=ws://localhost:3000 \ -SPACETIMEDB_MODULE=my-app \ +SPACETIMEDB_HOST=ws://localhost:3000 \ +SPACETIMEDB_DB_NAME=my-app \ npm run start # Or use a .env file with dotenv diff --git a/templates/nodejs-ts/src/main.ts b/templates/nodejs-ts/src/main.ts index e4b1bdd0733..f89e1ed49c6 100644 --- a/templates/nodejs-ts/src/main.ts +++ b/templates/nodejs-ts/src/main.ts @@ -10,8 +10,8 @@ import { } from './module_bindings/index.js'; // Configuration -const SPACETIMEDB_URI = process.env.SPACETIMEDB_URI ?? 'ws://localhost:3000'; -const MODULE_NAME = process.env.SPACETIMEDB_MODULE ?? 'nodejs-ts'; +const HOST = process.env.SPACETIMEDB_HOST ?? 'ws://localhost:3000'; +const DB_NAME = process.env.SPACETIMEDB_DB_NAME ?? 'nodejs-ts'; // Token persistence (file-based for Node.js instead of localStorage) const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -138,15 +138,15 @@ function onConnectError(_ctx: ErrorContext, error: Error): void { // Main entry point async function main(): Promise { console.log(`Connecting to SpacetimeDB...`); - console.log(` URI: ${SPACETIMEDB_URI}`); - console.log(` Module: ${MODULE_NAME}`); + console.log(` URI: ${HOST}`); + console.log(` Module: ${DB_NAME}`); const token = loadToken(); // Build and establish connection conn = DbConnection.builder() - .withUri(SPACETIMEDB_URI) - .withModuleName(MODULE_NAME) + .withUri(HOST) + .withModuleName(DB_NAME) .withToken(token) .onConnect(onConnect) .onDisconnect(onDisconnect) From 0fd575f8c17a98e521df02e1cf235fa0d2a07e9b Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:48:13 -0500 Subject: [PATCH 12/12] prettier --- templates/nodejs-ts/spacetimedb/src/index.ts | 8 ++++---- templates/nodejs-ts/src/main.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/templates/nodejs-ts/spacetimedb/src/index.ts b/templates/nodejs-ts/spacetimedb/src/index.ts index 900cb1bf2e9..3a5ddbc8257 100644 --- a/templates/nodejs-ts/spacetimedb/src/index.ts +++ b/templates/nodejs-ts/spacetimedb/src/index.ts @@ -9,15 +9,15 @@ export const spacetimedb = schema( ) ); -spacetimedb.init((_ctx) => { +spacetimedb.init(_ctx => { // Called when the module is initially published }); -spacetimedb.clientConnected((_ctx) => { +spacetimedb.clientConnected(_ctx => { // Called every time a new client connects }); -spacetimedb.clientDisconnected((_ctx) => { +spacetimedb.clientDisconnected(_ctx => { // Called every time a client disconnects }); @@ -25,7 +25,7 @@ spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { ctx.db.person.insert({ name }); }); -spacetimedb.reducer('say_hello', (ctx) => { +spacetimedb.reducer('say_hello', ctx => { for (const person of ctx.db.person.iter()) { console.info(`Hello, ${person.name}!`); } diff --git a/templates/nodejs-ts/src/main.ts b/templates/nodejs-ts/src/main.ts index f89e1ed49c6..f9939e073a5 100644 --- a/templates/nodejs-ts/src/main.ts +++ b/templates/nodejs-ts/src/main.ts @@ -53,7 +53,7 @@ function setupCLI(): void { console.log(' hello - Greet everyone (check server logs)'); console.log(' Ctrl+C - Quit\n'); - rl.on('line', (input) => { + rl.on('line', input => { const text = input.trim(); if (!text || !conn || !isReady) return; @@ -80,7 +80,11 @@ function setupCLI(): void { } // Connection callbacks -function onConnect(_conn: DbConnection, identity: Identity, token: string): void { +function onConnect( + _conn: DbConnection, + identity: Identity, + token: string +): void { console.log('\nConnected to SpacetimeDB!'); console.log(`Identity: ${identity.toHexString().slice(0, 16)}...`); @@ -90,7 +94,7 @@ function onConnect(_conn: DbConnection, identity: Identity, token: string): void // Subscribe to all tables _conn .subscriptionBuilder() - .onApplied((ctx) => { + .onApplied(ctx => { isReady = true; // Show current people @@ -167,7 +171,7 @@ process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); // Run the main function -main().catch((err) => { +main().catch(err => { console.error('Fatal error:', err); process.exit(1); });