Skip to content

Commit

Permalink
Export a kibana restricted type definition (#1239)
Browse files Browse the repository at this point in the history
  • Loading branch information
delvedor authored Jul 6, 2020
1 parent 8d7859d commit 34a9176
Show file tree
Hide file tree
Showing 7 changed files with 655 additions and 16 deletions.
465 changes: 465 additions & 0 deletions api/kibana.d.ts

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ interface ClientExtendsCallbackOptions {
}
}
declare type extendsCallback = (options: ClientExtendsCallbackOptions) => any;
interface ClientExtends {
(method: string, fn: extendsCallback): void;
(method: string, opts: { force: boolean }, fn: extendsCallback): void;
}
// /Extend API

interface NodeOptions {
Expand Down Expand Up @@ -107,9 +103,12 @@ declare class Client {
connectionPool: ConnectionPool;
transport: Transport;
serializer: Serializer;
extend: ClientExtends;
extend(method: string, fn: extendsCallback): void
extend(method: string, opts: { force: boolean }, fn: extendsCallback): void;
helpers: Helpers;
child(opts?: ClientOptions): Client;
close(): Promise<void>;
close(callback: Function): void;
close(callback?: Function): Promise<void> | void;
emit(event: string | symbol, ...args: any[]): boolean;
on(event: 'request', listener: (err: ApiError, meta: RequestEvent) => void): this;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"standard": "^13.0.2",
"stoppable": "^1.1.0",
"tap": "^14.4.1",
"tsd": "^0.11.0",
"tsd": "^0.12.1",
"workq": "^2.1.0",
"xmlbuilder2": "^2.1.2"
},
Expand Down
21 changes: 16 additions & 5 deletions scripts/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function start (opts) {
const apiOutputFolder = join(packageFolder, 'api')
const mainOutputFile = join(packageFolder, 'index.js')
const typeDefFile = join(__dirname, '..', 'index.d.ts')
const kibanaTypeDefFile = join(packageFolder, 'kibana.d.ts')
const docOutputFile = join(__dirname, '..', 'docs', 'reference.asciidoc')
const requestParamsOutputFile = join(packageFolder, 'requestParams.d.ts')
const allSpec = []
Expand All @@ -58,23 +59,33 @@ function start (opts) {
{ encoding: 'utf8' }
)

const { fn: factory, types } = genFactory(apiOutputFolder, [apiFolder, xPackFolder])
const { fn: factory, types, kibanaTypes } = genFactory(apiOutputFolder, [apiFolder, xPackFolder])
writeFileSync(
mainOutputFile,
factory,
{ encoding: 'utf8' }
)

const oldTypeDefString = readFileSync(typeDefFile, 'utf8')
const start = oldTypeDefString.indexOf('/* GENERATED */')
const end = oldTypeDefString.indexOf('/* /GENERATED */')
const newTypeDefString = oldTypeDefString.slice(0, start + 15) + '\n' + types + '\n ' + oldTypeDefString.slice(end)
let oldTypeDefString = readFileSync(typeDefFile, 'utf8')
let start = oldTypeDefString.indexOf('/* GENERATED */')
let end = oldTypeDefString.indexOf('/* /GENERATED */')
let newTypeDefString = oldTypeDefString.slice(0, start + 15) + '\n' + types + '\n ' + oldTypeDefString.slice(end)
writeFileSync(
typeDefFile,
newTypeDefString,
{ encoding: 'utf8' }
)

oldTypeDefString = readFileSync(kibanaTypeDefFile, 'utf8')
start = oldTypeDefString.indexOf('/* GENERATED */')
end = oldTypeDefString.indexOf('/* /GENERATED */')
newTypeDefString = oldTypeDefString.slice(0, start + 15) + '\n' + kibanaTypes + '\n ' + oldTypeDefString.slice(end)
writeFileSync(
kibanaTypeDefFile,
newTypeDefString,
{ encoding: 'utf8' }
)

lintFiles(log, () => {
log.text = 'Generating documentation'
const allSpec = apiFolderContents.filter(f => f !== '_common.json')
Expand Down
57 changes: 54 additions & 3 deletions scripts/utils/generateMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function genFactory (folder, paths) {
.reverse()
.reduce((acc, val) => {
const body = hasBody(paths, file.slice(0, -3))
const methods = acc === null ? buildMethodDefinition(val, name, body) : null
const methods = acc === null ? buildMethodDefinition({ kibana: false }, val, name, body) : null
const obj = {}
if (methods) {
for (const m of methods) {
Expand All @@ -53,6 +53,33 @@ function genFactory (folder, paths) {
})
.reduce((acc, val) => deepmerge(acc, val), {})

const kibanaTypes = apiFiles
.map(file => {
const name = file
.slice(0, -3)
.replace(/\.([a-z])/g, k => k[1].toUpperCase())
.replace(/_([a-z])/g, k => k[1].toUpperCase())

return file
.slice(0, -3) // remove `.js` extension
.split('.')
.reverse()
.reduce((acc, val) => {
const body = hasBody(paths, file.slice(0, -3))
const methods = acc === null ? buildMethodDefinition({ kibana: true }, val, name, body) : null
const obj = {}
if (methods) {
for (const m of methods) {
obj[m.key] = m.val
}
} else {
obj[camelify(val)] = acc
}
return obj
}, null)
})
.reduce((acc, val) => deepmerge(acc, val), {})

const apis = apiFiles
.map(file => {
// const name = format(file.slice(0, -3))
Expand Down Expand Up @@ -97,6 +124,18 @@ function genFactory (folder, paths) {
// remove useless quotes and commas
.replace(/"/g, '')
.replace(/,$/gm, '')
const kibanaTypesStr = Object.keys(kibanaTypes)
.map(key => {
const line = ` ${key}: ${JSON.stringify(kibanaTypes[key], null, 4)}`
if (line.slice(-1) === '}') {
return line.slice(0, -1) + ' }'
}
return line
})
.join('\n')
// remove useless quotes and commas
.replace(/"/g, '')
.replace(/,$/gm, '')

const fn = dedent`
// Licensed to Elasticsearch B.V under one or more agreements.
Expand Down Expand Up @@ -161,7 +200,7 @@ function genFactory (folder, paths) {
`

// new line at the end of file
return { fn: fn + '\n', types: typesStr }
return { fn: fn + '\n', types: typesStr, kibanaTypes: kibanaTypesStr }
}

// from snake_case to camelCase
Expand All @@ -177,11 +216,23 @@ function toPascalCase (str) {
return str[0].toUpperCase() + str.slice(1)
}

function buildMethodDefinition (api, name, hasBody) {
function buildMethodDefinition (opts, api, name, hasBody) {
const Name = toPascalCase(name)
const bodyType = ndjsonApiKey.includes(Name) ? 'RequestNDBody' : 'RequestBody'
const defaultBodyType = ndjsonApiKey.includes(Name) ? 'Record<string, any>[]' : 'Record<string, any>'

if (opts.kibana) {
if (hasBody) {
return [
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = unknown>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }
]
} else {
return [
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = unknown>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }
]
}
}

if (hasBody) {
let methods = [
{ key: `${api}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = unknown>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` },
Expand Down
4 changes: 2 additions & 2 deletions test/types/client.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { expectType } from 'tsd'
import { Client, ApiError, ApiResponse, RequestEvent, ResurrectEvent } from '../../'
import { TransportRequestCallback, TransportRequestPromise } from '../..//lib/Transport';
import { TransportRequestCallback, TransportRequestPromise } from '../../lib/Transport'

const client = new Client({
node: 'http://localhost:9200'
Expand Down Expand Up @@ -120,4 +120,4 @@ client.on('resurrect', (err, meta) => {
} catch (err) {
expectType<any>(err)
}
}
}
113 changes: 113 additions & 0 deletions test/types/kibana.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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

import { expectType, expectNotType, expectError } from 'tsd'
import { Client, RequestEvent, ResurrectEvent, ApiError, ApiResponse } from '../../'
import { KibanaClient } from '../../api/kibana'
import { TransportRequestPromise } from '../../lib/Transport'

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

client.on('request', (err, meta) => {
expectType<ApiError>(err)
expectType<RequestEvent>(meta)
})

client.on('response', (err, meta) => {
expectType<ApiError>(err)
expectType<RequestEvent>(meta)
})

client.on('sniff', (err, meta) => {
expectType<ApiError>(err)
expectType<RequestEvent>(meta)
})

client.on('resurrect', (err, meta) => {
expectType<null>(err)
expectType<ResurrectEvent>(meta)
})

// No generics
{
const response = await client.cat.count({ index: 'test' })

expectType<Record<string, any>>(response.body)
expectType<unknown>(response.meta.context)
}

// Define only the response body
{
const response = await client.cat.count<string>({ index: 'test' })

expectType<string>(response.body)
expectType<unknown>(response.meta.context)
}

// Define response body and the context
{
const response = await client.cat.count<string, string>({ index: 'test' })

expectType<string>(response.body)
expectType<string>(response.meta.context)
}

// Check API returned type and optional parameters
{
const promise = client.info()
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}

{
const promise = client.info({ pretty: true })
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}

{
const promise = client.info({ pretty: true }, { ignore: [404] })
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}

// body that does not respect the RequestBody constraint
expectError(
client.search({
index: 'hello',
body: 42
}).then(console.log)
)
/*
* We cannot test yet the absence of a property because tsd is still shipping
* TypeScript v3.7, as soon as it will be 3.9, we'll uncomment the following test.
// @ts-expect-error
client.async_search.get()
*/

// callback api is not supported
expectError(client.cat.count({ index: 'test' }, {}, (err: any, result: any) => {}))

// close api, only promises should be supported
// callback api is not supported
expectType<Promise<void>>(client.close())
expectError(client.close(() => {}))

// the child api should return a KibanaClient instance
const child = client.child()
expectType<KibanaClient>(child)
expectNotType<Client>(child)

0 comments on commit 34a9176

Please sign in to comment.