query key updates

This commit is contained in:
Nate Kelley 2025-02-13 14:28:45 -07:00
parent 01382338b1
commit 5d283d49d3
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 191 additions and 169 deletions

View File

@ -2,7 +2,6 @@
description: Rules and guidelines for the asset_interfaces directory containing TypeScript interface definitions for all API responses description: Rules and guidelines for the asset_interfaces directory containing TypeScript interface definitions for all API responses
globs: src/api/asset_interfaces/**/* globs: src/api/asset_interfaces/**/*
--- ---
# API Asset Interfaces Directory Rules # API Asset Interfaces Directory Rules
This directory (`src/api/asset_interfaces`) contains TypeScript interface definitions for all API responses. Each subdirectory represents a distinct API namespace. This directory (`src/api/asset_interfaces`) contains TypeScript interface definitions for all API responses. Each subdirectory represents a distinct API namespace.
@ -13,6 +12,7 @@ src/api/asset_interfaces/
├── [namespace]/ ├── [namespace]/
│ ├── index.ts # Exports all interfaces and types │ ├── index.ts # Exports all interfaces and types
│ ├── interfaces.ts # Contains type definitions │ ├── interfaces.ts # Contains type definitions
│ ├── queryKeys.ts # Contains query key definitions for TanStack Query
│ └── [other].ts # Optional additional type files │ └── [other].ts # Optional additional type files
``` ```
@ -21,6 +21,7 @@ src/api/asset_interfaces/
1. Each namespace MUST have: 1. Each namespace MUST have:
- An `index.ts` file that exports all types - An `index.ts` file that exports all types
- An `interfaces.ts` file containing type definitions - An `interfaces.ts` file containing type definitions
- A `queryKeys.ts` file that defines query key options for TanStack Query
2. Interface File (`interfaces.ts`) Requirements: 2. Interface File (`interfaces.ts`) Requirements:
- Must contain TypeScript interfaces, types, and enums for API responses - Must contain TypeScript interfaces, types, and enums for API responses
@ -32,12 +33,28 @@ src/api/asset_interfaces/
- Types: `TPascalCase` - Types: `TPascalCase`
- Enums: `EPascalCase` - Enums: `EPascalCase`
3. Index File (`index.ts`) Requirements: 3. Query Keys File (`queryKeys.ts`) Requirements:
- Defines reusable query keys and query options for TanStack React Query
- Uses `queryOptions` to create predefined query configurations
- Ensures consistent and structured key usage across API queries
- Example:
```typescript
import { queryOptions } from '@tanstack/react-query';
import type { BusterChat } from './chatInterfaces';
const chatsGetChat = (chatId: string) =>
queryOptions<BusterChat>({
queryKey: ['chats', 'get', chatId] as const,
staleTime: 10 * 1000
});
```
4. Index File (`index.ts`) Requirements:
- Must re-export all types from `interfaces.ts` - Must re-export all types from `interfaces.ts`
- Should not contain any type definitions - Should not contain any type definitions
- May include type utility functions if needed - May include type utility functions if needed
4. General Guidelines: 5. General Guidelines:
- Keep interfaces focused and single-responsibility - Keep interfaces focused and single-responsibility
- Use TypeScript's built-in utility types when appropriate - Use TypeScript's built-in utility types when appropriate
- Document breaking changes in type definitions - Document breaking changes in type definitions
@ -54,10 +71,22 @@ export interface IApiResponse<T> {
readonly message?: string; readonly message?: string;
} }
// queryKeys.ts
import { queryOptions } from '@tanstack/react-query';
import type { IApiResponse } from './interfaces';
const fetchDataQuery = (id: string) =>
queryOptions<IApiResponse<any>>({
queryKey: ['data', 'fetch', id] as const,
staleTime: 60 * 1000
});
// index.ts // index.ts
export * from './interfaces'; export * from './interfaces';
export * from './queryKeys';
``` ```
## Purpose ## Purpose
This directory serves as the single source of truth for API response types across the application. It ensures type safety and provides proper TypeScript intellisense when working with API responses. This directory serves as the single source of truth for API response types and query keys across the application. It ensures type safety, consistency, and provides proper TypeScript intellisense when working with API responses and queries.

View File

@ -1,48 +0,0 @@
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>({
queryKey: ['chats', 'get', chatId] as const,
staleTime: 10 * 1000
});
const chatsGetList = (filters?: GetChatListParams) =>
queryOptions<BusterChatListItem[]>({
queryKey: ['chats', 'list', filters] as const
});
const deleteChat = () => {
const queryKey = ['chats', 'list'] as const;
return queryOptions<BusterChatListItem[]>({
queryKey
});
};
export const chatQueryKeys = {
'/chats/get:getChat': chatsGetChat,
'/chats/list:getChatsList': chatsGetList,
'/chats/delete:deleteChat': deleteChat
};
const ExampleComponent = () => {
const queryClient = useQueryClient();
const options = chatQueryKeys['/chats/get:getChat']!('123');
const queryKey = options.queryKey;
const data = queryClient.getQueryData(queryKey);
const { data: data2 } = useQuery(options);
queryClient.setQueryData(queryKey, (d) => {
return d;
});
const options2 = chatQueryKeys['/chats/delete:deleteChat']!();
const queryKey2 = options2.queryKey;
const data3 = queryClient.getQueryData(queryKey2);
//
};

View File

@ -0,0 +1,41 @@
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>({
queryKey: ['chats', 'get', chatId] as const,
staleTime: 10 * 1000
});
const chatsGetList = (filters?: GetChatListParams) =>
queryOptions<BusterChatListItem[]>({
queryKey: ['chats', 'list', filters] as const,
staleTime: 10 * 1000
});
export const chatQueryKeys = {
'/chats/get:getChat': chatsGetChat,
'/chats/list:getChatsList': chatsGetList
};
// const ExampleComponent = () => {
// const queryClient = useQueryClient();
// const options = chatQueryKeys['/chats/get:getChat']!('123');
// const queryKey = options.queryKey;
// const data = queryClient.getQueryData(queryKey);
// const { data: data2 } = useQuery(options);
// queryClient.setQueryData(queryKey, (d) => {
// return d;
// });
// const options2 = chatQueryKeys['/chats/list:getChatsList']!();
// const queryKey2 = options2.queryKey;
// const data3 = queryClient.getQueryData(queryKey2);
// //
// };

View File

@ -1,12 +0,0 @@
import { queryOptions } from '@tanstack/react-query';
import { BusterCollectionListItem } from './interfaces';
import type { GetCollectionListParams } from '../../request_interfaces/collections';
const collectionsGetList = (filters?: GetCollectionListParams) =>
queryOptions<BusterCollectionListItem[]>({
queryKey: ['collections', 'list', filters] as const
});
export const collectionQueryKeys = {
'/collections/list:getCollectionsList': collectionsGetList
};

View File

@ -0,0 +1,18 @@
import { queryOptions } from '@tanstack/react-query';
import { BusterCollectionListItem, BusterCollection } from './interfaces';
import type { GetCollectionListParams } from '../../request_interfaces/collections';
const collectionsGetList = (filters?: GetCollectionListParams) =>
queryOptions<BusterCollectionListItem[]>({
queryKey: ['collections', 'list', filters] as const
});
const collectionsGetCollection = (collectionId: string) =>
queryOptions<BusterCollection>({
queryKey: ['collections', 'get', collectionId] as const
});
export const collectionQueryKeys = {
'/collections/list:getCollectionsList': collectionsGetList,
'/collections/get:collectionState': collectionsGetCollection
};

View File

@ -0,0 +1,18 @@
import { queryOptions } from '@tanstack/react-query';
import type { BusterDashboard, BusterDashboardResponse } from './interfaces';
const dashboardGetList = queryOptions<BusterDashboard[]>({
queryKey: ['dashboard', 'list'] as const,
staleTime: 10 * 1000
});
const dashboardGetDashboard = (dashboardId: string) =>
queryOptions<BusterDashboardResponse>({
queryKey: ['dashboard', 'get', dashboardId] as const,
staleTime: 10 * 1000
});
export const dashboardQueryKeys = {
'/dashboards/get:getDashboardState': dashboardGetDashboard,
'/dashboards/list:getDashboardsList': dashboardGetList
};

View File

@ -1,7 +1,11 @@
import { chatQueryKeys } from './chat/chatQueryKeys'; import { chatQueryKeys } from './chat/queryKeys';
import { collectionQueryKeys } from './collection/collectionQueryKeys'; import { collectionQueryKeys } from './collection/queryKeys';
import { userQueryKeys } from './users/queryKeys';
import { dashboardQueryKeys } from './dashboard/queryKeys';
export const queryKeys = { export const queryKeys = {
...chatQueryKeys, ...chatQueryKeys,
...collectionQueryKeys ...collectionQueryKeys,
...userQueryKeys,
...dashboardQueryKeys
}; };

View File

@ -0,0 +1,10 @@
import { queryOptions } from '@tanstack/react-query';
import { BusterUserFavorite } from './interfaces';
const favoritesGetList = queryOptions<BusterUserFavorite[]>({
queryKey: ['users', 'favorites', 'list'] as const
});
export const userQueryKeys = {
'/favorites/list:getFavoritesList': favoritesGetList
};

