@vovkasm/redux-persist
Version:
persist and rehydrate redux stores
894 lines (750 loc) • 26.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.ReduxPersist = global.ReduxPersist || {})));
}(this, (function (exports) { 'use strict';
var KEY_PREFIX = 'reduxPersist:';
var REHYDRATE = 'persist/REHYDRATE';
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();
/** Built-in value references. */
var Symbol$1 = root.Symbol;
/** Used for built-in method references. */
var objectProto$1 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$1.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto$1.toString;
/** Built-in value references. */
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty$1.call(value, symToStringTag$1),
tag = value[symToStringTag$1];
try {
value[symToStringTag$1] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag$1] = tag;
} else {
delete value[symToStringTag$1];
}
}
return result;
}
/** Used for built-in method references. */
var objectProto$2 = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString$1 = objectProto$2.toString;
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString$1.call(value);
}
/** `Object#toString` result references. */
var nullTag = '[object Null]';
var undefinedTag = '[object Undefined]';
/** Built-in value references. */
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? getRawTag(value)
: objectToString(value);
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/** Built-in value references. */
var getPrototype = overArg(Object.getPrototypeOf, Object);
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/** `Object#toString` result references. */
var objectTag = '[object Object]';
/** Used for built-in method references. */
var funcProto = Function.prototype;
var objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to infer the `Object` constructor. */
var objectCtorString = funcToString.call(Object);
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
return false;
}
var proto = getPrototype(value);
if (proto === null) {
return true;
}
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor == 'function' && Ctor instanceof Ctor &&
funcToString.call(Ctor) == objectCtorString;
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var _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;
};
function isStatePlainEnough(a) {
// isPlainObject + duck type not immutable
if (!a) return false;
if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) !== 'object') return false;
if (typeof a.asMutable === 'function') return false;
if (!isPlainObject(a)) return false;
return true;
}
function autoRehydrate() {
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var stateReconciler = config.stateReconciler || defaultStateReconciler;
return function (next) {
return function (reducer, initialState, enhancer) {
var store = next(liftReducer(reducer), initialState, enhancer);
return _extends({}, store, {
replaceReducer: function replaceReducer(reducer) {
return store.replaceReducer(liftReducer(reducer));
}
});
};
};
function liftReducer(reducer) {
var rehydrated = false;
var preRehydrateActions = [];
return function (state, action) {
if (action.type !== REHYDRATE) {
if (config.log && !rehydrated) preRehydrateActions.push(action); // store pre-rehydrate actions for debugging
return reducer(state, action);
} else {
if (config.log && !rehydrated) logPreRehydrate(preRehydrateActions);
rehydrated = true;
var inboundState = action.payload;
var reducedState = reducer(state, action);
return stateReconciler(state, inboundState, reducedState, config.log);
}
};
}
}
function logPreRehydrate(preRehydrateActions) {
var concernedActions = preRehydrateActions.slice(1);
if (concernedActions.length > 0) {
console.log('\n redux-persist/autoRehydrate: %d actions were fired before rehydration completed. This can be a symptom of a race\n condition where the rehydrate action may overwrite the previously affected state. Consider running these actions\n after rehydration:\n ', concernedActions.length, concernedActions);
}
}
function defaultStateReconciler(state, inboundState, reducedState, log) {
var newState = _extends({}, reducedState);
Object.keys(inboundState).forEach(function (key) {
// if initialState does not have key, skip auto rehydration
if (!state.hasOwnProperty(key)) return;
// if initial state is an object but inbound state is null/undefined, skip
if (_typeof(state[key]) === 'object' && !inboundState[key]) {
if (log) console.log('redux-persist/autoRehydrate: sub state for key `%s` is falsy but initial state is an object, skipping autoRehydrate.', key);
return;
}
// if reducer modifies substate, skip auto rehydration
if (state[key] !== reducedState[key]) {
if (log) console.log('redux-persist/autoRehydrate: sub state for key `%s` modified, skipping autoRehydrate.', key);
newState[key] = reducedState[key];
return;
}
// otherwise take the inboundState
if (isStatePlainEnough(inboundState[key]) && isStatePlainEnough(state[key])) newState[key] = _extends({}, state[key], inboundState[key]); // shallow merge
else newState[key] = inboundState[key]; // hard set
if (log) console.log('redux-persist/autoRehydrate: key `%s`, rehydrated to ', key, newState[key]);
});
return newState;
}
var hasNativeSupport = typeof global !== 'undefined' && typeof global.setImmediate !== 'undefined';
var setImmediate = hasNativeSupport ? function (fn, ms) {
return global.setImmediate(fn, ms);
} : function (fn, ms) {
return setTimeout(fn, ms);
};
var noStorage = function noStorage() {
/* noop */return null;
};
{
noStorage = function noStorage() {
console.error('redux-persist asyncLocalStorage requires a global localStorage object. Either use a different storage backend or if this is a universal redux application you probably should conditionally persist like so: https://gist.github.com/rt2zz/ac9eb396793f95ff3c3b');
return null;
};
}
function _hasStorage(storageType) {
if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(storageType in window)) {
return false;
}
try {
var storage = window[storageType];
var testKey = 'redux-persist ' + storageType + ' test';
storage.setItem(testKey, 'test');
storage.getItem(testKey);
storage.removeItem(testKey);
} catch (e) {
console.warn('redux-persist ' + storageType + ' test failed, persistence will be disabled.');
return false;
}
return true;
}
function hasLocalStorage() {
return _hasStorage('localStorage');
}
function hasSessionStorage() {
return _hasStorage('sessionStorage');
}
function getStorage(type) {
if (type === 'local') {
return hasLocalStorage() ? window.localStorage : { getItem: noStorage, setItem: noStorage, removeItem: noStorage, getAllKeys: noStorage };
}
if (type === 'session') {
return hasSessionStorage() ? window.sessionStorage : { getItem: noStorage, setItem: noStorage, removeItem: noStorage, getAllKeys: noStorage };
}
}
var createAsyncLocalStorage = function (type, config) {
var storage = getStorage(type);
return {
getAllKeys: function getAllKeys(cb) {
return new Promise(function (resolve, reject) {
try {
var keys = [];
for (var i = 0; i < storage.length; i++) {
keys.push(storage.key(i));
}
setImmediate(function () {
cb && cb(null, keys);
resolve(keys);
});
} catch (e) {
cb && cb(e);
reject(e);
}
});
},
getItem: function getItem(key, cb) {
return new Promise(function (resolve, reject) {
try {
var s = storage.getItem(key);
setImmediate(function () {
cb && cb(null, s);
resolve(s);
});
} catch (e) {
cb && cb(e);
reject(e);
}
});
},
setItem: function setItem(key, string, cb) {
return new Promise(function (resolve, reject) {
try {
storage.setItem(key, string);
setImmediate(function () {
cb && cb(null);
resolve();
});
} catch (e) {
cb && cb(e);
reject(e);
}
});
},
removeItem: function removeItem(key, cb) {
return new Promise(function (resolve, reject) {
try {
storage.removeItem(key);
setImmediate(function () {
cb && cb(null);
resolve();
});
} catch (e) {
cb && cb(e);
reject(e);
}
});
}
};
};
function purgeStoredState(config, keys) {
var storage = config.storage;
var keyPrefix = config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX;
// basic validation
if (Array.isArray(config)) throw new Error('redux-persist: purgeStoredState requires config as a first argument (found array). An array of keys is the optional second argument.');
if (!storage) throw new Error('redux-persist: config.storage required in purgeStoredState');
if (typeof keys === 'undefined') {
// if keys is not defined, purge all keys
return new Promise(function (resolve, reject) {
storage.getAllKeys(function (err, allKeys) {
if (err) {
console.warn('redux-persist: error during purgeStoredState in storage.getAllKeys');
reject(err);
} else {
resolve(purgeStoredState(config, allKeys.filter(function (key) {
return key.indexOf(keyPrefix) === 0;
}).map(function (key) {
return key.slice(keyPrefix.length);
})));
}
});
});
} else {
// otherwise purge specified keys
return Promise.all(keys.map(function (key) {
return storage.removeItem('' + keyPrefix + key, warnIfRemoveError(key));
}));
}
}
function warnIfRemoveError(key) {
return function removeError(err) {
if (err && "development" !== 'production') {
console.warn('Error storing data for key:', key, err);
}
};
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var stringify_1 = createCommonjsModule(function (module, exports) {
exports = module.exports = stringify;
exports.getSerialize = serializer;
function stringify(obj, replacer, spaces, cycleReplacer) {
return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
}
function serializer(replacer, cycleReplacer) {
var stack = [], keys = [];
if (cycleReplacer == null) cycleReplacer = function(key, value) {
if (stack[0] === value) return "[Circular ~]"
return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
};
return function(key, value) {
if (stack.length > 0) {
var thisPos = stack.indexOf(this);
~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value);
}
else stack.push(value);
return replacer == null ? value : replacer.call(this, key, value)
}
}
});
function createPersistor(store, config) {
// defaults
var serializer = config.serialize === false ? function (data) {
return data;
} : defaultSerializer;
var deserializer = config.serialize === false ? function (data) {
return data;
} : defaultDeserializer;
var blacklist = config.blacklist || [];
var whitelist = config.whitelist || false;
var transforms = config.transforms || [];
var debounce = config.debounce || false;
var keyPrefix = config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX;
// pluggable state shape (e.g. immutablejs)
var stateInit = config._stateInit || {};
var stateIterator = config._stateIterator || defaultStateIterator;
var stateGetter = config._stateGetter || defaultStateGetter;
var stateSetter = config._stateSetter || defaultStateSetter;
// storage with keys -> getAllKeys for localForage support
var storage = config.storage || createAsyncLocalStorage('local');
if (storage.keys && !storage.getAllKeys) {
storage.getAllKeys = storage.keys;
}
// initialize stateful values
var lastState = stateInit;
var paused = false;
var storesToProcess = [];
var timeIterator = null;
store.subscribe(function () {
if (paused) return;
var state = store.getState();
findStoresToProcess(state).forEach(function (key) {
if (storesToProcess.indexOf(key) !== -1) return;
storesToProcess.push(key);
});
var len = storesToProcess.length;
// time iterator (read: debounce)
if (timeIterator === null) {
timeIterator = setInterval(function () {
if (paused && len === storesToProcess.length || storesToProcess.length === 0) {
clearInterval(timeIterator);
timeIterator = null;
return;
}
var key = storesToProcess.shift();
persistCurrentStateForKey(state, key);
}, debounce);
}
lastState = state;
});
function findStoresToProcess(state) {
var keys = [];
stateIterator(state, function (subState, key) {
if (!passWhitelistBlacklist(key)) return;
var newState = stateGetter(state, key);
if (stateGetter(lastState, key) === newState) return;
keys.push(key);
});
return keys;
}
function persistCurrentStateForKey(state, key) {
var storageKey = createStorageKey(key);
var currentState = stateGetter(state, key);
var endState = transforms.reduce(function (subState, transformer) {
return transformer.in(subState, key);
}, currentState);
if (typeof endState === 'undefined') return null;
return storage.setItem(storageKey, serializer(endState), warnIfSetError(key));
}
function persistCurrentState() {
var state = store.getState();
var promises = storesToProcess.map(function (k) {
return persistCurrentStateForKey(state, k);
});
storesToProcess.splice(0);
return promises;
}
function passWhitelistBlacklist(key) {
if (whitelist && whitelist.indexOf(key) === -1) return false;
if (blacklist.indexOf(key) !== -1) return false;
return true;
}
function adhocRehydrate(incoming) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var state = {};
if (options.serial) {
stateIterator(incoming, function (subState, key) {
try {
var data = deserializer(subState);
var value = transforms.reduceRight(function (interState, transformer) {
return transformer.out(interState, key);
}, data);
state = stateSetter(state, key, value);
} catch (err) {
console.warn('Error rehydrating data for key "' + key + '"', subState, err);
}
});
} else state = incoming;
store.dispatch(rehydrateAction(state));
return state;
}
function createStorageKey(key) {
return '' + keyPrefix + key;
}
// return `persistor`
return {
rehydrate: adhocRehydrate,
pause: function pause() {
paused = true;
},
resume: function resume() {
paused = false;
},
purge: function purge(keys) {
return purgeStoredState({ storage: storage, keyPrefix: keyPrefix }, keys);
},
flush: persistCurrentState
};
}
function warnIfSetError(key) {
return function setError(err) {
if (err && "development" !== 'production') {
console.warn('Error storing data for key:', key, err);
}
};
}
function defaultSerializer(data) {
return stringify_1(data, null, null, function (k, v) {
return null;
throw new Error('\n redux-persist: cannot process cyclical state.\n Consider changing your state structure to have no cycles.\n Alternatively blacklist the corresponding reducer key.\n Cycle encounted at key "' + k + '" with value "' + v + '".\n ');
});
}
function defaultDeserializer(serial) {
return JSON.parse(serial);
}
function rehydrateAction(data) {
return {
type: REHYDRATE,
payload: data
};
}
function defaultStateIterator(collection, callback) {
return Object.keys(collection).forEach(function (key) {
return callback(collection[key], key);
});
}
function defaultStateGetter(state, key) {
return state[key];
}
function defaultStateSetter(state, key, value) {
state[key] = value;
return state;
}
function createTransform(inbound, outbound) {
var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var whitelist = config.whitelist || null;
var blacklist = config.blacklist || null;
function whitelistBlacklistCheck(key) {
if (whitelist && whitelist.indexOf(key) === -1) return true;
if (blacklist && blacklist.indexOf(key) !== -1) return true;
return false;
}
return {
in: function _in(state, key) {
return !whitelistBlacklistCheck(key) && inbound ? inbound(state, key) : state;
},
out: function out(state, key) {
return !whitelistBlacklistCheck(key) && outbound ? outbound(state, key) : state;
}
};
}
function getStoredState(config, onComplete) {
var storage = config.storage || createAsyncLocalStorage('local');
var deserializer = config.serialize === false ? function (data) {
return data;
} : defaultDeserializer$1;
var blacklist = config.blacklist || [];
var whitelist = config.whitelist || false;
var transforms = config.transforms || [];
var keyPrefix = config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX;
// fallback getAllKeys to `keys` if present (LocalForage compatability)
if (storage.keys && !storage.getAllKeys) storage = _extends({}, storage, { getAllKeys: storage.keys });
var restoredState = {};
var completionCount = 0;
storage.getAllKeys(function (err, allKeys) {
if (err) {
console.warn('redux-persist/getStoredState: Error in storage.getAllKeys');
complete(err);
}
var persistKeys = allKeys.filter(function (key) {
return key.indexOf(keyPrefix) === 0;
}).map(function (key) {
return key.slice(keyPrefix.length);
});
var keysToRestore = persistKeys.filter(passWhitelistBlacklist);
var restoreCount = keysToRestore.length;
if (restoreCount === 0) complete(null, restoredState);
keysToRestore.forEach(function (key) {
storage.getItem(createStorageKey(key), function (err, serialized) {
if (err && "development" !== 'production') console.warn('redux-persist/getStoredState: Error restoring data for key:', key, err);else restoredState[key] = rehydrate(key, serialized);
completionCount += 1;
if (completionCount === restoreCount) complete(null, restoredState);
});
});
});
function rehydrate(key, serialized) {
var state = null;
try {
var data = deserializer(serialized);
state = transforms.reduceRight(function (subState, transformer) {
return transformer.out(subState, key);
}, data);
} catch (err) {
console.warn('redux-persist/getStoredState: Error restoring data for key:', key, err);
}
return state;
}
function complete(err, restoredState) {
onComplete(err, restoredState);
}
function passWhitelistBlacklist(key) {
if (whitelist && whitelist.indexOf(key) === -1) return false;
if (blacklist.indexOf(key) !== -1) return false;
return true;
}
function createStorageKey(key) {
return '' + keyPrefix + key;
}
if (typeof onComplete !== 'function' && !!Promise) {
return new Promise(function (resolve, reject) {
onComplete = function onComplete(err, restoredState) {
if (err) reject(err);else resolve(restoredState);
};
});
}
}
function defaultDeserializer$1(serial) {
return JSON.parse(serial);
}
function persistStore(store) {
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var onComplete = arguments[2];
// defaults
// @TODO remove shouldRestore
var shouldRestore = !config.skipRestore;
if ("development" !== 'production' && config.skipRestore) console.warn('redux-persist: config.skipRestore has been deprecated. If you want to skip restoration use `createPersistor` instead');
var purgeKeys = null;
// create and pause persistor
var persistor = createPersistor(store, config);
persistor.pause();
// restore
if (shouldRestore) {
setImmediate(function () {
getStoredState(config, function (err, restoredState) {
if (err) {
complete(err);
return;
}
// do not persist state for purgeKeys
if (purgeKeys) {
if (purgeKeys === '*') restoredState = {};else purgeKeys.forEach(function (key) {
return delete restoredState[key];
});
}
try {
store.dispatch(rehydrateAction$1(restoredState, err));
} finally {
complete(err, restoredState);
}
});
});
} else setImmediate(complete);
function complete(err, restoredState) {
persistor.resume();
onComplete && onComplete(err, restoredState);
}
return _extends({}, persistor, {
purge: function purge(keys) {
purgeKeys = keys || '*';
return persistor.purge(keys);
}
});
}
function rehydrateAction$1(payload) {
var error = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return {
type: REHYDRATE,
payload: payload,
error: error
};
}
// @TODO remove in v5
var deprecated = function deprecated(cb, cb2, cb3) {
console.error('redux-persist: this method of importing storages has been removed. instead use `import { asyncLocalStorage } from "redux-persist/storages"`');
if (typeof cb === 'function') cb();
if (typeof cb2 === 'function') cb2();
if (typeof cb3 === 'function') cb3();
};
var deprecatedStorage = { getAllKeys: deprecated, getItem: deprecated, setItem: deprecated, removeItem: deprecated };
var storages = {
asyncLocalStorage: deprecatedStorage,
asyncSessionStorage: deprecatedStorage
};
exports.autoRehydrate = autoRehydrate;
exports.createPersistor = createPersistor;
exports.createTransform = createTransform;
exports.getStoredState = getStoredState;
exports.persistStore = persistStore;
exports.purgeStoredState = purgeStoredState;
exports.storages = storages;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=redux-persist.js.map