SPB Git

Repository Public

queries.ts 5.4 KB 98 lines typescript

1'use client';2import '@/lib/zod-config';3import { useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query';4import { AosSettings, type MissionDto, type ModelInfo, type PolicyRuleDto, type ProjectDto, type RunDto, type SpendSummary, type WaitDto } from '@aos/shared';5import type { z } from 'zod';6import { SystemStatus, type ArtifactDto, type AuditEvent, type Device, type Notification } from './dto-auth';7import { api, asList, fetchAuthStatus, qs } from './api';89export type PolicyRule = z.infer<typeof PolicyRuleDto>;1011export const qk = {12  auth: ['auth'] as const,13  projects: ['projects'] as const,14  models: ['models'] as const,15  missions: ['missions'] as const,16  mission: (id: string) => ['mission', id] as const,17  runs: (missionId: string) => ['runs', missionId] as const,18  artifacts: (runId: string) => ['artifacts', runId] as const,19  waits: ['waits'] as const,20  policy: ['policy'] as const,21  spend: ['spend'] as const,22  system: ['system'] as const,23  notifications: ['notifications'] as const,24  settings: ['settings'] as const,25  devices: ['devices'] as const,26};2728export const useAuthStatus = () => useQuery({ queryKey: qk.auth, queryFn: fetchAuthStatus, staleTime: 60_000 });2930export const useProjects = () => useQuery({ queryKey: qk.projects, queryFn: async () => asList<ProjectDto>(await api.get('/projects')) });31export const useModels = () => useQuery({ queryKey: qk.models, queryFn: async () => asList<ModelInfo>(await api.get('/models')), staleTime: 10 * 60_000 });3233/** The whole list (newest first); filters are applied on the client so that they stay live. */34export const useMissions = () =>35  useQuery({36    queryKey: qk.missions,37    queryFn: async () => asList<MissionDto>(await api.get('/missions')).sort((a, b) => b.createdAt.localeCompare(a.createdAt)),38  });39export const useMission = (id: string | null) =>40  useQuery({ queryKey: qk.mission(id ?? ''), queryFn: () => api.get<MissionDto>(`/missions/${id}`), enabled: !!id });41export const useRuns = (missionId: string | null) =>42  useQuery({ queryKey: qk.runs(missionId ?? ''), queryFn: async () => asList<RunDto>(await api.get(`/missions/${missionId}/runs`)), enabled: !!missionId });43export const useArtifacts = (runId: string | null) =>44  useQuery({ queryKey: qk.artifacts(runId ?? ''), queryFn: async () => asList<ArtifactDto>(await api.get(`/runs/${runId}/artifacts`)), enabled: !!runId, retry: false });4546/** Pending approvals and questions, oldest first (the one waiting longest on top). */47export const useWaits = () =>48  useQuery({49    queryKey: qk.waits,50    queryFn: async () =>51      asList<WaitDto>(await api.get(`/waits${qs({ status: 'pending' })}`))52        .filter((w) => w.status === 'pending' && (w.kind === 'approval' || w.kind === 'question'))53        .sort((a, b) => a.createdAt.localeCompare(b.createdAt)),54    refetchInterval: 60_000, // safety net behind the events channel55  });56export const usePendingCount = (): number => useWaits().data?.length ?? 0;5758export const usePolicyRules = () => useQuery({ queryKey: qk.policy, queryFn: async () => asList<PolicyRule>(await api.get('/policy/rules')) });59export const useSpend = () => useQuery({ queryKey: qk.spend, queryFn: () => api.get<SpendSummary>('/spend'), refetchInterval: 60_000 });60/** System status has no push channel: 15 s polling, paused while the tab is hidden. */61export const useSystem = () =>62  useQuery({ queryKey: qk.system, queryFn: async () => SystemStatus.parse(await api.get('/system/status')), refetchInterval: 15_000, refetchIntervalInBackground: false });63export const useAudit = (limit = 50) =>64  useQuery({ queryKey: ['audit', limit], queryFn: async () => asList<AuditEvent>(await api.get(`/system/audit${qs({ limit })}`)), retry: false });65export const useNotifications = () => useQuery({ queryKey: qk.notifications, queryFn: async () => asList<Notification>(await api.get('/notifications')), retry: false });66export const useDevices = () => useQuery({ queryKey: qk.devices, queryFn: async () => asList<Device>(await api.get('/auth/devices')) });6768export const DEFAULT_SETTINGS: AosSettings = AosSettings.parse({});69export const useSettingsQuery = () =>70  useQuery({ queryKey: qk.settings, queryFn: async () => AosSettings.parse(await api.get('/settings')), staleTime: 5 * 60_000 });71/** Settings with defaults while loading (never blocks rendering). */72export function useSettings(): AosSettings {73  return useSettingsQuery().data ?? DEFAULT_SETTINGS;74}75export async function saveSettings(qc: QueryClient, patch: Partial<AosSettings>): Promise<AosSettings> {76  const current = qc.getQueryData<AosSettings>(qk.settings) ?? DEFAULT_SETTINGS;77  const next = { ...current, ...patch };78  qc.setQueryData(qk.settings, next);79  try {80    const saved = AosSettings.parse(await api.put('/settings', next));81    qc.setQueryData(qk.settings, saved);82    void qc.invalidateQueries({ queryKey: qk.spend }); // the daily cap is part of the spend summary83    return saved;84  } catch (e) {85    qc.setQueryData(qk.settings, current);86    throw e;87  }88}89export function useSaveSettings(): (patch: Partial<AosSettings>) => Promise<AosSettings> {90  const qc = useQueryClient();91  return (patch) => saveSettings(qc, patch);92}9394export function upsertMission(qc: QueryClient, m: MissionDto): void {95  qc.setQueryData(qk.mission(m.id), m);96  qc.setQueryData<MissionDto[]>(qk.missions, (list) => (list ? [m, ...list.filter((x) => x.id !== m.id)].sort((a, b) => b.createdAt.localeCompare(a.createdAt)) : list));97}98
Files main
Menu

Theme