feat(web): add search query helpers (summary + 3 facet infinite queries)
createSearchQuery hits /api/search with limit=10 (the main /search page summary). createSearchArtistsInfiniteQuery / createSearchAlbumsInfiniteQuery / createSearchTracksInfiniteQuery each hit /api/search with limit=50 and project to their own facet, used by the per-facet overflow pages. All four are gated by q.length > 0 so empty queries don't fire.
This commit is contained in:
@@ -15,3 +15,18 @@ describe('qk key generators', () => {
|
|||||||
expect(qk.album('xyz')).toEqual(['album', 'xyz']);
|
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' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query';
|
import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query';
|
||||||
import { api } from './client';
|
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 type ArtistSort = 'alpha' | 'newest';
|
||||||
export const ARTIST_PAGE_SIZE = 50;
|
export const ARTIST_PAGE_SIZE = 50;
|
||||||
|
export const SEARCH_SUMMARY_LIMIT = 10;
|
||||||
|
export const SEARCH_FACET_PAGE_SIZE = 50;
|
||||||
|
|
||||||
export const qk = {
|
export const qk = {
|
||||||
artists: (sort: ArtistSort) => ['artists', { sort }] as const,
|
artists: (sort: ArtistSort) => ['artists', { sort }] as const,
|
||||||
artist: (id: string) => ['artist', id] as const,
|
artist: (id: string) => ['artist', id] as const,
|
||||||
album: (id: string) => ['album', 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) {
|
export function createArtistsQuery(sort: ArtistSort) {
|
||||||
@@ -39,3 +45,62 @@ export function createAlbumQuery(id: string) {
|
|||||||
queryFn: () => api.get<AlbumDetail>(`/api/albums/${id}`),
|
queryFn: () => api.get<AlbumDetail>(`/api/albums/${id}`),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createSearchQuery(q: string) {
|
||||||
|
return createQuery({
|
||||||
|
queryKey: qk.search(q),
|
||||||
|
queryFn: () =>
|
||||||
|
api.get<SearchResponse>(
|
||||||
|
`/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<SearchResponse>(
|
||||||
|
`/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<SearchResponse>(
|
||||||
|
`/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<SearchResponse>(
|
||||||
|
`/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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user