spincycle
Version:
A reactive message router and object manager that lets clients subscribe to object property changes on the server
620 lines (578 loc) • 20.1 kB
JavaScript
// Generated by CoffeeScript 1.9.3
(function() {
var $q, Chillman, debug, opts, spinpolymer, uuid,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
$q = Q;
opts = {
max: 1000,
maxAgeInMilliseconds: 1000 * 60 * 60 * 24 * 4
};
debug = true;
uuid = UUID4;
Chillman = (function() {
function Chillman() {}
Chillman.underwayCache = new LRUCache();
Chillman.callbackCache = new LRUCache();
Chillman.lookup = function(key, type, resolveFunc) {
var callbacks, q, underway;
q = $q.defer();
underway = Chillman.underwayCache.get(key + '_' + type);
if (underway) {
callbacks = Chillman.callbackCache.get(key + '_' + type) || [];
callbacks.push(q);
Chillman.callbackCache.set(key + '_' + type, callbacks);
} else {
Chillman.underwayCache.set(key + '_' + type, true);
Chillman._doLookup(key, type, resolveFunc, q);
}
return q.promise;
};
Chillman._doLookup = function(key, type, resolveFunc, q) {
return resolveFunc(key, type).then(function(result) {
var callbacks, cbcount;
Chillman.underwayCache.remove(key + '_' + type);
callbacks = Chillman.callbackCache.get(key + '_' + type) || [];
cbcount = callbacks.length;
callbacks.forEach(function(_q) {
_q.resolve(result);
if (--cbcount === 0) {
return Chillman.callbackCache.remove(key + '_' + type);
}
});
return q.resolve(result);
});
};
return Chillman;
})();
spinpolymer = (function() {
function spinpolymer(dbUrl) {
this.dbUrl = dbUrl;
this.flattenModel = bind(this.flattenModel, this);
this.listTargets = bind(this.listTargets, this);
this.getModelFor = bind(this.getModelFor, this);
this.emitMessage = bind(this.emitMessage, this);
this._deRegisterObjectsSubscriber = bind(this._deRegisterObjectsSubscriber, this);
this.deRegisterObjectsSubscriber = bind(this.deRegisterObjectsSubscriber, this);
this._registerObjectSubscriber = bind(this._registerObjectSubscriber, this);
this.registerObjectSubscriber = bind(this.registerObjectSubscriber, this);
this._registerPopulationSubscriber = bind(this._registerPopulationSubscriber, this);
this.registerPopulationChangeSubscriber = bind(this.registerPopulationChangeSubscriber, this);
this._deRegisterPopulationChangesSubscriber = bind(this._deRegisterPopulationChangesSubscriber, this);
this.deRegisterPopulationChangesSubscriber = bind(this.deRegisterPopulationChangesSubscriber, this);
this.registerListener = bind(this.registerListener, this);
this.hasSeenThisMessage = bind(this.hasSeenThisMessage, this);
this.setup = bind(this.setup, this);
this._emit = bind(this._emit, this);
this.emit = bind(this.emit, this);
this._doGet = bind(this._doGet, this);
this.open = false;
this.subscribers = {};
this.objsubscribers = [];
this.popsubscribers = {};
this.populationsubscribers = {};
this.objectsSubscribedTo = [];
this.onsubscribers = {};
this.outstandingMessages = [];
this.modelcache = [];
this.seenMessages = [];
this.sessionId = null;
this.objects = new LRUCache(opts);
this.failure = false;
this.failureMessage = '';
this.savedMessagesInCaseOfRetries = new LRUCache({
max: 1000,
maxAgeInMilliseconds: 5000
});
if (debug) {
console.log('polymer-spincycle dbUrl = ' + this.dbUrl);
}
this.subscribers['OBJECT_UPDATE'] = [
(function(_this) {
return function(obj) {
var k, o, objsubs, prop, results, v, val;
objsubs = _this.objsubscribers[obj.id] || [];
results = [];
for (k in objsubs) {
v = objsubs[k];
if (!_this.objects.get(obj.id)) {
_this.objects.set(obj.id, obj);
} else {
o = _this.objects.get(obj.id);
for (prop in obj) {
val = obj[prop];
o[prop] = val;
}
}
results.push(v(obj));
}
return results;
};
})(this)
];
this.subscribers['POPULATION_UPDATE'] = [
(function(_this) {
return function(update) {
var k, obj, objsubs, results, v;
obj = update.added || update.removed;
if (obj) {
objsubs = _this.populationsubscribers[obj.type] || {};
results = [];
for (k in objsubs) {
v = objsubs[k];
if (v.cb) {
results.push(v.cb(update));
} else {
results.push(void 0);
}
}
return results;
}
};
})(this)
];
this.setup();
}
spinpolymer.prototype.on = function(id, type, cb, onlyupdates) {
if (typeof id === 'object') {
xyzzy();
}
if (!onlyupdates) {
this.get(type, id).then(function(o) {
return cb(o);
});
}
return this._registerObjectSubscriber({
id: id,
type: type,
cb: cb
});
};
spinpolymer.prototype.get = function(type, id) {
var d, o;
if (typeof id === 'object') {
xyzzy();
}
d = $q.defer();
o = this.objects.get(id);
if (!o) {
Chillman.lookup(id, type, this._doGet).then(function(oo) {
return d.resolve(oo);
});
} else {
d.resolve(o);
}
return d.promise;
};
spinpolymer.prototype._doGet = function(k, t) {
var dd;
dd = $q.defer();
this.emitMessage({
target: '_get' + t,
type: t,
obj: {
id: k,
type: t
}
}).then((function(_this) {
return function(_oo) {
_this.objects.set(_oo.id, _oo);
return dd.resolve(_oo);
};
})(this));
return dd.promise;
};
spinpolymer.prototype.save = function(o) {
this.objects.set(o.id, o);
return this.emitMessage({
target: '_update' + o.type,
obj: o
}).then(function(sres) {
return console.log('saved obj result: ' + sres);
});
};
spinpolymer.prototype.failed = function(msg) {
console.log('spinclient message failed!! ' + JSON.toString(msg));
if (this.onFailure) {
return this.onFailure(msg.info);
}
};
spinpolymer.prototype.setSessionId = function(id) {
if (id) {
console.log('++++++++++++++++++++++++++++++++++++++ spinclient setting session id to ' + id);
return this.sessionId = id;
}
};
spinpolymer.prototype.dumpOutstanding = function() {
console.log('-------------------------------- ' + this.outstandingMessages.length + ' outstanding messages ---------------------------------');
this.outstandingMessages.forEach(function(os) {
return console.log(os.messageId + ' -> ' + os.target + ' - ' + os.d);
});
return console.log('-----------------------------------------------------------------------------------------');
};
spinpolymer.prototype.emit = function(message) {
return this._emit(message);
};
spinpolymer.prototype._emit = function(message) {
this.savedMessagesInCaseOfRetries.set(message.messageId, message);
return this.socket.emit('message', JSON.stringify(message));
};
spinpolymer.prototype.setup = function() {
console.log('.....connecting to "' + this.dbUrl + "'");
this.socket = io(this.dbUrl);
this.socket.on('connect', (function(_this) {
return function() {
return _this.emit({
target: 'listcommands'
});
};
})(this));
return this.socket.on('message', (function(_this) {
return function(reply) {
var detail, i, index, info, isNew, isPopulationUpdate, message, oldmsg, status, subs;
status = reply.status;
message = reply.payload;
info = reply.info;
isNew = !_this.hasSeenThisMessage(reply.messageId);
isPopulationUpdate = reply.info === 'POPULATION_UPDATE';
if (info === 'list of available targets') {
console.log('Spincycle server channel is up and awake');
return _this.open = true;
} else {
if (message && message.error && message.error === 'ERRCHILLMAN') {
oldmsg = _this.savedMessagesInCaseOfRetries[reply.messageId];
if (oldmsg) {
console.log('got ERRCHILLMAN from spinycle service, preparing to retry sending message...');
return setTimeout(function() {
console.log('resending message ' + oldmsg.messageId + ' due to target endpoint not open yet');
return _this.emit(oldmsg);
}, 250);
}
} else if (isNew) {
_this.savedMessagesInCaseOfRetries.remove(reply.messageId);
if (reply.messageId && reply.messageId !== 'undefined') {
_this.seenMessages.push(reply.messageId);
}
if (_this.seenMessages.length > 10) {
_this.seenMessages.shift();
}
index = -1;
if (reply.messageId) {
i = 0;
while (i < _this.outstandingMessages.length) {
index = i;
detail = _this.outstandingMessages[i];
if (detail && !detail.delivered && detail.messageId === reply.messageId) {
if (reply.status === 'FAILURE' || reply.status === 'NOT_ALLOWED') {
console.log('spinclient message FAILURE');
console.dir(reply);
_this.failure = true;
_this.failureMessage = reply.info;
console.log('--- initial message was');
console.dir(detail);
_this.failed(reply);
detail.d.reject(reply);
break;
} else {
detail.d.resolve(message);
break;
}
detail.delivered = true;
}
i++;
}
if (index > -1) {
return _this.outstandingMessages.splice(index, 1);
}
} else {
subs = _this.subscribers[info];
if (subs) {
return subs.forEach(function(listener) {
return listener(message);
});
} else {
if (debug) {
console.log('no subscribers for message ' + message);
}
if (debug) {
return console.dir(reply);
}
}
}
} else {
if (debug) {
return console.log('-- skipped resent message ' + reply.messageId);
}
}
}
};
})(this));
};
spinpolymer.prototype.hasSeenThisMessage = function(messageId) {
return this.seenMessages.some(function(mid) {
return messageId === mid;
});
};
spinpolymer.prototype.registerListener = function(detail) {
var subs;
subs = this.subscribers[detail.message] || [];
subs.push(detail.callback);
return this.subscribers[detail.message] = subs;
};
spinpolymer.prototype.deRegisterPopulationChangesSubscriber = function(detail) {
var count, j, k, len, localsubs, sid, type, v;
sid = detail.listenerid;
type = detail.type;
localsubs = this.populationsubscribers[type];
if (localsubs[sid]) {
console.log('deregistering local updates for model type ' + type);
delete localsubs[sid];
count = 0;
for (v = j = 0, len = localsubs.length; j < len; v = ++j) {
k = localsubs[v];
count++;
}
if (count === 1) {
return this._deRegisterPopulationChangesSubscriber('remotesid', type);
}
}
};
spinpolymer.prototype._deRegisterPopulationChangesSubscriber = function(sid, type) {
var subs;
subs = this.popsubscribers[type] || [];
if (subs && subs[sid]) {
delete subs[sid];
this.popsubscribers[type] = subs;
return this.emitMessage({
target: 'deRegisterForPopulationChangesFor',
type: type,
listenerid: sid
}).then(function(reply) {
return console.log('deregistering server updates for population changes for ' + type);
});
}
};
spinpolymer.prototype.registerPopulationChangeSubscriber = function(detail) {
var d, localsubs, sid;
d = $q.defer();
sid = uuid.generate();
localsubs = this.populationsubscribers[detail.type];
if (!localsubs) {
localsubs = {};
this._registerPopulationSubscriber({
type: detail.type,
cb: (function(_this) {
return function(updatedobj) {
var k, lsubs, results, v;
lsubs = _this.populationsubscribers[detail.type];
results = [];
for (k in lsubs) {
v = lsubs[k];
if (v.cb) {
results.push(v.cb(updatedobj));
} else {
results.push(void 0);
}
}
return results;
};
})(this)
}).then((function(_this) {
return function(remotesid) {
localsubs['remotesid'] = remotesid;
localsubs[sid] = detail;
_this.populationsubscribers[detail.type] = localsubs;
return d.resolve(sid);
};
})(this), (function(_this) {
return function(rejection) {
console.log('spinpolymer registerPopulationSubscriber rejection: ' + rejection);
return console.dir(rejection);
};
})(this));
} else {
localsubs[sid] = detail;
}
return d.promise;
};
spinpolymer.prototype._registerPopulationSubscriber = function(detail) {
var d, subs;
d = $q.defer();
subs = this.popsubscribers[detail.type] || {};
this.emitMessage({
target: 'registerForPopulationChangesFor',
type: detail.type
}).then((function(_this) {
return function(reply) {
subs[reply] = detail.cb;
_this.popsubscribers[detail.type] = subs;
return d.resolve(reply);
};
})(this), (function(_this) {
return function(reply) {
return _this.failed(reply);
};
})(this));
return d.promise;
};
spinpolymer.prototype.registerObjectSubscriber = function(detail) {
var d, localsubs, sid;
d = $q.defer();
sid = uuid.generate();
localsubs = this.objectsSubscribedTo[detail.id];
if (!localsubs) {
localsubs = [];
this._registerObjectSubscriber({
id: detail.id,
type: detail.type,
cb: (function(_this) {
return function(updatedobj) {
var k, lsubs, results, v;
lsubs = _this.objectsSubscribedTo[detail.id];
results = [];
for (k in lsubs) {
v = lsubs[k];
if (v.cb) {
results.push(v.cb(updatedobj));
} else {
results.push(void 0);
}
}
return results;
};
})(this)
}).then((function(_this) {
return function(remotesid) {
localsubs['remotesid'] = remotesid;
localsubs[sid] = detail;
_this.objectsSubscribedTo[detail.id] = localsubs;
return d.resolve(sid);
};
})(this), (function(_this) {
return function(rejection) {
console.log('spinpolymer registerObjectSubscriber rejection: ' + rejection);
return console.dir(rejection);
};
})(this));
} else {
localsubs[sid] = detail;
}
return d.promise;
};
spinpolymer.prototype._registerObjectSubscriber = function(detail) {
var d, subs;
d = $q.defer();
subs = this.objsubscribers[detail.id] || [];
this.emitMessage({
target: 'registerForUpdatesOn',
obj: {
id: detail.id,
type: detail.type
}
}).then((function(_this) {
return function(reply) {
subs[reply] = detail.cb;
_this.objsubscribers[detail.id] = subs;
return d.resolve(reply);
};
})(this), (function(_this) {
return function(reply) {
return _this.failed(reply);
};
})(this));
return d.promise;
};
spinpolymer.prototype.deRegisterObjectsSubscriber = function(sid, o) {
var count, j, k, len, localsubs, v;
localsubs = this.objectsSubscribedTo[o.id] || [];
if (localsubs[sid]) {
delete localsubs[sid];
count = 0;
for (v = j = 0, len = localsubs.length; j < len; v = ++j) {
k = localsubs[v];
count++;
}
if (count === 1) {
return this._deRegisterObjectsSubscriber('remotesid', o);
}
}
};
spinpolymer.prototype._deRegisterObjectsSubscriber = function(sid, o) {
var subs;
subs = this.objsubscribers[o.id] || [];
if (subs && subs[sid]) {
delete subs[sid];
this.objsubscribers[o.id] = subs;
return this.emitMessage({
target: 'deRegisterForUpdatesOn',
id: o.id,
type: o.type,
listenerid: sid
}).then(function(reply) {});
}
};
spinpolymer.prototype.emitMessage = function(detail) {
var d;
d = $q.defer();
detail.messageId = uuid.generate();
detail.sessionId = detail.sessionId || this.sessionId;
detail.d = d;
this.outstandingMessages.push(detail);
this.emit(detail);
return d.promise;
};
spinpolymer.prototype.getModelFor = function(type) {
var d;
d = $q.defer();
if (this.modelcache[type]) {
d.resolve(this.modelcache[type]);
} else {
this.emitMessage({
target: 'getModelFor',
modelname: type
}).then((function(_this) {
return function(model) {
_this.modelcache[type] = model;
return d.resolve(model);
};
})(this), (function(_this) {
return function(rejection) {
console.log('+++++++++++++++++ spinpolymer getModelFor rejection: ' + rejection);
return console.dir(rejection);
};
})(this));
}
return d.promise;
};
spinpolymer.prototype.listTargets = function() {
var d;
d = $q.defer();
this.emitMessage({
target: 'listcommands'
}).then(function(targets) {
return d.resolve(targets);
}, function(rejection) {
console.log('spinpolymer listTargets rejection: ' + rejection);
return console.dir(rejection);
});
return d.promise;
};
spinpolymer.prototype.flattenModel = function(model) {
var k, rv, v;
rv = {};
for (k in model) {
v = model[k];
if (angular.isArray(v)) {
rv[k] = v.map(function(e) {
return e.id;
});
} else {
rv[k] = v;
}
}
return rv;
};
return spinpolymer;
})();
window.SpinClient = spinpolymer;
}).call(this);
//# sourceMappingURL=spinclient.js.map