import { EdenClient, type EdenTreatyClient, type EmptyToVoid, type ExtractEdenTreatyRouteParams, type ExtractEdenTreatyRouteParamsInput, getPathParam, type HttpMutationMethod, type HttpQueryMethod, type InferRouteBody, type InferRouteError, type InferRouteOptions, type InferRouteOutput, parsePathsAndMethod, } from '@aydee-app/eden' import type { CancelOptions, InfiniteData, InvalidateOptions, InvalidateQueryFilters, Query, QueryFilters, QueryKey, RefetchOptions, RefetchQueryFilters, ResetOptions, SetDataOptions, Updater, UseInfiniteQueryOptions, UseQueryOptions, } from '@tanstack/react-query' import type { AnyElysia, RouteSchema } from 'elysia' import type { EdenQueryConfig } from '../../config' import { contextProps, type EdenContextPropsBase, type EdenContextState, getQueryType, } from '../../context' import type { EdenFetchInfiniteQueryOptions } from '../../integration/hooks/fetch-infinite' import type { EdenFetchQueryOptions } from '../../integration/hooks/fetch-query' import type { EdenUseInfiniteQueryOptions } from '../../integration/hooks/use-infinite-query' import type { EdenUseMutationOptions } from '../../integration/hooks/use-mutation' import type { ExtractCursorType, InfiniteCursorKey, ReservedInfiniteQueryKeys, } from '../../integration/internal/infinite-query' import { type EdenQueryKey, getMutationKey, getQueryKey, } from '../../integration/internal/query-key' import type { DeepPartial, Override, ProtectedIntersection } from '../../utils/types' export type EdenTreatyReactQueryUtils< TElysia extends AnyElysia, TSSRContext, > = ProtectedIntersection< DecoratedEdenTreatyReactQueryContextProps, EdenTreatyReactQueryUtilsProxy > export type DecoratedEdenTreatyReactQueryContextProps< TElysia extends AnyElysia, TSSRContext, > = Omit, 'client'> & { client: EdenTreatyClient } export type EdenTreatyReactQueryContextProps< TElysia extends AnyElysia, TSSRContext, > = EdenContextPropsBase & { client: EdenClient } export type EdenTreatyReactQueryUtilsProxy< TSchema extends Record, TPath extends any[] = [], TRouteParams = ExtractEdenTreatyRouteParams, > = EdenTreatyQueryUtilsUniversalUtils & { [K in keyof TSchema]: TSchema[K] extends RouteSchema ? EdenTreatyReactQueryUtilsMapping : EdenTreatyReactQueryUtilsProxy } & ({} extends TRouteParams ? {} : ( params: ExtractEdenTreatyRouteParamsInput, ) => EdenTreatyReactQueryUtilsProxy< TSchema[Extract], TPath >) type EdenTreatyReactQueryUtilsMapping< TRoute extends RouteSchema, TPath extends any[] = [], TMethod = '', TInput extends InferRouteOptions = InferRouteOptions, > = TMethod extends HttpQueryMethod ? EdenTreatyQueryUtilsQueryUtils & (InfiniteCursorKey extends keyof (TInput['params'] & TInput['query']) ? EdenTreatyQueryUtilsInfiniteUtils : {}) : TMethod extends HttpMutationMethod ? EdenQueryUtilsMutationUtils : `Unknown HTTP Method: ${TMethod & string}` export type EdenTreatyQueryUtilsQueryUtils< TRoute extends RouteSchema, TPath extends any[] = [], TInput = InferRouteOptions['query'], TOutput = InferRouteOutput, TError = InferRouteError, TKey extends QueryKey = EdenQueryKey, > = { fetch: ( input: EmptyToVoid, options?: EdenFetchQueryOptions, ) => Promise prefetch: ( input: EmptyToVoid, options?: EdenFetchQueryOptions, ) => Promise ensureData: ( input: EmptyToVoid, options?: EdenFetchQueryOptions, ) => Promise invalidate: ( input?: DeepPartial, filters?: Override< InvalidateQueryFilters, { predicate?: (query: Query) => boolean } >, options?: InvalidateOptions, ) => Promise refetch: ( input?: TInput, filters?: RefetchQueryFilters, options?: RefetchOptions, ) => Promise cancel: (input?: TInput, filters?: QueryFilters, options?: CancelOptions) => Promise reset: (input?: TInput, options?: ResetOptions) => Promise setData: ( input: TInput, updater: Updater, options?: SetDataOptions, ) => void /** * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientsetquerydata */ setQueriesData( input: TInput, filters: QueryFilters, updater: Updater, options?: SetDataOptions, ): [QueryKey, TOutput] getData: (input: EmptyToVoid) => TOutput | undefined options: ( input: EmptyToVoid, options?: UseQueryOptions, ) => UseQueryOptions } export type EdenTreatyQueryUtilsInfiniteUtils< TRoute extends RouteSchema, TPath extends any[] = [], TInput = InferRouteOptions['query'], TOutput = InferRouteOutput, TError = InferRouteError, TKey extends QueryKey = EdenQueryKey, > = { fetchInfinite: ( input: EmptyToVoid, options?: EdenFetchInfiniteQueryOptions, ) => Promise>>> prefetchInfinite: ( input: EmptyToVoid, options?: EdenFetchQueryOptions, ) => Promise getInfiniteData: ( input: EmptyToVoid, ) => InfiniteData>> | undefined setInfiniteData: ( input: TInput, updater: Updater< InfiniteData>> | undefined, InfiniteData>> | undefined >, options?: SetDataOptions, ) => void infiniteOptions: ( input: EmptyToVoid, options?: EdenUseInfiniteQueryOptions, ) => UseInfiniteQueryOptions } export type EdenQueryUtilsMutationUtils< TRoute extends RouteSchema, TPath extends any[] = [], TBody = InferRouteBody, TOutput = InferRouteOutput, TError = InferRouteError, _TKey extends QueryKey = EdenQueryKey, > = { setMutationDefaults( options: | EdenUseMutationOptions | ((args: { canonicalMutationFn: NonNullable< EdenUseMutationOptions['mutationFn'] > }) => EdenUseMutationOptions), ): void getMutationDefaults(): EdenUseMutationOptions | undefined isMutating(): number } /** * Utility hooks available at all levels of the utilities proxy. */ export type EdenTreatyQueryUtilsUniversalUtils = { /** * Invalidate the full router * @link https://trpc.io/docs/v10/useContext#query-invalidation * @link https://tanstack.com/query/v5/docs/framework/react/guides/query-invalidation */ invalidate( input?: undefined, filters?: InvalidateQueryFilters, options?: InvalidateOptions, ): Promise } export function createEdenTreatyQueryUtils( context: EdenContextState, config?: EdenQueryConfig, ): EdenTreatyReactQueryUtils { // const clientProxy = createTRPCClientProxy(context.client) const proxy = createEdenTreatyQueryUtilsProxy(context, config) const utils = new Proxy(() => {}, { get: (_target, path: string, _receiver): any => { const contextName = path as (typeof contextProps)[number] // if (contextName === 'client') { // return clientProxy // } if (contextProps.includes(contextName)) { return context[contextName] } return proxy[path as never] }, }) return utils as any } /** * @todo use config for something... */ export function createEdenTreatyQueryUtilsProxy( context: EdenContextState, config?: EdenQueryConfig, originalPaths: string[] = [], pathParams: Record[] = [], ): EdenTreatyReactQueryUtils { const proxy = new Proxy(() => {}, { get: (_target, path: string, _receiver) => { const nextPaths = path === 'index' ? [...originalPaths] : [...originalPaths, path] return createEdenTreatyQueryUtilsProxy(context, config, nextPaths, pathParams) }, apply: (_target, _thisArg, args) => { const pathParam = getPathParam(args) if (pathParam?.key != null) { const allPathParams = [...pathParams, pathParam.param] const pathsWithParams = [...originalPaths, `:${pathParam.key}`] return createEdenTreatyQueryUtilsProxy(context, config, pathsWithParams, allPathParams) } const argsCopy = [...args] /** * @example ['api', 'hello', 'get', 'invalidate'] */ const pathsCopy = [...originalPaths] /** * @example * * Original array: ['api', 'hello', 'get', 'invalidate'] * * Hook: 'invalidate' * * Resulting array: ['api', 'hello', 'get'] */ const hook = pathsCopy.pop() ?? '' /** * This will trim the method from the {@link pathsCopy} if it still exists. * * @example * * Previous array: ['api', 'hello', 'get'] * * Method: 'get' * * Resulting array: ['api', 'hello'] */ const { paths } = parsePathsAndMethod(pathsCopy) const queryType = getQueryType(hook) // The rest of the args are passed directly to the function. const firstArg = argsCopy.shift() let input: any = undefined if (firstArg != null) { input ??= {} input.query = firstArg } if (pathParams.length) { input ??= {} input.params = {} for (const param of pathParams) { for (const key in param) { input.params[key] = param[key] } } } const queryKey = getQueryKey(paths, input, queryType) switch (hook) { case 'fetch': { return context.fetchQuery(queryKey, ...argsCopy) } case 'fetchInfinite': { return context.fetchInfiniteQuery(queryKey, argsCopy[0]) } case 'prefetch': { return context.prefetchQuery(queryKey, ...argsCopy) } case 'prefetchInfinite': { return context.prefetchInfiniteQuery(queryKey, argsCopy[0]) } case 'ensureData': { return context.ensureQueryData(queryKey, ...argsCopy) } case 'invalidate': { return context.invalidateQueries(queryKey, ...argsCopy) } case 'reset': { return context.resetQueries(queryKey, ...argsCopy) } case 'refetch': { return context.refetchQueries(queryKey, ...argsCopy) } case 'cancel': { return context.cancelQuery(queryKey, ...argsCopy) } case 'setData': { return context.setQueryData(queryKey, argsCopy[0], argsCopy[1]) } case 'setQueriesData': { return context.setQueriesData(queryKey, argsCopy[0], argsCopy[1], argsCopy[2]) } case 'setInfiniteData': { return context.setInfiniteQueryData(queryKey, argsCopy[0], argsCopy[1]) } case 'getData': { return context.getQueryData(queryKey) } case 'getInfiniteData': { return context.getInfiniteQueryData(queryKey) } case 'setMutationDefaults': { return context.setMutationDefaults(getMutationKey(paths), firstArg) } case 'getMutationDefaults': { return context.getMutationDefaults(getMutationKey(paths)) } case 'isMutating': { return context.isMutating({ mutationKey: getMutationKey(paths) }) } default: { throw new TypeError(`eden.${originalPaths.join('.')} is not a function`) } } }, }) return proxy as any }