From 0842e2f1473494a46873e65e93f281a6288156a8 Mon Sep 17 00:00:00 2001 From: Alexey Lyakhov Date: Fri, 28 Jul 2023 16:39:10 +0700 Subject: [PATCH] enable exactOptionalPropertyTypes: true in ts config, adding | undefined for consistency, examples of fallthrough in downstream dependencies, fixed some any types, draft for review --- .vscode/settings.json | 3 +- e2e/shared-scripts/src/types.ts | 4 +- e2e/shared-server/src/index.ts | 4 +- e2e/vercel/scripts/createVercelDeployment.ts | 6 +- packages/fets/src/Response.ts | 4 +- packages/fets/src/client/auth/oauth.ts | 14 +- packages/fets/src/client/types.ts | 77 +++++---- packages/fets/src/plugins/ajv.ts | 33 +++- packages/fets/src/plugins/openapi.ts | 69 +++++---- packages/fets/src/typed-fetch.ts | 23 +-- packages/fets/src/types.ts | 155 ++++++++++--------- packages/fets/src/utils.ts | 12 +- packages/fets/src/zod/types.ts | 18 ++- tsconfig.json | 1 + website/src/pages/server/cors.mdx | 12 +- 15 files changed, 254 insertions(+), 181 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 25fa6215f..8400d618a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "cSpell.words": ["fets"] } diff --git a/e2e/shared-scripts/src/types.ts b/e2e/shared-scripts/src/types.ts index 899003742..5ebe11859 100644 --- a/e2e/shared-scripts/src/types.ts +++ b/e2e/shared-scripts/src/types.ts @@ -3,8 +3,8 @@ import { OutputValue, Stack } from '@pulumi/pulumi/automation'; export type DeploymentConfiguration = { name: string; - prerequisites?: (stack: Stack) => Promise; - config?: (stack: Stack) => Promise; + prerequisites?: ((stack: Stack) => Promise) | undefined; + config?: ((stack: Stack) => Promise) | undefined; program: () => Promise<{ [K in keyof TProgramOutput]: Output | TProgramOutput[K]; }>; diff --git a/e2e/shared-server/src/index.ts b/e2e/shared-server/src/index.ts index b25eb638f..7e366cc4e 100644 --- a/e2e/shared-server/src/index.ts +++ b/e2e/shared-server/src/index.ts @@ -1,8 +1,8 @@ import { createRouter, Response, useErrorHandling } from 'fets'; import { z } from 'zod'; -export function createTestServerAdapter(base?: string) { - return createRouter({ +export function createTestServerAdapter(base?: string | undefined) { + return createRouter({ base, plugins: [useErrorHandling()], }) diff --git a/e2e/vercel/scripts/createVercelDeployment.ts b/e2e/vercel/scripts/createVercelDeployment.ts index 6c4a9696b..3a7e230bc 100644 --- a/e2e/vercel/scripts/createVercelDeployment.ts +++ b/e2e/vercel/scripts/createVercelDeployment.ts @@ -105,7 +105,11 @@ class VercelProvider implements pulumi.dynamic.ResourceProvider { export class VercelDeployment extends pulumi.dynamic.Resource { public readonly url!: pulumi.Output; - constructor(name: string, props: VercelDeploymentInputs, opts?: pulumi.CustomResourceOptions) { + constructor( + name: string, + props: VercelDeploymentInputs, + opts?: pulumi.CustomResourceOptions | undefined, + ) { super( new VercelProvider(), name, diff --git a/packages/fets/src/Response.ts b/packages/fets/src/Response.ts index 3238dc33e..236a82544 100644 --- a/packages/fets/src/Response.ts +++ b/packages/fets/src/Response.ts @@ -9,7 +9,7 @@ export const defaultSerializer: JSONSerializer = obj => JSON.stringify(obj); export interface LazySerializedResponse { [LAZY_SERIALIZED_RESPONSE]: true; resolveWithSerializer(serializer: JSONSerializer): void; - init?: ResponseInit; + init?: ResponseInit | undefined; actualResponse: Response; jsonObj: any; json: () => Promise; @@ -27,7 +27,7 @@ function isHeadersLike(headers: any): headers is Headers { const JSON_CONTENT_TYPE = 'application/json; charset=utf-8'; -function getHeadersFromHeadersInit(init?: HeadersInit): Headers { +function getHeadersFromHeadersInit(init?: HeadersInit | undefined): Headers { let headers: Headers; if (isHeadersLike(init)) { headers = init; diff --git a/packages/fets/src/client/auth/oauth.ts b/packages/fets/src/client/auth/oauth.ts index 1e43ad953..a144fc11b 100644 --- a/packages/fets/src/client/auth/oauth.ts +++ b/packages/fets/src/client/auth/oauth.ts @@ -30,7 +30,7 @@ export type OASOAuthPathRequestParamsWithHeader = { /** * Your service can support different scopes for the client credentials grant. In practice, not many services actually support this. */ - scope?: string | string[]; + scope?: string | string[] | undefined; }; headers: { /** @@ -71,7 +71,7 @@ export type OASOAuthPath = TOAS extends { requestParams: | OASOAuthPathRequestParamsWithHeader | OASOAuthPathRequestParamsWithoutHeader, - requestInit?: RequestInit, + requestInit?: RequestInit | undefined, ): Promise< TypedResponseWithJSONStatusMap<{ 200: OAuthPathSuccessResponse; @@ -98,19 +98,19 @@ export type OAuthPathSuccessResponse = { /** * If the access token expires, the server should reply with the duration of time the access token is granted for. */ - expires_in?: number; + expires_in?: number | undefined; /** * If the access token will expire, * then it is useful to return a refresh token which applications can use to obtain another access token. * However, tokens issued with the implicit grant cannot be issued a refresh token. */ - refresh_token?: string; + refresh_token?: string | undefined; /** * If the scope the user granted is identical to the scope the app requested, this parameter is optional. * If the granted scope is different from the requested scope, * such as if the user modified the scope, then this parameter is required. */ - scope?: string; + scope?: string | undefined; }; /** @@ -128,11 +128,11 @@ export interface OAuthPathFailedResponse { /** * The error_description parameter can only include ASCII characters, and should be a sentence or two at most describing the circumstance of the error. */ - error_description?: string; + error_description?: string | undefined; /** * The error_uri is a great place to link to your API documentation for information about how to correct the specific error that was encountered. */ - error_uri?: string; + error_uri?: string | undefined; } export enum OAuthPathErrorType { diff --git a/packages/fets/src/client/types.ts b/packages/fets/src/client/types.ts index 3a3ef96f4..eb8a99b9e 100644 --- a/packages/fets/src/client/types.ts +++ b/packages/fets/src/client/types.ts @@ -122,7 +122,7 @@ export type OASParamObj< schema: JSONSchema; } ? FromSchema - : TParameter extends { type: JSONSchema7TypeName; enum?: any[] } + : TParameter extends { type: JSONSchema7TypeName; enum?: any[] | undefined } ? FromSchema<{ type: TParameter['type']; enum: TParameter['enum']; @@ -146,7 +146,7 @@ interface OASParamToRequestParam; + : never]?: OASParamObj | undefined; } : {}; } @@ -188,11 +188,11 @@ export type OASClient = { } ? ( requestParams: Simplify>, - init?: RequestInit, + init?: RequestInit | undefined, ) => Promise> : ( - requestParams?: Simplify>, - init?: RequestInit, + requestParams?: Simplify> | undefined, + init?: RequestInit | undefined, ) => Promise>; }; } & OASOAuthPath; @@ -273,9 +273,14 @@ export type OASRequestParams< * * The value of `json` will be stringified and sent as the request body with `Content-Type: application/json`. */ - json?: FromSchema< - OASMethodMap[TMethod]['requestBody']['content']['application/json']['schema'] - >; + json?: + | FromSchema< + OASMethodMap< + TOAS, + TPath + >[TMethod]['requestBody']['content']['application/json']['schema'] + > + | undefined; } : OASMethodMap[TMethod] extends { requestBody: { content: { 'multipart/form-data': { schema: JSONSchema } } }; @@ -300,12 +305,14 @@ export type OASRequestParams< * * The value of `formData` will be sent as the request body with `Content-Type: multipart/form-data`. */ - formData?: FromSchema< - OASMethodMap< - TOAS, - TPath - >[TMethod]['requestBody']['content']['multipart/form-data']['schema'] - >; + formData?: + | FromSchema< + OASMethodMap< + TOAS, + TPath + >[TMethod]['requestBody']['content']['multipart/form-data']['schema'] + > + | undefined; } : OASMethodMap[TMethod] extends { requestBody: { content: { 'application/x-www-form-urlencoded': { schema: JSONSchema } } }; @@ -330,12 +337,14 @@ export type OASRequestParams< * * The value of `formUrlEncoded` will be sent as the request body with `Content-Type: application/x-www-form-urlencoded`. */ - formUrlEncoded?: FromSchema< - OASMethodMap< - TOAS, - TPath - >[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema'] - >; + formUrlEncoded?: + | FromSchema< + OASMethodMap< + TOAS, + TPath + >[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema'] + > + | undefined; } : {}) & (OASMethodMap[TMethod] extends { parameters: { name: string; in: string }[] } @@ -399,19 +408,19 @@ export interface ClientOptions { /** * The base URL of the API */ - endpoint?: string; + endpoint?: string | undefined; /** * WHATWG compatible fetch implementation * * @see https://the-guild.dev/openapi/fets/client/client-configuration#customizing-the-fetch-function */ - fetchFn?: typeof fetch; + fetchFn?: typeof fetch | undefined; /** * Plugins to extend the client functionality * * @see https://the-guild.dev/openapi/fets/client/plugins */ - plugins?: ClientPlugin[]; + plugins?: ClientPlugin[] | undefined; } export type ClientOptionsWithStrictEndpoint = Omit< @@ -443,24 +452,24 @@ export type ClientOptionsWithStrictEndpoint = Omit endpoint: `${TProtocol}://${THost}${TBasePath}`; } : { - endpoint?: string; + endpoint?: string | undefined; }); export interface ClientRequestParams { json?: any; - formData?: FormData; - formUrlEncoded?: Record; - params?: Record; - query?: Record; - headers?: Record; + formData?: FormData | undefined; + formUrlEncoded?: Record | undefined; + params?: Record | undefined; + query?: Record | undefined; + headers?: Record | undefined; } -export type ClientMethod = (requestParams?: ClientRequestParams) => Promise; +export type ClientMethod = (requestParams?: ClientRequestParams | undefined) => Promise; export interface ClientPlugin { - onRequestInit?: OnRequestInitHook; - onFetch?: OnFetchHook; - onResponse?: OnResponseHook; + onRequestInit?: OnRequestInitHook | undefined; + onFetch?: OnFetchHook | undefined; + onResponse?: OnResponseHook | undefined; } export type OnRequestInitHook = (payload: ClientOnRequestInitPayload) => Promise | void; diff --git a/packages/fets/src/plugins/ajv.ts b/packages/fets/src/plugins/ajv.ts index c02e7333a..58e066d6a 100644 --- a/packages/fets/src/plugins/ajv.ts +++ b/packages/fets/src/plugins/ajv.ts @@ -17,10 +17,12 @@ import { getHeadersObj } from './utils.js'; type ValidateRequestFn = (request: RouterRequest) => PromiseOrValue; +const defaultComponents: RouterComponentsBase = { schemas: {} }; + export function useAjv({ - components = {}, + components = defaultComponents, }: { - components?: RouterComponentsBase; + components?: RouterComponentsBase | undefined; } = {}): RouterPlugin { const ajv = new Ajv({ strict: false, @@ -73,10 +75,15 @@ export function useAjv({ onRoute({ path, schemas, handlers }) { const validationMiddlewares = new Map(); if (schemas?.request?.headers && !isZodSchema(schemas.request.headers)) { - const validateFn = ajv.compile({ - ...schemas.request.headers, + const target = { components, - }); + ...schemas.request.headers, + }; + + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const validateFn = ajv.compile(target); validationMiddlewares.set('headers', request => { const headersObj = getHeadersObj(request.headers); const isValid = validateFn(headersObj); @@ -87,10 +94,14 @@ export function useAjv({ }); } if (schemas?.request?.params && !isZodSchema(schemas.request.params)) { + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error const validateFn = ajv.compile({ ...schemas.request.params, components, }); + validationMiddlewares.set('params', request => { const isValid = validateFn(request.params); if (!isValid) { @@ -100,10 +111,14 @@ export function useAjv({ }); } if (schemas?.request?.query && !isZodSchema(schemas.request.query)) { + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error const validateFn = ajv.compile({ ...schemas.request.query, components, }); + validationMiddlewares.set('query', request => { const isValid = validateFn(request.query); if (!isValid) { @@ -113,10 +128,14 @@ export function useAjv({ }); } if (schemas?.request?.json && !isZodSchema(schemas.request.json)) { + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error const validateFn = ajv.compile({ ...schemas.request.json, components, }); + validationMiddlewares.set('json', async request => { const contentType = request.headers.get('content-type'); if (contentType?.includes('json')) { @@ -134,10 +153,14 @@ export function useAjv({ }); } if (schemas?.request?.formData && !isZodSchema(schemas.request.formData)) { + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error const validateFn = ajv.compile({ ...schemas.request.formData, components, }); + validationMiddlewares.set('formData', async request => { const contentType = request.headers.get('content-type'); if ( diff --git a/packages/fets/src/plugins/openapi.ts b/packages/fets/src/plugins/openapi.ts index 85450c42e..576abce9e 100644 --- a/packages/fets/src/plugins/openapi.ts +++ b/packages/fets/src/plugins/openapi.ts @@ -1,5 +1,8 @@ import { zodToJsonSchema } from 'zod-to-json-schema'; import { Response } from '../Response.js'; +// TODO: not sure how html file is imported, but TS is complaining +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error import swaggerUiHtml from '../swagger-ui-html.js'; import { StatusCode } from '../typed-fetch.js'; import { @@ -11,30 +14,30 @@ import { import { isZodSchema } from '../zod/types.js'; export interface SwaggerUIOpts { - spec?: OpenAPIDocument; - dom_id?: string; - displayOperationId?: boolean; - tryItOutEnabled?: boolean; - requestSnippetsEnabled?: boolean; - displayRequestDuration?: boolean; - defaultModelRendering?: 'model' | 'example' | 'schema'; - defaultModelExpandDepth?: number; - defaultModelsExpandDepth?: number; - docExpansion?: 'none' | 'list' | 'full'; - filter?: boolean; - maxDisplayedTags?: number; - showExtensions?: boolean; - showCommonExtensions?: boolean; - tagsSorter?: 'alpha'; - operationsSorter?: 'alpha'; - showTags?: boolean; - showMutatedRequest?: boolean; - oauth2RedirectUrl?: string; - validatorUrl?: string; - deepLinking?: boolean; - presets?: any[]; - plugins?: any[]; - layout?: string; + spec?: OpenAPIDocument | undefined; + dom_id?: string | undefined; + displayOperationId?: boolean | undefined; + tryItOutEnabled?: boolean | undefined; + requestSnippetsEnabled?: boolean | undefined; + displayRequestDuration?: boolean | undefined; + defaultModelRendering?: 'model' | 'example' | 'schema' | undefined; + defaultModelExpandDepth?: number | undefined; + defaultModelsExpandDepth?: number | undefined; + docExpansion?: 'none' | 'list' | 'full' | undefined; + filter?: boolean | undefined; + maxDisplayedTags?: number | undefined; + showExtensions?: boolean | undefined; + showCommonExtensions?: boolean | undefined; + tagsSorter?: 'alpha' | undefined; + operationsSorter?: 'alpha' | undefined; + showTags?: boolean | undefined; + showMutatedRequest?: boolean | undefined; + oauth2RedirectUrl?: string | undefined; + validatorUrl?: string | undefined; + deepLinking?: boolean | undefined; + presets?: any[] | undefined; + plugins?: any[] | undefined; + layout?: string | undefined; } export type OpenAPIPluginOptions = { @@ -107,17 +110,27 @@ export function useOpenAPI({ if (schemas.responses) { for (const statusCode in schemas.responses) { let responseSchema = schemas.responses[statusCode as any as StatusCode]; + if (isZodSchema(responseSchema)) { - responseSchema = zodToJsonSchema(responseSchema as any, { - target: 'openApi3', - }); + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + responseSchema = zodToJsonSchema( + // TODO: possible bug? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + responseSchema, + { + target: 'openApi3', + }, + ); } operation.responses = operation.responses || {}; operation.responses[statusCode] = { description: '', content: { 'application/json': { - schema: responseSchema as any, + schema: responseSchema, }, }, }; diff --git a/packages/fets/src/typed-fetch.ts b/packages/fets/src/typed-fetch.ts index c5bdacdd2..598521fc2 100644 --- a/packages/fets/src/typed-fetch.ts +++ b/packages/fets/src/typed-fetch.ts @@ -125,7 +125,7 @@ export type TypedHeaders> = { }; export type TypedHeadersCtor = new >( - init?: TMap, + init?: TMap | undefined, ) => TypedHeaders; export type TypedResponseInit = Omit< @@ -146,7 +146,9 @@ export type TypedResponseInit = Omit< * * @see https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText */ - statusText?: TStatusCode extends keyof StatusTextMap ? StatusTextMap[TStatusCode] : string; + statusText?: + | (TStatusCode extends keyof StatusTextMap ? StatusTextMap[TStatusCode] : string) + | undefined; }; export type StatusTextMap = { @@ -294,7 +296,10 @@ export type TypedResponseCtor = Omit & { redirect(url: string | URL): TypedResponse, 302>; }; -export type TypedResponseWithJSONStatusMap> = { +// Wrapped StatusCodeMap in Partial<>, to fix packages/fets/src/client/auth/oauth.ts:76 error +export type TypedResponseWithJSONStatusMap< + TResponseJSONStatusMap extends Partial>, +> = { [TStatusCode in keyof TResponseJSONStatusMap]: TStatusCode extends StatusCode ? TypedResponse, TStatusCode> : never; @@ -318,7 +323,7 @@ export type TypedRequestInit< > = Omit & { method: TMethod; headers: TypedHeaders; - body?: Exclude | TFormData; + body?: Exclude | TFormData | undefined; }; export type TypedRequest< @@ -343,7 +348,7 @@ export type TypedRequestCtor = new < TFormData extends Record, >( input: string | TypedURL, - init?: TypedRequestInit, + init?: TypedRequestInit | undefined, ) => TypedRequest; export interface TypedURLSearchParams> { @@ -375,7 +380,7 @@ export interface TypedURLSearchParams>( - init?: TMap, + init?: TMap | undefined, ) => TypedURLSearchParams; export type TypedURL> = Omit< @@ -387,7 +392,7 @@ export type TypedURL> = O export type TypedURLCtor = new >( input: string, - base?: string | TypedURL, + base?: string | TypedURL | undefined, ) => TypedURL; export interface TypedFormData< @@ -396,7 +401,7 @@ export interface TypedFormData< append( name: TName, value: TMap[TName] extends any[] ? TMap[TName][0] : TMap[TName], - fileName?: string, + fileName?: string | undefined, ): void; delete(name: keyof TMap): void; get( @@ -411,7 +416,7 @@ export interface TypedFormData< set( name: TName, value: TMap[TName] extends any[] ? TMap[TName][0] : TMap[TName], - fileName?: string, + fileName?: string | undefined, ): void; forEach( callbackfn: (value: TMap[TName], key: TName, parent: this) => void, diff --git a/packages/fets/src/types.ts b/packages/fets/src/types.ts index c88b4888f..c53e3cec7 100644 --- a/packages/fets/src/types.ts +++ b/packages/fets/src/types.ts @@ -22,6 +22,7 @@ import { RouteZodSchemas, TypedRequestFromRouteZodSchemas, TypedResponseFromRouteZodSchemas, + ZodType, } from './zod/types.js'; export { TypedRequest as RouterRequest }; @@ -33,79 +34,91 @@ export type JSONSerializer = (obj: any) => string; export type JSONSchema = Exclude; export interface OpenAPIInfo { - title?: string; - description?: string; - version?: string; - license?: { - name?: string; - url?: string; - }; + title?: string | undefined; + description?: string | undefined; + version?: string | undefined; + license?: + | { + name?: string | undefined; + url?: string | undefined; + } + | undefined; } export type OpenAPIPathObject = Record & { - parameters?: OpenAPIParameterObject[]; + parameters?: OpenAPIParameterObject[] | undefined; }; export interface OpenAPIParameterObject { name: string; in: 'path' | 'query' | 'header' | 'cookie'; - required?: boolean; - schema?: any; + required?: boolean | undefined; + schema?: JSONSchema | ZodType | undefined; } export interface OpenAPIRequestBodyObject { - content?: Record; + content?: Record | undefined; } export interface OpenAPIOperationObject { - operationId?: string; - description?: string; - tags?: string[]; - parameters?: OpenAPIParameterObject[]; - requestBody?: OpenAPIRequestBodyObject; - responses?: Record; + operationId?: string | undefined; + description?: string | undefined; + tags?: string[] | undefined; + parameters?: OpenAPIParameterObject[] | undefined; + requestBody?: OpenAPIRequestBodyObject | undefined; + responses?: Record | undefined; } export interface OpenAPIResponseObject { - description?: string; - content?: Record; + description?: string | undefined; + content?: Record | undefined; } export interface OpenAPIMediaTypeObject { - schema?: any; + schema?: JSONSchema | ZodType | undefined; } export type OpenAPIDocument = { - openapi?: string; - info?: OpenAPIInfo; - servers?: { - url: string; - }[]; - paths?: Record; + openapi?: string | undefined; + info?: OpenAPIInfo | undefined; + servers?: + | { + url: string; + }[] + | undefined; + paths?: Record | undefined; components?: unknown; }; export interface RouterOpenAPIOptions extends OpenAPIDocument { - endpoint?: string | false; - components?: TComponents; + endpoint?: string | false | undefined; + components?: TComponents | undefined; } export interface RouterSwaggerUIOptions extends SwaggerUIOpts { - endpoint?: string | false; + endpoint?: string | false | undefined; } +// TODO: An example of TS error in case "exactOptionalPropertyTypes": true is not set in dependency type downstream. +// Interface 'RouterOptions' incorrectly extends interface 'ServerAdapterOptions'. +// Types of property 'plugins' are incompatible. +// Type 'RouterPlugin[] | undefined' is not assignable to type 'ServerAdapterPlugin[]'. +// Type 'undefined' is not assignable to type 'ServerAdapterPlugin[]'.ts(2430) +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error export interface RouterOptions extends ServerAdapterOptions { - base?: string; - plugins?: RouterPlugin[]; + base?: string | undefined; + plugins?: RouterPlugin[] | undefined; - openAPI?: RouterOpenAPIOptions; - swaggerUI?: RouterSwaggerUIOptions; + openAPI?: RouterOpenAPIOptions | undefined; + swaggerUI?: RouterSwaggerUIOptions | undefined; } export type RouterComponentsBase = { - schemas?: Record; + // TODO: Looks like undefined could not be mapped in FromRouterComponentSchema type, so maybe schemas should not be optional? + schemas: Record; }; /* Maybe later; @@ -171,7 +184,7 @@ export type FromRouterComponentSchema< export type PromiseOrValue = T | Promise; export type StatusCodeMap = { - [TKey in StatusCode]?: T; + [TKey in StatusCode]: T | undefined; }; export type TypedRouterHandlerTypeConfig< @@ -183,13 +196,13 @@ export type TypedRouterHandlerTypeConfig< TResponseJSONStatusMap extends StatusCodeMap = StatusCodeMap, > = { request: { - json?: TRequestJSON; - formData?: TRequestFormData; - headers?: TRequestHeaders; - query?: TRequestQueryParams; - params?: TRequestPathParams; + json?: TRequestJSON | undefined; + formData?: TRequestFormData | undefined; + headers?: TRequestHeaders | undefined; + query?: TRequestQueryParams | undefined; + params?: TRequestPathParams | undefined; }; - responses?: TResponseJSONStatusMap; + responses?: TResponseJSONStatusMap | undefined; }; export type TypedRequestFromTypeConfig< @@ -315,12 +328,12 @@ export type RouteHandler< // TODO: Remove Response from here export type OnRouteHookPayload = { - operationId?: string; - description?: string; - tags?: string[]; + operationId?: string | undefined; + description?: string | undefined; + tags?: string[] | undefined; method: HTTPMethod; path: string; - schemas?: RouteSchemas | RouteZodSchemas; + schemas?: RouteSchemas | RouteZodSchemas | undefined; openAPIDocument: OpenAPIDocument; handlers: RouteHandler[]; }; @@ -339,20 +352,22 @@ export type OnSerializeResponseHook = ( ) => void; export type RouterPlugin = ServerAdapterPlugin & { - onRouterInit?: OnRouterInitHook; - onRoute?: OnRouteHook; - onSerializeResponse?: OnSerializeResponseHook; + onRouterInit?: OnRouterInitHook | undefined; + onRoute?: OnRouteHook | undefined; + onSerializeResponse?: OnSerializeResponseHook | undefined; }; export type RouteSchemas = { - request?: { - headers?: JSONSchema; - params?: JSONSchema; - query?: JSONSchema; - json?: JSONSchema; - formData?: JSONSchema; - }; - responses?: StatusCodeMap; + request?: + | { + headers?: JSONSchema | undefined; + params?: JSONSchema | undefined; + query?: JSONSchema | undefined; + json?: JSONSchema | undefined; + formData?: JSONSchema | undefined; + } + | undefined; + responses?: StatusCodeMap | undefined; }; export type RouterSDKOpts< @@ -367,11 +382,11 @@ export type RouterSDKOpts< infer TPathParam > ? { - json?: TJSONBody; - formData?: TFormData; - headers?: THeaders; - query?: TQueryParams; - params?: TPathParam; + json?: TJSONBody | undefined; + formData?: TFormData | undefined; + headers?: THeaders | undefined; + query?: TQueryParams | undefined; + params?: TPathParam | undefined; } : never; @@ -382,7 +397,7 @@ export type RouterSDK< > = { [TPathKey in TPath]: { [TMethod in Lowercase]: ( - opts?: RouterSDKOpts, + opts?: RouterSDKOpts | undefined, ) => Promise>; }; }; @@ -475,11 +490,11 @@ export type AddRouteWithTypesOpts< TTypedRequest extends TypedRequest, TTypedResponse extends TypedResponse, > = { - operationId?: string; - description?: string; - method?: TMethod; - tags?: string[]; - internal?: boolean; + operationId?: string | undefined; + description?: string | undefined; + method?: TMethod | undefined; + tags?: string[] | undefined; + internal?: boolean | undefined; path: TPath; handler: | RouteHandler @@ -492,9 +507,9 @@ export type RouteInput< TMethod extends Lowercase = 'post', TParamType extends keyof RouterSDKOpts = 'json', > = TRouter extends Router - ? TRouterSDK[TPath][TMethod] extends (requestParams?: infer TRequestParams) => any + ? TRouterSDK[TPath][TMethod] extends (requestParams?: infer TRequestParams | undefined) => any ? TRequestParams extends { - [TParamTypeKey in TParamType]?: infer TParamTypeValue; + [TParamTypeKey in TParamType]?: infer TParamTypeValue | undefined; } ? TParamTypeValue : never @@ -521,7 +536,7 @@ export type RouterClient> = TRouter['__cli export type RouterInput> = { [TPath in keyof RouterClient]: { [TMethod in keyof RouterClient[TPath]]: RouterClient[TPath][TMethod] extends ( - requestParams?: infer TRequestParams, + requestParams?: infer TRequestParams | undefined, ) => any ? Required : never; diff --git a/packages/fets/src/utils.ts b/packages/fets/src/utils.ts index 7df9df7de..26477cc1d 100644 --- a/packages/fets/src/utils.ts +++ b/packages/fets/src/utils.ts @@ -10,14 +10,14 @@ export interface PatternHandlersObj { interface AddHandlerToMethodOpts { // Operation related options - operationId?: string; - description?: string; - tags?: string[]; + operationId?: string | undefined; + description?: string | undefined; + tags?: string[] | undefined; method: HTTPMethod; path: string; - schemas?: RouteSchemas; + schemas?: RouteSchemas | undefined; handlers: RouteHandler[]; - internal?: boolean; + internal?: boolean | undefined; // Router related options onRouteHooks: OnRouteHook[]; @@ -59,7 +59,7 @@ function preparePatternHandlerObjByMethod({ declare global { interface URLPattern { - isPattern?: boolean; + isPattern?: boolean | undefined; } } diff --git a/packages/fets/src/zod/types.ts b/packages/fets/src/zod/types.ts index 2bf4c2f47..a41fed615 100644 --- a/packages/fets/src/zod/types.ts +++ b/packages/fets/src/zod/types.ts @@ -10,14 +10,16 @@ export type ZodType = { _output: any; safeParse(input: any): any }; export type InferZodType = T['_output']; export type RouteZodSchemas = { - request?: { - json?: ZodType; - formData?: ZodType; - headers?: ZodType; - params?: ZodType; - query?: ZodType; - }; - responses?: StatusCodeMap; + request?: + | { + json?: ZodType | undefined; + formData?: ZodType | undefined; + headers?: ZodType | undefined; + params?: ZodType | undefined; + query?: ZodType | undefined; + } + | undefined; + responses?: StatusCodeMap | undefined; }; export type TypedRequestFromRouteZodSchemas< diff --git a/tsconfig.json b/tsconfig.json index 38bd46c3c..c15520dac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noPropertyAccessFromIndexSignature": false, + "exactOptionalPropertyTypes": true, "paths": { "@e2e/*": ["e2e/*/src/index.ts"], "fets": ["packages/fets/src/index.ts"] diff --git a/website/src/pages/server/cors.mdx b/website/src/pages/server/cors.mdx index 9be9c3a90..0ca025cbd 100644 --- a/website/src/pages/server/cors.mdx +++ b/website/src/pages/server/cors.mdx @@ -22,12 +22,12 @@ The `CORSOptions` can be configured as follows: ```ts export type CORSOptions = | { - origin?: string[] | string - methods?: string[] - allowedHeaders?: string[] - exposedHeaders?: string[] - credentials?: boolean - maxAge?: number + origin?: string[] | string | undefined + methods?: string[] | undefined + allowedHeaders?: string[] | undefined + exposedHeaders?: string[] | undefined + credentials?: boolean | undefined + maxAge?: number | undefined } | false ```