You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
3.9 KiB
142 lines
3.9 KiB
// @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) |
|
}
|
|
|