Skip to content

Commit

Permalink
feat: composition (#6437)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Haroen Viaene <[email protected]>
  • Loading branch information
e-krebs and Haroenv authored Jan 13, 2025
1 parent 3283795 commit cee83ab
Show file tree
Hide file tree
Showing 20 changed files with 940 additions and 39 deletions.
12 changes: 6 additions & 6 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"files": [
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.js",
"maxSize": "41.25 kB"
"maxSize": "41.75 kB"
},
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.min.js",
"maxSize": "13.25 kB"
"maxSize": "13.50 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
Expand All @@ -18,23 +18,23 @@
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "51.25 kB"
"maxSize": "51.75 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "65.50 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
"maxSize": "68.75 kB"
"maxSize": "69.25 kB"
},
{
"path": "packages/vue-instantsearch/vue3/umd/index.js",
"maxSize": "69 kB"
"maxSize": "69.50 kB"
},
{
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
"maxSize": "20.25 kB"
"maxSize": "20.50 kB"
},
{
"path": "packages/vue-instantsearch/vue3/cjs/index.js",
Expand Down
32 changes: 29 additions & 3 deletions packages/algoliasearch-helper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
TrendingFacetsQuery,
TrendingItemsQuery,
PlainRecommendParameters as ClientPlainRecommendParameters,
CompositionClient,
} from './types/algoliasearch';

