[kbn/server-route-repository] Add createRepositoryClient function (#189764)

### Summary

This PR adds the `createRepositoryClient` function, which takes the type
of a server route repository as a generic argument and creates a wrapper
around `core.http` that is type bound by the routes defined in the
repository, as well as their request param types and return types.
This function was extracted from the code that exists in the AI
Assistant plugin.

Other changes include:
* Adding usage documentation
* Creation of a new package `@kbn/server-route-repository-client` to
house `createRepositoryClient` so it can be safely imported in browser
side code
* Moving the types from ``@kbn/server-route-repository` to
``@kbn/server-route-repository-utils` in order to use them in both the
server and browser side
* Add some default types to the generics for `createServerRouteFactory`
(`createRepositoryClient` also has default types)
* Allow `registerRoutes` to take a generic to constrain the shape of
`dependencies` so that the type used when calling
`createServerRouteFactory` can be used in both places

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Milton Hultgren 2024-08-09 16:45:01 +02:00 committed by GitHub
parent 1a82dd6f57
commit 986001c9a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 695 additions and 28 deletions

1
.github/CODEOWNERS vendored
View file

@ -776,6 +776,7 @@ packages/kbn-securitysolution-t-grid @elastic/security-detection-engine
packages/kbn-securitysolution-utils @elastic/security-detection-engine
packages/kbn-server-http-tools @elastic/kibana-core
packages/kbn-server-route-repository @elastic/obs-knowledge-team
packages/kbn-server-route-repository-client @elastic/obs-knowledge-team
packages/kbn-server-route-repository-utils @elastic/obs-knowledge-team
x-pack/plugins/serverless @elastic/appex-sharedux
packages/serverless/settings/common @elastic/appex-sharedux @elastic/kibana-management

View file

@ -791,6 +791,7 @@
"@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils",
"@kbn/server-http-tools": "link:packages/kbn-server-http-tools",
"@kbn/server-route-repository": "link:packages/kbn-server-route-repository",
"@kbn/server-route-repository-client": "link:packages/kbn-server-route-repository-client",
"@kbn/server-route-repository-utils": "link:packages/kbn-server-route-repository-utils",
"@kbn/serverless": "link:x-pack/plugins/serverless",
"@kbn/serverless-common-settings": "link:packages/serverless/settings/common",

View file

@ -0,0 +1,3 @@
# @kbn/server-route-repository-client
Extension of `@kbn/server-route-repository` with the browser side parts of the `@kbn/server-route-repository` package.

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { createRepositoryClient } from './src/create_repository_client';
export type { DefaultClientOptions } from '@kbn/server-route-repository-utils';

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-server-route-repository-client'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-browser",
"id": "@kbn/server-route-repository-client",
"owner": "@elastic/obs-knowledge-team"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/server-route-repository-client",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,182 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as t from 'io-ts';
import { CoreSetup } from '@kbn/core-lifecycle-browser';
import { createRepositoryClient } from './create_repository_client';
describe('createRepositoryClient', () => {
const getMock = jest.fn();
const coreSetupMock = {
http: {
get: getMock,
},
} as unknown as CoreSetup;
beforeEach(() => {
jest.clearAllMocks();
});
it('provides a default value for options when they are not required', () => {
const repository = {
'GET /internal/handler': {
endpoint: 'GET /internal/handler',
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /internal/handler');
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', {
body: undefined,
query: undefined,
version: undefined,
});
});
it('extract the version from the endpoint', () => {
const repository = {
'GET /api/handler 2024-08-05': {
endpoint: 'GET /api/handler 2024-08-05',
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /api/handler 2024-08-05');
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/api/handler', {
body: undefined,
query: undefined,
version: '2024-08-05',
});
});
it('passes on the provided client parameters', () => {
const repository = {
'GET /internal/handler': {
endpoint: 'GET /internal/handler',
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /internal/handler', {
headers: {
some_header: 'header_value',
},
});
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', {
headers: {
some_header: 'header_value',
},
body: undefined,
query: undefined,
version: undefined,
});
});
it('replaces path params before making the call', () => {
const repository = {
'GET /internal/handler/{param}': {
endpoint: 'GET /internal/handler/{param}',
params: t.type({
path: t.type({
param: t.string,
}),
}),
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /internal/handler/{param}', {
params: {
path: {
param: 'param_value',
},
},
});
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler/param_value', {
body: undefined,
query: undefined,
version: undefined,
});
});
it('passes on the stringified body content when provided', () => {
const repository = {
'GET /internal/handler': {
endpoint: 'GET /internal/handler',
params: t.type({
body: t.type({
payload: t.string,
}),
}),
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /internal/handler', {
params: {
body: {
payload: 'body_value',
},
},
});
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', {
body: JSON.stringify({
payload: 'body_value',
}),
query: undefined,
version: undefined,
});
});
it('passes on the query parameters when provided', () => {
const repository = {
'GET /internal/handler': {
endpoint: 'GET /internal/handler',
params: t.type({
query: t.type({
parameter: t.string,
}),
}),
handler: jest.fn().mockResolvedValue('OK'),
},
};
const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock);
fetch('GET /internal/handler', {
params: {
query: {
parameter: 'query_value',
},
},
});
expect(getMock).toHaveBeenCalledTimes(1);
expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', {
body: undefined,
query: {
parameter: 'query_value',
},
version: undefined,
});
});
});

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { CoreSetup, CoreStart } from '@kbn/core-lifecycle-browser';
import {
RouteRepositoryClient,
ServerRouteRepository,
DefaultClientOptions,
formatRequest,
} from '@kbn/server-route-repository-utils';
export function createRepositoryClient<
TRepository extends ServerRouteRepository,
TClientOptions extends Record<string, any> = DefaultClientOptions
>(core: CoreStart | CoreSetup) {
return {
fetch: (endpoint, optionsWithParams) => {
const { params, ...options } = (optionsWithParams ?? { params: {} }) as unknown as {
params?: Partial<Record<string, any>>;
};
const { method, pathname, version } = formatRequest(endpoint, params?.path);
return core.http[method](pathname, {
...options,
body: params && params.body ? JSON.stringify(params.body) : undefined,
query: params?.query,
version,
});
},
} as { fetch: RouteRepositoryClient<TRepository, TClientOptions> };
}

