Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
{
"path": "packages/autocomplete-js/dist/umd/index.production.js",
"maxSize": "16.5 kB"
"maxSize": "16.75 kB"
},
{
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
Expand Down
9 changes: 6 additions & 3 deletions packages/autocomplete-core/src/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ function isRequesterDescription<TItem extends BaseItem>(
type PackedDescription<TItem extends BaseItem> = {
searchClient: SearchClient;
execute: Execute<TItem>;
requesterId?: string;
items: RequestDescriptionPreResolved<TItem>['requests'];
};

type RequestDescriptionPreResolved<TItem extends BaseItem> = Pick<
RequesterDescription<TItem>,
'execute' | 'searchClient' | 'transformResponse'
'execute' | 'requesterId' | 'searchClient' | 'transformResponse'
> & {
requests: Array<{
query: MultipleQueriesQuery;
Expand Down Expand Up @@ -90,15 +91,16 @@ export function resolve<TItem extends BaseItem>(
return acc;
}

const { searchClient, execute, requests } = current;
const { searchClient, execute, requesterId, requests } = current;

const container = acc.find<PackedDescription<TItem>>(
(item): item is PackedDescription<TItem> => {
return (
isDescription(current) &&
isDescription(item) &&
item.searchClient === searchClient &&
item.execute === execute
Boolean(requesterId) &&
item.requesterId === requesterId
);
}
);
Expand All @@ -108,6 +110,7 @@ export function resolve<TItem extends BaseItem>(
} else {
const request: PackedDescription<TItem> = {
execute,
requesterId,
items: requests,
searchClient,
};
Expand Down
121 changes: 121 additions & 0 deletions packages/autocomplete-js/src/__tests__/requester.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
createRequester,
fetchAlgoliaResults,
} from '@algolia/autocomplete-preset-algolia';
import { fireEvent, waitFor, within } from '@testing-library/dom';

import {
Expand Down Expand Up @@ -346,6 +350,123 @@ describe('requester', () => {
});
});

test('batches calls across requesters with same id', async () => {
const container = document.createElement('div');
const panelContainer = document.createElement('div');

document.body.appendChild(panelContainer);

const searchClient = createSearchClient({
search: jest.fn((requests) =>
Promise.resolve(
createMultiSearchResponse<{ label: string }>(
...requests.map(({ indexName, params: { query } }) => ({
hits: [{ objectID: '1', label: 'Hit 1' }],
index: indexName,
query,
}))
)
)
),
});

const getResults1 = (params) =>
createRequester(
fetchAlgoliaResults,
'custom-requester-id'
)({
transformResponse: (response) => response.hits,
})(params);

const getResults2 = (params) =>
createRequester(
fetchAlgoliaResults,
'custom-requester-id'
)({
transformResponse: (response) => response.hits,
})(params);

const getResults3 = (params) =>
createRequester(
fetchAlgoliaResults,
'different-requester-id'
)({
transformResponse: (response) => response.hits,
})(params);

const templates = {
item({ item }) {
return JSON.stringify(item);
},
};

autocomplete({
container,
panelContainer,
getSources({ query }) {
return [
{
sourceId: 'hits',
getItems() {
return getResults1({
searchClient,
queries: [
{
indexName: 'indexName',
query,
},
],
});
},
templates,
},
{
sourceId: 'hits-merged',
getItems() {
return getResults2({
searchClient,
queries: [
{
indexName: 'indexName',
query,
},
],
});
},
templates,
},
{
sourceId: 'hits-separate',
getItems() {
return getResults3({
searchClient,
queries: [
{
indexName: 'indexName',
query,
},
],
});
},
templates,
},
];
},
});

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, { target: { value: 'a' } });

await waitFor(() => {
expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
).toBeInTheDocument();

expect(searchClient.search).toHaveBeenCalledTimes(2);
});
});

test('transforms the response before forwarding it to the state', async () => {
const container = document.createElement('div');
const panelContainer = document.createElement('div');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('getAlgoliaFacets', () => {

expect(description).toEqual({
execute: expect.any(Function),
requesterId: 'algolia',
transformResponse: expect.any(Function),
searchClient,
queries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('getAlgoliaResults', () => {

expect(description).toEqual({
execute: expect.any(Function),
requesterId: 'algolia',
transformResponse: expect.any(Function),
searchClient,
queries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {

import { userAgents } from '../userAgents';

export const createAlgoliaRequester = createRequester((params) =>
fetchAlgoliaResults({
...params,
userAgents,
})
export const createAlgoliaRequester = createRequester(
(params) =>
fetchAlgoliaResults({
...params,
userAgents,
}),
'algolia'
);
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('createRequester', () => {

expect(description).toEqual({
execute: expect.any(Function),
requesterId: undefined,
transformResponse,
searchClient,
queries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('getAlgoliaFacets', () => {

expect(description).toEqual({
execute: expect.any(Function),
requesterId: 'algolia',
transformResponse: expect.any(Function),
searchClient,
queries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('getAlgoliaResults', () => {

expect(description).toEqual({
execute: expect.any(Function),
requesterId: 'algolia',
transformResponse: expect.any(Function),
searchClient,
queries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ import { fetchAlgoliaResults } from '../search';

import { createRequester } from './createRequester';

export const createAlgoliaRequester = createRequester(fetchAlgoliaResults);
export const createAlgoliaRequester = createRequester(
fetchAlgoliaResults,
'algolia'
);
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,33 @@ export type RequestParams<THit> = FetcherParams & {
};

export type RequesterDescription<THit> = {
/**
* The search client used for this request. Multiple queries with the same client are batched (if `requesterId` is also the same).
*/
searchClient: SearchClient;
/**
* Identifies requesters to confirm their queries should be batched.
* This ensures that requesters with the same client but different
* post-processing functions don't get batched.
* When falsy, batching is disabled.
* For example, the Algolia requesters use "algolia".
*/
requesterId?: string;
/**
* The search parameters used for this query.
*/
queries: MultipleQueriesQuery[];
/**
* Transforms the response of this search before returning it to the caller.
*/
transformResponse: TransformResponse<THit>;
/**
* Post-processing function for multi-queries.
*/
execute: Execute<THit>;
};

export function createRequester(fetcher: Fetcher) {
export function createRequester(fetcher: Fetcher, requesterId?: string) {
function execute<THit>(fetcherParams: ExecuteParams<THit>) {
return fetcher<THit>({
searchClient: fetcherParams.searchClient,
Expand All @@ -108,6 +128,7 @@ export function createRequester(fetcher: Fetcher) {
requestParams: RequestParams<TTHit>
): RequesterDescription<TTHit> {
return {
requesterId,
execute,
...requesterParams,
...requestParams,
Expand Down