@prairielearn/migrations 2.1.0 → 3.0.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/.mocharc.cjs +3 -0
- package/CHANGELOG.md +14 -0
- package/dist/batched-migrations/batched-migration-job.js +18 -22
- package/dist/batched-migrations/batched-migration-job.js.map +1 -1
- package/dist/batched-migrations/batched-migration-runner.d.ts +2 -2
- package/dist/batched-migrations/batched-migration-runner.js +22 -26
- package/dist/batched-migrations/batched-migration-runner.js.map +1 -1
- package/dist/batched-migrations/batched-migration-runner.test.js +53 -78
- package/dist/batched-migrations/batched-migration-runner.test.js.map +1 -1
- package/dist/batched-migrations/batched-migration.js +30 -41
- package/dist/batched-migrations/batched-migration.js.map +1 -1
- package/dist/batched-migrations/batched-migrations-runner.d.ts +1 -1
- package/dist/batched-migrations/batched-migrations-runner.js +32 -67
- package/dist/batched-migrations/batched-migrations-runner.js.map +1 -1
- package/dist/batched-migrations/batched-migrations-runner.test.js +41 -69
- package/dist/batched-migrations/batched-migrations-runner.test.js.map +1 -1
- package/dist/batched-migrations/fixtures/20230406184103_successful_migration.js +2 -4
- package/dist/batched-migrations/fixtures/20230406184103_successful_migration.js.map +1 -1
- package/dist/batched-migrations/fixtures/20230406184107_failing_migration.d.ts +9 -0
- package/dist/batched-migrations/fixtures/20230406184107_failing_migration.js +14 -0
- package/dist/batched-migrations/fixtures/20230406184107_failing_migration.js.map +1 -0
- package/dist/batched-migrations/fixtures/20230407230446_no_rows_migration.js +2 -4
- package/dist/batched-migrations/fixtures/20230407230446_no_rows_migration.js.map +1 -1
- package/dist/batched-migrations/index.d.ts +3 -3
- package/dist/batched-migrations/index.js +3 -17
- package/dist/batched-migrations/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/load-migrations.js +6 -16
- package/dist/load-migrations.js.map +1 -1
- package/dist/load-migrations.test.js +16 -44
- package/dist/load-migrations.test.js.map +1 -1
- package/dist/migrations/fixtures/20230407210430_insert_user.js +3 -6
- package/dist/migrations/fixtures/20230407210430_insert_user.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.js +1 -5
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/migrations.d.ts +1 -1
- package/dist/migrations/migrations.js +25 -57
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/migrations/migrations.test.js +12 -17
- package/dist/migrations/migrations.test.js.map +1 -1
- package/package.json +8 -7
- package/src/batched-migrations/batched-migration-job.ts +1 -1
- package/src/batched-migrations/batched-migration-runner.test.ts +4 -4
- package/src/batched-migrations/batched-migration-runner.ts +3 -3
- package/src/batched-migrations/batched-migration.ts +1 -1
- package/src/batched-migrations/batched-migrations-runner.test.ts +8 -8
- package/src/batched-migrations/batched-migrations-runner.ts +4 -4
- package/src/batched-migrations/fixtures/20230406184103_successful_migration.ts +1 -1
- package/src/batched-migrations/fixtures/{20230406184107_failing_migration.js → 20230406184107_failing_migration.ts} +2 -3
- package/src/batched-migrations/fixtures/20230407230446_no_rows_migration.ts +1 -1
- package/src/batched-migrations/index.ts +7 -7
- package/src/index.ts +7 -7
- package/src/load-migrations.test.ts +1 -1
- package/src/migrations/index.ts +1 -1
- package/src/migrations/migrations.test.ts +2 -2
- package/src/migrations/migrations.ts +2 -2
- package/tsconfig.json +4 -1
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
const sql = (0, postgres_1.loadSqlEquiv)(__filename);
|
|
7
|
-
exports.BatchedMigrationStatusSchema = zod_1.z.enum([
|
|
1
|
+
import { loadSqlEquiv, queryAsync, queryRow, queryRows, queryOptionalRow, } from '@prairielearn/postgres';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
4
|
+
export const BatchedMigrationStatusSchema = z.enum([
|
|
8
5
|
'pending',
|
|
9
6
|
'paused',
|
|
10
7
|
'running',
|
|
@@ -12,28 +9,27 @@ exports.BatchedMigrationStatusSchema = zod_1.z.enum([
|
|
|
12
9
|
'failed',
|
|
13
10
|
'succeeded',
|
|
14
11
|
]);
|
|
15
|
-
|
|
16
|
-
id:
|
|
17
|
-
project:
|
|
18
|
-
filename:
|
|
19
|
-
timestamp:
|
|
20
|
-
batch_size:
|
|
21
|
-
min_value:
|
|
22
|
-
max_value:
|
|
23
|
-
status:
|
|
24
|
-
created_at:
|
|
25
|
-
updated_at:
|
|
26
|
-
started_at:
|
|
12
|
+
export const BatchedMigrationRowSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
project: z.string(),
|
|
15
|
+
filename: z.string(),
|
|
16
|
+
timestamp: z.string(),
|
|
17
|
+
batch_size: z.number(),
|
|
18
|
+
min_value: z.bigint({ coerce: true }),
|
|
19
|
+
max_value: z.bigint({ coerce: true }),
|
|
20
|
+
status: BatchedMigrationStatusSchema,
|
|
21
|
+
created_at: z.date(),
|
|
22
|
+
updated_at: z.date(),
|
|
23
|
+
started_at: z.date().nullable(),
|
|
27
24
|
});
|
|
28
25
|
/**
|
|
29
26
|
* Identity function that helps to write correct batched migrations.
|
|
30
27
|
*/
|
|
31
|
-
function makeBatchedMigration(fns) {
|
|
28
|
+
export function makeBatchedMigration(fns) {
|
|
32
29
|
validateBatchedMigrationImplementation(fns);
|
|
33
30
|
return fns;
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
function validateBatchedMigrationImplementation(fns) {
|
|
32
|
+
export function validateBatchedMigrationImplementation(fns) {
|
|
37
33
|
if (typeof fns.getParameters !== 'function') {
|
|
38
34
|
throw new Error('getParameters() must be a function');
|
|
39
35
|
}
|
|
@@ -41,33 +37,26 @@ function validateBatchedMigrationImplementation(fns) {
|
|
|
41
37
|
throw new Error('execute() must be a function');
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
|
-
exports.validateBatchedMigrationImplementation = validateBatchedMigrationImplementation;
|
|
45
40
|
/**
|
|
46
41
|
* Inserts a new batched migration. If one already exists for the given
|
|
47
42
|
* project/timestamp pair, returns null, otherwise returns the inserted row.
|
|
48
43
|
*/
|
|
49
|
-
async function insertBatchedMigration(migration) {
|
|
50
|
-
return await
|
|
44
|
+
export async function insertBatchedMigration(migration) {
|
|
45
|
+
return await queryOptionalRow(sql.insert_batched_migration, migration, BatchedMigrationRowSchema);
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return await (0, postgres_1.queryRows)(sql.select_all_batched_migrations, { project }, exports.BatchedMigrationRowSchema);
|
|
47
|
+
export async function selectAllBatchedMigrations(project) {
|
|
48
|
+
return await queryRows(sql.select_all_batched_migrations, { project }, BatchedMigrationRowSchema);
|
|
55
49
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return await (0, postgres_1.queryRow)(sql.select_batched_migration, { project, id }, exports.BatchedMigrationRowSchema);
|
|
50
|
+
export async function selectBatchedMigration(project, id) {
|
|
51
|
+
return await queryRow(sql.select_batched_migration, { project, id }, BatchedMigrationRowSchema);
|
|
59
52
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return await (0, postgres_1.queryRow)(sql.select_batched_migration_for_timestamp, { project, timestamp }, exports.BatchedMigrationRowSchema);
|
|
53
|
+
export async function selectBatchedMigrationForTimestamp(project, timestamp) {
|
|
54
|
+
return await queryRow(sql.select_batched_migration_for_timestamp, { project, timestamp }, BatchedMigrationRowSchema);
|
|
63
55
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return await (0, postgres_1.queryRow)(sql.update_batched_migration_status, { id, status }, exports.BatchedMigrationRowSchema);
|
|
56
|
+
export async function updateBatchedMigrationStatus(id, status) {
|
|
57
|
+
return await queryRow(sql.update_batched_migration_status, { id, status }, BatchedMigrationRowSchema);
|
|
67
58
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await (0, postgres_1.queryAsync)(sql.retry_failed_jobs, { project, id });
|
|
59
|
+
export async function retryFailedBatchedMigrationJobs(project, id) {
|
|
60
|
+
await queryAsync(sql.retry_failed_jobs, { project, id });
|
|
71
61
|
}
|
|
72
|
-
exports.retryFailedBatchedMigrationJobs = retryFailedBatchedMigrationJobs;
|
|
73
62
|
//# sourceMappingURL=batched-migration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batched-migration.js","sourceRoot":"","sources":["../../src/batched-migrations/batched-migration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"batched-migration.js","sourceRoot":"","sources":["../../src/batched-migrations/batched-migration.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadSqlEquiv,\n queryAsync,\n queryRow,\n queryRows,\n queryOptionalRow,\n} from '@prairielearn/postgres';\nimport { z } from 'zod';\n\nconst sql = loadSqlEquiv(import.meta.filename);\n\nexport const BatchedMigrationStatusSchema = z.enum([\n 'pending',\n 'paused',\n 'running',\n 'finalizing',\n 'failed',\n 'succeeded',\n]);\nexport type BatchedMigrationStatus = z.infer<typeof BatchedMigrationStatusSchema>;\n\nexport const BatchedMigrationRowSchema = z.object({\n id: z.string(),\n project: z.string(),\n filename: z.string(),\n timestamp: z.string(),\n batch_size: z.number(),\n min_value: z.bigint({ coerce: true }),\n max_value: z.bigint({ coerce: true }),\n status: BatchedMigrationStatusSchema,\n created_at: z.date(),\n updated_at: z.date(),\n started_at: z.date().nullable(),\n});\nexport type BatchedMigrationRow = z.infer<typeof BatchedMigrationRowSchema>;\n\nexport interface BatchedMigrationParameters {\n min?: bigint | string | null;\n max: bigint | string | null;\n batchSize?: number;\n}\n\nexport interface BatchedMigrationImplementation {\n getParameters(): Promise<BatchedMigrationParameters>;\n execute(start: bigint, end: bigint): Promise<void>;\n}\n\n/**\n * Identity function that helps to write correct batched migrations.\n */\nexport function makeBatchedMigration<T extends BatchedMigrationImplementation>(fns: T): T {\n validateBatchedMigrationImplementation(fns);\n return fns;\n}\n\nexport function validateBatchedMigrationImplementation(\n fns: BatchedMigrationImplementation,\n): asserts fns is BatchedMigrationImplementation {\n if (typeof fns.getParameters !== 'function') {\n throw new Error('getParameters() must be a function');\n }\n if (typeof fns.execute !== 'function') {\n throw new Error('execute() must be a function');\n }\n}\n\ntype NewBatchedMigration = Pick<\n BatchedMigrationRow,\n 'project' | 'filename' | 'timestamp' | 'batch_size' | 'min_value' | 'max_value' | 'status'\n>;\n\n/**\n * Inserts a new batched migration. If one already exists for the given\n * project/timestamp pair, returns null, otherwise returns the inserted row.\n */\nexport async function insertBatchedMigration(\n migration: NewBatchedMigration,\n): Promise<BatchedMigrationRow | null> {\n return await queryOptionalRow(sql.insert_batched_migration, migration, BatchedMigrationRowSchema);\n}\n\nexport async function selectAllBatchedMigrations(project: string) {\n return await queryRows(sql.select_all_batched_migrations, { project }, BatchedMigrationRowSchema);\n}\n\nexport async function selectBatchedMigration(\n project: string,\n id: string,\n): Promise<BatchedMigrationRow> {\n return await queryRow(sql.select_batched_migration, { project, id }, BatchedMigrationRowSchema);\n}\n\nexport async function selectBatchedMigrationForTimestamp(\n project: string,\n timestamp: string,\n): Promise<BatchedMigrationRow> {\n return await queryRow(\n sql.select_batched_migration_for_timestamp,\n { project, timestamp },\n BatchedMigrationRowSchema,\n );\n}\n\nexport async function updateBatchedMigrationStatus(\n id: string,\n status: BatchedMigrationStatus,\n): Promise<BatchedMigrationRow> {\n return await queryRow(\n sql.update_batched_migration_status,\n { id, status },\n BatchedMigrationRowSchema,\n );\n}\n\nexport async function retryFailedBatchedMigrationJobs(project: string, id: string): Promise<void> {\n await queryAsync(sql.retry_failed_jobs, { project, id });\n}\n"]}
|
|
@@ -1,47 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.finalizeBatchedMigration = exports.enqueueBatchedMigration = exports.stopBatchedMigrations = exports.startBatchedMigrations = exports.initBatchedMigrations = exports.BatchedMigrationsRunner = void 0;
|
|
30
|
-
const node_events_1 = require("node:events");
|
|
31
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
-
const promises_1 = require("node:timers/promises");
|
|
33
|
-
const postgres_1 = require("@prairielearn/postgres");
|
|
34
|
-
const named_locks_1 = require("@prairielearn/named-locks");
|
|
35
|
-
const load_migrations_1 = require("../load-migrations");
|
|
36
|
-
const batched_migration_1 = require("./batched-migration");
|
|
37
|
-
const batched_migration_runner_1 = require("./batched-migration-runner");
|
|
38
|
-
const sql = (0, postgres_1.loadSqlEquiv)(__filename);
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
+
import { loadSqlEquiv, queryOptionalRow } from '@prairielearn/postgres';
|
|
5
|
+
import { doWithLock } from '@prairielearn/named-locks';
|
|
6
|
+
import { readAndValidateMigrationsFromDirectories } from '../load-migrations.js';
|
|
7
|
+
import { BatchedMigrationRowSchema, insertBatchedMigration, selectBatchedMigrationForTimestamp, updateBatchedMigrationStatus, validateBatchedMigrationImplementation, } from './batched-migration.js';
|
|
8
|
+
import { BatchedMigrationRunner } from './batched-migration-runner.js';
|
|
9
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
39
10
|
const DEFAULT_MIN_VALUE = 1n;
|
|
40
11
|
const DEFAULT_BATCH_SIZE = 1_000;
|
|
41
12
|
const DEFAULT_WORK_DURATION_MS = 60_000;
|
|
42
13
|
const DEFAULT_SLEEP_DURATION_MS = 30_000;
|
|
43
14
|
const EXTENSIONS = ['.js', '.ts', '.mjs', '.mts'];
|
|
44
|
-
class BatchedMigrationsRunner extends
|
|
15
|
+
export class BatchedMigrationsRunner extends EventEmitter {
|
|
45
16
|
options;
|
|
46
17
|
lockName;
|
|
47
18
|
running = false;
|
|
@@ -57,7 +28,7 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
57
28
|
}
|
|
58
29
|
getMigrationFiles = async () => {
|
|
59
30
|
if (!this.migrationFiles) {
|
|
60
|
-
this.migrationFiles = await
|
|
31
|
+
this.migrationFiles = await readAndValidateMigrationsFromDirectories(this.options.directories, EXTENSIONS);
|
|
61
32
|
}
|
|
62
33
|
return this.migrationFiles;
|
|
63
34
|
};
|
|
@@ -75,10 +46,10 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
75
46
|
*/
|
|
76
47
|
async loadMigrationImplementation(migrationFile) {
|
|
77
48
|
// We use dynamic imports to handle both CJS and ESM modules.
|
|
78
|
-
const migrationModulePath =
|
|
79
|
-
const migrationModule = await
|
|
49
|
+
const migrationModulePath = path.join(migrationFile.directory, migrationFile.filename);
|
|
50
|
+
const migrationModule = await import(migrationModulePath);
|
|
80
51
|
const migrationImplementation = migrationModule.default;
|
|
81
|
-
|
|
52
|
+
validateBatchedMigrationImplementation(migrationImplementation);
|
|
82
53
|
return migrationImplementation;
|
|
83
54
|
}
|
|
84
55
|
async enqueueBatchedMigration(identifier) {
|
|
@@ -94,7 +65,7 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
94
65
|
const minValue = BigInt(migrationParameters.min ?? DEFAULT_MIN_VALUE);
|
|
95
66
|
const maxValue = BigInt(migrationParameters.max ?? minValue);
|
|
96
67
|
const batchSize = migrationParameters.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
97
|
-
await
|
|
68
|
+
await insertBatchedMigration({
|
|
98
69
|
project: this.options.project,
|
|
99
70
|
filename: migrationFile.filename,
|
|
100
71
|
timestamp: migrationFile.timestamp,
|
|
@@ -106,20 +77,20 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
106
77
|
}
|
|
107
78
|
async finalizeBatchedMigration(identifier, options) {
|
|
108
79
|
const timestamp = identifier.split('_')[0];
|
|
109
|
-
let migration = await
|
|
80
|
+
let migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);
|
|
110
81
|
if (migration.status === 'succeeded')
|
|
111
82
|
return;
|
|
112
83
|
// If the migration isn't already in the finalizing state, mark it as such.
|
|
113
84
|
if (migration.status !== 'finalizing') {
|
|
114
|
-
migration = await
|
|
85
|
+
migration = await updateBatchedMigrationStatus(migration.id, 'finalizing');
|
|
115
86
|
}
|
|
116
|
-
await
|
|
87
|
+
await doWithLock(this.lockNameForTimestamp(timestamp), { autoRenew: true }, async () => {
|
|
117
88
|
const migrationFile = await this.getMigrationForIdentifier(identifier);
|
|
118
89
|
if (!migrationFile) {
|
|
119
90
|
throw new Error(`No migration found for identifier ${identifier}`);
|
|
120
91
|
}
|
|
121
92
|
const migrationImplementation = await this.loadMigrationImplementation(migrationFile);
|
|
122
|
-
const runner = new
|
|
93
|
+
const runner = new BatchedMigrationRunner(migration, migrationImplementation, {
|
|
123
94
|
// Always log progress unless explicitly disabled.
|
|
124
95
|
logProgress: options?.logProgress ?? true,
|
|
125
96
|
});
|
|
@@ -127,7 +98,7 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
127
98
|
// has attempted every job.
|
|
128
99
|
await runner.run();
|
|
129
100
|
});
|
|
130
|
-
migration = await
|
|
101
|
+
migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);
|
|
131
102
|
if (migration.status === 'succeeded')
|
|
132
103
|
return;
|
|
133
104
|
throw new Error(`Expected batched migration with identifier ${identifier} to be marked as 'succeeded', but it is '${migration.status}'.`);
|
|
@@ -163,7 +134,7 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
163
134
|
// We provide the signal here so that we can more quickly stop things
|
|
164
135
|
// when we're shutting down.
|
|
165
136
|
try {
|
|
166
|
-
await (
|
|
137
|
+
await sleep(sleepDurationMs, null, { ref: false, signal: this.abortController.signal });
|
|
167
138
|
}
|
|
168
139
|
catch (err) {
|
|
169
140
|
// We don't care about errors here, they should only ever occur when
|
|
@@ -175,13 +146,13 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
175
146
|
}
|
|
176
147
|
}
|
|
177
148
|
async getOrStartMigration() {
|
|
178
|
-
return
|
|
149
|
+
return doWithLock(this.lockName, {
|
|
179
150
|
// Don't fail if the lock couldn't be acquired immediately.
|
|
180
151
|
onNotAcquired: () => null,
|
|
181
152
|
}, async () => {
|
|
182
|
-
let migration = await
|
|
153
|
+
let migration = await queryOptionalRow(sql.select_running_migration, { project: this.options.project }, BatchedMigrationRowSchema);
|
|
183
154
|
if (!migration) {
|
|
184
|
-
migration = await
|
|
155
|
+
migration = await queryOptionalRow(sql.start_next_pending_migration, { project: this.options.project }, BatchedMigrationRowSchema);
|
|
185
156
|
}
|
|
186
157
|
return migration;
|
|
187
158
|
});
|
|
@@ -199,14 +170,14 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
199
170
|
return false;
|
|
200
171
|
}
|
|
201
172
|
let didWork = false;
|
|
202
|
-
await
|
|
173
|
+
await doWithLock(this.lockNameForTimestamp(migrationFile.timestamp), {
|
|
203
174
|
autoRenew: true,
|
|
204
175
|
// Do nothing if the lock could not immediately be acquired.
|
|
205
176
|
onNotAcquired: () => null,
|
|
206
177
|
}, async () => {
|
|
207
178
|
didWork = true;
|
|
208
179
|
const migrationImplementation = await this.loadMigrationImplementation(migrationFile);
|
|
209
|
-
const runner = new
|
|
180
|
+
const runner = new BatchedMigrationRunner(migration, migrationImplementation);
|
|
210
181
|
try {
|
|
211
182
|
await runner.run({ signal: this.abortController.signal, durationMs });
|
|
212
183
|
}
|
|
@@ -220,35 +191,31 @@ class BatchedMigrationsRunner extends node_events_1.EventEmitter {
|
|
|
220
191
|
this.abortController.abort();
|
|
221
192
|
// Spin until we're no longer running.
|
|
222
193
|
while (this.running) {
|
|
223
|
-
await (
|
|
194
|
+
await sleep(1000);
|
|
224
195
|
}
|
|
225
196
|
}
|
|
226
197
|
}
|
|
227
|
-
exports.BatchedMigrationsRunner = BatchedMigrationsRunner;
|
|
228
198
|
let runner = null;
|
|
229
199
|
function assertRunner(runner) {
|
|
230
200
|
if (!runner)
|
|
231
201
|
throw new Error('Batched migrations not initialized');
|
|
232
202
|
}
|
|
233
|
-
function initBatchedMigrations(options) {
|
|
203
|
+
export function initBatchedMigrations(options) {
|
|
234
204
|
if (runner)
|
|
235
205
|
throw new Error('Batched migrations already initialized');
|
|
236
206
|
runner = new BatchedMigrationsRunner(options);
|
|
237
207
|
return runner;
|
|
238
208
|
}
|
|
239
|
-
|
|
240
|
-
function startBatchedMigrations(options = {}) {
|
|
209
|
+
export function startBatchedMigrations(options = {}) {
|
|
241
210
|
assertRunner(runner);
|
|
242
211
|
runner.start(options);
|
|
243
212
|
return runner;
|
|
244
213
|
}
|
|
245
|
-
|
|
246
|
-
async function stopBatchedMigrations() {
|
|
214
|
+
export async function stopBatchedMigrations() {
|
|
247
215
|
assertRunner(runner);
|
|
248
216
|
await runner.stop();
|
|
249
217
|
runner = null;
|
|
250
218
|
}
|
|
251
|
-
exports.stopBatchedMigrations = stopBatchedMigrations;
|
|
252
219
|
/**
|
|
253
220
|
* Given a batched migration identifier like `20230406184103_migration`,
|
|
254
221
|
* enqueues it for execution by creating a row in the `batched_migrations`
|
|
@@ -260,11 +227,10 @@ exports.stopBatchedMigrations = stopBatchedMigrations;
|
|
|
260
227
|
*
|
|
261
228
|
* @param identifier The identifier of the batched migration to enqueue.
|
|
262
229
|
*/
|
|
263
|
-
async function enqueueBatchedMigration(identifier) {
|
|
230
|
+
export async function enqueueBatchedMigration(identifier) {
|
|
264
231
|
assertRunner(runner);
|
|
265
232
|
await runner.enqueueBatchedMigration(identifier);
|
|
266
233
|
}
|
|
267
|
-
exports.enqueueBatchedMigration = enqueueBatchedMigration;
|
|
268
234
|
/**
|
|
269
235
|
* Given a batched migration identifier like `20230406184103_migration`,
|
|
270
236
|
* synchronously runs it to completion. An error will be thrown if the final
|
|
@@ -273,9 +239,8 @@ exports.enqueueBatchedMigration = enqueueBatchedMigration;
|
|
|
273
239
|
* @param identifier The identifier of the batched migration to finalize.
|
|
274
240
|
* @param options Options for finalizing the batched migration.
|
|
275
241
|
*/
|
|
276
|
-
async function finalizeBatchedMigration(identifier, options) {
|
|
242
|
+
export async function finalizeBatchedMigration(identifier, options) {
|
|
277
243
|
assertRunner(runner);
|
|
278
244
|
await runner.finalizeBatchedMigration(identifier, options);
|
|
279
245
|
}
|
|
280
|
-
exports.finalizeBatchedMigration = finalizeBatchedMigration;
|
|
281
246
|
//# sourceMappingURL=batched-migrations-runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batched-migrations-runner.js","sourceRoot":"","sources":["../../src/batched-migrations/batched-migrations-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { loadSqlEquiv, queryOptionalRow } from '@prairielearn/postgres';\nimport { doWithLock } from '@prairielearn/named-locks';\n\nimport { MigrationFile, readAndValidateMigrationsFromDirectories } from '../load-migrations';\nimport {\n BatchedMigrationRowSchema,\n BatchedMigrationRow,\n insertBatchedMigration,\n BatchedMigrationStatus,\n selectBatchedMigrationForTimestamp,\n updateBatchedMigrationStatus,\n BatchedMigrationImplementation,\n validateBatchedMigrationImplementation,\n} from './batched-migration';\nimport { BatchedMigrationRunner } from './batched-migration-runner';\n\nconst sql = loadSqlEquiv(__filename);\n\nconst DEFAULT_MIN_VALUE = 1n;\nconst DEFAULT_BATCH_SIZE = 1_000;\nconst DEFAULT_WORK_DURATION_MS = 60_000;\nconst DEFAULT_SLEEP_DURATION_MS = 30_000;\nconst EXTENSIONS = ['.js', '.ts', '.mjs', '.mts'];\n\ninterface BatchedMigrationRunnerOptions {\n project: string;\n directories: string[];\n}\n\ninterface BatchedMigrationStartOptions {\n workDurationMs?: number;\n sleepDurationMs?: number;\n}\n\ninterface BatchedMigrationFinalizeOptions {\n logProgress?: boolean;\n}\n\nexport class BatchedMigrationsRunner extends EventEmitter {\n private readonly options: BatchedMigrationRunnerOptions;\n private readonly lockName: string;\n private running = false;\n private migrationFiles: MigrationFile[] | null = null;\n private abortController = new AbortController();\n\n constructor(options: BatchedMigrationRunnerOptions) {\n super();\n this.options = options;\n this.lockName = `batched-migrations:${this.options.project}`;\n }\n\n private lockNameForTimestamp(timestamp: string) {\n return `${this.lockName}:${timestamp}`;\n }\n\n private getMigrationFiles = async () => {\n if (!this.migrationFiles) {\n this.migrationFiles = await readAndValidateMigrationsFromDirectories(\n this.options.directories,\n EXTENSIONS,\n );\n }\n return this.migrationFiles;\n };\n\n private async getMigrationForIdentifier(identifier: string): Promise<MigrationFile | null> {\n const timestamp = identifier.split('_')[0];\n\n const migrationFiles = await this.getMigrationFiles();\n const migrationFile = migrationFiles.find((m) => m.timestamp === timestamp);\n return migrationFile ?? null;\n }\n\n /**\n * Loads the implementation for the migration with the given identifier. The identifier\n * must start with a 14-character timestamp. It may optionally be followed by\n * an underscore with additional characters, which are ignored. These should\n * typically be used to provide a human-readable name for the migration.\n */\n private async loadMigrationImplementation(migrationFile: MigrationFile) {\n // We use dynamic imports to handle both CJS and ESM modules.\n const migrationModulePath = path.join(migrationFile.directory, migrationFile.filename);\n const migrationModule = await import(migrationModulePath);\n\n const migrationImplementation = migrationModule.default as BatchedMigrationImplementation;\n validateBatchedMigrationImplementation(migrationImplementation);\n return migrationImplementation;\n }\n\n async enqueueBatchedMigration(identifier: string) {\n const migrationFile = await this.getMigrationForIdentifier(identifier);\n if (!migrationFile) {\n throw new Error(`No migration found for identifier ${identifier}`);\n }\n\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n const migrationParameters = await migrationImplementation.getParameters();\n\n // If `max` is null, that implies that there are no rows to process, so\n // we can immediately mark the migration as finished.\n const status: BatchedMigrationStatus =\n migrationParameters.max === null ? 'succeeded' : 'pending';\n\n const minValue = BigInt(migrationParameters.min ?? DEFAULT_MIN_VALUE);\n const maxValue = BigInt(migrationParameters.max ?? minValue);\n const batchSize = migrationParameters.batchSize ?? DEFAULT_BATCH_SIZE;\n\n await insertBatchedMigration({\n project: this.options.project,\n filename: migrationFile.filename,\n timestamp: migrationFile.timestamp,\n batch_size: batchSize,\n min_value: minValue,\n max_value: maxValue,\n status,\n });\n }\n\n async finalizeBatchedMigration(identifier: string, options?: BatchedMigrationFinalizeOptions) {\n const timestamp = identifier.split('_')[0];\n\n let migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);\n\n if (migration.status === 'succeeded') return;\n\n // If the migration isn't already in the finalizing state, mark it as such.\n if (migration.status !== 'finalizing') {\n migration = await updateBatchedMigrationStatus(migration.id, 'finalizing');\n }\n\n await doWithLock(this.lockNameForTimestamp(timestamp), { autoRenew: true }, async () => {\n const migrationFile = await this.getMigrationForIdentifier(identifier);\n if (!migrationFile) {\n throw new Error(`No migration found for identifier ${identifier}`);\n }\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n\n const runner = new BatchedMigrationRunner(migration, migrationImplementation, {\n // Always log progress unless explicitly disabled.\n logProgress: options?.logProgress ?? true,\n });\n\n // Because we don't give any arguments to `run()`, it will run until it\n // has attempted every job.\n await runner.run();\n });\n\n migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);\n\n if (migration.status === 'succeeded') return;\n\n throw new Error(\n `Expected batched migration with identifier ${identifier} to be marked as 'succeeded', but it is '${migration.status}'.`,\n );\n }\n\n start(options: BatchedMigrationStartOptions = {}) {\n if (this.running) {\n throw new Error('BatchedMigrationsRunner is already running');\n }\n\n this.loop(options);\n }\n\n async loop({ workDurationMs, sleepDurationMs }: BatchedMigrationStartOptions) {\n workDurationMs ??= DEFAULT_WORK_DURATION_MS;\n sleepDurationMs ??= DEFAULT_SLEEP_DURATION_MS;\n\n this.running = true;\n while (this.running) {\n if (this.abortController.signal.aborted) {\n // We assign this here so that `stop()` can tell when this loop is done\n // processing jobs.\n this.running = false;\n return;\n }\n\n let didWork = false;\n try {\n didWork = await this.maybePerformWork(workDurationMs);\n } catch (err) {\n this.emit('error', err);\n }\n\n // If we did work, we'll immediately try again since there's probably more\n // work to be done. If not, we'll sleep for a while - maybe some more work\n // will become available!\n if (!didWork) {\n // We provide the signal here so that we can more quickly stop things\n // when we're shutting down.\n try {\n await sleep(sleepDurationMs, null, { ref: false, signal: this.abortController.signal });\n } catch (err) {\n // We don't care about errors here, they should only ever occur when\n // the AbortController is aborted. Continue to the next iteration of\n // the loop so we can shut down.\n continue;\n }\n }\n }\n }\n\n private async getOrStartMigration(): Promise<BatchedMigrationRow | null> {\n return doWithLock(\n this.lockName,\n {\n // Don't fail if the lock couldn't be acquired immediately.\n onNotAcquired: () => null,\n },\n async () => {\n let migration = await queryOptionalRow(\n sql.select_running_migration,\n { project: this.options.project },\n BatchedMigrationRowSchema,\n );\n\n if (!migration) {\n migration = await queryOptionalRow(\n sql.start_next_pending_migration,\n { project: this.options.project },\n BatchedMigrationRowSchema,\n );\n }\n\n return migration;\n },\n );\n }\n\n async maybePerformWork(durationMs: number): Promise<boolean> {\n const migration = await this.getOrStartMigration();\n if (!migration) {\n // No work to do. Handle this case.\n return false;\n }\n\n // This server may not yet know about the current running migration. If\n // that's the case, we'll just skip it for now.\n const migrationFile = await this.getMigrationForIdentifier(migration.timestamp);\n if (!migrationFile) {\n return false;\n }\n\n let didWork = false;\n await doWithLock(\n this.lockNameForTimestamp(migrationFile.timestamp),\n {\n autoRenew: true,\n // Do nothing if the lock could not immediately be acquired.\n onNotAcquired: () => null,\n },\n async () => {\n didWork = true;\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n\n const runner = new BatchedMigrationRunner(migration, migrationImplementation);\n\n try {\n await runner.run({ signal: this.abortController.signal, durationMs });\n } catch (err) {\n this.emit('error', err);\n }\n },\n );\n\n return didWork;\n }\n\n async stop() {\n this.abortController.abort();\n\n // Spin until we're no longer running.\n while (this.running) {\n await sleep(1000);\n }\n }\n}\n\nlet runner: BatchedMigrationsRunner | null = null;\n\nfunction assertRunner(\n runner: BatchedMigrationsRunner | null,\n): asserts runner is BatchedMigrationsRunner {\n if (!runner) throw new Error('Batched migrations not initialized');\n}\n\nexport function initBatchedMigrations(options: BatchedMigrationRunnerOptions) {\n if (runner) throw new Error('Batched migrations already initialized');\n runner = new BatchedMigrationsRunner(options);\n return runner;\n}\n\nexport function startBatchedMigrations(options: BatchedMigrationStartOptions = {}) {\n assertRunner(runner);\n runner.start(options);\n return runner;\n}\n\nexport async function stopBatchedMigrations() {\n assertRunner(runner);\n await runner.stop();\n runner = null;\n}\n\n/**\n * Given a batched migration identifier like `20230406184103_migration`,\n * enqueues it for execution by creating a row in the `batched_migrations`\n * table.\n *\n * Despite taking a full identifier, only the timestamp is used to uniquely\n * identify the batched migration. The remaining part is just used to make\n * calls more human-readable.\n *\n * @param identifier The identifier of the batched migration to enqueue.\n */\nexport async function enqueueBatchedMigration(identifier: string) {\n assertRunner(runner);\n await runner.enqueueBatchedMigration(identifier);\n}\n\n/**\n * Given a batched migration identifier like `20230406184103_migration`,\n * synchronously runs it to completion. An error will be thrown if the final\n * status of the migration is not `succeeded`.\n *\n * @param identifier The identifier of the batched migration to finalize.\n * @param options Options for finalizing the batched migration.\n */\nexport async function finalizeBatchedMigration(\n identifier: string,\n options?: BatchedMigrationFinalizeOptions,\n) {\n assertRunner(runner);\n await runner.finalizeBatchedMigration(identifier, options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"batched-migrations-runner.js","sourceRoot":"","sources":["../../src/batched-migrations/batched-migrations-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { loadSqlEquiv, queryOptionalRow } from '@prairielearn/postgres';\nimport { doWithLock } from '@prairielearn/named-locks';\n\nimport { MigrationFile, readAndValidateMigrationsFromDirectories } from '../load-migrations.js';\nimport {\n BatchedMigrationRowSchema,\n BatchedMigrationRow,\n insertBatchedMigration,\n BatchedMigrationStatus,\n selectBatchedMigrationForTimestamp,\n updateBatchedMigrationStatus,\n BatchedMigrationImplementation,\n validateBatchedMigrationImplementation,\n} from './batched-migration.js';\nimport { BatchedMigrationRunner } from './batched-migration-runner.js';\n\nconst sql = loadSqlEquiv(import.meta.filename);\n\nconst DEFAULT_MIN_VALUE = 1n;\nconst DEFAULT_BATCH_SIZE = 1_000;\nconst DEFAULT_WORK_DURATION_MS = 60_000;\nconst DEFAULT_SLEEP_DURATION_MS = 30_000;\nconst EXTENSIONS = ['.js', '.ts', '.mjs', '.mts'];\n\ninterface BatchedMigrationRunnerOptions {\n project: string;\n directories: string[];\n}\n\ninterface BatchedMigrationStartOptions {\n workDurationMs?: number;\n sleepDurationMs?: number;\n}\n\ninterface BatchedMigrationFinalizeOptions {\n logProgress?: boolean;\n}\n\nexport class BatchedMigrationsRunner extends EventEmitter {\n private readonly options: BatchedMigrationRunnerOptions;\n private readonly lockName: string;\n private running = false;\n private migrationFiles: MigrationFile[] | null = null;\n private abortController = new AbortController();\n\n constructor(options: BatchedMigrationRunnerOptions) {\n super();\n this.options = options;\n this.lockName = `batched-migrations:${this.options.project}`;\n }\n\n private lockNameForTimestamp(timestamp: string) {\n return `${this.lockName}:${timestamp}`;\n }\n\n private getMigrationFiles = async () => {\n if (!this.migrationFiles) {\n this.migrationFiles = await readAndValidateMigrationsFromDirectories(\n this.options.directories,\n EXTENSIONS,\n );\n }\n return this.migrationFiles;\n };\n\n private async getMigrationForIdentifier(identifier: string): Promise<MigrationFile | null> {\n const timestamp = identifier.split('_')[0];\n\n const migrationFiles = await this.getMigrationFiles();\n const migrationFile = migrationFiles.find((m) => m.timestamp === timestamp);\n return migrationFile ?? null;\n }\n\n /**\n * Loads the implementation for the migration with the given identifier. The identifier\n * must start with a 14-character timestamp. It may optionally be followed by\n * an underscore with additional characters, which are ignored. These should\n * typically be used to provide a human-readable name for the migration.\n */\n private async loadMigrationImplementation(migrationFile: MigrationFile) {\n // We use dynamic imports to handle both CJS and ESM modules.\n const migrationModulePath = path.join(migrationFile.directory, migrationFile.filename);\n const migrationModule = await import(migrationModulePath);\n\n const migrationImplementation = migrationModule.default as BatchedMigrationImplementation;\n validateBatchedMigrationImplementation(migrationImplementation);\n return migrationImplementation;\n }\n\n async enqueueBatchedMigration(identifier: string) {\n const migrationFile = await this.getMigrationForIdentifier(identifier);\n if (!migrationFile) {\n throw new Error(`No migration found for identifier ${identifier}`);\n }\n\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n const migrationParameters = await migrationImplementation.getParameters();\n\n // If `max` is null, that implies that there are no rows to process, so\n // we can immediately mark the migration as finished.\n const status: BatchedMigrationStatus =\n migrationParameters.max === null ? 'succeeded' : 'pending';\n\n const minValue = BigInt(migrationParameters.min ?? DEFAULT_MIN_VALUE);\n const maxValue = BigInt(migrationParameters.max ?? minValue);\n const batchSize = migrationParameters.batchSize ?? DEFAULT_BATCH_SIZE;\n\n await insertBatchedMigration({\n project: this.options.project,\n filename: migrationFile.filename,\n timestamp: migrationFile.timestamp,\n batch_size: batchSize,\n min_value: minValue,\n max_value: maxValue,\n status,\n });\n }\n\n async finalizeBatchedMigration(identifier: string, options?: BatchedMigrationFinalizeOptions) {\n const timestamp = identifier.split('_')[0];\n\n let migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);\n\n if (migration.status === 'succeeded') return;\n\n // If the migration isn't already in the finalizing state, mark it as such.\n if (migration.status !== 'finalizing') {\n migration = await updateBatchedMigrationStatus(migration.id, 'finalizing');\n }\n\n await doWithLock(this.lockNameForTimestamp(timestamp), { autoRenew: true }, async () => {\n const migrationFile = await this.getMigrationForIdentifier(identifier);\n if (!migrationFile) {\n throw new Error(`No migration found for identifier ${identifier}`);\n }\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n\n const runner = new BatchedMigrationRunner(migration, migrationImplementation, {\n // Always log progress unless explicitly disabled.\n logProgress: options?.logProgress ?? true,\n });\n\n // Because we don't give any arguments to `run()`, it will run until it\n // has attempted every job.\n await runner.run();\n });\n\n migration = await selectBatchedMigrationForTimestamp(this.options.project, timestamp);\n\n if (migration.status === 'succeeded') return;\n\n throw new Error(\n `Expected batched migration with identifier ${identifier} to be marked as 'succeeded', but it is '${migration.status}'.`,\n );\n }\n\n start(options: BatchedMigrationStartOptions = {}) {\n if (this.running) {\n throw new Error('BatchedMigrationsRunner is already running');\n }\n\n this.loop(options);\n }\n\n async loop({ workDurationMs, sleepDurationMs }: BatchedMigrationStartOptions) {\n workDurationMs ??= DEFAULT_WORK_DURATION_MS;\n sleepDurationMs ??= DEFAULT_SLEEP_DURATION_MS;\n\n this.running = true;\n while (this.running) {\n if (this.abortController.signal.aborted) {\n // We assign this here so that `stop()` can tell when this loop is done\n // processing jobs.\n this.running = false;\n return;\n }\n\n let didWork = false;\n try {\n didWork = await this.maybePerformWork(workDurationMs);\n } catch (err) {\n this.emit('error', err);\n }\n\n // If we did work, we'll immediately try again since there's probably more\n // work to be done. If not, we'll sleep for a while - maybe some more work\n // will become available!\n if (!didWork) {\n // We provide the signal here so that we can more quickly stop things\n // when we're shutting down.\n try {\n await sleep(sleepDurationMs, null, { ref: false, signal: this.abortController.signal });\n } catch (err) {\n // We don't care about errors here, they should only ever occur when\n // the AbortController is aborted. Continue to the next iteration of\n // the loop so we can shut down.\n continue;\n }\n }\n }\n }\n\n private async getOrStartMigration(): Promise<BatchedMigrationRow | null> {\n return doWithLock(\n this.lockName,\n {\n // Don't fail if the lock couldn't be acquired immediately.\n onNotAcquired: () => null,\n },\n async () => {\n let migration = await queryOptionalRow(\n sql.select_running_migration,\n { project: this.options.project },\n BatchedMigrationRowSchema,\n );\n\n if (!migration) {\n migration = await queryOptionalRow(\n sql.start_next_pending_migration,\n { project: this.options.project },\n BatchedMigrationRowSchema,\n );\n }\n\n return migration;\n },\n );\n }\n\n async maybePerformWork(durationMs: number): Promise<boolean> {\n const migration = await this.getOrStartMigration();\n if (!migration) {\n // No work to do. Handle this case.\n return false;\n }\n\n // This server may not yet know about the current running migration. If\n // that's the case, we'll just skip it for now.\n const migrationFile = await this.getMigrationForIdentifier(migration.timestamp);\n if (!migrationFile) {\n return false;\n }\n\n let didWork = false;\n await doWithLock(\n this.lockNameForTimestamp(migrationFile.timestamp),\n {\n autoRenew: true,\n // Do nothing if the lock could not immediately be acquired.\n onNotAcquired: () => null,\n },\n async () => {\n didWork = true;\n const migrationImplementation = await this.loadMigrationImplementation(migrationFile);\n\n const runner = new BatchedMigrationRunner(migration, migrationImplementation);\n\n try {\n await runner.run({ signal: this.abortController.signal, durationMs });\n } catch (err) {\n this.emit('error', err);\n }\n },\n );\n\n return didWork;\n }\n\n async stop() {\n this.abortController.abort();\n\n // Spin until we're no longer running.\n while (this.running) {\n await sleep(1000);\n }\n }\n}\n\nlet runner: BatchedMigrationsRunner | null = null;\n\nfunction assertRunner(\n runner: BatchedMigrationsRunner | null,\n): asserts runner is BatchedMigrationsRunner {\n if (!runner) throw new Error('Batched migrations not initialized');\n}\n\nexport function initBatchedMigrations(options: BatchedMigrationRunnerOptions) {\n if (runner) throw new Error('Batched migrations already initialized');\n runner = new BatchedMigrationsRunner(options);\n return runner;\n}\n\nexport function startBatchedMigrations(options: BatchedMigrationStartOptions = {}) {\n assertRunner(runner);\n runner.start(options);\n return runner;\n}\n\nexport async function stopBatchedMigrations() {\n assertRunner(runner);\n await runner.stop();\n runner = null;\n}\n\n/**\n * Given a batched migration identifier like `20230406184103_migration`,\n * enqueues it for execution by creating a row in the `batched_migrations`\n * table.\n *\n * Despite taking a full identifier, only the timestamp is used to uniquely\n * identify the batched migration. The remaining part is just used to make\n * calls more human-readable.\n *\n * @param identifier The identifier of the batched migration to enqueue.\n */\nexport async function enqueueBatchedMigration(identifier: string) {\n assertRunner(runner);\n await runner.enqueueBatchedMigration(identifier);\n}\n\n/**\n * Given a batched migration identifier like `20230406184103_migration`,\n * synchronously runs it to completion. An error will be thrown if the final\n * status of the migration is not `succeeded`.\n *\n * @param identifier The identifier of the batched migration to finalize.\n * @param options Options for finalizing the batched migration.\n */\nexport async function finalizeBatchedMigration(\n identifier: string,\n options?: BatchedMigrationFinalizeOptions,\n) {\n assertRunner(runner);\n await runner.finalizeBatchedMigration(identifier, options);\n}\n"]}
|
|
@@ -1,41 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
const chai_1 = __importStar(require("chai"));
|
|
30
|
-
const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
|
|
31
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
-
const postgres_1 = require("@prairielearn/postgres");
|
|
33
|
-
const namedLocks = __importStar(require("@prairielearn/named-locks"));
|
|
34
|
-
const index_1 = require("../index");
|
|
35
|
-
const batched_migrations_runner_1 = require("./batched-migrations-runner");
|
|
36
|
-
const batched_migration_1 = require("./batched-migration");
|
|
37
|
-
chai_1.default.use(chai_as_promised_1.default);
|
|
38
|
-
const postgresTestUtils = (0, postgres_1.makePostgresTestUtils)({
|
|
1
|
+
import chai, { assert } from 'chai';
|
|
2
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { makePostgresTestUtils } from '@prairielearn/postgres';
|
|
5
|
+
import * as namedLocks from '@prairielearn/named-locks';
|
|
6
|
+
import { SCHEMA_MIGRATIONS_PATH, init } from '../index.js';
|
|
7
|
+
import { BatchedMigrationsRunner } from './batched-migrations-runner.js';
|
|
8
|
+
import { selectAllBatchedMigrations } from './batched-migration.js';
|
|
9
|
+
chai.use(chaiAsPromised);
|
|
10
|
+
const postgresTestUtils = makePostgresTestUtils({
|
|
39
11
|
database: 'prairielearn_migrations',
|
|
40
12
|
});
|
|
41
13
|
describe('BatchedMigrationsRunner', () => {
|
|
@@ -44,7 +16,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
44
16
|
await namedLocks.init(postgresTestUtils.getPoolConfig(), (err) => {
|
|
45
17
|
throw err;
|
|
46
18
|
});
|
|
47
|
-
await
|
|
19
|
+
await init([SCHEMA_MIGRATIONS_PATH], 'prairielearn_migrations');
|
|
48
20
|
});
|
|
49
21
|
afterEach(async () => {
|
|
50
22
|
await postgresTestUtils.resetDatabase();
|
|
@@ -54,63 +26,63 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
54
26
|
await postgresTestUtils.dropDatabase();
|
|
55
27
|
});
|
|
56
28
|
it('enqueues migrations', async () => {
|
|
57
|
-
const runner = new
|
|
29
|
+
const runner = new BatchedMigrationsRunner({
|
|
58
30
|
project: 'test',
|
|
59
|
-
directories: [
|
|
31
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
60
32
|
});
|
|
61
33
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
62
34
|
await runner.enqueueBatchedMigration('20230406184107_failing_migration');
|
|
63
35
|
await runner.enqueueBatchedMigration('20230407230446_no_rows_migration');
|
|
64
|
-
const migrations = await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
36
|
+
const migrations = await selectAllBatchedMigrations('test');
|
|
37
|
+
assert.lengthOf(migrations, 3);
|
|
38
|
+
assert.equal(migrations[0].timestamp, '20230406184103');
|
|
39
|
+
assert.equal(migrations[0].filename, '20230406184103_successful_migration.ts');
|
|
40
|
+
assert.equal(migrations[0].status, 'pending');
|
|
41
|
+
assert.equal(migrations[1].timestamp, '20230406184107');
|
|
42
|
+
assert.equal(migrations[1].filename, '20230406184107_failing_migration.ts');
|
|
43
|
+
assert.equal(migrations[1].status, 'pending');
|
|
44
|
+
assert.equal(migrations[2].timestamp, '20230407230446');
|
|
45
|
+
assert.equal(migrations[2].filename, '20230407230446_no_rows_migration.ts');
|
|
46
|
+
assert.equal(migrations[2].status, 'succeeded');
|
|
75
47
|
});
|
|
76
48
|
it('safely enqueues migrations multiple times', async () => {
|
|
77
|
-
const runner = new
|
|
49
|
+
const runner = new BatchedMigrationsRunner({
|
|
78
50
|
project: 'test',
|
|
79
|
-
directories: [
|
|
51
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
80
52
|
});
|
|
81
53
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
82
54
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
83
55
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
84
|
-
const migrations = await
|
|
85
|
-
|
|
56
|
+
const migrations = await selectAllBatchedMigrations('test');
|
|
57
|
+
assert.lengthOf(migrations, 1);
|
|
86
58
|
});
|
|
87
59
|
it('finalizes a successful migration', async () => {
|
|
88
|
-
const runner = new
|
|
60
|
+
const runner = new BatchedMigrationsRunner({
|
|
89
61
|
project: 'test',
|
|
90
|
-
directories: [
|
|
62
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
91
63
|
});
|
|
92
64
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
93
65
|
await runner.finalizeBatchedMigration('20230406184103_successful_migration', {
|
|
94
66
|
logProgress: false,
|
|
95
67
|
});
|
|
96
|
-
const migrations = await
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
68
|
+
const migrations = await selectAllBatchedMigrations('test');
|
|
69
|
+
assert.lengthOf(migrations, 1);
|
|
70
|
+
assert.equal(migrations[0].timestamp, '20230406184103');
|
|
71
|
+
assert.equal(migrations[0].status, 'succeeded');
|
|
100
72
|
});
|
|
101
73
|
it('finalizes a failing migration', async () => {
|
|
102
|
-
const runner = new
|
|
74
|
+
const runner = new BatchedMigrationsRunner({
|
|
103
75
|
project: 'test',
|
|
104
|
-
directories: [
|
|
76
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
105
77
|
});
|
|
106
78
|
await runner.enqueueBatchedMigration('20230406184107_failing_migration');
|
|
107
|
-
await
|
|
79
|
+
await assert.isRejected(runner.finalizeBatchedMigration('20230406184107_failing_migration', {
|
|
108
80
|
logProgress: false,
|
|
109
81
|
}), "but it is 'failed'");
|
|
110
|
-
const migrations = await
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
82
|
+
const migrations = await selectAllBatchedMigrations('test');
|
|
83
|
+
assert.lengthOf(migrations, 1);
|
|
84
|
+
assert.equal(migrations[0].timestamp, '20230406184107');
|
|
85
|
+
assert.equal(migrations[0].status, 'failed');
|
|
114
86
|
});
|
|
115
87
|
});
|
|
116
88
|
//# sourceMappingURL=batched-migrations-runner.test.js.map
|