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.
Files changed (79) hide show
  1. package/README.md +62 -18
  2. package/cli.js +2 -2
  3. package/package.json +3 -3
  4. package/screenshots/chat.png +0 -0
  5. package/screenshots/todos.png +0 -0
  6. package/templates/README.md.hbs +13 -12
  7. package/templates/client/eslint.config.js.hbs +7 -0
  8. package/templates/client/package.json.hbs +28 -8
  9. package/templates/client/src/chat/App.tsx.hbs +28 -22
  10. package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
  11. package/templates/client/src/chat/Message.tsx.hbs +8 -9
  12. package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
  13. package/templates/client/src/chat/Messages.tsx.hbs +5 -15
  14. package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
  15. package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
  16. package/templates/client/src/chat/app.ts.hbs +18 -18
  17. package/templates/client/src/chat/chatStore.ts.hbs +97 -35
  18. package/templates/client/src/chat/message.ts.hbs +4 -3
  19. package/templates/client/src/chat/messageInput.css.hbs +1 -1
  20. package/templates/client/src/chat/messageInput.ts.hbs +7 -8
  21. package/templates/client/src/chat/messages.ts.hbs +7 -12
  22. package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
  23. package/templates/client/src/chat/usernameInput.css.hbs +1 -1
  24. package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
  25. package/templates/client/src/chat/utils.ts.hbs +26 -0
  26. package/templates/client/src/drawing/App.tsx.hbs +26 -20
  27. package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
  28. package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
  29. package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
  30. package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
  31. package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
  32. package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
  33. package/templates/client/src/drawing/app.ts.hbs +18 -8
  34. package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
  35. package/templates/client/src/drawing/canvas.ts.hbs +84 -86
  36. package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
  37. package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
  38. package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
  39. package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
  40. package/templates/client/src/game/App.tsx.hbs +20 -16
  41. package/templates/client/src/game/Board.tsx.hbs +8 -8
  42. package/templates/client/src/game/Game.tsx.hbs +14 -21
  43. package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
  44. package/templates/client/src/game/Square.tsx.hbs +6 -11
  45. package/templates/client/src/game/Store.tsx.hbs +106 -16
  46. package/templates/client/src/game/app.ts.hbs +17 -6
  47. package/templates/client/src/game/board.ts.hbs +7 -7
  48. package/templates/client/src/game/game.ts.hbs +12 -18
  49. package/templates/client/src/game/gameStatus.ts.hbs +3 -3
  50. package/templates/client/src/game/square.ts.hbs +4 -4
  51. package/templates/client/src/game/store.ts.hbs +95 -23
  52. package/templates/client/src/index.tsx.hbs +5 -7
  53. package/templates/client/src/shared/Button.tsx.hbs +3 -3
  54. package/templates/client/src/shared/Input.tsx.hbs +2 -2
  55. package/templates/client/src/shared/Loading.tsx.hbs +5 -0
  56. package/templates/client/src/shared/button.ts.hbs +2 -2
  57. package/templates/client/src/shared/config.ts.hbs +4 -6
  58. package/templates/client/src/shared/input.ts.hbs +2 -2
  59. package/templates/client/src/shared/loading.css.hbs +21 -0
  60. package/templates/client/src/shared/loading.ts.hbs +13 -0
  61. package/templates/client/src/shared/pglite.ts.hbs +10 -0
  62. package/templates/client/src/shared/sqlite.ts.hbs +17 -0
  63. package/templates/client/src/todos/App.tsx.hbs +22 -22
  64. package/templates/client/src/todos/Store.tsx.hbs +106 -23
  65. package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
  66. package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
  67. package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
  68. package/templates/client/src/todos/app.ts.hbs +16 -10
  69. package/templates/client/src/todos/store.ts.hbs +94 -30
  70. package/templates/client/src/todos/todoInput.ts.hbs +5 -8
  71. package/templates/client/src/todos/todoItem.ts.hbs +3 -4
  72. package/templates/client/src/todos/todoList.ts.hbs +6 -8
  73. package/templates/client/vite-env.d.ts.hbs +4 -0
  74. package/templates/client/vite.config.js.hbs +53 -3
  75. package/templates/server/index-do.ts.hbs +10 -14
  76. package/templates/server/index-node.ts.hbs +3 -3
  77. package/templates/server/package.json.hbs +5 -5
  78. package/templates/server/wrangler.toml.hbs +1 -1
  79. /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
