@korajs/cli 1.0.0-beta.12 → 1.0.0-beta.13

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 (83) hide show
  1. package/README.md +25 -107
  2. package/dist/bin.cjs +808 -340
  3. package/dist/bin.cjs.map +1 -1
  4. package/dist/bin.js +588 -174
  5. package/dist/bin.js.map +1 -1
  6. package/dist/{chunk-G2PBYMMA.js → chunk-7Y4J2GXP.js} +168 -153
  7. package/dist/chunk-7Y4J2GXP.js.map +1 -0
  8. package/dist/{chunk-Z7GCXMGW.js → chunk-TIKGLNER.js} +44 -9
  9. package/dist/chunk-TIKGLNER.js.map +1 -0
  10. package/dist/{chunk-4IKCHRJ6.js → chunk-TVIRMDER.js} +122 -112
  11. package/dist/chunk-TVIRMDER.js.map +1 -0
  12. package/dist/create.cjs +63 -28
  13. package/dist/create.cjs.map +1 -1
  14. package/dist/create.js +2 -2
  15. package/dist/index.cjs +41 -14
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +21 -1
  18. package/dist/index.d.ts +21 -1
  19. package/dist/index.js +8 -2
  20. package/dist/{studio-server-NIFKZI4F.js → studio-server-XLGZNGG5.js} +11 -2
  21. package/dist/studio-server-XLGZNGG5.js.map +1 -0
  22. package/dist/vite.cjs +351 -0
  23. package/dist/vite.cjs.map +1 -0
  24. package/dist/vite.d.cts +161 -0
  25. package/dist/vite.d.ts +161 -0
  26. package/dist/vite.js +321 -0
  27. package/dist/vite.js.map +1 -0
  28. package/package.json +18 -8
  29. package/templates/react-basic/AGENTS.md +5 -5
  30. package/templates/react-basic/vite.config.ts +4 -1
  31. package/templates/react-sync/AGENTS.md +5 -5
  32. package/templates/react-sync/server.ts +8 -0
  33. package/templates/react-sync/src/vite-env.d.ts +1 -0
  34. package/templates/react-sync/vite.config.ts +4 -1
  35. package/templates/react-tailwind/AGENTS.md +5 -5
  36. package/templates/react-tailwind/vite.config.ts +10 -1
  37. package/templates/react-tailwind-sync/AGENTS.md +9 -7
  38. package/templates/react-tailwind-sync/server.ts +8 -0
  39. package/templates/react-tailwind-sync/src/App.tsx +1 -1
  40. package/templates/react-tailwind-sync/src/kora.ts +32 -0
  41. package/templates/react-tailwind-sync/src/main.tsx +1 -23
  42. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +4 -8
  43. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +2 -2
  44. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
  45. package/templates/react-tailwind-sync/src/vite-env.d.ts +1 -0
  46. package/templates/react-tailwind-sync/vite.config.ts +10 -1
  47. package/templates/svelte-basic/AGENTS.md +4 -4
  48. package/templates/svelte-basic/package.json.hbs +6 -0
  49. package/templates/svelte-basic/src/App.svelte +2 -1
  50. package/templates/svelte-basic/vite.config.ts +4 -1
  51. package/templates/svelte-sync/AGENTS.md +4 -4
  52. package/templates/svelte-sync/server.ts +8 -0
  53. package/templates/svelte-sync/src/App.svelte +2 -1
  54. package/templates/svelte-sync/vite.config.ts +4 -1
  55. package/templates/svelte-tailwind/AGENTS.md +4 -4
  56. package/templates/svelte-tailwind/package.json.hbs +7 -1
  57. package/templates/svelte-tailwind/src/App.svelte +2 -1
  58. package/templates/svelte-tailwind/vite.config.ts +10 -1
  59. package/templates/svelte-tailwind-sync/AGENTS.md +4 -4
  60. package/templates/svelte-tailwind-sync/package.json.hbs +1 -1
  61. package/templates/svelte-tailwind-sync/server.ts +8 -0
  62. package/templates/svelte-tailwind-sync/src/App.svelte +2 -1
  63. package/templates/svelte-tailwind-sync/vite.config.ts +10 -1
  64. package/templates/tauri-react/AGENTS.md +5 -5
  65. package/templates/tauri-react/package.json.hbs +1 -1
  66. package/templates/tauri-react/server.ts +8 -0
  67. package/templates/tauri-react/src/vite-env.d.ts +1 -0
  68. package/templates/vue-basic/AGENTS.md +4 -4
  69. package/templates/vue-basic/package.json.hbs +6 -0
  70. package/templates/vue-basic/vite.config.ts +4 -1
  71. package/templates/vue-sync/AGENTS.md +4 -4
  72. package/templates/vue-sync/server.ts +8 -0
  73. package/templates/vue-sync/vite.config.ts +4 -1
  74. package/templates/vue-tailwind/AGENTS.md +4 -4
  75. package/templates/vue-tailwind/package.json.hbs +6 -0
  76. package/templates/vue-tailwind/vite.config.ts +4 -1
  77. package/templates/vue-tailwind-sync/AGENTS.md +4 -4
  78. package/templates/vue-tailwind-sync/server.ts +8 -0
  79. package/templates/vue-tailwind-sync/vite.config.ts +4 -1
  80. package/dist/chunk-4IKCHRJ6.js.map +0 -1
  81. package/dist/chunk-G2PBYMMA.js.map +0 -1
  82. package/dist/chunk-Z7GCXMGW.js.map +0 -1
  83. package/dist/studio-server-NIFKZI4F.js.map +0 -1
package/dist/bin.cjs CHANGED
@@ -251,6 +251,15 @@ function parseJsonColumn(raw) {
251
251
  return null;
252
252
  }
253
253
  }
