chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
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<string, unknown>} */ 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<string, string>} */ params) => [
'git-tree',
params,
],
gitFile: (/** @type {Record<string, string>} */ params) => [
'git-file',
params,
],
gitLog: (/** @type {Record<string, string>} */ params) => ['git-log', params],
gitChecks: (/** @type {Record<string, string>} */ 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<string, unknown>} */ 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<unknown>} */ 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<unknown>} */ 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 });
};
}