-
Notifications
You must be signed in to change notification settings - Fork 863
/
createPersistoid.js
128 lines (108 loc) · 3.54 KB
/
createPersistoid.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// @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
const serialize = config.serialize === false ? x => x : defaultSerialize
// 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) {
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 (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)
}