Improve typings (#813)

The ApiResponse now accepts a generics that defaults to any, same for every API method that might need a body.
This commit is contained in:
Tomas Della Vedova
2019-04-10 11:40:25 +02:00
committed by delvedor
parent 48233503f7
commit 48c6ad15a6
7 changed files with 832 additions and 171 deletions

View File

@ -0,0 +1,125 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict'
/* eslint camelcase: 0 */
/* eslint no-unused-vars: 0 */
function buildDataFrameGetDataFrameTransformStats (opts) {
// eslint-disable-next-line no-unused-vars
const { makeRequest, ConfigurationError, handleError } = opts
/**
* Perform a [data_frame.get_data_frame_transform_stats](https://www.elastic.co/guide/en/elasticsearch/reference/current/get-data-frame-transform-stats.html) request
*
* @param {string} transform_id - The id of the transform for which to get stats. '_all' or '*' implies all transforms
* @param {number} from - skips a number of transform stats, defaults to 0
* @param {number} size - specifies a max number of transform stats to get, defaults to 100
*/
const acceptedQuerystring = [
'from',
'size'
]
const snakeCase = {
}
return function dataFrameGetDataFrameTransformStats (params, options, callback) {
options = options || {}
if (typeof options === 'function') {
callback = options
options = {}
}
if (typeof params === 'function' || params == null) {
callback = params
params = {}
options = {}
}
// check required parameters
if (params.body != null) {
const err = new ConfigurationError('This API does not require a body')
return handleError(err, callback)
}
// validate headers object
if (options.headers != null && typeof options.headers !== 'object') {
const err = new ConfigurationError(`Headers should be an object, instead got: ${typeof options.headers}`)
return handleError(err, callback)
}
var warnings = null
var { method, body, transformId, transform_id } = params
var querystring = semicopy(params, ['method', 'body', 'transformId', 'transform_id'])
if (method == null) {
method = 'GET'
}
var ignore = options.ignore || null
if (typeof ignore === 'number') {
ignore = [ignore]
}
var path = ''
path = '/' + '_data_frame' + '/' + 'transforms' + '/' + encodeURIComponent(transform_id || transformId) + '/' + '_stats'
// build request object
const request = {
method,
path,
body: null,
querystring
}
const requestOptions = {
ignore,
requestTimeout: options.requestTimeout || null,
maxRetries: options.maxRetries || null,
asStream: options.asStream || false,
headers: options.headers || null,
querystring: options.querystring || null,
compression: options.compression || false,
warnings
}
return makeRequest(request, requestOptions, callback)
function semicopy (obj, exclude) {
var target = {}
var keys = Object.keys(obj)
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i]
if (exclude.indexOf(key) === -1) {
target[snakeCase[key] || key] = obj[key]
if (acceptedQuerystring.indexOf(snakeCase[key] || key) === -1) {
warnings = warnings || []
warnings.push('Client - Unknown parameter: "' + key + '", sending it as query parameter')
}
}
}
return target
}
}
}
module.exports = buildDataFrameGetDataFrameTransformStats

