@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,17 @@
1
+ interface ImportMetaEnv {
2
+ readonly VITE_SYNC_URL?: string
3
+ }
4
+
5
+ interface ImportMeta {
6
+ readonly env: ImportMetaEnv
7
+ }
8
+
9
+ declare module '*?worker&url' {
10
+ const url: string
11
+ export default url
12
+ }
13
+
14
+ declare module '*?url' {
15
+ const url: string
16
+ export default url
17
+ }
@@ -8,8 +8,7 @@
8
8
  "noUncheckedIndexedAccess": true,
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "outDir": "dist",
12
- "types": ["vite/client"]
11
+ "outDir": "dist"
13
12
  },
14
13
  "include": ["src"]
15
14
  }
@@ -1,5 +1,5 @@
1
- import { existsSync, readdirSync, copyFileSync } from 'node:fs'
2
- import { resolve, join } from 'node:path'
1
+ import { copyFileSync, existsSync, readdirSync } from 'node:fs'
2
+ import { join, resolve } from 'node:path'
3
3
  import react from '@vitejs/plugin-react'
4
4
  import type { Plugin } from 'vite'
5
5
  import { defineConfig } from 'vite'
@@ -10,23 +10,23 @@ import { defineConfig } from 'vite'
10
10
  * cannot be used and SQLite WASM falls back to in-memory storage (no persistence).
11
11
  */
12
12
  function crossOriginIsolation(): Plugin {
13
- return {
14
- name: 'cross-origin-isolation',
15
- configureServer(server) {
16
- server.middlewares.use((_req, res, next) => {
17
- res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
18
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
19
- next()
20
- })
21
- },
22
- configurePreviewServer(server) {
23
- server.middlewares.use((_req, res, next) => {
24
- res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
25
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
26
- next()
27
- })
28
- },
29
- }
13
+ return {
14
+ name: 'cross-origin-isolation',
15
+ configureServer(server) {
16
+ server.middlewares.use((_req, res, next) => {
17
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
18
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
19
+ next()
20
+ })
21
+ },
22
+ configurePreviewServer(server) {
23
+ server.middlewares.use((_req, res, next) => {
24
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
25
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
26
+ next()
27
+ })
28
+ },
29
+ }
30
30
  }
31
31
 
32
32
  /**
@@ -36,50 +36,57 @@ function crossOriginIsolation(): Plugin {
36
36
  * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
37
37
  */
38
38
  function sqliteWasmHotfix(): Plugin {
39
- return {
40
- name: 'sqlite-wasm-hotfix',
41
- apply: 'build',
42
- closeBundle() {
43
- const assetsDir = resolve('dist', 'assets')
44
- if (!existsSync(assetsDir)) return
39
+ return {
40
+ name: 'sqlite-wasm-hotfix',
41
+ apply: 'build',
42
+ closeBundle() {
43
+ const assetsDir = resolve('dist', 'assets')
44
+ if (!existsSync(assetsDir)) return
45
45
 
46
- // Copy hashed sqlite3.wasm to unhashed name
47
- for (const file of readdirSync(assetsDir)) {
48
- if (/^sqlite3-.+\.wasm$/.test(file)) {
49
- copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
50
- break
51
- }
52
- }
46
+ // Copy hashed sqlite3.wasm to unhashed name
47
+ for (const file of readdirSync(assetsDir)) {
48
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
49
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
50
+ break
51
+ }
52
+ }
53
53
 
54
- // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
55
- const proxyFile = resolve('node_modules', '@sqlite.org', 'sqlite-wasm', 'sqlite-wasm', 'jswasm', 'sqlite3-opfs-async-proxy.js')
56
- if (existsSync(proxyFile)) {
57
- copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
58
- }
59
- },
60
- }
54
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
55
+ const proxyFile = resolve(
56
+ 'node_modules',
57
+ '@sqlite.org',
58
+ 'sqlite-wasm',
59
+ 'sqlite-wasm',
60
+ 'jswasm',
61
+ 'sqlite3-opfs-async-proxy.js',
62
+ )
63
+ if (existsSync(proxyFile)) {
64
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
65
+ }
66
+ },
67
+ }
61
68
  }
62
69
 
