@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,196 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createAuthStore } from '@korajs/auth/svelte'
|
|
3
|
+
import { createSyncStatusStore } from '@korajs/svelte'
|
|
4
|
+
import {
|
|
5
|
+
AlertCircle,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
Circle,
|
|
8
|
+
ClipboardList,
|
|
9
|
+
Loader2,
|
|
10
|
+
Plus,
|
|
11
|
+
Trash2,
|
|
12
|
+
Wifi,
|
|
13
|
+
WifiOff,
|
|
14
|
+
} from '@lucide/svelte'
|
|
15
|
+
import { createTodosStores } from './modules/todos/useTodos'
|
|
16
|
+
|
|
17
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
18
|
+
|
|
19
|
+
const syncStatus = createSyncStatusStore()
|
|
20
|
+
const auth = createAuthStore()
|
|
21
|
+
const todos = createTodosStores()
|
|
22
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = todos
|
|
23
|
+
|
|
24
|
+
let filter = $state<Filter>('all')
|
|
25
|
+
let input = $state('')
|
|
26
|
+
|
|
27
|
+
const filteredTodos = $derived(
|
|
28
|
+
filter === 'active'
|
|
29
|
+
? $activeTodos
|
|
30
|
+
: filter === 'completed'
|
|
31
|
+
? $completedTodos
|
|
32
|
+
: $allTodos,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const syncBadge = $derived.by(() => {
|
|
36
|
+
const s = $syncStatus.status
|
|
37
|
+
const pending = $syncStatus.pendingOperations ?? 0
|
|
38
|
+
const map: Record<string, { icon: typeof Wifi; color: string; label: string }> = {
|
|
39
|
+
connected: { icon: Wifi, color: 'text-emerald-400', label: 'Connected' },
|
|
40
|
+
syncing: { icon: Wifi, color: 'text-amber-400', label: 'Syncing' },
|
|
41
|
+
synced: { icon: Wifi, color: 'text-emerald-400', label: 'Synced' },
|
|
42
|
+
offline: { icon: WifiOff, color: 'text-gray-500', label: 'Offline' },
|
|
43
|
+
error: { icon: AlertCircle, color: 'text-red-400', label: 'Error' },
|
|
44
|
+
}
|
|
45
|
+
return { ...(map[s] ?? map.offline), pending }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
function handleSubmit(event: SubmitEvent): void {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
const title = input.trim()
|
|
51
|
+
if (!title) return
|
|
52
|
+
addTodo.mutate({ title })
|
|
53
|
+
input = ''
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function clearCompleted(): void {
|
|
57
|
+
for (const todo of $completedTodos) {
|
|
58
|
+
deleteTodo.mutate(todo.id)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function emptyMessage(f: Filter): string {
|
|
63
|
+
if (f === 'all') return 'No tasks yet. Add one above!'
|
|
64
|
+
if (f === 'active') return 'All caught up! No active tasks.'
|
|
65
|
+
return 'No completed tasks yet.'
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<div class="min-h-screen bg-gray-950 text-gray-100">
|
|
70
|
+
<div class="mx-auto max-w-2xl px-4 py-12">
|
|
71
|
+
<div class="mb-8 flex items-center justify-between">
|
|
72
|
+
<div class="flex items-center gap-3">
|
|
73
|
+
<ClipboardList class="h-8 w-8 text-indigo-400" />
|
|
74
|
+
<h1 class="text-2xl font-bold">My Tasks</h1>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="flex items-center gap-2">
|
|
77
|
+
<div class="flex items-center gap-2 rounded-full bg-gray-800 px-3 py-1.5 text-sm">
|
|
78
|
+
<svelte:component this={syncBadge.icon} class="h-4 w-4 {syncBadge.color}" />
|
|
79
|
+
<span class={syncBadge.color}>{syncBadge.label}</span>
|
|
80
|
+
{#if syncBadge.pending > 0}
|
|
81
|
+
<span class="rounded-full bg-gray-700 px-2 py-0.5 text-xs text-gray-400">
|
|
82
|
+
{syncBadge.pending} pending
|
|
83
|
+
</span>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{#if $auth.isAuthenticated}
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="max-w-44 truncate rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300"
|
|
90
|
+
onclick={() => $auth.signOut()}
|
|
91
|
+
>
|
|
92
|
+
{$auth.user?.email || 'Sign out'}
|
|
93
|
+
</button>
|
|
94
|
+
{:else}
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
class="rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300"
|
|
98
|
+
onclick={() => $auth.signInWithOAuth('google')}
|
|
99
|
+
>
|
|
100
|
+
Sign in
|
|
101
|
+
</button>
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{#if $auth.error}
|
|
107
|
+
<div class="mb-4 rounded-lg border border-red-400/30 px-3 py-2 text-sm text-red-300">
|
|
108
|
+
{$auth.error}
|
|
109
|
+
</div>
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
|
+
<div class="mb-8 grid grid-cols-3 gap-4">
|
|
113
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
114
|
+
<p class="text-sm text-gray-500">Total</p>
|
|
115
|
+
<p class="text-2xl font-bold text-gray-300">{$allTodos.length}</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
118
|
+
<p class="text-sm text-gray-500">Remaining</p>
|
|
119
|
+
<p class="text-2xl font-bold text-amber-400">{$activeTodos.length}</p>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
122
|
+
<p class="text-sm text-gray-500">Done</p>
|
|
123
|
+
<p class="text-2xl font-bold text-emerald-400">{$completedTodos.length}</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<form class="mb-8 flex gap-3" onsubmit={handleSubmit}>
|
|
128
|
+
<input
|
|
129
|
+
bind:value={input}
|
|
130
|
+
type="text"
|
|
131
|
+
placeholder="What needs to be done?"
|
|
132
|
+
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"
|
|
133
|
+
/>
|
|
134
|
+
<button
|
|
135
|
+
type="submit"
|
|
136
|
+
disabled={!input.trim()}
|
|
137
|
+
class="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white disabled:opacity-50"
|
|
138
|
+
>
|
|
139
|
+
<Plus class="h-4 w-4" />
|
|
140
|
+
Add
|
|
141
|
+
</button>
|
|
142
|
+
</form>
|
|
143
|
+
|
|
144
|
+
<div class="mb-6 flex gap-2">
|
|
145
|
+
{#each ['all', 'active', 'completed'] as const as f}
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
class="rounded-lg px-4 py-2 text-sm font-medium {filter === f
|
|
149
|
+
? 'bg-indigo-600 text-white'
|
|
150
|
+
: 'bg-gray-800 text-gray-400'}"
|
|
151
|
+
onclick={() => (filter = f)}
|
|
152
|
+
>
|
|
153
|
+
{f.charAt(0).toUpperCase() + f.slice(1)}
|
|
154
|
+
</button>
|
|
155
|
+
{/each}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="space-y-2">
|
|
159
|
+
{#if filteredTodos.length === 0}
|
|
160
|
+
<div class="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600">
|
|
161
|
+
{emptyMessage(filter)}
|
|
162
|
+
</div>
|
|
163
|
+
{:else}
|
|
164
|
+
{#each filteredTodos as todo (todo.id)}
|
|
165
|
+
<div
|
|
166
|
+
class="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3"
|
|
167
|
+
>
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
class="text-gray-500 hover:text-indigo-400"
|
|
171
|
+
onclick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
|
|
172
|
+
>
|
|
173
|
+
{#if todo.completed}
|
|
174
|
+
<CheckCircle2 class="h-5 w-5 text-emerald-400" />
|
|
175
|
+
{:else}
|
|
176
|
+
<Circle class="h-5 w-5" />
|
|
177
|
+
{/if}
|
|
178
|
+
</button>
|
|
179
|
+
<span class="flex-1 {todo.completed ? 'line-through text-gray-500' : ''}">
|
|
180
|
+
{String(todo.title)}
|
|
181
|
+
</span>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="text-gray-600 opacity-0 group-hover:opacity-100 hover:text-red-400"
|
|
185
|
+
onclick={() => deleteTodo.mutate(todo.id)}
|
|
186
|
+
>
|
|
187
|
+
<Trash2 class="h-4 w-4" />
|
|
188
|
+
</button>
|
|
189
|
+
</div>
|
|
190
|
+
{/each}
|
|
191
|
+
{/if}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<p class="mt-12 text-center text-xs text-gray-700">Powered by Kora — offline-first, real-time sync</p>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AuthClient } from '@korajs/auth'
|
|
3
|
+
import type { KoraAppLike } from '@korajs/svelte'
|
|
4
|
+
import AuthProvider from '@korajs/auth/svelte/AuthProvider.svelte'
|
|
5
|
+
import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
|
|
6
|
+
import App from './App.svelte'
|
|
7
|
+
|
|
8
|
+
let { kora, authClient }: { kora: KoraAppLike; authClient: AuthClient } = $props()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<AuthProvider client={authClient}>
|
|
12
|
+
{#snippet fallback()}
|
|
13
|
+
<div class="flex h-screen items-center justify-center bg-gray-950 text-gray-400">
|
|
14
|
+
Restoring session...
|
|
15
|
+
</div>
|
|
16
|
+
{/snippet}
|
|
17
|
+
|
|
18
|
+
<KoraProvider app={kora}>
|
|
19
|
+
{#snippet fallback()}
|
|
20
|
+
<div class="flex h-screen items-center justify-center bg-gray-950 text-gray-400">
|
|
21
|
+
Loading...
|
|
22
|
+
</div>
|
|
23
|
+
{/snippet}
|
|
24
|
+
<App />
|
|
25
|
+
</KoraProvider>
|
|
26
|
+
</AuthProvider>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createKoraAuth } from '@korajs/auth'
|
|
2
|
+
|
|
3
|
+
export const authServerUrl =
|
|
4
|
+
import.meta.env.VITE_AUTH_URL || `${window.location.protocol}//${window.location.host}`
|
|
5
|
+
|
|
6
|
+
export const authClient = createKoraAuth({
|
|
7
|
+
serverUrl: authServerUrl,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export async function completeOAuthCallbackFromLocation(): Promise<void> {
|
|
11
|
+
const url = new URL(window.location.href)
|
|
12
|
+
const code = url.searchParams.get('code')
|
|
13
|
+
const state = url.searchParams.get('state')
|
|
14
|
+
const provider = url.searchParams.get('provider') || 'google'
|
|
15
|
+
|
|
16
|
+
if (!code || !state) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await authClient.completeOAuthSignIn(provider, { code, state })
|
|
21
|
+
url.searchParams.delete('code')
|
|
22
|
+
url.searchParams.delete('state')
|
|
23
|
+
url.searchParams.delete('provider')
|
|
24
|
+
window.history.replaceState({}, document.title, `${url.pathname}${url.search}${url.hash}`)
|
|
25
|
+
}
|
|
@@ -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,32 @@
|
|
|
1
|
+
import { createKoraAuthSync } from '@korajs/auth'
|
|
2
|
+
import { createApp as createKoraApp } from 'korajs'
|
|
3
|
+
import { mount } from 'svelte'
|
|
4
|
+
import Root from './Root.svelte'
|
|
5
|
+
import { authClient, completeOAuthCallbackFromLocation } from './auth'
|
|
6
|
+
import schema from './schema'
|
|
7
|
+
import './index.css'
|
|
8
|
+
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
9
|
+
|
|
10
|
+
const syncUrl =
|
|
11
|
+
import.meta.env.VITE_SYNC_URL ||
|
|
12
|
+
`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
|
|
13
|
+
|
|
14
|
+
const kora = createKoraApp({
|
|
15
|
+
schema,
|
|
16
|
+
sync: {
|
|
17
|
+
url: syncUrl,
|
|
18
|
+
authClient: createKoraAuthSync({ authClient, schema }),
|
|
19
|
+
},
|
|
20
|
+
store: {
|
|
21
|
+
workerUrl: koraWorkerUrl,
|
|
22
|
+
},
|
|
23
|
+
devtools: true,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
kora.ready.then(() => kora.sync?.connect())
|
|
27
|
+
|
|
28
|
+
void completeOAuthCallbackFromLocation().catch((error) => {
|
|
29
|
+
console.error('[Kora Auth] OAuth callback failed:', error)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
mount(Root, { target: document.getElementById('app')!, props: { kora, authClient } })
|
|
@@ -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,25 @@
|
|
|
1
|
+
import { getCollection, createMutation, createQueryStore } from '@korajs/svelte'
|
|
2
|
+
import { derived } from 'svelte/store'
|
|
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 createTodosStores() {
|
|
13
|
+
const todos = getCollection('todos')
|
|
14
|
+
const allTodos = createQueryStore(orderedTodos(todos))
|
|
15
|
+
const addTodo = createMutation((data: CreateTodoInput) => createTodo(todos, data))
|
|
16
|
+
const toggleTodo = createMutation((id: string, data: UpdateTodoStatusInput) =>
|
|
17
|
+
updateTodoStatus(todos, id, data),
|
|
18
|
+
)
|
|
19
|
+
const removeTodo = createMutation((id: string) => deleteTodo(todos, id))
|
|
20
|
+
|
|
21
|
+
const activeTodos = derived(allTodos, ($all) => $all.filter((todo) => !todo.completed))
|
|
22
|
+
const completedTodos = derived(allTodos, ($all) => $all.filter((todo) => !!todo.completed))
|
|
23
|
+
|
|
24
|
+
return { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo: removeTodo }
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
/// <reference types="vite/client" />
|
|
3
|
+
|
|
4
|
+
interface ImportMetaEnv {
|
|
5
|
+
readonly VITE_SYNC_URL?: string
|
|
6
|
+
readonly VITE_AUTH_URL?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ImportMeta {
|
|
10
|
+
readonly env: ImportMetaEnv
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module '*?worker&url' {
|
|
14
|
+
const url: string
|
|
15
|
+
export default url
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare module '*?url' {
|
|
19
|
+
const url: string
|
|
20
|
+
export default url
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
"types": ["vite/client"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "src/**/*.svelte"],
|
|
13
|
+
"exclude": ["node_modules"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
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: [svelte(), 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
|
+
server: {
|
|
71
|
+
allowedHosts: true,
|
|
72
|
+
proxy: {
|
|
73
|
+
'/kora-sync': {
|
|
74
|
+
target: 'ws://localhost:3001',
|
|
75
|
+
ws: true,
|
|
76
|
+
rewriteWsOrigin: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
})
|
|
@@ -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,26 @@
|
|
|
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
|
+
"vue": "^3.5.0",
|
|
17
|
+
"yjs": "^13.6.30"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@korajs/cli": "{{koraVersion}}",
|
|
21
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
22
|
+
"typescript": "^5.7.0",
|
|
23
|
+
"vite": "^6.0.0",
|
|
24
|
+
"vue-tsc": "^2.2.0"
|
|
25
|
+
}
|
|
26
|
+
}
|