UNPKG

redux-code

Version:

Redux helpers for actions and reducers

152 lines (151 loc) 5.47 kB
"use strict"; /* 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]; }, }; })();