View File

@ -16,9 +16,10 @@ export const useSocketQueryEmitOn = <
socketResponse: TRoute, socketResponse: TRoute,
options: UseQueryOptions<TData, TError, TData, TQueryKey>, options: UseQueryOptions<TData, TError, TData, TQueryKey>,
callback?: (currentData: TData | null, newData: InferBusterSocketResponseData<TRoute>) => TData, callback?: (currentData: TData | null, newData: InferBusterSocketResponseData<TRoute>) => TData,
enabledTrigger?: boolean | string enabledTriggerProp?: boolean | string
) => { ) => {
const busterSocket = useBusterWebSocket(); const busterSocket = useBusterWebSocket();
const enabledTrigger = enabledTriggerProp ?? true;
const emitQueryFn = useMemoizedFn(async () => { const emitQueryFn = useMemoizedFn(async () => {
busterSocket.emit(socketRequest); busterSocket.emit(socketRequest);
@ -30,5 +31,7 @@ export const useSocketQueryEmitOn = <
} }
}, [enabledTrigger]); }, [enabledTrigger]);
return useSocketQueryOn(socketResponse, options, callback); const queryResult = useSocketQueryOn(socketResponse, options, callback);
return { ...queryResult, refetch: emitQueryFn };
}; };

View File

@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { type UseQueryOptions, useMutation, useQueryClient } from '@tanstack/react-query';
type QueryFunction,
type QueryKey,
type UseQueryOptions,
type UseMutationOptions,
useMutation,
useQueryClient
} from '@tanstack/react-query';
import type { import type {
BusterSocketRequest, BusterSocketRequest,
BusterSocketResponse, BusterSocketResponse,
@ -15,7 +8,6 @@ import type {
} from '@/api/buster_socket'; } from '@/api/buster_socket';
import { useBusterWebSocket } from '@/context/BusterWebSocket'; import { useBusterWebSocket } from '@/context/BusterWebSocket';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { queryKeys } from '../asset_interfaces';
import type { import type {
BusterSocketRequestConfig, BusterSocketRequestConfig,
BusterSocketRequestRoute, BusterSocketRequestRoute,
@ -23,7 +15,6 @@ import type {
InferBusterSocketRequestPayload, InferBusterSocketRequestPayload,
InferBusterSocketResponseData InferBusterSocketResponseData
} from './types'; } from './types';
import type { BusterChatListItem } from '@/api/asset_interfaces/chat';
export function useSocketQueryMutation< export function useSocketQueryMutation<
TRequestRoute extends BusterSocketRequestRoute, TRequestRoute extends BusterSocketRequestRoute,
@ -36,10 +27,13 @@ export function useSocketQueryMutation<
socketRequest: BusterSocketRequestConfig<TRequestRoute>, socketRequest: BusterSocketRequestConfig<TRequestRoute>,
socketResponse: BusterSocketResponseConfig<TRoute>, socketResponse: BusterSocketResponseConfig<TRoute>,
options?: UseQueryOptions<TQueryData, any, TQueryData, any>, options?: UseQueryOptions<TQueryData, any, TQueryData, any>,
preCallback?: (currentData: TQueryData | null, variables: TPayload) => TQueryData, preCallback?: (
callback?: (
currentData: TQueryData | null, currentData: TQueryData | null,
newData: InferBusterSocketResponseData<TRoute> variables: TPayload
) => TQueryData | Promise<TQueryData>,
callback?: (
newData: InferBusterSocketResponseData<TRoute>,
currentData: TQueryData | null
) => TQueryData ) => TQueryData
) { ) {
const busterSocket = useBusterWebSocket(); const busterSocket = useBusterWebSocket();
@ -50,7 +44,7 @@ export function useSocketQueryMutation<
if (queryKey && preCallback) { if (queryKey && preCallback) {
const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null; const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null;
const transformedData = preCallback(currentData, variables); const transformedData = await preCallback(currentData, variables);
queryClient.setQueryData(queryKey, transformedData); queryClient.setQueryData(queryKey, transformedData);
} }
@ -70,7 +64,7 @@ export function useSocketQueryMutation<
if (queryKey && callback) { if (queryKey && callback) {
const socketData = result as InferBusterSocketResponseData<TRoute>; const socketData = result as InferBusterSocketResponseData<TRoute>;
const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null; const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null;
const transformedData = callback(currentData, socketData); const transformedData = callback(socketData, currentData);
queryClient.setQueryData(queryKey, transformedData); queryClient.setQueryData(queryKey, transformedData);
return result as TData; return result as TData;
} }
@ -85,29 +79,3 @@ export function useSocketQueryMutation<
mutationFn mutationFn
}); });
} }
// const ExampleComponent = () => {
// const queryClient = useQueryClient();
// const options = queryKeys['/chats/list:getChatsList']();
// const data = queryClient.getQueryData(options.queryKey);
// data?.[0].created_by_avatar;
// const { mutate } = useSocketQueryMutation<
// '/chats/delete',
// '/chats/delete:deleteChat',
// unknown,
// { id: string }[],
// { id: string }[],
// BusterChatListItem[]
// >(
// { route: '/chats/delete' },
// { route: '/chats/delete:deleteChat' },
// options,
// (currentData, newData) => {
// currentData?.[0].created_by_avatar; // This should now be properly typed
// return currentData ?? [];
// }
// );
// mutate([{ id: '123' }]);
// };

