-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(supersearch): Add search request handling (#1178)
* Add useSearchRequest for request handling * Add debouncing * Add optional transform function prop * Add support for paginated data * Cancel pending fetches when fetching new data * Add snippet prop for result items * Add test api endpoint * Add tests * Add basic supersearch request handling in lxl-web * Update readme
- Loading branch information
1 parent
77c83e1
commit 27272fb
Showing
11 changed files
with
395 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
lxl-web/src/routes/api/[[lang=lang]]/supersearch/+server.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { env } from '$env/dynamic/private'; | ||
import { json } from '@sveltejs/kit'; | ||
import type { RequestHandler } from './$types.ts'; | ||
import { LxlLens } from '$lib/types/display'; | ||
import { getSupportedLocale } from '$lib/i18n/locales.js'; | ||
import { toString } from '$lib/utils/xl.js'; | ||
|
||
export const GET: RequestHandler = async ({ url, params, locals }) => { | ||
const displayUtil = locals.display; | ||
const locale = getSupportedLocale(params?.lang); | ||
|
||
const findResponse = await fetch(`${env.API_URL}/find?${url.searchParams.toString()}`); | ||
const data = await findResponse.json(); | ||
|
||
return json({ | ||
'@id': data['@id'], | ||
items: data.items?.map((item) => ({ | ||
'@id': item['@id'], | ||
'@type': item['@type'], | ||
heading: toString(displayUtil.lensAndFormat(item, LxlLens.CardHeading, locale)) | ||
})), | ||
totalItems: data.totalItems, | ||
'@context': data['@context'] | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
type JSONPrimitive = string | number | boolean | null | undefined; | ||
|
||
export type JSONValue = | ||
| JSONPrimitive | ||
| JSONValue[] | ||
| { | ||
[key: string]: JSONValue; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { JSONValue } from './json.js'; | ||
|
||
export type QueryFunction = (value: string) => URLSearchParams; | ||
export type PaginationQueryFunction = ( | ||
searchParams: URLSearchParams, | ||
data: JSONValue | ||
) => URLSearchParams | undefined; | ||
export type TransformFunction = (data: JSONValue) => JSONValue; | ||
|
||
export interface ResultItem { | ||
'@id'?: string; | ||
heading: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type | ||
function debounce(callback: Function, wait = 300) { | ||
let timeout: ReturnType<typeof setTimeout>; | ||
return (...args: unknown[]) => { | ||
clearTimeout(timeout); | ||
timeout = setTimeout(() => callback(...args), wait); | ||
}; | ||
} | ||
|
||
export default debounce; |
97 changes: 97 additions & 0 deletions
97
packages/supersearch/src/lib/utils/useSearchRequest.svelte.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import type { | ||
QueryFunction, | ||
PaginationQueryFunction, | ||
TransformFunction | ||
} from '$lib/types/superSearch.js'; | ||
import type { JSONValue } from '$lib/types/json.js'; | ||
import debounce from '$lib/utils/debounce.js'; | ||
|
||
export function useSearchRequest({ | ||
endpoint, | ||
queryFn, | ||
paginationQueryFn, | ||
transformFn, | ||
debouncedWait | ||
}: { | ||
endpoint: string | URL; | ||
queryFn: QueryFunction; | ||
paginationQueryFn?: PaginationQueryFunction; | ||
transformFn?: TransformFunction; | ||
debouncedWait?: number; | ||
}) { | ||
let isLoading = $state(false); | ||
let error: string | undefined = $state(); | ||
let data = $state(); | ||
let paginatedData = $state(); | ||
let moreSearchParams: URLSearchParams | undefined = $state(); | ||
const hasMorePaginatedData = $derived(!!moreSearchParams); | ||
|
||
let controller: AbortController; | ||
|
||
async function _fetchData(searchParams: URLSearchParams) { | ||
try { | ||
isLoading = true; | ||
error = undefined; | ||
|
||
controller?.abort(); | ||
controller = new AbortController(); | ||
|
||
const response = await fetch(`${endpoint}?${searchParams.toString()}`, { | ||
signal: controller.signal | ||
}); | ||
const jsonResponse = (await response.json()) as JSONValue; | ||
|
||
const _data = transformFn?.(jsonResponse) || jsonResponse; | ||
moreSearchParams = paginationQueryFn?.(searchParams, _data); | ||
|
||
return _data; | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
error = 'Failed to fetch data: ' + err.message; | ||
} else { | ||
error = 'Failed to fetch data'; | ||
} | ||
} finally { | ||
isLoading = false; | ||
} | ||
} | ||
|
||
async function fetchData(query: string) { | ||
data = await _fetchData(queryFn(query)); | ||
if (paginationQueryFn) { | ||
paginatedData = [data]; | ||
} | ||
} | ||
|
||
const debouncedFetchData = debounce((query: string) => fetchData(query), debouncedWait); | ||
|
||
async function fetchMoreData() { | ||
if (moreSearchParams) { | ||
const moreData = await _fetchData(moreSearchParams); | ||
paginatedData = [...((Array.isArray(paginatedData) && paginatedData) || []), moreData]; | ||
} | ||
} | ||
|
||
return { | ||
fetchData, | ||
debouncedFetchData, | ||
fetchMoreData, | ||
get isLoading() { | ||
return isLoading; | ||
}, | ||
get error() { | ||
return error; | ||
}, | ||
get data() { | ||
return data; | ||
}, | ||
get paginatedData() { | ||
return paginatedData; | ||
}, | ||
get hasMorePaginatedData() { | ||
return hasMorePaginatedData; | ||
} | ||
}; | ||
} | ||
|
||
export default useSearchRequest; |
Oops, something went wrong.