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!
-
- Reset the game!
-
-
-
-
-
-
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 .
-
-
- 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!
+ Player 1
+ Player 2
+ 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!
+ Reset Game
+
+
+
YOU WON!
+ Reset Game
+
+
+
+
\ 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;