import { createSelector } from 'reselect'; import { SetState } from 'zustand'; import createEntityAdapter from '../../adapters/create-entity-adapter/CreateEntityAdapter'; import createSliceAdapter from '../../adapters/create-slice-adapter'; import { createStateActions } from '../../models/actions-state/ActionsState'; import EntityState, { EntityStateType } from '../../models/entity-state'; import { Comparer, EntityId, SliceStateActions, Action, SliceSetActions, SetAction, SliceSelectors, Selector, MetaSliceSelectors, SelectIdMethod } from '../../types'; import { getISOString } from '../../utils'; export type EntitySliceSetActions = SliceSetActions & { addOne: SetAction; addMany: SetAction; upsertOne: SetAction; upsertMany: SetAction; removeOne: SetAction; removeMany: SetAction; removeAll: SetAction; setAll: SetAction; }; export type EntitySliceStateActions = SliceStateActions & { addOne: Action<[TEntity]>; addMany: Action<[TEntity[]]>; upsertOne: Action<[TEntity]>; upsertMany: Action<[TEntity[]]>; removeOne: Action<[EntityId]>; removeMany: Action<[EntityId[]]>; removeAll: Action; setAll: Action<[TEntity[]]>; }; export type EntitySliceSelectors< TAppState extends object, TEntity extends object, TSliceActions extends EntitySliceStateActions = EntitySliceStateActions, > = SliceSelectors, TSliceActions> & MetaSliceSelectors & { selectIds: Selector; selectEntities: Selector>; selectAll: Selector; selectTotal: Selector; selectById: Selector; }; type EntitySliceOptions< TAppState extends object, TEntity extends object, TSliceActions extends EntitySliceStateActions = EntitySliceStateActions, > = { name: keyof TAppState; selectSliceState: (appState: TAppState) => EntitySliceState; selectId: SelectIdMethod | keyof TEntity; sortComparer: Comparer; // | false; initialState?: Partial>; }; export type EntitySliceState = EntitySliceStateActions> = EntityStateType & { actions: TSliceActions }; export const createEntitySliceState = < TAppState extends object, TEntity extends object, TSliceStateActions extends EntitySliceStateActions = EntitySliceStateActions, TSliceSetActions extends EntitySliceSetActions = EntitySliceSetActions, >(set: SetState, state: EntityStateType, actions: TSliceSetActions): EntitySliceState => ({ ...state, actions: createStateActions(set, actions), }); export type EntitySlice< TAppState extends object, TEntity extends object, TSliceActions extends EntitySliceStateActions = EntitySliceStateActions, > = { name: keyof TAppState; actions: EntitySliceSetActions; selectors: EntitySliceSelectors; state: EntityStateType; }; const createEntitySlice = < TAppState extends object, TEntity extends object, TSliceActions extends EntitySliceStateActions = EntitySliceStateActions, >(options: EntitySliceOptions) : EntitySlice => { const selectors: EntitySliceSelectors = { 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), selectById: (id: EntityId) => createSelector(options.selectSliceState, (model) => model.entities?.[id]), }; const initialState = EntityState.create(options?.initialState); const entityAdapter = createEntityAdapter({ selectId: options.selectId, sortComparer: options.sortComparer, }); const sliceAdapter = createSliceAdapter>(); const modifyState = (state: EntitySliceState, lastModified: string | null): EntitySliceState => { return sliceAdapter.setLastModified(state, lastModified); }; const actions: EntitySliceSetActions = { addMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.addMany(state[options.name] as unknown as EntitySliceState, models), getISOString()), })), addOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.addOne(state[options.name] as unknown as EntitySliceState, model), getISOString()), })), removeAll: (set) => () => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeAll(state[options.name] as unknown as EntitySliceState), getISOString()), })), removeMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeMany(state[options.name] as unknown as EntitySliceState, models), getISOString()), })), removeOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.removeOne(state[options.name] as unknown as EntitySliceState, model), getISOString()), })), reset: (set) => () => set(state => ({ ...state, [options.name]: { actions: (state[options.name] as unknown as EntitySliceState).actions, ...initialState, }, })), setAll: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.setAll(state[options.name] as unknown as EntitySliceState, models), getISOString()), })), upsertMany: (set) => (models) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.upsertMany(state[options.name] as unknown as EntitySliceState, models), getISOString()), })), upsertOne: (set) => (model) => set(state => ({ ...state, [options.name]: modifyState(entityAdapter.upsertOne(state[options.name] as unknown as EntitySliceState, model), getISOString()), })), }; return ({ name: options.name, selectors: selectors, actions: actions, state: initialState, }); }; export default createEntitySlice;