Files
clean-architecture-frontend…/src/adapters/query-cache/tanstack-query-cache.js
T

115 lines
3.3 KiB
JavaScript

import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query";
import { createFailure } from "../../contracts/errors.js";
import { safeErrorKind } from "../../contracts/diagnostics.js";
export const QUERY_CACHE_DEFAULTS = Object.freeze({
staleTime: 30_000,
gcTime: 300_000,
refetchOnWindowFocus: true,
retry: false,
mutationRetry: false,
persistence: false,
});
/**
* @param {{diagnostics?: import("../../application/ports/diagnostics-port.js").DiagnosticsPort}} [dependencies]
*/
export function createQueryClient(dependencies = {}) {
/** @param {string} operation @param {unknown} error */
function report(operation, error) {
try {
dependencies.diagnostics?.record({
level: "warn",
eventId: "cache.operation.failed",
context: {
operation,
error_kind: safeErrorKind(error),
},
});
} catch {
// Query behavior remains independent from diagnostics.
}
}
return new QueryClient({
queryCache: new QueryCache({
onError: (error) => report("query", error),
}),
mutationCache: new MutationCache({
onError: (error) => report("mutation", error),
}),
defaultOptions: {
queries: {
staleTime: QUERY_CACHE_DEFAULTS.staleTime,
gcTime: QUERY_CACHE_DEFAULTS.gcTime,
refetchOnWindowFocus: QUERY_CACHE_DEFAULTS.refetchOnWindowFocus,
retry: QUERY_CACHE_DEFAULTS.retry,
},
mutations: {
retry: QUERY_CACHE_DEFAULTS.mutationRetry,
},
},
});
}
/**
* @param {QueryClient} queryClient
* @param {{diagnostics?: import("../../application/ports/diagnostics-port.js").DiagnosticsPort}} [dependencies]
* @returns {import("../../application/ports/query-cache-port.js").QueryCachePort}
*/
export function createQueryCacheAdapter(queryClient, dependencies = {}) {
return Object.freeze({
read(key) {
try {
return { ok: true, value: queryClient.getQueryData(key) };
} catch {
return cacheFailure("read", key, dependencies.diagnostics);
}
},
write(key, value) {
try {
queryClient.setQueryData(key, structuredClone(value));
return { ok: true };
} catch {
return cacheFailure("write", key, dependencies.diagnostics);
}
},
async invalidate(namespace) {
try {
await queryClient.invalidateQueries({ queryKey: namespace, exact: false });
return { ok: true };
} catch {
return cacheFailure("invalidate", namespace, dependencies.diagnostics);
}
},
});
}
/**
* @param {string} phase
* @param {readonly unknown[]} key
* @param {import("../../application/ports/diagnostics-port.js").DiagnosticsPort | undefined} diagnostics
*/
function cacheFailure(phase, key, diagnostics) {
const namespace = typeof key[0] === "string" ? key[0] : "unknown";
try {
diagnostics?.record({
level: "warn",
eventId: "cache.operation.failed",
context: {
operation: phase,
error_kind: "QUERY_CACHE_FAILURE",
},
});
} catch {
// Cache behavior remains independent from diagnostics.
}
return {
ok: /** @type {false} */ (false),
error: createFailure("QUERY_CACHE_FAILURE", "QUERY_CACHE", 0, {
code: `QUERY_CACHE_${phase.toUpperCase()}_FAILED`,
causeClass: `namespace:${namespace}`,
}),
};
}