View file

@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/server-route-repository-utils",
"@kbn/core-lifecycle-browser",
]
}

View file

@ -8,3 +8,20 @@
export { formatRequest } from './src/format_request';
export { parseEndpoint } from './src/parse_endpoint';
export type {
ServerRouteCreateOptions,
ServerRouteHandlerResources,
RouteParamsRT,
ServerRoute,
EndpointOf,
ReturnOf,
RouteRepositoryClient,
RouteState,
ClientRequestParamsOf,
DecodedRequestParamsOf,
ServerRouteRepository,
DefaultClientOptions,
DefaultRouteCreateOptions,
DefaultRouteHandlerResources,
} from './src/typings';

View file

@ -6,7 +6,16 @@
* Side Public License, v 1.
*/
import { IKibanaResponse } from '@kbn/core-http-server';
import type { HttpFetchOptions } from '@kbn/core-http-browser';
import type { IKibanaResponse } from '@kbn/core-http-server';
import type {
RequestHandlerContext,
Logger,
RouteConfigOptions,
RouteMethod,
KibanaRequest,
KibanaResponseFactory,
} from '@kbn/core/server';
import * as t from 'io-ts';
import { RequiredKeys } from 'utility-types';
@ -137,9 +146,25 @@ type MaybeOptionalArgs<T extends Record<string, any>> = RequiredKeys<T> extends
export type RouteRepositoryClient<
TServerRouteRepository extends ServerRouteRepository,
TAdditionalClientOptions extends Record<string, any>
> = <TEndpoint extends keyof TServerRouteRepository>(
> = <TEndpoint extends Extract<keyof TServerRouteRepository, string>>(
endpoint: TEndpoint,
...args: MaybeOptionalArgs<
ClientRequestParamsOf<TServerRouteRepository, TEndpoint> & TAdditionalClientOptions
>
) => Promise<ReturnOf<TServerRouteRepository, TEndpoint>>;
export type DefaultClientOptions = HttpFetchOptions;
interface CoreRouteHandlerResources {
request: KibanaRequest;
response: KibanaResponseFactory;
context: RequestHandlerContext;
}
export interface DefaultRouteHandlerResources extends CoreRouteHandlerResources {
logger: Logger;
}
export interface DefaultRouteCreateOptions {
options?: RouteConfigOptions<RouteMethod>;
}

View file

@ -15,5 +15,9 @@
"exclude": [
"target/**/*"
],
"kbn_references": []
"kbn_references": [
"@kbn/core-http-browser",
"@kbn/core-http-server",
"@kbn/core",
]
}

