Ading server testing and better handlers.
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
committed by
Fredrik Adelöw
parent
ef4d42ec30
commit
e1962a4f0a
@@ -54,7 +54,6 @@ import { initialize } from 'express-openapi';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import OpenAPIBackend from 'openapi-backend';
|
||||
|
||||
class ParsingError extends Error {
|
||||
toString() {
|
||||
@@ -105,23 +104,6 @@ export async function createRouter(
|
||||
logger.info('Catalog is running in readonly mode');
|
||||
}
|
||||
|
||||
// create api with your definition file or object
|
||||
const api = new OpenAPIBackend({ definition: './petstore.yml' });
|
||||
|
||||
// register your framework specific request handlers here
|
||||
api.register({
|
||||
getPets: (c, req, res) => {
|
||||
return res.status(200).json({ result: 'ok' });
|
||||
},
|
||||
getPetById: (c, req, res) => res.status(200).json({ result: 'ok' }),
|
||||
validationFail: (c, req, res) =>
|
||||
res.status(400).json({ err: c.validation.errors }),
|
||||
notFound: (c, req, res) => res.status(404).json({ err: 'not found' }),
|
||||
});
|
||||
|
||||
// initalize the backend
|
||||
api.init();
|
||||
|
||||
const validateDependency = (
|
||||
dependency: any,
|
||||
next: (req: express.Request, res: express.Response) => any,
|
||||
@@ -143,6 +125,7 @@ export async function createRouter(
|
||||
// NOTE: If using yaml you can provide a path relative to process.cwd() e.g.
|
||||
// apiDoc: './api-v1/api-doc.yml',
|
||||
apiDoc: yaml.load(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
fs.readFileSync(path.resolve(__dirname, '../../openapi.yaml'), 'utf-8'),
|
||||
) as any,
|
||||
operations: {
|
||||
@@ -345,12 +328,18 @@ export async function createRouter(
|
||||
},
|
||||
enableObjectCoercion: true,
|
||||
errorMiddleware: errorHandler(),
|
||||
errorTransformer: (openapiError, ajvError) => {
|
||||
switch (openapiError.errorCode) {
|
||||
errorTransformer: openapiError => {
|
||||
const error = openapiError as {
|
||||
errorCode: string;
|
||||
path: string;
|
||||
message: string;
|
||||
};
|
||||
// eslint-disable-next-line default-case
|
||||
switch (error.errorCode) {
|
||||
case 'type.openapi.requestValidation':
|
||||
throw new InputError(
|
||||
`Invalid field ${openapiError.path}`,
|
||||
new ParsingError(openapiError.message),
|
||||
`Invalid field ${error.path}`,
|
||||
new ParsingError(error.message),
|
||||
);
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"json-schema-to-ts": "^2.6.2",
|
||||
"openapi-types": "^12.1.0"
|
||||
"openapi-types": "^12.1.0",
|
||||
"openapi3-ts": "^3.1.2",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,26 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
import { FieldValues } from './types/fields';
|
||||
import { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
||||
import { IRouter, Router } from 'express';
|
||||
import core, { ParamsDictionary } from 'express-serve-static-core';
|
||||
import { FromSchema, JSONSchema7 } from 'json-schema-to-ts';
|
||||
import {
|
||||
AppendNonBlankKey,
|
||||
FieldPath,
|
||||
FieldPathValue,
|
||||
Path,
|
||||
} from './types/path';
|
||||
import { IRouter, Router, IRouterMatcher } from 'express';
|
||||
import core, {
|
||||
ParamsDictionary,
|
||||
RequestHandler,
|
||||
} from 'express-serve-static-core';
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
DocPath,
|
||||
DocPathMethod,
|
||||
DocPathTemplate,
|
||||
MethodAwareDocPath,
|
||||
PathTemplate,
|
||||
RequestBodySchema,
|
||||
RequiredDoc,
|
||||
ValueOf,
|
||||
} from './types';
|
||||
import { ResponseSchemas } from './types/response';
|
||||
|
||||
type RouterFn<TFieldValues extends FieldValues> = <
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
name: TFieldName,
|
||||
) => null;
|
||||
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
|
||||
|
||||
const doc = {
|
||||
openapi: '3.1.0',
|
||||
@@ -201,59 +198,6 @@ const doc = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const isUndefined = (a: any): a is undefined => a === undefined;
|
||||
const isNull = (a: any): a is null => a === null;
|
||||
|
||||
const isNullOrUndefined = (a: any): a is undefined | null =>
|
||||
isUndefined(a) || isNull(a);
|
||||
|
||||
const isDateObject = (value: unknown): value is Date => value instanceof Date;
|
||||
|
||||
const isObjectType = (value: unknown) => typeof value === 'object';
|
||||
|
||||
const isObject = <T extends object>(value: unknown): value is T =>
|
||||
!isNullOrUndefined(value) &&
|
||||
!Array.isArray(value) &&
|
||||
isObjectType(value) &&
|
||||
!isDateObject(value);
|
||||
|
||||
const compact = <TValue>(value: TValue[]) =>
|
||||
Array.isArray(value) ? value.filter(Boolean) : [];
|
||||
|
||||
export const resolve = <T>(
|
||||
obj: T,
|
||||
path: string,
|
||||
defaultValue?: unknown,
|
||||
): any => {
|
||||
if (!path || !isObject(obj)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const result = compact(path.split(/[,[\].]+?/)).reduce(
|
||||
(result, key) =>
|
||||
isNullOrUndefined(result) ? result : result[key as keyof {}],
|
||||
obj,
|
||||
);
|
||||
if (result === undefined || result === obj) {
|
||||
return obj[path as keyof T] === undefined
|
||||
? defaultValue
|
||||
: obj[path as keyof T];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* We want this to input path and have
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
const x: RouterFn<typeof doc.paths> = name => {
|
||||
console.log(resolve(doc, name));
|
||||
return null;
|
||||
};
|
||||
|
||||
x('/pets/{petId}');
|
||||
|
||||
type RemoveTail<
|
||||
S extends string,
|
||||
Tail extends string,
|
||||
@@ -279,79 +223,166 @@ export type RouteParameters<Route extends string> = string extends Route
|
||||
interface ParsedQs {
|
||||
[key: string]: undefined | string | string[] | ParsedQs | ParsedQs[];
|
||||
}
|
||||
interface ApiRouterMatcher<
|
||||
TFieldValues extends FieldValues,
|
||||
|
||||
// oh boy don't do this
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||
k: infer I,
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
type LastOf<T> = UnionToIntersection<
|
||||
T extends any ? () => T : never
|
||||
> extends () => infer R
|
||||
? R
|
||||
: never;
|
||||
|
||||
// TS4.0+
|
||||
type Push<T extends any[], V> = [...T, V];
|
||||
|
||||
// TS4.1+
|
||||
type TuplifyUnion<
|
||||
T,
|
||||
Method extends
|
||||
| 'all'
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'put'
|
||||
| 'delete'
|
||||
| 'patch'
|
||||
| 'options'
|
||||
| 'head',
|
||||
> {
|
||||
<
|
||||
TFieldName extends Path<TFieldValues> = Path<TFieldValues>,
|
||||
P = RouteParameters<TFieldName>,
|
||||
ResBody = any,
|
||||
ReqBody = any,
|
||||
ReqQuery = ParsedQs,
|
||||
Locals extends Record<string, any> = Record<string, any>,
|
||||
>(
|
||||
// (it's used as the default type parameter for P)
|
||||
path: TFieldName,
|
||||
// (This generic is meant to be passed explicitly.)
|
||||
...handlers: Array<RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals>>
|
||||
): T;
|
||||
}
|
||||
L = LastOf<T>,
|
||||
N = [T] extends [never] ? true : false,
|
||||
> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>;
|
||||
|
||||
type path = ApiRouterMatcher<typeof doc.paths, any, 'get'>;
|
||||
const test: path = a => {
|
||||
console.log(a);
|
||||
};
|
||||
test('/pets/{petId}');
|
||||
type ConvertAll<T, R extends ReadonlyArray<unknown> = []> = T extends [
|
||||
infer First extends JSONSchema7,
|
||||
...infer Rest,
|
||||
]
|
||||
? ConvertAll<Rest, [...R, FromSchema<First>]>
|
||||
: R;
|
||||
|
||||
export interface IApiRouter<ApiSpec, PathSpec, T> extends IRouter {
|
||||
all: ApiRouterMatcher<PathSpec, T, 'all'>;
|
||||
get: ApiRouterMatcher<PathSpec, T, 'get'>;
|
||||
}
|
||||
type ResponseToJsonSchema<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends PathTemplate<Extract<keyof Doc['paths'], string>>,
|
||||
Method extends DocPathMethod<Doc, Path>,
|
||||
> = ConvertAll<
|
||||
TuplifyUnion<ValueOf<ResponseSchemas<Doc, Path, Method>>>
|
||||
>[number];
|
||||
|
||||
export default class ApiRouter<
|
||||
ApiSpec,
|
||||
PathSpec extends OpenAPIV3_1.Document,
|
||||
T,
|
||||
> implements IApiRouter<ApiSpec, PathSpec, T>
|
||||
{
|
||||
type DocRequestHandler<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends DocPathTemplate<Doc>,
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
> = core.RequestHandler<
|
||||
core.ParamsDictionary,
|
||||
// From https://stackoverflow.com/questions/71393738/typescript-intersection-not-union-type-from-json-schema.
|
||||
ResponseToJsonSchema<Doc, Path, Method>,
|
||||
RequestBodySchema<Doc, Path, Method>,
|
||||
ParsedQs,
|
||||
Record<string, string>
|
||||
>;
|
||||
|
||||
export default class ApiRouter<Doc extends RequiredDoc> {
|
||||
private _router = Router();
|
||||
|
||||
constructor(private spec: OpenAPIV3_1.Document) {}
|
||||
constructor(private spec: OpenAPIV3_1.Document | OpenAPIV3.Document) {}
|
||||
|
||||
static fromSpec<ApiSpec, PathSpec, T>(spec: OpenAPIV3_1.Document) {
|
||||
return new ApiRouter<ApiSpec, PathSpec, T>(spec);
|
||||
static fromSpec<Doc extends RequiredDoc>(
|
||||
spec: OpenAPIV3_1.Document | OpenAPIV3.Document,
|
||||
) {
|
||||
return new ApiRouter<Doc>(spec);
|
||||
}
|
||||
|
||||
get<
|
||||
TFieldValues extends FieldValues = PathSpec,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
path: TFieldName,
|
||||
...handlers: core.RequestHandler<
|
||||
core.ParamsDictionary,
|
||||
any,
|
||||
FieldPathValue<TFieldValues, TFieldName>,
|
||||
ParsedQs,
|
||||
Record<string, string>
|
||||
>[]
|
||||
get<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'get'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'get'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
return this._router.get(path, ...handlers);
|
||||
this._router.get(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
post<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'post'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'post'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.post(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
all<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'all'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'all'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.all(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
put<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'put'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'put'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.put(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
delete<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'delete'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'delete'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.delete(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
patch<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'patch'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'patch'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.patch(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
options<
|
||||
Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'options'>,
|
||||
>(path: Path, ...handlers: DocRequestHandler<Doc, Path, 'options'>[]) {
|
||||
console.log(path);
|
||||
this._router.options(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
head<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, 'head'>>(
|
||||
path: Path,
|
||||
...handlers: DocRequestHandler<Doc, Path, 'head'>[]
|
||||
) {
|
||||
console.log(path);
|
||||
this._router.head(path, ...handlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
use(...handlers: core.RequestHandler[]) {
|
||||
return this._router.use(handlers);
|
||||
}
|
||||
|
||||
build() {
|
||||
return this._router;
|
||||
}
|
||||
}
|
||||
|
||||
const router = ApiRouter.fromSpec<typeof doc, typeof doc.paths, any>(doc);
|
||||
interface RouterOptions {}
|
||||
|
||||
router.get('/pets', (req, res) => {
|
||||
req.body.tags;
|
||||
});
|
||||
export async function createRouter(options: RouterOptions) {
|
||||
const router = ApiRouter.fromSpec<DeepWriteable<typeof doc>>(
|
||||
// As const forces the doc to readonly which conflicts with imported types.
|
||||
doc as DeepWriteable<typeof doc>,
|
||||
);
|
||||
|
||||
router.get('/pets/:uid', (req, res) => {
|
||||
res.json({
|
||||
id: 1,
|
||||
name: req.params['uid'],
|
||||
});
|
||||
});
|
||||
|
||||
// router.get('/pet') will complain with a TS error
|
||||
|
||||
router.post('/pets', (req, res) => {
|
||||
res.json({
|
||||
message: req.path,
|
||||
code: 1,
|
||||
});
|
||||
});
|
||||
return router.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import yn from 'yn';
|
||||
import { getRootLogger } from '@backstage/backend-common';
|
||||
import { startStandaloneServer } from './standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3004;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createServiceBuilder } from '@backstage/backend-common';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'app-backend' });
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
appPackageName: 'example-app',
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('', router);
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Pulled from https://github.com/varanauskas/oatx.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ContentObject,
|
||||
OpenAPIObject,
|
||||
ReferenceObject,
|
||||
} from 'openapi3-ts';
|
||||
|
||||
export type RequiredDoc = Pick<OpenAPIObject, 'paths' | 'components'>;
|
||||
export type PathDoc = { paths: Record<string, unknown> };
|
||||
|
||||
/**
|
||||
* Get value types of `T`
|
||||
*/
|
||||
export type ValueOf<T> = T[keyof T];
|
||||
|
||||
/**
|
||||
* Validate a string against OpenAPI path template
|
||||
* ```
|
||||
* const path = PathTemplate<"/posts/{postId}/comments/{commentId}"> = "/posts/1/comments/2"const pathWithParams: PathTemplate<"/posts/{postId}/comments/{commentId}"> = "/posts/1/comments/2";
|
||||
* const pathWithoutParams: PathTemplate<"/posts/comments"> = "/posts/comments";```
|
||||
* https://spec.openapis.org/oas/v3.1.0#path-templating-matching
|
||||
*/
|
||||
export type PathTemplate<Path extends string> =
|
||||
Path extends `${infer Prefix}{${string}}${infer Suffix}`
|
||||
? `${Prefix}${string}${PathTemplate<Suffix>}`
|
||||
: Path;
|
||||
|
||||
/**
|
||||
* Extract path as specified in OpenAPI `Doc` based on request path
|
||||
* ```
|
||||
* const spec = {
|
||||
* paths: {
|
||||
* "/posts/{postId}/comments/{commentId}": {},
|
||||
* "/posts/comments": {},
|
||||
* }
|
||||
* };
|
||||
* const specPathWithParams: DocPath<typeof spec, "/posts/1/comments/2"> = "/posts/{postId}/comments/{commentId}";
|
||||
* const specPathWithoutParams: DocPath<typeof spec, "/posts/comments"> = "/posts/comments";
|
||||
* ```
|
||||
*/
|
||||
export type DocPath<
|
||||
Doc extends PathDoc,
|
||||
Path extends PathTemplate<Extract<keyof Doc['paths'], string>>,
|
||||
> = ValueOf<{
|
||||
[Template in Extract<
|
||||
keyof Doc['paths'],
|
||||
string
|
||||
>]: Path extends PathTemplate<Template> ? Template : never;
|
||||
}>;
|
||||
|
||||
export type DocPathTemplate<Doc extends PathDoc> = PathTemplate<
|
||||
Extract<keyof Doc['paths'], string>
|
||||
>;
|
||||
|
||||
export type DocPathMethod<
|
||||
Doc extends Pick<RequiredDoc, 'paths'>,
|
||||
Path extends DocPathTemplate<Doc>,
|
||||
> = keyof Doc['paths'][DocPath<Doc, Path>];
|
||||
|
||||
export type MethodAwareDocPath<
|
||||
Doc extends PathDoc,
|
||||
Path extends PathTemplate<Extract<keyof Doc['paths'], string>>,
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
> = ValueOf<{
|
||||
[Template in Extract<
|
||||
keyof Doc['paths'],
|
||||
string
|
||||
>]: Path extends PathTemplate<Template>
|
||||
? Method extends DocPathMethod<Doc, Path>
|
||||
? PathTemplate<Template>
|
||||
: never
|
||||
: never;
|
||||
}>;
|
||||
|
||||
export type DocOperation<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends keyof Doc['paths'],
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
> = Doc['paths'][Path][Method];
|
||||
|
||||
export type ComponentTypes<Doc extends RequiredDoc> = Extract<
|
||||
keyof Doc['components'],
|
||||
string
|
||||
>;
|
||||
|
||||
export type ComponentRef<
|
||||
Doc extends RequiredDoc,
|
||||
Type extends ComponentTypes<Doc>,
|
||||
Ref extends ReferenceObject,
|
||||
> = Ref extends { $ref: `#/components/${Type}/${infer Name}` }
|
||||
? Name extends keyof Doc['components'][Type]
|
||||
? Doc['components'][Type][Name] extends ReferenceObject
|
||||
? ComponentRef<Doc, Type, Doc['components'][Type][Name]>
|
||||
: Doc['components'][Type][Name]
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type SchemaRef<Doc extends RequiredDoc, Schema> = Schema extends {
|
||||
$ref: `#/components/schemas/${infer Name}`;
|
||||
}
|
||||
? 'schemas' extends keyof Doc['components']
|
||||
? Name extends keyof Doc['components']['schemas']
|
||||
? SchemaRef<Doc, Doc['components']['schemas'][Name]>
|
||||
: never
|
||||
: never
|
||||
: { [Key in keyof Schema]: SchemaRef<Doc, Schema[Key]> };
|
||||
|
||||
export type ObjectWithContentSchema<
|
||||
Doc extends RequiredDoc,
|
||||
Object extends { content?: ContentObject },
|
||||
> = Object['content'] extends ContentObject
|
||||
? SchemaRef<Doc, Object['content']['application/json']['schema']>
|
||||
: never;
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
import { IsFlatObject, Noop } from './utils';
|
||||
|
||||
export type InternalFieldName = string;
|
||||
|
||||
export type FieldName<TFieldValues extends FieldValues> =
|
||||
IsFlatObject<TFieldValues> extends true
|
||||
? Extract<keyof TFieldValues, string>
|
||||
: string;
|
||||
|
||||
export type CustomElement<TFieldValues extends FieldValues> = {
|
||||
name: FieldName<TFieldValues>;
|
||||
type?: string;
|
||||
value?: any;
|
||||
disabled?: boolean;
|
||||
checked?: boolean;
|
||||
options?: HTMLOptionsCollection;
|
||||
files?: FileList | null;
|
||||
focus?: Noop;
|
||||
};
|
||||
|
||||
export type FieldValue<TFieldValues extends FieldValues> =
|
||||
TFieldValues[InternalFieldName];
|
||||
|
||||
export type FieldValues = Record<string, any>;
|
||||
|
||||
export type NativeFieldValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| unknown[];
|
||||
|
||||
export type FieldElement<TFieldValues extends FieldValues = FieldValues> =
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement
|
||||
| CustomElement<TFieldValues>;
|
||||
|
||||
export type Ref = FieldElement;
|
||||
|
||||
export type Field = {
|
||||
_f: {
|
||||
ref: Ref;
|
||||
name: InternalFieldName;
|
||||
refs?: HTMLInputElement[];
|
||||
mount?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type FieldRefs = Partial<Record<InternalFieldName, Field>>;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './common';
|
||||
export * from './requests';
|
||||
@@ -1,666 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import { FieldValues } from './fields';
|
||||
import {
|
||||
BrowserNativeObject,
|
||||
IsAny,
|
||||
IsNever,
|
||||
IsEqual,
|
||||
Primitive,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* Type alias to `string` which describes a lodash-like path through an object.
|
||||
* E.g. `'foo.bar.0.baz'`
|
||||
*/
|
||||
export type PathString = string;
|
||||
|
||||
/**
|
||||
* Type which can be traversed through with a {@link PathString}.
|
||||
* I.e. objects, arrays, and tuples
|
||||
*/
|
||||
export type Traversable = object;
|
||||
|
||||
/**
|
||||
* Type to query whether an array type T is a tuple type.
|
||||
* @typeParam T - type which may be an array or tuple
|
||||
* @example
|
||||
* ```
|
||||
* IsTuple<[number]> = true
|
||||
* IsTuple<number[]> = false
|
||||
* ```
|
||||
*/
|
||||
export type IsTuple<T extends ReadonlyArray<any>> = number extends T['length']
|
||||
? false
|
||||
: true;
|
||||
|
||||
/**
|
||||
* Type which can be used to index an array or tuple type.
|
||||
*/
|
||||
export type ArrayKey = number;
|
||||
|
||||
/**
|
||||
* Type which can be used to index an object.
|
||||
*/
|
||||
export type Key = string;
|
||||
|
||||
/**
|
||||
* Type to assert that a type is a {@link Key}.
|
||||
* @typeParam T - type which may be a {@link Key}
|
||||
*/
|
||||
export type AsKey<T> = Extract<T, Key>;
|
||||
|
||||
/**
|
||||
* Type to convert a type to a {@link Key}.
|
||||
* @typeParam T - type which may be converted to a {@link Key}
|
||||
*/
|
||||
export type ToKey<T> = T extends ArrayKey ? `${T}` : AsKey<T>;
|
||||
|
||||
/**
|
||||
* Type which describes a path through an object
|
||||
* as a list of individual {@link Key}s.
|
||||
*/
|
||||
export type PathTuple = Key[];
|
||||
|
||||
/**
|
||||
* Type to assert that a type is a {@link PathTuple}.
|
||||
* @typeParam T - type which may be a {@link PathTuple}
|
||||
*/
|
||||
export type AsPathTuple<T> = Extract<T, PathTuple>;
|
||||
|
||||
/**
|
||||
* Type to intersect a union type.
|
||||
* See https://fettblog.eu/typescript-union-to-intersection/
|
||||
* @typeParam U - union
|
||||
* @example
|
||||
* ```
|
||||
* UnionToIntersection<{ foo: string } | { bar: number }>
|
||||
* = { foo: string; bar: number }
|
||||
* ```
|
||||
*/
|
||||
export type UnionToIntersection<U> = (
|
||||
U extends any ? (_: U) => any : never
|
||||
) extends (_: infer I) => any
|
||||
? I
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Type which appends a {@link Key} to the {@link PathTuple} only if it is not
|
||||
* blank, i.e. not the empty string.
|
||||
* @typeParam PT - path
|
||||
* @typeParam K - key
|
||||
* @example
|
||||
* ```
|
||||
* AppendNonBlankKey<['foo'], 'bar'> = ['foo', 'bar']
|
||||
* AppendNonBlankKey<['foo'], ''> = ['foo']
|
||||
* ```
|
||||
*/
|
||||
export type AppendNonBlankKey<
|
||||
PT extends PathTuple,
|
||||
K extends Key,
|
||||
> = K extends '' ? PT : [...PT, K];
|
||||
|
||||
/**
|
||||
* Type to implement {@link SplitPathString} tail recursively.
|
||||
* @typeParam PS - remaining {@link PathString} which should be split into its
|
||||
* individual {@link Key}s
|
||||
* @typeParam PT - accumulator of the {@link Key}s which have been split from
|
||||
* the original {@link PathString} already
|
||||
*/
|
||||
type SplitPathStringImpl<
|
||||
PS extends PathString,
|
||||
PT extends PathTuple,
|
||||
> = PS extends `${infer K}.${infer R}`
|
||||
? SplitPathStringImpl<R, AppendNonBlankKey<PT, K>>
|
||||
: AppendNonBlankKey<PT, PS>;
|
||||
|
||||
/**
|
||||
* Type to split a {@link PathString} into a {@link PathTuple}.
|
||||
* The individual {@link Key}s may be empty strings.
|
||||
* @typeParam PS - {@link PathString} which should be split into its
|
||||
* individual {@link Key}s
|
||||
* @example
|
||||
* ```
|
||||
* SplitPathString<'foo'> = ['foo']
|
||||
* SplitPathString<'foo.bar.0.baz'> = ['foo', 'bar', '0', 'baz']
|
||||
* SplitPathString<'.'> = []
|
||||
* ```
|
||||
*/
|
||||
export type SplitPathString<PS extends PathString> = SplitPathStringImpl<
|
||||
PS,
|
||||
[]
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type to implement {@link JoinPathTuple} tail-recursively.
|
||||
* @typeParam PT - remaining {@link Key}s which needs to be joined
|
||||
* @typeParam PS - accumulator of the already joined {@link Key}s
|
||||
*/
|
||||
type JoinPathTupleImpl<
|
||||
PT extends PathTuple,
|
||||
PS extends PathString,
|
||||
> = PT extends [infer K, ...infer R]
|
||||
? JoinPathTupleImpl<AsPathTuple<R>, `${PS}.${AsKey<K>}`>
|
||||
: PS;
|
||||
|
||||
/**
|
||||
* Type to join a {@link PathTuple} to a {@link PathString}.
|
||||
* @typeParam PT - {@link PathTuple} which should be joined.
|
||||
* @example
|
||||
* ```
|
||||
* JoinPathTuple<['foo']> = 'foo'
|
||||
* JoinPathTuple<['foo', 'bar', '0', 'baz']> = 'foo.bar.0.baz'
|
||||
* JoinPathTuple<[]> = never
|
||||
* ```
|
||||
*/
|
||||
export type JoinPathTuple<PT extends PathTuple> = PT extends [
|
||||
infer K,
|
||||
...infer R,
|
||||
]
|
||||
? JoinPathTupleImpl<AsPathTuple<R>, AsKey<K>>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Type which converts all keys of an object to {@link Key}s.
|
||||
* @typeParam T - object type
|
||||
* @example
|
||||
* ```
|
||||
* MapKeys<{0: string}> = {'0': string}
|
||||
* ```
|
||||
*/
|
||||
type MapKeys<T> = { [K in keyof T as ToKey<K>]: T[K] };
|
||||
|
||||
/**
|
||||
* Type to access a type by a key.
|
||||
* - Returns undefined if it can't be indexed by that key.
|
||||
* - Returns null if the type is null.
|
||||
* - Returns undefined if the type is not traversable.
|
||||
* @typeParam T - type which is indexed by the key
|
||||
* @typeParam K - key into the type
|
||||
* ```
|
||||
* TryAccess<{foo: string}, 'foo'> = string
|
||||
* TryAccess<{foo: string}, 'bar'> = undefined
|
||||
* TryAccess<null, 'foo'> = null
|
||||
* TryAccess<string, 'foo'> = undefined
|
||||
* ```
|
||||
*/
|
||||
type TryAccess<T, K> = K extends keyof T
|
||||
? T[K]
|
||||
: T extends null
|
||||
? null
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* Type to access an array type by a key.
|
||||
* Returns undefined if the key is non-numeric.
|
||||
* @typeParam T - type which is indexed by the key
|
||||
* @typeParam K - key into the type
|
||||
* ```
|
||||
* TryAccessArray<string[], '0'> = string
|
||||
* TryAccessArray<string[], 'foo'> = undefined
|
||||
* ```
|
||||
*/
|
||||
type TryAccessArray<
|
||||
T extends ReadonlyArray<any>,
|
||||
K extends Key,
|
||||
> = K extends `${ArrayKey}` ? T[number] : TryAccess<T, K>;
|
||||
|
||||
/**
|
||||
* Type to evaluate the type which the given key points to.
|
||||
* @typeParam T - type which is indexed by the key
|
||||
* @typeParam K - key into the type
|
||||
* @example
|
||||
* ```
|
||||
* EvaluateKey<{foo: string}, 'foo'> = string
|
||||
* EvaluateKey<[number, string], '1'> = string
|
||||
* EvaluateKey<string[], '1'> = string
|
||||
* ```
|
||||
*/
|
||||
export type EvaluateKey<T, K extends Key> = T extends ReadonlyArray<any>
|
||||
? IsTuple<T> extends true
|
||||
? TryAccess<T, K>
|
||||
: TryAccessArray<T, K>
|
||||
: TryAccess<MapKeys<T>, K>;
|
||||
|
||||
/**
|
||||
* Type to evaluate the type which the given path points to.
|
||||
* @typeParam T - deeply nested type which is indexed by the path
|
||||
* @typeParam PT - path into the deeply nested type
|
||||
* @example
|
||||
* ```
|
||||
* EvaluatePath<{foo: {bar: string}}, ['foo', 'bar']> = string
|
||||
* EvaluatePath<[number, string], ['1']> = string
|
||||
* EvaluatePath<number, []> = number
|
||||
* EvaluatePath<number, ['foo']> = undefined
|
||||
* ```
|
||||
*/
|
||||
export type EvaluatePath<T, PT extends PathTuple> = PT extends [
|
||||
infer K,
|
||||
...infer R,
|
||||
]
|
||||
? EvaluatePath<EvaluateKey<T, AsKey<K>>, AsPathTuple<R>>
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Type which given a tuple type returns its own keys, i.e. only its indices.
|
||||
* @typeParam T - tuple type
|
||||
* @example
|
||||
* ```
|
||||
* TupleKeys<[number, string]> = '0' | '1'
|
||||
* ```
|
||||
*/
|
||||
export type TupleKeys<T extends ReadonlyArray<any>> = Exclude<
|
||||
keyof T,
|
||||
keyof any[]
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type which extracts all numeric keys from an object.
|
||||
* @typeParam T - type
|
||||
* @example
|
||||
* ```
|
||||
* NumericObjectKeys<{0: string, '1': string, foo: string}> = '0' | '1'
|
||||
* ```
|
||||
*/
|
||||
type NumericObjectKeys<T extends Traversable> = ToKey<
|
||||
Extract<keyof T, ArrayKey | `${ArrayKey}`>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type which extracts all numeric keys from an object, tuple, or array.
|
||||
* If a union is passed, it evaluates to the overlapping numeric keys.
|
||||
* @typeParam T - type
|
||||
* @example
|
||||
* ```
|
||||
* NumericKeys<{0: string, '1': string, foo: string}> = '0' | '1'
|
||||
* NumericKeys<number[]> = `${number}`
|
||||
* NumericKeys<[string, number]> = '0' | '1'
|
||||
* NumericKeys<{0: string, '1': string} | [number] | number[]> = '0'
|
||||
* ```
|
||||
*/
|
||||
export type NumericKeys<T extends Traversable> = UnionToIntersection<
|
||||
T extends ReadonlyArray<any>
|
||||
? IsTuple<T> extends true
|
||||
? [TupleKeys<T>]
|
||||
: [ToKey<ArrayKey>]
|
||||
: [NumericObjectKeys<T>]
|
||||
>[never];
|
||||
|
||||
/**
|
||||
* Type which extracts all keys from an object.
|
||||
* If a union is passed, it evaluates to the overlapping keys.
|
||||
* @typeParam T - object type
|
||||
* @example
|
||||
* ```
|
||||
* ObjectKeys<{foo: string, bar: string}, string> = 'foo' | 'bar'
|
||||
* ObjectKeys<{foo: string, bar: number}, string> = 'foo'
|
||||
* ```
|
||||
*/
|
||||
export type ObjectKeys<T extends Traversable> = Exclude<
|
||||
ToKey<keyof T>,
|
||||
`${string}.${string}` | ''
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type to check whether a type's property matches the constraint type
|
||||
* and return its key. Converts the key to a {@link Key}.
|
||||
* @typeParam T - type whose property should be checked
|
||||
* @typeParam K - key of the property
|
||||
* @typeParam U - constraint type
|
||||
* @example
|
||||
* ```
|
||||
* CheckKeyConstraint<{foo: string}, 'foo', string> = 'foo'
|
||||
* CheckKeyConstraint<{foo: string}, 'foo', number> = never
|
||||
* CheckKeyConstraint<string[], number, string> = `${number}`
|
||||
* ```
|
||||
*/
|
||||
export type CheckKeyConstraint<T, K extends Key, U> = K extends any
|
||||
? EvaluateKey<T, K> extends U
|
||||
? K
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Type which evaluates to true when the type is an array or tuple or is a union
|
||||
* which contains an array or tuple.
|
||||
* @typeParam T - type
|
||||
* @example
|
||||
* ```
|
||||
* ContainsIndexable<{foo: string}> = false
|
||||
* ContainsIndexable<{foo: string} | number[]> = true
|
||||
* ```
|
||||
*/
|
||||
export type ContainsIndexable<T> = IsNever<
|
||||
Extract<T, ReadonlyArray<any>>
|
||||
> extends true
|
||||
? false
|
||||
: true;
|
||||
|
||||
/**
|
||||
* Type to implement {@link Keys} for non-nullable values.
|
||||
* @typeParam T - non-nullable type whose property should be checked
|
||||
*/
|
||||
type KeysImpl<T> = [T] extends [Traversable]
|
||||
? ContainsIndexable<T> extends true
|
||||
? NumericKeys<T>
|
||||
: ObjectKeys<T>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Type to find all properties of a type that match the constraint type
|
||||
* and return their keys.
|
||||
* If a union is passed, it evaluates to the overlapping keys.
|
||||
* @typeParam T - type whose property should be checked
|
||||
* @typeParam U - constraint type
|
||||
* @example
|
||||
* ```
|
||||
* Keys<{foo: string, bar: string}, string> = 'foo' | 'bar'
|
||||
* Keys<{foo?: string, bar?: string}> = 'foo' | 'bar'
|
||||
* Keys<{foo: string, bar: number}, string> = 'foo'
|
||||
* Keys<[string, number], string> = '0'
|
||||
* Keys<string[], string> = `${number}`
|
||||
* Keys<{0: string, '1': string} | [number] | number[]> = '0'
|
||||
* ```
|
||||
*/
|
||||
export type Keys<T, U = unknown> = IsAny<T> extends true
|
||||
? Key
|
||||
: IsNever<T> extends true
|
||||
? Key
|
||||
: IsNever<NonNullable<T>> extends true
|
||||
? never
|
||||
: CheckKeyConstraint<T, KeysImpl<NonNullable<T>>, U>;
|
||||
|
||||
/**
|
||||
* Type to check whether a {@link Key} is present in a type.
|
||||
* If a union of {@link Key}s is passed, all {@link Key}s have to be present
|
||||
* in the type.
|
||||
* @typeParam T - type which is introspected
|
||||
* @typeParam K - key
|
||||
* @example
|
||||
* ```
|
||||
* HasKey<{foo: string}, 'foo'> = true
|
||||
* HasKey<{foo: string}, 'bar'> = false
|
||||
* HasKey<{foo: string}, 'foo' | 'bar'> = false
|
||||
* ```
|
||||
*/
|
||||
export type HasKey<T, K extends Key> = IsNever<Exclude<K, Keys<T>>>;
|
||||
|
||||
/**
|
||||
* Type to implement {@link ValidPathPrefix} tail recursively.
|
||||
* @typeParam T - type which the path should be checked against
|
||||
* @typeParam PT - path which should exist within the given type
|
||||
* @typeParam VPT - accumulates the prefix of {@link Key}s which have been
|
||||
* confirmed to exist already
|
||||
*/
|
||||
type ValidPathPrefixImpl<
|
||||
T,
|
||||
PT extends PathTuple,
|
||||
VPT extends PathTuple,
|
||||
> = PT extends [infer K, ...infer R]
|
||||
? HasKey<T, AsKey<K>> extends true
|
||||
? ValidPathPrefixImpl<
|
||||
EvaluateKey<T, AsKey<K>>,
|
||||
AsPathTuple<R>,
|
||||
AsPathTuple<[...VPT, K]>
|
||||
>
|
||||
: VPT
|
||||
: VPT;
|
||||
|
||||
/**
|
||||
* Type to find the longest path prefix which is still valid,
|
||||
* i.e. exists within the given type.
|
||||
* @typeParam T - type which the path should be checked against
|
||||
* @typeParam PT - path which should exist within the given type
|
||||
* @example
|
||||
* ```
|
||||
* ValidPathPrefix<{foo: {bar: string}}, ['foo', 'bar']> = ['foo', 'bar']
|
||||
* ValidPathPrefix<{foo: {bar: string}}, ['foo', 'ba']> = ['foo']
|
||||
* ```
|
||||
*/
|
||||
export type ValidPathPrefix<T, PT extends PathTuple> = ValidPathPrefixImpl<
|
||||
T,
|
||||
PT,
|
||||
[]
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type to check whether a path through a type exists.
|
||||
* @typeParam T - type which the path should be checked against
|
||||
* @typeParam PT - path which should exist within the given type
|
||||
* @example
|
||||
* ```
|
||||
* HasPath<{foo: {bar: string}}, ['foo', 'bar']> = true
|
||||
* HasPath<{foo: {bar: string}}, ['foo', 'ba']> = false
|
||||
* ```
|
||||
*/
|
||||
export type HasPath<T, PT extends PathTuple> = ValidPathPrefix<T, PT> extends PT
|
||||
? true
|
||||
: false;
|
||||
|
||||
/**
|
||||
* Helper function to break apart T1 and check if any are equal to T2
|
||||
*
|
||||
* See {@link IsEqual}
|
||||
*/
|
||||
type AnyIsEqual<T1, T2> = T1 extends T2
|
||||
? IsEqual<T1, T2> extends true
|
||||
? true
|
||||
: never
|
||||
: never;
|
||||
|
||||
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'all';
|
||||
|
||||
/**
|
||||
* Helper type for recursively constructing paths through a type.
|
||||
* This actually constructs the strings and recurses into nested
|
||||
* object types.
|
||||
*
|
||||
* See {@link Path}
|
||||
*/
|
||||
type PathImpl<K extends string | number, V, TraversedTypes> = V extends
|
||||
| Primitive
|
||||
| BrowserNativeObject
|
||||
? `${K}`
|
||||
: // Check so that we don't recurse into the same type
|
||||
// by ensuring that the types are mutually assignable
|
||||
// mutually required to avoid false positives of subtypes
|
||||
true extends AnyIsEqual<TraversedTypes, V>
|
||||
? `${K}`
|
||||
: true extends AnyIsEqual<HttpMethod, K>
|
||||
? ``
|
||||
: `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;
|
||||
|
||||
/**
|
||||
* Helper type for recursively constructing paths through a type.
|
||||
* This obsucres the internal type param TraversedTypes from exported contract.
|
||||
*
|
||||
* See {@link Path}
|
||||
*/
|
||||
type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
|
||||
? IsTuple<T> extends true
|
||||
? {
|
||||
[K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
|
||||
}[TupleKeys<T>]
|
||||
: PathImpl<ArrayKey, V, TraversedTypes>
|
||||
: {
|
||||
[K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
|
||||
}[keyof T];
|
||||
|
||||
/**
|
||||
* Type which eagerly collects all paths through a type
|
||||
* @typeParam T - type which should be introspected
|
||||
* @example
|
||||
* ```
|
||||
* Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
|
||||
* ```
|
||||
*/
|
||||
// We want to explode the union type and process each individually
|
||||
// so assignable types don't leak onto the stack from the base.
|
||||
export type Path<T> = T extends any ? PathInternal<T> : never;
|
||||
|
||||
/**
|
||||
* See {@link Path}
|
||||
*/
|
||||
export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>;
|
||||
|
||||
/**
|
||||
* Helper type for recursively constructing paths through a type.
|
||||
* This actually constructs the strings and recurses into nested
|
||||
* object types.
|
||||
*
|
||||
* See {@link ArrayPath}
|
||||
*/
|
||||
type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends
|
||||
| Primitive
|
||||
| BrowserNativeObject
|
||||
? IsAny<V> extends true
|
||||
? string
|
||||
: never
|
||||
: V extends ReadonlyArray<infer U>
|
||||
? U extends Primitive | BrowserNativeObject
|
||||
? IsAny<V> extends true
|
||||
? string
|
||||
: never
|
||||
: // Check so that we don't recurse into the same type
|
||||
// by ensuring that the types are mutually assignable
|
||||
// mutually required to avoid false positives of subtypes
|
||||
true extends AnyIsEqual<TraversedTypes, V>
|
||||
? never
|
||||
: `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`
|
||||
: true extends AnyIsEqual<TraversedTypes, V>
|
||||
? never
|
||||
: `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`;
|
||||
|
||||
/**
|
||||
* Helper type for recursively constructing paths through a type.
|
||||
* This obsucres the internal type param TraversedTypes from exported contract.
|
||||
*
|
||||
* See {@link ArrayPath}
|
||||
*/
|
||||
type ArrayPathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
|
||||
? IsTuple<T> extends true
|
||||
? {
|
||||
[K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
|
||||
}[TupleKeys<T>]
|
||||
: ArrayPathImpl<ArrayKey, V, TraversedTypes>
|
||||
: {
|
||||
[K in keyof T]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
|
||||
}[keyof T];
|
||||
|
||||
/**
|
||||
* Type which eagerly collects all paths through a type which point to an array
|
||||
* type.
|
||||
* @typeParam T - type which should be introspected.
|
||||
* @example
|
||||
* ```
|
||||
* Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz'
|
||||
* ```
|
||||
*/
|
||||
// We want to explode the union type and process each individually
|
||||
// so assignable types don't leak onto the stack from the base.
|
||||
export type ArrayPath<T> = T extends any ? ArrayPathInternal<T> : never;
|
||||
|
||||
/**
|
||||
* See {@link ArrayPath}
|
||||
*/
|
||||
export type FieldArrayPath<TFieldValues extends FieldValues> =
|
||||
ArrayPath<TFieldValues>;
|
||||
|
||||
/**
|
||||
* Type to evaluate the type which the given path points to.
|
||||
* @typeParam T - deeply nested type which is indexed by the path
|
||||
* @typeParam P - path into the deeply nested type
|
||||
* @example
|
||||
* ```
|
||||
* PathValue<{foo: {bar: string}}, 'foo.bar'> = string
|
||||
* PathValue<[number, string], '1'> = string
|
||||
* ```
|
||||
*/
|
||||
export type PathValue<T, P extends Path<T> | ArrayPath<T>> = T extends any
|
||||
? P extends `${infer K}.${infer R}`
|
||||
? K extends keyof T
|
||||
? R extends Path<T[K]>
|
||||
? PathValue<T[K], R>
|
||||
: never
|
||||
: K extends `${ArrayKey}`
|
||||
? T extends ReadonlyArray<infer V>
|
||||
? PathValue<V, R & Path<V>>
|
||||
: never
|
||||
: never
|
||||
: P extends keyof T
|
||||
? T[P]
|
||||
: P extends `${ArrayKey}`
|
||||
? T extends ReadonlyArray<infer V>
|
||||
? V
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* See {@link PathValue}
|
||||
*/
|
||||
export type FieldPathValue<
|
||||
TFieldValues extends FieldValues,
|
||||
TFieldPath extends FieldPath<TFieldValues>,
|
||||
> = PathValue<TFieldValues, TFieldPath>;
|
||||
|
||||
/**
|
||||
* See {@link PathValue}
|
||||
*/
|
||||
export type FieldArrayPathValue<
|
||||
TFieldValues extends FieldValues,
|
||||
TFieldArrayPath extends FieldArrayPath<TFieldValues>,
|
||||
> = PathValue<TFieldValues, TFieldArrayPath>;
|
||||
|
||||
/**
|
||||
* Type to evaluate the type which the given paths point to.
|
||||
* @typeParam TFieldValues - field values which are indexed by the paths
|
||||
* @typeParam TPath - paths into the deeply nested field values
|
||||
* @example
|
||||
* ```
|
||||
* FieldPathValues<{foo: {bar: string}}, ['foo', 'foo.bar']>
|
||||
* = [{bar: string}, string]
|
||||
* ```
|
||||
*/
|
||||
export type FieldPathValues<
|
||||
TFieldValues extends FieldValues,
|
||||
TPath extends FieldPath<TFieldValues>[] | readonly FieldPath<TFieldValues>[],
|
||||
> = {} & {
|
||||
[K in keyof TPath]: FieldPathValue<
|
||||
TFieldValues,
|
||||
TPath[K] & FieldPath<TFieldValues>
|
||||
>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type which eagerly collects all paths through a fieldType that matches a give type
|
||||
* @typeParam TFieldValues - field values which are indexed by the paths
|
||||
* @typeParam TValue - the value you want to match into each type
|
||||
* @example
|
||||
* ```typescript
|
||||
* FieldPathByValue<{foo: {bar: number}, baz: number, bar: string}, number>
|
||||
* = 'foo.bar' | 'baz'
|
||||
* ```
|
||||
*/
|
||||
export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = {
|
||||
[FieldKey in FieldPath<TFieldValues>]: FieldPathValue<
|
||||
TFieldValues,
|
||||
FieldKey
|
||||
> extends TValue
|
||||
? FieldKey
|
||||
: never;
|
||||
}[FieldPath<TFieldValues>];
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Pulled from https://github.com/varanauskas/oatx.
|
||||
*/
|
||||
import type { ReferenceObject, RequestBodyObject } from 'openapi3-ts';
|
||||
import type {
|
||||
ComponentRef,
|
||||
ComponentTypes,
|
||||
ObjectWithContentSchema,
|
||||
RequiredDoc,
|
||||
DocOperation,
|
||||
DocPath,
|
||||
DocPathMethod,
|
||||
DocPathTemplate,
|
||||
} from './common';
|
||||
|
||||
type RequestBody<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends Extract<keyof Doc['paths'], string>,
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
> = DocOperation<Doc, Path, Method>['requestBody'] extends ReferenceObject
|
||||
? 'requestBodies' extends ComponentTypes<Doc>
|
||||
? ComponentRef<
|
||||
Doc,
|
||||
'requestBodies',
|
||||
DocOperation<Doc, Path, Method>['requestBody']
|
||||
>
|
||||
: never
|
||||
: DocOperation<Doc, Path, Method>['requestBody'];
|
||||
|
||||
export type RequestBodySchema<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends DocPathTemplate<Doc>,
|
||||
Method extends DocPathMethod<Doc, Path>,
|
||||
> = RequestBody<Doc, DocPath<Doc, Path>, Method> extends RequestBodyObject
|
||||
? ObjectWithContentSchema<Doc, RequestBody<Doc, DocPath<Doc, Path>, Method>>
|
||||
: never;
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Pulled from https://github.com/varanauskas/oatx.
|
||||
*/
|
||||
import type { ReferenceObject, ResponseObject } from 'openapi3-ts';
|
||||
import type {
|
||||
ComponentRef,
|
||||
ComponentTypes,
|
||||
ObjectWithContentSchema,
|
||||
RequiredDoc,
|
||||
DocOperation,
|
||||
DocPath,
|
||||
DocPathMethod,
|
||||
DocPathTemplate,
|
||||
} from './common';
|
||||
|
||||
type Response<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends keyof Doc['paths'],
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
StatusCode extends keyof Doc['paths'][Path]['responses'],
|
||||
> = DocOperation<
|
||||
Doc,
|
||||
Path,
|
||||
Method
|
||||
>['responses'][StatusCode] extends ReferenceObject
|
||||
? 'responses' extends ComponentTypes<Doc>
|
||||
? ComponentRef<
|
||||
Doc,
|
||||
'responses',
|
||||
DocOperation<Doc, Path, Method>['responses'][StatusCode]
|
||||
>
|
||||
: never
|
||||
: DocOperation<Doc, Path, Method>['responses'][StatusCode];
|
||||
|
||||
type Responses<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends keyof Doc['paths'],
|
||||
Method extends keyof Doc['paths'][Path],
|
||||
> = {
|
||||
[StatusCode in keyof DocOperation<Doc, Path, Method>['responses']]: Response<
|
||||
Doc,
|
||||
Path,
|
||||
Method,
|
||||
StatusCode
|
||||
>;
|
||||
};
|
||||
|
||||
export type ResponseSchema<
|
||||
Doc extends RequiredDoc,
|
||||
Object extends ResponseObject,
|
||||
> = ObjectWithContentSchema<Doc, Object>;
|
||||
|
||||
export type ResponseSchemas<
|
||||
Doc extends RequiredDoc,
|
||||
Path extends DocPathTemplate<Doc>,
|
||||
Method extends DocPathMethod<Doc, Path>,
|
||||
> = {
|
||||
[StatusCode in keyof Responses<Doc, DocPath<Doc, Path>, Method>]: Responses<
|
||||
Doc,
|
||||
DocPath<Doc, Path>,
|
||||
Method
|
||||
>[StatusCode] extends ResponseObject
|
||||
? ResponseSchema<
|
||||
Doc,
|
||||
Responses<Doc, DocPath<Doc, Path>, Method>[StatusCode]
|
||||
>
|
||||
: never;
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
declare const $NestedValue: unique symbol;
|
||||
|
||||
/**
|
||||
* @deprecated to be removed in the next major version
|
||||
*/
|
||||
export type NestedValue<TValue extends object = object> = {
|
||||
[$NestedValue]: never;
|
||||
} & TValue;
|
||||
|
||||
/*
|
||||
Projects that React Hook Form installed don't include the DOM library need these interfaces to compile.
|
||||
React Native applications is no DOM available. The JavaScript runtime is ES6/ES2015 only.
|
||||
These definitions allow such projects to compile with only --lib ES6.
|
||||
|
||||
Warning: all of these interfaces are empty.
|
||||
If you want type definitions for various properties, you need to add `--lib DOM` (via command line or tsconfig.json).
|
||||
*/
|
||||
|
||||
export type Noop = () => void;
|
||||
|
||||
interface File extends Blob {
|
||||
readonly lastModified: number;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
interface FileList {
|
||||
readonly length: number;
|
||||
item(index: number): File | null;
|
||||
[index: number]: File;
|
||||
}
|
||||
|
||||
export type Primitive =
|
||||
| null
|
||||
| undefined
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| bigint;
|
||||
|
||||
export type BrowserNativeObject = Date | FileList | File;
|
||||
|
||||
export type EmptyObject = { [K in string | number]: never };
|
||||
|
||||
export type NonUndefined<T> = T extends undefined ? never : T;
|
||||
|
||||
export type LiteralUnion<T extends U, U extends Primitive> =
|
||||
| T
|
||||
| (U & { _?: never });
|
||||
|
||||
export type DeepPartial<T> = T extends BrowserNativeObject | NestedValue
|
||||
? T
|
||||
: { [K in keyof T]?: DeepPartial<T[K]> };
|
||||
|
||||
export type DeepPartialSkipArrayKey<T> = T extends
|
||||
| BrowserNativeObject
|
||||
| NestedValue
|
||||
? T
|
||||
: T extends ReadonlyArray<any>
|
||||
? { [K in keyof T]: DeepPartialSkipArrayKey<T[K]> }
|
||||
: { [K in keyof T]?: DeepPartialSkipArrayKey<T[K]> };
|
||||
|
||||
/**
|
||||
* Checks whether the type is any
|
||||
* See {@link https://stackoverflow.com/a/49928360/3406963}
|
||||
* @typeParam T - type which may be any
|
||||
* ```
|
||||
* IsAny<any> = true
|
||||
* IsAny<string> = false
|
||||
* ```
|
||||
*/
|
||||
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
||||
|
||||
/**
|
||||
* Checks whether the type is never
|
||||
* @typeParam T - type which may be never
|
||||
* ```
|
||||
* IsAny<never> = true
|
||||
* IsAny<string> = false
|
||||
* ```
|
||||
*/
|
||||
export type IsNever<T> = [T] extends [never] ? true : false;
|
||||
|
||||
/**
|
||||
* Checks whether T1 can be exactly (mutually) assigned to T2
|
||||
* @typeParam T1 - type to check
|
||||
* @typeParam T2 - type to check against
|
||||
* ```
|
||||
* IsEqual<string, string> = true
|
||||
* IsEqual<'foo', 'foo'> = true
|
||||
* IsEqual<string, number> = false
|
||||
* IsEqual<string, number> = false
|
||||
* IsEqual<string, 'foo'> = false
|
||||
* IsEqual<'foo', string> = false
|
||||
* IsEqual<'foo' | 'bar', 'foo'> = boolean // 'foo' is assignable, but 'bar' is not (true | false) -> boolean
|
||||
* ```
|
||||
*/
|
||||
export type IsEqual<T1, T2> = T1 extends T2
|
||||
? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2
|
||||
? true
|
||||
: false
|
||||
: false;
|
||||
|
||||
export type DeepMap<T, TValue> = IsAny<T> extends true
|
||||
? any
|
||||
: T extends BrowserNativeObject | NestedValue
|
||||
? TValue
|
||||
: T extends object
|
||||
? { [K in keyof T]: DeepMap<NonUndefined<T[K]>, TValue> }
|
||||
: TValue;
|
||||
|
||||
export type IsFlatObject<T extends object> = Extract<
|
||||
Exclude<T[keyof T], NestedValue | Date | FileList>,
|
||||
any[] | object
|
||||
> extends never
|
||||
? true
|
||||
: false;
|
||||
|
||||
export type Merge<A, B> = {
|
||||
[K in keyof A | keyof B]?: K extends keyof A & keyof B
|
||||
? [A[K], B[K]] extends [object, object]
|
||||
? Merge<A[K], B[K]>
|
||||
: A[K] | B[K]
|
||||
: K extends keyof A
|
||||
? A[K]
|
||||
: K extends keyof B
|
||||
? B[K]
|
||||
: never;
|
||||
};
|
||||
@@ -7454,6 +7454,8 @@ __metadata:
|
||||
express: ^4.18.2
|
||||
json-schema-to-ts: ^2.6.2
|
||||
openapi-types: ^12.1.0
|
||||
openapi3-ts: ^3.1.2
|
||||
ts-node: ^10.9.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -32110,6 +32112,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openapi3-ts@npm:^3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "openapi3-ts@npm:3.1.2"
|
||||
dependencies:
|
||||
yaml: ^2.1.3
|
||||
checksum: 77e5064ea9bff119ba8d8398a90246ae99174d6c23b2ed68355bb48be3243b6f4f0041a3fdcd9dba5ffe6ace786a8041d86d8a4049d65d2d40cd4bfca5bba459
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0":
|
||||
version: 5.4.0
|
||||
resolution: "openid-client@npm:5.4.0"
|
||||
|
||||
Reference in New Issue
Block a user