فهرست منبع

feat(elasticsearch-plugin): Update the ElasticSearch plugin to use ElasticSearch v9.1.0 (#3740)

Co-authored-by: Michael Bromley <michaelbromley@users.noreply.github.com>

BREAKING CHANGE: ElasticSearch must be updated to v9.1.0, using the new client and plugin with the older v7 version of ES will FAIL.
Upgrading ElasticSearch is a one-way process, migrating the DB schema from v7.10.2 to v9.1.0 with no option to downgrade.
The ES instance and the client library in both the plugin and Vendure project package.json must match the upgraded ES instance version (v9.1.0).
Leftovers.today 1 ماه پیش
والد
کامیت
71118d35b6

+ 7 - 1
.github/workflows/build_and_test.yml

@@ -103,8 +103,14 @@ jobs:
                     - 5432
                 options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
             elastic:
-                image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
+                image: docker.elastic.co/elasticsearch/elasticsearch:9.1.0
                 env:
+                    node.name: es
+                    cluster.name: es-docker-cluster
+                    xpack.security.enabled: false
+                    xpack.security.http.ssl.enabled: false
+                    xpack.security.transport.ssl.enabled: false
+                    xpack.license.self_generated.type: basic
                     discovery.type: single-node
                     bootstrap.memory_lock: true
                     ES_JAVA_OPTS: -Xms512m -Xmx512m

+ 9 - 3
docker-compose.yml

@@ -80,12 +80,19 @@ services:
         volumes:
             - keycloak_data:/opt/keycloak/data
     elasticsearch:
-        image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2
+        image: docker.elastic.co/elasticsearch/elasticsearch:9.1.0
         container_name: elasticsearch
         environment:
+            - node.name=es
+            - cluster.name=es-docker-cluster
+            - xpack.security.enabled=false
+            - xpack.security.http.ssl.enabled=false
+            - xpack.security.transport.ssl.enabled=false
+            - xpack.license.self_generated.type=basic
             - discovery.type=single-node
             - bootstrap.memory_lock=true
-            - 'ES_JAVA_OPTS=-Xms512m -Xmx512m'
+            - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
+        mem_limit: 536870912
         ulimits:
             memlock:
                 soft: -1
@@ -100,7 +107,6 @@ services:
         environment: []
         ports:
             - '6379:6379'
-
     jaeger:
         image: jaegertracing/all-in-one:latest
         container_name: jaeger

+ 17 - 5
packages/create/templates/docker-compose.hbs

@@ -76,15 +76,27 @@ services:
     # Checkout our Elasticsearch plugin: https://docs.vendure.io/reference/core-plugins/elasticsearch-plugin/
     # To run the elasticsearch container run "docker compose up -d elasticsearch"
     elasticsearch:
-        image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
+        image: docker.elastic.co/elasticsearch/elasticsearch:9.1.0
+        container_name: elasticsearch
         environment:
-            discovery.type: single-node
-            bootstrap.memory_lock: true
-            ES_JAVA_OPTS: -Xms512m -Xmx512m
+            - node.name=es
+            - cluster.name=es-docker-cluster
+            - xpack.security.enabled=false
+            - xpack.security.http.ssl.enabled=false
+            - xpack.security.transport.ssl.enabled=false
+            - xpack.license.self_generated.type=basic
+            - discovery.type=single-node
+            - bootstrap.memory_lock=true
+            - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
+        mem_limit: 536870912
+        ulimits:
+            memlock:
+                soft: -1
+                hard: -1
         volumes:
             - elasticsearch_data:/usr/share/elasticsearch/data
         ports:
-            - "9300:9200"
+            - 9200:9200
         labels:
             - "io.vendure.create.name={{{ escapeSingle name }}}"
 

+ 5 - 3
packages/elasticsearch-plugin/e2e/e2e-helpers.ts

@@ -54,9 +54,11 @@ export async function testMatchSearchTerm(client: SimpleGraphQLClient) {
             groupByProduct: true,
         },
     });
