@redux-devtools/rtk-query-monitor
Version:
rtk-query monitor for Redux DevTools
433 lines (425 loc) • 16.3 kB
JavaScript
;
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;
}