import { EdenClient, type EdenClientError, type EdenCreateClient, httpBatchLink, type HttpBatchLinkOptions, httpLink, type HTTPLinkOptions, type InferRouteOptions, parsePathsAndMethod, } from '@aydee-app/eden' import { type SkipToken, useInfiniteQuery as __useInfiniteQuery, useQueries as __useQueries, useQuery as __useQuery, useQueryClient, useSuspenseInfiniteQuery as __useSuspenseInfiniteQuery, useSuspenseQueries as __useSuspenseQueries, useSuspenseQuery as __useSuspenseQuery, } from '@tanstack/react-query' import type { AnyElysia } from 'elysia' import * as React from 'react' import type { EdenQueryConfig } from '../../config' import type { EdenContextProps, EdenContextState, EdenProvider, SSRState } from '../../context' import { createUtilityFunctions, EdenQueryContext } from '../../context' import { type EdenUseInfiniteQueryOptions, edenUseInfiniteQueryOptions, type EdenUseInfiniteQueryResult, } from '../../integration/hooks/use-infinite-query' import type { EdenUseMutationOptions, EdenUseMutationResult, } from '../../integration/hooks/use-mutation' import { getEdenUseMutationOptions, useEdenMutation } from '../../integration/hooks/use-mutation' import { type EdenUseQueryOptions, edenUseQueryOptions, type EdenUseQueryResult, } from '../../integration/hooks/use-query' import { edenUseSubscription, type EdenUseSubscriptionOptions, } from '../../integration/hooks/use-subscription' import type { EdenUseSuspenseInfiniteQueryOptions, EdenUseSuspenseInfiniteQueryResult, } from '../../integration/hooks/use-suspense-infinite-query' import type { EdenUseSuspenseQueryOptions, EdenUseSuspenseQueryResult, } from '../../integration/hooks/use-suspense-query' import { getEdenQueryExtension } from '../../integration/internal/query-hook-extension' import type { EdenUseQueryOptionsForUseQueries } from '../../integration/internal/use-query-options-for-use-queries' import type { EdenUseQueryOptionsForUseSuspenseQueries } from '../../integration/internal/use-query-options-for-use-suspense-queries' import { createEdenTreatyQueryUtils } from './query-utils' import type { EdenTreatyUseQueries } from './use-queries' import { createTreatyUseQueriesProxy } from './use-queries' import type { EdenTreatyUseSuspenseQueries } from './use-suspense-queries' import { createTreatyUseSuspenseQueriesProxy } from './use-suspense-queries' export function createEdenTreatyQueryRootHooks< TElysia extends AnyElysia, TSSRContext = unknown, TError = EdenClientError, >(config?: EdenQueryConfig) { type ProviderContext = EdenContextState const Context = (config?.context ?? EdenQueryContext) as React.Context const createClient: EdenCreateClient = (options) => { return new EdenClient(options) } const createHttpClient = (options?: HTTPLinkOptions) => { return new EdenClient({ links: [httpLink(options)], }) } /** * @warning * Ensure that the Elysia.js server uses the batch plugin; * the types will not verify whether or not this is detected. */ const createHttpBatchClient = (options?: HttpBatchLinkOptions) => { return new EdenClient({ links: [httpBatchLink(options) as any], }) } const createContext = (props: EdenContextProps) => { const { abortOnUnmount = false, client, queryClient, ssrContext = null, ssrState = false, } = props const utilityFunctions = createUtilityFunctions({ client, queryClient }) return { abortOnUnmount, queryClient, client, ssrContext, ssrState, ...utilityFunctions, } } const createUtils = (props: EdenContextProps) => { const context = createContext(props) return createEdenTreatyQueryUtils(context) } const EdenProvider: EdenProvider = (props) => { const { abortOnUnmount = false, client, queryClient, ssrContext, children } = props const [ssrState, setSSRState] = React.useState(props.ssrState ?? false) const contextValue = React.useMemo(() => { return createContext({ abortOnUnmount, client, queryClient, ssrContext, ssrState }) }, [abortOnUnmount, client, queryClient, ssrContext, ssrState]) React.useEffect(() => { // Only updating state to `mounted` if we are using SSR. // This makes it so we don't have an unnecessary re-render when opting out of SSR. setSSRState((state) => (state ? 'mounted' : false)) }, []) return {children} } const useRawContext = () => { const context = React.useContext(Context) if (!context) { throw new Error( 'Unable to find tRPC Context. Did you forget to wrap your App inside `withTRPC` HoC?', ) } return context } const useContext = (context = useRawContext()) => { // Create and return a stable reference of the utils context. return React.useMemo(() => createEdenTreatyQueryUtils(context), [context]) } const useQuery = ( originalPaths: readonly string[], input?: InferRouteOptions | SkipToken, options?: EdenUseQueryOptions, ): EdenUseQueryResult => { const context = useRawContext() const parsed = parsePathsAndMethod(originalPaths) const queryOptions = edenUseQueryOptions(parsed, context, input, options, config) type HookResult = EdenUseQueryResult const queryClient = context.queryClient ?? useQueryClient() const hook = __useQuery(queryOptions, queryClient) as HookResult const { paths } = parsed hook.eden = React.useMemo(() => getEdenQueryExtension({ path: paths }), [paths]) return hook } const useSuspenseQuery = ( originalPaths: readonly string[], input: InferRouteOptions, options?: EdenUseSuspenseQueryOptions, ): EdenUseSuspenseQueryResult => { const context = useRawContext() const parsed = parsePathsAndMethod(originalPaths) const queryOptions = edenUseQueryOptions(parsed, context, input, options, config) type HookResult = EdenUseQueryResult const queryClient = context.queryClient ?? useQueryClient() const hook = __useSuspenseQuery(queryOptions as any, queryClient) as HookResult const { paths } = parsed hook.eden = React.useMemo(() => getEdenQueryExtension({ path: paths }), [paths]) return [hook.data, hook as any] } const useInfiniteQuery = ( originalPaths: readonly string[], input?: InferRouteOptions | SkipToken, options?: EdenUseInfiniteQueryOptions, ): EdenUseInfiniteQueryResult => { const context = useRawContext() const parsed = parsePathsAndMethod(originalPaths) const queryOptions = edenUseInfiniteQueryOptions(parsed, context, input, options, config) type HookResult = EdenUseInfiniteQueryResult const queryClient = context.queryClient ?? useQueryClient() const hook = __useInfiniteQuery(queryOptions, queryClient) as HookResult const { paths } = parsed hook.eden = React.useMemo(() => getEdenQueryExtension({ path: paths }), [paths]) return hook } const useSuspenseInfiniteQuery = ( originalPaths: readonly string[], input: InferRouteOptions, options?: EdenUseSuspenseInfiniteQueryOptions, ): EdenUseSuspenseInfiniteQueryResult => { const context = useRawContext() const parsed = parsePathsAndMethod(originalPaths) const queryOptions = edenUseInfiniteQueryOptions(parsed, context, input, options, config) type HookResult = EdenUseInfiniteQueryResult const queryClient = context.queryClient ?? useQueryClient() const hook = __useSuspenseInfiniteQuery(queryOptions as any, queryClient) as HookResult const { paths } = parsed hook.eden = React.useMemo(() => getEdenQueryExtension({ path: paths }), [paths]) return [hook.data, hook] as any } const useQueries: EdenTreatyUseQueries = (queriesCallback) => { const context = useRawContext() const { ssrState, queryClient, prefetchQuery, client } = context const proxy = createTreatyUseQueriesProxy(client) const queries: readonly EdenUseQueryOptionsForUseQueries[] = queriesCallback(proxy) // Not SSR. if (!(typeof window === 'undefined' && ssrState === 'prepass')) { return __useQueries({ queries }, queryClient) } for (const query of queries) { const shouldSsr = query.eden?.ssr !== false if (shouldSsr && !queryClient.getQueryCache().find(query)) { void prefetchQuery(query.queryKey, query) } } return __useQueries({ queries }, queryClient) } const useSuspenseQueries: EdenTreatyUseSuspenseQueries = (queriesCallback) => { const context = useRawContext() const { queryClient, client } = context const proxy = createTreatyUseSuspenseQueriesProxy(client) const queries: readonly EdenUseQueryOptionsForUseSuspenseQueries[] = queriesCallback(proxy) const hook = __useSuspenseQueries({ queries }, queryClient) return [hook.map((h) => h.data), hook] as any } const useMutation = ( originalPaths: readonly string[], input?: InferRouteOptions, options?: EdenUseMutationOptions, ): EdenUseMutationResult => { const context = useRawContext() const parsed = parsePathsAndMethod(originalPaths) const mutationOptions = getEdenUseMutationOptions(parsed, context, input, options, config) type HookResult = EdenUseMutationResult const queryClient = context.queryClient ?? useQueryClient() const hook = useEdenMutation(mutationOptions, queryClient) as HookResult const { paths } = parsed hook.eden = React.useMemo(() => getEdenQueryExtension({ path: paths }), [paths]) return hook } /* istanbul ignore next -- @preserve */ const useSubscription = ( path: readonly string[], input: unknown, opts: EdenUseSubscriptionOptions, ) => { const context = useRawContext() return edenUseSubscription(path, input, opts, context) } return { Provider: EdenProvider, createClient, createHttpClient, createHttpBatchClient, createContext, createUtils, useContext, useUtils: useContext, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useQueries, useSuspenseQueries, useMutation, useSubscription, } } export type EdenTreatyQueryRootHooks< TElysia extends AnyElysia = AnyElysia, TSSRContext = unknown, > = ReturnType>