@korajs/cli 0.5.0 → 0.6.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/dist/bin.cjs +76 -7
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/{chunk-PSRM56B7.js → chunk-EEZNRI5W.js} +51 -6
- package/dist/chunk-EEZNRI5W.js.map +1 -0
- package/dist/{chunk-SOTZIWIF.js → chunk-Q2FBCOQD.js} +2 -2
- package/dist/{chunk-3WNFM3QB.js → chunk-VLTPEATY.js} +70 -4
- package/dist/chunk-VLTPEATY.js.map +1 -0
- package/dist/create.cjs +76 -7
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- 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 +2 -2
- package/package.json +6 -6
- package/templates/svelte-basic/.env.example +3 -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 +131 -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/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 +154 -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/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 +79 -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/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 +196 -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/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/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 +43 -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/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 +142 -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/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
- /package/dist/{chunk-SOTZIWIF.js.map → chunk-Q2FBCOQD.js.map} +0 -0
|
@@ -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,142 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
CheckCircle2,
|
|
4
|
+
Circle,
|
|
5
|
+
ClipboardList,
|
|
6
|
+
Loader2,
|
|
7
|
+
Plus,
|
|
8
|
+
Trash2,
|
|
9
|
+
} from 'lucide-vue-next'
|
|
10
|
+
import { computed, ref } from 'vue'
|
|
11
|
+
import { useTodos } from './modules/todos/useTodos'
|
|
12
|
+
|
|
13
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
14
|
+
|
|
15
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
|
|
16
|
+
|
|
17
|
+
const filter = ref<Filter>('all')
|
|
18
|
+
const input = ref('')
|
|
19
|
+
|
|
20
|
+
const filteredTodos = computed(() => {
|
|
21
|
+
if (filter.value === 'active') return activeTodos.value
|
|
22
|
+
if (filter.value === 'completed') return completedTodos.value
|
|
23
|
+
return allTodos.value
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function handleSubmit(): void {
|
|
27
|
+
const title = input.value.trim()
|
|
28
|
+
if (!title) return
|
|
29
|
+
addTodo.mutate({ title })
|
|
30
|
+
input.value = ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function clearCompleted(): void {
|
|
34
|
+
for (const todo of completedTodos.value) {
|
|
35
|
+
deleteTodo.mutate(todo.id)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function emptyMessage(f: Filter): string {
|
|
40
|
+
if (f === 'all') return 'No tasks yet. Add one above!'
|
|
41
|
+
if (f === 'active') return 'All caught up! No active tasks.'
|
|
42
|
+
return 'No completed tasks yet.'
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div class="min-h-screen bg-gray-950 text-gray-100">
|
|
48
|
+
<div class="mx-auto max-w-2xl px-4 py-12">
|
|
49
|
+
<div class="mb-8 flex items-center gap-3">
|
|
50
|
+
<ClipboardList class="h-8 w-8 text-indigo-400" />
|
|
51
|
+
<h1 class="text-2xl font-bold">My Tasks</h1>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="mb-8 grid grid-cols-3 gap-4">
|
|
55
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
56
|
+
<p class="text-sm text-gray-500">Total</p>
|
|
57
|
+
<p class="text-2xl font-bold text-gray-300">{{ allTodos.length }}</p>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
60
|
+
<p class="text-sm text-gray-500">Remaining</p>
|
|
61
|
+
<p class="text-2xl font-bold text-amber-400">{{ activeTodos.length }}</p>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
64
|
+
<p class="text-sm text-gray-500">Done</p>
|
|
65
|
+
<p class="text-2xl font-bold text-emerald-400">{{ completedTodos.length }}</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<form class="mb-8 flex gap-3" @submit.prevent="handleSubmit">
|
|
70
|
+
<input
|
|
71
|
+
v-model="input"
|
|
72
|
+
type="text"
|
|
73
|
+
placeholder="What needs to be done?"
|
|
74
|
+
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"
|
|
75
|
+
/>
|
|
76
|
+
<button
|
|
77
|
+
type="submit"
|
|
78
|
+
:disabled="addTodo.isLoading.value || !input.trim()"
|
|
79
|
+
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"
|
|
80
|
+
>
|
|
81
|
+
<Loader2 v-if="addTodo.isLoading.value" class="h-4 w-4 animate-spin" />
|
|
82
|
+
<Plus v-else class="h-4 w-4" />
|
|
83
|
+
Add
|
|
84
|
+
</button>
|
|
85
|
+
</form>
|
|
86
|
+
|
|
87
|
+
<div class="mb-6 flex gap-2">
|
|
88
|
+
<button
|
|
89
|
+
v-for="f in (['all', 'active', 'completed'] as const)"
|
|
90
|
+
:key="f"
|
|
91
|
+
type="button"
|
|
92
|
+
:class="[
|
|
93
|
+
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition',
|
|
94
|
+
filter === f
|
|
95
|
+
? 'bg-indigo-600 text-white'
|
|
96
|
+
: 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200',
|
|
97
|
+
]"
|
|
98
|
+
@click="filter = f"
|
|
99
|
+
>
|
|
100
|
+
{{ f.charAt(0).toUpperCase() + f.slice(1) }}
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="space-y-2">
|
|
105
|
+
<div
|
|
106
|
+
v-if="filteredTodos.length === 0"
|
|
107
|
+
class="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600"
|
|
108
|
+
>
|
|
109
|
+
{{ emptyMessage(filter) }}
|
|
110
|
+
</div>
|
|
111
|
+
<template v-else>
|
|
112
|
+
<div
|
|
113
|
+
v-for="todo in filteredTodos"
|
|
114
|
+
:key="todo.id"
|
|
115
|
+
class="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3"
|
|
116
|
+
>
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
class="shrink-0 text-gray-500 hover:text-indigo-400"
|
|
120
|
+
@click="toggleTodo.mutate(todo.id, { completed: !todo.completed })"
|
|
121
|
+
>
|
|
122
|
+
<CheckCircle2 v-if="todo.completed" class="h-5 w-5 text-emerald-400" />
|
|
123
|
+
<Circle v-else class="h-5 w-5" />
|
|
124
|
+
</button>
|
|
125
|
+
<span :class="['flex-1', todo.completed ? 'line-through text-gray-500' : '']">
|
|
126
|
+
{{ String(todo.title) }}
|
|
127
|
+
</span>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
class="text-gray-600 opacity-0 group-hover:opacity-100 hover:text-red-400"
|
|
131
|
+
@click="deleteTodo.mutate(todo.id)"
|
|
132
|
+
>
|
|
133
|
+
<Trash2 class="h-4 w-4" />
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
</template>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<p class="mt-12 text-center text-xs text-gray-700">Powered by Kora — offline-first</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</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 { computed } from 'vue'
|
|
2
|
+
import { useCollection, useMutation, useQuery } from '@korajs/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,105 @@
|
|
|
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
|
+
This starts both the Vite dev server and the Kora sync server.
|
|
12
|
+
|
|
13
|
+
- **App**: http://localhost:5173
|
|
14
|
+
- **Sync server**: ws://localhost:3001
|
|
15
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
schema.ts # Schema entry point
|
|
21
|
+
auth.ts # Auth client and OAuth callback handling
|
|
22
|
+
main.tsx # App initialization and Kora setup
|
|
23
|
+
App.tsx # Main application component
|
|
24
|
+
modules/
|
|
25
|
+
todos/
|
|
26
|
+
todo.schema.ts # Todo collection definition
|
|
27
|
+
todo.queries.ts # Reusable todo query builders
|
|
28
|
+
todo.mutations.ts # Todo mutation helpers
|
|
29
|
+
useTodos.ts # React hook connecting todos to the UI
|
|
30
|
+
kora-worker.ts # SQLite WASM web worker
|
|
31
|
+
index.css # Styles
|
|
32
|
+
server.ts # Kora sync server
|
|
33
|
+
kora.config.ts # Kora configuration
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In a feature module, `schema` defines data shape, `queries` contain reads only, `mutations` contain
|
|
37
|
+
writes, and `useTodos.ts` is the React binding that exposes todo data and actions to components.
|
|
38
|
+
|
|
39
|
+
## Environment Variables
|
|
40
|
+
|
|
41
|
+
| Variable | Default | Description |
|
|
42
|
+
|----------|---------|-------------|
|
|
43
|
+
| `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
|
|
44
|
+
| `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
|
|
45
|
+
| `PORT` | `3001` | Sync server port |
|
|
46
|
+
| `KORA_SERVER_DB` | `./kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
|
|
47
|
+
| `KORA_AUTH_DB` | `./kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
|
|
48
|
+
| `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
|
|
49
|
+
| `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
|
|
50
|
+
| `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
|
|
51
|
+
| `KORA_GOOGLE_CLIENT_ID` | (none) | Enables Google OAuth when set with `KORA_GOOGLE_REDIRECT_URI` |
|
|
52
|
+
| `KORA_GOOGLE_CLIENT_SECRET` | (none) | Google OAuth client secret for confidential web clients |
|
|
53
|
+
| `KORA_GOOGLE_REDIRECT_URI` | (none) | OAuth callback URL, usually your app URL that handles `code` and `state` |
|
|
54
|
+
| `KORA_OAUTH_PKCE` | `true` when no client secret | Enables PKCE for public desktop/mobile OAuth clients |
|
|
55
|
+
| `KORA_ADMIN_TOKEN` | (none) | Protects `/__kora`, `/__kora/status`, and `/__kora/events` |
|
|
56
|
+
| `KORA_METRICS_TOKEN` | (none) | Protects `/__kora/metrics` |
|
|
57
|
+
| `KORA_BACKUP_TOKEN` | (none) | Protects server backup and restore endpoints |
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
Set `KORA_AUTH_SECRET` to enable built-in auth on the sync server. Generate a secret with:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The template already creates `src/auth.ts`, wraps the app in `AuthProvider`, and wires sync with `createKoraAuthSync({ authClient, schema })`. To enable Google OAuth, set:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
KORA_AUTH_SECRET=...
|
|
71
|
+
KORA_GOOGLE_CLIENT_ID=...
|
|
72
|
+
KORA_GOOGLE_CLIENT_SECRET=...
|
|
73
|
+
KORA_GOOGLE_REDIRECT_URI=http://localhost:5173
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `authClient.signInWithOAuth('google')` for web redirect sign-in, or `authClient.getOAuthAuthorizationUrl('google')` plus `authClient.completeOAuthSignIn('google', { code, state })` for custom handoff flows. See the [Authentication Guide](https://ehoneahobed.github.io/kora/guide/authentication).
|
|
77
|
+
|
|
78
|
+
## Scripts
|
|
79
|
+
|
|
80
|
+
| Script | Description |
|
|
81
|
+
|--------|-------------|
|
|
82
|
+
| `npm run dev` | Start dev server + sync server |
|
|
83
|
+
| `npm run dev:server` | Start sync server only |
|
|
84
|
+
| `npm run build` | Build for production |
|
|
85
|
+
| `npm run preview` | Preview production build |
|
|
86
|
+
|
|
87
|
+
## Deployment
|
|
88
|
+
|
|
89
|
+
The client and sync server must be deployed separately:
|
|
90
|
+
|
|
91
|
+
- **Client**: Any static host (Vercel, Netlify, Cloudflare Pages)
|
|
92
|
+
- **Sync server**: Any Node.js host with WebSocket support (Railway, Render, Fly.io)
|
|
93
|
+
|
|
94
|
+
Set `VITE_SYNC_URL` to your deployed sync server URL when building:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
VITE_SYNC_URL=wss://your-server.example.com npm run build
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Learn More
|
|
101
|
+
|
|
102
|
+
- [Documentation](https://ehoneahobed.github.io/kora/)
|
|
103
|
+
- [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
|
|
104
|
+
- [Sync Configuration](https://ehoneahobed.github.io/kora/guide/sync-configuration)
|
|
105
|
+
- [Deployment Guide](https://ehoneahobed.github.io/kora/guide/deployment)
|
|
@@ -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,17 @@
|
|
|
1
|
+
import { defineConfig } from 'korajs/config'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
schema: './src/schema.ts',
|
|
5
|
+
dev: {
|
|
6
|
+
port: 5173,
|
|
7
|
+
sync: {
|
|
8
|
+
enabled: true,
|
|
9
|
+
port: 3001,
|
|
10
|
+
store: 'sqlite',
|
|
11
|
+
},
|
|
12
|
+
watch: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
debounceMs: 300,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "kora dev",
|
|
8
|
+
"dev:server": "tsx server.ts",
|
|
9
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
10
|
+
"start": "node --import tsx server.ts",
|
|
11
|
+
"preview": "vite preview"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"korajs": "{{koraVersion}}",
|
|
15
|
+
"@korajs/auth": "{{koraVersion}}",
|
|
16
|
+
"@korajs/vue": "{{koraVersion}}",
|
|
17
|
+
"@korajs/store": "{{koraVersion}}",
|
|
18
|
+
"@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
|
|
19
|
+
"lucide-vue-next": "^0.468.0",
|
|
20
|
+
"vue": "^3.5.0",
|
|
21
|
+
"ws": "^8.18.0",
|
|
22
|
+
"yjs": "^13.6.30"
|
|
23
|
+
},
|
|
24
|
+
"pnpm": {
|
|
25
|
+
"onlyBuiltDependencies": [
|
|
26
|
+
"better-sqlite3",
|
|
27
|
+
"esbuild",
|
|
28
|
+
"protobufjs"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@korajs/cli": "{{koraVersion}}",
|
|
33
|
+
"@korajs/server": "{{koraVersion}}",
|
|
34
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
35
|
+
"@types/ws": "^8.5.0",
|
|
36
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
37
|
+
"better-sqlite3": "^12.9.0",
|
|
38
|
+
"postgres": "^3.4.0",
|
|
39
|
+
"tailwindcss": "^4.0.0",
|
|
40
|
+
"tsx": "^4.19.0",
|
|
41
|
+
"typescript": "^5.7.0",
|
|
42
|
+
"vite": "^6.0.0",
|
|
43
|
+
"vue-tsc": "^2.2.0"
|
|
44
|
+
}
|
|
45
|
+
}
|