Repository: meilisearch/meilisearch-js
Pull request #1891: Implement the experimental feature for remote federated search requests.
Author: login: consoleLogIt
Labels: name: enhancement
description: New feature or request
Created: 2025-03-09T15:42:08Z | Merged: 2025-03-21T08:28:14Z

# Pull Request

## Related issue
Fixes #1872

## What does this PR do?
Implement the experimental feature for [remote federated search requests](https://www.meilisearch.com/docs/learn/multi_search/implement_sharding?utm_campaign=oss&utm_source=github).



## PR checklist
Please check if your PR fulfills the following requirements:
- [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)?
- [x] Have you read the contributing guidelines?
- [x] Have you made sure that the title is accurate and descriptive of the changes?

Thank you so much for contributing to Meilisearch!


Linked issue #1872: [v1.13] Remote federated search requests (experimental)
*Following the [changes related to Meilisearch v1.13.0 mega issue](https://github.com/meilisearch/integration-guides/issues/314):*

## Description

Implement the **experimental feature** for [remote federated search requests](https://www.meilisearch.com/docs/learn/multi_search/implement_sharding?utm_campaign=oss&utm_source=github).

For further reference, see the related [Meilisearch engine issue](https://github.com/meilisearch/meilisearch/issues/4980).

## Specifications

- [ ] Add support for the new [`network` API](https://www.meilisearch.com/docs/reference/api/network?utm_campaign=oss&utm_source=github)
- [ ] Update the multi-search functionality to accept the [`remote` parameter](https://www.meilisearch.com/docs/reference/api/multi_search?utm_campaign=oss&utm_source=github)
Linked issue comments: user: login: consoleLogIt
created_at: 2025-02-26T10:26:19Z
body: @Strift hi
 i would like to on this.

author_association: CONTRIBUTOR
user: login: Strift
created_at: 2025-02-27T08:06:59Z
body: Hello, thanks for your interest!

I'd love to review your PR for this!

We prefer not assigning issues because we've met users who asked to be assigned but never came back.
This sometimes discourages other volunteer contributors from opening a PR to fix this issue.

Make sure to check our [contributing guidelines](../blob/main/CONTRIBUTING.md). I'm looking forward to reviewing your PR.

author_association: COLLABORATOR
user: login: consoleLogIt
created_at: 2025-03-04T16:56:43Z
body: Hi @Strift @flevi29 
i was going to start working on this, but I need some guidance.
after reading the [doc](https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#implement-sharding-with-remote-federated-search) and going through the code.

This is what i have understood, please correct me if  i am missing something. 

1. add a function to toggleNetworkFeature.
( although i was thinking maybe we can add function let's say updateExperimentalFeatures and then call this function from an individual feature function like toggleNetworkFunction?, also i saw that there is another experimental feature that is being used called editDocumentsByFunction so maybe i can add this function as well. )
2. add function for the network api.
3. Update the multi-search functionality to accept the [remote parameter]

also, i can write a test for a multi-search query with a remote federation search and test it locally but i am not sure how you will you run this test in ci/cd since this requries multiple instances

thanks 

author_association: CONTRIBUTOR
user: login: Strift
created_at: 2025-03-09T06:22:08Z
body: Hello @consoleLogIt 

In this SDK, we don't create methods to enable experimental features. We could in the future, but it's not required. You can document that the feature needs to enabled in the tests, and in the README.

I would recommend creating the following methods for managing the remote instances:
- `setRemoteInstances(instances: Record<string, {url: string, searchApiKey: string}>)`
- `setInstanceName(name: string)`

Then, update the `search` methods to accept the new `remote` parameter in `federationOptions` object. Incude a link to the docs page in the JSdoc :)

> also, i can write a test for a multi-search query with a remote federation search and test it locally but i am not sure how you will you run this test in ci/cd since this requries multiple instances

I think we should be able to come up with something by creating multiple instances of Meilisearch via Docker Compose (local) and Github actions (CI). I haven't thought much about it yet though.
author_association: COLLABORATOR
user: login: consoleLogIt
created_at: 2025-03-09T10:04:58Z
body: @Strift 
hi

alright I understand.
i will work on it and create a draft pr for the review.
thanks 

author_association: CONTRIBUTOR
user: login: consoleLogIt
created_at: 2025-03-09T15:57:41Z
body: @Strift
hi 
I've created a PR. Could you please take a look whenever you get a chance?
thanks
author_association: CONTRIBUTOR

Diff:
diff --git a/src/meilisearch.ts b/src/meilisearch.ts
index 95215de03..b88307ac7 100644
--- a/src/meilisearch.ts
+++ b/src/meilisearch.ts
@@ -28,6 +28,7 @@ import type {
   BatchesResults,
   BatchesQuery,
   MultiSearchResponseOrSearchResponse,
+  Network,
 } from "./types.js";
 import { ErrorStatusCode } from "./types.js";
 import { HttpRequests } from "./http-requests.js";
@@ -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
    *
@@ -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,
@@ -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
   ///
diff --git a/src/types.ts b/src/types.ts
index 65378b1ed..43672478e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -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;
@@ -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;
 };
diff --git a/tests/client.test.ts b/tests/client.test.ts
index 1fffa8bf4..2d3910c8f 100644
--- a/tests/client.test.ts
+++ b/tests/client.test.ts
@@ -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,
+      );
+    });
+  },
+);
diff --git a/tests/search.test.ts b/tests/search.test.ts
index 3d2e3c720..d0b0a0d43 100644
--- a/tests/search.test.ts
+++ b/tests/search.test.ts
@@ -22,6 +22,7 @@ import {
   getClient,
   datasetWithNests,
   getKey,
+  HOST,
 } from "./utils/meilisearch-test-utils.js";
 
 const index = {
@@ -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");


Reviews:
user: login: Strift
body: Hey @consoleLogIt thanks for your PR!

I left a comment in the review. Please take a look at why the tests are failing
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-03-20T05:34:56Z
user: login: consoleLogIt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T06:00:09Z
user: login: consoleLogIt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T06:03:32Z
user: login: flevi29
body: @consoleLogIt I applied some changes. I think it's better to be closer to the main API as far as methods go, and the GET method was missing. The rest LGTM for now.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-20T19:33:34Z
user: login: Strift
body: Thanks for the review @flevi29, this is much better.

bors merge
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-21T08:25:01Z

Review comments:
diff_hunk: @@ -234,6 +257,36 @@ export class MeiliSearch {
     });
   }
 