View file

@ -2,12 +2,346 @@
Utility functions for creating a typed server route repository, and a typed client, generating runtime validation and type validation from the same route definition.
## Usage
## Overview
TBD
There are three main functions that make up this package:
1. `createServerRouteFactory`
2. `registerRoutes`
3. `createRepositoryClient`
## Server vs. Browser entry points
`createServerRouteFactory` and `registerRoutes` are used in the server and `createRepositoryClient` in the browser (thus it is imported from `@kbn/server-route-repository-client`).
This package can only be used on the server. The browser utilities can be found in `@kbn/server-route-repository-utils`.
`createServerRouteFactory` returns a function that can be used to create routes for the repository, when calling it you can specify the resources that will be available to your route handler as well as which other options should be specified on your routes.
When adding utilities to this package, please make sure to update the entry points accordingly and the [BUILD.bazel](./BUILD.bazel)'s `target_web` target build to include all the necessary files.
Once the routes have been created and put into a plain object (the "repository"), this repository can then be passed to `registerRoutes` which also accepts the dependencies to be injected into each route handler. `registerRoutes` handles the creation of the Core HTTP router, as well as the final registration of the routes with versioning and request validation.
By exporting the type of the repository from the server to the browser (make sure you use a `type` import), we can pass that as a generic argument to `createRepositoryClient` and get back a thin but strongly typed wrapper around the Core HTTP service, with auto completion for the available routes, type checking for the request parameters required by each specific route and response type inference. You can also add a generic type for which additional options the client should pass with each request.
## Basic example
In the server side, we'll start by creating the route factory, to make things easier it is recommended to keep this in its own file and export it.
> server/create_my_plugin_server_route.ts
```javascript
import { createServerRouteFactory } from '@kbn/server-route-repository';
import {
DefaultRouteHandlerResources,
DefaultRouteCreateOptions,
} from '@kbn/server-route-repository-utils';
export const createMyPluginServerRoute = createServerRouteFactory<
DefaultRouteHandlerResources,
DefaultRouteCreateOptions
>();
```
The two generic arguments are optional, this example shows a "default" setup which exposes what Core HTTP would normally provide (`request`, `context`, `response`) plus a logger.
Next, let's create a minimal route.
> server/my_route.ts
```javascript
import { createMyPluginServerRoute } from './create_my_plugin_server_route';
export const myRoute = createMyPluginServerRoute({
endpoint: 'GET /internal/my_plugin/route',
handler: async (resources) => {
const { request, context, response, logger } = resources;
return response.ok({
body: 'Hello, my route!',
});
},
});
```
After this we can add the route to a "repository", which is just a plain object, and call `registerRoutes`.
> server/plugin.ts
```javascript
import { registerRoutes } from '@kbn/server-route-repository';
import { myRoute } from './my_route';
const repository = {
...myRoute,
};
export type MyPluginRouteRepository = typeof repository;
class MyPlugin implements Plugin {
public setup(core: CoreSetup) {
registerRoutes({
core,
logger,
repository,
dependencies: {},
});
}
}
```
Since this example doesn't use any dependencies, the generic argument for `registerRoutes` is optional and we pass an empty object.
We also export the type of the repository, we'll need this for the client which is next!
The client can be created either in `setup` or `start`.
> browser/plugin.ts
```javascript
import { isHttpFetchError } from '@kbn/core-http-browser';
import { DefaultClientOptions } from '@kbn/server-route-repository-utils';
import { createRepositoryClient } from '@kbn/server-route-repository-client';
import type { MyPluginRouteRepository } from '../server/plugin';
export type MyPluginRepositoryClient =
ReturnType<typeof createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions>>;
class MyPlugin implements Plugin {
public setup(core: CoreSetup) {
const myPluginRepositoryClient =
createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions>(core);
myPluginRepositoryClient
.fetch('GET /internal/my_plugin/route')
.then((response) => console.log(response))
.catch((error) => {
if (isHttpFetchError(error)) {
console.log(error.message);
}
throw error;
});
}
}
```
This example prints 'Hello, my route!' and the type of the response is **inferred** to this.
We pass in the type of the repository that we (_type_) imported from the server. The second generic parameter for `createRepositoryClient` is optional.
We also export the type of the client itself so we can use it to type the client as we pass it around.
When using the client's `fetch` function, the first argument is the route to call and this is auto completed to only the available routes.
The second argument is optional in this case but allows you to send in any extra options.
The client translates the endpoint and the options (including request parameters) to the right Core HTTP request.
## Request parameter validation
When creating your routes, you can also provide an `io-ts` codec to be used when validating incoming requests.
```javascript
import * as t from 'io-ts';
const myRoute = createMyPluginServerRoute({
endpoint: 'GET /internal/my_plugin/route/{my_path_param}',
params: t.type({
path: t.type({
my_path_param: t.string,
}),
query: t.type({
my_query_param: t.string,
}),
body: t.type({
my_body_param: t.string,
}),
}),
handler: async (resources) => {
const { request, context, response, logger, params } = resources;
const { path, query, body } = params;
return response.ok({
body: 'Hello, my route!',
});
},
});
```
The `params` object is added to the route resources.
`path`, `query` and `body` are validated before your handler is called and the types are **inferred** inside of the handler.
When calling this endpoint, it will look like this:
```javascript
client('GET /internal/my_plugin/route/{my_path_param}', {
params: {
path: {
my_path_param: 'some_path_value',
},
query: {
my_query_param: 'some_query_value',
},
body: {
my_body_param: 'some_body_value',
},
},
}).then(console.log);
```
Where the shape of `params` is typed to match the expected shape, meaning you don't need to manually use the codec when calling the route.
## Public routes
To define a public route, you need to change the endpoint path and add a version.
```javascript
const myRoute = createMyPluginServerRoute({
endpoint: 'GET /api/my_plugin/route 2024-08-02',
handler: async (resources) => {
const { request, context, response, logger } = resources;
return response.ok({
body: 'Hello, my route!',
});
},
});
```
`registerRoutes` takes care of setting the `access` option correctly for you and using the right versioned router.
## Convenient return and throw
`registerRoutes` translate any returned or thrown non-Kibana response into a Kibana response (including `Boom`).
It also handles common concerns like abort signals.
```javascript
import { teapot } from '@hapi/boom';
const myRoute = createMyPluginServerRoute({
endpoint: 'GET /internal/my_plugin/route',
handler: async (resources) => {
const { request, context, response, logger } = resources;
const result = coinFlip();
if (result === 'heads') {
throw teapot();
} else {
return 'Hello, my route!';
}
},
});
```
Both the teapot error and the plain string will be translated into a Kibana response.
## Route dependencies
If you want to provide additional dependencies to your route, you need to change the generic argument to `createServerRouteFactory` and `registerRoutes`.
```javascript
import { createServerRouteFactory } from '@kbn/server-route-repository';
import { DefaultRouteHandlerResources } from '@kbn/server-route-repository-utils';
export interface MyPluginRouteDependencies {
myDependency: MyDependency;
}
export const createMyPluginServerRoute =
createServerRouteFactory<DefaultRouteHandlerResources & MyPluginRouteDependencies>();
```
If you don't want your route to have access to the default resources, you could pass in only `MyPluginRouteDependencies`.
Then we use the same type when calling `registerRoutes`
```javascript
registerRoutes<MyPluginRouteDependencies>({
core,
logger,
repository,
dependencies: {
myDependency: new MyDependency(),
},
});
```
This way, when creating a route, you will have `myDependency` available in the route resources.
```javascript
import { createMyPluginServerRoute } from './create_my_plugin_server_route';
export const myRoute = createMyPluginServerRoute({
endpoint: 'GET /internal/my_plugin/route',
handler: async (resources) => {
const { request, context, response, logger, myDependency } = resources;
return response.ok({
body: myDependency.sayHello(),
});
},
});
```
## Route creation options
Core HTTP allows certain options to be passed to the route when it's being created, and you may want to include your own options as well.
To do this, override the second generic argument when calling `createServerRouteFactory`.
```javascript
import { createServerRouteFactory } from '@kbn/server-route-repository';
import {
DefaultRouteHandlerResources,
DefaultRouteCreateOptions,
} from '@kbn/server-route-repository-utils';
interface MyPluginRouteCreateOptions {
isDangerous: boolean;
}
export const createMyPluginServerRoute = createServerRouteFactory<
DefaultRouteHandlerResources,
DefaultRouteCreateOptions & MyPluginRouteCreateOptions
>();
```
If you don't want your route to have access to the options provided by Core HTTP, you could pass in only `MyPluginRouteCreateOptions`.
You can then specify this option when creating the route.
```javascript
import { createMyPluginServerRoute } from './create_my_plugin_server_route';
export const myRoute = createMyPluginServerRoute({
options: {
access: 'internal',
},
isDangerous: true,
endpoint: 'GET /internal/my_plugin/route',
handler: async (resources) => {
const { request, context, response, logger } = resources;
return response.ok({
body: 'Hello, my route!',
});
},
});
```
## Client calling options
Core HTTP allows certain options to be passed with the request, and you may want to include your own options as well.
To do this, override the second generic argument when calling `createRepositoryClient`.
```javascript
import { DefaultClientOptions } from '@kbn/server-route-repository-utils';
import { createRepositoryClient } from '@kbn/server-route-repository-client';
import type { MyPluginRouteRepository } from '../server/plugin';
interface MyPluginClientOptions {
makeSafe: boolean;
}
export type MyPluginRepositoryClient =
ReturnType<typeof createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions & MyPluginClientOptions>>;
class MyPlugin implements Plugin {
public setup(core: CoreSetup) {
const myPluginRepositoryClient =
createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions & MyPluginClientOptions>(core);
myPluginRepositoryClient.fetch('GET /internal/my_plugin/route', {
makeSafe: true,
headers: {
my_plugin_header: 'I am a header',
},
}).then(console.log);
}
}
```
If you don't want your route to have access to the options provided by Core HTTP, you could pass in only `MyPluginClientOptions`.

