create socket query

This commit is contained in:
Nate Kelley 2025-02-12 12:34:49 -07:00
parent 51291d44e0
commit ad02a021ac
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
13 changed files with 88 additions and 100 deletions

View File

@ -15,15 +15,22 @@ import { useBusterNotifications } from '@/context/BusterNotifications';
import { RustApiError } from './buster_rest/errors'; import { RustApiError } from './buster_rest/errors';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
interface CreateQueryProps<T> extends UseQueryOptions { interface CreateQueryProps<TData, TError = unknown> {
queryKey: QueryKey; queryKey: QueryKey;
queryFn: () => Promise<TData>;
isUseSession?: boolean; isUseSession?: boolean;
useErrorNotification?: boolean; useErrorNotification?: boolean;
enabled?: boolean;
initialData?: TData;
refetchOnWindowFocus?: boolean;
refetchOnMount?: boolean;
staleTime?: number;
options?: Omit<UseQueryOptions<TData, TError, TData>, 'queryKey' | 'queryFn'>;
} }
export const PREFETCH_STALE_TIME = 1000 * 10; export const PREFETCH_STALE_TIME = 1000 * 10;
export const useCreateReactQuery = <T>({ export const useCreateReactQuery = <TData, TError = unknown>({
queryKey, queryKey,
queryFn, queryFn,
isUseSession = true, isUseSession = true,
@ -33,13 +40,13 @@ export const useCreateReactQuery = <T>({
refetchOnMount = true, refetchOnMount = true,
useErrorNotification = true, useErrorNotification = true,
staleTime, staleTime,
...rest options = {}
}: CreateQueryProps<T>) => { }: CreateQueryProps<TData, TError>) => {
const { openErrorNotification } = useBusterNotifications(); const { openErrorNotification } = useBusterNotifications();
const accessToken = useSupabaseContext((state) => state.accessToken); const accessToken = useSupabaseContext((state) => state.accessToken);
const baseEnabled = isUseSession ? !!accessToken : true; const baseEnabled = isUseSession ? !!accessToken : true;
const q = useQuery({ const q = useQuery<TData, TError>({
queryKey: queryKey, queryKey: queryKey,
queryFn, queryFn,
enabled: baseEnabled && !!enabled, enabled: baseEnabled && !!enabled,
@ -48,7 +55,7 @@ export const useCreateReactQuery = <T>({
refetchOnWindowFocus, refetchOnWindowFocus,
refetchOnMount, refetchOnMount,
staleTime, staleTime,
...rest ...options
}); });
useEffect(() => { useEffect(() => {

View File

@ -35,7 +35,7 @@ interface BusterSocket {
}) => Promise<Parameters<T['callback']>[0]>; }) => Promise<Parameters<T['callback']>[0]>;
} }
export const useBusterWebSocketHook = ({ const useBusterWebSocketHook = ({
socketURL, socketURL,
accessToken, accessToken,
checkTokenValidity checkTokenValidity
@ -195,13 +195,13 @@ export const BusterWebSocketProvider: React.FC<{
const accessToken = useSupabaseContext((state) => state.accessToken); const accessToken = useSupabaseContext((state) => state.accessToken);
const checkTokenValidity = useSupabaseContext((state) => state.checkTokenValidity); const checkTokenValidity = useSupabaseContext((state) => state.checkTokenValidity);
const value = useBusterWebSocketHook({ const busterSocketHook = useBusterWebSocketHook({
socketURL: BUSTER_WS_URL, socketURL: BUSTER_WS_URL,
accessToken, accessToken,
checkTokenValidity checkTokenValidity
}); });
return <BusterWebSocket.Provider value={value}>{children}</BusterWebSocket.Provider>; return <BusterWebSocket.Provider value={busterSocketHook}>{children}</BusterWebSocket.Provider>;
}); });
BusterWebSocketProvider.displayName = 'BusterWebSocketProvider'; BusterWebSocketProvider.displayName = 'BusterWebSocketProvider';
@ -212,7 +212,3 @@ const useBusterWebSocketSelector = <T,>(
export const useBusterWebSocket = () => { export const useBusterWebSocket = () => {
return useBusterWebSocketSelector((state) => state.busterSocket); return useBusterWebSocketSelector((state) => state.busterSocket);
}; };
export const useBusterWebSocketConnectionStatus = () => {
return useBusterWebSocketSelector((state) => state.connectionStatus);
};

View File

@ -1,8 +0,0 @@
import { UseBusterSocketQueryOptions } from './types';
export const DEFAULT_OPTIONS: Partial<UseBusterSocketQueryOptions<unknown, unknown>> = {
refetchOnWindowFocus: false,
refetchOnMount: true,
retry: 0,
staleTime: 0
};

View File

@ -1,12 +0,0 @@
import { BusterSocketRequest, BusterSocketResponse } from '@/api/buster_socket';
export const isSocketError = (error: unknown): error is Error => {
return error instanceof Error;
};
export const transformError = (error: unknown): Error => {
if (isSocketError(error)) {
return error;
}
return new Error('Unknown WebSocket error occurred');
};

View File

@ -1,3 +0,0 @@
export * from './types';
export * from './useBusterWebSocketQuery';
export * from './helpers';

View File

@ -1,25 +0,0 @@
import type { BusterSocketResponse, BusterSocketResponseRoute } from '@/api/buster_socket';
import { UseQueryOptions, 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 interface UseBusterSocketQueryOptions<TData, TError = unknown>
extends Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'> {}
export type UseBusterSocketQueryResult<TData, TError = unknown> = UseQueryResult<TData, TError>;

View File

@ -203,7 +203,7 @@ export const useUpdateMetricConfig = ({
route: '/metrics/update:updateMetricState', route: '/metrics/update:updateMetricState',
callback: onInitializeMetric callback: onInitializeMetric
} }
}) as Promise<[BusterMetric]>; });
} }
); );

