mirror of https://github.com/buster-so/buster.git
request queries
This commit is contained in:
parent
8ec0137fbc
commit
ba80aaf41d
|
@ -1,7 +1,6 @@
|
|||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { BusterChat, BusterChatListItem } from '@/api/asset_interfaces/chat';
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { ChatListEmitPayload } from '@/api/buster_socket/chats';
|
||||
import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { BusterChat, BusterChatListItem } from './chatInterfaces';
|
||||
import type { GetChatListParams } from '@/api/request_interfaces/chats';
|
||||
|
||||
const chatsGetChat = (chatId: string) =>
|
||||
queryOptions<BusterChat>({
|
||||
|
@ -9,19 +8,19 @@ const chatsGetChat = (chatId: string) =>
|
|||
staleTime: 10 * 1000
|
||||
});
|
||||
|
||||
const chatsGetList = (filters?: ChatListEmitPayload) =>
|
||||
const chatsGetList = (filters?: GetChatListParams) =>
|
||||
queryOptions<BusterChatListItem[]>({
|
||||
queryKey: ['chats', 'list', filters] as const
|
||||
});
|
||||
|
||||
const deleteChat = (chatId: string) => {
|
||||
const deleteChat = () => {
|
||||
const queryKey = ['chats', 'list'] as const;
|
||||
return queryOptions<BusterChatListItem[]>({
|
||||
queryKey
|
||||
});
|
||||
};
|
||||
|
||||
export const queryOptionsConfig = {
|
||||
export const chatQueryKeys = {
|
||||
'/chats/get:getChat': chatsGetChat,
|
||||
'/chats/list:getChatsList': chatsGetList,
|
||||
'/chats/delete:deleteChat': deleteChat
|
||||
|
@ -29,7 +28,7 @@ export const queryOptionsConfig = {
|
|||
|
||||
const ExampleComponent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = queryOptionsConfig['/chats/get:getChat']!('123');
|
||||
const options = chatQueryKeys['/chats/get:getChat']!('123');
|
||||
const queryKey = options.queryKey;
|
||||
|
||||
const data = queryClient.getQueryData(queryKey);
|
||||
|
@ -40,7 +39,7 @@ const ExampleComponent = () => {
|
|||
return d;
|
||||
});
|
||||
|
||||
const options2 = queryOptionsConfig['/chats/delete:deleteChat']!('123');
|
||||
const options2 = chatQueryKeys['/chats/delete:deleteChat']!();
|
||||
const queryKey2 = options2.queryKey;
|
||||
|
||||
const data3 = queryClient.getQueryData(queryKey2);
|
|
@ -1,2 +0,0 @@
|
|||
import type { BusterChat } from './chatInterfaces';
|
||||
import type { BusterChatMessage } from './chatMessageInterfaces';
|
|
@ -0,0 +1,11 @@
|
|||
import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { BusterCollectionListItem } from './interfaces';
|
||||
|
||||
const collectionsGetList = (filters?: GetCollectionListParams) =>
|
||||
queryOptions<BusterCollectionListItem[]>({
|
||||
queryKey: ['collections', 'list', filters] as const
|
||||
});
|
||||
|
||||
export const collectionQueryKeys = {
|
||||
'/collections/list:getCollectionsList': collectionsGetList
|
||||
};
|
|
@ -14,3 +14,4 @@ export * from './sql';
|
|||
export * from './permission_groups';
|
||||
export * from './dataset_groups';
|
||||
export * from './api_keys';
|
||||
export * from './queryKeys';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { chatQueryKeys } from './chat/chatQueryKeys';
|
||||
import { collectionQueryKeys } from './collection/collectionQueryKeys';
|
||||
|
||||
export const queryKeys = {
|
||||
...chatQueryKeys,
|
||||
...collectionQueryKeys
|
||||
};
|
|
@ -1,12 +1,12 @@
|
|||
import { mainApi } from '../instances';
|
||||
import { serverFetch } from '../../createServerInstance';
|
||||
import type { BusterChatListItem, BusterChat } from '@/api/asset_interfaces';
|
||||
import type { ChatListParams, GetChatParams } from './interfaces';
|
||||
import type { GetChatListParams, GetChatParams } from '../../request_interfaces/chats';
|
||||
|
||||
const CHATS_BASE = '/chats';
|
||||
|
||||
// Client-side fetch version
|
||||
export const getListChats = async (params?: ChatListParams): Promise<BusterChatListItem[]> => {
|
||||
export const getListChats = async (params?: GetChatListParams): Promise<BusterChatListItem[]> => {
|
||||
const { page_token = 0, page_size = 1000, admin_view = false } = params || {};
|
||||
return mainApi
|
||||
.get<BusterChatListItem[]>(`${CHATS_BASE}/list`, {
|
||||
|
@ -17,7 +17,7 @@ export const getListChats = async (params?: ChatListParams): Promise<BusterChatL
|
|||
|
||||
// Server-side fetch version
|
||||
export const getListChats_server = async (
|
||||
params?: ChatListParams
|
||||
params?: GetChatListParams
|
||||
): Promise<BusterChatListItem[]> => {
|
||||
const { page_token = 0, page_size = 1000, admin_view = false } = params || {};
|
||||
return await serverFetch<BusterChatListItem[]>(`${CHATS_BASE}/list`, {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GetChatParams, GetChatListParams } from '../../request_interfaces/chats';
|
||||
import type { BusterSocketRequestBase } from '../base_interfaces';
|
||||
|
||||
/**
|
||||
|
@ -45,13 +46,7 @@ export type ChatStopChat = BusterSocketRequestBase<
|
|||
* @interface ChatGetChat
|
||||
* @extends BusterSocketRequestBase
|
||||
*/
|
||||
export type ChatGetChat = BusterSocketRequestBase<
|
||||
'/chats/get',
|
||||
{
|
||||
/** The unique identifier of the chat to retrieve */
|
||||
id: string;
|
||||
}
|
||||
>;
|
||||
export type ChatGetChat = BusterSocketRequestBase<'/chats/get', GetChatParams>;
|
||||
|
||||
/**
|
||||
* Request type for unsubscribing from real-time updates of a specific chat.
|
||||
|
@ -71,17 +66,7 @@ export type ChatUnsubscribeFromChat = BusterSocketRequestBase<
|
|||
* @interface ChatListEmitPayload
|
||||
* @extends BusterSocketRequestBase
|
||||
*/
|
||||
export type ChatListEmitPayload = BusterSocketRequestBase<
|
||||
'/chats/list',
|
||||
{
|
||||
/** Pagination token indicating the page number */
|
||||
page_token: number;
|
||||
/** Number of chat items to return per page */
|
||||
page_size: number;
|
||||
/** When true, shows all organization chats (admin only). When false, shows only user's chats */
|
||||
admin_view: boolean;
|
||||
}
|
||||
>;
|
||||
export type ChatListEmitPayload = BusterSocketRequestBase<'/chats/list', GetChatListParams>;
|
||||
|
||||
/**
|
||||
* Request type for deleting a specific chat.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export * from './useSocketQueryMutation';
|
||||
export * from './useSocketQueryOn';
|
||||
export * from './useSocketQueryEmitAndOnce';
|
|
@ -25,7 +25,7 @@ import type {
|
|||
} from './types';
|
||||
import type { BusterChatListItem } from '@/api/asset_interfaces/chat';
|
||||
|
||||
export function useSocketMutation<
|
||||
export function useSocketQueryMutation<
|
||||
TRequestRoute extends BusterSocketRequestRoute,
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError = unknown,
|
||||
|
@ -92,7 +92,7 @@ const ExampleComponent = () => {
|
|||
const data = queryClient.getQueryData(options.queryKey);
|
||||
data?.[0].created_by_avatar;
|
||||
|
||||
const { mutate } = useSocketMutation<
|
||||
const { mutate } = useSocketQueryMutation<
|
||||
'/chats/delete',
|
||||
'/chats/delete:deleteChat',
|
||||
unknown,
|
|
@ -1 +0,0 @@
|
|||
export * from './buster_rest';
|
|
@ -0,0 +1 @@
|
|||
export * from './interfaces';
|
|
@ -1,4 +1,4 @@
|
|||
export interface ChatListParams {
|
||||
export interface GetChatListParams {
|
||||
/** Pagination token indicating the page number */
|
||||
page_token: number;
|
||||
/** Number of chat items to return per page */
|
|
@ -1,7 +1,8 @@
|
|||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { useSocketQueryMutation } from '@/hooks';
|
||||
import { useSocketQueryMutation } from '@/api/buster_socket_query';
|
||||
import { timeout } from '@/utils';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { queryKeys } from '@/api/asset_interfaces';
|
||||
|
||||
export const useCollectionCreate = () => {
|
||||
const { openConfirmModal } = useBusterNotifications();
|
||||
|
@ -30,16 +31,18 @@ export const useCollectionCreate = () => {
|
|||
useSocketQueryMutation(
|
||||
{ route: '/collections/delete' },
|
||||
{ route: '/collections/delete:deleteCollections' },
|
||||
{
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/collections/list:listCollections',
|
||||
callback: (data, variables) => {
|
||||
return data?.filter((collection) => !variables.ids.includes(collection.id)) || [];
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
queryKeys
|
||||
|
||||
// {
|
||||
// preSetQueryData: [
|
||||
// {
|
||||
// responseRoute: '/collections/list:listCollections',
|
||||
// callback: (data, variables) => {
|
||||
// return data?.filter((collection) => !variables.ids.includes(collection.id)) || [];
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
);
|
||||
|
||||
const deleteCollection = useMemoizedFn(async (id: string | string[], useConfirmModal = true) => {
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { BusterSocketRequest, BusterSocketResponseRoute } from '@/api/buster_socket';
|
||||
import { BusterSocketResponseConfig, InferBusterSocketResponseData } from './types';
|
||||
import { QueryKey } from '@tanstack/react-query';
|
||||
|
||||
export const createQueryKey: <TRoute extends BusterSocketResponseRoute>(
|
||||
socketResponse: BusterSocketResponseConfig<TRoute>['route'],
|
||||
callbackResult: InferBusterSocketResponseData<TRoute>,
|
||||
socketRequest?: BusterSocketRequest
|
||||
) => QueryKey = (socketResponse, callbackResult, socketRequest) => {
|
||||
if (socketRequest) return [socketResponse, socketRequest.route, socketRequest.payload];
|
||||
return [socketResponse];
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import { createQueryKey } from './helpers';
|
||||
import { BusterSocketResponseConfig } from './types';
|
||||
|
||||
export * from './useSocketQueryEmitAndOnce';
|
||||
export * from './useSocketQueryEmitOn';
|
||||
export * from './useSocketQueryOn';
|
||||
export * from './useSocketQueryMutation';
|
||||
|
||||
export { createQueryKey, type BusterSocketResponseConfig };
|
|
@ -1,59 +0,0 @@
|
|||
import { UseMutationOptions } from '@tanstack/react-query';
|
||||
import { BusterSocketResponseRoute } from '@/api/buster_socket';
|
||||
import type { InferBusterSocketResponseData, BusterSocketRequestRoute } from './types';
|
||||
|
||||
export type QueryDataStrategy = 'replace' | 'append' | 'prepend' | 'merge' | 'ignore';
|
||||
|
||||
export type PreSetQueryDataItem<TVariables> = {
|
||||
[Route in BusterSocketResponseRoute]: {
|
||||
responseRoute: Route;
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<Route>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<Route>;
|
||||
};
|
||||
}[BusterSocketResponseRoute];
|
||||
|
||||
export type SinglePreSetQueryDataItem<TRoute extends BusterSocketResponseRoute, TVariables> = {
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<TRoute>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<TRoute>;
|
||||
};
|
||||
|
||||
export type SocketQueryMutationOptions<
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError,
|
||||
TVariables
|
||||
> = Omit<
|
||||
UseMutationOptions<InferBusterSocketResponseData<TRoute>, TError, TVariables>,
|
||||
'mutationFn'
|
||||
> & {
|
||||
/**
|
||||
* Configuration for optimistically updating query data before the mutation completes.
|
||||
* Can be either a single item or an array of items.
|
||||
*/
|
||||
preSetQueryData?:
|
||||
| Array<PreSetQueryDataItem<TVariables>>
|
||||
| SinglePreSetQueryDataItem<TRoute, TVariables>;
|
||||
|
||||
/**
|
||||
* When true, adds a small delay before applying preSetQueryData to ensure React Query's cache
|
||||
* is properly initialized.
|
||||
* @default false
|
||||
*/
|
||||
awaitPrefetchQueryData?: boolean;
|
||||
|
||||
/**
|
||||
* Strategy for integrating mutation response data into existing query data.
|
||||
* @property 'replace' - Replace existing data
|
||||
* @property 'append' - Add to end of array
|
||||
* @property 'prepend' - Add to start of array
|
||||
* @property 'merge' - Merge objects (requires ID field)
|
||||
* @property 'ignore' - No automatic update
|
||||
* @default 'ignore'
|
||||
*/
|
||||
queryDataStrategy?: QueryDataStrategy;
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { QueryDataStrategy } from './mutationTypes';
|
||||
import { BusterSocketResponseRoute } from '@/api/buster_socket';
|
||||
import { InferBusterSocketResponseData } from './types';
|
||||
|
||||
export const executeQueryDataStrategy = async <TRoute extends BusterSocketResponseRoute>(
|
||||
queryClient: QueryClient,
|
||||
queryKey: unknown[],
|
||||
data: InferBusterSocketResponseData<TRoute>,
|
||||
strategy: QueryDataStrategy
|
||||
) => {
|
||||
if (strategy === 'ignore') return;
|
||||
|
||||
const strategies: Record<Exclude<QueryDataStrategy, 'ignore'>, () => Promise<void>> = {
|
||||
replace: async () => {
|
||||
await queryClient.setQueryData(queryKey, data);
|
||||
},
|
||||
append: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(queryKey, (prev) => [
|
||||
...(Array.isArray(prev) ? prev : []),
|
||||
data
|
||||
]);
|
||||
},
|
||||
prepend: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(queryKey, (prev) => [
|
||||
data,
|
||||
...(Array.isArray(prev) ? prev : [])
|
||||
]);
|
||||
},
|
||||
merge: async () => {
|
||||
if (typeof data === 'object' && data !== null && 'id' in data) {
|
||||
await queryClient.setQueryData<Record<string, InferBusterSocketResponseData<TRoute>>>(
|
||||
queryKey,
|
||||
(prev) => ({
|
||||
...(prev || {}),
|
||||
[(data as { id: string }).id]: data
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateStrategy = strategies[strategy as Exclude<QueryDataStrategy, 'ignore'>];
|
||||
|
||||
if (updateStrategy) {
|
||||
await updateStrategy();
|
||||
}
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
import type {
|
||||
BusterSocketRequest,
|
||||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
|
||||
/**
|
||||
* Infers the response data type from a BusterSocket route
|
||||
*/
|
||||
export type InferBusterSocketResponseData<TRoute extends BusterSocketResponseRoute> = Extract<
|
||||
BusterSocketResponse,
|
||||
{ route: TRoute }
|
||||
>['callback'] extends (d: infer D) => void
|
||||
? D
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Socket response configuration with optional error handler
|
||||
*/
|
||||
export type BusterSocketResponseConfig<TRoute extends BusterSocketResponseRoute> = {
|
||||
route: TRoute;
|
||||
onError?: (d: unknown) => void;
|
||||
};
|
||||
|
||||
export type UseBusterSocketQueryResult<TData, TError = unknown> = UseQueryResult<TData, TError>;
|
||||
|
||||
/**
|
||||
* Extract the route type from BusterSocketRequest
|
||||
*/
|
||||
export type BusterSocketRequestRoute = BusterSocketRequest['route'];
|
||||
|
||||
export type BusterSocketRequestConfig<TRoute extends BusterSocketRequestRoute> = {
|
||||
route: TRoute;
|
||||
};
|
||||
|
||||
export type InferBusterSocketRequestPayload<TRoute extends BusterSocketRequestRoute> = Extract<
|
||||
BusterSocketRequest,
|
||||
{ route: TRoute }
|
||||
>['payload'];
|
|
@ -1,57 +0,0 @@
|
|||
import { QueryKey, UseQueryOptions } from '@tanstack/react-query';
|
||||
import type {
|
||||
BusterSocketRequest,
|
||||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { useBusterWebSocket } from '@/context/BusterWebSocket';
|
||||
import type {
|
||||
UseBusterSocketQueryResult,
|
||||
InferBusterSocketResponseData,
|
||||
BusterSocketResponseConfig
|
||||
} from './types';
|
||||
import { useCreateReactQuery } from '@/api/createReactQuery';
|
||||
import { createQueryKey } from './helpers';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useSocketQueryEmitAndOnce<
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError = unknown
|
||||
>(
|
||||
socketRequest: BusterSocketRequest,
|
||||
socketResponse: BusterSocketResponseConfig<TRoute>,
|
||||
options?: Partial<Omit<UseQueryOptions<InferBusterSocketResponseData<TRoute>, TError>, 'queryFn'>>
|
||||
): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
|
||||
const queryKey = useMemo(
|
||||
() => options?.queryKey || createQueryKey(socketResponse, socketRequest),
|
||||
[options?.queryKey, socketResponse?.route, socketRequest?.route]
|
||||
);
|
||||
|
||||
const queryFn = async (): Promise<InferBusterSocketResponseData<TRoute>> => {
|
||||
try {
|
||||
const result = await busterSocket.emitAndOnce({
|
||||
emitEvent: socketRequest,
|
||||
responseEvent: {
|
||||
route: socketResponse.route,
|
||||
onError: socketResponse.onError,
|
||||
callback: (d: unknown) => d
|
||||
} as BusterSocketResponse
|
||||
});
|
||||
|
||||
return result as InferBusterSocketResponseData<TRoute>;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return useCreateReactQuery<InferBusterSocketResponseData<TRoute>, TError>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
isUseSession: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
options
|
||||
});
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import {
|
||||
BusterSocketRequest,
|
||||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { useQueryClient, UseQueryOptions } from '@tanstack/react-query';
|
||||
import {
|
||||
BusterSocketResponseConfig,
|
||||
InferBusterSocketResponseData,
|
||||
UseBusterSocketQueryResult
|
||||
} from './types';
|
||||
import { useSockeQueryOn } from './useSocketQueryOn';
|
||||
import { useBusterWebSocket } from '@/context/BusterWebSocket';
|
||||
import { useMemoizedFn, useMount } from 'ahooks';
|
||||
import { createQueryKey } from './helpers';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* A hook that emits a socket request on mount and listens for responses.
|
||||
*
|
||||
* @template TRoute - The type of socket response route
|
||||
* @template TError - The type of error that can occur
|
||||
*
|
||||
* @param socketRequest - The socket request to emit
|
||||
* @param socketResponse - Configuration for the socket response including route and error handler
|
||||
* @param options - Additional options for the React Query hook
|
||||
*
|
||||
* @returns A React Query result containing the response data and status
|
||||
*/
|
||||
export const useSocketQueryEmitOn = <TRoute extends BusterSocketResponseRoute, TError = unknown>(
|
||||
socketRequest: BusterSocketRequest,
|
||||
socketResponse: BusterSocketResponseConfig<TRoute>,
|
||||
optionsProps?: Omit<
|
||||
UseQueryOptions<InferBusterSocketResponseData<TRoute>, TError>,
|
||||
'queryKey' | 'queryFn'
|
||||
> & { enabled?: boolean | string }
|
||||
): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> => {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
const { enabled = true, ...options } = optionsProps || {};
|
||||
|
||||
const queryKey = createQueryKey(socketResponse, socketRequest);
|
||||
|
||||
const queryFn = useMemoizedFn(async () => {
|
||||
const res = await busterSocket.emitAndOnce({
|
||||
emitEvent: socketRequest,
|
||||
responseEvent: {
|
||||
...socketResponse,
|
||||
callback: (d: unknown) => d
|
||||
} as BusterSocketResponse
|
||||
});
|
||||
|
||||
return res;
|
||||
}) as () => Promise<InferBusterSocketResponseData<TRoute>>;
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
queryFn();
|
||||
}
|
||||
}, [enabled]);
|
||||
|
||||
return useSockeQueryOn(socketResponse, {
|
||||
...options,
|
||||
queryKey,
|
||||
queryFn
|
||||
});
|
||||
};
|
|
@ -1,123 +0,0 @@
|
|||
import {
|
||||
BusterSocketRequest,
|
||||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useBusterWebSocket } from '@/context/BusterWebSocket';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import {
|
||||
BusterSocketResponseConfig,
|
||||
InferBusterSocketResponseData,
|
||||
InferBusterSocketRequestPayload,
|
||||
BusterSocketRequestConfig,
|
||||
BusterSocketRequestRoute
|
||||
} from './types';
|
||||
import { SocketQueryMutationOptions } from './mutationTypes';
|
||||
import { executeQueryDataStrategy } from './queryDataStrategies';
|
||||
import { createQueryKey } from './helpers';
|
||||
|
||||
/**
|
||||
* A hook that creates a mutation for emitting socket requests and handling responses.
|
||||
* Supports optimistic updates and various strategies for updating the query cache.
|
||||
*
|
||||
* @template TRequestRoute - The socket request route type
|
||||
* @template TRoute - The socket response route type
|
||||
* @template TError - The error type that can occur
|
||||
* @template TVariables - The variables type passed to the mutation function
|
||||
*
|
||||
* @param socketRequest - The base socket request configuration
|
||||
* @param socketResponse - The socket response configuration with optional error handler
|
||||
* @param options - Additional options for configuring the mutation behavior
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { mutate } = useSocketQueryMutation(
|
||||
* { route: '/users/favorites/post' },
|
||||
* { route: '/users/favorites/post:createFavorite' },
|
||||
* {
|
||||
* preSetQueryData: [
|
||||
* {
|
||||
* responseRoute: '/users/favorites/list:listFavorites',
|
||||
* callback: (data, variables) => [...(data || []), variables]
|
||||
* }
|
||||
* ],
|
||||
* queryDataStrategy: 'append'
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export const useSocketQueryMutation = <
|
||||
TRequestRoute extends BusterSocketRequestRoute,
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError = unknown,
|
||||
TVariables = InferBusterSocketRequestPayload<TRequestRoute>
|
||||
>(
|
||||
socketRequest: BusterSocketRequestConfig<TRequestRoute>,
|
||||
socketResponse: BusterSocketResponseConfig<TRoute> & {
|
||||
callback?: (data: unknown) => InferBusterSocketResponseData<TRoute>;
|
||||
},
|
||||
options?: SocketQueryMutationOptions<TRoute, TError, TVariables>
|
||||
) => {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
const queryClient = useQueryClient();
|
||||
const { preSetQueryData, queryDataStrategy = 'ignore', ...mutationOptions } = options || {};
|
||||
|
||||
const handlePreSetQueryData = useMemoizedFn(async (variables: TVariables) => {
|
||||
if (!preSetQueryData) return;
|
||||
|
||||
if (options?.awaitPrefetchQueryData) {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
const arrayOfPreSetQueryData = Array.isArray(preSetQueryData)
|
||||
? preSetQueryData
|
||||
: [{ ...preSetQueryData, responseRoute: socketResponse.route }];
|
||||
|
||||
for (const item of arrayOfPreSetQueryData) {
|
||||
const { responseRoute, requestRoute, callback } = item!;
|
||||
const requestPayload: undefined | BusterSocketRequest = requestRoute
|
||||
? ({ route: requestRoute, payload: variables } as BusterSocketRequest)
|
||||
: undefined;
|
||||
const presetQueryKey = createQueryKey({ route: responseRoute! }, requestPayload);
|
||||
await queryClient.setQueryData(presetQueryKey, (prev: any) => callback(prev, variables));
|
||||
}
|
||||
});
|
||||
|
||||
const mutationFn = useMemoizedFn(async (variables: TVariables) => {
|
||||
const request = {
|
||||
...socketRequest,
|
||||
payload: variables
|
||||
} as BusterSocketRequest;
|
||||
|
||||
const queryKey = createQueryKey(socketResponse, request);
|
||||
await handlePreSetQueryData(variables);
|
||||
|
||||
const response = await busterSocket.emitAndOnce({
|
||||
emitEvent: request,
|
||||
responseEvent: {
|
||||
...socketResponse,
|
||||
callback: (data: unknown) => {
|
||||
socketResponse.callback?.(data);
|
||||
return data;
|
||||
}
|
||||
} as BusterSocketResponse
|
||||
});
|
||||
|
||||
if (response !== undefined) {
|
||||
await executeQueryDataStrategy(
|
||||
queryClient,
|
||||
queryKey as unknown[],
|
||||
response as InferBusterSocketResponseData<TRoute>,
|
||||
queryDataStrategy
|
||||
);
|
||||
}
|
||||
|
||||
return response as InferBusterSocketResponseData<TRoute>;
|
||||
});
|
||||
|
||||
return useMutation({
|
||||
...mutationOptions,
|
||||
mutationFn
|
||||
});
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import { QueryKey, useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
|
||||
import type {
|
||||
BusterSocketRequest,
|
||||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { useBusterWebSocket } from '@/context/BusterWebSocket';
|
||||
import type {
|
||||
UseBusterSocketQueryResult,
|
||||
InferBusterSocketResponseData,
|
||||
BusterSocketResponseConfig
|
||||
} from './types';
|
||||
import { useMount } from 'ahooks';
|
||||
import { createQueryKey } from './helpers';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useSockeQueryOn = <TRoute extends BusterSocketResponseRoute, TError = unknown>(
|
||||
socketResponse: BusterSocketResponseConfig<TRoute>,
|
||||
options?: {
|
||||
queryKey?: QueryKey;
|
||||
}
|
||||
): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> => {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// const queryKey = useMemo(
|
||||
// () => options?.queryKey || createQueryKey(socketResponse),
|
||||
// [options?.queryKey, socketResponse?.route]
|
||||
// );
|
||||
|
||||
useMount(() => {
|
||||
busterSocket.on({
|
||||
route: socketResponse.route,
|
||||
onError: socketResponse.onError,
|
||||
callback: (d: unknown) => {
|
||||
queryClient.setQueryData(queryKey, d as InferBusterSocketResponseData<TRoute>);
|
||||
}
|
||||
} as BusterSocketResponse);
|
||||
});
|
||||
|
||||
return useQuery({
|
||||
queryKey,
|
||||
...options,
|
||||
enabled: false //must be disabled to prevent automatic fetching
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue