import { Action, createSelector, Selector } from '@reduxjs/toolkit'; import { RtkQueryInspectorProps } from './containers/RtkQueryInspector'; import { ApiStats, QueryInfo, RtkQueryApiState, RtkQueryTag, SelectorsSource, RtkQueryProvided, QueryPreviewTabs, RtkResourceInfo, } from './types'; import { Comparator, queryComparators } from './utils/comparators'; import { FilterList, queryListFilters } from './utils/filters'; import { emptyRecord } from './utils/object'; import { escapeRegExpSpecialCharacter } from './utils/regexp'; import { getApiStatesOf, extractAllApiQueries, flipComparator, getQueryTagsOf, generateApiStatsOfCurrentQuery, getActionsOfCurrentQuery, extractAllApiMutations, } from './utils/rtk-query'; type InspectorSelector = Selector, Output>; export function computeSelectorSource>( props: RtkQueryInspectorProps, previous: SelectorsSource | null = null ): SelectorsSource { const { computedStates, currentStateIndex, monitorState, actionsById } = props; const userState = computedStates.length > 0 ? computedStates[currentStateIndex].state : null; if ( !previous || previous.userState !== userState || previous.monitorState !== monitorState || previous.actionsById !== actionsById ) { return { userState, monitorState, currentStateIndex, actionsById, }; } return previous; } export interface InspectorSelectors { readonly selectQueryComparator: InspectorSelector>; readonly selectApiStates: InspectorSelector< S, ReturnType >; readonly selectAllQueries: InspectorSelector< S, ReturnType >; readonly selectAllVisbileQueries: InspectorSelector; readonly selectCurrentQueryInfo: InspectorSelector; readonly selectSearchQueryRegex: InspectorSelector; readonly selectCurrentQueryTags: InspectorSelector; readonly selectApiStatsOfCurrentQuery: InspectorSelector; readonly selectApiOfCurrentQuery: InspectorSelector< S, RtkQueryApiState | null >; readonly selectTabCounters: InspectorSelector< S, Record >; readonly selectSubscriptionsOfCurrentQuery: InspectorSelector< S, RtkQueryApiState['subscriptions'][string] >; readonly selectActionsOfCurrentQuery: InspectorSelector< S, ReturnType >; } export function createInspectorSelectors(): InspectorSelectors { const selectQueryComparator = ({ monitorState, }: SelectorsSource): Comparator => { return queryComparators[monitorState.queryForm.values.queryComparator]; }; const selectQueryListFilter = ({ monitorState, }: SelectorsSource): FilterList => { return queryListFilters[monitorState.queryForm.values.queryFilter]; }; const selectActionsById = ({ actionsById }: SelectorsSource) => actionsById; const selectApiStates = createSelector( ({ userState }: SelectorsSource) => userState, getApiStatesOf ); const selectAllQueries = createSelector( selectApiStates, extractAllApiQueries ); const selectAllMutations = createSelector( selectApiStates, extractAllApiMutations ); const selectSearchQueryRegex = createSelector( ({ monitorState }: SelectorsSource) => monitorState.queryForm.values.searchValue, ({ monitorState }: SelectorsSource) => monitorState.queryForm.values.isRegexSearch, (searchValue, isRegexSearch) => { if (searchValue) { try { const regexPattern = isRegexSearch ? searchValue : escapeRegExpSpecialCharacter(searchValue); return new RegExp(regexPattern, 'i'); } catch (err) { // We notify that the search regex provided is not valid } } return null; } ); const selectComparatorOrder = ({ monitorState }: SelectorsSource) => monitorState.queryForm.values.isAscendingQueryComparatorOrder; const selectAllVisbileQueries = createSelector( [ selectQueryComparator, selectQueryListFilter, selectAllQueries, selectAllMutations, selectComparatorOrder, selectSearchQueryRegex, ], ( comparator, queryListFilter, queryList, mutationsList, isAscending, searchRegex ) => { const filteredList = queryListFilter( searchRegex, (queryList as RtkResourceInfo[]).concat(mutationsList) ); const computedComparator = isAscending ? comparator : flipComparator(comparator); return filteredList.slice().sort(computedComparator); } ); const selectCurrentQueryInfo = createSelector( selectAllQueries, selectAllMutations, ({ monitorState }: SelectorsSource) => monitorState.selectedQueryKey, (allQueries, allMutations, selectedQueryKey) => { if (!selectedQueryKey) { return null; } let currentQueryInfo: null | RtkResourceInfo = allQueries.find( (query) => query.queryKey === selectedQueryKey.queryKey && selectedQueryKey.reducerPath === query.reducerPath ) || null; if (!currentQueryInfo) { currentQueryInfo = allMutations.find( (mutation) => mutation.queryKey === selectedQueryKey.queryKey && selectedQueryKey.reducerPath === mutation.reducerPath ) || null; } return currentQueryInfo; } ); const selectApiOfCurrentQuery: InspectorSelector = (selectorsSource: SelectorsSource) => { const apiStates = selectApiStates(selectorsSource); const currentQueryInfo = selectCurrentQueryInfo(selectorsSource); if (!apiStates || !currentQueryInfo) { return null; } return apiStates[currentQueryInfo.reducerPath] ?? null; }; const selectProvidedOfCurrentQuery: InspectorSelector< S, null | RtkQueryProvided > = (selectorsSource: SelectorsSource) => { return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null; }; const selectSubscriptionsOfCurrentQuery = createSelector( [selectApiOfCurrentQuery, selectCurrentQueryInfo], (apiState, queryInfo) => { if (!queryInfo || !apiState) { return emptyRecord; } return apiState.subscriptions[queryInfo.queryKey]; } ); const selectCurrentQueryTags = createSelector( [selectCurrentQueryInfo, selectProvidedOfCurrentQuery], getQueryTagsOf ); const selectApiStatsOfCurrentQuery = createSelector( selectApiOfCurrentQuery, (selectorsSource) => selectorsSource.actionsById, (selectorsSource) => selectorsSource.currentStateIndex, generateApiStatsOfCurrentQuery ); const selectActionsOfCurrentQuery = createSelector( selectCurrentQueryInfo, selectActionsById, getActionsOfCurrentQuery ); const selectTabCounters = createSelector( [ selectSubscriptionsOfCurrentQuery, selectActionsOfCurrentQuery, selectCurrentQueryTags, ], (subscriptions, actions, tags) => { return { [QueryPreviewTabs.queryTags]: tags.length, [QueryPreviewTabs.querySubscriptions]: Object.keys(subscriptions ?? {}) .length, [QueryPreviewTabs.apiConfig]: 0, [QueryPreviewTabs.queryinfo]: 0, [QueryPreviewTabs.actions]: actions.length, }; } ); return { selectQueryComparator, selectApiStates, selectAllQueries, selectAllVisbileQueries, selectSearchQueryRegex, selectCurrentQueryInfo, selectCurrentQueryTags, selectApiStatsOfCurrentQuery, selectSubscriptionsOfCurrentQuery, selectApiOfCurrentQuery, selectTabCounters, selectActionsOfCurrentQuery, }; }