Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export a kibana restricted type definition #1239

Merged
merged 13 commits into from
Jul 6, 2020
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's easy to forget to update length if we change start token. Do you think you could you refer it explicitly?

const startToken = '/* GENERATED */';
let startPosition = oldTypeDefString.indexOf(startToken)
let newTypeDefString = oldTypeDefString.slice(0, start + startToken.length) + ...

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't expect it to change, also, we could still forget to update it here :D

Copy link

Choose a reason for hiding this comment

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

@delvedor Jumping in on @restrry comment. The recommended change is not only to cover the case where "start token" would change, but also to avoid "magic" number as per our styleguide (https://github.com/elastic/kibana/blob/master/STYLEGUIDE.md#magic-numbersstrings).
When I read 15 it does not mean anything, when I read startToken.length it is explicit what it is. Just my 2 cents 😊

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) {
delvedor marked this conversation as resolved.
Show resolved Hide resolved
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)