@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.
Files changed (200) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.cjs +2656 -207
  3. package/dist/bin.cjs.map +1 -1
  4. package/dist/bin.js +152 -96
  5. package/dist/bin.js.map +1 -1
  6. package/dist/chunk-5NI2FEQL.js +92 -0
  7. package/dist/chunk-5NI2FEQL.js.map +1 -0
  8. package/dist/chunk-6C4BHSRA.js +82 -0
  9. package/dist/chunk-6C4BHSRA.js.map +1 -0
  10. package/dist/{chunk-3WNFM3QB.js → chunk-B5YS4STN.js} +78 -99
  11. package/dist/chunk-B5YS4STN.js.map +1 -0
  12. package/dist/{chunk-PSRM56B7.js → chunk-BWTKRKNJ.js} +60 -7
  13. package/dist/chunk-BWTKRKNJ.js.map +1 -0
  14. package/dist/{chunk-SOTZIWIF.js → chunk-EOWLAAIV.js} +5 -3
  15. package/dist/{chunk-SOTZIWIF.js.map → chunk-EOWLAAIV.js.map} +1 -1
  16. package/dist/create.cjs +82 -7
  17. package/dist/create.cjs.map +1 -1
  18. package/dist/create.js +3 -2
  19. package/dist/create.js.map +1 -1
  20. package/dist/index.cjs +65 -3
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +3 -3
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +3 -2
  25. package/dist/lab-manager-KUDII6BT.js +280 -0
  26. package/dist/lab-manager-KUDII6BT.js.map +1 -0
  27. package/dist/schema-loader-GGHECMTM.js +9 -0
  28. package/dist/schema-loader-GGHECMTM.js.map +1 -0
  29. package/dist/spectator-manager-E2LH2P54.js +142 -0
  30. package/dist/spectator-manager-E2LH2P54.js.map +1 -0
  31. package/dist/studio-server-NIFKZI4F.js +1738 -0
  32. package/dist/studio-server-NIFKZI4F.js.map +1 -0
  33. package/package.json +8 -6
  34. package/templates/react-basic/AGENTS.md +87 -0
  35. package/templates/react-sync/AGENTS.md +87 -0
  36. package/templates/react-sync/src/App.tsx +8 -0
  37. package/templates/react-sync/src/index.css +11 -0
  38. package/templates/react-tailwind/AGENTS.md +87 -0
  39. package/templates/react-tailwind-sync/AGENTS.md +87 -0
  40. package/templates/react-tailwind-sync/src/App.tsx +28 -0
  41. package/templates/svelte-basic/.env.example +3 -0
  42. package/templates/svelte-basic/AGENTS.md +76 -0
  43. package/templates/svelte-basic/README.md.hbs +62 -0
  44. package/templates/svelte-basic/index.html.hbs +12 -0
  45. package/templates/svelte-basic/kora.config.ts +12 -0
  46. package/templates/svelte-basic/package.json.hbs +27 -0
  47. package/templates/svelte-basic/src/App.svelte +127 -0
  48. package/templates/svelte-basic/src/Root.svelte +14 -0
  49. package/templates/svelte-basic/src/index.css +287 -0
  50. package/templates/svelte-basic/src/kora-worker.ts +9 -0
  51. package/templates/svelte-basic/src/main.ts +16 -0
  52. package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
  53. package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
  54. package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
  55. package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
  56. package/templates/svelte-basic/src/schema.ts +9 -0
  57. package/templates/svelte-basic/src/vite-env.d.ts +12 -0
  58. package/templates/svelte-basic/svelte.config.js +5 -0
  59. package/templates/svelte-basic/tsconfig.json.hbs +14 -0
  60. package/templates/svelte-basic/vite.config.ts +52 -0
  61. package/templates/svelte-sync/.env.example +25 -0
  62. package/templates/svelte-sync/AGENTS.md +76 -0
  63. package/templates/svelte-sync/README.md.hbs +105 -0
  64. package/templates/svelte-sync/index.html.hbs +12 -0
  65. package/templates/svelte-sync/kora.config.ts +17 -0
  66. package/templates/svelte-sync/package.json.hbs +43 -0
  67. package/templates/svelte-sync/server.ts +90 -0
  68. package/templates/svelte-sync/src/App.svelte +150 -0
  69. package/templates/svelte-sync/src/Root.svelte +22 -0
  70. package/templates/svelte-sync/src/auth.ts +25 -0
  71. package/templates/svelte-sync/src/index.css +339 -0
  72. package/templates/svelte-sync/src/kora-worker.ts +9 -0
  73. package/templates/svelte-sync/src/main.ts +32 -0
  74. package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
  75. package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
  76. package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
  77. package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
  78. package/templates/svelte-sync/src/schema.ts +9 -0
  79. package/templates/svelte-sync/src/vite-env.d.ts +21 -0
  80. package/templates/svelte-sync/svelte.config.js +5 -0
  81. package/templates/svelte-sync/tsconfig.json.hbs +14 -0
  82. package/templates/svelte-sync/vite.config.ts +79 -0
  83. package/templates/svelte-tailwind/AGENTS.md +76 -0
  84. package/templates/svelte-tailwind/README.md.hbs +62 -0
  85. package/templates/svelte-tailwind/index.html.hbs +12 -0
  86. package/templates/svelte-tailwind/kora.config.ts +12 -0
  87. package/templates/svelte-tailwind/package.json.hbs +30 -0
  88. package/templates/svelte-tailwind/src/App.svelte +75 -0
  89. package/templates/svelte-tailwind/src/Root.svelte +14 -0
  90. package/templates/svelte-tailwind/src/index.css +7 -0
  91. package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
  92. package/templates/svelte-tailwind/src/main.ts +16 -0
  93. package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  94. package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
  95. package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
  96. package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
  97. package/templates/svelte-tailwind/src/schema.ts +9 -0
  98. package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
  99. package/templates/svelte-tailwind/svelte.config.js +5 -0
  100. package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
  101. package/templates/svelte-tailwind/vite.config.ts +53 -0
  102. package/templates/svelte-tailwind-sync/.env.example +25 -0
  103. package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
  104. package/templates/svelte-tailwind-sync/README.md.hbs +105 -0
  105. package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
  106. package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
  107. package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
  108. package/templates/svelte-tailwind-sync/server.ts +90 -0
  109. package/templates/svelte-tailwind-sync/src/App.svelte +192 -0
  110. package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
  111. package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
  112. package/templates/svelte-tailwind-sync/src/index.css +7 -0
  113. package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
  114. package/templates/svelte-tailwind-sync/src/main.ts +32 -0
  115. package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  116. package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  117. package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  118. package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
  119. package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
  120. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +21 -0
  121. package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
  122. package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
  123. package/templates/svelte-tailwind-sync/vite.config.ts +80 -0
  124. package/templates/tauri-react/AGENTS.md +91 -0
  125. package/templates/vue-basic/AGENTS.md +84 -0
  126. package/templates/vue-basic/README.md.hbs +62 -0
  127. package/templates/vue-basic/index.html.hbs +12 -0
  128. package/templates/vue-basic/kora.config.ts +12 -0
  129. package/templates/vue-basic/package.json.hbs +26 -0
  130. package/templates/vue-basic/src/App.vue +117 -0
  131. package/templates/vue-basic/src/index.css +287 -0
  132. package/templates/vue-basic/src/kora-worker.ts +9 -0
  133. package/templates/vue-basic/src/main.ts +20 -0
  134. package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
  135. package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
  136. package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
  137. package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
  138. package/templates/vue-basic/src/schema.ts +9 -0
  139. package/templates/vue-basic/src/vite-env.d.ts +17 -0
  140. package/templates/vue-basic/tsconfig.json.hbs +15 -0
  141. package/templates/vue-basic/vite.config.ts +69 -0
  142. package/templates/vue-sync/.env.example +25 -0
  143. package/templates/vue-sync/AGENTS.md +84 -0
  144. package/templates/vue-sync/README.md.hbs +105 -0
  145. package/templates/vue-sync/index.html.hbs +12 -0
  146. package/templates/vue-sync/kora.config.ts +17 -0
  147. package/templates/vue-sync/package.json.hbs +42 -0
  148. package/templates/vue-sync/server.ts +90 -0
  149. package/templates/vue-sync/src/App.vue +135 -0
  150. package/templates/vue-sync/src/auth.ts +25 -0
  151. package/templates/vue-sync/src/index.css +339 -0
  152. package/templates/vue-sync/src/kora-worker.ts +9 -0
  153. package/templates/vue-sync/src/main.ts +47 -0
  154. package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
  155. package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
  156. package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
  157. package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
  158. package/templates/vue-sync/src/schema.ts +9 -0
  159. package/templates/vue-sync/src/vite-env.d.ts +26 -0
  160. package/templates/vue-sync/tsconfig.json.hbs +15 -0
  161. package/templates/vue-sync/vite.config.ts +79 -0
  162. package/templates/vue-tailwind/AGENTS.md +84 -0
  163. package/templates/vue-tailwind/README.md.hbs +62 -0
  164. package/templates/vue-tailwind/index.html.hbs +12 -0
  165. package/templates/vue-tailwind/kora.config.ts +12 -0
  166. package/templates/vue-tailwind/package.json.hbs +29 -0
  167. package/templates/vue-tailwind/src/App.vue +135 -0
  168. package/templates/vue-tailwind/src/index.css +7 -0
  169. package/templates/vue-tailwind/src/kora-worker.ts +9 -0
  170. package/templates/vue-tailwind/src/main.ts +31 -0
  171. package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  172. package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
  173. package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
  174. package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
  175. package/templates/vue-tailwind/src/schema.ts +9 -0
  176. package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
  177. package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
  178. package/templates/vue-tailwind/vite.config.ts +70 -0
  179. package/templates/vue-tailwind-sync/.env.example +25 -0
  180. package/templates/vue-tailwind-sync/AGENTS.md +84 -0
  181. package/templates/vue-tailwind-sync/README.md.hbs +105 -0
  182. package/templates/vue-tailwind-sync/index.html.hbs +12 -0
  183. package/templates/vue-tailwind-sync/kora.config.ts +17 -0
  184. package/templates/vue-tailwind-sync/package.json.hbs +45 -0
  185. package/templates/vue-tailwind-sync/server.ts +90 -0
  186. package/templates/vue-tailwind-sync/src/App.vue +245 -0
  187. package/templates/vue-tailwind-sync/src/auth.ts +25 -0
  188. package/templates/vue-tailwind-sync/src/index.css +7 -0
  189. package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
  190. package/templates/vue-tailwind-sync/src/main.ts +56 -0
  191. package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  192. package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  193. package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  194. package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
  195. package/templates/vue-tailwind-sync/src/schema.ts +9 -0
  196. package/templates/vue-tailwind-sync/src/vite-env.d.ts +26 -0
  197. package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
  198. package/templates/vue-tailwind-sync/vite.config.ts +80 -0
  199. package/dist/chunk-3WNFM3QB.js.map +0 -1
  200. package/dist/chunk-PSRM56B7.js.map +0 -1
