import { UseQueryOptions, UseMutationOptions } from "@tanstack/react-query"; // --------------------------------------------------------------------------- // React Query shared types // --------------------------------------------------------------------------- export type QueryConfig = Omit< UseQueryOptions, "queryKey" | "queryFn" >; // --------------------------------------------------------------------------- // Endpoint interfaces // --------------------------------------------------------------------------- export interface EndpointWithInput { call(input: TInput): Promise; queryKey(input: TInput): readonly unknown[]; queryOptions( config: { input: TInput } & QueryConfig, ): UseQueryOptions; } export interface EndpointWithoutInput { call(): Promise; queryKey(): readonly unknown[]; queryOptions( config?: QueryConfig, ): UseQueryOptions; } export type Endpoint = TInput extends void ? EndpointWithoutInput : EndpointWithInput; // --------------------------------------------------------------------------- // Endpoint builder // --------------------------------------------------------------------------- export function endpoint( service: string, action: string, execute: (input: TInput) => Promise, queryKeyBuilder?: (input: TInput) => readonly unknown[], ) { const buildKey = (input?: TInput): readonly unknown[] => { if (queryKeyBuilder && input !== undefined) { return queryKeyBuilder(input as TInput); } return input === undefined ? [service, action] : [service, action, input]; }; const call = (input: TInput) => execute(input); const queryKey = (input?: TInput) => buildKey(input); const queryOptions = ( config?: { input?: TInput } & QueryConfig, ): UseQueryOptions< TResponse, Error, TResponse, readonly unknown[] > => { const { input, ...rest } = config ?? {}; return { ...rest, queryKey: buildKey(input), queryFn: () => execute(input as TInput), }; }; const mutationOptions = ( config?: Omit< UseMutationOptions< TResponse, Error, TInput >, "mutationFn" >, ): UseMutationOptions< TResponse, Error, TInput > => { return { ...config, mutationFn: ( variables: TInput, ): Promise => execute(variables), }; }; return { call, queryKey, queryOptions, mutationOptions }; } // --------------------------------------------------------------------------- // Helper utilities // --------------------------------------------------------------------------- export function unwrap(response: { data: T } | T): T { if ( response && typeof response === "object" && "data" in (response as object) ) { return (response as { data: T }).data; } return response as T; }