import { createSelector } from 'reselect'; import { SetState } from 'zustand'; import createEntityAdapter from '../../adapters/create-entity-adapter'; import createSliceAdapter from '../../adapters/create-slice-adapter'; import { createStateActions } from '../../models/actions-state/ActionsState'; import AsyncEntityState, { AsyncEntityStateType } from '../../models/async-entity-state'; import { Action, AsyncMetaSliceSelectors, AsyncSliceSetActions, AsyncSliceStateActions, Comparer, EntityId, SelectIdMethod, Selector, SetAction, SliceSelectors } from '../../types'; import { getISOString } from '../../utils'; export type AsyncEntitySliceSetActions = AsyncSliceSetActions & { addOne: SetAction; addMany: SetAction; hydrateOne: SetAction hydrateMany: SetAction; hydrateAll: SetAction; upsertOne: SetAction; upsertMany: SetAction; removeOne: SetAction; removeMany: SetAction; removeAll: SetAction; setAll: SetAction; }; export type AsyncEntitySliceStateActions = AsyncSliceStateActions & { addOne: Action<[TEntity]>; addMany: Action<[TEntity[]]>; hydrateOne: Action<[TEntity]> hydrateMany: Action<[TEntity[]]>; hydrateAll: Action<[TEntity[]]>; upsertOne: Action<[TEntity]>; upsertMany: Action<[TEntity[]]>; removeOne: Action<[EntityId]>; removeMany: Action<[EntityId[]]>; removeAll: Action; setAll: Action<[TEntity[]]>; }; export type AsyncEntitySliceState< TModel extends object, TError extends Error = Error, TSliceActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions, > = AsyncEntityStateType & { actions: TSliceActions }; export type AsyncEntitySliceSelectors< TAppState extends object, TEntity extends object, TError extends Error = Error, TSliceActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions, > = SliceSelectors, TSliceActions> & AsyncMetaSliceSelectors & { selectIds: Selector; selectEntities: Selector>; selectAll: Selector; selectTotal: Selector; selectById: Selector; }; type AsyncEntitySliceOptions< TAppState extends object, TEntity extends object, TError extends Error = Error, TSliceActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions, > = { name: keyof TAppState; selectSliceState: (appState: TAppState) => AsyncEntitySliceState; selectId: SelectIdMethod | keyof TEntity; sortComparer: Comparer; // | false; initialState?: Partial>; }; export type AsyncEntitySlice< TAppState extends object, TEntity extends object, TError extends Error = Error, TSliceActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions> = { name: keyof TAppState; actions: AsyncEntitySliceSetActions; selectors: AsyncEntitySliceSelectors; state: AsyncEntityStateType; }; export const createAsyncEntitySliceState = < TAppState extends object, TEntity extends object, TError extends Error = Error, TSliceStateActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions, TSliceSetActions extends AsyncEntitySliceSetActions = AsyncEntitySliceSetActions, >(set: SetState, state: AsyncEntityStateType, actions: TSliceSetActions): AsyncEntitySliceState => ({ ...state, actions: createStateActions(set, actions), }); const createAsyncEntitySlice = < TAppState extends object, TEntity extends object, TError extends Error = Error, TSliceActions extends AsyncEntitySliceStateActions = AsyncEntitySliceStateActions, >(options: AsyncEntitySliceOptions): AsyncEntitySlice => { const initialState = AsyncEntityState.create(options.initialState); const selectors: AsyncEntitySliceSelectors = { selectSliceState: createSelector(options.selectSliceState, (model) => model), selectActions: createSelector(options.selectSliceState, (model) => model.actions), selectEntities: createSelector(options.selectSliceState, (model) => model.entities), selectIds: createSelector(options.selectSliceState, (model) => model.ids), selectTotal: createSelector(options.selectSliceState, (model) => model.ids.length), selectAll: createSelector(options.selectSliceState, (model) => Object.values(model.entities)), selectLastModified: createSelector(options.selectSliceState, (model) => model.lastModified), selectLastHydrated: createSelector(options.selectSliceState, (model) => model.lastHydrated), selectById: (id: EntityId) => createSelector(options.selectSliceState, (model) => model.entities?.[id]), selectStatus: createSelector(options.selectSliceState, (model) => model.status), selectError: createSelector(options.selectSliceState, (model) => model.error), }; const entityAdapter = createEntityAdapter>({ selectId: options.selectId, sortComparer: options.sortComparer, }); const sliceAdapter = createSliceAdapter>(); const modifyState = (state: AsyncEntitySliceState, lastModified: string | null): AsyncEntitySliceState => { return sliceAdapter.setLastModified(state, lastModified); }; const hydrateState = (state: AsyncEntitySliceState, lastHydrated: string | null): AsyncEntitySliceState => { return sliceAdapter.setLastHydrated(sliceAdapter.setLastModified(state, null), lastHydrated); }; const actions: AsyncEntitySliceSetActions = { addMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.addMany(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), addOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.addOne(state[options.name] as unknown as AsyncEntitySliceState, model), getISOString()), })), hydrateAll: (set) => (models) => set(state => ({ ...state, [options.name]: hydrateState(entityAdapter.setAll(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), hydrateMany: (set) => (models) => set(state => ({ ...state, [options.name]: hydrateState(entityAdapter.upsertMany(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), hydrateOne: (set) => (model) => set(state => ({ ...state, [options.name]: hydrateState(entityAdapter.upsertOne(state[options.name] as unknown as AsyncEntitySliceState, model), getISOString()), })), removeAll: (set) => () => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeAll(state[options.name] as unknown as AsyncEntitySliceState), getISOString()), })), removeMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeMany(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), removeOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeOne(state[options.name] as unknown as AsyncEntitySliceState, model), getISOString()), })), reset: (set) => () => set(state => ({ ...state, [options.name]: { actions: (state[options.name] as unknown as AsyncEntitySliceState).actions, ...initialState, }, })), setAll: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.setAll(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), setError: (set) => (error) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncEntitySliceState, error: error, }, getISOString()), })), setStatus: (set) => (status) => set(state => ({ ...state, [options.name]: modifyState({ ...state[options.name] as unknown as AsyncEntitySliceState, status: status, }, getISOString()), })), upsertMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.upsertMany(state[options.name] as unknown as AsyncEntitySliceState, models), getISOString()), })), upsertOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.upsertOne(state[options.name] as unknown as AsyncEntitySliceState, model), getISOString()), })), }; return ({ name: options.name, actions: actions, selectors: selectors, state: initialState, }); }; export default createAsyncEntitySlice;