Starting router.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-01-19 17:17:28 -05:00
committed by Fredrik Adelöw
parent 046e04a7cf
commit d7585d4003
11 changed files with 1374 additions and 2 deletions
@@ -54,6 +54,7 @@ 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() {
@@ -104,6 +105,23 @@ 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,
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+5
View File
@@ -0,0 +1,5 @@
# @backstage/plugin-openapi-router-common
Welcome to the common package for the openapi-router plugin!
_This plugin was created through the Backstage CLI_
@@ -0,0 +1,37 @@
{
"name": "@backstage/plugin-openapi-router",
"description": "OpenAPI router with type completions.",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "backend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
},
"files": [
"dist"
],
"dependencies": {
"express": "^4.18.2",
"json-schema-to-ts": "^2.6.2",
"openapi-types": "^12.1.0"
}
}
@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Common functionalities for the openapi-router plugin.
*
* @packageDocumentation
*/
export * from './router';
+357
View File
@@ -0,0 +1,357 @@
/*
* 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 { OpenAPIV3_1 } from 'openapi-types';
import { FieldValues } from './types/fields';
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';
type RouterFn<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
name: TFieldName,
) => null;
const doc = {
openapi: '3.1.0',
info: {
version: '1.0.0',
title: 'Swagger Petstore',
license: {
name: 'MIT',
url: 'https://opensource.org/licenses/MIT',
},
},
servers: [
{
url: 'http://petstore.swagger.io/v1',
},
],
paths: {
'/pets': {
get: {
summary: 'List all pets',
operationId: 'listPets',
tags: ['pets'],
parameters: [
{
name: 'limit',
in: 'query',
description: 'How many items to return at one time (max 100)',
required: false,
schema: {
type: 'integer',
format: 'int32',
},
},
],
responses: {
'200': {
description: 'A paged array of pets',
headers: {
'x-next': {
description: 'A link to the next page of responses',
schema: {
type: 'string',
},
},
},
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Pets',
},
},
},
},
default: {
description: 'unexpected error',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
},
post: {
summary: 'Create a pet',
operationId: 'createPets',
tags: ['pets'],
responses: {
'201': {
description: 'Null response',
},
default: {
description: 'unexpected error',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
},
},
'/pets/{petId}': {
get: {
summary: 'Info for a specific pet',
operationId: 'showPetById',
tags: ['pets'],
parameters: [
{
name: 'petId',
in: 'path',
required: true,
description: 'The id of the pet to retrieve',
schema: {
type: 'string',
},
},
],
responses: {
'200': {
description: 'Expected response to a valid request',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Pet',
},
},
},
},
default: {
description: 'unexpected error',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
},
},
},
components: {
schemas: {
Pet: {
type: 'object',
required: ['id', 'name'],
properties: {
id: {
type: 'integer',
format: 'int64',
},
name: {
type: 'string',
},
tag: {
type: 'string',
},
},
},
Pets: {
type: 'array',
items: {
$ref: '#/components/schemas/Pet',
},
},
Error: {
type: 'object',
required: ['code', 'message'],
properties: {
code: {
type: 'integer',
format: 'int32',
},
message: {
type: 'string',
},
},
},
},
},
} 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,
> = S extends `${infer P}${Tail}` ? P : S;
type GetRouteParameter<S extends string> = RemoveTail<
RemoveTail<RemoveTail<S, `/${string}`>, `-${string}`>,
`.${string}`
>;
export type RouteParameters<Route extends string> = string extends Route
? ParamsDictionary
: Route extends `${string}(${string}`
? ParamsDictionary
: Route extends `${string}:${infer Rest}`
? (GetRouteParameter<Rest> extends never
? ParamsDictionary
: GetRouteParameter<Rest> extends `${infer ParamName}?`
? { [P in ParamName]?: string }
: { [P in GetRouteParameter<Rest>]: string }) &
(Rest extends `${GetRouteParameter<Rest>}${infer Next}`
? RouteParameters<Next>
: unknown)
: {};
interface ParsedQs {
[key: string]: undefined | string | string[] | ParsedQs | ParsedQs[];
}
interface ApiRouterMatcher<
TFieldValues extends FieldValues,
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;
}
type path = ApiRouterMatcher<typeof doc.paths, any, 'get'>;
const test: path = a => {
console.log(a);
};
test('/pets/{petId}');
export interface IApiRouter<ApiSpec, PathSpec, T> extends IRouter {
all: ApiRouterMatcher<PathSpec, T, 'all'>;
get: ApiRouterMatcher<PathSpec, T, 'get'>;
}
export default class ApiRouter<
ApiSpec,
PathSpec extends OpenAPIV3_1.Document,
T,
> implements IApiRouter<ApiSpec, PathSpec, T>
{
private _router = Router();
constructor(private spec: OpenAPIV3_1.Document) {}
static fromSpec<ApiSpec, PathSpec, T>(spec: OpenAPIV3_1.Document) {
return new ApiRouter<ApiSpec, PathSpec, T>(spec);
}
get<
TFieldValues extends FieldValues = PathSpec,
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
path: TFieldName,
...handlers: core.RequestHandler<
core.ParamsDictionary,
any,
FieldPathValue<FieldPathValue<TFieldValues, TFieldName>, 'get'>,
ParsedQs,
Record<string, string>
>[]
) {
console.log(path);
return this._router.get(path, ...handlers);
}
}
const router = ApiRouter.fromSpec<typeof doc, typeof doc.paths, any>(doc);
router.get('/pets', (req, res) => {
req.body.tags;
});
@@ -0,0 +1,16 @@
/*
* 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.
*/
export {};
@@ -0,0 +1,66 @@
/*
* 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,666 @@
/*
* 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,144 @@
/*
* 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;
};
+41 -2
View File
@@ -3342,7 +3342,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.20.13
resolution: "@babel/runtime@npm:7.20.13"
dependencies:
@@ -7446,6 +7446,17 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-openapi-router@workspace:plugins/openapi-router-common":
version: 0.0.0-use.local
resolution: "@backstage/plugin-openapi-router@workspace:plugins/openapi-router-common"
dependencies:
"@backstage/cli": "workspace:^"
express: ^4.18.2
json-schema-to-ts: ^2.6.2
openapi-types: ^12.1.0
languageName: unknown
linkType: soft
"@backstage/plugin-org-react@workspace:plugins/org-react":
version: 0.0.0-use.local
resolution: "@backstage/plugin-org-react@workspace:plugins/org-react"
@@ -23777,7 +23788,7 @@ __metadata:
languageName: node
linkType: hard
"express@npm:^4.17.1, express@npm:^4.17.3, express@npm:^4.18.1":
"express@npm:^4.17.1, express@npm:^4.17.3, express@npm:^4.18.1, express@npm:^4.18.2":
version: 4.18.2
resolution: "express@npm:4.18.2"
dependencies:
@@ -28324,6 +28335,18 @@ __metadata:
languageName: node
linkType: hard
"json-schema-to-ts@npm:^2.6.2":
version: 2.6.2
resolution: "json-schema-to-ts@npm:2.6.2"
dependencies:
"@babel/runtime": ^7.18.3
"@types/json-schema": ^7.0.9
ts-algebra: ^1.1.1
ts-toolbelt: ^9.6.0
checksum: e408f8d3d32dda1a001f194cf5514fea7ef0a4dc4ed4f5448b9ea02df3a071119a0517aaffb639de725f4a12102406dfdf7743455e0e30317e231bb551362039
languageName: node
linkType: hard
"json-schema-traverse@npm:^0.4.1":
version: 0.4.1
resolution: "json-schema-traverse@npm:0.4.1"
@@ -38580,6 +38603,15 @@ __metadata:
languageName: node
linkType: hard
"ts-algebra@npm:^1.1.1":
version: 1.1.1
resolution: "ts-algebra@npm:1.1.1"
dependencies:
ts-toolbelt: ^9.6.0
checksum: 29fe27215863520377a94a14ccf1e78e921be6a5c514a0a8250dab09e325b006a9571ef1f366875d58e495b59ba880620718a1dd38b6af598cc6ea6de82d4f66
languageName: node
linkType: hard
"ts-easing@npm:^0.2.0":
version: 0.2.0
resolution: "ts-easing@npm:0.2.0"
@@ -38672,6 +38704,13 @@ __metadata:
languageName: node
linkType: hard
"ts-toolbelt@npm:^9.6.0":
version: 9.6.0
resolution: "ts-toolbelt@npm:9.6.0"
checksum: 9f35fd95d895a5d32ea9fd2e532a695b0bae6cbff6832b77292efa188a0ed1ed6e54f63f74a8920390f3d909a7a3adb20a144686372a8e78b420246a9bd3d58a
languageName: node
linkType: hard
"tsconfig-paths@npm:^3.14.1":
version: 3.14.1
resolution: "tsconfig-paths@npm:3.14.1"