import { action , computed , makeObservable , observable , runInAction } from 'mobx' ; import { ISyncExecutor , SyncExecutor } from '@cloudbeaver/core-executor' ; import { MetadataMap } from '@cloudbeaver/core-utils' ; import { CachedResource , CachedResourceKey , ICachedResourceMetadata } from './CachedResource' ; import type { CachedResourceIncludeArgs , CachedResourceValueIncludes } from './CachedResourceIncludes' ; import { ResourceKey , resourceKeyList , ResourceKeyList , ResourceKeyUtils } from './ResourceKeyList' ; export type CachedMapResourceKey < TResource > = CachedResourceKey < TResource > ; export type CachedMapResourceValue < TResource > = TResource extends CachedResource < Map < any , infer T > , any , any , any > ? T : never ; export type CachedMapResourceArguments < TResource > = TResource extends CachedMapResource < any , any , infer T > ? T : never ; export type CachedMapResourceListGetter < TValue , TIncludes > = Array < CachedMapResourceGetter < TValue , TIncludes > > ; export type CachedMapResourceGetter < TValue , TIncludes > = CachedResourceValueIncludes < TValue , TIncludes > | undefined ; export type CachedMapResourceLoader < TRealKey extends ResourceKey < TKey > , TKey , TValue , TIncludes > = TRealKey extends ResourceKeyList < TKey > ? Array < CachedResourceValueIncludes < TValue , TIncludes > > : CachedResourceValueIncludes < TValue , TIncludes > ; export interface ICachedMapResourceMetadata extends ICachedResourceMetadata { includes : string [ ] ; } export const CachedMapAllKey = resourceKeyList < any > ( [ Symbol ( '@cached-map-resource/all' ) ] , 'all' ) ; export abstract class CachedMapResource < TKey , TValue , TArguments = Record < string , any > > extends CachedResource < Map < TKey , TValue > , ResourceKey < TKey > , TKey , string [ ] | undefined > { readonly onItemAdd : ISyncExecutor < ResourceKey < TKey > > ; readonly onItemDelete : ISyncExecutor < ResourceKey < TKey > > ; protected metadata : MetadataMap < TKey , ICachedMapResourceMetadata > ; get values ( ) : TValue [ ] { return Array . from ( this . data . values ( ) ) ; } get keys ( ) : TKey [ ] { return Array . from ( this . data . keys ( ) ) ; } constructor ( defaultIncludes ? , defaultValue ? ) { super ( defaultValue || new Map ( ) , defaultIncludes ) ; this . onItemAdd = new SyncExecutor < ResourceKey < TKey > > ( null ) ; this . onItemDelete = new SyncExecutor < ResourceKey < TKey > > ( null ) ; this . metadata = new MetadataMap ( ( ) => observable ( { outdated : true , loading : false , exception : null , includes : observable ( [ ... this . defaultIncludes ] ) , } , undefined , { deep : false } ) ) ; this . addAlias ( CachedMapAllKey , key => { if ( this . keys . length > 0 ) { return resourceKeyList ( this . keys , CachedMapAllKey . mark ) ; } return resourceKeyList ( [ ] ) ; } ) ; makeObservable < this , 'dataSet' | 'dataDelete' > ( this , { set : action , delete : action , clear : action , dataSet : action , dataDelete : action , values : computed , keys : computed , } ) ; } deleteInResource < T = TKey > ( resource , map ? ) { this . onItemDelete . addHandler ( param => { try { if ( this . logActivity ) { console . group ( this . getActionPrefixedName ( ' outdate - ' + resource . getName ( ) ) ) ; } if ( map ) { param = map ( param ) as any as TKey ; } resource . delete ( param as any as T ) ; } finally { if ( this . logActivity ) { console . groupEnd ( ) ; } } } ) ; return this ; } isIncludes ( key , includes ) { key = this . transformParam ( key ) ; return ResourceKeyUtils . every ( key , key => { const metadata = this . metadata . get ( key ) ; return includes . every ( include => metadata . includes . includes ( include ) ) ; } ) ; } getException ( key ) ; getException ( key ) ; getException ( key ) ; getException ( key ) { key = this . transformParam ( key ) ; return ResourceKeyUtils . map ( key , key => this . metadata . get ( key ) . exception ) ; } isOutdated ( key ) { if ( this . isAlias ( key ) && ! this . isAliasLoaded ( key ) ) { return true ; } key = this . transformParam ( key ) ; return ResourceKeyUtils . some ( key , key => { const metadata = this . metadata . get ( key ) ; return metadata . outdated ; } ) ; } isDataLoading ( key ) { key = this . transformParam ( key ) ; return ResourceKeyUtils . some ( key , key => this . metadata . get ( key ) . loading ) ; } markDataLoading ( key , includes ? ) { key = this . transformParam ( key ) ; ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; metadata . loading = true ; } ) ; } markDataLoaded ( key , includes ? ) { key = this . transformParam ( key ) ; if ( includes ) { this . commitIncludes ( key , includes ) ; } ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; metadata . loading = false ; } ) ; } markDataError ( exception , key ) { if ( this . isAlias ( key ) && ! this . isAliasLoaded ( key ) ) { this . loadedKeys . push ( key ) ; } key = this . transformParam ( key ) ; ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; metadata . exception = exception ; metadata . outdated = false ; } ) ; this . onDataError . execute ( { param : key , exception } ) ; } markOutdated ( ) ; markOutdated ( key ) ; markOutdated ( key ? ) { if ( ( ( key === undefined ? this . scheduler . executing : this . scheduler . isExecuting ( key ) ) ) && ! this . outdateWaitList . some ( param => this . includes ( key ! , param ) ) ) { this . outdateWaitList . push ( key as ResourceKey < TKey > ) ; return ; } this . markOutdatedSync ( key as ResourceKey < TKey > ) ; } cleanError ( ) ; cleanError ( key ) ; cleanError ( key ? ) { if ( key === undefined ) { key = resourceKeyList ( this . keys ) ; } else { key = this . transformParam ( key ) ; } ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; metadata . exception = null ; } ) ; } markUpdated ( ) ; markUpdated ( key ) ; markUpdated ( key ? ) { if ( key === undefined ) { key = resourceKeyList ( this . keys ) ; } else { if ( this . isAlias ( key ) && ! this . isAliasLoaded ( key ) ) { this . loadedKeys . push ( key ) ; } key = this . transformParam ( key ) ; } ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; metadata . outdated = false ; } ) ; } isLoaded ( key , includes ? ) { if ( this . isAlias ( key ) && ! this . isAliasLoaded ( key ) ) { return false ; } key = this . transformParam ( key ) ; return ResourceKeyUtils . every ( key , key => { if ( ! this . has ( key ) ) { return false ; } if ( includes ) { const metadata = this . metadata . get ( key ) ; if ( includes . some ( include => ! metadata . includes . includes ( include ) ) ) { return false ; } } return true ; } ) ; } get ( key ) ; get ( key ) ; get ( key ) ; get ( key ) { key = this . transformParam ( key ) ; return ResourceKeyUtils . map ( key , key => this . data . get ( key ) ) ; } set ( key , value ) ; set ( key , value ) ; set ( key , value ) ; set ( key , value ) { key = this . transformParam ( key ) ; ResourceKeyUtils . forEach ( key , ( key , i ) => { if ( i === - 1 ) { this . dataSet ( key , value as TValue ) ; } else { this . dataSet ( key , ( value as TValue [ ] ) [ i ] ) ; } } ) ; this . markUpdated ( key ) ; this . onItemAdd . execute ( key ) ; } delete ( key ) ; delete ( key ) ; delete ( key ) ; delete ( key ) { key = this . transformParam ( key ) ; this . onItemDelete . execute ( key ) ; ResourceKeyUtils . forEach ( key , key => { this . dataDelete ( key ) ; this . metadata . delete ( key ) ; } ) ; this . markUpdated ( key ) ; } clear ( ) { this . data . clear ( ) ; this . metadata . clear ( ) ; } async refresh < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async refresh < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async refresh < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async refresh < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) { await this . loadData ( key , true , includes ) ; return this . get ( key ) as Array < CachedResourceValueIncludes < TValue , T > > | CachedResourceValueIncludes < TValue , T > ; } async load < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async load < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async load < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) ; async load < T extends CachedResourceIncludeArgs < TValue , TArguments > = [ ] > ( key , includes ? ) { await this . loadData ( key , false , includes ) ; return this . get ( key ) as Array < CachedResourceValueIncludes < TValue , T > > | CachedResourceValueIncludes < TValue , T > ; } has ( key ) { if ( this . isAlias ( key ) && ! this . isAliasLoaded ( key ) ) { return false ; } key = this . transformParam ( key ) as TKey ; return this . data . has ( key ) ; } includes ( param , key ) { if ( param === key ) { return true ; } return ResourceKeyUtils . includes ( param , key ) ; } getIncludes ( key ? ) { if ( key === undefined ) { return this . defaultIncludes ; } key = this . transformParam ( key ) ; const metadata = this . metadata . get ( ResourceKeyUtils . first ( key ) ) ; return metadata . includes ; } getIncludesMap ( key ? , includes = this . defaultIncludes ) { const keyIncludes = this . getIncludes ( key ) ; return [ 'customIncludeBase' , ... includes , ... keyIncludes ] . reduce < any > ( ( map , key ) => { map [ key ] = true ; return map ; } , { } ) ; } protected dataSet ( key , value ) { this . data . set ( key , value ) ; } protected dataDelete ( key ) { this . data . delete ( key ) ; } protected lock ( param , second ) { if ( this . isAlias ( param ) || this . isAlias ( second ) ) { return true ; } return this . includes ( param , second ) ; } protected commitIncludes ( key , includes ) { key = this . transformParam ( key ) ; ResourceKeyUtils . forEach ( key , key => { const metadata = this . metadata . get ( key ) ; for ( const include of includes ) { if ( ! metadata . includes . includes ( include ) ) { metadata . includes . push ( include ) ; } } } ) ; } protected markOutdatedSync ( ) ; protected markOutdatedSync ( key ) ; protected markOutdatedSync ( key ? ) { if ( key === undefined ) { key = ResourceKeyUtils . join ( resourceKeyList ( this . keys ) , ... this . loadedKeys . map ( key => this . transformParam ( key ) ) ) ; this . loadedKeys = [ ] ; } else { if ( this . isAlias ( key ) ) { const index = this . loadedKeys . findIndex ( loadedKey => this . includes ( key ! , loadedKey ) ) ; if ( index >= 0 ) { this . loadedKeys . splice ( index , 1 ) ; } } key = this . transformParam ( key ) ; } runInAction ( ( ) => ResourceKeyUtils . forEach ( key ! , key => { const metadata = this . metadata . get ( key ) ; metadata . outdated = true ; } ) ) ; this . onDataOutdated . execute ( key ) ; } }