graph-crdt
Version:
A Delta State Graph CRDT variant
388 lines (294 loc) • 10.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _iterator4 = require('babel-runtime/core-js/symbol/iterator');
var _iterator5 = _interopRequireDefault(_iterator4);
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _symbol = require('babel-runtime/core-js/symbol');
var _symbol2 = _interopRequireDefault(_symbol);
var _eventemitter = require('eventemitter3');
var _eventemitter2 = _interopRequireDefault(_eventemitter);
var _Node = require('../Node');
var _Node2 = _interopRequireDefault(_Node);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @module graph-crdt.Graph
*/
var nodes = (0, _symbol2.default)('graph node container');
/**
* Container and interface for groups of nodes.
*
* @class Graph
*/
var Graph = function (_Emitter) {
(0, _inherits3.default)(Graph, _Emitter);
(0, _createClass3.default)(Graph, null, [{
key: 'create',
/**
* Instantiates a graph without needing `new`.
*
* @returns {Graph} - A graph instance.
*/
value: function create() {
return new Graph();
}
/**
* Imports a format compliant graph into a new one. It expects
* nested nodes to use the node metadata format. Useful for
* sending and importing graphs over the network.
*
* @param {Object} object - The raw graph object.
* @returns {Graph} - A new graph instance that consumes
* the imported data.
*/
}, {
key: 'source',
value: function source(object) {
// Create a new graph
var graph = Graph.create();
// For each node...
(0, _keys2.default)(object).forEach(function (key) {
var node = object[key];
// Make sure it's a node.
if (!(node instanceof _Node2.default)) {
node = _Node2.default.source(node);
}
// Get it's unique ID.
var _node$meta = node.meta(),
uid = _node$meta.uid;
// Add it to the new graph.
graph[nodes][uid] = node;
});
return graph;
}
}]);
function Graph() {
(0, _classCallCheck3.default)(this, Graph);
var _this = (0, _possibleConstructorReturn3.default)(this, (Graph.__proto__ || (0, _getPrototypeOf2.default)(Graph)).call(this));
_this[nodes] = {};
return _this;
}
/**
* Return the unmodified value of a node lookup.
*
* @param {String} key - The name/uid of the node.
* @returns {Node|null} - The node if found, otherwise `null`.
*/
(0, _createClass3.default)(Graph, [{
key: 'value',
value: function value(key) {
return this[nodes][key] || null;
}
}, {
key: 'new',
value: function _new() {
return new Graph();
}
/**
* Replays all the changes in the graph as though they occurred
* after the events in the target graph.
* @param {Graph} target - Preceding state.
* @return {Graph} - A new graph containing the rebased nodes.
*/
}, {
key: 'rebase',
value: function rebase(target) {
var rebased = this.new();
rebased.merge(target);
rebased.merge(this);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(this), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _step$value = (0, _slicedToArray3.default)(_step.value, 1),
id = _step$value[0];
var existing = target.value(id);
if (existing) {
rebased[nodes][id] = this.value(id).rebase(existing);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return rebased;
}
/**
* Figures out what fields are common to both graphs.
* @param {Graph} target - Any other graph.
* @return {Graph} - The shared properties between both graphs.
*/
}, {
key: 'overlap',
value: function overlap(target) {
var shared = this.new();
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)(this), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _step2$value = (0, _slicedToArray3.default)(_step2.value, 1),
key = _step2$value[0];
if (this.value(key) && target.value(key)) {
// Calculate the node overlap.
var nodeSource = this.value(key);
var nodeTarget = target.value(key);
var overlap = nodeSource.overlap(nodeTarget);
// Merge it into the new graph.
shared.merge((0, _defineProperty3.default)({}, key, overlap));
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return shared;
}
/**
* Merge one graph with another (graph union operation).
*
* @param {Object} graph - The graph to merge with.
* Items must be enumerable, and cannot be inherited from prototypes.
* @returns {Graph} - The `this` context.
*/
}, {
key: 'merge',
value: function merge(graph) {
/** Ensure it's a graph. */
if (!(graph instanceof Graph)) {
graph = Graph.source(graph);
}
var changes = {
update: this.new(),
history: this.new()
};
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = (0, _getIterator3.default)(graph), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var _step3$value = (0, _slicedToArray3.default)(_step3.value, 2),
uid = _step3$value[0],
node = _step3$value[1];
var target = this.value(uid);
if (!target) {
target = this[nodes][uid] = node.new();
}
var _target$merge = target.merge(node),
update = _target$merge.update,
history = _target$merge.history;
changes.update[nodes][uid] = update;
changes.history[nodes][uid] = history;
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
this.emit('update', changes.update);
this.emit('history', changes.history);
return changes;
}
/**
* Iterates over every node in the graph.
* @return {Array} - Every yielded value is a key/value pair.
*/
}, {
key: _iterator5.default,
value: _regenerator2.default.mark(function value() {
var object, key, value;
return _regenerator2.default.wrap(function value$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
object = this[nodes];
_context.t0 = _regenerator2.default.keys(object);
case 2:
if ((_context.t1 = _context.t0()).done) {
_context.next = 10;
break;
}
key = _context.t1.value;
if (!object.hasOwnProperty(key)) {
_context.next = 8;
break;
}
value = this.value(key);
_context.next = 8;
return [key, value];
case 8:
_context.next = 2;
break;
case 10:
case 'end':
return _context.stop();
}
}
}, value, this);
})
/* Coercion interfaces */
/**
* Used to serialize a graph (JSON.stringify calls this method).
*
* @private
* @returns {Object} - The hidden collection of nodes.
*/
}, {
key: 'toJSON',
value: function toJSON() {
return this[nodes];
}
}]);
return Graph;
}(_eventemitter2.default);
exports.default = Graph;