63
70
  export default defineConfig({
64
- plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix()],
65
- worker: {
66
- format: 'es',
67
- },
68
- optimizeDeps: {
69
- exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
70
- include: ['yjs'],
71
- },
72
- resolve: {
73
- dedupe: ['yjs'],
74
- },
75
- server: {
76
- allowedHosts: true,
77
- proxy: {
78
- '/kora-sync': {
79
- target: 'ws://localhost:3001',
80
- ws: true,
81
- rewriteWsOrigin: true,
82
- },
83
- },
84
- },
71
+ plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix()],
72
+ worker: {
73
+ format: 'es',
74
+ },
75
+ optimizeDeps: {
76
+ exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
77
+ include: ['yjs'],
78
+ },
79
+ resolve: {
80
+ dedupe: ['yjs'],
81
+ },
82
+ server: {
83
+ allowedHosts: true,
84
+ proxy: {
85
+ '/kora-sync': {
86
+ target: 'ws://localhost:3001',
87
+ ws: true,
88
+ rewriteWsOrigin: true,
89
+ },
90
+ },
91
+ },
85
92
  })
@@ -14,14 +14,23 @@ npm run dev
14
14
 
15
15
  ```
16
16
  src/
17
- schema.ts # Data schema definition
17
+ schema.ts # Schema entry point
18
18
  main.tsx # App initialization and Kora setup
19
19
  App.tsx # Main application component
20
+ modules/
21
+ todos/
22
+ todo.schema.ts # Todo collection definition
23
+ todo.queries.ts # Reusable todo query builders
24
+ todo.mutations.ts # Todo mutation helpers
25
+ useTodos.ts # React hook connecting todos to the UI
20
26
  kora-worker.ts # SQLite WASM web worker
21
27
  index.css # Styles
22
28
  kora.config.ts # Kora configuration
23
29
  ```
24
30
 
31
+ In a feature module, `schema` defines data shape, `queries` contain reads only, `mutations` contain
32
+ writes, and `useTodos.ts` is the React binding that exposes todo data and actions to components.
33
+
25
34
  ## Scripts
26
35
 
27
36
  | Script | Description |
@@ -1,212 +1,201 @@
1
+ import { CheckCircle2, Circle, ClipboardList, Loader2, Plus, Trash2 } from 'lucide-react'
1
2
  import { useState } from 'react'
2
- import { useQuery, useMutation, useCollection } from '@korajs/react'
3
- import {
4
- CheckCircle2,
5
- Circle,
6
- ClipboardList,
7
- Loader2,
8
- Plus,
9
- Trash2,
10
- } from 'lucide-react'
3
+ import { useTodos } from './modules/todos/useTodos'
11
4
 
12
5
  type Filter = 'all' | 'active' | 'completed'
13
6
 