View File

@ -1,14 +1,17 @@
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { useBusterWebSocket } from '../../BusterWebSocket'; import { useBusterWebSocket } from '../../BusterWebSocket';
import { useSocketQueryEmitAndOnce, useSocketQueryMutation } from '@/api/buster_socket_query';
export const useChatAssosciations = () => { export const useChatAssosciations = () => {
const busterSocket = useBusterWebSocket(); const busterSocket = useBusterWebSocket();
const x = useSocketQueryMutation('');
const onDeleteChat = useMemoizedFn(async (chatId: string) => { const onDeleteChat = useMemoizedFn(async (chatId: string) => {
await busterSocket.emit({ //
route: '/chats/delete', // await busterSocket.emit({
payload: { id: chatId } // route: '/chats/delete',
}); // payload: { id: chatId }
// });
}); });
return { return {

View File

@ -1,10 +1,15 @@
import { useSocketQueryEmitOn } from '@/hooks'; import { queryKeys } from '@/api/asset_interfaces';
import { useSocketQueryEmitOn } from '@/api/buster_socket_query';
export const useCollectionIndividual = ({ collectionId }: { collectionId: string | undefined }) => { export const useCollectionIndividual = ({ collectionId }: { collectionId: string | undefined }) => {
const id = collectionId || '';
const { data: collection, isFetched: isCollectionFetched } = useSocketQueryEmitOn( const { data: collection, isFetched: isCollectionFetched } = useSocketQueryEmitOn(
{ route: '/collections/get', payload: { id: collectionId || '' } }, { route: '/collections/get', payload: { id } },
{ route: '/collections/get:collectionState' }, '/collections/get:collectionState',
{ enabled: !!collectionId } queryKeys['/collections/get:collectionState'](id),
undefined,
!!id
); );
return { return {

View File

@ -1,11 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useSocketQueryEmitOn } from '@/hooks'; import { useSocketQueryEmitOn } from '@/api/buster_socket_query';
import type { CollectionsListEmit } from '@/api/buster_socket/collections'; import type { CollectionsListEmit } from '@/api/buster_socket/collections';
import { import {
ContextSelector, ContextSelector,
useContextSelector, useContextSelector,
createContext createContext
} from '@fluentui/react-context-selector'; } from '@fluentui/react-context-selector';
import { queryKeys } from '@/api/asset_interfaces';
type CollectionListFilters = Omit<CollectionsListEmit['payload'], 'page' | 'page_size'>; type CollectionListFilters = Omit<CollectionsListEmit['payload'], 'page' | 'page_size'>;
@ -18,7 +19,8 @@ export const useCollectionLists = () => {
refetch: refetchCollectionList refetch: refetchCollectionList
} = useSocketQueryEmitOn( } = useSocketQueryEmitOn(
{ route: '/collections/list', payload: { page: 0, page_size: 1000, ...collectionListFilters } }, { route: '/collections/list', payload: { page: 0, page_size: 1000, ...collectionListFilters } },
{ route: '/collections/list:listCollections' } '/collections/list:listCollections',
queryKeys['/collections/list:getCollectionsList']()
); );
return { return {

View File

@ -1,9 +1,8 @@
import { BusterDashboardResponse } from '@/api/asset_interfaces'; import { BusterDashboardResponse, queryKeys } from '@/api/asset_interfaces';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider'; import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import React, { useEffect } from 'react';
import { useBusterMetricsIndividualContextSelector } from '@/context/Metrics'; import { useBusterMetricsIndividualContextSelector } from '@/context/Metrics';
import { useSocketQueryEmitOn } from '@/hooks'; import { useSocketQueryEmitOn } from '@/api/buster_socket_query';
export const useBusterDashboardIndividual = ({ export const useBusterDashboardIndividual = ({
dashboardId = '' dashboardId = ''
@ -18,23 +17,22 @@ export const useBusterDashboardIndividual = ({
const { data: dashboardResponse, refetch: refreshDashboard } = useSocketQueryEmitOn( const { data: dashboardResponse, refetch: refreshDashboard } = useSocketQueryEmitOn(
{ route: '/dashboards/get', payload: { id: dashboardId, password } }, { route: '/dashboards/get', payload: { id: dashboardId, password } },
{ route: '/dashboards/get:getDashboardState' }, '/dashboards/get:getDashboardState',
{ enabled: !!dashboardId } queryKeys['/dashboards/get:getDashboardState'](dashboardId || ''),
(currentData, newData) => {
initializeDashboardMetrics(newData.metrics);
return newData;
},
!!dashboardId
); );
const initializeDashboard = useMemoizedFn((d: BusterDashboardResponse) => { const initializeDashboardMetrics = useMemoizedFn(
const metrics = d.metrics; (metrics: BusterDashboardResponse['metrics']) => {
for (const metric of metrics) { for (const metric of metrics) {
onInitializeMetric(metric); onInitializeMetric(metric);
} }
});
useEffect(() => {
if (dashboardResponse) {
initializeDashboard(dashboardResponse);
} }
}, [dashboardResponse]); );
const dashboard = dashboardResponse?.dashboard; const dashboard = dashboardResponse?.dashboard;
const metrics = dashboardResponse?.metrics || []; const metrics = dashboardResponse?.metrics || [];

View File

@ -1,55 +1,38 @@
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { useSocketQueryEmitOn, useSocketQueryMutation } from '@/hooks'; import { useSocketQueryEmitOn, useSocketQueryMutation } from '@/api/buster_socket_query';
import { queryKeys } from '@/api/asset_interfaces';
export const useFavoriteProvider = () => { export const useFavoriteProvider = () => {
const { data: userFavorites, refetch: refreshFavoritesList } = useSocketQueryEmitOn( const { data: userFavorites, refetch: refreshFavoritesList } = useSocketQueryEmitOn(
{ route: '/users/favorites/list', payload: {} }, { route: '/users/favorites/list', payload: {} },
{ route: '/users/favorites/list:listFavorites' } '/users/favorites/list:listFavorites',
queryKeys['/favorites/list:getFavoritesList']
); );
const { mutate: addItemToFavorite } = useSocketQueryMutation( const { mutate: addItemToFavorite } = useSocketQueryMutation(
{ route: '/users/favorites/post' }, { route: '/users/favorites/post' },
{ route: '/users/favorites/post:createFavorite' }, { route: '/users/favorites/post:createFavorite' },
{ queryKeys['/favorites/list:getFavoritesList'],
preSetQueryData: [ (prev, mutationParams) => [mutationParams, ...(prev || [])]
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => [mutationParams, ...(prev || [])]
}
]
}
); );
const { mutate: removeItemFromFavorite } = useSocketQueryMutation( const { mutate: removeItemFromFavorite } = useSocketQueryMutation(
{ route: '/users/favorites/delete' }, { route: '/users/favorites/delete' },
{ route: '/users/favorites/post:createFavorite' }, { route: '/users/favorites/post:createFavorite' },
{ queryKeys['/favorites/list:getFavoritesList'],
preSetQueryData: [ (prev, mutationParams) => prev?.filter((f) => f.id !== mutationParams.id) || []
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => prev?.filter((f) => f.id !== mutationParams.id) || []
}
]
}
); );
const { mutate: updateFavorites } = useSocketQueryMutation( const { mutate: updateFavorites } = useSocketQueryMutation(
{ route: '/users/favorites/update' }, { route: '/users/favorites/update' },
{ route: '/users/favorites/update:updateFavorite' }, { route: '/users/favorites/update:updateFavorite' },
{ queryKeys['/favorites/list:getFavoritesList'],
awaitPrefetchQueryData: true, (prev, mutationParams) => {
preSetQueryData: [
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => {
return mutationParams.favorites.map((id, index) => { return mutationParams.favorites.map((id, index) => {
let favorite = (prev || []).find((f) => f.id === id || f.collection_id === id)!; const favorite = (prev || []).find((f) => f.id === id || f.collection_id === id)!;
return { ...favorite, index }; return { ...favorite, index };
}); });
} }
}
]
}
); );
const bulkEditFavorites = useMemoizedFn(async (favorites: string[]) => { const bulkEditFavorites = useMemoizedFn(async (favorites: string[]) => {