@korajs/cli 0.4.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.
Files changed (253) hide show
  1. package/dist/bin.cjs +1636 -601
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +1086 -139
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-MVP5PDT4.js → chunk-EEZNRI5W.js} +51 -6
  6. package/dist/chunk-EEZNRI5W.js.map +1 -0
  7. package/dist/{chunk-YGVO4POI.js → chunk-Q2FBCOQD.js} +27 -15
  8. package/dist/chunk-Q2FBCOQD.js.map +1 -0
  9. package/dist/{chunk-CMSX76KM.js → chunk-VLTPEATY.js} +172 -99
  10. package/dist/chunk-VLTPEATY.js.map +1 -0
  11. package/dist/create.cjs +130 -54
  12. package/dist/create.cjs.map +1 -1
  13. package/dist/create.js +2 -2
  14. package/dist/index.cjs +143 -62
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +8 -6
  17. package/dist/index.d.ts +8 -6
  18. package/dist/index.js +2 -2
  19. package/package.json +9 -3
  20. package/templates/react-basic/README.md.hbs +10 -1
  21. package/templates/react-basic/src/App.tsx +112 -126
  22. package/templates/react-basic/src/index.css +165 -159
  23. package/templates/react-basic/src/kora-worker.ts +0 -1
  24. package/templates/react-basic/src/main.tsx +12 -12
  25. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  26. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  27. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  28. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  29. package/templates/react-basic/src/schema.ts +6 -12
  30. package/templates/react-basic/src/vite-env.d.ts +17 -0
  31. package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
  32. package/templates/react-basic/vite.config.ts +57 -50
  33. package/templates/react-sync/.env.example +19 -0
  34. package/templates/react-sync/README.md.hbs +43 -1
  35. package/templates/react-sync/package.json.hbs +3 -0
  36. package/templates/react-sync/server.ts +90 -26
  37. package/templates/react-sync/src/App.tsx +132 -130
  38. package/templates/react-sync/src/auth.ts +25 -0
  39. package/templates/react-sync/src/index.css +212 -165
  40. package/templates/react-sync/src/kora-worker.ts +0 -1
  41. package/templates/react-sync/src/main.tsx +34 -18
  42. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  43. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  44. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  45. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  46. package/templates/react-sync/src/schema.ts +6 -12
  47. package/templates/react-sync/src/vite-env.d.ts +17 -0
  48. package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  49. package/templates/react-sync/vite.config.ts +67 -60
  50. package/templates/react-tailwind/README.md.hbs +10 -1
  51. package/templates/react-tailwind/src/App.tsx +186 -197
  52. package/templates/react-tailwind/src/index.css +3 -3
  53. package/templates/react-tailwind/src/kora-worker.ts +0 -1
  54. package/templates/react-tailwind/src/main.tsx +19 -12
  55. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  56. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  57. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  58. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  59. package/templates/react-tailwind/src/schema.ts +6 -12
  60. package/templates/react-tailwind/src/vite-env.d.ts +17 -0
  61. package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
  62. package/templates/react-tailwind/vite.config.ts +57 -50
  63. package/templates/react-tailwind-sync/.env.example +19 -0
  64. package/templates/react-tailwind-sync/README.md.hbs +43 -1
  65. package/templates/react-tailwind-sync/package.json.hbs +3 -0
  66. package/templates/react-tailwind-sync/server.ts +90 -26
  67. package/templates/react-tailwind-sync/src/App.tsx +246 -222
  68. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  69. package/templates/react-tailwind-sync/src/index.css +3 -3
  70. package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
  71. package/templates/react-tailwind-sync/src/main.tsx +48 -18
  72. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-tailwind-sync/src/schema.ts +6 -12
  77. package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
  78. package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  79. package/templates/react-tailwind-sync/vite.config.ts +67 -60
  80. package/templates/svelte-basic/.env.example +3 -0
  81. package/templates/svelte-basic/README.md.hbs +62 -0
  82. package/templates/svelte-basic/index.html.hbs +12 -0
  83. package/templates/svelte-basic/kora.config.ts +12 -0
  84. package/templates/svelte-basic/package.json.hbs +27 -0
  85. package/templates/svelte-basic/src/App.svelte +131 -0
  86. package/templates/svelte-basic/src/Root.svelte +14 -0
  87. package/templates/svelte-basic/src/index.css +287 -0
  88. package/templates/svelte-basic/src/kora-worker.ts +9 -0
  89. package/templates/svelte-basic/src/main.ts +16 -0
  90. package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
  94. package/templates/svelte-basic/src/schema.ts +9 -0
  95. package/templates/svelte-basic/src/vite-env.d.ts +12 -0
  96. package/templates/svelte-basic/svelte.config.js +5 -0
  97. package/templates/svelte-basic/tsconfig.json.hbs +14 -0
  98. package/templates/svelte-basic/vite.config.ts +52 -0
  99. package/templates/svelte-sync/.env.example +25 -0
  100. package/templates/svelte-sync/README.md.hbs +105 -0
  101. package/templates/svelte-sync/index.html.hbs +12 -0
  102. package/templates/svelte-sync/kora.config.ts +17 -0
  103. package/templates/svelte-sync/package.json.hbs +43 -0
  104. package/templates/svelte-sync/server.ts +90 -0
  105. package/templates/svelte-sync/src/App.svelte +154 -0
  106. package/templates/svelte-sync/src/Root.svelte +22 -0
  107. package/templates/svelte-sync/src/auth.ts +25 -0
  108. package/templates/svelte-sync/src/index.css +339 -0
  109. package/templates/svelte-sync/src/kora-worker.ts +9 -0
  110. package/templates/svelte-sync/src/main.ts +32 -0
  111. package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
  112. package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
  113. package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
  114. package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
  115. package/templates/svelte-sync/src/schema.ts +9 -0
  116. package/templates/svelte-sync/src/vite-env.d.ts +21 -0
  117. package/templates/svelte-sync/svelte.config.js +5 -0
  118. package/templates/svelte-sync/tsconfig.json.hbs +14 -0
  119. package/templates/svelte-sync/vite.config.ts +79 -0
  120. package/templates/svelte-tailwind/README.md.hbs +62 -0
  121. package/templates/svelte-tailwind/index.html.hbs +12 -0
  122. package/templates/svelte-tailwind/kora.config.ts +12 -0
  123. package/templates/svelte-tailwind/package.json.hbs +30 -0
  124. package/templates/svelte-tailwind/src/App.svelte +79 -0
  125. package/templates/svelte-tailwind/src/Root.svelte +14 -0
  126. package/templates/svelte-tailwind/src/index.css +7 -0
  127. package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
  128. package/templates/svelte-tailwind/src/main.ts +16 -0
  129. package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  130. package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
  131. package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
  132. package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
  133. package/templates/svelte-tailwind/src/schema.ts +9 -0
  134. package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
  135. package/templates/svelte-tailwind/svelte.config.js +5 -0
  136. package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
  137. package/templates/svelte-tailwind/vite.config.ts +53 -0
  138. package/templates/svelte-tailwind-sync/.env.example +25 -0
  139. package/templates/svelte-tailwind-sync/README.md.hbs +105 -0
  140. package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
  141. package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
  142. package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
  143. package/templates/svelte-tailwind-sync/server.ts +90 -0
  144. package/templates/svelte-tailwind-sync/src/App.svelte +196 -0
  145. package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
  146. package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
  147. package/templates/svelte-tailwind-sync/src/index.css +7 -0
  148. package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
  149. package/templates/svelte-tailwind-sync/src/main.ts +32 -0
  150. package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  151. package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  152. package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  153. package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
  154. package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
  155. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +21 -0
  156. package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
  157. package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
  158. package/templates/svelte-tailwind-sync/vite.config.ts +80 -0
  159. package/templates/tauri-react/.env.example +20 -0
  160. package/templates/tauri-react/README.md.hbs +36 -1
  161. package/templates/tauri-react/dev.ts +11 -11
  162. package/templates/tauri-react/package.json.hbs +2 -0
  163. package/templates/tauri-react/server.ts +86 -12
  164. package/templates/tauri-react/src/App.tsx +769 -544
  165. package/templates/tauri-react/src/AppShell.tsx +125 -83
  166. package/templates/tauri-react/src/SetupScreen.tsx +257 -208
  167. package/templates/tauri-react/src/auth.ts +45 -0
  168. package/templates/tauri-react/src/main.tsx +3 -3
  169. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  170. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  171. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  172. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  173. package/templates/tauri-react/src/schema.ts +6 -12
  174. package/templates/tauri-react/src/sync-config.ts +47 -47
  175. package/templates/tauri-react/src/updater.ts +15 -15
  176. package/templates/tauri-react/src/vite-env.d.ts +17 -0
  177. package/templates/tauri-react/src-tauri/tauri.conf.json +1 -1
  178. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  179. package/templates/tauri-react/vite.config.ts +9 -9
  180. package/templates/vue-basic/README.md.hbs +62 -0
  181. package/templates/vue-basic/index.html.hbs +12 -0
  182. package/templates/vue-basic/kora.config.ts +12 -0
  183. package/templates/vue-basic/package.json.hbs +26 -0
  184. package/templates/vue-basic/src/App.vue +117 -0
  185. package/templates/vue-basic/src/index.css +287 -0
  186. package/templates/vue-basic/src/kora-worker.ts +9 -0
  187. package/templates/vue-basic/src/main.ts +20 -0
  188. package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
  189. package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
  190. package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
  191. package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
  192. package/templates/vue-basic/src/schema.ts +9 -0
  193. package/templates/vue-basic/src/vite-env.d.ts +17 -0
  194. package/templates/vue-basic/tsconfig.json.hbs +15 -0
  195. package/templates/vue-basic/vite.config.ts +69 -0
  196. package/templates/vue-sync/.env.example +25 -0
  197. package/templates/vue-sync/README.md.hbs +105 -0
  198. package/templates/vue-sync/index.html.hbs +12 -0
  199. package/templates/vue-sync/kora.config.ts +17 -0
  200. package/templates/vue-sync/package.json.hbs +42 -0
  201. package/templates/vue-sync/server.ts +90 -0
  202. package/templates/vue-sync/src/App.vue +135 -0
  203. package/templates/vue-sync/src/auth.ts +25 -0
  204. package/templates/vue-sync/src/index.css +339 -0
  205. package/templates/vue-sync/src/kora-worker.ts +9 -0
  206. package/templates/vue-sync/src/main.ts +43 -0
  207. package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
  208. package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
  209. package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
  210. package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
  211. package/templates/vue-sync/src/schema.ts +9 -0
  212. package/templates/vue-sync/src/vite-env.d.ts +26 -0
  213. package/templates/vue-sync/tsconfig.json.hbs +15 -0
  214. package/templates/vue-sync/vite.config.ts +79 -0
  215. package/templates/vue-tailwind/README.md.hbs +62 -0
  216. package/templates/vue-tailwind/index.html.hbs +12 -0
  217. package/templates/vue-tailwind/kora.config.ts +12 -0
  218. package/templates/vue-tailwind/package.json.hbs +29 -0
  219. package/templates/vue-tailwind/src/App.vue +142 -0
  220. package/templates/vue-tailwind/src/index.css +7 -0
  221. package/templates/vue-tailwind/src/kora-worker.ts +9 -0
  222. package/templates/vue-tailwind/src/main.ts +31 -0
  223. package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  224. package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
  225. package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
  226. package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
  227. package/templates/vue-tailwind/src/schema.ts +9 -0
  228. package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
  229. package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
  230. package/templates/vue-tailwind/vite.config.ts +70 -0
  231. package/templates/vue-tailwind-sync/.env.example +25 -0
  232. package/templates/vue-tailwind-sync/README.md.hbs +105 -0
  233. package/templates/vue-tailwind-sync/index.html.hbs +12 -0
  234. package/templates/vue-tailwind-sync/kora.config.ts +17 -0
  235. package/templates/vue-tailwind-sync/package.json.hbs +45 -0
  236. package/templates/vue-tailwind-sync/server.ts +90 -0
  237. package/templates/vue-tailwind-sync/src/App.vue +245 -0
  238. package/templates/vue-tailwind-sync/src/auth.ts +25 -0
  239. package/templates/vue-tailwind-sync/src/index.css +7 -0
  240. package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
  241. package/templates/vue-tailwind-sync/src/main.ts +56 -0
  242. package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  243. package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  244. package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  245. package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
  246. package/templates/vue-tailwind-sync/src/schema.ts +9 -0
  247. package/templates/vue-tailwind-sync/src/vite-env.d.ts +26 -0
  248. package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
  249. package/templates/vue-tailwind-sync/vite.config.ts +80 -0
  250. package/dist/chunk-CMSX76KM.js.map +0 -1
  251. package/dist/chunk-MVP5PDT4.js.map +0 -1
  252. package/dist/chunk-YGVO4POI.js.map +0 -1
  253. package/templates/tauri-react/tsconfig.json +0 -15
