create-tinybase 0.1.5 → 0.2.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/cli.js +2 -2
  4. package/package.json +6 -3
  5. package/screenshots/chat.png +0 -0
  6. package/screenshots/drawing.png +0 -0
  7. package/screenshots/game.png +0 -0
  8. package/screenshots/todos.png +0 -0
  9. package/templates/README.md.hbs +1 -0
  10. package/templates/{eslint.config.js.hbs → client/eslint.config.js.hbs} +5 -0
  11. package/templates/client/index.html.hbs +182 -0
  12. package/templates/client/package.json.hbs +76 -0
  13. package/templates/client/src/chat/App.tsx.hbs +40 -0
  14. package/templates/client/src/chat/ChatStore.tsx.hbs +70 -0
  15. package/templates/client/src/chat/Message.tsx.hbs +21 -0
  16. package/templates/client/src/chat/MessageInput.tsx.hbs +42 -0
  17. package/templates/client/src/chat/Messages.tsx.hbs +29 -0
  18. package/templates/client/src/chat/SettingsStore.tsx.hbs +34 -0
  19. package/templates/client/src/chat/UsernameInput.tsx.hbs +22 -0
  20. package/templates/client/src/chat/app.ts.hbs +39 -0
  21. package/templates/client/src/chat/chatStore.ts.hbs +50 -0
  22. package/templates/client/src/chat/message.css.hbs +20 -0
  23. package/templates/client/src/chat/message.ts.hbs +27 -0
  24. package/templates/client/src/chat/messageInput.css.hbs +6 -0
  25. package/templates/client/src/chat/messageInput.ts.hbs +42 -0
  26. package/templates/client/src/chat/messages.css.hbs +6 -0
  27. package/templates/client/src/chat/messages.ts.hbs +33 -0
  28. package/templates/client/src/chat/settingsStore.ts.hbs +19 -0
  29. package/templates/client/src/chat/usernameInput.css.hbs +14 -0
  30. package/templates/client/src/chat/usernameInput.ts.hbs +30 -0
  31. package/templates/client/src/drawing/App.tsx.hbs +36 -0
  32. package/templates/client/src/drawing/BrushSize.tsx.hbs +22 -0
  33. package/templates/client/src/drawing/Canvas.tsx.hbs +100 -0
  34. package/templates/client/src/drawing/CanvasStore.tsx.hbs +62 -0
  35. package/templates/client/src/drawing/ColorPicker.tsx.hbs +24 -0
  36. package/templates/client/src/drawing/DrawingControls.tsx.hbs +24 -0
  37. package/templates/client/src/drawing/SettingsStore.tsx.hbs +36 -0
  38. package/templates/client/src/drawing/app.ts.hbs +20 -0
  39. package/templates/client/src/drawing/brushSize.css.hbs +21 -0
  40. package/templates/client/src/drawing/brushSize.ts.hbs +33 -0
  41. package/templates/client/src/drawing/canvas.css.hbs +8 -0
  42. package/templates/client/src/drawing/canvas.ts.hbs +103 -0
  43. package/templates/client/src/drawing/canvasStore.ts.hbs +42 -0
  44. package/templates/client/src/drawing/colorPicker.css.hbs +21 -0
  45. package/templates/client/src/drawing/colorPicker.ts.hbs +34 -0
  46. package/templates/client/src/drawing/drawingControls.css.hbs +12 -0
  47. package/templates/client/src/drawing/drawingControls.ts.hbs +26 -0
  48. package/templates/client/src/drawing/settingsStore.ts.hbs +21 -0
  49. package/templates/client/src/game/App.tsx.hbs +28 -0
  50. package/templates/client/src/game/Board.tsx.hbs +27 -0
  51. package/templates/client/src/game/Game.tsx.hbs +78 -0
  52. package/templates/client/src/game/GameStatus.tsx.hbs +21 -0
  53. package/templates/client/src/game/Square.tsx.hbs +23 -0
  54. package/templates/client/src/game/Store.tsx.hbs +67 -0
  55. package/templates/client/src/game/app.ts.hbs +12 -0
  56. package/templates/client/src/game/board.css.hbs +13 -0
  57. package/templates/client/src/game/board.ts.hbs +39 -0
  58. package/templates/client/src/game/game.ts.hbs +74 -0
  59. package/templates/client/src/game/gameStatus.css.hbs +21 -0
  60. package/templates/client/src/game/gameStatus.ts.hbs +27 -0
  61. package/templates/client/src/game/square.css.hbs +38 -0
  62. package/templates/client/src/game/square.ts.hbs +11 -0
  63. package/templates/client/src/game/store.ts.hbs +47 -0
  64. package/templates/client/src/index.tsx.hbs +24 -0
  65. package/templates/client/src/shared/Button.tsx.hbs +16 -0
  66. package/templates/client/src/shared/Input.tsx.hbs +16 -0
  67. package/templates/client/src/shared/button.css.hbs +25 -0
  68. package/templates/client/src/shared/button.ts.hbs +16 -0
  69. package/templates/client/src/shared/config.ts.hbs +9 -0
  70. package/templates/client/src/shared/input.css.hbs +22 -0
  71. package/templates/client/src/shared/input.ts.hbs +17 -0
  72. package/templates/client/src/todos/App.tsx.hbs +32 -0
  73. package/templates/client/src/todos/Store.tsx.hbs +70 -0
  74. package/templates/client/src/todos/TodoInput.tsx.hbs +30 -0
  75. package/templates/client/src/todos/TodoItem.tsx.hbs +20 -0
  76. package/templates/client/src/todos/TodoList.tsx.hbs +18 -0
  77. package/templates/client/src/todos/app.ts.hbs +23 -0
  78. package/templates/client/src/todos/store.ts.hbs +49 -0
  79. package/templates/client/src/todos/todoInput.css.hbs +9 -0
  80. package/templates/client/src/todos/todoInput.ts.hbs +38 -0
  81. package/templates/client/src/todos/todoItem.css.hbs +33 -0
  82. package/templates/client/src/todos/todoItem.ts.hbs +28 -0
  83. package/templates/client/src/todos/todoList.css.hbs +14 -0
  84. package/templates/client/src/todos/todoList.ts.hbs +38 -0
  85. package/templates/package.json.hbs +21 -56
  86. package/templates/server/index-do.ts.hbs +22 -0
  87. package/templates/server/index-node.ts.hbs +8 -0
  88. package/templates/server/package.json.hbs +51 -0
  89. package/templates/server/tsconfig.json.hbs +13 -0
  90. package/templates/server/wrangler.toml.hbs +12 -0
  91. package/templates/index.html.hbs +0 -55
  92. package/templates/src/App.tsx.hbs +0 -52
  93. package/templates/src/Buttons.tsx.hbs +0 -26
  94. package/templates/src/app.ts.hbs +0 -36
  95. package/templates/src/index.css.hbs +0 -138
  96. package/templates/src/index.tsx.hbs +0 -18
  97. /package/templates/{.prettierrc.hbs → client/.prettierrc.hbs} +0 -0
  98. /package/templates/{public → client/public}/favicon.svg +0 -0
  99. /package/templates/{tsconfig.json.hbs → client/tsconfig.json.hbs} +0 -0
  100. /package/templates/{vite.config.js.hbs → client/vite.config.js.hbs} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) James Pearce, 2025 -
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # create-tinybase
2
+
3
+ A CLI tool to scaffold a new TinyBase application with full synchronization and local-first capabilities.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm init tinybase
9
+ ```
10
+
11
+ This will prompt you with questions to configure your new TinyBase app:
12
+
13
+ - **Project name** - Name of your project directory
14
+ - **Language** - TypeScript or JavaScript
15
+ - **Framework** - React or Vanilla JS
16
+ - **App type** - Todo app, Chat app, Drawing app, or Tic-tac-toe game
17
+ - **Store schemas** - TypeScript type safety for stores (TypeScript only)
18
+ - **Synchronization** - Enable real-time sync between clients
19
+ - **Server code** - Include Node.js or Cloudflare Durable Objects server
20
+ - **Prettier** - Include Prettier for code formatting
21
+ - **ESLint** - Include ESLint for code linting
22
+
23
+ After creating your project:
24
+
25
+ ```bash
26
+ cd my-tinybase-app/client
27
+ npm install
28
+ npm run dev
29
+ ```
30
+
31
+ If you included server code:
32
+
33
+ ```bash
34
+ # In a separate terminal
35
+ cd my-tinybase-app/server
36
+ npm install
37
+ npm run dev
38
+ ```
39
+
40
+ Your app will be available at `http://localhost:5173`
41
+
42
+ ## Features
43
+
44
+ - ⚡ **Fast Setup** - Get started in seconds with Vite
45
+ - 🔄 **Real-time Sync** - Built-in synchronization support
46
+ - 🎨 **Four Demo Apps** - Learn from complete examples
47
+ - 📦 **Zero Config** - Works out of the box
48
+ - 🔧 **Fully Customizable** - Modify templates to your needs
49
+ - 🌐 **Local-First** - Offline-capable by default
50
+ - 🔐 **Type-Safe** - Optional TypeScript schemas
51
+
52
+ ## Configuration Guide
53
+
54
+ ### Language Choice
55
+
56
+ **TypeScript** provides full type safety with:
57
+
58
+ - Typed store schemas (optional)
59
+ - IntelliSense support for TinyBase APIs
60
+ - Compile-time error checking
61
+ - Better IDE integration
62
+
63
+ **JavaScript** offers:
64
+
65
+ - Faster setup with no transpilation step
66
+ - Simpler learning curve
67
+ - Still fully functional with TinyBase
68
+
69
+ ### Framework Choice
70
+
71
+ **React** provides:
72
+
73
+ - Component-based architecture
74
+ - React hooks for TinyBase stores
75
+ - Automatic re-rendering on store updates
76
+ - Full ecosystem support
77
+
78
+ **Vanilla JS** offers:
79
+
80
+ - No framework dependencies
81
+ - Smaller bundle size
82
+ - Direct DOM manipulation
83
+ - Manual listener-based updates
84
+
85
+ ### App Types
86
+
87
+ #### Todo App
88
+
89
+ ![Todo App](screenshots/todos.png)
90
+
91
+ - Task list with add/complete/delete
92
+ - Single store for todos
93
+ - Demonstrates basic CRUD operations
94
+ - Perfect starter example
95
+
96
+ #### Chat App
97
+
98
+ ![Chat App](screenshots/chat.png)
99
+
100
+ - Multi-user messaging interface
101
+ - Dual stores: settings + messages
102
+ - Username configuration
103
+ - Real-time message sync
104
+
105
+ #### Drawing App
106
+
107
+ ![Drawing App](screenshots/drawing.png)
108
+
109
+ - Collaborative drawing canvas
110
+ - Brush size and color controls
111
+ - Dual stores: settings + canvas
112
+ - Optimized point-based stroke storage
113
+
114
+ #### Tic-tac-toe Game
115
+
116
+ ![Tic-tac-toe Game](screenshots/game.png)
117
+
118
+ - Two-player game board
119
+ - Win/draw detection
120
+ - Turn management
121
+ - Game state synchronization
122
+
123
+ ### Store Schemas (TypeScript Only)
124
+
125
+ When enabled, schemas provide:
126
+
127
+ - Full TypeScript typing for store structure
128
+ - Runtime validation
129
+ - Better autocomplete
130
+ - Type-safe data access
131
+
132
+ Schemas define:
133
+
134
+ - Table structures with cell types
135
+ - Value types
136
+ - Default values
137
+ - Strict typing for all store operations
138
+
139
+ ### Synchronization
140
+
141
+ **Enabled** (default):
142
+
143
+ - Real-time sync between browser tabs
144
+ - WebSocket-based synchronization
145
+ - Connects to demo server by default (wss://vite.tinybase.org)
146
+ - MergeableStore for conflict-free replication
147
+ - Automatic reconnection handling
148
+
149
+ **Disabled**:
150
+
151
+ - Local-only data storage
152
+ - No network dependencies
153
+ - Simpler architecture
154
+ - Still uses MergeableStore for consistency
155
+
156
+ ### Server Code
157
+
158
+ When synchronization is enabled, you can include server code:
159
+
160
+ **Node.js Server** (port 8043):
161
+
162
+ - WebSocket server using `ws` library
163
+ - TinyBase server synchronizer
164
+ - Runs with `npm run dev` in server directory
165
+ - Easy to deploy to any Node.js host
166
+
167
+ **Cloudflare Durable Objects** (port 8787):
168
+
169
+ - Serverless WebSocket server
170
+ - Edge computing with Durable Objects
171
+ - Global distribution
172
+ - Runs locally with Wrangler
173
+
174
+ **No Server**:
175
+
176
+ - Connects to public demo server
177
+ - Great for prototyping
178
+ - No local server management needed
179
+
180
+ ### Prettier & ESLint
181
+
182
+ **Prettier**:
183
+
184
+ - Automatic code formatting
185
+ - Consistent style across project
186
+ - Pre-configured settings
187
+ - Runs on save (with IDE integration)
188
+
189
+ **ESLint**:
190
+
191
+ - Code quality checks
192
+ - Catch common errors
193
+ - Import organization
194
+ - TypeScript-aware rules
195
+
196
+ ## Project Structure
197
+
198
+ All apps are created with a monorepo structure:
199
+
200
+ ```
201
+ my-tinybase-app/
202
+ ├── package.json # Root package (manages workspaces)
203
+ ├── README.md # Getting started guide
204
+ ├── client/ # Client application
205
+ │ ├── package.json # Client dependencies
206
+ │ ├── index.html # Entry HTML
207
+ │ ├── public/ # Static assets
208
+ │ └── src/ # Source code
209
+ │ ├── App.tsx # Main app component
210
+ │ ├── Store.tsx # TinyBase store setup
211
+ │ └── ... # App-specific components
212
+ └── server/ # Server code (optional)
213
+ ├── package.json # Server dependencies
214
+ └── src/
215
+ └── index.ts # Server entry point
216
+ ```
217
+
218
+ ## Learn More
219
+
220
+ - [TinyBase Documentation](https://tinybase.org)
221
+ - [TinyBase GitHub](https://github.com/tinyplex/tinybase)
222
+ - [Synchronization Guide](https://tinybase.org/guides/synchronization)
223
+ - [React Integration](https://tinybase.org/guides/building-uis/using-react)
224
+
225
+ ## License
226
+
227
+ MIT License - see [LICENSE](https://github.com/tinyplex/tinybase/blob/main/LICENSE) file for details.
package/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{existsSync as p}from"fs";import{dirname as m,join as o}from"path";import{createCLI as u}from"tinycreate";import{fileURLToPath as g}from"url";const d=m(g(import.meta.url)),y={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=o(process.cwd(),e);return p(t)?`Directory "${e}" already exists. Please choose a different name.`:!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:"confirm",name:"prettier",message:"Include Prettier?",initial:!1},{type:"confirm",name:"eslint",message:"Include ESLint?",initial:!1}],createContext:e=>{const{projectName:t,language:s,framework:n,prettier:r,eslint:c}=e,a=s==="typescript",l=!a,i=n==="react";return{projectName:t,language:s,framework:n,prettier:r,eslint:c,typescript:a,javascript:l,react:i,ext:a?i?"tsx":"ts":i?"jsx":"js"}},createDirectories:async e=>{const{mkdir:t}=await import("fs/promises"),{join:s}=await import("path");await t(s(e,"src"),{recursive:!0}),await t(s(e,"public"),{recursive:!0})},getFiles:()=>[{template:"package.json.hbs",output:"package.json",prettier:!0},{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:o(d,"templates"),installCommand:"{pm} install",devCommand:"{pm} run dev",onSuccess:e=>{console.log("Next steps:"),console.log(` cd ${e}`),console.log(" npm install"),console.log(" npm run dev")}};u(y).catch(e=>{console.error(e),process.exit(1)});
2
+ import{existsSync as v}from"fs";import{dirname as y,join as p}from"path";import{createCLI as f}from"tinycreate";import{fileURLToPath as h}from"url";const w=y(h(import.meta.url)),j={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=p(process.cwd(),e);return v(t)?`Directory "${e}" already exists. Please choose a different name.`:!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:"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:(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:!1},{type:"confirm",name:"eslint",message:"Include ESLint?",initial:!1}],createContext:e=>{const{projectName:t,language:s,framework:n,appType:r,prettier:m,eslint:u,schemas:o,sync:c,server:l,serverType:g}=e,a=s==="typescript",d=!a,i=n==="react";return{projectName:t,language:s,framework:n,appType:r,prettier:m,eslint:u,schemas:a&&(o===!0||o==="true"),sync:c===!0||c==="true",server:l===!0||l==="true",serverType:g||"node",typescript:a,javascript:d,react:i,ext:a?i?"tsx":"ts":i?"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:e=>{const t=e.server,s=[{template:"README.md.hbs",output:"README.md",prettier:!0},{template:"client/package.json.hbs",output:"client/package.json",prettier:!0}];return t&&s.push({template:"package.json.hbs",output:"package.json",prettier:!0}),s},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:p(w,"templates"),installCommand:"{pm} install",devCommand:"{pm} run dev",onSuccess:e=>{console.log("Next steps:"),console.log(` cd ${e}/client`),console.log(" npm install"),console.log(" npm run dev")}};f(j).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.1.5",
3
+ "version": "0.2.1",
4
4
  "author": "jamesgpearce",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,13 +23,16 @@
23
23
  "create-tinybase": "cli.js"
24
24
  },
25
25
  "dependencies": {
26
- "tinycreate": "^0.1.1"
26
+ "tinycreate": "^1.0.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=18.0.0"
30
30
  },
31
31
  "files": [
32
32
  "cli.js",
33
- "templates"
33
+ "templates",
34
+ "screenshots",
35
+ "README.md",
36
+ "LICENSE"
34
37
  ]
35
38
  }
Binary file
Binary file
Binary file
Binary file
@@ -5,6 +5,7 @@ A TinyBase app built with {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
5
5
  ## Getting Started
6
6
 
7
7
  ```bash
8
+ cd client
8
9
  npm install
9
10
  npm run dev
10
11
  ```
@@ -9,6 +9,11 @@
9
9
  export default [
10
10
  {{#if typescript}}
11
11
  ...tseslint.configs.recommended,
12
+ {
13
+ rules: {
14
+ '@typescript-eslint/no-unused-vars': 'warn',
15
+ },
16
+ },
12
17
  {{else}}
13
18
  {
14
19
  rules: {
@@ -0,0 +1,182 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;800&display=swap" />
10
+ {{includeFile template="client/public/favicon.svg" output="client/public/favicon.svg"}}
11
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
12
+ <title>
13
+ TinyBase {{#if (eq appType "chat")}}Chat{{else if (eq appType "drawing")}}Drawing{{else if (eq appType "game")}}Game{{else}}Todos{{/if}}
14
+ </title>
15
+ <style>
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ --hue: 270;
22
+ --fg: oklch(85% 0.01 var(--hue));
23
+ --fg2: oklch(60% 0.01 var(--hue));
24
+ --bg: oklch(20% 0.01 var(--hue));
25
+ --bg2: oklch(25% 0.01 var(--hue));
26
+ --border: oklch(30% 0.01 var(--hue));
27
+ --bg-header: oklch(30% 0.008 var(--hue) / 0.5);
28
+ --accent: #d81b60;
29
+ color: var(--fg);
30
+ background: var(--bg);
31
+ font-family: 'Inter', sans-serif;
32
+ user-select: none;
33
+ margin: 0;
34
+ padding: 0;
35
+ }
36
+
37
+ #appContainer {
38
+ display: flex;
39
+ flex-direction: column;
40
+ min-height: 100vh;
41
+ }
42
+
43
+ #topBar {
44
+ background: var(--bg-header);
45
+ border-bottom: 1px solid var(--border);
46
+ box-shadow: 0 1px 2px 0 #0007;
47
+ padding: 0.75rem 1.5rem;
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 1rem;
51
+ position: sticky;
52
+ top: 0;
53
+ z-index: 100;
54
+ backdrop-filter: blur(4px);
55
+ }
56
+
57
+ #topBarLogo {
58
+ height: 2rem;
59
+ width: 2rem;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ #topBarTitle {
64
+ font-size: 1.1rem;
65
+ font-weight: 600;
66
+ color: #fff;
67
+ flex: 1;
68
+ }
69
+
70
+ #topBarInfo {
71
+ position: relative;
72
+ }
73
+
74
+ #infoIcon {
75
+ width: 1.25rem;
76
+ height: 1.25rem;
77
+ border: 2px solid var(--fg2);
78
+ border-radius: 50%;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ font-size: 0.75rem;
83
+ font-weight: 800;
84
+ color: var(--fg2);
85
+ cursor: help;
86
+ transition: all 0.2s;
87
+ user-select: none;
88
+ }
89
+
90
+ #infoIcon:hover {
91
+ border-color: var(--accent);
92
+ color: var(--accent);
93
+ }
94
+
95
+ #infoIcon:hover #infoTooltip {
96
+ opacity: 1;
97
+ visibility: visible;
98
+ transform: translateY(0);
99
+ }
100
+
101
+ #infoTooltip {
102
+ position: absolute;
103
+ right: 0;
104
+ top: calc(100% + 0.5rem);
105
+ background: var(--bg2);
106
+ border: 1px solid var(--border);
107
+ border-radius: 0.375rem;
108
+ padding: 0.75rem 1rem;
109
+ font-size: 0.85rem;
110
+ font-weight: 400;
111
+ width: 20rem;
112
+ white-space: normal;
113
+ color: var(--fg);
114
+ line-height: 1.5;
115
+ opacity: 0;
116
+ visibility: hidden;
117
+ transform: translateY(-0.25rem);
118
+ transition: all 0.2s;
119
+ pointer-events: none;
120
+ box-shadow: 0 1px 2px 0 #0007;
121
+ }
122
+
123
+ #infoTooltip a {
124
+ color: var(--fg);
125
+ text-decoration: underline;
126
+ }
127
+
128
+ #infoTooltip::before {
129
+ content: '';
130
+ position: absolute;
131
+ right: 0.75rem;
132
+ top: -0.375rem;
133
+ width: 0.75rem;
134
+ height: 0.75rem;
135
+ background: var(--bg2);
136
+ border-left: 1px solid var(--border);
137
+ border-top: 1px solid var(--border);
138
+ transform: rotate(45deg);
139
+ }
140
+
141
+ #app {
142
+ max-width: 60rem;
143
+ width: 100%;
144
+ margin: 0 auto;
145
+ padding: 2rem 1.5rem;
146
+ }
147
+ </style>
148
+ </head>
149
+
150
+ <body>
151
+ <div id="appContainer">
152
+ <div id="topBar">
153
+ <img src="/favicon.svg" id="topBarLogo" alt="TinyBase" />
154
+ <div id="topBarTitle">
155
+ TinyBase {{#if (eq appType "chat")}}Chat{{else if (eq appType "drawing")}}Drawing{{else if (eq appType "game")}}Game{{else}}Todos{{/if}}
156
+ </div>
157
+ <div id="topBarInfo">
158
+ <div id="infoIcon">
159
+ i
160
+ <div id="infoTooltip">
161
+ {{#if (eq appType "todos")}}
162
+ A simple todo list application demonstrating TinyBase's reactive data management with CRUD operations.
163
+ {{else if (eq appType "chat")}}
164
+ A real-time chat application demonstrating TinyBase's reactive data management.
165
+ {{else if (eq appType "drawing")}}
166
+ A collaborative drawing canvas showcasing TinyBase's state synchronization.
167
+ {{else if (eq appType "game")}}
168
+ A tic-tac-toe game demonstrating turn-based logic and computed game state.
169
+ {{/if}}
170
+ <br><br>
171
+ Built with {{#if typescript}}TypeScript{{#if schemas}} (using typed store schemas){{/if}}{{else}}JavaScript{{/if}}{{#if react}} and React{{/if}}.
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ <div id="app"></div>
177
+ </div>
178
+ {{includeFile template="client/src/index.tsx.hbs" output="client/src/index.{{ext}}"}}
179
+ <script type="module" src="/src/index.{{ext}}"></script>
180
+ </body>
181
+
182
+ </html>
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "{{projectName}}{{#if server}}-client{{/if}}",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ {{#list}}
6
+ {{includeFile template="client/index.html.hbs" output="client/index.html"}}
7
+ "dev": "vite"
8
+ {{#if typescript}}
9
+ "build": "tsc && vite build"
10
+ {{else}}
11
+ "build": "vite build"
12
+ {{/if}}
13
+ "preview": "vite preview"
14
+ {{#if prettier}}
15
+ "format": "prettier --write ."
16
+ "format:check": "prettier --check ."
17
+ {{/if}}
18
+ {{#if eslint}}
19
+ "lint": "eslint ."
20
+ {{/if}}
21
+ {{/list}}
22
+ },
23
+ "devDependencies": {
24
+ {{#list}}
25
+ "vite": "^7.1.3"
26
+ {{#if typescript}}
27
+ "typescript": "^5.9.3"
28
+ "@types/node": "^25.0.3"
29
+ {{#if react}}
30
+ "@types/react": "^19.2.7"
31
+ "@types/react-dom": "^19.2.3"
32
+ {{/if}}
33
+ {{/if}}
34
+ {{#if react}}
35
+ "@vitejs/plugin-react": "^4.3.4"
36
+ {{/if}}
37
+ {{#if prettier}}
38
+ "prettier": "^3.7.4"
39
+ {{/if}}
40
+ {{#if eslint}}
41
+ "eslint": "^9.39.2"
42
+ {{#if typescript}}
43
+ "typescript-eslint": "^8.51.0"
44
+ {{/if}}
45
+ {{#if react}}
46
+ "eslint-plugin-react": "7.37.5"
47
+ "eslint-plugin-react-hooks": "^7.0.1"
48
+ {{/if}}
49
+ {{/if}}
50
+ {{/list}}
51
+ },
52
+ "dependencies": {
53
+ {{#list}}
54
+ "tinybase": "^7.3.1"
55
+ {{#if react}}
56
+ "react": "^19.2.3"
57
+ "react-dom": "^19.2.3"
58
+ {{/if}}
59
+ {{#if sync}}
60
+ "reconnecting-websocket": "^4.4.0"
61
+ {{/if}}
62
+ {{/list}}
63
+ }
64
+ }
65
+ {{#if prettier}}
66
+ {{includeFile template="client/.prettierrc.hbs" output="client/.prettierrc"}}
67
+ {{/if}}
68
+ {{#if eslint}}
69
+ {{includeFile template="client/eslint.config.js.hbs" output="client/eslint.config.js"}}
70
+ {{/if}}
71
+ {{#if react}}
72
+ {{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
73
+ {{/if}}
74
+ {{#if typescript}}
75
+ {{includeFile template="client/tsconfig.json.hbs" output="client/tsconfig.json"}}
76
+ {{/if}}
@@ -0,0 +1,40 @@
1
+ import {StrictMode} from 'react';
2
+
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';
9
+
10
+ {{includeFile template="client/src/chat/SettingsStore.tsx.hbs" output="client/src/SettingsStore.{{ext}}"}}
11
+ import {SettingsStore} from './SettingsStore';
12
+
13
+ {{includeFile template="client/src/chat/ChatStore.tsx.hbs" output="client/src/ChatStore.{{ext}}"}}
14
+ import {ChatStore} from './ChatStore';
15
+
16
+ {{includeFile template="client/src/chat/UsernameInput.tsx.hbs" output="client/src/UsernameInput.{{ext}}"}}
17
+ import {UsernameInput} from './UsernameInput';
18
+
19
+ {{includeFile template="client/src/chat/Messages.tsx.hbs" output="client/src/Messages.{{ext}}"}}
20
+ import {Messages} from './Messages';
21
+
22
+ {{includeFile template="client/src/chat/MessageInput.tsx.hbs" output="client/src/MessageInput.{{ext}}"}}
23
+ import {MessageInput} from './MessageInput';
24
+
25
+ const App = () => {
26
+ return (
27
+ <StrictMode>
28
+ <Provider>
29
+ <SettingsStore />
30
+ <ChatStore />
31
+ <UsernameInput />
32
+ <Messages />
33
+ <MessageInput />
34
+ <Inspector />
35
+ </Provider>
36
+ </StrictMode>
37
+ );
38
+ };
39
+
40
+ export {App};
@@ -0,0 +1,70 @@
1
+ {{#if schemas}}
2
+ import {createMergeableStore} from 'tinybase/with-schemas';
3
+ import * as UiReact from 'tinybase/ui-react/with-schemas';
4
+ import {type NoValuesSchema} from 'tinybase/with-schemas';
5
+ {{else}}
6
+ import {createMergeableStore} from 'tinybase';
7
+ import {useCreateStore, useProvideStore, useAddRowCallback, useRow, useRowIds, useStore} from 'tinybase/ui-react';
8
+ {{/if}}
9
+
10
+ export const STORE_ID = 'chat';
11
+
12
+ {{#if schemas}}
13
+ const TABLES_SCHEMA = {
14
+ messages: {
15
+ username: {type: 'string'},
16
+ text: {type: 'string'},
17
+ timestamp: {type: 'number'},
18
+ },
19
+ } as const;
20
+
21
+ type Schemas = [typeof TABLES_SCHEMA, NoValuesSchema];
22
+
23
+ const {useCreateStore, useProvideStore, useAddRowCallback, useRow, useRowIds, useStore} = UiReact as UiReact.WithSchemas<Schemas>;
24
+ {{/if}}
25
+
26
+ export {useAddRowCallback, useRow, useRowIds, useStore};
27
+
28
+ export const ChatStore = () => {
29
+ const store = useCreateStore(() =>
30
+ createMergeableStore(STORE_ID){{#if schemas}}
31
+ .setTablesSchema(TABLES_SCHEMA){{/if}}
32
+ .setDefaultContent([
33
+ {
34
+ messages: {
35
+ '1': {username: 'Alice', text: 'Hello!', timestamp: Date.now() - 60000},
36
+ '2': {username: 'Bob', text: 'Hi there!', timestamp: Date.now() - 30000},
37
+ },
38
+ },
39
+ {},
40
+ ]),
41
+ );
42
+
43
+ useProvideStore(STORE_ID, store);
44
+
45
+ {{#if sync}}
46
+ {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
47
+ {{addImport "import {SERVER} from './config';"}}
48
+ {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
49
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
50
+ {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
51
+ {{addImport "import type {MergeableStore} from 'tinybase';"}}
52
+
53
+ useCreateSynchronizer(store, async (store: MergeableStore) => {
54
+ const serverPathId = location.pathname;
55
+ const synchronizer = await createWsSynchronizer(
56
+ store,
57
+ new ReconnectingWebSocket(SERVER + serverPathId),
58
+ );
59
+ await synchronizer.startSync();
60
+
61
+ synchronizer.getWebSocket().addEventListener('open', () => {
62
+ synchronizer.load().then(() => synchronizer.save());
63
+ });
64
+
65
+ return synchronizer;
66
+ });
67
+ {{/if}}
68
+
69
+ return null;
70
+ };