Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat(Migrator): Added migration support
- Loading branch information
1 parent
8d87b73
commit cd5d63d
Showing
8 changed files
with
800 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import {Scope} from '../Scope'; | ||
import {Store} from '../Store'; | ||
import {Run} from './Run'; | ||
import * as Knex from 'knex'; | ||
import * as Bluebird from 'bluebird'; | ||
|
||
export class Migration { | ||
|
||
/** | ||
* @type {Scope} | ||
*/ | ||
private entityManager: Scope; | ||
|
||
/** | ||
* @type {{}[]} | ||
*/ | ||
private builders: Array<{store: string, schemaBuilder: Knex.SchemaBuilder, knex: Knex}> = []; | ||
|
||
/** | ||
* @type {Function} | ||
*/ | ||
private migration: Function; | ||
|
||
/** | ||
* @type {Run} | ||
*/ | ||
private migrationRun: Run; | ||
|
||
/** | ||
* Holds whether or not this Migration is a promise. | ||
* | ||
* @type {boolean} | ||
*/ | ||
private promise: boolean = false; | ||
|
||
/** | ||
* Construct a new Migration. | ||
* | ||
* @param {Function} migration | ||
* @param {Run} run | ||
*/ | ||
public constructor(migration: Function, run: Run) { | ||
this.migration = migration; | ||
this.entityManager = run.getEntityManager(); | ||
this.migrationRun = run; | ||
|
||
this.prepare(); | ||
} | ||
|
||
/** | ||
* Prepare the migration by running it. | ||
*/ | ||
private prepare(): void { | ||
let prepared = this.migration(this); | ||
|
||
if (prepared && 'then' in prepared) { | ||
this.promise = true; | ||
} | ||
} | ||
|
||
/** | ||
* Get a schemabuilder to work with. | ||
* | ||
* @param {string} store | ||
* | ||
* @returns {Knex.SchemaBuilder} | ||
*/ | ||
public getSchemaBuilder(store?: string): Knex.SchemaBuilder { | ||
return this.getBuilder(store).schema; | ||
} | ||
|
||
/** | ||
* Get a (reusable) transaction for `storeName` | ||
* | ||
* @param {string} storeName | ||
* | ||
* @returns {Bluebird<Knex.Transaction>} | ||
*/ | ||
public getTransaction(storeName?: string): Bluebird<Knex.Transaction> { | ||
return this.migrationRun.getTransaction(storeName); | ||
} | ||
|
||
/** | ||
* Get a builder. This includes the knex instance. | ||
* | ||
* @param {string} store | ||
* | ||
* @returns {{schema: Knex.SchemaBuilder, knex: Knex}} | ||
*/ | ||
public getBuilder(store?: string): {schema: Knex.SchemaBuilder, knex: Knex} { | ||
let connection = this.getConnection(store); | ||
let schemaBuilder = connection.schema; | ||
|
||
this.builders.push({store, schemaBuilder, knex: connection}); | ||
|
||
return {schema: schemaBuilder, knex: connection}; | ||
} | ||
|
||
/** | ||
* Get the SQL for current builders. | ||
* | ||
* @returns {string} | ||
*/ | ||
public getSQL(): string { | ||
if (this.promise) { | ||
throw new Error("It's not possible to get SQL for a promise based migration."); | ||
} | ||
|
||
return this.builders.map(builder => builder.schemaBuilder.toString()).join('\n'); | ||
} | ||
|
||
/** | ||
* Run the migration. | ||
* | ||
* @returns {Bluebird<any>} | ||
*/ | ||
public run(): Bluebird<any> { | ||
return Bluebird.each(this.builders, builder => { | ||
return this.getTransaction(builder.store).then(transaction => { | ||
return builder.schemaBuilder['transacting'](transaction).then(); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Get connection for store. | ||
* | ||
* @param {string} store | ||
* | ||
* @returns {knex} | ||
*/ | ||
private getConnection(store?: string): Knex { | ||
return this.entityManager.getStore(store).getConnection(Store.ROLE_MASTER); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as Promise from 'bluebird'; | ||
import {MigratorConfigInterface} from './MigratorConfigInterface'; | ||
|
||
export class MigrationFile { | ||
/** | ||
* @type {MigratorConfigInterface} | ||
*/ | ||
private config: MigratorConfigInterface; | ||
|
||
/** | ||
* @param {MigratorConfigInterface} config | ||
*/ | ||
public constructor(config: MigratorConfigInterface) { | ||
this.config = config; | ||
} | ||
|
||
/** | ||
* Get the config. | ||
* | ||
* @returns {MigratorConfigInterface} | ||
*/ | ||
public getConfig(): MigratorConfigInterface { | ||
return this.config; | ||
} | ||
|
||
/** | ||
* Create a new migration file. | ||
* | ||
* @param {string} name | ||
* | ||
* @returns {Bluebird} | ||
*/ | ||
public create(name: string): Promise<any> { | ||
let sourceFile = `${__dirname}/templates/migration.${this.config.extension}.js`; | ||
let targetFile = path.join(this.config.directory, `${this.makeMigrationName(name)}.${this.config.extension}`); | ||
let readStream = fs.createReadStream(sourceFile); | ||
let writeStream = fs.createWriteStream(targetFile); | ||
|
||
readStream.pipe(writeStream); | ||
|
||
return new Promise((resolve, reject) => { | ||
readStream.on('error', reject); | ||
writeStream.on('error', reject); | ||
writeStream.on('close', () => resolve()); | ||
}); | ||
} | ||
|
||
/** | ||
* Get all migrations from the directory. | ||
* | ||
* @returns {Bluebird<string[]>} | ||
*/ | ||
public getMigrations(): Promise<Array<string>> { | ||
return Promise.promisify(fs.readdir)(this.config.directory).then(contents => { | ||
let regexp = new RegExp(`\.${this.config.extension}$`); | ||
|
||
return contents | ||
.filter(migration => migration.search(regexp) > -1) | ||
.map(migration => migration.replace(regexp, '')); | ||
}); | ||
} | ||
|
||
/** | ||
* Make migration name. | ||
* | ||
* @param {string} name | ||
* | ||
* @returns {string} | ||
*/ | ||
private makeMigrationName(name): string { | ||
let date = new Date(); | ||
let pad = (source) => { | ||
source = source.toString(); | ||
|
||
return source[1] ? source : `0${source}`; | ||
}; | ||
|
||
return date.getFullYear().toString() + | ||
pad(date.getMonth() + 1) + | ||
pad(date.getDate()) + | ||
pad(date.getHours()) + | ||
pad(date.getMinutes()) + | ||
pad(date.getSeconds()) + `_${name}`; | ||
} | ||
} |
Oops, something went wrong.