Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import type {
Prettify,
ResolvePath,
SingletonBase,
HTTPHeaders
HTTPHeaders,
IsSkipped
} from './types'

type InvertedStatusMapKey = keyof InvertedStatusMap
Expand All @@ -29,18 +30,18 @@ export type ErrorContext<
> = Prettify<
{
body: Route['body']
query: undefined extends Route['query']
query: IsSkipped<Route['query']> extends true
? Record<string, string | undefined>
: Route['query']
params: undefined extends Route['params']
params: IsSkipped<Route['params']> extends true
? Path extends `${string}/${':' | '*'}${string}`
? ResolvePath<Path>
: { [key in string]: string }
: Route['params']
headers: undefined extends Route['headers']
headers: IsSkipped<Route['headers']> extends true
? Record<string, string | undefined>
: Route['headers']
cookie: undefined extends Route['cookie']
cookie: IsSkipped<Route['cookie']> extends true
? Record<string, Cookie<string | undefined>>
: Record<string, Cookie<string | undefined>> & {
[key in keyof Route['cookie']]-?: NonNullable<
Expand Down Expand Up @@ -122,20 +123,20 @@ export type Context<
> = Prettify<
{
body: PrettifyIfObject<Route['body']>
query: undefined extends Route['query']
query: IsSkipped<Route['query']> extends true
? Record<string, string>
: PrettifyIfObject<Route['query']>
params: undefined extends Route['params']
params: IsSkipped<Route['params']> extends true
? undefined extends Path
? Record<string, string>
: Path extends `${string}/${':' | '*'}${string}`
? ResolvePath<Path>
: never
: PrettifyIfObject<Route['params']>
headers: undefined extends Route['headers']
headers: IsSkipped<Route['headers']> extends true
? Record<string, string | undefined>
: PrettifyIfObject<Route['headers']>
cookie: undefined extends Route['cookie']
cookie: IsSkipped<Route['cookie']> extends true
? Record<string, Cookie<string | undefined>>
: Record<string, Cookie<string | undefined>> & {
[key in keyof Route['cookie']]-?: Cookie<
Expand Down
7 changes: 7 additions & 0 deletions src/type-system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,12 @@ export const ElysiaType = {
MaybeEmpty: <T extends TSchema>(schema: T, options?: SchemaOptions) =>
t.Union([schema, t.Null(), t.Undefined()], options),

/**
* Allow Optional and Undefined
*/
Undefinable: <T extends TSchema>(schema: T, options?: SchemaOptions) =>
t.Union([schema, t.Undefined()], options),

Cookie: <T extends TProperties>(
properties: T,
{
Expand Down Expand Up @@ -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
Expand Down
40 changes: 25 additions & 15 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<A['body']> extends true ? B['body'] : A['body']
headers: IsSkipped<A['headers']> extends true ? B['headers'] : A['headers']
query: IsSkipped<A['query']> extends true ? B['query'] : A['query']
params: IsNever<keyof A['params']> extends true
? IsNever<keyof B['params']> extends true
? ResolvePath<Path>
Expand All @@ -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<A['body']> extends true
? IsSkipped<B['body']> extends true
? undefined
: B['body']
: undefined extends B['body']
: IsSkipped<B['body']> extends true
? A['body']
: Prettify<A['body'] & B['body']>
headers: undefined extends A['headers']
? undefined extends B['headers']
headers: IsSkipped<A['headers']> extends true
? IsSkipped<B['headers']> extends true
? undefined
: B['headers']
: undefined extends B['headers']
: IsSkipped<B['headers']> extends true
? A['headers']
: Prettify<A['headers'] & B['headers']>
query: undefined extends A['query']
? undefined extends B['query']
query: IsSkipped<A['query']> extends true
? IsSkipped<B['query']> extends true
? undefined
: B['query']
: undefined extends B['query']
: IsSkipped<B['query']> extends true
? A['query']
: Prettify<A['query'] & B['query']>
params: IsNever<keyof A['params']> extends true
Expand All @@ -707,11 +707,11 @@ export interface MergeStandaloneSchema<
: IsNever<keyof B['params']> extends true
? A['params']
: Prettify<A['params'] & B['params']>
cookie: undefined extends A['cookie']
? undefined extends B['cookie']
cookie: IsSkipped<A['cookie']> extends true
? IsSkipped<B['cookie']> extends true
? undefined
: B['cookie']
: undefined extends B['cookie']
: IsSkipped<B['cookie']> extends true
? A['cookie']
: Prettify<A['cookie'] & B['cookie']>
response: {} extends A['response']
Expand Down Expand Up @@ -742,6 +742,16 @@ export type Handler<

export type IsAny<T> = 0 extends 1 & T ? true : false

export type IsUnknown<T> = IsAny<T> extends true
? false
: unknown extends T
? true
: false

export type IsUndefined<T> = [T] extends [undefined] ? true : false;

export type IsSkipped<T> = IsUnknown<T> extends true ? true : IsUndefined<T> extends true ? true : false;

export type Replace<Original, Target, With> =
IsAny<Target> extends true
? Original
Expand Down
16 changes: 16 additions & 0 deletions test/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2671,3 +2671,19 @@ type a = keyof {}
(typeof app)['~Routes']['get']['response'][200]
>().toEqualTypeOf<AsyncGenerator<'a', void, unknown>>()
}

// 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<unknown>();
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>();
}
Loading