14
7
  export function App() {
15
- const todos = useCollection('todos')
16
- const allTodos = useQuery(todos.where({}).orderBy('createdAt', 'desc'))
17
- const { mutate: addTodo, isLoading: isAdding } = useMutation(
18
- (data: { title: string }) => todos.insert(data)
19
- )
20
- const { mutate: toggleTodo } = useMutation(
21
- (id: string, data: { completed: boolean }) => todos.update(id, data)
22
- )
23
- const { mutate: deleteTodo } = useMutation(
24
- (id: string) => todos.delete(id)
25
- )
26
-
27
- const [filter, setFilter] = useState<Filter>('all')
28
- const [input, setInput] = useState('')
29
-
30
- const activeTodos = allTodos.filter((t) => !t.completed)
31
- const completedTodos = allTodos.filter((t) => !!t.completed)
32
- const filteredTodos =
33
- filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
34
-
35
- const handleSubmit = (e: React.FormEvent) => {
36
- e.preventDefault()
37
- const title = input.trim()
38
- if (title) {
39
- addTodo({ title })
40
- setInput('')
41
- }
42
- }
43
-
44
- const clearCompleted = () => {
45
- for (const todo of completedTodos) {
46
- deleteTodo(todo.id)
47
- }
48
- }
49
-
50
- return (
51
- <div className="min-h-screen bg-gray-950 text-gray-100">
52
- <div className="mx-auto max-w-2xl px-4 py-12">
53
- {/* Header */}
54
- <div className="flex items-center justify-between mb-8">
55
- <div className="flex items-center gap-3">
56
- <ClipboardList className="h-8 w-8 text-indigo-400" />
57
- <h1 className="text-2xl font-bold">My Tasks</h1>
58
- </div>
59
- <span className="rounded-full bg-gray-800 px-3 py-1.5 text-sm text-gray-400">
60
- Local-first
61
- </span>
62
- </div>
63
-
64
- {/* Stats */}
65
- <div className="grid grid-cols-3 gap-4 mb-8">
66
- <StatCard label="Total" value={allTodos.length} color="text-gray-300" />
67
- <StatCard label="Remaining" value={activeTodos.length} color="text-amber-400" />
68
- <StatCard label="Done" value={completedTodos.length} color="text-emerald-400" />
69
- </div>
70
-
71
- {/* Add form */}
72
- <form onSubmit={handleSubmit} className="flex gap-3 mb-8">
73
- <input
74
- type="text"
75
- value={input}
76
- onChange={(e) => setInput(e.target.value)}
77
- placeholder="What needs to be done?"
78
- className="flex-1 rounded-lg border border-gray-700 bg-gray-900 px-4 py-3 text-gray-100 placeholder-gray-500 outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
79
- />
80
- <button
81
- type="submit"
82
- disabled={isAdding || !input.trim()}
83
- className="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white transition hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
84
- >
85
- {isAdding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
86
- Add
87
- </button>
88
- </form>
89
-
90
- {/* Filter tabs */}
91
- <div className="flex gap-2 mb-6">
92
- {(['all', 'active', 'completed'] as const).map((f) => {
93
- const count = f === 'all' ? allTodos.length : f === 'active' ? activeTodos.length : completedTodos.length
94
- return (
95
- <button
96
- key={f}
97
- onClick={() => setFilter(f)}
98
- className={`flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition ${
99
- filter === f
100
- ? 'bg-indigo-600 text-white'
101
- : 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200'
102
- }`}
103
- >
104
- {f.charAt(0).toUpperCase() + f.slice(1)}
105
- <span
106
- className={`rounded-full px-2 py-0.5 text-xs ${
107
- filter === f ? 'bg-indigo-500 text-white' : 'bg-gray-700 text-gray-400'
108
- }`}
109
- >
110
- {count}
111
- </span>
112
- </button>
113
- )
114
- })}
115
- </div>
116
-
117
- {/* Todo list */}
118
- <div className="space-y-2">
119
- {filteredTodos.length === 0 ? (
120
- <EmptyState filter={filter} />
121
- ) : (
122
- filteredTodos.map((todo) => (
123
- <div
124
- key={todo.id}
125
- className="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3 transition hover:border-gray-700"
126
- >
127
- <button
128
- onClick={() => toggleTodo(todo.id, { completed: !todo.completed })}
129
- className="shrink-0 text-gray-500 hover:text-indigo-400 transition"
130
- >
131
- {todo.completed ? (
132
- <CheckCircle2 className="h-5 w-5 text-emerald-400" />
133
- ) : (
134
- <Circle className="h-5 w-5" />
135
- )}
136
- </button>
137
- <span
138
- className={`flex-1 ${
139
- todo.completed ? 'text-gray-500 line-through' : 'text-gray-100'
140
- }`}
141
- >
142
- {String(todo.title)}
143
- </span>
144
- {todo.createdAt && (
145
- <span className="text-xs text-gray-600">
146
- {formatTime(Number(todo.createdAt))}
147
- </span>
148
- )}
149
- <button
150
- onClick={() => deleteTodo(todo.id)}
151
- className="shrink-0 text-gray-600 opacity-0 transition hover:text-red-400 group-hover:opacity-100"
152
- >
153
- <Trash2 className="h-4 w-4" />
154
- </button>
155
- </div>
156
- ))
157
- )}
158
- </div>
159
-
160
- {/* Footer */}
161
- {allTodos.length > 0 && (
162
- <div className="mt-6 flex items-center justify-between text-sm text-gray-500">
163
- <span>{activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left</span>
164
- {completedTodos.length > 0 && (
165
- <button
166
- onClick={clearCompleted}
167
- className="text-gray-500 transition hover:text-gray-300"
168
- >
169
- Clear completed
170
- </button>
171
- )}
172
- </div>
173
- )}
174
-
175
- <p className="mt-12 text-center text-xs text-gray-700">
176
- Powered by Kora &mdash; offline-first, real-time sync
177
- </p>
178
- </div>
179
- </div>
180
- )
8
+ const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = useTodos()
9
+ const isAdding = addTodo.isLoading
10
+
11
+ const [filter, setFilter] = useState<Filter>('all')
12
+ const [input, setInput] = useState('')
13
+
14
+ const filteredTodos =
15
+ filter === 'active' ? activeTodos : filter === 'completed' ? completedTodos : allTodos
16
+
17
+ const handleSubmit = (e: React.FormEvent) => {
18
+ e.preventDefault()
19
+ const title = input.trim()
20
+ if (title) {
21
+ addTodo.mutate({ title })
22
+ setInput('')
23
+ }
24
+ }
25
+
26
+ const clearCompleted = () => {
27
+ for (const todo of completedTodos) {
28
+ deleteTodo.mutate(todo.id)
29
+ }
30
+ }
31
+
32
+ return (
33
+ <div className="min-h-screen bg-gray-950 text-gray-100">
34
+ <div className="mx-auto max-w-2xl px-4 py-12">
35
+ {/* Header */}
36
+ <div className="flex items-center justify-between mb-8">
37
+ <div className="flex items-center gap-3">
38
+ <ClipboardList className="h-8 w-8 text-indigo-400" />
39
+ <h1 className="text-2xl font-bold">My Tasks</h1>
40
+ </div>
41
+ <span className="rounded-full bg-gray-800 px-3 py-1.5 text-sm text-gray-400">
42
+ Local-first
43
+ </span>
44
+ </div>
45
+
46
+ {/* Stats */}
47
+ <div className="grid grid-cols-3 gap-4 mb-8">
48
+ <StatCard label="Total" value={allTodos.length} color="text-gray-300" />
49
+ <StatCard label="Remaining" value={activeTodos.length} color="text-amber-400" />
50
+ <StatCard label="Done" value={completedTodos.length} color="text-emerald-400" />
51
+ </div>
52
+
53
+ {/* Add form */}
54
+ <form onSubmit={handleSubmit} className="flex gap-3 mb-8">
55
+ <input
56
+ type="text"
57
+ value={input}
58
+ onChange={(e) => setInput(e.target.value)}
59
+ placeholder="What needs to be done?"
60
+ className="flex-1 rounded-lg border border-gray-700 bg-gray-900 px-4 py-3 text-gray-100 placeholder-gray-500 outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
61
+ />
62
+ <button
63
+ type="submit"
64
+ disabled={isAdding || !input.trim()}
65
+ className="flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white transition hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
66
+ >
67
+ {isAdding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
68
+ Add
69
+ </button>
70
+ </form>
71
+
72
+ {/* Filter tabs */}
73
+ <div className="flex gap-2 mb-6">
74
+ {(['all', 'active', 'completed'] as const).map((f) => {
75
+ const count =
76
+ f === 'all'
77
+ ? allTodos.length
78
+ : f === 'active'
79
+ ? activeTodos.length
80
+ : completedTodos.length
81
+ return (
82
+ <button
83
+ key={f}
84
+ onClick={() => setFilter(f)}
85
+ className={`flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition ${
86
+ filter === f
87
+ ? 'bg-indigo-600 text-white'
88
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-gray-200'
89
+ }`}
90
+ >
91
+ {f.charAt(0).toUpperCase() + f.slice(1)}
92
+ <span
93
+ className={`rounded-full px-2 py-0.5 text-xs ${
94
+ filter === f ? 'bg-indigo-500 text-white' : 'bg-gray-700 text-gray-400'
95
+ }`}
96
+ >
97
+ {count}
98
+ </span>
99
+ </button>
100
+ )
101
+ })}
102
+ </div>
103
+
104
+ {/* Todo list */}
105
+ <div className="space-y-2">
106
+ {filteredTodos.length === 0 ? (
107
+ <EmptyState filter={filter} />
108
+ ) : (
109
+ filteredTodos.map((todo) => (
110
+ <div
111
+ key={todo.id}
112
+ className="group flex items-center gap-3 rounded-lg border border-gray-800 bg-gray-900 px-4 py-3 transition hover:border-gray-700"
113
+ >
114
+ <button
115
+ onClick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
116
+ className="shrink-0 text-gray-500 hover:text-indigo-400 transition"
117
+ >
118
+ {todo.completed ? (
119
+ <CheckCircle2 className="h-5 w-5 text-emerald-400" />
120
+ ) : (
121
+ <Circle className="h-5 w-5" />
122
+ )}
123
+ </button>
124
+ <span
125
+ className={`flex-1 ${
126
+ todo.completed ? 'text-gray-500 line-through' : 'text-gray-100'
127
+ }`}
128
+ >
129
+ {String(todo.title)}
130
+ </span>
131
+ {todo.createdAt && (
132
+ <span className="text-xs text-gray-600">
133
+ {formatTime(Number(todo.createdAt))}
134
+ </span>
135
+ )}
136
+ <button
137
+ onClick={() => deleteTodo.mutate(todo.id)}
138
+ className="shrink-0 text-gray-600 opacity-0 transition hover:text-red-400 group-hover:opacity-100"
139
+ >
140
+ <Trash2 className="h-4 w-4" />
141
+ </button>
142
+ </div>
143
+ ))
144
+ )}
145
+ </div>
146
+
147
+ {/* Footer */}
148
+ {allTodos.length > 0 && (
149
+ <div className="mt-6 flex items-center justify-between text-sm text-gray-500">
150
+ <span>
151
+ {activeTodos.length} item{activeTodos.length !== 1 ? 's' : ''} left
152
+ </span>
153
+ {completedTodos.length > 0 && (
154
+ <button
155
+ onClick={clearCompleted}
156
+ className="text-gray-500 transition hover:text-gray-300"
157
+ >
158
+ Clear completed
159
+ </button>
160
+ )}
161
+ </div>
162
+ )}
163
+
164
+ <p className="mt-12 text-center text-xs text-gray-700">
165
+ Powered by Kora &mdash; offline-first, real-time sync
166
+ </p>
167
+ </div>
168
+ </div>
169
+ )
181
170
  }
182
171
 
183
172
  function StatCard({ label, value, color }: { label: string; value: number; color: string }) {
184
- return (
185
- <div className="rounded-lg border border-gray-800 bg-gray-900 p-4">
186
- <p className="text-sm text-gray-500">{label}</p>
187
- <p className={`text-2xl font-bold ${color}`}>{value}</p>
188
- </div>
189
- )
173
+ return (
174
+ <div className="rounded-lg border border-gray-800 bg-gray-900 p-4">
175
+ <p className="text-sm text-gray-500">{label}</p>
176
+ <p className={`text-2xl font-bold ${color}`}>{value}</p>
177
+ </div>
178
+ )
190
179
  }
191
180
 
192
181
  function EmptyState({ filter }: { filter: Filter }) {
193
- const messages: Record<Filter, string> = {
194
- all: 'No tasks yet. Add one above!',
195
- active: 'All caught up! No active tasks.',
196
- completed: 'No completed tasks yet.',
197
- }
198
- return (
199
- <div className="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600">
200
- {messages[filter]}
201
- </div>
202
- )
182
+ const messages: Record<Filter, string> = {
183
+ all: 'No tasks yet. Add one above!',
184
+ active: 'All caught up! No active tasks.',
185
+ completed: 'No completed tasks yet.',
186
+ }
187
+ return (
188
+ <div className="rounded-lg border border-dashed border-gray-800 py-12 text-center text-gray-600">
189
+ {messages[filter]}
190
+ </div>
191
+ )
203
192
  }
204
193
 
205
194
  function formatTime(timestamp: number): string {
206
- const date = new Date(timestamp)
207
- const now = new Date()
208
- if (date.toDateString() === now.toDateString()) {
209
- return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
210
- }
211
- return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
195
+ const date = new Date(timestamp)
196
+ const now = new Date()
197
+ if (date.toDateString() === now.toDateString()) {
198
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
199
+ }
200
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
212
201
  }
@@ -1,7 +1,7 @@
1
1
  @import "tailwindcss";
2
2
 
3
3
  @layer base {
4
- body {
5
- @apply bg-gray-950 text-gray-100 antialiased;
6
- }
4
+ body {
5
+ @apply bg-gray-950 text-gray-100 antialiased;
6
+ }
7
7
  }
@@ -4,7 +4,6 @@
4
4
  // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
5
  // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
6
  import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
-
8
7
  ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
9
8
 
10
9
  import '@korajs/store/sqlite-wasm/worker'