Browse files

Initial commit

  • Loading branch information...
0 parents commit 4a421a485263ddc2436c0b38d8dbdf72a7687a34 @tj committed Apr 22, 2011
Showing with 439 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +3 −0 .gitmodules
  3. +4 −0 .npmignore
  4. +5 −0 History.md
  5. +5 −0 Makefile
  6. +29 −0 Readme.md
  7. +43 −0 examples/migrate.js
  8. +2 −0 index.js
  9. +41 −0 lib/migrate.js
  10. +18 −0 lib/migration.js
  11. +155 −0 lib/set.js
  12. +10 −0 package.json
  13. +120 −0 test/test.migrate.js
4 .gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules
+*.sock
+.migrate
3 .gitmodules
@@ -0,0 +1,3 @@
+[submodule "support/should"]
+ path = support/should
+ url = git://github.com/visionmedia/should.js.git
4 .npmignore
@@ -0,0 +1,4 @@
+support
+test
+examples
+*.sock
5 History.md
@@ -0,0 +1,5 @@
+
+0.0.1 / 2010-01-03
+==================
+
+ * Initial release
5 Makefile
@@ -0,0 +1,5 @@
+
+test:
+ @node test/test.migrate.js
+
+.PHONY: test
29 Readme.md
@@ -0,0 +1,29 @@
+
+# migrate
+
+ Abstract migration framework for node
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 examples/migrate.js
@@ -0,0 +1,43 @@
+
+// $ npm install redis
+// $ redis-server
+
+var migrate = require('../')
+ , redis = require('redis')
+ , db = redis.createClient();
+
+migrate(__dirname + '/.migrate');
+
+migrate('add pets', function(next){
+ db.rpush('pets', 'tobi');
+ db.rpush('pets', 'loki', next);
+}, function(next){
+ db.rpop('pets');
+ db.rpop('pets', next);
+});
+
+migrate('add jane', function(next){
+ db.rpush('pets', 'jane', next);
+}, function(next){
+ db.rpop('pets', next);
+});
+
+migrate('add owners', function(next){
+ db.rpush('owners', 'taylor');
+ db.rpush('owners', 'tj', next);
+}, function(next){
+ db.rpop('owners');
+ db.rpop('owners', next);
+});
+
+migrate('coolest pet', function(next){
+ db.set('pets:coolest', 'tobi', next);
+}, function(next){
+ db.del('pets:coolest', next);
+});
+
+migrate().up(function(err){
+ if (err) throw err;
+ console.log('done');
+ process.exit();
+});
2 index.js
@@ -0,0 +1,2 @@
+
+module.exports = require('./lib/migrate');
41 lib/migrate.js
@@ -0,0 +1,41 @@
+
+/*!
+ * migrate
+ * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Migration = require('./migration')
+ , Set = require('./set');
+
+/**
+ * Expose the migrate function.
+ */
+
+exports = module.exports = migrate;
+
+/**
+ * Library version.
+ */
+
+exports.version = '0.0.1';
+
+function migrate(title, up, down) {
+ // migration
+ if ('string' == typeof title && up && down) {
+ migrate.set.migrations.push(new Migration(title, up, down));
+ // specify migration file
+ } else if ('string' == typeof title) {
+ migrate.set = new Set(title);
+ // no migration path
+ } else if (!migrate.set) {
+ throw new Error('must invoke migrate(path) before running migrations');
+ // run migrations
+ } else {
+ return migrate.set;
+ }
+}
18 lib/migration.js
@@ -0,0 +1,18 @@
+
+/*!
+ * migrate - Migration
+ * Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Expose `Migration`.
+ */
+
+module.exports = Migration;
+
+function Migration(title, up, down) {
+ this.title = title;
+ this.up = up;
+ this.down = down;
+}
155 lib/set.js
@@ -0,0 +1,155 @@
+
+/*!
+ * migrate - Set
+ * Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter
+ , fs = require('fs');
+
+/**
+ * Expose `Set`.
+ */
+
+module.exports = Set;
+
+/**
+ * Initialize a new migration `Set` with the given `path`
+ * which is used to store data between migrations.
+ *
+ * @param {String} path
+ * @api private
+ */
+
+function Set(path) {
+ this.migrations = [];
+ this.path = path;
+ this.pos = 0;
+};
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+Set.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Save the migration data and call `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+
+Set.prototype.save = function(fn){
+ this.emit('save');
+ var json = JSON.stringify(this);
+ fs.writeFile(this.path, json, fn);
+};
+
+/**
+ * Load the migration data and call `fn(err, obj)`.
+ *
+ * @param {Function} fn
+ * @return {Type}
+ * @api public
+ */
+
+Set.prototype.load = function(fn){
+ this.emit('load');
+ fs.readFile(this.path, 'utf8', function(err, json){
+ if (err) return fn(err);
+ try {
+ fn(null, JSON.parse(json));
+ } catch (err) {
+ fn(err);
+ }
+ });
+};
+
+/**
+ * Run down migrations and call `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+
+Set.prototype.down = function(fn){
+ this.migrate('down', fn);
+};
+
+/**
+ * Run up migrations and call `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+
+Set.prototype.up = function(fn){
+ this.migrate('up', fn);
+};
+
+/**
+ * Migrate in the given `direction`, calling `fn(err)`.
+ *
+ * @param {String} direction
+ * @param {Function} fn
+ * @api public
+ */
+
+Set.prototype.migrate = function(direction, fn){
+ var self = this;
+ fn = fn || function(){};
+ this.load(function(err, obj){
+ if (err) {
+ if ('ENOENT' != err.code) return fn(err);
+ } else {
+ self.pos = obj.pos;
+ }
+ self._migrate(direction, fn);
+ });
+};
+
+/**
+ * Perform migration.
+ *
+ * @api private
+ */
+
+Set.prototype._migrate = function(direction, fn){
+ var self = this
+ , migrations;
+
+ switch (direction) {
+ case 'up':
+ migrations = this.migrations.slice(this.pos);
+ this.pos += migrations.length;
+ break;
+ case 'down':
+ migrations = this.migrations.slice(0, this.pos).reverse();
+ this.pos = 0;
+ break;
+ }
+
+ function next(err, migration) {
+ // error from previous migration
+ if (err) return fn(err);
+
+ // done
+ if (!migration) {
+ self.emit('complete');
+ self.save(fn);
+ return;
+ }
+
+ migration[direction](function(err){
+ next(err, migrations.shift());
+ });
+ }
+
+ next(null, migrations.shift());
+};
10 package.json
@@ -0,0 +1,10 @@
+{
+ "name": "migrate"
+ , "version": "0.0.1"
+ , "description": "Abstract migration framework for node"
+ , "keywords": ["migrations"]
+ , "author": "TJ Holowaychuk <tj@vision-media.ca>"
+ , "dependencies": { "progress": ">= 0.0.3" }
+ , "main": "index"
+ , "engines": { "node": "0.4.x" }
+}
120 test/test.migrate.js
@@ -0,0 +1,120 @@
+
+/**
+ * Module dependencies.
+ */
+
+var migrate = require('../')
+ , should = require('../support/should')
+ , fs = require('fs');
+
+// remove migration file
+
+try {
+ fs.unlinkSync(__dirname + '/.migrate');
+} catch (err) {
+ // ignore
+}
+
+// dummy db
+
+var db = { pets: [] };
+
+// dummy migrations
+
+migrate(__dirname + '/.migrate');
+
+migrate('add guy ferrets', function(next){
+ db.pets.push({ name: 'tobi' });
+ db.pets.push({ name: 'loki' });
+ next();
+}, function(next){
+ db.pets.pop();
+ db.pets.pop();
+ next();
+});
+
+migrate('add girl ferrets', function(next){
+ db.pets.push({ name: 'jane' });
+ next();
+}, function(next){
+ db.pets.pop();
+ next();
+});
+
+migrate('add emails', function(next){
+ db.pets.forEach(function(pet){
+ pet.email = pet.name + '@learnboost.com';
+ });
+ next();
+}, function(next){
+ db.pets.forEach(function(pet){
+ delete pet.email;
+ });
+ next();
+});
+
+// tests
+
+migrate.version.should.match(/^\d+\.\d+\.\d+$/);
+
+// test migrating up / down several times
+
+var set = migrate();
+
+set.up(function(){
+ assertPets();
+ set.up(function(){
+ assertPets();
+ set.down(function(){
+ assertNoPets();
+ set.down(function(){
+ assertNoPets();
+ set.up(function(){
+ assertPets();
+ testNewMigrations();
+ });
+ });
+ });
+ });
+});
+
+// test adding / running new migrations
+
+function testNewMigrations() {
+ migrate('add dogs', function(next){
+ db.pets.push({ name: 'simon' });
+ db.pets.push({ name: 'suki' });
+ next();
+ }, function(next){
+ db.pets.pop();
+ db.pets.pop();
+ next();
+ });
+
+ set.up(function(){
+ assertPets.withDogs();
+ set.up(function(){
+ assertPets.withDogs();
+ set.down(function(){
+ assertNoPets();
+ });
+ });
+ });
+}
+
+// helpers
+
+function assertNoPets() {
+ db.pets.should.be.empty;
+}
+
+function assertPets() {
+ db.pets.should.have.length(3);
+ db.pets[0].name.should.equal('tobi');
+ db.pets[0].email.should.equal('tobi@learnboost.com');
+}
+
+assertPets.withDogs = function(){
+ db.pets.should.have.length(5);
+ db.pets[4].name.should.equal('suki');
+};

0 comments on commit 4a421a4

Please sign in to comment.