254
+ function stripOpMetadata(data) {
255
+ if (data === null) return null;
256
+ const fields = {};
257
+ for (const [key, value] of Object.entries(data)) {
258
+ if (key.startsWith("__kora_") && key.endsWith("__")) continue;
259
+ fields[key] = value;
260
+ }
261
+ return Object.keys(fields).length > 0 ? fields : null;
262
+ }
254
263
  function parseJsonArrayColumn(raw) {
255
264
  if (typeof raw !== "string" || raw.length === 0) {
256
265
  return [];
@@ -464,7 +473,7 @@ var init_db_reader = __esm({
464
473
  nodeId: String(row.node_id),
465
474
  type: String(row.type),
466
475
  recordId: String(row.record_id),
467
- data: parseJsonColumn(row.data),
476
+ data: stripOpMetadata(parseJsonColumn(row.data)),
468
477
  previousData: parseJsonColumn(row.previous_data),
469
478
  timestamp: parseVersionStamp(row.timestamp),
470
479
  sequenceNumber: Number(row.sequence_number),
@@ -1981,13 +1990,13 @@ var spectator_manager_exports = {};
1981
1990
  __export(spectator_manager_exports, {
1982
1991
  SpectatorManager: () => SpectatorManager
1983
1992
  });
1984
- var import_node_fs7, import_node_os, import_node_path28, FORWARDED_EVENTS, MAX_EVENT_BUFFER, SpectatorManager;
1993
+ var import_node_fs7, import_node_os, import_node_path29, FORWARDED_EVENTS, MAX_EVENT_BUFFER, SpectatorManager;
1985
1994
  var init_spectator_manager = __esm({
1986
1995
  "src/commands/studio/spectator-manager.ts"() {
1987
1996
  "use strict";
1988
1997
  import_node_fs7 = require("fs");
1989
1998
  import_node_os = require("os");
1990
- import_node_path28 = require("path");
1999
+ import_node_path29 = require("path");
1991
2000
  FORWARDED_EVENTS = [
1992
2001
  "merge:started",
1993
2002
  "merge:conflict",
@@ -2001,8 +2010,8 @@ var init_spectator_manager = __esm({
2001
2010
  SpectatorManager = class {
2002
2011
  constructor(options) {
2003
2012
  this.options = options;
2004
- this.tmpDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path28.join)((0, import_node_os.tmpdir)(), "kora-studio-spectator-"));
2005
- this.dbPath = (0, import_node_path28.join)(this.tmpDir, "spectator-replica.db");
2013
+ this.tmpDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path29.join)((0, import_node_os.tmpdir)(), "kora-studio-spectator-"));
2014
+ this.dbPath = (0, import_node_path29.join)(this.tmpDir, "spectator-replica.db");
2006
2015
  }
2007
2016
  options;
2008
2017
  runtime = null;
@@ -2185,13 +2194,13 @@ function summarizeEvent(type, payload) {
2185
2194
  return type.replace("lab:", "");
2186
2195
  }
2187
2196
  }
2188
- var import_node_fs8, import_node_os2, import_node_path29, import_core3, DEFAULT_CHAOS, FORWARDED_EVENTS2, MAX_EVENT_BUFFER2, LabManager;
2197
+ var import_node_fs8, import_node_os2, import_node_path30, import_core3, DEFAULT_CHAOS, FORWARDED_EVENTS2, MAX_EVENT_BUFFER2, LabManager;
2189
2198
  var init_lab_manager = __esm({
2190
2199
  "src/commands/studio/lab-manager.ts"() {
2191
2200
  "use strict";
2192
2201
  import_node_fs8 = require("fs");
2193
2202
  import_node_os2 = require("os");
2194
- import_node_path29 = require("path");
2203
+ import_node_path30 = require("path");
2195
2204
  import_core3 = require("@korajs/core");
2196
2205
  DEFAULT_CHAOS = {
2197
2206
  dropRate: 0,
@@ -2249,7 +2258,7 @@ var init_lab_manager = __esm({
2249
2258
  }
2250
2259
  async start(initialDevices = 2) {
2251
2260
  const harness = await this.loadHarness();
2252
- this.tmpDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path29.join)((0, import_node_os2.tmpdir)(), "kora-studio-lab-"));
2261
+ this.tmpDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path30.join)((0, import_node_os2.tmpdir)(), "kora-studio-lab-"));
2253
2262
  this.server = new harness.TestServer(this.schema);
2254
2263
  for (let i = 0; i < initialDevices; i++) {
2255
2264
  await this.addDevice();
@@ -2301,7 +2310,7 @@ var init_lab_manager = __esm({
2301
2310
  nodeId: entry.device.getNodeId(),
2302
2311
  connected: entry.device.isConnected(),
2303
2312
  chaos: { ...entry.chaos },
2304
- dbPath: (0, import_node_path29.join)(this.tmpDir ?? "", `test-device-${name}.db`),
2313
+ dbPath: (0, import_node_path30.join)(this.tmpDir ?? "", `test-device-${name}.db`),
2305
2314
  pendingOperations: entry.device.getSyncEngine()?.getStatus().pendingOperations ?? 0
2306
2315
  };
2307
2316
  }
@@ -2502,7 +2511,7 @@ await addTodo.mutateAsync({ title: 'x' }) // resolves with the result; throw
2502
2511
  const status = useSyncStatus() // status.status, status.pendingOperations
2503
2512
  \`\`\`
2504
2513
 
2505
- Rules: always render \`mutation.error\` somewhere. Keep \`<StrictMode>\` in place; the hooks are StrictMode-safe. \`useQuery\` returns data synchronously from the local store, so do not add loading spinners for local reads.`,
2514
+ Rules: always render \`mutation.error\` somewhere. Keep \`<StrictMode>\` in place; the hooks are StrictMode-safe. \`useQuery\` reads the local store: no network, no spinner. Its very first render returns \`[]\` before the local query has run; use \`useQueryState\` when you must tell "loading" from "no rows".`,
2506
2515
  vue: `## Vue bindings
2507
2516
 
2508
2517
  \`\`\`ts
@@ -2515,7 +2524,7 @@ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in
2515
2524
  const status = useSyncStatus() // status.status, status.pendingOperations
2516
2525
  \`\`\`
2517
2526
 
2518
- Rules: surface the mutation's error state in the UI. \`useQuery\` reads synchronously from the local store, so avoid loading spinners for local data. The Kora context comes from \`KoraProvider\`, wired in your app entry (for example \`src/main.ts\`).`,
2527
+ Rules: surface the mutation's error state in the UI. \`useQuery\` reads the local store (no network, no spinner); the ref holds \`[]\` until the first local result. The Kora context comes from \`KoraProvider\`, wired in your app entry (for example \`src/main.ts\`).`,
2519
2528
  svelte: `## Svelte bindings
2520
2529
 
2521
2530
  Kora's Svelte bindings from \`@korajs/svelte\` expose \`useCollection\`, \`useQuery\`, \`useMutation\`, and \`useSyncStatus\`, wired through the provider set up in your app entry.
@@ -2530,7 +2539,7 @@ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in
2530
2539
  const status = useSyncStatus() // readable store; read $status.status in markup
2531
2540
  \`\`\`
2532
2541
 
2533
- Rules: queries are reactive stores that read synchronously from the local database, so avoid loading spinners for local reads. Mutations expose an \`error\` state that must be surfaced in the UI.`
2542
+ Rules: queries are reactive stores over the local database (no network, no spinner; \`[]\` until the first local result). Mutations expose an \`error\` state that must be surfaced in the UI.`
2534
2543
  };
2535
2544
  function generateAgentsMd(framework) {
2536
2545
  return `# AGENTS.md
@@ -2547,9 +2556,9 @@ This project uses **Kora.js**, an offline-first application framework. All appli
2547
2556
  2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
2548
2557
  3. **Await readiness before direct collection access.** Outside the UI bindings, \`await app.ready\` before calling \`app.<collection>\` methods. The framework bindings handle this for you inside components.
2549
2558
  4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a read or write on connectivity. If you are checking \`navigator.onLine\` before a data operation, you are doing it wrong.
2550
- 5. **Surface mutation errors.** Fire-and-forget \`mutate\` calls fold errors into the mutation state. Always render the mutation's \`error\`, or handle the promise from the \`mutateAsync\` variant. Silent failure is the worst failure.
2559
+ 5. **Surface mutation errors.** Fire-and-forget \`mutate\` calls put errors into the mutation state. Always render the mutation's \`error\`, or handle the promise from the \`mutateAsync\` variant. Silent failure is the worst failure.
2551
2560
  6. **Do not add a state library for server or app data.** Kora's reactive queries are the store. Do not reach for react-query, SWR, Redux, Zustand, or similar for data that lives in a collection. Local UI state (form inputs, toggles) can use your framework's normal state tools.
2552
- 7. **Do not add loading spinners for local reads.** \`useQuery\` returns data synchronously from the local store, so there is no loading state to wait on for local data.
2561
+ 7. **Do not add network-style loading states for local reads.** \`useQuery\` reads the local database, which answers within a frame; its very first render returns an empty result. Use \`useQueryState\` only where "loading" and "no rows" must look different.
2553
2562
 
2554
2563
  ## Data API cheat sheet
2555
2564
 
@@ -2587,15 +2596,15 @@ export default defineSchema({
2587
2596
  })
2588
2597
  \`\`\`
2589
2598
 
2590
- If you change collection shapes, increment \`version\` and run \`npx kora migrate\`.
2599
+ If you change a collection's shape, increment \`version\` and add a \`migrations\` entry for it (\`migrate().addField(...)\`): each device migrates its local database on its next open. \`npx kora migrate\` generates the matching migration for server databases and a transform stub for clients still on the older version.
2591
2600
 
2592
2601
  ## Conflict handling
2593
2602
 
2594
- Concurrent edits merge automatically: last-write-wins per field, add-wins for arrays, and character-level CRDT for \`t.richtext()\` fields. When a field needs domain-specific merging (counters, quantities), add a \`resolve\` function in the schema rather than writing sync logic by hand. Never write your own conflict or merge code.
2603
+ Concurrent edits merge automatically: last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key, and character-level CRDT for \`t.richtext()\` fields. When a field needs domain-specific merging (counters, quantities), add a \`resolve\` function in the schema rather than writing sync logic by hand. Never write your own conflict or merge code.
2595
2604
 
2596
2605
  ## Sync and auth
2597
2606
 
2598
- Enable sync by adding one line to \`createApp\`: \`createApp({ schema, sync: { url } })\`. When the app is offline, writes queue locally and sync when connectivity returns. If the project uses \`@korajs/auth\`, pass its client as \`createApp({ schema, sync: { url, authClient } })\`. Local writes work without sign-in; sync requires the server to accept the connection.
2607
+ Enable sync with one option on \`createApp\`: \`createApp({ schema, sync: { url, autoConnect: true } })\` (without \`autoConnect\`, call \`await app.sync?.connect()\` after \`app.ready\`). When the app is offline, writes queue locally and sync when connectivity returns. If the project uses \`@korajs/auth\`, pass its binding as \`createApp({ schema, sync: { url, authClient: createKoraAuthSync({ authClient, schema }) } })\`; the server grants what each user may sync. Local writes work without sign-in; sync requires the server to accept the connection.
2599
2608
 
2600
2609
  ${FRAMEWORK_BINDINGS[framework]}
2601
2610
 
@@ -2603,7 +2612,7 @@ ${FRAMEWORK_BINDINGS[framework]}
2603
2612
 
2604
2613
  - \`npx kora dev\` runs the dev environment (app server, local sync server when configured, schema watcher).
2605
2614
  - \`npx kora doctor\` diagnoses a broken setup.
2606
- - \`npx kora migrate\` applies a schema change to the local store.
2615
+ - \`npx kora migrate\` generates a migration from a schema change (\`--apply\` runs it against a SQLite or Postgres database).
2607
2616
  - In a running app, the DevTools overlay (Ctrl+Shift+K, Cmd+Shift+K on macOS) inspects operations, merges, and sync status.
2608
2617
  `;
2609
2618
  }
@@ -2844,6 +2853,20 @@ var import_citty3 = require("citty");
2844
2853
 
2845
2854
  // src/errors.ts
2846
2855
  var import_core = require("@korajs/core");
2856
+ var DeployPlatformUnavailableError = class extends import_core.KoraError {
2857
+ constructor(platform, available) {
2858
+ super(
2859
+ `Deploying to "${platform}" is coming soon and not available yet. Choose one of: ${available.join(", ")}. To self-host, run the production server (\`pnpm build && pnpm start\`) in any Node 20+ environment.`,
2860
+ "DEPLOY_PLATFORM_UNAVAILABLE",
2861
+ { platform, available }
2862
+ );
2863
+ this.platform = platform;
2864
+ this.available = available;
2865
+ this.name = "DeployPlatformUnavailableError";
2866
+ }
2867
+ platform;
2868
+ available;
2869
+ };
2847
2870
  var ProjectExistsError = class extends import_core.KoraError {
2848
2871
  constructor(directory) {
2849
2872
  super(
@@ -3065,7 +3088,7 @@ var compactCommand = (0, import_citty3.defineCommand)({
3065
3088
  // src/commands/create/create-command.ts
3066
3089
  var import_node_child_process4 = require("child_process");
3067
3090
  var import_node_fs2 = require("fs");
3068
- var import_node_path10 = require("path");
3091
+ var import_node_path11 = require("path");
3069
3092
  var import_node_url4 = require("url");
3070
3093
  var import_citty4 = require("citty");
3071
3094
 
@@ -3475,6 +3498,32 @@ function isSupportedWebFramework(framework) {
3475
3498
  return framework === "react" || framework === "vue" || framework === "svelte";
3476
3499
  }
3477
3500
 
3501
+ // src/commands/create/pnpm-workspace.ts
3502
+ var import_promises6 = require("fs/promises");
3503
+ var import_node_path8 = require("path");
3504
+ async function writePnpmWorkspaceSettings(targetDir) {
3505
+ const manifest = JSON.parse(await (0, import_promises6.readFile)((0, import_node_path8.join)(targetDir, "package.json"), "utf-8"));
3506
+ const listed = manifest.pnpm?.onlyBuiltDependencies;
3507
+ const allowed = Array.isArray(listed) ? [...new Set(listed.filter((name) => typeof name === "string"))].sort() : [];
3508
+ await (0, import_promises6.writeFile)((0, import_node_path8.join)(targetDir, "pnpm-workspace.yaml"), renderPnpmWorkspace(allowed), "utf-8");
3509
+ return allowed;
3510
+ }
3511
+ function renderPnpmWorkspace(allowed) {
3512
+ const quote = (name) => `'${name.replace(/'/g, "''")}'`;
3513
+ const lines = [
3514
+ "# pnpm settings for this app. Dependencies listed here may run their install",
3515
+ "# (build) scripts: pnpm 10 reads onlyBuiltDependencies, pnpm 11+ reads allowBuilds.",
3516
+ "packages:",
3517
+ " - '.'"
3518
+ ];
3519
+ if (allowed.length > 0) {
3520
+ lines.push("onlyBuiltDependencies:", ...allowed.map((name) => ` - ${quote(name)}`));
3521
+ lines.push("allowBuilds:", ...allowed.map((name) => ` ${quote(name)}: true`));
3522
+ }
3523
+ return `${lines.join("\n")}
3524
+ `;
3525
+ }
3526
+
3478
3527
  // src/commands/create/preferences-flow.ts
3479
3528
  async function resolveCreatePreferencesFlow(params) {
3480
3529
  const { flags, prompts, store } = params;
@@ -3705,8 +3754,8 @@ function validateProjectName(name) {
3705
3754
  }
3706
3755
 
3707
3756
  // src/commands/create/sync-provider-preset.ts
3708
- var import_promises6 = require("fs/promises");
3709
- var import_node_path8 = require("path");
3757
+ var import_promises7 = require("fs/promises");
3758
+ var import_node_path9 = require("path");
3710
3759
  async function applySyncProviderPreset(options) {
3711
3760
  if (!isSyncTemplate(options.template)) {
3712
3761
  return;
@@ -3716,10 +3765,10 @@ async function applySyncProviderPreset(options) {
3716
3765
  }
3717
3766
  const providerName = getProviderDisplayName(options.dbProvider);
3718
3767
  const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
3719
- const envPath = (0, import_node_path8.join)(options.targetDir, ".env.example");
3720
- const readmePath = (0, import_node_path8.join)(options.targetDir, "README.md");
3721
- const existingReadme = await (0, import_promises6.readFile)(readmePath, "utf-8");
3722
- const existingEnv = await (0, import_promises6.readFile)(envPath, "utf-8");
3768
+ const envPath = (0, import_node_path9.join)(options.targetDir, ".env.example");
3769
+ const readmePath = (0, import_node_path9.join)(options.targetDir, "README.md");
3770
+ const existingReadme = await (0, import_promises7.readFile)(readmePath, "utf-8");
3771
+ const existingEnv = await (0, import_promises7.readFile)(envPath, "utf-8");
3723
3772
  const trimmedReadme = existingReadme.trimEnd();
3724
3773
  const trimmedEnv = existingEnv.trimEnd();
3725
3774
  const envSuffix = [
@@ -3739,9 +3788,9 @@ async function applySyncProviderPreset(options) {
3739
3788
  ""
3740
3789
  ].join("\n");
3741
3790
  const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
3742
- await (0, import_promises6.writeFile)(envPath, `${trimmedEnv}${envSuffix}
3791
+ await (0, import_promises7.writeFile)(envPath, `${trimmedEnv}${envSuffix}
3743
3792
  `, "utf-8");
3744
- await (0, import_promises6.writeFile)(readmePath, readmeTemplate, "utf-8");
3793
+ await (0, import_promises7.writeFile)(readmePath, readmeTemplate, "utf-8");
3745
3794
  }
3746
3795
  function isSyncTemplate(template) {
3747
3796
  return template === "react-sync" || template === "react-tailwind-sync" || template === "vue-sync" || template === "vue-tailwind-sync" || template === "svelte-sync" || template === "svelte-tailwind-sync" || template === "tauri-react";
@@ -3785,9 +3834,14 @@ function getProviderConnectionStringExample(provider) {
3785
3834
 
3786
3835
  // src/templates/composer.ts
3787
3836
  var import_node_fs = require("fs");
3788
- var import_promises7 = require("fs/promises");
3789
- var import_node_path9 = require("path");
3837
+ var import_promises8 = require("fs/promises");
3838
+ var import_node_path10 = require("path");
3790
3839
  var import_node_url3 = require("url");
3840
+
3841
+ // src/templates/tauri-version.ts
3842
+ var KORA_TAURI_TEMPLATE_VERSION = "0.4.3-beta.12";
3843
+
3844
+ // src/templates/composer.ts
3791
3845
  var import_meta = {};
3792
3846
  function substituteVariables(template, context) {
3793
3847
  return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
@@ -3796,16 +3850,16 @@ function substituteVariables(template, context) {
3796
3850
  });
3797
3851
  }
3798
3852
  function getTemplatePath(templateName) {
3799
- let dir = (0, import_node_path9.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
3853
+ let dir = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
3800
3854
  for (let i = 0; i < 7; i++) {
3801
- const candidate = (0, import_node_path9.resolve)(dir, "templates", templateName);
3855
+ const candidate = (0, import_node_path10.resolve)(dir, "templates", templateName);
3802
3856
  if ((0, import_node_fs.existsSync)(candidate)) {
3803
3857
  return candidate;
3804
3858
  }
3805
- dir = (0, import_node_path9.dirname)(dir);
3859
+ dir = (0, import_node_path10.dirname)(dir);
3806
3860
  }
3807
- const currentDir = (0, import_node_path9.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
3808
- return (0, import_node_path9.resolve)(currentDir, "..", "..", "..", "templates", templateName);
3861
+ const currentDir = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
3862
+ return (0, import_node_path10.resolve)(currentDir, "..", "..", "..", "templates", templateName);
3809
3863
  }
3810
3864
  function createCompatibilityLayerPlan(templateName) {
3811
3865
  const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
@@ -3927,6 +3981,7 @@ async function composeTemplateLayers(plan, targetDir, context) {
3927
3981
  projectName: context.projectName,
3928
3982
  packageManager: context.packageManager,
3929
3983
  koraVersion: context.koraVersion,
3984
+ tauriVersion: context.tauriVersion ?? KORA_TAURI_TEMPLATE_VERSION,
3930
3985
  dbProvider: context.dbProvider ?? "none"
3931
3986
  };
3932
3987
  for (const layer of plan.layers) {
@@ -3938,22 +3993,22 @@ async function composeTemplateLayers(plan, targetDir, context) {
3938
3993
  }
3939
3994
  }
3940
3995
  async function copyDirectory(src, dest, vars) {
3941
- await (0, import_promises7.mkdir)(dest, { recursive: true });
3942
- const entries = await (0, import_promises7.readdir)(src);
3996
+ await (0, import_promises8.mkdir)(dest, { recursive: true });
3997
+ const entries = await (0, import_promises8.readdir)(src);
3943
3998
  for (const entry of entries) {
3944
- const srcPath = (0, import_node_path9.join)(src, entry);
3945
- const srcStat = await (0, import_promises7.stat)(srcPath);
3999
+ const srcPath = (0, import_node_path10.join)(src, entry);
4000
+ const srcStat = await (0, import_promises8.stat)(srcPath);
3946
4001
  if (srcStat.isDirectory()) {
3947
- await copyDirectory(srcPath, (0, import_node_path9.join)(dest, entry), vars);
4002
+ await copyDirectory(srcPath, (0, import_node_path10.join)(dest, entry), vars);
3948
4003
  continue;
3949
4004
  }
3950
4005
  if (entry.endsWith(".hbs")) {
3951
- const content = await (0, import_promises7.readFile)(srcPath, "utf-8");
4006
+ const content = await (0, import_promises8.readFile)(srcPath, "utf-8");
3952
4007
  const outputName = entry.slice(0, -4);
3953
- await (0, import_promises7.writeFile)((0, import_node_path9.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
4008
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
3954
4009
  continue;
3955
4010
  }
3956
- await (0, import_promises7.copyFile)(srcPath, (0, import_node_path9.join)(dest, entry));
4011
+ await (0, import_promises8.copyFile)(srcPath, (0, import_node_path10.join)(dest, entry));
3957
4012
  }
3958
4013
  }
3959
4014
 
@@ -4106,7 +4161,7 @@ var createCommand = (0, import_citty4.defineCommand)({
4106
4161
  const editorDetection = detectEditorFromEnvironment();
4107
4162
  const gitContext = await detectGitContext(process.cwd());
4108
4163
  const monorepoContext = await detectMonorepoContext(process.cwd());
4109
- let targetDir = (0, import_node_path10.resolve)(process.cwd(), projectName);
4164
+ let targetDir = (0, import_node_path11.resolve)(process.cwd(), projectName);
4110
4165
  if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
4111
4166
  const useWorkspaceTarget = await prompts.confirm(
4112
4167
  `Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
@@ -4133,6 +4188,9 @@ var createCommand = (0, import_citty4.defineCommand)({
4133
4188
  db: selection.db,
4134
4189
  dbProvider: selection.dbProvider
4135
4190
  });
4191
+ if (pm === "pnpm") {
4192
+ await writePnpmWorkspaceSettings(targetDir);
4193
+ }
4136
4194
  if (!useDefaults && editorDetection.editor !== "unknown") {
4137
4195
  const shouldApplyEditorPreset = await prompts.confirm(
4138
4196
  `Detected ${formatEditor(editorDetection.editor)}. Add workspace recommendations for ${formatEditor(editorDetection.editor)}?`,
@@ -4276,16 +4334,16 @@ function relativeToTarget(targetDir, filePath) {
4276
4334
  }
4277
4335
  function resolveKoraVersion() {
4278
4336
  try {
4279
- let dir = (0, import_node_path10.dirname)((0, import_node_url4.fileURLToPath)(import_meta2.url));
4337
+ let dir = (0, import_node_path11.dirname)((0, import_node_url4.fileURLToPath)(import_meta2.url));
4280
4338
  for (let i = 0; i < 5; i++) {
4281
- const pkgPath = (0, import_node_path10.resolve)(dir, "package.json");
4339
+ const pkgPath = (0, import_node_path11.resolve)(dir, "package.json");
4282
4340
  if ((0, import_node_fs2.existsSync)(pkgPath)) {
4283
4341
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
4284
4342
  if (pkg.name === "@korajs/cli") {
4285
4343
  return deriveKoraTemplateVersion(pkg.version);
4286
4344
  }
4287
4345
  }
4288
- dir = (0, import_node_path10.dirname)(dir);
4346
+ dir = (0, import_node_path11.dirname)(dir);
4289
4347
  }
4290
4348
  return "latest";
4291
4349
  } catch {
@@ -4301,7 +4359,7 @@ function deriveKoraTemplateVersion(version) {
4301
4359
 
4302
4360
  // src/commands/deploy/deploy-command.ts
4303
4361
  var import_node_fs4 = require("fs");
4304
- var import_node_path21 = require("path");
4362
+ var import_node_path22 = require("path");
4305
4363
  var import_citty5 = require("citty");
4306
4364
  init_fs_helpers();
4307
4365
 
@@ -4315,19 +4373,23 @@ var DEPLOY_PLATFORMS = [
4315
4373
  "docker",
4316
4374
  "kora-cloud"
4317
4375
  ];
4376
+ var IMPLEMENTED_DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail"];
4377
+ function isImplementedDeployPlatform(platform) {
4378
+ return IMPLEMENTED_DEPLOY_PLATFORMS.includes(platform);
4379
+ }
4318
4380
  function isDeployPlatform(value) {
4319
4381
  return DEPLOY_PLATFORMS.includes(value);
4320
4382
  }
4321
4383
 
4322
4384
  // src/commands/deploy/adapters/aws-ecs-adapter.ts
4323
4385
  var import_node_child_process6 = require("child_process");
4324
- var import_node_path13 = require("path");
4386
+ var import_node_path14 = require("path");
4325
4387
 
4326
4388
  // src/commands/deploy/builder/client-builder.ts
4327
4389
  var import_node_child_process5 = require("child_process");
4328
4390
  var import_node_fs3 = require("fs");
4329
- var import_promises8 = require("fs/promises");
4330
- var import_node_path11 = require("path");
4391
+ var import_promises9 = require("fs/promises");
4392
+ var import_node_path12 = require("path");
4331
4393
  init_fs_helpers();
4332
4394
  async function buildClient(options) {
4333
4395
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
@@ -4349,15 +4411,15 @@ async function buildClient(options) {
4349
4411
  return { outDir: options.outDir };
4350
4412
  }
4351
4413
  async function patchSqliteWasmAssets(projectRoot, outDir) {
4352
- const assetsDir = (0, import_node_path11.join)(outDir, "assets");
4414
+ const assetsDir = (0, import_node_path12.join)(outDir, "assets");
4353
4415
  if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
4354
- const files = await (0, import_promises8.readdir)(assetsDir);
4416
+ const files = await (0, import_promises9.readdir)(assetsDir);
4355
4417
  const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
4356
4418
  if (hashedWasm && !files.includes("sqlite3.wasm")) {
4357
- await (0, import_promises8.copyFile)((0, import_node_path11.join)(assetsDir, hashedWasm), (0, import_node_path11.join)(assetsDir, "sqlite3.wasm"));
4419
+ await (0, import_promises9.copyFile)((0, import_node_path12.join)(assetsDir, hashedWasm), (0, import_node_path12.join)(assetsDir, "sqlite3.wasm"));
4358
4420
  }
4359
4421
  if (!files.includes("sqlite3-opfs-async-proxy.js")) {
4360
- const proxyFile = (0, import_node_path11.resolve)(
4422
+ const proxyFile = (0, import_node_path12.resolve)(
4361
4423
  projectRoot,
4362
4424
  "node_modules",
4363
4425
  "@sqlite.org",
@@ -4367,7 +4429,7 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
4367
4429
  "sqlite3-opfs-async-proxy.js"
4368
4430
  );
4369
4431
  if ((0, import_node_fs3.existsSync)(proxyFile)) {
4370
- await (0, import_promises8.copyFile)(proxyFile, (0, import_node_path11.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
4432
+ await (0, import_promises9.copyFile)(proxyFile, (0, import_node_path12.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
4371
4433
  }
4372
4434
  }
4373
4435
  }
@@ -4392,9 +4454,9 @@ async function runProcess(command, args, cwd) {
4392
4454
  }
4393
4455
 
4394
4456
  // src/commands/deploy/builder/server-bundler.ts
4395
- var import_promises9 = require("fs/promises");
4396
4457
  var import_promises10 = require("fs/promises");
4397
- var import_node_path12 = require("path");
4458
+ var import_promises11 = require("fs/promises");
4459
+ var import_node_path13 = require("path");
4398
4460
  var import_esbuild = require("esbuild");
4399
4461
  var DEFAULT_ENTRY_CANDIDATES = [
4400
4462
  "server.ts",
@@ -4410,8 +4472,8 @@ async function bundleServer(options) {
4410
4472
  `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
4411
4473
  );
4412
4474
  }
4413
- await (0, import_promises9.mkdir)(options.deployDirectory, { recursive: true });
4414
- const outputFilePath = (0, import_node_path12.join)(options.deployDirectory, "server-bundled.js");
4475
+ await (0, import_promises10.mkdir)(options.deployDirectory, { recursive: true });
4476
+ const outputFilePath = (0, import_node_path13.join)(options.deployDirectory, "server-bundled.js");
4415
4477
  await (0, import_esbuild.build)({
4416
4478
  entryPoints: [entryFilePath],
4417
4479
  outfile: outputFilePath,
@@ -4433,9 +4495,9 @@ async function bundleServer(options) {
4433
4495
  }
4434
4496
  async function resolveServerEntry(projectRoot, candidates) {
4435
4497
  for (const candidate of candidates) {
4436
- const fullPath = (0, import_node_path12.join)(projectRoot, candidate);
4498
+ const fullPath = (0, import_node_path13.join)(projectRoot, candidate);
4437
4499
  try {
4438
- await (0, import_promises10.access)(fullPath);
4500
+ await (0, import_promises11.access)(fullPath);
4439
4501
  return fullPath;
4440
4502
  } catch {
4441
4503
  }
@@ -4537,19 +4599,19 @@ var AwsEcsAdapter = class {
4537
4599
  appName: config.appName,
4538
4600
  region: config.region
4539
4601
  };
4540
- const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
4602
+ const deployDirectory = (0, import_node_path14.join)(config.projectRoot, ".kora", "deploy");
4541
4603
  await bundleServer({
4542
4604
  projectRoot: config.projectRoot,
4543
4605
  deployDirectory
4544
4606
  });
4545
4607
  const client = config.deployTarget === "sync-server" ? null : await buildClient({
4546
4608
  projectRoot: config.projectRoot,
4547
- outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
4609
+ outDir: (0, import_node_path14.join)(deployDirectory, "dist"),
4548
4610
  mode: "production"
4549
4611
  });
4550
4612
  return {
4551
4613
  clientDirectory: client?.outDir ?? null,
4552
- serverBundlePath: (0, import_node_path13.join)(deployDirectory, "server-bundled.js"),
4614
+ serverBundlePath: (0, import_node_path14.join)(deployDirectory, "server-bundled.js"),
4553
4615
  deployDirectory
4554
4616
  };
4555
4617
  }
@@ -4815,7 +4877,7 @@ function inferLogLevel(line) {
4815
4877
 
4816
4878
  // src/commands/deploy/adapters/aws-lightsail-adapter.ts
4817
4879
  var import_node_child_process7 = require("child_process");
4818
- var import_node_path14 = require("path");
4880
+ var import_node_path15 = require("path");
4819
4881
  var AwsLightsailAdapter = class {
4820
4882
  name = "aws-lightsail";
4821
4883
  logger = createLogger();
@@ -4888,19 +4950,19 @@ var AwsLightsailAdapter = class {
4888
4950
  appName: config.appName,
4889
4951
  region: config.region
4890
4952
  };
4891
- const deployDirectory = (0, import_node_path14.join)(config.projectRoot, ".kora", "deploy");
4953
+ const deployDirectory = (0, import_node_path15.join)(config.projectRoot, ".kora", "deploy");
4892
4954
  await bundleServer({
4893
4955
  projectRoot: config.projectRoot,
4894
4956
  deployDirectory
4895
4957
  });
4896
4958
  const client = config.deployTarget === "sync-server" ? null : await buildClient({
4897
4959
  projectRoot: config.projectRoot,
4898
- outDir: (0, import_node_path14.join)(deployDirectory, "dist"),
4960
+ outDir: (0, import_node_path15.join)(deployDirectory, "dist"),
4899
4961
  mode: "production"
4900
4962
  });
4901
4963
  return {
4902
4964
  clientDirectory: client?.outDir ?? null,
4903
- serverBundlePath: (0, import_node_path14.join)(deployDirectory, "server-bundled.js"),
4965
+ serverBundlePath: (0, import_node_path15.join)(deployDirectory, "server-bundled.js"),
4904
4966
  deployDirectory
4905
4967
  };
4906
4968
  }
@@ -5200,11 +5262,11 @@ function inferLogLevel2(line) {
5200
5262
 
5201
5263
  // src/commands/deploy/adapters/fly-adapter.ts
5202
5264
  var import_node_child_process8 = require("child_process");
5203
- var import_node_path16 = require("path");
5265
+ var import_node_path17 = require("path");
5204
5266
 
5205
5267
  // src/commands/deploy/artifacts/fly-toml-generator.ts
5206
- var import_promises11 = require("fs/promises");
5207
- var import_node_path15 = require("path");
5268
+ var import_promises12 = require("fs/promises");
5269
+ var import_node_path16 = require("path");
5208
5270
  var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
5209
5271
  function generateFlyToml(options) {
5210
5272
  const internalPort = options.internalPort ?? 3e3;
@@ -5234,9 +5296,9 @@ function generateFlyToml(options) {
5234
5296
  ].join("\n");
5235
5297
  }
5236
5298
  async function writeFlyTomlArtifact(deployDirectory, options) {
5237
- await (0, import_promises11.mkdir)(deployDirectory, { recursive: true });
5238
- const filePath = (0, import_node_path15.join)(deployDirectory, "fly.toml");
5239
- await (0, import_promises11.writeFile)(filePath, generateFlyToml(options), "utf-8");
5299
+ await (0, import_promises12.mkdir)(deployDirectory, { recursive: true });
5300
+ const filePath = (0, import_node_path16.join)(deployDirectory, "fly.toml");
5301
+ await (0, import_promises12.writeFile)(filePath, generateFlyToml(options), "utf-8");
5240
5302
  return filePath;
5241
5303
  }
5242
5304
 
@@ -5330,7 +5392,7 @@ var FlyAdapter = class {
5330
5392
  appName: config.appName,
5331
5393
  region: config.region
5332
5394
  };
5333
- const deployDirectory = (0, import_node_path16.join)(config.projectRoot, ".kora", "deploy");
5395
+ const deployDirectory = (0, import_node_path17.join)(config.projectRoot, ".kora", "deploy");
5334
5396
  await writeFlyTomlArtifact(deployDirectory, {
5335
5397
  appName: config.appName,
5336
5398
  region: config.region ?? "iad"
@@ -5341,12 +5403,12 @@ var FlyAdapter = class {
5341
5403
  });
5342
5404
  const client = config.deployTarget === "sync-server" ? null : await buildClient({
5343
5405
  projectRoot: config.projectRoot,
5344
- outDir: (0, import_node_path16.join)(deployDirectory, "dist"),
5406
+ outDir: (0, import_node_path17.join)(deployDirectory, "dist"),
5345
5407
  mode: "production"
5346
5408
  });
5347
5409
  return {
5348
5410
  clientDirectory: client?.outDir ?? null,
5349
- serverBundlePath: (0, import_node_path16.join)(deployDirectory, "server-bundled.js"),
5411
+ serverBundlePath: (0, import_node_path17.join)(deployDirectory, "server-bundled.js"),
5350
5412
  deployDirectory
5351
5413
  };
5352
5414
  }
@@ -5568,11 +5630,11 @@ ${stdout}`.toLowerCase();
5568
5630
 
5569
5631
  // src/commands/deploy/adapters/railway-adapter.ts
5570
5632
  var import_node_child_process9 = require("child_process");
5571
- var import_node_path18 = require("path");
5633
+ var import_node_path19 = require("path");
5572
5634
 
5573
5635
  // src/commands/deploy/artifacts/railway-json-generator.ts
5574
- var import_promises12 = require("fs/promises");
5575
- var import_node_path17 = require("path");
5636
+ var import_promises13 = require("fs/promises");
5637
+ var import_node_path18 = require("path");
5576
5638
  var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
5577
5639
  function generateRailwayJson(options) {
5578
5640
  const file = {
@@ -5597,9 +5659,9 @@ function generateRailwayJson(options) {
5597
5659
  `;
5598
5660
  }
5599
5661
  async function writeRailwayJsonArtifact(deployDirectory, options) {
5600
- await (0, import_promises12.mkdir)(deployDirectory, { recursive: true });
5601
- const filePath = (0, import_node_path17.join)(deployDirectory, "railway.json");
5602
- await (0, import_promises12.writeFile)(filePath, generateRailwayJson(options), "utf-8");
5662
+ await (0, import_promises13.mkdir)(deployDirectory, { recursive: true });
5663
+ const filePath = (0, import_node_path18.join)(deployDirectory, "railway.json");
5664
+ await (0, import_promises13.writeFile)(filePath, generateRailwayJson(options), "utf-8");
5603
5665
  return filePath;
5604
5666
  }
5605
5667
 
@@ -5688,7 +5750,7 @@ var RailwayAdapter = class {
5688
5750
  appName: config.appName,
5689
5751
  region: config.region
5690
5752
  };
5691
- const deployDirectory = (0, import_node_path18.join)(config.projectRoot, ".kora", "deploy");
5753
+ const deployDirectory = (0, import_node_path19.join)(config.projectRoot, ".kora", "deploy");
5692
5754
  await writeRailwayJsonArtifact(deployDirectory, {
5693
5755
  appName: config.appName,
5694
5756
  environment: config.environment
@@ -5699,12 +5761,12 @@ var RailwayAdapter = class {
5699
5761
  });
5700
5762
  const client = config.deployTarget === "sync-server" ? null : await buildClient({
5701
5763
  projectRoot: config.projectRoot,
5702
- outDir: (0, import_node_path18.join)(deployDirectory, "dist"),
5764
+ outDir: (0, import_node_path19.join)(deployDirectory, "dist"),
5703
5765
  mode: "production"
5704
5766
  });
5705
5767
  return {
5706
5768
  clientDirectory: client?.outDir ?? null,
5707
- serverBundlePath: (0, import_node_path18.join)(deployDirectory, "server-bundled.js"),
5769
+ serverBundlePath: (0, import_node_path19.join)(deployDirectory, "server-bundled.js"),
5708
5770
  deployDirectory
5709
5771
  };
5710
5772
  }
@@ -5989,8 +6051,8 @@ function createDeployAdapter(platform) {
5989
6051
  }
5990
6052
 
5991
6053
  // src/commands/deploy/artifacts/dockerfile-generator.ts
5992
- var import_promises13 = require("fs/promises");
5993
- var import_node_path19 = require("path");
6054
+ var import_promises14 = require("fs/promises");
6055
+ var import_node_path20 = require("path");
5994
6056
  var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
5995
6057
  function generateDockerfile(options = {}) {
5996
6058
  const nodeVersion = options.nodeVersion ?? "20-alpine";
@@ -6056,37 +6118,37 @@ function generateDockerIgnore() {
6056
6118
  ].join("\n");
6057
6119
  }
6058
6120
  async function writeDockerfileArtifact(deployDirectory, options = {}) {
6059
- await (0, import_promises13.mkdir)(deployDirectory, { recursive: true });
6060
- const dockerfilePath = (0, import_node_path19.join)(deployDirectory, "Dockerfile");
6061
- await (0, import_promises13.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
6121
+ await (0, import_promises14.mkdir)(deployDirectory, { recursive: true });
6122
+ const dockerfilePath = (0, import_node_path20.join)(deployDirectory, "Dockerfile");
6123
+ await (0, import_promises14.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
6062
6124
  if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
6063
- const pkgJsonPath = (0, import_node_path19.join)(deployDirectory, "package.json");
6064
- await (0, import_promises13.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
6125
+ const pkgJsonPath = (0, import_node_path20.join)(deployDirectory, "package.json");
6126
+ await (0, import_promises14.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
6065
6127
  }
6066
6128
  return dockerfilePath;
6067
6129
  }
6068
6130
  async function writeDockerIgnoreArtifact(deployDirectory) {
6069
- await (0, import_promises13.mkdir)(deployDirectory, { recursive: true });
6070
- const dockerIgnorePath = (0, import_node_path19.join)(deployDirectory, ".dockerignore");
6071
- await (0, import_promises13.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
6131
+ await (0, import_promises14.mkdir)(deployDirectory, { recursive: true });
6132
+ const dockerIgnorePath = (0, import_node_path20.join)(deployDirectory, ".dockerignore");
6133
+ await (0, import_promises14.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
6072
6134
  return dockerIgnorePath;
6073
6135
  }
6074
6136
 
6075
6137
  // src/commands/deploy/state/deploy-state.ts
6076
- var import_promises14 = require("fs/promises");
6077
- var import_node_path20 = require("path");
6078
- var KORA_DEPLOY_DIRECTORY = (0, import_node_path20.join)(".kora", "deploy");
6138
+ var import_promises15 = require("fs/promises");
6139
+ var import_node_path21 = require("path");
6140
+ var KORA_DEPLOY_DIRECTORY = (0, import_node_path21.join)(".kora", "deploy");
6079
6141
  var DEPLOY_STATE_FILENAME = "deploy.json";
6080
6142
  function resolveDeployDirectory(projectRoot) {
6081
- return (0, import_node_path20.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
6143
+ return (0, import_node_path21.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
6082
6144
  }
6083
6145
  function resolveDeployStatePath(projectRoot) {
6084
- return (0, import_node_path20.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
6146
+ return (0, import_node_path21.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
6085
6147
  }
6086
6148
  async function readDeployState(projectRoot) {
6087
6149
  const statePath = resolveDeployStatePath(projectRoot);
6088
6150
  try {
6089
- const source = await (0, import_promises14.readFile)(statePath, "utf-8");
6151
+ const source = await (0, import_promises15.readFile)(statePath, "utf-8");
6090
6152
  const parsed = JSON.parse(source);
6091
6153
  return parseDeployState(parsed);
6092
6154
  } catch (error) {
@@ -6133,13 +6195,13 @@ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new D
6133
6195
  return nextState;
6134
6196
  }
6135
6197
  async function resetDeployState(projectRoot) {
6136
- await (0, import_promises14.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
6198
+ await (0, import_promises15.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
6137
6199
  }
6138
6200
  async function persistDeployState(projectRoot, state) {
6139
6201
  const deployDir = resolveDeployDirectory(projectRoot);
6140
6202
  const statePath = resolveDeployStatePath(projectRoot);
6141
- await (0, import_promises14.mkdir)(deployDir, { recursive: true });
6142
- await (0, import_promises14.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
6203
+ await (0, import_promises15.mkdir)(deployDir, { recursive: true });
6204
+ await (0, import_promises15.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
6143
6205
  `, "utf-8");
6144
6206
  }
6145
6207
  function parseDeployState(value) {
@@ -6338,6 +6400,9 @@ var deployCommand = (0, import_citty5.defineCommand)({
6338
6400
  storedPlatform: existingState?.platform,
6339
6401
  confirm: confirmMode
6340
6402
  });
6403
+ if (!isImplementedDeployPlatform(platform)) {
6404
+ throw new DeployPlatformUnavailableError(platform, IMPLEMENTED_DEPLOY_PLATFORMS);
6405
+ }
6341
6406
  const appName = resolveAppName(args.app, existingState?.appName, projectRoot, confirmMode);
6342
6407
  const region = resolveRegion(args.region, existingState?.region, confirmMode);
6343
6408
  const deployDirectory = resolveDeployDirectory(projectRoot);
@@ -6448,11 +6513,11 @@ async function resolvePlatform(options) {
6448
6513
  value: "aws-lightsail"
6449
6514
  },
6450
6515
  {
6451
- label: "Render",
6516
+ label: "Render (coming soon)",
6452
6517
  value: "render"
6453
6518
  },
6454
6519
  {
6455
- label: "Docker (self-hosted)",
6520
+ label: "Docker (self-hosted, coming soon)",
6456
6521
  value: "docker"
6457
6522
  },
6458
6523
  {
@@ -6473,7 +6538,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
6473
6538
  "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
6474
6539
  );
6475
6540
  }
6476
- return sanitizeAppName((0, import_node_path21.basename)(projectRoot));
6541
+ return sanitizeAppName((0, import_node_path22.basename)(projectRoot));
6477
6542
  }
6478
6543
  function resolveRegion(argValue, storedValue, confirm) {
6479
6544
  if (typeof argValue === "string" && argValue.length > 0) return argValue;
@@ -6508,7 +6573,7 @@ function configureAdapterContext(adapter, context) {
6508
6573
  }
6509
6574
  }
6510
6575
  function detectDeployTarget(projectRoot) {
6511
- return (0, import_node_fs4.existsSync)((0, import_node_path21.join)(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
6576
+ return (0, import_node_fs4.existsSync)((0, import_node_path22.join)(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
6512
6577
  }
6513
6578
  function hasContextSetter(adapter) {
6514
6579
  return typeof adapter.setContext === "function";
@@ -6516,9 +6581,9 @@ function hasContextSetter(adapter) {
6516
6581
 
6517
6582
  // src/commands/dev/dev-command.ts
6518
6583
  var import_node_fs6 = require("fs");
6519
- var import_promises15 = require("fs/promises");
6520
- var import_node_path23 = require("path");
6584
+ var import_promises16 = require("fs/promises");
6521
6585
  var import_node_path24 = require("path");
6586
+ var import_node_path25 = require("path");
6522
6587
  var import_citty6 = require("citty");
6523
6588
  init_fs_helpers();
6524
6589
 
@@ -6627,7 +6692,7 @@ function delay(ms) {
6627
6692
  // src/commands/dev/schema-watcher.ts
6628
6693
  var import_node_child_process11 = require("child_process");
6629
6694
  var import_node_fs5 = require("fs");
6630
- var import_node_path22 = require("path");
6695
+ var import_node_path23 = require("path");
6631
6696
  init_fs_helpers();
6632
6697
  init_schema_loader();
6633
6698
  var SchemaWatcher = class {
@@ -6661,7 +6726,7 @@ var SchemaWatcher = class {
6661
6726
  this.watcher = null;
6662
6727
  }
6663
6728
  async regenerate() {
6664
- const koraBinJs = (0, import_node_path22.join)(
6729
+ const koraBinJs = (0, import_node_path23.join)(
6665
6730
  this.config.projectRoot,
6666
6731
  "node_modules",
6667
6732
  "@korajs",
@@ -6777,7 +6842,7 @@ var devCommand = (0, import_citty6.defineCommand)({
6777
6842
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
6778
6843
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
6779
6844
  if (!viteEntryPoint) {
6780
- throw new DevServerError("vite", (0, import_node_path23.join)(projectRoot, "node_modules", ".bin", "vite"));
6845
+ throw new DevServerError("vite", (0, import_node_path24.join)(projectRoot, "node_modules", ".bin", "vite"));
6781
6846
  }
6782
6847
  const syncServerFile = await findSyncServerFile(projectRoot);
6783
6848
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -6793,7 +6858,7 @@ var devCommand = (0, import_citty6.defineCommand)({
6793
6858
  }
6794
6859
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
6795
6860
  const hasServerPackage = await fileExists3(
6796
- (0, import_node_path23.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
6861
+ (0, import_node_path24.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
6797
6862
  );
6798
6863
  if (!hasServerPackage) {
6799
6864
  logger.warn(
@@ -6810,7 +6875,7 @@ var devCommand = (0, import_citty6.defineCommand)({
6810
6875
  }
6811
6876
  let configuredSchemaPath = null;
6812
6877
  if (typeof config?.schema === "string") {
6813
- const candidate = (0, import_node_path24.resolve)(projectRoot, config.schema);
6878
+ const candidate = (0, import_node_path25.resolve)(projectRoot, config.schema);
6814
6879
  if (await fileExists3(candidate)) {
6815
6880
  configuredSchemaPath = candidate;
6816
6881
  } else {
@@ -6938,10 +7003,10 @@ var devCommand = (0, import_citty6.defineCommand)({
6938
7003
  }
6939
7004
  });
6940
7005
  async function findSyncServerFile(projectRoot) {
6941
- const candidates = [(0, import_node_path23.join)(projectRoot, "server.ts"), (0, import_node_path23.join)(projectRoot, "server.js")];
7006
+ const candidates = [(0, import_node_path24.join)(projectRoot, "server.ts"), (0, import_node_path24.join)(projectRoot, "server.js")];
6942
7007
  for (const candidate of candidates) {
6943
7008
  try {
6944
- await (0, import_promises15.access)(candidate);
7009
+ await (0, import_promises16.access)(candidate);
6945
7010
  return candidate;
6946
7011
  } catch {
6947
7012
  }
@@ -6950,7 +7015,7 @@ async function findSyncServerFile(projectRoot) {
6950
7015
  }
6951
7016
  async function fileExists3(path) {
6952
7017
  try {
6953
- await (0, import_promises15.access)(path);
7018
+ await (0, import_promises16.access)(path);
6954
7019
  return true;
6955
7020
  } catch {
6956
7021
  return false;
@@ -6960,7 +7025,7 @@ function loadProjectEnv(projectRoot, mode = "development") {
6960
7025
  const files = [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`];
6961
7026
  const existingKeys = new Set(Object.keys(process.env));
6962
7027
  for (const file of files) {
6963
- const path = (0, import_node_path23.join)(projectRoot, file);
7028
+ const path = (0, import_node_path24.join)(projectRoot, file);
6964
7029
  if (!(0, import_node_fs6.existsSync)(path)) continue;
6965
7030
  const parsed = parseEnvFile((0, import_node_fs6.readFileSync)(path, "utf8"));
6966
7031
  for (const [key, value] of Object.entries(parsed)) {
@@ -6999,7 +7064,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
6999
7064
  const store = sync.store;
7000
7065
  if (store === void 0) return { type: "memory" };
7001
7066
  if (store === "memory") return { type: "memory" };
7002
- if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path23.join)(projectRoot, "kora-sync.db") };
7067
+ if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path24.join)(projectRoot, "kora-sync.db") };
7003
7068
  if (store === "postgres") {
7004
7069
  const connectionString = process.env.DATABASE_URL;
7005
7070
  if (!connectionString) return null;
@@ -7008,7 +7073,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
7008
7073
  if (typeof store === "object" && store !== null) {
7009
7074
  if (store.type === "memory") return { type: "memory" };
7010
7075
  if (store.type === "sqlite") {
7011
- const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path24.resolve)(projectRoot, store.filename) : (0, import_node_path23.join)(projectRoot, "kora-sync.db");
7076
+ const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path25.resolve)(projectRoot, store.filename) : (0, import_node_path24.join)(projectRoot, "kora-sync.db");
7012
7077
  return { type: "sqlite", filename };
7013
7078
  }
7014
7079
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -7076,8 +7141,8 @@ var import_citty7 = require("citty");
7076
7141
  init_fs_helpers();
7077
7142
 
7078
7143
  // src/commands/doctor/doctor-checks.ts
7079
- var import_promises16 = require("fs/promises");
7080
- var import_node_path25 = require("path");
7144
+ var import_promises17 = require("fs/promises");
7145
+ var import_node_path26 = require("path");
7081
7146
  init_fs_helpers();
7082
7147
  init_schema_loader();
7083
7148
  var STATUS_ENDPOINT = "/__kora/status";
@@ -7157,9 +7222,9 @@ async function runDoctorChecks(options) {
7157
7222
  }
7158
7223
  async function resolveSchemaPath(projectRoot, configSchema) {
7159
7224
  if (typeof configSchema === "string") {
7160
- const resolved = (0, import_node_path25.resolve)(projectRoot, configSchema);
7225
+ const resolved = (0, import_node_path26.resolve)(projectRoot, configSchema);
7161
7226
  try {
7162
- await (0, import_promises16.access)(resolved);
7227
+ await (0, import_promises17.access)(resolved);
7163
7228
  return resolved;
7164
7229
  } catch {
7165
7230
  }
@@ -7168,12 +7233,12 @@ async function resolveSchemaPath(projectRoot, configSchema) {
7168
7233
  }
7169
7234
  async function checkWorkerFile(projectRoot) {
7170
7235
  const candidates = [
7171
- (0, import_node_path25.join)(projectRoot, "src", "kora-worker.ts"),
7172
- (0, import_node_path25.join)(projectRoot, "src", "kora-worker.js")
7236
+ (0, import_node_path26.join)(projectRoot, "src", "kora-worker.ts"),
7237
+ (0, import_node_path26.join)(projectRoot, "src", "kora-worker.js")
7173
7238
  ];
7174
7239
  for (const path of candidates) {
7175
7240
  try {
7176
- await (0, import_promises16.access)(path);
7241
+ await (0, import_promises17.access)(path);
7177
7242
  return {
7178
7243
  id: "worker",
7179
7244
  title: "SQLite WASM worker",
@@ -7193,7 +7258,7 @@ async function checkWorkerFile(projectRoot) {
7193
7258
  }
7194
7259
  async function checkDependencies(projectRoot) {
7195
7260
  try {
7196
- const raw = await (0, import_promises16.readFile)((0, import_node_path25.join)(projectRoot, "package.json"), "utf-8");
7261
+ const raw = await (0, import_promises17.readFile)((0, import_node_path26.join)(projectRoot, "package.json"), "utf-8");
7197
7262
  const pkg = JSON.parse(raw);
7198
7263
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
7199
7264
  const hasKora = Boolean(deps.korajs || deps.kora);
@@ -7242,9 +7307,9 @@ async function resolveSyncHttpUrl(projectRoot, config) {
7242
7307
  }
7243
7308
  async function readEnvSyncUrl(projectRoot) {
7244
7309
  for (const file of [".env", ".env.local"]) {
7245
- const path = (0, import_node_path25.join)(projectRoot, file);
7310
+ const path = (0, import_node_path26.join)(projectRoot, file);
7246
7311
  try {
7247
- const content = await (0, import_promises16.readFile)(path, "utf-8");
7312
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
7248
7313
  for (const line of content.split("\n")) {
7249
7314
  const trimmed = line.trim();
7250
7315
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -7390,8 +7455,8 @@ var doctorCommand = (0, import_citty7.defineCommand)({
7390
7455
  });
7391
7456
 
7392
7457
  // src/commands/generate/generate-command.ts
7393
- var import_promises17 = require("fs/promises");
7394
- var import_node_path26 = require("path");
7458
+ var import_promises18 = require("fs/promises");
7459
+ var import_node_path27 = require("path");
7395
7460
  var import_citty8 = require("citty");
7396
7461
  init_fs_helpers();
7397
7462
  init_schema_loader();
@@ -7577,7 +7642,7 @@ var generateCommand = (0, import_citty8.defineCommand)({
7577
7642
  }
7578
7643
  let schemaPath;
7579
7644
  if (args.schema && typeof args.schema === "string") {
7580
- schemaPath = (0, import_node_path26.resolve)(args.schema);
7645
+ schemaPath = (0, import_node_path27.resolve)(args.schema);
7581
7646
  } else {
7582
7647
  const found = await findSchemaFile(projectRoot);
7583
7648
  if (!found) {
@@ -7600,9 +7665,9 @@ var generateCommand = (0, import_citty8.defineCommand)({
7600
7665
  }
7601
7666
  const output = generateTypes(schema);
7602
7667
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
7603
- const outputPath = (0, import_node_path26.resolve)(projectRoot, outputFile);
7604
- await (0, import_promises17.mkdir)((0, import_node_path26.dirname)(outputPath), { recursive: true });
7605
- await (0, import_promises17.writeFile)(outputPath, output, "utf-8");
7668
+ const outputPath = (0, import_node_path27.resolve)(projectRoot, outputFile);
7669
+ await (0, import_promises18.mkdir)((0, import_node_path27.dirname)(outputPath), { recursive: true });
7670
+ await (0, import_promises18.writeFile)(outputPath, output, "utf-8");
7606
7671
  logger.success(`Generated types at ${outputPath}`);
7607
7672
  }
7608
7673
  }),
@@ -7635,7 +7700,7 @@ var generateCommand = (0, import_citty8.defineCommand)({
7635
7700
  }
7636
7701
  let schemaPath;
7637
7702
  if (args.schema && typeof args.schema === "string") {
7638
- schemaPath = (0, import_node_path26.resolve)(projectRoot, args.schema);
7703
+ schemaPath = (0, import_node_path27.resolve)(projectRoot, args.schema);
7639
7704
  } else {
7640
7705
  const found = await findSchemaFile(projectRoot);
7641
7706
  if (!found) {
@@ -7649,13 +7714,13 @@ var generateCommand = (0, import_citty8.defineCommand)({
7649
7714
  schemaPath = found;
7650
7715
  }
7651
7716
  const schema = await loadSchemaDefinition(schemaPath, projectRoot);
7652
- const outputDir = typeof args.output === "string" ? (0, import_node_path26.resolve)(projectRoot, args.output) : (0, import_node_path26.resolve)(projectRoot, "kora/generated/hooks");
7717
+ const outputDir = typeof args.output === "string" ? (0, import_node_path27.resolve)(projectRoot, args.output) : (0, import_node_path27.resolve)(projectRoot, "kora/generated/hooks");
7653
7718
  const files = generateCollectionHooks(schema);
7654
7719
  for (const [name, content] of files) {
7655
7720
  const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
7656
- const filePath = (0, import_node_path26.resolve)(outputDir, name);
7657
- await (0, import_promises17.mkdir)((0, import_node_path26.dirname)(filePath), { recursive: true });
7658
- await (0, import_promises17.writeFile)(filePath, adjusted, "utf-8");
7721
+ const filePath = (0, import_node_path27.resolve)(outputDir, name);
7722
+ await (0, import_promises18.mkdir)((0, import_node_path27.dirname)(filePath), { recursive: true });
7723
+ await (0, import_promises18.writeFile)(filePath, adjusted, "utf-8");
7659
7724
  }
7660
7725
  logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
7661
7726
  logger.step("Run `kora generate types` first if ../types is missing.");
@@ -7784,8 +7849,8 @@ var logsCommand = (0, import_citty9.defineCommand)({
7784
7849
  });
7785
7850
 
7786
7851
  // src/commands/migrate/migrate-command.ts
7787
- var import_promises18 = require("fs/promises");
7788
- var import_node_path27 = require("path");
7852
+ var import_promises19 = require("fs/promises");
7853
+ var import_node_path28 = require("path");
7789
7854
  var import_citty10 = require("citty");
7790
7855
  init_fs_helpers();
7791
7856
 
@@ -7879,8 +7944,8 @@ function isBreakingChange(change) {
7879
7944
  if (change.type === "field-changed") {
7880
7945
  if (change.before.kind !== change.after.kind) return true;
7881
7946
  if (change.before.itemKind !== change.after.itemKind) return true;
7882
- if (serializeEnum(change.before.enumValues) !== serializeEnum(change.after.enumValues))
7883
- return true;
7947
+ const kept = new Set(change.after.enumValues ?? []);
7948
+ if ((change.before.enumValues ?? []).some((value) => !kept.has(value))) return true;
7884
7949
  if (change.before.required !== change.after.required && change.after.required) return true;
7885
7950
  return false;
7886
7951
  }
@@ -7909,22 +7974,473 @@ function compareChanges(left, right) {
7909
7974
  return 0;
7910
7975
  }
7911
7976
 
7977
+ // src/commands/migrate/table-evolution-directive.ts
7978
+ var import_internal = require("@korajs/core/internal");
7979
+ var EVOLVE_TABLE_DIRECTIVE = "--kora:evolve-table";
7980
+ function evolveFieldSpec(descriptor) {
7981
+ return {
7982
+ kind: descriptor.kind,
7983
+ ...descriptor.itemKind ? { itemKind: descriptor.itemKind } : {},
7984
+ ...descriptor.enumValues ? { enumValues: [...descriptor.enumValues] } : {},
7985
+ ...descriptor.defaultValue !== void 0 ? { default: descriptor.defaultValue } : {},
7986
+ ...descriptor.auto ? { auto: true } : {}
7987
+ };
7988
+ }
7989
+ function formatEvolveTableDirective(target) {
7990
+ return `${EVOLVE_TABLE_DIRECTIVE} ${JSON.stringify(target)}`;
7991
+ }
7992
+ function parseEvolveTableDirective(statement) {
7993
+ const trimmed = statement.trimStart();
7994
+ if (!trimmed.startsWith(EVOLVE_TABLE_DIRECTIVE)) return null;
7995
+ const payload = trimmed.slice(EVOLVE_TABLE_DIRECTIVE.length).trim();
7996
+ let parsed;
7997
+ try {
7998
+ parsed = JSON.parse(payload);
7999
+ } catch {
8000
+ parsed = null;
8001
+ }
8002
+ const target = parsed;
8003
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8004
+ const isStrings = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
8005
+ const isSpec = (value) => isRecord(value) && typeof value.kind === "string";
8006
+ if (!isRecord(target) || typeof target.table !== "string" || !isRecord(target.add) || !Object.values(target.add).every(isSpec) || !isStrings(target.drop) || !isRecord(target.change) || !Object.values(target.change).every(
8007
+ (change) => isRecord(change) && isSpec(change.from) && isSpec(change.to)
8008
+ ) || !isStrings(target.addIndexes) || !isStrings(target.removeIndexes)) {
8009
+ throw new Error(
8010
+ `Malformed migration directive "${statement}": expected ${EVOLVE_TABLE_DIRECTIVE} {"table", "add", "drop", "change", "addIndexes", "removeIndexes"}. Regenerate the migration with \`kora migrate\`.`
8011
+ );
8012
+ }
8013
+ return {
8014
+ table: target.table,
8015
+ add: target.add,
8016
+ drop: [...target.drop],
8017
+ change: target.change,
8018
+ addIndexes: [...target.addIndexes],
8019
+ removeIndexes: [...target.removeIndexes]
8020
+ };
8021
+ }
8022
+ async function expandEvolveTableForSqlite(target, query, now) {
8023
+ const catalog = await (0, import_internal.readSqliteTableCatalog)(query, target.table);
8024
+ if (!catalog) return [];
8025
+ const table = quoteIdentifier(target.table);
8026
+ const existing = new Map(
8027
+ catalog.columns.map((column) => [column.name, column.type.toUpperCase()])
8028
+ );
8029
+ const plan = planColumns(target, existing, sqliteType);
8030
+ const statements = [];
8031
+ const presentIndexes = await indexColumnsByName(query, target.table);
8032
+ const removedIndexNames = new Set(
8033
+ target.removeIndexes.flatMap((field) => koraIndexNames(target.table, field)).filter((name) => presentIndexes.has(name))
8034
+ );
8035
+ for (const name of removedIndexNames) {
8036
+ statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(name)}`);
8037
+ }
8038
+ if (plan.drop.length > 0 || plan.change.length > 0) {
8039
+ statements.push(
8040
+ ...await sqliteRebuildStatements(catalog, plan, query, removedIndexNames, now)
8041
+ );
8042
+ } else {
8043
+ for (const [field, spec] of plan.add) {
8044
+ statements.push(
8045
+ `ALTER TABLE ${table} ADD COLUMN ${quoteIdentifier(field)} ${sqliteType(spec)}${defaultClause(spec)}`
8046
+ );
8047
+ if (spec.auto && spec.kind === "timestamp" && spec.default === void 0) {
8048
+ statements.push(`UPDATE ${table} SET ${quoteIdentifier(field)} = ${now}`);
8049
+ }
8050
+ }
8051
+ }
8052
+ const indexed = await singleColumnIndexes(query, target.table, removedIndexNames);
8053
+ for (const field of target.addIndexes) {
8054
+ if (indexed.has(field)) continue;
8055
+ statements.push(
8056
+ `CREATE INDEX IF NOT EXISTS ${koraIndexNames(target.table, field)[0]} ON ${table} (${quoteIdentifier(field)})`
8057
+ );
8058
+ }
8059
+ return statements;
8060
+ }
8061
+ async function expandEvolveTableForPostgres(target, query, now) {
8062
+ const columns = await query(
8063
+ `SELECT column_name::text AS name, upper(data_type::text) AS type FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${sqlText(target.table)}`
8064
+ );
8065
+ if (columns.length === 0) return [];
8066
+ const table = quoteIdentifier(target.table);
8067
+ const existing = new Map(columns.map((row) => [String(row.name), String(row.type)]));
8068
+ const plan = planColumns(target, existing, postgresType);
8069
+ const statements = [];
8070
+ const indexNames = new Set(
8071
+ (await query(
8072
+ `SELECT indexname::text AS name FROM pg_indexes WHERE schemaname = current_schema() AND tablename = ${sqlText(target.table)}`
8073
+ )).map((row) => String(row.name))
8074
+ );
8075
+ for (const field of target.removeIndexes) {
8076
+ const [serverName, clientName] = koraIndexNames(target.table, field);
8077
+ if (indexNames.has(serverName.toLowerCase())) {
8078
+ statements.push(`DROP INDEX IF EXISTS ${serverName}`);
8079
+ }
8080
+ if (indexNames.has(clientName)) {
8081
+ statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(clientName)}`);
8082
+ }
8083
+ }
8084
+ for (const field of plan.drop) {
8085
+ statements.push(`ALTER TABLE ${table} DROP COLUMN IF EXISTS ${quoteIdentifier(field)}`);
8086
+ }
8087
+ for (const [field, change] of plan.change) {
8088
+ const column = quoteIdentifier(field);
8089
+ const type = postgresType(change.to);
8090
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${column} DROP DEFAULT`);
8091
+ statements.push(
8092
+ `ALTER TABLE ${table} ALTER COLUMN ${column} TYPE ${type} USING CAST((${postgresProjection(column, change.from, change.to, now)}) AS ${type})`
8093
+ );
8094
+ if (change.to.default !== void 0) {
8095
+ statements.push(
8096
+ `ALTER TABLE ${table} ALTER COLUMN ${column} SET DEFAULT ${(0, import_internal.sqlDefaultLiteral)(change.to.default)}`
8097
+ );
8098
+ }
8099
+ }
8100
+ for (const [field, spec] of plan.add) {
8101
+ statements.push(
8102
+ `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${quoteIdentifier(field)} ${postgresType(spec)}${defaultClause(spec)}`
8103
+ );
8104
+ if (spec.auto && spec.kind === "timestamp" && spec.default === void 0) {
8105
+ statements.push(`UPDATE ${table} SET ${quoteIdentifier(field)} = ${now}`);
8106
+ }
8107
+ }
8108
+ for (const field of target.addIndexes) {
8109
+ const name = koraIndexNames(target.table, field)[0];
8110
+ if (indexNames.has(name.toLowerCase())) continue;
8111
+ statements.push(`CREATE INDEX IF NOT EXISTS ${name} ON ${table} (${quoteIdentifier(field)})`);
8112
+ }
8113
+ return statements;
8114
+ }
8115
+ function planColumns(target, existing, typeOf) {
8116
+ const plan = { add: [], drop: [], change: [] };
8117
+ for (const [field, spec] of Object.entries(target.add)) {
8118
+ if (!existing.has(field)) plan.add.push([field, spec]);
8119
+ }
8120
+ for (const [field, change] of Object.entries(target.change)) {
8121
+ const current = existing.get(field);
8122
+ if (current === void 0) {
8123
+ plan.add.push([field, change.to]);
8124
+ continue;
8125
+ }
8126
+ const from = typeOf(change.from);
8127
+ const to = typeOf(change.to);
8128
+ if (from !== to && current === to) continue;
8129
+ plan.change.push([field, change]);
8130
+ }
8131
+ for (const field of target.drop) {
8132
+ if (existing.has(field) && !(field in target.add) && !(field in target.change)) {
8133
+ if ((0, import_internal.isKoraInternalColumn)(field)) {
8134
+ throw new Error(
8135
+ `Migration directive refuses to drop Kora's own column "${field}" of "${target.table}".`
8136
+ );
8137
+ }
8138
+ plan.drop.push(field);
8139
+ }
8140
+ }
8141
+ return plan;
8142
+ }
8143
+ async function sqliteRebuildStatements(catalog, plan, query, removedIndexNames, now) {
8144
+ const table = catalog.table;
8145
+ const temp = `_kora_evolve_${table}`;
8146
+ const dropped = new Set(plan.drop);
8147
+ const changed = new Map(plan.change);
8148
+ const ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid).filter((column) => !dropped.has(column.name));
8149
+ const pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk);
8150
+ const fkGroups = /* @__PURE__ */ new Map();
8151
+ for (const fk of catalog.foreignKeys) {
8152
+ const group = fkGroups.get(fk.id) ?? [];
8153
+ group.push(fk);
8154
+ fkGroups.set(fk.id, group);
8155
+ }
8156
+ const columnReferences = /* @__PURE__ */ new Map();
8157
+ const tableConstraints = [];
8158
+ for (const group of fkGroups.values()) {
8159
+ group.sort((a, b) => a.seq - b.seq);
8160
+ const first = group[0];
8161
+ if (!first || group.some((fk) => dropped.has(fk.from))) continue;
8162
+ const actions = foreignKeyActions(first);
8163
+ const targetRef = group.every((fk) => fk.to !== null && fk.to !== "") ? `${quoteIdentifier(first.table)}(${group.map((fk) => quoteIdentifier(fk.to ?? "")).join(", ")})` : quoteIdentifier(first.table);
8164
+ if (group.length === 1) {
8165
+ columnReferences.set(first.from, `REFERENCES ${targetRef}${actions}`);
8166
+ } else {
8167
+ tableConstraints.push(
8168
+ `FOREIGN KEY (${group.map((fk) => quoteIdentifier(fk.from)).join(", ")}) REFERENCES ${targetRef}${actions}`
8169
+ );
8170
+ }
8171
+ }
8172
+ if (pkColumns.length > 1) {
8173
+ tableConstraints.unshift(
8174
+ `PRIMARY KEY (${pkColumns.map((c) => quoteIdentifier(c.name)).join(", ")})`
8175
+ );
8176
+ }
8177
+ for (const unique of catalog.uniqueConstraints) {
8178
+ if (unique.some((column) => dropped.has(column))) continue;
8179
+ tableConstraints.push(`UNIQUE (${unique.map(quoteIdentifier).join(", ")})`);
8180
+ }
8181
+ for (const check of (0, import_internal.parseSqliteCheckConstraints)(catalog.sql)) {
8182
+ if ((0, import_internal.sqliteCheckReferencesColumn)(check, dropped)) continue;
8183
+ tableConstraints.push((0, import_internal.sqliteCheckConstraintDefinition)(check));
8184
+ }
8185
+ const definitions = [];
8186
+ const targetColumns = [];
8187
+ const selections = [];
8188
+ for (const column of ordered) {
8189
+ const change = changed.get(column.name);
8190
+ const parts = [quoteIdentifier(column.name)];
8191
+ if (change) {
8192
+ parts.push(sqliteType(change.to));
8193
+ } else if (column.type) {
8194
+ parts.push(column.type);
8195
+ }
8196
+ if (pkColumns.length === 1 && column.pk === 1) parts.push("PRIMARY KEY");
8197
+ if (column.notnull === 1) parts.push("NOT NULL");
8198
+ if (change) {
8199
+ if (change.to.default !== void 0) {
8200
+ parts.push(`DEFAULT ${(0, import_internal.sqlDefaultLiteral)(change.to.default)}`);
8201
+ }
8202
+ } else if (column.dflt_value !== null) {
8203
+ parts.push(`DEFAULT (${column.dflt_value})`);
8204
+ }
8205
+ const reference = columnReferences.get(column.name);
8206
+ if (reference) parts.push(reference);
8207
+ definitions.push(parts.join(" "));
8208
+ targetColumns.push(quoteIdentifier(column.name));
8209
+ selections.push(
8210
+ change ? sqliteProjection(quoteIdentifier(column.name), change.from, change.to, now) : quoteIdentifier(column.name)
8211
+ );
8212
+ }
8213
+ for (const [field, spec] of plan.add) {
8214
+ definitions.push(`${quoteIdentifier(field)} ${sqliteType(spec)}${defaultClause(spec)}`);
8215
+ targetColumns.push(quoteIdentifier(field));
8216
+ selections.push(fallbackLiteral(spec, now));
8217
+ }
8218
+ const indexColumns = await indexColumnsByName(query, table);
8219
+ const dependents = [];
8220
+ for (const row of await query(
8221
+ `SELECT type, name, sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`
8222
+ )) {
8223
+ const name = String(row.name);
8224
+ if (row.type === "index") {
8225
+ if (removedIndexNames.has(name)) continue;
8226
+ if ((indexColumns.get(name) ?? []).some((column) => dropped.has(column))) continue;
8227
+ }
8228
+ dependents.push(String(row.sql));
8229
+ }
8230
+ return [
8231
+ `DROP TABLE IF EXISTS ${quoteIdentifier(temp)}`,
8232
+ `CREATE TABLE ${quoteIdentifier(temp)} (
8233
+ ${[...definitions, ...tableConstraints].join(",\n ")}
8234
+ )`,
8235
+ `INSERT INTO ${quoteIdentifier(temp)} (${targetColumns.join(", ")}) SELECT ${selections.join(", ")} FROM ${quoteIdentifier(table)}`,
8236
+ `DROP TABLE ${quoteIdentifier(table)}`,
8237
+ `ALTER TABLE ${quoteIdentifier(temp)} RENAME TO ${quoteIdentifier(table)}`,
8238
+ ...dependents
8239
+ ];
8240
+ }
8241
+ async function indexColumnsByName(query, table) {
8242
+ const out = /* @__PURE__ */ new Map();
8243
+ for (const index of await query(`SELECT name FROM pragma_index_list(${sqlText(table)})`)) {
8244
+ const name = String(index.name);
8245
+ const info = await query(`SELECT name FROM pragma_index_info(${sqlText(name)}) ORDER BY seqno`);
8246
+ out.set(
8247
+ name,
8248
+ info.filter((row) => typeof row.name === "string").map((row) => String(row.name))
8249
+ );
8250
+ }
8251
+ return out;
8252
+ }
8253
+ async function singleColumnIndexes(query, table, ignore) {
8254
+ const out = /* @__PURE__ */ new Set();
8255
+ for (const [name, columns] of await indexColumnsByName(query, table)) {
8256
+ if (ignore.has(name)) continue;
8257
+ const only = columns[0];
8258
+ if (columns.length === 1 && only !== void 0) out.add(only);
8259
+ }
8260
+ return out;
8261
+ }
8262
+ function koraIndexNames(table, field) {
8263
+ quoteIdentifier(table);
8264
+ quoteIdentifier(field);
8265
+ return [`idx_${table}_${field}`, (0, import_internal.collectionIndexName)(table, field)];
8266
+ }
8267
+ function defaultClause(spec) {
8268
+ return spec.default !== void 0 ? ` DEFAULT ${(0, import_internal.sqlDefaultLiteral)(spec.default)}` : "";
8269
+ }
8270
+ function sqliteType(spec) {
8271
+ switch (spec.kind) {
8272
+ case "number":
8273
+ return "REAL";
8274
+ case "boolean":
8275
+ case "timestamp":
8276
+ return "INTEGER";
8277
+ case "richtext":
8278
+ return "BLOB";
8279
+ default:
8280
+ return "TEXT";
8281
+ }
8282
+ }
8283
+ function postgresType(spec) {
8284
+ switch (spec.kind) {
8285
+ case "number":
8286
+ return "DOUBLE PRECISION";
8287
+ case "boolean":
8288
+ return "INTEGER";
8289
+ case "timestamp":
8290
+ return "BIGINT";
8291
+ case "array":
8292
+ case "object":
8293
+ case "json":
8294
+ return "JSONB";
8295
+ case "richtext":
8296
+ return "BYTEA";
8297
+ default:
8298
+ return "TEXT";
8299
+ }
8300
+ }
8301
+ function fallbackLiteral(target, now) {
8302
+ if (target.auto && target.kind === "timestamp") return String(now);
8303
+ if (target.default !== void 0) return (0, import_internal.sqlDefaultLiteral)(target.default);
8304
+ return "NULL";
8305
+ }
8306
+ var NUMERIC_SOURCES = /* @__PURE__ */ new Set(["number", "timestamp", "boolean"]);
8307
+ var TEXT_SOURCES = /* @__PURE__ */ new Set(["string", "enum"]);
8308
+ var TRUE_WORDS = "('1','true','t','yes','y','on')";
8309
+ var FALSE_WORDS = "('0','false','f','no','n','off')";
8310
+ function sqliteProjection(column, source, target, now) {
8311
+ if (source.kind === target.kind && source.itemKind === target.itemKind) return column;
8312
+ const fallback = fallbackLiteral(target, now);
8313
+ if (target.kind === "string") return `CAST(${column} AS TEXT)`;
8314
+ if (target.kind === "number" || target.kind === "timestamp") {
8315
+ if (NUMERIC_SOURCES.has(source.kind) || TEXT_SOURCES.has(source.kind)) {
8316
+ const castType = target.kind === "number" ? "REAL" : "INTEGER";
8317
+ return `CASE WHEN ${column} IS NULL THEN NULL ELSE CAST(${column} AS ${castType}) END`;
8318
+ }
8319
+ }
8320
+ if (target.kind === "boolean") {
8321
+ if (NUMERIC_SOURCES.has(source.kind)) {
8322
+ return `CASE WHEN ${column} IS NULL THEN NULL WHEN CAST(${column} AS REAL) = 0 THEN 0 ELSE 1 END`;
8323
+ }
8324
+ if (TEXT_SOURCES.has(source.kind)) {
8325
+ const word = `LOWER(TRIM(CAST(${column} AS TEXT)))`;
8326
+ return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${word} IN ${TRUE_WORDS} THEN 1 WHEN ${word} IN ${FALSE_WORDS} THEN 0 ELSE ${fallback} END`;
8327
+ }
8328
+ }
8329
+ if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
8330
+ if (TEXT_SOURCES.has(source.kind)) {
8331
+ const allowed = target.enumValues.map((value) => (0, import_internal.sqlDefaultLiteral)(value)).join(", ");
8332
+ return `CASE WHEN ${column} IN (${allowed}) THEN ${column} ELSE ${fallback} END`;
8333
+ }
8334
+ }
8335
+ return fallback;
8336
+ }
8337
+ function postgresProjection(column, source, target, now) {
8338
+ if (source.kind === target.kind && source.itemKind === target.itemKind) return column;
8339
+ const fallback = fallbackLiteral(target, now);
8340
+ const text = `(${column})::text`;
8341
+ if (target.kind === "string") return text;
8342
+ if (target.kind === "number" || target.kind === "timestamp") {
8343
+ const toTarget = (value) => target.kind === "number" ? value : `round(${value})::bigint`;
8344
+ if (NUMERIC_SOURCES.has(source.kind)) {
8345
+ return `CASE WHEN ${column} IS NULL THEN NULL ELSE ${toTarget(`(${column})::double precision`)} END`;
8346
+ }
8347
+ if (TEXT_SOURCES.has(source.kind)) {
8348
+ const trimmed = `btrim(${text})`;
8349
+ return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${trimmed} ~ '^[-+]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][-+]?[0-9]+)?$' THEN ${toTarget(`(${trimmed})::double precision`)} ELSE ${fallback} END`;
8350
+ }
8351
+ }
8352
+ if (target.kind === "boolean") {
8353
+ if (NUMERIC_SOURCES.has(source.kind)) {
8354
+ return `CASE WHEN ${column} IS NULL THEN NULL WHEN (${column})::double precision = 0 THEN 0 ELSE 1 END`;
8355
+ }
8356
+ if (TEXT_SOURCES.has(source.kind)) {
8357
+ const word = `lower(btrim(${text}))`;
8358
+ return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${word} IN ${TRUE_WORDS} THEN 1 WHEN ${word} IN ${FALSE_WORDS} THEN 0 ELSE ${fallback} END`;
8359
+ }
8360
+ }
8361
+ if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
8362
+ if (TEXT_SOURCES.has(source.kind)) {
8363
+ const allowed = target.enumValues.map((value) => (0, import_internal.sqlDefaultLiteral)(value)).join(", ");
8364
+ return `CASE WHEN ${text} IN (${allowed}) THEN ${text} ELSE ${fallback} END`;
8365
+ }
8366
+ }
8367
+ return fallback;
8368
+ }
8369
+ function foreignKeyActions(fk) {
8370
+ let out = "";
8371
+ const onDelete = fk.on_delete?.toUpperCase();
8372
+ const onUpdate = fk.on_update?.toUpperCase();
8373
+ if (onDelete && onDelete !== "NO ACTION") out += ` ON DELETE ${onDelete}`;
8374
+ if (onUpdate && onUpdate !== "NO ACTION") out += ` ON UPDATE ${onUpdate}`;
8375
+ return out;
8376
+ }
8377
+ function sqlText(value) {
8378
+ return `'${value.replaceAll("'", "''")}'`;
8379
+ }
8380
+ function quoteIdentifier(identifier) {
8381
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
8382
+ throw new Error(`Invalid SQL identifier: ${identifier}`);
8383
+ }
8384
+ return `"${identifier}"`;
8385
+ }
8386
+
8387
+ // src/commands/migrate/value-domain-directive.ts
8388
+ var import_internal2 = require("@korajs/core/internal");
8389
+ var RELAX_VALUE_DOMAIN_DIRECTIVE = "--kora:relax-value-domain";
8390
+ function formatRelaxValueDomainDirective(target) {
8391
+ return `${RELAX_VALUE_DOMAIN_DIRECTIVE} ${JSON.stringify(target)}`;
8392
+ }
8393
+ function parseRelaxValueDomainDirective(statement) {
8394
+ const trimmed = statement.trimStart();
8395
+ if (!trimmed.startsWith(RELAX_VALUE_DOMAIN_DIRECTIVE)) return null;
8396
+ const payload = trimmed.slice(RELAX_VALUE_DOMAIN_DIRECTIVE.length).trim();
8397
+ let parsed;
8398
+ try {
8399
+ parsed = JSON.parse(payload);
8400
+ } catch {
8401
+ parsed = null;
8402
+ }
8403
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.table !== "string" || !Array.isArray(parsed.fields) || !parsed.fields.every((field) => typeof field === "string") || parsed.enums !== void 0 && (!Array.isArray(parsed.enums) || !parsed.enums.every((field) => typeof field === "string"))) {
8404
+ throw new Error(
8405
+ `Malformed migration directive "${statement}": expected ${RELAX_VALUE_DOMAIN_DIRECTIVE} {"table": "...", "fields": ["..."]}. Regenerate the migration with \`kora migrate\`.`
8406
+ );
8407
+ }
8408
+ const target = parsed;
8409
+ return {
8410
+ table: target.table,
8411
+ fields: [...target.fields],
8412
+ ...target.enums !== void 0 ? { enums: [...target.enums] } : {}
8413
+ };
8414
+ }
8415
+ async function expandRelaxValueDomainForSqlite(target, query) {
8416
+ return (0, import_internal2.planSqliteConstraintRelaxation)(query, [target.table], {
8417
+ [target.table]: target.enums ?? target.fields
8418
+ });
8419
+ }
8420
+ async function expandRelaxValueDomainForPostgres(target, query) {
8421
+ return (0, import_internal2.planPostgresConstraintRelaxation)(
8422
+ query,
8423
+ { [target.table]: target.fields },
8424
+ target.enums !== void 0 ? { [target.table]: target.enums } : {}
8425
+ );
8426
+ }
8427
+
7912
8428
  // src/commands/migrate/migration-generator.ts
7913
8429
  function generateMigration(previous, current, diff) {
7914
8430
  const up = [];
7915
- const down = [];
8431
+ const downGroups = [];
7916
8432
  for (const change of diff.changes) {
7917
8433
  if (change.type === "collection-added") {
7918
8434
  const collectionDef = current.collections[change.collection];
7919
8435
  if (!collectionDef) continue;
7920
8436
  up.push(...(0, import_core2.generateSQL)(change.collection, collectionDef));
7921
- down.push(...dropCollectionStatements(change.collection));
8437
+ downGroups.push(dropCollectionStatements(change.collection));
7922
8438
  }
7923
8439
  if (change.type === "collection-removed") {
7924
8440
  const collectionDef = previous.collections[change.collection];
7925
8441
  up.push(...dropCollectionStatements(change.collection));
7926
8442
  if (collectionDef) {
7927
- down.push(...(0, import_core2.generateSQL)(change.collection, collectionDef));
8443
+ downGroups.push((0, import_core2.generateSQL)(change.collection, collectionDef));
7928
8444
  }
7929
8445
  }
7930
8446
  }
@@ -7937,48 +8453,59 @@ function generateMigration(previous, current, diff) {
7937
8453
  const previousDef = previous.collections[collection];
7938
8454
  const currentDef = current.collections[collection];
7939
8455
  if (!previousDef || !currentDef) continue;
8456
+ const valueDomainOnly = diff.changes.every(
8457
+ (change) => change.collection !== collection || change.type === "field-changed" && isValueDomainChange(change.before, change.after)
8458
+ );
8459
+ if (valueDomainOnly) {
8460
+ const directive = relaxValueDomainDirective(collection, previousDef, currentDef);
8461
+ up.push(directive);
8462
+ downGroups.push([directive]);
8463
+ continue;
8464
+ }
7940
8465
  validateRebuildSafety(collection, previousDef, currentDef);
7941
- up.push(...generateRebuildStatements(collection, previousDef, currentDef));
7942
- down.push(...generateRebuildStatements(collection, currentDef, previousDef));
8466
+ const relax = relaxValueDomainDirective(collection, previousDef, currentDef);
8467
+ up.push(relax, formatEvolveTableDirective(evolveTarget(collection, previousDef, currentDef)));
8468
+ downGroups.push([
8469
+ relax,
8470
+ formatEvolveTableDirective(evolveTarget(collection, currentDef, previousDef))
8471
+ ]);
7943
8472
  }
7944
- down.reverse();
7945
8473
  return {
7946
8474
  up,
7947
- down,
8475
+ down: downGroups.reverse().flat(),
7948
8476
  summary: diff.changes.map(formatChange),
7949
8477
  containsBreakingChanges: diff.hasBreakingChanges
7950
8478
  };
7951
8479
  }
7952
- function generateRebuildStatements(collection, from, to) {
7953
- const table = quoteIdentifier(collection);
7954
- const tempTable = quoteIdentifier(`_kora_mig_${collection}_new`);
7955
- const targetColumns = [
7956
- "id TEXT PRIMARY KEY NOT NULL",
7957
- ...Object.entries(to.fields).map(([field, descriptor]) => columnDefinition(field, descriptor)),
7958
- "_created_at INTEGER NOT NULL",
7959
- "_updated_at INTEGER NOT NULL",
7960
- "_deleted INTEGER NOT NULL DEFAULT 0"
7961
- ];
7962
- const statements = [];
7963
- statements.push(`CREATE TABLE ${tempTable} (
7964
- ${targetColumns.join(",\n ")}
7965
- )`);
7966
- const toFields = Object.keys(to.fields);
7967
- const columns = ["id", ...toFields, "_created_at", "_updated_at", "_deleted"];
7968
- const selectExpressions = columns.map(
7969
- (column) => projectionForColumn(column, from.fields, to.fields[column] ?? null)
7970
- );
7971
- statements.push(
7972
- `INSERT INTO ${tempTable} (${columns.map(quoteIdentifier).join(", ")}) SELECT ${selectExpressions.join(", ")} FROM ${table}`
7973
- );
7974
- statements.push(`DROP TABLE ${table}`);
7975
- statements.push(`ALTER TABLE ${tempTable} RENAME TO ${table}`);
7976
- for (const indexField of to.indexes) {
7977
- statements.push(
7978
- `CREATE INDEX IF NOT EXISTS idx_${collection}_${indexField} ON ${table} (${quoteIdentifier(indexField)})`
7979
- );
8480
+ function isValueDomainChange(before, after) {
8481
+ return before.kind === after.kind && before.itemKind === after.itemKind && before.auto === after.auto;
8482
+ }
8483
+ function evolveTarget(collection, from, to) {
8484
+ const target = {
8485
+ table: collection,
8486
+ add: {},
8487
+ drop: [],
8488
+ change: {},
8489
+ addIndexes: to.indexes.filter((field) => !from.indexes.includes(field)),
8490
+ removeIndexes: from.indexes.filter((field) => !to.indexes.includes(field))
8491
+ };
8492
+ for (const [field, descriptor] of Object.entries(to.fields)) {
8493
+ const before = from.fields[field];
8494
+ if (!before) {
8495
+ target.add[field] = evolveFieldSpec(descriptor);
8496
+ } else if (before.kind !== descriptor.kind || before.itemKind !== descriptor.itemKind) {
8497
+ target.change[field] = { from: evolveFieldSpec(before), to: evolveFieldSpec(descriptor) };
8498
+ }
7980
8499
  }
7981
- return statements;
8500
+ target.drop = Object.keys(from.fields).filter((field) => !(field in to.fields));
8501
+ return target;
8502
+ }
8503
+ function relaxValueDomainDirective(collection, from, to) {
8504
+ const fields = [.../* @__PURE__ */ new Set([...Object.keys(from.fields), ...Object.keys(to.fields)])].sort();
8505
+ const enums = fields.filter(
8506
+ (field) => from.fields[field]?.kind === "enum" || to.fields[field]?.kind === "enum"
8507
+ );
8508
+ return formatRelaxValueDomainDirective({ table: collection, fields, enums });
7982
8509
  }
7983
8510
  function validateRebuildSafety(collection, from, to) {
7984
8511
  for (const [fieldName, descriptor] of Object.entries(to.fields)) {
@@ -8000,69 +8527,6 @@ function validateRebuildSafety(collection, from, to) {
8000
8527
  }
8001
8528
  }
8002
8529
  }
8003
- function projectionForColumn(column, fromFields, targetDescriptor) {
8004
- if (column === "id" || column === "_created_at" || column === "_updated_at" || column === "_deleted") {
8005
- return quoteIdentifier(column);
8006
- }
8007
- const sourceDescriptor = fromFields[column];
8008
- if (sourceDescriptor && targetDescriptor) {
8009
- return projectionForFieldTransform(column, sourceDescriptor, targetDescriptor);
8010
- }
8011
- if (sourceDescriptor) {
8012
- return quoteIdentifier(column);
8013
- }
8014
- if (!targetDescriptor) {
8015
- return "NULL";
8016
- }
8017
- if (targetDescriptor.auto && targetDescriptor.kind === "timestamp") {
8018
- return "CAST(strftime('%s','now') AS INTEGER) * 1000";
8019
- }
8020
- if (targetDescriptor.defaultValue !== void 0) {
8021
- return sqlLiteral(targetDescriptor.defaultValue);
8022
- }
8023
- return "NULL";
8024
- }
8025
- function projectionForFieldTransform(column, source, target) {
8026
- const sourceColumn = quoteIdentifier(column);
8027
- if (source.kind === target.kind && source.itemKind === target.itemKind) {
8028
- if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
8029
- const allowed = target.enumValues.map((value) => sqlLiteral(value)).join(", ");
8030
- const fallback = target.defaultValue !== void 0 ? sqlLiteral(target.defaultValue) : sourceColumn;
8031
- return `CASE WHEN ${sourceColumn} IN (${allowed}) THEN ${sourceColumn} ELSE ${fallback} END`;
8032
- }
8033
- return sourceColumn;
8034
- }
8035
- if (target.kind === "string") {
8036
- return `CAST(${sourceColumn} AS TEXT)`;
8037
- }
8038
- if (target.kind === "number" || target.kind === "timestamp") {
8039
- if (source.kind === "string" || source.kind === "enum" || source.kind === "number" || source.kind === "timestamp" || source.kind === "boolean") {
8040
- const castType = target.kind === "number" ? "REAL" : "INTEGER";
8041
- return `CASE WHEN ${sourceColumn} IS NULL THEN NULL ELSE CAST(${sourceColumn} AS ${castType}) END`;
8042
- }
8043
- }
8044
- if (target.kind === "boolean") {
8045
- if (source.kind === "number" || source.kind === "timestamp" || source.kind === "boolean") {
8046
- return `CASE WHEN ${sourceColumn} IS NULL THEN NULL WHEN CAST(${sourceColumn} AS REAL) = 0 THEN 0 ELSE 1 END`;
8047
- }
8048
- if (source.kind === "string" || source.kind === "enum") {
8049
- return `CASE WHEN ${sourceColumn} IS NULL THEN NULL WHEN LOWER(TRIM(CAST(${sourceColumn} AS TEXT))) IN ('1','true','t','yes','y','on') THEN 1 WHEN LOWER(TRIM(CAST(${sourceColumn} AS TEXT))) IN ('0','false','f','no','n','off') THEN 0 ELSE ${projectionFallback(target)} END`;
8050
- }
8051
- }
8052
- if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
8053
- if (source.kind === "string" || source.kind === "enum") {
8054
- const allowed = target.enumValues.map((value) => sqlLiteral(value)).join(", ");
8055
- return `CASE WHEN ${sourceColumn} IN (${allowed}) THEN ${sourceColumn} ELSE ${projectionFallback(target)} END`;
8056
- }
8057
- }
8058
- if (target.kind === "array" && source.kind === "array" && source.itemKind === target.itemKind) {
8059
- return sourceColumn;
8060
- }
8061
- if (target.auto && target.kind === "timestamp") {
8062
- return "CAST(strftime('%s','now') AS INTEGER) * 1000";
8063
- }
8064
- return projectionFallback(target);
8065
- }
8066
8530
  function canTransformField(source, target) {
8067
8531
  if (source.kind === target.kind && source.itemKind === target.itemKind) {
8068
8532
  return true;
@@ -8087,69 +8551,12 @@ function canTransformField(source, target) {
8087
8551
  }
8088
8552
  return false;
8089
8553
  }
8090
- function projectionFallback(target) {
8091
- if (target.auto && target.kind === "timestamp") {
8092
- return "CAST(strftime('%s','now') AS INTEGER) * 1000";
8093
- }
8094
- if (target.defaultValue !== void 0) {
8095
- return sqlLiteral(target.defaultValue);
8096
- }
8097
- return "NULL";
8098
- }
8099
8554
  function dropCollectionStatements(collection) {
8100
- const table = quoteIdentifier(collection);
8101
- const opsTable = quoteIdentifier(`_kora_ops_${collection}`);
8555
+ const table = quoteIdentifier2(collection);
8556
+ const opsTable = quoteIdentifier2(`_kora_ops_${collection}`);
8102
8557
  return [`DROP TABLE IF EXISTS ${table}`, `DROP TABLE IF EXISTS ${opsTable}`];
8103
8558
  }
8104
- function columnDefinition(fieldName, descriptor) {
8105
- const sqlType = mapFieldType(descriptor);
8106
- const parts = [quoteIdentifier(fieldName), sqlType];
8107
- if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
8108
- parts.push("NOT NULL");
8109
- }
8110
- if (descriptor.defaultValue !== void 0) {
8111
- parts.push(`DEFAULT ${sqlLiteral(descriptor.defaultValue)}`);
8112
- }
8113
- if (descriptor.kind === "enum" && descriptor.enumValues) {
8114
- const values = descriptor.enumValues.map((value) => sqlLiteral(value)).join(", ");
8115
- parts.push(`CHECK (${quoteIdentifier(fieldName)} IN (${values}))`);
8116
- }
8117
- return parts.join(" ");
8118
- }
8119
- function mapFieldType(descriptor) {
8120
- switch (descriptor.kind) {
8121
- case "string":
8122
- return "TEXT";
8123
- case "number":
8124
- return "REAL";
8125
- case "boolean":
8126
- return "INTEGER";
8127
- case "enum":
8128
- return "TEXT";
8129
- case "timestamp":
8130
- return "INTEGER";
8131
- case "array":
8132
- return "TEXT";
8133
- case "object":
8134
- return "TEXT";
8135
- case "json":
8136
- return "TEXT";
8137
- case "blob":
8138
- return "TEXT";
8139
- case "secret":
8140
- return "TEXT";
8141
- case "richtext":
8142
- return "BLOB";
8143
- }
8144
- }
8145
- function sqlLiteral(value) {
8146
- if (value === null) return "NULL";
8147
- if (typeof value === "number") return String(value);
8148
- if (typeof value === "boolean") return value ? "1" : "0";
8149
- if (typeof value === "string") return `'${value.replaceAll("'", "''")}'`;
8150
- return `'${JSON.stringify(value).replaceAll("'", "''")}'`;
8151
- }
8152
- function quoteIdentifier(identifier) {
8559
+ function quoteIdentifier2(identifier) {
8153
8560
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
8154
8561
  throw new Error(`Invalid SQL identifier: ${identifier}`);
8155
8562
  }
@@ -8228,6 +8635,8 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
8228
8635
  const driver = driverOverride ?? await loadSqliteDriver(projectRoot);
8229
8636
  const db = driver.open(path);
8230
8637
  let statementsApplied = 0;
8638
+ const foreignKeysOn = Number(db.query?.("PRAGMA foreign_keys")[0]?.foreign_keys ?? 0) === 1;
8639
+ if (foreignKeysOn) db.exec("PRAGMA foreign_keys = OFF");
8231
8640
  try {
8232
8641
  db.exec("BEGIN");
8233
8642
  db.exec(
@@ -8243,12 +8652,38 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
8243
8652
  skipped: true
8244
8653
  };
8245
8654
  }
8655
+ const now = Date.now();
8656
+ const catalogQuery = (table) => {
8657
+ const query = db.query?.bind(db);
8658
+ if (!query) {
8659
+ throw new Error(
8660
+ `Migration ${migrationId}: the SQLite driver cannot read the catalog needed to migrate "${table}".`
8661
+ );
8662
+ }
8663
+ return async (text) => query(text);
8664
+ };
8246
8665
  for (const statement of statements) {
8247
- db.exec(statement);
8666
+ const relax = parseRelaxValueDomainDirective(statement);
8667
+ const evolve = relax ? null : parseEvolveTableDirective(statement);
8668
+ if (relax) {
8669
+ const expanded = await expandRelaxValueDomainForSqlite(relax, catalogQuery(relax.table));
8670
+ for (const rebuild of expanded) db.exec(rebuild);
8671
+ } else if (evolve) {
8672
+ const expanded = await expandEvolveTableForSqlite(evolve, catalogQuery(evolve.table), now);
8673
+ for (const change of expanded) db.exec(change);
8674
+ } else {
8675
+ db.exec(statement);
8676
+ }
8248
8677
  statementsApplied++;
8249
8678
  }
8679
+ const violations = db.query?.("PRAGMA foreign_key_check") ?? [];
8680
+ if (violations.length > 0) {
8681
+ throw new Error(
8682
+ `Migration ${migrationId} left ${violations.length} foreign key violation(s) (first: ${JSON.stringify(violations[0])}); nothing was applied.`
8683
+ );
8684
+ }
8250
8685
  db.exec(
8251
- `INSERT OR REPLACE INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral2(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()})`
8686
+ `INSERT OR REPLACE INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()})`
8252
8687
  );
8253
8688
  db.exec("COMMIT");
8254
8689
  return {
@@ -8264,6 +8699,12 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
8264
8699
  }
8265
8700
  throw error;
8266
8701
  } finally {
8702
+ if (foreignKeysOn) {
8703
+ try {
8704
+ db.exec("PRAGMA foreign_keys = ON");
8705
+ } catch {
8706
+ }
8707
+ }
8267
8708
  if (typeof db.close === "function") {
8268
8709
  db.close();
8269
8710
  }
@@ -8285,6 +8726,9 @@ async function loadSqliteDriver(projectRoot) {
8285
8726
  const row = db.prepare("SELECT COUNT(*) AS count FROM _kora_migrations WHERE id = ?").get(id);
8286
8727
  return (row?.count ?? 0) > 0;
8287
8728
  },
8729
+ query(sql) {
8730
+ return db.prepare(sql).all();
8731
+ },
8288
8732
  close() {
8289
8733
  db.close();
8290
8734
  }
@@ -8298,7 +8742,11 @@ async function loadSqliteDriver(projectRoot) {
8298
8742
  }
8299
8743
  }
8300
8744
  async function runPostgresMigration(connectionString, statements, migrationId, fromVersion, toVersion, clientFactoryOverride) {
8301
- const sql = typeof clientFactoryOverride === "function" ? clientFactoryOverride(connectionString) : (await loadPostgresModule()).default(connectionString);
8745
+ const sql = typeof clientFactoryOverride === "function" ? clientFactoryOverride(connectionString) : (
8746
+ // One connection: BEGIN, the statements and COMMIT must share it (postgres.js
8747
+ // refuses a raw BEGIN on a pool, UNSAFE_TRANSACTION).
8748
+ (await loadPostgresModule()).default(connectionString, { max: 1 })
8749
+ );
8302
8750
  let statementsApplied = 0;
8303
8751
  try {
8304
8752
  await sql.unsafe("BEGIN");
@@ -8306,7 +8754,7 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
8306
8754
  "CREATE TABLE IF NOT EXISTS _kora_migrations (id TEXT PRIMARY KEY, from_version INTEGER NOT NULL, to_version INTEGER NOT NULL, applied_at BIGINT NOT NULL)"
8307
8755
  );
8308
8756
  const existing = await sql.unsafe(
8309
- `SELECT COUNT(*)::int AS count FROM _kora_migrations WHERE id = ${sqlLiteral2(migrationId)}`
8757
+ `SELECT COUNT(*)::int AS count FROM _kora_migrations WHERE id = ${sqlLiteral(migrationId)}`
8310
8758
  );
8311
8759
  if ((existing[0]?.count ?? 0) > 0) {
8312
8760
  await sql.unsafe("COMMIT");
@@ -8317,12 +8765,25 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
8317
8765
  skipped: true
8318
8766
  };
8319
8767
  }
8768
+ const now = Date.now();
8769
+ const query = async (text) => await sql.unsafe(text);
8320
8770
  for (const statement of statements) {
8321
- await sql.unsafe(statement);
8771
+ const relax = parseRelaxValueDomainDirective(statement);
8772
+ const evolve = relax ? null : parseEvolveTableDirective(statement);
8773
+ if (relax) {
8774
+ const expanded = await expandRelaxValueDomainForPostgres(relax, query);
8775
+ for (const alter of expanded) await sql.unsafe(alter);
8776
+ } else if (evolve) {
8777
+ const expanded = await expandEvolveTableForPostgres(evolve, query, now);
8778
+ for (const alter of expanded) await sql.unsafe(alter);
8779
+ } else {
8780
+ assertNotLegacyRebuild(statement, migrationId);
8781
+ await sql.unsafe(statement);
8782
+ }
8322
8783
  statementsApplied++;
8323
8784
  }
8324
8785
  await sql.unsafe(
8325
- `INSERT INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral2(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET from_version = EXCLUDED.from_version, to_version = EXCLUDED.to_version, applied_at = EXCLUDED.applied_at`
8786
+ `INSERT INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET from_version = EXCLUDED.from_version, to_version = EXCLUDED.to_version, applied_at = EXCLUDED.applied_at`
8326
8787
  );
8327
8788
  await sql.unsafe("COMMIT");
8328
8789
  return {
@@ -8343,7 +8804,14 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
8343
8804
  }
8344
8805
  }
8345
8806
  }
8346
- function sqlLiteral2(value) {
8807
+ function assertNotLegacyRebuild(statement, migrationId) {
8808
+ if (/^\s*CREATE\s+TABLE\s+"?_kora_mig_[A-Za-z0-9_]+_new"?/i.test(statement)) {
8809
+ throw new Error(
8810
+ `Migration ${migrationId} was generated by an earlier \`kora migrate\` as a SQLite-only table rebuild, which cannot run on Postgres. Regenerate it: restore kora/schema.snapshot.json to the schema version it migrates from, delete the migration's files, and run \`kora migrate\` again.`
8811
+ );
8812
+ }
8813
+ }
8814
+ function sqlLiteral(value) {
8347
8815
  return `'${value.replaceAll("'", "''")}'`;
8348
8816
  }
8349
8817
  async function loadPostgresModule() {
@@ -8431,12 +8899,12 @@ var migrateCommand = (0, import_citty10.defineCommand)({
8431
8899
  throw new InvalidProjectError(process.cwd());
8432
8900
  }
8433
8901
  const config = await loadKoraConfig(projectRoot);
8434
- const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path27.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path27.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
8902
+ const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path28.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path28.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
8435
8903
  if (!resolvedSchemaPath) {
8436
8904
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
8437
8905
  }
8438
8906
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
8439
- const snapshotFile = (0, import_node_path27.join)(projectRoot, SNAPSHOT_PATH);
8907
+ const snapshotFile = (0, import_node_path28.join)(projectRoot, SNAPSHOT_PATH);
8440
8908
  const previousSchema = await readSchemaSnapshot(snapshotFile);
8441
8909
  if (!previousSchema) {
8442
8910
  if (args["dry-run"] === true) {
@@ -8449,7 +8917,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
8449
8917
  return;
8450
8918
  }
8451
8919
  const diff = diffSchemas(previousSchema, currentSchema);
8452
- const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path27.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path27.resolve)(projectRoot, MIGRATIONS_DIR);
8920
+ const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path28.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path28.resolve)(projectRoot, MIGRATIONS_DIR);
8453
8921
  if (!diff.hasChanges) {
8454
8922
  logger.success("No schema changes detected.");
8455
8923
  if (args.apply === true) {
@@ -8501,7 +8969,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
8501
8969
  logger.warn("Dry run enabled: no files written, no migrations applied.");
8502
8970
  return;
8503
8971
  }
8504
- await (0, import_promises18.mkdir)(outputDir, { recursive: true });
8972
+ await (0, import_promises19.mkdir)(outputDir, { recursive: true });
8505
8973
  const migrationPath = await writeMigrationFile(
8506
8974
  outputDir,
8507
8975
  diff.fromVersion,
@@ -8537,7 +9005,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
8537
9005
  });
8538
9006
  async function readSchemaSnapshot(path) {
8539
9007
  try {
8540
- const content = await (0, import_promises18.readFile)(path, "utf-8");
9008
+ const content = await (0, import_promises19.readFile)(path, "utf-8");
8541
9009
  return JSON.parse(content);
8542
9010
  } catch {
8543
9011
  return null;
@@ -8558,15 +9026,15 @@ function isInteractiveTerminal2() {
8558
9026
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
8559
9027
  }
8560
9028
  async function writeSchemaSnapshot(path, schema) {
8561
- await (0, import_promises18.mkdir)((0, import_node_path27.dirname)(path), { recursive: true });
8562
- await (0, import_promises18.writeFile)(path, `${JSON.stringify(schema, null, 2)}
9029
+ await (0, import_promises19.mkdir)((0, import_node_path28.dirname)(path), { recursive: true });
9030
+ await (0, import_promises19.writeFile)(path, `${JSON.stringify(schema, null, 2)}
8563
9031
  `, "utf-8");
8564
9032
  }
8565
9033
  async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
8566
- const existing = await (0, import_promises18.readdir)(outputDir).catch(() => []);
9034
+ const existing = await (0, import_promises19.readdir)(outputDir).catch(() => []);
8567
9035
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
8568
9036
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
8569
- const path = (0, import_node_path27.join)(outputDir, filename);
9037
+ const path = (0, import_node_path28.join)(outputDir, filename);
8570
9038
  const migrationId = filename.replace(/\.ts$/, "");
8571
9039
  const fileContent = [
8572
9040
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -8578,10 +9046,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
8578
9046
  `export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
8579
9047
  ""
8580
9048
  ].join("\n");
8581
- await (0, import_promises18.writeFile)(path, fileContent, "utf-8");
8582
- const transformsPath = (0, import_node_path27.join)(outputDir, `${migrationId}.transforms.ts`);
8583
- await (0, import_promises18.writeFile)(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
8584
- await writeMigrationManifest((0, import_node_path27.join)(outputDir, `${migrationId}.json`), {
9049
+ await (0, import_promises19.writeFile)(path, fileContent, "utf-8");
9050
+ const transformsPath = (0, import_node_path28.join)(outputDir, `${migrationId}.transforms.ts`);
9051
+ await (0, import_promises19.writeFile)(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
9052
+ await writeMigrationManifest((0, import_node_path28.join)(outputDir, `${migrationId}.json`), {
8585
9053
  id: migrationId,
8586
9054
  fromVersion,
8587
9055
  toVersion,
@@ -8593,22 +9061,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
8593
9061
  return path;
8594
9062
  }
8595
9063
  async function writeMigrationManifest(path, manifest) {
8596
- await (0, import_promises18.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
9064
+ await (0, import_promises19.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
8597
9065
  `, "utf-8");
8598
9066
  }
8599
9067
  async function listMigrationManifests(outputDir) {
8600
- const files = await (0, import_promises18.readdir)(outputDir).catch(() => []);
9068
+ const files = await (0, import_promises19.readdir)(outputDir).catch(() => []);
8601
9069
  const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
8602
9070
  const manifests = [];
8603
9071
  for (const file of migrationFiles) {
8604
9072
  const id = file.replace(/\.ts$/, "");
8605
- const manifestPath = (0, import_node_path27.join)(outputDir, `${id}.json`);
9073
+ const manifestPath = (0, import_node_path28.join)(outputDir, `${id}.json`);
8606
9074
  const jsonManifest = await readMigrationManifest(manifestPath);
8607
9075
  if (jsonManifest) {
8608
9076
  manifests.push({ ...jsonManifest, id });
8609
9077
  continue;
8610
9078
  }
8611
- const migrationPath = (0, import_node_path27.join)(outputDir, file);
9079
+ const migrationPath = (0, import_node_path28.join)(outputDir, file);
8612
9080
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
8613
9081
  manifests.push(sourceManifest);
8614
9082
  }
@@ -8616,7 +9084,7 @@ async function listMigrationManifests(outputDir) {
8616
9084
  }
8617
9085
  async function readMigrationManifest(path) {
8618
9086
  try {
8619
- const content = await (0, import_promises18.readFile)(path, "utf-8");
9087
+ const content = await (0, import_promises19.readFile)(path, "utf-8");
8620
9088
  return JSON.parse(content);
8621
9089
  } catch (error) {
8622
9090
  const code = error.code;
@@ -8627,7 +9095,7 @@ async function readMigrationManifest(path) {
8627
9095
  }
8628
9096
  }
8629
9097
  async function readMigrationManifestFromSource(path, id) {
8630
- const content = await (0, import_promises18.readFile)(path, "utf-8");
9098
+ const content = await (0, import_promises19.readFile)(path, "utf-8");
8631
9099
  const versions = parseVersionsFromMigrationId(id);
8632
9100
  return {
8633
9101
  id,
@@ -8689,18 +9157,18 @@ function parseLiteralExport(source, exportName) {
8689
9157
  }
8690
9158
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
8691
9159
  if (typeof dbArg === "string") {
8692
- return (0, import_node_path27.resolve)(projectRoot, dbArg);
9160
+ return (0, import_node_path28.resolve)(projectRoot, dbArg);
8693
9161
  }
8694
9162
  const sync = config?.dev?.sync;
8695
9163
  if (typeof sync === "object" && sync !== null) {
8696
9164
  if (sync.store === "sqlite") {
8697
- return (0, import_node_path27.join)(projectRoot, "kora-sync.db");
9165
+ return (0, import_node_path28.join)(projectRoot, "kora-sync.db");
8698
9166
  }
8699
9167
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
8700
9168
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
8701
- return (0, import_node_path27.resolve)(projectRoot, sync.store.filename);
9169
+ return (0, import_node_path28.resolve)(projectRoot, sync.store.filename);
8702
9170
  }
8703
- return (0, import_node_path27.join)(projectRoot, "kora-sync.db");
9171
+ return (0, import_node_path28.join)(projectRoot, "kora-sync.db");
8704
9172
  }
8705
9173
  }
8706
9174
  return void 0;
@@ -8851,7 +9319,7 @@ function printStatus(status, url, logger) {
8851
9319
 
8852
9320
  // src/commands/studio/studio-command.ts
8853
9321
  var import_node_fs9 = require("fs");
8854
- var import_node_path30 = require("path");
9322
+ var import_node_path31 = require("path");
8855
9323
  var import_citty12 = require("citty");
8856
9324
  var studioCommand = (0, import_citty12.defineCommand)({
8857
9325
  meta: {
@@ -8902,7 +9370,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
8902
9370
  );
8903
9371
  }
8904
9372
  const { loadSchemaDefinition: loadSchemaDefinition2 } = await Promise.resolve().then(() => (init_schema_loader(), schema_loader_exports));
8905
- const schema = await loadSchemaDefinition2((0, import_node_path30.resolve)(process.cwd(), args.schema), process.cwd());
9373
+ const schema = await loadSchemaDefinition2((0, import_node_path31.resolve)(process.cwd(), args.schema), process.cwd());
8906
9374
  const { SpectatorManager: SpectatorManager2 } = await Promise.resolve().then(() => (init_spectator_manager(), spectator_manager_exports));
8907
9375
  const spectator = new SpectatorManager2({
8908
9376
  url: args.connect,
@@ -8929,7 +9397,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
8929
9397
  let schema = defaultLabSchema2();
8930
9398
  if (typeof args.schema === "string") {
8931
9399
  const { loadSchemaDefinition: loadSchemaDefinition2 } = await Promise.resolve().then(() => (init_schema_loader(), schema_loader_exports));
8932
- schema = await loadSchemaDefinition2((0, import_node_path30.resolve)(process.cwd(), args.schema), process.cwd());
9400
+ schema = await loadSchemaDefinition2((0, import_node_path31.resolve)(process.cwd(), args.schema), process.cwd());
8933
9401
  }
8934
9402
  const initialDevices = args.devices !== void 0 ? Math.max(1, Number.parseInt(args.devices, 10) || 2) : 2;
8935
9403
  const lab = new LabManager2(schema);
@@ -8954,7 +9422,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
8954
9422
  "Pass --db <path> to inspect a database, --lab for the sync laboratory, or --connect <wss url> for live spectator mode."
8955
9423
  );
8956
9424
  }
8957
- const dbPath = (0, import_node_path30.resolve)(process.cwd(), args.db);
9425
+ const dbPath = (0, import_node_path31.resolve)(process.cwd(), args.db);
8958
9426
  if (!(0, import_node_fs9.existsSync)(dbPath)) {
8959
9427
  throw new Error(`Database file not found: ${dbPath}`);
8960
9428
  }