123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- // @flow
- import { KEY_PREFIX, REHYDRATE } from './constants'
- import type { Persistoid, PersistConfig, Transform } from './types'
- type IntervalID = any // @TODO remove once flow < 0.63 support is no longer required.
- export default function createPersistoid(config: PersistConfig): Persistoid {
- // defaults
- const blacklist: ?Array<string> = config.blacklist || null
- const whitelist: ?Array<string> = config.whitelist || null
- const transforms = config.transforms || []
- const throttle = config.throttle || 0
- const storageKey = `${
- config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX
- }${config.key}`
- const storage = config.storage
- let serialize
- if (config.serialize === false) {
- serialize = x => x
- } else if (typeof config.serialize === 'function') {
- serialize = config.serialize
- } else {
- serialize = defaultSerialize
- }
- const writeFailHandler = config.writeFailHandler || null
- // initialize stateful values
- let lastState = {}
- let stagedState = {}
- let keysToProcess = []
- let timeIterator: ?IntervalID = null
- let writePromise = null
- const update = (state: Object) => {
- // add any changed keys to the queue
- Object.keys(state).forEach(key => {
- if (!passWhitelistBlacklist(key)) return // is keyspace ignored? noop
- if (lastState[key] === state[key]) return // value unchanged? noop
- if (keysToProcess.indexOf(key) !== -1) return // is key already queued? noop
- keysToProcess.push(key) // add key to queue
- })
- //if any key is missing in the new state which was present in the lastState,
- //add it for processing too
- Object.keys(lastState).forEach(key => {
- if (
- state[key] === undefined &&
- passWhitelistBlacklist(key) &&
- keysToProcess.indexOf(key) === -1 &&
- lastState[key] !== undefined
- ) {
- keysToProcess.push(key)
- }
- })
- // start the time iterator if not running (read: throttle)
- if (timeIterator === null) {
- timeIterator = setInterval(processNextKey, throttle)
- }
- lastState = state
- }
- function processNextKey() {
- if (keysToProcess.length === 0) {
- if (timeIterator) clearInterval(timeIterator)
- timeIterator = null
- return
- }
- let key = keysToProcess.shift()
- let endState = transforms.reduce((subState, transformer) => {
- return transformer.in(subState, key, lastState)
- }, lastState[key])
- if (endState !== undefined) {
- try {
- stagedState[key] = serialize(endState)
- } catch (err) {
- console.error(
- 'redux-persist/createPersistoid: error serializing state',
- err
- )
- }
- } else {
- //if the endState is undefined, no need to persist the existing serialized content
- delete stagedState[key]
- }
- if (keysToProcess.length === 0) {
- writeStagedState()
- }
- }
- function writeStagedState() {
- // cleanup any removed keys just before write.
- Object.keys(stagedState).forEach(key => {
- if (lastState[key] === undefined) {
- delete stagedState[key]
- }
- })
- writePromise = storage
- .setItem(storageKey, serialize(stagedState))
- .catch(onWriteFail)
- }
- function passWhitelistBlacklist(key) {
- if (whitelist && whitelist.indexOf(key) === -1 && key !== '_persist')
- return false
- if (blacklist && blacklist.indexOf(key) !== -1) return false
- return true
- }
- function onWriteFail(err) {
- // @TODO add fail handlers (typically storage full)
- if (writeFailHandler) writeFailHandler(err)
- if (err && process.env.NODE_ENV !== 'production') {
- console.error('Error storing data', err)
- }
- }
- const flush = () => {
- while (keysToProcess.length !== 0) {
- processNextKey()
- }
- return writePromise || Promise.resolve()
- }
- // return `persistoid`
- return {
- update,
- flush,
- }
- }
- // @NOTE in the future this may be exposed via config
- function defaultSerialize(data) {
- return JSON.stringify(data)
- }
|