+  ///
+  ///  CONFIGURE NETWORK OBJECT
+  ///
+
+  /**
+   * Set the network.remotes
+   *
+   * @param instances - Instances
+   * @returns Promise containing the remotes object
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#configuring-the-network-object}
+   */
+  async setRemoteInstances(
+    instances: Record<string, { url: string; searchApiKey: string }>,
+  ) {
+    const url = `network`;
+    return await this.httpRequest.patch(url, { remotes: instances });
+  }
+
+  /**
+   * Set the network.self
+   *
+   * @param name - Instance name
+   * @returns Promise containing the self property
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#specify-the-name-of-the-current-instance}
+   */
+  async setInstaceName(name: string) {
path: src/meilisearch.ts
user: login: Strift
body: There's a typo in this method name (the tests will need to be updated too)
created_at: 2025-03-20T05:30:38Z
author_association: COLLABORATOR
diff_hunk: @@ -234,6 +257,36 @@ export class MeiliSearch {
     });
   }
 
+  ///
+  ///  CONFIGURE NETWORK OBJECT
+  ///
+
+  /**
+   * Set the network.remotes
+   *
+   * @param instances - Instances
+   * @returns Promise containing the remotes object
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#configuring-the-network-object}
+   */
+  async setRemoteInstances(
+    instances: Record<string, { url: string; searchApiKey: string }>,
+  ) {
+    const url = `network`;
+    return await this.httpRequest.patch(url, { remotes: instances });
+  }
+
+  /**
+   * Set the network.self
+   *
+   * @param name - Instance name
+   * @returns Promise containing the self property
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#specify-the-name-of-the-current-instance}
+   */
+  async setInstaceName(name: string) {
path: src/meilisearch.ts
user: login: consoleLogIt
body: Hi @Strift, thanks for reviewing 
i'll fix the issues asap.

created_at: 2025-03-20T06:00:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -234,6 +257,36 @@ export class MeiliSearch {
     });
   }
 
