wr-eventstore
Version:
Node-eventstore is a node.js module for multiple databases. It can be very useful as eventstore if you work with (d)ddd, cqrs, eventsourcing, commands and events, etc.
468 lines (398 loc) • 13.3 kB
JavaScript
var util = require('util'),
Store = require('../base'),
_ = require('lodash'),
jsondate = require('jsondate'),
debug = require('debug')('eventstore:store:inmemory');
function InMemory(options) {
Store.call(this, options);
this.store = {};
this.snapshots = {};
this.undispatchedEvents = { _direct: {} };
this.options = options;
if (options.trackPosition)
this.position = 0;
}
util.inherits(InMemory, Store);
function deepFind (obj, pattern) {
var found;
if (pattern) {
var parts = pattern.split('.');
found = obj;
for (var i in parts) {
found = found[parts[i]];
if (_.isArray(found)) {
found = _.filter(found, function (item) {
var deepFound = deepFind(item, parts.slice(i + 1).join('.'));
return !!deepFound;
});
break;
}
if (!found) {
break;
}
}
}
return found;
}
_.extend(InMemory.prototype, {
connect: function (callback) {
this.emit('connect');
if (callback) callback(null, this);
},
disconnect: function (callback) {
this.emit('disconnect');
if (callback) callback(null);
},
clear: function (callback) {
this.store = {};
this.snapshots = {};
this.undispatchedEvents = { _direct: {} };
this.position = 0;
if (callback) callback(null);
},
getNextPositions: function(positions, callback) {
if (!this.options.trackPosition) {
return callback(null);
}
var range = [];
for(var i=0; i<positions; i++) {
range.push(++this.position);
}
callback(null, range);
},
addEvents: function (events, callback) {
if (!events || events.length === 0) {
callback(null);
return;
}
var found = _.find(events, function(evt) {
return !evt.aggregateId;
});
if (found) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
var aggregateId = events[0].aggregateId;
var aggregate = events[0].aggregate || '_general';
var context = events[0].context || '_general';
this.store[context] = this.store[context] || {};
this.store[context][aggregate] = this.store[context][aggregate] || {};
this.store[context][aggregate][aggregateId] = this.store[context][aggregate][aggregateId] || [];
this.store[context][aggregate][aggregateId] = this.store[context][aggregate][aggregateId].concat(events);
this.undispatchedEvents[context] = this.undispatchedEvents[context] || {};
this.undispatchedEvents[context][aggregate] = this.undispatchedEvents[context][aggregate] || {};
this.undispatchedEvents[context][aggregate][aggregateId] = this.undispatchedEvents[context][aggregate][aggregateId] || [];
this.undispatchedEvents[context][aggregate][aggregateId] = this.undispatchedEvents[context][aggregate][aggregateId].concat(events);
var self = this;
_.forEach(events, function(evt) {
self.undispatchedEvents._direct[evt.id] = evt;
});
callback(null);
},
getEvents: function (query, skip, limit, callback) {
var res = [];
for (var s in this.store) {
for (var ss in this.store[s]) {
for (var sss in this.store[s][ss]) {
res = res.concat(this.store[s][ss][sss]);
}
}
}
res = _.sortBy(res, function (e) {
return e.commitStamp.getTime();
});
if (!_.isEmpty(query)) {
res = _.filter(res, function(e) {
var keys = _.keys(query);
var values = _.values(query);
var found = false;
for (var i in keys) {
var key = keys[i];
var deepFound = deepFind(e, key);
if (_.isArray(deepFound) && deepFound.length > 0) {
found = true;
} else if (deepFound === values[i]) {
found = true;
} else {
found = false;
break;
}
}
return found;
});
}
if (limit === -1) {
return callback(null, _.cloneDeep(res.slice(skip)));
}
if (res.length <= skip) {
return callback(null, []);
}
callback(null, _.cloneDeep(res.slice(skip, skip + limit)));
},
getEventsSince: function (date, skip, limit, callback) {
var res = [];
for (var s in this.store) {
for (var ss in this.store[s]) {
for (var sss in this.store[s][ss]) {
res = res.concat(this.store[s][ss][sss]);
}
}
}
res = _.sortBy(res, function (e) {
return e.commitStamp.getTime();
});
res = _.filter(res, function(e) {
return e.commitStamp.getTime() >= date.getTime();
});
if (limit === -1) {
return callback(null, _.cloneDeep(res.slice(skip)));
}
if (res.length <= skip) {
return callback(null, []);
}
callback(null, _.cloneDeep(res.slice(skip, skip + limit)));
},
getEventsByRevision: function (query, revMin, revMax, callback) {
var res = [];
if (!query.aggregateId) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
if (query.context && query.aggregate) {
this.store[query.context] = this.store[query.context] || {};
this.store[query.context][query.aggregate] = this.store[query.context][query.aggregate] || {};
if (!this.store[query.context][query.aggregate][query.aggregateId]) {
return callback(null, _.cloneDeep(res));
}
else {
if (revMax === -1) {
res = res.concat(this.store[query.context][query.aggregate][query.aggregateId].slice(revMin));
}
else {
res = res.concat(this.store[query.context][query.aggregate][query.aggregateId].slice(revMin, revMax));
}
}
return callback(null, _.cloneDeep(res));
}
if (!query.context && query.aggregate) {
for (var s in this.store) {
var c = this.store[s];
if (c[query.aggregate] && c[query.aggregate][query.aggregateId]) {
if (revMax === -1) {
res = res.concat(c[query.aggregate][query.aggregateId].slice(revMin));
}
else {
res = res.concat(c[query.aggregate][query.aggregateId].slice(revMin, revMax));
}
}
}
return callback(null, _.cloneDeep(res));
}
if (query.context && !query.aggregate) {
var cc = this.store[query.context] || {};
for (var ss in cc) {
var a = cc[ss];
if (a[query.aggregateId]) {
if (revMax === -1) {
res = res.concat(a[query.aggregateId].slice(revMin));
}
else {
res = res.concat(a[query.aggregateId].slice(revMin, revMax));
}
}
}
return callback(null, _.cloneDeep(res));
}
if (!query.context && !query.aggregate) {
for (var sc in this.store) {
var cont = this.store[sc];
for (var sa in cont) {
var agg = cont[sa];
if (agg[query.aggregateId]) {
if (revMax === -1) {
res = res.concat(agg[query.aggregateId].slice(revMin));
}
else {
res = res.concat(agg[query.aggregateId].slice(revMin, revMax));
}
}
}
}
return callback(null, _.cloneDeep(res));
}
},
getLastEvent: function (query, callback) {
if (!query.aggregateId) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
var res = [];
for (var s in this.store) {
for (var ss in this.store[s]) {
for (var sss in this.store[s][ss]) {
res = res.concat(this.store[s][ss][sss]);
}
}
}
res = _.sortBy(res, function (e) {
return e.commitStamp.getTime();
});
if (!_.isEmpty(query)) {
res = _.filter(res, function(e) {
var keys = _.keys(query);
var values = _.values(query);
var found = false;
for (var i in keys) {
var key = keys[i];
var deepFound = deepFind(e, key);
if (_.isArray(deepFound) && deepFound.length > 0) {
found = true;
} else if (deepFound === values[i]) {
found = true;
} else {
found = false;
break;
}
}
return found;
});
}
callback(null, res[res.length - 1]);
},
getUndispatchedEvents: function (query, callback) {
var res = [];
for (var s in this.undispatchedEvents) {
if (s === '_direct') continue;
for (var ss in this.undispatchedEvents[s]) {
for (var sss in this.undispatchedEvents[s][ss]) {
res = res.concat(this.undispatchedEvents[s][ss][sss]);
}
}
}
res = _.sortBy(res, function (e) {
return e.commitStamp.getTime();
});
if (!_.isEmpty(query)) {
res = _.filter(res, function(e) {
var keys = _.keys(query);
var values = _.values(query);
var found = false;
for (var i in keys) {
var key = keys[i];
var deepFound = deepFind(e, key);
if (_.isArray(deepFound) && deepFound.length > 0) {
found = true;
} else if (deepFound === values[i]) {
found = true;
} else {
found = false;
break;
}
}
return found;
});
}
callback(null, res);
},
setEventToDispatched: function (id, callback) {
var evt = this.undispatchedEvents._direct[id];
var aggregateId = evt.aggregateId;
var aggregate = evt.aggregate || '_general';
var context = evt.context || '_general';
this.undispatchedEvents[context][aggregate][aggregateId] = _.reject(this.undispatchedEvents[context][aggregate][aggregateId], evt);
delete this.undispatchedEvents._direct[id];
callback(null);
},
addSnapshot: function(snap, callback) {
var aggregateId = snap.aggregateId;
var aggregate = snap.aggregate || '_general';
var context = snap.context || '_general';
if (!snap.aggregateId) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
this.snapshots[context] = this.snapshots[context] || {};
this.snapshots[context][aggregate] = this.snapshots[context][aggregate] || {};
this.snapshots[context][aggregate][aggregateId] = this.snapshots[context][aggregate][aggregateId] || [];
this.snapshots[context][aggregate][aggregateId].push(snap);
callback(null);
},
getSnapshot: function (query, revMax, callback) {
if (!query.aggregateId) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
var all = [];
for (var s in this.snapshots) {
for (var ss in this.snapshots[s]) {
for (var sss in this.snapshots[s][ss]) {
all = all.concat(this.snapshots[s][ss][sss]);
}
}
}
// all = _.sortBy(all, function (s) {
// return [(-s.revision), (-s.version)].join('_');
// });
all = _.sortBy(all, function (s) {
return (-s.commitStamp.getTime());
});
if (!_.isEmpty(query)) {
all = _.filter(all, function(a) {
var keys = _.keys(query);
var values = _.values(query);
var found = false;
for (var i in keys) {
var key = keys[i];
var deepFound = deepFind(a, key);
if (_.isArray(deepFound) && deepFound.length > 0) {
found = true;
} else if (deepFound === values[i]) {
found = true;
} else {
found = false;
break;
}
}
return found;
});
}
if (revMax === -1) {
return callback(null, all[0] ? jsondate.parse(JSON.stringify(all[0])) : null);
}
else {
for (var i = all.length - 1; i >= 0; i--) {
if (all[i].revision <= revMax) {
return callback(null, jsondate.parse(JSON.stringify(all[i])));
}
}
}
callback(null, null);
},
cleanSnapshots: function(query, callback) {
var aggregateId = query.aggregateId;
var aggregate = query.aggregate || '_general';
var context = query.context || '_general';
if (!aggregateId) {
var errMsg = 'aggregateId not defined!';
debug(errMsg);
if (callback) callback(new Error(errMsg));
return;
}
var snapshots = this.snapshots[context][aggregate][aggregateId] || [];
var length = snapshots.length;
snapshots = snapshots.slice(-1 * this.options.maxSnapshotsCount);
this.snapshots[context][aggregate][aggregateId] = snapshots;
callback(null, length - snapshots.length);
}
});
module.exports = InMemory;