redux-code
Version:
Redux helpers for actions and reducers
152 lines (151 loc) • 5.47 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
exports.memoryStorage = exports.sessionStorage = exports.localStorage = exports.persistStore = exports.persistReducer = exports.persistTypes = void 0;
/**
*
* Types of persist actions
*/
exports.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.
*/
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;
async function save() {
const state = toSave;
await 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 `${exports.persistTypes.REHYDRATE}/${key}`:
if (action.payload !== undefined)
state = merge(action.payload, state);
isPaused = false;
break;
case `${exports.persistTypes.PURGE}/${key}`:
config.storage.removeItem(key);
return state;
case `${exports.persistTypes.PAUSE}/${key}`:
isPaused = true;
break;
case `${exports.persistTypes.RESUME}/${key}`:
isPaused = false;
break;
default:
break;
}
}
const newState = reducer(state, action);
if (!isPaused && compare(newState, state, action))
persist(newState);
return newState;
};
}
exports.persistReducer = persistReducer;
/**
*
* Create a store enhancer that rehydrates the state of reducers
* @param {store} store The store to enhance.
* @param {storage} storage The storage to use.
*/
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: `${exports.persistTypes.REHYDRATE}/${key}`,
payload: stored ? (deserialize || JSON.parse)(stored) : undefined,
persist: key,
});
});
})).then(() => store.dispatch({ type: exports.persistTypes.COMPLETE }));
}
const promise = rehydrate();
return {
rehydrate,
then: promise.then.bind(promise),
purge: (key) => dispatchByKey(store.dispatch, { type: exports.persistTypes.PURGE }, key),
pause: (key) => dispatchByKey(store.dispatch, { type: exports.persistTypes.PAUSE }, key),
resume: (key) => dispatchByKey(store.dispatch, { type: exports.persistTypes.RESUME }, key),
};
}
exports.persistStore = persistStore;
const dispatchByKey = (dispatch, action, key) => {
for (const cfg of PERSISTORS) {
if (key === undefined || cfg.key === key)
dispatch({ ...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
*/
exports.localStorage = createAsyncStorage(globalThis.localStorage);
/**
*
* Create a storage that uses sessionStorage
*/
exports.sessionStorage = createAsyncStorage(globalThis.sessionStorage);
/**
*
* Create a storage that uses memory
*/
exports.memoryStorage = (function () {
const storage = {};
return {
setItem: async function (key, value) {
storage[key] = String(value);
},
getItem: async function (key) {
return storage[key];
},
removeItem: async function (key) {
delete storage[key];
},
};
})();