UNPKG

@vovkasm/redux-persist

Version:

persist and rehydrate redux stores

894 lines (750 loc) 26.6 kB
(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