@@ -1,23 +1,18 @@
1
1
  {{includeFile template="client/src/game/square.css.hbs" output="client/src/square.css"}}
2
- import './square.css';
3
- import {useCell, useStore, useValue, STORE_ID} from './Store';
2
+ {{addImport "import './square.css';"}}
3
+ {{addImport "import {useCell, useSetCellCallback, useValue, STORE_ID} from './Store';"}}
4
4
 
5
- export const Square = ({position, disabled, winning}: {position: number; disabled: boolean; winning: boolean}) => {
6
- const store = useStore(STORE_ID);
7
- const value = useCell('board', position.toString(), 'value', STORE_ID);
5
+ export const Square = ({position, disabled, winning}: {position: string; disabled: boolean; winning: boolean}) => {
6
+ const value = useCell('board', position, 'value', STORE_ID);
8
7
  const currentPlayer = useValue('currentPlayer', STORE_ID);
9
8
 
10
9
  const isDisabled = disabled || !!value;
11
10
 
12
- const handleClick = () => {
13
- if (!isDisabled) {
14
- store.setCell('board', position.toString(), 'value', currentPlayer);
15
- }
16
- };
11
+ const handleClick = useSetCellCallback('board', position, 'value', () => currentPlayer || 'X', [currentPlayer], STORE_ID);
17
12
 
18
13
  return (
19
14
  <button className={`square${isDisabled ? ' disabled' : '' }${winning ? ' winning' : '' }`} onClick={handleClick} disabled={isDisabled}>
20
15
  {value || ''}
21
16
  </button>
22
17
  );
23
- };
18
+ };
@@ -1,15 +1,20 @@
1
1
  {{#if schemas}}
2
- import {createMergeableStore} from 'tinybase/with-schemas';
3
- import * as UiReact from 'tinybase/ui-react/with-schemas';
4
- import {type NoTablesSchema} from 'tinybase/with-schemas';
2
+ {{addImport "import {createMergeableStore} from 'tinybase/with-schemas';"}}
3
+ {{addImport "import * as UiReact from 'tinybase/ui-react/with-schemas';"}}
5
4
  {{else}}
6
- import {createMergeableStore} from 'tinybase';
7
- import {useCreateStore, useProvideStore, useStore, useSetValuesCallback, useTableListener, useCell, useValue} from 'tinybase/ui-react';
5
+ {{addImport "import {createMergeableStore} from 'tinybase';"}}
6
+ {{addImport "import {useCreateMergeableStore, useProvideStore, useStore, useSetValuesCallback, useSetCellCallback, useTableListener, useCell, useValue} from 'tinybase/ui-react';"}}
8
7
  {{/if}}
9
8
 
10
9
  export const STORE_ID = 'game';
11
10
 
12
11
  {{#if schemas}}
12
+ const TABLES_SCHEMA = {
13
+ board: {
14
+ value: {type: 'string', default: ''},
15
+ },
16
+ } as const;
17
+
13
18
  const VALUES_SCHEMA = {
14
19
  currentPlayer: {type: 'string'},
15
20
  gameStatus: {type: 'string'},
@@ -17,16 +22,17 @@ export const STORE_ID = 'game';
17
22
  winningLine: {type: 'string', default: ''},
18
23
  } as const;
19
24
 
20
- type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA];
25
+ type Schemas = [typeof TABLES_SCHEMA, typeof VALUES_SCHEMA];
21
26
 
22
- const {useCreateStore, useProvideStore, useStore, useSetValuesCallback, useTableListener, useCell, useValue} = UiReact as UiReact.WithSchemas<Schemas>;
27
+ const {useCell, useCreateMergeableStore, {{#if persist}}useCreatePersister, {{/if}}useProvideStore, useSetCellCallback, useSetValuesCallback, useStore, useTableListener, useValue} = UiReact as UiReact.WithSchemas<Schemas>;
23
28
  {{/if}}
24
29
 
25
- export {useStore, useSetValuesCallback, useTableListener, useCell, useValue};
30
+ export {useStore, useSetValuesCallback, useSetCellCallback, useTableListener, useCell, useValue};
26
31
 
27
- export const Store = () => {
28
- const store = useCreateStore(() =>
29
- createMergeableStore(STORE_ID){{#if schemas}}
32
+ export const Store = ({onReady}: {onReady?: () => void}) => {
33
+ const store = useCreateMergeableStore(() =>
34
+ createMergeableStore(){{#if schemas}}
35
+ .setTablesSchema(TABLES_SCHEMA)
30
36
  .setValuesSchema(VALUES_SCHEMA){{/if}}
31
37
  .setDefaultContent([
32
38
  {},
@@ -39,15 +45,92 @@ gameStatus: 'playing',
39
45
 
40
46
  useProvideStore(STORE_ID, store);
41
47
 
48
+ {{#if persist}}
49
+ {{#if persistLocalStorage}}
50
+ {{#if schemas}}
51
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
52
+ {{else}}
53
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
54
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
55
+ {{/if}}
56
+
57
+ useCreatePersister(
58
+ store,
59
+ (store) => createLocalPersister(store, STORE_ID),
60
+ [],
61
+ async (persister) => {
62
+ await persister.load();
63
+ await persister.startAutoSave();
64
+ onReady?.();
65
+ },
66
+ );
67
+ {{/if}}
68
+ {{#if persistSqlite}}
69
+ {{#if schemas}}
70
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
71
+ {{else}}
72
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
73
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
74
+ {{/if}}
75
+ {{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.{{ext}}"}}
76
+ {{addImport "import {getDb} from './sqlite';"}}
77
+
78
+ useCreatePersister(
79
+ store,
80
+ async (store) => {
81
+ const {sqlite3, db} = await getDb();
82
+ return createSqliteWasmPersister(store, sqlite3, db, STORE_ID);
83
+ },
84
+ [],
85
+ async (persister) => {
86
+ await persister.load();
87
+ await persister.startAutoSave();
88
+ onReady?.();
89
+ },
90
+ );
91
+ {{/if}}
92
+ {{#if persistPglite}}
93
+ {{#if schemas}}
94
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
95
+ {{else}}
96
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
97
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
98
+ {{/if}}
99
+ {{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
100
+ {{addImport "import {getPgLite} from './pglite';"}}
101
+
102
+ useCreatePersister(
103
+ store,
104
+ async (store) => {
105
+ const pgLite = await getPgLite();
106
+ return await createPglitePersister(store, pgLite, 'game');
107
+ },
108
+ [],
109
+ async (persister) => {
110
+ await persister.load();
111
+ await persister.startAutoSave();
112
+ onReady?.();
113
+ },
114
+ );
115
+ {{/if}}
116
+ {{/if}}
117
+
42
118
  {{#if sync}}
43
119
  {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
44
120
  {{addImport "import {SERVER} from './config';"}}
45
121
  {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
46
- {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
47
- {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
48
- {{addImport "import type {MergeableStore} from 'tinybase';"}}
122
+ {{#if schemas}}
123
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
124
+ {{else}}
125
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
126
+ {{/if}}
127
+ {{#if schemas}}
128
+ {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react/with-schemas';"}}
129
+ {{else}}
130
+ {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
131
+ {{/if}}
49
132
 
50
- useCreateSynchronizer(store, async (store: MergeableStore) => {
133
+ useCreateSynchronizer(store, async (store) => {
51
134
  const serverPathId = location.pathname;
52
135
  const synchronizer = await createWsSynchronizer(
53
136
  store,
@@ -56,12 +139,19 @@ useProvideStore(STORE_ID, store);
56
139
  await synchronizer.startSync();
57
140
 
58
141
  synchronizer.getWebSocket().addEventListener('open', () => {
59
- synchronizer.load().then(() => synchronizer.save());
142
+ synchronizer.load().then(() => {
143
+ synchronizer.save();
144
+ {{#unless persist}}onReady?.();{{/unless}}
145
+ });
60
146
  });
61
147
 
62
148
  return synchronizer;
63
149
  });
64
150
  {{/if}}
65
151
 
152
+ {{#unless persist}}{{#unless sync}}
153
+ onReady?.();
154
+ {{/unless}}{{/unless}}
155
+
66
156
  return null;
67
157
  };
@@ -1,12 +1,23 @@
1
1
  {{includeFile template="client/src/game/store.ts.hbs" output="client/src/store.ts"}}
2
- import {store} from './store';
2
+ {{addImport "import {store, storeReady} from './store';"}}
3
+
4
+ {{includeFile template="client/src/shared/loading.ts.hbs" output="client/src/loading.{{ext}}"}}
5
+ {{addImport "import {showLoading, hideLoading} from './loading';"}}
3
6
 
4
7
  {{includeFile template="client/src/game/game.ts.hbs" output="client/src/game.{{ext}}"}}
5
- import {createGame} from './game';
8
+ {{addImport "import {createGame} from './game';"}}
6
9
 
7
- const app = () => {
10
+ export const app = async () => {
8
11
  const appContainer = document.getElementById('app')!;
9
- appContainer.appendChild(createGame(store));
10
- };
11
12
 
12
- export {app};
13
+ // Show loading spinner
14
+ const loadingDiv = showLoading(appContainer);
15
+
16
+ // Wait for store to be ready
17
+ await storeReady;
18
+
19
+ // Remove loading spinner
20
+ hideLoading(loadingDiv);
21
+
22
+ appContainer.appendChild(createGame(store));
23
+ };
@@ -1,9 +1,9 @@
1
1
  {{includeFile template="client/src/game/board.css.hbs" output="client/src/board.css"}}
2
- import './board.css';
3
- import type {GameStore} from './store';
2
+ {{addImport "import './board.css';"}}
3
+ {{addImport "import type {GameStore} from './store';"}}
4
4
 
5
5
  {{includeFile template="client/src/game/square.ts.hbs" output="client/src/square.{{ext}}"}}
6
- import {createSquare} from './square';
6
+ {{addImport "import {createSquare} from './square';"}}
7
7
 
8
8
  export const createBoard = (store: GameStore): HTMLDivElement => {
9
9
  const board = document.createElement('div');
@@ -14,13 +14,13 @@ const gameStatus = store.getValue('gameStatus');
14
14
  const winningLine = store.getValue('winningLine');
15
15
  const currentPlayer = store.getValue('currentPlayer');
16
16
  const winningPositions = new Set(
17
- winningLine ? winningLine.split(',').map(Number) : []
17
+ winningLine && typeof winningLine === 'string' ? winningLine.split(',').map(Number) : []
18
18
  );
19
19
  const disabled = gameStatus !== 'playing';
20
20
 
21
21
  board.innerHTML = '';
22
- for (let i = 0; i < 9; i++) { const cell=store.getCell('board', i.toString(), 'value' ); const square=createSquare( i, cell, ()=> {
23
- if (gameStatus === 'playing' && !cell) {
22
+ for (let i = 0; i < 9; i++) { const cell=store.getCell('board', i.toString(), 'value' ); const square=createSquare(cell as string, ()=> {
23
+ if (gameStatus === 'playing' && !cell && currentPlayer) {
24
24
  store.setCell('board', i.toString(), 'value', currentPlayer);
25
25
  }
26
26
  },
@@ -36,4 +36,4 @@ for (let i = 0; i < 9; i++) { const cell=store.getCell('board', i.toString(), 'v
36
36
  render();
37
37
 
38
38
  return board;
39
- };
39
+ };
@@ -1,18 +1,18 @@
1
- import type {GameStore} from './store';
1
+ {{addImport "import type {GameStore} from './store';"}}
2
2
 
3
3
  {{includeFile template="client/src/shared/button.ts.hbs" output="client/src/button.{{ext}}"}}
4
- import {createButton} from './button';
4
+ {{addImport "import {createButton} from './button';"}}
5
5
 
6
6
  {{includeFile template="client/src/game/board.ts.hbs" output="client/src/board.{{ext}}"}}
7
- import {createBoard} from './board';
7
+ {{addImport "import {createBoard} from './board';"}}
8
8
 
9
9
  {{includeFile template="client/src/game/gameStatus.ts.hbs" output="client/src/gameStatus.{{ext}}"}}
10
- import {createGameStatus} from './gameStatus';
10
+ {{addImport "import {createGameStatus} from './gameStatus';"}}
11
11
 
12
12
  const WINNING_LINES = [
13
- [0, 1, 2], [3, 4, 5], [6, 7, 8], // rows
14
- [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns
15
- [0, 4, 8], [2, 4, 6], // diagonals
13
+ ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'],
14
+ ['0', '3', '6'], ['1', '4', '7'], ['2', '5', '8'],
15
+ ['0', '4', '8'], ['2', '4', '6'],
16
16
  ];
17
17
 
18
18
  export const createGame = (store: GameStore): HTMLDivElement => {
@@ -20,14 +20,11 @@ const checkGameState = () => {
20
20
  const gameStatus = store.getValue('gameStatus');
21
21
  if (gameStatus !== 'playing') return;
22
22
 
23
- const board = store.getTable('board');
24
-
25
- // Check for winner
26
23
  for (const line of WINNING_LINES) {
27
24
  const [a, b, c] = line;
28
- const cellA = board[a]?.value;
29
- const cellB = board[b]?.value;
30
- const cellC = board[c]?.value;
25
+ const cellA = store.getCell('board', a, 'value');
26
+ const cellB = store.getCell('board', b, 'value');
27
+ const cellC = store.getCell('board', c, 'value');
31
28
 
32
29
  if (cellA && cellA === cellB && cellA === cellC) {
33
30
  store.setValue('gameStatus', 'won');
@@ -37,14 +34,11 @@ return;
37
34
  }
38
35
  }
39
36
 
40
- // Check for draw
41
- const filledCells = Object.values(board).filter((cell: any) => cell.value).length;
42
- if (filledCells === 9) {
37
+ if (store.getRowCount('board') === 9) {
43
38
  store.setValue('gameStatus', 'draw');
44
39
  return;
45
40
  }
46
41
 
47
- // Switch player after each move
48
42
  const currentPlayer = store.getValue('currentPlayer');
49
43
  store.setValue('currentPlayer', currentPlayer === 'X' ? 'O' : 'X');
50
44
  };
@@ -71,4 +65,4 @@ buttonContainer.appendChild(createButton('New Game', resetGame, 'primary'));
71
65
  gameContainer.appendChild(buttonContainer);
72
66
 
73
67
  return gameContainer;
74
- };
68
+ };
@@ -1,6 +1,6 @@
1
1
  {{includeFile template="client/src/game/gameStatus.css.hbs" output="client/src/gameStatus.css"}}
2
- import './gameStatus.css';
3
- import type {GameStore} from './store';
2
+ {{addImport "import './gameStatus.css';"}}
3
+ {{addImport "import type {GameStore} from './store';"}}
4
4
 
5
5
  export const createGameStatus = (store: GameStore): HTMLDivElement => {
6
6
  const status = document.createElement('div');
@@ -24,4 +24,4 @@ store.addValuesListener(render);
24
24
  render();
25
25
 
26
26
  return status;
27
- };
27
+ };
@@ -1,11 +1,11 @@
1
1
  {{includeFile template="client/src/game/square.css.hbs" output="client/src/square.css"}}
2
- import './square.css';
2
+ {{addImport "import './square.css';"}}
3
3
 
4
- export const createSquare = (position: number, value: string | undefined, onClick: () => void, disabled: boolean, winning: boolean): HTMLButtonElement => {
4
+ export const createSquare = (value: string | undefined, onClick: () => void, disabled: boolean, winning: boolean): HTMLButtonElement => {
5
5
  const square = document.createElement('button');
6
6
  square.className = `square${disabled ? ' disabled' : ''}${winning ? ' winning' : ''}`;
7
- square.textContent = value || '';
7
+ square.textContent = value ?? '';
8
8
  square.disabled = disabled;
9
9
  square.addEventListener('click', onClick);
10
10
  return square;
11
- };
11
+ };
@@ -1,12 +1,16 @@
1
1
  {{#if schemas}}
2
- import {createMergeableStore} from 'tinybase/with-schemas';
2
+ {{addImport "import {createMergeableStore} from 'tinybase/with-schemas';"}}
3
3
  {{else}}
4
- import {createMergeableStore} from 'tinybase';
4
+ {{addImport "import {createMergeableStore} from 'tinybase';"}}
5
5
  {{/if}}
6
6
 
7
- const STORE_ID = 'game';
8
-
9
7
  {{#if schemas}}
8
+ const TABLES_SCHEMA = {
9
+ board: {
10
+ value: {type: 'string', default: ''},
11
+ },
12
+ } as const;
13
+
10
14
  const VALUES_SCHEMA = {
11
15
  currentPlayer: {type: 'string'},
12
16
  gameStatus: {type: 'string'},
@@ -15,7 +19,8 @@ const STORE_ID = 'game';
15
19
  } as const;
16
20
 
17
21
  {{/if}}
18
- export const store = createMergeableStore(STORE_ID){{#if schemas}}
22
+ export const store = createMergeableStore(){{#if schemas}}
23
+ .setTablesSchema(TABLES_SCHEMA)
19
24
  .setValuesSchema(VALUES_SCHEMA){{/if}}
20
25
  .setDefaultContent([
21
26
  {},
@@ -25,23 +30,90 @@ gameStatus: 'playing',
25
30
  },
26
31
  ]);
27
32
 
28
- {{#if sync}}
29
- {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
30
- {{addImport "import {SERVER} from './config';"}}
31
- {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
32
- {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
33
-
34
- const serverPathId = location.pathname;
35
- createWsSynchronizer(
36
- store,
37
- new ReconnectingWebSocket(SERVER + serverPathId),
38
- ).then(async (synchronizer) => {
39
- await synchronizer.startSync();
40
-
41
- synchronizer.getWebSocket().addEventListener('open', () => {
42
- synchronizer.load().then(() => synchronizer.save());
43
- });
33
+ let resolveReady: () => void;
34
+ export const storeReady = new Promise<void>((resolve) => {
35
+ resolveReady = resolve;
44
36
  });
45
- {{/if}}
46
37
 
47
- export type GameStore = typeof store;
38
+ {{#if persist}}
39
+ {{#if persistLocalStorage}}
40
+ {{#if schemas}}
41
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
42
+ {{else}}
43
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
44
+ {{/if}}
45
+
46
+ const persister = createLocalPersister(store, 'game');
47
+ persister.load().then(() => {
48
+ persister.startAutoSave();
49
+ resolveReady();
50
+ });
51
+ {{/if}}
52
+ {{#if persistSqlite}}
53
+ {{#if schemas}}
54
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
55
+ {{else}}
56
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
57
+ {{/if}}
58
+ {{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.ts"}}
59
+ {{addImport "import {getDb} from './sqlite';"}}
60
+
61
+ getDb().then(({sqlite3, db}) => {
62
+ const persister = createSqliteWasmPersister(store, sqlite3, db, 'game');
63
+ persister.load().then(() => {
64
+ persister.startAutoSave();
65
+ resolveReady();
66
+ });
67
+ });
68
+ {{/if}}
69
+ {{#if persistPglite}}
70
+ {{#if schemas}}
71
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
72
+ {{else}}
73
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
74
+ {{/if}}
75
+ {{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
76
+ {{addImport "import {getPgLite} from './pglite';"}}
77
+
78
+ getPgLite().then((pgLite) => {
79
+ createPglitePersister(store, pgLite, 'game').then((persister) => {
80
+ persister.load().then(() => {
81
+ persister.startAutoSave();
82
+ resolveReady();
83
+ });
84
+ });
85
+ });
86
+ {{/if}}
87
+ {{/if}}
88
+
89
+ {{#if sync}}
90
+ {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
91
+ {{addImport "import {SERVER} from './config';"}}
92
+ {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
93
+ {{#if schemas}}
94
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
95
+ {{else}}
96
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
97
+ {{/if}}
98
+
99
+ const serverPathId = location.pathname;
100
+ createWsSynchronizer(
101
+ store,
102
+ new ReconnectingWebSocket(SERVER + serverPathId),
103
+ ).then(async (synchronizer) => {
104
+ await synchronizer.startSync();
105
+
106
+ synchronizer.getWebSocket().addEventListener('open', () => {
107
+ synchronizer.load().then(() => {
108
+ synchronizer.save();
109
+ {{#unless persist}}resolveReady();{{/unless}}
110
+ });
111
+ });
112
+ });
113
+ {{/if}}
114
+
115
+ {{#unless persist}}{{#unless sync}}
116
+ resolveReady();
117
+ {{/unless}}{{/unless}}
118
+
119
+ export type GameStore = typeof store;
@@ -1,23 +1,21 @@
1
1
  {{#if sync}}
2
- import {getUniqueId} from 'tinybase';
3
-
2
+ {{addImport "import {getUniqueId} from 'tinybase';"}}
4
3
  if (location.pathname === '/') {
5
4
  location.assign('/' + getUniqueId());
6
5
  }
7
6
  {{/if}}
7
+
8
8
  {{#if react}}
9
- import ReactDOM from 'react-dom/client';
10
- import {App} from './App';
9
+ {{addImport "import ReactDOM from 'react-dom/client';"}}
10
+ {{addImport "import {App} from './App';"}}
11
11
  {{includeFile template="client/src/{{appType}}/App.tsx.hbs" output="client/src/App.{{ext}}"}}
12
-
13
12
  addEventListener('load', () => {
14
13
  ReactDOM.createRoot(document.getElementById('app')!).render(
15
14
  <App />);
16
15
  });
17
16
  {{else}}
18
- import {app} from './app';
17
+ {{addImport "import {app} from './app';"}}
19
18
  {{includeFile template="client/src/{{appType}}/app.ts.hbs" output="client/src/app.{{ext}}"}}
20
-
21
19
  addEventListener('load', () => {
22
20
  app();
23
21
  });
@@ -1,6 +1,6 @@
1
1
  {{includeFile template="client/src/shared/button.css.hbs" output="client/src/button.css"}}
2
- import './button.css';
3
- import {ReactNode} from 'react';
2
+ {{addImport "import './button.css';"}}
3
+ {{addImport "import {ReactNode} from 'react';"}}
4
4
 
5
5
  interface ButtonProps {
6
6
  children: ReactNode;
@@ -13,4 +13,4 @@ export const Button = ({children, onClick, variant = 'default', type = 'button'}
13
13
  <button onClick={onClick} type={type} className={variant==='primary' ? 'primary' : undefined}>
14
14
  {children}
15
15
  </button>
16
- );
16
+ );
@@ -1,5 +1,5 @@
1
1
  {{includeFile template="client/src/shared/input.css.hbs" output="client/src/input.css"}}
2
- import './input.css';
2
+ {{addImport "import './input.css';"}}
3
3
 
4
4
  interface InputProps {
5
5
  value: string;
@@ -13,4 +13,4 @@ export const Input = ({value, onChange, placeholder = '', autoFocus = false}: In
13
13
  placeholder={placeholder}
14
14
  autoFocus={autoFocus}
15
15
  />
16
- );
16
+ );
@@ -0,0 +1,5 @@
1
+ {{addImport "import './loading.css';"}}
2
+ {{includeFile template="client/src/shared/loading.css.hbs" output="client/src/loading.css"}}
3
+
4
+ export const Loading = () =>
5
+ <div id="loading" />;
@@ -1,5 +1,5 @@
1
1
  {{includeFile template="client/src/shared/button.css.hbs" output="client/src/button.css"}}
2
- import './button.css';
2
+ {{addImport "import './button.css';"}}
3
3
 
4
4
  export const createButton = (
5
5
  text: string,
@@ -13,4 +13,4 @@ button.className = 'primary';
13
13
  }
14
14
  button.addEventListener('click', onClick);
15
15
  return button;
16
- };
16
+ };
@@ -1,9 +1,7 @@
1
- {{#if server}}
2
- {{#if (eq serverType "node")}}
3
- export const SERVER = 'ws://localhost:8043';
4
- {{else}}
5
- export const SERVER = 'ws://localhost:8787';
6
- {{/if}}
1
+ {{#if (eq syncType "node")}}
2
+ export const SERVER = 'ws://localhost:8043';
3
+ {{else if (eq syncType "durable-objects")}}
4
+ export const SERVER = 'ws://localhost:8787';
7
5
  {{else}}
8
6
  export const SERVER = 'wss://vite.tinybase.org';
9
7
  {{/if}}
@@ -1,5 +1,5 @@
1
1
  {{includeFile template="client/src/shared/input.css.hbs" output="client/src/input.css"}}
2
- import './input.css';
2
+ {{addImport "import './input.css';"}}
3
3
 
4
4
  export const createInput = (
5
5
  placeholder: string = '',
@@ -14,4 +14,4 @@ if (onInput) {
14
14
  input.addEventListener('input', () => onInput(input.value));
15
15
  }
16
16
  return input;
17
- };
17
+ };
@@ -0,0 +1,21 @@
1
+ #loading {
2
+ animation: spin 1s infinite linear;
3
+ height: 2rem;
4
+ margin: 40vh auto;
5
+ width: 2rem;
6
+ }
7
+
8
+ #loading::before {
9
+ content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="2rem" viewBox="0 0 100 100">
10
+ <path d="M50 10A40 40 0 1 1 10 50" stroke="%23d81b60" fill="none" stroke-width="4" />
11
+ </svg>');
12
+ }
13
+
14
+ @keyframes spin {
15
+ from {
16
+ transform: rotate(0);
17
+ }
18
+ to {
19
+ transform: rotate(360deg);
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ {{includeFile template="client/src/shared/loading.css.hbs" output="client/src/loading.css"}}
2
+ {{addImport "import './loading.css';"}}
3
+
4
+ export const showLoading = (container: HTMLElement): HTMLElement => {
5
+ const loadingDiv = document.createElement('div');
6
+ loadingDiv.id = 'loading';
7
+ container.appendChild(loadingDiv);
8
+ return loadingDiv;
9
+ };
10
+
11
+ export const hideLoading = (loadingDiv: HTMLElement): void => {
12
+ loadingDiv.remove();
13
+ };
@@ -0,0 +1,10 @@
1
+ {{addImport "import {PGlite} from '@electric-sql/pglite';"}}
2
+
3
+ let pgLitePromise: Promise<PGlite> | null = null;
4
+
5
+ export const getPgLite = async (): Promise<PGlite> => {
6
+ if (!pgLitePromise) {
7
+ pgLitePromise = PGlite.create('idb://local');
8
+ }
9
+ return await pgLitePromise;
10
+ };