redux-code
Version:
Redux helpers for actions and reducers
191 lines (190 loc) • 5.79 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
var __awaiter =
(this && this.__awaiter) ||
function (thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P
? value
: new P(function (resolve) {
resolve(value)
})
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value))
} catch (e) {
reject(e)
}
}
function rejected(value) {
try {
step(generator['throw'](value))
} catch (e) {
reject(e)
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected)
}
step((generator = generator.apply(thisArg, _arguments || [])).next())
})
}
/**
*
* Types of persist actions
*/
export const persistTypes = {
/** Rehydrate an reducer */
REHYDRATE: 'persist/rehydrate',
/** All reducers were completely rehydrated */
COMPLETE: 'persist/complete',
/** Purge an reducer state */
PURGE: 'persist/purge',
/** Pause an reducer persistence */
PAUSE: 'persist/pause',
/** Resume an reducer persistence */
RESUME: 'persist/resume',
}
const PERSISTORS = []
/**
*
* Create a mixin for a reducer that adds the ability to handle persistence.
* @param {config} config The configuration.
* @param {reducer} reducer The reducer to handle.
*/
export function persistReducer(config, reducer) {
const { key } = config
const serialize = config.serialize || JSON.stringify
const merge = config.merge || ((stored) => stored)
const throttle = config.throttle || 0
const compare = config.compare || ((state, oldState) => state !== oldState)
let waitForSave = null
let toSave = {}
let isPaused = true
function save() {
return __awaiter(this, void 0, void 0, function* () {
const state = toSave
yield config.storage.setItem(key, serialize(state))
waitForSave = state === toSave ? null : setTimeout(save, throttle)
})
}
function persist(state) {
toSave = state
if (!waitForSave) waitForSave = setTimeout(save, throttle)
}
PERSISTORS.push(config)
return (state, action) => {
if (action.persist !== undefined && action.persist === key) {
switch (action.type) {
case `${persistTypes.REHYDRATE}/${key}`:
if (action.payload !== undefined) state = merge(action.payload, state)
isPaused = false
break
case `${persistTypes.PURGE}/${key}`:
config.storage.removeItem(key)
return state
case `${persistTypes.PAUSE}/${key}`:
isPaused = true
break
case `${persistTypes.RESUME}/${key}`:
isPaused = false
break
default:
break
}
}
const newState = reducer(state, action)
if (!isPaused && compare(newState, state, action)) persist(newState)
return newState
}
}
/**
*
* Create a store enhancer that rehydrates the state of reducers
* @param {store} store The store to enhance.
* @param {storage} storage The storage to use.
*/
export function persistStore(store, storage) {
function rehydrate() {
return Promise.all(
PERSISTORS.map((cfg) => {
const { deserialize, key } = cfg
cfg.storage = cfg.storage === undefined ? storage : cfg.storage
if (!cfg.storage)
throw `Persistent reducer (${key}) doesn't have a storage and no default storage is provided`
return cfg.storage.getItem(key).then((stored) => {
store.dispatch({
type: `${persistTypes.REHYDRATE}/${key}`,
payload: stored ? (deserialize || JSON.parse)(stored) : undefined,
persist: key,
})
})
}),
).then(() => store.dispatch({ type: persistTypes.COMPLETE }))
}
const promise = rehydrate()
return {
rehydrate,
then: promise.then.bind(promise),
purge: (key) => dispatchByKey(store.dispatch, { type: persistTypes.PURGE }, key),
pause: (key) => dispatchByKey(store.dispatch, { type: persistTypes.PAUSE }, key),
resume: (key) => dispatchByKey(store.dispatch, { type: persistTypes.RESUME }, key),
}
}
const dispatchByKey = (dispatch, action, key) => {
for (const cfg of PERSISTORS) {
if (key === undefined || cfg.key === key)
dispatch(
Object.assign(Object.assign({}, action), {
type: `${action.type}/${cfg.key}`,
persist: cfg.key,
}),
)
}
}
function createAsyncStorage(storage) {
return {
setItem: (key, value) =>
new Promise((resolve) => {
resolve(storage.setItem(key, value))
}),
getItem: (key) => new Promise((resolve) => resolve(storage.getItem(key))),
removeItem: (key) => new Promise((resolve) => resolve(storage.removeItem(key))),
clear: () => new Promise((resolve) => resolve(storage.clear())),
}
}
/**
*
* Create a storage that uses localStorage
*/
export const localStorage = createAsyncStorage(globalThis.localStorage)
/**
*
* Create a storage that uses sessionStorage
*/
export const sessionStorage = createAsyncStorage(globalThis.sessionStorage)
/**
*
* Create a storage that uses memory
*/
export const memoryStorage = (function () {
const storage = {}
return {
setItem: function (key, value) {
return __awaiter(this, void 0, void 0, function* () {
storage[key] = String(value)
})
},
getItem: function (key) {
return __awaiter(this, void 0, void 0, function* () {
return storage[key]
})
},
removeItem: function (key) {
return __awaiter(this, void 0, void 0, function* () {
delete storage[key]
})
},
}
})()