115 lines
3.3 KiB
JavaScript
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}`,
|
|
}),
|
|
};
|
|
}
|