Skip to content

Instantly share code, notes, and snippets.

@arextar
Created January 1, 2012 21:22
Show Gist options
  • Save arextar/1548381 to your computer and use it in GitHub Desktop.
Save arextar/1548381 to your computer and use it in GitHub Desktop.
A nodejs module for persisting, querying, and manipulating data
exports.store = function(stay){
stay = require("./node-stay").store(stay);
var type = function(a){
var t = typeof a;
return t === 'object' ? {}.toString.call(a).slice(8, -1).toLowerCase() : t;
},
get = stay.get, set = stay.set, del = stay.del, exists = stay.exists
, it = {object:1, array:1}
function deepMap(obj, fn, pre){
var ret = {};
for(var x in obj){
if(type(obj[x]) === "object"){
ret[x] = deepMap(obj[x], fn, pre + "." + x);
}
else
{
ret[x] = fn(obj[x], pre + "." + x);
}
}
return ret;
}
function flatten(obj, parent, pre){
for(var x in obj){
if(type(obj[x]) === "object"){
flatten(obj[x], parent, (pre ? pre + "." : "") + x);
}
else
{
parent[(pre ? pre + "." : "") + x] = obj[x];
}
}
}
function Model(name, scheme, data){
this.name = name;
this.scheme = scheme;
flatten(scheme, this.flat = {}, "");
var d = {}, x;
flatten(data, d, "");
for(x in d) this.set(x, d[x]);
}
function coerce(what, typ){
return typ ? (type(typ) === 'string' ? storage.types[typ] : typ)(what) : what;
}
Model.prototype = {
get: function(prop){
if(prop){
return coerce(get(this.name + "." + prop), this.flat[prop]);
}
return deepMap(this.scheme, function(type, prop){
return exists(prop) ? coerce(get(prop), type) : null;
}, this.name)
},
set: function(prop, val){
if(val === null){
del(this.name + "." + prop);
}
else if(type(val) === 'object')
{
deepMap(val, function(val, prop){
set(prop, val);
}, this.name + "." + prop)
}
else if(type(prop) === 'object'){
deepMap(prop, function(val, prop){
set(prop, val);
}, this.name)
}
else
{
set(this.name + "." + prop, val);
}
},
has: function(prop){
return exists(this.name + "." + prop);
},
def: function(prop, val){
this.has(prop) || this.set(prop, val);
},
incr: function(prop, by){
this.set(prop, this.get(prop) + (by || 1));
},
decr: function(prop,by){
this.incr(prop, by?-by:-1);
},
remove: function(prop){
if(prop){
del(this.name + "." + prop);
}
else
{
var name = this.name + ".", x;
for(x in this.flat){
del(name + x);
}
}
},
rename: function(name){
var n = this.name + ".", na = name + ".", x;
for(x in this.flat){
set(na + x, get(n + x));
}
this.remove();
this.name = name;
}
}
function Collection(name, scheme){
this.model = scheme;
Model.call(this, name, {length:"int"}, {})
this.def("length", 0);
}
var proto = Collection.prototype = new Model, modelProto = Model.prototype;
proto.get = function(prop){
if(!prop){
for(var ret = [], l = this.size(), x = 0; x < l; x++){
ret[x] = modelProto.get.call({name:this.name + "." + x, scheme: this.model})
}
return ret;
}
return modelProto.get.call(this, prop)
}
proto.at = function(ind){
return ind < this.size() ? new Model(this.name + "." + ind, this.model) : null;
}
proto.size = function(){
return this.get("length");
}
proto.add = function(obj){
var ret = new Model(this.name + "." + this.size(), this.model, obj);
this.incr("length");
return ret;
}
proto.remove = function(at){
this.at(at).remove();
for(var x = at + 1, l = this.size(); x < l; x++){
this.at(x).rename(this.name + "." + (x - 1));
}
this.decr("length");
}
proto.each = function(fn){
for(var i = 0, l = this.size(); i < l; i++){
if(fn(this.at(i), i, this) === false) break;
}
}
proto.filter = function(filter, fn, single){
if(typeof fn === 'boolean'){
single = fn;
fn = null;
}
var ret = [], d = 0
this.each(function(model, ind, c){
if(filter(model)){
ret[d++] = model;
fn && fn(model, ind, c);
if(single) return false;
}
});
return single ? ret[0] : ret;
}
proto.is = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) === is;
}, fn, single);
}
proto.isnt = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) !== is;
}, fn, single);
}
proto.lt = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) < is;
}, fn, single);
}
proto.gt = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) > is;
}, fn, single);
}
proto.lte = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) <= is;
}, fn, single);
}
proto.gte = function(prop, is, fn, single){
return this.filter(function(model){
return model.get(prop) >= is;
}, fn, single);
}
var storage = {
model: function(name, scheme){
return new Model(name, scheme);
},
collection: function(name, scheme){
return new Collection(name, scheme);
},
data: stay.data,
types:{
string: String,
"int": parseInt,
number: parseFloat,
date: function(str){return new Date(str)}
}
}
return storage;
};
var fs = require('fs');
exports.store = function(fname){
var data = {}, saveTO = null;
// Read the file contents, if the file exists, synchronously into the data object
try{
fs.readFileSync(fname, 'utf8').split("\u0000").forEach(function(line){
line = line.split("\u0001");
data[line[0]] = line[1];
});
}
catch(e){}
// Function to make a string out of the data object
function stringify(){
var dat = [], d = 0, x;
for(x in data) dat[d++] = x + "\u0001" + data[x];
return dat.join("\u0000");
}
// Function to save string to file async (a timeout is used so a block of modifying calls will all be modified in the file at the same time)
function save(){
clearTimeout(saveTO);
saveTO = setTimeout(function(){
fs.writeFile(fname, stringify());
saveTO = null;
}, 13)
}
// When the process exits, make sure the file is saved
process.on("exit", function(){
if(saveTO !== null){
clearTimeout(saveTO);
fs.writeFileSync(fname, stringify());
}
})
return {
get: function(prop){
return data[prop];
},
set: function(prop, val){
data[prop] = val + "";
save();
},
exists: function(prop){
return typeof data[prop] !== "undefined";
},
del: function(prop){
delete data[prop];
save();
},
save: save,
data: data
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment