redux-persist-2
Version:
persist and rehydrate redux stores
169 lines (144 loc) • 5.12 kB
JavaScript
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
}