createPersistoid.js.flow 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // @flow
  2. import { KEY_PREFIX, REHYDRATE } from './constants'
  3. import type { Persistoid, PersistConfig, Transform } from './types'
  4. type IntervalID = any // @TODO remove once flow < 0.63 support is no longer required.
  5. export default function createPersistoid(config: PersistConfig): Persistoid {
  6. // defaults
  7. const blacklist: ?Array<string> = config.blacklist || null
  8. const whitelist: ?Array<string> = config.whitelist || null
  9. const transforms = config.transforms || []
  10. const throttle = config.throttle || 0
  11. const storageKey = `${
  12. config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX
  13. }${config.key}`
  14. const storage = config.storage
  15. let serialize
  16. if (config.serialize === false) {
  17. serialize = x => x
  18. } else if (typeof config.serialize === 'function') {
  19. serialize = config.serialize
  20. } else {
  21. serialize = defaultSerialize
  22. }
  23. const writeFailHandler = config.writeFailHandler || null
  24. // initialize stateful values
  25. let lastState = {}
  26. let stagedState = {}
  27. let keysToProcess = []
  28. let timeIterator: ?IntervalID = null
  29. let writePromise = null
  30. const update = (state: Object) => {
  31. // add any changed keys to the queue
  32. Object.keys(state).forEach(key => {
  33. if (!passWhitelistBlacklist(key)) return // is keyspace ignored? noop
  34. if (lastState[key] === state[key]) return // value unchanged? noop
  35. if (keysToProcess.indexOf(key) !== -1) return // is key already queued? noop
  36. keysToProcess.push(key) // add key to queue
  37. })
  38. //if any key is missing in the new state which was present in the lastState,
  39. //add it for processing too
  40. Object.keys(lastState).forEach(key => {
  41. if (
  42. state[key] === undefined &&
  43. passWhitelistBlacklist(key) &&
  44. keysToProcess.indexOf(key) === -1 &&
  45. lastState[key] !== undefined
  46. ) {
  47. keysToProcess.push(key)
  48. }
  49. })
  50. // start the time iterator if not running (read: throttle)
  51. if (timeIterator === null) {
  52. timeIterator = setInterval(processNextKey, throttle)
  53. }
  54. lastState = state
  55. }
  56. function processNextKey() {
  57. if (keysToProcess.length === 0) {
  58. if (timeIterator) clearInterval(timeIterator)
  59. timeIterator = null
  60. return
  61. }
  62. let key = keysToProcess.shift()
  63. let endState = transforms.reduce((subState, transformer) => {
  64. return transformer.in(subState, key, lastState)
  65. }, lastState[key])
  66. if (endState !== undefined) {
  67. try {
  68. stagedState[key] = serialize(endState)
  69. } catch (err) {
  70. console.error(
  71. 'redux-persist/createPersistoid: error serializing state',
  72. err
  73. )
  74. }
  75. } else {
  76. //if the endState is undefined, no need to persist the existing serialized content
  77. delete stagedState[key]
  78. }
  79. if (keysToProcess.length === 0) {
  80. writeStagedState()
  81. }
  82. }
  83. function writeStagedState() {
  84. // cleanup any removed keys just before write.
  85. Object.keys(stagedState).forEach(key => {
  86. if (lastState[key] === undefined) {
  87. delete stagedState[key]
  88. }
  89. })
  90. writePromise = storage
  91. .setItem(storageKey, serialize(stagedState))
  92. .catch(onWriteFail)
  93. }
  94. function passWhitelistBlacklist(key) {
  95. if (whitelist && whitelist.indexOf(key) === -1 && key !== '_persist')
  96. return false
  97. if (blacklist && blacklist.indexOf(key) !== -1) return false
  98. return true
  99. }
  100. function onWriteFail(err) {
  101. // @TODO add fail handlers (typically storage full)
  102. if (writeFailHandler) writeFailHandler(err)
  103. if (err && process.env.NODE_ENV !== 'production') {
  104. console.error('Error storing data', err)
  105. }
  106. }
  107. const flush = () => {
  108. while (keysToProcess.length !== 0) {
  109. processNextKey()
  110. }
  111. return writePromise || Promise.resolve()
  112. }
  113. // return `persistoid`
  114. return {
  115. update,
  116. flush,
  117. }
  118. }
  119. // @NOTE in the future this may be exposed via config
  120. function defaultSerialize(data) {
  121. return JSON.stringify(data)
  122. }