masterrecord 0.3.31 → 0.3.32
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/Migrations/cli.js +17 -4
- package/Migrations/dependencyGraph.js +108 -0
- package/Migrations/migrationTemplate.js +144 -0
- package/Migrations/migrations.js +42 -10
- package/context.js +205 -0
- package/package.json +1 -1
- package/readme.md +574 -0
- package/test/seed-data-test.js +212 -0
- package/test/seed-features-integration-test.js +418 -0
- package/test/seed-migration-template-test.js +220 -0
package/Migrations/cli.js
CHANGED
|
@@ -275,15 +275,17 @@ program.option('-V', 'output the version');
|
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
277
|
var cleanEntities = migration.cleanEntities(contextInstance.__entities);
|
|
278
|
+
var seedData = contextInstance.__contextSeedData || {};
|
|
279
|
+
var seedConfig = contextInstance.__contextSeedConfig || {};
|
|
278
280
|
|
|
279
281
|
// Skip if no changes between snapshot schema and current entities
|
|
280
|
-
const has = migration.hasChanges(contextSnapshot.schema || [], cleanEntities || []);
|
|
282
|
+
const has = migration.hasChanges(contextSnapshot.schema || [], cleanEntities || [], seedData);
|
|
281
283
|
if(!has){
|
|
282
284
|
console.log(`No changes detected for ${path.basename(contextAbs)}. Skipping.`);
|
|
283
285
|
return;
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
var newEntity = migration.template(name, contextSnapshot.schema, cleanEntities);
|
|
288
|
+
var newEntity = migration.template(name, contextSnapshot.schema, cleanEntities, seedData, seedConfig);
|
|
287
289
|
if(!fs.existsSync(migBase)){
|
|
288
290
|
try{ fs.mkdirSync(migBase, { recursive: true }); }catch(_){ /* ignore */ }
|
|
289
291
|
}
|
|
@@ -465,6 +467,7 @@ program.option('-V', 'output the version');
|
|
|
465
467
|
executedLocation : executedLocation,
|
|
466
468
|
context : contextInstance,
|
|
467
469
|
contextEntities : cleanEntities,
|
|
470
|
+
contextSeedData: contextInstance.__contextSeedData || {},
|
|
468
471
|
contextFileName: contextFileName
|
|
469
472
|
}
|
|
470
473
|
|
|
@@ -582,6 +585,8 @@ program.option('-V', 'output the version');
|
|
|
582
585
|
executedLocation : executedLocation,
|
|
583
586
|
context : contextInstance,
|
|
584
587
|
contextEntities : cleanEntities,
|
|
588
|
+
contextSeedData: contextInstance.__contextSeedData || {},
|
|
589
|
+
contextSeedConfig: contextInstance.__contextSeedConfig || {},
|
|
585
590
|
contextFileName: contextFileName
|
|
586
591
|
}
|
|
587
592
|
migration.createSnapShot(snap);
|
|
@@ -832,6 +837,8 @@ program.option('-V', 'output the version');
|
|
|
832
837
|
executedLocation : executedLocation,
|
|
833
838
|
context : contextInstance,
|
|
834
839
|
contextEntities : cleanEntities,
|
|
840
|
+
contextSeedData: contextInstance.__contextSeedData || {},
|
|
841
|
+
contextSeedConfig: contextInstance.__contextSeedConfig || {},
|
|
835
842
|
contextFileName: path.basename(snapshotFile).replace('_contextSnapShot.json','')
|
|
836
843
|
}
|
|
837
844
|
migration.createSnapShot(snap);
|
|
@@ -887,13 +894,15 @@ program.option('-V', 'output the version');
|
|
|
887
894
|
}
|
|
888
895
|
var migration = new Migration();
|
|
889
896
|
var cleanEntities = migration.cleanEntities(contextInstance.__entities);
|
|
897
|
+
var seedData = contextInstance.__contextSeedData || {};
|
|
898
|
+
var seedConfig = contextInstance.__contextSeedConfig || {};
|
|
890
899
|
// If no changes, skip with message
|
|
891
|
-
const has = migration.hasChanges(cs.schema || [], cleanEntities || []);
|
|
900
|
+
const has = migration.hasChanges(cs.schema || [], cleanEntities || [], seedData);
|
|
892
901
|
if(!has){
|
|
893
902
|
console.log(`No changes detected for ${path.basename(contextAbs)}. Skipping.`);
|
|
894
903
|
continue;
|
|
895
904
|
}
|
|
896
|
-
var newEntity = migration.template(name, cs.schema, cleanEntities);
|
|
905
|
+
var newEntity = migration.template(name, cs.schema, cleanEntities, seedData, seedConfig);
|
|
897
906
|
if(!fs.existsSync(migBase)){
|
|
898
907
|
try{ fs.mkdirSync(migBase, { recursive: true }); }catch(_){ /* ignore */ }
|
|
899
908
|
}
|
|
@@ -1010,6 +1019,8 @@ program.option('-V', 'output the version');
|
|
|
1010
1019
|
executedLocation : executedLocation,
|
|
1011
1020
|
context : contextInstance,
|
|
1012
1021
|
contextEntities : cleanEntities,
|
|
1022
|
+
contextSeedData: contextInstance.__contextSeedData || {},
|
|
1023
|
+
contextSeedConfig: contextInstance.__contextSeedConfig || {},
|
|
1013
1024
|
contextFileName: entry.ctxName
|
|
1014
1025
|
}
|
|
1015
1026
|
migration.createSnapShot(snap);
|
|
@@ -1072,6 +1083,8 @@ program.option('-V', 'output the version');
|
|
|
1072
1083
|
file : abs,
|
|
1073
1084
|
executedLocation : executedLocation,
|
|
1074
1085
|
contextEntities : [],
|
|
1086
|
+
contextSeedData: {},
|
|
1087
|
+
contextSeedConfig: {},
|
|
1075
1088
|
contextFileName: key
|
|
1076
1089
|
};
|
|
1077
1090
|
migration.createSnapShot(snap);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency graph for topological sorting of seed data
|
|
3
|
+
* Uses Kahn's algorithm to order tables by foreign key dependencies
|
|
4
|
+
*/
|
|
5
|
+
class DependencyGraph {
|
|
6
|
+
constructor(entities) {
|
|
7
|
+
this.entities = entities;
|
|
8
|
+
this.graph = new Map(); // adjacency list: parent -> [children]
|
|
9
|
+
this.inDegree = new Map(); // in-degree for each table (number of dependencies)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build dependency graph from entity relationships
|
|
14
|
+
* Tables with belongsTo relationships depend on their foreign tables
|
|
15
|
+
*/
|
|
16
|
+
buildFromEntities() {
|
|
17
|
+
// Initialize graph structure for all entities
|
|
18
|
+
this.entities.forEach(entity => {
|
|
19
|
+
const tableName = entity.__name;
|
|
20
|
+
if (!tableName) return;
|
|
21
|
+
|
|
22
|
+
this.graph.set(tableName, []);
|
|
23
|
+
this.inDegree.set(tableName, 0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Build edges from belongsTo relationships
|
|
27
|
+
this.entities.forEach(entity => {
|
|
28
|
+
const tableName = entity.__name;
|
|
29
|
+
if (!tableName) return;
|
|
30
|
+
|
|
31
|
+
// Find belongsTo relationships (dependencies)
|
|
32
|
+
Object.keys(entity).forEach(key => {
|
|
33
|
+
const field = entity[key];
|
|
34
|
+
|
|
35
|
+
// Check if this is a belongsTo relationship with a foreign table
|
|
36
|
+
if (field && typeof field === 'object' &&
|
|
37
|
+
field.relationshipType === 'belongsTo' &&
|
|
38
|
+
field.foreignTable) {
|
|
39
|
+
|
|
40
|
+
const foreignTable = field.foreignTable;
|
|
41
|
+
|
|
42
|
+
// Ensure foreign table exists in graph
|
|
43
|
+
if (!this.graph.has(foreignTable)) {
|
|
44
|
+
this.graph.set(foreignTable, []);
|
|
45
|
+
this.inDegree.set(foreignTable, 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add edge: foreignTable -> tableName (tableName depends on foreignTable)
|
|
49
|
+
this.graph.get(foreignTable).push(tableName);
|
|
50
|
+
this.inDegree.set(tableName, this.inDegree.get(tableName) + 1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Perform topological sort using Kahn's algorithm
|
|
58
|
+
* @returns {Array<string>} Ordered list of table names
|
|
59
|
+
* @throws {Error} If circular dependency detected
|
|
60
|
+
*/
|
|
61
|
+
topologicalSort() {
|
|
62
|
+
const result = [];
|
|
63
|
+
const queue = [];
|
|
64
|
+
const inDegreeCopy = new Map(this.inDegree);
|
|
65
|
+
|
|
66
|
+
// Start with nodes that have no dependencies (in-degree = 0)
|
|
67
|
+
for (const [node, degree] of inDegreeCopy.entries()) {
|
|
68
|
+
if (degree === 0) {
|
|
69
|
+
queue.push(node);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
while (queue.length > 0) {
|
|
74
|
+
const current = queue.shift();
|
|
75
|
+
result.push(current);
|
|
76
|
+
|
|
77
|
+
// Process all neighbors (tables that depend on current)
|
|
78
|
+
const neighbors = this.graph.get(current) || [];
|
|
79
|
+
neighbors.forEach(neighbor => {
|
|
80
|
+
inDegreeCopy.set(neighbor, inDegreeCopy.get(neighbor) - 1);
|
|
81
|
+
if (inDegreeCopy.get(neighbor) === 0) {
|
|
82
|
+
queue.push(neighbor);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Detect cycles: if we couldn't visit all nodes, there's a cycle
|
|
88
|
+
if (result.length !== this.inDegree.size) {
|
|
89
|
+
const unvisited = Array.from(this.inDegree.keys()).filter(k => !result.includes(k));
|
|
90
|
+
throw new Error(`Circular dependency detected in tables: ${unvisited.join(' <-> ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get topologically sorted list filtered to only tables with seed data
|
|
98
|
+
* @param {Object} seedData - Object with table names as keys
|
|
99
|
+
* @returns {Array<string>} Ordered list of table names that have seed data
|
|
100
|
+
*/
|
|
101
|
+
filterToSeededTables(seedData) {
|
|
102
|
+
const sorted = this.topologicalSort();
|
|
103
|
+
const seededTables = Object.keys(seedData);
|
|
104
|
+
return sorted.filter(table => seededTables.includes(table));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = DependencyGraph;
|
|
@@ -152,6 +152,150 @@ module.exports = ${this.name};
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
seedData(type, tableName, records, currentEnv = 'development'){
|
|
156
|
+
if(!records || records.length === 0) return;
|
|
157
|
+
|
|
158
|
+
if(type === "up"){
|
|
159
|
+
// Filter records by environment first
|
|
160
|
+
const filteredRecords = records.filter(record => {
|
|
161
|
+
const envCondition = record.__seedEnv;
|
|
162
|
+
if (envCondition && envCondition.strategy === 'generation-time') {
|
|
163
|
+
return envCondition.conditions.includes(currentEnv);
|
|
164
|
+
}
|
|
165
|
+
return true; // No environment condition, include record
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (filteredRecords.length === 0) return;
|
|
169
|
+
|
|
170
|
+
// Check if all records are factory-generated
|
|
171
|
+
const allGenerated = filteredRecords.every(r => r.__seedMeta?.generated);
|
|
172
|
+
|
|
173
|
+
// Use optimized loop syntax for bulk factory data (10+ records)
|
|
174
|
+
if (allGenerated && filteredRecords.length >= 10) {
|
|
175
|
+
this.#up += os.EOL + ` const factoryRecords = [`;
|
|
176
|
+
|
|
177
|
+
filteredRecords.forEach((record, i) => {
|
|
178
|
+
const cleanRecord = { ...record };
|
|
179
|
+
delete cleanRecord.__rollback;
|
|
180
|
+
delete cleanRecord.__seedEnv;
|
|
181
|
+
delete cleanRecord.__seedStrategy;
|
|
182
|
+
delete cleanRecord.__seedMeta;
|
|
183
|
+
|
|
184
|
+
const recordStr = JSON.stringify(cleanRecord);
|
|
185
|
+
this.#up += os.EOL + ` ${recordStr}${i < filteredRecords.length - 1 ? ',' : ''}`;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
this.#up += os.EOL + ` ];`;
|
|
189
|
+
this.#up += os.EOL + ` for (const record of factoryRecords) {`;
|
|
190
|
+
this.#up += os.EOL + ` await table.${tableName}.create(record);`;
|
|
191
|
+
this.#up += os.EOL + ` }`;
|
|
192
|
+
} else {
|
|
193
|
+
// Standard individual inserts for non-factory or small batches
|
|
194
|
+
filteredRecords.forEach(record => {
|
|
195
|
+
const strategy = record.__seedStrategy;
|
|
196
|
+
|
|
197
|
+
// Clean up metadata before generating migration code
|
|
198
|
+
const cleanRecord = { ...record };
|
|
199
|
+
delete cleanRecord.__rollback;
|
|
200
|
+
delete cleanRecord.__seedEnv;
|
|
201
|
+
delete cleanRecord.__seedStrategy;
|
|
202
|
+
delete cleanRecord.__seedMeta;
|
|
203
|
+
|
|
204
|
+
// Handle upsert strategy
|
|
205
|
+
if (strategy && strategy.type === 'upsert') {
|
|
206
|
+
this._generateUpsert(tableName, cleanRecord, strategy);
|
|
207
|
+
} else {
|
|
208
|
+
// Standard insert
|
|
209
|
+
const recordStr = JSON.stringify(cleanRecord);
|
|
210
|
+
|
|
211
|
+
// Check if record is too long for single line (> 80 chars)
|
|
212
|
+
if (recordStr.length > 80) {
|
|
213
|
+
// Multi-line format with proper indentation
|
|
214
|
+
const formattedRecord = JSON.stringify(cleanRecord, null, 12)
|
|
215
|
+
.split('\n')
|
|
216
|
+
.join(os.EOL + ' ');
|
|
217
|
+
this.#up += os.EOL + ` await table.${tableName}.create(${formattedRecord});`;
|
|
218
|
+
} else {
|
|
219
|
+
// Single-line format
|
|
220
|
+
this.#up += os.EOL + ` await table.${tableName}.create(${recordStr});`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_generateUpsert(tableName, cleanRecord, strategy) {
|
|
229
|
+
const conflictKey = strategy.conflictKey === 'primaryKey'
|
|
230
|
+
? (cleanRecord.id !== undefined ? 'id' : Object.keys(cleanRecord)[0])
|
|
231
|
+
: strategy.conflictKey;
|
|
232
|
+
|
|
233
|
+
const conflictValue = cleanRecord[conflictKey];
|
|
234
|
+
if (conflictValue === undefined) {
|
|
235
|
+
throw new Error(`Upsert requires a value for conflict key: ${conflictKey}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.#up += os.EOL + ` {`;
|
|
239
|
+
this.#up += os.EOL + ` const existing = await table.${tableName}.where(r => r.${conflictKey} == ${JSON.stringify(conflictValue)}).single();`;
|
|
240
|
+
this.#up += os.EOL + ` if (existing) {`;
|
|
241
|
+
|
|
242
|
+
// Update logic
|
|
243
|
+
if (strategy.updateFields && Array.isArray(strategy.updateFields)) {
|
|
244
|
+
strategy.updateFields.forEach(field => {
|
|
245
|
+
if (cleanRecord[field] !== undefined) {
|
|
246
|
+
this.#up += os.EOL + ` existing.${field} = ${JSON.stringify(cleanRecord[field])};`;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
// Update all fields except conflict key
|
|
251
|
+
Object.keys(cleanRecord).forEach(field => {
|
|
252
|
+
if (field !== conflictKey) {
|
|
253
|
+
this.#up += os.EOL + ` existing.${field} = ${JSON.stringify(cleanRecord[field])};`;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.#up += os.EOL + ` await existing.save();`;
|
|
259
|
+
this.#up += os.EOL + ` } else {`;
|
|
260
|
+
this.#up += os.EOL + ` await table.${tableName}.create(${JSON.stringify(cleanRecord)});`;
|
|
261
|
+
this.#up += os.EOL + ` }`;
|
|
262
|
+
this.#up += os.EOL + ` }`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
seedDataDown(type, tableName, records, config){
|
|
266
|
+
if(type !== "down" || !config || !config.generateDownMigrations) return;
|
|
267
|
+
if(!records || records.length === 0) return;
|
|
268
|
+
|
|
269
|
+
// Reverse order for safe FK deletion (children before parents)
|
|
270
|
+
const reversed = [...records].reverse();
|
|
271
|
+
|
|
272
|
+
reversed.forEach(record => {
|
|
273
|
+
const rollback = record.__rollback;
|
|
274
|
+
if (!rollback || !rollback.value) {
|
|
275
|
+
// Skip if no rollback metadata (e.g., no primary key specified)
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const pkValue = rollback.value;
|
|
280
|
+
const pkKey = rollback.key || 'id';
|
|
281
|
+
|
|
282
|
+
// Generate delete code with error handling
|
|
283
|
+
this.#down += os.EOL + ` try {`;
|
|
284
|
+
this.#down += os.EOL + ` const record = await table.${tableName}.findById(${JSON.stringify(pkValue)});`;
|
|
285
|
+
this.#down += os.EOL + ` if (record) await record.delete();`;
|
|
286
|
+
this.#down += os.EOL + ` } catch (e) {`;
|
|
287
|
+
|
|
288
|
+
if (config.onRollbackError === 'throw') {
|
|
289
|
+
this.#down += os.EOL + ` throw new Error('Seed rollback failed: ${tableName} id=${pkValue} - ' + e.message);`;
|
|
290
|
+
} else if (config.onRollbackError === 'warn') {
|
|
291
|
+
this.#down += os.EOL + ` console.warn('Seed rollback: ${tableName} id=${pkValue} not found or error:', e.message);`;
|
|
292
|
+
}
|
|
293
|
+
// else ignore (onRollbackError === 'ignore')
|
|
294
|
+
|
|
295
|
+
this.#down += os.EOL + ` }`;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
155
299
|
}
|
|
156
300
|
|
|
157
301
|
module.exports = MigrationTemplate;
|
package/Migrations/migrations.js
CHANGED
|
@@ -28,7 +28,8 @@ class Migrations{
|
|
|
28
28
|
newIndexes : [],
|
|
29
29
|
deletedIndexes : [],
|
|
30
30
|
newCompositeIndexes : [],
|
|
31
|
-
deletedCompositeIndexes : []
|
|
31
|
+
deletedCompositeIndexes : [],
|
|
32
|
+
newSeedData : []
|
|
32
33
|
}
|
|
33
34
|
tables.push(table);
|
|
34
35
|
});
|
|
@@ -46,7 +47,8 @@ class Migrations{
|
|
|
46
47
|
newIndexes : [],
|
|
47
48
|
deletedIndexes : [],
|
|
48
49
|
newCompositeIndexes : [],
|
|
49
|
-
deletedCompositeIndexes : []
|
|
50
|
+
deletedCompositeIndexes : [],
|
|
51
|
+
newSeedData : []
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
oldSchema.forEach(function (oldItem, index) {
|
|
@@ -161,7 +163,7 @@ class Migrations{
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
// build table to build new migration snapshot
|
|
164
|
-
#buildMigrationObject(oldSchema, newSchema){
|
|
166
|
+
#buildMigrationObject(oldSchema, newSchema, newSeedData = {}){
|
|
165
167
|
|
|
166
168
|
var tables = this.#organizeSchemaByTables(oldSchema, newSchema);
|
|
167
169
|
|
|
@@ -173,6 +175,7 @@ class Migrations{
|
|
|
173
175
|
tables = this.#findDeletedIndexes(tables);
|
|
174
176
|
tables = this.#findNewCompositeIndexes(tables);
|
|
175
177
|
tables = this.#findDeletedCompositeIndexes(tables);
|
|
178
|
+
tables = this.#findNewSeedData(tables, newSeedData);
|
|
176
179
|
return tables;
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -325,6 +328,17 @@ class Migrations{
|
|
|
325
328
|
return tables;
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
#findNewSeedData(tables, newSeedData) {
|
|
332
|
+
// newSeedData is from schema snapshot: { tableName: [records] }
|
|
333
|
+
tables.forEach(function(item) {
|
|
334
|
+
const tableSeedData = newSeedData[item.name];
|
|
335
|
+
if (tableSeedData && tableSeedData.length > 0) {
|
|
336
|
+
item.newSeedData = tableSeedData;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
return tables;
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
|
|
329
343
|
|
|
330
344
|
findContextFile(executedLocation, contextFileName){
|
|
@@ -397,11 +411,18 @@ class Migrations{
|
|
|
397
411
|
const relMigrationFolder = '.'; // the snapshot sits inside migrationsDirectory
|
|
398
412
|
const relSnapshotLocation = path.basename(snapshotPath);
|
|
399
413
|
|
|
414
|
+
// Order seed data by dependencies if context instance is available
|
|
415
|
+
const orderedSeedData = snap.context && snap.context.getOrderedSeedData
|
|
416
|
+
? snap.context.getOrderedSeedData()
|
|
417
|
+
: snap.contextSeedData || {};
|
|
418
|
+
|
|
400
419
|
const content = {
|
|
401
420
|
contextLocation: relContextLocation,
|
|
402
421
|
migrationFolder: relMigrationFolder,
|
|
403
422
|
snapShotLocation: relSnapshotLocation,
|
|
404
|
-
schema : snap.contextEntities
|
|
423
|
+
schema : snap.contextEntities,
|
|
424
|
+
seedData: orderedSeedData,
|
|
425
|
+
seedConfig: snap.contextSeedConfig || {}
|
|
405
426
|
};
|
|
406
427
|
|
|
407
428
|
const jsonContent = JSON.stringify(content, null, 2);
|
|
@@ -455,8 +476,8 @@ class Migrations{
|
|
|
455
476
|
}
|
|
456
477
|
|
|
457
478
|
// Returns true if there are any changes between old and new schema
|
|
458
|
-
hasChanges(oldSchema, newSchema){
|
|
459
|
-
const tables = this.#buildMigrationObject(oldSchema, newSchema);
|
|
479
|
+
hasChanges(oldSchema, newSchema, newSeedData = {}){
|
|
480
|
+
const tables = this.#buildMigrationObject(oldSchema, newSchema, newSeedData);
|
|
460
481
|
for(const t of tables){
|
|
461
482
|
if(!t) continue;
|
|
462
483
|
if((t.newTables && t.newTables.length) ||
|
|
@@ -467,6 +488,7 @@ class Migrations{
|
|
|
467
488
|
(t.deletedIndexes && t.deletedIndexes.length) ||
|
|
468
489
|
(t.newCompositeIndexes && t.newCompositeIndexes.length) ||
|
|
469
490
|
(t.deletedCompositeIndexes && t.deletedCompositeIndexes.length) ||
|
|
491
|
+
(t.newSeedData && t.newSeedData.length) ||
|
|
470
492
|
(t.old === null) || (t.new === null)){
|
|
471
493
|
return true;
|
|
472
494
|
}
|
|
@@ -474,16 +496,20 @@ class Migrations{
|
|
|
474
496
|
return false;
|
|
475
497
|
}
|
|
476
498
|
|
|
477
|
-
template(name, oldSchema, newSchema){
|
|
499
|
+
template(name, oldSchema, newSchema, newSeedData = {}, seedConfig = {}, currentEnv = null){
|
|
478
500
|
var MT = new MigrationTemplate(name);
|
|
479
|
-
|
|
480
|
-
|
|
501
|
+
// Determine current environment if not provided
|
|
502
|
+
if (!currentEnv) {
|
|
503
|
+
currentEnv = process.env.NODE_ENV || process.env.master || 'development';
|
|
504
|
+
}
|
|
505
|
+
var tables = this.#buildMigrationObject(oldSchema, newSchema, newSeedData);
|
|
506
|
+
|
|
481
507
|
tables.forEach(function (item, index) {
|
|
482
508
|
if(item.old === null){
|
|
483
509
|
MT.createTable("up", column, item.name);
|
|
484
510
|
MT.dropTable("down", column, item.name);
|
|
485
511
|
}
|
|
486
|
-
|
|
512
|
+
|
|
487
513
|
if(item.new === null){
|
|
488
514
|
MT.dropTable("up", column, item.name);
|
|
489
515
|
MT.createTable("down", column, item.name);
|
|
@@ -529,6 +555,12 @@ class Migrations{
|
|
|
529
555
|
MT.dropCompositeIndex("up", indexInfo);
|
|
530
556
|
});
|
|
531
557
|
|
|
558
|
+
// Generate seed data code
|
|
559
|
+
if (item.newSeedData && item.newSeedData.length > 0) {
|
|
560
|
+
MT.seedData("up", item.name, item.newSeedData, currentEnv);
|
|
561
|
+
MT.seedDataDown("down", item.name, item.newSeedData, seedConfig);
|
|
562
|
+
}
|
|
563
|
+
|
|
532
564
|
});
|
|
533
565
|
|
|
534
566
|
return MT.get();
|