import { KEY_PREFIX, REHYDRATE } from './constants'; // @TODO remove once flow < 0.63 support is no longer required. export default function createPersistoid(config) { // defaults var blacklist = config.blacklist || null; var whitelist = config.whitelist || null; var transforms = config.transforms || []; var throttle = config.throttle || 0; var storageKey = "".concat(config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX).concat(config.key); var storage = config.storage; var serialize; if (config.serialize === false) { serialize = function serialize(x) { return x; }; } else if (typeof config.serialize === 'function') { serialize = config.serialize; } else { serialize = defaultSerialize; } var writeFailHandler = config.writeFailHandler || null; // initialize stateful values var lastState = {}; var stagedState = {}; var keysToProcess = []; var timeIterator = null; var writePromise = null; var update = function update(state) { // add any changed keys to the queue Object.keys(state).forEach(function (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(function (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; } var key = keysToProcess.shift(); var endState = transforms.reduce(function (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(function (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); } } var flush = function flush() { while (keysToProcess.length !== 0) { processNextKey(); } return writePromise || Promise.resolve(); }; // return `persistoid` return { update: update, flush: flush }; } // @NOTE in the future this may be exposed via config function defaultSerialize(data) { return JSON.stringify(data); }