512
api/requestParams.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -4289,6 +4289,149 @@ link:http://www.elastic.co/guide/en/elasticsearch/reference/current[Reference]
|===
<<<<<<< HEAD
=======
=== dataFrame.deleteDataFrameTransform
[source,js]
----
client.dataFrame.deleteDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/delete-data-frame-transform.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id of the transform to delete
|===
=== dataFrame.getDataFrameTransform
[source,js]
----
client.dataFrame.getDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/get-data-frame-transform.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id or comma delimited list of id expressions of the transforms to get, '_all' or '*' implies get all transforms
|`from`
|`number` - skips a number of transform configs, defaults to 0
|`size`
|`number` - specifies a max number of transforms to get, defaults to 100
|===
=== dataFrame.getDataFrameTransformStats
[source,js]
----
client.dataFrame.getDataFrameTransformStats([params] [, options] [, callback])
----
link:{ref}/get-data-frame-transform-stats.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id of the transform for which to get stats. '_all' or '*' implies all transforms
|`from`
|`number` - skips a number of transform stats, defaults to 0
|`size`
|`number` - specifies a max number of transform stats to get, defaults to 100
|===
=== dataFrame.previewDataFrameTransform
[source,js]
----
client.dataFrame.previewDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/preview-data-frame-transform.html[Reference]
[cols=2*]
|===
|`body`
|`object` - The definition for the data_frame transform to preview
|===
=== dataFrame.putDataFrameTransform
[source,js]
----
client.dataFrame.putDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/put-data-frame-transform.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id of the new transform.
|`body`
|`object` - The data frame transform definition
|===
=== dataFrame.startDataFrameTransform
[source,js]
----
client.dataFrame.startDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/start-data-frame-transform.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id of the transform to start
|`timeout`
|`string` - Controls the time to wait for the transform to start
|===
=== dataFrame.stopDataFrameTransform
[source,js]
----
client.dataFrame.stopDataFrameTransform([params] [, options] [, callback])
----
link:{ref}/stop-data-frame-transform.html[Reference]
[cols=2*]
|===
|`transform_id` or `transformId`
|`string` - The id of the transform to stop
|`wait_for_completion` or `waitForCompletion`
|`boolean` - Whether to wait for the transform to fully stop before returning or not. Default to false
|`timeout`
|`string` - Controls the time to wait until the transform has stopped. Default to 30 seconds
|===
=== graph.explore
[source,js]
----
client.graph.explore([params] [, options] [, callback])
----
link:{ref}/graph-explore-api.html[Reference]
[cols=2*]
|===
|`index`
|`string, string[]` - A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
|`type`
|`string, string[]` - A comma-separated list of document types to search; leave empty to perform the operation on all types
|`routing`
|`string` - Specific routing value
|`timeout`
|`string` - Explicit operation timeout
|`body`
|`object` - Graph Query DSL
|===
>>>>>>> a21281f... Improve typings (#813)
=== ilm.deleteLifecycle
[source,js]
----

View File

@ -3,58 +3,146 @@
The client offers a first-class support for TypeScript, since it ships the type definitions for every exposed API.
While the client offers type definitions for Request parameters, Request bodies and responses are shipped with `any` because there is not an official spec that defines them, so we cannot make guarantees over them (but since they are shipped with `any`, you can easily override them with your own typing definitions).
NOTE: If you are using TypeScript you will be required to use _snake_case_ style to define the API parameters instead of _camelCase_.
=== How to extend the provided typings?
Extend the provided typings is very straightforward, you should declare a custom `.d.ts` file and then write inside your type extensions, following there is an example of how do it.
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`.
For example:
[source,ts]
----
declare module '@elastic/elasticsearch' {
export interface ShardsResponse {
total: number;
successful: number;
failed: number;
skipped: number;
}
import { RequestParams } from '@elastic/elasticsearch'
export interface Explanation {
value: number;
description: string;
details: Explanation[];
}
export interface SearchResponse<T> {
took: number;
timed_out: boolean;
_scroll_id?: string;
_shards: ShardsResponse;
hits: {
total: number;
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 MSearchResponse<T> {
responses?: Array<SearchResponse<T>>;
interface SearchBody {
query: {
match: { foo: string }
}
}
export {};
const searchParams: RequestParams.Search<SearchBody> = {
index: 'test',
body: {
query: {
match: { foo: 'bar' }
}
}
}
// This is valid as well
const searchParams: RequestParams.Search = {
index: 'test',
body: {
query: {
match: { foo: 'bar' }
}
}
}
----
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`.
[source,ts]
----
interface SearchResponse<T> {
hits: {
hits: Array<{
_source: T;
}>
}
}
// Define the intefrace of the source object
interface Source {
foo: string
}
client.search(searchParams)
.then((response: ApiResponse<SearchResponse<Source>>) => console.log(response))
.catch((err: Error) => {})
----
=== A complete example
[source,ts]
----
import {
Client,
// Object that contains the type definitions of every API method
RequestParams,
// Interface of the generic API response
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 }
}
}
// Complete definition of the Search response
interface ShardsResponse {
total: number;
successful: number;
failed: number;
skipped: number;
}
interface Explanation {
value: number;
description: string;
details: Explanation[];
}
interface SearchResponse<T> {
took: number;
timed_out: boolean;
_scroll_id?: string;
_shards: ShardsResponse;
hits: {
total: number;
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;
}
// Define the intefrace 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<SearchResponse<Source>> = await client.search(searchParams)
console.log(response.body)
}
run().catch(console.log)
----

4
lib/Transport.d.ts vendored
View File

@ -49,7 +49,7 @@ interface TransportOptions {
headers?: anyObject;
}
export interface RequestEvent {
export interface RequestEvent<T = any> {
body: any;
statusCode: number | null;
headers: anyObject | null;
@ -71,7 +71,7 @@ export interface RequestEvent {
// ApiResponse and RequestEvent are the same thing
// we are doing this for have more clear names
export interface ApiResponse extends RequestEvent {}
export interface ApiResponse<T = any> extends RequestEvent<T> {}
declare type anyObject = {
[key: string]: any;

View File

@ -74,10 +74,10 @@ export interface Generic {
}
const code = `
export interface ${name[0].toUpperCase() + name.slice(1)} extends Generic {
export interface ${name[0].toUpperCase() + name.slice(1)}${body ? '<T = any>' : ''} extends Generic {
${partsArr.map(genLine).join('\n ')}
${paramsArr.map(genLine).join('\n ')}
${body ? `body${body.required ? '' : '?'}: any;` : ''}
${body ? `body${body.required ? '' : '?'}: T;` : ''}
}
`

View File

@ -22,6 +22,7 @@
import {
Client,
ApiResponse,
RequestParams,
RequestEvent,
ResurrectEvent,
events,
@ -94,6 +95,40 @@ client.index({
.then((result: ApiResponse) => {})
.catch((err: Error) => {})
// --- Use generics ---
// Define the search parameters
interface SearchBody {
query: {
match: { foo: string }
}
}
const searchParams: RequestParams.Search<SearchBody> = {
index: 'test',
body: {
query: {
match: { foo: 'bar' }
}
}
}
// Dewfine the interface of the search response
interface SearchResponse<T> {
hits: {
hits: Array<{
_source: T;
}>
}
}
// Define the intefrace of the source object
interface Source {
foo: string
}
client.search(searchParams)
.then((response: ApiResponse<SearchResponse<Source>>) => console.log(response))
.catch((err: Error) => {})
// extend client
client.extend('namespace.method', (options: ClientExtendsCallbackOptions) => {
return function (params: any) {