344 lines
7.8 KiB
JavaScript
344 lines
7.8 KiB
JavaScript
var safeRequire = require('../utils').safeRequire;
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
var cradle = safeRequire('cradle');
|
|
|
|
/**
|
|
* Private functions for internal use
|
|
*/
|
|
function CradleAdapter(client) {
|
|
this._models = {};
|
|
this.client = client;
|
|
}
|
|
|
|
function createdbif(client, callback) {
|
|
client.exists(function (err, exists) {
|
|
if (err) callback(err);
|
|
if (!exists) {
|
|
client.create(function () {
|
|
callback();
|
|
});
|
|
}
|
|
else {
|
|
callback();
|
|
}
|
|
});
|
|
}
|
|
|
|
function naturalize(data, model) {
|
|
data.nature = model;
|
|
//TODO: maybe this is not a really good idea
|
|
if (data.date) data.date = data.date.toString();
|
|
return data;
|
|
}
|
|
function idealize(data) {
|
|
data.id = data._id;
|
|
return data;
|
|
}
|
|
function stringify(data) {
|
|
return data ? data.toString() : data
|
|
}
|
|
|
|
function errorHandler(callback, func) {
|
|
return function (err, res) {
|
|
if (err) {
|
|
console.log('cradle', err);
|
|
callback(err);
|
|
} else {
|
|
if (func) {
|
|
func(res, function (res) {
|
|
callback(null, res);
|
|
});
|
|
} else {
|
|
callback(null, res);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function synchronize(functions, args, callback) {
|
|
if (functions.length === 0) callback();
|
|
if (functions.length > 0 && args.length === functions.length) {
|
|
functions[0](args[0][0], args[0][1], function (err, res) {
|
|
if (err) callback(err);
|
|
functions.splice(0, 1);
|
|
args.splice(0, 1);
|
|
synchronize(functions, args, callback);
|
|
});
|
|
}
|
|
};
|
|
|
|
function applyFilter(filter) {
|
|
if (typeof filter.where === 'function') {
|
|
return filter.where;
|
|
}
|
|
var keys = Object.keys(filter.where);
|
|
return function (obj) {
|
|
var pass = true;
|
|
keys.forEach(function (key) {
|
|
if (!test(filter.where[key], obj[key])) {
|
|
pass = false;
|
|
}
|
|
});
|
|
return pass;
|
|
}
|
|
|
|
function test(example, value) {
|
|
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
|
|
return value.match(example);
|
|
}
|
|
// not strict equality
|
|
return example == value;
|
|
}
|
|
}
|
|
|
|
function numerically(a, b) {
|
|
return a[this[0]] - b[this[0]];
|
|
}
|
|
|
|
function literally(a, b) {
|
|
return a[this[0]] > b[this[0]];
|
|
}
|
|
|
|
function filtering(res, model, filter, instance) {
|
|
|
|
if (model) {
|
|
if (filter == null) filter = {};
|
|
if (filter.where == null) filter.where = {};
|
|
filter.where.nature = model;
|
|
}
|
|
// do we need some filtration?
|
|
if (filter.where) {
|
|
res = res ? res.filter(applyFilter(filter)) : res;
|
|
}
|
|
|
|
// do we need some sorting?
|
|
if (filter.order) {
|
|
var props = instance[model].properties;
|
|
var allNumeric = true;
|
|
var orders = filter.order;
|
|
var reverse = false;
|
|
if (typeof filter.order === "string") {
|
|
orders = [filter.order];
|
|
}
|
|
|
|
orders.forEach(function (key, i) {
|
|
var m = key.match(/\s+(A|DE)SC$/i);
|
|
if (m) {
|
|
key = key.replace(/\s+(A|DE)SC/i, '');
|
|
if (m[1] === 'DE') reverse = true;
|
|
}
|
|
orders[i] = key;
|
|
if (props[key].type.name !== 'Number') {
|
|
allNumeric = false;
|
|
}
|
|
});
|
|
if (allNumeric) {
|
|
res = res.sort(numerically.bind(orders));
|
|
} else {
|
|
res = res.sort(literally.bind(orders));
|
|
}
|
|
if (reverse) res = res.reverse();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Connection/Disconnection
|
|
*/
|
|
exports.initialize = function (dataSource, callback) {
|
|
if (!cradle) return;
|
|
|
|
// when using cradle if we dont wait for the dataSource to be connected, the models fails to load correctly.
|
|
dataSource.waitForConnect = true;
|
|
if (!dataSource.settings.url) {
|
|
var host = dataSource.settings.host || 'localhost';
|
|
var port = dataSource.settings.port || '5984';
|
|
var options = dataSource.settings.options || {
|
|
cache: true,
|
|
raw: false
|
|
};
|
|
if (dataSource.settings.username) {
|
|
options.auth = {};
|
|
options.auth.username = dataSource.settings.username;
|
|
if (dataSource.settings.password) {
|
|
options.auth.password = dataSource.settings.password;
|
|
}
|
|
}
|
|
var database = dataSource.settings.database || 'loopback-datasource-juggler';
|
|
|
|
dataSource.settings.host = host;
|
|
dataSource.settings.port = port;
|
|
dataSource.settings.database = database;
|
|
dataSource.settings.options = options;
|
|
}
|
|
dataSource.client = new (cradle.Connection)(dataSource.settings.host, dataSource.settings.port, dataSource.settings.options).database(dataSource.settings.database);
|
|
|
|
createdbif(
|
|
dataSource.client,
|
|
errorHandler(callback, function () {
|
|
dataSource.connector = new CradleAdapter(dataSource.client);
|
|
process.nextTick(callback);
|
|
}));
|
|
};
|
|
|
|
CradleAdapter.prototype.disconnect = function () {
|
|
};
|
|
|
|
/**
|
|
* Write methods
|
|
*/
|
|
CradleAdapter.prototype.define = function (descr) {
|
|
this._models[descr.model.modelName] = descr;
|
|
};
|
|
|
|
CradleAdapter.prototype.create = function (model, data, callback) {
|
|
this.client.save(
|
|
stringify(data.id),
|
|
naturalize(data, model),
|
|
errorHandler(callback, function (res, cb) {
|
|
cb(res.id);
|
|
})
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.save = function (model, data, callback) {
|
|
this.client.save(
|
|
stringify(data.id),
|
|
naturalize(data, model),
|
|
errorHandler(callback)
|
|
)
|
|
};
|
|
|
|
CradleAdapter.prototype.updateAttributes = function (model, id, data, callback) {
|
|
this.client.merge(
|
|
stringify(id),
|
|
data,
|
|
errorHandler(callback, function (doc, cb) {
|
|
cb(idealize(doc));
|
|
})
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.updateOrCreate = function (model, data, callback) {
|
|
this.client.get(
|
|
stringify(data.id),
|
|
function (err, doc) {
|
|
if (err) {
|
|
this.create(model, data, callback);
|
|
} else {
|
|
this.updateAttributes(model, data.id, data, callback);
|
|
}
|
|
}.bind(this)
|
|
)
|
|
};
|
|
|
|
/**
|
|
* Read methods
|
|
*/
|
|
CradleAdapter.prototype.exists = function (model, id, callback) {
|
|
this.client.get(
|
|
stringify(id),
|
|
errorHandler(callback, function (doc, cb) {
|
|
cb(!!doc);
|
|
})
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.find = function (model, id, callback) {
|
|
this.client.get(
|
|
stringify(id),
|
|
errorHandler(callback, function (doc, cb) {
|
|
cb(idealize(doc));
|
|
})
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.count = function (model, callback, where) {
|
|
this.models(
|
|
model,
|
|
{where: where},
|
|
callback,
|
|
function (docs, cb) {
|
|
cb(docs.length);
|
|
}
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.models = function (model, filter, callback, func) {
|
|
var limit = 200;
|
|
var skip = 0;
|
|
if (filter != null) {
|
|
limit = filter.limit || limit;
|
|
skip = filter.skip || skip;
|
|
}
|
|
|
|
var self = this;
|
|
|
|
self.client.save('_design/' + model, {
|
|
views: {
|
|
all: {
|
|
map: 'function(doc) { if (doc.nature == "' + model + '") { emit(doc._id, doc); } }'
|
|
}
|
|
}
|
|
}, function () {
|
|
self.client.view(model + '/all', {include_docs: true, limit: limit, skip: skip},
|
|
errorHandler(callback, function (res, cb) {
|
|
var docs = res.map(function (doc) {
|
|
return idealize(doc);
|
|
});
|
|
var filtered = filtering(docs, model, filter, this._models)
|
|
|
|
func ? func(filtered, cb) : cb(filtered);
|
|
}.bind(self)));
|
|
});
|
|
};
|
|
|
|
CradleAdapter.prototype.all = function (model, filter, callback) {
|
|
this.models(
|
|
model,
|
|
filter,
|
|
callback
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Detroy methods
|
|
*/
|
|
CradleAdapter.prototype.destroy = function (model, id, callback) {
|
|
this.client.remove(
|
|
stringify(id),
|
|
function (err, doc) {
|
|
callback(err);
|
|
}
|
|
);
|
|
};
|
|
|
|
CradleAdapter.prototype.destroyAll = function (model, callback) {
|
|
this.models(
|
|
model,
|
|
null,
|
|
callback,
|
|
function (docs, cb) {
|
|
var docIds = docs.map(function (doc) {
|
|
return doc.id;
|
|
});
|
|
this.client.get(docIds, function (err, res) {
|
|
if (err) cb(err);
|
|
|
|
var funcs = res.map(function (doc) {
|
|
return this.client.remove.bind(this.client);
|
|
}.bind(this));
|
|
|
|
var args = res.map(function (doc) {
|
|
return [doc._id, doc._rev];
|
|
});
|
|
|
|
synchronize(funcs, args, cb);
|
|
}.bind(this));
|
|
}.bind(this)
|
|
);
|
|
};
|