import { QueryClient, useMutation, useQueryClient, } from '@tanstack/react-query'; // Fresh for 30s, so returning to a screen you just left (now that navigation is // link-based) shows cached data instantly instead of refetching. Writes keep it // honest by invalidating the queries they touch, and coming back to the tab // refetches anything past the 30s window, so records written elsewhere show up. export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 30_000, retry: 1 }, }, }); /** One place for the query keys, so a write invalidates exactly what a read used. */ export const queryKeys = { overview: ['overview'], identity: ['identity'], apps: ['apps'], blobs: (/** @type {Record} */ filters) => ['blobs', filters], spaces: ['spaces'], spaceMembers: (/** @type {string} */ space) => ['space-members', space], sites: ['sites'], siteFiles: (/** @type {string} */ name) => ['site-files', name], gitRepos: ['git-repos'], ociRepos: ['oci-repos'], npmPackages: ['npm-packages'], driveFiles: (/** @type {{folder: string, space: string}} */ params) => [ 'drive-files', params, ], driveTrash: (/** @type {string} */ space) => ['drive-trash', space], gitTree: (/** @type {Record} */ params) => [ 'git-tree', params, ], gitFile: (/** @type {Record} */ params) => [ 'git-file', params, ], gitLog: (/** @type {Record} */ params) => ['git-log', params], gitChecks: (/** @type {Record} */ params) => [ 'git-checks', params, ], installs: ['installs'], installUpdates: ['install-updates'], activity: ['activity'], backups: ['backups'], passkeys: ['passkeys'], sessions: ['sessions'], email: ['email'], appPasswords: ['app-passwords'], records: (/** @type {Record} */ params) => [ 'records', params, ], version: ['version'], lexiconStatus: (/** @type {string[]} */ collections) => [ 'lexicon-status', collections, ], lexiconSchema: (/** @type {string} */ nsid) => ['lexicon-schema', nsid], }; /** * An imperative write (button action) that refreshes the given queries when it * succeeds. Keeps the `run` / `busy` / `error` shape the buttons already use, * backed by `useMutation`; hand it the keys whose data the write changes. * @param {unknown[][]} [invalidate] */ export function useApiMutation(invalidate = []) { const qc = useQueryClient(); const mutation = useMutation({ mutationFn: (/** @type {() => Promise} */ fn) => fn(), onSuccess: () => { for (const key of invalidate) qc.invalidateQueries({ queryKey: key }); }, }); return { // The rejection is captured in `error`; swallow it so callers need no catch. run: (/** @type {() => Promise} */ fn) => mutation.mutateAsync(fn).catch(() => {}), busy: mutation.isPending, error: mutation.error ? mutation.error.message : '', clear: mutation.reset, }; } /** Invalidate query keys after a form submits — the form-shell counterpart. */ export function useInvalidate() { const qc = useQueryClient(); return (/** @type {unknown[][]} */ keys) => { for (const key of keys) qc.invalidateQueries({ queryKey: key }); }; }