UNPKG

@charliehess/redux-persist

Version:
189 lines (159 loc) 5.65 kB
import { KEY_PREFIX, REHYDRATE } from './constants' import createAsyncLocalStorage from './defaults/asyncLocalStorage' import purgeStoredState from './purgeStoredState' import stringify from 'json-stringify-safe' export default function createPersistor (store, config) { // defaults const serializer = config.serialize === false ? (data) => data : defaultSerializer const deserializer = config.serialize === false ? (data) => data : defaultDeserializer const blacklist = config.blacklist || [] const whitelist = config.whitelist || false const transforms = config.transforms || [] const debounce = config.debounce || false const keyPrefix = config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX const asyncTransforms = config.asyncTransforms || false // pluggable state shape (e.g. immutablejs) const stateInit = config._stateInit || {} const stateIterator = config._stateIterator || defaultStateIterator const stateGetter = config._stateGetter || defaultStateGetter const stateSetter = config._stateSetter || defaultStateSetter // storage with keys -> getAllKeys for localForage support let storage = config.storage || createAsyncLocalStorage('local') if (storage.keys && !storage.getAllKeys) { storage.getAllKeys = storage.keys } // initialize stateful values let lastState = stateInit let paused = false let storesToProcess = [] let timeIterator = null let isTransforming = false 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 (isTransforming) { return } if (storesToProcess.length === 0) { clearInterval(timeIterator) timeIterator = null return } let key = storesToProcess.shift() let storageKey = createStorageKey(key) let currentState = stateGetter(store.getState(), key) isTransforming = true function assignKeyInStorage (endState) { if (typeof endState === 'undefined') return storage.setItem(storageKey, serializer(endState), warnIfSetError(key)) isTransforming = false } applyInboundTransforms(currentState, key, assignKeyInStorage) }, debounce) } lastState = state }) function applyInboundTransforms (currentState, key, assignKeyInStorage) { if (asyncTransforms) { transforms.reduce((promise, transformer) => { return promise .then(() => Promise.resolve(transformer.in(currentState, key)).then((subState) => { currentState = subState return currentState })) .catch(console.error) }, Promise.resolve()).then((result) => { assignKeyInStorage(result) }) } else { let result = transforms.reduce((subState, transformer) => { return transformer.in(subState, key) }, currentState) assignKeyInStorage(result) } } 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) { if (asyncTransforms) { throw new Error(`Async transforms not implemented with serial: true`) } stateIterator(incoming, (subState, key) => { try { let data = deserializer(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 createStorageKey (key) { return `${keyPrefix}${key}` } // return `persistor` return { rehydrate: adhocRehydrate, pause: () => { paused = true }, resume: () => { paused = false }, purge: (keys) => purgeStoredState({storage, keyPrefix}, keys) } } function warnIfSetError (key) { return function setError (err) { if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) } } } function defaultSerializer (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 defaultDeserializer (serial) { return JSON.parse(serial) } function rehydrateAction (data) { return { type: REHYDRATE, payload: data } } function defaultStateIterator (collection, callback) { return Object.keys(collection).forEach((key) => callback(collection[key], key)) } function defaultStateGetter (state, key) { return state[key] } function defaultStateSetter (state, key, value) { state[key] = value return state }