@code-o-mat/history-cache
Version:
A data object structure that logs all changes using the immutable library
468 lines (395 loc) • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _bunyan = _interopRequireDefault(require("bunyan"));
var _immutable = require("immutable");
var _immutableMapSymbolPreprocessor = require("immutable-map-symbol-preprocessor");
var _v = _interopRequireDefault(require("uuid/v4"));
var _values = require("@code-o-mat/globals/lib/constants/values");
var _types = require("@code-o-mat/globals/lib/constants/types");
var _jsTypes = require("@code-o-mat/globals/lib/assertions/js-types");
var _utilMethods = require("./util-methods.js");
var _methods = require("./constants/methods");
var _log = _interopRequireDefault(require("./log"));
var _meta = _interopRequireDefault(require("@code-o-mat/globals/lib/abstracts/meta"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
var log = _bunyan.default.createLogger({
name: "HistoryCache"
});
var _APPEND_TO_HISTORY = _methods.APPEND_TO_HISTORY;
var _FIND_INDEX = _methods.FIND_INDEX;
var _TRIM = _methods.TRIM;
var HistoryCache =
/*#__PURE__*/
function (_Meta) {
_inherits(HistoryCache, _Meta);
function HistoryCache() {
var _this;
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, HistoryCache);
_this = _possibleConstructorReturn(this, (HistoryCache.__proto__ || Object.getPrototypeOf(HistoryCache)).call(this));
Object.defineProperty(_assertThisInitialized(_this), "FAMILY", {
configurable: true,
enumerable: true,
writable: true,
value: _types.CACHE
});
Object.defineProperty(_assertThisInitialized(_this), "TYPE", {
configurable: true,
enumerable: true,
writable: true,
value: _types.HISTORY_CACHE
});
(0, _jsTypes.assertIsObject)(data, 'Invalid data object for Cache');
var dataMap = (0, _immutableMapSymbolPreprocessor.createMap)(data);
_this[_values.PROPS] = {
data: dataMap,
index: 0,
limit: _values.NONE,
history: (0, _immutable.List)(),
eventIndex: (0, _immutable.Map)(),
tagIndex: (0, _immutable.Map)(),
UID: (0, _v.default)(),
UIDIndex: (0, _immutable.Map)()
};
_this[_methods.APPEND_TO_HISTORY](dataMap, 'START', 'DEFAULT STATE');
return _this;
}
/**
* get the UID of this historyCache.
* @return {string} the UID
*/
_createClass(HistoryCache, [{
key: "uid",
value: function uid() {
return this[_values.PROPS].UID;
}
/**
* gets or sets the maximum limit for the history to be kept.
* if no param, returs the limit, if an integer,
* sets the limit and returns the HistoryCache.
* @param {int|NONE} limit
* @return {HistoryCache | int}
*/
}, {
key: "limit",
value: function limit() {
var _limit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _values.NONE;
if (_limit === _values.NONE) return this[_values.PROPS].limit;
(0, _jsTypes.assertIsIntOrNONE)(_limit, 'Invalid limit set for history');
this[_values.PROPS].limit = _limit === -1 ? _values.NONE : _limit;
return this;
}
/**
* get the current data as a js object
* @return {Object}
*/
}, {
key: "data",
value: function data() {
return this[_values.PROPS].data.toJS();
} // ITEM OPERATIONS
/**
* Get a value by key.
* @param {*} key
* @return {*} value or NONE if not exists
*/
}, {
key: "get",
value: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _values.NONE;
var dflt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _values.NONE;
(0, _jsTypes.assertIsKeyOrNONE)(key);
return this[_values.PROPS].data.get(key, dflt);
}
/**
* Get a value by key and version
*/
}, {
key: "getFrom",
value: function getFrom() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _values.NONE;
var term = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _values.NONE;
var dflt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _values.NONE;
if (term === _values.NONE) return this.get(key);
(0, _jsTypes.assertIsKeyOrNONE)(key);
var version = this.version(term);
if (version === _values.NONE) return dflt;
return version.data.get(key, dflt);
}
}, {
key: "merge",
value: function merge(newData) {
(0, _jsTypes.assertIsObject)(newData, "HistoryCache Invalid Object Error:\n Must merge JavaScript object or Immutable Map");
if (_immutable.Map.isMap(newData) === false) newData = (0, _immutableMapSymbolPreprocessor.createMap)(newData);
this[_values.PROPS].data = this[_values.PROPS].data.merge(newData);
return this;
}
/**
* Set a value at data[key]
* Will create a new log entry.
* @param {*} key
* @param {*} value
* @param {string|NONE} comment
* @return {HistoryCache} this for continuation.
*/
}, {
key: "set",
value: function set(key, value) {
var comment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _values.NONE;
(0, _jsTypes.assertIsKeyOrNONE)(key);
(0, _jsTypes.assertIsStringOrNONE)(comment);
var newData = this[_values.PROPS].data.set(key, value);
var valueStr = String(value).slice(0, 25);
this[_methods.APPEND_TO_HISTORY](newData, "set ".concat(key, " = ").concat(valueStr), comment);
this[_values.PROPS].data = newData;
return this;
}
/**
* checks out an earlier version of the cache if exists.
* This will re-attach the earlier version to the
* history as the latest... If the search term is not found in the history,
* the current data state will be returned and no history will be affected.
* Additionally, a warning will be thrown.
* @param {*} term A search term that retrieve the history index via FIND_INDEX method.
* @return {Object} object with the following params;
* data: the recovered data
* note: (if history not found, notes the attempt and its failure)
* event: auto generated event for this checkout.
* comment: the comment for this checkout
* originalMeta: The event and comment from the data in its original history.
*
*/
}, {
key: "checkout",
value: function checkout(term) {
var checkoutComment = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _values.NONE;
var index = this[_methods.FIND_INDEX](term);
(0, _jsTypes.assertIsStringOrNONE)(checkoutComment);
var history = this[_values.PROPS].history;
var checkoutEventNote = "Checkout of ".concat(String(term));
if (index === _values.NONE) {
log.warn("Ignoring checkout of a non-existent version.");
return _extends({}, _history.get(-1), {
note: "".concat(checkoutEventNote, ": Not Found")
});
}
var checkedOutData = history.get(index);
var newDataItem = this[_methods.APPEND_TO_HISTORY]((0, _immutableMapSymbolPreprocessor.createMap)(checkedOutData), "checkout of ".concat(String(term), " from history"), checkoutComment);
var event = checkedOutData.event,
comment = checkedOutData.comment;
return _extends({}, checkedOutData, {
event: newDataItem.event,
comment: newDataItem.comment,
originalMeta: {
event: event,
comment: comment
}
});
}
/**
* get the revision index of the current data.
* @return {int} the index
*/
}, {
key: "index",
value: function index() {
return this[_values.PROPS].index;
}
}, {
key: "tag",
value: function (_tag) {
function tag() {
return _tag.apply(this, arguments);
}
tag.toString = function () {
return _tag.toString();
};
return tag;
}(function () {
var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _values.NONE;
(0, _jsTypes.assertIsStringOrNONE)(tag);
if (tag === _values.NONE) {
log.warn("Tried to set an invalid tag in HistoryCache. \n Tags must be a string");
return this;
}
var _PROPS = this[_values.PROPS],
history = _PROPS.history,
tagIndex = _PROPS.tagIndex;
var lastHistoryItem = history.last();
this[_values.PROPS].history = history.pop().push(_extends({}, lastHistoryItem, {
tag: tag
}));
this[_values.PROPS].tagIndex = tagIndex.set(tag.slice(0, 100), this[_values.PROPS].index);
return this;
})
/**
* retrieves the data from an earlier version.
* but does not touch anything in the history
* and does not reset the version.
* @return { Map }
*/
}, {
key: "version",
value: function version(term) {
var index = this[_methods.FIND_INDEX](term);
if (index === _values.NONE) {
log.warn('Tried to get an undefined HistoryCache version');
return _values.NONE;
}
return this[_values.PROPS].history.get(index);
}
/**
* returns a Log object.. this will
* get the log in form of .js object, JSON or a string
* @return { Object | String }
*/
}, {
key: "log",
value: function log(count) {
return new _log.default(this[_values.PROPS].history, count);
}
/**
* Resets everything. Erases all history and resets data
* to the original default state.
* @return {[type]} [description]
*/
}, {
key: "reset",
value: function reset() {
var defaultData = this[_values.PROPS].history.get(0);
this[_values.PROPS].index = 0;
this[_methods.APPEND_TO_HISTORY](defaultData, event = 'RESET', tag = 'RESET');
}
/**
* @function APPEND_TO_HISTORY
* put a data state into the history.
* @private
* @param { Map } data
* @param { string } event
* @param { string } comment.
* @return {Object}
* data: the data appended.
* event: the event named.
* comment: the comment
*
*/
}, {
key: _APPEND_TO_HISTORY,
value: function value(data) {
var event = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _values.NONE;
var comment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _values.NONE;
var _PROPS2 = this[_values.PROPS],
history = _PROPS2.history,
eventIndex = _PROPS2.eventIndex,
currentTag = _PROPS2.currentTag,
UIDIndex = _PROPS2.UIDIndex,
index = _PROPS2.index,
limit = _PROPS2.limit;
if (comment === _values.NONE) comment = "Revision: ".concat(index);
var UID = (0, _v.default)();
var date = new Date();
var historyDataItem = {
date: date,
UID: UID,
data: data,
event: event,
comment: comment,
tag: currentTag === _values.NONE ? '' : currentTag
};
var trimmed = this[_methods.TRIM](index, limit, history.push(historyDataItem), eventIndex.set(event, index), UIDIndex.set(UID, index));
this[_values.PROPS].history = trimmed.history;
this[_values.PROPS].eventIndex = trimmed.eventIndex;
this[_values.PROPS].UIDIndex = trimmed.UIDIndex;
this[_values.PROPS].index++;
this[_values.PROPS].currentTag = _values.NONE;
return historyDataItem;
}
/**
* @function FIND_INDEX
* @description: Find a history index to match the search term.
* @private
* @param { * } term
* @return { uint | NONE } the index that matches the search term
*/
}, {
key: _FIND_INDEX,
value: function value(term) {
(0, _utilMethods.assertIsSearchTerm)(term, "Invalid Search Term for History Cache ".concat(String(term)));
var index = this[_values.PROPS].index;
if (Number.isInteger(term) === true && term > -1) {
if (term > index - 1) {
log.warn("Invalid index, returning most recent");
return index - 1;
}
return term;
}
var _PROPS3 = this[_values.PROPS],
eventIndex = _PROPS3.eventIndex,
tagIndex = _PROPS3.tagIndex,
UIDIndex = _PROPS3.UIDIndex;
var foundIndex = UIDIndex.get(term, _values.NONE);
if (foundIndex !== _values.NONE) return foundIndex;
foundIndex = tagIndex.get(term, _values.NONE);
if (foundIndex !== _values.NONE) return foundIndex;
return eventIndex.get(term, _values.NONE);
}
/**
* @function TRIM
* @description If there is a history limit, will
* trim a single entry from all history objects if that limit
* is exceeded.
* The earliest history object that occurs AFTER the default
* setting is always the one deleted. The default settings are always
* left intact.
* @private
* @return {Object}
* history
* historyEventIndex,
* historyUidIndex
*/
}, {
key: _TRIM,
value: function value(index, limit, history, eventIndex, UIDIndex) {
if (limit === _values.NONE || index < limit) return {
history: history,
eventIndex: eventIndex,
UIDIndex: UIDIndex
};
var dataToDelete = history.get(1);
var event = dataToDelete.get('event');
var UID = dataToDelete.get('UID');
return {
history: history.delete(1),
eventIndex: eventIndex.delete(event),
UIDIndex: UIDIndex.delete(UID)
};
}
}]);
return HistoryCache;
}(_meta.default);
Object.defineProperty(HistoryCache, "FAMILY", {
configurable: true,
enumerable: true,
writable: true,
value: _types.CACHE
});
Object.defineProperty(HistoryCache, "TYPE", {
configurable: true,
enumerable: true,
writable: true,
value: _types.HISTORY_CACHE
});
var _default = HistoryCache;
exports.default = _default;