@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
package/dist/bin.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createCommand
4
- } from "./chunk-MVP5PDT4.js";
4
+ } from "./chunk-EEZNRI5W.js";
5
5
  import {
6
6
  deployCommand,
7
7
  generateTypes
8
- } from "./chunk-YGVO4POI.js";
8
+ } from "./chunk-Q2FBCOQD.js";
9
9
  import {
10
10
  DevServerError,
11
11
  InvalidProjectError,
@@ -16,21 +16,187 @@ import {
16
16
  hasTsxInstalled,
17
17
  promptConfirm,
18
18
  resolveProjectBinaryEntryPoint
19
- } from "./chunk-CMSX76KM.js";
19
+ } from "./chunk-VLTPEATY.js";
20
20
 
21
21
  // src/bin.ts
22
- import { defineCommand as defineCommand4, runMain } from "citty";
22
+ import { defineCommand as defineCommand9, runMain } from "citty";
23
23
 
24
- // src/commands/dev/dev-command.ts
25
- import { access as access2 } from "fs/promises";
26
- import { join as join3 } from "path";
27
- import { resolve } from "path";
24
+ // src/commands/backup/backup-command.ts
25
+ import { readFile, writeFile } from "fs/promises";
26
+ import "path";
28
27
  import { defineCommand } from "citty";
