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}`, }), }; }