import { isPlainObject , omit , union , without , isArray } from 'lodash' ; export type Id = string ; export type Ids = string [ ] ; export interface IEntity { id : Id ; } export interface IEntityMap { [ key ] : IEntity ; } export interface INorm { ids : Ids ; entities : IEntityMap ; } export default { create : ( ) : INorm => ( { entities : { } , ids : [ ] , } ) , fromArray : ( array ) => array . reduce ( ( acc , entity ) => ( { entities : { ... acc . entities , [ entity . id ] : entity } , ids : union ( acc . ids , [ entity . id ] ) , } ) , { entities : { } , ids : [ ] , } ) , pickFrom : ( norm , ids ) => ( { entities : mapPickIds ( ids , norm . entities ) , ids , } ) , remove : ( norm , entityIdOrIds ) : INorm => { const ids = isArray ( entityIdOrIds ) ? without . apply ( null , [ norm . ids , ... entityIdOrIds ] ) : without ( norm . ids , entityIdOrIds ) ; return { entities : omit ( norm . entities , entityIdOrIds ) as IEntityMap , ids , } ; } , toArray : ( arg1 , arg2 ? ) => { const { ids , entities } = ( arg2 ? { ids : arg1 , entities : arg2 } : arg1 as INorm ) ; if ( ! ids || ! entities ) { console . warn ( 'Normalized.toArray: argument error' ) ; return [ ] ; } return ( ids as Ids ) . map ( id => entities [ id ] ) as IEntity [ ] ; } , upsert : ( norm , entityOrEntities , { merge } = { } ) => { const isSingle = isPlainObject ( entityOrEntities ) ; if ( isSingle ) { const entity = entityOrEntities as IEntity ; return { entities : { ... norm . entities , [ entity . id ] : merge ? mergeIntoExisting ( norm , entity ) : entity } , ids : union ( norm . ids , [ entity . id ] ) , } ; } const entities = merge ? ( entityOrEntities as IEntity [ ] ) . map ( ( entity ) => mergeIntoExisting ( norm , entity ) ) : entityOrEntities as IEntity [ ] ; return { entities : { ... norm . entities , ... arrayToMap ( entities ) } , ids : union ( norm . ids , entities . map ( ( { id } ) => id ) ) , } ; } , } ; const mergeIntoExisting = ( norm , item ) => Object . assign ( { } , norm . entities [ item . id ] || { } , item ) ; const mapPickIds = ( ids , map ) => ids . reduce ( ( acc , id ) => ( { ... acc , [ id ] : map [ id ] } ) , { } ) ; const arrayToMap = ( array ) => array . reduce ( ( acc , entity ) => ( { ... acc , [ entity . id ] : entity } ) , { } ) ;