View file

@ -22,4 +22,6 @@ export type {
ServerRoute,
RouteParamsRT,
RouteState,
} from './src/typings';
DefaultRouteCreateOptions,
DefaultRouteHandlerResources,
} from '@kbn/server-route-repository-utils';

View file

@ -5,16 +5,19 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
ServerRouteCreateOptions,
ServerRouteHandlerResources,
RouteParamsRT,
ServerRoute,
} from './typings';
ServerRouteCreateOptions,
ServerRouteHandlerResources,
DefaultRouteHandlerResources,
DefaultRouteCreateOptions,
} from '@kbn/server-route-repository-utils';
export function createServerRouteFactory<
TRouteHandlerResources extends ServerRouteHandlerResources,
TRouteCreateOptions extends ServerRouteCreateOptions
TRouteHandlerResources extends ServerRouteHandlerResources = DefaultRouteHandlerResources,
TRouteCreateOptions extends ServerRouteCreateOptions = DefaultRouteCreateOptions
>(): <
TEndpoint extends string,
TReturnType,

View file

@ -7,10 +7,10 @@
*/
import Boom from '@hapi/boom';
import { formatErrors, strictKeysRt } from '@kbn/io-ts-utils';
import { RouteParamsRT } from '@kbn/server-route-repository-utils';
import { isLeft } from 'fp-ts/lib/Either';
import * as t from 'io-ts';
import { isEmpty, isPlainObject, omitBy } from 'lodash';
import { RouteParamsRT } from './typings';
interface KibanaRequestParams {
body: unknown;

View file

@ -88,19 +88,19 @@ describe('registerRoutes', () => {
registerRoutes({
core: coreSetup,
repository: {
internal: {
'GET /internal/app/feature': {
endpoint: 'GET /internal/app/feature',
handler: internalHandler,
params: paramsRt,
options: internalOptions,
},
public: {
'GET /api/app/feature version': {
endpoint: 'GET /api/app/feature version',
handler: publicHandler,
params: paramsRt,
options: publicOptions,
},
error: {
'GET /internal/app/feature/error': {
endpoint: 'GET /internal/app/feature/error',
handler: errorHandler,
params: paramsRt,
@ -124,11 +124,6 @@ describe('registerRoutes', () => {
expect(internalRoute.options).toEqual(internalOptions);
expect(internalRoute.validate).toEqual(routeValidationObject);
const [errorRoute] = get.mock.calls[1];
expect(errorRoute.path).toEqual('/internal/app/feature/error');
expect(errorRoute.options).toEqual(internalOptions);
expect(errorRoute.validate).toEqual(routeValidationObject);
expect(getWithVersion).toHaveBeenCalledTimes(1);
const [publicRoute] = getWithVersion.mock.calls[0];
expect(publicRoute.path).toEqual('/api/app/feature');

View file

@ -14,10 +14,13 @@ import type { CoreSetup } from '@kbn/core-lifecycle-server';
import type { Logger } from '@kbn/logging';
import * as t from 'io-ts';
import { merge, pick } from 'lodash';
import { parseEndpoint } from '@kbn/server-route-repository-utils';
import {
ServerRoute,
ServerRouteCreateOptions,
parseEndpoint,
} from '@kbn/server-route-repository-utils';
import { decodeRequestParams } from './decode_request_params';
import { routeValidationObject } from './route_validation_object';
import type { ServerRoute, ServerRouteCreateOptions } from './typings';
const CLIENT_CLOSED_REQUEST = {
statusCode: 499,
@ -26,7 +29,7 @@ const CLIENT_CLOSED_REQUEST = {
},
};
export function registerRoutes({
export function registerRoutes<TDependencies extends Record<string, any>>({
core,
repository,
logger,
@ -35,7 +38,7 @@ export function registerRoutes({
core: CoreSetup;
repository: Record<string, ServerRoute<string, any, any, any, ServerRouteCreateOptions>>;
logger: Logger;
dependencies: Record<string, any>;
dependencies: TDependencies;
}) {
const routes = Object.values(repository);

View file

@ -7,9 +7,9 @@
*/
import * as t from 'io-ts';
import { kibanaResponseFactory } from '@kbn/core/server';
import { EndpointOf, ReturnOf, RouteRepositoryClient } from '@kbn/server-route-repository-utils';
import { createServerRouteFactory } from './create_server_route_factory';
import { decodeRequestParams } from './decode_request_params';
import { EndpointOf, ReturnOf, RouteRepositoryClient } from './typings';
function assertType<TShape = never>(value: TShape) {
return value;

View file

@ -1546,6 +1546,8 @@
"@kbn/server-http-tools/*": ["packages/kbn-server-http-tools/*"],
"@kbn/server-route-repository": ["packages/kbn-server-route-repository"],
"@kbn/server-route-repository/*": ["packages/kbn-server-route-repository/*"],
"@kbn/server-route-repository-client": ["packages/kbn-server-route-repository-client"],
"@kbn/server-route-repository-client/*": ["packages/kbn-server-route-repository-client/*"],
"@kbn/server-route-repository-utils": ["packages/kbn-server-route-repository-utils"],
"@kbn/server-route-repository-utils/*": ["packages/kbn-server-route-repository-utils/*"],
"@kbn/serverless": ["x-pack/plugins/serverless"],

View file

@ -6368,6 +6368,10 @@
version "0.0.0"
uid ""
"@kbn/server-route-repository-client@link:packages/kbn-server-route-repository-client":
version "0.0.0"
uid ""
"@kbn/server-route-repository-utils@link:packages/kbn-server-route-repository-utils":
version "0.0.0"
uid ""