Skip to content

Implement the experimental feature for remote federated search requests. #1891

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

Merged
merged 11 commits into from
Mar 21, 2025
51 changes: 50 additions & 1 deletion src/meilisearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
BatchesResults,
BatchesQuery,
MultiSearchResponseOrSearchResponse,
Network,
} from "./types.js";
import { ErrorStatusCode } from "./types.js";
import { HttpRequests } from "./http-requests.js";
Expand Down Expand Up @@ -201,7 +202,8 @@ export class MeiliSearch {
* Perform multiple search queries.
*
* It is possible to make multiple search queries on the same index or on
* different ones
* different ones. With network feature enabled, you can also search across
* remote instances.
*
* @example
*
Expand All @@ -212,11 +214,33 @@ export class MeiliSearch {
* { indexUid: "books", q: "flower" },
* ],
* });
*
* // Federated search with remote instance (requires network feature enabled)
* client.multiSearch({
* federation: {},
* queries: [
* {
* indexUid: "movies",
* q: "wonder",
* federationOptions: {
* remote: "meilisearch instance name",
* },
* },
* {
* indexUid: "movies",
* q: "wonder",
* federationOptions: {
* remote: "meilisearch instance name",
* },
* },
* ],
* });
* ```
*
* @param queries - Search queries
* @param extraRequestInit - Additional request configuration options
* @returns Promise containing the search responses
* @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#perform-a-search}
*/
async multiSearch<
T1 extends MultiSearchParams | FederatedMultiSearchParams,
Expand All @@ -234,6 +258,31 @@ export class MeiliSearch {
});
}

///
/// Network
///

/**
* {@link https://www.meilisearch.com/docs/reference/api/network#get-the-network-object}
*
* @experimental
*/
async getNetwork(): Promise<Network> {
return await this.httpRequest.get({ path: "network" });
}

/**
* {@link https://www.meilisearch.com/docs/reference/api/network#update-the-network-object}
*
* @experimental
*/
async updateNetwork(network: Partial<Network>): Promise<Network> {
return await this.httpRequest.patch({
path: "network",
body: network,
});
}

///
/// TASKS
///
Expand Down
22 changes: 21 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export type MergeFacets = {
maxValuesPerFacet?: number | null;
};

export type FederationOptions = { weight: number };
export type FederationOptions = { weight: number; remote?: string };
export type MultiSearchFederation = {
limit?: number;
offset?: number;
Expand All @@ -274,6 +274,26 @@ export type FederatedMultiSearchParams = {
queries: MultiSearchQueryWithFederation[];
};

/**
* {@link https://www.meilisearch.com/docs/reference/api/network#the-remote-object}
*
* @see `meilisearch_types::features::Remote` at {@link https://github.com/meilisearch/meilisearch}
*/
export type Remote = {
url: string;
searchApiKey: string | null;
};

/**
* {@link https://www.meilisearch.com/docs/reference/api/network#the-network-object}
*
* @see `meilisearch_types::features::Network` at {@link https://github.com/meilisearch/meilisearch}
*/
export type Network = {
self: string | null;
remotes: Record<string, Remote>;
};

export type CategoriesDistribution = {
[category: string]: number;
};
Expand Down
32 changes: 32 additions & 0 deletions tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -899,3 +899,35 @@ describe.each([
);
});
});

describe.each([{ permission: "Master" }])(
"Test network methods",
({ permission }) => {
const instanceName = "instance_1";

test(`${permission} key: Update and get network settings`, async () => {
const client = await getClient(permission);

const instances = {
[instanceName]: {
url: "http://instance-1:7700",
searchApiKey: "search-key-1",
},
};

await client.updateNetwork({ self: instanceName, remotes: instances });
const response = await client.getNetwork();
expect(response).toHaveProperty("self", instanceName);
expect(response).toHaveProperty("remotes");
expect(response.remotes).toHaveProperty("instance_1");
expect(response.remotes["instance_1"]).toHaveProperty(
"url",
instances[instanceName].url,
);
expect(response.remotes["instance_1"]).toHaveProperty(
"searchApiKey",
instances[instanceName].searchApiKey,
);
});
},
);
55 changes: 55 additions & 0 deletions tests/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getClient,
datasetWithNests,
getKey,
HOST,
} from "./utils/meilisearch-test-utils.js";

const index = {
Expand Down Expand Up @@ -235,6 +236,60 @@ describe.each([
expect(response2.hits[0].id).toEqual(1344);
});

test(`${permission} key: Multi index search with federation and remote`, async () => {
const adminKey = await getKey("Admin");

// first enable the network endpoint.
await fetch(`${HOST}/experimental-features`, {
body: JSON.stringify({ network: true }),
headers: {
Authorization: `Bearer ${adminKey}`,
"Content-Type": "application/json",
},
method: "PATCH",
});

const masterClient = await getClient("Master");

const searchKey = await getKey("Search");

// set the remote name and instances
const instanceName = "instance_1";
await masterClient.updateNetwork({
self: instanceName,
remotes: { [instanceName]: { url: HOST, searchApiKey: searchKey } },
});

const searchClient = await getClient(permission);

const response = await searchClient.multiSearch<
FederatedMultiSearchParams,
Books | { id: number; asd: string }
>({
federation: {},
queries: [
{
indexUid: index.uid,
q: "456",
attributesToSearchOn: ["id"],
federationOptions: { weight: 1, remote: instanceName },
},
{
indexUid: index.uid,
q: "1344",
federationOptions: { weight: 0.9, remote: instanceName },
attributesToSearchOn: ["id"],
},
],
});

expect(response).toHaveProperty("hits");
expect(Array.isArray(response.hits)).toBe(true);
expect(response.hits.length).toEqual(2);
expect(response.hits[0].id).toEqual(456);
expect(response.hits[0]._federation).toHaveProperty("remote", instanceName);
});

test(`${permission} key: Multi search with facetsByIndex`, async () => {
const client = await getClient(permission);
const masterClient = await getClient("Master");
Expand Down