;(function(){ // Generic javascript utilities. var Type = Gun; //Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} Type.fn = {is: function(fn){ return (!!fn && 'function' == typeof fn) }} Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }} Type.num = {is: function(n){ return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }} Type.text = {is: function(t){ return (typeof t == 'string') }} Type.text.ify = function(t){ if(Type.text.is(t)){ return t } if(typeof JSON !== "undefined"){ return JSON.stringify(t) } return (t && t.toString)? t.toString() : t; } Type.text.random = function(l, c){ var s = ''; l = l || 24; // you are not going to make a 0 length random number, so no need to check type c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } return s; } Type.text.match = function(t, o){ var tmp, u; if('string' !== typeof t){ return false } if('string' == typeof o){ o = {'=': o} } o = o || {}; tmp = (o['='] || o['*'] || o['>'] || o['<']); if(t === tmp){ return true } if(u !== o['=']){ return false } tmp = (o['*'] || o['>'] || o['<']); if(t.slice(0, (tmp||'').length) === tmp){ return true } if(u !== o['*']){ return false } if(u !== o['>'] && u !== o['<']){ return (t >= o['>'] && t <= o['<'])? true : false; } if(u !== o['>'] && t >= o['>']){ return true } if(u !== o['<'] && t <= o['<']){ return true } return false; } Type.text.hash = function(s, c){ // via SO if(typeof s !== 'string'){ return } c = c || 0; if(!s.length){ return c } for(var i=0,l=s.length,n; i B){ return 1 } else { return 0 } } } Type.list.map = function(l, c, _){ return obj_map(l, c, _) } Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation Type.obj = {is: function(o){ return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} Type.obj.put = function(o, k, v){ return (o||{})[k] = v, o } Type.obj.has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) } Type.obj.del = function(o, k){ if(!o){ return } o[k] = null; delete o[k]; return o; } Type.obj.as = function(o, k, v, u){ return o[k] = o[k] || (u === v? {} : v) } Type.obj.ify = function(o){ if(obj_is(o)){ return o } try{o = JSON.parse(o); }catch(e){o={}}; return o; } ;(function(){ var u; function map(v,k){ if(obj_has(this,k) && u !== this[k]){ return } this[k] = v; } Type.obj.to = function(from, to){ to = to || {}; obj_map(from, map, to); return to; } }()); Type.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! } ;(function(){ function empty(v,i){ var n = this.n, u; if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } if(u !== i){ return true } } Type.obj.empty = function(o, n){ if(!o){ return true } return obj_map(o,empty,{n:n})? false : true; } }()); ;(function(){ function t(k,v){ if(2 === arguments.length){ t.r = t.r || {}; t.r[k] = v; return; } t.r = t.r || []; t.r.push(k); }; var keys = Object.keys, map, u; Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) } Type.obj.map = map = function(l, c, _){ var u, i = 0, x, r, ll, lle, f = 'function' == typeof c; t.r = u; if(keys && obj_is(l)){ ll = keys(l); lle = true; } _ = _ || {}; if(list_is(l) || ll){ x = (ll || l).length; for(;i < x; i++){ var ii = (i + Type.list.index); if(f){ r = lle? c.call(_, l[ll[i]], ll[i], t) : c.call(_, l[i], ii, t); if(r !== u){ return r } } else { //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now } } } else { for(i in l){ if(f){ if(obj_has(l,i)){ r = _? c.call(_, l[i], i, t) : c(l[i], i, t); if(r !== u){ return r } } } else { //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! if(c === l[i]){ return i } // use this for now } } } return f? t.r : Type.list.index? 0 : -1; } }()); Type.time = {}; Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } var fn_is = Type.fn.is; var list_is = Type.list.is; var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; var Val = {}; Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. if(v === u){ return false } if(v === null){ return true } // "deletes", nulling out keys. if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(text_is(v) // by "text" we mean strings. || bi_is(v) // by "binary" we mean boolean. || num_is(v)){ // by "number" we mean integers or decimals. return true; // simple values are valid. } return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. } Val.link = Val.rel = {_: '#'}; ;(function(){ Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. var o = {}; obj_map(v, map, o); if(o.id){ // a valid id was found. return o.id; // yay! Return it. } } return false; // the value was not a valid soul relation. } function map(s, k){ var o = this; // map over the object... if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. o.id = s; // we found the soul! } else { return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. } } }()); Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. Type.obj.has._ = '.'; var rel_ = Val.link._, u; var bi_is = Type.bi.is; var num_is = Type.num.is; var text_is = Type.text.is; var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; Type.val = Val; var Node = {_: '_'}; Node.soul = function(n, o){ return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. Node.soul.ify = function(n, o){ // put a soul on an object. o = (typeof o === 'string')? {soul: o} : o || {}; n = n || {}; // make sure it exists. n._ = n._ || {}; // make sure meta exists. n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. return n; } Node.soul._ = Val.link._; ;(function(){ Node.is = function(n, cb, as){ var s; // checks to see if an object is a valid node. if(!obj_is(n)){ return false } // must be an object. if(s = Node.soul(n)){ // must have a soul on it. return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); } return false; // nope! This was not a valid node. } function map(v, k){ // we invert this because the way we check for this is via a negation. if(k === Node._){ return } // skip over the metadata. if(!Val.is(v)){ return true } // it is true that this is an invalid node. if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. } }()); ;(function(){ Node.ify = function(obj, o, as){ // returns a node from a shallow object. if(!o){ o = {} } else if(typeof o === 'string'){ o = {soul: o} } else if('function' == typeof o){ o = {map: o} } if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } if(o.node = Node.soul.ify(o.node || {}, o)){ obj_map(obj, map, {o:o,as:as}); } return o.node; // This will only be a valid node if the object wasn't already deep! } function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. if(o.map){ tmp = o.map.call(this.as, v, ''+k, o.node); if(u === tmp){ obj_del(o.node, k); } else if(o.node){ o.node[k] = tmp } return; } if(Val.is(v)){ o.node[k] = v; } } }()); var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; var text = Type.text, text_random = text.random; var soul_ = Node.soul._; var u; Type.node = Node; var State = Type.state; State.lex = function(){ return State().toString(36).replace('.','') } State.to = function(from, k, to){ var val = (from||{})[k]; if(obj_is(val)){ val = obj_copy(val); } return State.ify(to, k, State.is(from, k), val, Node.soul(from)); } ;(function(){ State.map = function(cb, s, as){ var u; // for use with Node.ify var o = obj_is(o = cb || s)? o : null; cb = fn_is(cb = cb || s)? cb : null; if(o && !cb){ s = num_is(s)? s : State(); o[N_] = o[N_] || {}; obj_map(o, map, {o:o,s:s}); return o; } as = as || obj_is(s)? s : u; s = num_is(s)? s : State(); return function(v, k, o, opt){ if(!cb){ map.call({o: o, s: s}, v,k); return v; } cb.call(as || this || {}, v, k, o, opt); if(obj_has(o,k) && u === o[k]){ return } map.call({o: o, s: s}, v,k); } } function map(v,k){ if(N_ === k){ return } State.ify(this.o, k, this.s) ; } }()); var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; var num = Type.num, num_is = num.is; var fn = Type.fn, fn_is = fn.is; var N_ = Node._, u; var Graph = {}; ;(function(){ Graph.is = function(g, cb, fn, as){ // checks to see if an object is a valid graph. if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. } function map(n, s){ // we invert this because the way'? we check for this is via a negation. if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. if(!this.cb){ return } nf.n = n; nf.as = this.as; // sequential race conditions aren't races. this.cb.call(nf.as, n, s, nf); } function nf(fn){ // optional callback for each node. if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. } }()); ;(function(){ Graph.ify = function(obj, env, as){ var at = {path: [], obj: obj}; if(!env){ env = {}; } else if(typeof env === 'string'){ env = {soul: env}; } else if('function' == typeof env){ env.map = env; } if(typeof as === 'string'){ env.soul = env.soul || as; as = u; } if(env.soul){ at.link = Val.link.ify(env.soul); } env.shell = (as||{}).shell; env.graph = env.graph || {}; env.seen = env.seen || []; env.as = env.as || as; node(env, at); env.root = at.node; return env.graph; } function node(env, at){ var tmp; if(tmp = seen(env, at)){ return tmp } at.env = env; at.soul = soul; if(Node.ify(at.obj, map, at)){ at.link = at.link || Val.link.ify(Node.soul(at.node)); if(at.obj !== env.shell){ env.graph[Val.link.is(at.link)] = at.node; } } return at; } function map(v,k,n){ var at = this, env = at.env, is, tmp; if(Node._ === k && obj_has(v,Val.link._)){ return n._; // TODO: Bug? } if(!(is = valid(v,k,n, at,env))){ return } if(!k){ at.node = at.node || n || {}; if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? at.node._ = obj_copy(v._); } at.node = Node.soul.ify(at.node, Val.link.is(at.link)); at.link = at.link || Val.link.ify(Node.soul(at.node)); } if(tmp = env.map){ tmp.call(env.as || {}, v,k,n, at); if(obj_has(n,k)){ v = n[k]; if(u === v){ obj_del(n, k); return; } if(!(is = valid(v,k,n, at,env))){ return } } } if(!k){ return at.node } if(true === is){ return v; } tmp = node(env, {obj: v, path: at.path.concat(k)}); if(!tmp.node){ return } return tmp.link; //{'#': Node.soul(tmp.node)}; } function soul(id){ var at = this; var prev = Val.link.is(at.link), graph = at.env.graph; at.link = at.link || Val.link.ify(id); at.link[Val.link._] = id; if(at.node && at.node[Node._]){ at.node[Node._][Val.link._] = id; } if(obj_has(graph, prev)){ graph[id] = graph[prev]; obj_del(graph, prev); } } function valid(v,k,n, at,env){ var tmp; if(Val.is(v)){ return true } if(obj_is(v)){ return 1 } if(tmp = env.invalid){ v = tmp.call(env.as || {}, v,k,n); return valid(v,k,n, at,env); } env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } } function seen(env, at){ var arr = env.seen, i = arr.length, has; while(i--){ has = arr[i]; if(at.obj === has.obj){ return has } } arr.push(at); } }()); Graph.node = function(node){ var soul = Node.soul(node); if(!soul){ return } return obj_put({}, soul, node); } ;(function(){ Graph.to = function(graph, root, opt){ if(!graph){ return } var obj = {}; opt = opt || {seen: {}}; obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); return obj; } function map(v,k){ var tmp, obj; if(Node._ === k){ if(obj_empty(v, Val.link._)){ return; } this.obj[k] = obj_copy(v); return; } if(!(tmp = Val.link.is(v))){ this.obj[k] = v; return; } if(obj = this.opt.seen[tmp]){ this.obj[k] = obj; return; } this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); } }()); var fn_is = Type.fn.is; var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; var u; Type.graph = Graph; }());