@prairielearn/migrations 2.1.0 → 3.0.1
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 +25 -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 +14 -13
- package/src/batched-migrations/batched-migration-job.ts +2 -1
- package/src/batched-migrations/batched-migration-runner.test.ts +8 -6
- package/src/batched-migrations/batched-migration-runner.ts +10 -9
- package/src/batched-migrations/batched-migration.ts +3 -2
- package/src/batched-migrations/batched-migrations-runner.test.ts +15 -12
- package/src/batched-migrations/batched-migrations-runner.ts +7 -5
- 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 +6 -5
- package/src/migrations/index.ts +1 -1
- package/src/migrations/migrations.test.ts +5 -3
- package/src/migrations/migrations.ts +6 -5
- package/tsconfig.json +4 -1
|
@@ -1,44 +1,15 @@
|
|
|
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.initWithLock = exports.getMigrationsToExecute = exports.init = void 0;
|
|
30
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
31
|
-
const path_1 = __importDefault(require("path"));
|
|
32
|
-
const namedLocks = __importStar(require("@prairielearn/named-locks"));
|
|
33
|
-
const logger_1 = require("@prairielearn/logger");
|
|
34
|
-
const sqldb = __importStar(require("@prairielearn/postgres"));
|
|
35
|
-
const error = __importStar(require("@prairielearn/error"));
|
|
36
|
-
const load_migrations_1 = require("../load-migrations");
|
|
37
|
-
const sql = sqldb.loadSqlEquiv(__filename);
|
|
38
|
-
async function init(directories, project) {
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import * as error from '@prairielearn/error';
|
|
4
|
+
import { logger } from '@prairielearn/logger';
|
|
5
|
+
import * as namedLocks from '@prairielearn/named-locks';
|
|
6
|
+
import * as sqldb from '@prairielearn/postgres';
|
|
7
|
+
import { parseAnnotations, readAndValidateMigrationsFromDirectories, sortMigrationFiles, } from '../load-migrations.js';
|
|
8
|
+
const sql = sqldb.loadSqlEquiv(import.meta.filename);
|
|
9
|
+
export async function init(directories, project) {
|
|
39
10
|
const migrationDirectories = Array.isArray(directories) ? directories : [directories];
|
|
40
11
|
const lockName = 'migrations';
|
|
41
|
-
|
|
12
|
+
logger.verbose(`Waiting for lock ${lockName}`);
|
|
42
13
|
await namedLocks.doWithLock(lockName, {
|
|
43
14
|
// Migrations *might* take a long time to run, so we'll enable automatic
|
|
44
15
|
// lock renewal so that our lock doesn't get killed by the Postgres
|
|
@@ -49,13 +20,12 @@ async function init(directories, project) {
|
|
|
49
20
|
// Postgres is locking a whole table, which is unacceptable in production.
|
|
50
21
|
autoRenew: true,
|
|
51
22
|
}, async () => {
|
|
52
|
-
|
|
23
|
+
logger.verbose(`Acquired lock ${lockName}`);
|
|
53
24
|
await initWithLock(migrationDirectories, project);
|
|
54
25
|
});
|
|
55
|
-
|
|
26
|
+
logger.verbose(`Released lock ${lockName}`);
|
|
56
27
|
}
|
|
57
|
-
|
|
58
|
-
function getMigrationsToExecute(migrationFiles, executedMigrations) {
|
|
28
|
+
export function getMigrationsToExecute(migrationFiles, executedMigrations) {
|
|
59
29
|
// If no migrations have ever been run, run them all.
|
|
60
30
|
if (executedMigrations.length === 0) {
|
|
61
31
|
return migrationFiles;
|
|
@@ -63,9 +33,8 @@ function getMigrationsToExecute(migrationFiles, executedMigrations) {
|
|
|
63
33
|
const executedMigrationTimestamps = new Set(executedMigrations.map((m) => m.timestamp));
|
|
64
34
|
return migrationFiles.filter((m) => !executedMigrationTimestamps.has(m.timestamp));
|
|
65
35
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
logger_1.logger.verbose('Starting DB schema migration');
|
|
36
|
+
export async function initWithLock(directories, project) {
|
|
37
|
+
logger.verbose('Starting DB schema migration');
|
|
69
38
|
// Create the migrations table if needed
|
|
70
39
|
await sqldb.queryAsync(sql.create_migrations_table, {});
|
|
71
40
|
// Apply necessary changes to the migrations table as needed.
|
|
@@ -74,7 +43,7 @@ async function initWithLock(directories, project) {
|
|
|
74
43
|
}
|
|
75
44
|
catch (err) {
|
|
76
45
|
if (err.routine === 'errorMissingColumn') {
|
|
77
|
-
|
|
46
|
+
logger.info('Altering migrations table');
|
|
78
47
|
await sqldb.queryAsync(sql.add_projects_column, {});
|
|
79
48
|
}
|
|
80
49
|
else {
|
|
@@ -86,7 +55,7 @@ async function initWithLock(directories, project) {
|
|
|
86
55
|
}
|
|
87
56
|
catch (err) {
|
|
88
57
|
if (err.routine === 'errorMissingColumn') {
|
|
89
|
-
|
|
58
|
+
logger.info('Altering migrations table again');
|
|
90
59
|
await sqldb.queryAsync(sql.add_timestamp_column, {});
|
|
91
60
|
}
|
|
92
61
|
else {
|
|
@@ -94,7 +63,7 @@ async function initWithLock(directories, project) {
|
|
|
94
63
|
}
|
|
95
64
|
}
|
|
96
65
|
let allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });
|
|
97
|
-
const migrationFiles = await
|
|
66
|
+
const migrationFiles = await readAndValidateMigrationsFromDirectories(directories, [
|
|
98
67
|
'.sql',
|
|
99
68
|
'.js',
|
|
100
69
|
'.ts',
|
|
@@ -116,21 +85,21 @@ async function initWithLock(directories, project) {
|
|
|
116
85
|
// Refetch the list of migrations from the database.
|
|
117
86
|
allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });
|
|
118
87
|
// Sort the migration files into execution order.
|
|
119
|
-
const sortedMigrationFiles =
|
|
88
|
+
const sortedMigrationFiles = sortMigrationFiles(migrationFiles);
|
|
120
89
|
// Figure out which migrations have to be applied.
|
|
121
90
|
const migrationsToExecute = getMigrationsToExecute(sortedMigrationFiles, allMigrations.rows);
|
|
122
91
|
for (const { directory, filename, timestamp } of migrationsToExecute) {
|
|
123
92
|
if (allMigrations.rows.length === 0) {
|
|
124
93
|
// if we are running all the migrations then log at a lower level
|
|
125
|
-
|
|
94
|
+
logger.verbose(`Running migration ${filename}`);
|
|
126
95
|
}
|
|
127
96
|
else {
|
|
128
|
-
|
|
97
|
+
logger.info(`Running migration ${filename}`);
|
|
129
98
|
}
|
|
130
|
-
const migrationPath =
|
|
99
|
+
const migrationPath = path.join(directory, filename);
|
|
131
100
|
if (filename.endsWith('.sql')) {
|
|
132
|
-
const migrationSql = await
|
|
133
|
-
const annotations =
|
|
101
|
+
const migrationSql = await fs.readFile(migrationPath, 'utf8');
|
|
102
|
+
const annotations = parseAnnotations(migrationSql);
|
|
134
103
|
try {
|
|
135
104
|
if (annotations.has('NO TRANSACTION')) {
|
|
136
105
|
await sqldb.queryAsync(migrationSql, {});
|
|
@@ -147,7 +116,7 @@ async function initWithLock(directories, project) {
|
|
|
147
116
|
}
|
|
148
117
|
}
|
|
149
118
|
else {
|
|
150
|
-
const migrationModule = await
|
|
119
|
+
const migrationModule = await import(migrationPath);
|
|
151
120
|
const implementation = migrationModule.default;
|
|
152
121
|
if (typeof implementation !== 'function') {
|
|
153
122
|
throw new Error(`Migration ${filename} does not export a default function`);
|
|
@@ -162,5 +131,4 @@ async function initWithLock(directories, project) {
|
|
|
162
131
|
});
|
|
163
132
|
}
|
|
164
133
|
}
|
|
165
|
-
exports.initWithLock = initWithLock;
|
|
166
134
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations/migrations.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\n\nimport * as namedLocks from '@prairielearn/named-locks';\nimport { logger } from '@prairielearn/logger';\nimport * as sqldb from '@prairielearn/postgres';\nimport * as error from '@prairielearn/error';\n\nimport {\n MigrationFile,\n parseAnnotations,\n readAndValidateMigrationsFromDirectories,\n sortMigrationFiles,\n} from '../load-migrations';\n\nconst sql = sqldb.loadSqlEquiv(__filename);\n\nexport async function init(directories: string | string[], project: string) {\n const migrationDirectories = Array.isArray(directories) ? directories : [directories];\n const lockName = 'migrations';\n logger.verbose(`Waiting for lock ${lockName}`);\n await namedLocks.doWithLock(\n lockName,\n {\n // Migrations *might* take a long time to run, so we'll enable automatic\n // lock renewal so that our lock doesn't get killed by the Postgres\n // idle session timeout.\n //\n // That said, we should generally try to keep migrations executing as\n // quickly as possible. A long-running migration likely means that\n // Postgres is locking a whole table, which is unacceptable in production.\n autoRenew: true,\n },\n async () => {\n logger.verbose(`Acquired lock ${lockName}`);\n await initWithLock(migrationDirectories, project);\n },\n );\n logger.verbose(`Released lock ${lockName}`);\n}\n\nexport function getMigrationsToExecute(\n migrationFiles: MigrationFile[],\n executedMigrations: { timestamp: string | null }[],\n): MigrationFile[] {\n // If no migrations have ever been run, run them all.\n if (executedMigrations.length === 0) {\n return migrationFiles;\n }\n\n const executedMigrationTimestamps = new Set(executedMigrations.map((m) => m.timestamp));\n return migrationFiles.filter((m) => !executedMigrationTimestamps.has(m.timestamp));\n}\n\nexport async function initWithLock(directories: string[], project: string) {\n logger.verbose('Starting DB schema migration');\n\n // Create the migrations table if needed\n await sqldb.queryAsync(sql.create_migrations_table, {});\n\n // Apply necessary changes to the migrations table as needed.\n try {\n await sqldb.queryAsync('SELECT project FROM migrations;', {});\n } catch (err: any) {\n if (err.routine === 'errorMissingColumn') {\n logger.info('Altering migrations table');\n await sqldb.queryAsync(sql.add_projects_column, {});\n } else {\n throw err;\n }\n }\n try {\n await sqldb.queryAsync('SELECT timestamp FROM migrations;', {});\n } catch (err: any) {\n if (err.routine === 'errorMissingColumn') {\n logger.info('Altering migrations table again');\n await sqldb.queryAsync(sql.add_timestamp_column, {});\n } else {\n throw err;\n }\n }\n\n let allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });\n\n const migrationFiles = await readAndValidateMigrationsFromDirectories(directories, [\n '.sql',\n '.js',\n '.ts',\n '.mjs',\n ]);\n\n // Validation: if we not all previously-executed migrations have timestamps,\n // prompt the user to deploy an earlier version that includes both indexes\n // and timestamps.\n const migrationsMissingTimestamps = allMigrations.rows.filter((m) => !m.timestamp);\n if (migrationsMissingTimestamps.length > 0) {\n throw new Error(\n [\n 'The following migrations are missing timestamps:',\n migrationsMissingTimestamps.map((m) => ` ${m.filename}`),\n // This revision was the most recent commit to `master` before the\n // code handling indexes was removed.\n 'You must deploy revision 1aa43c7348fa24cf636413d720d06a2fa9e38ef2 first.',\n ].join('\\n'),\n );\n }\n\n // Refetch the list of migrations from the database.\n allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });\n\n // Sort the migration files into execution order.\n const sortedMigrationFiles = sortMigrationFiles(migrationFiles);\n\n // Figure out which migrations have to be applied.\n const migrationsToExecute = getMigrationsToExecute(sortedMigrationFiles, allMigrations.rows);\n\n for (const { directory, filename, timestamp } of migrationsToExecute) {\n if (allMigrations.rows.length === 0) {\n // if we are running all the migrations then log at a lower level\n logger.verbose(`Running migration ${filename}`);\n } else {\n logger.info(`Running migration ${filename}`);\n }\n\n const migrationPath = path.join(directory, filename);\n if (filename.endsWith('.sql')) {\n const migrationSql = await fs.readFile(migrationPath, 'utf8');\n const annotations = parseAnnotations(migrationSql);\n try {\n if (annotations.has('NO TRANSACTION')) {\n await sqldb.queryAsync(migrationSql, {});\n } else {\n await sqldb.runInTransactionAsync(async () => {\n await sqldb.queryAsync(migrationSql, {});\n });\n }\n } catch (err) {\n error.addData(err, { sqlFile: filename });\n throw err;\n }\n } else {\n const migrationModule = await import(migrationPath);\n const implementation = migrationModule.default;\n if (typeof implementation !== 'function') {\n throw new Error(`Migration ${filename} does not export a default function`);\n }\n await implementation();\n }\n\n // Record the migration.\n await sqldb.queryAsync(sql.insert_migration, {\n filename,\n timestamp,\n project,\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations/migrations.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'path';\n\nimport fs from 'fs-extra';\n\nimport * as error from '@prairielearn/error';\nimport { logger } from '@prairielearn/logger';\nimport * as namedLocks from '@prairielearn/named-locks';\nimport * as sqldb from '@prairielearn/postgres';\n\nimport {\n MigrationFile,\n parseAnnotations,\n readAndValidateMigrationsFromDirectories,\n sortMigrationFiles,\n} from '../load-migrations.js';\n\nconst sql = sqldb.loadSqlEquiv(import.meta.filename);\n\nexport async function init(directories: string | string[], project: string) {\n const migrationDirectories = Array.isArray(directories) ? directories : [directories];\n const lockName = 'migrations';\n logger.verbose(`Waiting for lock ${lockName}`);\n await namedLocks.doWithLock(\n lockName,\n {\n // Migrations *might* take a long time to run, so we'll enable automatic\n // lock renewal so that our lock doesn't get killed by the Postgres\n // idle session timeout.\n //\n // That said, we should generally try to keep migrations executing as\n // quickly as possible. A long-running migration likely means that\n // Postgres is locking a whole table, which is unacceptable in production.\n autoRenew: true,\n },\n async () => {\n logger.verbose(`Acquired lock ${lockName}`);\n await initWithLock(migrationDirectories, project);\n },\n );\n logger.verbose(`Released lock ${lockName}`);\n}\n\nexport function getMigrationsToExecute(\n migrationFiles: MigrationFile[],\n executedMigrations: { timestamp: string | null }[],\n): MigrationFile[] {\n // If no migrations have ever been run, run them all.\n if (executedMigrations.length === 0) {\n return migrationFiles;\n }\n\n const executedMigrationTimestamps = new Set(executedMigrations.map((m) => m.timestamp));\n return migrationFiles.filter((m) => !executedMigrationTimestamps.has(m.timestamp));\n}\n\nexport async function initWithLock(directories: string[], project: string) {\n logger.verbose('Starting DB schema migration');\n\n // Create the migrations table if needed\n await sqldb.queryAsync(sql.create_migrations_table, {});\n\n // Apply necessary changes to the migrations table as needed.\n try {\n await sqldb.queryAsync('SELECT project FROM migrations;', {});\n } catch (err: any) {\n if (err.routine === 'errorMissingColumn') {\n logger.info('Altering migrations table');\n await sqldb.queryAsync(sql.add_projects_column, {});\n } else {\n throw err;\n }\n }\n try {\n await sqldb.queryAsync('SELECT timestamp FROM migrations;', {});\n } catch (err: any) {\n if (err.routine === 'errorMissingColumn') {\n logger.info('Altering migrations table again');\n await sqldb.queryAsync(sql.add_timestamp_column, {});\n } else {\n throw err;\n }\n }\n\n let allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });\n\n const migrationFiles = await readAndValidateMigrationsFromDirectories(directories, [\n '.sql',\n '.js',\n '.ts',\n '.mjs',\n ]);\n\n // Validation: if we not all previously-executed migrations have timestamps,\n // prompt the user to deploy an earlier version that includes both indexes\n // and timestamps.\n const migrationsMissingTimestamps = allMigrations.rows.filter((m) => !m.timestamp);\n if (migrationsMissingTimestamps.length > 0) {\n throw new Error(\n [\n 'The following migrations are missing timestamps:',\n migrationsMissingTimestamps.map((m) => ` ${m.filename}`),\n // This revision was the most recent commit to `master` before the\n // code handling indexes was removed.\n 'You must deploy revision 1aa43c7348fa24cf636413d720d06a2fa9e38ef2 first.',\n ].join('\\n'),\n );\n }\n\n // Refetch the list of migrations from the database.\n allMigrations = await sqldb.queryAsync(sql.get_migrations, { project });\n\n // Sort the migration files into execution order.\n const sortedMigrationFiles = sortMigrationFiles(migrationFiles);\n\n // Figure out which migrations have to be applied.\n const migrationsToExecute = getMigrationsToExecute(sortedMigrationFiles, allMigrations.rows);\n\n for (const { directory, filename, timestamp } of migrationsToExecute) {\n if (allMigrations.rows.length === 0) {\n // if we are running all the migrations then log at a lower level\n logger.verbose(`Running migration ${filename}`);\n } else {\n logger.info(`Running migration ${filename}`);\n }\n\n const migrationPath = path.join(directory, filename);\n if (filename.endsWith('.sql')) {\n const migrationSql = await fs.readFile(migrationPath, 'utf8');\n const annotations = parseAnnotations(migrationSql);\n try {\n if (annotations.has('NO TRANSACTION')) {\n await sqldb.queryAsync(migrationSql, {});\n } else {\n await sqldb.runInTransactionAsync(async () => {\n await sqldb.queryAsync(migrationSql, {});\n });\n }\n } catch (err) {\n error.addData(err, { sqlFile: filename });\n throw err;\n }\n } else {\n const migrationModule = await import(migrationPath);\n const implementation = migrationModule.default;\n if (typeof implementation !== 'function') {\n throw new Error(`Migration ${filename} does not export a default function`);\n }\n await implementation();\n }\n\n // Record the migration.\n await sqldb.queryAsync(sql.insert_migration, {\n filename,\n timestamp,\n project,\n });\n }\n}\n"]}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const chai_1 = require("chai");
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const postgres_1 = require("@prairielearn/postgres");
|
|
9
|
-
const migrations_1 = require("./migrations");
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { assert } from 'chai';
|
|
3
|
+
import { makePostgresTestUtils, queryAsync } from '@prairielearn/postgres';
|
|
4
|
+
import { getMigrationsToExecute, initWithLock } from './migrations.js';
|
|
10
5
|
describe('migrations', () => {
|
|
11
6
|
describe('getMigrationsToExecute', () => {
|
|
12
7
|
it('handles the case of no executed migrations', () => {
|
|
@@ -17,7 +12,7 @@ describe('migrations', () => {
|
|
|
17
12
|
timestamp: '20220101010101',
|
|
18
13
|
},
|
|
19
14
|
];
|
|
20
|
-
|
|
15
|
+
assert.deepEqual(getMigrationsToExecute(migrationFiles, []), migrationFiles);
|
|
21
16
|
});
|
|
22
17
|
it('handles case where subset of migrations have been executed', () => {
|
|
23
18
|
const migrationFiles = [
|
|
@@ -45,7 +40,7 @@ describe('migrations', () => {
|
|
|
45
40
|
timestamp: '20220101010102',
|
|
46
41
|
},
|
|
47
42
|
];
|
|
48
|
-
|
|
43
|
+
assert.deepEqual(getMigrationsToExecute(migrationFiles, executedMigrations), [
|
|
49
44
|
{
|
|
50
45
|
directory: 'migrations',
|
|
51
46
|
timestamp: '20220101010103',
|
|
@@ -55,7 +50,7 @@ describe('migrations', () => {
|
|
|
55
50
|
});
|
|
56
51
|
});
|
|
57
52
|
describe('initWithLock', () => {
|
|
58
|
-
const postgresTestUtils =
|
|
53
|
+
const postgresTestUtils = makePostgresTestUtils({
|
|
59
54
|
database: 'prairielearn_migrations',
|
|
60
55
|
});
|
|
61
56
|
before(async () => {
|
|
@@ -65,13 +60,13 @@ describe('migrations', () => {
|
|
|
65
60
|
await postgresTestUtils.dropDatabase();
|
|
66
61
|
});
|
|
67
62
|
it('runs both SQL and JavaScript migrations', async () => {
|
|
68
|
-
const migrationDir =
|
|
69
|
-
await
|
|
63
|
+
const migrationDir = path.join(import.meta.dirname, 'fixtures');
|
|
64
|
+
await initWithLock([migrationDir], 'prairielearn_migrations');
|
|
70
65
|
// If both migrations ran successfully, there should be a single user
|
|
71
66
|
// in the database.
|
|
72
|
-
const users = await
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
const users = await queryAsync('SELECT * FROM users', {});
|
|
68
|
+
assert.lengthOf(users.rows, 1);
|
|
69
|
+
assert.equal(users.rows[0].name, 'Test User');
|
|
75
70
|
});
|
|
76
71
|
});
|
|
77
72
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.test.js","sourceRoot":"","sources":["../../src/migrations/migrations.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrations.test.js","sourceRoot":"","sources":["../../src/migrations/migrations.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAE3E,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEvE,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,cAAc,GAAG;gBACrB;oBACE,SAAS,EAAE,YAAY;oBACvB,QAAQ,EAAE,iBAAiB;oBAC3B,SAAS,EAAE,gBAAgB;iBAC5B;aACF,CAAC;YACF,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;YACpE,MAAM,cAAc,GAAG;gBACrB;oBACE,SAAS,EAAE,YAAY;oBACvB,QAAQ,EAAE,8BAA8B;oBACxC,SAAS,EAAE,gBAAgB;iBAC5B;gBACD;oBACE,SAAS,EAAE,YAAY;oBACvB,QAAQ,EAAE,8BAA8B;oBACxC,SAAS,EAAE,gBAAgB;iBAC5B;gBACD;oBACE,SAAS,EAAE,YAAY;oBACvB,QAAQ,EAAE,8BAA8B;oBACxC,SAAS,EAAE,gBAAgB;iBAC5B;aACF,CAAC;YACF,MAAM,kBAAkB,GAAG;gBACzB;oBACE,SAAS,EAAE,gBAAgB;iBAC5B;gBACD;oBACE,SAAS,EAAE,gBAAgB;iBAC5B;aACF,CAAC;YACF,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,cAAc,EAAE,kBAAkB,CAAC,EAAE;gBAC3E;oBACE,SAAS,EAAE,YAAY;oBACvB,SAAS,EAAE,gBAAgB;oBAC3B,QAAQ,EAAE,8BAA8B;iBACzC;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;YAC9C,QAAQ,EAAE,yBAAyB;SACpC,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,IAAI,EAAE;YAChB,MAAM,iBAAiB,CAAC,cAAc,EAAE,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,iBAAiB,CAAC,YAAY,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YAChE,MAAM,YAAY,CAAC,CAAC,YAAY,CAAC,EAAE,yBAAyB,CAAC,CAAC;YAE9D,qEAAqE;YACrE,mBAAmB;YACnB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;YAC1D,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import path from 'node:path';\n\nimport { assert } from 'chai';\n\nimport { makePostgresTestUtils, queryAsync } from '@prairielearn/postgres';\n\nimport { getMigrationsToExecute, initWithLock } from './migrations.js';\n\ndescribe('migrations', () => {\n describe('getMigrationsToExecute', () => {\n it('handles the case of no executed migrations', () => {\n const migrationFiles = [\n {\n directory: 'migrations',\n filename: '001_testing.sql',\n timestamp: '20220101010101',\n },\n ];\n assert.deepEqual(getMigrationsToExecute(migrationFiles, []), migrationFiles);\n });\n\n it('handles case where subset of migrations have been executed', () => {\n const migrationFiles = [\n {\n directory: 'migrations',\n filename: '20220101010101_testing_1.sql',\n timestamp: '20220101010101',\n },\n {\n directory: 'migrations',\n filename: '20220101010102_testing_2.sql',\n timestamp: '20220101010102',\n },\n {\n directory: 'migrations',\n filename: '20220101010103_testing_3.sql',\n timestamp: '20220101010103',\n },\n ];\n const executedMigrations = [\n {\n timestamp: '20220101010101',\n },\n {\n timestamp: '20220101010102',\n },\n ];\n assert.deepEqual(getMigrationsToExecute(migrationFiles, executedMigrations), [\n {\n directory: 'migrations',\n timestamp: '20220101010103',\n filename: '20220101010103_testing_3.sql',\n },\n ]);\n });\n });\n\n describe('initWithLock', () => {\n const postgresTestUtils = makePostgresTestUtils({\n database: 'prairielearn_migrations',\n });\n\n before(async () => {\n await postgresTestUtils.createDatabase();\n });\n\n after(async () => {\n await postgresTestUtils.dropDatabase();\n });\n\n it('runs both SQL and JavaScript migrations', async () => {\n const migrationDir = path.join(import.meta.dirname, 'fixtures');\n await initWithLock([migrationDir], 'prairielearn_migrations');\n\n // If both migrations ran successfully, there should be a single user\n // in the database.\n const users = await queryAsync('SELECT * FROM users', {});\n assert.lengthOf(users.rows, 1);\n assert.equal(users.rows[0].name, 'Test User');\n });\n });\n});\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/migrations",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "./dist/index.js",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
@@ -10,28 +11,28 @@
|
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsc && tscp",
|
|
12
13
|
"dev": "tsc --watch --preserveWatchOutput & tscp --watch",
|
|
13
|
-
"test": "mocha
|
|
14
|
+
"test": "mocha src/**/*.test.ts"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
17
18
|
"@types/fs-extra": "^11.0.4",
|
|
18
19
|
"@types/mocha": "^10.0.6",
|
|
19
|
-
"@types/node": "^20.12.
|
|
20
|
-
"chai": "^
|
|
21
|
-
"chai-as-promised": "^7.1.
|
|
20
|
+
"@types/node": "^20.12.11",
|
|
21
|
+
"chai": "^5.1.1",
|
|
22
|
+
"chai-as-promised": "^7.1.2",
|
|
22
23
|
"mocha": "^10.4.0",
|
|
23
24
|
"tmp-promise": "^3.0.3",
|
|
24
|
-
"tsx": "^4.
|
|
25
|
-
"typescript": "^5.4.
|
|
25
|
+
"tsx": "^4.10.2",
|
|
26
|
+
"typescript": "^5.4.5",
|
|
26
27
|
"typescript-cp": "^0.1.9"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@prairielearn/error": "^
|
|
30
|
-
"@prairielearn/logger": "^
|
|
31
|
-
"@prairielearn/named-locks": "^
|
|
32
|
-
"@prairielearn/postgres": "^
|
|
30
|
+
"@prairielearn/error": "^2.0.1",
|
|
31
|
+
"@prairielearn/logger": "^2.0.1",
|
|
32
|
+
"@prairielearn/named-locks": "^3.0.1",
|
|
33
|
+
"@prairielearn/postgres": "^2.0.1",
|
|
33
34
|
"fs-extra": "^11.2.0",
|
|
34
|
-
"serialize-error": "^
|
|
35
|
-
"zod": "^3.
|
|
35
|
+
"serialize-error": "^11.0.3",
|
|
36
|
+
"zod": "^3.23.8"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
|
|
2
3
|
import { loadSqlEquiv, queryRows } from '@prairielearn/postgres';
|
|
3
4
|
|
|
4
|
-
const sql = loadSqlEquiv(
|
|
5
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
5
6
|
|
|
6
7
|
export const BatchedMigrationJobStatusSchema = z.enum(['pending', 'failed', 'succeeded']);
|
|
7
8
|
export type BatchedMigrationJobStatus = z.infer<typeof BatchedMigrationJobStatusSchema>;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { assert } from 'chai';
|
|
2
|
-
|
|
3
|
-
import * as namedLocks from '@prairielearn/named-locks';
|
|
2
|
+
|
|
4
3
|
import * as error from '@prairielearn/error';
|
|
4
|
+
import * as namedLocks from '@prairielearn/named-locks';
|
|
5
|
+
import { makePostgresTestUtils, queryAsync, queryRow, queryRows } from '@prairielearn/postgres';
|
|
6
|
+
|
|
7
|
+
import { SCHEMA_MIGRATIONS_PATH, init } from '../index.js';
|
|
5
8
|
|
|
9
|
+
import { BatchedMigrationJobRowSchema } from './batched-migration-job.js';
|
|
10
|
+
import { BatchedMigrationRunner } from './batched-migration-runner.js';
|
|
6
11
|
import {
|
|
7
12
|
BatchedMigrationRowSchema,
|
|
8
13
|
insertBatchedMigration,
|
|
9
14
|
makeBatchedMigration,
|
|
10
15
|
updateBatchedMigrationStatus,
|
|
11
|
-
} from './batched-migration';
|
|
12
|
-
import { BatchedMigrationJobRowSchema } from './batched-migration-job';
|
|
13
|
-
import { BatchedMigrationRunner } from './batched-migration-runner';
|
|
14
|
-
import { SCHEMA_MIGRATIONS_PATH, init } from '../index';
|
|
16
|
+
} from './batched-migration.js';
|
|
15
17
|
|
|
16
18
|
const postgresTestUtils = makePostgresTestUtils({
|
|
17
19
|
database: 'prairielearn_migrations',
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { loadSqlEquiv, queryAsync, queryRow, queryOptionalRow } from '@prairielearn/postgres';
|
|
2
|
-
import { logger } from '@prairielearn/logger';
|
|
3
1
|
import { serializeError } from 'serialize-error';
|
|
4
2
|
import { z } from 'zod';
|
|
5
3
|
|
|
4
|
+
import { logger } from '@prairielearn/logger';
|
|
5
|
+
import { loadSqlEquiv, queryAsync, queryRow, queryOptionalRow } from '@prairielearn/postgres';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BatchedMigrationJobRowSchema,
|
|
9
|
+
BatchedMigrationJobStatus,
|
|
10
|
+
BatchedMigrationJobRow,
|
|
11
|
+
} from './batched-migration-job.js';
|
|
6
12
|
import {
|
|
7
13
|
BatchedMigrationStatus,
|
|
8
14
|
BatchedMigrationRow,
|
|
9
15
|
updateBatchedMigrationStatus,
|
|
10
16
|
BatchedMigrationStatusSchema,
|
|
11
17
|
BatchedMigrationImplementation,
|
|
12
|
-
} from './batched-migration';
|
|
13
|
-
import {
|
|
14
|
-
BatchedMigrationJobRowSchema,
|
|
15
|
-
BatchedMigrationJobStatus,
|
|
16
|
-
BatchedMigrationJobRow,
|
|
17
|
-
} from './batched-migration-job';
|
|
18
|
+
} from './batched-migration.js';
|
|
18
19
|
|
|
19
|
-
const sql = loadSqlEquiv(
|
|
20
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
20
21
|
|
|
21
22
|
interface BatchedMigrationRunnerOptions {
|
|
22
23
|
logProgress?: boolean;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
loadSqlEquiv,
|
|
3
5
|
queryAsync,
|
|
@@ -5,9 +7,8 @@ import {
|
|
|
5
7
|
queryRows,
|
|
6
8
|
queryOptionalRow,
|
|
7
9
|
} from '@prairielearn/postgres';
|
|
8
|
-
import { z } from 'zod';
|
|
9
10
|
|
|
10
|
-
const sql = loadSqlEquiv(
|
|
11
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
11
12
|
|
|
12
13
|
export const BatchedMigrationStatusSchema = z.enum([
|
|
13
14
|
'pending',
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import chai, { assert } from 'chai';
|
|
2
|
-
import chaiAsPromised from 'chai-as-promised';
|
|
3
1
|
import path from 'node:path';
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
import { use as chaiUse, assert } from 'chai';
|
|
4
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
5
|
+
|
|
5
6
|
import * as namedLocks from '@prairielearn/named-locks';
|
|
7
|
+
import { makePostgresTestUtils } from '@prairielearn/postgres';
|
|
8
|
+
|
|
9
|
+
import { SCHEMA_MIGRATIONS_PATH, init } from '../index.js';
|
|
6
10
|
|
|
7
|
-
import {
|
|
8
|
-
import { BatchedMigrationsRunner } from './batched-migrations-runner';
|
|
9
|
-
import { selectAllBatchedMigrations } from './batched-migration';
|
|
11
|
+
import { selectAllBatchedMigrations } from './batched-migration.js';
|
|
12
|
+
import { BatchedMigrationsRunner } from './batched-migrations-runner.js';
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
chaiUse(chaiAsPromised);
|
|
12
15
|
|
|
13
16
|
const postgresTestUtils = makePostgresTestUtils({
|
|
14
17
|
database: 'prairielearn_migrations',
|
|
@@ -35,7 +38,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
35
38
|
it('enqueues migrations', async () => {
|
|
36
39
|
const runner = new BatchedMigrationsRunner({
|
|
37
40
|
project: 'test',
|
|
38
|
-
directories: [path.join(
|
|
41
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
@@ -49,7 +52,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
49
52
|
assert.equal(migrations[0].filename, '20230406184103_successful_migration.ts');
|
|
50
53
|
assert.equal(migrations[0].status, 'pending');
|
|
51
54
|
assert.equal(migrations[1].timestamp, '20230406184107');
|
|
52
|
-
assert.equal(migrations[1].filename, '20230406184107_failing_migration.
|
|
55
|
+
assert.equal(migrations[1].filename, '20230406184107_failing_migration.ts');
|
|
53
56
|
assert.equal(migrations[1].status, 'pending');
|
|
54
57
|
assert.equal(migrations[2].timestamp, '20230407230446');
|
|
55
58
|
assert.equal(migrations[2].filename, '20230407230446_no_rows_migration.ts');
|
|
@@ -59,7 +62,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
59
62
|
it('safely enqueues migrations multiple times', async () => {
|
|
60
63
|
const runner = new BatchedMigrationsRunner({
|
|
61
64
|
project: 'test',
|
|
62
|
-
directories: [path.join(
|
|
65
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
63
66
|
});
|
|
64
67
|
|
|
65
68
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
@@ -74,7 +77,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
74
77
|
it('finalizes a successful migration', async () => {
|
|
75
78
|
const runner = new BatchedMigrationsRunner({
|
|
76
79
|
project: 'test',
|
|
77
|
-
directories: [path.join(
|
|
80
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
await runner.enqueueBatchedMigration('20230406184103_successful_migration');
|
|
@@ -91,7 +94,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
91
94
|
it('finalizes a failing migration', async () => {
|
|
92
95
|
const runner = new BatchedMigrationsRunner({
|
|
93
96
|
project: 'test',
|
|
94
|
-
directories: [path.join(
|
|
97
|
+
directories: [path.join(import.meta.dirname, 'fixtures')],
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
await runner.enqueueBatchedMigration('20230406184107_failing_migration');
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import { doWithLock } from '@prairielearn/named-locks';
|
|
6
|
+
import { loadSqlEquiv, queryOptionalRow } from '@prairielearn/postgres';
|
|
7
|
+
|
|
8
|
+
import { MigrationFile, readAndValidateMigrationsFromDirectories } from '../load-migrations.js';
|
|
6
9
|
|
|
7
|
-
import {
|
|
10
|
+
import { BatchedMigrationRunner } from './batched-migration-runner.js';
|
|
8
11
|
import {
|
|
9
12
|
BatchedMigrationRowSchema,
|
|
10
13
|
BatchedMigrationRow,
|
|
@@ -14,10 +17,9 @@ import {
|
|
|
14
17
|
updateBatchedMigrationStatus,
|
|
15
18
|
BatchedMigrationImplementation,
|
|
16
19
|
validateBatchedMigrationImplementation,
|
|
17
|
-
} from './batched-migration';
|
|
18
|
-
import { BatchedMigrationRunner } from './batched-migration-runner';
|
|
20
|
+
} from './batched-migration.js';
|
|
19
21
|
|
|
20
|
-
const sql = loadSqlEquiv(
|
|
22
|
+
const sql = loadSqlEquiv(import.meta.filename);
|
|
21
23
|
|
|
22
24
|
const DEFAULT_MIN_VALUE = 1n;
|
|
23
25
|
const DEFAULT_BATCH_SIZE = 1_000;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const { makeBatchedMigration } = require('../batched-migration');
|
|
1
|
+
import { makeBatchedMigration } from '../batched-migration.js';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export default makeBatchedMigration({
|
|
5
4
|
async getParameters() {
|
|
6
5
|
return {
|
|
7
6
|
min: 2n,
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
export {
|
|
2
|
-
BatchedMigrationRow,
|
|
3
|
-
BatchedMigrationStatus,
|
|
2
|
+
type BatchedMigrationRow,
|
|
3
|
+
type BatchedMigrationStatus,
|
|
4
4
|
makeBatchedMigration,
|
|
5
5
|
selectAllBatchedMigrations,
|
|
6
6
|
selectBatchedMigration,
|
|
7
7
|
selectBatchedMigrationForTimestamp,
|
|
8
8
|
retryFailedBatchedMigrationJobs,
|
|
9
|
-
} from './batched-migration';
|
|
9
|
+
} from './batched-migration.js';
|
|
10
10
|
export {
|
|
11
|
-
BatchedMigrationJobRow,
|
|
12
|
-
BatchedMigrationJobStatus,
|
|
11
|
+
type BatchedMigrationJobRow,
|
|
12
|
+
type BatchedMigrationJobStatus,
|
|
13
13
|
selectRecentJobsWithStatus,
|
|
14
|
-
} from './batched-migration-job';
|
|
14
|
+
} from './batched-migration-job.js';
|
|
15
15
|
export {
|
|
16
16
|
initBatchedMigrations,
|
|
17
17
|
startBatchedMigrations,
|
|
18
18
|
stopBatchedMigrations,
|
|
19
19
|
enqueueBatchedMigration,
|
|
20
20
|
finalizeBatchedMigration,
|
|
21
|
-
} from './batched-migrations-runner';
|
|
21
|
+
} from './batched-migrations-runner.js';
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
|
|
3
|
-
export { init } from './migrations';
|
|
3
|
+
export { init } from './migrations/index.js';
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
|
-
BatchedMigrationRow,
|
|
7
|
-
BatchedMigrationStatus,
|
|
8
|
-
BatchedMigrationJobRow,
|
|
9
|
-
BatchedMigrationJobStatus,
|
|
6
|
+
type BatchedMigrationRow,
|
|
7
|
+
type BatchedMigrationStatus,
|
|
8
|
+
type BatchedMigrationJobRow,
|
|
9
|
+
type BatchedMigrationJobStatus,
|
|
10
10
|
makeBatchedMigration,
|
|
11
11
|
initBatchedMigrations,
|
|
12
12
|
startBatchedMigrations,
|
|
@@ -18,6 +18,6 @@ export {
|
|
|
18
18
|
selectBatchedMigrationForTimestamp,
|
|
19
19
|
selectRecentJobsWithStatus,
|
|
20
20
|
retryFailedBatchedMigrationJobs,
|
|
21
|
-
} from './batched-migrations';
|
|
21
|
+
} from './batched-migrations/index.js';
|
|
22
22
|
|
|
23
|
-
export const SCHEMA_MIGRATIONS_PATH = path.resolve(
|
|
23
|
+
export const SCHEMA_MIGRATIONS_PATH = path.resolve(import.meta.dirname, '..', 'schema-migrations');
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import chai, { assert } from 'chai';
|
|
2
|
-
import chaiAsPromised from 'chai-as-promised';
|
|
3
1
|
import path from 'path';
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
import { use as chaiUse, assert } from 'chai';
|
|
4
|
+
import chaiAsPromised from 'chai-as-promised';
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
|
+
import tmp from 'tmp-promise';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
parseAnnotations,
|
|
9
10
|
readAndValidateMigrationsFromDirectory,
|
|
10
11
|
sortMigrationFiles,
|
|
11
|
-
} from './load-migrations';
|
|
12
|
+
} from './load-migrations.js';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
chaiUse(chaiAsPromised);
|
|
14
15
|
|
|
15
16
|
async function withMigrationFiles(files: string[], fn: (tmpDir: string) => Promise<void>) {
|
|
16
17
|
await tmp.withDir(
|
package/src/migrations/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { init } from './migrations';
|
|
1
|
+
export { init } from './migrations.js';
|