feat: Quickstart and client for Plain JS Script Tags (#4161)

# Description of Changes

The PR implements the following updates:

- Create a quickstart guide and client support for using SpacetimeDB
directly in browser with script tags
- A template to easily work with/test the implementation and get started
with script tags

The bundle is served via
`https://unpkg.com/spacetimedb@latest/dist/browser.bundle.js`, which
only works after the package is published to npm, therefore to test
locally we can use the `browser-ts` template and local bundle:
1. Build local bundle
```
cd crates/bindings-typescript
pnpm install
pnpm build
```
2. Use local bundle built in template`index.html` (swap the URL from
`unpkg`)
```diff
- <script src="https://unpkg.com/spacetimedb@latest/dist/browser.bundle.js"></script>
+ <script src="../../crates/bindings-typescript/dist/browser.bundle.js"></script>
```
3. Open `browser-ts/index.html` directly in browser along with
`spacetime start` + publish the module `spacetime publish --project-path
spacetimedb browser-ts`

# Screenshots

<img width="1106" height="707" alt="image"
src="https://github.com/user-attachments/assets/715bea26-f54b-42be-97f9-79dcd57d153f"
/>

<img width="1490" height="855" alt="image"
src="https://github.com/user-attachments/assets/2b1aeaf9-22ba-4ae6-bc63-771805b8c2b9"
/>


<!-- Please describe your change, mention any related tickets, and so on
here. -->

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [ ] <!-- maybe a test you want to do -->
- [ ] <!-- maybe a test you want a reviewer to do, so they can check it
off when they're satisfied. -->

---------

Co-authored-by: = <cloutiertyler@gmail.com>
This commit is contained in:
clockwork-tien
2026-02-11 13:09:10 +02:00
committed by GitHub
parent b0f3f8d008
commit aca374a781
25 changed files with 696 additions and 70 deletions
@@ -0,0 +1,118 @@
---
title: Browser Quickstart
sidebar_label: Browser
slug: /quickstarts/browser
hide_table_of_contents: true
pagination_next: intro/quickstarts/typescript
---
import { InstallCardLink } from "@site/src/components/InstallCardLink";
import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps";
Get a SpacetimeDB app running in the browser with inline JavaScript.
## Prerequisites
- [Node.js](https://nodejs.org/) 18+ installed
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
<InstallCardLink />
---
<StepByStep>
<Step title="Create your project">
<StepText>
Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module.
This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings.
</StepText>
<StepCode>
```bash
spacetime dev --template browser-ts my-spacetime-app
```
</StepCode>
</Step>
<Step title="Build the client bindings">
<StepText>
The generated TypeScript bindings need to be bundled into a JavaScript file that can be loaded in the browser via a script tag.
</StepText>
<StepCode>
```bash
cd my-spacetime-app
npm install
npm run build
```
</StepCode>
</Step>
<Step title="Open in browser">
<StepText>
Open `index.html` directly in your browser. The app connects to SpacetimeDB and displays data in real-time.
The JavaScript code runs inline in a script tag, using the bundled `DbConnection` class.
</StepText>
<StepCode>
```html
<!-- Load the bundled bindings -->
<script src="dist/bindings.iife.js"></script>
<script>
const conn = DbConnection.builder()
.withUri('ws://localhost:3000')
.withModuleName('my-spacetime-app')
.withToken(localStorage.getItem('auth_token'))
.onConnect((conn, identity, token) => {
localStorage.setItem('auth_token', token);
console.log('Connected:', identity.toHexString());
// Subscribe to tables
conn.subscriptionBuilder()
.onApplied(() => {
for (const person of conn.db.person.iter()) {
console.log(person.name);
}
})
.subscribe(['SELECT * FROM person']);
})
.build();
</script>
```
</StepCode>
</Step>
<Step title="Call reducers">
<StepText>
Reducers are functions that modify data — they're the only way to write to the database.
</StepText>
<StepCode>
```javascript
// Call a reducer with named arguments
conn.reducers.add({ name: 'Alice' });
```
</StepCode>
</Step>
<Step title="React to changes">
<StepText>
Register callbacks to update your UI when data changes.
</StepText>
<StepCode>
```javascript
conn.db.person.onInsert((ctx, person) => {
console.log('New person:', person.name);
});
conn.db.person.onDelete((ctx, person) => {
console.log('Removed:', person.name);
});
```
</StepCode>
</Step>
</StepByStep>
## 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
+19 -70
View File
@@ -177,7 +177,7 @@ importers:
version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': '@docusaurus/plugin-content-docs':
specifier: 3.9.2 specifier: 3.9.2
version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/preset-classic': '@docusaurus/preset-classic':
specifier: 3.9.2 specifier: 3.9.2
version: 3.9.2(@algolia/client-search@5.39.0)(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) version: 3.9.2(@algolia/client-search@5.39.0)(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)
@@ -286,6 +286,19 @@ importers:
specifier: ^7.1.5 specifier: ^7.1.5
version: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4) version: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4)
templates/browser-ts:
dependencies:
spacetimedb:
specifier: workspace:*
version: link:../../crates/bindings-typescript
devDependencies:
typescript:
specifier: ~5.6.2
version: 5.6.3
vite:
specifier: ^7.1.5
version: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4)
templates/chat-react-ts: templates/chat-react-ts:
dependencies: dependencies:
react: react:
@@ -10817,7 +10830,7 @@ snapshots:
'@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.9.2 '@docusaurus/logger': 3.9.2
'@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -10854,46 +10867,6 @@ snapshots:
- webpack-cli - webpack-cli
'@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies:
'@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.9.2
'@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/react-router-config': 5.0.11
combine-promises: 1.2.0
fs-extra: 11.3.2
js-yaml: 4.1.0
lodash: 4.17.21
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
schema-dts: 1.1.5
tslib: 2.8.1
utility-types: 3.11.0
webpack: 5.102.0
transitivePeerDependencies:
- '@docusaurus/faster'
- '@mdx-js/react'
- '@parcel/css'
- '@rspack/core'
- '@swc/core'
- '@swc/css'
- bufferutil
- csso
- debug
- esbuild
- lightningcss
- supports-color
- typescript
- uglify-js
- utf-8-validate
- webpack-cli
'@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies: dependencies:
'@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.9.2 '@docusaurus/logger': 3.9.2
@@ -11162,7 +11135,7 @@ snapshots:
dependencies: dependencies:
'@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
@@ -11210,7 +11183,7 @@ snapshots:
'@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/theme-translations': 3.9.2 '@docusaurus/theme-translations': 3.9.2
@@ -11250,35 +11223,11 @@ snapshots:
- utf-8-validate - utf-8-validate
- webpack-cli - webpack-cli
'@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11
'@types/react': 18.3.23
'@types/react-router-config': 5.0.11
clsx: 2.1.1
parse-numeric-range: 1.3.0
prism-react-renderer: 2.4.1(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.8.1
utility-types: 3.11.0
transitivePeerDependencies:
- '@swc/core'
- esbuild
- supports-color
- uglify-js
- webpack-cli
'@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11 '@types/history': 4.7.11
@@ -11303,7 +11252,7 @@ snapshots:
'@docsearch/react': 4.2.0(@algolia/client-search@5.39.0)(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) '@docsearch/react': 4.2.0(@algolia/client-search@5.39.0)(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)
'@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.9.2 '@docusaurus/logger': 3.9.2
'@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/theme-translations': 3.9.2 '@docusaurus/theme-translations': 3.9.2
'@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+1
View File
@@ -5,6 +5,7 @@ packages:
- 'templates/react-ts' - 'templates/react-ts'
- 'templates/basic-ts' - 'templates/basic-ts'
- 'templates/vue-ts' - 'templates/vue-ts'
- 'templates/browser-ts'
- 'templates/svelte-ts' - 'templates/svelte-ts'
- 'modules/benchmarks-ts' - 'modules/benchmarks-ts'
- 'modules/module-test-ts' - 'modules/module-test-ts'
+5
View File
@@ -0,0 +1,5 @@
{
"description": "Browser web app with TypeScript server",
"client_lang": "typescript",
"server_lang": "typescript"
}
+1
View File
@@ -0,0 +1 @@
../../licenses/apache2.txt
+122
View File
@@ -0,0 +1,122 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SpacetimeDB Browser App</title>
</head>
<body
style="
font-family:
system-ui,
-apple-system,
sans-serif;
padding: 2rem;
max-width: 600px;
margin: 0 auto;
"
>
<h1>SpacetimeDB Browser App</h1>
<div style="margin-bottom: 1rem">
Status: <strong id="status" style="color: red">Disconnected</strong>
</div>
<form id="add-form" style="margin-bottom: 2rem">
<input
type="text"
id="name-input"
placeholder="Enter name"
style="padding: 0.5rem; margin-right: 0.5rem"
disabled
/>
<button type="submit" id="add-btn" style="padding: 0.5rem 1rem" disabled>
Add Person
</button>
</form>
<div>
<h2>People (<span id="count">0</span>)</h2>
<ul id="people-list">
<li style="color: #888">No people yet. Add someone above!</li>
</ul>
</div>
<!-- Load the bundled bindings (run `npm run build` first) -->
<script src="dist/bindings.iife.js"></script>
<script>
const HOST = 'ws://localhost:3000';
const DB_NAME = 'browser-ts';
const statusEl = document.getElementById('status');
const nameInput = document.getElementById('name-input');
const addBtn = document.getElementById('add-btn');
const addForm = document.getElementById('add-form');
const peopleList = document.getElementById('people-list');
const countEl = document.getElementById('count');
function renderPeople(conn) {
const people = Array.from(conn.db.person.iter());
countEl.textContent = people.length;
if (people.length === 0) {
peopleList.innerHTML =
'<li style="color: #888;">No people yet. Add someone above!</li>';
return;
}
peopleList.innerHTML = people
.map(p => '<li>' + escapeHtml(p.name || '') + '</li>')
.join('');
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
const conn = DbConnection.builder()
.withUri(HOST)
.withModuleName(DB_NAME)
.withToken(localStorage.getItem('auth_token') || undefined)
.onConnect((conn, identity, token) => {
localStorage.setItem('auth_token', token);
console.log('Connected with identity:', identity.toHexString());
statusEl.textContent = 'Connected';
statusEl.style.color = 'green';
nameInput.disabled = false;
addBtn.disabled = false;
conn.subscriptionBuilder()
.onApplied(() => renderPeople(conn))
.subscribe(['SELECT * FROM person']);
conn.db.person.onInsert(() => renderPeople(conn));
conn.db.person.onDelete(() => renderPeople(conn));
})
.onDisconnect(() => {
console.log('Disconnected from SpacetimeDB');
statusEl.textContent = 'Disconnected';
statusEl.style.color = 'red';
nameInput.disabled = true;
addBtn.disabled = true;
})
.onConnectError((_ctx, error) => {
console.error('Connection error:', error);
statusEl.textContent = 'Error: ' + error.message;
statusEl.style.color = 'red';
})
.build();
addForm.addEventListener('submit', e => {
e.preventDefault();
const name = nameInput.value.trim();
if (name) {
conn.reducers.add({ name });
nameInput.value = '';
}
});
</script>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@clockworklabs/browser-ts",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"build": "vite build",
"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": {
"spacetimedb": "workspace:*"
},
"devDependencies": {
"typescript": "~5.6.2",
"vite": "^7.1.5"
}
}
@@ -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.*"
}
}
@@ -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!');
});
@@ -0,0 +1,22 @@
/*
* 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",
/* The following options are required by SpacetimeDB
* and should not be modified
*/
"target": "ESNext",
"lib": ["ES2021", "dom"],
"module": "ESNext",
"isolatedModules": true,
"noEmit": true
},
"include": ["./**/*"]
}
+6
View File
@@ -0,0 +1,6 @@
// Re-export generated bindings as globals for use in script tags
export { DbConnection } from './module_bindings';
// Make DbConnection available globally
import { DbConnection } from './module_bindings';
(window as any).DbConnection = DbConnection;
+15
View File
@@ -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(),
};
+15
View File
@@ -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(),
});
+151
View File
@@ -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';
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 Add from './add_type';
export { Add };
import Init from './init_type';
export { Init };
import OnConnect from './on_connect_type';
export { OnConnect };
import OnDisconnect from './on_disconnect_type';
export { OnDisconnect };
import Person from './person_type';
export { Person };
import SayHello from './say_hello_type';
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<typeof tablesSchema.schemaType> =
__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<typeof REMOTE_MODULE>;
/** 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<typeof REMOTE_MODULE>;
/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */
export type SubscriptionHandle = __SubscriptionHandleImpl<typeof REMOTE_MODULE>;
/** 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<DbConnection> {}
/** 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<typeof REMOTE_MODULE> {
/** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */
static builder = (): DbConnectionBuilder => {
return new DbConnectionBuilder(
REMOTE_MODULE,
(config: __DbConnectionConfig<typeof REMOTE_MODULE>) =>
new DbConnection(config)
);
};
/** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */
override subscriptionBuilder = (): SubscriptionBuilder => {
return new SubscriptionBuilder(this);
};
}
+13
View File
@@ -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', {});
@@ -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 {};
@@ -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', {});
@@ -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 {};
@@ -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', {});
+15
View File
@@ -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(),
});
+15
View File
@@ -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(),
});
@@ -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 {};
@@ -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', {});
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+14
View File
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/bindings.ts'),
name: 'Bindings',
fileName: 'bindings',
formats: ['iife'],
},
outDir: 'dist',
},
});