268 lines
10 KiB
JavaScript
268 lines
10 KiB
JavaScript
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 <code>plugin</code> 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> | S)) | ((key: string, storage: AsyncStorage) => Promise<any>)}
|
|
*/
|
|
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> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
|
|
*/
|
|
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<S>} 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> | 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> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
|
|
*/
|
|
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<S>} 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
|