@korajs/cli 0.0.0-canary-20261008174129

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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/bin.cjs +9465 -0
  4. package/dist/bin.cjs.map +1 -0
  5. package/dist/bin.js +3150 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/chunk-5NI2FEQL.js +92 -0
  8. package/dist/chunk-5NI2FEQL.js.map +1 -0
  9. package/dist/chunk-6C4BHSRA.js +82 -0
  10. package/dist/chunk-6C4BHSRA.js.map +1 -0
  11. package/dist/chunk-7Y4J2GXP.js +796 -0
  12. package/dist/chunk-7Y4J2GXP.js.map +1 -0
  13. package/dist/chunk-BURFQVMV.js +719 -0
  14. package/dist/chunk-BURFQVMV.js.map +1 -0
  15. package/dist/chunk-TVIRMDER.js +2569 -0
  16. package/dist/chunk-TVIRMDER.js.map +1 -0
  17. package/dist/create.cjs +1368 -0
  18. package/dist/create.cjs.map +1 -0
  19. package/dist/create.js +11 -0
  20. package/dist/create.js.map +1 -0
  21. package/dist/index.cjs +3489 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +772 -0
  24. package/dist/index.d.ts +772 -0
  25. package/dist/index.js +129 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lab-manager-KUDII6BT.js +280 -0
  28. package/dist/lab-manager-KUDII6BT.js.map +1 -0
  29. package/dist/schema-loader-GGHECMTM.js +9 -0
  30. package/dist/schema-loader-GGHECMTM.js.map +1 -0
  31. package/dist/spectator-manager-FTH2NFSE.js +145 -0
  32. package/dist/spectator-manager-FTH2NFSE.js.map +1 -0
  33. package/dist/studio-server-XLGZNGG5.js +1747 -0
  34. package/dist/studio-server-XLGZNGG5.js.map +1 -0
  35. package/dist/vite.cjs +351 -0
  36. package/dist/vite.cjs.map +1 -0
  37. package/dist/vite.d.cts +161 -0
  38. package/dist/vite.d.ts +161 -0
  39. package/dist/vite.js +321 -0
  40. package/dist/vite.js.map +1 -0
  41. package/package.json +79 -0
  42. package/templates/react-basic/.env.example +3 -0
  43. package/templates/react-basic/AGENTS.md +87 -0
  44. package/templates/react-basic/README.md.hbs +62 -0
  45. package/templates/react-basic/index.html.hbs +12 -0
  46. package/templates/react-basic/kora.config.ts +13 -0
  47. package/templates/react-basic/package.json.hbs +35 -0
  48. package/templates/react-basic/src/App.tsx +132 -0
  49. package/templates/react-basic/src/index.css +287 -0
  50. package/templates/react-basic/src/kora-worker.ts +9 -0
  51. package/templates/react-basic/src/main.tsx +24 -0
  52. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  53. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  54. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  55. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  56. package/templates/react-basic/src/schema.ts +9 -0
  57. package/templates/react-basic/src/vite-env.d.ts +18 -0
  58. package/templates/react-basic/tsconfig.json.hbs +14 -0
  59. package/templates/react-basic/vite.config.ts +88 -0
  60. package/templates/react-sync/.env.example +25 -0
  61. package/templates/react-sync/AGENTS.md +87 -0
  62. package/templates/react-sync/README.md.hbs +106 -0
  63. package/templates/react-sync/index.html.hbs +12 -0
  64. package/templates/react-sync/kora.config.ts +17 -0
  65. package/templates/react-sync/package.json.hbs +44 -0
  66. package/templates/react-sync/server.ts +141 -0
  67. package/templates/react-sync/src/App.tsx +160 -0
  68. package/templates/react-sync/src/auth.ts +25 -0
  69. package/templates/react-sync/src/index.css +350 -0
  70. package/templates/react-sync/src/kora-worker.ts +9 -0
  71. package/templates/react-sync/src/main.tsx +52 -0
  72. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-sync/src/schema.ts +9 -0
  77. package/templates/react-sync/src/vite-env.d.ts +19 -0
  78. package/templates/react-sync/tsconfig.json.hbs +14 -0
  79. package/templates/react-sync/vite.config.ts +105 -0
  80. package/templates/react-tailwind/.env.example +3 -0
  81. package/templates/react-tailwind/AGENTS.md +87 -0
  82. package/templates/react-tailwind/README.md.hbs +62 -0
  83. package/templates/react-tailwind/index.html.hbs +12 -0
  84. package/templates/react-tailwind/kora.config.ts +13 -0
  85. package/templates/react-tailwind/package.json.hbs +38 -0
  86. package/templates/react-tailwind/src/App.tsx +201 -0
  87. package/templates/react-tailwind/src/index.css +7 -0
  88. package/templates/react-tailwind/src/kora-worker.ts +9 -0
  89. package/templates/react-tailwind/src/main.tsx +31 -0
  90. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  94. package/templates/react-tailwind/src/schema.ts +9 -0
  95. package/templates/react-tailwind/src/vite-env.d.ts +18 -0
  96. package/templates/react-tailwind/tsconfig.json.hbs +14 -0
  97. package/templates/react-tailwind/vite.config.ts +95 -0
  98. package/templates/react-tailwind-sync/.env.example +25 -0
  99. package/templates/react-tailwind-sync/AGENTS.md +89 -0
  100. package/templates/react-tailwind-sync/README.md.hbs +106 -0
  101. package/templates/react-tailwind-sync/index.html.hbs +12 -0
  102. package/templates/react-tailwind-sync/kora.config.ts +17 -0
  103. package/templates/react-tailwind-sync/package.json.hbs +47 -0
  104. package/templates/react-tailwind-sync/server.ts +141 -0
  105. package/templates/react-tailwind-sync/src/App.tsx +293 -0
  106. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  107. package/templates/react-tailwind-sync/src/index.css +7 -0
  108. package/templates/react-tailwind-sync/src/kora-worker.ts +9 -0
  109. package/templates/react-tailwind-sync/src/kora.ts +32 -0
  110. package/templates/react-tailwind-sync/src/main.tsx +44 -0
  111. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +21 -0
  112. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  113. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  114. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  115. package/templates/react-tailwind-sync/src/schema.ts +9 -0
  116. package/templates/react-tailwind-sync/src/vite-env.d.ts +19 -0
  117. package/templates/react-tailwind-sync/tsconfig.json.hbs +14 -0
  118. package/templates/react-tailwind-sync/vite.config.ts +112 -0
  119. package/templates/svelte-basic/.env.example +3 -0
  120. package/templates/svelte-basic/AGENTS.md +76 -0
  121. package/templates/svelte-basic/README.md.hbs +62 -0
  122. package/templates/svelte-basic/index.html.hbs +12 -0
  123. package/templates/svelte-basic/kora.config.ts +12 -0
  124. package/templates/svelte-basic/package.json.hbs +33 -0
  125. package/templates/svelte-basic/src/App.svelte +128 -0
  126. package/templates/svelte-basic/src/Root.svelte +14 -0
  127. package/templates/svelte-basic/src/index.css +287 -0
  128. package/templates/svelte-basic/src/kora-worker.ts +9 -0
  129. package/templates/svelte-basic/src/main.ts +16 -0
  130. package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
  131. package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
  132. package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
  133. package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
  134. package/templates/svelte-basic/src/schema.ts +9 -0
  135. package/templates/svelte-basic/src/vite-env.d.ts +12 -0
  136. package/templates/svelte-basic/svelte.config.js +5 -0
  137. package/templates/svelte-basic/tsconfig.json.hbs +14 -0
  138. package/templates/svelte-basic/vite.config.ts +57 -0
  139. package/templates/svelte-sync/.env.example +25 -0
  140. package/templates/svelte-sync/AGENTS.md +76 -0
  141. package/templates/svelte-sync/README.md.hbs +106 -0
  142. package/templates/svelte-sync/index.html.hbs +12 -0
  143. package/templates/svelte-sync/kora.config.ts +17 -0
  144. package/templates/svelte-sync/package.json.hbs +43 -0
  145. package/templates/svelte-sync/server.ts +141 -0
  146. package/templates/svelte-sync/src/App.svelte +151 -0
  147. package/templates/svelte-sync/src/Root.svelte +22 -0
  148. package/templates/svelte-sync/src/auth.ts +25 -0
  149. package/templates/svelte-sync/src/index.css +339 -0
  150. package/templates/svelte-sync/src/kora-worker.ts +9 -0
  151. package/templates/svelte-sync/src/main.ts +32 -0
  152. package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
  153. package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
  154. package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
  155. package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
  156. package/templates/svelte-sync/src/schema.ts +9 -0
  157. package/templates/svelte-sync/src/vite-env.d.ts +22 -0
  158. package/templates/svelte-sync/svelte.config.js +5 -0
  159. package/templates/svelte-sync/tsconfig.json.hbs +14 -0
  160. package/templates/svelte-sync/vite.config.ts +91 -0
  161. package/templates/svelte-tailwind/AGENTS.md +76 -0
  162. package/templates/svelte-tailwind/README.md.hbs +62 -0
  163. package/templates/svelte-tailwind/index.html.hbs +12 -0
  164. package/templates/svelte-tailwind/kora.config.ts +12 -0
  165. package/templates/svelte-tailwind/package.json.hbs +36 -0
  166. package/templates/svelte-tailwind/src/App.svelte +76 -0
  167. package/templates/svelte-tailwind/src/Root.svelte +14 -0
  168. package/templates/svelte-tailwind/src/index.css +7 -0
  169. package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
  170. package/templates/svelte-tailwind/src/main.ts +16 -0
  171. package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  172. package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
  173. package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
  174. package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
  175. package/templates/svelte-tailwind/src/schema.ts +9 -0
  176. package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
  177. package/templates/svelte-tailwind/svelte.config.js +5 -0
  178. package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
  179. package/templates/svelte-tailwind/vite.config.ts +64 -0
  180. package/templates/svelte-tailwind-sync/.env.example +25 -0
  181. package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
  182. package/templates/svelte-tailwind-sync/README.md.hbs +106 -0
  183. package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
  184. package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
  185. package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
  186. package/templates/svelte-tailwind-sync/server.ts +141 -0
  187. package/templates/svelte-tailwind-sync/src/App.svelte +193 -0
  188. package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
  189. package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
  190. package/templates/svelte-tailwind-sync/src/index.css +7 -0
  191. package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
  192. package/templates/svelte-tailwind-sync/src/main.ts +32 -0
  193. package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  194. package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  195. package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  196. package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
  197. package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
  198. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +22 -0
  199. package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
  200. package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
  201. package/templates/svelte-tailwind-sync/vite.config.ts +98 -0
  202. package/templates/tauri-react/.env.example +27 -0
  203. package/templates/tauri-react/.github/workflows/release-desktop.yml +107 -0
  204. package/templates/tauri-react/AGENTS.md +91 -0
  205. package/templates/tauri-react/README.md.hbs +183 -0
  206. package/templates/tauri-react/dev.ts +37 -0
  207. package/templates/tauri-react/index.html.hbs +16 -0
  208. package/templates/tauri-react/kora.config.ts +17 -0
  209. package/templates/tauri-react/package.json.hbs +46 -0
  210. package/templates/tauri-react/server.ts +148 -0
  211. package/templates/tauri-react/src/App.tsx +803 -0
  212. package/templates/tauri-react/src/AppShell.tsx +158 -0
  213. package/templates/tauri-react/src/SetupScreen.tsx +288 -0
  214. package/templates/tauri-react/src/auth.ts +45 -0
  215. package/templates/tauri-react/src/main.tsx +9 -0
  216. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  217. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  218. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  219. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  220. package/templates/tauri-react/src/schema.ts +9 -0
  221. package/templates/tauri-react/src/sync-config.ts +103 -0
  222. package/templates/tauri-react/src/updater.ts +38 -0
  223. package/templates/tauri-react/src/vite-env.d.ts +19 -0
  224. package/templates/tauri-react/src-tauri/Cargo.toml +19 -0
  225. package/templates/tauri-react/src-tauri/build.rs +3 -0
  226. package/templates/tauri-react/src-tauri/capabilities/default.json +12 -0
  227. package/templates/tauri-react/src-tauri/icons/128x128.png +0 -0
  228. package/templates/tauri-react/src-tauri/icons/128x128@2x.png +0 -0
  229. package/templates/tauri-react/src-tauri/icons/32x32.png +0 -0
  230. package/templates/tauri-react/src-tauri/icons/icon.icns +0 -0
  231. package/templates/tauri-react/src-tauri/icons/icon.ico +0 -0
  232. package/templates/tauri-react/src-tauri/src/lib.rs +8 -0
  233. package/templates/tauri-react/src-tauri/src/main.rs +6 -0
  234. package/templates/tauri-react/src-tauri/tauri.conf.json +44 -0
  235. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  236. package/templates/tauri-react/vite.config.ts +15 -0
  237. package/templates/vue-basic/AGENTS.md +84 -0
  238. package/templates/vue-basic/README.md.hbs +62 -0
  239. package/templates/vue-basic/index.html.hbs +12 -0
  240. package/templates/vue-basic/kora.config.ts +12 -0
  241. package/templates/vue-basic/package.json.hbs +32 -0
  242. package/templates/vue-basic/src/App.vue +117 -0
  243. package/templates/vue-basic/src/index.css +287 -0
  244. package/templates/vue-basic/src/kora-worker.ts +9 -0
  245. package/templates/vue-basic/src/main.ts +20 -0
  246. package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
  247. package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
  248. package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
  249. package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
  250. package/templates/vue-basic/src/schema.ts +9 -0
  251. package/templates/vue-basic/src/vite-env.d.ts +17 -0
  252. package/templates/vue-basic/tsconfig.json.hbs +15 -0
  253. package/templates/vue-basic/vite.config.ts +74 -0
  254. package/templates/vue-sync/.env.example +25 -0
  255. package/templates/vue-sync/AGENTS.md +84 -0
  256. package/templates/vue-sync/README.md.hbs +106 -0
  257. package/templates/vue-sync/index.html.hbs +12 -0
  258. package/templates/vue-sync/kora.config.ts +17 -0
  259. package/templates/vue-sync/package.json.hbs +42 -0
  260. package/templates/vue-sync/server.ts +141 -0
  261. package/templates/vue-sync/src/App.vue +135 -0
  262. package/templates/vue-sync/src/auth.ts +25 -0
  263. package/templates/vue-sync/src/index.css +339 -0
  264. package/templates/vue-sync/src/kora-worker.ts +9 -0
  265. package/templates/vue-sync/src/main.ts +47 -0
  266. package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
  267. package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
  268. package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
  269. package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
  270. package/templates/vue-sync/src/schema.ts +9 -0
  271. package/templates/vue-sync/src/vite-env.d.ts +27 -0
  272. package/templates/vue-sync/tsconfig.json.hbs +15 -0
  273. package/templates/vue-sync/vite.config.ts +91 -0
  274. package/templates/vue-tailwind/AGENTS.md +84 -0
  275. package/templates/vue-tailwind/README.md.hbs +62 -0
  276. package/templates/vue-tailwind/index.html.hbs +12 -0
  277. package/templates/vue-tailwind/kora.config.ts +12 -0
  278. package/templates/vue-tailwind/package.json.hbs +35 -0
  279. package/templates/vue-tailwind/src/App.vue +135 -0
  280. package/templates/vue-tailwind/src/index.css +7 -0
  281. package/templates/vue-tailwind/src/kora-worker.ts +9 -0
  282. package/templates/vue-tailwind/src/main.ts +31 -0
  283. package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  284. package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
  285. package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
  286. package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
  287. package/templates/vue-tailwind/src/schema.ts +9 -0
  288. package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
  289. package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
  290. package/templates/vue-tailwind/vite.config.ts +75 -0
  291. package/templates/vue-tailwind-sync/.env.example +25 -0
  292. package/templates/vue-tailwind-sync/AGENTS.md +84 -0
  293. package/templates/vue-tailwind-sync/README.md.hbs +106 -0
  294. package/templates/vue-tailwind-sync/index.html.hbs +12 -0
  295. package/templates/vue-tailwind-sync/kora.config.ts +17 -0
  296. package/templates/vue-tailwind-sync/package.json.hbs +45 -0
  297. package/templates/vue-tailwind-sync/server.ts +141 -0
  298. package/templates/vue-tailwind-sync/src/App.vue +245 -0
  299. package/templates/vue-tailwind-sync/src/auth.ts +25 -0
  300. package/templates/vue-tailwind-sync/src/index.css +7 -0
  301. package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
  302. package/templates/vue-tailwind-sync/src/main.ts +56 -0
  303. package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  304. package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  305. package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  306. package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
  307. package/templates/vue-tailwind-sync/src/schema.ts +9 -0
  308. package/templates/vue-tailwind-sync/src/vite-env.d.ts +27 -0
  309. package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
  310. package/templates/vue-tailwind-sync/vite.config.ts +92 -0
