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.
- package/dist/aggregate/index.d.ts +1 -1
- package/dist/aggregate/index.js +1 -1
- package/dist/auth/http/index.d.ts +1 -1
- package/dist/auth/index.d.ts +10 -10
- package/dist/auth/index.js +5 -4
- package/dist/auth/nextjs/index.d.ts +2 -2
- package/dist/auth/nextjs/index.js +2 -2
- package/dist/{caller-factory-D3OuR1eI.js → caller-factory-CCsm4Dut.js} +2 -2
- package/dist/cli.mjs +414 -5
- package/dist/{codegen-Cz1idI3-.mjs → codegen-BS36cYTH.mjs} +88 -5
- package/dist/{create-schema-orm-69VF4CFV.js → create-schema-orm-OcyA0apQ.js} +10 -13
- package/dist/crpc/index.d.ts +2 -2
- package/dist/crpc/index.js +3 -3
- package/dist/customFunctions-RnzME_cJ.js +167 -0
- package/dist/{http-types-BCf2wCgp.d.ts → http-types-BK7FuIcR.d.ts} +1 -1
- package/dist/id-BcBb900m.js +121 -0
- package/dist/orm/index.d.ts +4 -3
- package/dist/orm/index.js +706 -165
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/ratelimit/index.d.ts +222 -0
- package/dist/plugins/ratelimit/index.js +846 -0
- package/dist/plugins/ratelimit/react/index.d.ts +76 -0
- package/dist/plugins/ratelimit/react/index.js +294 -0
- package/dist/{procedure-caller-CcjtUFvL.d.ts → procedure-caller-DYjpq7rG.d.ts} +4 -19
- package/dist/rsc/index.d.ts +3 -3
- package/dist/rsc/index.js +4 -4
- package/dist/runtime-C0WcYGY0.js +1028 -0
- package/dist/schema-Bx6j2doh.js +204 -0
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +4 -3
- package/dist/{runtime-B9xQFY8W.js → table-B7yzBihE.js} +3 -1088
- package/dist/text-enum-CFdcLUuw.js +30 -0
- package/dist/{types-CIBGEYXq.d.ts → types-f53SgpBL.d.ts} +1 -1
- package/dist/validators-BcQFm1oY.d.ts +88 -0
- package/dist/{customFunctions-CZnCwoR3.js → validators-D_i3BK7v.js} +67 -165
- package/dist/watcher.mjs +1 -1
- package/dist/{where-clause-compiler-CRP-i1Qa.d.ts → where-clause-compiler-BIjTkVVJ.d.ts} +138 -2
- package/package.json +4 -1
- /package/dist/{create-schema-BdZOL6ns.js → create-schema-BsN0jL5S.js} +0 -0
- /package/dist/{error-Be4OcwwD.js → error-CAGGSN5H.js} +0 -0
- /package/dist/{meta-utils-DDVYp9Xf.js → meta-utils-NRyocOSc.js} +0 -0
- /package/dist/{query-context-BDSis9rT.js → query-context-DEUFBhXS.js} +0 -0
- /package/dist/{query-context-DGExXZIV.d.ts → query-context-ji7By8u0.d.ts} +0 -0
- /package/dist/{query-options-B0c1b6pZ.js → query-options-CSCmKYdJ.js} +0 -0
- /package/dist/{transformer-Dh0w2py0.js → transformer-ogg-4d78.js} +0 -0
- /package/dist/{types-DwGkkq2s.d.ts → types-BTb_4BaU.d.ts} +0 -0
- /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-
|
|
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:
|
|
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:
|
|
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
|
|
3123
|
+
backfillConfig,
|
|
2779
3124
|
mode: "resume",
|
|
2780
|
-
deploymentArgs
|
|
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`.");
|