Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}'
Expand Down
24 changes: 18 additions & 6 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,18 @@ export class Migrations<DataModel extends GenericDataModel> {
)
);
}
// 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 (
Expand Down Expand Up @@ -231,8 +234,8 @@ export class Migrations<DataModel extends GenericDataModel> {
* # 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.
Expand Down Expand Up @@ -310,7 +313,7 @@ export class Migrations<DataModel extends GenericDataModel> {
"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" }',
});
}
Expand Down Expand Up @@ -457,6 +460,7 @@ export class Migrations<DataModel extends GenericDataModel> {
* @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.
Expand All @@ -468,14 +472,16 @@ export class Migrations<DataModel extends GenericDataModel> {
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,
});
}

Expand Down Expand Up @@ -649,6 +655,7 @@ function logStatusAndInstructions(
cursor?: string | null;
batchSize?: number;
dryRun?: boolean;
reset?: boolean;
}
) {
const output: Record<string, unknown> = {};
Expand Down Expand Up @@ -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`,
Expand Down
4 changes: 3 additions & 1 deletion src/component/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const runMigrationArgs = {
)
),
dryRun: v.boolean(),
reset: v.optional(v.boolean()),
};

export const migrate = mutation({
Expand Down Expand Up @@ -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, {
Expand All @@ -148,6 +149,7 @@ export const migrate = mutation({
next: rest,
batchSize,
dryRun,
...(args.reset ? { reset: true, cursor: null } : {}),
});
}
break;
Expand Down
1 change: 1 addition & 0 deletions src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof migrationArgs>;

Expand Down
Loading