UNPKG

@redux-devtools/rtk-query-monitor

Version:
433 lines (425 loc) 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractAllApiMutations = extractAllApiMutations; exports.extractAllApiQueries = extractAllApiQueries; exports.flipComparator = flipComparator; exports.generateApiStatsOfCurrentQuery = generateApiStatsOfCurrentQuery; exports.getActionsOfCurrentQuery = getActionsOfCurrentQuery; exports.getApiStateOf = getApiStateOf; exports.getApiStatesOf = getApiStatesOf; exports.getProvidedOf = getProvidedOf; exports.getQueryStatusFlags = getQueryStatusFlags; exports.getQuerySubscriptionsOf = getQuerySubscriptionsOf; exports.getQueryTagsOf = getQueryTagsOf; exports.isApiSlice = isApiSlice; exports.isQuerySelected = isQuerySelected; exports.matchesEndpoint = matchesEndpoint; var _toolkit = require("@reduxjs/toolkit"); var _query = require("@reduxjs/toolkit/query"); var _types = require("../types"); var _monitorConfig = require("../monitor-config"); var _comparators = require("./comparators"); var _object = require("./object"); var _formatters = require("./formatters"); var statistics = _interopRequireWildcard(require("./statistics")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const rtkqueryApiStateKeys = ['queries', 'mutations', 'config', 'provided', 'subscriptions']; /** * Type guard used to select apis from the user store state. * @param val * @returns {boolean} */ function isApiSlice(val) { if (!(0, _toolkit.isPlainObject)(val)) { return false; } for (let i = 0, len = rtkqueryApiStateKeys.length; i < len; i++) { if (!(0, _toolkit.isPlainObject)(val[rtkqueryApiStateKeys[i]])) { return false; } } return true; } /** * Indexes api states by their `reducerPath`. * * Returns `null` if there are no api slice or `reduxStoreState` * is not an object. * * @param reduxStoreState * @returns */ function getApiStatesOf(reduxStoreState) { if (!(0, _toolkit.isPlainObject)(reduxStoreState)) { return null; } const output = {}; const keys = Object.keys(reduxStoreState); for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const value = reduxStoreState[key]; if (isApiSlice(value)) { output[key] = value; } } if (Object.keys(output).length === 0) { return null; } return output; } function extractAllApiQueries(apiStatesByReducerPath) { if (!apiStatesByReducerPath) { return _object.emptyArray; } const reducerPaths = Object.keys(apiStatesByReducerPath); const output = []; for (let i = 0, len = reducerPaths.length; i < len; i++) { const reducerPath = reducerPaths[i]; const api = apiStatesByReducerPath[reducerPath]; const queryKeys = Object.keys(api.queries); for (let j = 0, qKeysLen = queryKeys.length; j < qKeysLen; j++) { const queryKey = queryKeys[j]; const state = api.queries[queryKey]; if (state) { output.push({ type: 'query', reducerPath, queryKey, state }); } } } return output; } function extractAllApiMutations(apiStatesByReducerPath) { if (!apiStatesByReducerPath) { return _object.emptyArray; } const reducerPaths = Object.keys(apiStatesByReducerPath); const output = []; for (let i = 0, len = reducerPaths.length; i < len; i++) { const reducerPath = reducerPaths[i]; const api = apiStatesByReducerPath[reducerPath]; const mutationKeys = Object.keys(api.mutations); for (let j = 0, mKeysLen = mutationKeys.length; j < mKeysLen; j++) { const queryKey = mutationKeys[j]; const state = api.mutations[queryKey]; if (state) { output.push({ type: 'mutation', reducerPath, queryKey, state }); } } } return output; } function computeQueryTallyOf(queryState) { const queries = Object.values(queryState); const output = { count: 0 }; for (let i = 0, len = queries.length; i < len; i++) { const query = queries[i]; if (query) { output.count++; if (!output[query.status]) { output[query.status] = 1; } else { output[query.status]++; } } } return output; } function tallySubscriptions(subsState) { const subsOfQueries = Object.values(subsState); let output = 0; for (let i = 0, len = subsOfQueries.length; i < len; i++) { const subsOfQuery = subsOfQueries[i]; if (subsOfQuery) { output += Object.keys(subsOfQuery).length; } } return output; } function computeRtkQueryRequests(type, api, sortedActions, currentStateIndex) { const requestById = {}; const matcher = type === 'queries' ? matchesExecuteQuery(api.config.reducerPath) : matchesExecuteMutation(api.config.reducerPath); for (let i = 0, len = sortedActions.length; i < len && i <= currentStateIndex; i++) { const action = sortedActions[i]; if (matcher(action)) { let requestRecord = requestById[action.meta.requestId]; if (!requestRecord) { const queryCacheKey = action.meta?.arg?.queryCacheKey; const queryKey = typeof queryCacheKey === 'string' ? queryCacheKey : action.meta.requestId; const endpointName = action.meta?.arg?.endpointName ?? '-'; requestById[action.meta.requestId] = requestRecord = { queryKey, requestId: action.meta.requestId, endpointName, status: action.meta.requestStatus }; } requestRecord.status = action.meta.requestStatus; if (action.meta.requestStatus === _query.QueryStatus.pending && typeof action.meta.startedTimeStamp === 'number') { requestRecord.startedTimeStamp = action.meta.startedTimeStamp; } if (action.meta.requestStatus === _query.QueryStatus.fulfilled && typeof action.meta.fulfilledTimeStamp === 'number') { requestRecord.fulfilledTimeStamp = action.meta.fulfilledTimeStamp; } } } const requestIds = Object.keys(requestById); // Patch queries that have pending actions that are committed for (let i = 0, len = requestIds.length; i < len; i++) { const requestId = requestIds[i]; const request = requestById[requestId]; if (typeof request.startedTimeStamp === 'undefined' && typeof request.fulfilledTimeStamp === 'number') { const startedTimeStampFromCache = api[type][request.queryKey]?.startedTimeStamp; if (typeof startedTimeStampFromCache === 'number') { request.startedTimeStamp = startedTimeStampFromCache; } } } // Add queries that have pending and fulfilled actions committed const queryCacheEntries = Object.entries(api[type] ?? {}); for (let i = 0, len = queryCacheEntries.length; i < len; i++) { const [queryCacheKey, queryCache] = queryCacheEntries[i]; const requestId = type === 'queries' ? queryCache?.requestId ?? '' : queryCacheKey; if (queryCache && !Object.prototype.hasOwnProperty.call(requestById, requestId)) { const startedTimeStamp = queryCache?.startedTimeStamp; const fulfilledTimeStamp = queryCache?.fulfilledTimeStamp; if (typeof startedTimeStamp === 'number' && typeof fulfilledTimeStamp === 'number') { requestById[requestId] = { queryKey: queryCacheKey, requestId, endpointName: queryCache.endpointName ?? '', startedTimeStamp, fulfilledTimeStamp, status: queryCache.status }; } } } return requestById; } function formatRtkRequest(rtkRequest) { if (!rtkRequest) { return null; } const fulfilledTimeStamp = rtkRequest.fulfilledTimeStamp; const startedTimeStamp = rtkRequest.startedTimeStamp; const output = { queryKey: rtkRequest.queryKey, requestId: rtkRequest.requestId, endpointName: rtkRequest.endpointName, startedAt: '-', completedAt: '-', duration: '-' }; if (typeof fulfilledTimeStamp === 'number' && typeof startedTimeStamp === 'number') { output.startedAt = new Date(startedTimeStamp).toISOString(); output.completedAt = new Date(fulfilledTimeStamp).toISOString(); output.duration = (0, _formatters.formatMs)(fulfilledTimeStamp - startedTimeStamp); } return output; } function computeQueryApiTimings(requestById) { const requests = Object.values(requestById); let latestRequest = null; let oldestRequest = null; let slowestRequest = null; let fastestRequest = null; let slowestDuration = 0; let fastestDuration = Number.MAX_SAFE_INTEGER; const pendingDurations = []; for (let i = 0, len = requests.length; i < len; i++) { const request = requests[i]; const { fulfilledTimeStamp, startedTimeStamp } = request; if (typeof fulfilledTimeStamp === 'number') { const latestFulfilledTimeStamp = latestRequest?.fulfilledTimeStamp || 0; const oldestFulfilledTimeStamp = oldestRequest?.fulfilledTimeStamp || Number.MAX_SAFE_INTEGER; if (fulfilledTimeStamp > latestFulfilledTimeStamp) { latestRequest = request; } if (fulfilledTimeStamp < oldestFulfilledTimeStamp) { oldestRequest = request; } if (typeof startedTimeStamp === 'number' && startedTimeStamp <= fulfilledTimeStamp) { const pendingDuration = fulfilledTimeStamp - startedTimeStamp; pendingDurations.push(pendingDuration); if (pendingDuration > slowestDuration) { slowestDuration = pendingDuration; slowestRequest = request; } if (pendingDuration < fastestDuration) { fastestDuration = pendingDuration; fastestRequest = request; } } } } const average = pendingDurations.length > 0 ? (0, _formatters.formatMs)(statistics.mean(pendingDurations)) : '-'; const median = pendingDurations.length > 0 ? (0, _formatters.formatMs)(statistics.median(pendingDurations)) : '-'; return { latest: formatRtkRequest(latestRequest), oldest: formatRtkRequest(oldestRequest), slowest: formatRtkRequest(slowestRequest), fastest: formatRtkRequest(fastestRequest), average, median }; } function computeApiTimings(api, actionsById, currentStateIndex) { const sortedActions = Object.entries(actionsById).sort((thisAction, thatAction) => (0, _comparators.compareJSONPrimitive)(Number(thisAction[0]), Number(thatAction[0]))).map(entry => entry[1].action); const queryRequestsById = computeRtkQueryRequests('queries', api, sortedActions, currentStateIndex); const mutationRequestsById = computeRtkQueryRequests('mutations', api, sortedActions, currentStateIndex); return { queries: computeQueryApiTimings(queryRequestsById), mutations: computeQueryApiTimings(mutationRequestsById) }; } function generateApiStatsOfCurrentQuery(api, actionsById, currentStateIndex) { if (!api) { return null; } return { timings: computeApiTimings(api, actionsById, currentStateIndex), tally: { cachedQueries: computeQueryTallyOf(api.queries), cachedMutations: computeQueryTallyOf(api.mutations), tagTypes: Object.keys(api.provided).length, subscriptions: tallySubscriptions(api.subscriptions) } }; } function flipComparator(comparator) { return function flipped(a, b) { return comparator(b, a); }; } function isQuerySelected(selectedQueryKey, queryInfo) { return !!selectedQueryKey && selectedQueryKey.queryKey === queryInfo.queryKey && selectedQueryKey.reducerPath === queryInfo.reducerPath; } function getApiStateOf(queryInfo, apiStates) { if (!apiStates || !queryInfo) { return null; } return apiStates[queryInfo.reducerPath] ?? null; } function getQuerySubscriptionsOf(queryInfo, apiStates) { if (!apiStates || !queryInfo) { return null; } return apiStates[queryInfo.reducerPath]?.subscriptions?.[queryInfo.queryKey] ?? null; } function getProvidedOf(queryInfo, apiStates) { if (!apiStates || !queryInfo) { return null; } return apiStates[queryInfo.reducerPath]?.provided ?? null; } function getQueryTagsOf(resInfo, provided) { if (!resInfo || resInfo.type === 'mutation' || !provided) { return _object.emptyArray; } // Handle `api.provided` schema change with RTK Query tag handling. // Originally, `api.provided` was a `Record<string, Record<string, string[]>>`, // directly containing the tag names. // With https://github.com/reduxjs/redux-toolkit/pull/4910 , that changes to // change the top level to be `{tags, keys}`, with `tags` containing the tag names. // Handle the newer structure by extracting the right field if it exists. const actualProvided = (0, _types.isRtkQuery262Provided)(provided) ? provided.tags : provided; const tagTypes = Object.keys(actualProvided); if (tagTypes.length < 1) { return _object.emptyArray; } const output = []; for (const [type, tagIds] of Object.entries(actualProvided)) { if (tagIds) { for (const [id, queryKeys] of Object.entries(tagIds)) { if (queryKeys.includes(resInfo.queryKey)) { const tag = { type }; if (id !== _monitorConfig.missingTagId) { tag.id = id; } output.push(tag); } } } } return output; } /** * Computes query status flags. * @param status * @see https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values * @see https://github.com/reduxjs/redux-toolkit/blob/b718e01d323d3ab4b913e5d88c9b90aa790bb975/src/query/core/apiState.ts#L63 */ function getQueryStatusFlags(_ref) { let { status, data } = _ref; return { isUninitialized: status === _query.QueryStatus.uninitialized, isFetching: status === _query.QueryStatus.pending, isSuccess: status === _query.QueryStatus.fulfilled && !!data, isError: status === _query.QueryStatus.rejected }; } /** * endpoint matcher * @param endpointName * @see https://github.com/reduxjs/redux-toolkit/blob/b718e01d323d3ab4b913e5d88c9b90aa790bb975/src/query/core/buildThunks.ts#L415 */ function matchesEndpoint(endpointName) { return action => endpointName != null && action?.meta?.arg?.endpointName === endpointName; } function matchesQueryKey(queryKey) { return action => action?.meta?.arg?.queryCacheKey === queryKey; } function macthesRequestId(requestId) { return action => action?.meta?.requestId === requestId; } function matchesReducerPath(reducerPath) { return action => typeof action?.type === 'string' && action.type.startsWith(reducerPath); } function matchesExecuteQuery(reducerPath) { return action => { return typeof action?.type === 'string' && action.type.startsWith(`${reducerPath}/executeQuery`) && typeof action.meta?.requestId === 'string' && typeof action.meta?.requestStatus === 'string'; }; } function matchesExecuteMutation(reducerPath) { return action => typeof action?.type === 'string' && action.type.startsWith(`${reducerPath}/executeMutation`) && typeof action.meta?.requestId === 'string' && typeof action.meta?.requestStatus === 'string'; } function getActionsOfCurrentQuery(currentQuery, actionById) { if (!currentQuery) { return _object.emptyArray; } let matcher; if (currentQuery.type === 'mutation') { matcher = (0, _toolkit.isAllOf)(matchesReducerPath(currentQuery.reducerPath), macthesRequestId(currentQuery.queryKey)); } else { matcher = (0, _toolkit.isAllOf)(matchesReducerPath(currentQuery.reducerPath), matchesQueryKey(currentQuery.queryKey)); } const output = []; for (const [, liftedAction] of Object.entries(actionById)) { if (matcher(liftedAction?.action)) { output.push(liftedAction.action); } } return output.length === 0 ? _object.emptyArray : output; }