-    expect(result.search.items.map(i => i.productName).sort((a, b) => a.localeCompare(b))).toEqual(
-        ['Camera Lens', 'Instant Camera', 'SLR Camera'].sort((a, b) => a.localeCompare(b)),
-    );
+    expect(result.search.items.map(i => i.productName).sort()).toEqual([
+        'Camera Lens',
+        'Instant Camera',
+        'SLR Camera',
+    ]);
 }
 
 export async function testMatchFacetIdsAnd(client: SimpleGraphQLClient) {

+ 1 - 1
packages/elasticsearch-plugin/package.json

@@ -25,7 +25,7 @@
         "access": "public"
     },
     "dependencies": {
-        "@elastic/elasticsearch": "~7.9.1",
+        "@elastic/elasticsearch": "9.1.0",
         "deepmerge": "^4.2.2",
         "fast-deep-equal": "^3.1.3"
     },

+ 105 - 54
packages/elasticsearch-plugin/src/elasticsearch.service.ts

@@ -69,7 +69,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             while (attempts < connectionAttempts) {
                 attempts++;
                 try {
-                    const pingResult = await this.client.ping({}, { requestTimeout: 1000 });
+                    const pingResult = await this.client.ping({}, { requestTimeout: 1000, meta: true });
                     if (pingResult.body) {
                         Logger.verbose('Ping to Elasticsearch successful', loggerCtx);
                         return resolve();
@@ -95,7 +95,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
 
         const createIndex = async (indexName: string) => {
             const index = indexPrefix + indexName;
-            const result = await this.client.indices.exists({ index });
+            const result = await this.client.indices.exists({ index }, { meta: true });
 
             if (!result.body) {
                 Logger.verbose(`Index "${index}" does not exist. Creating...`, loggerCtx);
@@ -108,10 +108,17 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             } else {
                 Logger.verbose(`Index "${index}" exists`, loggerCtx);
 
-                const existingIndexSettingsResult = await this.client.indices.getSettings({ index });
-                const existingIndexSettings =
-                    existingIndexSettingsResult.body[Object.keys(existingIndexSettingsResult.body)[0]]
-                        .settings.index;
+                const existingIndexSettingsResult = await this.client.indices.getSettings(
+                    { index },
+                    { meta: true },
+                );
+                let existingIndexSettings;
+
+                if (existingIndexSettingsResult.body) {
+                    existingIndexSettings = (existingIndexSettingsResult.body as Record<string, any>)[
+                        Object.keys(existingIndexSettingsResult.body)[0]
+                    ].settings.index;
+                }
 
                 const tempName = new Date().getTime();
                 const nameSalt = Math.random().toString(36).substring(7);
@@ -125,8 +132,11 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
                     this.options.indexMappingProperties,
                     false,
                 );
-                const tempIndexSettingsResult = await this.client.indices.getSettings({ index: tempIndex });
-                const tempIndexSettings = tempIndexSettingsResult.body[tempIndex].settings.index;
+                const tempIndexSettingsResult = await this.client.indices.getSettings(
+                    { index: tempIndex },
+                    { meta: true },
+                );
+                const tempIndexSettings = tempIndexSettingsResult.body[tempIndex]?.settings?.index;
 
                 const indexParamsToExclude = [
                     'routing',
@@ -138,7 +148,9 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
                     'version',
                 ];
                 for (const param of indexParamsToExclude) {
-                    delete tempIndexSettings[param];
+                    if (tempIndexSettings) {
+                        delete tempIndexSettings[param];
+                    }
                     delete existingIndexSettings[param];
                 }
                 if (!equal(tempIndexSettings, existingIndexSettings))
@@ -147,14 +159,20 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
                         loggerCtx,
                     );
                 else {
-                    const existingIndexMappingsResult = await this.client.indices.getMapping({ index });
+                    const existingIndexMappingsResult = await this.client.indices.getMapping(
+                        { index },
+                        { meta: true },
+                    );
                     const existingIndexMappings =
                         existingIndexMappingsResult.body[Object.keys(existingIndexMappingsResult.body)[0]]
                             .mappings;
 
-                    const tempIndexMappingsResult = await this.client.indices.getMapping({
-                        index: tempIndex,
-                    });
+                    const tempIndexMappingsResult = await this.client.indices.getMapping(
+                        {
+                            index: tempIndex,
+                        },
+                        { meta: true },
+                    );
                     const tempIndexMappings = tempIndexMappingsResult.body[tempIndex].mappings;
                     if (!equal(tempIndexMappings, existingIndexMappings))
                         // eslint-disable-next-line max-len
@@ -200,16 +218,25 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
 
         if (groupByProduct || groupBySKU) {
             try {
-                const { body }: { body: SearchResponseBody<VariantIndexItem> } = await this.client.search({
-                    index: indexPrefix + VARIANT_INDEX_NAME,
-                    body: elasticSearchBody,
-                });
+                const { body } = await this.client.search(
+                    {
+                        index: indexPrefix + VARIANT_INDEX_NAME,
+                        ...elasticSearchBody,
+                    },
+                    { meta: true },
+                );
 
                 const totalItems = await this.totalHits(ctx, input, enabledOnly);
 
                 await this.eventBus.publish(new SearchEvent(ctx, input));
                 return {
-                    items: body.hits.hits.map(hit => this.mapProductToSearchResult(hit, groupByProduct, groupBySKU)),
+                    items: body.hits.hits.map(hit =>
+                        this.mapProductToSearchResult(
+                            hit as SearchHit<VariantIndexItem>,
+                            groupByProduct,
+                            groupBySKU,
+                        ),
+                    ),
                     totalItems,
                 };
             } catch (e: any) {
@@ -228,14 +255,21 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             }
         } else {
             try {
-                const { body }: { body: SearchResponseBody<VariantIndexItem> } = await this.client.search({
-                    index: indexPrefix + VARIANT_INDEX_NAME,
-                    body: elasticSearchBody,
-                });
+                const { body } = await this.client.search(
+                    {
+                        index: indexPrefix + VARIANT_INDEX_NAME,
+                        ...elasticSearchBody,
+                    },
+                    { meta: true },
+                );
                 await this.eventBus.publish(new SearchEvent(ctx, input));
                 return {
-                    items: body.hits.hits.map(hit => this.mapVariantToSearchResult(hit)),
-                    totalItems: body.hits.total ? body.hits.total.value : 0,
+                    items: body.hits.hits.map(hit =>
+                        this.mapVariantToSearchResult(hit as SearchHit<VariantIndexItem>),
+                    ),
+                    totalItems: Number(
+                        body.hits.total && typeof body.hits.total === 'object' ? body.hits.total.value : 0,
+                    ),
                 };
             } catch (e: any) {
                 if (e.meta.body.error.type && e.meta.body.error.type === 'search_phase_execution_exception') {
@@ -272,24 +306,27 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
         elasticSearchBody.from = 0;
         elasticSearchBody.size = 0;
         elasticSearchBody.aggs = {
-                total: {
-                    cardinality: {
-                        field: groupBySKU ? 'sku.keyword' : 'productId',
-                    },
+            total: {
+                cardinality: {
+                    field: groupBySKU ? 'sku.keyword' : 'productId',
                 },
-            };
-        const { body }: { body: SearchResponseBody<VariantIndexItem> } = await this.client.search({
-            index: indexPrefix + VARIANT_INDEX_NAME,
-            body: elasticSearchBody,
-        });
+            },
+        };
+        const response = await this.client.search(
+            {
+                index: indexPrefix + VARIANT_INDEX_NAME,
+                ...elasticSearchBody,
+            },
+            { meta: true },
+        );
 
-        const { aggregations } = body;
+        const { aggregations } = response.body;
         if (!aggregations) {
             throw new InternalServerError(
                 'An error occurred when querying Elasticsearch for priceRange aggregations',
             );
         }
-        return aggregations.total ? aggregations.total.value : 0;
+        return aggregations.total ? Number((aggregations.total as any).value) : 0;
     }
 
     /**
@@ -412,19 +449,22 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             };
         }
 
-        let body: SearchResponseBody<VariantIndexItem>;
+        let body;
         try {
-            const result = await this.client.search<SearchResponseBody<VariantIndexItem>>({
-                index: indexPrefix + VARIANT_INDEX_NAME,
-                body: elasticSearchBody,
-            });
+            const result = await this.client.search<SearchResponseBody<VariantIndexItem>>(
+                {
+                    index: indexPrefix + VARIANT_INDEX_NAME,
+                    ...elasticSearchBody,
+                },
+                { meta: true },
+            );
             body = result.body;
         } catch (e: any) {
             Logger.error(e.message, loggerCtx, e.stack);
             throw e;
         }
 
-        return body.aggregations ? body.aggregations.aggregation_field.buckets : [];
+        return body.aggregations ? (body.aggregations.aggregation_field as any).buckets : [];
     }
 
     async priceRange(ctx: RequestContext, input: ElasticSearchInput): Promise<SearchPriceData> {
@@ -473,12 +513,15 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
                 },
             },
         };
-        const { body }: { body: SearchResponseBody<VariantIndexItem> } = await this.client.search({
-            index: indexPrefix + VARIANT_INDEX_NAME,
-            body: elasticSearchBody,
-        });
+        const result = await this.client.search(
+            {
+                index: indexPrefix + VARIANT_INDEX_NAME,
+                ...elasticSearchBody,
+            },
+            { meta: true },
+        );
 
-        const { aggregations } = body;
+        const { aggregations } = result.body;
         if (!aggregations) {
             throw new InternalServerError(
                 'An error occurred when querying Elasticsearch for priceRange aggregations',
@@ -491,15 +534,19 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
 
         return {
             range: {
-                min: aggregations.minPrice.value || 0,
-                max: aggregations.maxPrice.value || 0,
+                min: (aggregations.minPrice as any).value || 0,
+                max: (aggregations.maxPrice as any).value || 0,
             },
             rangeWithTax: {
-                min: aggregations.minPriceWithTax.value || 0,
-                max: aggregations.maxPriceWithTax.value || 0,
+                min: (aggregations.minPriceWithTax as any).value || 0,
+                max: (aggregations.maxPriceWithTax as any).value || 0,
             },
-            buckets: aggregations.prices.buckets.map(mapPriceBuckets).filter(x => 0 < x.count),
-            bucketsWithTax: aggregations.pricesWithTax.buckets.map(mapPriceBuckets).filter(x => 0 < x.count),
+            buckets: (aggregations.prices as any).buckets
+                .map(mapPriceBuckets)
+                .filter((x: { count: number }) => 0 < x.count),
+            bucketsWithTax: (aggregations.pricesWithTax as any).buckets
+                .map(mapPriceBuckets)
+                .filter((x: { count: number }) => 0 < x.count),
         };
     }
 
@@ -544,9 +591,13 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             'variant',
         );
         return result;
-    } 
+    }
 
-    private mapProductToSearchResult(hit: SearchHit<VariantIndexItem>, groupByProduct: boolean = false, groupBySKU: boolean = false): ElasticSearchResult {
+    private mapProductToSearchResult(
+        hit: SearchHit<VariantIndexItem>,
+        groupByProduct: boolean = false,
+        groupBySKU: boolean = false,
+    ): ElasticSearchResult {
         const source = hit._source;
         const fields = hit.fields;
         const { productAsset, productVariantAsset } = this.getSearchResultAssets(source);

+ 62 - 36
packages/elasticsearch-plugin/src/indexing/indexer.controller.ts

@@ -30,12 +30,11 @@ import {
 import { Observable } from 'rxjs';
 import { In, IsNull } from 'typeorm';
 
-import { ELASTIC_SEARCH_OPTIONS, VARIANT_INDEX_NAME, loggerCtx } from '../constants';
+import { ELASTIC_SEARCH_OPTIONS, loggerCtx, VARIANT_INDEX_NAME } from '../constants';
 import { ElasticsearchOptions } from '../options';
 import {
     BulkOperation,
     BulkOperationDoc,
-    BulkResponseBody,
     ProductChannelMessageData,
     ProductIndexItem,
     ReindexMessageData,
@@ -377,9 +376,9 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         updateProductScript: { source: string; params?: any },
         updateVariantScript: { source: string; params?: any },
     ): Promise<boolean> {
-        const result1 = await this.client.update_by_query({
-            index: this.options.indexPrefix + indexName,
-            body: {
+        const result1 = await this.client.updateByQuery(
+            {
+                index: this.options.indexPrefix + indexName,
                 script: updateProductScript,
                 query: {
                     term: {
@@ -387,13 +386,18 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                     },
                 },
             },
-        });
-        for (const failure of result1.body.failures) {
-            Logger.error(`${failure.cause.type as string}: ${failure.cause.reason as string}`, loggerCtx);
+            { meta: true },
+        );
+
+        if (result1.body.failures) {
+            for (const failure of result1.body.failures) {
+                Logger.error(`${failure.cause.type}: ${failure.cause.reason as string}`, loggerCtx);
+            }
         }
-        const result2 = await this.client.update_by_query({
-            index: this.options.indexPrefix + indexName,
-            body: {
+
+        const result2 = await this.client.updateByQuery(
+            {
+                index: this.options.indexPrefix + indexName,
                 script: updateVariantScript,
                 query: {
                     term: {
@@ -401,11 +405,20 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                     },
                 },
             },
-        });
-        for (const failure of result1.body.failures) {
-            Logger.error(`${failure.cause.type as string}: ${failure.cause.reason as string}`, loggerCtx);
+            { meta: true },
+        );
+
+        if (result2.body.failures) {
+            for (const failure of result2.body.failures) {
+                Logger.error(`${failure.cause.type}: ${failure.cause.reason as string}`, loggerCtx);
+            }
         }
-        return result1.body.failures.length === 0 && result2.body.failures === 0;
+
+        if (result1.body.failures && result2.body.failures) {
+            return result1.body.failures.length === 0 && result2.body.failures.length === 0;
+        }
+
+        return false;
     }
 
     private async updateProductsInternal(ctx: MutableRequestContext, productIds: ID[]) {
@@ -414,20 +427,29 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
 
     private async switchAlias(reindexVariantAliasName: string, variantIndexName: string): Promise<void> {
         try {
-            const reindexVariantAliasExist = await this.client.indices.existsAlias({
-                name: reindexVariantAliasName,
-            });
+            const reindexVariantAliasExist = await this.client.indices.existsAlias(
+                {
+                    name: reindexVariantAliasName,
+                },
+                { meta: true },
+            );
             if (reindexVariantAliasExist) {
                 const reindexVariantIndexName = await getIndexNameByAlias(
                     this.client,
                     reindexVariantAliasName,
                 );
-                const originalVariantAliasExist = await this.client.indices.existsAlias({
-                    name: variantIndexName,
-                });
-                const originalVariantIndexExist = await this.client.indices.exists({
-                    index: variantIndexName,
-                });
+                const originalVariantAliasExist = await this.client.indices.existsAlias(
+                    {
+                        name: variantIndexName,
+                    },
+                    { meta: true },
+                );
+                const originalVariantIndexExist = await this.client.indices.exists(
+                    {
+                        index: variantIndexName,
+                    },
+                    { meta: true },
+                );
 
                 const originalVariantIndexName = await getIndexNameByAlias(this.client, variantIndexName);
 
@@ -460,12 +482,10 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                 }
 
                 await this.client.indices.updateAliases({
-                    body: {
-                        actions,
-                    },
+                    actions,
                 });
 
-                if (originalVariantAliasExist.body) {
+                if (originalVariantAliasExist.body && originalVariantIndexName) {
                     await this.client.indices.delete({
                         index: [originalVariantIndexName],
                     });
@@ -474,9 +494,12 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         } catch (e: any) {
             Logger.error('Could not switch indexes');
         } finally {
-            const reindexVariantAliasExist = await this.client.indices.existsAlias({
-                name: reindexVariantAliasName,
-            });
+            const reindexVariantAliasExist = await this.client.indices.existsAlias(
+                {
+                    name: reindexVariantAliasName,
+                },
+                { meta: true },
+            );
             if (reindexVariantAliasExist.body) {
                 const reindexVariantAliasResult = await this.client.indices.getAlias({
                     name: reindexVariantAliasName,
@@ -835,11 +858,14 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         }
         try {
             const fullIndexName = this.options.indexPrefix + indexName;
-            const { body }: { body: BulkResponseBody } = await this.client.bulk({
-                refresh: true,
-                index: fullIndexName,
-                body: operations,
-            });
+            const { body } = await this.client.bulk(
+                {
+                    refresh: true,
+                    index: fullIndexName,
+                    body: operations,
+                },
+                { meta: true },
+            );
 
             if (body.errors) {
                 Logger.error(

+ 15 - 19
packages/elasticsearch-plugin/src/indexing/indexing-utils.ts

@@ -70,12 +70,10 @@ export async function createIndices(
         if (mapAlias) {
             await client.indices.create({
                 index,
-                body: {
-                    mappings: {
-                        properties: mappings,
-                    },
-                    settings: indexSettings,
+                mappings: {
+                    properties: mappings,
                 },
+                settings: indexSettings,
             });
             await client.indices.putAlias({
                 index,
@@ -85,14 +83,11 @@ export async function createIndices(
         } else {
             await client.indices.create({
                 index: alias,
-                body: {
-                    mappings: {
-                        properties: mappings,
-                    },
-                    settings: indexSettings,
+                mappings: {
+                    properties: mappings,
                 },
+                settings: indexSettings,
             });
-            Logger.verbose(`Created index "${alias}"`, loggerCtx);
         }
     };
 
@@ -109,8 +104,10 @@ export async function createIndices(
 export async function deleteIndices(client: Client, prefix: string) {
     try {
         const index = await getIndexNameByAlias(client, prefix + VARIANT_INDEX_NAME);
-        await client.indices.delete({ index });
-        Logger.verbose(`Deleted index "${index}"`, loggerCtx);
+        if (index) {
+            await client.indices.delete({ index });
+            Logger.verbose(`Deleted index "${index}"`, loggerCtx);
+        }
     } catch (e: any) {
         Logger.error(e, loggerCtx);
     }
@@ -121,10 +118,8 @@ export async function deleteByChannel(client: Client, prefix: string, channelId:
         const index = prefix + VARIANT_INDEX_NAME;
         await client.deleteByQuery({
             index,
-            body: {
-                query: {
-                    match: { channelId },
-                },
+            query: {
+                match: { channelId },
             },
         });
         Logger.verbose(`Deleted index "${index} for channel "${channelId}"`, loggerCtx);
@@ -147,12 +142,13 @@ export function getClient(
 }
 
 export async function getIndexNameByAlias(client: Client, aliasName: string) {
-    const aliasExist = await client.indices.existsAlias({ name: aliasName });
+    const aliasExist = await client.indices.existsAlias({ name: aliasName }, { meta: true });
     if (aliasExist.body) {
         const alias = await client.indices.getAlias({
             name: aliasName,
         });
-        return Object.keys(alias.body)[0];
+        const keys = Object.keys(alias);
+        return keys.at(0);
     } else {
         return aliasName;
     }

+ 18 - 4
packages/elasticsearch-plugin/src/plugin.ts

@@ -63,11 +63,25 @@ function getCustomResolvers(options: ElasticsearchRuntimeOptions) {
  * to support a wide range of use-cases such as indexing of custom properties, fine control over search index configuration, and to leverage
  * advanced Elasticsearch features like spacial search.
  *
- * ## Installation
+ * **ElasticSearch v9.1.0 is supported**
  *
- * **Requires Elasticsearch v7.0 < required Elasticsearch version < 7.10 **
- * Elasticsearch version 7.10.2 will throw error due to incompatibility with elasticsearch-js client.
- * [Check here for more info](https://github.com/elastic/elasticsearch-js/issues/1519).
+ * **Important information about versions and ElasticSearch security:**
+ * The version of ElasticSearch that is deployed, the version of the JS library @elastic/elasticsearch installed in your Vendure project and the version
+ * of the JS library @elastic/elasticsearch used in the @vendure/elasticsearch-plugin must all match to avoid any issues. ElasticSearch does not allow @latest
+ * in its repository so these versions must be updated regularly.
+ * | Package  | Version |
+ * | ------------- | ------------- |
+ * | ElasticSearch  | v9.1.0  |
+ * | @elastic/elasticsearch  | v9.1.0  |
+ * | @vendure/elasticsearch-plugin | v3.5.0  |
+ * | Last updated | Aug 5, 2025 |
+ *
+ * With ElasticSearch v8+, basic authentication, SSL, and TLS are enabled by default and may result in your client and plugin not being able to connect to
+ * ElasticSearch successfully if your client is not configured appropriately. You must also set ```xpack.license.self_generated.type=basic``` if you are
+ * using the free Community Edition of ElasticSearch.
+ *
+ * Review the ElasticSearch docker [example](<https://github.com/vendure-ecommerce/vendure/blob/master/docker-compose.yml>) here for development
+ * and testing without authentication and security enabled. Refer to ElasticSearch documentation to enable authentication and security in production.
  *
  * `yarn add \@elastic/elasticsearch \@vendure/elasticsearch-plugin`
  *