create-tinybase 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -18
- package/cli.js +2 -2
- package/package.json +3 -3
- package/screenshots/chat.png +0 -0
- package/screenshots/todos.png +0 -0
- package/templates/README.md.hbs +13 -12
- package/templates/client/eslint.config.js.hbs +7 -0
- package/templates/client/package.json.hbs +28 -8
- package/templates/client/src/chat/App.tsx.hbs +28 -22
- package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
- package/templates/client/src/chat/Message.tsx.hbs +8 -9
- package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
- package/templates/client/src/chat/Messages.tsx.hbs +5 -15
- package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
- package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
- package/templates/client/src/chat/app.ts.hbs +18 -18
- package/templates/client/src/chat/chatStore.ts.hbs +97 -35
- package/templates/client/src/chat/message.ts.hbs +4 -3
- package/templates/client/src/chat/messageInput.css.hbs +1 -1
- package/templates/client/src/chat/messageInput.ts.hbs +7 -8
- package/templates/client/src/chat/messages.ts.hbs +7 -12
- package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
- package/templates/client/src/chat/usernameInput.css.hbs +1 -1
- package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
- package/templates/client/src/chat/utils.ts.hbs +26 -0
- package/templates/client/src/drawing/App.tsx.hbs +26 -20
- package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
- package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
- package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
- package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
- package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
- package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
- package/templates/client/src/drawing/app.ts.hbs +18 -8
- package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
- package/templates/client/src/drawing/canvas.ts.hbs +84 -86
- package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
- package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
- package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
- package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
- package/templates/client/src/game/App.tsx.hbs +20 -16
- package/templates/client/src/game/Board.tsx.hbs +8 -8
- package/templates/client/src/game/Game.tsx.hbs +14 -21
- package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
- package/templates/client/src/game/Square.tsx.hbs +6 -11
- package/templates/client/src/game/Store.tsx.hbs +106 -16
- package/templates/client/src/game/app.ts.hbs +17 -6
- package/templates/client/src/game/board.ts.hbs +7 -7
- package/templates/client/src/game/game.ts.hbs +12 -18
- package/templates/client/src/game/gameStatus.ts.hbs +3 -3
- package/templates/client/src/game/square.ts.hbs +4 -4
- package/templates/client/src/game/store.ts.hbs +95 -23
- package/templates/client/src/index.tsx.hbs +5 -7
- package/templates/client/src/shared/Button.tsx.hbs +3 -3
- package/templates/client/src/shared/Input.tsx.hbs +2 -2
- package/templates/client/src/shared/Loading.tsx.hbs +5 -0
- package/templates/client/src/shared/button.ts.hbs +2 -2
- package/templates/client/src/shared/config.ts.hbs +4 -6
- package/templates/client/src/shared/input.ts.hbs +2 -2
- package/templates/client/src/shared/loading.css.hbs +21 -0
- package/templates/client/src/shared/loading.ts.hbs +13 -0
- package/templates/client/src/shared/pglite.ts.hbs +10 -0
- package/templates/client/src/shared/sqlite.ts.hbs +17 -0
- package/templates/client/src/todos/App.tsx.hbs +22 -22
- package/templates/client/src/todos/Store.tsx.hbs +106 -23
- package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
- package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
- package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
- package/templates/client/src/todos/app.ts.hbs +16 -10
- package/templates/client/src/todos/store.ts.hbs +94 -30
- package/templates/client/src/todos/todoInput.ts.hbs +5 -8
- package/templates/client/src/todos/todoItem.ts.hbs +3 -4
- package/templates/client/src/todos/todoList.ts.hbs +6 -8
- package/templates/client/vite-env.d.ts.hbs +4 -0
- package/templates/client/vite.config.js.hbs +53 -3
- package/templates/server/index-do.ts.hbs +10 -14
- package/templates/server/index-node.ts.hbs +3 -3
- package/templates/server/package.json.hbs +5 -5
- package/templates/server/wrangler.toml.hbs +1 -1
- /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ This will prompt you with questions to configure your new TinyBase app:
|
|
|
22
22
|
- **Framework** - React or Vanilla JS
|
|
23
23
|
- **App type** - Todo app, Chat app, Drawing app, or Tic-tac-toe game
|
|
24
24
|
- **Store schemas** - TypeScript type safety for stores (TypeScript only)
|
|
25
|
-
- **Synchronization** -
|
|
26
|
-
- **
|
|
25
|
+
- **Synchronization** - None, remote demo server, local Node server, or local Durable Objects server
|
|
26
|
+
- **Persistence** - None, Local Storage, SQLite, or PGLite
|
|
27
27
|
- **Prettier** - Include Prettier for code formatting
|
|
28
28
|
- **ESLint** - Include ESLint for code linting
|
|
29
29
|
|
|
@@ -146,44 +146,88 @@ Schemas define:
|
|
|
146
146
|
|
|
147
147
|
### Synchronization
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
Choose from four synchronization options:
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
- WebSocket-based synchronization
|
|
153
|
-
- Connects to demo server by default (wss://vite.tinybase.org)
|
|
154
|
-
- MergeableStore for conflict-free replication
|
|
155
|
-
- Automatic reconnection handling
|
|
156
|
-
|
|
157
|
-
**Disabled**:
|
|
151
|
+
**None**:
|
|
158
152
|
|
|
159
153
|
- Local-only data storage
|
|
160
154
|
- No network dependencies
|
|
161
155
|
- Simpler architecture
|
|
162
156
|
- Still uses MergeableStore for consistency
|
|
163
157
|
|
|
164
|
-
|
|
158
|
+
**Via remote demo server (stateless)** (default):
|
|
165
159
|
|
|
166
|
-
|
|
160
|
+
- Real-time sync between browser tabs
|
|
161
|
+
- WebSocket-based synchronization
|
|
162
|
+
- Connects to demo server (wss://vite.tinybase.org)
|
|
163
|
+
- MergeableStore for conflict-free replication
|
|
164
|
+
- Automatic reconnection handling
|
|
165
|
+
- Great for prototyping
|
|
166
|
+
- No local server management needed
|
|
167
167
|
|
|
168
|
-
**
|
|
168
|
+
**Via local node server (stateless)** (port 8043):
|
|
169
169
|
|
|
170
|
+
- Real-time sync with local Node.js server
|
|
170
171
|
- WebSocket server using `ws` library
|
|
171
172
|
- TinyBase server synchronizer
|
|
172
173
|
- Runs with `npm run dev` in server directory
|
|
174
|
+
- Stateless - no data persistence
|
|
173
175
|
- Easy to deploy to any Node.js host
|
|
176
|
+
- Full control over server code
|
|
174
177
|
|
|
175
|
-
**
|
|
178
|
+
**Via local DurableObjects server (stateful)** (port 8787):
|
|
176
179
|
|
|
180
|
+
- Real-time sync with Cloudflare Durable Objects
|
|
177
181
|
- Serverless WebSocket server
|
|
178
182
|
- Edge computing with Durable Objects
|
|
183
|
+
- Stateful - data persists in Durable Objects storage
|
|
179
184
|
- Global distribution
|
|
180
185
|
- Runs locally with Wrangler
|
|
186
|
+
- Designed for production deployment
|
|
181
187
|
|
|
182
|
-
|
|
188
|
+
### Persistence
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
Choose how to persist store data on the client:
|
|
191
|
+
|
|
192
|
+
**None**:
|
|
193
|
+
|
|
194
|
+
- No client-side persistence
|
|
195
|
+
- Data lost on page refresh
|
|
196
|
+
- Simplest option for demos
|
|
197
|
+
- Still works with synchronization
|
|
198
|
+
|
|
199
|
+
**Local Storage** (default):
|
|
200
|
+
|
|
201
|
+
- Browser localStorage persistence
|
|
202
|
+
- Data persists across sessions
|
|
203
|
+
- Simple and widely supported
|
|
204
|
+
- Automatic load/save with `createLocalPersister`
|
|
205
|
+
- Best for most use cases
|
|
206
|
+
|
|
207
|
+
**SQLite**:
|
|
208
|
+
|
|
209
|
+
- Browser-based SQLite via WebAssembly
|
|
210
|
+
- Structured database storage
|
|
211
|
+
- Uses `@sqlite.org/sqlite-wasm` package
|
|
212
|
+
- IndexedDB-backed (`:local:` prefix)
|
|
213
|
+
- Good for complex data structures
|
|
214
|
+
- Slightly larger bundle size
|
|
215
|
+
|
|
216
|
+
**PGLite**:
|
|
217
|
+
|
|
218
|
+
- PostgreSQL in the browser
|
|
219
|
+
- Full PostgreSQL compatibility
|
|
220
|
+
- Uses `@electric-sql/pglite` package
|
|
221
|
+
- IndexedDB-backed (`idb://` prefix)
|
|
222
|
+
- Advanced SQL features
|
|
223
|
+
- Larger bundle size
|
|
224
|
+
|
|
225
|
+
**Important Notes:**
|
|
226
|
+
|
|
227
|
+
- Persistence is configured **before** synchronization to avoid race conditions
|
|
228
|
+
- Works with all sync types (or no sync)
|
|
229
|
+
- In multi-store apps, all stores get persistence
|
|
230
|
+
- Data is automatically loaded on app start and saved on changes
|
|
187
231
|
|
|
188
232
|
### Prettier & ESLint
|
|
189
233
|
|
package/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{existsSync as
|
|
3
|
-
`,questions:[{type:"text",name:"projectName",message:"Project name:",initial:"my-tinybase-app",validate:e=>{if(e.length===0)return"Project name is required";const t=u(process.cwd(),e);return
|
|
2
|
+
import{existsSync as w}from"fs";import{dirname as f,join as u}from"path";import{createCLI as k,detectPackageManager as x}from"tinycreate";import{fileURLToPath as R}from"url";const S=f(R(import.meta.url)),P={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 w(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",T=i?c?"tsx":"ts":c?"jsx":"js",a=d||"remote",h=a!=="none",b=a==="node"||a==="durable-objects",j=a==="durable-objects"?"durable-objects":"node",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:h,server:b,serverType:j,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:T}},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(S,"templates"),installCommand:"{pm} install",devCommand:"{pm} run dev",workingDirectory:"client",onSuccess:(e,t)=>{const s=t.syncType,n=s==="node"||s==="durable-objects",o=x();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`)}};k(P).catch(e=>{console.error(e),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-tinybase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "jamesgpearce",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"homepage": "https://tinybase.org",
|
|
11
|
-
"description": "The CLI to build
|
|
11
|
+
"description": "The CLI to build new apps using TinyBase, a reactive data store and sync engine.",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"tiny",
|
|
14
14
|
"sync engine",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"create-tinybase": "cli.js"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"tinycreate": "^1.0.
|
|
26
|
+
"tinycreate": "^1.0.4"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=18.0.0"
|
package/screenshots/chat.png
CHANGED
|
Binary file
|
package/screenshots/todos.png
CHANGED
|
Binary file
|
package/templates/README.md.hbs
CHANGED
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
# {{projectName}}
|
|
7
7
|
|
|
8
|
-
A TinyBase app built with {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
|
|
9
|
-
and {{#if react}}React{{else}}Vanilla JS{{/if}}.
|
|
8
|
+
A TinyBase app built with {{#if typescript}}TypeScript{{else}}JavaScript{{/if}} and {{#if react}}React{{else}}Vanilla JS{{/if}}.
|
|
10
9
|
|
|
11
10
|
## Getting Started
|
|
12
11
|
|
|
13
|
-
Alongside this file, you will see a directory called `client` (and optionally
|
|
14
|
-
one called `server` if
|
|
12
|
+
Alongside this file, you will see a directory called `client`{{#if server}} (and optionally
|
|
13
|
+
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,16 +23,18 @@ npm run dev
|
|
|
24
23
|
PNPM, Yarn, and Bun should also work. Your app will be available at
|
|
25
24
|
`http://localhost:5173`, or whichever port Vite specifies in the console.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
{{#if server}}
|
|
27
|
+
If you included server code, start the server in a separate terminal, ideally
|
|
28
|
+
before starting the client:
|
|
29
29
|
|
|
30
|
-
```bash
|
|
31
|
-
# In a separate terminal
|
|
32
|
-
cd server
|
|
33
|
-
npm install
|
|
34
|
-
npm run dev
|
|
35
|
-
```
|
|
30
|
+
```bash
|
|
31
|
+
# In a separate terminal
|
|
32
|
+
cd server
|
|
33
|
+
npm install
|
|
34
|
+
npm run dev
|
|
35
|
+
```
|
|
36
36
|
|
|
37
|
+
{{/if}}
|
|
37
38
|
After that, start hacking!
|
|
38
39
|
|
|
39
40
|
## Learn More
|
|
@@ -5,13 +5,20 @@
|
|
|
5
5
|
{{addImport "import pluginReact from 'eslint-plugin-react';"}}
|
|
6
6
|
{{addImport "import pluginReactHooks from 'eslint-plugin-react-hooks';"}}
|
|
7
7
|
{{/if}}
|
|
8
|
+
{{addImport "import globals from 'globals';"}}
|
|
8
9
|
|
|
9
10
|
export default [
|
|
11
|
+
{
|
|
12
|
+
languageOptions: {
|
|
13
|
+
globals: globals.browser,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
10
16
|
{{#if typescript}}
|
|
11
17
|
...tseslint.configs.recommended,
|
|
12
18
|
{
|
|
13
19
|
rules: {
|
|
14
20
|
'@typescript-eslint/no-unused-vars': 'warn',
|
|
21
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
15
22
|
},
|
|
16
23
|
},
|
|
17
24
|
{{else}}
|
|
@@ -22,25 +22,31 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
{{#list}}
|
|
25
|
-
"vite": "^7.1
|
|
25
|
+
"vite": "^7.3.1"
|
|
26
26
|
{{#if typescript}}
|
|
27
27
|
"typescript": "^5.9.3"
|
|
28
|
-
"@types/node": "^25.0.
|
|
28
|
+
"@types/node": "^25.0.9"
|
|
29
29
|
{{#if react}}
|
|
30
|
-
"@types/react": "^19.2.
|
|
30
|
+
"@types/react": "^19.2.8"
|
|
31
31
|
"@types/react-dom": "^19.2.3"
|
|
32
32
|
{{/if}}
|
|
33
33
|
{{/if}}
|
|
34
34
|
{{#if react}}
|
|
35
|
-
"@vitejs/plugin-react": "^
|
|
35
|
+
"@vitejs/plugin-react": "^5.1.2"
|
|
36
|
+
{{/if}}
|
|
37
|
+
{{#if persistSqlite}}
|
|
38
|
+
"vite-plugin-static-copy": "^3.2.0"
|
|
39
|
+
{{/if}}
|
|
40
|
+
{{#if persistPglite}}
|
|
41
|
+
"vite-plugin-static-copy": "^3.2.0"
|
|
36
42
|
{{/if}}
|
|
37
43
|
{{#if prettier}}
|
|
38
|
-
"prettier": "^3.
|
|
44
|
+
"prettier": "^3.8.0"
|
|
39
45
|
{{/if}}
|
|
40
46
|
{{#if eslint}}
|
|
41
47
|
"eslint": "^9.39.2"
|
|
42
48
|
{{#if typescript}}
|
|
43
|
-
"typescript-eslint": "^8.
|
|
49
|
+
"typescript-eslint": "^8.53.1"
|
|
44
50
|
{{/if}}
|
|
45
51
|
{{#if react}}
|
|
46
52
|
"eslint-plugin-react": "7.37.5"
|
|
@@ -51,7 +57,7 @@
|
|
|
51
57
|
},
|
|
52
58
|
"dependencies": {
|
|
53
59
|
{{#list}}
|
|
54
|
-
"tinybase": "^7.3.
|
|
60
|
+
"tinybase": "^7.3.2"
|
|
55
61
|
{{#if react}}
|
|
56
62
|
"react": "^19.2.3"
|
|
57
63
|
"react-dom": "^19.2.3"
|
|
@@ -59,18 +65,32 @@
|
|
|
59
65
|
{{#if sync}}
|
|
60
66
|
"reconnecting-websocket": "^4.4.0"
|
|
61
67
|
{{/if}}
|
|
68
|
+
{{#if persistSqlite}}
|
|
69
|
+
"@sqlite.org/sqlite-wasm": "^3.50.4-build1"
|
|
70
|
+
{{/if}}
|
|
71
|
+
{{#if persistPglite}}
|
|
72
|
+
"@electric-sql/pglite": "^0.3.15"
|
|
73
|
+
{{/if}}
|
|
62
74
|
{{/list}}
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
{{#if prettier}}
|
|
66
|
-
{{includeFile template="client/.prettierrc.hbs" output="client/.prettierrc"}}
|
|
78
|
+
{{includeFile template="client/.prettierrc.json.hbs" output="client/.prettierrc.json"}}
|
|
67
79
|
{{/if}}
|
|
68
80
|
{{#if eslint}}
|
|
69
81
|
{{includeFile template="client/eslint.config.js.hbs" output="client/eslint.config.js"}}
|
|
70
82
|
{{/if}}
|
|
71
83
|
{{#if react}}
|
|
72
84
|
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
85
|
+
{{else}}
|
|
86
|
+
{{#if persistSqlite}}
|
|
87
|
+
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
88
|
+
{{/if}}
|
|
89
|
+
{{#if persistPglite}}
|
|
90
|
+
{{includeFile template="client/vite.config.js.hbs" output="client/vite.config.js"}}
|
|
91
|
+
{{/if}}
|
|
73
92
|
{{/if}}
|
|
74
93
|
{{#if typescript}}
|
|
75
94
|
{{includeFile template="client/tsconfig.json.hbs" output="client/tsconfig.json"}}
|
|
95
|
+
{{includeFile template="client/vite-env.d.ts.hbs" output="client/src/vite-env.d.ts"}}
|
|
76
96
|
{{/if}}
|
|
@@ -1,40 +1,46 @@
|
|
|
1
|
-
import {StrictMode} from 'react';
|
|
1
|
+
{{addImport "import {StrictMode, useState} from 'react';"}}
|
|
2
|
+
{{includeFile template="client/src/shared/Loading.tsx.hbs" output="client/src/Loading.{{ext}}"}}
|
|
3
|
+
{{addImport "import {Loading} from './Loading';"}}
|
|
2
4
|
|
|
3
|
-
{{
|
|
4
|
-
|
|
5
|
-
{{else}}
|
|
6
|
-
import {Provider} from 'tinybase/ui-react';
|
|
7
|
-
{{/if}}
|
|
8
|
-
import {Inspector} from 'tinybase/ui-react-inspector';
|
|
5
|
+
{{addImport "import {Provider} from 'tinybase/ui-react';"}}
|
|
6
|
+
{{addImport "import {Inspector} from 'tinybase/ui-react-inspector';"}}
|
|
9
7
|
|
|
10
8
|
{{includeFile template="client/src/chat/SettingsStore.tsx.hbs" output="client/src/SettingsStore.{{ext}}"}}
|
|
11
|
-
import {SettingsStore} from './SettingsStore';
|
|
9
|
+
{{addImport "import {SettingsStore} from './SettingsStore';"}}
|
|
12
10
|
|
|
13
11
|
{{includeFile template="client/src/chat/ChatStore.tsx.hbs" output="client/src/ChatStore.{{ext}}"}}
|
|
14
|
-
import {ChatStore} from './ChatStore';
|
|
12
|
+
{{addImport "import {ChatStore} from './ChatStore';"}}
|
|
15
13
|
|
|
16
14
|
{{includeFile template="client/src/chat/UsernameInput.tsx.hbs" output="client/src/UsernameInput.{{ext}}"}}
|
|
17
|
-
import {UsernameInput} from './UsernameInput';
|
|
15
|
+
{{addImport "import {UsernameInput} from './UsernameInput';"}}
|
|
18
16
|
|
|
19
17
|
{{includeFile template="client/src/chat/Messages.tsx.hbs" output="client/src/Messages.{{ext}}"}}
|
|
20
|
-
import {Messages} from './Messages';
|
|
18
|
+
{{addImport "import {Messages} from './Messages';"}}
|
|
21
19
|
|
|
22
20
|
{{includeFile template="client/src/chat/MessageInput.tsx.hbs" output="client/src/MessageInput.{{ext}}"}}
|
|
23
|
-
import {MessageInput} from './MessageInput';
|
|
21
|
+
{{addImport "import {MessageInput} from './MessageInput';"}}
|
|
22
|
+
|
|
23
|
+
export const App = () => {
|
|
24
|
+
const [settingsReady, setSettingsReady] = useState(false);
|
|
25
|
+
const [chatReady, setChatReady] = useState(false);
|
|
26
|
+
const loading = !settingsReady || !chatReady;
|
|
24
27
|
|
|
25
|
-
const App = () => {
|
|
26
28
|
return (
|
|
27
29
|
<StrictMode>
|
|
28
30
|
<Provider>
|
|
29
|
-
<SettingsStore />
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
<SettingsStore onReady={()=> setSettingsReady(true)} />
|
|
32
|
+
<ChatStore onReady={()=> setChatReady(true)} />
|
|
33
|
+
{loading ? (
|
|
34
|
+
<Loading />
|
|
35
|
+
) : (
|
|
36
|
+
<>
|
|
37
|
+
<UsernameInput />
|
|
38
|
+
<Messages />
|
|
39
|
+
<MessageInput />
|
|
40
|
+
<Inspector />
|
|
41
|
+
</>
|
|
42
|
+
)}
|
|
35
43
|
</Provider>
|
|
36
44
|
</StrictMode>
|
|
37
45
|
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export {App};
|
|
46
|
+
};
|
|
@@ -1,56 +1,130 @@
|
|
|
1
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';
|
|
2
|
+
{{addImport "import {createMergeableStore, type Row} from 'tinybase/with-schemas';"}}
|
|
3
|
+
{{addImport "import * as UiReact from 'tinybase/ui-react/with-schemas';"}}
|
|
4
|
+
{{addImport "import {type NoValuesSchema} from 'tinybase/with-schemas';"}}
|
|
5
5
|
{{else}}
|
|
6
|
-
import {createMergeableStore} from 'tinybase';
|
|
7
|
-
import {
|
|
6
|
+
{{addImport "import {createMergeableStore} from 'tinybase';"}}
|
|
7
|
+
{{addImport "import {useCreateMergeableStore, useProvideStore, useAddRowCallback, useRow, useSortedRowIds, useStore} from 'tinybase/ui-react';"}}
|
|
8
8
|
{{/if}}
|
|
9
9
|
|
|
10
|
+
{{includeFile template="client/src/chat/utils.ts.hbs" output="client/src/utils.{{ext}}"}}
|
|
11
|
+
{{addImport "import {getDefaultContent} from './utils';"}}
|
|
12
|
+
|
|
10
13
|
export const STORE_ID = 'chat';
|
|
11
14
|
|
|
12
15
|
{{#if schemas}}
|
|
13
16
|
const TABLES_SCHEMA = {
|
|
14
17
|
messages: {
|
|
15
|
-
username: {type: 'string'},
|
|
16
|
-
text: {type: 'string'},
|
|
17
|
-
timestamp: {type: 'number'},
|
|
18
|
+
username: {type: 'string', default: ''},
|
|
19
|
+
text: {type: 'string', default: ''},
|
|
20
|
+
timestamp: {type: 'number', default: 0},
|
|
18
21
|
},
|
|
19
22
|
} as const;
|
|
20
23
|
|
|
21
24
|
type Schemas = [typeof TABLES_SCHEMA, NoValuesSchema];
|
|
22
25
|
|
|
23
|
-
const {
|
|
26
|
+
const {useAddRowCallback, useCreateMergeableStore, {{#if persist}}useCreatePersister, {{/if}}useProvideStore, useRow, useSortedRowIds, useStore} = UiReact as UiReact.WithSchemas<Schemas>;
|
|
24
27
|
{{/if}}
|
|
25
28
|
|
|
26
|
-
export {
|
|
29
|
+
export type MessageRow = {{#if schemas}}Row<typeof TABLES_SCHEMA, 'messages'>{{else}}{username: string; text: string; timestamp: number}{{/if}};
|
|
30
|
+
|
|
31
|
+
export {useAddRowCallback, useRow, useSortedRowIds, useStore};
|
|
27
32
|
|
|
28
|
-
export const ChatStore = () => {
|
|
29
|
-
const store =
|
|
30
|
-
createMergeableStore(
|
|
33
|
+
export const ChatStore = ({onReady}: {onReady?: () => void}) => {
|
|
34
|
+
const store = useCreateMergeableStore(() =>
|
|
35
|
+
createMergeableStore(){{#if schemas}}
|
|
31
36
|
.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
|
-
]),
|
|
37
|
+
.setDefaultContent(getDefaultContent()),
|
|
41
38
|
);
|
|
42
39
|
|
|
43
40
|
useProvideStore(STORE_ID, store);
|
|
44
41
|
|
|
42
|
+
{{#if persist}}
|
|
43
|
+
{{#if persistLocalStorage}}
|
|
44
|
+
{{#if schemas}}
|
|
45
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
|
|
46
|
+
{{else}}
|
|
47
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
|
|
48
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
49
|
+
{{/if}}
|
|
50
|
+
|
|
51
|
+
useCreatePersister(
|
|
52
|
+
store,
|
|
53
|
+
(store) => createLocalPersister(store, STORE_ID),
|
|
54
|
+
[],
|
|
55
|
+
async (persister) => {
|
|
56
|
+
await persister.load();
|
|
57
|
+
await persister.startAutoSave();
|
|
58
|
+
onReady?.();
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{#if persistSqlite}}
|
|
63
|
+
{{#if schemas}}
|
|
64
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
|
|
65
|
+
{{else}}
|
|
66
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
|
|
67
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
68
|
+
{{/if}}
|
|
69
|
+
{{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.{{ext}}"}}
|
|
70
|
+
{{addImport "import {getDb} from './sqlite';"}}
|
|
71
|
+
|
|
72
|
+
useCreatePersister(
|
|
73
|
+
store,
|
|
74
|
+
async (store) => {
|
|
75
|
+
const {sqlite3, db} = await getDb();
|
|
76
|
+
return createSqliteWasmPersister(store, sqlite3, db, STORE_ID);
|
|
77
|
+
},
|
|
78
|
+
[],
|
|
79
|
+
async (persister) => {
|
|
80
|
+
await persister.load();
|
|
81
|
+
await persister.startAutoSave();
|
|
82
|
+
onReady?.();
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
{{/if}}
|
|
86
|
+
{{#if persistPglite}}
|
|
87
|
+
{{#if schemas}}
|
|
88
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
|
|
89
|
+
{{else}}
|
|
90
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
|
|
91
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
92
|
+
{{/if}}
|
|
93
|
+
{{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
|
|
94
|
+
{{addImport "import {getPgLite} from './pglite';"}}
|
|
95
|
+
|
|
96
|
+
useCreatePersister(
|
|
97
|
+
store,
|
|
98
|
+
async (store) => {
|
|
99
|
+
const pgLite = await getPgLite();
|
|
100
|
+
return await createPglitePersister(store, pgLite, 'chat');
|
|
101
|
+
},
|
|
102
|
+
[],
|
|
103
|
+
async (persister) => {
|
|
104
|
+
await persister.load();
|
|
105
|
+
await persister.startAutoSave();
|
|
106
|
+
onReady?.();
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
{{/if}}
|
|
110
|
+
{{/if}}
|
|
111
|
+
|
|
45
112
|
{{#if sync}}
|
|
46
113
|
{{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
|
|
47
114
|
{{addImport "import {SERVER} from './config';"}}
|
|
48
115
|
{{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
|
|
49
|
-
{{
|
|
50
|
-
|
|
51
|
-
{{
|
|
116
|
+
{{#if schemas}}
|
|
117
|
+
{{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
|
|
118
|
+
{{else}}
|
|
119
|
+
{{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
|
|
120
|
+
{{/if}}
|
|
121
|
+
{{#if schemas}}
|
|
122
|
+
{{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react/with-schemas';"}}
|
|
123
|
+
{{else}}
|
|
124
|
+
{{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
|
|
125
|
+
{{/if}}
|
|
52
126
|
|
|
53
|
-
useCreateSynchronizer(store, async (store
|
|
127
|
+
useCreateSynchronizer(store, async (store) => {
|
|
54
128
|
const serverPathId = location.pathname;
|
|
55
129
|
const synchronizer = await createWsSynchronizer(
|
|
56
130
|
store,
|
|
@@ -59,12 +133,19 @@ useProvideStore(STORE_ID, store);
|
|
|
59
133
|
await synchronizer.startSync();
|
|
60
134
|
|
|
61
135
|
synchronizer.getWebSocket().addEventListener('open', () => {
|
|
62
|
-
synchronizer.load().then(() =>
|
|
136
|
+
synchronizer.load().then(() => {
|
|
137
|
+
synchronizer.save();
|
|
138
|
+
{{#unless persist}}onReady?.();{{/unless}}
|
|
139
|
+
});
|
|
63
140
|
});
|
|
64
141
|
|
|
65
142
|
return synchronizer;
|
|
66
143
|
});
|
|
67
144
|
{{/if}}
|
|
68
145
|
|
|
146
|
+
{{#unless persist}}{{#unless sync}}
|
|
147
|
+
onReady?.();
|
|
148
|
+
{{/unless}}{{/unless}}
|
|
149
|
+
|
|
69
150
|
return null;
|
|
70
151
|
};
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import {useRow, STORE_ID} from './ChatStore';
|
|
1
|
+
{{addImport "import {useRow, type MessageRow, STORE_ID} from './ChatStore';"}}
|
|
2
2
|
|
|
3
3
|
{{includeFile template="client/src/chat/message.css.hbs" output="client/src/message.css"}}
|
|
4
|
-
import './message.css';
|
|
4
|
+
{{addImport "import './message.css';"}}
|
|
5
5
|
|
|
6
6
|
interface MessageProps {
|
|
7
7
|
rowId: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const Message = ({rowId}: MessageProps) => {
|
|
11
|
-
const
|
|
12
|
-
const time = new Date((row as any).timestamp).toLocaleTimeString();
|
|
11
|
+
const {username, text, timestamp} = useRow('messages', rowId, STORE_ID) as MessageRow;
|
|
13
12
|
|
|
14
13
|
return (
|
|
15
14
|
<div className="message">
|
|
16
|
-
<span className="username">{
|
|
17
|
-
<span className="text">{
|
|
18
|
-
<span className="time">{
|
|
15
|
+
<span className="username">{username}:</span>
|
|
16
|
+
<span className="text">{text}</span>
|
|
17
|
+
<span className="time">{new Date(timestamp).toLocaleTimeString()}</span>
|
|
19
18
|
</div>
|
|
20
|
-
)
|
|
21
|
-
};
|
|
19
|
+
)
|
|
20
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{{includeFile template="client/src/chat/messageInput.css.hbs" output="client/src/messageInput.css"}}
|
|
2
|
-
import {useState} from 'react';
|
|
3
|
-
import {useAddRowCallback, STORE_ID as CHAT_STORE_ID} from './ChatStore';
|
|
4
|
-
import {useValue, STORE_ID as SETTINGS_STORE_ID} from './SettingsStore';
|
|
5
|
-
import './messageInput.css';
|
|
2
|
+
{{addImport "import {useState} from 'react';"}}
|
|
3
|
+
{{addImport "import {useAddRowCallback, STORE_ID as CHAT_STORE_ID} from './ChatStore';"}}
|
|
4
|
+
{{addImport "import {useValue, STORE_ID as SETTINGS_STORE_ID} from './SettingsStore';"}}
|
|
5
|
+
{{addImport "import './messageInput.css';"}}
|
|
6
6
|
|
|
7
7
|
{{includeFile template="client/src/shared/Button.tsx.hbs" output="client/src/Button.{{ext}}"}}
|
|
8
|
-
import {Button} from './Button';
|
|
8
|
+
{{addImport "import {Button} from './Button';"}}
|
|
9
9
|
|
|
10
10
|
{{includeFile template="client/src/shared/Input.tsx.hbs" output="client/src/Input.{{ext}}"}}
|
|
11
|
-
import {Input} from './Input';
|
|
11
|
+
{{addImport "import {Input} from './Input';"}}
|
|
12
12
|
|
|
13
13
|
export const MessageInput = () => {
|
|
14
14
|
const [message, setMessage] = useState('');
|
|
@@ -39,4 +39,4 @@ return (
|
|
|
39
39
|
<Button type="submit" variant="primary">Send</Button>
|
|
40
40
|
</form>
|
|
41
41
|
);
|
|
42
|
-
};
|
|
42
|
+
};
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {useRowIds, useStore, STORE_ID} from './ChatStore';
|
|
1
|
+
{{addImport "import {useSortedRowIds, STORE_ID} from './ChatStore';"}}
|
|
3
2
|
|
|
4
3
|
{{includeFile template="client/src/chat/messages.css.hbs" output="client/src/messages.css"}}
|
|
5
|
-
import './messages.css';
|
|
4
|
+
{{addImport "import './messages.css';"}}
|
|
6
5
|
|
|
7
6
|
{{includeFile template="client/src/chat/Message.tsx.hbs" output="client/src/Message.{{ext}}"}}
|
|
8
|
-
import {Message} from './Message';
|
|
7
|
+
{{addImport "import {Message} from './Message';"}}
|
|
9
8
|
|
|
10
9
|
export const Messages = () => {
|
|
11
|
-
const
|
|
12
|
-
const messageIds = useRowIds('messages', STORE_ID);
|
|
13
|
-
|
|
14
|
-
const sortedIds = useMemo(() => {
|
|
15
|
-
return [...messageIds].sort((a, b) => {
|
|
16
|
-
const rowA = store.getRow('messages', a) as any;
|
|
17
|
-
const rowB = store.getRow('messages', b) as any;
|
|
18
|
-
return rowA.timestamp - rowB.timestamp;
|
|
19
|
-
});
|
|
20
|
-
}, [messageIds, store]);
|
|
10
|
+
const sortedIds = useSortedRowIds('messages', 'timestamp', false, 0, undefined, STORE_ID);
|
|
21
11
|
|
|
22
12
|
return (
|
|
23
13
|
<div id="messages">
|
|
@@ -26,4 +16,4 @@ return (
|
|
|
26
16
|
))}
|
|
27
17
|
</div>
|
|
28
18
|
);
|
|
29
|
-
};
|
|
19
|
+
};
|