UNPKG

merkle-btree

Version:
491 lines (430 loc) 16.1 kB
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function lt(a, b) { return a < b; } function lte(a, b) { return a <= b; } function gt(a, b) { return a > b; } function gte(a, b) { return a >= b; } function any() { return true; } function beginsWith(str, begin) { return str.substring(0, begin.length) === begin; } var KeyElement = function KeyElement(key, value, targetHash) { _classCallCheck(this, KeyElement); this.key = key; this.value = value; this.targetHash = targetHash; }; var TreeNode = function () { function TreeNode(leftChildHash) { var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var hash = arguments[2]; _classCallCheck(this, TreeNode); this.hash = hash; this.leftChildHash = leftChildHash; this.keys = keys; var zero = new KeyElement("", null, leftChildHash); if (!this.keys.length || this.keys[0].key.length) { this.keys.unshift(zero); } } TreeNode.prototype.get = function get(key, storage) { var nextKey = this.keys[0]; for (var _iterator = this.keys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } var k = _ref; if (key < k.key) { break; } nextKey = k; } if (nextKey.targetHash) { return storage.get(nextKey.targetHash).then(function (serialized) { return TreeNode.deserialize(serialized, nextKey.targetHash).get(key, storage); }); } if (nextKey.key === key) { return Promise.resolve(nextKey.value); } return Promise.resolve(null); // not found }; TreeNode.prototype.searchText = function searchText(query) { var limit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100; var cursor = arguments[2]; var reverse = arguments[3]; var storage = arguments[4]; var lowerBound = cursor || query; var upperBound = undefined; var includeLowerBound = !cursor; var includeUpperBound = true; if (reverse) { lowerBound = query; upperBound = cursor || undefined; includeLowerBound = true; includeUpperBound = !cursor; } return this.searchRange(lowerBound, upperBound, query, limit, includeLowerBound, includeUpperBound, reverse, storage); }; TreeNode.prototype.searchRange = function searchRange(lowerBound, upperBound, queryText, limit) { var includeLowerBound = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; var includeUpperBound = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; var reverse = arguments[6]; var storage = arguments[7]; var matches = []; var _this = this; var lowerBoundCheck = void 0, upperBoundCheck = void 0; if (lowerBound) { lowerBoundCheck = includeLowerBound ? gte : gt; } else { lowerBoundCheck = any; } if (upperBound) { upperBoundCheck = includeUpperBound ? lte : lt; } else { upperBoundCheck = any; } function iterate(i) { if (i < 0 || i >= _this.keys.length) { return Promise.resolve(matches); } if (limit && matches.length >= limit) { matches = matches.slice(0, limit + 1); return Promise.resolve(matches); } var next = reverse ? i - 1 : i + 1; var k = _this.keys[i]; if (next >= 0 && next < _this.keys.length) { if (!reverse && lowerBound >= _this.keys[next].key) { // search only nodes whose keys are in the query range return iterate(next); } if (reverse && upperBound < _this.keys[next].key) { // search only nodes whose keys are in the query range return iterate(next); } } // return if search range upper / lower bound was passed if (reverse && i + 1 < _this.keys.length && !lowerBoundCheck(_this.keys[i + 1].key, lowerBound)) { return Promise.resolve(matches); } if (!reverse && !upperBoundCheck(k.key, upperBound)) { return Promise.resolve(matches); } if (k.targetHash) { // branch node return storage.get(k.targetHash).then(function (serialized) { var newLimit = limit ? limit - matches.length : undefined; return TreeNode.deserialize(serialized, k.targetHash).searchRange(lowerBound, upperBound, queryText, newLimit, includeLowerBound, includeUpperBound, reverse, storage); }).then(function (m) { if (m) { if (matches.length && !m.length) { return Promise.resolve(matches); // matches were previously found but now we're out of range } if (queryText && !m.length) { // text search yielded no results where they could have been return Promise.resolve(matches); } Array.prototype.push.apply(matches, m); } return iterate(next); }); } // leaf node if (k.key.length && k.value) { if (queryText && matches.length && !beginsWith(k.key, queryText)) { return Promise.resolve(matches); // matches were previously found but now we're out of range } if (lowerBoundCheck(k.key, lowerBound) && upperBoundCheck(k.key, upperBound)) { // leaf node with matching key if (!queryText || beginsWith(k.key, queryText)) { matches.push({ key: k.key, value: k.value }); } } } return iterate(next); } return iterate(reverse ? this.keys.length - 1 : 0); }; TreeNode.prototype.save = function save(storage) { var _this2 = this; return storage.put(this.serialize()).then(function (hash) { _this2.hash = hash; return _this2.hash; }); }; TreeNode.prototype._getLeafInsertIndex = function _getLeafInsertIndex(key) { var _getNextSmallestIndex2 = this._getNextSmallestIndex(key), index = _getNextSmallestIndex2.index, exists = _getNextSmallestIndex2.exists; var leafInsertIndex = index + 1; if (key > this.keys[this.keys.length - 1].key) { leafInsertIndex = this.keys.length; } return { leafInsertIndex: leafInsertIndex, exists: exists }; }; TreeNode.prototype._getNextSmallestIndex = function _getNextSmallestIndex(key) { var index = 0, exists = void 0; for (var i = 1; i < this.keys.length; i++) { if (key === this.keys[i].key) { exists = true; break; } if (key < this.keys[i].key) { break; } index = i; } return { index: index, exists: exists }; }; TreeNode.prototype._splitNode = function _splitNode(storage) { var medianIndex = Math.floor(this.keys.length / 2); var median = this.keys[medianIndex]; var leftChild = new TreeNode(null, this.keys.slice(0, medianIndex)); var putLeftChild = storage.put(leftChild.serialize()); var rightSet = this.keys.slice(medianIndex, this.keys.length); if (this.keys[0].targetHash) { // branch node rightSet.shift(); } var rightChild = new TreeNode(this.keys[medianIndex].targetHash, rightSet); var putRightChild = storage.put(rightChild.serialize()); return Promise.all([putLeftChild, putRightChild, this._removeIfNotEmpty(storage)]).then(function (_ref2) { var leftChildHash = _ref2[0], rightChildHash = _ref2[1]; var rightChildElement = new KeyElement(median.key, null, rightChildHash); return new TreeNode(leftChildHash, [rightChildElement]); }); }; TreeNode.prototype._removeIfNotEmpty = function _removeIfNotEmpty(storage) { // TODO: this breaks stuff when multiple trees use the same storage if (this.hash && this.keys.length > 0) { return storage.remove(this.hash); } return Promise.resolve(); }; TreeNode.prototype._saveToLeafNode = function _saveToLeafNode(key, value, storage, maxChildren) { var _this3 = this; var keyElement = new KeyElement(key, value, null); var _getLeafInsertIndex2 = this._getLeafInsertIndex(key), leafInsertIndex = _getLeafInsertIndex2.leafInsertIndex, exists = _getLeafInsertIndex2.exists; if (exists) { this.keys[leafInsertIndex] = keyElement; return this._removeIfNotEmpty(storage).then(function () { return storage.put(_this3.serialize()); }).then(function (hash) { return new TreeNode(_this3.leftChildHash, _this3.keys, hash); }); } this.keys.splice(leafInsertIndex, 0, keyElement); if (this.keys.length < maxChildren) { return this._removeIfNotEmpty(storage).then(function () { return storage.put(_this3.serialize()); }).then(function (hash) { return new TreeNode(_this3.leftChildHash, _this3.keys, hash); }); } return this._splitNode(storage); }; TreeNode.prototype._saveToBranchNode = function _saveToBranchNode(key, value, storage, maxChildren) { var _this4 = this; var _getNextSmallestIndex3 = this._getNextSmallestIndex(key), index = _getNextSmallestIndex3.index; var nextSmallest = this.keys[index]; return storage.get(nextSmallest.targetHash).then(function (serialized) { return TreeNode.deserialize(serialized, nextSmallest.targetHash).put(key, value, storage, maxChildren); }).then(function (modifiedChild) { if (!modifiedChild.hash) { // we split a child and need to add the median to our keys _this4.keys[index] = new KeyElement(nextSmallest.key, null, modifiedChild.keys[0].targetHash); _this4.keys.splice(index + 1, 0, modifiedChild.keys[modifiedChild.keys.length - 1]); if (_this4.keys.length < maxChildren) { return _this4._removeIfNotEmpty(storage).then(function () { return storage.put(_this4.serialize()); }).then(function (hash) { return new TreeNode(_this4.leftChildHash, _this4.keys, hash); }); } return _this4._splitNode(storage); } // The child element was not split _this4.keys[index] = new KeyElement(_this4.keys[index].key, null, modifiedChild.hash); storage.remove(_this4.hash); return _this4._removeIfNotEmpty(storage).then(function () { storage.put(_this4.serialize()); }).then(function (hash) { return new TreeNode(_this4.leftChildHash, _this4.keys, hash); }); }); }; TreeNode.prototype.put = function put(key, value, storage, maxChildren) { if (!this.keys[0].targetHash) { return this._saveToLeafNode(key, value, storage, maxChildren); } return this._saveToBranchNode(key, value, storage, maxChildren); }; TreeNode.prototype.delete = function _delete(key, storage) { var _this5 = this; var nextKey = this.keys[0]; var i = void 0; for (i = 0; i < this.keys.length; i++) { if (key < this.keys[i].key) { i = Math.max(i - 1, 0); break; } nextKey = this.keys[i]; } var q = Promise.resolve(); if (nextKey.targetHash) { q = q.then(function () { return storage.get(nextKey.targetHash).then(function (serialized) { return TreeNode.deserialize(serialized, nextKey.targetHash).delete(key, storage); }).then(function (newNode) { var oldHash = nextKey.targetHash; nextKey.targetHash = newNode.hash; return storage.remove(oldHash); }); }); } else if (nextKey.key === key) { this.keys.splice(i, 1); } return q.then(function () { return storage.put(_this5.serialize()); }).then(function (hash) { return new TreeNode(_this5.leftChildHash, _this5.keys, hash); }); }; TreeNode.prototype.size = function size(storage) { return this.keys.reduce(function (promise, key) { return promise.then(function (size) { if (key.targetHash) { return storage.get(key.targetHash).then(function (serialized) { return TreeNode.deserialize(serialized).size(storage); }).then(function (childSize) { return childSize + size; }); } return size; }); }, Promise.resolve(this.keys.length)); }; TreeNode.prototype.smallestKey = function smallestKey() { if (this.keys.length) { return this.keys[0]; } return null; }; TreeNode.prototype.smallestNonZeroKey = function smallestNonZeroKey() { if (this.keys.length > 1) { return this.keys[1]; } return null; }; TreeNode.prototype.rebalance = function rebalance() {}; TreeNode.prototype.print = function print(storage) { var lvl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var str = "node: " + this.hash + "\n"; var indentation = ""; for (var i = 0; i <= lvl; i++) { indentation += "- "; } this.keys.forEach(function (key) { str += indentation; str += "key: " + key.key; if (key.targetHash) { str += " link: " + key.targetHash; } if (key.value) { str += "\n" + indentation + " value: " + JSON.stringify(key.value); } str += "\n"; }); this.keys.forEach(function (key) { if (key.targetHash) { storage.get(key.targetHash).then(function (serialized) { var child = TreeNode.deserialize(serialized, key.targetHash); str += "\n"; str += indentation; str += child.print(storage, lvl + 1); }); } }); return str; }; TreeNode.prototype.serialize = function serialize() { return JSON.stringify({ leftChildHash: this.leftChildHash, keys: this.keys }); }; TreeNode.prototype.toSortedList = function toSortedList(storage) { var list = []; var _this = this; function getNextVal(i) { if (i >= _this.keys.length) { return Promise.resolve(list); } if (_this.keys[i].targetHash) { return storage.get(_this.keys[i].targetHash).then(function (serialized) { return TreeNode.deserialize(serialized, _this.keys[i].targetHash).toSortedList(); }).then(function (children) { list = list.concat(children); return getNextVal(i + 1); }); } else if (_this.keys[i].key.length) { list.push(_this.keys[i]); } return getNextVal(i + 1); } return getNextVal(0); }; TreeNode.deserialize = function deserialize(data, hash) { data = JSON.parse(data); return new TreeNode(data.leftChildHash, data.keys, hash); }; /* Create a tree from a [{key,value,targetHash}, ...] list sorted in ascending order by k. targetHash must be null for leaf nodes. */ TreeNode.fromSortedList = function fromSortedList(list, maxChildren, storage) { function addNextParentNode(parentNodeList) { if (list.length) { var keys = list.splice(0, maxChildren); return storage.put(new TreeNode(keys[0].targetHash, keys).serialize()).then(function (res) { parentNodeList.push({ key: keys[1].key, targetHash: res, value: null }); return addNextParentNode(parentNodeList); }); } if (parentNodeList.length && parentNodeList[0].targetHash) { parentNodeList[0].key = ""; } return TreeNode.fromSortedList(parentNodeList, maxChildren, storage); } if (list.length > maxChildren) { return addNextParentNode([]); } var targetHash = list.length ? list[0].targetHash : null; var node = new TreeNode(targetHash, list); return storage.put(node.serialize()).then(function (hash) { node.hash = hash; return node; }); }; return TreeNode; }(); export default TreeNode;