1 import type { Selector } from '@reduxjs/toolkit';
2 import type { Action, Reducer } from 'redux';
4 import type { MaybeArray } from '@proton/pass/types';
6 import { commitReducer } from './reducers/commit';
7 import { failReducer } from './reducers/fail';
8 import { initiateReducer } from './reducers/initiate';
9 import { revertReducer } from './reducers/revert';
10 import { asIfNotFailedSubSelector } from './selectors/select-is-failed';
11 import { asIfNotOptimisticSubSelector } from './selectors/select-is-optimistic';
16 WithOptimisticReducer,
17 WrappedOptimisticState,
19 import { HistoryFlag } from './types';
20 import { getActionFromHistoryItem, unwrapOptimisticState } from './utils/transformers';
21 import { withHistoryAction } from './utils/with-history-action';
23 export const withInitialOptimisticState = <T extends object>(state: T): WrappedOptimisticState<T> => {
24 const initialOptimistic: { optimistic: OptimisticState<T> } = {
26 checkpoint: undefined,
37 const applyOptimisticMatcher = (action: Action) => (matcher: OptimisticMatcher) =>
38 typeof matcher === 'string' ? matcher === action.type : matcher(action);
40 const withOptimistic = <T extends object>(
41 matchersList: OptimisticMatchers[],
43 options?: { sanitizeAction: (action: Action) => Action }
45 reducer: WithOptimisticReducer<T>;
47 asIfNotFailed: Selector<WrappedOptimisticState<T>, WrappedOptimisticState<T>>;
48 asIfNotOptimistic: Selector<WrappedOptimisticState<T>, WrappedOptimisticState<T>>;
51 const initialState = reducer(undefined, { type: '__OPTIMISTIC_INIT__' });
52 const initialOptimisticState = withInitialOptimisticState(initialState);
54 const wrappedReducer: WithOptimisticReducer<T> = (outer = initialOptimisticState, optimisticAction: Action) => {
55 const action = options?.sanitizeAction?.(optimisticAction) ?? optimisticAction;
57 const inner = unwrapOptimisticState(outer);
58 const nextInner = reducer(inner, action);
60 const { optimistic } = outer;
62 for (const [index, matchers] of Object.entries(matchersList)) {
63 for (const [key, matcher] of Object.entries(matchers) as [string, MaybeArray<OptimisticMatcher>][]) {
64 const match = Array.isArray(matcher)
65 ? matcher.map(applyOptimisticMatcher(action)).find(Boolean)
66 : applyOptimisticMatcher(action)(matcher);
69 const id = typeof match === 'string' ? match : index;
71 switch (key as keyof OptimisticMatchers) {
73 const nextOptimistic = initiateReducer(inner, reducer, action, optimistic, id);
76 ...nextOptimistic.history
77 .map(getActionFromHistoryItem)
78 .reduce(reducer, nextOptimistic.checkpoint!),
79 optimistic: nextOptimistic,
86 optimistic: withHistoryAction(
90 type: HistoryFlag.OPTIMISTIC_EFFECT,
92 commitReducer(reducer, optimistic, id)
100 optimistic: withHistoryAction(
104 type: HistoryFlag.OPTIMISTIC_EFFECT,
106 failReducer(optimistic, id)
112 const [revertedInner, nextOptimistic] = revertReducer(inner, reducer, optimistic, id);
115 ...reducer(revertedInner, action),
116 optimistic: withHistoryAction(
120 type: HistoryFlag.OPTIMISTIC_EFFECT,
131 if (inner === nextInner) {
137 optimistic: withHistoryAction({ type: HistoryFlag.DETERMINISTIC, action }, outer.optimistic),
141 wrappedReducer.innerReducer = reducer;
144 reducer: wrappedReducer,
146 asIfNotFailed: asIfNotFailedSubSelector(wrappedReducer),
147 asIfNotOptimistic: asIfNotOptimisticSubSelector(wrappedReducer),
152 export default withOptimistic;