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.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/cli.js +2 -2
- package/package.json +6 -3
- package/screenshots/chat.png +0 -0
- package/screenshots/drawing.png +0 -0
- package/screenshots/game.png +0 -0
- package/screenshots/todos.png +0 -0
- package/templates/README.md.hbs +1 -0
- package/templates/{eslint.config.js.hbs → client/eslint.config.js.hbs} +5 -0
- package/templates/client/index.html.hbs +182 -0
- package/templates/client/package.json.hbs +76 -0
- package/templates/client/src/chat/App.tsx.hbs +40 -0
- package/templates/client/src/chat/ChatStore.tsx.hbs +70 -0
- package/templates/client/src/chat/Message.tsx.hbs +21 -0
- package/templates/client/src/chat/MessageInput.tsx.hbs +42 -0
- package/templates/client/src/chat/Messages.tsx.hbs +29 -0
- package/templates/client/src/chat/SettingsStore.tsx.hbs +34 -0
- package/templates/client/src/chat/UsernameInput.tsx.hbs +22 -0
- package/templates/client/src/chat/app.ts.hbs +39 -0
- package/templates/client/src/chat/chatStore.ts.hbs +50 -0
- package/templates/client/src/chat/message.css.hbs +20 -0
- package/templates/client/src/chat/message.ts.hbs +27 -0
- package/templates/client/src/chat/messageInput.css.hbs +6 -0
- package/templates/client/src/chat/messageInput.ts.hbs +42 -0
- package/templates/client/src/chat/messages.css.hbs +6 -0
- package/templates/client/src/chat/messages.ts.hbs +33 -0
- package/templates/client/src/chat/settingsStore.ts.hbs +19 -0
- package/templates/client/src/chat/usernameInput.css.hbs +14 -0
- package/templates/client/src/chat/usernameInput.ts.hbs +30 -0
- package/templates/client/src/drawing/App.tsx.hbs +36 -0
- package/templates/client/src/drawing/BrushSize.tsx.hbs +22 -0
- package/templates/client/src/drawing/Canvas.tsx.hbs +100 -0
- package/templates/client/src/drawing/CanvasStore.tsx.hbs +62 -0
- package/templates/client/src/drawing/ColorPicker.tsx.hbs +24 -0
- package/templates/client/src/drawing/DrawingControls.tsx.hbs +24 -0
- package/templates/client/src/drawing/SettingsStore.tsx.hbs +36 -0
- package/templates/client/src/drawing/app.ts.hbs +20 -0
- package/templates/client/src/drawing/brushSize.css.hbs +21 -0
- package/templates/client/src/drawing/brushSize.ts.hbs +33 -0
- package/templates/client/src/drawing/canvas.css.hbs +8 -0
- package/templates/client/src/drawing/canvas.ts.hbs +103 -0
- package/templates/client/src/drawing/canvasStore.ts.hbs +42 -0
- package/templates/client/src/drawing/colorPicker.css.hbs +21 -0
- package/templates/client/src/drawing/colorPicker.ts.hbs +34 -0
- package/templates/client/src/drawing/drawingControls.css.hbs +12 -0
- package/templates/client/src/drawing/drawingControls.ts.hbs +26 -0
- package/templates/client/src/drawing/settingsStore.ts.hbs +21 -0
- package/templates/client/src/game/App.tsx.hbs +28 -0
- package/templates/client/src/game/Board.tsx.hbs +27 -0
- package/templates/client/src/game/Game.tsx.hbs +78 -0
- package/templates/client/src/game/GameStatus.tsx.hbs +21 -0
- package/templates/client/src/game/Square.tsx.hbs +23 -0
- package/templates/client/src/game/Store.tsx.hbs +67 -0
- package/templates/client/src/game/app.ts.hbs +12 -0
- package/templates/client/src/game/board.css.hbs +13 -0
- package/templates/client/src/game/board.ts.hbs +39 -0
- package/templates/client/src/game/game.ts.hbs +74 -0
- package/templates/client/src/game/gameStatus.css.hbs +21 -0
- package/templates/client/src/game/gameStatus.ts.hbs +27 -0
- package/templates/client/src/game/square.css.hbs +38 -0
- package/templates/client/src/game/square.ts.hbs +11 -0
- package/templates/client/src/game/store.ts.hbs +47 -0
- package/templates/client/src/index.tsx.hbs +24 -0
- package/templates/client/src/shared/Button.tsx.hbs +16 -0
- package/templates/client/src/shared/Input.tsx.hbs +16 -0
- package/templates/client/src/shared/button.css.hbs +25 -0
- package/templates/client/src/shared/button.ts.hbs +16 -0
- package/templates/client/src/shared/config.ts.hbs +9 -0
- package/templates/client/src/shared/input.css.hbs +22 -0
- package/templates/client/src/shared/input.ts.hbs +17 -0
- package/templates/client/src/todos/App.tsx.hbs +32 -0
- package/templates/client/src/todos/Store.tsx.hbs +70 -0
- package/templates/client/src/todos/TodoInput.tsx.hbs +30 -0
- package/templates/client/src/todos/TodoItem.tsx.hbs +20 -0
- package/templates/client/src/todos/TodoList.tsx.hbs +18 -0
- package/templates/client/src/todos/app.ts.hbs +23 -0
- package/templates/client/src/todos/store.ts.hbs +49 -0
- package/templates/client/src/todos/todoInput.css.hbs +9 -0
- package/templates/client/src/todos/todoInput.ts.hbs +38 -0
- package/templates/client/src/todos/todoItem.css.hbs +33 -0
- package/templates/client/src/todos/todoItem.ts.hbs +28 -0
- package/templates/client/src/todos/todoList.css.hbs +14 -0
- package/templates/client/src/todos/todoList.ts.hbs +38 -0
- package/templates/package.json.hbs +21 -56
- package/templates/server/index-do.ts.hbs +22 -0
- package/templates/server/index-node.ts.hbs +8 -0
- package/templates/server/package.json.hbs +51 -0
- package/templates/server/tsconfig.json.hbs +13 -0
- package/templates/server/wrangler.toml.hbs +12 -0
- package/templates/index.html.hbs +0 -55
- package/templates/src/App.tsx.hbs +0 -52
- package/templates/src/Buttons.tsx.hbs +0 -26
- package/templates/src/app.ts.hbs +0 -36
- package/templates/src/index.css.hbs +0 -138
- package/templates/src/index.tsx.hbs +0 -18
- /package/templates/{.prettierrc.hbs → client/.prettierrc.hbs} +0 -0
- /package/templates/{public → client/public}/favicon.svg +0 -0
- /package/templates/{tsconfig.json.hbs → client/tsconfig.json.hbs} +0 -0
- /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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
|
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=
|
|
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
|
|
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.
|
|
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
|
package/templates/README.md.hbs
CHANGED
|
@@ -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
|
+
};
|