@gaddario98/react-queries
Version:
```bash npm install @gaddario98/base-queries ```
373 lines (355 loc) • 18.4 kB
TypeScript
import * as jotai from 'jotai';
import * as jotai_utils from 'jotai/utils';
import * as _tanstack_react_query from '@tanstack/react-query';
import { UseQueryOptions, useQuery, UseMutationOptions, MutateOptions, useMutation, UseMutationResult, QueryClient } from '@tanstack/react-query';
import { NotificationConfig } from '@gaddario98/react-notifications';
import { AxiosRequestConfig } from 'axios';
import * as _gaddario98_react_state from '@gaddario98/react-state';
import { PersistQueryClientOptions } from '@tanstack/react-query-persist-client';
import { PropsWithChildren } from 'react';
type ApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type Endpoint = Record<string, string>;
interface QueriesProps<TProps, TResponse, TConverter = null> {
endpoint: [keyof Endpoint, string] | [keyof Endpoint];
queryKeyToInvalidate?: Array<string>;
headers?: AxiosRequestConfig['headers'];
method: ApiMethod;
converter?: (props: TProps) => TConverter;
customRequest?: (url: string, method: string, data: TProps) => Promise<TResponse>;
mutateOptions?: MutateOptions<TResponse, Error, TProps, unknown>;
isTest?: boolean;
notification?: {
success?: NotificationConfig | ((res: TResponse) => NotificationConfig);
error?: NotificationConfig | ((error: string) => NotificationConfig);
translationOption?: Record<string, any>;
};
}
interface QueryProps<Key extends string, TResponse> {
endpoint: [keyof Endpoint, string] | [keyof Endpoint];
queryKey: Array<string>;
enabled: boolean;
keyToMap: Key;
disableLoading?: boolean;
customQueryFn?: () => Promise<TResponse>;
headers?: AxiosRequestConfig['headers'];
disableAuthControl?: boolean;
onDataChanged?: (data: TResponse) => void;
onStateChange?: (state: QueryResult<TResponse>) => void;
options?: Omit<Parameters<typeof useQuery<TResponse>>['0'], 'queryKey' | 'queryFn'>;
}
interface CustomQueryOptions<TResponse> extends Omit<UseQueryOptions<TResponse, Error>, 'queryKey' | 'queryFn'> {
endpoint: [keyof Endpoint, string] | [keyof Endpoint];
queryKey: Array<string>;
headers?: AxiosRequestConfig['headers'];
disableAuthControl?: boolean;
onDataChanged?: (data: TResponse | undefined) => void;
onStateChange?: (state: QueryResult<TResponse>) => void;
options?: Omit<Parameters<typeof useQuery<TResponse>>['0'], 'queryKey' | 'queryFn'>;
keyToMap?: string;
disableLoading?: boolean;
customQueryFn?: () => Promise<TResponse>;
}
interface CustomMutationOptions<TProps, TResponse> extends UseMutationOptions<TResponse, Error, TProps, unknown> {
endpoint: [keyof Endpoint, string] | [keyof Endpoint];
method: ApiMethod;
headers?: AxiosRequestConfig['headers'];
queryKeyToInvalidate?: Array<string>;
customRequest?: (url: string, method: string, data: TProps) => Promise<TResponse>;
converter?: (props: TProps) => any;
isTest?: boolean;
notification?: {
success?: NotificationConfig | ((res: TResponse) => NotificationConfig);
error?: NotificationConfig | ((error: string) => NotificationConfig);
translationOption?: Record<string, any>;
};
}
type QueryDefinition<K extends string, T extends 'query' | 'mutation' | 'websocket', P, R, C = any> = {
key: K;
props: P;
response?: R;
converter: C;
type: T;
};
type QueriesArray = Array<QueryDefinition<string, any, any, any, any>>;
type ExtractQuery<Q extends QueriesArray> = Extract<Q[number], {
type: 'query';
}>;
type ExtractWebSocket<Q extends QueriesArray> = Extract<Q[number], {
type: 'websocket';
}>;
type ExtractQueryResponse<Q extends QueriesArray, K extends Q[number]['key']> = Extract<Q[number], {
key: K;
}>;
type ExtractMutation<Q extends QueriesArray> = Extract<Q[number], {
type: 'mutation';
}>;
type CustomMutation<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown> = typeof useMutation<TData, TError, TVariables, TOnMutateResult> & {
endpoint: string;
};
type AllMutation<Q extends QueriesArray = QueriesArray> = {
[K in ExtractMutation<Q>['key']]: ReturnType<CustomMutation<ExtractQueryResponse<Q, K>['response'], Error, ExtractQueryResponse<Q, K>['props'], unknown>> & {
endpoint: QueriesProps<any, any>['endpoint'];
};
};
type ExtractMutationByKey<Q extends QueriesArray = QueriesArray, K extends ExtractMutation<Q>['key'] = ExtractMutation<Q>['key']> = ReturnType<CustomMutation<ExtractQueryResponse<Q, K>['response'], Error, ExtractQueryResponse<Q, K>['props'], unknown>>;
type ExtractQueryByKey<Q extends QueriesArray = QueriesArray, K extends ExtractQuery<Q>['key'] = ExtractQuery<Q>['key']> = {
data?: ExtractQueryResponse<Q, K>['response'];
isLoading: boolean;
isLoadingMapped: boolean;
isFetching: boolean;
isPending: boolean;
error: Error | null;
refetch: () => Promise<unknown>;
};
interface MutationConfig<TProps = unknown, TResponse = unknown, TConverter = any> extends QueriesProps<TProps, TResponse, TConverter> {
method: 'POST' | 'PUT' | 'DELETE' | 'PATCH';
onStateChange?: (state: MutationStateInternal<TResponse>) => void;
}
interface QueryResult<TResponse> {
data?: TResponse;
isLoading: boolean;
isLoadingMapped: boolean;
isFetching: boolean;
isPending: boolean;
error: Error | null;
refetch: () => Promise<unknown>;
}
type MultipleQueryResponse<Q extends QueriesArray> = {
[K in ExtractQuery<Q>['key']]: {
data: ExtractQueryResponse<Q, K>['response'];
isLoading: boolean;
isLoadingMapped: boolean;
isFetching: boolean;
isPending: boolean;
error: Error | null;
refetch: () => Promise<unknown>;
};
};
interface QueryConfig<K extends string, TResponse> extends CustomQueryOptions<TResponse> {
keyToMap: K;
}
type SingleQueryConfig<Q extends QueryDefinition<any, any, any, any>> = Q extends QueryDefinition<infer K, infer T, infer P, infer R, infer C> ? T extends 'mutation' ? {
type: 'mutation';
mutationConfig: MutationConfig<P, R, C>;
key: K;
} : T extends 'query' ? {
type: 'query';
queryConfig?: Omit<QueryConfig<K, R>, 'keyToMap'>;
key: K;
} : WebSocketDefinition<K> : never;
type QueryAtIndex<Q extends QueriesArray, I extends keyof Q> = Q[I] extends QueryDefinition<infer K, infer T, infer P, infer R, infer C> ? QueryDefinition<K, T, P, R, C> : never;
type QueryConfigArray<Q extends QueriesArray> = {
[I in keyof Q]: SingleQueryConfig<QueryAtIndex<Q, I>>;
};
interface ContextValue<Q extends QueriesArray> {
allMutation: AllMutation<Q>;
allQuery: MultipleQueryResponse<Q>;
allWebSocket: MultipleWebSocketResponse<Q[number]['key']>;
refreshQueries: () => void;
}
type MultipleWebSocketResponse<K extends string = string> = {
[key in K]: WebSocketResult;
};
type MutationItem<Q extends QueriesArray> = Extract<QueryConfigArray<Q>[number], {
type: 'mutation';
mutationConfig: any;
}>;
type QueryItem<Q extends QueriesArray> = Extract<QueryConfigArray<Q>[number], {
type: 'query';
}>;
interface WebSocketDefinition<TKey extends string = string> {
key: TKey;
endpoint?: string;
onMessage?: (data: any) => void;
invalidateQueriesOnMessage?: Array<string>;
autoConnect?: boolean;
type: 'websocket';
}
type WebSocketsArray = Array<WebSocketDefinition<string>>;
interface WebSocketResult {
lastMessage: any;
sendMessage: (message: any) => void;
status: 'connecting' | 'open' | 'closed';
}
interface MutationStateInternal<TData = unknown> {
status: 'idle' | 'pending' | 'success' | 'error';
data: TData | undefined;
error: Error | null;
submittedAt: number | null;
variables: unknown | undefined;
}
type MutationActionInternal = {
type: 'RESET';
key: string;
} | {
type: 'PENDING';
key: string;
submittedAt: number;
variables: unknown;
} | {
type: 'SUCCESS';
key: string;
data: unknown;
} | {
type: 'ERROR';
key: string;
error: Error;
};
type StringKey<T> = Extract<keyof T, string>;
type QueryTopKey<Q extends QueriesArray> = StringKey<MultipleQueryResponse<Q>>;
type QuerySubKey<Q extends QueriesArray, K extends QueryTopKey<Q>> = StringKey<MultipleQueryResponse<Q>[K]>;
type QueryCompositeKey<Q extends QueriesArray> = {
[K in QueryTopKey<Q>]: K | `${K}.${QuerySubKey<Q, K>}`;
}[QueryTopKey<Q>];
type QueryValue<Q extends QueriesArray, K extends QueryCompositeKey<Q>> = K extends `${infer Top}.${infer Sub}` ? Top extends QueryTopKey<Q> ? Sub extends QuerySubKey<Q, Top> ? MultipleQueryResponse<Q>[Top][Sub] : never : never : K extends QueryTopKey<Q> ? MultipleQueryResponse<Q>[K] : never;
type MutationTopKey<Q extends QueriesArray> = StringKey<AllMutation<Q>>;
type MutationSubKey<Q extends QueriesArray, K extends MutationTopKey<Q>> = StringKey<AllMutation<Q>[K]>;
type MutationCompositeKey<Q extends QueriesArray> = {
[K in MutationTopKey<Q>]: K | `${K}.${MutationSubKey<Q, K>}`;
}[MutationTopKey<Q>];
type MutationValue<Q extends QueriesArray, K extends MutationCompositeKey<Q>> = K extends `${infer Top}.${infer Sub}` ? Top extends MutationTopKey<Q> ? Sub extends MutationSubKey<Q, Top> ? AllMutation<Q>[Top][Sub] : never : never : K extends MutationTopKey<Q> ? AllMutation<Q>[K] : never;
type GetApiValuesFunction<Q extends QueriesArray> = {
<K extends QueryTopKey<Q>>(type: 'query', key: K): MultipleQueryResponse<Q>[K];
<K extends QueryCompositeKey<Q>>(type: 'query', key: K): QueryValue<Q, K>;
<K extends QueryCompositeKey<Q>>(type: 'query', key: K, defaultValue: unknown): NonNullable<QueryValue<Q, K>>;
<K extends MutationCompositeKey<Q>>(type: 'mutation', key: K, defaultValue: unknown): NonNullable<MutationValue<Q, K>>;
<K extends QueryTopKey<Q>>(type: 'query', key: K, defaultValue: MultipleQueryResponse<Q>[K]['data']): MultipleQueryResponse<Q>[K]['data'];
<K extends QueryCompositeKey<Q>>(type: 'query', key: K, defaultValue: QueryValue<Q, K>): NonNullable<QueryValue<Q, K>>;
<K extends MutationTopKey<Q>>(type: 'mutation', key: K): AllMutation<Q>[K];
<K extends MutationCompositeKey<Q>>(type: 'mutation', key: K): MutationValue<Q, K>;
<K extends MutationTopKey<Q>>(type: 'mutation', key: K, defaultValue: AllMutation<Q>[K]['data']): AllMutation<Q>[K]['data'];
<K extends MutationCompositeKey<Q>>(type: 'mutation', key: K, defaultValue: MutationValue<Q, K>): NonNullable<MutationValue<Q, K>>;
(type: 'query' | 'mutation', key: string, defaultValue?: unknown): unknown;
};
type QueryStoreEntry<Q extends QueriesArray = QueriesArray> = QueryResult<Q[number]['response']>;
type MutationStoreEntry<Q extends QueriesArray = QueriesArray> = UseMutationResult<Q[number]['response'], Error, Q[number]['props'], unknown>;
declare const DEFAULT_QUERY_ENTRY: QueryStoreEntry;
declare const DEFAULT_MUTATION_ENTRY: MutationStoreEntry;
/**
* Global atom storing all query results.
* Key format: "scopeId:queryKey"
*/
declare const queriesAtom: jotai.WritableAtom<Record<string, QueryStoreEntry<QueriesArray>>, [Record<string, QueryStoreEntry<QueriesArray>> | typeof jotai_utils.RESET | ((prev: Record<string, QueryStoreEntry<QueriesArray>>) => Record<string, QueryStoreEntry<QueriesArray>> | typeof jotai_utils.RESET)], void>;
/**
* Global atom storing all mutation results.
* Key format: "scopeId:mutationKey"
*/
declare const mutationsAtom: jotai.PrimitiveAtom<Record<string, MutationStoreEntry<QueriesArray>>> & {
init: Record<string, MutationStoreEntry<QueriesArray>>;
};
declare const getCompositeKey: (scopeId: string, key: string) => string;
/**
* Creates a derived atom for accessing queries of a specific scope.
*/
declare const createScopeQueriesAtom: (scopeId: string) => jotai.WritableAtom<Record<string, QueryStoreEntry<QueriesArray>>, [update: Record<string, QueryStoreEntry<QueriesArray>>], void>;
/**
* Creates a derived atom for accessing mutations of a specific scope.
*/
declare const createScopeMutationsAtom: (scopeId: string) => jotai.WritableAtom<Record<string, MutationStoreEntry<QueriesArray>>, [update: Record<string, MutationStoreEntry<QueriesArray>>], void>;
declare const createQuerySelector: <Q extends QueriesArray>(scopeId: string, queryKey: string) => jotai.Atom<QueryStoreEntry<Q>>;
declare const createMutationSelector: <Q extends QueriesArray>(scopeId: string, mutationKey: string) => jotai.Atom<MutationStoreEntry<Q>>;
interface EncryptionConfig {
secretKey: string;
enabled?: boolean;
encryptFn?: (data: unknown, key: string) => Promise<string>;
decryptFn?: (data: string, key: string) => Promise<unknown>;
}
type ApiConverter<TProps, TConverter> = (props: TProps) => TConverter;
interface ApiRequestFnProps<TProps, TConverter = TProps> {
url: string;
method: ApiMethod;
body?: TProps;
headers?: AxiosRequestConfig['headers'];
converter?: ApiConverter<TProps, TConverter>;
}
interface ApiNotificationMessage {
message: string;
type: 'success' | 'error' | 'info' | 'warning';
autoHideDuration?: number;
textTransOption?: Record<string, unknown>;
ns?: string;
}
interface ApiConfig {
endpoints: Endpoint;
requestFn: <TProps, TResponse, TConverter = TProps>(props: ApiRequestFnProps<TProps, TConverter>) => Promise<TResponse>;
validateAuthFn?: () => boolean;
defaultHeaders?: Record<string, string>;
persistOptions?: Omit<PersistQueryClientOptions, 'queryClient'>;
queryClient: QueryClient;
websocketConfig?: {
url: string;
onMessage?: (message: unknown) => void;
autoConnect?: boolean;
};
encryption?: EncryptionConfig;
showNotification?: (notification: ApiNotificationMessage) => void;
}
declare const apiConfigAtom: _gaddario98_react_state.PrimitiveAtom<ApiConfig>;
declare const useApiConfigValue: () => ApiConfig;
declare const useApiConfigState: () => [ApiConfig, (value: ApiConfig) => void];
declare const useApiConfigReset: () => () => void;
declare const useQueryApi: <TResponse>(options: CustomQueryOptions<TResponse>, id?: string) => QueryResult<TResponse>;
declare const useMutateApi: <TProps, TResponse>(options: CustomMutationOptions<TProps, TResponse>, id?: string) => _tanstack_react_query.UseMutationResult<TResponse, Error, TProps, unknown>;
declare const useMultipleQuery: <Q extends QueriesArray>(settings?: Array<QueryProps<Q[number]["key"], Q[number]["response"]>>) => MultipleQueryResponse<Q>;
declare const useMultipleMutation: <Q extends QueriesArray>(configs: Array<MutationItem<Q>>) => AllMutation<Q>;
declare const useMultipleWebSocket: <K extends string>(configs?: Array<WebSocketDefinition<K>>) => MultipleWebSocketResponse<K>;
interface UseApiOptions {
/**
* Unique identifier for this scope. Used as the key prefix for Jotai atoms.
* @default 'default'
*/
scopeId?: string;
/**
* Whether to persist query/mutation results to Jotai atoms.
* @default true
*/
persistToAtoms?: boolean;
}
/**
* Main hook that integrates TanStack Query with Jotai for state persistence.
*/
declare function useApi<Q extends QueriesArray>(configs: QueryConfigArray<Q>, options?: UseApiOptions): ContextValue<Q>;
/**
* @deprecated Use options object instead: useApi(configs, { scopeId: 'my-scope' })
*/
declare function useApi<Q extends QueriesArray>(configs: QueryConfigArray<Q>, id?: string): ContextValue<Q>;
declare const useWebSocket: (endpoint?: string, // specific socket endpoint if different from default
options?: {
onMessage?: (data: unknown) => void;
invalidateQueriesOnMessage?: Array<string>;
}) => WebSocketResult;
declare const useInvalidateQueries: () => {
invalidateQueries: (queryKeys: Array<Array<string>>) => Promise<void>;
};
/**
* Hook to read all queries for a scope.
*/
declare function useJotaiQueries<Q extends QueriesArray>(options: {
scopeId: string;
}): Record<string, QueryStoreEntry<Q>>;
/**
* Hook to read all mutations for a scope.
*/
declare function useJotaiMutations<Q extends QueriesArray>(options: {
scopeId: string;
}): Record<string, MutationStoreEntry<Q>>;
interface UseApiValuesProps {
scopeId: string;
}
declare const useApiValues: <Q extends QueriesArray>({ scopeId, }: UseApiValuesProps) => {
get: GetApiValuesFunction<Q>;
};
declare const QueriesProvider: React.FC<PropsWithChildren>;
declare const algorithm: {
name: string;
length: number;
};
declare const str2ab: (str: string) => ArrayBuffer;
declare const ab2str: (buf: ArrayBuffer) => string;
declare const importKey: (secretKey: string) => Promise<CryptoKey>;
declare const encryptData: (data: any, secretKey: string) => Promise<string>;
declare const decryptData: (encryptedData: string, secretKey: string) => Promise<any>;
declare const apiRequest: <TProps, TResponse, TConverter = TProps>({ method, url, body, headers, converter, }: ApiRequestFnProps<TProps, TConverter>) => Promise<TResponse>;
declare const fetchRequest: <TProps, TResponse>(url: string, headers?: AxiosRequestConfig["headers"]) => Promise<TResponse>;
export { DEFAULT_MUTATION_ENTRY, DEFAULT_QUERY_ENTRY, QueriesProvider, ab2str, algorithm, apiConfigAtom, apiRequest, createMutationSelector, createQuerySelector, createScopeMutationsAtom, createScopeQueriesAtom, decryptData, encryptData, fetchRequest, getCompositeKey, importKey, mutationsAtom, queriesAtom, str2ab, useApi, useApiConfigReset, useApiConfigState, useApiConfigValue, useApiValues, useInvalidateQueries, useJotaiMutations, useJotaiQueries, useMultipleMutation, useMultipleQuery, useMultipleWebSocket, useMutateApi, useQueryApi, useWebSocket };
export type { AllMutation, ApiConfig, ApiConverter, ApiMethod, ApiNotificationMessage, ApiRequestFnProps, ContextValue, CustomMutation, CustomMutationOptions, CustomQueryOptions, EncryptionConfig, Endpoint, ExtractMutation, ExtractMutationByKey, ExtractQuery, ExtractQueryByKey, ExtractQueryResponse, ExtractWebSocket, GetApiValuesFunction, MultipleQueryResponse, MultipleWebSocketResponse, MutationActionInternal, MutationConfig, MutationItem, MutationStateInternal, MutationStoreEntry, QueriesArray, QueriesProps, QueryAtIndex, QueryConfig, QueryConfigArray, QueryDefinition, QueryItem, QueryProps, QueryResult, QueryStoreEntry, SingleQueryConfig, UseApiOptions, WebSocketDefinition, WebSocketResult, WebSocketsArray };