UNPKG

redux-code

Version:

Redux helpers for actions and reducers

191 lines (190 loc) 5.79 kB
/* 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] }) }, } })()