@@ -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,46 @@
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
+ "@lucide/svelte": "^0.468.0",
20
+ "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
21
+ "svelte": "^5.0.0",
22
+ "ws": "^8.18.0",
23
+ "yjs": "^13.6.30"
24
+ },
25
+ "pnpm": {
26
+ "onlyBuiltDependencies": [
27
+ "better-sqlite3",
28
+ "esbuild",
29
+ "protobufjs"
30
+ ]
31
+ },
32
+ "devDependencies": {
33
+ "@korajs/cli": "{{koraVersion}}",
34
+ "@korajs/server": "{{koraVersion}}",
35
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
36
+ "@tailwindcss/vite": "^4.0.0",
37
+ "@types/ws": "^8.5.0",
38
+ "better-sqlite3": "^12.9.0",
39
+ "postgres": "^3.4.0",
40
+ "svelte-check": "^4.1.0",
41
+ "tailwindcss": "^4.0.0",
42
+ "tsx": "^4.19.0",
43
+ "typescript": "^5.7.0",
44
+ "vite": "^6.0.0"
45
+ }
46
+ }
@@ -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,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,7 @@
1
+ @import "tailwindcss";
2
+
3
+ @layer base {
4
+ body {
5
+ @apply bg-gray-950 text-gray-100 antialiased;
6
+ }
7
+ }
@@ -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,5 @@
1
+ import type { CollectionAccessor } from 'korajs'
2
+
3
+ export function orderedTodos(todos: CollectionAccessor) {
4
+ return todos.where({}).orderBy('createdAt', 'desc')
5
+ }
@@ -0,0 +1,10 @@
1
+ import { t } from 'korajs'
2
+
3
+ export const todos = {
4
+ fields: {
5
+ title: t.string(),
6
+ completed: t.boolean().default(false),
7
+ createdAt: t.timestamp().auto(),
8
+ },
9
+ indexes: ['completed', 'createdAt'],
10
+ }
@@ -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,9 @@
1
+ import { defineSchema } from 'korajs'
2
+ import { todos } from './modules/todos/todo.schema'
3
+
4
+ export default defineSchema({
5
+ version: 1,
6
+ collections: {
7
+ todos,
8
+ },
9
+ })
@@ -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
+ }