@ftschopp/dynatable-migrations 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +102 -134
- package/USAGE.md +199 -60
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/down.d.ts +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +5 -20
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -17
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +5 -20
- package/dist/commands/up.js.map +1 -1
- package/dist/core/client.d.ts +7 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +25 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/loader.d.ts +8 -0
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/loader.js +76 -43
- package/dist/core/loader.js.map +1 -1
- package/dist/core/runner.d.ts +6 -2
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +124 -67
- package/dist/core/runner.js.map +1 -1
- package/dist/core/tracker.d.ts +20 -1
- package/dist/core/tracker.d.ts.map +1 -1
- package/dist/core/tracker.js +273 -83
- package/dist/core/tracker.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +10 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -3
- package/src/cli.ts +8 -3
- package/src/commands/down.ts +6 -22
- package/src/commands/status.ts +2 -19
- package/src/commands/up.ts +9 -22
- package/src/core/client.ts +24 -0
- package/src/core/loader.ts +88 -46
- package/src/core/runner.ts +151 -78
- package/src/core/tracker.ts +306 -94
- package/src/index.ts +2 -1
- package/src/types/index.ts +13 -1
package/src/core/tracker.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
PutCommand,
|
|
4
|
+
GetCommand,
|
|
5
|
+
QueryCommand,
|
|
6
|
+
UpdateCommand,
|
|
7
|
+
DeleteCommand,
|
|
8
|
+
TransactWriteCommand,
|
|
9
|
+
} from '@aws-sdk/lib-dynamodb';
|
|
3
10
|
import { MigrationTracker, MigrationRecord, SchemaChange, MigrationConfig } from '../types';
|
|
4
11
|
|
|
12
|
+
const LOCK_TTL_SECONDS = 300; // 5 minutes
|
|
13
|
+
|
|
5
14
|
export class DynamoDBMigrationTracker implements MigrationTracker {
|
|
6
15
|
private client: DynamoDBDocumentClient;
|
|
7
16
|
private tableName: string;
|
|
8
17
|
private trackingPrefix: string;
|
|
9
18
|
private gsi1Name: string;
|
|
19
|
+
private lockId: string | null = null;
|
|
10
20
|
|
|
11
21
|
constructor(client: DynamoDBDocumentClient, config: MigrationConfig) {
|
|
12
22
|
this.client = client;
|
|
@@ -36,18 +46,104 @@ export class DynamoDBMigrationTracker implements MigrationTracker {
|
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Acquire a distributed lock to prevent concurrent migrations
|
|
51
|
+
*/
|
|
52
|
+
async acquireLock(): Promise<boolean> {
|
|
53
|
+
const lockKey = {
|
|
54
|
+
PK: `${this.trackingPrefix}#LOCK`,
|
|
55
|
+
SK: `${this.trackingPrefix}#LOCK`,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const expiresAt = now + LOCK_TTL_SECONDS * 1000;
|
|
60
|
+
this.lockId = `lock-${now}-${Math.random().toString(36).substring(7)}`;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await this.client.send(
|
|
64
|
+
new PutCommand({
|
|
65
|
+
TableName: this.tableName,
|
|
66
|
+
Item: {
|
|
67
|
+
...lockKey,
|
|
68
|
+
lockId: this.lockId,
|
|
69
|
+
acquiredAt: new Date().toISOString(),
|
|
70
|
+
expiresAt,
|
|
71
|
+
},
|
|
72
|
+
// Only succeed if lock doesn't exist or has expired
|
|
73
|
+
ConditionExpression:
|
|
74
|
+
'attribute_not_exists(PK) OR expiresAt < :now',
|
|
75
|
+
ExpressionAttributeValues: {
|
|
76
|
+
':now': now,
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
return true;
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
if (error.name === 'ConditionalCheckFailedException') {
|
|
83
|
+
this.lockId = null;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Release the distributed lock
|
|
92
|
+
*/
|
|
93
|
+
async releaseLock(): Promise<void> {
|
|
94
|
+
if (!this.lockId) return;
|
|
95
|
+
|
|
96
|
+
const lockKey = {
|
|
97
|
+
PK: `${this.trackingPrefix}#LOCK`,
|
|
98
|
+
SK: `${this.trackingPrefix}#LOCK`,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await this.client.send(
|
|
103
|
+
new DeleteCommand({
|
|
104
|
+
TableName: this.tableName,
|
|
105
|
+
Key: lockKey,
|
|
106
|
+
// Only delete if we own the lock
|
|
107
|
+
ConditionExpression: 'lockId = :lockId',
|
|
108
|
+
ExpressionAttributeValues: {
|
|
109
|
+
':lockId': this.lockId,
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
} catch (error: any) {
|
|
114
|
+
// Lock may have expired or been taken by someone else
|
|
115
|
+
if (error.name !== 'ConditionalCheckFailedException') {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
this.lockId = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all applied migrations with pagination support
|
|
125
|
+
*/
|
|
39
126
|
async getAppliedMigrations(): Promise<MigrationRecord[]> {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
127
|
+
const items: MigrationRecord[] = [];
|
|
128
|
+
let lastKey: Record<string, any> | undefined;
|
|
129
|
+
|
|
130
|
+
do {
|
|
131
|
+
const result = await this.client.send(
|
|
132
|
+
new QueryCommand({
|
|
133
|
+
TableName: this.tableName,
|
|
134
|
+
KeyConditionExpression: 'PK = :pk',
|
|
135
|
+
ExpressionAttributeValues: {
|
|
136
|
+
':pk': this.trackingPrefix,
|
|
137
|
+
},
|
|
138
|
+
ExclusiveStartKey: lastKey,
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
items.push(...((result.Items || []) as MigrationRecord[]));
|
|
143
|
+
lastKey = result.LastEvaluatedKey;
|
|
144
|
+
} while (lastKey);
|
|
49
145
|
|
|
50
|
-
return
|
|
146
|
+
return items;
|
|
51
147
|
}
|
|
52
148
|
|
|
53
149
|
async getCurrentVersion(): Promise<string | null> {
|
|
@@ -64,70 +160,138 @@ export class DynamoDBMigrationTracker implements MigrationTracker {
|
|
|
64
160
|
return result.Item?.currentVersion || null;
|
|
65
161
|
}
|
|
66
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Mark migration as applied using TransactWrite for atomicity
|
|
165
|
+
* Handles both new migrations and re-applying rolled back migrations
|
|
166
|
+
*/
|
|
67
167
|
async markAsApplied(
|
|
68
168
|
version: string,
|
|
69
169
|
name: string,
|
|
70
170
|
schemaDefinition?: Record<string, any>,
|
|
71
|
-
schemaChanges?: SchemaChange[]
|
|
171
|
+
schemaChanges?: SchemaChange[],
|
|
172
|
+
checksum?: string
|
|
72
173
|
): Promise<void> {
|
|
73
174
|
const now = new Date().toISOString();
|
|
74
175
|
|
|
75
|
-
//
|
|
76
|
-
await this.
|
|
77
|
-
new PutCommand({
|
|
78
|
-
TableName: this.tableName,
|
|
79
|
-
Item: {
|
|
80
|
-
PK: this.trackingPrefix,
|
|
81
|
-
SK: version,
|
|
82
|
-
version,
|
|
83
|
-
name,
|
|
84
|
-
timestamp: now,
|
|
85
|
-
appliedAt: now,
|
|
86
|
-
status: 'applied',
|
|
87
|
-
schemaDefinition,
|
|
88
|
-
schemaChanges,
|
|
89
|
-
} as MigrationRecord,
|
|
90
|
-
})
|
|
91
|
-
);
|
|
176
|
+
// Check if migration record already exists (e.g., rolled back or failed)
|
|
177
|
+
const existing = await this.getMigration(version);
|
|
92
178
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
179
|
+
if (existing) {
|
|
180
|
+
// Update existing record (re-applying a rolled back or failed migration)
|
|
181
|
+
// Build dynamic update expression - DynamoDB doesn't accept undefined values
|
|
182
|
+
const setParts = ['#status = :status', 'appliedAt = :appliedAt', '#name = :name'];
|
|
183
|
+
const expressionValues: Record<string, any> = {
|
|
184
|
+
':status': 'applied',
|
|
185
|
+
':appliedAt': now,
|
|
186
|
+
':name': name,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (schemaDefinition !== undefined) {
|
|
190
|
+
setParts.push('schemaDefinition = :schemaDef');
|
|
191
|
+
expressionValues[':schemaDef'] = schemaDefinition;
|
|
192
|
+
}
|
|
193
|
+
if (schemaChanges !== undefined) {
|
|
194
|
+
setParts.push('schemaChanges = :schemaChanges');
|
|
195
|
+
expressionValues[':schemaChanges'] = schemaChanges;
|
|
196
|
+
}
|
|
197
|
+
if (checksum !== undefined) {
|
|
198
|
+
setParts.push('checksum = :checksum');
|
|
199
|
+
expressionValues[':checksum'] = checksum;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await this.client.send(
|
|
203
|
+
new TransactWriteCommand({
|
|
204
|
+
TransactItems: [
|
|
205
|
+
{
|
|
206
|
+
Update: {
|
|
207
|
+
TableName: this.tableName,
|
|
208
|
+
Key: {
|
|
209
|
+
PK: this.trackingPrefix,
|
|
210
|
+
SK: version,
|
|
211
|
+
},
|
|
212
|
+
UpdateExpression: `SET ${setParts.join(', ')} REMOVE #error, failedAt, rolledBackAt`,
|
|
213
|
+
ExpressionAttributeNames: {
|
|
214
|
+
'#status': 'status',
|
|
215
|
+
'#name': 'name',
|
|
216
|
+
'#error': 'error',
|
|
217
|
+
},
|
|
218
|
+
ExpressionAttributeValues: expressionValues,
|
|
219
|
+
// Only allow re-applying if not currently applied
|
|
220
|
+
ConditionExpression: '#status <> :status',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
Update: {
|
|
225
|
+
TableName: this.tableName,
|
|
226
|
+
Key: {
|
|
227
|
+
PK: `${this.trackingPrefix}#CURRENT`,
|
|
228
|
+
SK: `${this.trackingPrefix}#CURRENT`,
|
|
229
|
+
},
|
|
230
|
+
UpdateExpression:
|
|
231
|
+
'SET currentVersion = :version, updatedAt = :updatedAt, GSI1SK = :gsi1sk',
|
|
232
|
+
ExpressionAttributeValues: {
|
|
233
|
+
':version': version,
|
|
234
|
+
':updatedAt': now,
|
|
235
|
+
':gsi1sk': version,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
} else {
|
|
243
|
+
// Create new migration record
|
|
244
|
+
await this.client.send(
|
|
245
|
+
new TransactWriteCommand({
|
|
246
|
+
TransactItems: [
|
|
247
|
+
{
|
|
248
|
+
Put: {
|
|
249
|
+
TableName: this.tableName,
|
|
250
|
+
Item: {
|
|
251
|
+
PK: this.trackingPrefix,
|
|
252
|
+
SK: version,
|
|
253
|
+
version,
|
|
254
|
+
name,
|
|
255
|
+
timestamp: now,
|
|
256
|
+
appliedAt: now,
|
|
257
|
+
status: 'applied',
|
|
258
|
+
schemaDefinition,
|
|
259
|
+
schemaChanges,
|
|
260
|
+
checksum,
|
|
261
|
+
} as MigrationRecord,
|
|
262
|
+
// Ensure migration wasn't already applied
|
|
263
|
+
ConditionExpression: 'attribute_not_exists(PK)',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
Update: {
|
|
268
|
+
TableName: this.tableName,
|
|
269
|
+
Key: {
|
|
270
|
+
PK: `${this.trackingPrefix}#CURRENT`,
|
|
271
|
+
SK: `${this.trackingPrefix}#CURRENT`,
|
|
272
|
+
},
|
|
273
|
+
UpdateExpression:
|
|
274
|
+
'SET currentVersion = :version, updatedAt = :updatedAt, GSI1SK = :gsi1sk',
|
|
275
|
+
ExpressionAttributeValues: {
|
|
276
|
+
':version': version,
|
|
277
|
+
':updatedAt': now,
|
|
278
|
+
':gsi1sk': version,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
}
|
|
109
286
|
}
|
|
110
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Mark migration as rolled back using TransactWrite for atomicity
|
|
290
|
+
*/
|
|
111
291
|
async markAsRolledBack(version: string): Promise<void> {
|
|
112
|
-
|
|
113
|
-
new UpdateCommand({
|
|
114
|
-
TableName: this.tableName,
|
|
115
|
-
Key: {
|
|
116
|
-
PK: this.trackingPrefix,
|
|
117
|
-
SK: version,
|
|
118
|
-
},
|
|
119
|
-
UpdateExpression: 'SET #status = :status, rolledBackAt = :timestamp',
|
|
120
|
-
ExpressionAttributeNames: {
|
|
121
|
-
'#status': 'status',
|
|
122
|
-
},
|
|
123
|
-
ExpressionAttributeValues: {
|
|
124
|
-
':status': 'rolled_back',
|
|
125
|
-
':timestamp': new Date().toISOString(),
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
);
|
|
292
|
+
const now = new Date().toISOString();
|
|
129
293
|
|
|
130
|
-
// Find previous version
|
|
294
|
+
// Find previous version
|
|
131
295
|
const migrations = await this.getAppliedMigrations();
|
|
132
296
|
const appliedMigrations = migrations
|
|
133
297
|
.filter((m) => m.status === 'applied' && m.version !== version)
|
|
@@ -135,43 +299,91 @@ export class DynamoDBMigrationTracker implements MigrationTracker {
|
|
|
135
299
|
|
|
136
300
|
const previousVersion = appliedMigrations[0]?.version || 'v0000';
|
|
137
301
|
|
|
302
|
+
// Use TransactWrite to atomically update record and pointer
|
|
138
303
|
await this.client.send(
|
|
139
|
-
new
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
304
|
+
new TransactWriteCommand({
|
|
305
|
+
TransactItems: [
|
|
306
|
+
{
|
|
307
|
+
Update: {
|
|
308
|
+
TableName: this.tableName,
|
|
309
|
+
Key: {
|
|
310
|
+
PK: this.trackingPrefix,
|
|
311
|
+
SK: version,
|
|
312
|
+
},
|
|
313
|
+
UpdateExpression: 'SET #status = :status, rolledBackAt = :timestamp',
|
|
314
|
+
ExpressionAttributeNames: {
|
|
315
|
+
'#status': 'status',
|
|
316
|
+
},
|
|
317
|
+
ExpressionAttributeValues: {
|
|
318
|
+
':status': 'rolled_back',
|
|
319
|
+
':timestamp': now,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
Update: {
|
|
325
|
+
TableName: this.tableName,
|
|
326
|
+
Key: {
|
|
327
|
+
PK: `${this.trackingPrefix}#CURRENT`,
|
|
328
|
+
SK: `${this.trackingPrefix}#CURRENT`,
|
|
329
|
+
},
|
|
330
|
+
UpdateExpression:
|
|
331
|
+
'SET currentVersion = :version, updatedAt = :updatedAt, GSI1SK = :gsi1sk',
|
|
332
|
+
ExpressionAttributeValues: {
|
|
333
|
+
':version': previousVersion,
|
|
334
|
+
':updatedAt': now,
|
|
335
|
+
':gsi1sk': previousVersion,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
],
|
|
151
340
|
})
|
|
152
341
|
);
|
|
153
342
|
}
|
|
154
343
|
|
|
155
344
|
async markAsFailed(version: string, error: string): Promise<void> {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
345
|
+
// Check if the migration record exists first
|
|
346
|
+
const existing = await this.getMigration(version);
|
|
347
|
+
|
|
348
|
+
if (existing) {
|
|
349
|
+
// Update existing record
|
|
350
|
+
await this.client.send(
|
|
351
|
+
new UpdateCommand({
|
|
352
|
+
TableName: this.tableName,
|
|
353
|
+
Key: {
|
|
354
|
+
PK: this.trackingPrefix,
|
|
355
|
+
SK: version,
|
|
356
|
+
},
|
|
357
|
+
UpdateExpression: 'SET #status = :status, #error = :error, failedAt = :timestamp',
|
|
358
|
+
ExpressionAttributeNames: {
|
|
359
|
+
'#status': 'status',
|
|
360
|
+
'#error': 'error',
|
|
361
|
+
},
|
|
362
|
+
ExpressionAttributeValues: {
|
|
363
|
+
':status': 'failed',
|
|
364
|
+
':error': error,
|
|
365
|
+
':timestamp': new Date().toISOString(),
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
);
|
|
369
|
+
} else {
|
|
370
|
+
// Create new record for failed migration
|
|
371
|
+
await this.client.send(
|
|
372
|
+
new PutCommand({
|
|
373
|
+
TableName: this.tableName,
|
|
374
|
+
Item: {
|
|
375
|
+
PK: this.trackingPrefix,
|
|
376
|
+
SK: version,
|
|
377
|
+
version,
|
|
378
|
+
name: 'unknown',
|
|
379
|
+
status: 'failed',
|
|
380
|
+
error,
|
|
381
|
+
failedAt: new Date().toISOString(),
|
|
382
|
+
timestamp: new Date().toISOString(),
|
|
383
|
+
},
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
}
|
|
175
387
|
}
|
|
176
388
|
|
|
177
389
|
async recordSchemaChange(change: SchemaChange): Promise<void> {
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,9 @@ export * from './types';
|
|
|
9
9
|
// Core
|
|
10
10
|
export { DynamoDBMigrationTracker } from './core/tracker';
|
|
11
11
|
export { MigrationLoader } from './core/loader';
|
|
12
|
-
export { MigrationRunner } from './core/runner';
|
|
12
|
+
export { MigrationRunner, type RunOptions } from './core/runner';
|
|
13
13
|
export { ConfigLoader, loadConfig } from './core/config';
|
|
14
|
+
export { createDynamoDBClient } from './core/client';
|
|
14
15
|
|
|
15
16
|
// Template
|
|
16
17
|
export { generateMigrationTemplate } from './templates/migration';
|
package/src/types/index.ts
CHANGED
|
@@ -138,6 +138,16 @@ export interface MigrationTracker {
|
|
|
138
138
|
*/
|
|
139
139
|
getCurrentVersion(): Promise<string | null>;
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Acquire a distributed lock
|
|
143
|
+
*/
|
|
144
|
+
acquireLock(): Promise<boolean>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Release the distributed lock
|
|
148
|
+
*/
|
|
149
|
+
releaseLock(): Promise<void>;
|
|
150
|
+
|
|
141
151
|
/**
|
|
142
152
|
* Mark migration as applied
|
|
143
153
|
*/
|
|
@@ -145,7 +155,8 @@ export interface MigrationTracker {
|
|
|
145
155
|
version: string,
|
|
146
156
|
name: string,
|
|
147
157
|
schemaDefinition?: Record<string, any>,
|
|
148
|
-
schemaChanges?: SchemaChange[]
|
|
158
|
+
schemaChanges?: SchemaChange[],
|
|
159
|
+
checksum?: string
|
|
149
160
|
): Promise<void>;
|
|
150
161
|
|
|
151
162
|
/**
|
|
@@ -182,6 +193,7 @@ export interface MigrationFile {
|
|
|
182
193
|
name: string;
|
|
183
194
|
filePath: string;
|
|
184
195
|
migration: Migration;
|
|
196
|
+
checksum?: string;
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
/**
|