UNPKG

@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
"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;