diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8209f032ca848..e07292b2eef19 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/package.json b/package.json index 3797434824a6a..7ac737aa32b13 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/kbn-server-route-repository-client/README.md b/packages/kbn-server-route-repository-client/README.md new file mode 100644 index 0000000000000..59257f7c9a652 --- /dev/null +++ b/packages/kbn-server-route-repository-client/README.md @@ -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. diff --git a/packages/kbn-server-route-repository-client/index.ts b/packages/kbn-server-route-repository-client/index.ts new file mode 100644 index 0000000000000..d2572e009bd66 --- /dev/null +++ b/packages/kbn-server-route-repository-client/index.ts @@ -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'; diff --git a/packages/kbn-server-route-repository-client/jest.config.js b/packages/kbn-server-route-repository-client/jest.config.js new file mode 100644 index 0000000000000..b913a23f9441a --- /dev/null +++ b/packages/kbn-server-route-repository-client/jest.config.js @@ -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: ['/packages/kbn-server-route-repository-client'], +}; diff --git a/packages/kbn-server-route-repository-client/kibana.jsonc b/packages/kbn-server-route-repository-client/kibana.jsonc new file mode 100644 index 0000000000000..5a3974d7ab3ca --- /dev/null +++ b/packages/kbn-server-route-repository-client/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/server-route-repository-client", + "owner": "@elastic/obs-knowledge-team" +} diff --git a/packages/kbn-server-route-repository-client/package.json b/packages/kbn-server-route-repository-client/package.json new file mode 100644 index 0000000000000..0f4289c3c86fd --- /dev/null +++ b/packages/kbn-server-route-repository-client/package.json @@ -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" +} \ No newline at end of file diff --git a/packages/kbn-server-route-repository-client/src/create_repository_client.test.ts b/packages/kbn-server-route-repository-client/src/create_repository_client.test.ts new file mode 100644 index 0000000000000..c7e12e2440fed --- /dev/null +++ b/packages/kbn-server-route-repository-client/src/create_repository_client.test.ts @@ -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(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(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(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(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(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(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, + }); + }); +}); diff --git a/packages/kbn-server-route-repository-client/src/create_repository_client.ts b/packages/kbn-server-route-repository-client/src/create_repository_client.ts new file mode 100644 index 0000000000000..ff7bc13234dba --- /dev/null +++ b/packages/kbn-server-route-repository-client/src/create_repository_client.ts @@ -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 = DefaultClientOptions +>(core: CoreStart | CoreSetup) { + return { + fetch: (endpoint, optionsWithParams) => { + const { params, ...options } = (optionsWithParams ?? { params: {} }) as unknown as { + params?: Partial>; + }; + + 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 }; +} diff --git a/packages/kbn-server-route-repository-client/tsconfig.json b/packages/kbn-server-route-repository-client/tsconfig.json new file mode 100644 index 0000000000000..8ef10ede60a1a --- /dev/null +++ b/packages/kbn-server-route-repository-client/tsconfig.json @@ -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", + ] +} diff --git a/packages/kbn-server-route-repository-utils/index.ts b/packages/kbn-server-route-repository-utils/index.ts index 02ff5f70211fa..bfcc03c0e64c5 100644 --- a/packages/kbn-server-route-repository-utils/index.ts +++ b/packages/kbn-server-route-repository-utils/index.ts @@ -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'; diff --git a/packages/kbn-server-route-repository/src/typings.ts b/packages/kbn-server-route-repository-utils/src/typings.ts similarity index 85% rename from packages/kbn-server-route-repository/src/typings.ts rename to packages/kbn-server-route-repository-utils/src/typings.ts index 28212f80ca5da..0539d9ea1d38e 100644 --- a/packages/kbn-server-route-repository/src/typings.ts +++ b/packages/kbn-server-route-repository-utils/src/typings.ts @@ -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> = RequiredKeys extends export type RouteRepositoryClient< TServerRouteRepository extends ServerRouteRepository, TAdditionalClientOptions extends Record -> = ( +> = >( endpoint: TEndpoint, ...args: MaybeOptionalArgs< ClientRequestParamsOf & TAdditionalClientOptions > ) => Promise>; + +export type DefaultClientOptions = HttpFetchOptions; + +interface CoreRouteHandlerResources { + request: KibanaRequest; + response: KibanaResponseFactory; + context: RequestHandlerContext; +} + +export interface DefaultRouteHandlerResources extends CoreRouteHandlerResources { + logger: Logger; +} + +export interface DefaultRouteCreateOptions { + options?: RouteConfigOptions; +} diff --git a/packages/kbn-server-route-repository-utils/tsconfig.json b/packages/kbn-server-route-repository-utils/tsconfig.json index 87f865132f4b4..0f3dd221ec6b7 100644 --- a/packages/kbn-server-route-repository-utils/tsconfig.json +++ b/packages/kbn-server-route-repository-utils/tsconfig.json @@ -15,5 +15,9 @@ "exclude": [ "target/**/*" ], - "kbn_references": [] + "kbn_references": [ + "@kbn/core-http-browser", + "@kbn/core-http-server", + "@kbn/core", + ] } diff --git a/packages/kbn-server-route-repository/README.md b/packages/kbn-server-route-repository/README.md index 6810b092403bc..f46a8f3ee3677 100644 --- a/packages/kbn-server-route-repository/README.md +++ b/packages/kbn-server-route-repository/README.md @@ -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>; + +class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + const myPluginRepositoryClient = + createRepositoryClient(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(); +``` + +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({ + 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>; + +class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + const myPluginRepositoryClient = + createRepositoryClient(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`. diff --git a/packages/kbn-server-route-repository/index.ts b/packages/kbn-server-route-repository/index.ts index ef942919f1480..e4e43523d25c3 100644 --- a/packages/kbn-server-route-repository/index.ts +++ b/packages/kbn-server-route-repository/index.ts @@ -22,4 +22,6 @@ export type { ServerRoute, RouteParamsRT, RouteState, -} from './src/typings'; + DefaultRouteCreateOptions, + DefaultRouteHandlerResources, +} from '@kbn/server-route-repository-utils'; diff --git a/packages/kbn-server-route-repository/src/create_server_route_factory.ts b/packages/kbn-server-route-repository/src/create_server_route_factory.ts index b506b5263e857..9361e1c12dbd0 100644 --- a/packages/kbn-server-route-repository/src/create_server_route_factory.ts +++ b/packages/kbn-server-route-repository/src/create_server_route_factory.ts @@ -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, diff --git a/packages/kbn-server-route-repository/src/decode_request_params.ts b/packages/kbn-server-route-repository/src/decode_request_params.ts index 0893524a3f9e9..bae6e4d4a0e12 100644 --- a/packages/kbn-server-route-repository/src/decode_request_params.ts +++ b/packages/kbn-server-route-repository/src/decode_request_params.ts @@ -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; diff --git a/packages/kbn-server-route-repository/src/register_routes.test.ts b/packages/kbn-server-route-repository/src/register_routes.test.ts index d6599241f5f72..39180a093835a 100644 --- a/packages/kbn-server-route-repository/src/register_routes.test.ts +++ b/packages/kbn-server-route-repository/src/register_routes.test.ts @@ -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'); diff --git a/packages/kbn-server-route-repository/src/register_routes.ts b/packages/kbn-server-route-repository/src/register_routes.ts index 4f409adda9577..fcf3c5c3281ee 100644 --- a/packages/kbn-server-route-repository/src/register_routes.ts +++ b/packages/kbn-server-route-repository/src/register_routes.ts @@ -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>({ core, repository, logger, @@ -35,7 +38,7 @@ export function registerRoutes({ core: CoreSetup; repository: Record>; logger: Logger; - dependencies: Record; + dependencies: TDependencies; }) { const routes = Object.values(repository); diff --git a/packages/kbn-server-route-repository/src/test_types.ts b/packages/kbn-server-route-repository/src/test_types.ts index a55c1014317ac..16447a6ef000f 100644 --- a/packages/kbn-server-route-repository/src/test_types.ts +++ b/packages/kbn-server-route-repository/src/test_types.ts @@ -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(value: TShape) { return value; diff --git a/tsconfig.base.json b/tsconfig.base.json index a88413e811ebd..e9fb8a5b9c426 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], diff --git a/yarn.lock b/yarn.lock index 88e404ef09a39..458f01dd1ebb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 ""