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.
Files changed (81) hide show
  1. package/README.md +62 -18
  2. package/cli.js +2 -2
  3. package/package.json +7 -4
  4. package/screenshots/chat.png +0 -0
  5. package/screenshots/drawing.png +0 -0
  6. package/screenshots/todos.png +0 -0
  7. package/templates/README.md.hbs +130 -12
  8. package/templates/client/eslint.config.js.hbs +7 -0
  9. package/templates/client/package.json.hbs +28 -8
  10. package/templates/client/src/chat/App.tsx.hbs +28 -22
  11. package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
  12. package/templates/client/src/chat/Message.tsx.hbs +8 -9
  13. package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
  14. package/templates/client/src/chat/Messages.tsx.hbs +5 -15
  15. package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
  16. package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
  17. package/templates/client/src/chat/app.ts.hbs +18 -18
  18. package/templates/client/src/chat/chatStore.ts.hbs +97 -35
  19. package/templates/client/src/chat/message.ts.hbs +4 -3
  20. package/templates/client/src/chat/messageInput.css.hbs +1 -1
  21. package/templates/client/src/chat/messageInput.ts.hbs +7 -8
  22. package/templates/client/src/chat/messages.ts.hbs +7 -12
  23. package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
  24. package/templates/client/src/chat/usernameInput.css.hbs +1 -1
  25. package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
  26. package/templates/client/src/chat/utils.ts.hbs +26 -0
  27. package/templates/client/src/drawing/App.tsx.hbs +26 -20
  28. package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
  29. package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
  30. package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
  31. package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
  32. package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
  33. package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
  34. package/templates/client/src/drawing/app.ts.hbs +18 -8
  35. package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
  36. package/templates/client/src/drawing/canvas.ts.hbs +84 -86
  37. package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
  38. package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
  39. package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
  40. package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
  41. package/templates/client/src/game/App.tsx.hbs +20 -16
  42. package/templates/client/src/game/Board.tsx.hbs +8 -8
  43. package/templates/client/src/game/Game.tsx.hbs +14 -21
  44. package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
  45. package/templates/client/src/game/Square.tsx.hbs +6 -11
  46. package/templates/client/src/game/Store.tsx.hbs +106 -16
  47. package/templates/client/src/game/app.ts.hbs +17 -6
  48. package/templates/client/src/game/board.ts.hbs +7 -7
  49. package/templates/client/src/game/game.ts.hbs +12 -18
  50. package/templates/client/src/game/gameStatus.ts.hbs +3 -3
  51. package/templates/client/src/game/square.ts.hbs +4 -4
  52. package/templates/client/src/game/store.ts.hbs +95 -23
  53. package/templates/client/src/index.tsx.hbs +5 -7
  54. package/templates/client/src/shared/Button.tsx.hbs +3 -3
  55. package/templates/client/src/shared/Input.tsx.hbs +2 -2
  56. package/templates/client/src/shared/Loading.tsx.hbs +5 -0
  57. package/templates/client/src/shared/button.ts.hbs +2 -2
  58. package/templates/client/src/shared/config.ts.hbs +4 -6
  59. package/templates/client/src/shared/input.ts.hbs +2 -2
  60. package/templates/client/src/shared/loading.css.hbs +21 -0
  61. package/templates/client/src/shared/loading.ts.hbs +13 -0
  62. package/templates/client/src/shared/pglite.ts.hbs +10 -0
  63. package/templates/client/src/shared/sqlite.ts.hbs +17 -0
  64. package/templates/client/src/todos/App.tsx.hbs +22 -22
  65. package/templates/client/src/todos/Store.tsx.hbs +106 -23
  66. package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
  67. package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
  68. package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
  69. package/templates/client/src/todos/app.ts.hbs +16 -10
  70. package/templates/client/src/todos/store.ts.hbs +94 -30
  71. package/templates/client/src/todos/todoInput.ts.hbs +5 -8
  72. package/templates/client/src/todos/todoItem.ts.hbs +3 -4
  73. package/templates/client/src/todos/todoList.ts.hbs +6 -8
  74. package/templates/client/vite-env.d.ts.hbs +4 -0
  75. package/templates/client/vite.config.js.hbs +53 -3
  76. package/templates/server/index.ts.hbs +43 -0
  77. package/templates/server/package.json.hbs +6 -7
  78. package/templates/server/wrangler.toml.hbs +1 -1
  79. package/templates/server/index-do.ts.hbs +0 -22
  80. package/templates/server/index-node.ts.hbs +0 -8
  81. /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** - Enable real-time sync between clients