@@ -0,0 +1,76 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents working in this project. Humans: this is useful for you too.
4
+
5
+ ## What this project is
6
+
7
+ This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
8
+
9
+ ## Golden rules
10
+
11
+ 1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
12
+ 2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
13
+ 3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
14
+ 4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
15
+ 5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
16
+ 6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
17
+ 7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
18
+
19
+ ## Data API cheat sheet
20
+
21
+ ```ts
22
+ await app.ready
23
+ const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
24
+ await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
25
+ await app.todos.delete(rec.id)
26
+ const one = await app.todos.findById(rec.id)
27
+ const unsubscribe = app.todos
28
+ .where({ completed: false })
29
+ .orderBy('createdAt', 'desc')
30
+ .subscribe((rows) => {/* fires immediately, then on every change */})
31
+ ```
32
+
33
+ Schema example (`src/schema.ts`):
34
+
35
+ ```ts
36
+ import { defineSchema, t } from 'korajs'
37
+
38
+ export default defineSchema({
39
+ version: 1,
40
+ collections: {
41
+ todos: {
42
+ fields: {
43
+ title: t.string(),
44
+ completed: t.boolean().default(false),
45
+ tags: t.array(t.string()).default([]),
46
+ priority: t.enum(['low', 'medium', 'high']).default('medium'),
47
+ createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
48
+ },
49
+ indexes: ['completed', 'createdAt'],
50
+ },
51
+ },
52
+ })
53
+ ```
54
+
55
+ If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
56
+
57
+ ## Project conventions
58
+
59
+ - Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
60
+ - Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
61
+ - `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
62
+
63
+ ## Commands
64
+
65
+ - `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
66
+ - `npm run build` type-checks and builds for production.
67
+ - `npx kora doctor` diagnoses a broken setup.
68
+ - DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
69
+
70
+ ## Sync and auth
71
+
72
+ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })`. In dev, the sync URL is derived from the page host and proxied by Vite; in production set `VITE_SYNC_URL`. Auth (if present in this template) uses `@korajs/auth`; the client is created in `src/auth.ts`. Local writes work without sign-in; sync requires the server to accept the connection.
73
+
74
+ ## Svelte bindings
75
+
76
+ Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores over the local database (no network, no spinner; `[]` until the first local result); mutations expose error state that must be surfaced in the UI.
@@ -0,0 +1,106 @@
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/kora-sync
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/kora-sync` | WebSocket URL for the sync endpoint |
44
+ | `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
45
+ | `PORT` | `3001` | Sync server port |
46
+ | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
+ | `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
48
+ | `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
49
+ | `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
50
+ | `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
51
+ | `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
52
+ | `KORA_GOOGLE_CLIENT_ID` | (none) | Enables Google OAuth when set with `KORA_GOOGLE_REDIRECT_URI` |
53
+ | `KORA_GOOGLE_CLIENT_SECRET` | (none) | Google OAuth client secret for confidential web clients |
54
+ | `KORA_GOOGLE_REDIRECT_URI` | (none) | OAuth callback URL, usually your app URL that handles `code` and `state` |
55
+ | `KORA_OAUTH_PKCE` | `true` when no client secret | Enables PKCE for public desktop/mobile OAuth clients |
56
+ | `KORA_ADMIN_TOKEN` | (none) | Protects `/__kora`, `/__kora/status`, and `/__kora/events` |
57
+ | `KORA_METRICS_TOKEN` | (none) | Protects `/__kora/metrics` |
58
+ | `KORA_BACKUP_TOKEN` | (none) | Protects server backup and restore endpoints |
59
+
60
+ ## Authentication
61
+
62
+ Set `KORA_AUTH_SECRET` to enable built-in auth on the sync server. Generate a secret with:
63
+
64
+ ```bash
65
+ node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
66
+ ```
67
+
68
+ The template already creates `src/auth.ts`, wraps the app in `AuthProvider`, and wires sync with `createKoraAuthSync({ authClient, schema })`. To enable Google OAuth, set:
69
+
70
+ ```bash
71
+ KORA_AUTH_SECRET=...
72
+ KORA_GOOGLE_CLIENT_ID=...
73
+ KORA_GOOGLE_CLIENT_SECRET=...
74
+ KORA_GOOGLE_REDIRECT_URI=http://localhost:5173
75
+ ```
76
+
77
+ 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).
78
+
79
+ ## Scripts
80
+
81
+ | Script | Description |
82
+ |--------|-------------|
83
+ | `npm run dev` | Start dev server + sync server |
84
+ | `npm run dev:server` | Start sync server only |
85
+ | `npm run build` | Build for production |
86
+ | `npm run preview` | Preview production build |
87
+
88
+ ## Deployment
89
+
90
+ The client and sync server must be deployed separately:
91
+
92
+ - **Client**: Any static host (Vercel, Netlify, Cloudflare Pages)
93
+ - **Sync server**: Any Node.js host with WebSocket support (Railway, Render, Fly.io)
94
+
95
+ Set `VITE_SYNC_URL` to your deployed sync server URL when building:
96
+
97
+ ```bash
98
+ VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
99
+ ```
100
+
101
+ ## Learn More
102
+
103
+ - [Documentation](https://ehoneahobed.github.io/kora/)
104
+ - [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
105
+ - [Sync Configuration](https://ehoneahobed.github.io/kora/guide/sync-configuration)
106
+ - [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.479.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,141 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import {
3
+ type CreateKoraAuthServerOptions,
4
+ createKoraAuthServer,
5
+ createPostgresOAuthStores,
6
+ createPostgresUserStore,
7
+ createSqliteOAuthStores,
8
+ createSqliteUserStore,
9
+ googleProvider,
10
+ } from '@korajs/auth/server'
11
+ import {
12
+ createPostgresServerStore,
13
+ createProductionServer,
14
+ createSqliteServerStore,
15
+ } from '@korajs/server'
16
+ import schema from './src/schema'
17
+
18
+ loadLocalEnv()
19
+
20
+ function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
21
+ const existingKeys = new Set(Object.keys(process.env))
22
+ for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
23
+ if (!existsSync(file)) continue
24
+ for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
25
+ if (!existingKeys.has(key)) {
26
+ process.env[key] = value
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ function parseEnvFile(contents: string): Record<string, string> {
33
+ const env: Record<string, string> = {}
34
+ for (const rawLine of contents.split(/\r?\n/)) {
35
+ const line = rawLine.trim()
36
+ if (!line || line.startsWith('#')) continue
37
+ const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
38
+ const equalsIndex = normalized.indexOf('=')
39
+ if (equalsIndex <= 0) continue
40
+ const key = normalized.slice(0, equalsIndex).trim()
41
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
42
+ env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
43
+ }
44
+ return env
45
+ }
46
+
47
+ function parseEnvValue(value: string): string {
48
+ const quote = value[0]
49
+ if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
50
+ const inner = value.slice(1, -1)
51
+ return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
52
+ }
53
+ const commentIndex = value.search(/\s#/)
54
+ return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
55
+ }
56
+
57
+ async function createStore() {
58
+ if (process.env.DATABASE_URL) {
59
+ return createPostgresServerStore({
60
+ connectionString: process.env.DATABASE_URL,
61
+ })
62
+ }
63
+
64
+ return createSqliteServerStore({
65
+ filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
66
+ })
67
+ }
68
+
69
+ async function start() {
70
+ const store = await createStore()
71
+ await store.setSchema(schema)
72
+
73
+ const syncPath = process.env.KORA_SYNC_PATH || '/kora-sync'
74
+ const auth = await createAuth()
75
+ const server = createProductionServer({
76
+ store,
77
+ port: Number(process.env.PORT) || 3001,
78
+ staticDir: './dist',
79
+ syncPath,
80
+ httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
81
+ syncOptions: {
82
+ schemaVersion: schema.version,
83
+ ...(auth ? { auth: auth.auth } : {}),
84
+ },
85
+ operationalAuth: {
86
+ adminToken: process.env.KORA_ADMIN_TOKEN,
87
+ metricsToken: process.env.KORA_METRICS_TOKEN,
88
+ backupToken: process.env.KORA_BACKUP_TOKEN,
89
+ },
90
+ })
91
+
92
+ const url = await server.start()
93
+ console.log(`Kora app running at ${url}`)
94
+ console.log(` Sync endpoint: ${url.replace('http', 'ws')}${syncPath}`)
95
+ if (auth) console.log(` Auth endpoint: ${url}/auth`)
96
+ }
97
+
98
+ void start()
99
+
100
+ async function createAuth() {
101
+ if (!process.env.KORA_AUTH_SECRET) {
102
+ return null
103
+ }
104
+
105
+ const oauth = await createOAuthConfig()
106
+ // Users and token revocations persist in the same database (the user store's
107
+ // revocation store is used automatically), so sign-outs survive restarts.
108
+ const userStore = process.env.DATABASE_URL
109
+ ? await createPostgresUserStore({ connectionString: process.env.DATABASE_URL })
110
+ : await createSqliteUserStore({ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db' })
111
+ return createKoraAuthServer({
112
+ jwtSecret: process.env.KORA_AUTH_SECRET,
113
+ userStore,
114
+ ...(oauth ? { oauth } : {}),
115
+ })
116
+ }
117
+
118
+ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth'] | undefined> {
119
+ if (!process.env.KORA_GOOGLE_CLIENT_ID || !process.env.KORA_GOOGLE_REDIRECT_URI) {
120
+ return undefined
121
+ }
122
+
123
+ const oauthStores = process.env.DATABASE_URL
124
+ ? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
125
+ : await createSqliteOAuthStores({
126
+ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
127
+ })
128
+
129
+ return {
130
+ providers: [
131
+ googleProvider({
132
+ clientId: process.env.KORA_GOOGLE_CLIENT_ID,
133
+ clientSecret: process.env.KORA_GOOGLE_CLIENT_SECRET,
134
+ redirectUri: process.env.KORA_GOOGLE_REDIRECT_URI,
135
+ pkce: process.env.KORA_OAUTH_PKCE === 'true' || !process.env.KORA_GOOGLE_CLIENT_SECRET,
136
+ }),
137
+ ],
138
+ stateStore: oauthStores.stateStore,
139
+ linkedIdentityStore: oauthStores.linkedIdentityStore,
140
+ }
141
+ }
@@ -0,0 +1,193 @@
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
+ // biome-ignore lint/style/useConst: reassigned in the markup, which Biome does not see
25
+ let filter = $state<Filter>('all')
26
+ let input = $state('')
27
+
28
+ const filteredTodos = $derived(
29
+ filter === 'active' ? $activeTodos : filter === 'completed' ? $completedTodos : $allTodos,
30
+ )
31
+
32
+ const syncBadge = $derived.by(() => {
33
+ const s = $syncStatus.status
34
+ const pending = $syncStatus.pendingOperations ?? 0
35
+ const map: Record<string, { icon: typeof Wifi; color: string; label: string }> = {
36
+ connected: { icon: Wifi, color: 'text-emerald-400', label: 'Connected' },
37
+ syncing: { icon: Wifi, color: 'text-amber-400', label: 'Syncing' },
38
+ synced: { icon: Wifi, color: 'text-emerald-400', label: 'Synced' },
39
+ offline: { icon: WifiOff, color: 'text-gray-500', label: 'Offline' },
40
+ error: { icon: AlertCircle, color: 'text-red-400', label: 'Error' },
41
+ }
42
+ return { ...(map[s] ?? map.offline), pending }
43
+ })
44
+
45
+ function handleSubmit(event: SubmitEvent): void {
46
+ event.preventDefault()
47
+ const title = input.trim()
48
+ if (!title) return
49
+ addTodo.mutate({ title })
50
+ input = ''
51
+ }
52
+
53
+ function clearCompleted(): void {
54
+ for (const todo of $completedTodos) {
55
+ deleteTodo.mutate(todo.id)
56
+ }
57
+ }
58
+
59
+ function emptyMessage(f: Filter): string {
60
+ if (f === 'all') return 'No tasks yet. Add one above!'
61
+ if (f === 'active') return 'All caught up! No active tasks.'
62
+ return 'No completed tasks yet.'
63
+ }
64
+ </script>
65
+
66
+ <div class="min-h-screen bg-gray-950 text-gray-100">
67
+ <div class="mx-auto max-w-2xl px-4 py-12">
68
+ <div class="mb-8 flex items-center justify-between">
69
+ <div class="flex items-center gap-3">
70
+ <ClipboardList class="h-8 w-8 text-indigo-400" />
71
+ <h1 class="text-2xl font-bold">My Tasks</h1>
72
+ </div>
73
+ <div class="flex items-center gap-2">
74
+ <div class="flex items-center gap-2 rounded-full bg-gray-800 px-3 py-1.5 text-sm">
75
+ <svelte:component this={syncBadge.icon} class="h-4 w-4 {syncBadge.color}" />
76
+ <span class={syncBadge.color}>{syncBadge.label}</span>
77
+ {#if syncBadge.pending > 0}
78
+ <span class="rounded-full bg-gray-700 px-2 py-0.5 text-xs text-gray-400">
79
+ {syncBadge.pending} pending
80
+ </span>
81
+ {/if}
82
+ </div>
83
+ {#if $auth.isAuthenticated}
84
+ <button
85
+ type="button"
86
+ class="max-w-44 truncate rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300"
87
+ onclick={() => $auth.signOut()}
88
+ >
89
+ {$auth.user?.email || 'Sign out'}
90
+ </button>
91
+ {:else}
92
+ <button
93
+ type="button"
94
+ class="rounded-full border border-gray-700 bg-gray-900 px-3 py-1.5 text-sm text-gray-300"
95
+ onclick={() => $auth.signInWithOAuth('google')}
96
+ >
97
+ Sign in
98
+ </button>
99
+ {/if}
100
+ </div>
101
+ </div>
102
+
103
+ {#if $auth.error}
104
+ <div class="mb-4 rounded-lg border border-red-400/30 px-3 py-2 text-sm text-red-300">
105
+ {$auth.error}
106
+ </div>
107
+ {/if}
108
+
109
+ <div class="mb-8 grid grid-cols-3 gap-4">
110
+ <div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
111
+ <p class="text-sm text-gray-500">Total</p>
112
+ <p class="text-2xl font-bold text-gray-300">{$allTodos.length}</p>
113
+ </div>
114
+ <div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
115
+ <p class="text-sm text-gray-500">Remaining</p>
116
+ <p class="text-2xl font-bold text-amber-400">{$activeTodos.length}</p>
117
+ </div>
118
+ <div class="rounded-lg border border-gray-800 bg-gray-900 p-4">
119
+ <p class="text-sm text-gray-500">Done</p>
120
+ <p class="text-2xl font-bold text-emerald-400">{$completedTodos.length}</p>
121
+ </div>
122
+ </div>
123
+
124
+ <form class="mb-8 flex gap-3" onsubmit={handleSubmit}>
125
+ <input
126
+ bind:value={input}
127
+ type="text"
128
+ placeholder="What needs to be done?"
129
+ 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"
130
+ />
131
+ <button
132
+ type="submit"
133
+ disabled={!input.trim()}
134
+ class="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white disabled:opacity-50"
135
+ >
136
+ <Plus class="h-4 w-4" />
137
+ Add
138
+ </button>
139
+ </form>
140
+
141
+ <div class="mb-6 flex gap-2">
142
+ {#each ['all', 'active', 'completed'] as const as f}
143
+ <button
144
+ type="button"
145
+ class="rounded-lg px-4 py-2 text-sm font-medium {filter === f
146
+ ? 'bg-indigo-600 text-white'
147
+ : 'bg-gray-800 text-gray-400'}"
148
+ onclick={() => (filter = f)}
149
+ >
150
+ {f.charAt(0).toUpperCase() + f.slice(1)}
151
+ </button>
152
+ {/each}
153
+ </div>
154
+
155
+ <div class="space-y-2">
156
+ {#if filteredTodos.length === 0}
157
+ <div class="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600">
158
+ {emptyMessage(filter)}
159
+ </div>
160
+ {:else}
161
+ {#each filteredTodos as todo (todo.id)}
162
+ <div
163
+ class="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3"
164
+ >
165
+ <button
166
+ type="button"
167
+ class="text-gray-500 hover:text-indigo-400"
168
+ onclick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
169
+ >
170
+ {#if todo.completed}
171
+ <CheckCircle2 class="h-5 w-5 text-emerald-400" />
172
+ {:else}
173
+ <Circle class="h-5 w-5" />
174
+ {/if}
175
+ </button>
176
+ <span class="flex-1 {todo.completed ? 'line-through text-gray-500' : ''}">
177
+ {String(todo.title)}
178
+ </span>
179
+ <button
180
+ type="button"
181
+ class="text-gray-600 opacity-0 group-hover:opacity-100 hover:text-red-400"
182
+ onclick={() => deleteTodo.mutate(todo.id)}
183
+ >
184
+ <Trash2 class="h-4 w-4" />
185
+ </button>
186
+ </div>
187
+ {/each}
188
+ {/if}
189
+ </div>
190
+
191
+ <p class="mt-12 text-center text-xs text-gray-700">Powered by Kora — offline-first, real-time sync</p>
192
+ </div>
193
+ </div>
@@ -0,0 +1,26 @@
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 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'