@prairielearn/migrations 2.0.0 → 2.0.2
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/CHANGELOG.md +23 -0
- package/package.json +7 -7
- package/src/batched-migrations/batched-migration-job.ts +2 -2
- package/src/batched-migrations/batched-migration-runner.test.ts +3 -3
- package/src/batched-migrations/batched-migration-runner.ts +11 -11
- package/src/batched-migrations/batched-migration.ts +10 -10
- package/src/batched-migrations/batched-migrations-runner.test.ts +1 -1
- package/src/batched-migrations/batched-migrations-runner.ts +7 -7
- package/src/load-migrations.test.ts +5 -5
- package/src/load-migrations.ts +3 -3
- package/src/migrations/migrations.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @prairielearn/migrations
|
|
2
2
|
|
|
3
|
+
## 2.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2b003b4d9: Upgrade all dependencies
|
|
8
|
+
- Updated dependencies [2b003b4d9]
|
|
9
|
+
- Updated dependencies [297bbce5a]
|
|
10
|
+
- @prairielearn/named-locks@1.4.0
|
|
11
|
+
- @prairielearn/postgres@1.7.2
|
|
12
|
+
- @prairielearn/logger@1.0.2
|
|
13
|
+
- @prairielearn/error@1.0.3
|
|
14
|
+
|
|
15
|
+
## 2.0.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 8fd47d928: Upgrade all dependencies
|
|
20
|
+
- Updated dependencies [8fd47d928]
|
|
21
|
+
- @prairielearn/named-locks@1.3.3
|
|
22
|
+
- @prairielearn/postgres@1.7.1
|
|
23
|
+
- @prairielearn/logger@1.0.1
|
|
24
|
+
- @prairielearn/error@1.0.2
|
|
25
|
+
|
|
3
26
|
## 2.0.0
|
|
4
27
|
|
|
5
28
|
### Major Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/migrations",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
17
17
|
"@types/fs-extra": "^11.0.1",
|
|
18
18
|
"@types/mocha": "^10.0.1",
|
|
19
|
-
"@types/node": "^18.16.
|
|
19
|
+
"@types/node": "^18.16.19",
|
|
20
20
|
"mocha": "^10.2.0",
|
|
21
21
|
"ts-node": "^10.9.1",
|
|
22
|
-
"typescript": "^5.
|
|
22
|
+
"typescript": "^5.1.6",
|
|
23
23
|
"typescript-cp": "^0.1.8"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@prairielearn/error": "^1.0.
|
|
27
|
-
"@prairielearn/logger": "^1.0.
|
|
28
|
-
"@prairielearn/named-locks": "^1.
|
|
29
|
-
"@prairielearn/postgres": "^1.7.
|
|
26
|
+
"@prairielearn/error": "^1.0.3",
|
|
27
|
+
"@prairielearn/logger": "^1.0.2",
|
|
28
|
+
"@prairielearn/named-locks": "^1.4.0",
|
|
29
|
+
"@prairielearn/postgres": "^1.7.2",
|
|
30
30
|
"fs-extra": "^11.1.1",
|
|
31
31
|
"serialize-error": "^8.1.0",
|
|
32
32
|
"zod": "^3.21.4"
|
|
@@ -24,11 +24,11 @@ export type BatchedMigrationJobRow = z.infer<typeof BatchedMigrationJobRowSchema
|
|
|
24
24
|
export async function selectRecentJobsWithStatus(
|
|
25
25
|
batchedMigrationId: string,
|
|
26
26
|
status: BatchedMigrationJobStatus,
|
|
27
|
-
limit: number
|
|
27
|
+
limit: number,
|
|
28
28
|
): Promise<BatchedMigrationJobRow[]> {
|
|
29
29
|
return queryValidatedRows(
|
|
30
30
|
sql.select_recent_jobs_with_status,
|
|
31
31
|
{ batched_migration_id: batchedMigrationId, status, limit },
|
|
32
|
-
BatchedMigrationJobRowSchema
|
|
32
|
+
BatchedMigrationJobRowSchema,
|
|
33
33
|
);
|
|
34
34
|
}
|
|
@@ -57,7 +57,7 @@ async function getBatchedMigration(migrationId: string) {
|
|
|
57
57
|
return queryValidatedOneRow(
|
|
58
58
|
'SELECT * FROM batched_migrations WHERE id = $id;',
|
|
59
59
|
{ id: migrationId },
|
|
60
|
-
BatchedMigrationRowSchema
|
|
60
|
+
BatchedMigrationRowSchema,
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -65,7 +65,7 @@ async function getBatchedMigrationJobs(migrationId: string) {
|
|
|
65
65
|
return queryValidatedRows(
|
|
66
66
|
'SELECT * FROM batched_migration_jobs WHERE batched_migration_id = $batched_migration_id ORDER BY id ASC;',
|
|
67
67
|
{ batched_migration_id: migrationId },
|
|
68
|
-
BatchedMigrationJobRowSchema
|
|
68
|
+
BatchedMigrationJobRowSchema,
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -74,7 +74,7 @@ async function resetFailedBatchedMigrationJobs(migrationId: string) {
|
|
|
74
74
|
"UPDATE batched_migration_jobs SET status = 'pending', updated_at = CURRENT_TIMESTAMP WHERE batched_migration_id = $batched_migration_id AND status = 'failed'",
|
|
75
75
|
{
|
|
76
76
|
batched_migration_id: migrationId,
|
|
77
|
-
}
|
|
77
|
+
},
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -37,7 +37,7 @@ export class BatchedMigrationRunner {
|
|
|
37
37
|
constructor(
|
|
38
38
|
migration: BatchedMigrationRow,
|
|
39
39
|
migrationImplementation: BatchedMigrationImplementation,
|
|
40
|
-
options: BatchedMigrationRunnerOptions = {}
|
|
40
|
+
options: BatchedMigrationRunnerOptions = {},
|
|
41
41
|
) {
|
|
42
42
|
this.options = options;
|
|
43
43
|
this.migration = migration;
|
|
@@ -55,7 +55,7 @@ export class BatchedMigrationRunner {
|
|
|
55
55
|
return queryValidatedSingleColumnOneRow(
|
|
56
56
|
sql.batched_migration_has_incomplete_jobs,
|
|
57
57
|
{ batched_migration_id: migration.id },
|
|
58
|
-
z.boolean()
|
|
58
|
+
z.boolean(),
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -63,7 +63,7 @@ export class BatchedMigrationRunner {
|
|
|
63
63
|
return queryValidatedSingleColumnOneRow(
|
|
64
64
|
sql.batched_migration_has_failed_jobs,
|
|
65
65
|
{ batched_migration_id: migration.id },
|
|
66
|
-
z.boolean()
|
|
66
|
+
z.boolean(),
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -73,7 +73,7 @@ export class BatchedMigrationRunner {
|
|
|
73
73
|
{
|
|
74
74
|
id: migration.id,
|
|
75
75
|
},
|
|
76
|
-
BatchedMigrationStatusSchema
|
|
76
|
+
BatchedMigrationStatusSchema,
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -92,12 +92,12 @@ export class BatchedMigrationRunner {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
private async getNextBatchBounds(
|
|
95
|
-
migration: BatchedMigrationRow
|
|
95
|
+
migration: BatchedMigrationRow,
|
|
96
96
|
): Promise<null | [bigint, bigint]> {
|
|
97
97
|
const lastJob = await queryValidatedZeroOrOneRow(
|
|
98
98
|
sql.select_last_batched_migration_job,
|
|
99
99
|
{ batched_migration_id: migration.id },
|
|
100
|
-
BatchedMigrationJobRowSchema
|
|
100
|
+
BatchedMigrationJobRowSchema,
|
|
101
101
|
);
|
|
102
102
|
|
|
103
103
|
const nextMin = lastJob ? lastJob.max_value + 1n : migration.min_value;
|
|
@@ -129,7 +129,7 @@ export class BatchedMigrationRunner {
|
|
|
129
129
|
private async finishJob(
|
|
130
130
|
job: BatchedMigrationJobRow,
|
|
131
131
|
status: Extract<BatchedMigrationJobStatus, 'failed' | 'succeeded'>,
|
|
132
|
-
data?: unknown
|
|
132
|
+
data?: unknown,
|
|
133
133
|
) {
|
|
134
134
|
await queryAsync(sql.finish_batched_migration_job, {
|
|
135
135
|
id: job.id,
|
|
@@ -140,7 +140,7 @@ export class BatchedMigrationRunner {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
private async getOrCreateNextMigrationJob(
|
|
143
|
-
migration: BatchedMigrationRow
|
|
143
|
+
migration: BatchedMigrationRow,
|
|
144
144
|
): Promise<BatchedMigrationJobRow | null> {
|
|
145
145
|
const nextBatchBounds = await this.getNextBatchBounds(migration);
|
|
146
146
|
if (nextBatchBounds) {
|
|
@@ -151,7 +151,7 @@ export class BatchedMigrationRunner {
|
|
|
151
151
|
min_value: nextBatchBounds[0],
|
|
152
152
|
max_value: nextBatchBounds[1],
|
|
153
153
|
},
|
|
154
|
-
BatchedMigrationJobRowSchema
|
|
154
|
+
BatchedMigrationJobRowSchema,
|
|
155
155
|
);
|
|
156
156
|
} else {
|
|
157
157
|
// Pick up any old pending jobs from this migration. These will only exist if
|
|
@@ -160,14 +160,14 @@ export class BatchedMigrationRunner {
|
|
|
160
160
|
return queryValidatedZeroOrOneRow(
|
|
161
161
|
sql.select_first_pending_batched_migration_job,
|
|
162
162
|
{ batched_migration_id: migration.id },
|
|
163
|
-
BatchedMigrationJobRowSchema
|
|
163
|
+
BatchedMigrationJobRowSchema,
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
private async runMigrationJob(
|
|
169
169
|
migration: BatchedMigrationRow,
|
|
170
|
-
migrationImplementation: BatchedMigrationImplementation
|
|
170
|
+
migrationImplementation: BatchedMigrationImplementation,
|
|
171
171
|
) {
|
|
172
172
|
const nextJob = await this.getOrCreateNextMigrationJob(migration);
|
|
173
173
|
if (nextJob) {
|
|
@@ -54,7 +54,7 @@ export function makeBatchedMigration<T extends BatchedMigrationImplementation>(f
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export function validateBatchedMigrationImplementation(
|
|
57
|
-
fns: BatchedMigrationImplementation
|
|
57
|
+
fns: BatchedMigrationImplementation,
|
|
58
58
|
): asserts fns is BatchedMigrationImplementation {
|
|
59
59
|
if (typeof fns.getParameters !== 'function') {
|
|
60
60
|
throw new Error('getParameters() must be a function');
|
|
@@ -74,12 +74,12 @@ type NewBatchedMigration = Pick<
|
|
|
74
74
|
* project/timestamp pair, returns null, otherwise returns the inserted row.
|
|
75
75
|
*/
|
|
76
76
|
export async function insertBatchedMigration(
|
|
77
|
-
migration: NewBatchedMigration
|
|
77
|
+
migration: NewBatchedMigration,
|
|
78
78
|
): Promise<BatchedMigrationRow | null> {
|
|
79
79
|
return queryValidatedZeroOrOneRow(
|
|
80
80
|
sql.insert_batched_migration,
|
|
81
81
|
migration,
|
|
82
|
-
BatchedMigrationRowSchema
|
|
82
|
+
BatchedMigrationRowSchema,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -87,40 +87,40 @@ export async function selectAllBatchedMigrations(project: string) {
|
|
|
87
87
|
return queryValidatedRows(
|
|
88
88
|
sql.select_all_batched_migrations,
|
|
89
89
|
{ project },
|
|
90
|
-
BatchedMigrationRowSchema
|
|
90
|
+
BatchedMigrationRowSchema,
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export async function selectBatchedMigration(
|
|
95
95
|
project: string,
|
|
96
|
-
id: string
|
|
96
|
+
id: string,
|
|
97
97
|
): Promise<BatchedMigrationRow> {
|
|
98
98
|
return queryValidatedOneRow(
|
|
99
99
|
sql.select_batched_migration,
|
|
100
100
|
{ project, id },
|
|
101
|
-
BatchedMigrationRowSchema
|
|
101
|
+
BatchedMigrationRowSchema,
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export async function selectBatchedMigrationForTimestamp(
|
|
106
106
|
project: string,
|
|
107
|
-
timestamp: string
|
|
107
|
+
timestamp: string,
|
|
108
108
|
): Promise<BatchedMigrationRow> {
|
|
109
109
|
return queryValidatedOneRow(
|
|
110
110
|
sql.select_batched_migration_for_timestamp,
|
|
111
111
|
{ project, timestamp },
|
|
112
|
-
BatchedMigrationRowSchema
|
|
112
|
+
BatchedMigrationRowSchema,
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export async function updateBatchedMigrationStatus(
|
|
117
117
|
id: string,
|
|
118
|
-
status: BatchedMigrationStatus
|
|
118
|
+
status: BatchedMigrationStatus,
|
|
119
119
|
): Promise<BatchedMigrationRow> {
|
|
120
120
|
return queryValidatedOneRow(
|
|
121
121
|
sql.update_batched_migration_status,
|
|
122
122
|
{ id, status },
|
|
123
|
-
BatchedMigrationRowSchema
|
|
123
|
+
BatchedMigrationRowSchema,
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -100,7 +100,7 @@ describe('BatchedMigrationsRunner', () => {
|
|
|
100
100
|
runner.finalizeBatchedMigration('20230406184107_failing_migration', {
|
|
101
101
|
logProgress: false,
|
|
102
102
|
}),
|
|
103
|
-
"but it is 'failed'"
|
|
103
|
+
"but it is 'failed'",
|
|
104
104
|
);
|
|
105
105
|
|
|
106
106
|
const migrations = await selectAllBatchedMigrations('test');
|
|
@@ -60,7 +60,7 @@ export class BatchedMigrationsRunner extends EventEmitter {
|
|
|
60
60
|
if (!this.migrationFiles) {
|
|
61
61
|
this.migrationFiles = await readAndValidateMigrationsFromDirectories(
|
|
62
62
|
this.options.directories,
|
|
63
|
-
EXTENSIONS
|
|
63
|
+
EXTENSIONS,
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
return this.migrationFiles;
|
|
@@ -153,7 +153,7 @@ export class BatchedMigrationsRunner extends EventEmitter {
|
|
|
153
153
|
if (migration.status === 'succeeded') return;
|
|
154
154
|
|
|
155
155
|
throw new Error(
|
|
156
|
-
`Expected batched migration with identifier ${identifier} to be marked as 'succeeded', but it is '${migration.status}'
|
|
156
|
+
`Expected batched migration with identifier ${identifier} to be marked as 'succeeded', but it is '${migration.status}'.`,
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -208,14 +208,14 @@ export class BatchedMigrationsRunner extends EventEmitter {
|
|
|
208
208
|
let migration = await queryValidatedZeroOrOneRow(
|
|
209
209
|
sql.select_running_migration,
|
|
210
210
|
{ project: this.options.project },
|
|
211
|
-
BatchedMigrationRowSchema
|
|
211
|
+
BatchedMigrationRowSchema,
|
|
212
212
|
);
|
|
213
213
|
|
|
214
214
|
if (!migration) {
|
|
215
215
|
migration = await queryValidatedZeroOrOneRow(
|
|
216
216
|
sql.start_next_pending_migration,
|
|
217
217
|
{ project: this.options.project },
|
|
218
|
-
BatchedMigrationRowSchema
|
|
218
|
+
BatchedMigrationRowSchema,
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -252,7 +252,7 @@ export class BatchedMigrationsRunner extends EventEmitter {
|
|
|
252
252
|
} catch (err) {
|
|
253
253
|
this.emit('error', err);
|
|
254
254
|
}
|
|
255
|
-
}
|
|
255
|
+
},
|
|
256
256
|
);
|
|
257
257
|
|
|
258
258
|
return didWork;
|
|
@@ -271,7 +271,7 @@ export class BatchedMigrationsRunner extends EventEmitter {
|
|
|
271
271
|
let runner: BatchedMigrationsRunner | null = null;
|
|
272
272
|
|
|
273
273
|
function assertRunner(
|
|
274
|
-
runner: BatchedMigrationsRunner | null
|
|
274
|
+
runner: BatchedMigrationsRunner | null,
|
|
275
275
|
): asserts runner is BatchedMigrationsRunner {
|
|
276
276
|
if (!runner) throw new Error('Batched migrations not initialized');
|
|
277
277
|
}
|
|
@@ -320,7 +320,7 @@ export async function enqueueBatchedMigration(identifier: string) {
|
|
|
320
320
|
*/
|
|
321
321
|
export async function finalizeBatchedMigration(
|
|
322
322
|
identifier: string,
|
|
323
|
-
options?: BatchedMigrationFinalizeOptions
|
|
323
|
+
options?: BatchedMigrationFinalizeOptions,
|
|
324
324
|
) {
|
|
325
325
|
assertRunner(runner);
|
|
326
326
|
await runner.finalizeBatchedMigration(identifier, options);
|
|
@@ -20,7 +20,7 @@ async function withMigrationFiles(files: string[], fn: (tmpDir: string) => Promi
|
|
|
20
20
|
}
|
|
21
21
|
await fn(tmpDir.path);
|
|
22
22
|
},
|
|
23
|
-
{ unsafeCleanup: true }
|
|
23
|
+
{ unsafeCleanup: true },
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -30,7 +30,7 @@ describe('load-migrations', () => {
|
|
|
30
30
|
await withMigrationFiles(['001_testing.sql'], async (tmpDir) => {
|
|
31
31
|
await assert.isRejected(
|
|
32
32
|
readAndValidateMigrationsFromDirectory(tmpDir, ['.sql']),
|
|
33
|
-
'Invalid migration filename: 001_testing.sql'
|
|
33
|
+
'Invalid migration filename: 001_testing.sql',
|
|
34
34
|
);
|
|
35
35
|
});
|
|
36
36
|
});
|
|
@@ -41,9 +41,9 @@ describe('load-migrations', () => {
|
|
|
41
41
|
async (tmpDir) => {
|
|
42
42
|
await assert.isRejected(
|
|
43
43
|
readAndValidateMigrationsFromDirectory(tmpDir, ['.sql']),
|
|
44
|
-
'Duplicate migration timestamp'
|
|
44
|
+
'Duplicate migration timestamp',
|
|
45
45
|
);
|
|
46
|
-
}
|
|
46
|
+
},
|
|
47
47
|
);
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -84,7 +84,7 @@ describe('load-migrations', () => {
|
|
|
84
84
|
filename: '20220101010103_testing_3.sql',
|
|
85
85
|
timestamp: '20220101010103',
|
|
86
86
|
},
|
|
87
|
-
]
|
|
87
|
+
],
|
|
88
88
|
);
|
|
89
89
|
});
|
|
90
90
|
});
|
package/src/load-migrations.ts
CHANGED
|
@@ -26,10 +26,10 @@ export interface MigrationFile {
|
|
|
26
26
|
|
|
27
27
|
export async function readAndValidateMigrationsFromDirectory(
|
|
28
28
|
dir: string,
|
|
29
|
-
extensions: string[]
|
|
29
|
+
extensions: string[],
|
|
30
30
|
): Promise<MigrationFile[]> {
|
|
31
31
|
const migrationFiles = (await fs.readdir(dir)).filter((m) =>
|
|
32
|
-
extensions.some((e) => m.endsWith(e))
|
|
32
|
+
extensions.some((e) => m.endsWith(e)),
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
const migrations = migrationFiles.map((mf) => {
|
|
@@ -71,7 +71,7 @@ export async function readAndValidateMigrationsFromDirectory(
|
|
|
71
71
|
|
|
72
72
|
export async function readAndValidateMigrationsFromDirectories(
|
|
73
73
|
directories: string[],
|
|
74
|
-
extensions: string[]
|
|
74
|
+
extensions: string[],
|
|
75
75
|
): Promise<MigrationFile[]> {
|
|
76
76
|
const allMigrations: MigrationFile[] = [];
|
|
77
77
|
for (const directory of directories) {
|
|
@@ -34,14 +34,14 @@ export async function init(directories: string | string[], project: string) {
|
|
|
34
34
|
async () => {
|
|
35
35
|
logger.verbose(`Acquired lock ${lockName}`);
|
|
36
36
|
await initWithLock(migrationDirectories, project);
|
|
37
|
-
}
|
|
37
|
+
},
|
|
38
38
|
);
|
|
39
39
|
logger.verbose(`Released lock ${lockName}`);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function getMigrationsToExecute(
|
|
43
43
|
migrationFiles: MigrationFile[],
|
|
44
|
-
executedMigrations: { timestamp: string | null }[]
|
|
44
|
+
executedMigrations: { timestamp: string | null }[],
|
|
45
45
|
): MigrationFile[] {
|
|
46
46
|
// If no migrations have ever been run, run them all.
|
|
47
47
|
if (executedMigrations.length === 0) {
|
|
@@ -101,7 +101,7 @@ export async function initWithLock(directories: string[], project: string) {
|
|
|
101
101
|
// This revision was the most recent commit to `master` before the
|
|
102
102
|
// code handling indexes was removed.
|
|
103
103
|
'You must deploy revision 1aa43c7348fa24cf636413d720d06a2fa9e38ef2 first.',
|
|
104
|
-
].join('\n')
|
|
104
|
+
].join('\n'),
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|