import { createQuery } from '@tanstack/svelte-query'; import { api, apiFetch } from './client'; import { qk } from './queries'; import type { LidarrRequest, LidarrRequestKind } from './types'; /** Cadence for in-flight request polling. Tuned to match the operator's "fast enough to feel live, slow enough to not hammer the server" threshold from #369. */ const POLL_INTERVAL_MS = 12_000; /** True if any row is mid-ingest (status === 'approved'). The status flips to 'completed' / 'rejected' when Lidarr finishes, so this predicate lets the page poll exactly while there's something interesting to watch. */ export function hasInFlightRequest(rows: readonly LidarrRequest[] | undefined): boolean { return rows?.some((r) => r.status === 'approved') ?? false; } // CreateRequestParams is the shape callers pass; unused MBID fields are // optional, and we omit them from the wire body rather than sending empty // strings (the backend tolerates either, but this keeps test expectations // crisp and matches the spec §5 wire shape). export type CreateRequestParams = { kind: LidarrRequestKind; lidarr_artist_mbid: string; lidarr_album_mbid?: string; lidarr_track_mbid?: string; artist_name: string; album_title?: string; track_title?: string; }; type CreateRequestBody = { kind: LidarrRequestKind; lidarr_artist_mbid: string; artist_name: string; lidarr_album_mbid?: string; lidarr_track_mbid?: string; album_title?: string; track_title?: string; }; function buildCreateBody(params: CreateRequestParams): CreateRequestBody { const body: CreateRequestBody = { kind: params.kind, lidarr_artist_mbid: params.lidarr_artist_mbid, artist_name: params.artist_name }; if (params.lidarr_album_mbid) body.lidarr_album_mbid = params.lidarr_album_mbid; if (params.lidarr_track_mbid) body.lidarr_track_mbid = params.lidarr_track_mbid; if (params.album_title) body.album_title = params.album_title; if (params.track_title) body.track_title = params.track_title; return body; } export async function createRequest(params: CreateRequestParams): Promise { return api.post('/api/requests', buildCreateBody(params)); } export async function listMyRequests(): Promise { return api.get('/api/requests'); } export async function getRequest(id: string): Promise { return api.get(`/api/requests/${id}`); } // Server returns the cancelled row body (not 204) so callers can patch the // cache without a refetch. api.del's return type is fixed to null, so we // drop down to apiFetch here to keep typing honest. export async function cancelRequest(id: string): Promise { return apiFetch(`/api/requests/${id}`, { method: 'DELETE' }) as Promise; } export function createMyRequestsQuery() { return createQuery({ queryKey: qk.myRequests(), queryFn: listMyRequests, staleTime: 60_000, // Auto-poll while at least one user request is mid-ingest. Stops // automatically when all rows are pending/completed/rejected. // refetchIntervalInBackground defaults to false → polling pauses // while the tab is hidden and resumes on focus. refetchInterval: (query) => hasInFlightRequest(query.state.data as LidarrRequest[] | undefined) ? POLL_INTERVAL_MS : false }); }