fets
Version:
TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience
201 lines (200 loc) • 14.6 kB
TypeScript
import { FromSchema as FromSchemaOriginal, JSONSchema as JSONSchemaOrBoolean } from 'json-schema-to-ts';
import { OpenAPIV3_1 } from 'openapi-types';
import { FetchAPI, ServerAdapter, ServerAdapterOptions, ServerAdapterPlugin, ServerAdapterRequestHandler, UWSRequest, UWSResponse } from '@whatwg-node/server';
import { SwaggerUIOpts } from './plugins/openapi.js';
import { LazySerializedResponse } from './Response.js';
import type { HTTPMethod, StatusCode, TypedRequest, TypedResponse, TypedResponseWithJSONStatusMap } from './typed-fetch.js';
import { AddRouteWithZodSchemasOpts, RouteZodSchemas, TypedRequestFromRouteZodSchemas, TypedResponseFromRouteZodSchemas } from './zod/types.js';
export { TypedRequest as RouterRequest };
export type JSONSerializer = (obj: any) => string;
export type JSONSchema = Exclude<JSONSchemaOrBoolean, boolean>;
export interface RouterOpenAPIOptions<TComponents extends RouterComponentsBase> extends Omit<Partial<OpenAPIV3_1.Document>, 'components'> {
endpoint?: string | false;
components?: TComponents;
}
export interface RouterSwaggerUIOptions extends SwaggerUIOpts {
endpoint?: string | false;
}
type UWSRequestHandler = (res: UWSResponse, req: UWSRequest) => void | Promise<void>;
export type UWSApp = Record<'get' | 'post' | 'put' | 'patch' | 'any', (path: string, handler: UWSRequestHandler) => UWSApp>;
export interface RouterOptions<TServerContext, TComponents extends RouterComponentsBase> extends ServerAdapterOptions<TServerContext> {
base?: string;
plugins?: RouterPlugin<TServerContext>[];
openAPI?: RouterOpenAPIOptions<TComponents>;
swaggerUI?: RouterSwaggerUIOptions;
app?: UWSApp;
}
export type RouterComponentsBase = {
schemas?: Record<string, JSONSchema>;
};
export type FromSchema<T> = T extends JSONSchema ? FromSchemaOriginal<T, {
deserialize: [
{
pattern: {
type: 'string';
format: 'binary';
};
output: File;
},
{
pattern: {
type: 'number';
format: 'int64';
};
output: bigint;
},
{
pattern: {
type: 'integer';
format: 'int64';
};
output: bigint;
}
];
}> : never;
export type FromRouterComponentSchema<TRouter extends Router<any, any, any>, TName extends string> = TRouter extends Router<any, infer TComponents, any> ? TComponents extends Required<RouterComponentsBase> ? FromSchema<TComponents['schemas'][TName]> : never : never;
export type PromiseOrValue<T> = T | Promise<T>;
export type StatusCodeMap<T> = {
[TKey in StatusCode]?: T;
};
export type TypedRouterHandlerTypeConfig<TRequestJSON = any, TRequestFormData extends Record<string, FormDataEntryValue> = Record<string, FormDataEntryValue>, TRequestHeaders extends Record<string, string> = Record<string, string>, TRequestQueryParams extends Record<string, string | string[]> = Record<string, string | string[]>, TRequestPathParams extends Record<string, any> = Record<string, any>, TResponseJSONStatusMap extends StatusCodeMap<any> = StatusCodeMap<any>> = {
request: {
json?: TRequestJSON;
formData?: TRequestFormData;
headers?: TRequestHeaders;
query?: TRequestQueryParams;
params?: TRequestPathParams;
};
responses?: TResponseJSONStatusMap;
};
export type TypedRequestFromTypeConfig<TMethod extends HTTPMethod, TTypeConfig extends TypedRouterHandlerTypeConfig> = TTypeConfig extends {
request: Required<TypedRouterHandlerTypeConfig>['request'];
} ? TTypeConfig extends TypedRouterHandlerTypeConfig<infer TRequestJSON, infer TRequestFormData, infer TRequestHeaders, infer TRequestQueryParams, infer TRequestPathParams> ? TypedRequest<TRequestJSON, TRequestFormData, TRequestHeaders, TMethod, TRequestQueryParams, TRequestPathParams> : never : TypedRequest;
export type TypedResponseFromTypeConfig<TTypeConfig extends TypedRouterHandlerTypeConfig> = TTypeConfig extends {
responses: infer TResponses;
} ? TResponses extends StatusCodeMap<any> ? TypedResponseWithJSONStatusMap<TResponses> : never : TypedResponse;
export interface RouterBaseObject<TServerContext, TComponents extends RouterComponentsBase, TRouterSDK extends RouterSDK<string, TypedRequest, TypedResponse>> {
openAPIDocument: OpenAPIV3_1.Document;
handle: ServerAdapterRequestHandler<TServerContext>;
plugins: RouterPlugin<TServerContext>[];
fetchAPI: FetchAPI;
route<TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequestFromRouteSchemas<TComponents, TRouteSchemas, TMethod>, TTypedResponse extends TypedResponseFromRouteSchemas<TComponents, TRouteSchemas>>(opts: AddRouteWithSchemasOpts<TServerContext, TComponents, TRouteSchemas, TMethod, TPath, TTypedRequest, TTypedResponse>): Router<TServerContext, TComponents, TRouterSDK & RouterSDK<TPath, TTypedRequest, TTypedResponse>>;
route<TRouteZodSchemas extends RouteZodSchemas, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequestFromRouteZodSchemas<TRouteZodSchemas, TMethod>, TTypedResponse extends TypedResponseFromRouteZodSchemas<TRouteZodSchemas>>(opts: AddRouteWithZodSchemasOpts<TServerContext, TRouteZodSchemas, TMethod, TPath, TTypedRequest, TTypedResponse>): Router<TServerContext, TComponents, TRouterSDK & RouterSDK<TPath, TTypedRequest, TTypedResponse>>;
route<TTypeConfig extends TypedRouterHandlerTypeConfig, TMethod extends HTTPMethod = HTTPMethod, TTypedRequest extends TypedRequestFromTypeConfig<TMethod, TTypeConfig> = TypedRequestFromTypeConfig<TMethod, TTypeConfig>, TTypedResponse extends TypedResponseFromTypeConfig<TTypeConfig> = TypedResponseFromTypeConfig<TTypeConfig>, TPath extends string = string>(opts: AddRouteWithTypesOpts<TServerContext, TMethod, TPath, TTypedRequest, TTypedResponse>): Router<TServerContext, TComponents, TRouterSDK & RouterSDK<TPath, TTypedRequest, TTypedResponse>>;
__client: TRouterSDK;
__onRouterInitHooks: OnRouterInitHook<TServerContext>[];
__onSerializeResponseHooks: OnSerializeResponseHook<TServerContext>[];
}
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 RouteHandler<TServerContext, TTypedRequest extends TypedRequest, TTypedResponse extends TypedResponse> = (request: TTypedRequest, context: TServerContext) => PromiseOrValue<TTypedResponse | Response | void>;
export type OnRouteHookPayload<TServerContext> = {
operationId?: string;
description?: string;
tags?: string[];
method: HTTPMethod;
path: string;
schemas?: RouteSchemas | RouteZodSchemas;
openAPIDocument: OpenAPIV3_1.Document;
handlers: RouteHandler<TServerContext, TypedRequest, TypedResponse>[];
};
export type OnRouterInitHook<TServerContext> = (router: Router<TServerContext, any, any>) => void;
export type OnSerializeResponsePayload<TServerContext> = {
request: TypedRequest;
serverContext: TServerContext;
lazyResponse: LazySerializedResponse;
};
export type OnSerializeResponseHook<TServerContext> = (payload: OnSerializeResponsePayload<TServerContext>) => void;
export type RouterPlugin<TServerContext> = ServerAdapterPlugin<TServerContext> & {
onRouterInit?: OnRouterInitHook<TServerContext>;
onRoute?: OnRouteHook<TServerContext>;
onSerializeResponse?: OnSerializeResponseHook<TServerContext>;
};
export type RouteSchemas = {
request?: {
headers?: JSONSchema;
params?: JSONSchema;
query?: JSONSchema;
json?: JSONSchema;
formData?: JSONSchema;
};
responses?: StatusCodeMap<JSONSchema>;
};
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> ? {
json?: TJSONBody;
formData?: TFormData;
headers?: THeaders;
query?: TQueryParams;
params?: TPathParam;
} : never;
export type RouterSDK<TPath extends string = string, TTypedRequest extends TypedRequest = TypedRequest, TTypedResponse extends TypedResponse = TypedResponse> = {
[TPathKey in TPath]: {
[TMethod in Lowercase<TTypedRequest['method']>]: (opts?: RouterSDKOpts<TTypedRequest, TTypedRequest['method']>) => Promise<Exclude<TTypedResponse, undefined>>;
};
};
export type FromSchemaWithComponents<TComponents, TSchema extends JSONSchema> = TComponents extends {
schemas: Record<string, JSONSchema>;
} ? FromSchema<{
components: TComponents;
} & TSchema> : FromSchema<TSchema>;
export type TypedRequestFromRouteSchemas<TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod> = TRouteSchemas extends {
request: Required<RouteSchemas>['request'];
} ? TypedRequest<TRouteSchemas['request'] extends {
json: JSONSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['json']> : any, TRouteSchemas['request'] extends {
formData: JSONSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['formData']> extends Record<string, FormDataEntryValue> ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['formData']> : Record<string, FormDataEntryValue> : Record<string, FormDataEntryValue>, TRouteSchemas['request'] extends {
headers: JSONSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['headers']> extends Record<string, string> ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['headers']> : Record<string, string> : Record<string, string>, TMethod, TRouteSchemas['request'] extends {
query: JSONSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['query']> extends Record<string, string> ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['query']> : Record<string, string | string[]> : Record<string, string | string[]>, TRouteSchemas['request'] extends {
params: JSONSchema;
} ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['params']> extends Record<string, string> ? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['params']> : Record<string, any> : Record<string, any>> : TypedRequest<any, Record<string, FormDataEntryValue>, Record<string, string>, TMethod>;
export type TypedResponseFromRouteSchemas<TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas> = TRouteSchemas extends {
responses: StatusCodeMap<JSONSchema>;
} ? TypedResponseWithJSONStatusMap<{
[TStatusCode in keyof TRouteSchemas['responses']]: TRouteSchemas['responses'][TStatusCode] extends JSONSchema ? FromSchemaWithComponents<TComponents, TRouteSchemas['responses'][TStatusCode]> : never;
}> : TypedResponse;
export type AddRouteWithSchemasOpts<TServerContext, TComponents extends RouterComponentsBase, TRouteSchemas extends RouteSchemas, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequestFromRouteSchemas<TComponents, TRouteSchemas, TMethod>, TTypedResponse extends TypedResponseFromRouteSchemas<TComponents, TRouteSchemas>> = {
schemas: TRouteSchemas;
} & AddRouteWithTypesOpts<TServerContext, TMethod, TPath, TTypedRequest, TTypedResponse>;
export type AddRouteWithTypesOpts<TServerContext, TMethod extends HTTPMethod, TPath extends string, TTypedRequest extends TypedRequest, TTypedResponse extends TypedResponse> = {
operationId?: string;
description?: string;
method?: TMethod | Uppercase<TMethod>;
tags?: string[];
path: TPath;
handler: RouteHandler<TServerContext, TTypedRequest, TTypedResponse> | 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;