import { IObservableArray , values , observable , entries } from "mobx" ; import { fail , ObjectNode , mobxShallow , AnyObjectNode , IAnyComplexType } from "../../internal" ; let identifierCacheId = 0 ; internal hidden export class IdentifierCache { private cacheId = identifierCacheId ++ ; private cache = observable . map < string , IObservableArray < AnyObjectNode > > ( ) ; private lastCacheModificationPerId = observable . map < string , number > ( ) ; constructor ( ) { } private updateLastCacheModificationPerId ( identifier ) { const lcm = this . lastCacheModificationPerId . get ( identifier ) ; this . lastCacheModificationPerId . set ( identifier , lcm === undefined ? 1 : lcm + 1 ) ; } getLastCacheModificationPerId ( identifier ) { const modificationId = this . lastCacheModificationPerId . get ( identifier ) || 0 ; return `${ this . cacheId }-${ modificationId }` ; } addNodeToCache ( node , lastCacheUpdate = true ) { if ( node . identifierAttribute ) { const identifier = node . identifier ! ; if ( ! this . cache . has ( identifier ) ) { this . cache . set ( identifier , observable . array < AnyObjectNode > ( [ ] , mobxShallow ) ) ; } const set = this . cache . get ( identifier ) ! ; if ( set . indexOf ( node ) !== - 1 ) throw fail ( `Already registered` ) ; set . push ( node ) ; if ( lastCacheUpdate ) { this . updateLastCacheModificationPerId ( identifier ) ; } } } mergeCache ( node ) { values ( node . identifierCache ! . cache ) . forEach ( ( nodes ) => nodes . forEach ( ( child ) => { this . addNodeToCache ( child ) ; } ) ) ; } notifyDied ( node ) { if ( node . identifierAttribute ) { const id = node . identifier ! ; const set = this . cache . get ( id ) ; if ( set ) { set . remove ( node ) ; if ( ! set . length ) { this . cache . delete ( id ) ; } this . updateLastCacheModificationPerId ( node . identifier ! ) ; } } } splitCache ( node ) { const res = new IdentifierCache ( ) ; const basePath = node . path ; entries ( this . cache ) . forEach ( ( [ id , nodes ] ) => { let modified = false ; for ( let i = nodes . length - 1 ; i >= 0 ; i -- ) { if ( nodes [ i ] . path . indexOf ( basePath ) === 0 ) { res . addNodeToCache ( nodes [ i ] , false ) ; nodes . splice ( i , 1 ) ; modified = true ; } } if ( modified ) { this . updateLastCacheModificationPerId ( id ) ; } } ) ; return res ; } has ( type , identifier ) { const set = this . cache . get ( identifier ) ; if ( ! set ) return false ; return set . some ( ( candidate ) => type . isAssignableFrom ( candidate . type ) ) ; } resolve < IT extends IAnyComplexType > ( type , identifier ) { const set = this . cache . get ( identifier ) ; if ( ! set ) return null ; const matches = set . filter ( ( candidate ) => type . isAssignableFrom ( candidate . type ) ) ; switch ( matches . length ) { case 0 : return null ; case 1 : return matches [ 0 ] ; default : throw fail ( `Cannot resolve a reference to type '${ type . name }' with id: '${ identifier }' unambigously, there are multiple candidates: ${ matches . map ( ( n ) => n . path ) . join ( ", " ) }` ) ; } } }