diff --git a/examples/admin/app.js b/examples/admin/app.js index 895c6836..7fb395ab 100644 --- a/examples/admin/app.js +++ b/examples/admin/app.js @@ -17,5 +17,6 @@ app.listen(port); console.log('Express started on port ' + port + ' with /gun'); gun.load('blob/data').blank(function(){ // in case there is no data on this key - gun.set({ hello: "world", from: "Mark Nadal" }).key('blob/data'); // save some sample data + console.log("blankety blank"); + gun.set({ hello: "world", from: "Mark Nadal",_:{'#':'0DFXd0ckJ9cXGczusNf1ovrE'}}).key('blob/data'); // save some sample data }); \ No newline at end of file diff --git a/examples/admin/duel.html b/examples/admin/duel.html deleted file mode 100644 index 74250474..00000000 --- a/examples/admin/duel.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - -
-
-

GET READY!

- -
-
- -
-

GUNSLINGER

- Old West Duel! Two players are needed, whoever can tap the screen first to draw their pistol and shoot wins!
- Fastest gun in the west, nut'n seconds, by nobody. -
- Player 1: -
-
- Player 2: -
- Last duel won by no one in 0 seconds against nobody. -
- - - - - \ No newline at end of file diff --git a/examples/admin/index.html b/examples/admin/index.html index b58b923d..dc061d9e 100644 --- a/examples/admin/index.html +++ b/examples/admin/index.html @@ -62,15 +62,12 @@ var gun = Gun([location + 'gun']); angular.module('admin', []).controller('editor', function($scope){ $scope.data = {}; - $scope.$data = gun.load('blob/data', function(data){ - $scope.data = data; - $scope.$apply(); - Gun.on(data._[Gun.sym.id]).event(function(node){ // one liner-ify this! - Gun.obj.map(node, function(val, field){ - $scope.data[field] = val; - }); - $scope.$apply(); + $scope.$data = gun.load('blob/data').get(function(data){ + Gun.obj.map(data, function(val, field){ + if(val === $scope.data[field]){ return } + $scope.data[field] = val; }); + $scope.$apply(); }); $scope.add = function(a,b,c){ $scope.$data.path($scope.field).set( $scope.data[$scope.field] = 'value' ); diff --git a/examples/admin/package.json b/examples/admin/package.json index adb8bd3b..a9f9782e 100644 --- a/examples/admin/package.json +++ b/examples/admin/package.json @@ -9,7 +9,7 @@ , "dependencies": { "express": "~>4.9.0", "body-parser": "~>1.8.1", - "gun": "0.0.6-e" + "gun": "0.0.7" } , "scripts": { "start": "node app.js", diff --git a/examples/admin/slinger.html b/examples/admin/slinger.html new file mode 100644 index 00000000..6e079611 --- /dev/null +++ b/examples/admin/slinger.html @@ -0,0 +1,195 @@ + + + + + + + + +
+ +
+ +

GUN SLINGER

+

Select!

+ + +
Next game available in 15 seconds or less...
+
+
+

GET READY!

+
+
+

FIRE!

+
by tapping this screen
+
+
+

STOP!

+
...waiting for the other player...
+
+
+

DISQUALIFIED!

+
+
+

YOU DIED!

+
+
+

YOU BOTH DIED!

+ +
+
+

YOU WON!

+ +
+ +
+ \ No newline at end of file diff --git a/gate/s3.js b/gate/s3.js index 06671654..0f2cc95b 100644 --- a/gate/s3.js +++ b/gate/s3.js @@ -5,7 +5,7 @@ return new s3(opt); } var s = this; - s.own = a.on.split(); + s.on = a.on.create(); s.mime = require('mime'); s.AWS = require('aws-sdk'); s.config = {}; @@ -33,7 +33,7 @@ s3.chain = s3.prototype; s3.chain.put = function(key, o, cb, m){ if(!key){ return } - var m = m || {} + m = m || {} m.Bucket = m.Bucket || this.config.bucket; m.Key = m.Key || key; if(a.obj.is(o) || a.list.is(o)){ @@ -56,7 +56,7 @@ Bucket: s.config.bucket ,Key: key }, id = s3.id(m); - s.own.on(id).once(function(e,d,t,m,r){ + s.on(id).once(function(e,d,t,m,r){ delete s.batch[id]; if(!a.fns.is(cb)){ return } try{ cb(e,d,t,m,r); @@ -69,15 +69,16 @@ s.batch[id] = (s.batch[id] || 0) + 1; console.log("no batch!"); s.S3().getObject(m, function(e,r){ - var d, t, m, r = r || (this && this.httpResponse); - if(e || !r){ return s.own.on(id).emit(e) } + var d, t, m; + r = r || (this && this.httpResponse); + if(e || !r){ return s.on(id).emit(e) } r.Text = r.text = t = (r.Body||r.body||'').toString('utf8'); r.Type = r.type = r.ContentType || (r.headers||{})['content-type']; if(r.type && 'json' === s.mime.extension(r.type)){ d = a.obj.ify(t); } m = r.Metadata; - s.own.on(id).emit(e, d, t, m, r); // Warning about the r parameter, is is the raw response and may result in stupid SAX errors. + s.on(id).emit(e, d, t, m, r); // Warning about the r parameter, is is the raw response and may result in stupid SAX errors. }); return s; } diff --git a/gun.js b/gun.js index 4e73c329..753e7437 100644 --- a/gun.js +++ b/gun.js @@ -1,4 +1,4 @@ -;(function(own){ +;(function(){ function Gun(opt){ var gun = this; if(!Gun.is(gun)){ @@ -6,445 +6,569 @@ } gun.opt(opt); } - Gun.is = function(gun){ return (gun instanceof Gun)? true : false } - Gun._ = {}; - Gun.chain = Gun.prototype; - Gun.chain.opt = function(opt, stun){ // idempotently update or set options - var gun = this; - gun._ = gun._ || {}; - gun.__ = gun.__ || {}; - if(!opt){ return gun } - gun.__.opt = gun.__.opt || {}; - gun.__.keys = gun.__.keys || {}; - gun.__.nodes = gun.__.nodes || {}; - if(Gun.text.is(opt)){ opt = {peers: opt} } - if(Gun.list.is(opt)){ opt = {peers: opt} } - if(Gun.text.is(opt.peers)){ opt.peers = [opt.peers] } - if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) } - gun.__.opt.peers = opt.peers || gun.__.opt.peers || {}; - gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {}; - gun.__.opt.hooks = gun.__.opt.hooks || {}; - Gun.obj.map(opt.hooks, function(h, f){ - if(!Gun.fns.is(h)){ return } - gun.__.opt.hooks[f] = h; - }); - if(!stun){ Gun.on('opt').emit(gun, opt) } - return gun; + Gun._ = { + soul: '#' + ,meta: '_' + ,HAM: '>' } - Gun.chain.chain = function(from){ - var gun = Gun(); - from = from || this; - gun.__ = from.__; - gun._ = {}; - Gun.obj.map(from._, function(val, field){ - gun._[field] = val; - }); - Gun.chain.chain.events(gun); - return gun; - } - Gun.chain.chain.events = function(gun){ - gun._.events = Gun.on.split(); // we want events per chain - gun._.events.trace = gun._.events.trace || 0; - gun._.events.at = gun._.events.at || 0; - } - Gun.chain.load = function(key, cb, opt){ - var gun = this; // this.chain(); - Gun.chain.chain.events(gun); - cb = cb || function(){}; - if(cb.node = gun.__.keys[key]){ // set this to the current node, too! - Gun.log("from gun"); // remember to do all the same stack stuff here also! - return cb(Gun.obj.copy(gun._.node = cb.node)), gun; // TODO: BUG: This needs to be frozen/copied, and react the same as below! - } - cb.fn = function(){} - gun._.key = key; - // missing: hear shots! - if(Gun.fns.is(gun.__.opt.hooks.load)){ - gun.__.opt.hooks.load(key, function(err, data){ - gun._.loaded = (gun._.loaded || 0) + 1; // TODO: loading should be idempotent even if we got an err or no data - if(err){ return (gun._.dud||cb.fn)(err) } - if(!data){ return (gun._.blank||cb.fn)() } - var context = {nodes: {}}; - context.nodes[data._[own.sym.id]] = data; - context = Gun.chain.set.now.union.call(gun, context.nodes); // data safely transformed - if(context.err){ return (gun._.dud||cb.fn)(context.err) } - gun._.node = gun.__.keys[key] = gun.__.nodes[data._[own.sym.id]]; - //console.log("compare", gun._, gun.__); - cb(Gun.obj.copy(gun._.node)); - gun._.events.on(gun._.events.at += 1).emit(gun._.node); - gun._.events.at = 0; // ???? reset it back once everything is done? the returns above don't allow for this. - }, opt); - } else { - Gun.log("Warning! You have no persistence layer to load from!"); - } - return gun; - } - Gun.chain.key = function(key, cb){ // TODO: Need to setImmediate if not loaded yet? - Gun.log("make key", key); - cb = cb || function(){}; - this.__.keys[key] = this._.node; - if(Gun.fns.is(this.__.opt.hooks.key)){ - this.__.opt.hooks.key(key, this._.node, function(err, data){ - Gun.log("key made", key); - if(err){ return cb(err) } - return cb(null); - }); - } else { - Gun.log("Warning! You have no key hook!"); - } - return this; - } - Gun.chain.path = function(path){ // The focal point follows the path - var gun = this.chain(); - path = (path||'').split('.'); - Gun.log("PATH stack trace", path, gun._.events.trace + 1, 'was it before loaded?', this._); - gun._.events.on(gun._.events.trace += 1).event(function trace(node){ - Gun.log("stack at", gun._.events.at); - if(!path.length){ // if the path resolves to another node, we finish here - Gun.log("PATH resolved with node"); - gun._.events.on(gun._.events.at += 1).emit(node); - return; - } - var field = path.shift() - , val = node[field]; - gun._.field = field; - if(field = Gun.ify.is.id(val)){ // we might end on a link, so we must resolve - gun._.events.at -= 1; // take a step back because we need to be the next step again - gun.load(field, trace, {id: true}).blank(function(){ }).dud(function(){ }); // TODO: Need to map these to the real blank/dud - } else { - if(path.length){ // we cannot go any further, despite the fact there is more path, which means the thing we wanted does not exist - Gun.log("PATH failed to resolve"); - gun._.events.on(gun._.events.at += 1).emit(); - } else { // we are done, and this should be the value we wanted. - Gun.log("PATH resolved", node, val); - gun._.events.on(gun._.events.at += 1).emit(val); + ;(function(Gun){ + Gun.is = function(gun){ return (gun instanceof Gun)? true : false } + Gun.union = function(graph, prime){ + var context = Gun.shot(); + context.nodes = {}; + context('done');context('change'); + Gun.obj.map(prime, function(node, soul){ + var vertex = graph[soul], env; + if(!vertex){ // disjoint + context.nodes[node._[Gun._.soul]] = graph[node._[Gun._.soul]] = node; + context('change').fire(node); + return; } - } - }); - if(this._.loaded){ // this was the previous chain, gun is the new one - Gun.log("Send off!", gun._.events.at + 1, path); - gun._.events.on(gun._.events.at += 1).emit(this._.node); - } - return gun; - } - Gun.chain.get = function(cb){ - var gun = this; - gun._.events.on(gun._.events.trace += 1).event(function(node){ - if(gun._.field){ - return cb.call(gun, (node||{})[gun._.field]); // copy data first? - } - cb.call(gun, Gun.obj.copy(node)); // we do here. - }); - if(gun._.loaded){ - gun._.events.at -= 1; // IDK why we are doing this, just trying to get something to work. - Gun.log("GET stack trace", gun._.events.trace, gun._.events.at, gun); - gun._.events.on(gun._.events.at += 1).emit(this._.node); - } - return this; - } - /* - ACID compliant, unfortunately the vocabulary is vague, as such the following is an explicit definition: - A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set. - If you want sets to be independent of each other, you need to set each piece of the data individually. - C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state. - I - Isolation, the conflict resolution algorithm guarantees idempotent transactions, across every peer, regardless of any partition, - including a peer acting by itself or one having been disconnected from the network. - D - Durability, if the acknowledgement receipt is received, then the state at which the final persistence hook was called on is guaranteed to have been written. - The live state at point of confirmation may or may not be different than when it was called. - If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled. - */ - Gun.chain.set = function(val, cb, opt){ // TODO: set failed miserably to catch depth references in social tests - opt = opt || {}; - var gun = this, set; - if(gun._.field){ // a field cannot be 0! - set = {}; // in case we are doing a set on a field, not on a node - set[gun._.field] = val; // we create a blank node with the field/value to be set - set._ = Gun.ify.id.call(gun, {}, gun._.node); // and then set their ids to be the same - val = set; // that way they will merge correctly for us during the union! - } - cb = Gun.fns.is(cb)? cb : function(){}; - set = Gun.ify.call(gun, val); - cb.root = set.root; - if(set.err){ return cb(set.err), gun } - set = gun.set.now(set.nodes, Gun.time.is()); // set time state on nodes? - if(set.err){ return cb(set.err), gun } - Gun.union(gun.__.nodes, set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta - gun._.node = gun.__.nodes[cb.root._[own.sym.id]] || cb.root; // TODO? Maybe BUG! if val is a new node on a field, _.node should now be that! Or will that happen automatically? - if(Gun.fns.is(gun.__.opt.hooks.set)){ - gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved - //Gun.log("gun set hook callback called"); - if(err){ return cb(err) } - return cb(null); + env = Gun.HAM(vertex, node, function(current, field, deltaValue){ + if(!current){ return } + var change = {}; + current[field] = change[field] = deltaValue; // current and vertex are the same + current._[Gun._.HAM][field] = node._[Gun._.HAM][field]; + change._ = current._; + context.nodes[change._[Gun._.soul]] = change; + context('change').fire(change); + }).upper(function(c){ + context.err = c.err; + context.up -= 1; + if(!context.up){ + context('done').fire(context.err, context); + } + }); + context.up += env.up; }); - } else { - Gun.log("Warning! You have no persistence layer to save to!"); - } - return gun; - } - Gun.chain.set.now = function(nodes, now){ - var context = {}; - context.nodes = nodes; - context.now = now = (now === 0)? now : now || Gun.time.is(); - Gun.obj.map(context.nodes, function(node, id){ - if(!node || !id || !node._ || !node._[own.sym.id] || node._[own.sym.id] !== id){ - return context.err = {err: "There is a corruption of nodes and or their ids", corrupt: true}; + if(!context.up){ + context('done').fire(context.err, context); } - var states = node._[own.sym.HAM] = node._[own.sym.HAM] || {}; - Gun.obj.map(node, function(val, field){ - if(field == own.sym.meta){ return } - val = states[field]; - states[field] = (val === 0)? val : val || now; - }); - }); - return context; - } - Gun.chain.set.now.union = function(prime){ - var gun = Gun.is(this)? this : null - , context = {nodes: {}}; - if(!gun){ - context.err = {err: "No gun instance!", corrupt: true}; return context; } - Gun.obj.map(prime, function(node){ - var set = Gun.ify.call(gun, node); - if(set.err){ return context.err = set.err } - Gun.obj.map(set.nodes, function(node, id){ - context.nodes[id] = node; - }); - }); - if(context.err){ return context } - Gun.union(gun.__.nodes, context.nodes); // need to move good primes onto context.nodes; - return context; - } - Gun.chain.match = function(){ // same as path, except using objects - return this; - } - Gun.chain.blank = function(blank){ - this._.blank = Gun.fns.is(blank)? blank : function(){}; - return this; - } - Gun.chain.dud = function(dud){ - this._.dud = Gun.fns.is(dud)? dud : function(){}; - return this; - } - Gun.fns = {}; - Gun.fns.is = function(fn){ return (fn instanceof Function)? true : false } - Gun.bi = {}; - Gun.bi.is = function(b){ return (b instanceof Boolean || typeof b == 'boolean')? true : false } - Gun.num = {}; - Gun.num.is = function(n){ - return ((n===0)? true : (!isNaN(n) && !Gun.bi.is(n) && !Gun.list.is(n) && !Gun.text.is(n))? true : false ); - } - Gun.text = {}; - Gun.text.is = function(t){ return typeof t == 'string'? true : false } - Gun.text.ify = function(t){ - if(Gun.text.is(t)){ return t } - if(JSON){ return JSON.stringify(t) } - return (t && t.toString)? t.toString() : t; - } - Gun.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 || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghiklmnopqrstuvwxyz'; - while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } - return s; - } - Gun.list = {}; - Gun.list.is = function(l){ return (l instanceof Array)? true : false } - Gun.list.slit = Array.prototype.slice; - Gun.list.sort = function(k){ // create a new sort function - return function(A,B){ - if(!A || !B){ return 0 } A = A[k]; B = B[k]; - if(A < B){ return -1 }else if(A > B){ return 1 } - else { return 0 } - } - } - Gun.list.map = function(l, c, _){ return Gun.obj.map(l, c, _) } - Gun.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation - Gun.obj = {}; - Gun.obj.is = function(o){ return (o instanceof Object && !Gun.list.is(o) && !Gun.fns.is(o))? true : false } - Gun.obj.del = function(o, k){ - if(!o){ return } - o[k] = null; - delete o[k]; - return true; - } - Gun.obj.ify = function(o){ - if(Gun.obj.is(o)){ return o } - try{o = JSON.parse(o); - }catch(e){o={}}; - return o; - } - Gun.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! - } - Gun.obj.has = function(o, t){ return Object.prototype.hasOwnProperty.call(o, t) } - Gun.obj.map = function(l, c, _){ - var u, i = 0, ii = 0, x, r, rr, f = Gun.fns.is(c), - t = function(k,v){ - if(v !== u){ - rr = rr || {}; - rr[k] = v; - return; - } rr = rr || []; - rr.push(k); - }; - if(Gun.list.is(l)){ - x = l.length; - for(;i < x; i++){ - ii = (i + Gun.list.index); - if(f){ - r = _? c.call(_, l[i], ii, t) : c(l[i], ii, t); - if(r !== u){ return r } - } else { - //if(gun.test.is(c,l[i])){ return ii } // should implement deep equality testing! - if(c === l[i]){ return ii } // use this for now + Gun.HAM = function(current, delta, each){ // HAM only handles primitives values, all other data structures need to be built ontop and reduce to HAM. + function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ // TODO: Lester's comments on roll backs could be vulnerable to divergence, investigate! + if(machineState < incomingState){ + // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. + return {amnesiaQuarantine: true}; } - } - } else { - for(i in l){ - if(f){ - if(Gun.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 } + if(incomingState < currentState){ + // the incoming value is within the boundary of the machine's state, but not within the range. + return {quarantineState: true}; } - } - } - return f? rr : Gun.list.index? 0 : -1; - } - Gun.time = {}; - Gun.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } - Gun.on = (function(){ - function On(on){ - var e = On.is(this)? this : events; - return e._ = e._ || {}, e._.on = Gun.text.ify(on), e; - } - On.is = function(on){ return (on instanceof On)? true : false } - On.split = function(){ return new On() } - On.sort = Gun.list.sort('i'); - On.echo = On.prototype; - On.echo.on = On; - On.echo.emit = function(what){ - var on = this._.on; - if(!on){ return } - this._.events = this._.events || {}; - var e = this._.events[on] = this._.events[on] || (this._.events[on] = []) - , args = arguments; - if(!(this._.events[on] = Gun.list.map(e, function(hear, i, map){ - if(!hear.as){ return } - map(hear); - hear.as.apply(hear, args); - }))){ Gun.obj.del(this._.events,on) } - } - On.echo.event = function(as, i){ - var on = this._.on, e; - if(!on || !as){ return } - this._.events = this._.events || {}; - on = this._.events[on] = this._.events[on] || (this._.events[on] = []); - e = {as: as, i: i || 0, off: function(){ return !(e.as = false) }}; - return on.push(e), on.sort(On.sort), e; - } - On.echo.once = function(as, i){ - var on = this._.on, once = function(){ - this.off(); - as.apply(this, arguments); - } - return this.event(once, i); - } - var events = On.split(); - return On; - }()); - Gun.roulette = function(l, c){ - var gun = Gun.is(this)? this : {}; - if(gun._ && gun.__.opt && gun.__.opt.uuid){ - if(Gun.fns.is(gun.__.opt.uuid)){ - return gun.__.opt.uuid(l, c); - } - l = l || gun.__.opt.uuid.length; - } - return Gun.text.random(l, c); - } - Gun.union = function(graph, prime){ - var context = { nodes: {}}; - Gun.obj.map(prime, function(node, id){ - var vertex = graph[id]; - if(!vertex){ // disjoint - context.nodes[node._[own.sym.id]] = graph[node._[own.sym.id]] = node; - return; - } - Gun.HAM(vertex, node, function(current, field, deltaValue){ // partial - vertex[field] = deltaValue; // vertex and current are the same - vertex._[own.sym.HAM][field] = node._[own.sym.HAM][field]; - }); - }); - } - Gun.HAM = function(current, delta, some){ // TODO: BUG! HAM on sub-graphs has not yet been put into code, thus divergences could occur - this is alpha! - function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ // TODO: Lester's comments on roll backs could be vulnerable to divergence, investigate! - if(machineState < incomingState){ - // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. - return {amnesiaQuarantine: true}; - } - if(incomingState < currentState){ - // the incoming value is within the boundary of the machine's state, but not within the range. - return {quarantineState: true}; - } - if(currentState < incomingState){ - // the incoming value is within both the boundary and the range of the machine's state. - return {converge: true, incoming: true}; - } - if(incomingState === currentState){ - if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different - return {state: true}; - } - /* - The following is a naive implementation, but will always work. - Never change it unless you have specific needs that absolutely require it. - If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. - As a result, it is highly discouraged to modify despite the fact that it is naive, - because convergence (data integrity) is generally more important. - Any difference in this algorithm must be given a new and different name. - */ - if(String(incomingValue) < String(currentValue)){ // String only works on primitive values! - return {converge: true, current: true}; - } - if(String(currentValue) < String(incomingValue)){ // String only works on primitive values! + if(currentState < incomingState){ + // the incoming value is within both the boundary and the range of the machine's state. return {converge: true, incoming: true}; } + if(incomingState === currentState){ + if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different + return {state: true}; + } + /* + The following is a naive implementation, but will always work. + Never change it unless you have specific needs that absolutely require it. + If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. + As a result, it is highly discouraged to modify despite the fact that it is naive, + because convergence (data integrity) is generally more important. + Any difference in this algorithm must be given a new and different name. + */ + if(String(incomingValue) < String(currentValue)){ // String only works on primitive values! + return {converge: true, current: true}; + } + if(String(currentValue) < String(incomingValue)){ // String only works on primitive values! + return {converge: true, incoming: true}; + } + } + return {err: "you have not properly handled recursion through your data or filtered it as JSON"}; } - return {err: "you have not properly handled recursion through your data or filtered it as JSON"}; + var context = Gun.shot(); + context.HAM = {}; + context.states = {}; + context.states.delta = delta._[Gun._.HAM]; + context.states.current = current._[Gun._.HAM] = current._[Gun._.HAM] || {}; + context('lower');context('upper');context.up = context.up || 0; + Gun.obj.map(delta, function update(deltaValue, field){ + if(field === Gun._.meta){ return } + if(!Gun.obj.has(current, field)){ // does not need to be applied through HAM + each.call({incoming: true, converge: true}, current, field, deltaValue); + return; + } + var serverState = Gun.time.is(); + // add more checks? + var state = HAM(serverState, context.states.delta[field], context.states.current[field], deltaValue, current[field]); + //console.log("HAM:", field, deltaValue, context.states.delta[field], context.states.current[field], 'the', state, (context.states.delta[field] - serverState)); + if(state.err){ + Gun.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err); + return; + } + if(state.state || state.quarantineState || state.current){ + context('lower').fire(context, state, current, field, deltaValue); + return; + } + if(state.incoming){ + each.call(state, current, field, deltaValue); + return; + } + if(state.amnesiaQuarantine){ + context.up += 1; + Gun.schedule(context.states.delta[field], function(){ + update(deltaValue, field); + context.up -= 1; + context('upper').fire(context, state, current, field, deltaValue); + }); + } + }); + if(!context.up){ + context('upper').fire(context, {}); + } + return context; } - var states = current._[own.sym.HAM] = current._[own.sym.HAM] || {} // TODO: need to cover the state of the node itself, not just the fields? - , deltaStates = delta._[own.sym.HAM]; - Gun.obj.map(delta, function update(deltaValue, field){ - if(field === Gun.sym.meta){ return } - if(!Gun.obj.has(current, field)){ - some(current, field, deltaValue); - return; + Gun.roulette = function(l, c){ + var gun = Gun.is(this)? this : {}; + if(gun._ && gun.__.opt && gun.__.opt.uuid){ + if(Gun.fns.is(gun.__.opt.uuid)){ + return gun.__.opt.uuid(l, c); + } + l = l || gun.__.opt.uuid.length; } - var serverState = Gun.time.is(); - // add more checks? - var state = HAM(serverState, deltaStates[field], states[field], deltaValue, current[field]); - //console.log("HAM:", field, deltaValue, deltaStates[field], current[field], 'the', state, (deltaStates[field] - serverState)); - if(state.err){ - Gun.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err); - return; + return Gun.text.random(l, c); + } + Gun.log = function(a, b, c, d, e, f){ + //console.log(a, b, c, d, e, f); + //console.log.apply(console, arguments); + } + }(Gun)); + ;(function(Chain){ + Chain.opt = function(opt, stun){ // idempotently update or set options + var gun = this; + gun._ = gun._ || {}; + gun.__ = gun.__ || {}; + gun.shot = Gun.shot(); + gun.shot('then'); + gun.shot('err'); + if(!opt){ return gun } + gun.__.opt = gun.__.opt || {}; + gun.__.keys = gun.__.keys || {}; + gun.__.graph = gun.__.graph || {}; + gun.__.on = gun.__.on || Gun.on.create(); + if(Gun.text.is(opt)){ opt = {peers: opt} } + if(Gun.list.is(opt)){ opt = {peers: opt} } + if(Gun.text.is(opt.peers)){ opt.peers = [opt.peers] } + if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) } + gun.__.opt.peers = opt.peers || gun.__.opt.peers || {}; + gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {}; + gun.__.opt.hooks = gun.__.opt.hooks || {}; + Gun.obj.map(opt.hooks, function(h, f){ + if(!Gun.fns.is(h)){ return } + gun.__.opt.hooks[f] = h; + }); + if(!stun){ Gun.on('opt').emit(gun, opt) } + return gun; + } + Chain.chain = function(from){ + var gun = Gun(); + from = from || this; + gun.back = from; + gun.__ = from.__; + gun._ = {}; + Gun.obj.map(from._, function(val, field){ + gun._[field] = val; + }); + return gun; + } + Chain.load = function(key, cb, opt){ + var gun = this.chain(); + gun.shot.then(cb = cb || function(){}); + cb.soul = (key||{})[Gun._.soul]; + if(cb.soul){ + cb.node = gun.__.graph[cb.soul]; + } else { + gun._.key = key; + cb.node = gun.__.keys[key]; } - if(state.state || state.quarantineState || state.current){ return } - if(state.incoming){ - some(current, field, deltaValue); - return; + if(cb.node){ // set this to the current node, too! + Gun.log("from gun"); // remember to do all the same stack stuff here also! + var freeze = Gun.obj.copy(gun._.node = cb.node); + gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now. + return gun; // TODO: BUG: This needs to react the same as below! } - if(state.amnesiaQuarantine){ - Gun.schedule(deltaStates[field], function(){ - update(deltaValue, field); + cb.fn = function(){} + // missing: hear shots! + if(Gun.fns.is(gun.__.opt.hooks.load)){ + gun.__.opt.hooks.load(key, function(err, data){ + gun._.loaded = (gun._.loaded || 0) + 1; // TODO: loading should be idempotent even if we got an err or no data + if(err){ return (gun._.dud||cb.fn)(err) } + if(!data){ return (gun._.blank||cb.fn)() } + var context = gun.union(data); // safely transform the data + if(context.err){ return (gun._.dud||cb.fn)(context.err) } + gun._.node = gun.__.graph[data._[Gun._.soul]]; // don't wait for the union to be done because we want the immediate state not the intended state. + if(!cb.soul){ gun.__.keys[key] = gun._.node } + var freeze = Gun.obj.copy(gun._.node); + gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now. + }, opt); + } else { + Gun.log("Warning! You have no persistence layer to load from!"); + } + return gun; + } + Chain.key = function(key, cb){ + var gun = this; + gun.shot.then(function(){ + Gun.log("make key", key); + cb = cb || function(){}; + cb.node = gun.__.keys[key] = gun._.node; + if(!cb.node){ return gun } + if(Gun.fns.is(gun.__.opt.hooks.key)){ + gun.__.opt.hooks.key(key, cb.node._[Gun._.soul], function(err, data){ + Gun.log("key made", key); + if(err){ return cb(err) } + return cb(null); + }); + } else { + Gun.log("Warning! You have no key hook!"); + } + }); + if(!gun.back){ gun.shot('then').fire() } + return gun; + } + /* + how many different ways can we get something? + Find via a singular path + .path('blah').get(blah); + Find via multiple paths with the callback getting called many times + .path('foo', 'bar').get(foorOrBar); + Find via multiple paths with the callback getting called once with matching arguments + .path('foo', 'bar').get(foo, bar) + Find via multiple paths with the result aggregated into an object of pre-given fields + .path('foo', 'bar').get({foo: foo, bar: bar}) || .path({a: 'foo', b: 'bar'}).get({a: foo, b: bar}) + Find via multiple paths where the fields and values must match + .path({foo: val, bar: val}).get({}) + */ + Chain.path = function(path){ // The focal point follows the path + var gun = this.chain(); + path = (path || '').split('.'); + gun.back.shot.then(function trace(node){ // should handle blank and err! Err already handled? + //console.log("shot path", path, node); + gun.field = null; + gun._.node = gun.back._.node; + if(!path.length){ // if the path resolves to another node, we finish here + return gun.shot('then').fire(node); // already frozen from loaded. + } + var field = path.shift() + , val = node[field]; + gun.field = field; + if(Gun.ify.is.soul(val)){ // we might end on a link, so we must resolve + return gun.load(val).shot.then(trace); + } else + if(path.length){ // we cannot go any further, despite the fact there is more path, which means the thing we wanted does not exist + gun.shot('then').fire(); + } else { // we are done, and this should be the value we wanted. + gun.shot('then').fire(val); // primitive values are passed as copies in JS. + } + }); + // if(!gun.back){ gun.shot('then').fire() } // replace below with this? maybe??? + if(gun.back && gun.back._ && gun.back._.loaded){ + gun._.node = gun.back._.node; + gun.back.shot('then').fire(gun.back._.node); + } + return gun; + } + Chain.get = function(cb){ + var gun = this; + gun.shot.then(function(val){ + cb.call(gun, val); // frozen from done. + gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){ + if(!delta){ return } + if(!gun.field){ + cb.call(gun, Gun.obj.copy(gun._.node)); + return; + } + if(Gun.obj.has(delta, gun.field)){ + cb.call(gun, delta[gun.field]); + } + }) + }); + return gun; + } + /* + ACID compliant, unfortunately the vocabulary is vague, as such the following is an explicit definition: + A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set. + If you want sets to be independent of each other, you need to set each piece of the data individually. + C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state. + I - Isolation, the conflict resolution algorithm guarantees idempotent transactions, across every peer, regardless of any partition, + including a peer acting by itself or one having been disconnected from the network. + D - Durability, if the acknowledgement receipt is received, then the state at which the final persistence hook was called on is guaranteed to have been written. + The live state at point of confirmation may or may not be different than when it was called. + If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled. + */ + Chain.set = function(val, cb, opt){ // TODO: set failed miserably to catch depth references in social tests + opt = opt || {}; + var gun = this, set; + gun.shot.then(function(){ + if(gun.field){ // a field cannot be 0! + set = {}; // in case we are doing a set on a field, not on a node + set[gun.field] = val; // we create a blank node with the field/value to be set + set._ = Gun.ify.soul.call(gun, {}, gun._.node); // and then set their souls to be the same + val = set; // that way they will merge correctly for us during the union! + } + cb = Gun.fns.is(cb)? cb : function(){}; + set = Gun.ify.call(gun, val); + cb.root = set.root; + if(set.err){ return cb(set.err), gun } + set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes? + if(set.err){ return cb(set.err), gun } + Gun.union(gun.__.graph, set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta + gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root; + // TODO? ^ Maybe BUG! if val is a new node on a field, _.node should now be that! Or will that happen automatically? + if(Gun.fns.is(gun.__.opt.hooks.set)){ + gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved + //Gun.log("gun set hook callback called"); + if(err){ return cb(err) } + return cb(null); + }); + } else { + Gun.log("Warning! You have no persistence layer to save to!"); + } + }); + if(!gun.back){ gun.shot('then').fire() } + return gun; + } + Chain.union = function(prime, cb){ + var tmp, gun = this, context = Gun.shot(); + context.nodes = {}; + cb = cb || function(){} + if(!prime){ + context.err = {err: "No data to merge!"}; + } else + if(prime._ && prime._[Gun._.soul]){ + tmp = {}; + tmp[prime._[Gun._.soul]] = prime; + prime = tmp; + } + if(!gun || context.err){ + cb(context.err = context.err || {err: "No gun instance!", corrupt: true}, context); + return context; + } + Gun.obj.map(prime, function(node){ // map over the prime graph, to get each node that has been modified + var set = Gun.ify.call(gun, node); + if(set.err){ return context.err = set.err } // check to see if the node is valid + Gun.obj.map(set.nodes, function(node, soul){ // if so, map over it, and any other nodes that were deserialized from it + context.nodes[soul] = node; // into a valid context we'll actually do a union on. }); + }); + if(context.err){ return cb(context.err, context), context } // if any errors happened in the previous steps, then fail. + Gun.union(gun.__.graph, context.nodes).done(function(err, env){ // now merge prime into the graph + context.err = err || env.err; + cb(context.err, context || {}); + }).change(function(delta){ + if(!delta || !delta._ || !delta._[Gun._.soul]){ return } + gun.__.on(delta._[Gun._.soul]).emit(Gun.obj.copy(delta)); // this is in reaction to HAM + }); + return context; + } + Chain.match = function(){ // same as path, except using objects + return this; + } + Chain.blank = function(blank){ + this._.blank = Gun.fns.is(blank)? blank : function(){}; + return this; + } + Chain.dud = function(dud){ + this._.dud = Gun.fns.is(dud)? dud : function(){}; + return this; + } + }(Gun.chain = Gun.prototype)); + ;(function(Util){ + Util.fns = {}; + Util.fns.is = function(fn){ return (fn instanceof Function)? true : false } + Util.bi = {}; + Util.bi.is = function(b){ return (b instanceof Boolean || typeof b == 'boolean')? true : false } + Util.num = {}; + Util.num.is = function(n){ + return ((n===0)? true : (!isNaN(n) && !Util.bi.is(n) && !Util.list.is(n) && !Util.text.is(n))? true : false ); + } + Util.text = {}; + Util.text.is = function(t){ return typeof t == 'string'? true : false } + Util.text.ify = function(t){ + if(Util.text.is(t)){ return t } + if(JSON){ return JSON.stringify(t) } + return (t && t.toString)? t.toString() : t; + } + Util.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 || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghiklmnopqrstuvwxyz'; + while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } + return s; + } + Util.list = {}; + Util.list.is = function(l){ return (l instanceof Array)? true : false } + Util.list.slit = Array.prototype.slice; + Util.list.sort = function(k){ // creates a new sort function based off some field + return function(A,B){ + if(!A || !B){ return 0 } A = A[k]; B = B[k]; + if(A < B){ return -1 }else if(A > B){ return 1 } + else { return 0 } } - }); - } - ;(function(schedule){ + } + Util.list.map = function(l, c, _){ return Util.obj.map(l, c, _) } + Util.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation + Util.obj = {}; + Util.obj.is = function(o){ return (o instanceof Object && !Util.list.is(o) && !Util.fns.is(o))? true : false } + Util.obj.del = function(o, k){ + if(!o){ return } + o[k] = null; + delete o[k]; + return true; + } + Util.obj.ify = function(o){ + if(Util.obj.is(o)){ return o } + try{o = JSON.parse(o); + }catch(e){o={}}; + return o; + } + Util.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! + } + Util.obj.has = function(o, t){ return Object.prototype.hasOwnProperty.call(o, t) } + Util.obj.map = function(l, c, _){ + var u, i = 0, ii = 0, x, r, rr, f = Util.fns.is(c), + t = function(k,v){ + if(v !== u){ + rr = rr || {}; + rr[k] = v; + return; + } rr = rr || []; + rr.push(k); + }; + if(Util.list.is(l)){ + x = l.length; + for(;i < x; i++){ + ii = (i + Util.list.index); + if(f){ + r = _? c.call(_, l[i], ii, t) : c(l[i], ii, t); + if(r !== u){ return r } + } else { + //if(Util.test.is(c,l[i])){ return ii } // should implement deep equality testing! + if(c === l[i]){ return ii } // use this for now + } + } + } else { + for(i in l){ + if(f){ + if(Util.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 } + } + } + } + return f? rr : Util.list.index? 0 : -1; + } + Util.time = {}; + Util.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } + }(Gun)); + ;Gun.shot=(function(){ + // I hate the idea of using setTimeouts in my code to do callbacks (promises and sorts) + // as there is no way to guarantee any type of state integrity or the completion of callback. + // However, I have fallen. HAM is suppose to assure side effect free safety of unknown states. + var setImmediate = setImmediate || function(cb){setTimeout(cb,0)} + function Flow(){ + var chain = new Flow.chain(); + return chain.$ = function(where){ + (chain._ = chain._ || {})[where] = chain._[where] || []; + chain.$[where] = chain.$[where] || function(fn){ + (chain._[where]||[]).push(fn); + return chain.$; + } + chain.where = where; + return chain; + } + } + Flow.is = function(flow){ return (Flow instanceof flow)? true : false } + ;Flow.chain=(function(){ + function Chain(){ + if(!(this instanceof Chain)){ + return new Chain(); + } + } + Chain.chain = Chain.prototype; + Chain.chain.pipe = function(a,s,d,f){ + var me = this + , where = me.where + , args = Array.prototype.slice.call(arguments); + setImmediate(function(){ + if(!me || !me._ || !me._[where]){ return } + while(0 < me._[where].length){ + (me._[where].shift()||function(){}).apply(me, args); + } + // do a done? That would be nice. :) + }); + return me; + } + return Chain; + }()); + return Flow; + }());Gun.shot.chain.chain.fire=Gun.shot.chain.chain.pipe; + ;Gun.on=(function(){ + function On(where){ + if(where){ + return (On.event = On.event || On.create())(where); + } + return On.create(); + } + On.is = function(on){ return (On instanceof on)? true : false } + On.create = function(){ + var chain = new On.chain(); + return chain.$ = function(where){ + chain.where = where; + return chain; + } + } + On.sort = Gun.list.sort('i'); + ;On.chain=(function(){ + function Chain(){ + if(!(this instanceof Chain)){ + return new Chain(); + } + } + Chain.chain = Chain.prototype; + Chain.chain.emit = function(what){ + var me = this + , where = me.where + , args = arguments + , on = (me._ = me._ || {})[where] = me._[where] || []; + if(!(me._[where] = Gun.list.map(on, function(hear, i, map){ + if(!hear || !hear.as){ return } + map(hear); + hear.as.apply(hear, args); + }))){ Gun.obj.del(on, where) } + } + Chain.chain.event = function(as, i){ + if(!as){ return } + var me = this + , where = me.where + , args = arguments + , on = (me._ = me._ || {})[where] = me._[where] || [] + , e = {as: as, i: i || 0, off: function(){ return !(e.as = false) }}; + return on.push(e), on.sort(On.sort), e; + } + Chain.chain.once = function(as, i){ + var me = this + , once = function(){ + this.off(); + as.apply(this, arguments) + } + return me.event(once, i) + } + return Chain; + }()); + return On; + }()); + ;(function(schedule){ // maybe use lru-cache schedule.waiting = []; schedule.soonest = Infinity; schedule.sort = Gun.list.sort('when'); @@ -507,20 +631,20 @@ context.err = err; return; } - symbol = Gun.ify.id.call(gun, symbol, seen); + symbol = Gun.ify.soul.call(gun, symbol, seen); return symbol; } else { //Gun.log("seen nowhere", sub._, sub.path, data); if(sub._){ context.seen.push({data: data, node: value}); } else { - value._ = Gun.ify.id.call(gun, {}, data); + value._ = Gun.ify.soul.call(gun, {}, data); context.seen.push({data: data, node: value}); - context.nodes[value._[own.sym.id]] = value; + context.nodes[value._[Gun._.soul]] = value; } } Gun.obj.map(data, function(val, field){ - var subs = {path: sub.path + field + '.', _: sub._ || (field == own.sym.meta)? true : false }; + var subs = {path: sub.path + field + '.', _: sub._ || (field == Gun._.meta)? true : false }; val = ify(val, context, subs); //Gun.log('>>>>', sub.path + field, 'is', val); if(context.err){ return true } @@ -529,8 +653,8 @@ value[field] = val; }); if(sub._){ return value } - if(!value._ || !value._[own.sym.id]){ return } - symbol[own.sym.id] = value._[own.sym.id]; + if(!value._ || !value._[Gun._.soul]){ return } + symbol[Gun._.soul] = value._[Gun._.soul]; return symbol; } else if(Gun.list.is(data)){ @@ -543,13 +667,13 @@ context.err = err; return true; } - return Gun.obj.map(val, function(id, field){ - if(field !== own.sym.id){ + return Gun.obj.map(val, function(soul, field){ + if(field !== Gun._.soul){ context.err = err; return true; } - if(unique[id]){ return } - unique[id] = 1; + if(unique[soul]){ return } + unique[soul] = 1; map(val); }); }); @@ -568,42 +692,59 @@ ify(data, context); return context; } - Gun.ify.id = function(to, from){ + Gun.ify.state = function(nodes, now){ + var context = {}; + context.nodes = nodes; + context.now = now = (now === 0)? now : now || Gun.time.is(); + Gun.obj.map(context.nodes, function(node, soul){ + if(!node || !soul || !node._ || !node._[Gun._.soul] || node._[Gun._.soul] !== soul){ + return context.err = {err: "There is a corruption of nodes and or their souls", corrupt: true}; + } + var states = node._[Gun._.HAM] = node._[Gun._.HAM] || {}; + Gun.obj.map(node, function(val, field){ + if(field == Gun._.meta){ return } + val = states[field]; + states[field] = (val === 0)? val : val || now; + }); + }); + return context; + } + Gun.ify.soul = function(to, from){ var gun = this; to = to || {}; - if(Gun.ify.id.is(from)){ - to[own.sym.id] = from._[own.sym.id]; + if(Gun.ify.soul.is(from)){ + to[Gun._.soul] = from._[Gun._.soul]; return to; } - to[own.sym.id] = Gun.roulette.call(gun); + to[Gun._.soul] = Gun.roulette.call(gun); return to; } - Gun.ify.id.is = function(o){ - if(o && o._ && o._[own.sym.id]){ + Gun.ify.soul.is = function(o){ + if(o && o._ && o._[Gun._.soul]){ return true; } } - Gun.ify.is = function(v){ // null, binary, number (!Infinity), text, or a ref. + Gun.ify.is = function(v){ // null, binary, number (!Infinity), text, or a rel. if(v === null){ return true } // deletes - if(v === Infinity){ return false } + if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(Gun.bi.is(v) || Gun.num.is(v) || Gun.text.is(v)){ return true; // simple values } var yes; - if(yes = Gun.ify.is.id(v)){ + if(yes = Gun.ify.is.soul(v)){ return yes; } return false; } - Gun.ify.is.id = function(v){ + Gun.ify.is.soul = function(v){ if(Gun.obj.is(v)){ var yes; - Gun.obj.map(v, function(id, field){ + Gun.obj.map(v, function(soul, field){ if(yes){ return yes = false } - if(field === own.sym.id && Gun.text.is(id)){ - yes = id; + if(field === Gun._.soul && Gun.text.is(soul)){ + yes = soul; } }); if(yes){ @@ -613,15 +754,6 @@ return false; } }()); - Gun.log = function(a, b, c, d, e, f){ //s, l){ - //console.log(a, b, c, d, e, f); - //console.log.apply(console, arguments); - } - own.sym = Gun.sym = { - id: '#' - ,meta: '_' - ,HAM: '>' - } if(typeof window !== "undefined"){ window.Gun = Gun; } else { @@ -645,8 +777,12 @@ tab.store.del = function(key){ return store.removeItem(key) } }()); tab.load = tab.load || function(key, cb, opt){ + if(!key){ return } cb = cb || function(){}; opt = opt || {}; + if(key[Gun._.soul]){ + key = '_' + tab.query(key); + } Gun.obj.map(gun.__.opt.peers, function(peer, url){ tab.ajax(url + '/' + key, null, function(err, reply){ console.log('via', url, key, reply); @@ -662,7 +798,7 @@ //console.log("We are sub", tab.subscribe.sub); var data = reply.body; if(!data || !data._){ return } - tab.subscribe(data._[Gun.sym.id]); + tab.subscribe(data._[Gun._.soul]); }()); }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}, header: {'Gun-Sub': 1}}); }); @@ -672,12 +808,12 @@ console.log("urlify delta", nodes); var s = '' , uri = encodeURIComponent; - Gun.obj.map(nodes, function(delta, id){ + Gun.obj.map(nodes, function(delta, soul){ var ham; - if(!delta || !delta._ || !(ham = delta._[Gun.sym.HAM])){ return } - s += uri('#') + '=' + uri(id) + '&'; + if(!delta || !delta._ || !(ham = delta._[Gun._.HAM])){ return } + s += uri('#') + '=' + uri(soul) + '&'; Gun.obj.map(delta, function(val, field){ - if(field === Gun.sym.meta){ return } + if(field === Gun._.meta){ return } s += uri(field) + '=' + uri(Gun.text.ify(val)) + uri('>') + uri(ham[field]) + '&'; }) }); @@ -717,27 +853,27 @@ tab.store.del(respond.id); }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}}); }); - Gun.obj.map(nodes, function(node, id){ - Gun.on(id).emit(node, true); // TODO: Temporary hack, I want to rebroadcast back to ourselves. IDK if this is always useful, and we shouldn't use global. + Gun.obj.map(nodes, function(node, soul){ + gun.__.on(soul).emit(node, true); // should we emit difference between local and not? }); } tab.set.defer = {}; - tab.subscribe = function(id){ // TODO: BUG!!! ERROR! Handle disconnection (onerror)!!!! + tab.subscribe = function(soul){ // TODO: BUG!!! ERROR! Handle disconnection (onerror)!!!! tab.subscribe.to = tab.subscribe.to || {}; - if(id){ - tab.subscribe.to[id] = 1; + if(soul){ + tab.subscribe.to[soul] = 1; } var opt = { header: {'Gun-Sub': 1}, headers: { 'Gun-Sub': tab.subscribe.sub || '' } - }, query = tab.subscribe.sub? '' : tab.subscribe.query(tab.subscribe.to); + }, query = tab.subscribe.sub? '' : tab.query(tab.subscribe.to); console.log("subscribing poll", tab.subscribe.sub); Gun.obj.map(gun.__.opt.peers, function(peer, url){ tab.ajax(url + query, null, function(err, reply){ if(err || !reply || !reply.body || reply.body.err){ // not interested in any null/0/''/undefined values - console.log(err, reply); + //console.log(err, reply); return; } console.log("poll", 1 || reply); @@ -745,26 +881,8 @@ if(reply.headers){ tab.subscribe.sub = reply.headers['gun-sub'] || tab.subscribe.sub; } - var data = reply.body - , union = function(node){ // maybe we shouldn't have this type of logic, below, in a hook? - // should we pass it off to a gun API? same with everywhere else this shows up then. - if(!node || !node._ || !node._[Gun.sym.id]){ return } // do anything? - var context = {nodes: {}}; - context.nodes[node._[Gun.sym.id]] = node; - context = Gun.chain.set.now.union.call(gun, context.nodes); - if(context.err){ return } // do anything? - Gun.obj.map(context.nodes, function(node, id){ - Gun.on(id).emit(node); // TODO: we shouldn't use Gun's global event namespace like this, change to local - }); - } - if(!data){ return } // do anything? - if(data._){ - union(data); - } else { - Gun.obj.map(data, function(node, id){ - union(node); - }); - } + if(!reply.body){ return } // do anything? + gun.union(reply.body); // safely transform data }, opt); }); } @@ -772,7 +890,7 @@ clearTimeout(tab.subscribe.poll.id); tab.subscribe.poll.id = setTimeout(tab.subscribe, 1); //1000 * 10); // should enable some server-side control of this. } - tab.subscribe.query = function(params){ + tab.query = function(params){ var s = '?' , uri = encodeURIComponent; Gun.obj.map(params, function(val, field){ diff --git a/on.js b/on.js new file mode 100644 index 00000000..e0a58a54 --- /dev/null +++ b/on.js @@ -0,0 +1,46 @@ +;(function(){ + var setImmediate = setImmediate || function(cb){setTimeout(cb,0)} + function On(){ + var chain = new Chain(); + return chain.$ = function(where){ + chain.$[where] = function(fn){ + chain.$[where] = fn; + } + chain.where = where; + return chain; + } + } + On.is = function(On){ return (On instanceof On)? true : false } + function Chain(){ + if(!(this instanceof Chain)){ + return new Chain(); + } + } + Chain.chain = Chain.prototype; + Chain.chain.emit = function(a,s,d,f){ + var me = this + , where = me.where + , args = Array.prototype.slice.call(arguments); + setImmediate(function(){ + if(!me || !me.$ || !me.$[where]){ return } + me.$[where].apply(me, args); + }); + return me; + } + if(typeof window !== "undefined"){ + window.On = On; + } else { + module.exports = On; + } + + ;(function(){ // test + var doSomething = function(){ + var cb = On(); + cb('now').emit(1,2,3); + return cb; + } + doSomething('foo', 'bar').now(function(a,b,c){ + console.log("Oh yeah baby", a,b,c); + }) + }()); +}()); \ No newline at end of file diff --git a/package.json b/package.json index 6749ba6d..f045698d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "name": "gun" -, "version": "0.0.6-e" +, "version": "0.0.7" , "author": "Mark Nadal" , "description": "Graph engine." , "engines": { diff --git a/shots.js b/shots.js index 5bef19bc..1b2cb060 100644 --- a/shots.js +++ b/shots.js @@ -38,10 +38,10 @@ } } meta.CORS(req, res); // add option to disable this - if(reply.chunk){ + if(Gun.obj.has(reply,'chunk')){ res.write(Gun.text.ify(reply.chunk) || ''); } - if(reply.body){ + if(Gun.obj.has(reply,'body')){ res.end(Gun.text.ify(reply.body) || ''); } }); @@ -71,17 +71,18 @@ if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this! s3.load = s3.load || function(key, cb, opt){ + if(!key){ return } cb = cb || function(){}; opt = opt || {}; - if(opt.id){ - key = s3.prefix + s3.prenode + key; + if(key[Gun._.soul]){ + key = s3.prefix + s3.prenode + key[Gun._.soul]; } else { key = s3.prefix + s3.prekey + key; } s3.get(key, function(err, data, text, meta){ console.log('via s3', key, err); - if(meta && (key = meta[Gun.sym.id])){ - return s3.load(key, cb, {id: true}); + if(meta && meta[Gun._.soul]){ + return s3.load(meta, cb); } if(err && err.statusCode == 404){ err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong). @@ -96,16 +97,16 @@ var next = s3.next , ack = Gun.text.random(8) , batch = s3.batch[next] = s3.batch[next] || {}; - s3.event.on(ack).once(cb); - Gun.obj.map(nodes, function(node, id){ + s3.on(ack).once(cb); + Gun.obj.map(nodes, function(node, soul){ cb.count += 1; - batch[id] = (batch[id] || 0) + 1; - //console.log("set listener for", next + ':' + id, batch[id], cb.count); - s3.event.on(next + ':' + id).event(function(){ + batch[soul] = (batch[soul] || 0) + 1; + //console.log("set listener for", next + ':' + soul, batch[soul], cb.count); + s3.on(next + ':' + soul).event(function(){ cb.count -= 1; //console.log("transaction", cb.count); if(!cb.count){ - s3.event.on(ack).emit(); + s3.on(ack).emit(); this.off(); // MEMORY LEAKS EVERYWHERE!!!!!!!!!!!!!!!! FIX THIS!!!!!!!!! } }); @@ -125,39 +126,40 @@ var now = s3.next , batch = s3.batch[s3.next]; s3.next = Gun.time.is(); - Gun.obj.map(batch, function put(exists, id){ - var node = gun.__.nodes[id]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone? - s3.put(s3.prefix + s3.prenode + id, node, function(err, reply){ - console.log("s3 put reply", id, err, reply); + Gun.obj.map(batch, function put(exists, soul){ + var node = gun.__.graph[soul]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone? + s3.put(s3.prefix + s3.prenode + soul, node, function(err, reply){ + console.log("s3 put reply", soul, err, reply); if(err || !reply){ - put(exists, id); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! + put(exists, soul); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! return; } - s3.event.on(now + ':' + id).emit(200); + s3.on(now + ':' + soul).emit(200); }); }); } s3.next = s3.next || Gun.time.is(); - s3.event = s3.event || Gun.on.split(); + s3.on = s3.on || Gun.on.create(); s3.batching = s3.batching || 0; s3.batched = s3.batched || {}; s3.batch = s3.batch || {}; s3.persisted = s3.persisted || {}; s3.wait = s3.wait || null; - s3.key = s3.key || function(key, node, cb){ - var id = node._[Gun.sym.id]; - if(!id){ - return cb({err: "No ID!"}); + s3.key = s3.key || function(key, soul, cb){ + var meta = {}; + meta[Gun._.soul] = soul = Gun.text.is(soul)? soul : (soul||{})[Gun._.soul]; + if(!soul){ + return cb({err: "No soul!"}); } s3.put(s3.prefix + s3.prekey + key, '', function(err, reply){ // key is 2 bytes??? Should be smaller - console.log("s3 put reply", id, err, reply); + console.log("s3 put reply", soul, err, reply); if(err || !reply){ - s3.key(key, node, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! + s3.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! return; } cb(); - }, {Metadata: {'#': id}}); + }, {Metadata: meta}); } gun.server.transport = (function(){ @@ -176,12 +178,13 @@ return tran.load(req, req.tran); // else load the state for the tab! } tran.load = function(req, cb){ - var reply = {}; + var reply = {}, key; reply.headers = {'Content-Type': tran.json}; reply.headers['Gun-Sub'] = req.tab.sub = req.sub; - gun.load(req.url.key, function(node){ - console.log("Loading for", req.tab); - tran.sub.scribe(req.tab, node._[Gun.sym.id]); + key = (Gun._.meta == req.url.key)? req.url.query : req.url.key; + console.log("Loading", req.url.key, 'for', req.tab); + gun.load(key, function(node){ + tran.sub.scribe(req.tab, node._[Gun._.soul]); cb({ headers: reply.headers ,body: node @@ -202,79 +205,84 @@ if(!req.body){ return cb({body: {err: "No body"}}) } // raw test for now, no auth: // should probably load all the nodes first? YES. - var context = Gun.chain.set.now.union.call(gun, req.body); // data safely transformed - //console.log("body?", req.body, context.err); - if(context.err){ return cb({body: {err: context.err}}) } - // WARNING! TODO: BUG! Do not send OK confirmation if amnesiaQuaratine is activated! Not until after it has actually been processed!!! - if(Gun.fns.is(gun.__.opt.hooks.set)){ - gun.__.opt.hooks.set(context.nodes, function saved(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved - var body = {}; - if(err){ - body.err = err ; - } - if(!req.sub){ - if(!err){ - body = defer.map({}, context.nodes, 1); + var context = gun.union(req.body, function(err, context){ // data safely transformed + cb = cb || function(){}; + if(err || context.err){ return cb({body: {err: context.err}}) } + if(Gun.fns.is(gun.__.opt.hooks.set)){ + gun.__.opt.hooks.set(context.nodes, function saved(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved + var body = {}; + if(err){ + body.err = err ; } - return cb({body: body}); - } - var now = tran.post.s[req.sub]; // begin our stupid Chrome fix, we should abstract this out into defer (where it belogns) to keep things clean. - if(!now){ return } // utoh we've lost our reply to the tab! - clearTimeout(now.timeout); - now.body = now.body || {}; // make sure we have a body for our multi-response in a single response. - if(req.wait){ // did this request get deferred? - (now.body.refed = now.body.refed || {})[req.wait] = err? {err: err} : defer.map({}, context.nodes, 1); // then reply to it "here". - } else { - now.body.reply = err? {err: err} : defer.map({}, context.nodes, 1); // else this is the original POST that had to be upgraded. - } - if(0 < (now.count = ((now.count || 0) - 1))){ // Don't reply till all deferred POSTs have successfully heard back from S3. (Sarcasm: Like counting guarantees that) - return now.timeout = setTimeout(saved, gun.__.opt.throttle * 2 * 1000); // reply not guaranteed, so time it out, in seconds. - } - if(Gun.fns.is(now)){ - now({body: now.body}); // FINALLY reply for ALL the POSTs for that session that accumulated. - } else { - // console.log("Error! We deleted our response!"); - } - Gun.obj.del(tran.post.s, req.sub); // clean up our memory. - // need to rewrite that if Stream is enabled that both Stream + State save are guaranteed before replying. - }); - // stuff past this point is just stupid implementation optimizations. - function defer(nodes, req){ // because Chrome can only handle 4 requests at a time, sad face. - if(!req.sub){ - return; - } - var next = tran.post.s[req.sub]; - if(!next){ // was there a previous POST? If not, we become the previous POST. - //cb({chunk: ''}); // because on some services (heroku) you need to reply starting a stream to keep the connection open. - return tran.post.s[req.sub] = cb; - } - next.count = (next.count || 1) + 1; // start counting how many we accumulate - next.body = next.body || {}; // this becomes the polyfill for all the posts - next.body.refed = next.body.refed || {}; // where we refeed the responses for the deferred POSTs. - req.wait = Gun.text.random(); // generate an random id for this deferred POST. - next.body.refed[req.wait] = false; // establish that we are incomplete. - cb({body: {defer: req.wait}}); // end this POST immediately so Chrome only ever uses a couple connections. - cb = null; // null it out so we don't accidentally reply to it once we hear back from S3. - } - defer.map = function(now, nodes, val){ // shortcut for maping which nodes were saved successfully - if(!now){ return } - Gun.obj.map(nodes, function(node, id, map){ - now[id] = val; + if(!req.sub){ + if(!err){ + body = defer.map({}, context.nodes, 1); + } + return cb({body: body}); + } + var now = tran.post.s[req.sub]; // begin our stupid Chrome fix, we should abstract this out into defer (where it belogns) to keep things clean. + if(!now){ return } // utoh we've lost our reply to the tab! + clearTimeout(now.timeout); + now.body = now.body || {}; // make sure we have a body for our multi-response in a single response. + if(req.wait){ // did this request get deferred? + (now.body.refed = now.body.refed || {})[req.wait] = err? {err: err} : defer.map({}, context.nodes, 1); // then reply to it "here". + } else { + now.body.reply = err? {err: err} : defer.map({}, context.nodes, 1); // else this is the original POST that had to be upgraded. + } + if(0 < (now.count = ((now.count || 0) - 1))){ + // Don't reply till all deferred POSTs have successfully heard back from S3. (Sarcasm: Like counting guarantees that) + return now.timeout = setTimeout(saved, gun.__.opt.throttle * 2 * 1000); // reply not guaranteed, so time it out, in seconds. + } + if(Gun.fns.is(now)){ + now({body: now.body}); // FINALLY reply for ALL the POSTs for that session that accumulated. + } else { + // console.log("Error! We deleted our response!"); + } + Gun.obj.del(tran.post.s, req.sub); // clean up our memory. + // need to rewrite that if Stream is enabled that both Stream + State save are guaranteed before replying. }); - return now; + // stuff past this point is just stupid implementation optimizations. + function defer(nodes, req){ // because Chrome can only handle 4 requests at a time, sad face. + if(!req.sub){ + return; + } + var next = tran.post.s[req.sub]; + if(!next){ // was there a previous POST? If not, we become the previous POST. + //cb({chunk: ''}); // because on some services (heroku) you need to reply starting a stream to keep the connection open. + return tran.post.s[req.sub] = cb; + } + next.count = (next.count || 1) + 1; // start counting how many we accumulate + next.body = next.body || {}; // this becomes the polyfill for all the posts + next.body.refed = next.body.refed || {}; // where we refeed the responses for the deferred POSTs. + req.wait = Gun.text.random(); // generate an random id for this deferred POST. + next.body.refed[req.wait] = false; // establish that we are incomplete. + cb({body: {defer: req.wait}}); // end this POST immediately so Chrome only ever uses a couple connections. + cb = null; // null it out so we don't accidentally reply to it once we hear back from S3. + } + defer.map = function(now, nodes, val){ // shortcut for maping which nodes were saved successfully + if(!now){ return } + Gun.obj.map(nodes, function(node, soul, map){ + now[soul] = val; + }); + return now; + } + defer(context.nodes, req); // actually do the weird stuff to make Chrome not be slow + } else { + context.err = "Warning! You have no persistence layer to save to!"; + Gun.log(context.err); + cb({body: {err: "Server has no persistence layer!"}}); } - defer(context.nodes, req); // actually do the weird stuff to make Chrome not be slow - } else { - context.err = "Warning! You have no persistence layer to save to!"; - Gun.log(context.err); - cb({body: {err: "Server has no persistence layer!"}}); + }); + if(context.err){ + cb({body: {err: context.err}}); + return cb = null; } - Gun.obj.map(context.nodes, function(node, id){ // live push the stream out in realtime to every tab subscribed + Gun.obj.map(context.nodes, function(node, soul){ // live push the stream out in realtime to every tab subscribed var msg = {}; msg.headers = req.headers; // polyfill the delta as its own message. msg.body = node; - //console.log("emit delta", id); - tran.push.on(id).emit(msg); + console.log("emit delta", soul); + tran.push(soul).emit(msg); }); } tran.post.s = {}; @@ -282,7 +290,6 @@ //console.log("<-- ", req.sub, req.tran ," -->"); req.tab = tran.sub.s[req.sub]; if(!req.tab){ - console.log(req.url.query); cb({ headers: {'Gun-Sub': ''} ,body: {err: "Please re-initialize sub."} @@ -316,10 +323,10 @@ Gun.obj.del(tran.sub.s, tab.sub) }, gun.__.opt.disconnect * mult * 1000); // in seconds } - tran.sub.scribe = function(tab, id){ + tran.sub.scribe = function(tab, soul){ tran.sub.s[tab.sub] = tab; tab.subs = tab.subs || {}; - tab.subs[id] = tab.subs[id] || tran.push.on(id).event(function(req){ + tab.subs[soul] = tab.subs[soul] || tran.push(soul).event(function(req){ if(!req){ return } if(!tab){ return this.off() } // resolve any dangling callbacks req.sub = req.sub || req.headers['gun-sub']; @@ -349,13 +356,13 @@ }); } reply.headers["Content-Type"] = tran.json; - if(res.chunk){ + if(Gun.obj.has(res,'chunk')){ cb({ headers: reply.headers ,chunk: Gun.text.ify(res.chunk) + '\n' }) } - if(res.body){ + if(Gun.obj.has(res,'body')){ cb({ headers: reply.headers ,body: Gun.text.ify(res.body) @@ -382,10 +389,10 @@ reply.headers[field] = val; }); } - if(res.chunk && (!reply.body || Gun.list.is(reply.chunks))){ + if(Gun.obj.has(res,'chunk') && (!reply.body || Gun.list.is(reply.chunks))){ (reply.chunks = reply.chunks || []).push(res.chunk); } - if(res.body){ + if(Gun.obj.has(res,'body')){ reply.body = res.body; // self-reference yourself so on the client we can get the headers and body. reply.body = ';'+ cb.jsonp + '(' + Gun.text.ify(reply) + ');'; // javascriptify it! can't believe the client trusts us. cb(reply); @@ -393,7 +400,7 @@ } } tran.json = 'application/json'; - tran.push = Gun.on.split(); + tran.push = Gun.on.create(); return tran; }()); diff --git a/test/common.js b/test/common.js index 608ab812..683a34d0 100644 --- a/test/common.js +++ b/test/common.js @@ -1,5 +1,153 @@ describe('Gun', function(){ - var Gun = require('../gun'); + var Gun = require('../gun') + , t = {}; + describe('Utility', function(){ + describe('Type Check', function(){ + it('binary', function(){ + expect(Gun.bi.is(false)).to.be(true); + expect(Gun.bi.is(true)).to.be(true); + expect(Gun.bi.is('')).to.be(false); + expect(Gun.bi.is('a')).to.be(false); + expect(Gun.bi.is(0)).to.be(false); + expect(Gun.bi.is(1)).to.be(false); + expect(Gun.bi.is([])).to.be(false); + expect(Gun.bi.is([1])).to.be(false); + expect(Gun.bi.is({})).to.be(false); + expect(Gun.bi.is({a:1})).to.be(false); + expect(Gun.bi.is(function(){})).to.be(false); + }); + it('number',function(){ + expect(Gun.num.is(0)).to.be(true); + expect(Gun.num.is(1)).to.be(true); + expect(Gun.num.is(Infinity)).to.be(true); + expect(Gun.num.is(NaN)).to.be(false); + expect(Gun.num.is('')).to.be(false); + expect(Gun.num.is('a')).to.be(false); + expect(Gun.num.is([])).to.be(false); + expect(Gun.num.is([1])).to.be(false); + expect(Gun.num.is({})).to.be(false); + expect(Gun.num.is({a:1})).to.be(false); + expect(Gun.num.is(false)).to.be(false); + expect(Gun.num.is(true)).to.be(false); + expect(Gun.num.is(function(){})).to.be(false); + }); + it('text',function(){ + expect(Gun.text.is('')).to.be(true); + expect(Gun.text.is('a')).to.be(true); + expect(Gun.text.is(false)).to.be(false); + expect(Gun.text.is(true)).to.be(false); + expect(Gun.text.is(0)).to.be(false); + expect(Gun.text.is(1)).to.be(false); + expect(Gun.text.is([])).to.be(false); + expect(Gun.text.is([1])).to.be(false); + expect(Gun.text.is({})).to.be(false); + expect(Gun.text.is({a:1})).to.be(false); + expect(Gun.text.is(function(){})).to.be(false); + }); + it('list',function(){ + expect(Gun.list.is([])).to.be(true); + expect(Gun.list.is([1])).to.be(true); + expect(Gun.list.is(0)).to.be(false); + expect(Gun.list.is(1)).to.be(false); + expect(Gun.list.is('')).to.be(false); + expect(Gun.list.is('a')).to.be(false); + expect(Gun.list.is({})).to.be(false); + expect(Gun.list.is({a:1})).to.be(false); + expect(Gun.list.is(false)).to.be(false); + expect(Gun.list.is(true)).to.be(false); + expect(Gun.list.is(function(){})).to.be(false); + }); + it('obj',function(){ + expect(Gun.obj.is({})).to.be(true); + expect(Gun.obj.is({a:1})).to.be(true); + expect(Gun.obj.is(0)).to.be(false); + expect(Gun.obj.is(1)).to.be(false); + expect(Gun.obj.is('')).to.be(false); + expect(Gun.obj.is('a')).to.be(false); + expect(Gun.obj.is([])).to.be(false); + expect(Gun.obj.is([1])).to.be(false); + expect(Gun.obj.is(false)).to.be(false); + expect(Gun.obj.is(true)).to.be(false); + expect(Gun.obj.is(function(){})).to.be(false); + }); + it('fns',function(){ + expect(Gun.fns.is(function(){})).to.be(true); + expect(Gun.fns.is('')).to.be(false); + expect(Gun.fns.is('a')).to.be(false); + expect(Gun.fns.is(0)).to.be(false); + expect(Gun.fns.is(1)).to.be(false); + expect(Gun.fns.is([])).to.be(false); + expect(Gun.fns.is([1])).to.be(false); + expect(Gun.fns.is({})).to.be(false); + expect(Gun.fns.is({a:1})).to.be(false); + expect(Gun.fns.is(false)).to.be(false); + expect(Gun.fns.is(true)).to.be(false); + }); + it('time',function(){ + t.ts = Gun.time.is(); + expect(13 <= t.ts.toString().length).to.be.ok(); + expect(Gun.num.is(t.ts)).to.be.ok(); + expect(Gun.time.is(new Date())).to.be.ok(); + }); + }); + describe('Text', function(){ + it('ify',function(){ + expect(Gun.text.ify(0)).to.be('0'); + expect(Gun.text.ify(22)).to.be('22'); + expect(Gun.text.ify([true,33,'yay'])).to.be('[true,33,"yay"]'); + expect(Gun.text.ify({a:0,b:'1',c:[0,'1'],d:{e:'f'}})).to.be('{"a":0,"b":"1","c":[0,"1"],"d":{"e":"f"}}'); + expect(Gun.text.ify(false)).to.be('false'); + expect(Gun.text.ify(true)).to.be('true'); + }); + it('random',function(){ + expect(Gun.text.random().length).to.be(24); + expect(Gun.text.random(11).length).to.be(11); + expect(Gun.text.random(4).length).to.be(4); + t.tr = Gun.text.random(2,'as'); expect((t.tr=='as'||t.tr=='aa'||t.tr=='sa'||t.tr=='ss')).to.be.ok(); + }); + }); + describe('List', function(){ + it('slit',function(){ + (function(){ + expect(Gun.list.slit.call(arguments, 0)).to.eql([1,2,3,'a','b','c']); + }(1,2,3,'a','b','c')); + }); + it('sort',function(){ + expect([{i:9},{i:4},{i:1},{i:-3},{i:0}].sort(Gun.list.sort('i'))).to.eql([{i:-3},{i:0},{i:1},{i:4},{i:9}]); + }); + it('map',function(){ + expect(Gun.list.map([1,2,3,4,5],function(v,i,t){ t(v+=this.d); this.d=v; },{d:0})).to.eql([1,3,6,10,15]); + expect(Gun.list.map([2,3,0,4],function(v,i,t){ if(!v){ return } t(v*=this.d); this.d=v; },{d:1})).to.eql([2,6,24]); + expect(Gun.list.map([true,false,NaN,Infinity,'',9],function(v,i,t){ if(i===3){ return 0 }})).to.be(0); + }); + }); + describe('Object', function(){ + it('del',function(){ + var obj = {a:1,b:2}; + Gun.obj.del(obj,'a'); + expect(obj).to.eql({b:2}); + }); + it('has',function(){ + var obj = {a:1,b:2}; + expect(Gun.obj.has(obj,'a')).to.be.ok(); + }); + it('copy',function(){ + var obj = {"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}; + var copy = Gun.obj.copy(obj); + expect(copy).to.eql(obj); + expect(copy).to.not.be(obj); + }); + it('ify',function(){ + expect(Gun.obj.ify('[0,1]')).to.eql([0,1]); + expect(Gun.obj.ify('{"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}')).to.eql({"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}); + }); + it('map',function(){ + expect(Gun.obj.map({a:'z',b:'y',c:'x'},function(v,i,t){ t(v,i) })).to.eql({x:'c',y:'b',z:'a'}); + expect(Gun.obj.map({a:'z',b:false,c:'x'},function(v,i,t){ if(!v){ return } t(i,v) })).to.eql({a:'z',c:'x'}); + expect(Gun.obj.map({a:'z',b:3,c:'x'},function(v,i,t){ if(v===3){ return 0 }})).to.be(0); + }); + }); + }); it('ify', function(){ var data, test;