import { createSelector } from 'reselect'; import { SetState } from 'zustand'; import createSliceAdapter from '../../adapters/create-slice-adapter'; import { createStateActions } from '../../models/actions-state/ActionsState'; import AsyncModelState, { AsyncModelStateType } from '../../models/async-model-state'; import { Action, AsyncMetaSliceSelectors, AsyncSliceStateActions, AsyncSliceSetActions, SetAction, SliceSelectors } from '../../types'; import { getISOString } from '../../utils'; export type AsyncModelSliceSelectors< TAppState extends object, TModel extends object, TError extends Error = Error, TSliceStateActions extends AsyncModelSliceStateActions = AsyncModelSliceStateActions, > = SliceSelectors, TSliceStateActions> & AsyncMetaSliceSelectors & { selectModel: (state: TAppState) => TModel; }; export type AsyncModelSliceSetActions = AsyncSliceSetActions & { hydrate: SetAction; set: SetAction; update: SetAction; }; export type AsyncModelSliceStateActions = AsyncSliceStateActions & { hydrate: Action<[TModel]>; set: Action<[TModel]>; update: Action<[TModel]>; }; export type AsyncModelSliceState< TModel extends object, TError extends Error = Error, TSliceActions extends AsyncModelSliceStateActions = AsyncModelSliceStateActions, > = AsyncModelStateType & { actions: TSliceActions }; export type AsyncModelSlice = AsyncModelSliceStateActions> = { name: keyof TAppState; actions: AsyncModelSliceSetActions; selectors: AsyncModelSliceSelectors; state: AsyncModelStateType; }; export const createAsyncModelSliceState = < TAppState extends object, TModel extends object, TError extends Error = Error, TSliceStateActions extends AsyncModelSliceStateActions = AsyncModelSliceStateActions, TSliceSetActions extends AsyncModelSliceSetActions = AsyncModelSliceSetActions, >(set: SetState, state: AsyncModelStateType, actions: TSliceSetActions): AsyncModelSliceState => ({ ...state, actions: createStateActions(set, actions), }); const createAsyncModelSlice = = AsyncModelSliceStateActions>(options: { name: keyof TAppState; selectSliceState: (appState: TAppState) => AsyncModelSliceState initialState?: Partial> }): AsyncModelSlice => { const initialState = AsyncModelState.create(options.initialState); const selectors: AsyncModelSliceSelectors = { selectActions: createSelector(options.selectSliceState, (model) => model.actions), selectSliceState: createSelector(options.selectSliceState, (model) => model), selectModel: createSelector(options.selectSliceState, (model) => model.model), selectStatus: createSelector(options.selectSliceState, (model) => model.status), selectError: createSelector(options.selectSliceState, (model) => model.error), selectLastHydrated: createSelector(options.selectSliceState, (model) => model.lastHydrated), selectLastModified: createSelector(options.selectSliceState, (model) => model.lastModified), }; const sliceAdapter = createSliceAdapter>(); const modifyState = (state: AsyncModelSliceState, lastModified: string | null): AsyncModelSliceState => { return sliceAdapter.setLastModified(state, lastModified); }; const hydrateState = (state: AsyncModelSliceState, lastHydrated: string | null): AsyncModelSliceState => { return sliceAdapter.setLastHydrated(sliceAdapter.setLastModified(state, null), lastHydrated); }; const actions: AsyncModelSliceSetActions = { reset: (set) => () => set(state => ({ ...state, [options.name]: { actions: (state[options.name] as unknown as AsyncModelSliceState).actions, ...initialState, }, })), hydrate: (set) => (model) => set(state => ({ ...state, [options.name]: hydrateState({ ...state[options.name] as unknown as AsyncModelSliceState, model: model, }, getISOString()), })), update: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncModelSliceState, model: model, }, getISOString()), })), set: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncModelSliceState, model: model, }, getISOString()), })), setError: (set) => (error) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncModelSliceState, error: error, }, getISOString()), })), setStatus: (set) => (status) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncModelSliceState, status: status, }, getISOString()), })), }; return ({ name: options.name, actions: actions, selectors: selectors, state: initialState, }); }; export default createAsyncModelSlice;