Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4 #158

Merged
merged 9 commits into from
Aug 28, 2016
Merged

v4 #158

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# What about redux-persist?
This is a fork of redux-persist published as `redux-persist-2` until the PR https://github.com/rt2zz/redux-persist/pull/113 gets merged.

# Redux Persist
Persist and rehydrate a redux store.

Expand Down
59 changes: 33 additions & 26 deletions src/autoRehydrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { REHYDRATE } from './constants'
import isStatePlainEnough from './utils/isStatePlainEnough'

export default function autoRehydrate (config = {}) {
const stateReconciler = config._stateReconciler || defaultStateReconciler

return (next) => (reducer, initialState, enhancer) => {
return next(createRehydrationReducer(reducer), initialState, enhancer)
}
Expand All @@ -19,32 +21,8 @@ export default function autoRehydrate (config = {}) {

let inboundState = action.payload
let reducedState = reducer(state, action)
let newState = {...reducedState}

Object.keys(inboundState).forEach((key) => {
// if initialState does not have key, skip auto rehydration
if (!state.hasOwnProperty(key)) return

// if initial state is an object but inbound state is null/undefined, skip
if (typeof state[key] === 'object' && !inboundState[key]) {
if (config.log) console.log('redux-persist/autoRehydrate: sub state for key `%s` is falsy but initial state is an object, skipping autoRehydrate.', key)
return
}

// if reducer modifies substate, skip auto rehydration
if (state[key] !== reducedState[key]) {
if (config.log) console.log('redux-persist/autoRehydrate: sub state for key `%s` modified, skipping autoRehydrate.', key)
newState[key] = reducedState[key]
return
}

// otherwise take the inboundState
if (isStatePlainEnough(inboundState[key]) && isStatePlainEnough(state[key])) newState[key] = {...state[key], ...inboundState[key]} // shallow merge
else newState[key] = inboundState[key] // hard set

if (config.log) console.log('redux-persist/autoRehydrate: key `%s`, rehydrated to ', key, newState[key])
})
return newState

return stateReconciler(state, inboundState, reducedState, config.log)
}
}
}
Expand All @@ -59,3 +37,32 @@ function logPreRehydrate (preRehydrateActions) {
`, preRehydrateActions.length)
}
}

function defaultStateReconciler (state, inboundState, reducedState, log) {
let newState = {...reducedState}

Object.keys(inboundState).forEach((key) => {
// if initialState does not have key, skip auto rehydration
if (!state.hasOwnProperty(key)) return

// if initial state is an object but inbound state is null/undefined, skip
if (typeof state[key] === 'object' && !inboundState[key]) {
if (log) console.log('redux-persist/autoRehydrate: sub state for key `%s` is falsy but initial state is an object, skipping autoRehydrate.', key)
return
}

// if reducer modifies substate, skip auto rehydration
if (state[key] !== reducedState[key]) {
if (log) console.log('redux-persist/autoRehydrate: sub state for key `%s` modified, skipping autoRehydrate.', key)
newState[key] = reducedState[key]
return
}

// otherwise take the inboundState
if (isStatePlainEnough(inboundState[key]) && isStatePlainEnough(state[key])) newState[key] = {...state[key], ...inboundState[key]} // shallow merge
else newState[key] = inboundState[key] // hard set

if (log) console.log('redux-persist/autoRehydrate: key `%s`, rehydrated to ', key, newState[key])
})
return newState
}
70 changes: 33 additions & 37 deletions src/createPersistor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KEY_PREFIX, REHYDRATE } from './constants'
import createAsyncLocalStorage from './defaults/asyncLocalStorage'
import isStatePlainEnough from './utils/isStatePlainEnough'
import purgeStoredState from './purgeStoredState'
import stringify from 'json-stringify-safe'
import { forEach } from 'lodash'

Expand All @@ -13,13 +13,19 @@ export default function createPersistor (store, config) {
const transforms = config.transforms || []
const debounce = config.debounce || false
const keyPrefix = config.keyPrefix || KEY_PREFIX
let storage = config.storage || createAsyncLocalStorage('local')

// fallback getAllKeys to `keys` if present (LocalForage compatability)
// pluggable state shape (e.g. immutablejs)
const stateInit = config._stateInit || {}
const stateIterator = config._stateIterator || defaultStateIterator
const stateGetter = config._stateGetter || defaultStateGetter
const stateSetter = config._stateSetter || defaultStateSetter

// storage with keys -> getAllKeys for localForage support
let storage = config.storage || createAsyncLocalStorage('local')
if (storage.keys && !storage.getAllKeys) storage = {...storage, getAllKeys: storage.keys}

// initialize stateful values
let lastState = {}
let lastState = stateInit
let paused = false
let storesToProcess = []
let timeIterator = null
Expand All @@ -28,13 +34,10 @@ export default function createPersistor (store, config) {
if (paused) return

let state = store.getState()
if (process.env.NODE_ENV !== 'production') {
if (!isStatePlainEnough(state)) console.warn('redux-persist: State is not plain enough to persist. Can only persist plain objects.')
}

forEach(state, (subState, key) => {
stateIterator(state, (subState, key) => {
if (!passWhitelistBlacklist(key)) return
if (lastState[key] === state[key]) return
if (stateGetter(lastState, key) === stateGetter(state, key)) return
if (storesToProcess.indexOf(key) !== -1) return
storesToProcess.push(key)
})
Expand All @@ -50,7 +53,7 @@ export default function createPersistor (store, config) {

let key = storesToProcess[0]
let storageKey = createStorageKey(key)
let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), store.getState()[storesToProcess[0]])
let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), stateGetter(store.getState(), key))
if (typeof endState !== 'undefined') storage.setItem(storageKey, serialize(endState), warnIfSetError(key))
storesToProcess.shift()
}, debounce)
Expand All @@ -71,9 +74,10 @@ export default function createPersistor (store, config) {
forEach(incoming, (subState, key) => {
try {
let data = deserialize(subState)
state[key] = transforms.reduceRight((interState, transformer) => {
let value = transforms.reduceRight((interState, transformer) => {
return transformer.out(interState, key)
}, data)
state = stateSetter(state, key, value)
} catch (err) {
if (process.env.NODE_ENV !== 'production') console.warn(`Error rehydrating data for key "${key}"`, subState, err)
}
Expand All @@ -84,24 +88,6 @@ export default function createPersistor (store, config) {
return state
}

function purge (keys) {
if (typeof keys === 'undefined') {
purgeAll()
} else {
forEach(keys, (key) => {
storage.removeItem(createStorageKey(key), warnIfRemoveError(key))
})
}
}

function purgeAll () {
// @TODO deprecate
storage.getAllKeys((err, allKeys) => {
if (err && process.env.NODE_ENV !== 'production') { console.warn('Error in storage.getAllKeys') }
purge(allKeys.filter((key) => key.indexOf(keyPrefix) === 0).map((key) => key.slice(keyPrefix.length)))
})
}

function createStorageKey (key) {
return `${keyPrefix}${key}`
}
Expand All @@ -111,14 +97,11 @@ export default function createPersistor (store, config) {
rehydrate: adhocRehydrate,
pause: () => { paused = true },
resume: () => { paused = false },
purge,
purgeAll
}
}

function warnIfRemoveError (key) {
return function removeError (err) {
if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) }
purge: (keys) => purgeStoredState({storage, keyPrefix}, keys),
purgeAll: () => {
console.warn('redux-persist: purgeAll is deprecated. use `persistor.purge()` instead')
return purgeStoredState({storage, keyPrefix})
}
}
}

Expand Down Expand Up @@ -150,3 +133,16 @@ function rehydrateAction (data) {
payload: data
}
}

function defaultStateIterator (collection, callback) {
return forEach(collection, callback)
}

function defaultStateGetter (state, key) {
return state[key]
}

function defaultStateSetter (state, key, value) {
state[key] = value
return state
}
90 changes: 53 additions & 37 deletions src/defaults/asyncLocalStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,48 +35,64 @@ function getStorage (type) {
export default function (type) {
let storage = getStorage(type)
return {
getItem: function (key, cb) {
try {
var s = storage.getItem(key)
nextTick(() => {
cb(null, s)
})
} catch (e) {
cb(e)
}
getItem (key, cb) {
return new Promise((resolve, reject) => {
try {
var s = storage.getItem(key)
nextTick(() => {
cb && cb(null, s)
resolve(s)
})
} catch (e) {
cb && cb(e)
reject(e)
}
})
},
setItem: function (key, string, cb) {
try {
storage.setItem(key, string)
nextTick(() => {
cb(null)
})
} catch (e) {
cb(e)
}
setItem (key, string, cb) {
return new Promise((resolve, reject) => {
try {
storage.setItem(key, string)
nextTick(() => {
cb && cb(null)
resolve()
})
} catch (e) {
cb && cb(e)
reject(e)
}
})
},
removeItem: function (key, cb) {
try {
storage.removeItem(key)
nextTick(() => {
cb(null)
})
} catch (e) {
cb(e)
}
removeItem (key, cb) {
return new Promise((resolve, reject) => {
try {
storage.removeItem(key)
nextTick(() => {
cb && cb(null)
resolve()
})
} catch (e) {
cb && cb(e)
reject(e)
}
})
},
getAllKeys: function (cb) {
try {
var keys = []
for (var i = 0; i < storage.length; i++) {
keys.push(storage.key(i))
return new Promise((resolve, reject) => {
try {
var keys = []
for (var i = 0; i < storage.length; i++) {
keys.push(storage.key(i))
}
nextTick(() => {
cb && cb(null, keys)
resolve(keys)
})
} catch (e) {
cb && cb(e)
reject(e)
}
nextTick(() => {
cb(null, keys)
})
} catch (e) {
cb(e)
}
})
}
}
}
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import createAsyncLocalStorage from './defaults/asyncLocalStorage'

import autoRehydrate from './autoRehydrate'
import createPersistor from './createPersistor'
import createTransform from './createTransform'
import getStoredState from './getStoredState'
import persistStore from './persistStore'
import purgeStoredState from './purgeStoredState'

import createAsyncLocalStorage from './defaults/asyncLocalStorage'
const storages = {
asyncLocalStorage: createAsyncLocalStorage('local'),
asyncSessionStorage: createAsyncLocalStorage('session')
}

export { autoRehydrate, createPersistor, createTransform, getStoredState, persistStore, storages }
export { autoRehydrate, createPersistor, createTransform, getStoredState, persistStore, purgeStoredState, storages }
33 changes: 33 additions & 0 deletions src/purgeStoredState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { KEY_PREFIX } from './constants'

export default function purgeStoredState (config, keys) {
const storage = config.storage
const keyPrefix = config.keyPrefix || KEY_PREFIX

// basic validation
if (Array.isArray(config)) throw new Error('redux-persist: purgeStoredState requires config as a first argument (found array). An array of keys is the optional second argument.')
if (!storage) throw new Error('redux-persist: config.storage required in purgeStoredState')

if (typeof keys === 'undefined') { // if keys is not defined, purge all keys
return new Promise((resolve, reject) => {
storage.getAllKeys((err, allKeys) => {
if (err && process.env.NODE_ENV !== 'production') {
console.warn('redux-persist: error during purgeStoredState in storage.getAllKeys')
reject(err)
} else {
resolve(purgeStoredState(config, allKeys.filter((key) => key.indexOf(keyPrefix) === 0).map((key) => key.slice(keyPrefix.length))))
}
})
})
} else { // otherwise purge specified keys
return Promise.all(keys.map((key) => {
return storage.removeItem(`${keyPrefix}${key}`, warnIfRemoveError(key))
}))
}
}

function warnIfRemoveError (key) {
return function removeError (err) {
if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) }
}
}
19 changes: 19 additions & 0 deletions test/purgeStoredState.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import test from 'ava'

import { purgeStoredState, storages } from '../src'

test('purgeStoredState (all) returns a promise', t => {
let purgeResult = purgeStoredState({ storage: storages.asyncLocalStorage })
t.true(isPromise(purgeResult))
return purgeResult
})

test('purgeStoredState (whitelist) returns a promise', t => {
let purgeResult = purgeStoredState({ storage: storages.asyncLocalStorage }, ['foo'])
t.true(isPromise(purgeResult))
return purgeResult
})

function isPromise (something) {
return typeof something === 'object' && typeof something.then === 'function'
}