@@ -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,43 @@
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": "vite build",
10
+ "check": "svelte-check --tsconfig ./tsconfig.json",
11
+ "start": "node --import tsx server.ts",
12
+ "preview": "vite preview"
13
+ },
14
+ "dependencies": {
15
+ "korajs": "{{koraVersion}}",
16
+ "@korajs/auth": "{{koraVersion}}",
17
+ "@korajs/svelte": "{{koraVersion}}",
18
+ "@korajs/store": "{{koraVersion}}",
19
+ "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
20
+ "svelte": "^5.0.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
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
35
+ "@types/ws": "^8.5.0",
36
+ "better-sqlite3": "^12.9.0",
37
+ "postgres": "^3.4.0",
38
+ "svelte-check": "^4.1.0",
39
+ "tsx": "^4.19.0",
40
+ "typescript": "^5.7.0",
41
+ "vite": "^6.0.0"
42
+ }
43
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ type CreateKoraAuthServerOptions,
3
+ createKoraAuthServer,
4
+ createPostgresOAuthStores,
5
+ createSqliteOAuthStores,
6
+ googleProvider,
7
+ } from '@korajs/auth/server'
8
+ import {
9
+ createPostgresServerStore,
10
+ createProductionServer,
11
+ createSqliteServerStore,
12
+ } from '@korajs/server'
13
+ import schema from './src/schema'
14
+
15
+ async function createStore() {
16
+ if (process.env.DATABASE_URL) {
17
+ return createPostgresServerStore({
18
+ connectionString: process.env.DATABASE_URL,
19
+ })
20
+ }
21
+
22
+ return createSqliteServerStore({
23
+ filename: process.env.KORA_SERVER_DB || './kora-server.db',
24
+ })
25
+ }
26
+
27
+ async function start() {
28
+ const store = await createStore()
29
+ await store.setSchema(schema)
30
+
31
+ const syncPath = process.env.KORA_SYNC_PATH || '/kora-sync'
32
+ const auth = await createAuth()
33
+ const server = createProductionServer({
34
+ store,
35
+ port: Number(process.env.PORT) || 3001,
36
+ staticDir: './dist',
37
+ syncPath,
38
+ httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
39
+ syncOptions: auth ? { auth: auth.auth } : undefined,
40
+ operationalAuth: {
41
+ adminToken: process.env.KORA_ADMIN_TOKEN,
42
+ metricsToken: process.env.KORA_METRICS_TOKEN,
43
+ backupToken: process.env.KORA_BACKUP_TOKEN,
44
+ },
45
+ })
46
+
47
+ const url = await server.start()
48
+ console.log(`Kora app running at ${url}`)
49
+ console.log(` Sync endpoint: ${url.replace('http', 'ws')}${syncPath}`)
50
+ if (auth) console.log(` Auth endpoint: ${url}/auth`)
51
+ }
52
+
53
+ void start()
54
+
55
+ async function createAuth() {
56
+ if (!process.env.KORA_AUTH_SECRET) {
57
+ return null
58
+ }
59
+
60
+ const oauth = await createOAuthConfig()
61
+ return createKoraAuthServer({
62
+ jwtSecret: process.env.KORA_AUTH_SECRET,
63
+ ...(oauth ? { oauth } : {}),
64
+ })
65
+ }
66
+
67
+ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth'] | undefined> {
68
+ if (!process.env.KORA_GOOGLE_CLIENT_ID || !process.env.KORA_GOOGLE_REDIRECT_URI) {
69
+ return undefined
70
+ }
71
+
72
+ const oauthStores = process.env.DATABASE_URL
73
+ ? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
74
+ : await createSqliteOAuthStores({
75
+ filename: process.env.KORA_AUTH_DB || './kora-auth.db',
76
+ })
77
+
78
+ return {
79
+ providers: [
80
+ googleProvider({
81
+ clientId: process.env.KORA_GOOGLE_CLIENT_ID,
82
+ clientSecret: process.env.KORA_GOOGLE_CLIENT_SECRET,
83
+ redirectUri: process.env.KORA_GOOGLE_REDIRECT_URI,
84
+ pkce: process.env.KORA_OAUTH_PKCE === 'true' || !process.env.KORA_GOOGLE_CLIENT_SECRET,
85
+ }),
86
+ ],
87
+ stateStore: oauthStores.stateStore,
88
+ linkedIdentityStore: oauthStores.linkedIdentityStore,
89
+ }
90
+ }
@@ -0,0 +1,150 @@
1
+ <script lang="ts">
2
+ import { createAuthStore } from '@korajs/auth/svelte'
3
+ import { createSyncStatusStore } from '@korajs/svelte'
4
+ import { createTodosStores } from './modules/todos/useTodos'
5
+
6
+ type Filter = 'all' | 'active' | 'completed'
7
+
8
+ const syncStatus = createSyncStatusStore()
9
+ const auth = createAuthStore()
10
+ const todos = createTodosStores()
11
+ const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = todos
12
+
13
+ const filter = $state<Filter>('all')
14
+ let input = $state('')
15
+
16
+ const filteredTodos = $derived(
17
+ filter === 'active' ? $activeTodos : filter === 'completed' ? $completedTodos : $allTodos,
18
+ )
19
+
20
+ function handleSubmit(event: SubmitEvent): void {
21
+ event.preventDefault()
22
+ const title = input.trim()
23
+ if (!title) return
24
+ addTodo.mutate({ title })
25
+ input = ''
26
+ }
27
+
28
+ function clearCompleted(): void {
29
+ for (const todo of $completedTodos) {
30
+ deleteTodo.mutate(todo.id)
31
+ }
32
+ }
33
+
34
+ function formatTime(timestamp: number): string {
35
+ const date = new Date(timestamp)
36
+ const now = new Date()
37
+ if (date.toDateString() === now.toDateString()) {
38
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
39
+ }
40
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
41
+ }
42
+ </script>
43
+
44
+ <div class="app">
45
+ <div class="header">
46
+ <h1>My Tasks</h1>
47
+ <div class="header-actions">
48
+ <div class="sync-badge">
49
+ <span class="sync-dot {$syncStatus.status}"></span>
50
+ <span>{$syncStatus.status}</span>
51
+ </div>
52
+ {#if $auth.isAuthenticated}
53
+ <button type="button" class="auth-button" onclick={() => $auth.signOut()}>
54
+ {$auth.user?.email || 'Sign out'}
55
+ </button>
56
+ {:else}
57
+ <button type="button" class="auth-button" onclick={() => $auth.signInWithOAuth('google')}>
58
+ Sign in
59
+ </button>
60
+ {/if}
61
+ </div>
62
+ </div>
63
+
64
+ {#if $auth.error}
65
+ <div class="auth-error">{$auth.error}</div>
66
+ {/if}
67
+
68
+ <div class="stats">
69
+ <div class="stat-card">
70
+ <div class="label">Total</div>
71
+ <div class="value muted">{$allTodos.length}</div>
72
+ </div>
73
+ <div class="stat-card">
74
+ <div class="label">Remaining</div>
75
+ <div class="value warning">{$activeTodos.length}</div>
76
+ </div>
77
+ <div class="stat-card">
78
+ <div class="label">Done</div>
79
+ <div class="value success">{$completedTodos.length}</div>
80
+ </div>
81
+ </div>
82
+
83
+ <form class="add-form" onsubmit={handleSubmit}>
84
+ <input bind:value={input} type="text" placeholder="What needs to be done?" />
85
+ <button type="submit" disabled={!input.trim()}>Add</button>
86
+ </form>
87
+
88
+ <div class="filters">
89
+ {#each ['all', 'active', 'completed'] as const as f}
90
+ <button
91
+ type="button"
92
+ class="filter-btn {filter === f ? 'active' : ''}"
93
+ onclick={() => (filter = f)}
94
+ >
95
+ {f.charAt(0).toUpperCase() + f.slice(1)}
96
+ <span class="badge">
97
+ {f === 'all'
98
+ ? $allTodos.length
99
+ : f === 'active'
100
+ ? $activeTodos.length
101
+ : $completedTodos.length}
102
+ </span>
103
+ </button>
104
+ {/each}
105
+ </div>
106
+
107
+ <div class="todo-list">
108
+ {#if filteredTodos.length === 0}
109
+ <div class="empty-state">
110
+ {#if filter === 'all'}
111
+ No tasks yet. Add one above!
112
+ {:else if filter === 'active'}
113
+ All caught up! No active tasks.
114
+ {:else}
115
+ No completed tasks yet.
116
+ {/if}
117
+ </div>
118
+ {:else}
119
+ {#each filteredTodos as todo (todo.id)}
120
+ <div class="todo-item">
121
+ <button
122
+ type="button"
123
+ class="toggle {todo.completed ? 'checked' : ''}"
124
+ onclick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
125
+ >
126
+ {todo.completed ? '✓' : ''}
127
+ </button>
128
+ <span class="title {todo.completed ? 'done' : ''}">{String(todo.title)}</span>
129
+ {#if todo.createdAt}
130
+ <span class="time">{formatTime(Number(todo.createdAt))}</span>
131
+ {/if}
132
+ <button type="button" class="delete-btn" onclick={() => deleteTodo.mutate(todo.id)}>
133
+ ×
134
+ </button>
135
+ </div>
136
+ {/each}
137
+ {/if}
138
+ </div>
139
+
140
+ {#if $allTodos.length > 0}
141
+ <div class="footer">
142
+ <span>{$activeTodos.length} item{$activeTodos.length !== 1 ? 's' : ''} left</span>
143
+ {#if $completedTodos.length > 0}
144
+ <button type="button" onclick={clearCompleted}>Clear completed</button>
145
+ {/if}
146
+ </div>
147
+ {/if}
148
+
149
+ <p class="branding">Powered by Kora — offline-first, real-time sync</p>
150
+ </div>
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { AuthClient } from '@korajs/auth'
3
+ import AuthProvider from '@korajs/auth/svelte/AuthProvider.svelte'
4
+ import type { KoraAppLike } from '@korajs/svelte'
5
+ import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
6
+ import App from './App.svelte'
7
+
8
+ const { kora, authClient }: { kora: KoraAppLike; authClient: AuthClient } = $props()
9
+ </script>
10
+
11
+ <AuthProvider client={authClient}>
12
+ {#snippet fallback()}
13
+ <div>Restoring session...</div>
14
+ {/snippet}
15
+
16
+ <KoraProvider app={kora}>
17
+ {#snippet fallback()}
18
+ <div>Loading...</div>
19
+ {/snippet}
20
+ <App />
21
+ </KoraProvider>
22
+ </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,339 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ :root {
10
+ --color-bg: #0a0a0f;
11
+ --color-surface: #111118;
12
+ --color-border: #1e1e2a;
13
+ --color-border-hover: #2a2a3a;
14
+ --color-text: #e4e4e7;
15
+ --color-text-muted: #71717a;
16
+ --color-text-dim: #3f3f46;
17
+ --color-primary: #6366f1;
18
+ --color-primary-hover: #818cf8;
19
+ --color-success: #34d399;
20
+ --color-warning: #fbbf24;
21
+ --color-danger: #f87171;
22
+ --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
23
+ --radius: 8px;
24
+ }
25
+
26
+ body {
27
+ font-family: var(--font-sans);
28
+ background: var(--color-bg);
29
+ color: var(--color-text);
30
+ line-height: 1.6;
31
+ -webkit-font-smoothing: antialiased;
32
+ }
33
+
34
+ .app {
35
+ max-width: 640px;
36
+ margin: 0 auto;
37
+ padding: 48px 16px;
38
+ }
39
+
40
+ .header {
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ margin-bottom: 32px;
45
+ }
46
+
47
+ .header h1 {
48
+ font-size: 1.5rem;
49
+ font-weight: 700;
50
+ }
51
+
52
+ .header-actions {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ }
57
+
58
+ .sync-badge {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ gap: 6px;
62
+ padding: 4px 12px;
63
+ border-radius: 999px;
64
+ background: var(--color-surface);
65
+ font-size: 0.8rem;
66
+ }
67
+
68
+ .sync-dot {
69
+ width: 8px;
70
+ height: 8px;
71
+ border-radius: 50%;
72
+ }
73
+
74
+ .sync-dot.connected,
75
+ .sync-dot.synced {
76
+ background: var(--color-success);
77
+ }
78
+ .sync-dot.syncing {
79
+ background: var(--color-warning);
80
+ }
81
+ .sync-dot.offline {
82
+ background: var(--color-text-muted);
83
+ }
84
+ .sync-dot.error {
85
+ background: var(--color-danger);
86
+ }
87
+
88
+ .auth-button {
89
+ border: 1px solid var(--color-border);
90
+ border-radius: 999px;
91
+ background: var(--color-surface);
92
+ color: var(--color-text);
93
+ cursor: pointer;
94
+ font-size: 0.8rem;
95
+ max-width: 180px;
96
+ overflow: hidden;
97
+ padding: 4px 12px;
98
+ text-overflow: ellipsis;
99
+ white-space: nowrap;
100
+ }
101
+
102
+ .auth-button:hover {
103
+ border-color: var(--color-border-hover);
104
+ }
105
+
106
+ .auth-error {
107
+ border: 1px solid rgba(248, 113, 113, 0.35);
108
+ border-radius: var(--radius);
109
+ color: var(--color-danger);
110
+ font-size: 0.85rem;
111
+ margin-bottom: 16px;
112
+ padding: 10px 12px;
113
+ }
114
+
115
+ .stats {
116
+ display: grid;
117
+ grid-template-columns: repeat(3, 1fr);
118
+ gap: 12px;
119
+ margin-bottom: 32px;
120
+ }
121
+
122
+ .stat-card {
123
+ background: var(--color-surface);
124
+ border: 1px solid var(--color-border);
125
+ border-radius: var(--radius);
126
+ padding: 16px;
127
+ }
128
+
129
+ .stat-card .label {
130
+ font-size: 0.8rem;
131
+ color: var(--color-text-muted);
132
+ }
133
+
134
+ .stat-card .value {
135
+ font-size: 1.5rem;
136
+ font-weight: 700;
137
+ }
138
+
139
+ .stat-card .value.muted {
140
+ color: var(--color-text-muted);
141
+ }
142
+ .stat-card .value.warning {
143
+ color: var(--color-warning);
144
+ }
145
+ .stat-card .value.success {
146
+ color: var(--color-success);
147
+ }
148
+
149
+ .add-form {
150
+ display: flex;
151
+ gap: 12px;
152
+ margin-bottom: 32px;
153
+ }
154
+
155
+ .add-form input {
156
+ flex: 1;
157
+ padding: 10px 16px;
158
+ border: 1px solid var(--color-border);
159
+ border-radius: var(--radius);
160
+ background: var(--color-surface);
161
+ color: var(--color-text);
162
+ font-size: 0.95rem;
163
+ outline: none;
164
+ }
165
+
166
+ .add-form input:focus {
167
+ border-color: var(--color-primary);
168
+ }
169
+
170
+ .add-form input::placeholder {
171
+ color: var(--color-text-muted);
172
+ }
173
+
174
+ .add-form button {
175
+ padding: 10px 20px;
176
+ border: none;
177
+ border-radius: var(--radius);
178
+ background: var(--color-primary);
179
+ color: white;
180
+ font-size: 0.95rem;
181
+ font-weight: 500;
182
+ cursor: pointer;
183
+ }
184
+
185
+ .add-form button:hover {
186
+ background: var(--color-primary-hover);
187
+ }
188
+
189
+ .add-form button:disabled {
190
+ opacity: 0.5;
191
+ cursor: not-allowed;
192
+ }
193
+
194
+ .filters {
195
+ display: flex;
196
+ gap: 8px;
197
+ margin-bottom: 24px;
198
+ }
199
+
200
+ .filter-btn {
201
+ padding: 6px 16px;
202
+ border: none;
203
+ border-radius: var(--radius);
204
+ background: var(--color-surface);
205
+ color: var(--color-text-muted);
206
+ font-size: 0.85rem;
207
+ font-weight: 500;
208
+ cursor: pointer;
209
+ }
210
+
211
+ .filter-btn:hover {
212
+ background: var(--color-border-hover);
213
+ color: var(--color-text);
214
+ }
215
+
216
+ .filter-btn.active {
217
+ background: var(--color-primary);
218
+ color: white;
219
+ }
220
+
221
+ .filter-btn .badge {
222
+ display: inline-block;
223
+ margin-left: 6px;
224
+ padding: 1px 6px;
225
+ border-radius: 999px;
226
+ font-size: 0.75rem;
227
+ background: rgba(255, 255, 255, 0.15);
228
+ }
229
+
230
+ .filter-btn:not(.active) .badge {
231
+ background: var(--color-border);
232
+ }
233
+
234
+ .todo-list {
235
+ display: flex;
236
+ flex-direction: column;
237
+ gap: 8px;
238
+ }
239
+
240
+ .todo-item {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 12px;
244
+ padding: 12px 16px;
245
+ border: 1px solid var(--color-border);
246
+ border-radius: var(--radius);
247
+ background: var(--color-surface);
248
+ }
249
+
250
+ .todo-item:hover {
251
+ border-color: var(--color-border-hover);
252
+ }
253
+
254
+ .todo-item .toggle {
255
+ background: none;
256
+ border: 2px solid var(--color-text-muted);
257
+ width: 20px;
258
+ height: 20px;
259
+ border-radius: 50%;
260
+ cursor: pointer;
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ color: transparent;
265
+ font-size: 0.7rem;
266
+ flex-shrink: 0;
267
+ }
268
+
269
+ .todo-item .toggle.checked {
270
+ border-color: var(--color-success);
271
+ background: var(--color-success);
272
+ color: white;
273
+ }
274
+
275
+ .todo-item .title {
276
+ flex: 1;
277
+ }
278
+
279
+ .todo-item .title.done {
280
+ text-decoration: line-through;
281
+ color: var(--color-text-muted);
282
+ }
283
+
284
+ .todo-item .time {
285
+ font-size: 0.75rem;
286
+ color: var(--color-text-dim);
287
+ }
288
+
289
+ .todo-item .delete-btn {
290
+ background: none;
291
+ border: none;
292
+ color: var(--color-text-dim);
293
+ cursor: pointer;
294
+ padding: 4px;
295
+ opacity: 0;
296
+ transition: opacity 0.15s;
297
+ }
298
+
299
+ .todo-item:hover .delete-btn {
300
+ opacity: 1;
301
+ }
302
+
303
+ .todo-item .delete-btn:hover {
304
+ color: var(--color-danger);
305
+ }
306
+
307
+ .empty-state {
308
+ padding: 48px 0;
309
+ text-align: center;
310
+ color: var(--color-text-dim);
311
+ border: 1px dashed var(--color-border);
312
+ border-radius: var(--radius);
313
+ }
314
+
315
+ .footer {
316
+ display: flex;
317
+ justify-content: space-between;
318
+ margin-top: 24px;
319
+ font-size: 0.85rem;
320
+ color: var(--color-text-muted);
321
+ }
322
+
323
+ .footer button {
324
+ background: none;
325
+ border: none;
326
+ color: var(--color-text-muted);
327
+ cursor: pointer;
328
+ }
329
+
330
+ .footer button:hover {
331
+ color: var(--color-text);
332
+ }
333
+
334
+ .branding {
335
+ margin-top: 48px;
336
+ text-align: center;
337
+ font-size: 0.75rem;
338
+ color: var(--color-text-dim);
339
+ }
@@ -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'