This commit is contained in:
Stefan Pejcic
2024-11-07 19:03:37 +01:00
parent c6df945ed5
commit 09f9f9502d
2472 changed files with 620417 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
import type { StackFrame } from "error-stack-parser";
const unrelatedFunctionName = "renderWithHooks";
export const cleanStack = (stack: StackFrame[]) => {
const firstUnrelatedIndex = stack.findIndex(
(frame) => frame.functionName === unrelatedFunctionName,
);
if (firstUnrelatedIndex !== -1) {
return stack.slice(0, firstUnrelatedIndex);
}
return stack;
};

View File

@@ -0,0 +1,4 @@
export const REFINE_PACKAGE_FILEPATH_REGEXP =
__DEV_CONDITION__ !== "development"
? /node_modules\/refinedev\/(?<name>.*?)\//
: /\/refine\/packages\/(?<name>.*?)\//;

View File

@@ -0,0 +1,17 @@
import type { TraceType } from "@refinedev/devtools-shared";
import type { MutationKey, QueryKey } from "@tanstack/react-query";
export const createIdentifier = (
key?: QueryKey | MutationKey,
trace?: TraceType[],
) => {
const simpleTrace = trace?.map(
(t) =>
`${t.file}:${t.line}:${t.column}#${t.function}-${t.packageName}-${
t.isRefine ? 1 : 0
}`,
);
const str = JSON.stringify([...(key ?? []), ...(simpleTrace ?? [])]);
return str;
};

View File

@@ -0,0 +1 @@
declare const __DEV_CONDITION__: string;

View File

@@ -0,0 +1,13 @@
import { REFINE_PACKAGE_FILEPATH_REGEXP } from "./constants";
export const getPackageNameFromFilename = (filename?: string) => {
if (!filename) return;
const match = filename.match(REFINE_PACKAGE_FILEPATH_REGEXP);
const name = match?.groups?.name;
if (!name) return;
return `@refinedev/${name}`;
};

View File

@@ -0,0 +1,53 @@
import {
type DevtoolsEvent,
type DevtoolsEventPayloads,
type RefineHook,
scopes,
} from "@refinedev/devtools-shared";
export type Activity =
DevtoolsEventPayloads[DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE]["updatedActivities"][number];
export const getResourcePath = (
hookName: RefineHook,
legacyKey: boolean,
): string | null => {
if (scopes[hookName] === "auth") {
return null;
}
if (hookName === "useCan") {
if (legacyKey) {
return "key[1].resource";
}
return "key[1]";
}
if (scopes[hookName] === "audit-log") {
if (hookName === "useLog") {
return "variables.resource";
}
return "key[1]";
}
if (scopes[hookName] === "data") {
if (hookName === "useCustom" || hookName === "useCustomMutation") {
return null;
}
switch (hookName) {
case "useList":
case "useInfiniteList":
case "useOne":
case "useMany":
if (legacyKey) {
return "key[1]";
}
return "key[2]";
case "useCreate":
case "useCreateMany":
case "useDelete":
case "useDeleteMany":
case "useUpdate":
case "useUpdateMany":
return "variables.resource";
}
}
return null;
};

View File

@@ -0,0 +1,33 @@
import ErrorStackParser from "error-stack-parser";
import { cleanStack } from "./clean-stack";
import { isRefineStack } from "./is-refine-stack";
import { getPackageNameFromFilename } from "./get-package-name-from-filename";
import type { TraceType } from "@refinedev/devtools-shared";
export function getTrace(excludeFromTrace?: string[]) {
if (__DEV_CONDITION__ !== "development") {
return [];
}
try {
const error = new Error();
const stack = ErrorStackParser.parse(error);
const clean = cleanStack(stack);
const traces = clean
.map(
(frame) =>
({
file: frame.fileName,
line: frame.lineNumber,
column: frame.columnNumber,
function: frame.functionName,
isRefine: isRefineStack(frame.fileName),
packageName: getPackageNameFromFilename(frame.fileName),
}) as TraceType,
)
.filter((trace) => trace.function)
.filter((trace) => !excludeFromTrace?.includes(trace.function ?? ""));
return traces.slice(1);
} catch (error) {
return [];
}
}

View File

