UNPKG

jsonstore-js

Version:

A javascript JSON data store with manifold abilities of data processing

442 lines (435 loc) 16.4 kB
'use strict'; /** * options: * store * copyStore * cacheKeys * localStorage * **/ var utils = require('./utils'); var array = require('./array'); var object = require('./object'); var patchMethods = require('./patch'); var PathListener = require('./PathListener'); var JSON_STORE_CACHE_KEY_PREFIX = 'JSON_STORE_CACHE_KEY_PREFIX'; var emptyFunc = function emptyFunc() {}; function JSONDataStore(options) { options = options || {}; this.initialOptions = utils.copy(options); var store = options.store, copyStore = options.copyStore !== false; this.copyStore = copyStore; this.store = copyStore ? utils.copy(store) : store; this.cacheKeys = this._getStoreKeysMap(options.cacheKeys, this.store); this.flashKeys = this._getStoreKeysMap(options.flashKeys, this.store); this.cacheKeyPrefix = options.cacheKeyPrefix || JSON_STORE_CACHE_KEY_PREFIX; this.localStorage = options.localStorage; // 'do' about attributes this.patches = []; this.relativePatches = []; this.backPatches = []; this.currentPath = []; this.isDoing = false; this.pathListener = new PathListener({ store: this.store, copyStore: copyStore, flashKeys: this.flashKeys }); this.initialMutationActionPath = []; } JSONDataStore.prototype = { _storeUpdated: function _storeUpdated() { this._updateCache(this.initialMutationActionPath[0]); this.pathListener.checkPath(this.initialMutationActionPath); }, _getStoreKeysMap: function _getStoreKeysMap(keys, store) { var keysMap = {}; if (utils.type(keys) === 'array') { keys.forEach(function (key) { if (Object.hasOwnProperty.call(store, key)) { keysMap[key] = true; } }); } return keysMap; }, _getRef: function _getRef(path) { var ref = this.store, i = 0, len = path.length; for (; i < len; i++) { ref = ref[path[i]]; } return ref; }, _detectPath: function _detectPath(path) { var detected = [], ref = this.store, i = 0, len = path.length, key = void 0, keyType = void 0, refType = void 0; for (; i < len; i++) { key = path[i]; keyType = utils.type(key); refType = utils.type(ref); if (refType === 'object') { if (object.hasOwnProperty.call(key, '__value')) { var objKey = object.getObjectKeyByValue(ref, key.__value); if (objKey) { ref = ref[objKey]; detected.push(objKey); } else { return []; } } else if (object.hasOwnProperty.call(ref, key)) { ref = ref[key]; detected.push(key); } else { return []; } } else if (refType === 'array') { if (object.hasOwnProperty.call(key, '__value')) { var index = array.getArrayIndexByValue(ref, key.__value); if (index > -1) { ref = ref[index]; detected.push(index); } else { return []; } } else if (object.hasOwnProperty.call(ref, key)) { ref = ref[key]; detected.push(key); } else { return []; } } } return detected; }, _formatPath: function _formatPath(path, detect) { var pathType = utils.type(path); if (pathType === 'undefined' || pathType === 'null') { path = []; } else if (pathType !== 'array') { path = [path]; } if (detect !== false) { var detected = this._detectPath(path); if (detected.length === path.length) { return detected; } return null; } return path.slice(); }, _moveArrayItem: function _moveArrayItem(path, moveUp) { var fullPath = this._getFullPath(path); if (!fullPath || fullPath.length < 1) return this; var itemIndex = fullPath.pop(), arr = this._getRef(fullPath); if (utils.type(arr) !== 'array') return this; var method = moveUp === true ? 'createMoveUp' : 'createMoveDown', reverseMethod = method === 'createMoveUp' ? 'createMoveDown' : 'createMoveUp'; if (this.isDoing) { this.patches.push(patchMethods[method](fullPath.concat(itemIndex))); this.relativePatches.push(patchMethods[method](this._getRelativePath(fullPath.concat(itemIndex)))); if (moveUp === true && itemIndex > 0 || moveUp !== true && itemIndex < arr.length - 1) { this.backPatches.unshift(patchMethods[reverseMethod](fullPath.concat(moveUp === true ? itemIndex - 1 : itemIndex + 1))); } } if (moveUp === true) { array.moveArrayItemUp(arr, itemIndex); } else { array.moveArrayItemDown(arr, itemIndex); } this._storeUpdated(); return this; }, _getFullPath: function _getFullPath(path) { if (utils.isReferenceType(path) && path.isFull) { return path; } var currentPath = this._formatPath(this.currentPath, false), fullPath = currentPath.concat(this._formatPath(path, false)), formattedFullPath = this._formatPath(fullPath); if (formattedFullPath) { formattedFullPath.isFull = true; } return formattedFullPath; }, _getRelativePath: function _getRelativePath(fullPath) { return fullPath.slice(this.currentPath.length); }, _composeCacheKey: function _composeCacheKey(key) { return this.cacheKeyPrefix + '@' + key; }, _updateCache: function _updateCache(key) { if (this.cacheKeys[key] && this.localStorage && typeof this.localStorage.setItem === 'function') { this.localStorage.setItem(this._composeCacheKey(key), this.get(key)); } }, registerPathListener: function registerPathListener(path, callback, group, check) { path = Array.isArray(path) ? path : [path]; this.pathListener.registerListener(path, callback, group, check); }, removeListenerByPath: function removeListenerByPath(path, callback) { path = Array.isArray(path) ? path : [path]; this.pathListener.removeListenerByPath(path, callback); }, removeListenerByGroup: function removeListenerByGroup(group) { this.pathListener.removeListenerByGroup(group); }, removeAllListeners: function removeAllListeners() { this.pathListener.removeAllListeners(); }, loadCache: function loadCache(success, error) { var _this = this; error = typeof error === 'function' ? error : emptyFunc; if (this.localStorage && typeof this.localStorage.multiGet === 'function') { var cacheKeys = this.initialOptions.cacheKeys || []; var composedKeys = cacheKeys.map(function (key) { return _this._composeCacheKey(key); }); this.localStorage.multiGet(composedKeys, function (cache) { var parsedCache = {}; composedKeys.forEach(function (composedKey, index) { var key = cacheKeys[index]; var cachedValue = cache[composedKey]; _this.set(key, cachedValue === null ? _this.get(key) : cachedValue); parsedCache[key] = cache[composedKey]; }); success(parsedCache); }, error); } else { error('localStorage is undefined'); } }, reInit: function reInit(options) { JSONDataStore.call(this, options || this.initialOptions); return this; }, goTo: function goTo(path, addUp) { if (!this.isDoing) { throw new Error('You are using store.goTo outside store.do!'); } if (addUp === true) { this.currentPath = this._getFullPath(path); } else { this.currentPath = this._formatPath(path); } return this; }, do: function _do(name, action, a, b, c, d, e, f) { var result = {}; this.isDoing = true; if (typeof name === 'function') { name(this, action, a, b, c, d, e, f); } else if (typeof action === 'function') { action(this, a, b, c, d, e, f); } else { throw new Error('Invalid parameter action.'); } // compose result result.patches = this.patches; result.relativePatches = this.relativePatches; result.backPatches = this.backPatches; // reset 'do' about attributes this.patches = []; this.relativePatches = []; this.backPatches = []; this.currentPath = []; this.isDoing = false; return result; }, add: function add(path, value, key, parentPath) { this.initialMutationActionPath = parentPath !== undefined ? parentPath : this._formatPath(path, false); var ref = void 0, refType = void 0; path = this._getFullPath(path); if (!path || !utils.isReferenceType(ref = this._getRef(path)) || (refType = utils.type(ref)) === 'object' && !utils.isCommonKeyType(key)) { return this; } if (this.isDoing) { this.patches.push(patchMethods.createAdd(path, value, key)); this.relativePatches.push(patchMethods.createAdd(this._getRelativePath(path), value, key)); if (refType === 'object') { this.backPatches.unshift(patchMethods.createRemove(path.concat(key))); } else { this.backPatches.unshift(patchMethods.createUpdate(path, this.get(path), true)); } } if (refType === 'object') { ref[key] = value; } else { var index = array.parseArrayIndex(key); if (index !== undefined) { ref.splice(index, 0, value); } else { ref.push(value); } } this._storeUpdated(); return this; }, remove: function remove(path, parentPath) { this.initialMutationActionPath = parentPath !== undefined ? parentPath : this._formatPath(path, false); if (!(path = this._getFullPath(path))) return this; if (this.isDoing) { this.patches.push(patchMethods.createRemove(path)); this.relativePatches.push(patchMethods.createRemove(this._getRelativePath(path))); this.backPatches.unshift(patchMethods.createUpdate(path, this.get(path), true)); } if (path.length < 1) { this.store = undefined; return this; } var lastKey = path.pop(), ref = this._getRef(path), refType = utils.type(ref); if (refType === 'array') { ref.splice(lastKey, 1); } else if (refType === 'object') { delete ref[lastKey]; } this._storeUpdated(); return this; }, update: function update(path, value, forceUpdate, parentPath) { this.initialMutationActionPath = parentPath !== undefined ? parentPath : this._formatPath(path, false); path = this._formatPath(path, false); var lastKey = void 0, fullPath = this._getFullPath(path); if (fullPath) { if (this.isDoing) { this.patches.push(patchMethods.createUpdate(fullPath, value)); this.relativePatches.push(patchMethods.createUpdate(this._getRelativePath(fullPath), value)); this.backPatches.unshift(patchMethods.createUpdate(fullPath, this.get(fullPath))); } lastKey = fullPath.pop(); if (lastKey !== undefined) { this._getRef(fullPath)[lastKey] = value; } else { this.store = value; } this._storeUpdated(); return this; } else if (forceUpdate === true && path.length > 0) { lastKey = path.pop(); return this.add(path, value, lastKey, this.initialMutationActionPath); } return this; }, set: function set(path, value) { return this.update(path, value, true, this._formatPath(path, false)); }, moveUp: function moveUp(path) { this.initialMutationActionPath = this._formatPath(path, false); return this._moveArrayItem(path, true); }, moveDown: function moveDown(path) { this.initialMutationActionPath = this._formatPath(path, false); return this._moveArrayItem(path); }, moveTo: function moveTo(from, to, key) { var parentFromPath = this._formatPath(from, false), parentToPath = this._formatPath(to, false); from = this._getFullPath(from); to = this._getFullPath(to); if (!from || !to || !utils.isReferenceType(this._getRef(to))) return this; this.add(to, this._getRef(from), key, parentToPath); this.remove(from, parentFromPath); return this; }, exchange: function exchange(from, to) { var parentFromPath = this._formatPath(from, false), parentToPath = this._formatPath(to, false); from = this._getFullPath(from); to = this._getFullPath(to); if (from && to) { var fromRef = this._getRef(from), toRef = this.get(to); this.update(from, toRef, false, parentFromPath); this.update(to, fromRef, false, parentToPath); } return this; }, extendObject: function extendObject(path, a, b, c, d, e, f) { this.initialMutationActionPath = this._formatPath(path, false); var ref = void 0; if (!(path = this._getFullPath(path)) || utils.type(ref = this._getRef(path)) !== 'object') return this; if (this.isDoing) { this.patches.push(patchMethods.createExtendObject.apply(this, arguments)); this.relativePatches.push(patchMethods.createExtendObject(this._getRelativePath(path), a, b, c, d, e, f)); this.backPatches.push(patchMethods.createUpdate(path, this.get(path))); } object.extend(ref, a, b, c, d, e, f); this._storeUpdated(); return this; }, spreadArray: function spreadArray(path, begin, infilling, simpleInfilling, count) { this.initialMutationActionPath = this._formatPath(path, false); var ref = void 0; if (!(path = this._getFullPath(path)) || utils.type(ref = this._getRef(path)) !== 'array') { return this; } begin = typeof begin === 'number' ? begin : ref.length; if (!(utils.type(begin) === 'number')) return this; if (this.isDoing) { this.patches.push(patchMethods.createSpreadArray(path, begin, infilling, simpleInfilling, count)); this.relativePatches.push(patchMethods.createSpreadArray(this._getRelativePath(path), begin, infilling, simpleInfilling, count)); this.backPatches.unshift(patchMethods.createUpdate(path, this.get(path))); } array.spreadArray(ref, begin, infilling, simpleInfilling, count); this._storeUpdated(); return this; }, spread2dArrayRow: function spread2dArrayRow(path, begin, rows, simpleInfilling, count) { this.initialMutationActionPath = this._formatPath(path, false); var ref = void 0; if (!(path = this._getFullPath(path)) || !array.is2dArray(ref = this._getRef(path)) || !(utils.type(begin) === 'number')) { return this; } begin = typeof begin === 'number' ? begin : ref.length; if (!(utils.type(begin) === 'number')) return this; if (this.isDoing) { this.patches.push(patchMethods.createSpread2dArrayRow(path, begin, rows, simpleInfilling, count)); this.relativePatches.push(patchMethods.createSpread2dArrayRow(this._getRelativePath(path), begin, rows, simpleInfilling, count)); this.backPatches.unshift(patchMethods.createUpdate(path, this.get(path))); } array.spread2dArrayRow(ref, begin, rows, simpleInfilling, count); this._storeUpdated(); return this; }, spread2dArrayCol: function spread2dArrayCol(path, begin, cols, simpleInfilling, count) { this.initialMutationActionPath = this._formatPath(path, false); var ref = void 0; if (!(path = this._getFullPath(path)) || !array.is2dArray(ref = this._getRef(path)) || !(utils.type(begin) === 'number')) { return this; } begin = typeof begin === 'number' ? begin : ref[0].length; if (!(utils.type(begin) === 'number')) return this; if (this.isDoing) { this.patches.push(patchMethods.createSpread2dArrayCol(path, begin, cols, simpleInfilling, count)); this.relativePatches.push(patchMethods.createSpread2dArrayCol(this._getRelativePath(path), begin, cols, simpleInfilling, count)); this.backPatches.unshift(patchMethods.createUpdate(path, this.get(path))); } array.spread2dArrayCol(ref, begin, cols, simpleInfilling, count); this._storeUpdated(); return this; }, get: function get(path) { if (path = this._getFullPath(path)) { return this.copyStore ? utils.copy(this._getRef(path)) : this._getRef(path); } }, patch: function patch() { throw new Error('This method is deprecated, use JSONStore.patch instead.'); }, applyPatch: function applyPatch(patches) { patches = utils.type(patches) === 'array' ? patches : [patches]; patches.forEach(function (patch) { this[patch.type].apply(this, patch.args); }.bind(this)); return this; } }; JSONDataStore.Patch = patchMethods; module.exports = JSONDataStore;