Skip to content

Improve search #1925

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export default tseslint.config([
{
files: ["tests/**/*.test.ts"],
extends: [vitest.configs.recommended],
rules: {
"vitest/expect-expect": [
"error",
{ assertFunctionNames: ["t.expect", "expect", "assert"] },
],
},
},
// Disable any style linting, as prettier takes care of that separately
prettier,
Expand Down
3 changes: 1 addition & 2 deletions playgrounds/javascript/src/meilisearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@

export async function getSearchResponse(element: HTMLDivElement) {
const params: Parameters<Index["search"]> = [
"philoudelphia",
{ attributesToHighlight: ["title"] },
{ q: "philoudelphia", attributesToHighlight: ["title"] },

Check failure on line 41 in playgrounds/javascript/src/meilisearch.ts

View workflow job for this annotation

GitHub Actions / types-check

Type '{ q: string; attributesToHighlight: string[]; }' is not assignable to type 'SearchQueryWithRequiredPagination'.
];

const response = await client.index(indexUid).search(...params);
Expand Down
9 changes: 1 addition & 8 deletions src/http-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,7 @@
): void {
for (const [key, val] of Object.entries(recordToAppend)) {
if (val != null) {
searchParams.set(
key,
Array.isArray(val)
? val.join()
: val instanceof Date
? val.toISOString()
: String(val),
);
searchParams.set(key, Array.isArray(val) ? val.join() : String(val));
}
}
}
Expand Down Expand Up @@ -254,7 +247,7 @@
: (JSON.parse(responseBody) as T | MeiliSearchErrorResponse);

if (!response.ok) {
throw new MeiliSearchApiError(

Check failure on line 250 in src/http-requests.ts

View workflow job for this annotation

GitHub Actions / integration-tests (Node.js 18)

tests/client.test.ts > Test network methods > Master key: Update and get network settings

MeiliSearchApiError: Using the /network route requires enabling the `network` experimental feature. See https://github.com/orgs/meilisearch/discussions/805 ❯ HttpRequests.#request src/http-requests.ts:250:13 ❯ MeiliSearch.updateNetwork src/meilisearch.ts:262:12 ❯ tests/client.test.ts:887:7 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { response: { constructor: 'Function<Response>', type: 'basic', url: 'http://127.0.0.1:7700/network', redirected: false, status: 400, ok: false, statusText: 'Bad Request', headers: { constructor: 'Function<Headers>', append: 'Function<append>', delete: 'Function<delete>', get: 'Function<get>', has: 'Function<has>', set: 'Function<set>', getSetCookie: 'Function<getSetCookie>', keys: 'Function<keys>', values: 'Function<values>', entries: 'Function<entries>', forEach: 'Function<forEach>' }, body: { constructor: 'Function<ReadableStream>', locked: true, cancel: 'Function<cancel>', getReader: 'Function<getReader>', pipeThrough: 'Function<pipeThrough>', pipeTo: 'Function<pipeTo>', tee: 'Function<tee>', values: 'Function<values>' }, bodyUsed: true, clone: 'Function<clone>', blob: 'Function<blob>', arrayBuffer: 'Function<arrayBuffer>', text: 'Function<text>', json: 'Function<json>', formData: 'Function<formData>' } }

Check failure on line 250 in src/http-requests.ts

View workflow job for this annotation

GitHub Actions / integration-tests (Node.js 20)

tests/client.test.ts > Test network methods > Master key: Update and get network settings

MeiliSearchApiError: Using the /network route requires enabling the `network` experimental feature. See https://github.com/orgs/meilisearch/discussions/805 ❯ HttpRequests.#request src/http-requests.ts:250:13 ❯ MeiliSearch.updateNetwork src/meilisearch.ts:262:12 ❯ tests/client.test.ts:887:7 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { response: { constructor: 'Function<Response>', type: 'basic', url: 'http://127.0.0.1:7700/network', redirected: false, status: 400, ok: false, statusText: 'Bad Request', headers: { constructor: 'Function<Headers>', append: 'Function<append>', delete: 'Function<delete>', get: 'Function<get>', has: 'Function<has>', set: 'Function<set>', getSetCookie: 'Function<getSetCookie>', keys: 'Function<keys>', values: 'Function<values>', entries: 'Function<entries>', forEach: 'Function<forEach>' }, body: { constructor: 'Function<ReadableStream>', locked: true, cancel: 'Function<cancel>', getReader: 'Function<getReader>', pipeThrough: 'Function<pipeThrough>', pipeTo: 'Function<pipeTo>', tee: 'Function<tee>', values: 'Function<values>' }, bodyUsed: true, clone: 'Function<clone>', blob: 'Function<blob>', arrayBuffer: 'Function<arrayBuffer>', text: 'Function<text>', json: 'Function<json>', formData: 'Function<formData>', bytes: 'Function<bytes>' } }

Check failure on line 250 in src/http-requests.ts

View workflow job for this annotation

GitHub Actions / integration-tests (Node.js 22)

tests/client.test.ts > Test network methods > Master key: Update and get network settings

MeiliSearchApiError: Using the /network route requires enabling the `network` experimental feature. See https://github.com/orgs/meilisearch/discussions/805 ❯ HttpRequests.#request src/http-requests.ts:250:13 ❯ MeiliSearch.updateNetwork src/meilisearch.ts:262:12 ❯ tests/client.test.ts:887:7 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { response: { constructor: 'Function<Response>', type: 'basic', url: 'http://127.0.0.1:7700/network', redirected: false, status: 400, ok: false, statusText: 'Bad Request', headers: { constructor: 'Function<Headers>', append: 'Function<append>', delete: 'Function<delete>', get: 'Function<get>', has: 'Function<has>', set: 'Function<set>', getSetCookie: 'Function<getSetCookie>', keys: 'Function<keys>', values: 'Function<values>', entries: 'Function<entries>', forEach: 'Function<forEach>' }, body: { constructor: 'Function<ReadableStream>', locked: true, cancel: 'Function<cancel>', getReader: 'Function<getReader>', pipeThrough: 'Function<pipeThrough>', pipeTo: 'Function<pipeTo>', tee: 'Function<tee>', values: 'Function<values>' }, bodyUsed: true, clone: 'Function<clone>', blob: 'Function<blob>', arrayBuffer: 'Function<arrayBuffer>', text: 'Function<text>', json: 'Function<json>', formData: 'Function<formData>', bytes: 'Function<bytes>' } }
response,
parsedResponse as MeiliSearchErrorResponse | undefined,
);
Expand Down
176 changes: 80 additions & 96 deletions src/indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,8 @@
* Copyright: 2019, MeiliSearch
*/

import { MeiliSearchError } from "./errors/index.js";
import type {
Config,
SearchResponse,
SearchParams,
Filter,
SearchRequestGET,
IndexObject,
IndexOptions,
IndexStats,
Expand All @@ -30,33 +25,44 @@ import type {
TypoTolerance,
PaginationSettings,
Faceting,
ResourceResults,
RawDocumentAdditionOptions,
ContentType,
DocumentsIds,
DocumentsDeletionQuery,
SearchForFacetValuesParams,
SearchForFacetValuesResponse,
SeparatorTokens,
NonSeparatorTokens,
Dictionary,
ProximityPrecision,
Embedders,
SearchCutoffMs,
SearchSimilarDocumentsParams,
LocalizedAttributes,
UpdateDocumentsByFunctionOptions,
ExtraRequestInit,
PrefixSearch,
RecordAny,
SearchQuery,
FacetSearchQuery,
FacetSearchResult,
SimilarQuery,
SimilarResult,
EnqueuedTaskPromise,
ResourceResults,
SearchQueryWithOffsetLimit,
SearchResultWithOffsetLimit,
SearchQueryWithRequiredPagination,
SearchResultWithPagination,
SearchResult,
SearchQueryGet,
SearchQueryWithRequiredPaginationGet,
SearchQueryWithOffsetLimitGet,
} from "./types/index.js";
import { HttpRequests } from "./http-requests.js";
import {
getHttpRequestsWithEnqueuedTaskPromise,
TaskClient,
type HttpRequestsWithEnqueuedTaskPromise,
} from "./task.js";
import { stringifyRecordKeyValues } from "./utils.js";

export class Index<T extends RecordAny = RecordAny> {
uid: string;
Expand Down Expand Up @@ -87,103 +93,81 @@ export class Index<T extends RecordAny = RecordAny> {
/// SEARCH
///

/**
* Search for documents into an index
*
* @param query - Query string
* @param options - Search options
* @param config - Additional request configuration options
* @returns Promise containing the search response
*/
async search<D extends RecordAny = T, S extends SearchParams = SearchParams>(
query?: string | null,
options?: S,
extraRequestInit?: ExtraRequestInit,
): Promise<SearchResponse<D, S>> {
return await this.httpRequest.post<SearchResponse<D, S>>({
// TODO: If no params are provided, it's set to pagination | offset limit
// If empty object param is provided it's set to pagination
/** {@link https://www.meilisearch.com/docs/reference/api/search} */
search(
searchQuery?: SearchQueryWithOffsetLimit,
init?: ExtraRequestInit,
): Promise<SearchResultWithOffsetLimit<T>>;
search(
searchQuery?: SearchQueryWithRequiredPagination,
init?: ExtraRequestInit,
): Promise<SearchResultWithPagination<T>>;
async search(
searchQuery?: SearchQuery,
init?: ExtraRequestInit,
): Promise<SearchResult<T>> {
return await this.httpRequest.post({
path: `indexes/${this.uid}/search`,
body: { q: query, ...options },
extraRequestInit,
});
}

/**
* Search for documents into an index using the GET method
*
* @param query - Query string
* @param options - Search options
* @param config - Additional request configuration options
* @returns Promise containing the search response
*/
async searchGet<
D extends RecordAny = T,
S extends SearchParams = SearchParams,
>(
query?: string | null,
options?: S,
extraRequestInit?: ExtraRequestInit,
): Promise<SearchResponse<D, S>> {
// TODO: Make this a type thing instead of a runtime thing
const parseFilter = (filter?: Filter): string | undefined => {
if (typeof filter === "string") return filter;
else if (Array.isArray(filter))
throw new MeiliSearchError(
"The filter query parameter should be in string format when using searchGet",
);
else return undefined;
};

const getParams: SearchRequestGET = {
q: query,
...options,
filter: parseFilter(options?.filter),
sort: options?.sort?.join(","),
facets: options?.facets?.join(","),
attributesToRetrieve: options?.attributesToRetrieve?.join(","),
attributesToCrop: options?.attributesToCrop?.join(","),
attributesToHighlight: options?.attributesToHighlight?.join(","),
vector: options?.vector?.join(","),
attributesToSearchOn: options?.attributesToSearchOn?.join(","),
};

return await this.httpRequest.get<SearchResponse<D, S>>({
body: searchQuery,
extraRequestInit: init,
});
}

/** {@link https://www.meilisearch.com/docs/reference/api/search#search-in-an-index-with-get} */
searchGet(
searchQuery?: SearchQueryWithOffsetLimitGet,
init?: ExtraRequestInit,
): Promise<SearchResultWithOffsetLimit<T>>;
searchGet(
searchQuery?: SearchQueryWithRequiredPaginationGet,
init?: ExtraRequestInit,
): Promise<SearchResultWithPagination<T>>;
async searchGet(
searchQuery?: SearchQueryGet,
init?: ExtraRequestInit,
): Promise<SearchResult<T>> {
return await this.httpRequest.get({
path: `indexes/${this.uid}/search`,
params: getParams,
extraRequestInit,
params: stringifyRecordKeyValues(searchQuery, ["filter"]),
extraRequestInit: init,
});
}

/**
* Search for facet values
*
* @param params - Parameters used to search on the facets
* @param config - Additional request configuration options
* @returns Promise containing the search response
*/
/** {@link https://www.meilisearch.com/docs/reference/api/facet_search#facet-search} */
async searchForFacetValues(
params: SearchForFacetValuesParams,
extraRequestInit?: ExtraRequestInit,
): Promise<SearchForFacetValuesResponse> {
return await this.httpRequest.post<SearchForFacetValuesResponse>({
facetSearchQuery: FacetSearchQuery,
init?: ExtraRequestInit,
): Promise<FacetSearchResult> {
return await this.httpRequest.post({
path: `indexes/${this.uid}/facet-search`,
body: params,
extraRequestInit,
body: facetSearchQuery,
extraRequestInit: init,
});
}

/**
* Search for similar documents
*
* @param params - Parameters used to search for similar documents
* @returns Promise containing the search response
*/
async searchSimilarDocuments<
D extends RecordAny = T,
S extends SearchParams = SearchParams,
>(params: SearchSimilarDocumentsParams): Promise<SearchResponse<D, S>> {
return await this.httpRequest.post<SearchResponse<D, S>>({
/** {@link https://www.meilisearch.com/docs/reference/api/similar} */
async searchSimilarDocuments(
similarQuery: SimilarQuery,
init?: ExtraRequestInit,
): Promise<SimilarResult> {
return await this.httpRequest.post({
path: `indexes/${this.uid}/similar`,
body: params,
body: similarQuery,
extraRequestInit: init,
});
}

/** {@link https://www.meilisearch.com/docs/reference/api/similar#get-similar-documents-with-get} */
async searchSimilarDocumentsGet(
similarQuery: SimilarQuery,
init?: ExtraRequestInit,
): Promise<SimilarResult> {
return await this.httpRequest.get({
path: `indexes/${this.uid}/similar`,
params: stringifyRecordKeyValues(similarQuery, ["filter"]),
extraRequestInit: init,
});
}

Expand Down Expand Up @@ -313,7 +297,7 @@ export class Index<T extends RecordAny = RecordAny> {
: // Else use `GET /documents` method
await this.httpRequest.get<ResourceResults<D[]>>({
path: relativeBaseURL,
params,
params: params as Omit<typeof params, "filter">,
});
}

Expand Down
Loading
Loading