waterline
Advanced tools
Comparing version 0.0.5202 to 0.0.5204
277
adapter.js
@@ -54,31 +54,5 @@ var async = require('async'); | ||
// If id is not defined, add it | ||
// TODO: Make this check for ANY primary key | ||
if(this.config.defaultPK && !attributes.id) { | ||
attributes.id = { | ||
type: 'INTEGER', | ||
autoIncrement: true, | ||
'default': 'AUTO_INCREMENT', | ||
constraints: { | ||
unique: true, | ||
primaryKey: true | ||
} | ||
}; | ||
} | ||
// Marshal attributes to a standard format | ||
attributes = require('./augmentAttributes')(attributes,this.config); | ||
// If the adapter config allows it, and they aren't already specified, | ||
// extend definition with updatedAt and createdAt | ||
var now = {type: 'DATE', 'default': 'NOW'}; | ||
if(this.config.createdAt && !attributes.createdAt) attributes.createdAt = now; | ||
if(this.config.updatedAt && !attributes.updatedAt) attributes.updatedAt = now; | ||
// Convert string-defined attributes into fully defined objects | ||
for(var attr in attributes) { | ||
if(_.isString(attributes[attr])) { | ||
attributes[attr] = { | ||
type: attributes[attr] | ||
}; | ||
} | ||
} | ||
// Verify that collection doesn't already exist | ||
@@ -100,4 +74,66 @@ // and then define it and trigger callback | ||
}; | ||
this.alter = function(collectionName, newAttrs, cb) { | ||
adapter.alter ? adapter.alter(collectionName, newAttrs, cb) : cb(); | ||
this.alter = function(collectionName, attributes, cb) { | ||
var self = this; | ||
adapter.alter ? adapter.alter(collectionName, attributes, cb) : defaultAlter(cb); | ||
// Default behavior | ||
function defaultAlter(done) { | ||
// Alter the schema | ||
self.describe(collectionName, function afterDescribe (err, oldAttributes) { | ||
if (err) return done(err); | ||
// Keep track of previously undefined attributes | ||
// for use when updating the actual data | ||
var newAttributes = {}; | ||
// Iterate through each attribute in the new definition | ||
_.each(attributes, function checkAttribute(attribute,attrName) { | ||
// If the attribute doesn't exist, create it | ||
if (!oldAttributes[attrName]) { | ||
newAttributes[attrName] = attribute; | ||
} | ||
// If the old attribute is not exactly the same, or it doesn't exist, (re)create it | ||
if ( !oldAttributes[attrName] || !_.isEqual(oldAttributes[attrName],attribute) ) { | ||
oldAttributes[attrName] = attribute; | ||
} | ||
}); | ||
// Then alter the actual data as necessary | ||
self.find(collectionName,null, function afterFind (err,data) { | ||
if (err) return done(err); | ||
// Update the data belonging to this attribute to reflect the new properties | ||
// Realistically, this will mainly be about constraints, and primarily uniquness | ||
// It'd be good if waterline could enforce all constraints at this time, | ||
// but there's a trade-off with destroying people's data | ||
// TODO: Figure this out | ||
// For new columns, just use the default value if one exists (otherwise use null) | ||
_.each(newAttributes, function checkAttribute(attribute,attrName) { | ||
if (attribute.defaultValue) { | ||
data[attrName] = attribute.defaultValue; | ||
} | ||
}); | ||
// Create deferred object | ||
var $$ = new parley(); | ||
var $_self = $$(self); | ||
// Dumbly drop the table and redefine it | ||
$_self.drop(collectionName); | ||
$_self.define(collectionName, attributes); | ||
// Then dumbly add the data back in | ||
$_self.createAll(collectionName,data); | ||
$$(function(xcb) { done && done(); xcb(); })(); | ||
}); | ||
}); | ||
} | ||
}; | ||
@@ -120,11 +156,13 @@ | ||
adapter.create ? adapter.create(collectionName, values, cb) : cb(); | ||
adapter.create(collectionName, values, cb); | ||
// TODO: Return model instance Promise object for joins, etc. | ||
}; | ||
this.find = function(collectionName, options, cb) { | ||
this.find = function(collectionName, criteria, cb) { | ||
if(!adapter.find) return cb("No find() method defined in adapter!"); | ||
options = normalizeCriteria(options); | ||
adapter.find ? adapter.find(collectionName, options, cb) : cb(); | ||
criteria = normalizeCriteria(criteria); | ||
if (_.isString(criteria)) return cb(criteria); | ||
adapter.find(collectionName, criteria, cb); | ||
// TODO: Return model instance Promise object for joins, etc. | ||
@@ -135,2 +173,3 @@ }; | ||
criteria = normalizeCriteria(criteria); | ||
if (_.isString(criteria)) return cb(criteria); | ||
@@ -141,3 +180,3 @@ // TODO: Validate constraints using Anchor | ||
adapter.update ? adapter.update(collectionName, criteria, values, cb) : cb(); | ||
adapter.update(collectionName, criteria, values, cb); | ||
@@ -149,4 +188,6 @@ // TODO: Return model instance Promise object for joins, etc. | ||
criteria = normalizeCriteria(criteria); | ||
adapter.destroy ? adapter.destroy(collectionName, criteria, cb) : cb(); | ||
if (_.isString(criteria)) return cb(criteria); | ||
adapter.destroy(collectionName, criteria, cb); | ||
// TODO: Return model instance Promise object for joins, etc. | ||
@@ -161,2 +202,4 @@ }; | ||
criteria = normalizeCriteria(criteria); | ||
if (_.isString(criteria)) return cb(criteria); | ||
if(adapter.findOrCreate) adapter.findOrCreate(collectionName, criteria, values, cb); | ||
@@ -176,2 +219,4 @@ else { | ||
criteria = normalizeCriteria(criteria); | ||
if (_.isString(criteria)) return cb(criteria); | ||
if(adapter.findAndUpdate) adapter.findAndUpdate(collectionName, criteria, values, cb); | ||
@@ -184,2 +229,4 @@ else this.update(collectionName, criteria, values, cb); | ||
criteria = normalizeCriteria(criteria); | ||
if (_.isString(criteria)) return cb(criteria); | ||
if(adapter.findAndDestroy) adapter.findAndDestroy(collectionName, criteria, cb); | ||
@@ -201,3 +248,6 @@ else this.destroy(collectionName, criteria, cb); | ||
// Custom user adapter behavior | ||
if (adapter.createAll) adapter.createAll(collectionName,valuesList,cb); | ||
// Default behavior | ||
else { | ||
@@ -224,4 +274,9 @@ async.forEach(valuesList, function (values,cb) { | ||
// App-level transaction | ||
this.transaction = function(transactionName, cb) { | ||
/** | ||
* App-level transaction | ||
* @transactionName a unique identifier for this transaction | ||
* @atomicLogic the logic to be run atomically | ||
* @afterUnlock (optional) the function to trigger after unlock() is called | ||
*/ | ||
this.transaction = function(transactionName, atomicLogic, afterUnlock) { | ||
var self = this; | ||
@@ -233,9 +288,10 @@ | ||
name: transactionName, | ||
timestamp: epoch(), | ||
cb: cb | ||
atomicLogic: atomicLogic, | ||
afterUnlock: afterUnlock | ||
}; | ||
// console.log("Generating lock "+newLock.uuid+" ("+transactionName+")"); | ||
// console.log("Generating lock "+newLock.uuid+" ("+transactionName+")",newLock); | ||
// write new lock to commit log | ||
this.transactionCollection.create(newLock, function(err) { | ||
if(err) return cb(err, function() { | ||
this.transactionCollection.create(newLock, function afterCreatingTransaction(err) { | ||
if(err) return atomicLogic(err, function() { | ||
throw err; | ||
@@ -245,4 +301,4 @@ }); | ||
// Check if lock was written, and is the oldest with the proper name | ||
self.transactionCollection.findAll(function(err, locks) { | ||
if(err) return cb(err, function() { | ||
self.transactionCollection.findAll(function afterLookingUpTransactions(err, locks) { | ||
if(err) return atomicLogic(err, function() { | ||
throw err; | ||
@@ -252,16 +308,14 @@ }); | ||
var conflict = false; | ||
_.each(locks, function(entry) { | ||
_.each(locks, function eachLock (entry) { | ||
// If a conflict IS found, respect the oldest | ||
// (the conflict-causer is responsible for cleaning up his entry-- ignore it!) | ||
if(entry.name === newLock.name && entry.uuid !== newLock.uuid && true && //entry.timestamp <= newLock.timestamp && | ||
entry.id < newLock.id) conflict = entry; | ||
if(entry.name === newLock.name && | ||
entry.uuid !== newLock.uuid && | ||
entry.id < newLock.id) conflict = entry; | ||
}); | ||
// If there are no conflicts, the lock is acquired! | ||
if(!conflict) self.lock(newLock, newLock.cb); | ||
if(!conflict) acquireLock(newLock); | ||
// Otherwise, get in line | ||
// In other words, do nothing-- | ||
// unlock() will grant lock request in order it was received | ||
// Otherwise, get in line: a lock was acquired before mine, do nothing | ||
else { | ||
@@ -280,5 +334,12 @@ /* | ||
this.lock = function(newLock, cb) { | ||
var self = this; | ||
// console.log("====> Lock "+newLock.id+" acquired "+newLock.uuid+" ("+newLock.name+")"); | ||
/** | ||
* acquireLock() is run after the lock is acquired, but before passing control to the atomic app logic | ||
* | ||
* @newLock the object representing the lock to acquire | ||
* @name name of the lock | ||
* @atomicLogic the transactional logic to be run atomically | ||
* @afterUnlock (optional) the function to run after the lock is subsequently released | ||
*/ | ||
var acquireLock = function(newLock) { | ||
// console.log("====> Lock "+newLock.id+" acquired "+newLock.uuid+" ("+newLock.name+")",newLock); | ||
@@ -289,5 +350,5 @@ var warningTimer = setTimeout(function() { | ||
cb(null, function unlock(cb) { | ||
newLock.atomicLogic(null, function unlock () { | ||
clearTimeout(warningTimer); | ||
self.unlock(newLock, cb); | ||
releaseLock(newLock,arguments); | ||
}); | ||
@@ -297,7 +358,12 @@ }; | ||
this.unlock = function(currentLock, cb) { | ||
var self = this; | ||
// releaseLock() will grant pending lock requests in the order they were received | ||
// | ||
// @currentLock the lock currently acquired | ||
// @afterUnlockArgs the arguments to pass to the afterUnlock function | ||
var releaseLock = function(currentLock, afterUnlockArgs) { | ||
var cb = currentLock.afterUnlock; | ||
// Get all locks | ||
self.transactionCollection.findAll(function(err, locks) { | ||
self.transactionCollection.findAll(function afterLookingUpTransactions(err, locks) { | ||
if(err) return cb && cb(err); | ||
@@ -315,12 +381,13 @@ | ||
uuid: currentLock.uuid | ||
}, function(err) { | ||
}, function afterLockReleased (err) { | ||
if(err) return cb && cb(err); | ||
// Trigger unlock's callback if specified | ||
cb && cb(); | ||
// > NOTE: do this before triggering the next queued transaction | ||
// to prevent transactions from monopolizing the event loop | ||
cb && cb.apply(null, afterUnlockArgs); | ||
// Now allow the nextInLine lock to be acquired | ||
// This marks the end of the previous transaction | ||
nextInLine && self.lock(nextInLine, nextInLine.cb); | ||
nextInLine && acquireLock(nextInLine); | ||
}); | ||
@@ -346,3 +413,3 @@ }); | ||
var self = this; | ||
this.drop(collection.identity, function(err, data) { | ||
this.drop(collection.identity, function afterDrop (err, data) { | ||
if(err) cb(err); | ||
@@ -358,52 +425,9 @@ else self.define(collection.identity, collection, cb); | ||
// Check that collection exists-- if it doesn't go ahead and add it and get out | ||
this.describe(collection.identity, function(err, data) { | ||
this.describe(collection.identity, function afterDescribe (err, data) { | ||
data = _.clone(data); | ||
if(err) return cb(err); | ||
else if(!data) return self.define(collection.identity, collection, cb); | ||
// TODO: move all of this to the alter() call in the adapter | ||
// If it *DOES* exist, we'll try to guess what changes need to be made | ||
// Iterate through each attribute in this collection's schema | ||
_.each(collection.attributes, function checkAttribute(attribute,attrName) { | ||
// Make sure that a comparable field exists in the data store | ||
if (!data[attrName]) { | ||
data[attrName] = attribute; | ||
// Add the default value for this new attribute to each row in the data model | ||
// TODO | ||
} | ||
// And that it matches completely | ||
else { | ||
data[attrName] = attribute; | ||
// Update the data belonging to this attribute to reflect the new properties | ||
// Realistically, this will mainly be about constraints, and primarily uniquness | ||
// It'd be good if waterline could enforce all constraints at this time, | ||
// but there's a trade-off with destroying people's data | ||
// TODO | ||
} | ||
}); | ||
// Now iterate through each attribute in the adapter's data store | ||
// and remove any that don't have an analog in the collection definition | ||
// Also prune the data belonging to removed attributes from rows | ||
// TODO: | ||
// Persist that | ||
// Check that the attribute exists in the data store | ||
// TODO | ||
// If not, alter the collection to include it | ||
// TODO | ||
// Iterate through each attribute in this collection | ||
// and make sure that a comparable field exists in the model | ||
// TODO | ||
// If not, alter the collection and remove it | ||
// TODO | ||
// cb(); | ||
else if(!data) return self.define(collection.identity, collection.attributes, cb); | ||
// Otherwise, if it *DOES* exist, we'll try and guess what changes need to be made | ||
else self.alter(collection.identity, collection.attributes, cb); | ||
}); | ||
@@ -418,12 +442,2 @@ }, | ||
// Always grant access to a few of Adapter's methods to the user adapter instance | ||
// (things that may or may not be defined by the user adapter) | ||
adapter.transaction = function(name, cb) { | ||
return self.transaction(name, cb); | ||
}; | ||
// adapter.teardown = adapter.teardown || self.teardown; | ||
// adapter.teardownCollection = adapter.teardownCollection || self.teardownCollection; | ||
// Bind adapter methods to self | ||
@@ -486,5 +500,6 @@ _.bindAll(adapter); | ||
_.each(criteria, function(val, key) { | ||
if(val === undefined) delete criteria[key]; | ||
if(_.isUndefined(val)) delete criteria[key]; | ||
}); | ||
// Convert id and id strings into a criteria | ||
if((_.isFinite(criteria) || _.isString(criteria)) && +criteria > 0) { | ||
@@ -495,5 +510,7 @@ criteria = { | ||
} | ||
if(!_.isObject(criteria)) { | ||
throw 'Invalid options/criteria :: ' + criteria; | ||
} | ||
// Return string to indicate an error | ||
if(!_.isObject(criteria)) return ('Invalid options/criteria :: ' + criteria); | ||
// If criteria doesn't seem to contain operational keys, assume all the keys are criteria | ||
if(!criteria.where && !criteria.limit && !criteria.skip && !criteria.offset && !criteria.order) { | ||
@@ -513,8 +530,2 @@ criteria = { | ||
return criteria; | ||
} | ||
// Number of miliseconds since the Unix epoch Jan 1st, 1970 | ||
function epoch() { | ||
return(new Date()).getTime(); | ||
} |
module.exports = require('waterline-dirty')({ | ||
inMemory: true, | ||
migrate: 'drop' | ||
inMemory: true | ||
}); |
@@ -75,4 +75,7 @@ var _ = require('underscore'); | ||
var attributes = _.clone(this.attributes) || {}; | ||
attributes = require('./augmentAttributes')(attributes,_.extend({},config,this.config)); | ||
// For each defined attribute, create a dynamic finder function | ||
_.each(this.attributes,function (attrDef, attrName) { | ||
_.each(attributes,function (attrDef, attrName) { | ||
self['findBy'+_.str.capitalize(attrName)] = self.generateDynamicFinder(attrName); | ||
@@ -85,3 +88,2 @@ }); | ||
////////////////////////////////////////// | ||
@@ -243,4 +245,14 @@ // Promises / Deferred Objects | ||
this.transaction = function (name, cb) { | ||
return this.adapter.transaction(name, cb); | ||
this.transaction = function (transactionName, atomicLogic, afterUnlock) { | ||
var usage = _.str.capitalize(this.identity)+'.transaction(transactionName, atomicLogicFunction, afterUnlockFunction)'; | ||
if (!atomicLogic) { | ||
return usageError('Missing required parameter: atomicLogicFunction!',usage); | ||
} | ||
else if (!_.isFunction(atomicLogic)) { | ||
return usageError('Invalid atomicLogicFunction! Not a function: '+atomicLogic,usage); | ||
} | ||
else if (afterUnlock && !_.isFunction(afterUnlock)) { | ||
return usageError('Invalid afterUnlockFunction! Not a function: '+afterUnlock,usage); | ||
} | ||
else return this.adapter.transaction(transactionName, atomicLogic, afterUnlock); | ||
}; | ||
@@ -247,0 +259,0 @@ |
{ | ||
"name": "waterline", | ||
"version": "0.0.5202", | ||
"version": "0.0.5204", | ||
"description": "Adaptable data access layer for Node.js", | ||
@@ -40,4 +40,4 @@ "main": "waterline.js", | ||
"microtime": "~0.3.3", | ||
"waterline-dirty": "0.0.03" | ||
"waterline-dirty": "0.0.0501" | ||
} | ||
} |
@@ -20,6 +20,5 @@ /** | ||
it('should be able to acquire lock', function(done) { | ||
User.transaction("test", function(err, unlock) { | ||
unlock(); | ||
done(err); | ||
}); | ||
User.transaction("test", function(err, cb) { | ||
cb(); | ||
},done); | ||
}); | ||
@@ -32,3 +31,3 @@ | ||
User.transaction('test', function(err, unlock1) { | ||
if(err) return done(err); | ||
if(err) throw new Error(err); | ||
if(!unlock1) throw new Error("No unlock() method provided!"); | ||
@@ -39,3 +38,3 @@ | ||
User.transaction('test', function(err, unlock2) { | ||
if(err) return done(err); | ||
if(err) throw new Error(err); | ||
if(!unlock2) throw new Error("No unlock() method provided!"); | ||
@@ -46,14 +45,13 @@ | ||
// Release lock so other tests can use the 'test' transaction | ||
unlock2(); | ||
if(_.isEqual(orderingTest, ['lock', 'unlock', 'lock'])) done(); | ||
if(_.isEqual(orderingTest, ['lock', 'unlock', 'lock'])) unlock2(); | ||
else { | ||
console.error(orderingTest); | ||
throw "The lock was acquired by two users at once!"; | ||
throw new Error("The lock was acquired by two users at once!"); | ||
} | ||
}); | ||
}, done); | ||
// Set timeout to release the lock after a 1/20 of a second | ||
setTimeout(function() { | ||
unlock1(testAppendUnlock); | ||
testAppendUnlock(); | ||
unlock1(); | ||
}, 50); | ||
@@ -96,2 +94,4 @@ }); | ||
User.transaction('test_create',function(err,unlock) { | ||
if (err) throw new Error(err); | ||
User.create({ | ||
@@ -101,17 +101,16 @@ name: constellation, | ||
},function(err) { | ||
if (err) throw new Error(err); | ||
// Wait a short moment to introduce an element of choas | ||
setTimeout(function() { | ||
unlock(); | ||
cb(); | ||
},Math.round(Math.random())*5); | ||
}); | ||
}); | ||
},cb); | ||
}, function(err) { | ||
if (err) throw new Error(err); | ||
User.find({ type: type },function (err,users) { | ||
if(users.length != items.length) { | ||
console.error("Users: "); | ||
console.error(users); | ||
return done('Proper users were not created!'); | ||
} | ||
done(); | ||
if(users.length === items.length) return done(); | ||
else return done('Proper users were not created!'); | ||
}); | ||
@@ -118,0 +117,0 @@ }); |
61511
25
1585
+ Addedwaterline-dirty@0.0.0501(transitive)
- Removedwaterline-dirty@0.0.03(transitive)
Updatedwaterline-dirty@0.0.0501