@danstackme/apity
Version:
Type-safe API client generator for React applications with file-based routing and runtime validation
122 lines (115 loc) • 6.47 kB
text/typescript
import * as _tanstack_react_query from '@tanstack/react-query';
import { UseQueryOptions, UseMutationOptions, QueryClient } from '@tanstack/react-query';
import { AxiosInstance } from 'axios';
import * as react_jsx_runtime from 'react/jsx-runtime';
import { z } from 'zod';
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface Register {
}
type ConsumerFetchEndpoints = Register extends {
fetchEndpoints: infer T;
} ? T : never;
type ConsumerMutateEndpoints = Register extends {
mutateEndpoints: infer T;
} ? T : never;
interface ApiEndpoint<TMethod extends HttpMethod, TResponse = unknown, TBody = unknown, TQuery = unknown> {
method: TMethod;
response?: TResponse;
body?: TBody;
query?: TQuery;
}
type AnyApiEndpoint = ApiEndpoint<any, any, any, any>;
type FetchEndpoint<TResponse, TBody, TQuery> = ApiEndpoint<"GET", TResponse, TBody, TQuery>;
type MutateEndpoint<TMethod extends Exclude<HttpMethod, "GET">, TResponse, TBody, TQuery> = ApiEndpoint<TMethod, TResponse, TBody, TQuery>;
type ApiEndpoints = {
[path: string]: FetchEndpoint<any, any, any>[] | MutateEndpoint<Exclude<HttpMethod, "GET">, any, any, any>[] | readonly ApiEndpoint<HttpMethod>[];
};
interface ApiConfig {
baseUrl: string;
queryClient?: QueryClient;
client?: AxiosInstance;
headers?: Record<string, string>;
fetchEndpoints: ApiEndpoints;
mutateEndpoints: ApiEndpoints;
middleware?: {
before?: (config: any) => any;
after?: (response: any) => any;
onError?: (error: any) => any;
};
}
interface ApiContext {
client: AxiosInstance;
queryClient: QueryClient;
config: ApiConfig;
middleware: ((config: any) => any)[];
fetchEndpoints: ApiEndpoints;
mutateEndpoints: ApiEndpoints;
}
type GetPathParamType<TPath extends string> = HasPathParams<TPath> extends true ? {
params: ExtractPathParams<TPath>;
} : {
params?: ExtractPathParams<TPath>;
};
type UseFetchOptionsType<TPath extends FetchPath> = Omit<UseQueryOptions<FetchResponseFor<TPath, "GET">, Error, FetchResponseFor<TPath, "GET">, any>, "queryKey" | "queryFn"> & {
path: TPath;
} & GetPathParamType<TPath> & GetQueryParamType<TPath, "GET">;
type UseMutateOptionsType<TPath extends MutatePath, TMethod extends AvailableMutateMethods<TPath>> = Omit<UseMutationOptions<MutateResponseFor<TPath, TMethod>, Error, MutateBodyFor<TPath, TMethod>>, "mutationFn"> & {
path: TPath;
method: TMethod;
} & GetPathParamType<TPath> & GetQueryParamType<TPath, TMethod>;
type GetQueryParamType<TPath extends FetchPath | MutatePath, TMethod extends HttpMethod, TEndpoint extends AnyApiEndpoint = EndpointsByMethod<TPath, TMethod>> = HasRequiredField<NonNullable<TEndpoint["query"]>> extends true ? NonNullable<RequiredQueryParams<TEndpoint>> : NonNullable<OptionalQueryParams<TEndpoint>>;
type ExtractPathParams<TPath extends string> = TPath extends `${string}[${infer Param}]${infer Rest}` ? {
[K in Param]: string | number;
} & (Rest extends `${string}[${string}]${string}` ? ExtractPathParams<Rest> : {}) : {};
type FetchPath = keyof ConsumerFetchEndpoints & string;
type MutatePath = keyof ConsumerMutateEndpoints & string;
type MutateMethodsFor<P extends MutatePath, M extends HttpMethod> = Extract<ConsumerMutateEndpoints[P][number]["method"], M> extends never ? never : M;
type MutateResponseFor<P extends MutatePath, M extends HttpMethod> = Extract<ConsumerMutateEndpoints[P][number]["method"], M> extends never ? never : Extract<ConsumerMutateEndpoints[P][number], {
method: M;
}>["response"];
type MutateBodyFor<P extends MutatePath, M extends HttpMethod> = Extract<ConsumerMutateEndpoints[P][number]["method"], M> extends never ? never : Extract<ConsumerMutateEndpoints[P][number], {
method: M;
}>["body"];
type FetchResponseFor<P extends FetchPath, M extends HttpMethod> = Extract<ConsumerFetchEndpoints[P][number]["method"], M> extends never ? never : Extract<ConsumerFetchEndpoints[P][number], {
method: M;
}>["response"];
type AvailableMutateMethods<P extends MutatePath> = MutateMethodsFor<P, "POST"> | MutateMethodsFor<P, "PUT"> | MutateMethodsFor<P, "DELETE"> | MutateMethodsFor<P, "PATCH">;
type EndpointsByMethod<TPath extends FetchPath | MutatePath, TMethod extends HttpMethod> = TMethod extends "GET" ? Extract<ConsumerFetchEndpoints[TPath][number], {
method: "GET";
}> : Extract<ConsumerMutateEndpoints[TPath][number], {
method: TMethod;
}>;
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends {
[P in K]: T[K];
} ? never : K;
}[keyof T];
type HasRequiredField<T> = RequiredKeys<T> extends never ? false : true;
type HasPathParams<T extends string> = T extends `${string}[${string}]${string}` ? true : false;
type OptionalQueryParams<TEndpoint extends AnyApiEndpoint> = {
query?: NonNullable<TEndpoint["query"]>;
};
type RequiredQueryParams<TEndpoint extends AnyApiEndpoint> = {
query: NonNullable<TEndpoint["query"]>;
};
declare function useFetch<TPath extends FetchPath>(options: UseFetchOptionsType<TPath>): _tanstack_react_query.UseQueryResult<FetchResponseFor<TPath, "GET">, Error>;
declare function useMutate<TPath extends MutatePath, TMethod extends AvailableMutateMethods<TPath>>(options: UseMutateOptionsType<TPath, TMethod>): _tanstack_react_query.UseMutationResult<MutateResponseFor<TPath, TMethod>, Error, MutateBodyFor<TPath, TMethod>, unknown>;
interface ApiContextValue {
client: AxiosInstance;
queryClient: ApiContext["queryClient"];
config: ApiContext["config"];
}
declare function useApiContext(): ApiContextValue;
interface ApiProviderProps {
children: React.ReactNode;
api: ApiContext;
}
declare function ApiProvider({ children, api }: ApiProviderProps): react_jsx_runtime.JSX.Element;
declare function createApiEndpoint<TMethod extends HttpMethod, TResponse = unknown, TBody = unknown, TQuery = unknown>(config: {
method: TMethod;
response?: z.ZodType<TResponse>;
body?: z.ZodType<TBody>;
query?: z.ZodType<TQuery>;
}): TMethod extends "GET" ? FetchEndpoint<TResponse, TBody, TQuery> : TMethod extends Exclude<HttpMethod, "GET"> ? MutateEndpoint<TMethod, TResponse, TBody, TQuery> : never;
declare function createApi(config: ApiConfig): ApiContext;
export { type ApiConfig, type ApiContext, type ApiEndpoint, type ApiEndpoints, ApiProvider, type FetchEndpoint, type HttpMethod, type MutateEndpoint, type Register, createApi, createApiEndpoint, useApiContext, useFetch, useMutate };