26
- - **Server code** - Include Node.js or Cloudflare Durable Objects server
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
- **Enabled** (default):
149
+ Choose from four synchronization options:
150
150
 
151
- - Real-time sync between browser tabs
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
- ### Server Code
158
+ **Via remote demo server (stateless)** (default):
165
159
 
166
- When synchronization is enabled, you can include server code:
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
- **Node.js Server** (port 8043):
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
- **Cloudflare Durable Objects** (port 8787):
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
- **No Server**:
188
+ ### Persistence
183
189
 
184
- - Connects to public demo server
185
- - Great for prototyping
186
- - No local server management needed
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 y}from"fs";import{dirname as w,join as u}from"path";import{createCLI as f,detectPackageManager as h}from"tinycreate";import{fileURLToPath as k}from"url";const j=w(k(import.meta.url)),x={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 y(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:"confirm",name:"sync",message:"Enable synchronization?",initial:!0},{type:(e,t)=>t.sync?"confirm":null,name:"server",message:"Add code for server?",initial:!1},{type:(e,t)=>t.server?"select":null,name:"serverType",message:"Server type:",choices:[{title:"Node",value:"node"},{title:"Durable Objects",value:"durable-objects"}],initial:0},{type:"confirm",name:"prettier",message:"Include Prettier?",initial:!0},{type:"confirm",name:"eslint",message:"Include ESLint?",initial:!0},{type:(e,t)=>t.server?null:"confirm",name:"installAndRun",message:"Install dependencies and start dev server?",initial:!0}],createContext:e=>{const{projectName:t,language:s,framework:n,appType:r,prettier:m,eslint:g,schemas:i,sync:c,server:l,serverType:d,installAndRun:p}=e,a=s==="typescript",v=!a,o=n==="react";return{projectName:t,language:s,framework:n,appType:r,prettier:m,eslint:g,schemas:a&&(i===!0||i==="true"),sync:c===!0||c==="true",server:l===!0||l==="true",serverType:d||"node",installAndRun:p===!0||p==="true",typescript:a,javascript:v,react:o,ext:a?o?"tsx":"ts":o?"jsx":"js"}},createDirectories:async(e,t)=>{const{mkdir:s}=await import("fs/promises"),{join:n}=await import("path"),r=t.server;await s(n(e,"client/src"),{recursive:!0}),await s(n(e,"client/public"),{recursive:!0}),r&&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),r=e.transpile??(/\.(ts|tsx)\.hbs$/.test(e.template)&&s===!0);return{...e,prettier:n,transpile:r}},templateRoot:u(j,"templates"),installCommand:"{pm} install",devCommand:"{pm} run dev",workingDirectory:"client",onSuccess:(e,t)=>{const s=t.server,n=h();console.log("Next steps:"),console.log(),s&&(console.log("To run the server:"),console.log(` cd ${e}/server`),console.log(` ${n} install`),console.log(` ${n} run dev`),console.log()),console.log("To run the client:"),console.log(` cd ${e}/client`),console.log(` ${n} install`),console.log(` ${n} run dev`)}};f(x).catch(e=>{console.error(e),process.exit(1)});
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.2.5",
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 a new app using TinyBase, a reactive data store and sync engine.",
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.3"
29
+ "tinycreate": "^1.0.4"
27
30
  },
28
31
  "engines": {
29
32
  "node": ">=18.0.0"
Binary file
Binary file
Binary file
@@ -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` (and optionally
14
- one called `server` if you included server code).
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
- If you included server code, start the server in a separate terminal, ideally
28
- before starting the client:
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.3"
25
+ "vite": "^7.3.1"
26
26
  {{#if typescript}}
27
27
  "typescript": "^5.9.3"
28
- "@types/node": "^25.0.3"
28
+ "@types/node": "^25.0.9"
29
29
  {{#if react}}
30
- "@types/react": "^19.2.7"
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": "^4.3.4"
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.7.4"
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.51.0"
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.1"
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
- {{#if schemas}}
4
- import {Provider} from 'tinybase/ui-react/with-schemas';
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
- <ChatStore />
31
- <UsernameInput />
32
- <Messages />
33
- <MessageInput />
34
- <Inspector />
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
+ };