@@ -0,0 +1,38 @@
import type { RefineHook, TraceType } from "@refinedev/devtools-shared";
import { getTrace } from "./get-trace";
import { getResourcePath } from "./get-resource-path";
export type XRayResponse = {
hookName: string;
trace: TraceType[];
resourcePath: string | null;
legacyKey: boolean;
resourceName?: string;
};
export function getXRay(
hookName: string,
legacyKey: boolean,
resourceName?: string,
excludeFromTrace?: string[],
): XRayResponse {
if (__DEV_CONDITION__ !== "development") {
return {
hookName: "",
trace: [],
resourcePath: null,
legacyKey: false,
};
}
const trace = getTrace(excludeFromTrace).slice(1);
const resourcePath = getResourcePath(hookName as RefineHook, legacyKey);
return {
hookName,
trace,
resourcePath,
legacyKey,
resourceName,
};
}

View File

@@ -0,0 +1,3 @@
export { getTrace } from "./get-trace.js";
export { getXRay } from "./get-xray.js";
export { useQuerySubscription } from "./use-query-subscription.js";

View File

@@ -0,0 +1,9 @@
import { REFINE_PACKAGE_FILEPATH_REGEXP } from "./constants";
export const isRefineStack = (filename?: string) => {
if (!filename) return false;
const match = filename.match(REFINE_PACKAGE_FILEPATH_REGEXP);
return !!match;
};

View File

@@ -0,0 +1,46 @@
import { DevtoolsEvent, send } from "@refinedev/devtools-shared";
import type { Mutation, Query } from "@tanstack/react-query";
import { createIdentifier } from "./create-identifier";
import type { XRayResponse } from "./get-xray";
export const createMutationListener =
(ws: WebSocket) => (mutation?: Mutation) => {
if (!mutation?.meta?.trace) return;
const meta: XRayResponse = mutation?.meta as any;
new Promise<void>((resolve) => {
send(ws, DevtoolsEvent.ACTIVITY, {
type: "mutation",
identifier: createIdentifier(
mutation?.options.mutationKey,
mutation?.meta?.trace as any,
),
key: mutation?.options.mutationKey as any,
status: mutation?.state.status,
state: mutation?.state,
variables: mutation?.state?.variables,
...meta,
});
resolve();
});
};
export const createQueryListener = (ws: WebSocket) => (query: Query) => {
if (!query?.meta?.trace) return;
const meta: XRayResponse = query?.meta as any;
new Promise<void>((resolve) => {
send(ws, DevtoolsEvent.ACTIVITY, {
type: "query",
identifier: createIdentifier(query.queryKey, query.meta?.trace as any),
key: query.queryKey as any,
status: query.state.status,
state: query.state,
...meta,
});
resolve();
});
};

View File

@@ -0,0 +1,77 @@
import {
DevToolsContext,
DevtoolsEvent,
receive,
} from "@refinedev/devtools-shared";
import type { QueryClient } from "@tanstack/react-query";
import React, { useContext } from "react";
import { createQueryListener, createMutationListener } from "./listeners";
const empty = {};
const noop = () => empty;
export const useQuerySubscription =
__DEV_CONDITION__ !== "development"
? noop
: (queryClient: QueryClient) => {
const { ws } = useContext(DevToolsContext);
const queryCacheSubscription = React.useRef<() => void>();
const mutationCacheSubscription = React.useRef<() => void>();
React.useEffect(() => {
if (!ws) return () => 0;
const queryCache = queryClient.getQueryCache();
const queryListener = createQueryListener(ws);
queryCache.getAll().forEach(queryListener);
queryCacheSubscription.current = queryCache.subscribe(
({ query, type }) =>
(type === "added" || type === "updated") && queryListener(query),
);
return () => {
queryCacheSubscription.current?.();
};
}, [ws, queryClient]);
React.useEffect(() => {
if (!ws) return () => 0;
const mutationCache = queryClient.getMutationCache();
const mutationListener = createMutationListener(ws);
mutationCache.getAll().forEach(mutationListener);
mutationCacheSubscription.current = mutationCache.subscribe(
({ mutation, type }) =>
(type === "added" || type === "updated") &&
mutationListener(mutation),
);
return () => {
mutationCacheSubscription.current?.();
};
}, [ws, queryClient]);
React.useEffect(() => {
if (!ws) return () => 0;
const cb = receive(
ws,
DevtoolsEvent.DEVTOOLS_INVALIDATE_QUERY_ACTION,
({ queryKey }) => {
if (queryKey) {
queryClient.invalidateQueries(queryKey);
}
},
);
return cb;
}, [ws, queryClient]);
return {};
};