28
+ var DEFAULT_SYNC_PORT = 3001;
29
+ var backupCommand = defineCommand({
30
+ meta: {
31
+ name: "backup",
32
+ description: "Backup and restore Kora sync server data"
33
+ },
34
+ subCommands: {
35
+ create: defineCommand({
36
+ meta: {
37
+ name: "create",
38
+ description: "Create a backup of the sync server"
39
+ },
40
+ args: {
41
+ url: {
42
+ type: "string",
43
+ description: "Sync server URL (default: http://localhost:3001)",
44
+ default: `http://localhost:${DEFAULT_SYNC_PORT}`
45
+ },
46
+ out: {
47
+ type: "string",
48
+ description: "Output file path (default: kora-backup-<timestamp>.kora)"
49
+ },
50
+ token: {
51
+ type: "string",
52
+ description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
53
+ }
54
+ },
55
+ async run({ args }) {
56
+ const logger = createLogger();
57
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
58
+ const outFile = typeof args.out === "string" ? args.out : `kora-backup-${Date.now()}.kora`;
59
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
60
+ logger.banner();
61
+ logger.info(`Exporting backup from ${url}...`);
62
+ try {
63
+ const backupUrl = `${url.replace(/\/$/, "")}/__kora/backup/export`;
64
+ const response = await fetch(backupUrl, {
65
+ method: "POST",
66
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
67
+ });
68
+ if (!response.ok) {
69
+ const error = await response.json().catch(() => ({ message: response.statusText }));
70
+ throw new Error(
71
+ `Backup failed: ${error.message ?? response.statusText}`
72
+ );
73
+ }
74
+ const buffer = await response.arrayBuffer();
75
+ await writeFile(outFile, new Uint8Array(buffer));
76
+ const size = (buffer.byteLength / 1024).toFixed(1);
77
+ logger.success(`Backup saved to ${outFile} (${size} KB)`);
78
+ } catch (error) {
79
+ logger.error("Backup failed");
80
+ if (error instanceof Error) logger.error(error.message);
81
+ logger.blank();
82
+ logger.step("Make sure the Kora sync server is running.");
83
+ process.exit(1);
84
+ }
85
+ }
86
+ }),
87
+ restore: defineCommand({
88
+ meta: {
89
+ name: "restore",
90
+ description: "Restore a backup to the sync server"
91
+ },
92
+ args: {
93
+ file: {
94
+ type: "string",
95
+ description: "Backup file path",
96
+ required: true
97
+ },
98
+ url: {
99
+ type: "string",
100
+ description: "Sync server URL (default: http://localhost:3001)",
101
+ default: `http://localhost:${DEFAULT_SYNC_PORT}`
102
+ },
103
+ merge: {
104
+ type: "boolean",
105
+ description: "Merge with existing data instead of replacing",
106
+ default: false
107
+ },
108
+ token: {
109
+ type: "string",
110
+ description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
111
+ }
112
+ },
113
+ async run({ args }) {
114
+ const logger = createLogger();
115
+ const filePath = typeof args.file === "string" ? args.file : "";
116
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
117
+ const merge = args.merge === true;
118
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
119
+ logger.banner();
120
+ logger.info(`Restoring backup from ${filePath} to ${url}...`);
121
+ try {
122
+ const data = await readFile(filePath);
123
+ const restoreUrl = `${url.replace(/\/$/, "")}/__kora/backup/import?merge=${merge}`;
124
+ const response = await fetch(restoreUrl, {
125
+ method: "POST",
126
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0,
127
+ body: data
128
+ });
129
+ if (!response.ok) {
130
+ const error = await response.json().catch(() => ({ message: response.statusText }));
131
+ throw new Error(
132
+ `Restore failed: ${error.message ?? response.statusText}`
133
+ );
134
+ }
135
+ const result = await response.json();
136
+ if (result.success) {
137
+ logger.success(
138
+ `Restored ${result.operationsRestored} operations${result.duration ? ` in ${result.duration}ms` : ""}`
139
+ );
140
+ } else {
141
+ logger.error("Restore completed with errors");
142
+ }
143
+ } catch (error) {
144
+ logger.error("Restore failed");
145
+ if (error instanceof Error) logger.error(error.message);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ }),
150
+ info: defineCommand({
151
+ meta: {
152
+ name: "info",
153
+ description: "Show backup file information"
154
+ },
155
+ args: {
156
+ file: {
157
+ type: "string",
158
+ description: "Backup file path",
159
+ required: true
160
+ }
161
+ },
162
+ async run({ args }) {
163
+ const logger = createLogger();
164
+ const filePath = typeof args.file === "string" ? args.file : "";
165
+ try {
166
+ const data = await readFile(filePath);
167
+ const { readBackupManifest } = await import("@korajs/store");
168
+ const manifest = readBackupManifest(
169
+ new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
170
+ );
171
+ logger.banner();
172
+ logger.info(`Backup: ${filePath}`);
173
+ logger.blank();
174
+ logger.step(`Created: ${new Date(manifest.createdAt).toISOString()}`);
175
+ logger.step(`Node ID: ${manifest.nodeId}`);
176
+ logger.step(`Schema: v${manifest.schemaVersion}`);
177
+ logger.step(`Operations: ${manifest.operationCount.toLocaleString()}`);
178
+ logger.step(`Collections: ${manifest.collections.join(", ") || "(none)"}`);
179
+ logger.step(`Checksum: ${manifest.checksum.slice(0, 16)}...`);
180
+ logger.blank();
181
+ logger.step(`Format version: ${manifest.version}`);
182
+ } catch (error) {
183
+ logger.error("Failed to read backup file");
184
+ if (error instanceof Error) logger.error(error.message);
185
+ process.exit(1);
186
+ }
187
+ }
188
+ })
189
+ }
190
+ });
191
+
192
+ // src/commands/compact/compact-command.ts
193
+ import { resolve } from "path";
194
+ import { defineCommand as defineCommand2 } from "citty";
29
195
 
30
196
  // src/commands/dev/kora-config.ts
31
197
  import { spawn } from "child_process";
32
198
  import { access } from "fs/promises";
33
- import { extname, join } from "path";
199
+ import { extname, join as join2 } from "path";
34
200
  import { pathToFileURL } from "url";
35
201
  var CONFIG_CANDIDATES = [
36
202
  "kora.config.ts",
@@ -53,7 +219,7 @@ async function loadKoraConfig(projectRoot) {
53
219
  }
54
220
  async function findKoraConfigFile(projectRoot) {
55
221
  for (const file of CONFIG_CANDIDATES) {
56
- const candidate = join(projectRoot, file);
222
+ const candidate = join2(projectRoot, file);
57
223
  try {
58
224
  await access(candidate);
59
225
  return candidate;
@@ -81,7 +247,7 @@ async function loadTypeScriptConfig(configPath, projectRoot) {
81
247
  }
82
248
  }
83
249
  async function runCommand(command, args, cwd) {
84
- return await new Promise((resolve4, reject) => {
250
+ return await new Promise((resolve6, reject) => {
85
251
  const child = spawn(command, args, {
86
252
  cwd,
87
253
  stdio: ["ignore", "pipe", "pipe"],
@@ -100,7 +266,7 @@ async function runCommand(command, args, cwd) {
100
266
  });
101
267
  child.on("exit", (code) => {
102
268
  if (code === 0) {
103
- resolve4(stdout.trim());
269
+ resolve6(stdout.trim());
104
270
  return;
105
271
  }
106
272
  reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
@@ -118,6 +284,156 @@ function toConfigObject(mod) {
118
284
  return value;
119
285
  }
120
286
 
287
+ // src/commands/migrate/schema-loader.ts
288
+ import { spawn as spawn2 } from "child_process";
289
+ import { extname as extname2 } from "path";
290
+ import { pathToFileURL as pathToFileURL2 } from "url";
291
+ async function loadSchemaDefinition(schemaPath, projectRoot) {
292
+ const ext = extname2(schemaPath);
293
+ const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${pathToFileURL2(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
294
+ return extractSchema(moduleValue);
295
+ }
296
+ function extractSchema(value) {
297
+ if (typeof value !== "object" || value === null) {
298
+ throw new Error("Schema module must export an object.");
299
+ }
300
+ const moduleRecord = value;
301
+ const candidate = moduleRecord.default ?? moduleRecord;
302
+ if (!isSchemaDefinition(candidate)) {
303
+ throw new Error("Schema module must export a valid SchemaDefinition as default export.");
304
+ }
305
+ return candidate;
306
+ }
307
+ function isSchemaDefinition(value) {
308
+ if (typeof value !== "object" || value === null) return false;
309
+ const object = value;
310
+ return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
311
+ }
312
+ async function loadTypeScriptModule(schemaPath, projectRoot) {
313
+ if (!await hasTsxInstalled(projectRoot)) {
314
+ throw new Error(
315
+ `Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
316
+ );
317
+ }
318
+ const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
319
+ const output = await runCommand2(
320
+ process.execPath,
321
+ ["--import", "tsx", "--eval", script, schemaPath],
322
+ projectRoot
323
+ );
324
+ try {
325
+ return JSON.parse(output);
326
+ } catch {
327
+ throw new Error(`Failed to parse schema module output for ${schemaPath}`);
328
+ }
329
+ }
330
+ async function runCommand2(command, args, cwd) {
331
+ return await new Promise((resolve6, reject) => {
332
+ const child = spawn2(command, args, {
333
+ cwd,
334
+ stdio: ["ignore", "pipe", "pipe"],
335
+ env: process.env
336
+ });
337
+ let stdout = "";
338
+ let stderr = "";
339
+ child.stdout?.on("data", (chunk) => {
340
+ stdout += chunk.toString("utf-8");
341
+ });
342
+ child.stderr?.on("data", (chunk) => {
343
+ stderr += chunk.toString("utf-8");
344
+ });
345
+ child.on("error", (error) => {
346
+ reject(error);
347
+ });
348
+ child.on("exit", (code) => {
349
+ if (code === 0) {
350
+ resolve6(stdout.trim());
351
+ return;
352
+ }
353
+ reject(
354
+ new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
355
+ );
356
+ });
357
+ });
358
+ }
359
+
360
+ // src/commands/compact/compact-command.ts
361
+ var compactCommand = defineCommand2({
362
+ meta: {
363
+ name: "compact",
364
+ description: "Compact the local Kora operation log after server ack"
365
+ },
366
+ args: {
367
+ db: {
368
+ type: "string",
369
+ description: "Path to the local SQLite database (required)"
370
+ },
371
+ schema: {
372
+ type: "string",
373
+ description: "Path to schema file"
374
+ },
375
+ strategy: {
376
+ type: "string",
377
+ description: "Compaction strategy: after-ack | after-days | never",
378
+ default: "after-ack"
379
+ },
380
+ days: {
381
+ type: "string",
382
+ description: "Age threshold when strategy is after-days",
383
+ default: "30"
384
+ }
385
+ },
386
+ async run({ args }) {
387
+ const logger = createLogger();
388
+ const projectRoot = await findProjectRoot();
389
+ if (!projectRoot) {
390
+ throw new InvalidProjectError(process.cwd());
391
+ }
392
+ const dbPath = typeof args.db === "string" ? resolve(projectRoot, args.db) : void 0;
393
+ if (!dbPath) {
394
+ throw new Error("Missing --db <path> to the local SQLite database file.");
395
+ }
396
+ const config = await loadKoraConfig(projectRoot);
397
+ const schemaPath = typeof args.schema === "string" ? resolve(projectRoot, args.schema) : typeof config?.schema === "string" ? resolve(projectRoot, config.schema) : void 0;
398
+ if (!schemaPath) {
399
+ throw new Error("Could not resolve schema path. Pass --schema <path>.");
400
+ }
401
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
402
+ const { BetterSqlite3Adapter } = await import("@korajs/store/better-sqlite3");
403
+ const { Store } = await import("@korajs/store");
404
+ const strategyName = typeof args.strategy === "string" ? args.strategy : "after-ack";
405
+ const days = Number.parseInt(typeof args.days === "string" ? args.days : "30", 10);
406
+ const store = new Store({
407
+ schema,
408
+ adapter: new BetterSqlite3Adapter(dbPath)
409
+ });
410
+ await store.open();
411
+ let result;
412
+ if (strategyName === "never") {
413
+ result = await store.compact({ mode: "never" });
414
+ } else if (strategyName === "after-days") {
415
+ result = await store.compact({ mode: "after-days", days });
416
+ } else if (strategyName === "after-ack") {
417
+ result = await store.compact({ mode: "after-ack" });
418
+ } else {
419
+ await store.close();
420
+ throw new Error(`Unknown strategy "${strategyName}". Use after-ack, after-days, or never.`);
421
+ }
422
+ await store.close();
423
+ logger.banner();
424
+ logger.success(`Compacted ${result.deletedCount} operation log entries.`);
425
+ for (const [nodeId, seq] of result.watermark) {
426
+ logger.step(` ${nodeId}: seq <= ${seq}`);
427
+ }
428
+ }
429
+ });
430
+
431
+ // src/commands/dev/dev-command.ts
432
+ import { access as access2 } from "fs/promises";
433
+ import { join as join4 } from "path";
434
+ import { resolve as resolve2 } from "path";
435
+ import { defineCommand as defineCommand3 } from "citty";
436
+
121
437
  // src/commands/dev/process-manager.ts
122
438
  import { spawn as spawnChild } from "child_process";
123
439
  var ProcessManager = class {
@@ -132,8 +448,8 @@ var ProcessManager = class {
132
448
  let resolveExit;
133
449
  const runningProcess = {
134
450
  child,
135
- exitPromise: new Promise((resolve4) => {
136
- resolveExit = resolve4;
451
+ exitPromise: new Promise((resolve6) => {
452
+ resolveExit = resolve6;
137
453
  }),
138
454
  stdoutBuffer: "",
139
455
  stderrBuffer: ""
@@ -200,15 +516,15 @@ var ProcessManager = class {
200
516
  }
201
517
  };
202
518
  function delay(ms) {
203
- return new Promise((resolve4) => {
204
- setTimeout(resolve4, ms);
519
+ return new Promise((resolve6) => {
520
+ setTimeout(resolve6, ms);
205
521
  });
206
522
  }
207
523
 
208
524
  // src/commands/dev/schema-watcher.ts
209
- import { spawn as spawn2 } from "child_process";
525
+ import { spawn as spawn3 } from "child_process";
210
526
  import { watch } from "fs";
211
- import { join as join2 } from "path";
527
+ import { join as join3 } from "path";
212
528
  var SchemaWatcher = class {
213
529
  constructor(config) {
214
530
  this.config = config;
@@ -236,7 +552,7 @@ var SchemaWatcher = class {
236
552
  this.watcher = null;
237
553
  }
238
554
  async regenerate() {
239
- const koraBinJs = join2(
555
+ const koraBinJs = join3(
240
556
  this.config.projectRoot,
241
557
  "node_modules",
242
558
  "@korajs",
@@ -263,8 +579,8 @@ var SchemaWatcher = class {
263
579
  }
264
580
  };
265
581
  async function spawnCommand(command, args, cwd) {
266
- await new Promise((resolve4, reject) => {
267
- const child = spawn2(command, args, {
582
+ await new Promise((resolve6, reject) => {
583
+ const child = spawn3(command, args, {
268
584
  cwd,
269
585
  stdio: ["ignore", "pipe", "pipe"],
270
586
  env: process.env
@@ -280,7 +596,7 @@ async function spawnCommand(command, args, cwd) {
280
596
  });
281
597
  child.on("exit", (code) => {
282
598
  if (code === 0) {
283
- resolve4();
599
+ resolve6();
284
600
  return;
285
601
  }
286
602
  reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
@@ -302,7 +618,7 @@ function toError(error) {
302
618
  }
303
619
 
304
620
  // src/commands/dev/dev-command.ts
305
- var devCommand = defineCommand({
621
+ var devCommand = defineCommand3({
306
622
  meta: {
307
623
  name: "dev",
308
624
  description: "Start the Kora development environment"
@@ -342,7 +658,7 @@ var devCommand = defineCommand({
342
658
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
343
659
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
344
660
  if (!viteEntryPoint) {
345
- throw new DevServerError("vite", join3(projectRoot, "node_modules", ".bin", "vite"));
661
+ throw new DevServerError("vite", join4(projectRoot, "node_modules", ".bin", "vite"));
346
662
  }
347
663
  const syncServerFile = await findSyncServerFile(projectRoot);
348
664
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -358,7 +674,7 @@ var devCommand = defineCommand({
358
674
  }
359
675
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
360
676
  const hasServerPackage = await fileExists(
361
- join3(projectRoot, "node_modules", "@korajs", "server", "package.json")
677
+ join4(projectRoot, "node_modules", "@korajs", "server", "package.json")
362
678
  );
363
679
  if (!hasServerPackage) {
364
680
  logger.warn(
@@ -375,7 +691,7 @@ var devCommand = defineCommand({
375
691
  }
376
692
  let configuredSchemaPath = null;
377
693
  if (typeof config?.schema === "string") {
378
- const candidate = resolve(projectRoot, config.schema);
694
+ const candidate = resolve2(projectRoot, config.schema);
379
695
  if (await fileExists(candidate)) {
380
696
  configuredSchemaPath = candidate;
381
697
  } else {
@@ -390,8 +706,8 @@ var devCommand = defineCommand({
390
706
  let schemaWatcher = null;
391
707
  let shuttingDown = false;
392
708
  let resolveFinished;
393
- const finished = new Promise((resolve4) => {
394
- resolveFinished = resolve4;
709
+ const finished = new Promise((resolve6) => {
710
+ resolveFinished = resolve6;
395
711
  });
396
712
  const onManagedProcessExit = () => {
397
713
  if (!processManager.hasRunning() && !shuttingDown) {
@@ -434,11 +750,15 @@ var devCommand = defineCommand({
434
750
  logger.step(" Schema watcher disabled (schema.ts not found)");
435
751
  }
436
752
  logger.blank();
753
+ logger.step(" Kora DevTools overlay: Ctrl+Shift+K (Cmd+Shift+K on macOS) when devtools: true");
437
754
  processManager.spawn({
438
755
  label: "vite",
439
756
  command: process.execPath,
440
757
  args: [viteEntryPoint, "--port", String(vitePort)],
441
758
  cwd: projectRoot,
759
+ env: {
760
+ KORA_DEV: "1"
761
+ },
442
762
  onExit: onManagedProcessExit
443
763
  });
444
764
  if (shouldStartSync && hasTsx && syncServerFile) {
@@ -489,7 +809,7 @@ var devCommand = defineCommand({
489
809
  }
490
810
  });
491
811
  async function findSyncServerFile(projectRoot) {
492
- const candidates = [join3(projectRoot, "server.ts"), join3(projectRoot, "server.js")];
812
+ const candidates = [join4(projectRoot, "server.ts"), join4(projectRoot, "server.js")];
493
813
  for (const candidate of candidates) {
494
814
  try {
495
815
  await access2(candidate);
@@ -513,7 +833,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
513
833
  const store = sync.store;
514
834
  if (store === void 0) return { type: "memory" };
515
835
  if (store === "memory") return { type: "memory" };
516
- if (store === "sqlite") return { type: "sqlite", filename: join3(projectRoot, "kora-sync.db") };
836
+ if (store === "sqlite") return { type: "sqlite", filename: join4(projectRoot, "kora-sync.db") };
517
837
  if (store === "postgres") {
518
838
  const connectionString = process.env.DATABASE_URL;
519
839
  if (!connectionString) return null;
@@ -522,7 +842,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
522
842
  if (typeof store === "object" && store !== null) {
523
843
  if (store.type === "memory") return { type: "memory" };
524
844
  if (store.type === "sqlite") {
525
- const filename = typeof store.filename === "string" && store.filename.length > 0 ? resolve(projectRoot, store.filename) : join3(projectRoot, "kora-sync.db");
845
+ const filename = typeof store.filename === "string" && store.filename.length > 0 ? resolve2(projectRoot, store.filename) : join4(projectRoot, "kora-sync.db");
526
846
  return { type: "sqlite", filename };
527
847
  }
528
848
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -585,17 +905,387 @@ process.stdout.write('Managed sync server running on ws://localhost:' + String(c
585
905
  await new Promise(() => {});
586
906
  `;
587
907
 
908
+ // src/commands/doctor/doctor-command.ts
909
+ import { defineCommand as defineCommand4 } from "citty";
910
+
911
+ // src/commands/doctor/doctor-checks.ts
912
+ import { access as access3, readFile as readFile2 } from "fs/promises";
913
+ import { join as join5, resolve as resolve3 } from "path";
914
+ var STATUS_ENDPOINT = "/__kora/status";
915
+ async function runDoctorChecks(options) {
916
+ const { projectRoot } = options;
917
+ const results = [];
918
+ results.push({
919
+ id: "project-root",
920
+ title: "Project root",
921
+ status: "pass",
922
+ message: projectRoot
923
+ });
924
+ const config = await loadKoraConfig(projectRoot);
925
+ if (!config) {
926
+ results.push({
927
+ id: "kora-config",
928
+ title: "kora.config",
929
+ status: "warn",
930
+ message: "No kora.config.* file found.",
931
+ fix: "Add kora.config.ts with schema path and dev settings (see create-kora-app templates)."
932
+ });
933
+ } else {
934
+ results.push({
935
+ id: "kora-config",
936
+ title: "kora.config",
937
+ status: "pass",
938
+ message: "Configuration file loaded."
939
+ });
940
+ }
941
+ const schemaPath = await resolveSchemaPath(projectRoot, config?.schema);
942
+ if (!schemaPath) {
943
+ results.push({
944
+ id: "schema",
945
+ title: "Schema",
946
+ status: "fail",
947
+ message: "Could not find schema.ts in src/ or project root.",
948
+ fix: "Add src/schema.ts or set schema in kora.config.ts."
949
+ });
950
+ } else {
951
+ try {
952
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
953
+ results.push({
954
+ id: "schema",
955
+ title: "Schema",
956
+ status: "pass",
957
+ message: `Loaded schema v${schema.version} (${Object.keys(schema.collections).length} collection(s)).`
958
+ });
959
+ const workerCheck = await checkWorkerFile(projectRoot);
960
+ results.push(workerCheck);
961
+ const depsCheck = await checkDependencies(projectRoot);
962
+ results.push(depsCheck);
963
+ results.push(opfsBrowserNote());
964
+ if (!options.skipNetwork) {
965
+ const syncUrl = options.syncHttpUrl ?? await resolveSyncHttpUrl(projectRoot, config);
966
+ const syncChecks = await checkSyncServer(syncUrl, schema);
967
+ results.push(...syncChecks);
968
+ } else {
969
+ results.push({
970
+ id: "sync-server",
971
+ title: "Sync server",
972
+ status: "skip",
973
+ message: "Network checks skipped."
974
+ });
975
+ }
976
+ } catch (error) {
977
+ const message = error instanceof Error ? error.message : String(error);
978
+ results.push({
979
+ id: "schema",
980
+ title: "Schema",
981
+ status: "fail",
982
+ message: `Failed to load schema: ${message}`,
983
+ fix: "Ensure the schema exports defineSchema() as default and TypeScript can be evaluated (install tsx)."
984
+ });
985
+ }
986
+ }
987
+ return results;
988
+ }
989
+ async function resolveSchemaPath(projectRoot, configSchema) {
990
+ if (typeof configSchema === "string") {
991
+ const resolved = resolve3(projectRoot, configSchema);
992
+ try {
993
+ await access3(resolved);
994
+ return resolved;
995
+ } catch {
996
+ }
997
+ }
998
+ return findSchemaFile(projectRoot);
999
+ }
1000
+ async function checkWorkerFile(projectRoot) {
1001
+ const candidates = [
1002
+ join5(projectRoot, "src", "kora-worker.ts"),
1003
+ join5(projectRoot, "src", "kora-worker.js")
1004
+ ];
1005
+ for (const path of candidates) {
1006
+ try {
1007
+ await access3(path);
1008
+ return {
1009
+ id: "worker",
1010
+ title: "SQLite WASM worker",
1011
+ status: "pass",
1012
+ message: `Found ${path.replace(projectRoot, ".")}.`
1013
+ };
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ return {
1018
+ id: "worker",
1019
+ title: "SQLite WASM worker",
1020
+ status: "warn",
1021
+ message: "No src/kora-worker.ts found.",
1022
+ fix: "Add kora-worker.ts and pass store.workerUrl from Vite (?worker&url) in createApp()."
1023
+ };
1024
+ }
1025
+ async function checkDependencies(projectRoot) {
1026
+ try {
1027
+ const raw = await readFile2(join5(projectRoot, "package.json"), "utf-8");
1028
+ const pkg = JSON.parse(raw);
1029
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1030
+ const hasKora = Boolean(deps.korajs || deps.kora);
1031
+ const hasStore = Boolean(deps["@korajs/store"]);
1032
+ if (hasKora || hasStore) {
1033
+ return {
1034
+ id: "dependencies",
1035
+ title: "Dependencies",
1036
+ status: "pass",
1037
+ message: hasKora ? "korajs is listed in package.json." : "@korajs/store is listed in package.json."
1038
+ };
1039
+ }
1040
+ return {
1041
+ id: "dependencies",
1042
+ title: "Dependencies",
1043
+ status: "fail",
1044
+ message: "No korajs or @korajs/store dependency found.",
1045
+ fix: "Run pnpm add korajs @korajs/react (or your package manager equivalent)."
1046
+ };
1047
+ } catch {
1048
+ return {
1049
+ id: "dependencies",
1050
+ title: "Dependencies",
1051
+ status: "fail",
1052
+ message: "Could not read package.json.",
1053
+ fix: "Run this command from a Kora app directory with a valid package.json."
1054
+ };
1055
+ }
1056
+ }
1057
+ function opfsBrowserNote() {
1058
+ return {
1059
+ id: "opfs",
1060
+ title: "OPFS persistence",
1061
+ status: "skip",
1062
+ message: "OPFS availability is checked in the browser at runtime. Open the app and confirm no IndexedDB fallback warning in the console.",
1063
+ fix: "Use a Chromium-based browser with storage access; avoid private mode if persistence fails."
1064
+ };
1065
+ }
1066
+ async function resolveSyncHttpUrl(projectRoot, config) {
1067
+ const envUrl = await readEnvSyncUrl(projectRoot);
1068
+ if (envUrl) {
1069
+ return toSyncStatusHttpBase(envUrl);
1070
+ }
1071
+ const syncPort = typeof config?.dev?.sync === "object" && typeof config.dev.sync.port === "number" ? config.dev.sync.port : 3001;
1072
+ return `http://localhost:${syncPort}`;
1073
+ }
1074
+ async function readEnvSyncUrl(projectRoot) {
1075
+ for (const file of [".env", ".env.local"]) {
1076
+ const path = join5(projectRoot, file);
1077
+ try {
1078
+ const content = await readFile2(path, "utf-8");
1079
+ for (const line of content.split("\n")) {
1080
+ const trimmed = line.trim();
1081
+ if (!trimmed || trimmed.startsWith("#")) continue;
1082
+ const match = trimmed.match(/^VITE_SYNC_URL=(.+)$/);
1083
+ if (match?.[1]) {
1084
+ return match[1].trim().replace(/^["']|["']$/g, "");
1085
+ }
1086
+ }
1087
+ } catch {
1088
+ }
1089
+ }
1090
+ return null;
1091
+ }
1092
+ function toSyncStatusHttpBase(url) {
1093
+ try {
1094
+ const parsed = new URL(url);
1095
+ if (parsed.protocol === "ws:") {
1096
+ parsed.protocol = "http:";
1097
+ } else if (parsed.protocol === "wss:") {
1098
+ parsed.protocol = "https:";
1099
+ }
1100
+ parsed.pathname = "";
1101
+ parsed.search = "";
1102
+ parsed.hash = "";
1103
+ return parsed.toString().replace(/\/$/, "");
1104
+ } catch {
1105
+ return "http://localhost:3001";
1106
+ }
1107
+ }
1108
+ async function checkSyncServer(httpBase, schema) {
1109
+ const statusUrl = `${httpBase.replace(/\/$/, "")}${STATUS_ENDPOINT}`;
1110
+ try {
1111
+ const response = await fetch(statusUrl, {
1112
+ signal: AbortSignal.timeout(5e3)
1113
+ });
1114
+ if (!response.ok) {
1115
+ return [
1116
+ {
1117
+ id: "sync-server",
1118
+ title: "Sync server",
1119
+ status: "fail",
1120
+ message: `GET ${statusUrl} returned ${response.status}.`,
1121
+ fix: "Start the sync server with `kora dev` or `pnpm run dev` in a sync template."
1122
+ }
1123
+ ];
1124
+ }
1125
+ const body = await response.json();
1126
+ const results = [
1127
+ {
1128
+ id: "sync-server",
1129
+ title: "Sync server",
1130
+ status: "pass",
1131
+ message: `Reachable at ${statusUrl} (server v${body.version ?? "unknown"}).`
1132
+ }
1133
+ ];
1134
+ if (typeof body.schemaVersion === "number" && body.schemaVersion !== schema.version) {
1135
+ results.push({
1136
+ id: "schema-version",
1137
+ title: "Schema version",
1138
+ status: "warn",
1139
+ message: `Client schema v${schema.version} differs from server schema v${body.schemaVersion}.`,
1140
+ fix: "Run migrations on the server and client, or align schema.version before syncing."
1141
+ });
1142
+ } else {
1143
+ results.push({
1144
+ id: "schema-version",
1145
+ title: "Schema version",
1146
+ status: "pass",
1147
+ message: `Client and server both use schema v${schema.version}.`
1148
+ });
1149
+ }
1150
+ return results;
1151
+ } catch (error) {
1152
+ const detail = error instanceof Error ? error.message : String(error);
1153
+ return [
1154
+ {
1155
+ id: "sync-server",
1156
+ title: "Sync server",
1157
+ status: "warn",
1158
+ message: `Could not reach ${statusUrl}: ${detail}`,
1159
+ fix: "Start the sync server, or pass --skip-network for local-only checks."
1160
+ }
1161
+ ];
1162
+ }
1163
+ }
1164
+ function doctorHasFailures(results) {
1165
+ return results.some((r) => r.status === "fail");
1166
+ }
1167
+
1168
+ // src/commands/doctor/doctor-command.ts
1169
+ var doctorCommand = defineCommand4({
1170
+ meta: {
1171
+ name: "doctor",
1172
+ description: "Check Kora project setup (schema, worker, sync server, versions)"
1173
+ },
1174
+ args: {
1175
+ url: {
1176
+ type: "string",
1177
+ description: "Sync server HTTP base URL for status probe (default: from .env or localhost:3001)"
1178
+ },
1179
+ "skip-network": {
1180
+ type: "boolean",
1181
+ description: "Skip sync server and schema version network checks",
1182
+ default: false
1183
+ }
1184
+ },
1185
+ async run({ args }) {
1186
+ const logger = createLogger();
1187
+ const projectRoot = await findProjectRoot();
1188
+ if (!projectRoot) {
1189
+ throw new InvalidProjectError(process.cwd());
1190
+ }
1191
+ const syncHttpUrl = typeof args.url === "string" ? args.url : void 0;
1192
+ const skipNetwork = args["skip-network"] === true;
1193
+ logger.banner();
1194
+ logger.info(`Checking project at ${projectRoot}`);
1195
+ logger.blank();
1196
+ const results = await runDoctorChecks({
1197
+ projectRoot,
1198
+ syncHttpUrl,
1199
+ skipNetwork
1200
+ });
1201
+ for (const check of results) {
1202
+ const prefix = check.status === "pass" ? "success" : check.status === "warn" ? "warn" : check.status === "fail" ? "error" : "step";
1203
+ logger[prefix](`${check.title}: ${check.message}`);
1204
+ if (check.fix && check.status !== "pass" && check.status !== "skip") {
1205
+ logger.step(` Fix: ${check.fix}`);
1206
+ }
1207
+ }
1208
+ logger.blank();
1209
+ if (doctorHasFailures(results)) {
1210
+ logger.error("Doctor found blocking issues.");
1211
+ process.exitCode = 1;
1212
+ return;
1213
+ }
1214
+ const warnings = results.filter((r) => r.status === "warn").length;
1215
+ if (warnings > 0) {
1216
+ logger.warn(`Doctor finished with ${warnings} warning(s).`);
1217
+ } else {
1218
+ logger.success("All checks passed.");
1219
+ }
1220
+ }
1221
+ });
1222
+
588
1223
  // src/commands/generate/generate-command.ts
589
- import { mkdir, writeFile } from "fs/promises";
590
- import { dirname, resolve as resolve2 } from "path";
591
- import { defineCommand as defineCommand2 } from "citty";
592
- var generateCommand = defineCommand2({
1224
+ import { mkdir, writeFile as writeFile2 } from "fs/promises";
1225
+ import { dirname, resolve as resolve4 } from "path";
1226
+ import { defineCommand as defineCommand5 } from "citty";
1227
+
1228
+ // src/commands/generate/hook-generator.ts
1229
+ function generateCollectionHooks(schema) {
1230
+ const files = /* @__PURE__ */ new Map();
1231
+ for (const collectionName of Object.keys(schema.collections)) {
1232
+ const pascal = toPascalCase(collectionName);
1233
+ const hookName = `use${pascal}`;
1234
+ const content = [
1235
+ "// Auto-generated by `kora generate hooks` \u2014 safe to edit",
1236
+ `// Schema v${String(schema.version)}`,
1237
+ "",
1238
+ "import { useCollection, useMutation, useQuery } from '@korajs/react'",
1239
+ `import type { ${pascal}InsertInput, ${pascal}Record, ${pascal}UpdateInput } from '../types'`,
1240
+ "",
1241
+ "/**",
1242
+ ` * Collection hook for \`${collectionName}\`.`,
1243
+ " * Add useQuery(...) calls with your QueryBuilder factories.",
1244
+ " */",
1245
+ `export function ${hookName}() {`,
1246
+ ` const ${collectionName} = useCollection('${collectionName}')`,
1247
+ "",
1248
+ ` const insert = useMutation((data: ${pascal}InsertInput) => ${collectionName}.insert(data))`,
1249
+ ` const update = useMutation((id: string, data: ${pascal}UpdateInput) =>`,
1250
+ ` ${collectionName}.update(id, data),`,
1251
+ " )",
1252
+ ` const remove = useMutation((id: string) => ${collectionName}.delete(id))`,
1253
+ "",
1254
+ " return {",
1255
+ ` ${collectionName},`,
1256
+ " insert,",
1257
+ " update,",
1258
+ " remove,",
1259
+ " }",
1260
+ "}",
1261
+ ""
1262
+ ].join("\n");
1263
+ files.set(`${collectionName}.ts`, content);
1264
+ }
1265
+ const indexLines = [
1266
+ "// Auto-generated by `kora generate hooks`",
1267
+ "",
1268
+ ...Object.keys(schema.collections).map((name) => {
1269
+ const pascal = toPascalCase(name);
1270
+ return `export { use${pascal} } from './${name}'`;
1271
+ }),
1272
+ ""
1273
+ ];
1274
+ files.set("index.ts", indexLines.join("\n"));
1275
+ return files;
1276
+ }
1277
+ function toPascalCase(value) {
1278
+ return value.split(/[-_]/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1279
+ }
1280
+
1281
+ // src/commands/generate/generate-command.ts
1282
+ var generateCommand = defineCommand5({
593
1283
  meta: {
594
1284
  name: "generate",
595
1285
  description: "Generate code from your Kora schema"
596
1286
  },
597
1287
  subCommands: {
598
- types: defineCommand2({
1288
+ types: defineCommand5({
599
1289
  meta: {
600
1290
  name: "types",
601
1291
  description: "Generate TypeScript types from your schema"
@@ -619,7 +1309,7 @@ var generateCommand = defineCommand2({
619
1309
  }
620
1310
  let schemaPath;
621
1311
  if (args.schema && typeof args.schema === "string") {
622
- schemaPath = resolve2(args.schema);
1312
+ schemaPath = resolve4(args.schema);
623
1313
  } else {
624
1314
  const found = await findSchemaFile(projectRoot);
625
1315
  if (!found) {
@@ -634,7 +1324,7 @@ var generateCommand = defineCommand2({
634
1324
  }
635
1325
  logger.step(`Reading schema from ${schemaPath}...`);
636
1326
  const schemaModule = await import(schemaPath);
637
- const schema = extractSchema(schemaModule);
1327
+ const schema = extractSchema2(schemaModule);
638
1328
  if (!schema) {
639
1329
  logger.error("Schema file must export a SchemaDefinition as the default export.");
640
1330
  process.exitCode = 1;
@@ -642,31 +1332,193 @@ var generateCommand = defineCommand2({
642
1332
  }
643
1333
  const output = generateTypes(schema);
644
1334
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
645
- const outputPath = resolve2(projectRoot, outputFile);
1335
+ const outputPath = resolve4(projectRoot, outputFile);
646
1336
  await mkdir(dirname(outputPath), { recursive: true });
647
- await writeFile(outputPath, output, "utf-8");
1337
+ await writeFile2(outputPath, output, "utf-8");
648
1338
  logger.success(`Generated types at ${outputPath}`);
649
1339
  }
1340
+ }),
1341
+ hooks: defineCommand5({
1342
+ meta: {
1343
+ name: "hooks",
1344
+ description: "Generate per-collection React hook stubs"
1345
+ },
1346
+ args: {
1347
+ schema: {
1348
+ type: "string",
1349
+ description: "Path to schema file"
1350
+ },
1351
+ output: {
1352
+ type: "string",
1353
+ description: "Output directory for hooks",
1354
+ default: "kora/generated/hooks"
1355
+ },
1356
+ types: {
1357
+ type: "string",
1358
+ description: "Relative import path to generated types from hook files",
1359
+ default: "../types"
1360
+ }
1361
+ },
1362
+ async run({ args }) {
1363
+ const logger = createLogger();
1364
+ const projectRoot = await findProjectRoot();
1365
+ if (!projectRoot) {
1366
+ throw new InvalidProjectError(process.cwd());
1367
+ }
1368
+ let schemaPath;
1369
+ if (args.schema && typeof args.schema === "string") {
1370
+ schemaPath = resolve4(projectRoot, args.schema);
1371
+ } else {
1372
+ const found = await findSchemaFile(projectRoot);
1373
+ if (!found) {
1374
+ throw new SchemaNotFoundError([
1375
+ "src/schema.ts",
1376
+ "schema.ts",
1377
+ "src/schema.js",
1378
+ "schema.js"
1379
+ ]);
1380
+ }
1381
+ schemaPath = found;
1382
+ }
1383
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
1384
+ const outputDir = typeof args.output === "string" ? resolve4(projectRoot, args.output) : resolve4(projectRoot, "kora/generated/hooks");
1385
+ const files = generateCollectionHooks(schema);
1386
+ for (const [name, content] of files) {
1387
+ const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
1388
+ const filePath = resolve4(outputDir, name);
1389
+ await mkdir(dirname(filePath), { recursive: true });
1390
+ await writeFile2(filePath, adjusted, "utf-8");
1391
+ }
1392
+ logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
1393
+ logger.step("Run `kora generate types` first if ../types is missing.");
1394
+ }
650
1395
  })
651
1396
  }
652
1397
  });
653
- function extractSchema(mod) {
1398
+ function extractSchema2(mod) {
654
1399
  if (typeof mod !== "object" || mod === null) return null;
655
1400
  const record = mod;
656
1401
  const candidate = record.default ?? record;
657
- if (isSchemaDefinition(candidate)) return candidate;
1402
+ if (isSchemaDefinition2(candidate)) return candidate;
658
1403
  return null;
659
1404
  }
660
- function isSchemaDefinition(value) {
1405
+ function isSchemaDefinition2(value) {
661
1406
  if (typeof value !== "object" || value === null) return false;
662
1407
  const obj = value;
663
1408
  return typeof obj.version === "number" && typeof obj.collections === "object" && obj.collections !== null;
664
1409
  }
665
1410
 
1411
+ // src/commands/logs/logs-command.ts
1412
+ import { defineCommand as defineCommand6 } from "citty";
1413
+ var DEFAULT_SYNC_PORT2 = 3001;
1414
+ var EVENTS_ENDPOINT = "/__kora/events";
1415
+ var logsCommand = defineCommand6({
1416
+ meta: {
1417
+ name: "logs",
1418
+ description: "Stream real-time events from a Kora sync server"
1419
+ },
1420
+ args: {
1421
+ url: {
1422
+ type: "string",
1423
+ description: "Sync server URL (default: http://localhost:3001)",
1424
+ default: `http://localhost:${DEFAULT_SYNC_PORT2}`
1425
+ },
1426
+ follow: {
1427
+ type: "boolean",
1428
+ description: "Follow log output (like tail -f)",
1429
+ default: true,
1430
+ alias: "f"
1431
+ },
1432
+ level: {
1433
+ type: "string",
1434
+ description: "Filter by log level (info, warn, error)"
1435
+ },
1436
+ token: {
1437
+ type: "string",
1438
+ description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
1439
+ }
1440
+ },
1441
+ async run({ args }) {
1442
+ const logger = createLogger();
1443
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT2}`;
1444
+ const follow = args.follow !== false;
1445
+ const levelFilter = typeof args.level === "string" ? args.level : null;
1446
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
1447
+ const eventsUrl = `${url.replace(/\/$/, "")}${EVENTS_ENDPOINT}`;
1448
+ logger.banner();
1449
+ logger.info(`Connecting to ${eventsUrl}...`);
1450
+ logger.blank();
1451
+ try {
1452
+ const response = await fetch(eventsUrl, {
1453
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
1454
+ });
1455
+ if (!response.ok) {
1456
+ throw new Error(
1457
+ `Failed to connect to ${eventsUrl}: ${response.status} ${response.statusText}`
1458
+ );
1459
+ }
1460
+ const reader = response.body?.getReader();
1461
+ if (!reader) {
1462
+ throw new Error("Response body is not readable");
1463
+ }
1464
+ const decoder = new TextDecoder();
1465
+ let buffer = "";
1466
+ const processLine = (line) => {
1467
+ if (line.startsWith("event: ")) {
1468
+ return;
1469
+ }
1470
+ if (line.startsWith("data: ")) {
1471
+ const data = line.slice(6);
1472
+ try {
1473
+ const parsed = JSON.parse(data);
1474
+ const level = String(parsed.level ?? "info");
1475
+ if (levelFilter && level !== levelFilter) return;
1476
+ const timestamp = parsed.timestamp ? new Date(parsed.timestamp).toISOString() : "";
1477
+ const event = String(parsed.event ?? "unknown");
1478
+ const nodeId = parsed.nodeId ? ` [${parsed.nodeId}]` : "";
1479
+ const session = parsed.sessionId ? ` <${parsed.sessionId}>` : "";
1480
+ const count = parsed.count ? ` (${parsed.count})` : "";
1481
+ const error = parsed.error ? ` \u2014 ${parsed.error}` : "";
1482
+ const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\u25CF";
1483
+ const color = level === "error" ? "\x1B[31m" : level === "warn" ? "\x1B[33m" : "\x1B[36m";
1484
+ console.log(
1485
+ `${color}${prefix}${"\x1B[0m"} ${timestamp.slice(11, 23)} ${event}${nodeId}${session}${count}${error}`
1486
+ );
1487
+ } catch {
1488
+ }
1489
+ }
1490
+ };
1491
+ const read = async () => {
1492
+ while (true) {
1493
+ const { done, value } = await reader.read();
1494
+ if (done) break;
1495
+ buffer += decoder.decode(value, { stream: true });
1496
+ const lines = buffer.split("\n");
1497
+ buffer = lines.pop() ?? "";
1498
+ for (const line of lines) {
1499
+ processLine(line.trim());
1500
+ }
1501
+ if (!follow) break;
1502
+ }
1503
+ };
1504
+ await read();
1505
+ } catch (error) {
1506
+ logger.error(`Failed to connect to ${eventsUrl}`);
1507
+ if (error instanceof Error) {
1508
+ logger.error(error.message);
1509
+ }
1510
+ logger.blank();
1511
+ logger.step("Make sure the Kora sync server is running.");
1512
+ logger.step("Start it with: kora dev");
1513
+ process.exit(1);
1514
+ }
1515
+ }
1516
+ });
1517
+
666
1518
  // src/commands/migrate/migrate-command.ts
667
- import { mkdir as mkdir2, readFile, readdir, writeFile as writeFile2 } from "fs/promises";
668
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
669
- import { defineCommand as defineCommand3 } from "citty";
1519
+ import { mkdir as mkdir2, readFile as readFile3, readdir, writeFile as writeFile3 } from "fs/promises";
1520
+ import { dirname as dirname2, join as join6, resolve as resolve5 } from "path";
1521
+ import { defineCommand as defineCommand7 } from "citty";
670
1522
 
671
1523
  // src/commands/migrate/migration-generator.ts
672
1524
  import { generateSQL } from "@korajs/core";
@@ -1231,83 +2083,34 @@ async function loadPostgresModule() {
1231
2083
  }
1232
2084
  }
1233
2085
 
1234
- // src/commands/migrate/schema-loader.ts
1235
- import { spawn as spawn3 } from "child_process";
1236
- import { extname as extname2 } from "path";
1237
- import { pathToFileURL as pathToFileURL2 } from "url";
1238
- async function loadSchemaDefinition(schemaPath, projectRoot) {
1239
- const ext = extname2(schemaPath);
1240
- const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${pathToFileURL2(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
1241
- return extractSchema2(moduleValue);
1242
- }
1243
- function extractSchema2(value) {
1244
- if (typeof value !== "object" || value === null) {
1245
- throw new Error("Schema module must export an object.");
1246
- }
1247
- const moduleRecord = value;
1248
- const candidate = moduleRecord.default ?? moduleRecord;
1249
- if (!isSchemaDefinition2(candidate)) {
1250
- throw new Error("Schema module must export a valid SchemaDefinition as default export.");
1251
- }
1252
- return candidate;
1253
- }
1254
- function isSchemaDefinition2(value) {
1255
- if (typeof value !== "object" || value === null) return false;
1256
- const object = value;
1257
- return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
1258
- }
1259
- async function loadTypeScriptModule(schemaPath, projectRoot) {
1260
- if (!await hasTsxInstalled(projectRoot)) {
1261
- throw new Error(
1262
- `Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
1263
- );
1264
- }
1265
- const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
1266
- const output = await runCommand2(
1267
- process.execPath,
1268
- ["--import", "tsx", "--eval", script, schemaPath],
1269
- projectRoot
1270
- );
1271
- try {
1272
- return JSON.parse(output);
1273
- } catch {
1274
- throw new Error(`Failed to parse schema module output for ${schemaPath}`);
1275
- }
1276
- }
1277
- async function runCommand2(command, args, cwd) {
1278
- return await new Promise((resolve4, reject) => {
1279
- const child = spawn3(command, args, {
1280
- cwd,
1281
- stdio: ["ignore", "pipe", "pipe"],
1282
- env: process.env
1283
- });
1284
- let stdout = "";
1285
- let stderr = "";
1286
- child.stdout?.on("data", (chunk) => {
1287
- stdout += chunk.toString("utf-8");
1288
- });
1289
- child.stderr?.on("data", (chunk) => {
1290
- stderr += chunk.toString("utf-8");
1291
- });
1292
- child.on("error", (error) => {
1293
- reject(error);
1294
- });
1295
- child.on("exit", (code) => {
1296
- if (code === 0) {
1297
- resolve4(stdout.trim());
1298
- return;
1299
- }
1300
- reject(
1301
- new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
1302
- );
1303
- });
1304
- });
2086
+ // src/commands/migrate/operation-transform-generator.ts
2087
+ function generateOperationTransformModule(fromVersion, toVersion) {
2088
+ return [
2089
+ "import type { Operation, OperationTransform } from '@korajs/core'",
2090
+ "",
2091
+ "/**",
2092
+ ` * Operation transforms for schema v${fromVersion} \u2192 v${toVersion}.`,
2093
+ " * Implement `transform` to rewrite or drop legacy operations during sync.",
2094
+ " * @see https://korajs.dev/docs/migrations",
2095
+ " */",
2096
+ "export const operationTransforms: OperationTransform[] = [",
2097
+ " {",
2098
+ ` fromVersion: ${fromVersion},`,
2099
+ ` toVersion: ${toVersion},`,
2100
+ " transform(operation: Operation): Operation | null {",
2101
+ " // Return null to drop unmigratable operations.",
2102
+ " return operation",
2103
+ " },",
2104
+ " },",
2105
+ "]",
2106
+ ""
2107
+ ].join("\n");
1305
2108
  }
1306
2109
 
1307
2110
  // src/commands/migrate/migrate-command.ts
1308
2111
  var SNAPSHOT_PATH = "kora/schema.snapshot.json";
1309
2112
  var MIGRATIONS_DIR = "kora/migrations";
1310
- var migrateCommand = defineCommand3({
2113
+ var migrateCommand = defineCommand7({
1311
2114
  meta: {
1312
2115
  name: "migrate",
1313
2116
  description: "Detect schema changes and generate/apply migrations"
@@ -1349,12 +2152,12 @@ var migrateCommand = defineCommand3({
1349
2152
  throw new InvalidProjectError(process.cwd());
1350
2153
  }
1351
2154
  const config = await loadKoraConfig(projectRoot);
1352
- const resolvedSchemaPath = typeof args.schema === "string" ? resolve3(projectRoot, args.schema) : typeof config?.schema === "string" ? resolve3(projectRoot, config.schema) : await findSchemaFile(projectRoot);
2155
+ const resolvedSchemaPath = typeof args.schema === "string" ? resolve5(projectRoot, args.schema) : typeof config?.schema === "string" ? resolve5(projectRoot, config.schema) : await findSchemaFile(projectRoot);
1353
2156
  if (!resolvedSchemaPath) {
1354
2157
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
1355
2158
  }
1356
2159
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
1357
- const snapshotFile = join4(projectRoot, SNAPSHOT_PATH);
2160
+ const snapshotFile = join6(projectRoot, SNAPSHOT_PATH);
1358
2161
  const previousSchema = await readSchemaSnapshot(snapshotFile);
1359
2162
  if (!previousSchema) {
1360
2163
  if (args["dry-run"] === true) {
@@ -1367,7 +2170,7 @@ var migrateCommand = defineCommand3({
1367
2170
  return;
1368
2171
  }
1369
2172
  const diff = diffSchemas(previousSchema, currentSchema);
1370
- const outputDir = typeof args["output-dir"] === "string" ? resolve3(projectRoot, args["output-dir"]) : resolve3(projectRoot, MIGRATIONS_DIR);
2173
+ const outputDir = typeof args["output-dir"] === "string" ? resolve5(projectRoot, args["output-dir"]) : resolve5(projectRoot, MIGRATIONS_DIR);
1371
2174
  if (!diff.hasChanges) {
1372
2175
  logger.success("No schema changes detected.");
1373
2176
  if (args.apply === true) {
@@ -1455,7 +2258,7 @@ var migrateCommand = defineCommand3({
1455
2258
  });
1456
2259
  async function readSchemaSnapshot(path) {
1457
2260
  try {
1458
- const content = await readFile(path, "utf-8");
2261
+ const content = await readFile3(path, "utf-8");
1459
2262
  return JSON.parse(content);
1460
2263
  } catch {
1461
2264
  return null;
@@ -1477,14 +2280,14 @@ function isInteractiveTerminal() {
1477
2280
  }
1478
2281
  async function writeSchemaSnapshot(path, schema) {
1479
2282
  await mkdir2(dirname2(path), { recursive: true });
1480
- await writeFile2(path, `${JSON.stringify(schema, null, 2)}
2283
+ await writeFile3(path, `${JSON.stringify(schema, null, 2)}
1481
2284
  `, "utf-8");
1482
2285
  }
1483
2286
  async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
1484
2287
  const existing = await readdir(outputDir).catch(() => []);
1485
2288
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
1486
2289
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
1487
- const path = join4(outputDir, filename);
2290
+ const path = join6(outputDir, filename);
1488
2291
  const migrationId = filename.replace(/\.ts$/, "");
1489
2292
  const fileContent = [
1490
2293
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -1496,8 +2299,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
1496
2299
  `export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
1497
2300
  ""
1498
2301
  ].join("\n");
1499
- await writeFile2(path, fileContent, "utf-8");
1500
- await writeMigrationManifest(join4(outputDir, `${migrationId}.json`), {
2302
+ await writeFile3(path, fileContent, "utf-8");
2303
+ const transformsPath = join6(outputDir, `${migrationId}.transforms.ts`);
2304
+ await writeFile3(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
2305
+ await writeMigrationManifest(join6(outputDir, `${migrationId}.json`), {
1501
2306
  id: migrationId,
1502
2307
  fromVersion,
1503
2308
  toVersion,
@@ -1509,7 +2314,7 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
1509
2314
  return path;
1510
2315
  }
1511
2316
  async function writeMigrationManifest(path, manifest) {
1512
- await writeFile2(path, `${JSON.stringify(manifest, null, 2)}
2317
+ await writeFile3(path, `${JSON.stringify(manifest, null, 2)}
1513
2318
  `, "utf-8");
1514
2319
  }
1515
2320
  async function listMigrationManifests(outputDir) {
@@ -1518,13 +2323,13 @@ async function listMigrationManifests(outputDir) {
1518
2323
  const manifests = [];
1519
2324
  for (const file of migrationFiles) {
1520
2325
  const id = file.replace(/\.ts$/, "");
1521
- const manifestPath = join4(outputDir, `${id}.json`);
2326
+ const manifestPath = join6(outputDir, `${id}.json`);
1522
2327
  const jsonManifest = await readMigrationManifest(manifestPath);
1523
2328
  if (jsonManifest) {
1524
2329
  manifests.push({ ...jsonManifest, id });
1525
2330
  continue;
1526
2331
  }
1527
- const migrationPath = join4(outputDir, file);
2332
+ const migrationPath = join6(outputDir, file);
1528
2333
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
1529
2334
  manifests.push(sourceManifest);
1530
2335
  }
@@ -1532,7 +2337,7 @@ async function listMigrationManifests(outputDir) {
1532
2337
  }
1533
2338
  async function readMigrationManifest(path) {
1534
2339
  try {
1535
- const content = await readFile(path, "utf-8");
2340
+ const content = await readFile3(path, "utf-8");
1536
2341
  return JSON.parse(content);
1537
2342
  } catch (error) {
1538
2343
  const code = error.code;
@@ -1543,7 +2348,7 @@ async function readMigrationManifest(path) {
1543
2348
  }
1544
2349
  }
1545
2350
  async function readMigrationManifestFromSource(path, id) {
1546
- const content = await readFile(path, "utf-8");
2351
+ const content = await readFile3(path, "utf-8");
1547
2352
  const versions = parseVersionsFromMigrationId(id);
1548
2353
  return {
1549
2354
  id,
@@ -1560,9 +2365,14 @@ function parseVersionsFromMigrationId(id) {
1560
2365
  if (!match) {
1561
2366
  throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
1562
2367
  }
2368
+ const fromVersionText = match[1];
2369
+ const toVersionText = match[2];
2370
+ if (fromVersionText === void 0 || toVersionText === void 0) {
2371
+ throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
2372
+ }
1563
2373
  return {
1564
- fromVersion: Number.parseInt(match[1], 10),
1565
- toVersion: Number.parseInt(match[2], 10)
2374
+ fromVersion: Number.parseInt(fromVersionText, 10),
2375
+ toVersion: Number.parseInt(toVersionText, 10)
1566
2376
  };
1567
2377
  }
1568
2378
  function parseStringArrayExport(source, exportName) {
@@ -1600,18 +2410,18 @@ function parseLiteralExport(source, exportName) {
1600
2410
  }
1601
2411
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
1602
2412
  if (typeof dbArg === "string") {
1603
- return resolve3(projectRoot, dbArg);
2413
+ return resolve5(projectRoot, dbArg);
1604
2414
  }
1605
2415
  const sync = config?.dev?.sync;
1606
2416
  if (typeof sync === "object" && sync !== null) {
1607
2417
  if (sync.store === "sqlite") {
1608
- return join4(projectRoot, "kora-sync.db");
2418
+ return join6(projectRoot, "kora-sync.db");
1609
2419
  }
1610
2420
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
1611
2421
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
1612
- return resolve3(projectRoot, sync.store.filename);
2422
+ return resolve5(projectRoot, sync.store.filename);
1613
2423
  }
1614
- return join4(projectRoot, "kora-sync.db");
2424
+ return join6(projectRoot, "kora-sync.db");
1615
2425
  }
1616
2426
  }
1617
2427
  return void 0;
@@ -1628,18 +2438,155 @@ function resolvePostgresConnectionString(config) {
1628
2438
  return void 0;
1629
2439
  }
1630
2440
 
2441
+ // src/commands/status/status-command.ts
2442
+ import { defineCommand as defineCommand8 } from "citty";
2443
+ var DEFAULT_SYNC_PORT3 = 3001;
2444
+ var STATUS_ENDPOINT2 = "/__kora/status";
2445
+ async function fetchServerStatus(url, token) {
2446
+ const statusUrl = `${url.replace(/\/$/, "")}${STATUS_ENDPOINT2}`;
2447
+ const response = await fetch(statusUrl, {
2448
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
2449
+ });
2450
+ if (!response.ok) {
2451
+ throw new Error(
2452
+ `Failed to fetch status from ${statusUrl}: ${response.status} ${response.statusText}`
2453
+ );
2454
+ }
2455
+ return await response.json();
2456
+ }
2457
+ function formatUptime(ms) {
2458
+ const seconds = Math.floor(ms / 1e3);
2459
+ const minutes = Math.floor(seconds / 60);
2460
+ const hours = Math.floor(minutes / 60);
2461
+ const days = Math.floor(hours / 24);
2462
+ const parts = [];
2463
+ if (days > 0) parts.push(`${days}d`);
2464
+ if (hours % 24 > 0) parts.push(`${hours % 24}h`);
2465
+ if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
2466
+ parts.push(`${seconds % 60}s`);
2467
+ return parts.join(" ");
2468
+ }
2469
+ var statusCommand = defineCommand8({
2470
+ meta: {
2471
+ name: "status",
2472
+ description: "Show Kora sync server status"
2473
+ },
2474
+ args: {
2475
+ url: {
2476
+ type: "string",
2477
+ description: "Sync server URL (default: http://localhost:3001)",
2478
+ default: `http://localhost:${DEFAULT_SYNC_PORT3}`
2479
+ },
2480
+ watch: {
2481
+ type: "boolean",
2482
+ description: "Live-updating status (like htop)",
2483
+ default: false,
2484
+ alias: "w"
2485
+ },
2486
+ token: {
2487
+ type: "string",
2488
+ description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
2489
+ }
2490
+ },
2491
+ async run({ args }) {
2492
+ const logger = createLogger();
2493
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT3}`;
2494
+ const watch2 = args.watch === true;
2495
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
2496
+ try {
2497
+ if (watch2) {
2498
+ console.clear();
2499
+ logger.banner();
2500
+ logger.info(`Connecting to ${url}...`);
2501
+ const interval = setInterval(async () => {
2502
+ try {
2503
+ const status = await fetchServerStatus(url, token);
2504
+ printStatus(status, url, logger);
2505
+ } catch {
2506
+ }
2507
+ }, 2e3);
2508
+ try {
2509
+ const status = await fetchServerStatus(url, token);
2510
+ printStatus(status, url, logger);
2511
+ } catch {
2512
+ }
2513
+ await new Promise(() => {
2514
+ process.on("SIGINT", () => {
2515
+ clearInterval(interval);
2516
+ process.exit(0);
2517
+ });
2518
+ process.on("SIGTERM", () => {
2519
+ clearInterval(interval);
2520
+ process.exit(0);
2521
+ });
2522
+ });
2523
+ } else {
2524
+ const status = await fetchServerStatus(url, token);
2525
+ printStatus(status, url, logger);
2526
+ }
2527
+ } catch (error) {
2528
+ logger.error(`Failed to connect to ${url}`);
2529
+ if (error instanceof Error) {
2530
+ logger.error(error.message);
2531
+ }
2532
+ logger.blank();
2533
+ logger.step("Make sure the Kora sync server is running.");
2534
+ logger.step("Start it with: kora dev");
2535
+ process.exit(1);
2536
+ }
2537
+ }
2538
+ });
2539
+ function printStatus(status, url, logger) {
2540
+ console.clear();
2541
+ logger.banner();
2542
+ logger.info(`Kora Sync Server \u2014 ${url}`);
2543
+ logger.blank();
2544
+ if (!status.running) {
2545
+ logger.error("Server is not running");
2546
+ return;
2547
+ }
2548
+ logger.step(`Status: Running (uptime: ${formatUptime(status.uptime)}) \u2014 v${status.version}`);
2549
+ logger.step(`Schema: v${status.schemaVersion}`);
2550
+ logger.blank();
2551
+ logger.step(`Connections: ${status.connectedClients} connected`);
2552
+ logger.step(` Peak: ${status.peakConnections}`);
2553
+ logger.step(` Total: ${status.connectionsTotal}`);
2554
+ if (status.connectedNodeIds.length > 0) {
2555
+ logger.step(" Active nodes:");
2556
+ for (const nodeId of status.connectedNodeIds) {
2557
+ logger.step(` \u2022 ${nodeId}`);
2558
+ }
2559
+ }
2560
+ logger.blank();
2561
+ logger.step("Operations:");
2562
+ logger.step(` Received: ${status.operationsReceived.toLocaleString()}`);
2563
+ logger.step(` Sent: ${status.operationsSent.toLocaleString()}`);
2564
+ logger.step(` Total: ${status.totalOperations.toLocaleString()}`);
2565
+ logger.blank();
2566
+ if (status.errorCount > 0) {
2567
+ logger.warn(`Errors: ${status.errorCount}`);
2568
+ } else {
2569
+ logger.step("Errors: 0");
2570
+ }
2571
+ }
2572
+
1631
2573
  // src/bin.ts
1632
- var main = defineCommand4({
2574
+ var main = defineCommand9({
1633
2575
  meta: {
1634
2576
  name: "kora",
1635
2577
  description: "Kora.js \u2014 Offline-first application framework"
1636
2578
  },
1637
2579
  subCommands: {
2580
+ backup: backupCommand,
2581
+ compact: compactCommand,
1638
2582
  create: createCommand,
1639
2583
  dev: devCommand,
2584
+ doctor: doctorCommand,
1640
2585
  deploy: deployCommand,
1641
2586
  generate: generateCommand,
1642
- migrate: migrateCommand
2587
+ logs: logsCommand,
2588
+ migrate: migrateCommand,
2589
+ status: statusCommand
1643
2590
  }
1644
2591
  });
1645
2592
  runMain(main);