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