refactor: share path-root query predicate (WealthLedger/frontend#142) #146
@@ -5,6 +5,7 @@ import {
|
|||||||
ApiErrorCode,
|
ApiErrorCode,
|
||||||
apiErrorCode,
|
apiErrorCode,
|
||||||
isApiErrorCode,
|
isApiErrorCode,
|
||||||
|
isGetUnder,
|
||||||
} from "@/shared/api";
|
} from "@/shared/api";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -26,21 +27,14 @@ async function mutationData<T>(
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bankCardGetPredicate = isGetUnder(["/api/v1/bank-cards"]);
|
||||||
|
|
||||||
function useBankMutationOptions() {
|
function useBankMutationOptions() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const invalidate = () =>
|
const invalidate = () =>
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
predicate: ({ queryKey }) => {
|
predicate: ({ queryKey }) => bankCardGetPredicate(queryKey),
|
||||||
const [method, path] = queryKey;
|
|
||||||
|
|
||||||
return (
|
|
||||||
method === "get"
|
|
||||||
&& typeof path === "string"
|
|
||||||
&& (path === "/api/v1/bank-cards"
|
|
||||||
|| path.startsWith("/api/v1/bank-cards/"))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,19 +2,18 @@ import type { QueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
apiQueryClient,
|
apiQueryClient,
|
||||||
|
isGetUnder,
|
||||||
type paths,
|
type paths,
|
||||||
} from "@/shared/api";
|
} from "@/shared/api";
|
||||||
|
|
||||||
type TbankConnectionStatus
|
type TbankConnectionStatus
|
||||||
= paths["/api/v1/tbank-connection"]["get"]["responses"][200]["content"]["application/json"];
|
= paths["/api/v1/tbank-connection"]["get"]["responses"][200]["content"]["application/json"];
|
||||||
|
|
||||||
function isInvestmentPath(path: string) {
|
const investmentGetPredicate = isGetUnder([
|
||||||
return [
|
"/api/v1/brokerage-accounts",
|
||||||
"/api/v1/brokerage-accounts",
|
"/api/v1/stocks",
|
||||||
"/api/v1/stocks",
|
"/api/v1/bonds",
|
||||||
"/api/v1/bonds",
|
]);
|
||||||
].some(root => path === root || path.startsWith(`${root}/`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const tbankConnectionQueryOptions = () =>
|
const tbankConnectionQueryOptions = () =>
|
||||||
apiQueryClient.queryOptions("get", "/api/v1/tbank-connection");
|
apiQueryClient.queryOptions("get", "/api/v1/tbank-connection");
|
||||||
@@ -27,15 +26,7 @@ function commitTbankConnectionBoundary(
|
|||||||
) {
|
) {
|
||||||
queryClient.setQueryData(tbankConnectionQueryKey, status);
|
queryClient.setQueryData(tbankConnectionQueryKey, status);
|
||||||
void queryClient.resetQueries({
|
void queryClient.resetQueries({
|
||||||
predicate: ({ queryKey }) => {
|
predicate: ({ queryKey }) => investmentGetPredicate(queryKey),
|
||||||
const [method, path] = queryKey;
|
|
||||||
|
|
||||||
return (
|
|
||||||
method === "get"
|
|
||||||
&& typeof path === "string"
|
|
||||||
&& isInvestmentPath(path)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export {
|
|||||||
isApiErrorCode,
|
isApiErrorCode,
|
||||||
isBrokerageAccessLoss,
|
isBrokerageAccessLoss,
|
||||||
} from "./problem-details";
|
} from "./problem-details";
|
||||||
|
export { isGetUnder } from "./query-predicate";
|
||||||
export { isReadFailureExhausted, readRetry } from "./read-failure";
|
export { isReadFailureExhausted, readRetry } from "./read-failure";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { isGetUnder } from "./query-predicate";
|
||||||
|
|
||||||
|
describe("isGetUnder", () => {
|
||||||
|
it("matches the root itself and nested paths for get queries only", () => {
|
||||||
|
const predicate = isGetUnder(["/api/v1/bank-cards"]);
|
||||||
|
|
||||||
|
expect(predicate(["get", "/api/v1/bank-cards"])).toBe(true);
|
||||||
|
expect(predicate(["get", "/api/v1/bank-cards/{id}"])).toBe(true);
|
||||||
|
expect(predicate(["get", "/api/v1/bank-cards"])).toBe(true);
|
||||||
|
expect(predicate(["get", "/api/v1/bank-cardsx"])).toBe(false);
|
||||||
|
expect(predicate(["post", "/api/v1/bank-cards"])).toBe(false);
|
||||||
|
expect(predicate(["get", "/api/v1/other"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks every root and tolerates init and non-string parts", () => {
|
||||||
|
const predicate = isGetUnder(["/api/v1/stocks", "/api/v1/bonds"]);
|
||||||
|
|
||||||
|
expect(predicate(["get", "/api/v1/stocks/{uid}/quote", { params: {} }]))
|
||||||
|
.toBe(true);
|
||||||
|
expect(predicate(["get", "/api/v1/bonds"])).toBe(true);
|
||||||
|
expect(predicate([undefined, "/api/v1/stocks"])).toBe(false);
|
||||||
|
expect(predicate(["get", undefined])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
// openapi-react-query shapes query keys as [method, path, init].
|
||||||
|
function isGetUnder(roots: readonly string[]) {
|
||||||
|
const predicate = (queryKey: QueryKey) => {
|
||||||
|
const [method, path] = queryKey as readonly [unknown, unknown];
|
||||||
|
|
||||||
|
return (
|
||||||
|
method === "get"
|
||||||
|
&& typeof path === "string"
|
||||||
|
&& roots.some(root => path === root || path.startsWith(`${root}/`))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isGetUnder };
|
||||||
Reference in New Issue
Block a user