224 lines
5.1 KiB
TypeScript
224 lines
5.1 KiB
TypeScript
import {IAction, IResolvedAction} from '../actions'
|
|
import {ICRUDAction} from './ICRUDAction'
|
|
import {ICRUDMethod} from './ICRUDMethod'
|
|
import {indexBy, without} from '@rondo/common'
|
|
|
|
type Filter<T, U> = T extends U ? T : never
|
|
|
|
export interface ICRUDEntity {
|
|
readonly id: number
|
|
}
|
|
|
|
export interface ICRUDMethodStatus {
|
|
readonly isLoading: boolean
|
|
readonly error: string
|
|
}
|
|
|
|
export interface ICRUDState<T extends ICRUDEntity> {
|
|
readonly ids: ReadonlyArray<number>
|
|
readonly byId: Record<number, T>
|
|
status: ICRUDStatus
|
|
}
|
|
|
|
export interface ICRUDStatus {
|
|
readonly save: ICRUDMethodStatus
|
|
readonly update: ICRUDMethodStatus
|
|
readonly remove: ICRUDMethodStatus
|
|
readonly findOne: ICRUDMethodStatus
|
|
readonly findMany: ICRUDMethodStatus
|
|
}
|
|
|
|
export class CRUDReducer<
|
|
T extends ICRUDEntity,
|
|
ActionType extends string,
|
|
> {
|
|
readonly defaultState: ICRUDState<T>
|
|
|
|
constructor(readonly actionName: ActionType) {
|
|
|
|
const defaultMethodStatus = this.getDefaultMethodStatus()
|
|
this.defaultState = {
|
|
ids: [],
|
|
byId: {},
|
|
|
|
status: {
|
|
save: defaultMethodStatus,
|
|
update: defaultMethodStatus,
|
|
remove: defaultMethodStatus,
|
|
findOne: defaultMethodStatus,
|
|
findMany: defaultMethodStatus,
|
|
},
|
|
}
|
|
}
|
|
|
|
getDefaultMethodStatus(): ICRUDMethodStatus {
|
|
return {
|
|
error: '',
|
|
isLoading: false,
|
|
}
|
|
}
|
|
|
|
protected getSuccessStatus(): ICRUDMethodStatus {
|
|
return {
|
|
isLoading: false,
|
|
error: '',
|
|
}
|
|
}
|
|
|
|
handleRejected = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<ICRUDAction<T, ActionType>, {status: 'rejected'}>,
|
|
): ICRUDState<T> => {
|
|
return {
|
|
...state,
|
|
status: {
|
|
...state.status,
|
|
[action.method]: {
|
|
isLoading: false,
|
|
error: action.payload.message,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
handleLoading = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<ICRUDAction<T, ActionType>, {status: 'pending'}>,
|
|
): ICRUDState<T> => {
|
|
return {
|
|
...state,
|
|
status: {
|
|
...state.status,
|
|
[action.method]: {
|
|
isLoading: true,
|
|
error: '',
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
handleFindOne = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<
|
|
ICRUDAction<T, ActionType>, {method: 'findOne', status: 'resolved'}>,
|
|
): ICRUDState<T> => {
|
|
const {payload} = action
|
|
return {
|
|
...state,
|
|
ids: [...state.ids, payload.id],
|
|
byId: {
|
|
[payload.id]: payload,
|
|
},
|
|
status: {
|
|
...state.status,
|
|
[action.method]: this.getSuccessStatus(),
|
|
},
|
|
}
|
|
}
|
|
|
|
handleSave = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<
|
|
ICRUDAction<T, ActionType>, {method: 'save', status: 'resolved'}>,
|
|
): ICRUDState<T> => {
|
|
const {payload} = action
|
|
return {
|
|
...state,
|
|
ids: [...state.ids, payload.id],
|
|
byId: {
|
|
[payload.id]: payload,
|
|
},
|
|
status: {
|
|
...state.status,
|
|
[action.method]: this.getSuccessStatus(),
|
|
},
|
|
}
|
|
}
|
|
|
|
handleUpdate = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<
|
|
ICRUDAction<T, ActionType>, {method: 'update', status: 'resolved'}>,
|
|
): ICRUDState<T> => {
|
|
const {payload} = action
|
|
return {
|
|
...state,
|
|
byId: {
|
|
[payload.id]: payload,
|
|
},
|
|
status: {
|
|
...state.status,
|
|
[action.method]: this.getSuccessStatus(),
|
|
},
|
|
}
|
|
}
|
|
|
|
handleRemove = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<
|
|
ICRUDAction<T, ActionType>, {method: 'remove', status: 'resolved'}>,
|
|
): ICRUDState<T> => {
|
|
const {payload} = action
|
|
return {
|
|
...state,
|
|
ids: state.ids.filter(id => id !== payload.id),
|
|
byId: without(state.byId, payload.id),
|
|
status: {
|
|
...state.status,
|
|
[action.method]: this.getSuccessStatus(),
|
|
},
|
|
}
|
|
}
|
|
|
|
handleFindMany = (
|
|
state: ICRUDState<T>,
|
|
action: Filter<
|
|
ICRUDAction<T, ActionType>, {method: 'findMany', status: 'resolved'}>,
|
|
): ICRUDState<T> => {
|
|
const {payload} = action
|
|
return {
|
|
...state,
|
|
ids: payload.map(item => item.id),
|
|
byId: indexBy(payload, 'id' as any),
|
|
status: {
|
|
...state.status,
|
|
[action.method]: this.getSuccessStatus(),
|
|
},
|
|
}
|
|
}
|
|
|
|
reduce = (
|
|
state: ICRUDState<T> | undefined,
|
|
action: ICRUDAction<T, ActionType>,
|
|
): ICRUDState<T> => {
|
|
const {defaultState} = this
|
|
state = state || defaultState
|
|
|
|
if (action.type !== this.actionName) {
|
|
return state
|
|
}
|
|
|
|
switch (action.status) {
|
|
case 'pending':
|
|
return this.handleLoading(state, action)
|
|
case 'rejected':
|
|
return this.handleRejected(state, action)
|
|
case 'resolved':
|
|
switch (action.method) {
|
|
case 'save':
|
|
return this.handleSave(state, action)
|
|
case 'update':
|
|
return this.handleUpdate(state, action)
|
|
case 'remove':
|
|
return this.handleRemove(state, action)
|
|
case 'findOne':
|
|
return this.handleFindOne(state, action)
|
|
case 'findMany':
|
|
return this.handleFindMany(state, action)
|
|
}
|
|
default:
|
|
return state
|
|
}
|
|
}
|
|
}
|