create-tinybase 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -18
- package/cli.js +2 -2
- package/package.json +7 -4
- package/screenshots/chat.png +0 -0
- package/screenshots/drawing.png +0 -0
- package/screenshots/todos.png +0 -0
- package/templates/README.md.hbs +130 -12
- package/templates/client/eslint.config.js.hbs +7 -0
- package/templates/client/package.json.hbs +28 -8
- package/templates/client/src/chat/App.tsx.hbs +28 -22
- package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
- package/templates/client/src/chat/Message.tsx.hbs +8 -9
- package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
- package/templates/client/src/chat/Messages.tsx.hbs +5 -15
- package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
- package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
- package/templates/client/src/chat/app.ts.hbs +18 -18
- package/templates/client/src/chat/chatStore.ts.hbs +97 -35
- package/templates/client/src/chat/message.ts.hbs +4 -3
- package/templates/client/src/chat/messageInput.css.hbs +1 -1
- package/templates/client/src/chat/messageInput.ts.hbs +7 -8
- package/templates/client/src/chat/messages.ts.hbs +7 -12
- package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
- package/templates/client/src/chat/usernameInput.css.hbs +1 -1
- package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
- package/templates/client/src/chat/utils.ts.hbs +26 -0
- package/templates/client/src/drawing/App.tsx.hbs +26 -20
- package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
- package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
- package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
- package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
- package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
- package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
- package/templates/client/src/drawing/app.ts.hbs +18 -8
- package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
- package/templates/client/src/drawing/canvas.ts.hbs +84 -86
- package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
- package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
- package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
- package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
- package/templates/client/src/game/App.tsx.hbs +20 -16
- package/templates/client/src/game/Board.tsx.hbs +8 -8
- package/templates/client/src/game/Game.tsx.hbs +14 -21
- package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
- package/templates/client/src/game/Square.tsx.hbs +6 -11
- package/templates/client/src/game/Store.tsx.hbs +106 -16
- package/templates/client/src/game/app.ts.hbs +17 -6
- package/templates/client/src/game/board.ts.hbs +7 -7
- package/templates/client/src/game/game.ts.hbs +12 -18
- package/templates/client/src/game/gameStatus.ts.hbs +3 -3
- package/templates/client/src/game/square.ts.hbs +4 -4
- package/templates/client/src/game/store.ts.hbs +95 -23
- package/templates/client/src/index.tsx.hbs +5 -7
- package/templates/client/src/shared/Button.tsx.hbs +3 -3
- package/templates/client/src/shared/Input.tsx.hbs +2 -2
- package/templates/client/src/shared/Loading.tsx.hbs +5 -0
- package/templates/client/src/shared/button.ts.hbs +2 -2
- package/templates/client/src/shared/config.ts.hbs +4 -6
- package/templates/client/src/shared/input.ts.hbs +2 -2
- package/templates/client/src/shared/loading.css.hbs +21 -0
- package/templates/client/src/shared/loading.ts.hbs +13 -0
- package/templates/client/src/shared/pglite.ts.hbs +10 -0
- package/templates/client/src/shared/sqlite.ts.hbs +17 -0
- package/templates/client/src/todos/App.tsx.hbs +22 -22
- package/templates/client/src/todos/Store.tsx.hbs +106 -23
- package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
- package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
- package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
- package/templates/client/src/todos/app.ts.hbs +16 -10
- package/templates/client/src/todos/store.ts.hbs +94 -30
- package/templates/client/src/todos/todoInput.ts.hbs +5 -8
- package/templates/client/src/todos/todoItem.ts.hbs +3 -4
- package/templates/client/src/todos/todoList.ts.hbs +6 -8
- package/templates/client/vite-env.d.ts.hbs +4 -0
- package/templates/client/vite.config.js.hbs +53 -3
- package/templates/server/index.ts.hbs +43 -0
- package/templates/server/package.json.hbs +6 -7
- package/templates/server/wrangler.toml.hbs +1 -1
- package/templates/server/index-do.ts.hbs +0 -22
- package/templates/server/index-node.ts.hbs +0 -8
- /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ This will prompt you with questions to configure your new TinyBase app:
|
|
|
22
22
|
- **Framework** - React or Vanilla JS
|
|
23
23
|
- **App type** - Todo app, Chat app, Drawing app, or Tic-tac-toe game
|
|
24
24
|
- **Store schemas** - TypeScript type safety for stores (TypeScript only)
|
|
25
|
-
- **Synchronization** -
|
|
26
|
-
- **
|
|
25
|
+
- **Synchronization** - None, remote demo server, local Node server, or local Durable Objects server
|
|
26
|
+
- **Persistence** - None, Local Storage, SQLite, or PGLite
|
|
27
27
|
- **Prettier** - Include Prettier for code formatting
|
|
28
28
|
- **ESLint** - Include ESLint for code linting
|
|
29
29
|
|
|
@@ -146,44 +146,88 @@ Schemas define:
|
|
|
146
146
|
|
|
147
147
|
### Synchronization
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
Choose from four synchronization options:
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
- WebSocket-based synchronization
|
|
153
|
-
- Connects to demo server by default (wss://vite.tinybase.org)
|
|
154
|
-
- MergeableStore for conflict-free replication
|
|
155
|
-
- Automatic reconnection handling
|
|
156
|
-
|
|
157
|
-
**Disabled**:
|
|
151
|
+
**None**:
|
|
158
152
|
|
|
159
153
|
- Local-only data storage
|
|
160
154
|
- No network dependencies
|
|
161
155
|
- Simpler architecture
|
|
162
156
|
- Still uses MergeableStore for consistency
|
|
163
157
|
|
|
164
|
-
|
|
158
|
+
**Via remote demo server (stateless)** (default):
|
|
165
159
|
|
|
166
|
-
|
|
160
|
+
- Real-time sync between browser tabs
|
|
161
|
+
- WebSocket-based synchronization
|
|
162
|
+
- Connects to demo server (wss://vite.tinybase.org)
|
|
163
|
+
- MergeableStore for conflict-free replication
|
|
164
|
+
- Automatic reconnection handling
|
|
165
|
+
- Great for prototyping
|
|
166
|
+
- No local server management needed
|
|
167
167
|
|
|
168
|
-
**
|
|
168
|
+
**Via local node server (stateless)** (port 8043):
|
|
169
169
|
|
|
170
|
+
- Real-time sync with local Node.js server
|
|
170
171
|
- WebSocket server using `ws` library
|
|
171
172
|
- TinyBase server synchronizer
|
|
172
173
|
- Runs with `npm run dev` in server directory
|
|
174
|
+
- Stateless - no data persistence
|
|
173
175
|
- Easy to deploy to any Node.js host
|
|
176
|
+
- Full control over server code
|
|
174
177
|
|
|
175
|
-
**
|
|
178
|
+
**Via local DurableObjects server (stateful)** (port 8787):
|
|
176
179
|
|
|
180
|
+
- Real-time sync with Cloudflare Durable Objects
|
|
177
181
|
- Serverless WebSocket server
|
|
178
182
|
- Edge computing with Durable Objects
|
|
183
|
+
- Stateful - data persists in Durable Objects storage
|
|
179
184
|
- Global distribution
|
|
180
185
|
- Runs locally with Wrangler
|
|
186
|
+
- Designed for production deployment
|
|
181
187
|
|
|
182
|
-
|
|
188
|
+
### Persistence
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
Choose how to persist store data on the client:
|
|
191
|
+
|
|
192
|
+
**None**:
|
|
193
|
+
|
|
194
|
+
- No client-side persistence
|
|
195
|
+
- Data lost on page refresh
|
|
196
|
+
- Simplest option for demos
|
|
197
|
+
- Still works with synchronization
|
|
198
|
+
|
|
199
|
+
**Local Storage** (default):
|
|
200
|
+
|
|
201
|
+
- Browser localStorage persistence
|
|
202
|
+
- Data persists across sessions
|
|
203
|
+
- Simple and widely supported
|
|
204
|
+
- Automatic load/save with `createLocalPersister`
|
|
205
|
+
- Best for most use cases
|
|
206
|
+
|
|
207
|
+
**SQLite**:
|
|
208
|
+
|
|
209
|
+
- Browser-based SQLite via WebAssembly
|
|
210
|
+
- Structured database storage
|
|
211
|
+
- Uses `@sqlite.org/sqlite-wasm` package
|
|
212
|
+
- IndexedDB-backed (`:local:` prefix)
|
|
213
|
+
- Good for complex data structures
|
|
214
|
+
- Slightly larger bundle size
|
|
215
|
+
|
|
216
|
+
**PGLite**:
|
|
217
|
+
|
|
218
|
+
- PostgreSQL in the browser
|
|
219
|
+
- Full PostgreSQL compatibility
|
|
220
|
+
- Uses `@electric-sql/pglite` package
|
|
221
|
+
- IndexedDB-backed (`idb://` prefix)
|
|
222
|
+
- Advanced SQL features
|
|
223
|
+
- Larger bundle size
|
|
224
|
+
|
|
225
|
+
**Important Notes:**
|
|
226
|
+
|
|
227
|
+
- Persistence is configured **before** synchronization to avoid race conditions
|
|
228
|
+
- Works with all sync types (or no sync)
|
|
229
|
+
- In multi-store apps, all stores get persistence
|
|
230
|
+
- Data is automatically loaded on app start and saved on changes
|
|
187
231
|
|
|
188
232
|
### Prettier & ESLint
|
|
189
233
|
|
package/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{existsSync as
|
|
3
|
-
`,questions:[{type:"text",name:"projectName",message:"Project name:",initial:"my-tinybase-app",validate:e=>{if(e.length===0)return"Project name is required";const t=u(process.cwd(),e);return
|
|
2
|
+
import{existsSync as f}from"fs";import{dirname as k,join as u}from"path";import{createCLI as x,detectPackageManager as R}from"tinycreate";import{fileURLToPath as S}from"url";const P=k(S(import.meta.url)),C={welcomeMessage:`\u{1F389} Welcome to TinyBase!
|
|
3
|
+
`,questions:[{type:"text",name:"projectName",message:"Project name:",initial:"my-tinybase-app",validate:e=>{if(e.length===0)return"Project name is required";const t=u(process.cwd(),e);return f(t)?`Directory "${e}" already exists. Please choose a different name.`:!0}},{type:"select",name:"appType",message:"App type:",choices:[{title:"Todo app",value:"todos"},{title:"Chat app",value:"chat"},{title:"Drawing app",value:"drawing"},{title:"Tic-tac-toe game",value:"game"}],initial:0},{type:"select",name:"language",message:"Language:",choices:[{title:"TypeScript",value:"typescript"},{title:"JavaScript",value:"javascript"}],initial:0},{type:"select",name:"framework",message:"Framework:",choices:[{title:"React",value:"react"},{title:"Vanilla",value:"vanilla"}],initial:0},{type:(e,t)=>t.language==="typescript"?"confirm":null,name:"schemas",message:"Include store schemas?",initial:!1},{type:"select",name:"syncType",message:"Synchronization:",choices:[{title:"None",value:"none"},{title:"Via remote demo server (stateless)",value:"remote"},{title:"Via local node server (stateless)",value:"node"},{title:"Via local DurableObjects server (stateful)",value:"durable-objects"}],initial:1},{type:"select",name:"persistenceType",message:"Persistence:",choices:[{title:"None",value:"none"},{title:"Local Storage",value:"local-storage"},{title:"SQLite",value:"sqlite"},{title:"PGlite",value:"pglite"}],initial:1},{type:"confirm",name:"prettier",message:"Include Prettier?",initial:!0},{type:"confirm",name:"eslint",message:"Include ESLint?",initial:!0},{type:(e,t)=>t.syncType!=="node"&&t.syncType!=="durable-objects"?"confirm":null,name:"installAndRun",message:"Install dependencies and start dev server?",initial:!0}],createContext:e=>{const{projectName:t,language:s,framework:n,appType:o,prettier:m,eslint:g,schemas:l,syncType:d,persistenceType:y,installAndRun:p}=e,i=s==="typescript",v=!i,c=n==="react",b=i?c?"tsx":"ts":c?"jsx":"js",a=d||"remote",T=a!=="none",h=a==="node"||a==="durable-objects",j=a==="durable-objects"?"durable-objects":"node",w=a==="durable-objects",r=y||"local-storage";return{projectName:t,language:s,framework:n,appType:o,prettier:m,eslint:g,schemas:i&&(l===!0||l==="true"),syncType:a,sync:T,server:h,serverType:j,isDurableObject:w,persistenceType:r,persist:r!=="none",persistLocalStorage:r==="local-storage",persistSqlite:r==="sqlite",persistPglite:r==="pglite",installAndRun:p===!0||p==="true",typescript:i,javascript:v,react:c,ext:b}},createDirectories:async(e,t)=>{const{mkdir:s}=await import("fs/promises"),{join:n}=await import("path"),o=t.server;await s(n(e,"client/src"),{recursive:!0}),await s(n(e,"client/public"),{recursive:!0}),o&&await s(n(e,"server"),{recursive:!0})},getFiles:()=>[{template:"README.md.hbs",output:"README.md",prettier:!0}],processIncludedFile:(e,t)=>{const{javascript:s}=t,n=e.prettier??/\.(js|jsx|ts|tsx|css|json|html|md)$/.test(e.output),o=e.transpile??(/\.(ts|tsx)\.hbs$/.test(e.template)&&s===!0);return{...e,prettier:n,transpile:o}},templateRoot:u(P,"templates"),installCommand:"{pm} install",devCommand:"{pm} run dev",workingDirectory:"client",onSuccess:(e,t)=>{const s=t.syncType,n=s==="node"||s==="durable-objects",o=R();console.log("Next steps:"),console.log(),n&&(console.log("To run the server:"),console.log(` cd ${e}/server`),console.log(` ${o} install`),console.log(` ${o} run dev`),console.log()),console.log("To run the client:"),console.log(` cd ${e}/client`),console.log(` ${o} install`),console.log(` ${o} run dev`)}};x(C).catch(e=>{console.error(e),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-tinybase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "jamesgpearce",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"homepage": "https://tinybase.org",
|
|
11
|
-
"description": "The CLI to build
|
|
11
|
+
"description": "The CLI to build new apps using TinyBase, a reactive data store and sync engine.",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"tiny",
|
|
14
14
|
"sync engine",
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
"reactive",
|
|
17
17
|
"state",
|
|
18
18
|
"data",
|
|
19
|
-
"react"
|
|
19
|
+
"react",
|
|
20
|
+
"sqlite",
|
|
21
|
+
"pglite",
|
|
22
|
+
"durable-objects"
|
|
20
23
|
],
|
|
21
24
|
"type": "module",
|
|
22
25
|
"bin": {
|
|
23
26
|
"create-tinybase": "cli.js"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
|
-
"tinycreate": "^1.0.
|
|
29
|
+
"tinycreate": "^1.0.4"
|
|
27
30
|
},
|
|
28
31
|
"engines": {
|
|
29
32
|
"node": ">=18.0.0"
|
package/screenshots/chat.png
CHANGED
|
Binary file
|
package/screenshots/drawing.png
CHANGED
|
Binary file
|
package/screenshots/todos.png
CHANGED
|
Binary file
|
package/templates/README.md.hbs
CHANGED
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
# {{projectName}}
|
|
7
7
|
|
|
8
|
-
A TinyBase app built with {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
|
|
9
|
-
and {{#if react}}React{{else}}Vanilla JS{{/if}}.
|
|
8
|
+
A TinyBase app built with {{#if typescript}}TypeScript{{else}}JavaScript{{/if}} and {{#if react}}React{{else}}Vanilla JS{{/if}}.
|
|
10
9
|
|
|
11
10
|
## Getting Started
|
|
12
11
|
|
|
13
|
-
Alongside this file, you will see a directory called `client`
|
|
14
|
-
one called `server` if
|
|
12
|
+
Alongside this file, you will see a directory called `client`{{#if server
|
|
13
|
+
}} (and one called `server` for your {{#if (eq syncType "node")}}local Node.js server{{else if (eq syncType "durable-objects")}}local Cloudflare Durable Objects server{{/if}}){{/if}}.
|
|
15
14
|
|
|
16
15
|
To start the web client with Vite, run the following commands:
|
|
17
16
|
|
|
@@ -24,18 +23,137 @@ npm run dev
|
|
|
24
23
|
PNPM, Yarn, and Bun should also work. Your app will be available at
|
|
25
24
|
`http://localhost:5173`, or whichever port Vite specifies in the console.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
{{#if server}}
|
|
27
|
+
If you included server code, start the server in a separate terminal, ideally
|
|
28
|
+
before starting the client:
|
|
29
29
|
|
|
30
|
-
```bash
|
|
31
|
-
# In a separate terminal
|
|
32
|
-
cd server
|
|
33
|
-
npm install
|
|
34
|
-
npm run dev
|
|
35
|
-
```
|
|
30
|
+
```bash
|
|
31
|
+
# In a separate terminal
|
|
32
|
+
cd server
|
|
33
|
+
npm install
|
|
34
|
+
npm run dev
|
|
35
|
+
```
|
|
36
36
|
|
|
37
|
+
{{/if}}
|
|
37
38
|
After that, start hacking!
|
|
38
39
|
|
|
40
|
+
## Structure
|
|
41
|
+
|
|
42
|
+
This project is organized into {{#if server}}two main directories{{else}}a single `client` directory{{/if}}:
|
|
43
|
+
|
|
44
|
+
### Client
|
|
45
|
+
|
|
46
|
+
The `client` directory contains your {{#if react}}React-based{{else}}vanilla JavaScript{{/if}} web application,
|
|
47
|
+
built with Vite and {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}.
|
|
48
|
+
|
|
49
|
+
#### Key Files
|
|
50
|
+
|
|
51
|
+
- **`index.html`** - The main HTML file that loads your app
|
|
52
|
+
- **`src/index.{{ext}}`** - Entry point that bootstraps the application{{#if react}} and renders the React component tree{{/if}}
|
|
53
|
+
- **`src/{{#if react}}App{{else}}app{{/if}}.{{ext}}`** - {{#if react}}Main React component that renders the {{#if (eq appType "todos")}}todo list{{else if (eq appType "chat")}}chat interface{{else if (eq appType "drawing")}}drawing
|
|
54
|
+
canvas{{else if (eq appType "game")}}game{{/if}}
|
|
55
|
+
{{else}}Main application logic{{/if}}
|
|
56
|
+
-
|
|
57
|
+
**`src/{{#if react}}{{#if (eq appType "chat")}}ChatStore{{else if (eq appType "drawing")}}CanvasStore{{else}}Store{{/if}}{{else}}{{#if (eq appType "chat")}}chatStore{{else if (eq appType "drawing")}}canvasStore{{else}}store{{/if}}{{/if}}.{{ext}}`**
|
|
58
|
+
- TinyBase {{#if (eq appType "chat")}}chat messages{{else if (eq appType "drawing")}}drawing canvas{{else}}main{{/if}} store configuration
|
|
59
|
+
{{#if (eq appType "chat")}}
|
|
60
|
+
- **`src/{{#if react}}SettingsStore{{else}}settingsStore{{/if}}.{{ext}}`** - TinyBase settings store (not synchronized)
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{#if (eq appType "drawing")}}
|
|
63
|
+
- **`src/{{#if react}}SettingsStore{{else}}settingsStore{{/if}}.{{ext}}`** - TinyBase settings store (not synchronized)
|
|
64
|
+
{{/if}}
|
|
65
|
+
- **`src/config.{{ext}}`** - Configuration settings{{#if server}} (including server connection URL){{/if}}
|
|
66
|
+
|
|
67
|
+
#### How It Works
|
|
68
|
+
|
|
69
|
+
The app uses **TinyBase** to manage application state in a reactive data store.
|
|
70
|
+
{{#if schemas}}Type-safe schemas define the structure of your data, providing autocomplete
|
|
71
|
+
and compile-time type checking.{{/if}}
|
|
72
|
+
|
|
73
|
+
{{#if sync}}
|
|
74
|
+
The store is a **MergeableStore**, which supports real-time synchronization across
|
|
75
|
+
multiple clients. When you open the app, it generates a unique room ID in the URL
|
|
76
|
+
path (e.g., `/abc123`). Anyone who visits the same URL will share the same data
|
|
77
|
+
and see live updates from other users.
|
|
78
|
+
|
|
79
|
+
{{/if}}
|
|
80
|
+
{{#if persistLocalStorage}}
|
|
81
|
+
**Persistence** is enabled using browser LocalStorage.
|
|
82
|
+
Your data is automatically saved and restored when you reload the page.
|
|
83
|
+
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{#if persistSqlite}}
|
|
86
|
+
**Persistence** is enabled using SQLite WASM (running entirely in the browser).
|
|
87
|
+
Your data is automatically saved and restored when you reload the page.
|
|
88
|
+
|
|
89
|
+
{{/if}}
|
|
90
|
+
{{#if persistPglite}}
|
|
91
|
+
**Persistence** is enabled using PGlite (Postgres WASM running entirely in the browser).
|
|
92
|
+
Your data is automatically saved and restored when you reload the page.
|
|
93
|
+
|
|
94
|
+
{{/if}}
|
|
95
|
+
{{#if sync}}
|
|
96
|
+
**Synchronization** is powered by TinyBase's WebSocket synchronizer{{#if server}}, connecting
|
|
97
|
+
to your {{#if (eq syncType "durable-objects")}}Cloudflare Durable Objects{{else}}Node.js{{/if}} server{{else}} (connecting to a demo server at vite.tinybase.org){{/if}}.
|
|
98
|
+
When data changes locally, it's sent to the server and broadcast to all other
|
|
99
|
+
clients in the same room. The MergeableStore handles conflict resolution using
|
|
100
|
+
CRDTs, ensuring all clients converge to the same state.
|
|
101
|
+
|
|
102
|
+
{{/if}}
|
|
103
|
+
{{#if react}}
|
|
104
|
+
**React Integration** - TinyBase provides React hooks that automatically subscribe
|
|
105
|
+
to store changes and trigger re-renders when data updates. This means your UI
|
|
106
|
+
stays in sync with your data with minimal boilerplate.
|
|
107
|
+
|
|
108
|
+
Components like {{#if (eq appType "todos")}}`TodoList` and `TodoItem` use hooks like `useSortedRowIds`, `useRow`, and
|
|
109
|
+
callback hooks like `useAddRowCallback` and `useDelRowCallback`
|
|
110
|
+
{{else if (eq appType "chat")}}`MessageList` and `MessageInput` use TinyBase hooks to display and send messages
|
|
111
|
+
{{else if (eq appType "drawing")}}`Canvas` uses TinyBase hooks to track drawing state across users
|
|
112
|
+
{{else}}`Game` uses TinyBase hooks to manage game state{{/if}} to interact
|
|
113
|
+
with the store.
|
|
114
|
+
|
|
115
|
+
{{/if}}
|
|
116
|
+
{{#if server}}
|
|
117
|
+
### Server
|
|
118
|
+
|
|
119
|
+
The `server` directory contains your {{#if (eq syncType "durable-objects")}}Cloudflare Durable Objects{{else}}Node.js{{/if}} WebSocket server.
|
|
120
|
+
|
|
121
|
+
#### Key Files
|
|
122
|
+
|
|
123
|
+
- **`index.{{ext}}`** - Server entry point
|
|
124
|
+
|
|
125
|
+
#### How It Works
|
|
126
|
+
|
|
127
|
+
{{#if (eq syncType "durable-objects")}}
|
|
128
|
+
The server uses **Cloudflare Durable Objects** to provide persistent, distributed
|
|
129
|
+
synchronization. Each room (identified by the URL path) maps to a Durable Object
|
|
130
|
+
instance that:
|
|
131
|
+
|
|
132
|
+
- Maintains the canonical state in SQL storage (using Durable Object SQL Storage)
|
|
133
|
+
- Handles WebSocket connections from clients
|
|
134
|
+
- Broadcasts changes to all connected clients in the room
|
|
135
|
+
- Automatically persists data, ensuring durability across deployments
|
|
136
|
+
|
|
137
|
+
The `TinyBaseDurableObject` class extends `WsServerDurableObject` and creates a
|
|
138
|
+
MergeableStore with a SQL storage persister. Cloudflare's infrastructure ensures
|
|
139
|
+
that all clients connecting to the same path are routed to the same Durable
|
|
140
|
+
Object instance.
|
|
141
|
+
|
|
142
|
+
To deploy to Cloudflare Workers, you'll need to configure your `wrangler.toml`
|
|
143
|
+
file with your Durable Object bindings.
|
|
144
|
+
{{else}}
|
|
145
|
+
The server runs a **Node.js WebSocket server** using the `ws` library. It acts
|
|
146
|
+
as a relay between clients, broadcasting state changes to all connected clients
|
|
147
|
+
in the same room.
|
|
148
|
+
|
|
149
|
+
When a client connects to a path (e.g., `/abc123`), the server groups them with
|
|
150
|
+
other clients on the same path and ensures all changes are synchronized.
|
|
151
|
+
|
|
152
|
+
The server logs when clients join or leave rooms, helping you monitor activity
|
|
153
|
+
during development.
|
|
154
|
+
{{/if}}
|
|
155
|
+
|
|
156
|
+
{{/if}}
|
|
39
157
|
## Learn More
|
|
40
158
|
|
|
41
159
|
- [TinyBase Documentation](https://tinybase.org)
|
|
@@ -5,13 +5,20 @@
|
|
|
5
5
|
{{addImport "import pluginReact from 'eslint-plugin-react';"}}
|
|
6
6
|
{{addImport "import pluginReactHooks from 'eslint-plugin-react-hooks';"}}
|
|
7
7
|
{{/if}}
|
|
8
|
+
{{addImport "import globals from 'globals';"}}
|
|
8
9
|
|
|
9
10
|
export default [
|
|
11
|
+
{
|
|
12
|
+
languageOptions: {
|
|
13
|
+
globals: globals.browser,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
10
16
|
{{#if typescript}}
|
|
11
17
|
...tseslint.configs.recommended,
|
|
12
18
|
{
|
|
13
19
|
rules: {
|
|
14
20
|
'@typescript-eslint/no-unused-vars': 'warn',
|
|
21
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
15
22
|
},
|
|
16
23
|
},
|
|
17
24
|
{{else}}
|
|
@@ -22,25 +22,31 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
{{#list}}
|
|
25
|
-
"vite": "^7.1
|
|
25
|
+
"vite": "^7.3.1"
|
|
26
26
|
{{#if typescript}}
|
|
27
27
|
"typescript": "^5.9.3"
|
|
28
|
-
"@types/node": "^25.0.
|
|
28
|
+
"@types/node": "^25.0.9"
|
|
29
29
|
{{#if react}}
|
|
30
|
-
"@types/react": "^19.2.
|
|
30
|
+
"@types/react": "^19.2.8"
|
|
31
31
|
"@types/react-dom": "^19.2.3"
|
|
32
32
|
{{/if}}
|
|
33
33
|
{{/if}}
|
|
34
34
|
{{#if react}}
|
|
35
|
-
"@vitejs/plugin-react": "^
|
|
35
|
+
"@vitejs/plugin-react": "^5.1.2"
|
|
36
|
+
{{/if}}
|
|
37
|
+
{{#if persistSqlite}}
|
|
38
|
+
"vite-plugin-static-copy": "^3.2.0"
|
|
39
|
+
{{/if}}
|
|
40
|
+
{{#if persistPglite}}
|
|
41
|
+
"vite-plugin-static-copy": "^3.2.0"
|
|
36
42
|
{{/if}}
|
|
37
43
|
{{#if prettier}}
|
|
38
|
-
"prettier": "^3.
|
|
44
|
+
"prettier": "^3.8.0"
|
|
39
45
|
{{/if}}
|
|
40
46
|
{{#if eslint}}
|
|
41
47
|
"eslint": "^9.39.2"
|
|
42
48
|
{{#if typescript}}
|
|
43
|
-
"typescript-eslint": "^8.
|
|
49
|
+
"typescript-eslint": "^8.53.1"
|
|
44
50
|
{{/if}}
|
|
45
51
|
{{#if react}}
|
|
46
52
|
"eslint-plugin-react": "7.37.5"
|
|
@@ -51,7 +57,7 @@
|
|
|
51
57
|
},
|
|
52
58
|
"dependencies": {
|
|
53
59
|
{{#list}}
|
|
54
|
-
"tinybase": "^7.3.
|
|
60
|
+
"tinybase": "^7.3.2"
|
|
55
61
|
{{#if react}}
|
|
56
62
|
"react": "^19.2.3"
|
|
57
63
|
"react-dom": "^19.2.3"
|
|
@@ -59,18 +65,32 @@
|
|
|
59
65
|
{{#if sync}}
|
|
60
66
|
"reconnecting-websocket": "^4.4.0"
|
|
61
67
|
{{/if}}
|
|
68
|
+
{{#if persistSqlite}}
|
|
69
|
+
"@sqlite.org/sqlite-wasm": "^3.50.4-build1"
|
|
70
|
+
{{/if}}
|
|
71
|
+
{{#if persistPglite}}
|
|
72
|
+
"@electric-sql/pglite": "^0.3.15"
|
|
73
|
+
{{/if}}
|
|
62
74
|
{{/list}}
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
{{#if prettier}}
|
|
66
|
-
{{includeFile template="client/.prettierrc.hbs" output="client/.prettierrc"}}
|
|
78
|
+
{{includeFile template="client/.prettierrc.json.hbs" output="client/.prettierrc.json"}}
|
|
67
79
|
{{/if}}
|
|
68
80
|
{{#if eslint}}
|
|
69
81
|
{{includeFile template="client/eslint.config.js.hbs" output="client/eslint.config.js"}}
|
|
70
82
|
{{/if}}
|
|
71
83
|
{{#if react}}
|
|
72
84
|
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
85
|
+
{{else}}
|
|
86
|
+
{{#if persistSqlite}}
|
|
87
|
+
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
88
|
+
{{/if}}
|
|
89
|
+
{{#if persistPglite}}
|
|
90
|
+
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
91
|
+
{{/if}}
|
|
73
92
|
{{/if}}
|
|
74
93
|
{{#if typescript}}
|
|
75
94
|
{{includeFile template="client/tsconfig.json.hbs" output="client/tsconfig.json"}}
|
|
95
|
+
{{includeFile template="client/vite-env.d.ts.hbs" output="client/src/vite-env.d.ts"}}
|
|
76
96
|
{{/if}}
|
|
@@ -1,40 +1,46 @@
|
|
|
1
|
-
import {StrictMode} from 'react';
|
|
1
|
+
{{addImport "import {StrictMode, useState} from 'react';"}}
|
|
2
|
+
{{includeFile template="client/src/shared/Loading.tsx.hbs" output="client/src/Loading.{{ext}}"}}
|
|
3
|
+
{{addImport "import {Loading} from './Loading';"}}
|
|
2
4
|
|
|
3
|
-
{{
|
|
4
|
-
|
|
5
|
-
{{else}}
|
|
6
|
-
import {Provider} from 'tinybase/ui-react';
|
|
7
|
-
{{/if}}
|
|
8
|
-
import {Inspector} from 'tinybase/ui-react-inspector';
|
|
5
|
+
{{addImport "import {Provider} from 'tinybase/ui-react';"}}
|
|
6
|
+
{{addImport "import {Inspector} from 'tinybase/ui-react-inspector';"}}
|
|
9
7
|
|
|
10
8
|
{{includeFile template="client/src/chat/SettingsStore.tsx.hbs" output="client/src/SettingsStore.{{ext}}"}}
|
|
11
|
-
import {SettingsStore} from './SettingsStore';
|
|
9
|
+
{{addImport "import {SettingsStore} from './SettingsStore';"}}
|
|
12
10
|
|
|
13
11
|
{{includeFile template="client/src/chat/ChatStore.tsx.hbs" output="client/src/ChatStore.{{ext}}"}}
|
|
14
|
-
import {ChatStore} from './ChatStore';
|
|
12
|
+
{{addImport "import {ChatStore} from './ChatStore';"}}
|
|
15
13
|
|
|
16
14
|
{{includeFile template="client/src/chat/UsernameInput.tsx.hbs" output="client/src/UsernameInput.{{ext}}"}}
|
|
17
|
-
import {UsernameInput} from './UsernameInput';
|
|
15
|
+
{{addImport "import {UsernameInput} from './UsernameInput';"}}
|
|
18
16
|
|
|
19
17
|
{{includeFile template="client/src/chat/Messages.tsx.hbs" output="client/src/Messages.{{ext}}"}}
|
|
20
|
-
import {Messages} from './Messages';
|
|
18
|
+
{{addImport "import {Messages} from './Messages';"}}
|
|
21
19
|
|
|
22
20
|
{{includeFile template="client/src/chat/MessageInput.tsx.hbs" output="client/src/MessageInput.{{ext}}"}}
|
|
23
|
-
import {MessageInput} from './MessageInput';
|
|
21
|
+
{{addImport "import {MessageInput} from './MessageInput';"}}
|
|
22
|
+
|
|
23
|
+
export const App = () => {
|
|
24
|
+
const [settingsReady, setSettingsReady] = useState(false);
|
|
25
|
+
const [chatReady, setChatReady] = useState(false);
|
|
26
|
+
const loading = !settingsReady || !chatReady;
|
|
24
27
|
|
|
25
|
-
const App = () => {
|
|
26
28
|
return (
|
|
27
29
|
<StrictMode>
|
|
28
30
|
<Provider>
|
|
29
|
-
<SettingsStore />
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
<SettingsStore onReady={()=> setSettingsReady(true)} />
|
|
32
|
+
<ChatStore onReady={()=> setChatReady(true)} />
|
|
33
|
+
{loading ? (
|
|
34
|
+
<Loading />
|
|
35
|
+
) : (
|
|
36
|
+
<>
|
|
37
|
+
<UsernameInput />
|
|
38
|
+
<Messages />
|
|
39
|
+
<MessageInput />
|
|
40
|
+
<Inspector />
|
|
41
|
+
</>
|
|
42
|
+
)}
|
|
35
43
|
</Provider>
|
|
36
44
|
</StrictMode>
|
|
37
45
|
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export {App};
|
|
46
|
+
};
|