~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
98 lines · 3.2 KB Raw History
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 });
  };
}