@@ -185,15 +185,18 @@ export class Migrations<DataModel extends GenericDataModel> {
185185 )
186186 ) ;
187187 }
188+ // Handle reset option: reset sets cursor to null for all migrations
189+ const cursor = args . reset ? null : args . cursor ;
188190 let status : MigrationStatus ;
189191 try {
190192 status = await ctx . runMutation ( this . component . lib . migrate , {
191193 name,
192194 fnHandle,
193- cursor : args . cursor ,
195+ cursor,
194196 batchSize : args . batchSize ,
195197 next,
196198 dryRun : args . dryRun ?? false ,
199+ reset : args . reset ,
197200 } ) ;
198201 } catch ( e ) {
199202 if (
@@ -231,8 +234,8 @@ export class Migrations<DataModel extends GenericDataModel> {
231234 * # Start or resume a migration. No-ops if it's already done:
232235 * npx convex run migrations:run '{"fn": "migrations:foo"}'
233236 *
234- * # Restart a migration from a cursor (null is from the beginning ):
235- * npx convex run migrations:run '{"fn": "migrations:foo", "cursor ": null }'
237+ * # Restart a migration from the beginning (also resets next migrations ):
238+ * npx convex run migrations:run '{"fn": "migrations:foo", "reset ": true }'
236239 *
237240 * # Dry run - runs one batch but doesn't schedule or commit changes.
238241 * # so you can see what it would do without committing the transaction.
@@ -310,7 +313,7 @@ export class Migrations<DataModel extends GenericDataModel> {
310313 "Running this from the CLI or dashboard? Here's some args to use:"
311314 ) ;
312315 console . warn ( {
313- "Dry run" : '{ "dryRun": true, "cursor ": null }' ,
316+ "Dry run" : '{ "dryRun": true, "reset ": true }' ,
314317 "For real" : '{ "fn": "path/to/migrations:yourFnName" }' ,
315318 } ) ;
316319 }
@@ -457,6 +460,7 @@ export class Migrations<DataModel extends GenericDataModel> {
457460 * @param opts.cursor The cursor to start from.
458461 * null: start from the beginning.
459462 * undefined: start or resume from where it failed. If done, it won't restart.
463+ * @param opts.reset If true, restarts the migration from the beginning.
460464 * @param opts.batchSize The number of documents to process in a batch.
461465 * @param opts.dryRun If true, it will run a batch and then throw an error.
462466 * It's helpful to see what it would do without committing the transaction.
@@ -468,14 +472,16 @@ export class Migrations<DataModel extends GenericDataModel> {
468472 cursor ?: string | null ;
469473 batchSize ?: number ;
470474 dryRun ?: boolean ;
475+ reset ?: boolean ;
471476 }
472477 ) {
473478 return ctx . runMutation ( this . component . lib . migrate , {
474479 name : getFunctionName ( fnRef ) ,
475480 fnHandle : await createFunctionHandle ( fnRef ) ,
476- cursor : opts ?. cursor ,
481+ cursor : opts ?. reset ? null : opts ?. cursor ,
477482 batchSize : opts ?. batchSize ,
478483 dryRun : opts ?. dryRun ?? false ,
484+ reset : opts ?. reset ,
479485 } ) ;
480486 }
481487
@@ -649,6 +655,7 @@ function logStatusAndInstructions(
649655 cursor ?: string | null ;
650656 batchSize ?: number ;
651657 dryRun ?: boolean ;
658+ reset ?: boolean ;
652659 }
653660) {
654661 const output : Record < string , unknown > = { } ;
@@ -703,7 +710,12 @@ function logStatusAndInstructions(
703710 prod : `--prod` ,
704711 } ;
705712 } else {
706- output [ "toStartOver" ] = JSON . stringify ( { ...args , cursor : null } ) ;
713+ // Suggest reset: true instead of cursor: null
714+ const { cursor : _cursor , ...argsWithoutCursor } = args ;
715+ output [ "toStartOver" ] = JSON . stringify ( {
716+ ...argsWithoutCursor ,
717+ reset : true ,
718+ } ) ;
707719 if ( status . next ?. length ) {
708720 output [ "toMonitorStatus" ] = {
709721 cmd : `${ run } --watch lib:getStatus` ,
0 commit comments