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
globs: src/api/asset_interfaces/**/*
---
# 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.
@ -13,7 +12,8 @@ src/api/asset_interfaces/
├── [namespace]/
│ ├── index.ts # Exports all interfaces and types
│ ├── interfaces.ts # Contains type definitions
│ └── [other].ts # Optional additional type files
│ ├── queryKeys.ts # Contains query key definitions for TanStack Query
│ └── [other].ts # Optional additional type files
```
## Rules and Guidelines
@ -21,6 +21,7 @@ src/api/asset_interfaces/
1. Each namespace MUST have:
- An `index.ts` file that exports all types
- 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:
- Must contain TypeScript interfaces, types, and enums for API responses
@ -32,12 +33,28 @@ src/api/asset_interfaces/
- Types: `TPascalCase`
- 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`
- Should not contain any type definitions
- May include type utility functions if needed
4. General Guidelines:
5. General Guidelines:
- Keep interfaces focused and single-responsibility
- Use TypeScript's built-in utility types when appropriate
- Document breaking changes in type definitions
@ -54,10 +71,22 @@ export interface IApiResponse<T> {
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
export * from './interfaces';
export * from './queryKeys';
```
## 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 { collectionQueryKeys } from './collection/collectionQueryKeys';
import { chatQueryKeys } from './chat/queryKeys';
import { collectionQueryKeys } from './collection/queryKeys';
import { userQueryKeys } from './users/queryKeys';
import { dashboardQueryKeys } from './dashboard/queryKeys';
export const queryKeys = {
...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,
options: UseQueryOptions<TData, TError, TData, TQueryKey>,
callback?: (currentData: TData | null, newData: InferBusterSocketResponseData<TRoute>) => TData,
enabledTrigger?: boolean | string
enabledTriggerProp?: boolean | string
) => {
const busterSocket = useBusterWebSocket();
const enabledTrigger = enabledTriggerProp ?? true;
const emitQueryFn = useMemoizedFn(async () => {
busterSocket.emit(socketRequest);
@ -30,5 +31,7 @@ export const useSocketQueryEmitOn = <
}
}, [enabledTrigger]);
return useSocketQueryOn(socketResponse, options, callback);
const queryResult = useSocketQueryOn(socketResponse, options, callback);
return { ...queryResult, refetch: emitQueryFn };
};

View File

@ -1,13 +1,6 @@
'use client';
import {
type QueryFunction,
type QueryKey,
type UseQueryOptions,
type UseMutationOptions,
useMutation,
useQueryClient
} from '@tanstack/react-query';
import { type UseQueryOptions, useMutation, useQueryClient } from '@tanstack/react-query';
import type {
BusterSocketRequest,
BusterSocketResponse,
@ -15,7 +8,6 @@ import type {
} from '@/api/buster_socket';
import { useBusterWebSocket } from '@/context/BusterWebSocket';
import { useMemoizedFn } from 'ahooks';
import { queryKeys } from '../asset_interfaces';
import type {
BusterSocketRequestConfig,
BusterSocketRequestRoute,
@ -23,7 +15,6 @@ import type {
InferBusterSocketRequestPayload,
InferBusterSocketResponseData
} from './types';
import type { BusterChatListItem } from '@/api/asset_interfaces/chat';
export function useSocketQueryMutation<
TRequestRoute extends BusterSocketRequestRoute,
@ -36,10 +27,13 @@ export function useSocketQueryMutation<
socketRequest: BusterSocketRequestConfig<TRequestRoute>,
socketResponse: BusterSocketResponseConfig<TRoute>,
options?: UseQueryOptions<TQueryData, any, TQueryData, any>,
preCallback?: (currentData: TQueryData | null, variables: TPayload) => TQueryData,
callback?: (
preCallback?: (
currentData: TQueryData | null,
newData: InferBusterSocketResponseData<TRoute>
variables: TPayload
) => TQueryData | Promise<TQueryData>,
callback?: (
newData: InferBusterSocketResponseData<TRoute>,
currentData: TQueryData | null
) => TQueryData
) {
const busterSocket = useBusterWebSocket();
@ -50,7 +44,7 @@ export function useSocketQueryMutation<
if (queryKey && preCallback) {
const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null;
const transformedData = preCallback(currentData, variables);
const transformedData = await preCallback(currentData, variables);
queryClient.setQueryData(queryKey, transformedData);
}
@ -70,7 +64,7 @@ export function useSocketQueryMutation<
if (queryKey && callback) {
const socketData = result as InferBusterSocketResponseData<TRoute>;
const currentData = queryClient.getQueryData<TQueryData>(queryKey) ?? null;
const transformedData = callback(currentData, socketData);
const transformedData = callback(socketData, currentData);
queryClient.setQueryData(queryKey, transformedData);
return result as TData;
}
@ -85,29 +79,3 @@ export function useSocketQueryMutation<
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 { useBusterWebSocket } from '../../BusterWebSocket';
import { useSocketQueryEmitAndOnce, useSocketQueryMutation } from '@/api/buster_socket_query';
export const useChatAssosciations = () => {
const busterSocket = useBusterWebSocket();
const x = useSocketQueryMutation('');
const onDeleteChat = useMemoizedFn(async (chatId: string) => {
await busterSocket.emit({
route: '/chats/delete',
payload: { id: chatId }
});
//
// await busterSocket.emit({
// route: '/chats/delete',
// payload: { id: chatId }
// });
});
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 }) => {
const id = collectionId || '';
const { data: collection, isFetched: isCollectionFetched } = useSocketQueryEmitOn(
{ route: '/collections/get', payload: { id: collectionId || '' } },
{ route: '/collections/get:collectionState' },
{ enabled: !!collectionId }
{ route: '/collections/get', payload: { id } },
'/collections/get:collectionState',
queryKeys['/collections/get:collectionState'](id),
undefined,
!!id
);
return {

View File

@ -1,11 +1,12 @@
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 {
ContextSelector,
useContextSelector,
createContext
} from '@fluentui/react-context-selector';
import { queryKeys } from '@/api/asset_interfaces';
type CollectionListFilters = Omit<CollectionsListEmit['payload'], 'page' | 'page_size'>;
@ -18,7 +19,8 @@ export const useCollectionLists = () => {
refetch: refetchCollectionList
} = useSocketQueryEmitOn(
{ route: '/collections/list', payload: { page: 0, page_size: 1000, ...collectionListFilters } },
{ route: '/collections/list:listCollections' }
'/collections/list:listCollections',
queryKeys['/collections/list:getCollectionsList']()
);
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 { useMemoizedFn } from 'ahooks';
import React, { useEffect } from 'react';
import { useBusterMetricsIndividualContextSelector } from '@/context/Metrics';
import { useSocketQueryEmitOn } from '@/hooks';
import { useSocketQueryEmitOn } from '@/api/buster_socket_query';
export const useBusterDashboardIndividual = ({
dashboardId = ''
@ -18,23 +17,22 @@ export const useBusterDashboardIndividual = ({
const { data: dashboardResponse, refetch: refreshDashboard } = useSocketQueryEmitOn(
{ route: '/dashboards/get', payload: { id: dashboardId, password } },
{ route: '/dashboards/get:getDashboardState' },
{ enabled: !!dashboardId }
'/dashboards/get:getDashboardState',
queryKeys['/dashboards/get:getDashboardState'](dashboardId || ''),
(currentData, newData) => {
initializeDashboardMetrics(newData.metrics);
return newData;
},
!!dashboardId
);
const initializeDashboard = useMemoizedFn((d: BusterDashboardResponse) => {
const metrics = d.metrics;
for (const metric of metrics) {
onInitializeMetric(metric);
const initializeDashboardMetrics = useMemoizedFn(
(metrics: BusterDashboardResponse['metrics']) => {
for (const metric of metrics) {
onInitializeMetric(metric);
}
}
});
useEffect(() => {
if (dashboardResponse) {
initializeDashboard(dashboardResponse);
}
}, [dashboardResponse]);
);
const dashboard = dashboardResponse?.dashboard;
const metrics = dashboardResponse?.metrics || [];

View File

@ -1,54 +1,37 @@
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 = () => {
const { data: userFavorites, refetch: refreshFavoritesList } = useSocketQueryEmitOn(
{ route: '/users/favorites/list', payload: {} },
{ route: '/users/favorites/list:listFavorites' }
'/users/favorites/list:listFavorites',
queryKeys['/favorites/list:getFavoritesList']
);
const { mutate: addItemToFavorite } = useSocketQueryMutation(
{ route: '/users/favorites/post' },
{ route: '/users/favorites/post:createFavorite' },
{
preSetQueryData: [
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => [mutationParams, ...(prev || [])]
}
]
}
queryKeys['/favorites/list:getFavoritesList'],
(prev, mutationParams) => [mutationParams, ...(prev || [])]
);
const { mutate: removeItemFromFavorite } = useSocketQueryMutation(
{ route: '/users/favorites/delete' },
{ route: '/users/favorites/post:createFavorite' },
{
preSetQueryData: [
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => prev?.filter((f) => f.id !== mutationParams.id) || []
}
]
}
queryKeys['/favorites/list:getFavoritesList'],
(prev, mutationParams) => prev?.filter((f) => f.id !== mutationParams.id) || []
);
const { mutate: updateFavorites } = useSocketQueryMutation(
{ route: '/users/favorites/update' },
{ route: '/users/favorites/update:updateFavorite' },
{
awaitPrefetchQueryData: true,
preSetQueryData: [
{
responseRoute: '/users/favorites/list:listFavorites',
callback: (prev, mutationParams) => {
return mutationParams.favorites.map((id, index) => {
let favorite = (prev || []).find((f) => f.id === id || f.collection_id === id)!;
return { ...favorite, index };
});
}
}
]
queryKeys['/favorites/list:getFavoritesList'],
(prev, mutationParams) => {
return mutationParams.favorites.map((id, index) => {
const favorite = (prev || []).find((f) => f.id === id || f.collection_id === id)!;
return { ...favorite, index };
});
}
);