better-convex 0.7.2 → 0.8.0

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