Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
122 changes: 122 additions & 0 deletions api/ResponseTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

export interface Search<T = any> {
Comment thread
delvedor marked this conversation as resolved.
Outdated
took: number;
timed_out: boolean;
_scroll_id?: string;
_shards: Shards;
hits: {
total: {
value: number;
relation: string;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is only correct when the server is running Elasticsearch 7.0 and above, and only when the request did not set the property rest_total_hits_as_int:

rest_total_hits_as_int
    (Optional, boolean) Indicates whether hits.total should be rendered as an integer or an object in the rest search response. Defaults to false.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, this type definitions will land only for 7.x and above, so it shouldn't be an issue.

and only when the request did not set the property rest_total_hits_as_int:

How would you solve this? The easiest solutions that come to my mind is to use a union, in the same way I did in BulkItems.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, just for general awareness, I'd recommend looking at a discriminated union feature.

Anyway, what looks simpler to me is just using two separate overloads:

// Don't export this one, because it is likely to be removed when the legacy interface is removed
interface BasicSearchResponse {
    /* all stable fields */
}

export interface SearchResponse extends BasicSearchResponse {
    hits: {
        total: {
            value: number,
            relation: "gte" | "eq" // @delvedor I suggest using an explicit string literal union here instead of just a `string`
        }
    }
}

// legacy, thus the interface name is verbose:
export interface SearchResponseWithTotalHintsAsInt extends BasicSearchResponse {
    hits: { total: number }
}

// don't export it for the same reason as BasicSearchResponse
interface BasicSearchParams { /* all fields except for rest_total_hits_as_int */ }

export interface SearchParams extends BasicSearchParams {
    rest_total_hits_as_int?: false;
}

export interface SearchParamsWithTotalHintsAsInt extends BasicSearchParams {
    rest_total_hits_as_int: true;
}

export class Client {
    search(params: SearchParams): Promise<SearchResponse>;
    // @deprecated: please use other overload
    search(params: SearchParamsWithTotalHintsAsInt): Promise<SearchResponseWithTotalHintsAsInt>;
}

max_score: number;
hits: Array<{
_index: string;
_type: string;
_id: string;
_score: number;
_source: T;
_version?: number;
_explanation?: Explanation;
fields?: any;
highlight?: any;
inner_hits?: any;
matched_queries?: string[];
sort?: string[];
}>;
};
aggregations?: any;
}

export interface MSearch<T = any> {
responses?: Array<Search<T>>;
}

export interface Shards {
total: number;
successful: number;
failed: number;
skipped: number;
}

export interface Explanation {
value: number;
description: string;
details: Explanation[];
}

export interface Create {
_shards: Shards;
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}

export interface Index extends Create {}

export interface Delete {
_shards: Shards;
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}

export interface Update {
_shards: Shards;
_index: string;
_type: string;
_id: string;
_version: number;
result: string;
}

export interface Get<T = any> {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
found: boolean;
_source: T
}


export interface Bulk {
took: number;
errors: boolean;
items: Array<BulkItem>;
}

type BulkItem =
| { index: BulkIndex }
| { create: BulkCreate }
| { update: BulkUpdate }
| { delete: BulkDelete }

interface BulkIndex extends Index {
status: number;
}

interface BulkCreate extends Create {
status: number;
}

interface BulkUpdate extends Update {
status: number;
}

interface BulkDelete extends Delete {
status: number;
}
64 changes: 62 additions & 2 deletions docs/typescript.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

The client offers a first-class support for TypeScript, since it ships the type definitions for every exposed API.

NOTE: If you are using TypeScript you will be required to use _snake_case_ style to define the API parameters instead of _camelCase_.
NOTE: If you are using TypeScript you will be required to use _snake_case_ style to define the API parameters instead of _camelCase_.

Other than the types for the surface API, the client offers the types for every request method, via the `RequestParams`, if you need the types for a search request for instance, you can access them via `RequestParams.Search`.
Every API that supports a body, accepts a https://www.typescriptlang.org/docs/handbook/generics.html[generics] which represents the type of the request body, if you don't configure anything, it will default to `any`.
Expand Down Expand Up @@ -40,7 +40,7 @@ const searchParams: RequestParams.Search = {
}
----

You can find the type definiton of a response in `ApiResponse`, which accepts a generics as well if you want to specify the body type, otherwise it defaults to `any`.
You can find the type definition of a response in `ApiResponse`, which accepts a generics as well if you want to specify the body type, otherwise it defaults to `any`.

[source,ts]
----
Expand Down Expand Up @@ -146,3 +146,63 @@ async function run (): Promise<void> {

run().catch(console.log)
----

=== Response body definitions

Currently, there is no support for all the response definitions, the client offers only a small subset to help the users with the most commonly used APIs.

* `Index`
* `Create`
* `Update`
* `Delete`
* `Search`
* `MSearch`
* `Bulk`

_At the moment, we are not planning on expanding more the types offered out of the box since we are studying a more structured approach that will allow us to generate all the response type definitions automatically._

You can access the response type definitions via the `ResponseParams`.
Every API that contains a `_source` object accepts a https://www.typescriptlang.org/docs/handbook/generics.html[generics] which represents the type of the `_source` object, if you don't configure anything, it will default to `any`.

The example you saw above can now be rewritten as follows:
[source,ts]
----
import {
Client,
RequestParams,
ResponseParams,
ApiResponse,
} from '@elastic/elasticsearch'

const client = new Client({ node: 'http://localhost:9200' })

// Define the type of the body for the Search request
interface SearchBody {
query: {
match: { foo: string }
}
}

// Define the interface of the source object
interface Source {
foo: string
}

async function run (): Promise<void> {
// Define the search parameters
const searchParams: RequestParams.Search<SearchBody> = {
index: 'test',
body: {
query: {
match: { foo: 'bar' }
}
}
}

// Craft the final type definition
const response: ApiResponse<ResponseParams.Search<Source>> = await client.search(searchParams)
console.log(response.body)
}

run().catch(console.log)
----
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Connection, { AgentOptions, agentFn } from './lib/Connection';
import { ConnectionPool, ResurrectEvent, BasicAuth, ApiKeyAuth } from './lib/pool';
import Serializer from './lib/Serializer';
import * as RequestParams from './api/requestParams';
import * as ResponseParams from './api/ResponseTypes';
import * as errors from './lib/errors';

declare type anyObject = {
Expand Down Expand Up @@ -656,6 +657,7 @@ export {
RequestEvent,
ResurrectEvent,
RequestParams,
ResponseParams,
ClientOptions,
NodeOptions,
ClientExtendsCallbackOptions
Expand Down