@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.
Files changed (60) hide show
  1. package/.mocharc.cjs +3 -0
  2. package/CHANGELOG.md +14 -0
  3. package/dist/batched-migrations/batched-migration-job.js +18 -22
  4. package/dist/batched-migrations/batched-migration-job.js.map +1 -1
  5. package/dist/batched-migrations/batched-migration-runner.d.ts +2 -2
  6. package/dist/batched-migrations/batched-migration-runner.js +22 -26
  7. package/dist/batched-migrations/batched-migration-runner.js.map +1 -1
  8. package/dist/batched-migrations/batched-migration-runner.test.js +53 -78
  9. package/dist/batched-migrations/batched-migration-runner.test.js.map +1 -1
  10. package/dist/batched-migrations/batched-migration.js +30 -41
  11. package/dist/batched-migrations/batched-migration.js.map +1 -1
  12. package/dist/batched-migrations/batched-migrations-runner.d.ts +1 -1
  13. package/dist/batched-migrations/batched-migrations-runner.js +32 -67
  14. package/dist/batched-migrations/batched-migrations-runner.js.map +1 -1
  15. package/dist/batched-migrations/batched-migrations-runner.test.js +41 -69
  16. package/dist/batched-migrations/batched-migrations-runner.test.js.map +1 -1
  17. package/dist/batched-migrations/fixtures/20230406184103_successful_migration.js +2 -4
  18. package/dist/batched-migrations/fixtures/20230406184103_successful_migration.js.map +1 -1
  19. package/dist/batched-migrations/fixtures/20230406184107_failing_migration.d.ts +9 -0
  20. package/dist/batched-migrations/fixtures/20230406184107_failing_migration.js +14 -0
  21. package/dist/batched-migrations/fixtures/20230406184107_failing_migration.js.map +1 -0
  22. package/dist/batched-migrations/fixtures/20230407230446_no_rows_migration.js +2 -4
  23. package/dist/batched-migrations/fixtures/20230407230446_no_rows_migration.js.map +1 -1
  24. package/dist/batched-migrations/index.d.ts +3 -3
  25. package/dist/batched-migrations/index.js +3 -17
  26. package/dist/batched-migrations/index.js.map +1 -1
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +4 -22
  29. package/dist/index.js.map +1 -1
  30. package/dist/load-migrations.js +6 -16
  31. package/dist/load-migrations.js.map +1 -1
  32. package/dist/load-migrations.test.js +16 -44
  33. package/dist/load-migrations.test.js.map +1 -1
  34. package/dist/migrations/fixtures/20230407210430_insert_user.js +3 -6
  35. package/dist/migrations/fixtures/20230407210430_insert_user.js.map +1 -1
  36. package/dist/migrations/index.d.ts +1 -1
  37. package/dist/migrations/index.js +1 -5
  38. package/dist/migrations/index.js.map +1 -1
  39. package/dist/migrations/migrations.d.ts +1 -1
  40. package/dist/migrations/migrations.js +25 -57
  41. package/dist/migrations/migrations.js.map +1 -1
  42. package/dist/migrations/migrations.test.js +12 -17
  43. package/dist/migrations/migrations.test.js.map +1 -1
  44. package/package.json +8 -7
  45. package/src/batched-migrations/batched-migration-job.ts +1 -1
  46. package/src/batched-migrations/batched-migration-runner.test.ts +4 -4
  47. package/src/batched-migrations/batched-migration-runner.ts +3 -3
  48. package/src/batched-migrations/batched-migration.ts +1 -1
  49. package/src/batched-migrations/batched-migrations-runner.test.ts +8 -8
  50. package/src/batched-migrations/batched-migrations-runner.ts +4 -4
  51. package/src/batched-migrations/fixtures/20230406184103_successful_migration.ts +1 -1
  52. package/src/batched-migrations/fixtures/{20230406184107_failing_migration.js → 20230406184107_failing_migration.ts} +2 -3
  53. package/src/batched-migrations/fixtures/20230407230446_no_rows_migration.ts +1 -1
  54. package/src/batched-migrations/index.ts +7 -7
  55. package/src/index.ts +7 -7
  56. package/src/load-migrations.test.ts +1 -1
  57. package/src/migrations/index.ts +1 -1
  58. package/src/migrations/migrations.test.ts +2 -2
  59. package/src/migrations/migrations.ts +2 -2
  60. package/tsconfig.json +4 -1
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.retryFailedBatchedMigrationJobs = exports.updateBatchedMigrationStatus = exports.selectBatchedMigrationForTimestamp = exports.selectBatchedMigration = exports.selectAllBatchedMigrations = exports.insertBatchedMigration = exports.validateBatchedMigrationImplementation = exports.makeBatchedMigration = exports.BatchedMigrationRowSchema = exports.BatchedMigrationStatusSchema = void 0;
4
- const postgres_1 = require("@prairielearn/postgres");
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
- exports.BatchedMigrationRowSchema = zod_1.z.object({
16
- id: zod_1.z.string(),
17
- project: zod_1.z.string(),
18
- filename: zod_1.z.string(),
19
- timestamp: zod_1.z.string(),
20
- batch_size: zod_1.z.number(),
21
- min_value: zod_1.z.bigint({ coerce: true }),
22
- max_value: zod_1.z.bigint({ coerce: true }),
23
- status: exports.BatchedMigrationStatusSchema,
24
- created_at: zod_1.z.date(),
25
- updated_at: zod_1.z.date(),
26
- started_at: zod_1.z.date().nullable(),
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
- exports.makeBatchedMigration = makeBatchedMigration;
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 (0, postgres_1.queryOptionalRow)(sql.insert_batched_migration, migration, exports.BatchedMigrationRowSchema);
44
+ export async function insertBatchedMigration(migration) {
45
+ return await queryOptionalRow(sql.insert_batched_migration, migration, BatchedMigrationRowSchema);
51
46
  }
52
- exports.insertBatchedMigration = insertBatchedMigration;
53
- async function selectAllBatchedMigrations(project) {
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
- exports.selectAllBatchedMigrations = selectAllBatchedMigrations;
57
- async function selectBatchedMigration(project, id) {
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
- exports.selectBatchedMigration = selectBatchedMigration;
61
- async function selectBatchedMigrationForTimestamp(project, timestamp) {
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
- exports.selectBatchedMigrationForTimestamp = selectBatchedMigrationForTimestamp;
65
- async function updateBatchedMigrationStatus(id, status) {
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
- exports.updateBatchedMigrationStatus = updateBatchedMigrationStatus;
69
- async function retryFailedBatchedMigrationJobs(project, id) {
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":"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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(__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
+ {"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,4 +1,4 @@
1
- /// <reference types="node" />
1
+ /// <reference types="node" resolution-mode="require"/>
2
2
  import { EventEmitter } from 'node:events';
3
3
  interface BatchedMigrationRunnerOptions {
4
4
  project: string;
@@ -1,47 +1,18 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
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 node_events_1.EventEmitter {
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 (0, load_migrations_1.readAndValidateMigrationsFromDirectories)(this.options.directories, EXTENSIONS);
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 = node_path_1.default.join(migrationFile.directory, migrationFile.filename);
79
- const migrationModule = await Promise.resolve(`${migrationModulePath}`).then(s => __importStar(require(s)));
49
+ const migrationModulePath = path.join(migrationFile.directory, migrationFile.filename);
50
+ const migrationModule = await import(migrationModulePath);
80
51
  const migrationImplementation = migrationModule.default;
81
- (0, batched_migration_1.validateBatchedMigrationImplementation)(migrationImplementation);
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 (0, batched_migration_1.insertBatchedMigration)({
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 (0, batched_migration_1.selectBatchedMigrationForTimestamp)(this.options.project, timestamp);
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 (0, batched_migration_1.updateBatchedMigrationStatus)(migration.id, 'finalizing');
85
+ migration = await updateBatchedMigrationStatus(migration.id, 'finalizing');
115
86
  }
116
- await (0, named_locks_1.doWithLock)(this.lockNameForTimestamp(timestamp), { autoRenew: true }, async () => {
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 batched_migration_runner_1.BatchedMigrationRunner(migration, migrationImplementation, {
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 (0, batched_migration_1.selectBatchedMigrationForTimestamp)(this.options.project, timestamp);
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 (0, promises_1.setTimeout)(sleepDurationMs, null, { ref: false, signal: this.abortController.signal });
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 (0, named_locks_1.doWithLock)(this.lockName, {
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 (0, postgres_1.queryOptionalRow)(sql.select_running_migration, { project: this.options.project }, batched_migration_1.BatchedMigrationRowSchema);
153
+ let migration = await queryOptionalRow(sql.select_running_migration, { project: this.options.project }, BatchedMigrationRowSchema);
183
154
  if (!migration) {
184
- migration = await (0, postgres_1.queryOptionalRow)(sql.start_next_pending_migration, { project: this.options.project }, batched_migration_1.BatchedMigrationRowSchema);
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 (0, named_locks_1.doWithLock)(this.lockNameForTimestamp(migrationFile.timestamp), {
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 batched_migration_runner_1.BatchedMigrationRunner(migration, migrationImplementation);
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 (0, promises_1.setTimeout)(1000);
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
- exports.initBatchedMigrations = initBatchedMigrations;
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
- exports.startBatchedMigrations = startBatchedMigrations;
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
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
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
- 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 (0, index_1.init)([index_1.SCHEMA_MIGRATIONS_PATH], 'prairielearn_migrations');
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 batched_migrations_runner_1.BatchedMigrationsRunner({
29
+ const runner = new BatchedMigrationsRunner({
58
30
  project: 'test',
59
- directories: [node_path_1.default.join(__dirname, 'fixtures')],
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 (0, batched_migration_1.selectAllBatchedMigrations)('test');
65
- chai_1.assert.lengthOf(migrations, 3);
66
- chai_1.assert.equal(migrations[0].timestamp, '20230406184103');
67
- chai_1.assert.equal(migrations[0].filename, '20230406184103_successful_migration.ts');
68
- chai_1.assert.equal(migrations[0].status, 'pending');
69
- chai_1.assert.equal(migrations[1].timestamp, '20230406184107');
70
- chai_1.assert.equal(migrations[1].filename, '20230406184107_failing_migration.js');
71
- chai_1.assert.equal(migrations[1].status, 'pending');
72
- chai_1.assert.equal(migrations[2].timestamp, '20230407230446');
73
- chai_1.assert.equal(migrations[2].filename, '20230407230446_no_rows_migration.ts');
74
- chai_1.assert.equal(migrations[2].status, 'succeeded');
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 batched_migrations_runner_1.BatchedMigrationsRunner({
49
+ const runner = new BatchedMigrationsRunner({
78
50
  project: 'test',
79
- directories: [node_path_1.default.join(__dirname, 'fixtures')],
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 (0, batched_migration_1.selectAllBatchedMigrations)('test');
85
- chai_1.assert.lengthOf(migrations, 1);
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 batched_migrations_runner_1.BatchedMigrationsRunner({
60
+ const runner = new BatchedMigrationsRunner({
89
61
  project: 'test',
90
- directories: [node_path_1.default.join(__dirname, 'fixtures')],
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 (0, batched_migration_1.selectAllBatchedMigrations)('test');
97
- chai_1.assert.lengthOf(migrations, 1);
98
- chai_1.assert.equal(migrations[0].timestamp, '20230406184103');
99
- chai_1.assert.equal(migrations[0].status, 'succeeded');
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 batched_migrations_runner_1.BatchedMigrationsRunner({
74
+ const runner = new BatchedMigrationsRunner({
103
75
  project: 'test',
104
- directories: [node_path_1.default.join(__dirname, 'fixtures')],
76
+ directories: [path.join(import.meta.dirname, 'fixtures')],
105
77
  });
106
78
  await runner.enqueueBatchedMigration('20230406184107_failing_migration');
107
- await chai_1.assert.isRejected(runner.finalizeBatchedMigration('20230406184107_failing_migration', {
79
+ await assert.isRejected(runner.finalizeBatchedMigration('20230406184107_failing_migration', {
108
80
  logProgress: false,
109
81
  }), "but it is 'failed'");
110
- const migrations = await (0, batched_migration_1.selectAllBatchedMigrations)('test');
111
- chai_1.assert.lengthOf(migrations, 1);
112
- chai_1.assert.equal(migrations[0].timestamp, '20230406184107');
113
- chai_1.assert.equal(migrations[0].status, 'failed');
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