import { ObjectFetcher } from "graphql-ts-client-api" ; import { Dispatch , SetStateAction } from "react" ; import { MutationResult } from "../../entities/MutationResult" ; import { QueryArgs } from "../../entities/QueryArgs" ; import { QueryResult } from "../../entities/QueryResult" ; import { State } from "../State" ; import { QueryResultChangeEvent , StateManagerImpl , StateValueChangeEvent , StateValueChangeListener } from "./StateManagerImpl" ; import { StateValue } from "./StateValue" ; import { OptionArgs , VariableArgs } from "./Args" ; import { AsyncOptions , MutationOptions , ParameterizedStateAccessingOptions , QueryOptions , ReleasePolicy , ReleasePolicyOptions , StateAccessingOptions } from "../Types" ; export class StateValueHolder { private stateValue ? : StateValue ; private previousOptionArgs ? : OptionArgs ; private stateValueChangeListener ? : StateValueChangeListener ; private releasePolicy ? : ReleasePolicy < any > ; private deferred ? : { readonly state : State < any > ; readonly scopePath : string ; readonly options ? : StateAccessingOptions & ReleasePolicyOptions < any > ; } ; constructor ( private stateManager , private scopePath , private localUpdater ) { } get ( ) { const value = this . stateValue ; if ( value === undefined ) { throw new Error ( "Illegal StateValueHolder that has not been set or has been released" ) ; } return value ; } set ( state , scopePath , options ? ) { const serializableOptions = this . serializableOptions ( options ) ; const optionArgs = OptionArgs . of ( serializableOptions ) ; if ( this . stateValue ?. stateInstance ?. state [ " $name" ] === state [ " $name" ] && this . scopePath === scopePath && this . previousOptionArgs ?. key === optionArgs ?. key ) { return ; } if ( this . stateValue ?. loadable . loading ) { this . deferred = { state , scopePath , options } ; return ; } this . release ( ) ; this . scopePath = scopePath ; this . previousOptionArgs = optionArgs ; this . stateValue = this . stateManager . scope ( scopePath ) . instance ( state , serializableOptions ?. scope ?? "auto" ) . retain ( VariableArgs . of ( ( serializableOptions as Partial < ParameterizedStateAccessingOptions < any > > ) ?. variables ) ) ; this . stateValueChangeListener = ( e ) => { if ( e . stateValue === this . stateValue ) { const deferred = this . deferred ; this . localUpdater ( old => old + 1 ) ; if ( deferred !== undefined && ! this . stateValue . loadable . loading ) { this . deferred = undefined ; this . set ( deferred . state , deferred . scopePath , deferred . options ) ; } } } ; this . stateManager . addStateValueChangeListener ( this . stateValueChangeListener ) ; } release ( ) { try { const listener = this . stateValueChangeListener ; if ( listener !== undefined ) { this . stateValueChangeListener = undefined ; this . stateManager . removeStateValueChangeListener ( listener ) ; } } finally { const value = this . stateValue ; if ( value !== undefined ) { this . stateValue = undefined ; this . previousOptionArgs = undefined ; value . stateInstance . release ( value . args , this . releasePolicy ) ; } } } private serializableOptions ( options ? ) { if ( options === undefined ) { this . releasePolicy = undefined ; return undefined ; } const releasePolicy = ( options as AsyncOptions < any > ) . releasePolicy ; if ( releasePolicy === undefined ) { this . releasePolicy = undefined ; return options ; } this . releasePolicy = releasePolicy ; const newOptions = { ... options } ; newOptions . releasePolicy = undefined ; return newOptions ; } } export class QueryResultHolder { private queryResult ? : QueryResult ; private queryResultChangeListener ? : ( e : QueryResultChangeEvent ) => void ; private releasePolicy ? : ReleasePolicy < any > ; private deferred ? : { readonly fetcher : ObjectFetcher < string , object , object > ; readonly windowId ? : string ; readonly ids ? : ReadonlyArray < any > ; readonly options ? : QueryOptions < any > & ReleasePolicyOptions < any > ; } ; constructor ( private stateManager , private localUpdater ) { } get ( ) { const result = this . queryResult ; if ( result === undefined ) { throw new Error ( "Illegal QueryResultHolder that has not been set or has been released" ) ; } return result ; } set ( fetcher , windowId , ids ? , options ? ) { const serializableOptions = this . serializableOptions ( options ) ; const oldQueryArgs = this . queryResult ?. queryArgs ; const newQueryArgs = QueryArgs . create ( fetcher , windowId ? { schema : this . stateManager . entityManager . schema , loadMode : "initial" } : undefined , ids , OptionArgs . of ( serializableOptions ) ) ; if ( oldQueryArgs ?. key === newQueryArgs . key ) { return ; } if ( this . queryResult ?. loadable . loading ) { this . deferred = { fetcher , windowId , ids , options } ; return ; } if ( ! this . stateManager . entityManager . schema . isAcceptable ( fetcher . fetchableType ) ) { throw new Error ( "Cannot accept that fetcher because it is not configured in the state manager" ) ; } this . release ( ) ; this . queryResult = this . stateManager . entityManager . retain ( newQueryArgs ) ; this . queryResultChangeListener = ( e ) => { if ( e . queryResult === this . queryResult ) { const deferred = this . deferred ; this . localUpdater ( old => old + 1 ) ; if ( deferred !== undefined && ! this . queryResult . loadable . loading ) { this . deferred = undefined ; this . set ( deferred . fetcher , deferred . windowId , deferred . ids , deferred . options ) ; } } } ; this . stateManager . addQueryResultChangeListener ( this . queryResultChangeListener ) ; } release ( ) { try { const listener = this . queryResultChangeListener ; if ( listener !== undefined ) { this . queryResultChangeListener = undefined ; this . stateManager . removeQueryResultChangeListener ( listener ) ; } } finally { const result = this . queryResult ; if ( result !== undefined ) { this . queryResult = undefined ; this . stateManager . entityManager . release ( result . queryArgs , this . releasePolicy ) ; } } } private serializableOptions ( options ? ) { if ( options === undefined ) { this . releasePolicy = undefined ; return undefined ; } const releasePolicy = ( options as AsyncOptions < any > ) . releasePolicy ; if ( releasePolicy === undefined ) { this . releasePolicy = undefined ; return options ; } this . releasePolicy = releasePolicy ; const newOptions = { ... options } ; newOptions . releasePolicy = undefined ; return newOptions ; } } export class MutationResultHolder { private mutationResult ? : MutationResult ; private previousFetcher ? : ObjectFetcher < "Mutation" , any , any > ; private previousFetcherJson ? : string ; private previousVariables ? : any ; private previousVariablesArgs ? : VariableArgs ; constructor ( private stateManager , private localUpdater ) { } get ( ) { const result = this . mutationResult ; if ( result === undefined ) { throw new Error ( "Illegal QueryResultHolder that has not been set or has been released" ) ; } return result ; } set ( fetcher , options ? ) { let result ; if ( this . isSameFetcher ( fetcher ) && this . isSameVariables ( options ?. variables ) ) { result = this . mutationResult ! ; } else { result = new MutationResult ( this . stateManager , this . localUpdater , fetcher , options ?. variables ) ; this . mutationResult = result ; this . localUpdater ( old => old + 1 ) ; } result . onSuccess = options ?. onSuccess ; result . onError = options ?. onError ; result . onCompelete = options ?. onCompelete ; } private isSameFetcher ( fetcher ) { if ( this . previousFetcher === fetcher ) { return true ; } const json = fetcher . toJSON ( ) ; if ( this . previousFetcherJson === json ) { return true ; } this . previousFetcher = fetcher ; this . previousFetcherJson = json ; return false ; } private isSameVariables ( variables ) { if ( this . previousVariables === variables ) { return true ; } const args = VariableArgs . of ( variables ) ; if ( this . previousVariablesArgs ?. key === args ?. key ) { return true ; } this . previousVariables = variables ; this . previousVariablesArgs = args ; return false ; } }