UNPKG

redux-persist-2

Version:

persist and rehydrate redux stores

169 lines (144 loc) 5.12 kB
import * as constants from './constants' import createAsyncLocalStorage from './defaults/asyncLocalStorage' import stringify from 'json-stringify-safe' import { forEach } from 'lodash' export default function createPersistor (store, config) { // defaults const lastStateInit = config.lastStateInit || {} const stateIterator = config.stateIterator || defaultStateIterator const stateGetter = config.stateGetter || defaultStateGetter const stateSetter = config.stateSetter || defaultStateSetter const serialize = config.serialize || defaultSerialize const deserialize = config.deserialize || defaultDeserialize const blacklist = config.blacklist || [] const whitelist = config.whitelist || false const transforms = config.transforms || [] const debounce = config.debounce || false let storage = config.storage || createAsyncLocalStorage('local') // fallback getAllKeys to `keys` if present (LocalForage compatability) if (storage.keys && !storage.getAllKeys) storage = {...storage, getAllKeys: storage.keys} // initialize stateful values let lastState = lastStateInit let paused = false let purgeMode = false let storesToProcess = [] let timeIterator = null store.subscribe(() => { if (paused) return let state = store.getState() stateIterator(state, (subState, key) => { if (!passWhitelistBlacklist(key)) return if (stateGetter(lastState, key) === stateGetter(state, key)) return if (storesToProcess.indexOf(key) !== -1) return storesToProcess.push(key) }) // time iterator (read: debounce) if (timeIterator === null) { timeIterator = setInterval(() => { if (storesToProcess.length === 0) { clearInterval(timeIterator) timeIterator = null return } let key = storesToProcess[0] let storageKey = createStorageKey(key) let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), stateGetter(store.getState(), key)) if (typeof endState !== 'undefined') storage.setItem(storageKey, serialize(endState), warnIfSetError(key)) storesToProcess.shift() }, debounce) } lastState = state }) function passWhitelistBlacklist (key) { if (whitelist && whitelist.indexOf(key) === -1) return false if (blacklist.indexOf(key) !== -1) return false return true } function adhocRehydrate (incoming, options = {}) { let state = {} if (options.serial) { forEach(incoming, (subState, key) => { try { let data = deserialize(subState) let value = transforms.reduceRight((interState, transformer) => { return transformer.out(interState, key) }, data) state = stateSetter(state, key, value) } catch (err) { if (process.env.NODE_ENV !== 'production') console.warn(`Error rehydrating data for key "${key}"`, subState, err) } }) } else state = incoming store.dispatch(rehydrateAction(state)) return state } function purge (keys) { if (typeof keys === 'undefined') { purgeAll() } else { purgeMode = keys forEach(keys, (key) => { storage.removeItem(createStorageKey(key), warnIfRemoveError(key)) }) } } function purgeAll () { purgeMode = '*' storage.getAllKeys((err, allKeys) => { if (err && process.env.NODE_ENV !== 'production') { console.warn('Error in storage.getAllKeys') } purge(allKeys.filter((key) => key.indexOf(constants.keyPrefix) === 0).map((key) => key.slice(constants.keyPrefix.length))) }) } // return `persistor` return { rehydrate: adhocRehydrate, pause: () => { paused = true }, resume: () => { paused = false }, purge, purgeAll, _getPurgeMode: () => purgeMode } } function warnIfRemoveError (key) { return function removeError (err) { if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) } } } function warnIfSetError (key) { return function setError (err) { if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) } } } function createStorageKey (key) { return constants.keyPrefix + key } function defaultSerialize (data) { return stringify(data, null, null, (k, v) => { if (process.env.NODE_ENV !== 'production') return null throw new Error(` redux-persist: cannot process cyclical state. Consider changing your state structure to have no cycles. Alternatively blacklist the corresponding reducer key. Cycle encounted at key "${k}" with value "${v}". `) }) } function defaultDeserialize (serial) { return JSON.parse(serial) } function rehydrateAction (data) { return { type: constants.REHYDRATE, payload: data } } function defaultStateIterator (collection, callback) { return forEach(collection, callback) } function defaultStateGetter (state, key) { return state[key] } function defaultStateSetter (state, key, value) { state[key] = value return state }