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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{{includeFile template="client/src/todos/todoList.css.hbs" output="client/src/todoList.css"}}
|
|
2
|
+
import './todoList.css';
|
|
3
|
+
import {useRowIds, STORE_ID} from './Store';
|
|
4
|
+
|
|
5
|
+
{{includeFile template="client/src/todos/TodoItem.tsx.hbs" output="client/src/TodoItem.{{ext}}"}}
|
|
6
|
+
import {TodoItem} from './TodoItem';
|
|
7
|
+
|
|
8
|
+
export const TodoList = () => {
|
|
9
|
+
const todoIds = useRowIds('todos', STORE_ID);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div id="todoList">
|
|
13
|
+
{todoIds.map((id) => (
|
|
14
|
+
<TodoItem key={id} rowId={id} />
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{{includeFile template="client/src/todos/store.ts.hbs" output="client/src/store.ts"}}
|
|
2
|
+
import {store} from './store';
|
|
3
|
+
|
|
4
|
+
{{includeFile template="client/src/todos/todoInput.ts.hbs" output="client/src/todoInput.{{ext}}"}}
|
|
5
|
+
import {createTodoInput} from './todoInput';
|
|
6
|
+
|
|
7
|
+
{{includeFile template="client/src/todos/todoList.ts.hbs" output="client/src/todoList.{{ext}}"}}
|
|
8
|
+
import {createTodoList} from './todoList';
|
|
9
|
+
|
|
10
|
+
const app = () => {
|
|
11
|
+
const appContainer = document.getElementById('app')!;
|
|
12
|
+
|
|
13
|
+
const todoInputContainer = createTodoInput(store);
|
|
14
|
+
appContainer.appendChild(todoInputContainer);
|
|
15
|
+
|
|
16
|
+
// Focus the input after it's in the DOM
|
|
17
|
+
const input = todoInputContainer.querySelector('input')!;
|
|
18
|
+
input.focus();
|
|
19
|
+
|
|
20
|
+
appContainer.appendChild(createTodoList(store));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export {app};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{{#if schemas}}
|
|
2
|
+
import {createMergeableStore} from 'tinybase/with-schemas';
|
|
3
|
+
{{else}}
|
|
4
|
+
import {createMergeableStore} from 'tinybase';
|
|
5
|
+
{{/if}}
|
|
6
|
+
|
|
7
|
+
const STORE_ID = 'todos';
|
|
8
|
+
|
|
9
|
+
{{#if schemas}}
|
|
10
|
+
const TODOS_SCHEMA = {
|
|
11
|
+
todos: {
|
|
12
|
+
text: {type: 'string'},
|
|
13
|
+
completed: {type: 'boolean'},
|
|
14
|
+
},
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
{{/if}}
|
|
18
|
+
export const store = createMergeableStore(STORE_ID){{#if schemas}}
|
|
19
|
+
.setTablesSchema(TODOS_SCHEMA){{/if}}
|
|
20
|
+
.setDefaultContent([
|
|
21
|
+
{
|
|
22
|
+
todos: {
|
|
23
|
+
'1': {text: 'Learn TinyBase', completed: false},
|
|
24
|
+
'2': {text: 'Build an app', completed: false},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
{{#if sync}}
|
|
31
|
+
{{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
|
|
32
|
+
{{addImport "import {SERVER} from './config';"}}
|
|
33
|
+
{{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
|
|
34
|
+
{{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
|
|
35
|
+
|
|
36
|
+
const serverPathId = location.pathname;
|
|
37
|
+
createWsSynchronizer(
|
|
38
|
+
store,
|
|
39
|
+
new ReconnectingWebSocket(SERVER + serverPathId),
|
|
40
|
+
).then(async (synchronizer) => {
|
|
41
|
+
await synchronizer.startSync();
|
|
42
|
+
|
|
43
|
+
synchronizer.getWebSocket().addEventListener('open', () => {
|
|
44
|
+
synchronizer.load().then(() => synchronizer.save());
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
{{/if}}
|
|
48
|
+
|
|
49
|
+
export type TodosStore = typeof store;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{{includeFile template="client/src/todos/todoInput.css.hbs" output="client/src/todoInput.css"}}
|
|
2
|
+
import './todoInput.css';
|
|
3
|
+
import {type TodosStore} from './store';
|
|
4
|
+
|
|
5
|
+
{{includeFile template="client/src/shared/button.ts.hbs" output="client/src/button.{{ext}}"}}
|
|
6
|
+
import {createButton} from './button';
|
|
7
|
+
|
|
8
|
+
{{includeFile template="client/src/shared/input.ts.hbs" output="client/src/input.{{ext}}"}}
|
|
9
|
+
import {createInput} from './input';
|
|
10
|
+
|
|
11
|
+
export const createTodoInput = (store: TodosStore): HTMLDivElement => {
|
|
12
|
+
const container = document.createElement('div');
|
|
13
|
+
container.id = 'todoInput';
|
|
14
|
+
|
|
15
|
+
const input = createInput('What needs to be done?');
|
|
16
|
+
|
|
17
|
+
const addTodo = () => {
|
|
18
|
+
const text = input.value.trim();
|
|
19
|
+
if (text) {
|
|
20
|
+
store.addRow('todos', {text, completed: false});
|
|
21
|
+
input.value = '';
|
|
22
|
+
input.focus();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
input.addEventListener('keypress', (e) => {
|
|
27
|
+
if (e.key === 'Enter') {
|
|
28
|
+
addTodo();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const addButton = createButton('Add', addTodo, 'primary');
|
|
33
|
+
|
|
34
|
+
container.appendChild(input);
|
|
35
|
+
container.appendChild(addButton);
|
|
36
|
+
|
|
37
|
+
return container;
|
|
38
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.todoItem {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 0.75rem;
|
|
5
|
+
padding: 0.75rem;
|
|
6
|
+
border-bottom: 1px solid var(--border);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.todoItem:last-child {
|
|
10
|
+
border-bottom: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.todoItem input[type="checkbox"] {
|
|
14
|
+
width: 1.25rem;
|
|
15
|
+
height: 1.25rem;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
flex-shrink: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.todoItem label {
|
|
21
|
+
flex: 1;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
user-select: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.todoItem.completed label {
|
|
27
|
+
text-decoration: line-through;
|
|
28
|
+
opacity: 0.6;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.todoItem button {
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{{includeFile template="client/src/todos/todoItem.css.hbs" output="client/src/todoItem.css"}}
|
|
2
|
+
import './todoItem.css';
|
|
3
|
+
|
|
4
|
+
{{includeFile template="client/src/shared/button.ts.hbs" output="client/src/button.{{ext}}"}}
|
|
5
|
+
import {createButton} from './button';
|
|
6
|
+
|
|
7
|
+
export const createTodoItem = (id: string, text: string, completed: boolean, onToggle: () => void, onDelete: () => void): HTMLDivElement => {
|
|
8
|
+
const item = document.createElement('div');
|
|
9
|
+
item.className = `todoItem${completed ? ' completed' : ''}`;
|
|
10
|
+
|
|
11
|
+
const checkbox = document.createElement('input');
|
|
12
|
+
checkbox.type = 'checkbox';
|
|
13
|
+
checkbox.checked = completed;
|
|
14
|
+
checkbox.id = `todo-${id}`;
|
|
15
|
+
checkbox.addEventListener('change', onToggle);
|
|
16
|
+
|
|
17
|
+
const label = document.createElement('label');
|
|
18
|
+
label.textContent = text;
|
|
19
|
+
label.htmlFor = `todo-${id}`;
|
|
20
|
+
|
|
21
|
+
const deleteBtn = createButton('Delete', onDelete);
|
|
22
|
+
|
|
23
|
+
item.appendChild(checkbox);
|
|
24
|
+
item.appendChild(label);
|
|
25
|
+
item.appendChild(deleteBtn);
|
|
26
|
+
|
|
27
|
+
return item;
|
|
28
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#todoList {
|
|
2
|
+
border: 1px solid var(--border);
|
|
3
|
+
border-radius: 0.375rem;
|
|
4
|
+
overflow: hidden;
|
|
5
|
+
background: var(--bg2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
#todoList:empty::before {
|
|
9
|
+
content: 'No todos yet. Add one above!';
|
|
10
|
+
display: block;
|
|
11
|
+
padding: 2rem;
|
|
12
|
+
text-align: center;
|
|
13
|
+
color: var(--fg2);
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{{includeFile template="client/src/todos/todoList.css.hbs" output="client/src/todoList.css"}}
|
|
2
|
+
import './todoList.css';
|
|
3
|
+
import {type TodosStore} from './store';
|
|
4
|
+
|
|
5
|
+
{{includeFile template="client/src/todos/todoItem.ts.hbs" output="client/src/todoItem.{{ext}}"}}
|
|
6
|
+
import {createTodoItem} from './todoItem';
|
|
7
|
+
|
|
8
|
+
export const createTodoList = (store: TodosStore): HTMLDivElement => {
|
|
9
|
+
const list = document.createElement('div');
|
|
10
|
+
list.id = 'todoList';
|
|
11
|
+
|
|
12
|
+
const render = () => {
|
|
13
|
+
const todos = store.getTable('todos');
|
|
14
|
+
const todoIds = Object.keys(todos);
|
|
15
|
+
|
|
16
|
+
list.innerHTML = '';
|
|
17
|
+
todoIds.forEach((id) => {
|
|
18
|
+
const todo = todos[id];
|
|
19
|
+
const item = createTodoItem(
|
|
20
|
+
id,
|
|
21
|
+
todo.text,
|
|
22
|
+
todo.completed,
|
|
23
|
+
() => {
|
|
24
|
+
store.setPartialRow('todos', id, {completed: !todo.completed});
|
|
25
|
+
},
|
|
26
|
+
() => {
|
|
27
|
+
store.delRow('todos', id);
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
list.appendChild(item);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
store.addTablesListener(render);
|
|
35
|
+
render();
|
|
36
|
+
|
|
37
|
+
return list;
|
|
38
|
+
};
|
|
@@ -1,73 +1,38 @@
|
|
|
1
|
+
{{includeFile template="README.md.hbs" output="README.md"}}
|
|
1
2
|
{
|
|
2
3
|
"name": "{{projectName}}",
|
|
3
4
|
"version": "1.0.0",
|
|
5
|
+
"private": true,
|
|
4
6
|
"scripts": {
|
|
5
7
|
{{#list}}
|
|
6
|
-
{{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
8
|
+
{{#if server}}
|
|
9
|
+
"dev": "concurrently \"npm run dev -w client\" \"npm run dev -w server\""
|
|
10
|
+
"dev:client": "npm run dev -w client"
|
|
11
|
+
"dev:server": "npm run dev -w server"
|
|
10
12
|
{{else}}
|
|
11
|
-
"
|
|
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 ."
|
|
13
|
+
"dev": "npm run dev -w client"
|
|
20
14
|
{{/if}}
|
|
15
|
+
"build": "npm run build -w client"
|
|
16
|
+
"preview": "npm run preview -w client"
|
|
21
17
|
{{/list}}
|
|
22
18
|
},
|
|
19
|
+
{{#if server}}
|
|
20
|
+
"workspaces": ["client", "server"],
|
|
21
|
+
{{else}}
|
|
22
|
+
"workspaces": ["client"],
|
|
23
|
+
{{/if}}
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
{{#list}}
|
|
25
|
-
|
|
26
|
-
|
|
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"
|
|
26
|
+
{{#if server}}
|
|
27
|
+
"concurrently": "^9.1.2"
|
|
58
28
|
{{/if}}
|
|
59
29
|
{{/list}}
|
|
60
30
|
}
|
|
61
31
|
}
|
|
62
|
-
{{#if
|
|
63
|
-
{{includeFile template=".
|
|
64
|
-
{{/if}}
|
|
65
|
-
{{#if eslint}}
|
|
66
|
-
{{includeFile template="eslint.config.js.hbs" output="eslint.config.js"}}
|
|
32
|
+
{{#if server}}
|
|
33
|
+
{{includeFile template="server/package.json.hbs" output="server/package.json"}}
|
|
67
34
|
{{/if}}
|
|
68
|
-
{{#if
|
|
69
|
-
{{includeFile template="
|
|
35
|
+
{{#if prettier}}
|
|
36
|
+
{{includeFile template="client/.prettierrc.hbs" output=".prettierrc"}}
|
|
70
37
|
{{/if}}
|
|
71
|
-
{{
|
|
72
|
-
{{includeFile template="tsconfig.json.hbs" output="tsconfig.json"}}
|
|
73
|
-
{{/if}}
|
|
38
|
+
{{includeFile template="client/package.json.hbs" output="client/package.json"}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {Id, IdAddedOrRemoved} from 'tinybase';
|
|
2
|
+
import {
|
|
3
|
+
getWsServerDurableObjectFetch,
|
|
4
|
+
WsServerDurableObject,
|
|
5
|
+
} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
|
|
6
|
+
|
|
7
|
+
export class TinyBaseDurableObject extends WsServerDurableObject {
|
|
8
|
+
onPathId(pathId: Id, addedOrRemoved: IdAddedOrRemoved) {
|
|
9
|
+
console.info((addedOrRemoved ? 'Added' : 'Removed') + ` path ${pathId}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
onClientId(pathId: Id, clientId: Id, addedOrRemoved: IdAddedOrRemoved) {
|
|
13
|
+
console.info(
|
|
14
|
+
(addedOrRemoved ? 'Added' : 'Removed') +
|
|
15
|
+
` client ${clientId} on path ${pathId}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
fetch: getWsServerDurableObjectFetch('TinyBaseDurableObjects'),
|
|
22
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
|
|
2
|
+
import {WebSocketServer} from 'ws';
|
|
3
|
+
|
|
4
|
+
const PORT = 8043;
|
|
5
|
+
|
|
6
|
+
const wsServer = createWsServer(new WebSocketServer({port: PORT}));
|
|
7
|
+
|
|
8
|
+
console.log(`WebSocket server running on ws://localhost:${PORT}`);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
{{#list}}
|
|
8
|
+
{{#if (eq serverType "node")}}
|
|
9
|
+
{{includeFile template="server/index-node.ts.hbs" output="server/index.ts"}}
|
|
10
|
+
"dev": "tsx watch index.ts"
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
{{else}}
|
|
13
|
+
{{includeFile template="server/index-do.ts.hbs" output="server/index.ts"}}
|
|
14
|
+
{{includeFile template="server/wrangler.toml.hbs" output="server/wrangler.toml"}}
|
|
15
|
+
{{#if typescript}}
|
|
16
|
+
{{includeFile template="server/tsconfig.json.hbs" output="server/tsconfig.json"}}
|
|
17
|
+
{{/if}}
|
|
18
|
+
"deploy": "wrangler deploy"
|
|
19
|
+
"dev": "wrangler dev"
|
|
20
|
+
"start": "wrangler dev"
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{/list}}
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
{{#list}}
|
|
26
|
+
"tinybase": "^7.3.1"
|
|
27
|
+
{{#if (eq serverType "node")}}
|
|
28
|
+
"ws": "^8.18.3"
|
|
29
|
+
{{/if}}
|
|
30
|
+
{{/list}}
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
{{#list}}
|
|
34
|
+
{{#if (eq serverType "node")}}
|
|
35
|
+
"@types/ws": "^8.18.1"
|
|
36
|
+
{{else}}
|
|
37
|
+
"@cloudflare/workers-types": "^4.20250828.1"
|
|
38
|
+
{{/if}}
|
|
39
|
+
{{#if typescript}}
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
{{#if (eq serverType "node")}}
|
|
42
|
+
"tsx": "^4.19.3"
|
|
43
|
+
{{/if}}
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{#if (eq serverType "durable-objects")}}
|
|
46
|
+
"wrangler": "^4.33.1"
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{/list}}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"types": ["@cloudflare/workers-types"],
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"strict": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["index.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#:schema node_modules/wrangler/config-schema.json
|
|
2
|
+
name = "{{projectName}}-server"
|
|
3
|
+
main = "index.ts"
|
|
4
|
+
compatibility_date = "2024-10-11"
|
|
5
|
+
|
|
6
|
+
[[durable_objects.bindings]]
|
|
7
|
+
name = "TinyBaseDurableObjects"
|
|
8
|
+
class_name = "TinyBaseDurableObject"
|
|
9
|
+
|
|
10
|
+
[[migrations]]
|
|
11
|
+
tag = "v1"
|
|
12
|
+
new_classes = ["TinyBaseDurableObject"]
|
package/templates/index.html.hbs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
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="public/favicon.svg" output="public/favicon.svg"}}
|
|
11
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
12
|
-
<title>
|
|
13
|
-
TinyBase / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}{{#if react}} + React{{/if}}
|
|
14
|
-
</title>
|
|
15
|
-
</head>
|
|
16
|
-
|
|
17
|
-
<body>
|
|
18
|
-
{{#if react}}
|
|
19
|
-
<div id="app"></div>
|
|
20
|
-
{{else}}
|
|
21
|
-
<div id="app">
|
|
22
|
-
<header>
|
|
23
|
-
<h1>
|
|
24
|
-
<img src="/favicon.svg" />
|
|
25
|
-
TinyBase / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
|
|
26
|
-
</h1>
|
|
27
|
-
</header>
|
|
28
|
-
<p>
|
|
29
|
-
This is more or less the simplest TinyBase app you could imagine. It
|
|
30
|
-
contains a handful of key-values and tables which are mutated with the
|
|
31
|
-
buttons below. There's no fancy rendering - just the raw JSON of the
|
|
32
|
-
underlying store. See the full set of
|
|
33
|
-
<a href="https://tinybase.org/demos/">TinyBase demos</a> for more
|
|
34
|
-
interesting examples!
|
|
35
|
-
</p>
|
|
36
|
-
<div id="buttons">
|
|
37
|
-
<button id="countButton">Increment number</button>
|
|
38
|
-
<button id="randomButton">Random number</button>
|
|
39
|
-
<button id="addPetButton">Add a pet</button>
|
|
40
|
-
</div>
|
|
41
|
-
<details open>
|
|
42
|
-
<summary>Values</summary>
|
|
43
|
-
<pre id="valuesJson"></pre>
|
|
44
|
-
</details>
|
|
45
|
-
<details open>
|
|
46
|
-
<summary>Tables</summary>
|
|
47
|
-
<pre id="tablesJson"></pre>
|
|
48
|
-
</details>
|
|
49
|
-
</div>
|
|
50
|
-
{{/if}}
|
|
51
|
-
{{includeFile template="src/index.tsx.hbs" output="src/index.{{ext}}"}}
|
|
52
|
-
<script type="module" src="/src/index.{{ext}}"></script>
|
|
53
|
-
</body>
|
|
54
|
-
|
|
55
|
-
</html>
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import {StrictMode} from 'react';
|
|
2
|
-
import {createStore} from 'tinybase';
|
|
3
|
-
import {Provider, useCreateStore} from 'tinybase/ui-react';
|
|
4
|
-
import {SortedTableInHtmlTable, ValuesInHtmlTable} from 'tinybase/ui-react-dom';
|
|
5
|
-
import {Inspector} from 'tinybase/ui-react-inspector';
|
|
6
|
-
import {Buttons} from './Buttons';
|
|
7
|
-
|
|
8
|
-
export const App = () => {
|
|
9
|
-
const store = useCreateStore(() => {
|
|
10
|
-
// Create the TinyBase Store and initialize the Store's data
|
|
11
|
-
return createStore()
|
|
12
|
-
.setValue('counter', 0)
|
|
13
|
-
.setRow('pets', '0', {name: 'fido', species: 'dog'})
|
|
14
|
-
.setTable('species', {
|
|
15
|
-
dog: {price: 5},
|
|
16
|
-
cat: {price: 4},
|
|
17
|
-
fish: {price: 2},
|
|
18
|
-
worm: {price: 1},
|
|
19
|
-
parrot: {price: 3},
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<StrictMode>
|
|
25
|
-
<Provider store={store}>
|
|
26
|
-
<header>
|
|
27
|
-
<h1>
|
|
28
|
-
<img src="/favicon.svg" />
|
|
29
|
-
TinyBase / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}} + React
|
|
30
|
-
</h1>
|
|
31
|
-
</header>
|
|
32
|
-
<p>
|
|
33
|
-
This is more or less the simplest TinyBase app you could imagine. It
|
|
34
|
-
contains a handful of key-values and tables which are mutated with the
|
|
35
|
-
buttons below, and rendered with plain table components. See the full
|
|
36
|
-
set of <a href="https://tinybase.org/demos/">TinyBase demos</a> for
|
|
37
|
-
more interesting examples!
|
|
38
|
-
</p>
|
|
39
|
-
<Buttons />
|
|
40
|
-
<div>
|
|
41
|
-
<h2>Values</h2>
|
|
42
|
-
<ValuesInHtmlTable />
|
|
43
|
-
</div>
|
|
44
|
-
<div>
|
|
45
|
-
<h2>Pets Table</h2>
|
|
46
|
-
<SortedTableInHtmlTable tableId="pets" cellId="name" limit={5} sortOnClick={true} className="sortedTable" paginator={true} />
|
|
47
|
-
</div>
|
|
48
|
-
<Inspector />
|
|
49
|
-
</Provider>
|
|
50
|
-
</StrictMode>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {useAddRowCallback, useSetValueCallback} from 'tinybase/ui-react';
|
|
2
|
-
import type {ValueOrUndefined} from 'tinybase';
|
|
3
|
-
|
|
4
|
-
// Convenience function for generating a random integer
|
|
5
|
-
const getRandom = (max = 100) => Math.floor(Math.random() * max);
|
|
6
|
-
|
|
7
|
-
export const Buttons = () => {
|
|
8
|
-
// Attach events to the buttons to mutate the data in the TinyBase Store
|
|
9
|
-
const handleCount = useSetValueCallback(
|
|
10
|
-
'counter',
|
|
11
|
-
() => (value: ValueOrUndefined) => ((value ?? 0) as number) + 1,
|
|
12
|
-
);
|
|
13
|
-
const handleRandom = useSetValueCallback('random', () => getRandom());
|
|
14
|
-
const handleAddPet = useAddRowCallback('pets', (_, store) => ({
|
|
15
|
-
name: ['fido', 'felix', 'bubbles', 'lowly', 'polly'][getRandom(5)],
|
|
16
|
-
species: store.getRowIds('species')[getRandom(5)],
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div id="buttons">
|
|
21
|
-
<button onClick={handleCount}>Increment number</button>
|
|
22
|
-
<button onClick={handleRandom}>Random number</button>
|
|
23
|
-
<button onClick={handleAddPet}>Add a pet</button>
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
};
|
package/templates/src/app.ts.hbs
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import {createStore} from 'tinybase';
|
|
2
|
-
|
|
3
|
-
const onClick = (id: string, onClick: () => void) =>
|
|
4
|
-
document.getElementById(id)!.addEventListener('click', onClick);
|
|
5
|
-
|
|
6
|
-
const updateJson = (id: string, content: unknown) =>
|
|
7
|
-
(document.getElementById(id)!.innerText = JSON.stringify(content, null, 2));
|
|
8
|
-
|
|
9
|
-
const getRandom = (max = 100) => Math.floor(Math.random() * max);
|
|
10
|
-
|
|
11
|
-
export const app = () => {
|
|
12
|
-
const store = createStore();
|
|
13
|
-
|
|
14
|
-
onClick('countButton', () => store.setValue('counter', (value) => value + 1));
|
|
15
|
-
onClick('randomButton', () => store.setValue('random', getRandom()));
|
|
16
|
-
onClick('addPetButton', () =>
|
|
17
|
-
store.addRow('pets', {
|
|
18
|
-
name: ['fido', 'felix', 'bubbles', 'lowly', 'polly'][getRandom(5)],
|
|
19
|
-
species: store.getRowIds('species')[getRandom(5)],
|
|
20
|
-
}),
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
store.addValuesListener(() => updateJson('valuesJson', store.getValues()));
|
|
24
|
-
store.addTablesListener(() => updateJson('tablesJson', store.getTables()));
|
|
25
|
-
|
|
26
|
-
store
|
|
27
|
-
.setValue('counter', 0)
|
|
28
|
-
.setRow('pets', '0', {name: 'fido', species: 'dog'})
|
|
29
|
-
.setTable('species', {
|
|
30
|
-
dog: {price: 5},
|
|
31
|
-
cat: {price: 4},
|
|
32
|
-
fish: {price: 2},
|
|
33
|
-
worm: {price: 1},
|
|
34
|
-
parrot: {price: 3},
|
|
35
|
-
});
|
|
36
|
-
};
|