diff --git a/src/context.ts b/src/context.ts index 0f862dbc..f96c91e0 100644 --- a/src/context.ts +++ b/src/context.ts @@ -12,7 +12,8 @@ import type { Prettify, ResolvePath, SingletonBase, - HTTPHeaders + HTTPHeaders, + IsSkipped } from './types' type InvertedStatusMapKey = keyof InvertedStatusMap @@ -29,18 +30,18 @@ export type ErrorContext< > = Prettify< { body: Route['body'] - query: undefined extends Route['query'] + query: IsSkipped extends true ? Record : Route['query'] - params: undefined extends Route['params'] + params: IsSkipped extends true ? Path extends `${string}/${':' | '*'}${string}` ? ResolvePath : { [key in string]: string } : Route['params'] - headers: undefined extends Route['headers'] + headers: IsSkipped extends true ? Record : Route['headers'] - cookie: undefined extends Route['cookie'] + cookie: IsSkipped extends true ? Record> : Record> & { [key in keyof Route['cookie']]-?: NonNullable< @@ -122,20 +123,20 @@ export type Context< > = Prettify< { body: PrettifyIfObject - query: undefined extends Route['query'] + query: IsSkipped extends true ? Record : PrettifyIfObject - params: undefined extends Route['params'] + params: IsSkipped extends true ? undefined extends Path ? Record : Path extends `${string}/${':' | '*'}${string}` ? ResolvePath : never : PrettifyIfObject - headers: undefined extends Route['headers'] + headers: IsSkipped extends true ? Record : PrettifyIfObject - cookie: undefined extends Route['cookie'] + cookie: IsSkipped extends true ? Record> : Record> & { [key in keyof Route['cookie']]-?: Cookie< diff --git a/src/type-system/index.ts b/src/type-system/index.ts index 009980bd..68dc1ee3 100644 --- a/src/type-system/index.ts +++ b/src/type-system/index.ts @@ -419,6 +419,12 @@ export const ElysiaType = { MaybeEmpty: (schema: T, options?: SchemaOptions) => t.Union([schema, t.Null(), t.Undefined()], options), + /** + * Allow Optional and Undefined + */ + Undefinable: (schema: T, options?: SchemaOptions) => + t.Union([schema, t.Undefined()], options), + Cookie: ( properties: T, { @@ -574,6 +580,7 @@ t.Files = (arg) => { t.Nullable = ElysiaType.Nullable t.MaybeEmpty = ElysiaType.MaybeEmpty +t.Undefinable = ElysiaType.Undefinable t.Cookie = ElysiaType.Cookie t.Date = ElysiaType.Date t.UnionEnum = ElysiaType.UnionEnum diff --git a/src/types.ts b/src/types.ts index 0dec442a..4bf4e145 100644 --- a/src/types.ts +++ b/src/types.ts @@ -654,9 +654,9 @@ export interface MergeSchema< in out B extends RouteSchema, Path extends string = '' > { - body: undefined extends A['body'] ? B['body'] : A['body'] - headers: undefined extends A['headers'] ? B['headers'] : A['headers'] - query: undefined extends A['query'] ? B['query'] : A['query'] + body: IsSkipped extends true ? B['body'] : A['body'] + headers: IsSkipped extends true ? B['headers'] : A['headers'] + query: IsSkipped extends true ? B['query'] : A['query'] params: IsNever extends true ? IsNever extends true ? ResolvePath @@ -679,25 +679,25 @@ export interface MergeStandaloneSchema< in out B extends RouteSchema, Path extends string = '' > { - body: undefined extends A['body'] - ? undefined extends B['body'] + body: IsSkipped extends true + ? IsSkipped extends true ? undefined : B['body'] - : undefined extends B['body'] + : IsSkipped extends true ? A['body'] : Prettify - headers: undefined extends A['headers'] - ? undefined extends B['headers'] + headers: IsSkipped extends true + ? IsSkipped extends true ? undefined : B['headers'] - : undefined extends B['headers'] + : IsSkipped extends true ? A['headers'] : Prettify - query: undefined extends A['query'] - ? undefined extends B['query'] + query: IsSkipped extends true + ? IsSkipped extends true ? undefined : B['query'] - : undefined extends B['query'] + : IsSkipped extends true ? A['query'] : Prettify params: IsNever extends true @@ -707,11 +707,11 @@ export interface MergeStandaloneSchema< : IsNever extends true ? A['params'] : Prettify - cookie: undefined extends A['cookie'] - ? undefined extends B['cookie'] + cookie: IsSkipped extends true + ? IsSkipped extends true ? undefined : B['cookie'] - : undefined extends B['cookie'] + : IsSkipped extends true ? A['cookie'] : Prettify response: {} extends A['response'] @@ -742,6 +742,16 @@ export type Handler< export type IsAny = 0 extends 1 & T ? true : false +export type IsUnknown = IsAny extends true + ? false + : unknown extends T + ? true + : false + +export type IsUndefined = [T] extends [undefined] ? true : false; + +export type IsSkipped = IsUnknown extends true ? true : IsUndefined extends true ? true : false; + export type Replace = IsAny extends true ? Original diff --git a/test/types/index.ts b/test/types/index.ts index feff09ee..0759d6a3 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -2671,3 +2671,19 @@ type a = keyof {} (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf>() } + +// infer Undefinable query, headers and body correctly +{ + const app = new Elysia().post("/", ({ query: { name = 'Elysia' } = {} }) => { + return `Hello ${name}`; + }, { + query: t.Undefinable(t.Object({ name: t.String() })), + headers: t.Undefinable(t.Object({ token: t.String() })), + body: t.Undefinable(t.Object({ id: t.Number() })), + }); + + expectTypeOf(app["~Routes"].post.query).not.toEqualTypeOf(); + expectTypeOf(app["~Routes"].post.query).toEqualTypeOf<{ name: string } | undefined>(); + expectTypeOf(app["~Routes"].post.headers).toEqualTypeOf<{ token: string } | undefined>(); + expectTypeOf(app["~Routes"].post.body).toEqualTypeOf<{ id: number } | undefined>(); +} \ No newline at end of file