Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(umd): batch requests when using plugins via UMD #902

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
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
);
}
);
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.
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
* 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