conveyor-client
Version:
A client for communicating with the Conveyor event sourcing engine via the HTTP/Webhook interface'
159 lines (128 loc) • 5.68 kB
JavaScript
'use strict';
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const impl = require('./impl');
const Repository = require('./Repository');
const dbTools = require('./dbTools');
const privateToken_ = Symbol('private-token');
/**
* Repository that eagerly builds up it collection of objects in-memory
* and keeps them updated as new events are streamed in.
*
* An instance should only be created via the EagerRepository.create()
* static method.
*/
class EagerRepository extends Repository {
constructor(connectionOrClient, name, basePath, ModelClass, privateToken) {
super(connectionOrClient, name, basePath);
if (privateToken !== privateToken_) {
throw new Error('EagerRepository must be created via EagerRepository.create()');
}
this.ModelClass = ModelClass;
// State to hold messages received before all historical messages have been delivered
this.buffer = [];
this.hasFetched = false;
// Internal storage for all models, sorted by ID
this.table = [];
// By default, models are not indexed in any way, and searching does a full
// scan of the table. Indexing functions may be specified that build
// up ordered indexes that allow for efficient retrieval. Yes, this is
// essentially a tiny, inefficient in-memory database.
this.indexes = {};
this.indexFns = {};
this.handle = this.handle.bind(this);
}
static create(connectionOrClient, name, basePath, ModelClass) {
return _asyncToGenerator(function* () {
const inst = new EagerRepository(connectionOrClient, name, basePath, ModelClass, privateToken_);
impl.subscribe(inst.connection, inst.basePath, function (event) {
if (!inst.hasFetched) {
buffered.push(event);
return;
}
inst.handle(event);
});
const feed = yield impl.getEvents(inst.connection, inst.basePath);
const firstSubscribedEvent = inst.buffer.length > 0 ? inst.buffer : null;
feed.events.filter(function (e) {
return firstSubscribedEvent === null || e['tx-id'] < firstSubscribedEvent['tx-id'];
}).forEach(inst.handle);
inst.hasFetched = true;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = inst.buffer[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
const event = _step.value;
inst.handle(event);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return inst;
})();
}
get indexPairs() {
return Object.keys(this.indexes).map(idxName => [this.indexes[idxName], this.indexFns[idxName]]);
}
createIndex(name, fn) {
const idx = [];
this.indexFns[name] = fn;
this.indexes[name] = idx;
// Add existing rows to index
this.table.forEach(row => dbTools.idxInsert(idx, row, fn));
}
queryIndex(name, value) {
const idx = this.indexes[name];
if (!idx) {
throw new Error(`Cannot query nonexistent index: ${name}`);
}
return dbTools.indexedTblLookup(this.table, idx, value);
}
handle(evt) {
const path = evt.feed.split('/');
const id = path[path.length - 1];
let model = dbTools.tblLookup(this.table, id);
const idxNames = Object.keys(this.indexFns);
const isNew = !model;
if (isNew) {
model = new this.ModelClass();
} else if (evt.data.type === 'deleted') {
dbTools.removeFromAll(this.table, this.indexPairs, model);
return;
} else {
// Remove from existing indexes
// TODO: only rewrite in index for indexes that actually change, not every index
idxNames.forEach(idxName => dbTools.idxRemove(this.indexes[idxName], model, this.indexFns[idxName]));
}
model.handle(evt.data);
if (isNew) {
dbTools.tblInsert(this.table, model);
}
// (re-)add to indexes with possibly updated values
idxNames.forEach(idxName => dbTools.idxInsert(this.indexes[idxName], model, this.indexFns[idxName]));
}
fetch(id) {
var _this = this;
return _asyncToGenerator(function* () {
return dbTools.tblLookup(_this.table, id);
})();
}
fetchAll() {
var _this2 = this;
return _asyncToGenerator(function* () {
return _this2.table;
})();
}
}
module.exports = EagerRepository;