@korajs/cli 0.5.0 → 1.0.0-beta.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/LICENSE +21 -0
- package/dist/bin.cjs +2656 -207
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +152 -96
- package/dist/bin.js.map +1 -1
- package/dist/chunk-5NI2FEQL.js +92 -0
- package/dist/chunk-5NI2FEQL.js.map +1 -0
- package/dist/chunk-6C4BHSRA.js +82 -0
- package/dist/chunk-6C4BHSRA.js.map +1 -0
- package/dist/{chunk-3WNFM3QB.js → chunk-B5YS4STN.js} +78 -99
- package/dist/chunk-B5YS4STN.js.map +1 -0
- package/dist/{chunk-PSRM56B7.js → chunk-BWTKRKNJ.js} +60 -7
- package/dist/chunk-BWTKRKNJ.js.map +1 -0
- package/dist/{chunk-SOTZIWIF.js → chunk-EOWLAAIV.js} +5 -3
- package/dist/{chunk-SOTZIWIF.js.map → chunk-EOWLAAIV.js.map} +1 -1
- package/dist/create.cjs +82 -7
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +3 -2
- package/dist/create.js.map +1 -1
- package/dist/index.cjs +65 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/lab-manager-KUDII6BT.js +280 -0
- package/dist/lab-manager-KUDII6BT.js.map +1 -0
- package/dist/schema-loader-GGHECMTM.js +9 -0
- package/dist/schema-loader-GGHECMTM.js.map +1 -0
- package/dist/spectator-manager-E2LH2P54.js +142 -0
- package/dist/spectator-manager-E2LH2P54.js.map +1 -0
- package/dist/studio-server-NIFKZI4F.js +1738 -0
- package/dist/studio-server-NIFKZI4F.js.map +1 -0
- package/package.json +8 -6
- package/templates/react-basic/AGENTS.md +87 -0
- package/templates/react-sync/AGENTS.md +87 -0
- package/templates/react-sync/src/App.tsx +8 -0
- package/templates/react-sync/src/index.css +11 -0
- package/templates/react-tailwind/AGENTS.md +87 -0
- package/templates/react-tailwind-sync/AGENTS.md +87 -0
- package/templates/react-tailwind-sync/src/App.tsx +28 -0
- package/templates/svelte-basic/.env.example +3 -0
- package/templates/svelte-basic/AGENTS.md +76 -0
- package/templates/svelte-basic/README.md.hbs +62 -0
- package/templates/svelte-basic/index.html.hbs +12 -0
- package/templates/svelte-basic/kora.config.ts +12 -0
- package/templates/svelte-basic/package.json.hbs +27 -0
- package/templates/svelte-basic/src/App.svelte +127 -0
- package/templates/svelte-basic/src/Root.svelte +14 -0
- package/templates/svelte-basic/src/index.css +287 -0
- package/templates/svelte-basic/src/kora-worker.ts +9 -0
- package/templates/svelte-basic/src/main.ts +16 -0
- package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-basic/src/schema.ts +9 -0
- package/templates/svelte-basic/src/vite-env.d.ts +12 -0
- package/templates/svelte-basic/svelte.config.js +5 -0
- package/templates/svelte-basic/tsconfig.json.hbs +14 -0
- package/templates/svelte-basic/vite.config.ts +52 -0
- package/templates/svelte-sync/.env.example +25 -0
- package/templates/svelte-sync/AGENTS.md +76 -0
- package/templates/svelte-sync/README.md.hbs +105 -0
- package/templates/svelte-sync/index.html.hbs +12 -0
- package/templates/svelte-sync/kora.config.ts +17 -0
- package/templates/svelte-sync/package.json.hbs +43 -0
- package/templates/svelte-sync/server.ts +90 -0
- package/templates/svelte-sync/src/App.svelte +150 -0
- package/templates/svelte-sync/src/Root.svelte +22 -0
- package/templates/svelte-sync/src/auth.ts +25 -0
- package/templates/svelte-sync/src/index.css +339 -0
- package/templates/svelte-sync/src/kora-worker.ts +9 -0
- package/templates/svelte-sync/src/main.ts +32 -0
- package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-sync/src/schema.ts +9 -0
- package/templates/svelte-sync/src/vite-env.d.ts +21 -0
- package/templates/svelte-sync/svelte.config.js +5 -0
- package/templates/svelte-sync/tsconfig.json.hbs +14 -0
- package/templates/svelte-sync/vite.config.ts +79 -0
- package/templates/svelte-tailwind/AGENTS.md +76 -0
- package/templates/svelte-tailwind/README.md.hbs +62 -0
- package/templates/svelte-tailwind/index.html.hbs +12 -0
- package/templates/svelte-tailwind/kora.config.ts +12 -0
- package/templates/svelte-tailwind/package.json.hbs +30 -0
- package/templates/svelte-tailwind/src/App.svelte +75 -0
- package/templates/svelte-tailwind/src/Root.svelte +14 -0
- package/templates/svelte-tailwind/src/index.css +7 -0
- package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
- package/templates/svelte-tailwind/src/main.ts +16 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-tailwind/src/schema.ts +9 -0
- package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
- package/templates/svelte-tailwind/svelte.config.js +5 -0
- package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
- package/templates/svelte-tailwind/vite.config.ts +53 -0
- package/templates/svelte-tailwind-sync/.env.example +25 -0
- package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
- package/templates/svelte-tailwind-sync/README.md.hbs +105 -0
- package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
- package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
- package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
- package/templates/svelte-tailwind-sync/server.ts +90 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +192 -0
- package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
- package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
- package/templates/svelte-tailwind-sync/src/index.css +7 -0
- package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
- package/templates/svelte-tailwind-sync/src/main.ts +32 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
- package/templates/svelte-tailwind-sync/src/vite-env.d.ts +21 -0
- package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
- package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
- package/templates/svelte-tailwind-sync/vite.config.ts +80 -0
- package/templates/tauri-react/AGENTS.md +91 -0
- package/templates/vue-basic/AGENTS.md +84 -0
- package/templates/vue-basic/README.md.hbs +62 -0
- package/templates/vue-basic/index.html.hbs +12 -0
- package/templates/vue-basic/kora.config.ts +12 -0
- package/templates/vue-basic/package.json.hbs +26 -0
- package/templates/vue-basic/src/App.vue +117 -0
- package/templates/vue-basic/src/index.css +287 -0
- package/templates/vue-basic/src/kora-worker.ts +9 -0
- package/templates/vue-basic/src/main.ts +20 -0
- package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-basic/src/schema.ts +9 -0
- package/templates/vue-basic/src/vite-env.d.ts +17 -0
- package/templates/vue-basic/tsconfig.json.hbs +15 -0
- package/templates/vue-basic/vite.config.ts +69 -0
- package/templates/vue-sync/.env.example +25 -0
- package/templates/vue-sync/AGENTS.md +84 -0
- package/templates/vue-sync/README.md.hbs +105 -0
- package/templates/vue-sync/index.html.hbs +12 -0
- package/templates/vue-sync/kora.config.ts +17 -0
- package/templates/vue-sync/package.json.hbs +42 -0
- package/templates/vue-sync/server.ts +90 -0
- package/templates/vue-sync/src/App.vue +135 -0
- package/templates/vue-sync/src/auth.ts +25 -0
- package/templates/vue-sync/src/index.css +339 -0
- package/templates/vue-sync/src/kora-worker.ts +9 -0
- package/templates/vue-sync/src/main.ts +47 -0
- package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-sync/src/schema.ts +9 -0
- package/templates/vue-sync/src/vite-env.d.ts +26 -0
- package/templates/vue-sync/tsconfig.json.hbs +15 -0
- package/templates/vue-sync/vite.config.ts +79 -0
- package/templates/vue-tailwind/AGENTS.md +84 -0
- package/templates/vue-tailwind/README.md.hbs +62 -0
- package/templates/vue-tailwind/index.html.hbs +12 -0
- package/templates/vue-tailwind/kora.config.ts +12 -0
- package/templates/vue-tailwind/package.json.hbs +29 -0
- package/templates/vue-tailwind/src/App.vue +135 -0
- package/templates/vue-tailwind/src/index.css +7 -0
- package/templates/vue-tailwind/src/kora-worker.ts +9 -0
- package/templates/vue-tailwind/src/main.ts +31 -0
- package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-tailwind/src/schema.ts +9 -0
- package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
- package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
- package/templates/vue-tailwind/vite.config.ts +70 -0
- package/templates/vue-tailwind-sync/.env.example +25 -0
- package/templates/vue-tailwind-sync/AGENTS.md +84 -0
- package/templates/vue-tailwind-sync/README.md.hbs +105 -0
- package/templates/vue-tailwind-sync/index.html.hbs +12 -0
- package/templates/vue-tailwind-sync/kora.config.ts +17 -0
- package/templates/vue-tailwind-sync/package.json.hbs +45 -0
- package/templates/vue-tailwind-sync/server.ts +90 -0
- package/templates/vue-tailwind-sync/src/App.vue +245 -0
- package/templates/vue-tailwind-sync/src/auth.ts +25 -0
- package/templates/vue-tailwind-sync/src/index.css +7 -0
- package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
- package/templates/vue-tailwind-sync/src/main.ts +56 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-tailwind-sync/src/schema.ts +9 -0
- package/templates/vue-tailwind-sync/src/vite-env.d.ts +26 -0
- package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
- package/templates/vue-tailwind-sync/vite.config.ts +80 -0
- package/dist/chunk-3WNFM3QB.js.map +0 -1
- package/dist/chunk-PSRM56B7.js.map +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **App**: http://localhost:5173
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
schema.ts # Schema entry point
|
|
18
|
+
main.tsx # App initialization and Kora setup
|
|
19
|
+
App.tsx # Main application component
|
|
20
|
+
modules/
|
|
21
|
+
todos/
|
|
22
|
+
todo.schema.ts # Todo collection definition
|
|
23
|
+
todo.queries.ts # Reusable todo query builders
|
|
24
|
+
todo.mutations.ts # Todo mutation helpers
|
|
25
|
+
useTodos.ts # React hook connecting todos to the UI
|
|
26
|
+
kora-worker.ts # SQLite WASM web worker
|
|
27
|
+
index.css # Styles
|
|
28
|
+
kora.config.ts # Kora configuration
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In a feature module, `schema` defines data shape, `queries` contain reads only, `mutations` contain
|
|
32
|
+
writes, and `useTodos.ts` is the React binding that exposes todo data and actions to components.
|
|
33
|
+
|
|
34
|
+
## Scripts
|
|
35
|
+
|
|
36
|
+
| Script | Description |
|
|
37
|
+
|--------|-------------|
|
|
38
|
+
| `npm run dev` | Start dev server |
|
|
39
|
+
| `npm run build` | Build for production |
|
|
40
|
+
| `npm run preview` | Preview production build |
|
|
41
|
+
|
|
42
|
+
## Adding Sync
|
|
43
|
+
|
|
44
|
+
To enable real-time sync across devices, update `src/main.tsx`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const app = createApp({
|
|
48
|
+
schema,
|
|
49
|
+
sync: {
|
|
50
|
+
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
|
|
51
|
+
},
|
|
52
|
+
// ...
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then create a `server.ts` and install `@korajs/server`. See the [Sync Configuration guide](https://ehoneahobed.github.io/kora/guide/sync-configuration).
|
|
57
|
+
|
|
58
|
+
## Learn More
|
|
59
|
+
|
|
60
|
+
- [Documentation](https://ehoneahobed.github.io/kora/)
|
|
61
|
+
- [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
|
|
62
|
+
- [Schema Design](https://ehoneahobed.github.io/kora/guide/schema-design)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "kora dev",
|
|
8
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"korajs": "{{koraVersion}}",
|
|
13
|
+
"@korajs/vue": "{{koraVersion}}",
|
|
14
|
+
"@korajs/store": "{{koraVersion}}",
|
|
15
|
+
"@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
|
|
16
|
+
"lucide-vue-next": "^0.468.0",
|
|
17
|
+
"vue": "^3.5.0",
|
|
18
|
+
"yjs": "^13.6.30"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@korajs/cli": "{{koraVersion}}",
|
|
22
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
23
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
24
|
+
"tailwindcss": "^4.0.0",
|
|
25
|
+
"typescript": "^5.7.0",
|
|
26
|
+
"vite": "^6.0.0",
|
|
27
|
+
"vue-tsc": "^2.2.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { CheckCircle2, Circle, ClipboardList, Loader2, Plus, Trash2 } from 'lucide-vue-next'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
import { useTodos } from './modules/todos/useTodos'
|
|
5
|
+
|
|
6
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
7
|
+
|
|
8
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
|
|
9
|
+
|
|
10
|
+
const filter = ref<Filter>('all')
|
|
11
|
+
const input = ref('')
|
|
12
|
+
|
|
13
|
+
const filteredTodos = computed(() => {
|
|
14
|
+
if (filter.value === 'active') return activeTodos.value
|
|
15
|
+
if (filter.value === 'completed') return completedTodos.value
|
|
16
|
+
return allTodos.value
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function handleSubmit(): void {
|
|
20
|
+
const title = input.value.trim()
|
|
21
|
+
if (!title) return
|
|
22
|
+
addTodo.mutate({ title })
|
|
23
|
+
input.value = ''
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function clearCompleted(): void {
|
|
27
|
+
for (const todo of completedTodos.value) {
|
|
28
|
+
deleteTodo.mutate(todo.id)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emptyMessage(f: Filter): string {
|
|
33
|
+
if (f === 'all') return 'No tasks yet. Add one above!'
|
|
34
|
+
if (f === 'active') return 'All caught up! No active tasks.'
|
|
35
|
+
return 'No completed tasks yet.'
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div class="min-h-screen bg-gray-950 text-gray-100">
|
|
41
|
+
<div class="mx-auto max-w-2xl px-4 py-12">
|
|
42
|
+
<div class="mb-8 flex items-center gap-3">
|
|
43
|
+
<ClipboardList class="h-8 w-8 text-indigo-400" />
|
|
44
|
+
<h1 class="text-2xl font-bold">My Tasks</h1>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="mb-8 grid grid-cols-3 gap-4">
|
|
48
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
49
|
+
<p class="text-sm text-gray-500">Total</p>
|
|
50
|
+
<p class="text-2xl font-bold text-gray-300">{{ allTodos.length }}</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
53
|
+
<p class="text-sm text-gray-500">Remaining</p>
|
|
54
|
+
<p class="text-2xl font-bold text-amber-400">{{ activeTodos.length }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
57
|
+
<p class="text-sm text-gray-500">Done</p>
|
|
58
|
+
<p class="text-2xl font-bold text-emerald-400">{{ completedTodos.length }}</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<form class="mb-8 flex gap-3" @submit.prevent="handleSubmit">
|
|
63
|
+
<input
|
|
64
|
+
v-model="input"
|
|
65
|
+
type="text"
|
|
66
|
+
placeholder="What needs to be done?"
|
|
67
|
+
class="flex-1 rounded-lg border border-gray-700 bg-gray-900 px-4 py-3 text-gray-100 placeholder-gray-500 outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
|
|
68
|
+
/>
|
|
69
|
+
<button
|
|
70
|
+
type="submit"
|
|
71
|
+
:disabled="addTodo.isLoading.value || !input.trim()"
|
|
72
|
+
class="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white transition hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-50"
|
|
73
|
+
>
|
|
74
|
+
<Loader2 v-if="addTodo.isLoading.value" class="h-4 w-4 animate-spin" />
|
|
75
|
+
<Plus v-else class="h-4 w-4" />
|
|
76
|
+
Add
|
|
77
|
+
</button>
|
|
78
|
+
</form>
|
|
79
|
+
|
|
80
|
+
<div class="mb-6 flex gap-2">
|
|
81
|
+
<button
|
|
82
|
+
v-for="f in (['all', 'active', 'completed'] as const)"
|
|
83
|
+
:key="f"
|
|
84
|
+
type="button"
|
|
85
|
+
:class="[
|
|
86
|
+
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition',
|
|
87
|
+
filter === f
|
|
88
|
+
? 'bg-indigo-600 text-white'
|
|
89
|
+
: 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200',
|
|
90
|
+
]"
|
|
91
|
+
@click="filter = f"
|
|
92
|
+
>
|
|
93
|
+
{{ f.charAt(0).toUpperCase() + f.slice(1) }}
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="space-y-2">
|
|
98
|
+
<div
|
|
99
|
+
v-if="filteredTodos.length === 0"
|
|
100
|
+
class="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600"
|
|
101
|
+
>
|
|
102
|
+
{{ emptyMessage(filter) }}
|
|
103
|
+
</div>
|
|
104
|
+
<template v-else>
|
|
105
|
+
<div
|
|
106
|
+
v-for="todo in filteredTodos"
|
|
107
|
+
:key="todo.id"
|
|
108
|
+
class="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3"
|
|
109
|
+
>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
class="shrink-0 text-gray-500 hover:text-indigo-400"
|
|
113
|
+
@click="toggleTodo.mutate(todo.id, { completed: !todo.completed })"
|
|
114
|
+
>
|
|
115
|
+
<CheckCircle2 v-if="todo.completed" class="h-5 w-5 text-emerald-400" />
|
|
116
|
+
<Circle v-else class="h-5 w-5" />
|
|
117
|
+
</button>
|
|
118
|
+
<span :class="['flex-1', todo.completed ? 'line-through text-gray-500' : '']">
|
|
119
|
+
{{ String(todo.title) }}
|
|
120
|
+
</span>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="text-gray-600 opacity-0 group-hover:opacity-100 hover:text-red-400"
|
|
124
|
+
@click="deleteTodo.mutate(todo.id)"
|
|
125
|
+
>
|
|
126
|
+
<Trash2 class="h-4 w-4" />
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<p class="mt-12 text-center text-xs text-gray-700">Powered by Kora — offline-first</p>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Web Worker entry point for SQLite WASM.
|
|
2
|
+
// Loaded automatically by the Kora store to run SQLite in a background thread.
|
|
3
|
+
|
|
4
|
+
// Import the WASM binary URL so Vite resolves it with the correct content hash.
|
|
5
|
+
// Without this, production builds fail because sqlite3 looks for the unhashed filename.
|
|
6
|
+
import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
|
|
7
|
+
;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
|
|
8
|
+
|
|
9
|
+
import '@korajs/store/sqlite-wasm/worker'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { KoraProvider } from '@korajs/vue'
|
|
2
|
+
import { createApp as createKoraApp } from 'korajs'
|
|
3
|
+
import { createApp, h } from 'vue'
|
|
4
|
+
import App from './App.vue'
|
|
5
|
+
import schema from './schema'
|
|
6
|
+
import './index.css'
|
|
7
|
+
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
8
|
+
|
|
9
|
+
const kora = createKoraApp({
|
|
10
|
+
schema,
|
|
11
|
+
store: {
|
|
12
|
+
workerUrl: koraWorkerUrl,
|
|
13
|
+
},
|
|
14
|
+
devtools: true,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
createApp({
|
|
18
|
+
render: () =>
|
|
19
|
+
h(
|
|
20
|
+
KoraProvider,
|
|
21
|
+
{
|
|
22
|
+
app: kora,
|
|
23
|
+
fallback: h(
|
|
24
|
+
'div',
|
|
25
|
+
{ class: 'flex h-screen items-center justify-center bg-gray-950 text-gray-400' },
|
|
26
|
+
'Loading...',
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
{ default: () => h(App) },
|
|
30
|
+
),
|
|
31
|
+
}).mount('#app')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CollectionAccessor } from 'korajs'
|
|
2
|
+
|
|
3
|
+
export interface CreateTodoInput {
|
|
4
|
+
title: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UpdateTodoStatusInput {
|
|
8
|
+
completed: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createTodo(todos: CollectionAccessor, data: CreateTodoInput) {
|
|
12
|
+
return todos.insert({ title: data.title })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function updateTodoStatus(
|
|
16
|
+
todos: CollectionAccessor,
|
|
17
|
+
id: string,
|
|
18
|
+
data: UpdateTodoStatusInput,
|
|
19
|
+
) {
|
|
20
|
+
return todos.update(id, { completed: data.completed })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function deleteTodo(todos: CollectionAccessor, id: string) {
|
|
24
|
+
return todos.delete(id)
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCollection, useMutation, useQuery } from '@korajs/vue'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
type CreateTodoInput,
|
|
5
|
+
type UpdateTodoStatusInput,
|
|
6
|
+
createTodo,
|
|
7
|
+
deleteTodo,
|
|
8
|
+
updateTodoStatus,
|
|
9
|
+
} from './todo.mutations'
|
|
10
|
+
import { orderedTodos } from './todo.queries'
|
|
11
|
+
|
|
12
|
+
export function useTodos() {
|
|
13
|
+
const todos = useCollection('todos')
|
|
14
|
+
const allTodos = useQuery(orderedTodos(todos))
|
|
15
|
+
const addTodo = useMutation((data: CreateTodoInput) => createTodo(todos, data))
|
|
16
|
+
const toggleTodo = useMutation((id: string, data: UpdateTodoStatusInput) =>
|
|
17
|
+
updateTodoStatus(todos, id, data),
|
|
18
|
+
)
|
|
19
|
+
const removeTodo = useMutation((id: string) => deleteTodo(todos, id))
|
|
20
|
+
|
|
21
|
+
const activeTodos = computed(() => allTodos.value.filter((todo) => !todo.completed))
|
|
22
|
+
const completedTodos = computed(() => allTodos.value.filter((todo) => !!todo.completed))
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
allTodos,
|
|
26
|
+
activeTodos,
|
|
27
|
+
completedTodos,
|
|
28
|
+
addTodo,
|
|
29
|
+
toggleTodo,
|
|
30
|
+
deleteTodo: removeTodo,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
declare module '*.vue' {
|
|
4
|
+
import type { DefineComponent } from 'vue'
|
|
5
|
+
const component: DefineComponent<object, object, unknown>
|
|
6
|
+
export default component
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module '*?worker&url' {
|
|
10
|
+
const url: string
|
|
11
|
+
export default url
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module '*?url' {
|
|
15
|
+
const url: string
|
|
16
|
+
export default url
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"jsx": "preserve",
|
|
11
|
+
"lib": ["ES2022", "DOM"],
|
|
12
|
+
"types": ["vite/client"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts", "src/**/*.vue"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import vue from '@vitejs/plugin-vue'
|
|
5
|
+
import type { Plugin } from 'vite'
|
|
6
|
+
import { defineConfig } from 'vite'
|
|
7
|
+
|
|
8
|
+
function crossOriginIsolation(): Plugin {
|
|
9
|
+
return {
|
|
10
|
+
name: 'cross-origin-isolation',
|
|
11
|
+
configureServer(server) {
|
|
12
|
+
server.middlewares.use((_req, res, next) => {
|
|
13
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
14
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
|
|
15
|
+
next()
|
|
16
|
+
})
|
|
17
|
+
},
|
|
18
|
+
configurePreviewServer(server) {
|
|
19
|
+
server.middlewares.use((_req, res, next) => {
|
|
20
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
21
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
|
|
22
|
+
next()
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sqliteWasmHotfix(): Plugin {
|
|
29
|
+
return {
|
|
30
|
+
name: 'sqlite-wasm-hotfix',
|
|
31
|
+
apply: 'build',
|
|
32
|
+
closeBundle() {
|
|
33
|
+
const assetsDir = resolve('dist', 'assets')
|
|
34
|
+
if (!existsSync(assetsDir)) return
|
|
35
|
+
|
|
36
|
+
for (const file of readdirSync(assetsDir)) {
|
|
37
|
+
if (/^sqlite3-.+\.wasm$/.test(file)) {
|
|
38
|
+
copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const proxyFile = resolve(
|
|
44
|
+
'node_modules',
|
|
45
|
+
'@sqlite.org',
|
|
46
|
+
'sqlite-wasm',
|
|
47
|
+
'sqlite-wasm',
|
|
48
|
+
'jswasm',
|
|
49
|
+
'sqlite3-opfs-async-proxy.js',
|
|
50
|
+
)
|
|
51
|
+
if (existsSync(proxyFile)) {
|
|
52
|
+
copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default defineConfig({
|
|
59
|
+
plugins: [vue(), tailwindcss(), crossOriginIsolation(), sqliteWasmHotfix()],
|
|
60
|
+
worker: {
|
|
61
|
+
format: 'es',
|
|
62
|
+
},
|
|
63
|
+
optimizeDeps: {
|
|
64
|
+
exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
|
|
65
|
+
include: ['yjs'],
|
|
66
|
+
},
|
|
67
|
+
resolve: {
|
|
68
|
+
dedupe: ['yjs'],
|
|
69
|
+
},
|
|
70
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Kora Sync Server
|
|
2
|
+
# WebSocket URL for the sync server (used by the client)
|
|
3
|
+
VITE_SYNC_URL=ws://localhost:3001
|
|
4
|
+
|
|
5
|
+
# Sync server port
|
|
6
|
+
PORT=3001
|
|
7
|
+
|
|
8
|
+
# Server database
|
|
9
|
+
# Leave DATABASE_URL empty to use SQLite at KORA_SERVER_DB.
|
|
10
|
+
KORA_SERVER_DB=./kora-server.db
|
|
11
|
+
DATABASE_URL=
|
|
12
|
+
|
|
13
|
+
# Operational endpoints (/__kora, /__kora/status, /__kora/events, /__kora/metrics, backups)
|
|
14
|
+
# Set these in production. Use Authorization: Bearer <token> when calling protected endpoints.
|
|
15
|
+
KORA_ADMIN_TOKEN=
|
|
16
|
+
KORA_METRICS_TOKEN=
|
|
17
|
+
KORA_BACKUP_TOKEN=
|
|
18
|
+
|
|
19
|
+
# Optional sync path
|
|
20
|
+
KORA_SYNC_PATH=/kora-sync
|
|
21
|
+
|
|
22
|
+
# Auth
|
|
23
|
+
# Set this to enable built-in /auth/* routes and authenticated sync.
|
|
24
|
+
# Generate with: node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
|
|
25
|
+
KORA_AUTH_SECRET=
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this project. Humans: this is useful for you too.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
|
|
8
|
+
|
|
9
|
+
## Golden rules
|
|
10
|
+
|
|
11
|
+
1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
|
|
12
|
+
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
|
+
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
|
+
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls swallow errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
|
+
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
|
+
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
|
+
|
|
19
|
+
## Data API cheat sheet
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await app.ready
|
|
23
|
+
const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
|
|
24
|
+
await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
|
|
25
|
+
await app.todos.delete(rec.id)
|
|
26
|
+
const one = await app.todos.findById(rec.id)
|
|
27
|
+
const unsubscribe = app.todos
|
|
28
|
+
.where({ completed: false })
|
|
29
|
+
.orderBy('createdAt', 'desc')
|
|
30
|
+
.subscribe((rows) => {/* fires immediately, then on every change */})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Schema example (`src/schema.ts`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineSchema, t } from 'korajs'
|
|
37
|
+
|
|
38
|
+
export default defineSchema({
|
|
39
|
+
version: 1,
|
|
40
|
+
collections: {
|
|
41
|
+
todos: {
|
|
42
|
+
fields: {
|
|
43
|
+
title: t.string(),
|
|
44
|
+
completed: t.boolean().default(false),
|
|
45
|
+
tags: t.array(t.string()).default([]),
|
|
46
|
+
priority: t.enum(['low', 'medium', 'high']).default('medium'),
|
|
47
|
+
createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
|
|
48
|
+
},
|
|
49
|
+
indexes: ['completed', 'createdAt'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you bump collections in a way that changes shapes, increment `version` and run `npx kora migrate`.
|
|
56
|
+
|
|
57
|
+
## Project conventions
|
|
58
|
+
|
|
59
|
+
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, add-wins for arrays). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
|
+
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
- `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
|
|
66
|
+
- `npm run build` type-checks and builds for production.
|
|
67
|
+
- `npx kora doctor` diagnoses a broken setup.
|
|
68
|
+
- DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
|
|
69
|
+
|
|
70
|
+
## Sync and auth
|
|
71
|
+
|
|
72
|
+
Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })`. In dev, the sync URL is derived from the page host and proxied by Vite; in production set `VITE_SYNC_URL`. Auth (if present in this template) uses `@korajs/auth`; the client is created in `src/auth.ts`. Local writes work without sign-in; sync requires the server to accept the connection.
|
|
73
|
+
|
|
74
|
+
## Vue bindings
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/vue'
|
|
78
|
+
|
|
79
|
+
const todos = useCollection('todos')
|
|
80
|
+
const rows = useQuery(orderedTodos(todos)) // readonly reactive ref
|
|
81
|
+
const addTodo = useMutation((data) => todos.insert(data))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Rules: always surface the mutation's error state in the UI. `useQuery` reads synchronously from the local store, so avoid loading spinners for local data. The Kora context is provided by the `KoraProvider` component wired in `src/main.ts`.
|