+  ///
+  ///  CONFIGURE NETWORK OBJECT
+  ///
+
+  /**
+   * Set the network.remotes
+   *
+   * @param instances - Instances
+   * @returns Promise containing the remotes object
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#configuring-the-network-object}
+   */
+  async setRemoteInstances(
+    instances: Record<string, { url: string; searchApiKey: string }>,
+  ) {
+    const url = `network`;
+    return await this.httpRequest.patch(url, { remotes: instances });
+  }
+
+  /**
+   * Set the network.self
+   *
+   * @param name - Instance name
+   * @returns Promise containing the self property
+   * @see {@link https://www.meilisearch.com/docs/learn/multi_search/implement_sharding#specify-the-name-of-the-current-instance}
+   */
+  async setInstaceName(name: string) {
path: src/meilisearch.ts
user: login: consoleLogIt
body: although when i created the PR, all the tests passed: https://github.com/meilisearch/meilisearch-js/actions/runs/13750026482

created_at: 2025-03-20T06:03:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-09T15:47:39Z
body: ## [Codecov](https://app.codecov.io/gh/meilisearch/meilisearch-js/pull/1891?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meilisearch) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.36%. Comparing base [(`d6446f5`)](https://app.codecov.io/gh/meilisearch/meilisearch-js/commit/d6446f524537a86862b13d1e90bfb7a8117e902a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meilisearch) to head [(`9bd16a5`)](https://app.codecov.io/gh/meilisearch/meilisearch-js/commit/9bd16a5cd943aea02864ccc12c33e0b9db7227a1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meilisearch).
> Report is 12 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #1891   +/-   ##
=======================================
  Coverage   99.36%   99.36%           
=======================================
  Files          16       16           
  Lines        1566     1575    +9     
  Branches      343      345    +2     
=======================================
+ Hits         1556     1565    +9     
  Misses         10       10           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/meilisearch/meilisearch-js/pull/1891?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meilisearch).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meilisearch).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: consoleLogIt
created_at: 2025-03-18T15:52:52Z
body: Hi @flevi29 @Strift, I created this PR last week and was wondering if there are any updates on it. Please let me know if there's something i need to update. Thanks! 
author_association: CONTRIBUTOR
user: login: consoleLogIt
created_at: 2025-03-20T14:09:36Z
body: Hi @Strift, this may not be an issue but when i run the style check locally i get this following error. 
![image](https://github.com/user-attachments/assets/78d369f8-504b-4991-89f6-7a175601eae3)

author_association: CONTRIBUTOR
user: login: meili-bors[bot]
created_at: 2025-03-21T08:28:13Z
body: Build succeeded:
  * [types-check](https://github.com/meilisearch/meilisearch-js/actions/runs/13987576751/job/39164124152)
  * [style-check](https://github.com/meilisearch/meilisearch-js/actions/runs/13987576751/job/39164123874)
  * [integration-tests (Node.js 22)](https://github.com/meilisearch/meilisearch-js/actions/runs/13987576751/job/39164124408)
  * [integration-tests (Node.js 18)](https://github.com/meilisearch/meilisearch-js/actions/runs/13987576751/job/39164123384)
  * [integration-tests (Node.js 20)](https://github.com/meilisearch/meilisearch-js/actions/runs/13987576751/job/39164123628)
author_association: CONTRIBUTOR
user: login: consoleLogIt
created_at: 2025-03-21T13:23:30Z
body: Thanks for the review guys!
@flevi29 yeah that makes sense.
author_association: CONTRIBUTOR