jugglingdb
Version:
Node.js ORM for every database: redis, mysql, mongodb, postgres, sqlite, ...
301 lines (265 loc) • 8.45 kB
JavaScript
exports.initialize = function initializeSchema(schema, callback) {
schema.adapter = new Memory();
schema.adapter.connect(callback);
};
function Memory(m) {
if (m) {
this.isTransaction = true;
this.cache = m.cache;
this.ids = m.ids;
this._models = m._models;
} else {
this.isTransaction = false;
this.cache = {};
this.ids = {};
this._models = {};
}
}
Memory.prototype.connect = function(callback) {
if (this.isTransaction) {
this.onTransactionExec = callback;
} else {
process.nextTick(callback);
}
};
Memory.prototype.define = function defineModel(descr) {
const m = descr.model.modelName;
this._models[m] = descr;
this.cache[this.table(m)] = {};
this.ids[m] = 0;
};
Memory.prototype.create = function create(model, data, callback) {
const id = data.id ? data.id : this.ids[model] += 1;
data.id = id;
this.cache[this.table(model)][id] = JSON.stringify(data);
process.nextTick(() => {
callback(null, id, 1);
});
};
/**
* Updates the respective record
*
* @param {Object} params - { where:{uid:'10'}, update:{ Name:'New name' } }
* @param callback(err, obj)
*/
Memory.prototype.update = function(model, params, callback) {
const mem = this;
this.all(model, { where: params.where, limit: params.limit, skip: params.skip }, (err, records) => {
let wait = records.length;
records.forEach(record => {
mem.updateAttributes(model, record.id, params.update, done);
});
if (wait === 0) {
callback();
}
function done() {
wait += -1;
if (wait === 0) {
callback();
}
}
});
};
Memory.prototype.updateOrCreate = function(model, data, callback) {
const mem = this;
this.find(model, data.id, (err, exists) => {
if (exists) {
mem.save(model, merge(exists, data), callback);
} else {
mem.create(model, data, (err, id) => {
data.id = id;
callback(err, data);
});
}
});
};
Memory.prototype.save = function save(model, data, callback) {
this.cache[this.table(model)][data.id] = JSON.stringify(data);
process.nextTick(() => callback(null, data));
};
Memory.prototype.exists = function exists(model, id, callback) {
const table = this.table(model);
process.nextTick(() => {
callback(null, this.cache[table].hasOwnProperty(id));
});
};
Memory.prototype.find = function find(model, id, callback) {
const table = this.table(model);
process.nextTick(() => {
callback(null, id in this.cache[table] && this.fromDb(model, this.cache[table][id]));
});
};
Memory.prototype.destroy = function destroy(model, id, callback) {
delete this.cache[this.table(model)][id];
process.nextTick(callback);
};
Memory.prototype.fromDb = function(model, data) {
if (!data) {
return null;
}
data = JSON.parse(data);
const props = this._models[model].properties;
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val === 'undefined' || val === null) {
return;
}
if (props[key]) {
switch (props[key].type.name) {
case 'Date':
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
break;
case 'Boolean':
val = Boolean(val);
break;
}
}
data[key] = val;
});
return data;
};
Memory.prototype.all = function all(model, filter, callback) {
const table = this.table(model);
let nodes = Object.keys(this.cache[table]).map(key => {
return this.fromDb(model, this.cache[table][key]);
});
if (filter) {
// do we need some sorting?
if (filter.order) {
let orders = filter.order;
if (typeof filter.order === 'string') {
orders = [filter.order];
}
orders.forEach((key, i) => {
let reverse = 1;
const m = key.match(/\s+(A|DE)SC$/i);
if (m) {
key = key.replace(/\s+(A|DE)SC/i, '');
if (m[1].toLowerCase() === 'de') {
reverse = -1;
}
}
orders[i] = { key, reverse };
});
nodes = nodes.sort(sorting.bind(orders));
}
// do we need some filtration?
if (filter.where) {
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
}
// limit/skip
filter.skip = filter.skip || 0;
filter.limit = filter.limit || nodes.length;
const countBeforeLimit = nodes.length;
nodes = nodes.slice(filter.skip, filter.skip + filter.limit);
nodes.countBeforeLimit = countBeforeLimit;
}
process.nextTick(() => {
if (filter && filter.include) {
this._models[model].model.include(nodes, filter.include, callback);
} else {
callback(null, nodes);
}
});
function sorting(a, b) {
for (let i = 0, l = this.length; i < l; i += 1) {
if (a[this[i].key] > b[this[i].key]) {
return 1 * this[i].reverse;
} else if (a[this[i].key] < b[this[i].key]) {
return -1 * this[i].reverse;
}
}
return 0;
}
};
function applyFilter(filter) {
if (typeof filter.where === 'function') {
return filter.where;
}
const keys = Object.keys(filter.where);
return function(obj) {
let pass = true;
keys.forEach(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);
}
if (typeof example === 'undefined') {
return undefined;
}
if (typeof value === 'undefined') {
return undefined;
}
if (typeof example === 'object') {
if (example === null) {
return value === null;
}
if (example.inq) {
if (!value) {
return false;
}
for (let i = 0; i < example.inq.length; i += 1) {
if (String(example.inq[i]) === String(value)) {
return true;
}
}
return false;
}
}
// not strict equality
return String(example !== null ? example.toString() : example) ===
String(value !== null ? value.toString() : value);
}
}
Memory.prototype.destroyAll = function destroyAll(model, callback) {
const table = this.table(model);
Object.keys(this.cache[table]).forEach(id => {
delete this.cache[table][id];
});
this.cache[table] = {};
process.nextTick(callback);
};
Memory.prototype.count = function count(model, where, callback) {
const cache = this.cache[this.table(model)];
let data = Object.keys(cache);
if (where) {
data = data.filter(id => {
let ok = true;
Object.keys(where).forEach(key => {
if (String(JSON.parse(cache[id])[key]) !== String(where[key])) {
ok = false;
}
});
return ok;
});
}
process.nextTick(() => callback(null, data.length));
};
Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
data.id = id;
const base = JSON.parse(this.cache[this.table(model)][id]);
this.save(model, merge(base, data), cb);
};
Memory.prototype.transaction = function() {
return new Memory(this);
};
Memory.prototype.exec = function(callback) {
this.onTransactionExec();
setTimeout(callback, 50);
};
Memory.prototype.table = function(model) {
return this._models[model].model.tableName;
};
function merge(base, update) {
if (!base) {
return update;
}
Object.keys(update).forEach(key => base[key] = update[key]);
return base;
}