Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions lib/migration/migration.classFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

/**
* Inject dependencies in migration
* @param {Ilorm} ilorm The ilorm linked with the Migration class
* @returns {Migration} The Migration class bound with this ilorm instance
*/
function injectDependencies({ ilorm, }) {
const migrationsIndex = [];

/**
* Migration class
* class to manage migration of data
*/
return class Migration {
/**
* Create a migration
* @param {Number} timestamp The timestamp associate with this migration
* @param {Schema} schema The schema bound with this migration
* @param {Function} up Handler called to apply this migration
* @param {Function} down Handler called to rollback this migration
*/
constructor({ timestamp, schema, up, down, }) {
this.time = timestamp;
this.schema = schema;
this.up = up;
this.down = down;

migrationsIndex.push(this);
migrationsIndex.sort((migrationA, migrationB) => migrationA.time - migrationB.time);
}

/**
* Apply this migration
*/
up() {

}

/**
* Rollback this migration
*/
down() {

}
};
}

module.exports = injectDependencies;
44 changes: 44 additions & 0 deletions lib/migration/migrationConnector.classFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Inject dependencies to create MigrationConnector
* @param {ilorm} ilorm Ilorm instance bound with the context
* @param {Connector} Connector The connector linked with the Migration Connector
* @returns {MigrationConnector} Return the MigrationConnector
*/
function injectDependencies({ ilorm, Connector, }) {
const { Schema, newModel, } = ilorm;
const migrationSchema = new Schema({
appliedAt: Schema.date().default(() => Date.now()),
version: Schema.string(),
});
const modelParams = {
connector: new Connector({
sourceName: 'ilormMigration',
}),
schema: migrationSchema,
};

/**
* MigrationModel class
* Model managed by ilorm to manage migration, only used if the user use migration system
*/
class MigrationModel extends newModel(modelParams) {
/**
* Get last migration
* @returns {MigrationModel} The last migration applied
*/
getLastMigration() {
return this.query()
.appliedAt.useAsSortDesc()
.findOne();
}
}

/**
* Manage all migration as a Connector level
*/
return class MigrationConnector {

};
}

module.exports = injectDependencies;
50 changes: 50 additions & 0 deletions lib/schema/baseSchema.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class BaseSchema {
* @param {Object} options Options to apply to the schema
*/
constructor(schemaDefinition, options = {}) {
this.pastSchema = [];
this.definition = schemaDefinition;
this.properties = Object.keys(this.definition);
this.options = {
Expand Down Expand Up @@ -60,6 +61,55 @@ class BaseSchema {
return new this(schema.definition, schema.options);
}

/**
* Declare the function to run to apply this schema (in schema migration)
* @param {Function} handler The handler to run
* @returns {Schema} Return schema for chained call
*/
up(handler) {
this.onUp = handler;

return this;
}

/**
* Declare the function to rollback from this schema (in schema migration)
* @param {Function} handler The handler to run
* @returns {Schema} Return schema for chained call
*/
down(handler) {
this.onDown = handler;

return this;
}

/**
* Declare a past version of the current schema
* @param {Number} timestamp The timestamp associate with this schema version
* @param {Object} schema The previous schema definition
* @returns {Schema} Return version of the schema for chained call
*/
version(timestamp, schema = null) {
if (this.currentSchema) {
return this.currentSchema.version(timestamp, schema);
}

if (!schema) {
this.timestamp = timestamp;

return this;
}

const instantiatedSchema = new this.constructor(schema);

instantiatedSchema.timestamp = timestamp;
instantiatedSchema.currentSchema = this;

this.pastSchema.push(instantiatedSchema);
this.pastSchema.sort((schemaA, schemaB) => schemaB.timestamp - schemaA.timestamp);

return instantiatedSchema;
}

/**
* Return primary key of the current schema
Expand Down
1 change: 1 addition & 0 deletions spec/common/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ module.exports = {
extra: {
collectionManager: true,
transaction: true,
migration: true,
},
};
99 changes: 99 additions & 0 deletions spec/common/extra/migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const { expect, } = require('chai');

const TIME_V1 = 1581125250;
const TIME_V2 = 1601825250;
const TIME_V3 = 1611825250;

module.exports = (TestContext) => {
const testContext = new TestContext();

describe('Migration', () => {
after(async () => {
await testContext.deleteSource('users');

return testContext.finalCleanUp();
});

const { Schema, newModel, } = testContext.ilorm;

// Always most recent version;
const userSchema = new Schema({
id: Schema.string(),
firstName: Schema.string(),
lastName: Schema.string(),
age: Schema.number(),
});

it('API up / down should work', () => {
userSchema
.version(TIME_V3)
.up((UserModel) => {
UserModel.query()
.stream()
.on('data', (user) => {
const [ firstName, lastName, ] = user.name.split(' ');

user.firstName = firstName;
user.lastName = lastName;
user.age = parseInt(user.previous.age);
user.save();
});
})
.down((UserModel) => {
UserModel.query()
.stream()
.on('data', (user) => {
user.name = `${user.firstName} ${user.lastName}`;
user.age = `${user.previous.age}`;
user.save();
});
});
});

it('API version should work', () => {
userSchema
.version(TIME_V2, {
id: Schema.string(),
name: Schema.string(),
age: Schema.string(),
})
.version(TIME_V1, {
id: Schema.string(),
name: Schema.string(),
})
.up(async (UserModel) => {
const user = new UserModel();

user.id = '12345';
user.name = 'Guillaume Daix';

await user.save();
});
});

let UserModel;

it('Should build db with the most recent schema', async () => {
UserModel = newModel({
connector: new testContext.Connector({
sourceName: 'users',
}),
schema: userSchema,
});

// Will apply migration in order;
await UserModel.applyMigration();

// The migration TIME_V1 insert a user on an old schema, in theory, still in the db;
const user = await UserModel.query().findOne();

expect(user.id).to.be.equals('12345');
expect(user.firstName).to.be.equals('Guillaume');
expect(user.lastName).to.be.equals('Daix');
});

it('Should rollback on past schema', () => {});

it('Should re-up on last schema', () => {});
});
};
1 change: 1 addition & 0 deletions spec/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const TESTS = {
extra: {
collectionManager: require('./extra/collectionManager'),
transaction: require('./extra/transaction'),
migration: require('./extra/migration'),
},
};

Expand Down