import deepmerge from 'deepmerge'; /** * Created by championswimmer on 22/07/17. */ let MockStorage; // @ts-ignore { MockStorage = class { get length() { return Object.keys(this).length; } key(index) { return Object.keys(this)[index]; } setItem(key, data) { this[key] = data.toString(); } getItem(key) { return this[key]; } removeItem(key) { delete this[key]; } clear() { for (let key of Object.keys(this)) { delete this[key]; } } }; } // tslint:disable: variable-name class SimplePromiseQueue { constructor() { this._queue = []; this._flushing = false; } enqueue(promise) { this._queue.push(promise); if (!this._flushing) { return this.flushQueue(); } return Promise.resolve(); } flushQueue() { this._flushing = true; const chain = () => { const nextTask = this._queue.shift(); if (nextTask) { return nextTask.then(chain); } else { this._flushing = false; } }; return Promise.resolve(chain()); } } const options = { replaceArrays: { arrayMerge: (destinationArray, sourceArray, options) => sourceArray }, concatArrays: { arrayMerge: (target, source, options) => target.concat(...source) } }; function merge(into, from, mergeOption) { return deepmerge(into, from, options[mergeOption]); } let FlattedJSON = JSON; /** * A class that implements the vuex persistence. * @type S type of the 'state' inside the store (default: any) */ class VuexPersistence { /** * Create a {@link VuexPersistence} object. * Use the plugin function of this class as a * Vuex plugin. * @param {PersistOptions} options */ constructor(options) { // tslint:disable-next-line:variable-name this._mutex = new SimplePromiseQueue(); /** * Creates a subscriber on the store. automatically is used * when this is used a vuex plugin. Not for manual usage. * @param store */ this.subscriber = (store) => (handler) => store.subscribe(handler); if (typeof options === 'undefined') options = {}; this.key = ((options.key != null) ? options.key : 'vuex'); this.subscribed = false; this.supportCircular = options.supportCircular || false; if (this.supportCircular) { FlattedJSON = require('flatted'); } this.mergeOption = options.mergeOption || 'replaceArrays'; let localStorageLitmus = true; try { window.localStorage.getItem(''); } catch (err) { localStorageLitmus = false; } /** * 1. First, prefer storage sent in optinos * 2. Otherwise, use window.localStorage if available * 3. Finally, try to use MockStorage * 4. None of above? Well we gotta fail. */ if (options.storage) { this.storage = options.storage; } else if (localStorageLitmus) { this.storage = window.localStorage; } else if (MockStorage) { this.storage = new MockStorage(); } else { throw new Error("Neither 'window' is defined, nor 'MockStorage' is available"); } /** * How this works is - * 1. If there is options.reducer function, we use that, if not; * 2. We check options.modules; * 1. If there is no options.modules array, we use entire state in reducer * 2. Otherwise, we create a reducer that merges all those state modules that are * defined in the options.modules[] array * @type {((state: S) => {}) | ((state: S) => S) | ((state: any) => {})} */ this.reducer = ((options.reducer != null) ? options.reducer : ((options.modules == null) ? ((state) => state) : ((state) => options.modules.reduce((a, i) => merge(a, { [i]: state[i] }, this.mergeOption), { /* start empty accumulator*/})))); this.filter = options.filter || ((mutation) => true); this.strictMode = options.strictMode || false; this.RESTORE_MUTATION = function RESTORE_MUTATION(state, savedState) { const mergedState = merge(state, savedState || {}, this.mergeOption); for (const propertyName of Object.keys(mergedState)) { this._vm.$set(state, propertyName, mergedState[propertyName]); } }; this.asyncStorage = options.asyncStorage || false; if (this.asyncStorage) { /** * Async {@link #VuexPersistence.restoreState} implementation * @type {((key: string, storage?: Storage) => * (Promise | S)) | ((key: string, storage: AsyncStorage) => Promise)} */ this.restoreState = ((options.restoreState != null) ? options.restoreState : ((key, storage) => (storage).getItem(key) .then((value) => typeof value === 'string' // If string, parse, or else, just return ? (this.supportCircular ? FlattedJSON.parse(value || '{}') : JSON.parse(value || '{}')) : (value || {})))); /** * Async {@link #VuexPersistence.saveState} implementation * @type {((key: string, state: {}, storage?: Storage) => * (Promise | void)) | ((key: string, state: {}, storage?: Storage) => Promise)} */ this.saveState = ((options.saveState != null) ? options.saveState : ((key, state, storage) => (storage).setItem(key, // Second argument is state _object_ if asyc storage, stringified otherwise // do not stringify the state if the storage type is async (this.asyncStorage ? merge({}, state || {}, this.mergeOption) : (this.supportCircular ? FlattedJSON.stringify(state) : JSON.stringify(state)))))); /** * Async version of plugin * @param {Store} store */ this.plugin = (store) => { /** * For async stores, we're capturing the Promise returned * by the `restoreState()` function in a `restored` property * on the store itself. This would allow app developers to * determine when and if the store's state has indeed been * refreshed. This approach was suggested by GitHub user @hotdogee. * See https://github.com/championswimmer/vuex-persist/pull/118#issuecomment-500914963 * @since 2.1.0 */ store.restored = (this.restoreState(this.key, this.storage)).then((savedState) => { /** * If in strict mode, do only via mutation */ if (this.strictMode) { store.commit('RESTORE_MUTATION', savedState); } else { store.replaceState(merge(store.state, savedState || {}, this.mergeOption)); } this.subscriber(store)((mutation, state) => { if (this.filter(mutation)) { this._mutex.enqueue(this.saveState(this.key, this.reducer(state), this.storage)); } }); this.subscribed = true; }); }; } else { /** * Sync {@link #VuexPersistence.restoreState} implementation * @type {((key: string, storage?: Storage) => * (Promise | S)) | ((key: string, storage: Storage) => (any | string | {}))} */ this.restoreState = ((options.restoreState != null) ? options.restoreState : ((key, storage) => { const value = (storage).getItem(key); if (typeof value === 'string') { // If string, parse, or else, just return return (this.supportCircular ? FlattedJSON.parse(value || '{}') : JSON.parse(value || '{}')); } else { return (value || {}); } })); /** * Sync {@link #VuexPersistence.saveState} implementation * @type {((key: string, state: {}, storage?: Storage) => * (Promise | void)) | ((key: string, state: {}, storage?: Storage) => Promise)} */ this.saveState = ((options.saveState != null) ? options.saveState : ((key, state, storage) => (storage).setItem(key, // Second argument is state _object_ if localforage, stringified otherwise (this.supportCircular ? FlattedJSON.stringify(state) : JSON.stringify(state))))); /** * Sync version of plugin * @param {Store} store */ this.plugin = (store) => { const savedState = this.restoreState(this.key, this.storage); if (this.strictMode) { store.commit('RESTORE_MUTATION', savedState); } else { store.replaceState(merge(store.state, savedState || {}, this.mergeOption)); } this.subscriber(store)((mutation, state) => { if (this.filter(mutation)) { this.saveState(this.key, this.reducer(state), this.storage); } }); this.subscribed = true; }; } } } export default VuexPersistence; export { MockStorage, VuexPersistence }; //# sourceMappingURL=index.js.map