fets
Version:
TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience
426 lines (425 loc) • 23.6 kB
TypeScript
import type { Pipe, Strings, Tuples } from 'hotscript';
import type { FromSchema as FromSchemaOriginal, JSONSchema as JSONSchemaOrBoolean } from 'json-schema-to-ts';
import type { ErrorHandler, FetchAPI, ServerAdapter, ServerAdapterOptions, ServerAdapterPlugin, ServerAdapterRequestHandler } from '@whatwg-node/server';
import type { ClientTypedResponsePromise } from './client/clientResponse.js';
import type { ClientRequestInit } from './client/types.js';
import type { SwaggerUIOpts } from './plugins/openapi.js';
import type { HTTPMethod, StatusCode, TypedRequest, TypedResponse, TypedResponseWithJSONStatusMap } from './typed-fetch.js';
export { TypedRequest as RouterRequest };
export type Simplify<T> = {
[KeyType in keyof T]: Simplify<T[KeyType]>;
} & {};
export type JSONSerializer = (obj: any) => string;
export type JSONSchema = Exclude<JSONSchemaOrBoolean, boolean>;
/**
* Schema accepted by route definitions.
* Plain JSON Schema objects and TypeBox schemas are both allowed.
* Neither is reliably assignable to `json-schema-to-ts`'s `JSONSchema` under
* `exactOptionalPropertyTypes` (optional props often include `| undefined`), so routes
* use this structural type while `FromSchema` still resolves the concrete schema.
*/
export type RouteSchema = {
[key: string]: unknown;
static?: unknown;
};
export interface OpenAPIInfo {
title?: string | undefined;
description?: string | undefined;
version?: string | undefined;
license?: {
name?: string | undefined;
url?: string | undefined;
} | undefined;
termsOfService?: string | undefined;
contact?: {
name?: string | undefined;
url?: string | undefined;
email?: string | undefined;
} | undefined;
}
export type OpenAPIPathObject = Record<string, OpenAPIOperationObject> & {
parameters?: OpenAPIParameterObject[] | undefined;
};
export interface OpenAPIParameterObject {
name: string;
in: 'path' | 'query' | 'header' | 'cookie';
required?: boolean | undefined;
schema?: any;
}
export interface OpenAPIRequestBodyObject {
content?: Record<string, OpenAPIMediaTypeObject> | undefined;
}
export interface OpenAPIOperationObject {
operationId?: string | undefined;
description?: string | undefined;
tags?: string[] | undefined;
parameters?: OpenAPIParameterObject[] | undefined;
requestBody?: OpenAPIRequestBodyObject | undefined;
responses?: Record<string | number, OpenAPIResponseObject> | undefined;
security?: any[] | undefined;
}
export interface OpenAPIResponseObject {
description?: string | undefined;
content?: Record<string, OpenAPIMediaTypeObject> | undefined;
}
export interface OpenAPIMediaTypeObject {
schema?: any;
}
export type OpenAPIDocument = {
openapi?: string | undefined;
info?: OpenAPIInfo | undefined;
servers?: {
url: string;
}[] | string[] | undefined;
paths?: Record<string, OpenAPIPathObject> | undefined;
components?: unknown;
};
export interface RouterOpenAPIOptions<TComponents extends RouterComponentsBase> extends OpenAPIDocument {
endpoint?: string | false | undefined;
components?: TComponents | undefined;
includeValidationErrors?: boolean | undefined;
}
export interface RouterSwaggerUIOptions extends SwaggerUIOpts {
endpoint?: string | false | undefined;
}
export interface RouterOptions<TServerContext, TComponents extends RouterComponentsBase> extends ServerAdapterOptions<TServerContext> {
base?: string | undefined;
/** Must stay optional-without-`undefined` to match `ServerAdapterOptions` under EOPT. */
plugins?: RouterPlugin<TServerContext, TComponents>[];
openAPI?: RouterOpenAPIOptions<TComponents> | undefined;
swaggerUI?: RouterSwaggerUIOptions | undefined;
landingPage?: boolean | undefined;
onError?: ErrorHandler<TServerContext> | undefined;
}
export type RouterComponentsBase = {
schemas?: Record<string, JSONSchema> | undefined;
securitySchemes?: Record<string, SecurityScheme> | undefined;
};
export type BasicAuthSecurityScheme = {
type: 'http';
scheme: 'basic';
};
export type BearerAuthSecurityScheme = {
type: 'http';
scheme: 'bearer';
};
export type ApiKeySecurityScheme = {
type: 'apiKey';
name: string;
in: 'header' | 'query' | 'cookie';
};
export type SecurityScheme = BasicAuthSecurityScheme | BearerAuthSecurityScheme | ApiKeySecurityScheme;
export type Circular<TJSONSchema extends JSONSchema> = TJSONSchema extends {
properties: {
[key: string]: JSONSchema;
};
} ? TJSONSchema extends PropertyValue<TJSONSchema, Property<TJSONSchema>> ? true : [Extract<PropertyValue<TJSONSchema, Property<TJSONSchema>>, {
$id: string;
}>] extends [never] ? [ArrayItemValue<PropertyValue<TJSONSchema, Property<TJSONSchema>>>] extends [never] ? Circular<PropertyValue<TJSONSchema, Property<TJSONSchema>>> : ArrayItemValue<PropertyValue<TJSONSchema, Property<TJSONSchema>>> extends TJSONSchema ? true : Circular<PropertyValue<TJSONSchema, Property<TJSONSchema>>> : true : false;
export type Property<TJSONSchema extends JSONSchema> = keyof TJSONSchema['properties'];
export type PropertyValue<TJSONSchema extends JSONSchema, TProperty extends keyof TJSONSchema['properties']> = TJSONSchema['properties'][TProperty];
export type ArrayItemValue<TJSONSchema extends JSONSchema> = TJSONSchema extends {
items: infer TItems extends JSONSchema;
} ? TItems : never;
type HasCircularAnyOfRef<T extends JSONSchema> = T extends {
properties: Record<string, JSONSchema>;
} ? {
[K in keyof T['properties']]: ContainsAnyOfWithId<T['properties'][K]>;
}[keyof T['properties']] extends false ? false : true : false;
type ContainsAnyOfWithId<T> = T extends {
anyOf: infer Items extends readonly JSONSchema[];
} ? AnyHasId<Items> : T extends {
oneOf: infer Items extends readonly JSONSchema[];
} ? AnyHasId<Items> : T extends {
items: infer I extends JSONSchema;
} ? ContainsAnyOfWithId<I> : false;
type AnyHasId<Items extends readonly JSONSchema[]> = Items extends readonly [
infer Head extends JSONSchema,
...infer Tail extends readonly JSONSchema[]
] ? Head extends {
$id: string;
} ? true : AnyHasId<Tail> : false;
export type DirectType<T extends JSONSchema> = T extends {
static: infer U;
} ? U : T extends {
anyOf: infer Items extends readonly JSONSchema[];
} ? DirectUnionType<Items> : T extends {
oneOf: infer Items extends readonly JSONSchema[];
} ? DirectUnionType<Items> : T extends {
allOf: infer Items extends readonly JSONSchema[];
} ? DirectIntersectionType<Items> : T extends {
type: 'array';
items: infer Items extends JSONSchema;
} ? DirectType<Items>[] : T extends {
type: 'string';
enum: infer Vals extends readonly string[];
} ? Vals[number] : T extends {
type: 'string';
format: 'binary';
} ? File : T extends {
type: 'string';
} ? string : T extends {
type: 'integer' | 'number';
format: 'int64';
} ? number | bigint : T extends {
type: 'integer' | 'number';
} ? number : T extends {
type: 'boolean';
} ? boolean : T extends {
type: 'null';
} ? null : T extends {
type: 'object';
properties: infer Props extends Record<string, JSONSchema>;
required: infer Req extends readonly string[];
} ? {
[K in Extract<keyof Props, Req[number]>]: DirectType<Props[K]>;
} & {
[K in Exclude<keyof Props, Req[number]>]?: DirectType<Props[K]> | undefined;
} : T extends {
type: 'object';
properties: infer Props extends Record<string, JSONSchema>;
} ? {
[K in keyof Props]?: DirectType<Props[K]> | undefined;
} : T extends {
type: 'object';
} ? Record<string, unknown> : unknown;
type DirectUnionType<Items extends readonly JSONSchema[]> = Items extends readonly [
infer Head extends JSONSchema,
...infer Tail extends readonly JSONSchema[]
] ? DirectType<Head> | DirectUnionType<Tail> : never;
type DirectIntersectionType<Items extends readonly JSONSchema[]> = Items extends readonly [
infer Head extends JSONSchema,
...infer Tail extends readonly JSONSchema[]
] ? DirectType<Head> & DirectIntersectionType<Tail> : unknown;
type UseDirectType<T extends JSONSchema> = HasCircularAnyOfRef<T> extends true ? true : Circular<T> extends true ? true : false;
/**
* Under `exactOptionalPropertyTypes`, optional props (`prop?: T`) do not accept
* explicit `undefined`. Schema-inferred objects often produce values like
* `string | undefined` from `.get()` / missing JSON fields — widen optionals so
* those remain assignable.
*/
type AddUndefToOptionals<T> = T extends any ? T extends object ? T extends readonly any[] ? T : {
[K in keyof T]: {} extends Pick<T, K> ? T[K] | undefined : T[K];
} : T : never;
type FromSchemaResult<T> = T extends {
static: infer U;
} ? U : T extends JSONSchema ? UseDirectType<T> extends true ? DirectType<T> : FromSchemaOriginal<T, {
deserialize: [
{
pattern: {
type: 'string';
format: 'binary';
};
output: File;
},
{
pattern: {
type: 'number';
format: 'int64';
};
output: bigint | number;
},
{
pattern: {
type: 'integer';
format: 'int64';
};
output: bigint | number;
}
];
}> : never;
export type FromSchema<T> = AddUndefToOptionals<FromSchemaResult<T>>;
export type FromRouterComponentSchema<TRouter extends Router<any, any, any>, TName extends string> = TRouter extends Router<any, infer TComponents, any> ? TComponents extends {
schemas: Record<string, JSONSchema>;
} ? FromSchema<TComponents['schemas'][TName]> : never : never;
export type PromiseOrValue<T> = T | Promise<T>;
export type StatusCodeMap<T> = {
[TKey in StatusCode]?: T;
};
export interface RouterBaseObject<TServerContext, TComponents extends RouterComponentsBase, TRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse>> {
openAPIDocument: OpenAPIDocument;
handle: ServerAdapterRequestHandler<TServerContext>;
route<const TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequestFromRouteSchemas<TComponents, TRouteSchemas, TMethod, TPath>, TTypedResponse extends TypedResponseFromRouteSchemas<TComponents, TRouteSchemas>>(opts: RouteWithSchemasOpts<TServerContext, TComponents, TRouteSchemas, TMethod, TPath, TTypedRequest, TTypedResponse>): Router<TServerContext, TComponents, TRouterSDK & RouterSDK<TPath, TTypedRequest, TTypedResponse>>;
route<TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequest<any, any, any, TMethod, any, Record<ExtractPathParamsWithPattern<TPath>, string>>, TTypedResponse extends TypedResponse>(opts: RouteWithTypesOpts<TServerContext, TMethod, TPath, TTypedRequest, TTypedResponse>): Router<TServerContext, TComponents, TRouterSDK & RouterSDK<TPath, TTypedRequest, TTypedResponse>>;
use<TSubServerContext, TSubComponents extends RouterComponentsBase, TSubRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse>>(subRouter: Router<TSubServerContext, TSubComponents, TSubRouterSDK>): Router<TServerContext, TComponents, TRouterSDK & TSubRouterSDK>;
use<const TPrefix extends string, TSubServerContext, TSubComponents extends RouterComponentsBase, TSubRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse>>(prefix: TPrefix, subRouter: Router<TSubServerContext, TSubComponents, TSubRouterSDK>): Router<TServerContext, TComponents, TRouterSDK & {
[TKey in keyof TSubRouterSDK as TKey extends string ? `${TPrefix}${TKey}` : TKey]: TSubRouterSDK[TKey];
}>;
__client: TRouterSDK;
__onRouterInitHooks: OnRouterInitHook<TServerContext>[];
__routes: RouteWithSchemasOpts<any, RouterComponentsBase, RouteSchemas, HTTPMethod, string, TypedRequest, TypedResponse>[];
__base: string;
}
export type Router<TServerContext, TComponents extends RouterComponentsBase, TRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse>> = ServerAdapter<TServerContext, RouterBaseObject<TServerContext, TComponents, TRouterSDK>>;
export type OnRouteHook<TServerContext> = (payload: OnRouteHookPayload<TServerContext>) => void;
export type OnRouteHandleHook<TServerContext, TComponents extends RouterComponentsBase> = (payload: OnRouteHandleHookPayload<TServerContext, TComponents>) => void;
export interface OnRouteHandleHookPayload<TServerContext, TComponents extends RouterComponentsBase> {
request: TypedRequest;
route: RouteWithSchemasOpts<TServerContext, TComponents, RouteSchemas, HTTPMethod, string, TypedRequest, TypedResponse>;
}
export type RouteHandler<TServerContext = {}, TTypedRequest extends TypedRequest = TypedRequest, TTypedResponse extends TypedResponse = TypedResponse> = (
/**
* The request object represents the incoming HTTP request.
* This object implements [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) interface.
*/
request: TTypedRequest, context: TServerContext) => PromiseOrValue<TTypedResponse>;
export type OnRouteHookPayload<TServerContext> = {
basePath: string;
openAPIDocument: OpenAPIDocument;
routeByPatternByMethod: Map<HTTPMethod, Map<URLPattern, RouteWithSchemasOpts<any, RouterComponentsBase, RouteSchemas, HTTPMethod, string, TypedRequest, TypedResponse>>>;
routeByPathByMethod: Map<HTTPMethod, Map<string, RouteWithSchemasOpts<any, RouterComponentsBase, RouteSchemas, HTTPMethod, string, TypedRequest, TypedResponse>>>;
route: RouteWithSchemasOpts<TServerContext, RouterComponentsBase, RouteSchemas, HTTPMethod, string, TypedRequest, TypedResponse>;
fetchAPI: FetchAPI;
};
export type OnRouterInitHook<TServerContext> = (router: Router<TServerContext, any, any>) => void;
export type RouterPlugin<TServerContext, TComponents extends RouterComponentsBase> = ServerAdapterPlugin<TServerContext> & {
onRouterInit?: OnRouterInitHook<TServerContext>;
onRoute?: OnRouteHook<TServerContext>;
onRouteHandle?: OnRouteHandleHook<TServerContext, TComponents>;
};
/**
* Structural object schema accepted by routes (plain JSON Schema or TypeBox).
* Intentionally not tied to `json-schema-to-ts`'s `JSONSchema`, which TypeBox
* cannot satisfy under `exactOptionalPropertyTypes`.
*/
type ObjectSchema = {
type: 'object';
properties?: Record<string, unknown>;
required?: readonly string[];
[key: string]: unknown;
};
type ObjectSchemaWithPrimitiveProperties = {
type: 'object';
properties: Record<string, {
type: 'string' | 'number' | 'integer' | 'boolean' | 'null';
[key: string]: unknown;
}>;
required?: readonly string[];
[key: string]: unknown;
};
export type RouteSchemas = {
request?: {
headers?: ObjectSchemaWithPrimitiveProperties;
params?: ObjectSchemaWithPrimitiveProperties;
query?: ObjectSchema;
json?: RouteSchema;
formData?: ObjectSchema;
};
responses?: StatusCodeMap<RouteSchema>;
};
export type RouterSDKOpts<TTypedRequest extends TypedRequest = TypedRequest, TMethod extends HTTPMethod = HTTPMethod> = TTypedRequest extends TypedRequest<infer TJSONBody, infer TFormData, infer THeaders, TMethod, infer TQueryParams, infer TPathParam> ? Simplify<(Partial<TJSONBody> extends TJSONBody ? {
json?: TJSONBody;
} : {
json: TJSONBody;
}) & (Partial<THeaders> extends THeaders ? {
headers?: THeaders;
} : {
headers: THeaders;
}) & (Partial<TQueryParams> extends TQueryParams ? {
query?: TQueryParams;
} : {
query: TQueryParams;
}) & (Partial<TPathParam> extends TPathParam ? {
params?: TPathParam;
} : {
params: TPathParam;
})> & (Partial<TFormData> extends TFormData ? {
formData?: TFormData;
} : {
formData: TFormData;
}) : never;
export type RouterSDK<TPath extends string = string, TTypedRequest extends TypedRequest = TypedRequest, TTypedResponse extends TypedResponse = TypedResponse> = {
[TPathKey in TPath]: {
[TMethod in Lowercase<TTypedRequest['method']>]: Partial<RouterSDKOpts<TTypedRequest, TTypedRequest['method']>> extends RouterSDKOpts<TTypedRequest, TTypedRequest['method']> ? (opts?: RouterSDKOpts<TTypedRequest, TTypedRequest['method']> & ClientRequestInit) => ClientTypedResponsePromise<Exclude<TTypedResponse, undefined>> : (opts: RouterSDKOpts<TTypedRequest, TTypedRequest['method']> & ClientRequestInit) => ClientTypedResponsePromise<Exclude<TTypedResponse, undefined>>;
};
};
export type FromSchemaWithComponents<TComponents, TSchema> = TComponents extends {
schemas: Record<string, JSONSchema>;
} ? FromSchema<{
components: TComponents;
} & TSchema> : FromSchema<TSchema>;
type EnsureObject<T> = T extends object ? T : {};
export type TypedRequestFromRouteSchemas<TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod, TPath extends string> = TRouteSchemas extends {
request: Required<RouteSchemas>['request'];
} ? TypedRequest<TRouteSchemas['request'] extends {
json: RouteSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['json']> : {}, TRouteSchemas['request'] extends {
formData: ObjectSchema;
} ? EnsureObject<FromSchemaWithComponents<TComponents, TRouteSchemas['request']['formData']>> : {}, TRouteSchemas['request'] extends {
headers: ObjectSchemaWithPrimitiveProperties;
} ? EnsureObject<FromSchemaWithComponents<TComponents, TRouteSchemas['request']['headers']>> : {}, TMethod, TRouteSchemas['request'] extends {
query: ObjectSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['query']> : {}, TRouteSchemas['request'] extends {
params: ObjectSchemaWithPrimitiveProperties;
} ? EnsureObject<FromSchemaWithComponents<TComponents, TRouteSchemas['request']['params']>> extends Record<string, any> ? EnsureObject<FromSchemaWithComponents<TComponents, TRouteSchemas['request']['params']>> : Record<ExtractPathParamsWithPattern<TPath>, string> : Record<ExtractPathParamsWithPattern<TPath>, string>> : TypedRequest<any, Partial<Record<string, FormDataEntryValue>>, Partial<Record<string, string>>, TMethod, any, Record<ExtractPathParamsWithPattern<TPath>, string>>;
export type TypedResponseFromRouteSchemas<TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas> = TRouteSchemas extends {
responses: StatusCodeMap<RouteSchema>;
} ? TypedResponseWithJSONStatusMap<{
[TStatusCode in keyof TRouteSchemas['responses']]: TRouteSchemas['responses'][TStatusCode] extends RouteSchema ? FromSchemaWithComponents<TComponents, TRouteSchemas['responses'][TStatusCode]> : never;
}> : TypedResponse;
export type RouteWithSchemasOpts<TServerContext, TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequestFromRouteSchemas<TComponents, TRouteSchemas, TMethod, TPath>, TTypedResponse extends TypedResponseFromRouteSchemas<TComponents, TRouteSchemas>> = {
schemas: TRouteSchemas;
security?: SecuritySchemeRefsFromComponents<TComponents>[] | undefined;
} & RouteWithTypesOpts<TServerContext, TMethod, TPath, TTypedRequest, TTypedResponse>;
export type SecuritySchemeRefsFromComponents<TComponents extends RouterComponentsBase> = TComponents extends {
securitySchemes: Record<string, SecurityScheme>;
} ? Record<keyof TComponents['securitySchemes'], any> : never;
export type RouteWithTypesOpts<TServerContext, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequest<any, any, any, TMethod, any, Record<ExtractPathParamsWithPattern<TPath>, string>>, TTypedResponse extends TypedResponse> = {
operationId?: string | undefined;
description?: string | undefined;
method?: TMethod | undefined;
tags?: string[] | undefined;
internal?: boolean | undefined;
path: TPath;
handler: RouteHandler<TServerContext, TTypedRequest, TTypedResponse>;
};
export type RouteInput<TRouter extends Router<any, any, {}>, TPath extends string, TMethod extends Lowercase<HTTPMethod> = 'post', TParamType extends keyof RouterSDKOpts = 'json'> = TRouter extends Router<any, any, infer TRouterSDK> ? TRouterSDK[TPath][TMethod] extends (requestParams?: infer TRequestParams) => any ? TRequestParams extends {
[TParamTypeKey in TParamType]?: infer TParamTypeValue;
} ? TParamTypeValue : never : never : never;
export type RouteOutput<TRouter extends Router<any, any, {}>, TPath extends string, TMethod extends Lowercase<HTTPMethod> = 'post', TStatusCode extends StatusCode = 200> = TRouter extends Router<any, any, infer TRouterSDK> ? TRouterSDK extends RouterSDK ? TRouterSDK[TPath][TMethod] extends (...args: any[]) => Promise<infer TTypedResponse> ? TTypedResponse extends TypedResponse<infer TJSONBody, any, TStatusCode> ? TJSONBody : never : never : never : never;
export type RouterClient<TRouter extends Router<any, any, any>> = TRouter['__client'];
export type RouterInput<TRouter extends Router<any, any, any>> = {
[TPath in keyof RouterClient<TRouter>]: {
[TMethod in keyof RouterClient<TRouter>[TPath]]: RouterClient<TRouter>[TPath][TMethod] extends (requestParams?: infer TRequestParams) => any ? Required<TRequestParams> : never;
};
};
export type RouterJsonPostInput<TRouter extends Router<any, any, any>> = {
[TPath in keyof RouterClient<TRouter>]: {
[TMethod in keyof RouterClient<TRouter>[TPath]]: RouterInput<TRouter>[TPath][TMethod] extends {
json: infer TJSON;
} ? TJSON : never;
}['post'];
};
export type RouterJsonPostSuccessOutput<TRouter extends Router<any, any, any>> = {
[TPath in keyof RouterClient<TRouter>]: {
[TMethod in keyof RouterClient<TRouter>[TPath]]: RouterOutput<TRouter>[TPath][TMethod][200];
}['post'];
};
export type RouterOutput<TRouter extends Router<any, any, any>> = {
[TPath in keyof RouterClient<TRouter>]: {
[TMethod in keyof RouterClient<TRouter>[TPath]]: RouterClient<TRouter>[TPath][TMethod] extends (requestParams?: any) => Promise<infer TTypedResponse> ? {
[TStatusCode in StatusCode]: TTypedResponse extends TypedResponse<infer TJSONBody, any, TStatusCode> ? TJSONBody : never;
} : never;
};
};
export type RouterComponentSchema<TRouter extends Router<any, any, any>, TName extends string> = TRouter extends Router<any, infer TComponents, any> ? TComponents extends {
schemas: Record<string, JSONSchema>;
} ? FromSchema<TComponents['schemas'][TName]> : never : never;
type SplitByDelimiter<T extends string, D extends string> = T extends `${infer P}${D}${infer Q}` ? [P, ...SplitByDelimiter<Q, D>] : [T];
type IsPathParameter<T extends string> = T extends `{${infer U}}` ? U : never;
type ExtractPathParametersFromSegment<T extends string> = IsPathParameter<T>;
type ExtractPathParameters<T extends any[]> = {
[K in keyof T]: ExtractPathParametersFromSegment<T[K]>;
};
type TupleToUnion<T> = T extends any[] ? T[number] : never;
type ExtractSegments<TPath extends string> = SplitByDelimiter<TPath, '/'>;
type ExtractSubSegments<T extends any[]> = {
[K in keyof T]: SplitByDelimiter<T[K], ';'>;
};
export type ExtractPathParamsWithBrackets<TPath extends string> = TupleToUnion<ExtractPathParameters<ExtractSubSegments<ExtractSegments<TPath>>[number]>>;
export type ExtractPathParamsWithPattern<TPath extends string> = Pipe<TPath, [
Strings.Split<'/'>,
Tuples.Filter<Strings.StartsWith<':'>>,
Tuples.Map<Strings.Trim<':'>>,
Tuples.ToUnion
]>;