diff --git a/docs/reference/basic-config.md b/docs/reference/basic-config.md index c1e3ec234..7b523cbeb 100644 --- a/docs/reference/basic-config.md +++ b/docs/reference/basic-config.md @@ -20,12 +20,16 @@ const client = new Client({ ### `node` or `nodes` -The Elasticsearch endpoint to use. It can be a single string or an array of strings: +The {{es}} endpoint to use. It can be a single string or an array of strings: ```js node: 'http://localhost:9200' ``` +```js +nodes: ['http://localhost:9200', 'http://localhost:9201'] +``` + Or it can be an object (or an array of objects) that represents the node: ```js @@ -52,7 +56,6 @@ Default: `null` Your authentication data. You can use both basic authentication and [ApiKey](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-api-key). See [Authentication](/reference/connecting.md#authentication) for more details. - Basic authentication: @@ -113,7 +116,7 @@ Max ping request timeout in milliseconds for each request. Type: `number, boolean`
Default: `false` -Perform a sniff operation every `n` milliseconds. +Perform a sniff operation every `n` milliseconds. :::{tip} Sniffing might not be the best solution. Before using the various `sniff` options, review this [blog post](https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how). @@ -182,7 +185,7 @@ Options: `'gzip'`, `false` Type: `http.SecureContextOptions`
Default: `null` -The [tls configuraton](https://nodejs.org/api/tls.md). +The [tls configuraton](https://nodejs.org/api/tls.html). --- @@ -192,7 +195,6 @@ Type: `string, URL`
Default: `null` If you are using an http(s) proxy, you can put its url here. The client will automatically handle the connection to it. - ```js const client = new Client({ @@ -213,7 +215,7 @@ const client = new Client({ Type: `http.AgentOptions, function`
Default: `null` -http agent [options](https://nodejs.org/api/http.md#http_new_agent_options), or a function that returns an actual http agent instance. If you want to disable the http agent use entirely (and disable the `keep-alive` feature), set the agent to `false`. +http agent [options](https://nodejs.org/api/http.html#http_new_agent_options), or a function that returns an actual http agent instance. If you want to disable the http agent use entirely (and disable the `keep-alive` feature), set the agent to `false`. ```js const client = new Client({ @@ -394,4 +396,26 @@ When configured, `maxResponseSize` verifies that the uncompressed response size Type: `number`
Default: `null` -When configured, `maxCompressedResponseSize` verifies that the compressed response size is lower than the configured number. If it’s higher, the request will be canceled. The `maxCompressedResponseSize` cannot be higher than the value of `buffer.constants.MAX_STRING_LENGTH`. \ No newline at end of file +When configured, `maxCompressedResponseSize` verifies that the compressed response size is lower than the configured number. If it’s higher, the request will be canceled. The `maxCompressedResponseSize` cannot be higher than the value of `buffer.constants.MAX_STRING_LENGTH`. + +--- + +### `redaction` + +Type: `object`
+Default: A configuration that will replace known sources of sensitive data in `Error` metadata + +Options for how to redact potentially sensitive data from metadata attached to `Error` objects + +::::{note} +[Read about redaction](/reference/advanced-config.md#redaction) for more details +:::: + +--- + +### `serverMode` + +Type: `string`
+Default: `"stack"` + +Setting to `"stack"` sets defaults assuming a traditional (non-serverless) {{es}} instance. Setting to `"serverless"` sets defaults to work more seamlessly with [Elastic Cloud Serverless](https://www.elastic.co/guide/en/serverless/current/intro.html), like enabling compression and disabling features that assume the possibility of multiple {{es}} nodes. diff --git a/docs/reference/client-helpers.md b/docs/reference/client-helpers.md index 38c29198e..c80562db4 100644 --- a/docs/reference/client-helpers.md +++ b/docs/reference/client-helpers.md @@ -11,15 +11,12 @@ The client comes with an handy collection of helpers to give you a more comforta The client helpers are experimental, and the API may change in the next minor releases. The helpers will not work in any Node.js version lower than 10. :::: - - ## Bulk helper [bulk-helper] Added in `v7.7.0` Running bulk requests can be complex due to the shape of the API, this helper aims to provide a nicer developer experience around the Bulk API. - ### Usage [_usage_3] ```js @@ -67,10 +64,8 @@ To create a new instance of the Bulk helper, access it as shown in the example a | `wait` | How much time to wait before retries in milliseconds.
*Default:* 5000.

```js
const b = client.helpers.bulk({
wait: 3000
})
```
| | `refreshOnCompletion` | If `true`, at the end of the bulk operation it runs a refresh on all indices or on the specified indices.
*Default:* false.

```js
const b = client.helpers.bulk({
refreshOnCompletion: true
// or
refreshOnCompletion: 'index-name'
})
```
| - ### Supported operations [_supported_operations] - #### Index [_index_2] ```js @@ -84,7 +79,6 @@ client.helpers.bulk({ }) ``` - #### Create [_create_4] ```js @@ -98,7 +92,6 @@ client.helpers.bulk({ }) ``` - #### Update [_update_3] ```js @@ -116,7 +109,6 @@ client.helpers.bulk({ }) ``` - #### Delete [_delete_10] ```js @@ -130,7 +122,6 @@ client.helpers.bulk({ }) ``` - ### Abort a bulk operation [_abort_a_bulk_operation] If needed, you can abort a bulk operation at any time. The bulk helper returns a [thenable](https://promisesaplus.com/), which has an `abort` method. @@ -139,7 +130,6 @@ If needed, you can abort a bulk operation at any time. The bulk helper returns a The abort method stops the execution of the bulk operation, but if you are using a concurrency higher than one, the operations that are already running will not be stopped. :::: - ```js const { createReadStream } = require('fs') const split = require('split2') @@ -164,7 +154,6 @@ const b = client.helpers.bulk({ console.log(await b) ``` - ### Passing custom options to the Bulk API [_passing_custom_options_to_the_bulk_api] You can pass any option supported by the link: [Bulk API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk) to the helper, and the helper uses those options in conjunction with the Bulk API call. @@ -181,7 +170,6 @@ const result = await client.helpers.bulk({ }) ``` - ### Usage with an async generator [_usage_with_an_async_generator] ```js @@ -214,7 +202,6 @@ const result = await client.helpers.bulk({ console.log(result) ``` - ### Modifying a document before operation [_modifying_a_document_before_operation] Added in `v8.8.2` @@ -241,14 +228,12 @@ const result = await client.helpers.bulk({ console.log(result) ``` - ## Multi search helper [multi-search-helper] Added in `v7.8.0` If you send search request at a high rate, this helper might be useful for you. It uses the multi search API under the hood to batch the requests and improve the overall performances of your application. The `result` exposes a `documents` property as well, which allows you to access directly the hits sources. - ### Usage [_usage_4] ```js @@ -278,7 +263,6 @@ To create a new instance of the multi search (msearch) helper, you should access | `retries` | How many times an operation is retried before to resolve the request. An operation is retried only in case of a 429 error.
*Default:* Client max retries.

```js
const m = client.helpers.msearch({
retries: 3
})
```
| | `wait` | How much time to wait before retries in milliseconds.
*Default:* 5000.

```js
const m = client.helpers.msearch({
wait: 3000
})
```
| - ### Stopping the msearch helper [_stopping_the_msearch_helper] If needed, you can stop an msearch processor at any time. The msearch helper returns a [thenable](https://promisesaplus.com/), which has an `stop` method. @@ -291,7 +275,6 @@ The `stop` method accepts an optional error, that will be dispatched every subse The stop method stops the execution of the msearch processor, but if you are using a concurrency higher than one, the operations that are already running will not be stopped. :::: - ```js const { Client } = require('@elastic/elasticsearch') @@ -318,7 +301,6 @@ m.search( setImmediate(() => m.stop()) ``` - ## Search helper [search-helper] Added in `v7.7.0` @@ -340,7 +322,6 @@ for (const doc of documents) { } ``` - ## Scroll search helper [scroll-search-helper] Added in `v7.7.0` @@ -362,7 +343,6 @@ for await (const result of scrollSearch) { } ``` - ### Clear a scroll search [_clear_a_scroll_search] If needed, you can clear a scroll search by calling `result.clear()`: @@ -375,7 +355,6 @@ for await (const result of scrollSearch) { } ``` - ### Quickly getting the documents [_quickly_getting_the_documents] If you only need the documents from the result of a scroll search, you can access them via `result.documents`: @@ -386,7 +365,6 @@ for await (const result of scrollSearch) { } ``` - ## Scroll documents helper [scroll-documents-helper] Added in `v7.7.0` @@ -408,15 +386,12 @@ for await (const doc of scrollSearch) { } ``` - ## ES|QL helper [esql-helper] ES|QL queries can return their results in [several formats](docs-content://explore-analyze/query-filter/languages/esql-rest.md#esql-rest-format). The default JSON format returned by ES|QL queries contains arrays of values for each row, with column names and types returned separately: - ### Usage [_usage_5] - #### `toRecords` [_torecords] Added in `v8.14.0` @@ -494,7 +469,6 @@ const result = await client.helpers .toRecords() ``` - #### `toArrowReader` [_toarrowreader] Added in `v8.16.0` @@ -516,7 +490,6 @@ for (const recordBatch of reader) { } ``` - #### `toArrowTable` [_toarrowtable] Added in `v8.16.0` diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 0367bdc12..d6519a589 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -7,13 +7,8 @@ mapped_pages: The client is designed to be easily configured for your needs. In the following section, you can see the possible options that you can use to configure it. -* [Basic configuration](/reference/basic-config.md) -* [Advanced configuration](/reference/advanced-config.md) -* [Timeout best practices](docs-content://troubleshoot/elasticsearch/elasticsearch-client-javascript-api/nodejs.md) -* [Creating a child client](/reference/child.md) -* [Testing](/reference/client-testing.md) - - - - - +- [Basic configuration](/reference/basic-config.md) +- [Advanced configuration](/reference/advanced-config.md) +- [Timeout best practices](docs-content://troubleshoot/elasticsearch/elasticsearch-client-javascript-api/nodejs.md) +- [Creating a child client](/reference/child.md) +- [Testing](/reference/client-testing.md) diff --git a/docs/reference/connecting.md b/docs/reference/connecting.md index 72eab6b5c..887dc587d 100644 --- a/docs/reference/connecting.md +++ b/docs/reference/connecting.md @@ -11,7 +11,6 @@ This page contains the information you need to connect and use the Client with { This document contains code snippets to show you how to connect to various {{es}} providers. - ### Elastic Cloud [auth-ec] If you are using [Elastic Cloud](https://www.elastic.co/cloud), the client offers an easy way to connect to it via the `cloud` option. You must pass the Cloud ID that you can find in the cloud console, then your username and password inside the `auth` option. @@ -20,12 +19,10 @@ If you are using [Elastic Cloud](https://www.elastic.co/cloud), the client offer When connecting to Elastic Cloud, the client will automatically enable both request and response compression by default, since it yields significant throughput improvements. Moreover, the client will also set the tls option `secureProtocol` to `TLSv1_2_method` unless specified otherwise. You can still override this option by configuring them. :::: - ::::{important} Do not enable sniffing when using Elastic Cloud, since the nodes are behind a load balancer, Elastic Cloud will take care of everything for you. Take a look [here](https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how) to know more. :::: - ```js const { Client } = require('@elastic/elasticsearch') const client = new Client({ @@ -39,6 +36,24 @@ const client = new Client({ }) ``` +## Connecting to an Elastic Cloud Serverless instance [connect-serverless] + +The Node.js client is built to support connecting to [Elastic Cloud Serverless](https://www.elastic.co/guide/en/serverless/current/intro.html). By setting the `serverMode` option to `"serverless"`, several default options will be modified to better suit the serverless environment. + +```js +const { Client } = require('@elastic/elasticsearch') +const client = new Client({ + cloud: { + id: '' + }, + auth: { + username: 'elastic', + password: 'changeme' + }, + serverMode: 'serverless' +}) + +``` ## Connecting to a self-managed cluster [connect-self-managed-new] @@ -62,7 +77,6 @@ When you start {{es}} for the first time you’ll see a distinct block like the Depending on the circumstances there are two options for verifying the HTTPS connection, either verifying with the CA certificate itself or via the HTTP CA certificate fingerprint. - ### TLS configuration [auth-tls] The generated root CA certificate can be found in the `certs` directory in your {{es}} config location (`$ES_CONF_PATH/certs/http_ca.crt`). If you’re running {{es}} in Docker there is [additional documentation for retrieving the CA certificate](docs-content://deploy-manage/deploy/self-managed/install-elasticsearch-with-docker.md). @@ -84,7 +98,6 @@ const client = new Client({ }) ``` - ### CA fingerprint [auth-ca-fingerprint] You can configure the client to only trust certificates that are signed by a specific CA certificate (CA certificate pinning) by providing a `caFingerprint` option. This will verify that the fingerprint of the CA certificate that has signed the certificate of the server matches the supplied value. You must configure a SHA256 digest. @@ -125,14 +138,12 @@ The output of `openssl x509` will look something like this: SHA256 Fingerprint=A5:2D:D9:35:11:E8:C6:04:5E:21:F1:66:54:B7:7C:9E:E0:F3:4A:EA:26:D9:F4:03:20:B5:31:C4:74:67:62:28 ``` - ## Connecting without security enabled [connect-no-security] ::::{warning} Running {{es}} without security enabled is not recommended. :::: - If your cluster is configured with [security explicitly disabled](elasticsearch://reference/elasticsearch/configuration-reference/security-settings.md) then you can connect via HTTP: ```js @@ -142,12 +153,10 @@ const client = new Client({ }) ``` - ## Authentication strategies [auth-strategies] Following you can find all the supported authentication strategies. - ### ApiKey authentication [auth-apikey] You can use the [ApiKey](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-api-key) authentication by passing the `apiKey` parameter via the `auth` option. The `apiKey` parameter can be either a base64 encoded string or an object with the values that you can obtain from the [create api key endpoint](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-api-key). @@ -156,7 +165,6 @@ You can use the [ApiKey](https://www.elastic.co/docs/api/doc/elasticsearch/opera If you provide both basic authentication credentials and the ApiKey configuration, the ApiKey takes precedence. :::: - ```js const { Client } = require('@elastic/elasticsearch') const client = new Client({ @@ -180,7 +188,6 @@ const client = new Client({ }) ``` - ### Bearer authentication [auth-bearer] You can provide your credentials by passing the `bearer` token parameter via the `auth` option. Useful for [service account tokens](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-service-token). Be aware that it does not handle automatic token refresh. @@ -195,7 +202,6 @@ const client = new Client({ }) ``` - ### Basic authentication [auth-basic] You can provide your credentials by passing the `username` and `password` parameters via the `auth` option. @@ -204,7 +210,6 @@ You can provide your credentials by passing the `username` and `password` parame If you provide both basic authentication credentials and the Api Key configuration, the Api Key will take precedence. :::: - ```js const { Client } = require('@elastic/elasticsearch') const client = new Client({ @@ -225,7 +230,6 @@ const client = new Client({ }) ``` - ## Usage [client-usage] Using the client is straightforward, it supports all the public APIs of {{es}}, and every method exposes the same signature. @@ -278,8 +282,6 @@ In this case, the result will be: The body is a boolean value when you use `HEAD` APIs. :::: - - ### Aborting a request [_aborting_a_request] If needed, you can abort a running request by using the `AbortController` standard. @@ -288,7 +290,6 @@ If needed, you can abort a running request by using the `AbortController` standa If you abort a request, the request will fail with a `RequestAbortedError`. :::: - ```js const AbortController = require('node-abort-controller') const { Client } = require('@elastic/elasticsearch') @@ -308,7 +309,6 @@ const result = await client.search({ }, { signal: abortController.signal }) ``` - ### Request specific options [_request_specific_options] If needed you can pass request specific options in a second object: @@ -352,7 +352,6 @@ The supported request specific options are: This section illustrates the best practices for leveraging the {{es}} client in a Function-as-a-Service (FaaS) environment. The most influential optimization is to initialize the client outside of the function, the global scope. This practice does not only improve performance but also enables background functionality as – for example – [sniffing](https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how). The following examples provide a skeleton for the best practices. - ### GCP Cloud Functions [_gcp_cloud_functions] ```js @@ -369,7 +368,6 @@ exports.testFunction = async function (req, res) { } ``` - ### AWS Lambda [_aws_lambda] ```js @@ -386,7 +384,6 @@ exports.handler = async function (event, context) { } ``` - ### Azure Functions [_azure_functions] ```js @@ -410,7 +407,6 @@ Resources used to assess these recommendations: * [Azure Functions Python developer guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=azurecli-linux%2Capplication-level#global-variables) * [AWS Lambda: Comparing the effect of global scope](https://docs.aws.amazon.com/lambda/latest/operatorguide/global-scope.html) - ## Connecting through a proxy [client-connect-proxy] Added in `v7.10.0` @@ -421,7 +417,6 @@ If you need to pass through an http(s) proxy for connecting to {{es}}, the clien In versions 8.0+ of the client, the default `Connection` type is set to `UndiciConnection`, which does not support proxy configurations. To use a proxy, you will need to use the `HttpConnection` class from `@elastic/transport` instead. :::: - ```js import { HttpConnection } from '@elastic/transport' @@ -455,7 +450,6 @@ const client = new Client({ }) ``` - ## Error handling [client-error-handling] The client exposes a variety of error objects that you can use to enhance your error handling. You can find all the error objects inside the `errors` key in the client. @@ -506,7 +500,6 @@ const client = new Client({ }) ``` - ## Closing a client’s connections [close-connections] If you would like to close all open connections being managed by an instance of the client, use the `close()` function: @@ -518,7 +511,6 @@ const client = new Client({ client.close(); ``` - ## Automatic product check [product-check] Since v7.14.0, the client performs a required product check before the first call. This pre-flight product check allows the client to establish the version of Elasticsearch that it is communicating with. The product check requires one additional HTTP request to be sent to the server as part of the request pipeline before the main API call is sent. In most cases, this will succeed during the very first API call that the client sends. Once the product check completes, no further product check HTTP requests are sent for subsequent API calls. diff --git a/src/client.ts b/src/client.ts index 43f78a6e5..a50670ca3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -10,6 +10,7 @@ import buffer from 'node:buffer' import os from 'node:os' import { Transport, + TransportOptions, UndiciConnection, WeightedConnectionPool, CloudConnectionPool, @@ -54,6 +55,8 @@ if (transportVersion.includes('-')) { } const nodeVersion = process.versions.node +const serverlessApiVersion = '2023-10-31' + export interface NodeOptions { /** @property url Elasticsearch node's location */ url: URL @@ -180,6 +183,9 @@ export interface ClientOptions { * @remarks Read https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/advanced-config.html#redaction for more details * @defaultValue Configuration that will replace known sources of sensitive data */ redaction?: RedactionOptions + /** @property serverMode Setting to "serverless" will change some default behavior, like enabling compression and disabling features that assume the possibility of multiple Elasticsearch nodes. + * @defaultValue "stack", which sets defaults for a traditional (non-serverless) Elasticsearch instance. */ + serverMode?: 'stack' | 'serverless' } export default class Client extends API { @@ -192,15 +198,18 @@ export default class Client extends API { constructor (opts: ClientOptions) { super() - // @ts-expect-error kChild symbol is for internal use only - if ((opts.cloud != null) && opts[kChild] === undefined) { - const { id } = opts.cloud - // the cloud id is `cluster-name:base64encodedurl` - // the url is a string divided by two '$', the first is the cloud url - // the second the elasticsearch instance, the third the kibana instance - const cloudUrls = Buffer.from(id.split(':')[1], 'base64').toString().split('$') - opts.node = `https://${cloudUrls[1]}.${cloudUrls[0]}` + // @ts-expect-error kChild symbol is for internal use only + if ((opts.cloud != null || opts.serverMode === 'serverless') && opts[kChild] === undefined) { + if (opts.cloud != null) { + const { id } = opts.cloud + // the cloud id is `cluster-name:base64encodedurl` + // the url is a string divided by two '$', the first is the cloud url + // the second the elasticsearch instance, the third the kibana instance + const cloudUrls = Buffer.from(id.split(':')[1], 'base64').toString().split('$') + + opts.node = `https://${cloudUrls[1]}.${cloudUrls[0]}` + } // Cloud has better performance with compression enabled // see https://github.com/elastic/elasticsearch-py/pull/704. @@ -225,11 +234,16 @@ export default class Client extends API { } } + const headers: Record = { + 'user-agent': `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${nodeVersion}; Transport ${transportVersion})` + } + if (opts.serverMode === 'serverless') headers['elastic-api-version'] = serverlessApiVersion + const options: Required = Object.assign({}, { Connection: UndiciConnection, - Transport: SniffingTransport, + Transport: opts.serverMode === 'serverless' ? Transport : SniffingTransport, Serializer, - ConnectionPool: (opts.cloud != null) ? CloudConnectionPool : WeightedConnectionPool, + ConnectionPool: (opts.cloud != null || opts.serverMode === 'serverless') ? CloudConnectionPool : WeightedConnectionPool, maxRetries: 3, pingTimeout: 3000, sniffInterval: false, @@ -241,9 +255,7 @@ export default class Client extends API { tls: null, caFingerprint: null, agent: null, - headers: { - 'user-agent': `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${nodeVersion}; Transport ${transportVersion})` - }, + headers, nodeFilter: null, generateRequestId: null, name: 'elasticsearch-js', @@ -257,7 +269,8 @@ export default class Client extends API { redaction: { type: 'replace', additionalKeys: [] - } + }, + serverMode: 'stack' }, opts) if (options.caFingerprint != null && isHttpConnection(opts.node ?? opts.nodes)) { @@ -326,7 +339,13 @@ export default class Client extends API { // ensure default connection values are inherited when creating new connections // see https://github.com/elastic/elasticsearch-js/issues/1791 - const nodes = options.node ?? options.nodes + let nodes = options.node ?? options.nodes + + // serverless only supports one node, so pick the first one + if (options.serverMode === 'serverless' && Array.isArray(nodes)) { + nodes = nodes[0] + } + let nodeOptions: Array = Array.isArray(nodes) ? nodes : [nodes] type ConnectionDefaults = Record nodeOptions = nodeOptions.map(opt => { @@ -354,20 +373,14 @@ export default class Client extends API { this.connectionPool.addConnection(nodeOptions) } - this.transport = new options.Transport({ + let transportOptions: TransportOptions = { diagnostic: this.diagnostic, connectionPool: this.connectionPool, serializer: this.serializer, maxRetries: options.maxRetries, requestTimeout: options.requestTimeout, - sniffInterval: options.sniffInterval, - sniffOnStart: options.sniffOnStart, - sniffOnConnectionFault: options.sniffOnConnectionFault, - sniffEndpoint: options.sniffEndpoint, compression: options.compression, headers: options.headers, - nodeFilter: options.nodeFilter, - nodeSelector: options.nodeSelector, generateRequestId: options.generateRequestId, name: options.name, opaqueIdPrefix: options.opaqueIdPrefix, @@ -375,13 +388,25 @@ export default class Client extends API { productCheck: 'Elasticsearch', maxResponseSize: options.maxResponseSize, maxCompressedResponseSize: options.maxCompressedResponseSize, - vendoredHeaders: { - jsonContentType: 'application/vnd.elasticsearch+json; compatible-with=9', - ndjsonContentType: 'application/vnd.elasticsearch+x-ndjson; compatible-with=9', - accept: 'application/vnd.elasticsearch+json; compatible-with=9,text/plain' - }, redaction: options.redaction - }) + } + if (options.serverMode !== 'serverless') { + transportOptions = Object.assign({}, transportOptions, { + sniffInterval: options.sniffInterval, + sniffOnStart: options.sniffOnStart, + sniffOnConnectionFault: options.sniffOnConnectionFault, + sniffEndpoint: options.sniffEndpoint, + nodeFilter: options.nodeFilter, + nodeSelector: options.nodeSelector, + vendoredHeaders: { + jsonContentType: 'application/vnd.elasticsearch+json; compatible-with=9', + ndjsonContentType: 'application/vnd.elasticsearch+x-ndjson; compatible-with=9', + accept: 'application/vnd.elasticsearch+json; compatible-with=9,text/plain' + } + }) + } + + this.transport = new options.Transport(transportOptions) this.helpers = new Helpers({ client: this, diff --git a/test/unit/client.test.ts b/test/unit/client.test.ts index 3da9a8842..e57f4d092 100644 --- a/test/unit/client.test.ts +++ b/test/unit/client.test.ts @@ -9,7 +9,7 @@ import { setTimeout } from 'node:timers/promises' import { test } from 'tap' import FakeTimers from '@sinonjs/fake-timers' import { buildServer, connection } from '../utils' -import { Client, errors } from '../..' +import { Client, errors, SniffingTransport } from '../..' import * as symbols from '@elastic/transport/lib/symbols' import { BaseConnectionPool, CloudConnectionPool, WeightedConnectionPool, HttpConnection } from '@elastic/transport' @@ -558,3 +558,68 @@ test('disablePrototypePoisoningProtection is true by default', async t => { constructorAction: 'ignore' }) }) + +test('serverless defaults', t => { + t.test('uses CloudConnectionPool by default', t => { + const client = new Client({ node: 'http://localhost:9200', serverMode: 'serverless' }) + t.ok(client.connectionPool instanceof CloudConnectionPool) + t.equal(client.connectionPool.size, 1) + t.end() + }) + + t.test('selects one node if multiple are provided', t => { + const client = new Client({ nodes: ['http://localhost:9200', 'http://localhost:9201'], serverMode: 'serverless' }) + t.equal(client.connectionPool.size, 1) + t.end() + }) + + t.test('uses TLSv1_2_method by default', t => { + const client = new Client({ + node: 'https://localhost:9200', + serverMode: 'serverless', + auth: { + username: 'elastic', + password: 'changeme' + } + }) + + const connection = client.connectionPool.connections.find(c => c.id === 'https://localhost:9200/') + + t.equal(connection?.headers?.authorization, `Basic ${Buffer.from('elastic:changeme').toString('base64')}`) + t.same(connection?.tls, { secureProtocol: 'TLSv1_2_method' }) + t.equal(connection?.url.hostname, 'localhost') + t.equal(connection?.url.protocol, 'https:') + + t.end() + }) + + t.test('elastic-api-version header exists on all requests', async t => { + t.plan(1) + + const Connection = connection.buildMockConnection({ + onRequest (opts) { + t.equal(opts.headers?.['elastic-api-version'], '2023-10-31') + return { + statusCode: 200, + body: { hello: 'world' } + } + } + }) + + const client = new Client({ + node: 'http://localhost:9200', + serverMode: 'serverless', + Connection, + }) + + await client.transport.request({ method: 'GET', path: '/' }) + }) + + t.test('sniffing transport not used', t => { + const client = new Client({ node: 'http://localhost:9200', serverMode: 'serverless' }) + t.ok(!(client.transport instanceof SniffingTransport)) + t.end() + }) + + t.end() +})