import { Store } from './store' ; import { Id , PaginationInfo } from 'ngx-tethys/types' ; import { helpers , produce } from 'ngx-tethys/util' ; import { mergeReferences , buildReferencesKeyBy , ReferenceArrayExtractAllowKeys } from 'ngx-tethys/util' ; import { map } from 'rxjs/operators' ; import { ReferencesIdDictionary , OnCombineRefsFn } from './references' ; export interface EntityStoreOptions < TEntity = unknown , TReferences = unknown > { idKey ? : string ; referencesIdKeys ? : ReferenceArrayExtractAllowKeys < TReferences > ; } export interface EntityAddOptions { prepend ? : boolean ; afterId ? : Id ; autoGotoLastPage ? : boolean ; } export interface EntityState < TEntity , TReferences = unknown > { pagination ? : PaginationInfo ; entities : TEntity [ ] ; references ? : TReferences ; } export class EntityStore < TState extends EntityState < TEntity , TReferences > , TEntity , TReferences = unknown > extends Store < TState > { protected options : EntityStoreOptions < TEntity , TReferences > ; private internalReferencesIdMap : ReferencesIdDictionary < TReferences > ; get entities ( ) { return this . snapshot . entities ; } entities$ = this . select ( state => { return state . entities ; } ) ; entitiesWithRefs$ = this . entities$ . pipe ( map ( entities => { if ( ! entities ) { return entities ; } return entities . map ( entity => { const newEntity = { ... entity } ; if ( this [ 'onCombineRefs' ] ) { if ( ! newEntity [ 'refs' ] ) { newEntity [ 'refs' ] = { } ; } this [ 'onCombineRefs' ] ( newEntity , this . internalReferencesIdMap , this . snapshot . references ) ; } else { throw new Error ( `onCombineRefs is not empty` ) ; } return newEntity ; } ) ; } ) ) ; private resetPagination ( pagination , count ) { pagination . count = count ; const pageCount = Math . ceil ( pagination . count / pagination . pageSize ) ; pagination . pageCount = pageCount ; this . snapshot . pagination = { ... pagination } ; } private increasePagination ( amount ) { const pagination = this . snapshot . pagination ; this . resetPagination ( pagination , pagination . count + amount ) ; } private decreasePagination ( amount ) { const pagination = this . snapshot . pagination ; if ( pagination ) { this . resetPagination ( pagination , pagination . count - amount ) ; } } private buildReferencesIdMap ( ) { if ( this . snapshot . references ) { this . internalReferencesIdMap = buildReferencesKeyBy ( this . snapshot . references , this . options . referencesIdKeys ) ; } } constructor ( initialState = { entities : [ ] as TEntity [ ] } , options = { idKey : '_id' } ) { super ( initialState ) ; this . options = { idKey : '_id' , ... options } ; if ( ! this . options . idKey ) { throw new Error ( `idKey is required in EntityStore` ) ; } this . buildReferencesIdMap ( ) ; } example initialize ( entities , pagination ? ) { const state = this . snapshot ; state . entities = entities || [ ] ; state . pagination = pagination ; this . next ( state ) ; } example initializeWithReferences ( entities , references , pagination ? ) { const state = this . snapshot ; state . entities = entities || [ ] ; state . pagination = pagination ; state . references = references ; this . buildReferencesIdMap ( ) ; this . next ( state ) ; } param entity param references param addOptions private addInternal ( entity , references ? , addOptions ? ) { const addEntities = helpers . coerceArray ( entity ) ; if ( addEntities . length === 0 ) { return ; } const state = this . snapshot ; state . entities = produce ( state . entities ) . add ( addEntities , addOptions ) ; if ( state . references ) { mergeReferences ( state . references , references , this . options . referencesIdKeys ) ; this . buildReferencesIdMap ( ) ; } if ( state . pagination ) { this . increasePagination ( addEntities . length ) ; if ( addOptions && ! addOptions . prepend && addOptions . autoGotoLastPage ) { state . pagination . pageIndex = state . pagination . pageCount ; } } this . next ( state ) ; } example add ( entity , addOptions ? ) { this . addInternal ( entity , undefined , addOptions ) ; } example addWithReferences ( entity , references , addOptions ? ) { this . addInternal ( entity , references , addOptions ) ; } example private updateInternal ( idsOrFn , newStateOrFn , references ? ) { const ids = helpers . coerceArray ( idsOrFn ) ; const state = this . snapshot ; for ( let i = 0 ; i < state . entities . length ; i ++ ) { const oldEntity = state . entities [ i ] ; if ( ids . indexOf ( oldEntity [ this . options . idKey ] ) > - 1 ) { const newState = helpers . isFunction ( newStateOrFn ) ? ( newStateOrFn as any ) ( oldEntity ) : newStateOrFn ; state . entities [ i ] = { ... ( oldEntity as any ) , ... newState } ; } } state . entities = [ ... state . entities ] ; if ( state . references ) { mergeReferences ( state . references , references , this . options . referencesIdKeys ) ; this . buildReferencesIdMap ( ) ; } this . next ( state ) ; } example update ( idsOrFn , newStateOrFn ) { this . updateInternal ( idsOrFn , newStateOrFn , undefined ) ; } example updateWithReferences ( idsOrFn , newStateOrFn , references ) { this . updateInternal ( idsOrFn , newStateOrFn , references ) ; } example remove ( id ) ; remove ( predicate ) ; remove ( idsOrFn ? ) { const state = this . snapshot ; const originalLength = state . entities . length ; state . entities = produce ( state . entities , this . options ) . remove ( idsOrFn ) ; this . decreasePagination ( originalLength - state . entities . length ) ; this . next ( state ) ; } trackBy = ( _index , entity ) => { return entity [ this . options . idKey ] ; } ; clearPagination ( ) { const state = this . snapshot ; state . pagination = null ; this . next ( state ) ; } clear ( ) { const state = this . snapshot ; state . entities = [ ] ; state . pagination = null ; state . references = null ; this . next ( state ) ; } }