diff --git a/web/src/lib/api/queries.test.ts b/web/src/lib/api/queries.test.ts index d100ef5b..100cec33 100644 --- a/web/src/lib/api/queries.test.ts +++ b/web/src/lib/api/queries.test.ts @@ -15,3 +15,18 @@ describe('qk key generators', () => { expect(qk.album('xyz')).toEqual(['album', 'xyz']); }); }); + +describe('qk search keys', () => { + test('search summary key includes q', () => { + expect(qk.search('foo')).toEqual(['search', { q: 'foo' }]); + }); + test('searchArtists key includes q', () => { + expect(qk.searchArtists('foo')).toEqual(['searchArtists', { q: 'foo' }]); + }); + test('searchAlbums key includes q', () => { + expect(qk.searchAlbums('foo')).toEqual(['searchAlbums', { q: 'foo' }]); + }); + test('searchTracks key includes q', () => { + expect(qk.searchTracks('foo')).toEqual(['searchTracks', { q: 'foo' }]); + }); +}); diff --git a/web/src/lib/api/queries.ts b/web/src/lib/api/queries.ts index 6d6148d0..8fb531a8 100644 --- a/web/src/lib/api/queries.ts +++ b/web/src/lib/api/queries.ts @@ -1,14 +1,20 @@ import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'; import { api } from './client'; -import type { ArtistRef, ArtistDetail, AlbumDetail, Page } from './types'; +import type { ArtistRef, ArtistDetail, AlbumDetail, Page, SearchResponse } from './types'; export type ArtistSort = 'alpha' | 'newest'; export const ARTIST_PAGE_SIZE = 50; +export const SEARCH_SUMMARY_LIMIT = 10; +export const SEARCH_FACET_PAGE_SIZE = 50; export const qk = { artists: (sort: ArtistSort) => ['artists', { sort }] as const, artist: (id: string) => ['artist', id] as const, album: (id: string) => ['album', id] as const, + search: (q: string) => ['search', { q }] as const, + searchArtists: (q: string) => ['searchArtists', { q }] as const, + searchAlbums: (q: string) => ['searchAlbums', { q }] as const, + searchTracks: (q: string) => ['searchTracks', { q }] as const, }; export function createArtistsQuery(sort: ArtistSort) { @@ -39,3 +45,62 @@ export function createAlbumQuery(id: string) { queryFn: () => api.get(`/api/albums/${id}`), }); } + +export function createSearchQuery(q: string) { + return createQuery({ + queryKey: qk.search(q), + queryFn: () => + api.get( + `/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_SUMMARY_LIMIT}` + ), + enabled: q.length > 0 + }); +} + +export function createSearchArtistsInfiniteQuery(q: string) { + return createInfiniteQuery({ + queryKey: qk.searchArtists(q), + queryFn: ({ pageParam = 0 }) => + api.get( + `/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}` + ).then((r) => r.artists), + initialPageParam: 0, + getNextPageParam: (last) => { + const loaded = last.offset + last.items.length; + return loaded >= last.total ? undefined : loaded; + }, + enabled: q.length > 0 + }); +} + +export function createSearchAlbumsInfiniteQuery(q: string) { + return createInfiniteQuery({ + queryKey: qk.searchAlbums(q), + queryFn: ({ pageParam = 0 }) => + api.get( + `/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}` + ).then((r) => r.albums), + initialPageParam: 0, + getNextPageParam: (last) => { + const loaded = last.offset + last.items.length; + return loaded >= last.total ? undefined : loaded; + }, + enabled: q.length > 0 + }); +} + +export function createSearchTracksInfiniteQuery(q: string) { + return createInfiniteQuery({ + queryKey: qk.searchTracks(q), + queryFn: ({ pageParam = 0 }) => + api.get( + `/api/search?q=${encodeURIComponent(q)}&limit=${SEARCH_FACET_PAGE_SIZE}&offset=${pageParam}` + ).then((r) => r.tracks), + initialPageParam: 0, + getNextPageParam: (last) => { + const loaded = last.offset + last.items.length; + return loaded >= last.total ? undefined : loaded; + }, + enabled: q.length > 0 + }); +}