better-convex 0.7.2 → 0.7.3

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 (36) hide show
  1. package/dist/aggregate/index.d.ts +1 -1
  2. package/dist/auth/http/index.d.ts +1 -1
  3. package/dist/auth/index.d.ts +4 -4
  4. package/dist/auth/index.js +5 -4
  5. package/dist/auth/nextjs/index.d.ts +2 -2
  6. package/dist/auth/nextjs/index.js +2 -2
  7. package/dist/{caller-factory-D3OuR1eI.js → caller-factory-4uND4vnj.js} +2 -2
  8. package/dist/cli.mjs +413 -5
  9. package/dist/{codegen-Cz1idI3-.mjs → codegen-BS36cYTH.mjs} +88 -5
  10. package/dist/{create-schema-orm-69VF4CFV.js → create-schema-orm-DtuyK2RB.js} +1 -1
  11. package/dist/crpc/index.d.ts +2 -2
  12. package/dist/crpc/index.js +3 -3
  13. package/dist/customFunctions-C_i_0joT.js +167 -0
  14. package/dist/{http-types-BCf2wCgp.d.ts → http-types-BsnDV7Je.d.ts} +1 -1
  15. package/dist/orm/index.d.ts +4 -3
  16. package/dist/orm/index.js +707 -4
  17. package/dist/{procedure-caller-CcjtUFvL.d.ts → procedure-caller-Cj_lgUev.d.ts} +4 -19
  18. package/dist/rsc/index.d.ts +3 -3
  19. package/dist/rsc/index.js +4 -4
  20. package/dist/server/index.d.ts +2 -2
  21. package/dist/server/index.js +4 -3
  22. package/dist/{types-CIBGEYXq.d.ts → types-DZFvhoPJ.d.ts} +1 -1
  23. package/dist/{customFunctions-CZnCwoR3.js → validators-B7oIJCAp.js} +67 -165
  24. package/dist/validators-BDrWGp4M.d.ts +88 -0
  25. package/dist/watcher.mjs +1 -1
  26. package/dist/{where-clause-compiler-CRP-i1Qa.d.ts → where-clause-compiler-HUa2223D.d.ts} +106 -2
  27. package/package.json +1 -1
  28. /package/dist/{create-schema-BdZOL6ns.js → create-schema-DE9ZtH8n.js} +0 -0
  29. /package/dist/{error-Be4OcwwD.js → error-C7AOPlv2.js} +0 -0
  30. /package/dist/{meta-utils-DDVYp9Xf.js → meta-utils-C9_6WIzj.js} +0 -0
  31. /package/dist/{query-context-DGExXZIV.d.ts → query-context-BMXt2TKe.d.ts} +0 -0
  32. /package/dist/{query-context-BDSis9rT.js → query-context-yQVARct0.js} +0 -0
  33. /package/dist/{query-options-B0c1b6pZ.js → query-options-Bjo6j5cC.js} +0 -0
  34. /package/dist/{transformer-Dh0w2py0.js → transformer-BsX4RWes.js} +0 -0
  35. /package/dist/{types-DgwvxKbT.d.ts → types-DarApWtO.d.ts} +0 -0
  36. /package/dist/{types-DwGkkq2s.d.ts → types-kgwiK-xe.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { Ft as ConvexTableWithColumns, J as ConvexTextBuilderInitial, Z as ConvexNumberBuilderInitial, et as ConvexIdBuilderInitial, st as ConvexCustomBuilderInitial } from "../where-clause-compiler-CRP-i1Qa.js";
1
+ import { At as ConvexCustomBuilderInitial, Ct as ConvexIdBuilderInitial, _t as ConvexTextBuilderInitial, bt as ConvexNumberBuilderInitial, in as ConvexTableWithColumns } from "../where-clause-compiler-HUa2223D.js";
2
2
  import * as convex_values0 from "convex/values";
3
3
  import { GenericId, Infer, Value } from "convex/values";
4
4
  import { DocumentByName, GenericDataModel, GenericDatabaseReader, GenericDatabaseWriter, TableNamesInDataModel } from "convex/server";
@@ -1,4 +1,4 @@
1
- import { t as GetAuth } from "../../types-DgwvxKbT.js";
1
+ import { t as GetAuth } from "../../types-DarApWtO.js";
2
2
  import { HttpRouter } from "convex/server";
3
3
  import { MiddlewareHandler } from "hono";
4
4
 
@@ -1,7 +1,7 @@
1
- import { a as QueryCtxWithPreferredOrmQueryTable, n as LookupByIdResultByCtx, t as DocByCtx } from "../query-context-DGExXZIV.js";
2
- import { t as GetAuth } from "../types-DgwvxKbT.js";
3
- import { A as GenericCtx } from "../procedure-caller-CcjtUFvL.js";
4
- import "../http-types-BCf2wCgp.js";
1
+ import { a as QueryCtxWithPreferredOrmQueryTable, n as LookupByIdResultByCtx, t as DocByCtx } from "../query-context-BMXt2TKe.js";
2
+ import { t as GetAuth } from "../types-DarApWtO.js";
3
+ import { A as GenericCtx } from "../procedure-caller-Cj_lgUev.js";
4
+ import "../http-types-BsnDV7Je.js";
5
5
  import "../server/index.js";
6
6
  import * as convex_values0 from "convex/values";
7
7
  import { Infer } from "convex/values";
@@ -1,5 +1,6 @@
1
- import { a as partial, h as asyncMap, l as isQueryCtx, n as customCtx, r as customMutation, u as isRunMutationCtx } from "../customFunctions-CZnCwoR3.js";
2
- import { a as mergedStream, l as unsetToken, o as stream, t as getByIdWithOrmQueryFallback, v as eq } from "../query-context-BDSis9rT.js";
1
+ import { r as partial, s as asyncMap } from "../validators-B7oIJCAp.js";
2
+ import { n as customCtx, o as isQueryCtx, r as customMutation, s as isRunMutationCtx } from "../customFunctions-C_i_0joT.js";
3
+ import { a as mergedStream, l as unsetToken, o as stream, t as getByIdWithOrmQueryFallback, v as eq } from "../query-context-yQVARct0.js";
3
4
  import { v } from "convex/values";
4
5
  import { internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
5
6
  import { convex } from "@convex-dev/better-auth/plugins";
@@ -772,13 +773,13 @@ const ORM_SCHEMA_OPTIONS = Symbol.for("better-convex:OrmSchemaOptions");
772
773
  const hasOrmSchemaMetadata = (schema) => !!schema && typeof schema === "object" && ORM_SCHEMA_OPTIONS in schema;
773
774
  const createAuthSchema = async ({ file, schema, tables }) => {
774
775
  if (hasOrmSchemaMetadata(schema)) {
775
- const { createSchemaOrm } = await import("../create-schema-orm-69VF4CFV.js");
776
+ const { createSchemaOrm } = await import("../create-schema-orm-DtuyK2RB.js");
776
777
  return createSchemaOrm({
777
778
  file,
778
779
  tables
779
780
  });
780
781
  }
781
- const { createSchema } = await import("../create-schema-BdZOL6ns.js");
782
+ const { createSchema } = await import("../create-schema-DE9ZtH8n.js");
782
783
  return createSchema({
783
784
  file,
784
785
  tables
@@ -1,5 +1,5 @@
1
- import { B as ConvexContext, H as LazyCaller } from "../../procedure-caller-CcjtUFvL.js";
2
- import "../../http-types-BCf2wCgp.js";
1
+ import { B as ConvexContext, H as LazyCaller } from "../../procedure-caller-Cj_lgUev.js";
2
+ import "../../http-types-BsnDV7Je.js";
3
3
  import "../../server/index.js";
4
4
  import { GetTokenOptions } from "@convex-dev/better-auth/utils";
5
5
 
@@ -1,5 +1,5 @@
1
- import { n as defaultIsUnauthorized } from "../../error-Be4OcwwD.js";
2
- import { t as createCallerFactory } from "../../caller-factory-D3OuR1eI.js";
1
+ import { n as defaultIsUnauthorized } from "../../error-C7AOPlv2.js";
2
+ import { t as createCallerFactory } from "../../caller-factory-4uND4vnj.js";
3
3
  import { getToken } from "@convex-dev/better-auth/utils";
4
4
 
5
5
  //#region src/auth-nextjs/index.ts
@@ -1,5 +1,5 @@
1
- import { i as getFunctionType, n as getFuncRef, t as buildMetaIndex } from "./meta-utils-DDVYp9Xf.js";
2
- import { s as getTransformer } from "./transformer-Dh0w2py0.js";
1
+ import { i as getFunctionType, n as getFuncRef, t as buildMetaIndex } from "./meta-utils-C9_6WIzj.js";
2
+ import { s as getTransformer } from "./transformer-BsX4RWes.js";
3
3
  import { fetchAction, fetchMutation, fetchQuery } from "convex/nextjs";
4
4
 
5
5
  //#region src/server/caller.ts
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as getConvexConfig, t as generateMeta } from "./codegen-Cz1idI3-.mjs";
2
+ import { n as getConvexConfig, t as generateMeta } from "./codegen-BS36cYTH.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { createHash } from "node:crypto";
5
5
  import fs from "node:fs";
@@ -1784,6 +1784,15 @@ function createDefaultConfig() {
1784
1784
  pollIntervalMs: 1e3,
1785
1785
  timeoutMs: 9e5,
1786
1786
  strict: false
1787
+ },
1788
+ migrations: {
1789
+ enabled: "auto",
1790
+ wait: true,
1791
+ batchSize: 256,
1792
+ pollIntervalMs: 1e3,
1793
+ timeoutMs: 9e5,
1794
+ strict: false,
1795
+ allowDrift: true
1787
1796
  }
1788
1797
  },
1789
1798
  codegen: {
@@ -1799,6 +1808,15 @@ function createDefaultConfig() {
1799
1808
  pollIntervalMs: 1e3,
1800
1809
  timeoutMs: 9e5,
1801
1810
  strict: true
1811
+ },
1812
+ migrations: {
1813
+ enabled: "auto",
1814
+ wait: true,
1815
+ batchSize: 256,
1816
+ pollIntervalMs: 1e3,
1817
+ timeoutMs: 9e5,
1818
+ strict: true,
1819
+ allowDrift: false
1802
1820
  }
1803
1821
  }
1804
1822
  };
@@ -1843,6 +1861,18 @@ function parseAggregateBackfillConfig(value, fieldName, configPath) {
1843
1861
  if ("strict" in value) parsed.strict = parseBoolean(value.strict, `${fieldName}.strict`, configPath);
1844
1862
  return parsed;
1845
1863
  }
1864
+ function parseMigrationConfig(value, fieldName, configPath) {
1865
+ if (!isRecord(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: expected object.`);
1866
+ const parsed = {};
1867
+ if ("enabled" in value) parsed.enabled = parseBackfillEnabled(value.enabled, `${fieldName}.enabled`, configPath);
1868
+ if ("wait" in value) parsed.wait = parseBoolean(value.wait, `${fieldName}.wait`, configPath);
1869
+ if ("batchSize" in value) parsed.batchSize = parsePositiveInteger(value.batchSize, `${fieldName}.batchSize`, configPath);
1870
+ if ("pollIntervalMs" in value) parsed.pollIntervalMs = parsePositiveInteger(value.pollIntervalMs, `${fieldName}.pollIntervalMs`, configPath);
1871
+ if ("timeoutMs" in value) parsed.timeoutMs = parsePositiveInteger(value.timeoutMs, `${fieldName}.timeoutMs`, configPath);
1872
+ if ("strict" in value) parsed.strict = parseBoolean(value.strict, `${fieldName}.strict`, configPath);
1873
+ if ("allowDrift" in value) parsed.allowDrift = parseBoolean(value.allowDrift, `${fieldName}.allowDrift`, configPath);
1874
+ return parsed;
1875
+ }
1846
1876
  function parseCommandConfig(value, fieldName, configPath) {
1847
1877
  if (!isRecord(value)) throw new Error(`Invalid ${fieldName} in ${configPath}: expected object.`);
1848
1878
  const parsed = {};
@@ -1850,6 +1880,7 @@ function parseCommandConfig(value, fieldName, configPath) {
1850
1880
  if ("convexArgs" in value) parsed.convexArgs = parseStringArray(value.convexArgs, `${fieldName}.convexArgs`, configPath);
1851
1881
  if (fieldName === "codegen" && "scope" in value && value.scope !== void 0) parsed.scope = parseScope(value.scope, `${fieldName}.scope`, configPath);
1852
1882
  if (fieldName === "dev" && "aggregateBackfill" in value && value.aggregateBackfill !== void 0) parsed.aggregateBackfill = parseAggregateBackfillConfig(value.aggregateBackfill, `${fieldName}.aggregateBackfill`, configPath);
1883
+ if (fieldName === "dev" && "migrations" in value && value.migrations !== void 0) parsed.migrations = parseMigrationConfig(value.migrations, `${fieldName}.migrations`, configPath);
1853
1884
  return parsed;
1854
1885
  }
1855
1886
  function parseDeployConfig(value, configPath) {
@@ -1857,6 +1888,7 @@ function parseDeployConfig(value, configPath) {
1857
1888
  const parsed = {};
1858
1889
  if ("convexArgs" in value) parsed.convexArgs = parseStringArray(value.convexArgs, "deploy.convexArgs", configPath);
1859
1890
  if ("aggregateBackfill" in value && value.aggregateBackfill !== void 0) parsed.aggregateBackfill = parseAggregateBackfillConfig(value.aggregateBackfill, "deploy.aggregateBackfill", configPath);
1891
+ if ("migrations" in value && value.migrations !== void 0) parsed.migrations = parseMigrationConfig(value.migrations, "deploy.migrations", configPath);
1860
1892
  return parsed;
1861
1893
  }
1862
1894
  function loadBetterConvexConfig(configPathArg) {
@@ -1885,6 +1917,10 @@ function loadBetterConvexConfig(configPathArg) {
1885
1917
  ...config.dev.aggregateBackfill,
1886
1918
  ...parsed.aggregateBackfill
1887
1919
  };
1920
+ if (parsed.migrations !== void 0) config.dev.migrations = {
1921
+ ...config.dev.migrations,
1922
+ ...parsed.migrations
1923
+ };
1888
1924
  }
1889
1925
  if ("codegen" in rawConfig) {
1890
1926
  const parsed = parseCommandConfig(rawConfig.codegen, "codegen", resolvedConfigPath);
@@ -1899,6 +1935,10 @@ function loadBetterConvexConfig(configPathArg) {
1899
1935
  ...config.deploy.aggregateBackfill,
1900
1936
  ...parsed.aggregateBackfill
1901
1937
  };
1938
+ if (parsed.migrations !== void 0) config.deploy.migrations = {
1939
+ ...config.deploy.migrations,
1940
+ ...parsed.migrations
1941
+ };
1902
1942
  }
1903
1943
  return config;
1904
1944
  }
@@ -2009,6 +2049,7 @@ const __dirname = dirname(__filename);
2009
2049
  const realConvex = join(dirname(createRequire(import.meta.url).resolve("convex/package.json")), "bin/main.js");
2010
2050
  const MISSING_BACKFILL_FUNCTION_RE = /could not find function|function .* was not found|unknown function/i;
2011
2051
  const GITIGNORE_CONVEX_ENTRY_RE = /(^|\r?\n)\.convex\/?\s*(\r?\n|$)/m;
2052
+ const TS_EXTENSION_RE = /\.ts$/;
2012
2053
  const AGGREGATE_STATE_RELATIVE_PATH = join(".convex", "better-convex", "aggregate-backfill-state.json");
2013
2054
  const AGGREGATE_STATE_VERSION = 1;
2014
2055
  const VALID_SCOPES = new Set([
@@ -2290,6 +2331,85 @@ function extractBackfillCliOptions(args) {
2290
2331
  overrides
2291
2332
  };
2292
2333
  }
2334
+ function extractMigrationCliOptions(args) {
2335
+ const remainingArgs = [];
2336
+ const overrides = {};
2337
+ for (let i = 0; i < args.length; i += 1) {
2338
+ const arg = args[i];
2339
+ if (arg === "--migrations") {
2340
+ const { value, nextIndex } = readFlagValue(args, i, "--migrations");
2341
+ if (!VALID_BACKFILL_ENABLED.has(value)) throw new Error("Invalid --migrations value. Expected auto, on, or off.");
2342
+ overrides.enabled = value;
2343
+ i = nextIndex;
2344
+ continue;
2345
+ }
2346
+ if (arg.startsWith("--migrations=")) {
2347
+ const value = arg.slice(13);
2348
+ if (!VALID_BACKFILL_ENABLED.has(value)) throw new Error("Invalid --migrations value. Expected auto, on, or off.");
2349
+ overrides.enabled = value;
2350
+ continue;
2351
+ }
2352
+ if (arg === "--migrations-wait") {
2353
+ overrides.wait = true;
2354
+ continue;
2355
+ }
2356
+ if (arg === "--no-migrations-wait") {
2357
+ overrides.wait = false;
2358
+ continue;
2359
+ }
2360
+ if (arg === "--migrations-strict") {
2361
+ overrides.strict = true;
2362
+ continue;
2363
+ }
2364
+ if (arg === "--no-migrations-strict") {
2365
+ overrides.strict = false;
2366
+ continue;
2367
+ }
2368
+ if (arg === "--migrations-allow-drift") {
2369
+ overrides.allowDrift = true;
2370
+ continue;
2371
+ }
2372
+ if (arg === "--no-migrations-allow-drift") {
2373
+ overrides.allowDrift = false;
2374
+ continue;
2375
+ }
2376
+ if (arg === "--migrations-batch-size") {
2377
+ const { value, nextIndex } = readFlagValue(args, i, "--migrations-batch-size");
2378
+ overrides.batchSize = parsePositiveIntegerArg("--migrations-batch-size", value);
2379
+ i = nextIndex;
2380
+ continue;
2381
+ }
2382
+ if (arg.startsWith("--migrations-batch-size=")) {
2383
+ overrides.batchSize = parsePositiveIntegerArg("--migrations-batch-size", arg.slice(24));
2384
+ continue;
2385
+ }
2386
+ if (arg === "--migrations-timeout-ms") {
2387
+ const { value, nextIndex } = readFlagValue(args, i, "--migrations-timeout-ms");
2388
+ overrides.timeoutMs = parsePositiveIntegerArg("--migrations-timeout-ms", value);
2389
+ i = nextIndex;
2390
+ continue;
2391
+ }
2392
+ if (arg.startsWith("--migrations-timeout-ms=")) {
2393
+ overrides.timeoutMs = parsePositiveIntegerArg("--migrations-timeout-ms", arg.slice(24));
2394
+ continue;
2395
+ }
2396
+ if (arg === "--migrations-poll-ms") {
2397
+ const { value, nextIndex } = readFlagValue(args, i, "--migrations-poll-ms");
2398
+ overrides.pollIntervalMs = parsePositiveIntegerArg("--migrations-poll-ms", value);
2399
+ i = nextIndex;
2400
+ continue;
2401
+ }
2402
+ if (arg.startsWith("--migrations-poll-ms=")) {
2403
+ overrides.pollIntervalMs = parsePositiveIntegerArg("--migrations-poll-ms", arg.slice(21));
2404
+ continue;
2405
+ }
2406
+ remainingArgs.push(arg);
2407
+ }
2408
+ return {
2409
+ remainingArgs,
2410
+ overrides
2411
+ };
2412
+ }
2293
2413
  function extractResetCliOptions(args) {
2294
2414
  const remainingArgs = [];
2295
2415
  let confirmed = false;
@@ -2355,6 +2475,27 @@ function resolveBackfillConfig(base, overrides) {
2355
2475
  strict: overrides.strict ?? resolvedBase.strict
2356
2476
  };
2357
2477
  }
2478
+ function resolveMigrationConfig(base, overrides) {
2479
+ const resolvedBase = base ?? {
2480
+ enabled: "auto",
2481
+ wait: true,
2482
+ batchSize: 256,
2483
+ timeoutMs: 9e5,
2484
+ pollIntervalMs: 1e3,
2485
+ strict: false,
2486
+ allowDrift: true
2487
+ };
2488
+ return {
2489
+ ...resolvedBase,
2490
+ enabled: overrides.enabled ?? resolvedBase.enabled,
2491
+ wait: overrides.wait ?? resolvedBase.wait,
2492
+ batchSize: overrides.batchSize ?? resolvedBase.batchSize,
2493
+ timeoutMs: overrides.timeoutMs ?? resolvedBase.timeoutMs,
2494
+ pollIntervalMs: overrides.pollIntervalMs ?? resolvedBase.pollIntervalMs,
2495
+ strict: overrides.strict ?? resolvedBase.strict,
2496
+ allowDrift: overrides.allowDrift ?? resolvedBase.allowDrift
2497
+ };
2498
+ }
2358
2499
  function extractRunDeploymentArgs(args) {
2359
2500
  const deploymentArgs = [];
2360
2501
  for (let i = 0; i < args.length; i += 1) {
@@ -2505,6 +2646,179 @@ async function runAggregatePruneFlow(params) {
2505
2646
  else console.info("ℹ️ aggregateBackfill prune no-op");
2506
2647
  return 0;
2507
2648
  }
2649
+ function slugifyMigrationName(name) {
2650
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2651
+ }
2652
+ function createMigrationTimestamp(now = /* @__PURE__ */ new Date()) {
2653
+ return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")}_${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")}`;
2654
+ }
2655
+ function extractMigrationDownOptions(args) {
2656
+ const remainingArgs = [];
2657
+ let steps;
2658
+ let to;
2659
+ for (let i = 0; i < args.length; i += 1) {
2660
+ const arg = args[i];
2661
+ if (arg === "--steps") {
2662
+ const { value, nextIndex } = readFlagValue(args, i, "--steps");
2663
+ steps = parsePositiveIntegerArg("--steps", value);
2664
+ i = nextIndex;
2665
+ continue;
2666
+ }
2667
+ if (arg.startsWith("--steps=")) {
2668
+ steps = parsePositiveIntegerArg("--steps", arg.slice(8));
2669
+ continue;
2670
+ }
2671
+ if (arg === "--to") {
2672
+ const { value, nextIndex } = readFlagValue(args, i, "--to");
2673
+ to = value;
2674
+ i = nextIndex;
2675
+ continue;
2676
+ }
2677
+ if (arg.startsWith("--to=")) {
2678
+ const value = arg.slice(5);
2679
+ if (!value) throw new Error("Missing value for --to.");
2680
+ to = value;
2681
+ continue;
2682
+ }
2683
+ remainingArgs.push(arg);
2684
+ }
2685
+ if (steps !== void 0 && to !== void 0) throw new Error("Use either --steps or --to, not both.");
2686
+ return {
2687
+ remainingArgs,
2688
+ steps,
2689
+ to
2690
+ };
2691
+ }
2692
+ function renderMigrationManifest(ids) {
2693
+ const sorted = [...new Set(ids)].sort((a, b) => a.localeCompare(b));
2694
+ const importLines = sorted.map((id, index) => `import { migration as migration_${index} } from './${id}';`);
2695
+ const entryLines = sorted.map((_, index) => ` migration_${index},`);
2696
+ return `// biome-ignore-all format: generated
2697
+ // This file is auto-generated by better-convex migrate create.
2698
+ // Do not edit manually.
2699
+
2700
+ import { defineMigrationSet } from 'better-convex/orm';
2701
+ ${importLines.join("\n")}
2702
+
2703
+ export const migrations = defineMigrationSet([
2704
+ ${entryLines.join("\n")}
2705
+ ]);
2706
+ `;
2707
+ }
2708
+ async function runMigrationCreate(params) {
2709
+ const { migrationName, functionsDir } = params;
2710
+ const normalizedName = slugifyMigrationName(migrationName);
2711
+ if (!normalizedName) throw new Error("Migration name must include at least one letter or digit.");
2712
+ const migrationId = `${createMigrationTimestamp()}_${normalizedName}`;
2713
+ const migrationsDir = join(functionsDir, "migrations");
2714
+ const migrationFile = join(migrationsDir, `${migrationId}.ts`);
2715
+ const manifestFile = join(migrationsDir, "manifest.ts");
2716
+ fs.mkdirSync(migrationsDir, { recursive: true });
2717
+ if (fs.existsSync(migrationFile)) throw new Error(`Migration file already exists for '${migrationId}'. Wait one second and retry.`);
2718
+ const migrationSource = `import { defineMigration } from '../generated/migrations.gen';
2719
+
2720
+ export const migration = defineMigration({
2721
+ id: '${migrationId}',
2722
+ description: '${migrationName.replaceAll("'", "\\'")}',
2723
+ up: {
2724
+ table: 'replace_with_table_name',
2725
+ migrateOne: async () => {
2726
+ // TODO: implement migration logic.
2727
+ },
2728
+ },
2729
+ down: {
2730
+ table: 'replace_with_table_name',
2731
+ migrateOne: async () => {
2732
+ // TODO: implement rollback logic.
2733
+ },
2734
+ },
2735
+ });
2736
+ `;
2737
+ fs.writeFileSync(migrationFile, migrationSource);
2738
+ const existingMigrationIds = fs.readdirSync(migrationsDir).filter((file) => file.endsWith(".ts")).map((file) => file.replace(TS_EXTENSION_RE, "")).filter((id) => id !== "manifest").sort((a, b) => a.localeCompare(b));
2739
+ fs.writeFileSync(manifestFile, renderMigrationManifest(existingMigrationIds));
2740
+ console.info(`ℹ️ created migration ${migrationId}`);
2741
+ console.info(`ℹ️ file: ${migrationFile}`);
2742
+ console.info(`ℹ️ manifest: ${manifestFile}`);
2743
+ }
2744
+ async function runMigrationFlow(params) {
2745
+ const { execaFn, realConvexPath, migrationConfig, deploymentArgs, signal, context, direction, steps, to } = params;
2746
+ if (signal?.aborted || migrationConfig.enabled === "off") return 0;
2747
+ const kickoff = await runConvexFunction(execaFn, realConvexPath, "generated/server:migrationRun", {
2748
+ direction,
2749
+ batchSize: migrationConfig.batchSize,
2750
+ allowDrift: migrationConfig.allowDrift,
2751
+ ...steps !== void 0 ? { steps } : {},
2752
+ ...to !== void 0 ? { to } : {}
2753
+ }, deploymentArgs, { echoOutput: false });
2754
+ if (kickoff.exitCode !== 0) {
2755
+ const combinedOutput = `${kickoff.stdout}\n${kickoff.stderr}`;
2756
+ if (migrationConfig.enabled === "auto" && isMissingBackfillFunctionOutput(combinedOutput)) {
2757
+ if (context === "deploy") console.info("ℹ️ migration runtime not found in this deployment; skipping (auto mode).");
2758
+ return 0;
2759
+ }
2760
+ return kickoff.exitCode;
2761
+ }
2762
+ const payload = parseConvexRunJson(kickoff.stdout);
2763
+ const kickoffStatus = typeof payload === "object" && payload !== null && !Array.isArray(payload) && typeof payload.status === "string" ? payload.status : "running";
2764
+ const driftMessages = typeof payload === "object" && payload !== null && !Array.isArray(payload) && Array.isArray(payload.drift) ? payload.drift.map((entry) => entry?.message).filter((entry) => typeof entry === "string") : [];
2765
+ if (kickoffStatus === "drift_blocked") {
2766
+ const message = driftMessages[0] ?? "Migration drift detected and blocked by current policy.";
2767
+ if (migrationConfig.strict) {
2768
+ console.error(`❌ ${message}`);
2769
+ return 1;
2770
+ }
2771
+ console.warn(`⚠️ ${message}`);
2772
+ return 0;
2773
+ }
2774
+ if (kickoffStatus === "noop") {
2775
+ const noopMessage = direction === "down" ? "No applied migrations to roll back." : "No pending migrations to apply.";
2776
+ console.info(`ℹ️ ${noopMessage}`);
2777
+ return 0;
2778
+ }
2779
+ if (kickoffStatus === "dry_run") {
2780
+ console.info("ℹ️ migration dry run completed (no writes committed).");
2781
+ return 0;
2782
+ }
2783
+ const runId = typeof payload === "object" && payload !== null && !Array.isArray(payload) && typeof payload.runId === "string" ? payload.runId : void 0;
2784
+ if (!migrationConfig.wait || signal?.aborted || !runId) return 0;
2785
+ const deadline = Date.now() + migrationConfig.timeoutMs;
2786
+ let lastStatusLine = "";
2787
+ while (!signal?.aborted) {
2788
+ const statusResult = await runConvexFunction(execaFn, realConvexPath, "generated/server:migrationStatus", { runId }, deploymentArgs, { echoOutput: false });
2789
+ if (statusResult.exitCode !== 0) return statusResult.exitCode;
2790
+ const statusPayload = parseConvexRunJson(statusResult.stdout);
2791
+ const runStatus = typeof statusPayload === "object" && statusPayload !== null && !Array.isArray(statusPayload) ? statusPayload.activeRun?.status ?? statusPayload.runs?.[0]?.status ?? "unknown" : "unknown";
2792
+ const currentIndex = typeof statusPayload === "object" && statusPayload !== null && !Array.isArray(statusPayload) && typeof statusPayload.runs?.[0]?.currentIndex === "number" ? statusPayload.runs[0].currentIndex : 0;
2793
+ const total = typeof statusPayload === "object" && statusPayload !== null && !Array.isArray(statusPayload) && Array.isArray(statusPayload.runs?.[0]?.migrationIds) ? statusPayload.runs?.[0]?.migrationIds?.length ?? 0 : 0;
2794
+ const statusLine = `${runStatus}:${currentIndex}/${total}`;
2795
+ if (statusLine !== lastStatusLine && total > 0) {
2796
+ lastStatusLine = statusLine;
2797
+ console.info(`ℹ️ migration ${runStatus} ${currentIndex}/${total}`);
2798
+ }
2799
+ if (runStatus === "completed" || runStatus === "noop") return 0;
2800
+ if (runStatus === "failed" || runStatus === "canceled") {
2801
+ const message = `Migrations ${runStatus} for run ${runId}.`;
2802
+ if (migrationConfig.strict) {
2803
+ console.error(`❌ ${message}`);
2804
+ return 1;
2805
+ }
2806
+ console.warn(`⚠️ ${message}`);
2807
+ return 0;
2808
+ }
2809
+ if (Date.now() > deadline) {
2810
+ const timeoutMessage = `Migrations timed out after ${migrationConfig.timeoutMs}ms.`;
2811
+ if (migrationConfig.strict) {
2812
+ console.error(`❌ ${timeoutMessage}`);
2813
+ return 1;
2814
+ }
2815
+ console.warn(`⚠️ ${timeoutMessage}`);
2816
+ return 0;
2817
+ }
2818
+ await sleep(migrationConfig.pollIntervalMs, signal);
2819
+ }
2820
+ return 0;
2821
+ }
2508
2822
  async function runDevSchemaBackfillIfNeeded(params) {
2509
2823
  const { execaFn, realConvexPath, backfillConfig, functionsDir, deploymentArgs, signal } = params;
2510
2824
  const fingerprint = await computeAggregateIndexFingerprint(functionsDir);
@@ -2551,13 +2865,15 @@ async function run(argv, deps) {
2551
2865
  if (command === "dev") {
2552
2866
  if (cliScope) throw new Error("`--scope` is not supported for `better-convex dev`. Use `better-convex codegen --scope <all|auth|orm>` for scoped generation.");
2553
2867
  const config = loadBetterConvexConfigFn(configPath);
2554
- const { remainingArgs: devCommandArgs, overrides: devBackfillOverrides } = extractBackfillCliOptions(convexArgs);
2868
+ const { remainingArgs: devArgsWithoutMigrationFlags, overrides: devMigrationOverrides } = extractMigrationCliOptions(convexArgs);
2869
+ const { remainingArgs: devCommandArgs, overrides: devBackfillOverrides } = extractBackfillCliOptions(devArgsWithoutMigrationFlags);
2555
2870
  const outputDir = cliOutputDir ?? config.outputDir;
2556
2871
  const debug = cliDebug || config.dev.debug;
2557
2872
  const generateApi = config.api;
2558
2873
  const generateAuth = config.auth;
2559
2874
  const convexDevArgs = [...config.dev.convexArgs, ...devCommandArgs];
2560
2875
  const devBackfillConfig = resolveBackfillConfig(config.dev.aggregateBackfill, devBackfillOverrides);
2876
+ const devMigrationConfig = resolveMigrationConfig(config.dev.migrations, devMigrationOverrides);
2561
2877
  const { functionsDir } = getConvexConfigFn(outputDir);
2562
2878
  const schemaPath = join(functionsDir, "schema.ts");
2563
2879
  const deploymentArgs = extractRunDeploymentArgs(convexDevArgs);
@@ -2627,6 +2943,21 @@ async function run(argv, deps) {
2627
2943
  schemaBackfillInFlight = null;
2628
2944
  });
2629
2945
  };
2946
+ if (devMigrationConfig.enabled !== "off") (async () => {
2947
+ try {
2948
+ if (await runMigrationFlow({
2949
+ execaFn,
2950
+ realConvexPath,
2951
+ migrationConfig: devMigrationConfig,
2952
+ deploymentArgs,
2953
+ signal: backfillAbortController.signal,
2954
+ context: "dev",
2955
+ direction: "up"
2956
+ }) !== 0 && !backfillAbortController.signal.aborted) console.warn("⚠️ migration up failed in dev (continuing without blocking).");
2957
+ } catch (error) {
2958
+ if (!backfillAbortController.signal.aborted) console.warn(`⚠️ migration up errored in dev: ${error.message}`);
2959
+ }
2960
+ })();
2630
2961
  if (devBackfillConfig.enabled !== "off") (async () => {
2631
2962
  try {
2632
2963
  if (await runAggregateBackfillFlow({
@@ -2760,7 +3091,8 @@ async function run(argv, deps) {
2760
3091
  }
2761
3092
  if (command === "deploy") {
2762
3093
  const config = loadBetterConvexConfigFn(configPath);
2763
- const { remainingArgs: deployCommandArgs, overrides: deployBackfillOverrides } = extractBackfillCliOptions(convexArgs);
3094
+ const { remainingArgs: deployArgsWithoutMigrationFlags, overrides: deployMigrationOverrides } = extractMigrationCliOptions(convexArgs);
3095
+ const { remainingArgs: deployCommandArgs, overrides: deployBackfillOverrides } = extractBackfillCliOptions(deployArgsWithoutMigrationFlags);
2764
3096
  const deployArgs = [...config.deploy.convexArgs, ...deployCommandArgs];
2765
3097
  const deployResult = await execaFn("node", [
2766
3098
  realConvexPath,
@@ -2772,15 +3104,91 @@ async function run(argv, deps) {
2772
3104
  reject: false
2773
3105
  });
2774
3106
  if ((deployResult.exitCode ?? 1) !== 0) return deployResult.exitCode ?? 1;
3107
+ const migrationConfig = resolveMigrationConfig(config.deploy.migrations, deployMigrationOverrides);
3108
+ const backfillConfig = resolveBackfillConfig(config.deploy.aggregateBackfill, deployBackfillOverrides);
3109
+ const deploymentArgs = extractRunDeploymentArgs(deployArgs);
3110
+ const migrationExitCode = await runMigrationFlow({
3111
+ execaFn,
3112
+ realConvexPath,
3113
+ migrationConfig,
3114
+ deploymentArgs,
3115
+ context: "deploy",
3116
+ direction: "up"
3117
+ });
3118
+ if (migrationExitCode !== 0) return migrationExitCode;
2775
3119
  return runAggregateBackfillFlow({
2776
3120
  execaFn,
2777
3121
  realConvexPath,
2778
- backfillConfig: resolveBackfillConfig(config.deploy.aggregateBackfill, deployBackfillOverrides),
3122
+ backfillConfig,
2779
3123
  mode: "resume",
2780
- deploymentArgs: extractRunDeploymentArgs(deployArgs),
3124
+ deploymentArgs,
2781
3125
  context: "deploy"
2782
3126
  });
2783
3127
  }
3128
+ if (command === "migrate") {
3129
+ const subcommand = restArgs[0];
3130
+ if (subcommand !== "create" && subcommand !== "up" && subcommand !== "down" && subcommand !== "status" && subcommand !== "cancel") throw new Error("Unknown migrate command. Use: `better-convex migrate create|up|down|status|cancel`.");
3131
+ const config = loadBetterConvexConfigFn(configPath);
3132
+ if (subcommand === "create") {
3133
+ const rawName = restArgs.slice(1).join(" ").trim();
3134
+ if (!rawName) throw new Error("Missing migration name. Usage: `better-convex migrate create <name>`.");
3135
+ const { functionsDir } = getConvexConfigFn(cliOutputDir ?? config.outputDir);
3136
+ await runMigrationCreate({
3137
+ migrationName: rawName,
3138
+ functionsDir
3139
+ });
3140
+ return 0;
3141
+ }
3142
+ const { remainingArgs: migrationCommandArgs, overrides: migrationOverrides } = extractMigrationCliOptions(restArgs.slice(1));
3143
+ const migrationConfig = {
3144
+ ...resolveMigrationConfig(config.deploy.migrations, migrationOverrides),
3145
+ enabled: "on"
3146
+ };
3147
+ const commandArgs = [...config.deploy.convexArgs, ...migrationCommandArgs];
3148
+ const deploymentArgs = extractRunDeploymentArgs(commandArgs);
3149
+ if (subcommand === "up") return runMigrationFlow({
3150
+ execaFn,
3151
+ realConvexPath,
3152
+ migrationConfig,
3153
+ deploymentArgs,
3154
+ context: "migration",
3155
+ direction: "up"
3156
+ });
3157
+ if (subcommand === "down") {
3158
+ const { remainingArgs, steps, to } = extractMigrationDownOptions(commandArgs);
3159
+ return runMigrationFlow({
3160
+ execaFn,
3161
+ realConvexPath,
3162
+ migrationConfig,
3163
+ deploymentArgs: extractRunDeploymentArgs(remainingArgs),
3164
+ context: "migration",
3165
+ direction: "down",
3166
+ steps,
3167
+ to
3168
+ });
3169
+ }
3170
+ if (subcommand === "status") return (await runConvexFunction(execaFn, realConvexPath, "generated/server:migrationStatus", {}, deploymentArgs)).exitCode;
3171
+ let runId;
3172
+ const cancelArgs = [];
3173
+ for (let i = 0; i < commandArgs.length; i += 1) {
3174
+ const arg = commandArgs[i];
3175
+ if (arg === "--run-id") {
3176
+ const { value, nextIndex } = readFlagValue(commandArgs, i, "--run-id");
3177
+ runId = value;
3178
+ i = nextIndex;
3179
+ continue;
3180
+ }
3181
+ if (arg.startsWith("--run-id=")) {
3182
+ const value = arg.slice(9);
3183
+ if (!value) throw new Error("Missing value for --run-id.");
3184
+ runId = value;
3185
+ continue;
3186
+ }
3187
+ cancelArgs.push(arg);
3188
+ }
3189
+ const cancelDeploymentArgs = extractRunDeploymentArgs(cancelArgs);
3190
+ return (await runConvexFunction(execaFn, realConvexPath, "generated/server:migrationCancel", runId ? { runId } : {}, cancelDeploymentArgs)).exitCode;
3191
+ }
2784
3192
  if (command === "aggregate") {
2785
3193
  const subcommand = restArgs[0];
2786
3194
  if (subcommand !== "rebuild" && subcommand !== "backfill" && subcommand !== "prune") throw new Error("Unknown aggregate command. Use: `better-convex aggregate backfill`, `better-convex aggregate rebuild`, or `better-convex aggregate prune`.");