View File

@ -1,3 +1,4 @@
export * from './react'; export * from './react';
export * from './dom'; export * from './dom';
export * from './useDebounceSearch'; export * from './useDebounceSearch';
export * from './useBusterWebSocketQuery';

View File

@ -1,8 +0,0 @@
import { UseQueryOptions } from '@tanstack/react-query';
export const DEFAULT_OPTIONS: Partial<UseQueryOptions> = {
refetchOnWindowFocus: false,
refetchOnMount: true,
retry: 0,
staleTime: 0
};

View File

@ -0,0 +1 @@
export * from './useBusterWebSocketQuery';

View File

@ -1,13 +1,22 @@
import { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; import type { BusterSocketResponse, BusterSocketResponseRoute } from '@/api/buster_socket';
import { import { UseQueryResult } from '@tanstack/react-query';
BusterSocketRequestBase,
BusterSocketResponseBase
} from '@/api/buster_socket/base_interfaces';
export interface UseBusterSocketQueryOptions<TData, TError = unknown> /**
extends Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'> { * Infers the response data type from a BusterSocket route
socketRequest: BusterSocketRequestBase; */
socketResponse: Omit<BusterSocketResponseBase, 'callback' | 'onError'>; 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>; export type UseBusterSocketQueryResult<TData, TError = unknown> = UseQueryResult<TData, TError>;

View File

@ -0,0 +1,40 @@
'use client';
import { QueryKey, useQuery, useQueryClient } from '@tanstack/react-query';
import type { BusterSocketResponse, BusterSocketResponseRoute } from '@/api/buster_socket';
import { useBusterWebSocket } from '@/context/BusterWebSocket';
import type {
UseBusterSocketQueryResult,
InferBusterSocketResponseData,
BusterSocketResponseConfig
} from './types';
import { useMount } from 'ahooks';
export const useBusterWebSocketOn = <TRoute extends BusterSocketResponseRoute, TError = unknown>(
queryKey: QueryKey,
socketResponse: BusterSocketResponseConfig<TRoute>
): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> => {
const busterSocket = useBusterWebSocket();
const queryClient = useQueryClient();
useMount(() => {
busterSocket.on({
route: socketResponse.route,
onError: socketResponse.onError,
callback: (d: unknown) => {
queryClient.setQueryData(queryKey, d as InferBusterSocketResponseData<TRoute>);
queryClient.invalidateQueries({ queryKey });
}
} as BusterSocketResponse);
});
return useQuery({
queryKey
});
};
const ExampleUsage = () => {
const { data, isFetched } = useBusterWebSocketOn(['chats', 'get', '123'], {
route: '/chats/get:getChat'
});
};

View File

@ -1,13 +1,11 @@
import { useQuery, QueryKey, UseQueryOptions } from '@tanstack/react-query'; import { QueryKey, UseQueryOptions } from '@tanstack/react-query';
import type { import type {
BusterSocketRequest, BusterSocketRequest,
BusterSocketResponse, BusterSocketResponse,
BusterSocketResponseRoute BusterSocketResponseRoute
} from '@/api/buster_socket'; } from '@/api/buster_socket';
import { useBusterWebSocket } from '../useBusterWebSocket'; import { useBusterWebSocket } from '@/context/BusterWebSocket';
import { transformError } from './helpers';
import type { import type {
UseBusterSocketQueryOptions,
UseBusterSocketQueryResult, UseBusterSocketQueryResult,
InferBusterSocketResponseData, InferBusterSocketResponseData,
BusterSocketResponseConfig BusterSocketResponseConfig
@ -18,7 +16,10 @@ export function useBusterWebSocketQuery<TRoute extends BusterSocketResponseRoute
queryKey: QueryKey, queryKey: QueryKey,
socketRequest: BusterSocketRequest, socketRequest: BusterSocketRequest,
socketResponse: BusterSocketResponseConfig<TRoute>, socketResponse: BusterSocketResponseConfig<TRoute>,
options?: UseQueryOptions<InferBusterSocketResponseData<TRoute>, TError> options?: Omit<
UseQueryOptions<InferBusterSocketResponseData<TRoute>, TError>,
'queryKey' | 'queryFn'
>
): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> { ): UseBusterSocketQueryResult<InferBusterSocketResponseData<TRoute>, TError> {
const busterSocket = useBusterWebSocket(); const busterSocket = useBusterWebSocket();
@ -39,30 +40,19 @@ export function useBusterWebSocketQuery<TRoute extends BusterSocketResponseRoute
} }
}; };
// return useCreateReactQuery<InferBusterSocketResponseData<TRoute>>({ return useCreateReactQuery<InferBusterSocketResponseData<TRoute>, TError>({
// queryKey,
// queryFn,
// isUseSession: false
// });
return useQuery<
InferBusterSocketResponseData<TRoute>,
TError,
InferBusterSocketResponseData<TRoute>
>({
queryKey, queryKey,
queryFn, queryFn,
...options isUseSession: false,
options
}); });
} }
// Example usage with automatic type inference // Example usage with automatic type inference
export const ExampleUsage = () => { const ExampleUsage = () => {
const { data, isLoading, error } = useBusterWebSocketQuery( const { data, isLoading, error } = useBusterWebSocketQuery(
['chats', 'get', '123'], ['chats', 'get', '123'],
{ route: '/chats/get', payload: { id: '123' } }, { route: '/chats/get', payload: { id: '123' } },
{ route: '/chats/get:getChat' } { route: '/chats/get:getChat' }
); );
useCreateReactQuery;
}; };