diff --git a/README.md b/README.md index 83873b9..93b9025 100644 --- a/README.md +++ b/README.md @@ -195,9 +195,11 @@ await migrations.runOne(ctx, internal.example.setDefaultValue); - If it is already running it will refuse to start another duplicate worker. - If it had previously failed on some batch, it will continue from that batch - unless you manually specify `cursor`. + unless you manually specify `cursor` or `reset`. - If you provide an explicit `cursor` (`null` means to start at the beginning), it will start from there. +- If you pass `reset: true`, it will restart the migration from the beginning + for all migrations in the group (including any `next` migrations). - If you pass `true` for `dryRun` then it will run one batch and then throw, so no changes are committed, and you can see what it would have done. See [below](#test-a-migration-with-dryrun) @@ -277,7 +279,15 @@ npx convex run migrations:runIt '{dryRun: true}' ### Restart a migration -Pass `null` for the `cursor` to force a migration to start over. +Pass `reset: true` to force a migration to start over from the beginning. +This will reset the cursor for all migrations in the group. + +```sh +npx convex run migrations:runIt '{reset: true}' +``` + +Alternatively, you can pass `null` for the `cursor` to restart just the +current migration: ```sh npx convex run migrations:runIt '{cursor: null}' diff --git a/src/client/index.ts b/src/client/index.ts index 13d84fb..8b14971 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -185,15 +185,18 @@ export class Migrations { ) ); } + // Handle reset option: reset sets cursor to null for all migrations + const cursor = args.reset ? null : args.cursor; let status: MigrationStatus; try { status = await ctx.runMutation(this.component.lib.migrate, { name, fnHandle, - cursor: args.cursor, + cursor, batchSize: args.batchSize, next, dryRun: args.dryRun ?? false, + reset: args.reset, }); } catch (e) { if ( @@ -231,8 +234,8 @@ export class Migrations { * # Start or resume a migration. No-ops if it's already done: * npx convex run migrations:run '{"fn": "migrations:foo"}' * - * # Restart a migration from a cursor (null is from the beginning): - * npx convex run migrations:run '{"fn": "migrations:foo", "cursor": null }' + * # Restart a migration from the beginning (also resets next migrations): + * npx convex run migrations:run '{"fn": "migrations:foo", "reset": true }' * * # Dry run - runs one batch but doesn't schedule or commit changes. * # so you can see what it would do without committing the transaction. @@ -310,7 +313,7 @@ export class Migrations { "Running this from the CLI or dashboard? Here's some args to use:" ); console.warn({ - "Dry run": '{ "dryRun": true, "cursor": null }', + "Dry run": '{ "dryRun": true, "reset": true }', "For real": '{ "fn": "path/to/migrations:yourFnName" }', }); } @@ -457,6 +460,7 @@ export class Migrations { * @param opts.cursor The cursor to start from. * null: start from the beginning. * undefined: start or resume from where it failed. If done, it won't restart. + * @param opts.reset If true, restarts the migration from the beginning. * @param opts.batchSize The number of documents to process in a batch. * @param opts.dryRun If true, it will run a batch and then throw an error. * It's helpful to see what it would do without committing the transaction. @@ -468,14 +472,16 @@ export class Migrations { cursor?: string | null; batchSize?: number; dryRun?: boolean; + reset?: boolean; } ) { return ctx.runMutation(this.component.lib.migrate, { name: getFunctionName(fnRef), fnHandle: await createFunctionHandle(fnRef), - cursor: opts?.cursor, + cursor: opts?.reset ? null : opts?.cursor, batchSize: opts?.batchSize, dryRun: opts?.dryRun ?? false, + reset: opts?.reset, }); } @@ -649,6 +655,7 @@ function logStatusAndInstructions( cursor?: string | null; batchSize?: number; dryRun?: boolean; + reset?: boolean; } ) { const output: Record = {}; @@ -703,7 +710,12 @@ function logStatusAndInstructions( prod: `--prod`, }; } else { - output["toStartOver"] = JSON.stringify({ ...args, cursor: null }); + // Suggest reset: true instead of cursor: null + const { cursor: _cursor, ...argsWithoutCursor } = args; + output["toStartOver"] = JSON.stringify({ + ...argsWithoutCursor, + reset: true, + }); if (status.next?.length) { output["toMonitorStatus"] = { cmd: `${run} --watch lib:getStatus`, diff --git a/src/component/lib.ts b/src/component/lib.ts index 6fa9041..1d90e68 100644 --- a/src/component/lib.ts +++ b/src/component/lib.ts @@ -36,6 +36,7 @@ const runMigrationArgs = { ) ), dryRun: v.boolean(), + reset: v.optional(v.boolean()), }; export const migrate = mutation({ @@ -139,7 +140,7 @@ export const migrate = mutation({ .query("migrations") .withIndex("name", (q) => q.eq("name", next[i]!.name)) .unique(); - if (!doc || !doc.isDone) { + if (args.reset || !doc || !doc.isDone) { const [nextFn, ...rest] = next.slice(i); if (nextFn) { await ctx.scheduler.runAfter(0, api.lib.migrate, { @@ -148,6 +149,7 @@ export const migrate = mutation({ next: rest, batchSize, dryRun, + ...(args.reset ? { reset: true, cursor: null } : {}), }); } break; diff --git a/src/shared.ts b/src/shared.ts index c09de88..3ed6872 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -6,6 +6,7 @@ export const migrationArgs = { batchSize: v.optional(v.number()), dryRun: v.optional(v.boolean()), next: v.optional(v.array(v.string())), + reset: v.optional(v.boolean()), }; export type MigrationArgs = ObjectType;