import type { GQtyClient , GQtyError } from 'gqty' ; import type { SchedulerPromiseValue } from 'gqty/Scheduler' ; import { useForceUpdate , useIsMounted , useIsomorphicLayoutEffect , } from '../common' ; import type { ReactClientOptionsWithDefaults } from '../utils' ; export interface UsePreparedQueryOptions { suspense ? : boolean ; } export interface PreparedQuery < GeneratedSchema extends { query : object ; } , TFunction extends ( query : GeneratedSchema [ 'query' ] , args : any ) => any > { preload ( ... [ args ] ) : Promise < ReturnType < TFunction > > ; refetch ( ... [ args ] ) : Promise < ReturnType < TFunction > > ; usePrepared ( opts ? ) : { data : ReturnType < TFunction > | undefined ; error ? : GQtyError | undefined ; isLoading : boolean ; isRefetching : boolean ; called : boolean ; } ; callback : TFunction ; } export interface PrepareQuery < GeneratedSchema extends { query : object ; } > { < TFunction extends ( query : GeneratedSchema [ 'query' ] , args : any ) => any > ( fn ) : PreparedQuery < GeneratedSchema , TFunction > ; } export function createPrepareQuery < GeneratedSchema extends { query : object ; mutation : object ; subscription : object ; } > ( { prefetch , query , refetch : refetchClient } , { defaults : { preparedSuspense : defaultSuspense } , } ) { const emptyDataSymbol = Symbol ( ) ; const prepareQuery = function prepareQuery < TFunction extends ( query : GeneratedSchema [ 'query' ] , args : any ) => any > ( fn ) : PreparedQuery < GeneratedSchema , TFunction > { const state = { data : emptyDataSymbol , isLoading : false , isRefetching : false , called : false , } ; let promiseOnTheFly ; const subscribers = new Set < ( ) => void > ( ) ; function updateSubs ( ) { setTimeout ( ( ) => { if ( subscribers . size ) { for ( const cb of subscribers ) cb ( ) ; } } , 0 ) ; } async function refetch ( ... [ args ] ) { state . called = true ; state . isLoading = true ; state . isRefetching = true ; updateSubs ( ) ; try { await refetchClient ( ( ) => fn ( query , args as Parameters < TFunction > [ '1' ] ) ) ; return await preload ( args ) ; } finally { state . isLoading = false ; state . isRefetching = false ; } } async function preload ( ... [ args ] ) { state . called = true ; state . isLoading = true ; try { const result = prefetch ( ( query ) => fn ( query , args as Parameters < TFunction > [ '1' ] ) as ReturnType < TFunction > ) ; if ( result instanceof Promise ) { promiseOnTheFly = result ; updateSubs ( ) ; result . schedulerPromise . then ( ( { error } ) => { if ( error ) { state . error = error ; } else { delete state . error ; } } ) ; const data = ( state . data = await result ) ; if ( promiseOnTheFly === result ) promiseOnTheFly = undefined ; return data ; } else { delete state . error ; } return ( state . data = result ) ; } finally { state . isLoading = false ; updateSubs ( ) ; } } function usePrepared ( { suspense = defaultSuspense , } = { } ) { const isMounted = useIsMounted ( ) ; const forceUpdate = useForceUpdate ( ) ; if ( promiseOnTheFly ) { const promise = promiseOnTheFly . then ( ( ) => { if ( isMounted . current ) forceUpdate ( ) ; } ) ; if ( suspense ) throw promise ; } useIsomorphicLayoutEffect ( ( ) => { let isMounted = true ; const cb = ( ) => isMounted && forceUpdate ( ) ; subscribers . add ( cb ) ; return ( ) => { isMounted = false ; subscribers . delete ( cb ) ; } ; } , [ forceUpdate ] ) ; return { ... state , data : state . data !== emptyDataSymbol ? state . data : undefined , } ; } return { preload , refetch , usePrepared , callback : fn , } ; } ; return prepareQuery ; }