/**
Expand All @@ -30,7 +31,7 @@ import type {
* @param searchResultsOptions
*/
declare function algoliasearchHelper(
client: SearchClient,
client: SearchClient | CompositionClient,
index: string,
opts?: algoliasearchHelper.PlainSearchParameters,
searchResultsOptions?: algoliasearchHelper.SearchResultsOptions
Expand Down Expand Up @@ -98,6 +99,11 @@ declare namespace algoliasearchHelper {
*/
searchOnlyWithDerivedHelpers(): this;

/**
* Private method to search using composition API
*/
searchWithComposition(): this;

/**
* Private method for search, without triggering events
*/
Expand Down Expand Up @@ -192,6 +198,26 @@ declare namespace algoliasearchHelper {
userState?: PlainSearchParameters
): Promise<SearchForFacetValues.Result>;

/**
* Search for facet values using the Composition API & based on a query and the name of a faceted attribute.
* This triggers a search and will return a promise. On top of using the query, it also sends
* the parameters from the state so that the search is narrowed down to only the possible values.
*
* See the description of [FacetSearchResult](reference.html#FacetSearchResult)
* @param facet the name of the faceted attribute
* @param query the string query for the search
* @param [maxFacetHits] the maximum number values returned. Should be > 0 and <= 100
* @param [userState] the set of custom parameters to use on top of the current state. Setting a property to `undefined` removes
* it in the generated query.
* @return the results of the search
*/
searchForCompositionFacetValues(
facet: string,
query: string,
maxFacetHits: number,
userState?: PlainSearchParameters
): Promise<SearchForFacetValues.Result>;

/**
* Sets the text query used for the search.
*
Expand Down Expand Up @@ -379,8 +405,8 @@ declare namespace algoliasearchHelper {
*/
containsRefinement(...any: any[]): any;
clearCache(): this;
setClient(client: SearchClient): this;
getClient(): SearchClient;
setClient(client: SearchClient | CompositionClient): this;
getClient(): SearchClient | CompositionClient;
derive(
deriveFn: (oldParams: SearchParameters) => SearchParameters,
deriveRecommendFn?: (
Expand Down
144 changes: 144 additions & 0 deletions packages/algoliasearch-helper/src/algoliasearch.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ AlgoliaSearchHelper.prototype.searchOnlyWithDerivedHelpers = function () {
return this;
};

AlgoliaSearchHelper.prototype.searchWithComposition = function () {
this._runComposition({ onlyWithDerivedHelpers: true });
return this;
};
/**
* Sends the recommendation queries set in the state. When the method is
* called, it triggers a `fetch` event. The results will be available through
Expand Down Expand Up @@ -457,6 +461,81 @@ AlgoliaSearchHelper.prototype.searchForFacetValues = function (
);
};

/**
* Search for facet values using the Composition API & based on a query and the name of a faceted attribute.
* This triggers a search and will return a promise. On top of using the query, it also sends
* the parameters from the state so that the search is narrowed down to only the possible values.
*
* See the description of [FacetSearchResult](reference.html#FacetSearchResult)
* @param {string} facet the name of the faceted attribute
* @param {string} query the string query for the search
* @param {number} [maxFacetHits] the maximum number values returned. Should be > 0 and <= 100
* @param {object} [userState] the set of custom parameters to use on top of the current state. Setting a property to `undefined` removes
* it in the generated query.
* @return {promise.<FacetSearchResult>} the results of the search
*/
AlgoliaSearchHelper.prototype.searchForCompositionFacetValues = function (
facet,
query,
maxFacetHits,
userState
) {
if (typeof this.client.searchForFacetValues !== 'function') {
throw new Error(
'search for facet values (searchable) was called, but this client does not have a function client.searchForFacetValues'
);
}

var state = this.state.setQueryParameters(userState || {});
var isDisjunctive = state.isDisjunctiveFacet(facet);

this._currentNbQueries++;
// eslint-disable-next-line consistent-this
var self = this;
var searchForFacetValuesPromise;

searchForFacetValuesPromise = this.client.searchForFacetValues({
compositionID: state.index,
facetName: facet,
searchForFacetValuesRequest: {
params: {
query: query,
maxFacetHits: maxFacetHits,
searchQuery: requestBuilder._getCompositionHitsSearchParams(state),
},
},
});

this.emit('searchForFacetValues', {
state: state,
facet: facet,
query: query,
});

return searchForFacetValuesPromise.then(
function addIsRefined(content) {
self._currentNbQueries--;
if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');

content = content.results[0];

content.facetHits.forEach(function (f) {
f.escapedValue = escapeFacetValue(f.value);
f.isRefined = isDisjunctive
? state.isDisjunctiveFacetRefined(facet, f.escapedValue)
: state.isFacetRefined(facet, f.escapedValue);
});

return content;
},
function (e) {
self._currentNbQueries--;
if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');
throw e;
}
);
};

/**
* Sets the text query used for the search.
*
Expand Down Expand Up @@ -1578,6 +1657,71 @@ AlgoliaSearchHelper.prototype._search = function (options) {
return undefined;
};

/**
* Perform the underlying queries
* @private
* @param {boolean} [options.onlyWithDerivedHelpers=false] if true, only the derived helpers will be queried
* @return {undefined} does not return anything
* @fires search
* @fires result
* @fires error
*/
AlgoliaSearchHelper.prototype._runComposition = function () {
var state = this.state;
var states = [];
var mainQueries = [];

var derivedQueries = this.derivedHelpers.map(function (derivedHelper) {
var derivedState = derivedHelper.getModifiedState(state);
var derivedStateQueries =
requestBuilder._getCompositionQueries(derivedState);

states.push({
state: derivedState,
queriesCount: derivedStateQueries.length,
helper: derivedHelper,
});

derivedHelper.emit('search', {
state: derivedState,
results: derivedHelper.lastResults,
});

return derivedStateQueries;
});

var queries = Array.prototype.concat.apply(mainQueries, derivedQueries);

var queryId = this._queryId++;
this._currentNbQueries++;

if (!queries.length) {
return Promise.resolve({ results: [] }).then(
this._dispatchAlgoliaResponse.bind(this, states, queryId)
);
}

if (queries.length > 1) {
throw new Error('Only one query is allowed when using a composition.');
}

var query = queries[0];

try {
this.client
.search(query)
.then(this._dispatchAlgoliaResponse.bind(this, states, queryId))
.catch(this._dispatchAlgoliaError.bind(this, queryId));
} catch (error) {
// If we reach this part, we're in an internal error state
this.emit('error', {
error: error,
});
}

return undefined;
};

AlgoliaSearchHelper.prototype._recommend = function () {
var searchState = this.state;
var recommendState = this.recommendState;
Expand Down
65 changes: 65 additions & 0 deletions packages/algoliasearch-helper/src/requestBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ var requestBuilder = {
return queries;
},

/**
* Get all the queries to send to the client, those queries can used directly
* with the Algolia client.
* @private
* @param {SearchParameters} state The state from which to get the queries
* @return {object[]} The queries
*/
_getCompositionQueries: function getQueries(state) {
return [
{
compositionID: state.index,
requestBody: {
params: requestBuilder._getCompositionHitsSearchParams(state),
},
},
];
},

/**
* Build search parameters used to fetch hits
* @private
Expand Down Expand Up @@ -159,6 +177,53 @@ var requestBuilder = {
return sortObject(merge({}, state.getQueryParams(), additionalParams));
},

/**
* Build search parameters used to fetch hits
* @private
* @param {SearchParameters} state The state from which to get the queries
* @return {object.<string, any>} The search parameters for hits
*/
_getCompositionHitsSearchParams: function (state) {
var facets = state.facets
.concat(
state.disjunctiveFacets.map(function (value) {
return 'disjunctive(' + value + ')';
})
)
.concat(requestBuilder._getHitsHierarchicalFacetsAttributes(state))
.sort();

var facetFilters = requestBuilder._getFacetFilters(state);
var numericFilters = requestBuilder._getNumericFilters(state);
var tagFilters = requestBuilder._getTagFilters(state);
var additionalParams = {};

if (facets.length > 0) {
additionalParams.facets = facets.indexOf('*') > -1 ? ['*'] : facets;
}

if (tagFilters.length > 0) {
additionalParams.tagFilters = tagFilters;
}

if (facetFilters.length > 0) {
additionalParams.facetFilters = facetFilters;
}

if (numericFilters.length > 0) {
additionalParams.numericFilters = numericFilters;
}

var params = state.getQueryParams();

delete params.highlightPreTag;
delete params.highlightPostTag;
// not a valid search parameter, it is handled in _getCompositionQueries
delete params.index;

return sortObject(merge({}, params, additionalParams));
},

/**
* Build search parameters used to fetch a disjunctive facet
* @private
Expand Down
11 changes: 11 additions & 0 deletions packages/algoliasearch-helper/types/algoliasearch.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,14 @@ export interface SearchClient {
: never;
addAlgoliaAgent?: DefaultSearchClient['addAlgoliaAgent'];
}

export interface CompositionClient {
search: <T>(request: {
compositionID: string;
requestBody: { params: SearchOptions };
}) => Promise<{
results: Array<AlgoliaSearch.SearchResponse<T>>;
}>;
initIndex?: never;
addAlgoliaAgent?: DefaultSearchClient['addAlgoliaAgent'];
}
Loading

0 comments on commit cee83ab

Please sign in to comment.