Skip to content

Instantly share code, notes, and snippets.

@panzelva
Last active May 16, 2023 10:14
Show Gist options
  • Save panzelva/b0e565870c950c4cfd77188d69a3064a to your computer and use it in GitHub Desktop.
Save panzelva/b0e565870c950c4cfd77188d69a3064a to your computer and use it in GitHub Desktop.
/* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as joi from 'joi'
type ArrayType<T> = T extends (infer U)[] ? U : never
declare module 'joi' {
interface Root {
extend(...extensions: Array<joi.Extension | joi.ExtensionFactory>): this
any<T extends any>(): BoxAnySchema<Box<T, false>>
string<T extends string>(): BoxStringSchema<Box<T, false>>
number<T extends number>(): BoxNumberSchema<Box<T, false>>
boolean<T extends boolean>(): BoxBooleanSchema<Box<T, false>>
date<T extends Date>(): BoxDateSchema<Box<T, false>>
// eslint-disable-next-line @typescript-eslint/ban-types
func<T extends Function>(): BoxFunctionSchema<Box<T, false>>
array(): BoxArraySchema<Box<never, false>>
object<T extends mappedSchemaMap>(schema?: T): BoxObjectSchema<Box<extractMap<T>, false>>
alternatives<T extends mappedSchema[]>(
...alts: T
): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>
alternatives<T extends mappedSchema[]>(
alts: T
): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>
alt<T extends mappedSchema[]>(
...alts: T
): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>
alt<T extends mappedSchema[]>(
alts: T
): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>
}
/**
* Field requirements interface
*/
interface Box<T, R extends boolean> {
/** Type the schema holds */
T: T
/** If this attribute is required when inside an object */
R: R
}
// Operators
type BoxType<B, nT> = B extends Box<infer oT, infer oR> ? Box<nT, oR> : B
type BoxUnion<B, nT> = B extends Box<infer oT, infer oR> ? Box<oT | nT, oR> : B
type BoxIntersection<B, nT> = B extends Box<infer oT, infer oR> ? Box<oT & nT, oR> : B
type BoxReq<B, nR extends boolean> = B extends Box<infer oT, infer oR> ? Box<oT, nR> : B
type BoxSchema = Box<any, boolean>
// eslint-disable-next-line @typescript-eslint/ban-types
type primitiveType = string | number | boolean | Function | Date | undefined | null | void
type mappedSchema = joi.SchemaLike | BoxedPrimitive
type mappedSchemaMap = { [K: string]: mappedSchema }
type extendsGuard<T, S> = S extends T ? S : T
/**
* Every Schema that implements the Box to allow the extraction
*/
type BoxedPrimitive<T extends BoxSchema = any> =
| BoxAnySchema<T>
| BoxStringSchema<T>
| BoxNumberSchema<T>
| BoxBooleanSchema<T>
| BoxDateSchema<T>
| BoxFunctionSchema<T>
| BoxArraySchema<T>
| BoxObjectSchema<T>
| BoxAlternativesSchema<T>
interface BoxAnySchema<N extends Box<any, boolean>> extends joi.AnySchema {
__schemaTypeLiteral: 'BoxAnySchema'
default<T>(
value: T,
description?: string
): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxUnion<B, T>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxUnion<B, T>> : never
allow<T>(values: T[]): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
valid<T>(
...values: T[]
): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxType<B, T>> : never
valid<T>(values: T[]): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxType<B, T>> : never
valid(...values: any[]): this
valid(values: any[]): this
required(): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxAnySchema<infer B> ? BoxAnySchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxStringSchema<N extends BoxSchema> extends joi.StringSchema {
__schemaTypeLiteral: 'BoxStringSchema'
default<T extends string>(
value: T,
description?: string
): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxUnion<B, T>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
valid<T extends string>(
...values: T[]
): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxType<B, T>> : never
valid<T extends string>(
values: T[]
): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxType<B, T>> : never
valid(...values: any[]): this
valid(values: any[]): this
required(): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxStringSchema<infer B> ? BoxStringSchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxNumberSchema<N extends BoxSchema> extends joi.NumberSchema {
__schemaTypeLiteral: 'BoxNumberSchema'
default<T extends number>(
value: T,
description?: string
): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxUnion<B, T>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
valid<T extends string>(
...values: T[]
): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxType<B, T>> : never
valid<T extends string>(
values: T[]
): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxType<B, T>> : never
valid(...values: any[]): this
valid(values: any[]): this
required(): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxNumberSchema<infer B> ? BoxNumberSchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxBooleanSchema<N extends BoxSchema> extends joi.BooleanSchema {
__schemaTypeLiteral: 'BoxBooleanSchema'
default<T extends boolean>(
value: T,
description?: string
): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxUnion<B, T>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
valid<T extends string>(
...values: T[]
): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxType<B, T>> : never
valid<T extends string>(
values: T[]
): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxType<B, T>> : never
valid(...values: any[]): this
valid(values: any[]): this
required(): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxBooleanSchema<infer B> ? BoxBooleanSchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxDateSchema<N extends BoxSchema> extends joi.DateSchema {
__schemaTypeLiteral: 'BoxDateSchema'
default<T extends Date>(
value: T,
description?: string
): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxUnion<B, T>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
valid<T extends string>(
...values: T[]
): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxType<B, T>> : never
valid<T extends string>(
values: T[]
): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxType<B, T>> : never
valid(...values: any[]): this
valid(values: any[]): this
required(): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxDateSchema<infer B> ? BoxDateSchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxFunctionSchema<N extends BoxSchema> extends joi.FunctionSchema {
__schemaTypeLiteral: 'BoxFunctionSchema'
allow<T>(
...values: T[]
): this extends BoxFunctionSchema<infer B> ? BoxFunctionSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxFunctionSchema<infer B> ? BoxFunctionSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
required(): this extends BoxFunctionSchema<infer B> ? BoxFunctionSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxFunctionSchema<infer B> ? BoxFunctionSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxFunctionSchema<infer B>
? BoxFunctionSchema<BoxReq<B, false>>
: never
optional(): this
}
interface BoxArraySchema<N extends BoxSchema> extends joi.ArraySchema {
__schemaTypeLiteral: 'BoxArraySchema'
default<T extends any[]>(
value: T,
description?: string
): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxUnion<B, ArrayType<T>>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
items<T extends mappedSchema>(
type: T
): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxUnion<B, extractType<T>>> : never
items(...types: joi.SchemaLike[]): this
items(types: joi.SchemaLike[]): this
required(): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxArraySchema<infer B> ? BoxArraySchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxObjectSchema<N extends BoxSchema> extends joi.ObjectSchema {
__schemaTypeLiteral: 'BoxObjectSchema'
default<T extends mappedSchemaMap>(
value: T,
description?: string
): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxUnion<B, extractType<T>>> : never
default(value: any, description?: string): this
default(): this
allow<T>(
...values: T[]
): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
keys<T extends mappedSchemaMap>(
schema: T
): this extends BoxObjectSchema<infer B>
? BoxObjectSchema<BoxIntersection<B, extractMap<T>>>
: never
keys(schema?: joi.SchemaMap): this
append<T extends mappedSchemaMap>(
schema: T
): this extends BoxObjectSchema<infer B>
? BoxObjectSchema<BoxIntersection<B, extractMap<T>>>
: never
append(schema?: joi.SchemaMap): this
pattern<S extends BoxStringSchema<any>, T extends mappedSchema>(
pattern: S,
schema: T
): this extends BoxObjectSchema<infer B>
? BoxObjectSchema<BoxIntersection<B, extractMap<{ [key in extractType<S>]: T }>>>
: never
pattern<T extends mappedSchema>(
pattern: RegExp,
schema: T
): this extends BoxObjectSchema<infer B>
? BoxObjectSchema<BoxIntersection<B, extractMap<{ [key: string]: T }>>>
: never
pattern(pattern: RegExp | joi.SchemaLike, schema: joi.SchemaLike): this
required(): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxReq<B, true>> : never
required(): this
exist(): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxReq<B, true>> : never
exist(): this
optional(): this extends BoxObjectSchema<infer B> ? BoxObjectSchema<BoxReq<B, false>> : never
optional(): this
}
interface BoxAlternativesSchema<N extends BoxSchema> extends joi.AlternativesSchema {
__schemaTypeLiteral: 'BoxAlternativesSchema'
allow<T>(
...values: T[]
): this extends BoxAlternativesSchema<infer B> ? BoxAlternativesSchema<BoxUnion<B, T>> : never
allow<T>(
values: T[]
): this extends BoxAlternativesSchema<infer B> ? BoxAlternativesSchema<BoxUnion<B, T>> : never
allow(...values: any[]): this
allow(values: any[]): this
try<T extends mappedSchema[]>(
...values: T
): this extends BoxAlternativesSchema<infer O>
? O extends Box<infer oT, infer oR>
? BoxAlternativesSchema<BoxType<O, oT | extractType<T>>>
: BoxAlternativesSchema<Box<extractType<T>, false>>
: BoxAlternativesSchema<Box<extractType<T>, false>>
try<T extends mappedSchema[]>(
values: T
): this extends BoxAlternativesSchema<infer O>
? O extends Box<infer oT, infer oR>
? BoxAlternativesSchema<BoxType<O, oT | extractType<T>>>
: BoxAlternativesSchema<Box<extractType<T>, false>>
: BoxAlternativesSchema<Box<extractType<T>, false>>
try(...types: joi.SchemaLike[]): this
try(types: joi.SchemaLike[]): this
required(): this extends BoxAlternativesSchema<infer B>
? BoxAlternativesSchema<BoxReq<B, true>>
: never
required(): this
exist(): this extends BoxAlternativesSchema<infer B>
? BoxAlternativesSchema<BoxReq<B, true>>
: never
exist(): this
optional(): this extends BoxAlternativesSchema<infer B>
? BoxAlternativesSchema<BoxReq<B, false>>
: never
optional(): this
when<
R,
T1 extends mappedSchema,
T2 extends mappedSchema,
T extends { then: T1; otherwise: T2 }
>(
ref: R,
defs: T
): this extends BoxAlternativesSchema<infer O>
? O extends Box<infer oT, infer oR>
? BoxAlternativesSchema<
BoxType<O, oT | extractType<T['then']> | extractType<T['otherwise']>>
>
: BoxAlternativesSchema<Box<extractType<T['then']> | extractType<T['otherwise']>, false>>
: BoxAlternativesSchema<Box<extractType<T['then']> | extractType<T['otherwise']>, false>>
when(ref: string | joi.Reference, options: joi.WhenOptions): this
when(ref: joi.Schema, options: joi.WhenSchemaOptions): this
}
type maybeExtractBox<T> = T extends Box<infer O, infer R> ? O : T
type Required<T, K = keyof T> = {
[j in K extends keyof T
? T[K] extends BoxedPrimitive<infer B>
? B['R'] extends true
? K
: never
: never
: never]: true
}
type Optional<T, K = keyof T> = {
[j in K extends keyof T
? T[K] extends BoxedPrimitive<infer B>
? B['R'] extends false
? K
: never
: never
: never]: true
}
type extractMap<T extends mappedSchemaMap> = { [K in keyof Optional<T>]?: extractType<T[K]> } &
{ [K in keyof Required<T>]: extractType<T[K]> }
type extractOne<T extends mappedSchema> =
/** Primitive types */
T extends primitiveType
? T
: /** Holds the extracted type */
T extends BoxAnySchema<infer O>
? maybeExtractBox<O>
: T extends BoxBooleanSchema<infer O>
? maybeExtractBox<O>
: T extends BoxStringSchema<infer O>
? maybeExtractBox<O>
: T extends BoxNumberSchema<infer O>
? maybeExtractBox<O>
: T extends BoxDateSchema<infer O>
? maybeExtractBox<O>
: T extends BoxFunctionSchema<infer O>
? maybeExtractBox<O>
: T extends BoxArraySchema<infer O>
? maybeExtractBox<O>[]
: T extends BoxObjectSchema<infer O>
? maybeExtractBox<O>
: T extends BoxAlternativesSchema<infer O>
? maybeExtractBox<O>
: T extends joi.AnySchema
? any
: any
type extractType<T extends mappedSchema> =
/**
* Hack to support [Schema1, Schema2, ...N] alternatives notation
* Can't use extractType directly here because of cycles:
* ```
* T extends Array<infer O> ? extractType<O> :
* ^ cycle
* ```
*/
T extends Array<infer O>
? O extends joi.SchemaLike
? extractOne<O>
: O extends BoxedPrimitive
? extractOne<O>
: O
: /**
* Handle Objects as schemas, without Joi.object at the root.
* It needs to come first than mappedSchema.
* It is difficult to avoid it to be inferred from extends clause.
*/
T extends mappedSchemaMap
? extractMap<T>
: /**
* This is the base case for every schema implemented
*/
T extends joi.SchemaLike
? extractOne<T>
: T extends BoxedPrimitive
? extractOne<T>
: /**
* Default case to handle primitives and schemas
*/
extractOne<T>
}
@omairvaiyani
Copy link

I'm getting these errors on Typescript 4.1.2

types/joi.d.ts:424:8 - error TS2577: Return type annotation circularly references itself.

424     ): this extends BoxAlternativesSchema<infer O>
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
425       ? O extends Box<infer oT, infer oR>
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... 
429         : BoxAlternativesSchema<Box<extractType<T['then']> | extractType<T['otherwise']>, false>>
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
430       : BoxAlternativesSchema<Box<extractType<T['then']> | extractType<T['otherwise']>, false>>

Pretty much the same circular reference error throughout the file, any idea?

@kbrownlees
Copy link

This works great @panzelva! FYI it does seem a little outdated. Any idea how much you actually edit from https://github.com/TCMiranda/joi-extract-type/blob/master/index.ts? The piece I just hit is the extend signature is wrong here, should be something like this now I believe:

        extend(
            extension: joi.Extension | joi.Extension[],
            ...extensions: Array<joi.Extension | joi.Extension[]>
        ): this

@panzelva
Copy link
Author

@kbrownlees Hello! Yes, this is gist is somewhat outdated - the reason is that in the end we moved away from Hapi (that was using Joi extensively) to Fastify and we lost reason for using Joi in the first place.

Also in other cases we started using Zod, that has extract-type functionality build in.

Feel free to adjust the gist as needed and if possible share your updated version to TCMiranda/joi-extract-type#22, I believe that other comunity members would appreciate it.

@dp-franklin
Copy link

dp-franklin commented Jan 23, 2023

Here's a tweaked version that should solve a few issues. I'm no Joi expert so do with it what you will:

  • Fixes Typescript compiler error on v4.9.4. (May also fix issue of the compiler crashing on versions 4.6 through 4.8 but I haven't confirmed)
  • Fixes issue where .default(value) extracts to an optional type
  • Fixes optional type extraction
    • Because of this, .alternatives([...]) must also be fixed to exclude undefined from the extracted types
joi.d.ts
import joi from "joi";

declare module "joi" {
  interface Root {
    extend(...extensions: Array<joi.Extension | joi.ExtensionFactory>): this;

    any<T>(): BoxAnySchema<Box<T, false>>;

    string<T extends string>(): BoxStringSchema<Box<T, false>>;

    number<T extends number>(): BoxNumberSchema<Box<T, false>>;

    boolean<T extends boolean>(): BoxBooleanSchema<Box<T, false>>;
    bool<T extends boolean>(): BoxBooleanSchema<Box<T, false>>;

    date<T extends Date>(): BoxDateSchema<Box<T, false>>;

    func<T extends Function>(): BoxFunctionSchema<Box<T, false>>;

    array(): BoxArraySchema<Box<never, false>>;

    object<T extends mappedSchemaMap>(schema?: T): BoxObjectSchema<Box<extractMap<T>, false>>;

    alternatives<T extends mappedSchema[]>(
      ...alts: T
    ): BoxAlternativesSchema<Box<Exclude<extractType<typeof alts[number]>, undefined>, false>>;

    alternatives<T extends mappedSchema[]>(
      alts: T
    ): BoxAlternativesSchema<Box<Exclude<extractType<typeof alts[number]>, undefined>, false>>;

    alt<T extends mappedSchema[]>(...alts: T): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>;

    alt<T extends mappedSchema[]>(alts: T): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>;
  }

  /**
   * Field requirements interface
   */
  interface Box<T, R extends boolean> {
    /** Type the schema holds */
    T: T;
    /** If this attribute is required when inside an object */
    R: R;
  }

  // Operators
  type BoxType<B extends BoxSchema, nT> = Box<nT, B["R"]>;
  type BoxUnion<B extends BoxSchema, nT> = Box<B["T"] | nT, B["R"]>;
  type BoxIntersection<B extends BoxSchema, nT> = Box<B["T"] & nT, B["R"]>;
  type BoxReq<B extends BoxSchema, nR extends boolean> = Box<B["T"], nR>;
  type BoxSchema = Box<any, boolean>;

  type primitiveType = string | number | boolean | Function | Date | undefined | null | void;
  type mappedSchema = joi.SchemaLike | BoxedPrimitive;
  type mappedSchemaMap = { [K: string]: mappedSchema };
  type extendsGuard<T, S> = S extends T ? S : T;

  /**
   * Every Schema that implements the Box to allow the extraction
   */
  type BoxedPrimitive<T extends BoxSchema = any> =
    | BoxAnySchema<T>
    | BoxStringSchema<T>
    | BoxNumberSchema<T>
    | BoxBooleanSchema<T>
    | BoxDateSchema<T>
    | BoxFunctionSchema<T>
    | BoxArraySchema<T>
    | BoxObjectSchema<T>
    | BoxAlternativesSchema<T>;

  interface BoxAnySchema<N extends Box<any, boolean>> extends joi.AnySchema {
    __schemaTypeLiteral: "BoxAnySchema";

    default(value: any, description?: string): BoxAnySchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxAnySchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxAnySchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T>(...values: T[]): BoxAnySchema<BoxType<N, T>>;
    valid<T>(values: T[]): BoxAnySchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxAnySchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxAnySchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxAnySchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxStringSchema<N extends BoxSchema> extends joi.StringSchema {
    __schemaTypeLiteral: "BoxStringSchema";

    default(value: string, description?: string): BoxStringSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxStringSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxStringSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxStringSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxStringSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxStringSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxStringSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxStringSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxNumberSchema<N extends BoxSchema> extends joi.NumberSchema {
    __schemaTypeLiteral: "BoxNumberSchema";

    default(value: number, description?: string): BoxNumberSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxNumberSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxNumberSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxNumberSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxNumberSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxNumberSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxNumberSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxNumberSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxBooleanSchema<N extends BoxSchema> extends joi.BooleanSchema {
    __schemaTypeLiteral: "BoxBooleanSchema";

    default(value: boolean, description?: string): BoxBooleanSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxBooleanSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxBooleanSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxBooleanSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxBooleanSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxBooleanSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxBooleanSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxBooleanSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxDateSchema<N extends BoxSchema> extends joi.DateSchema {
    __schemaTypeLiteral: "BoxDateSchema";

    default(value: Date, description?: string): BoxDateSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxDateSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxDateSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxDateSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxDateSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxDateSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxDateSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxDateSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxFunctionSchema<N extends BoxSchema> extends joi.FunctionSchema {
    __schemaTypeLiteral: "BoxFunctionSchema";

    allow<T>(...values: T[]): BoxFunctionSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxFunctionSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    required(): BoxFunctionSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxFunctionSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxFunctionSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxArraySchema<N extends BoxSchema> extends joi.ArraySchema {
    __schemaTypeLiteral: "BoxArraySchema";

    default(value: N["T"][], description?: string): BoxArraySchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxArraySchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxArraySchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    items<T extends mappedSchema>(type: T): BoxArraySchema<BoxUnion<N, extractType<T>>>;

    items(...types: joi.SchemaLike[]): this;
    items(types: joi.SchemaLike[]): this;

    required(): BoxArraySchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxArraySchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxArraySchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxObjectSchema<N extends BoxSchema> extends joi.ObjectSchema {
    __schemaTypeLiteral: "BoxObjectSchema";

    default(value: N["T"], description?: string): BoxObjectSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxObjectSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxObjectSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    keys<T extends mappedSchemaMap>(schema: T): BoxObjectSchema<BoxIntersection<N, extractMap<T>>>;
    keys(schema?: joi.SchemaMap): this;

    append<T extends mappedSchemaMap>(schema: T): BoxObjectSchema<BoxIntersection<N, extractMap<T>>>;
    append(schema?: joi.SchemaMap): this;

    pattern<S extends BoxStringSchema<any>, T extends mappedSchema>(
      pattern: S,
      schema: T
    ): BoxObjectSchema<BoxIntersection<N, extractMap<{ [key in extractType<S>]: T }>>>;

    pattern<T extends mappedSchema>(
      pattern: RegExp,
      schema: T
    ): BoxObjectSchema<BoxIntersection<N, extractMap<{ [key: string]: T }>>>;

    pattern(pattern: RegExp | joi.SchemaLike, schema: joi.SchemaLike): this;

    required(): BoxObjectSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxObjectSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxObjectSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxAlternativesSchema<N extends BoxSchema> extends joi.AlternativesSchema {
    __schemaTypeLiteral: "BoxAlternativesSchema";

    allow<T>(...values: T[]): BoxAlternativesSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxAlternativesSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    try<T extends mappedSchema[]>(
      ...values: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T>>>
      : BoxAlternativesSchema<Box<extractType<T>, false>>;

    try<T extends mappedSchema[]>(
      values: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T>>>
      : BoxAlternativesSchema<Box<extractType<T>, false>>;

    try(...types: joi.SchemaLike[]): this;
    try(types: joi.SchemaLike[]): this;

    required(): BoxAlternativesSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxAlternativesSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxAlternativesSchema<BoxReq<N, false>>;
    optional(): this;

    when<R, T1 extends mappedSchema, T2 extends mappedSchema, T extends { then: T1; otherwise: T2 }>(
      ref: R,
      defs: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T["then"]> | extractType<T["otherwise"]>>>
      : BoxAlternativesSchema<Box<extractType<T["then"]> | extractType<T["otherwise"]>, false>>;

    when(ref: string | joi.Reference, options: joi.WhenOptions): this;
    when(ref: joi.Schema, options: joi.WhenSchemaOptions): this;
  }

  type maybeExtractBox<T> = T extends Box<infer O, infer R> ? (R extends true ? O : O | undefined) : T;

  type Required<T, K = keyof T> = {
    [j in K extends keyof T ? (T[K] extends BoxedPrimitive<infer B> ? (B["R"] extends true ? K : never) : never) : never]: true;
  };

  type Optional<T, K = keyof T> = {
    [j in K extends keyof T ? (T[K] extends BoxedPrimitive<infer B> ? (B["R"] extends false ? K : never) : never) : never]: true;
  };

  type extractMap<T extends mappedSchemaMap> = { [K in keyof Optional<T>]?: extractType<T[K]> } & {
    [K in keyof Required<T>]: extractType<T[K]>;
  };

  type extractOne<T extends mappedSchema> =
    /** Primitive types */
    T extends primitiveType
      ? T
      : /** Holds the extracted type */
      T extends BoxAnySchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxBooleanSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxStringSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxNumberSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxDateSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxFunctionSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxArraySchema<infer O>
      ? maybeExtractBox<O>[]
      : T extends BoxObjectSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxAlternativesSchema<infer O>
      ? maybeExtractBox<O>
      : T extends joi.AnySchema
      ? any
      : any;

  type extractType<T extends mappedSchema> =
    /**
     * Hack to support [Schema1, Schema2, ...N] alternatives notation
     * Can't use extractType directly here because of cycles:
     * ```
     * T extends Array<infer O> ? extractType<O> :
     *                            ^ cycle
     * ```
     */
    T extends Array<infer O>
      ? O extends joi.SchemaLike
        ? extractOne<O>
        : O extends BoxedPrimitive
        ? extractOne<O>
        : O
      : /**
       * Handle Objects as schemas, without Joi.object at the root.
       * It needs to come first than mappedSchema.
       * It is difficult to avoid it to be inferred from extends clause.
       */
      T extends mappedSchemaMap
      ? extractMap<T>
      : /**
       * This is the base case for every schema implemented
       */
      T extends joi.SchemaLike
      ? extractOne<T>
      : T extends BoxedPrimitive
      ? extractOne<T>
      : /**
         * Default case to handle primitives and schemas
         */
        extractOne<T>;
}

@yeongsheng-tan
Copy link

Here's a tweaked version that should solve a few issues. I'm no Joi expert so do with it what you will:

  • Fixes Typescript compiler error on v4.9.4. (May also fix issue of the compiler crashing on versions 4.6 through 4.8 but I haven't confirmed)

  • Fixes issue where .default(value) extracts to an optional type

  • Fixes optional type extraction

    • Because of this, .alternatives([...]) must also be fixed to exclude undefined from the extracted types

joi.d.ts

import joi from "joi";

declare module "joi" {
  interface Root {
    extend(...extensions: Array<joi.Extension | joi.ExtensionFactory>): this;

    any<T>(): BoxAnySchema<Box<T, false>>;

    string<T extends string>(): BoxStringSchema<Box<T, false>>;

    number<T extends number>(): BoxNumberSchema<Box<T, false>>;

    boolean<T extends boolean>(): BoxBooleanSchema<Box<T, false>>;
    bool<T extends boolean>(): BoxBooleanSchema<Box<T, false>>;

    date<T extends Date>(): BoxDateSchema<Box<T, false>>;

    func<T extends Function>(): BoxFunctionSchema<Box<T, false>>;

    array(): BoxArraySchema<Box<never, false>>;

    object<T extends mappedSchemaMap>(schema?: T): BoxObjectSchema<Box<extractMap<T>, false>>;

    alternatives<T extends mappedSchema[]>(
      ...alts: T
    ): BoxAlternativesSchema<Box<Exclude<extractType<typeof alts[number]>, undefined>, false>>;

    alternatives<T extends mappedSchema[]>(
      alts: T
    ): BoxAlternativesSchema<Box<Exclude<extractType<typeof alts[number]>, undefined>, false>>;

    alt<T extends mappedSchema[]>(...alts: T): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>;

    alt<T extends mappedSchema[]>(alts: T): BoxAlternativesSchema<Box<extractType<typeof alts[number]>, false>>;
  }

  /**
   * Field requirements interface
   */
  interface Box<T, R extends boolean> {
    /** Type the schema holds */
    T: T;
    /** If this attribute is required when inside an object */
    R: R;
  }

  // Operators
  type BoxType<B extends BoxSchema, nT> = Box<nT, B["R"]>;
  type BoxUnion<B extends BoxSchema, nT> = Box<B["T"] | nT, B["R"]>;
  type BoxIntersection<B extends BoxSchema, nT> = Box<B["T"] & nT, B["R"]>;
  type BoxReq<B extends BoxSchema, nR extends boolean> = Box<B["T"], nR>;
  type BoxSchema = Box<any, boolean>;

  type primitiveType = string | number | boolean | Function | Date | undefined | null | void;
  type mappedSchema = joi.SchemaLike | BoxedPrimitive;
  type mappedSchemaMap = { [K: string]: mappedSchema };
  type extendsGuard<T, S> = S extends T ? S : T;

  /**
   * Every Schema that implements the Box to allow the extraction
   */
  type BoxedPrimitive<T extends BoxSchema = any> =
    | BoxAnySchema<T>
    | BoxStringSchema<T>
    | BoxNumberSchema<T>
    | BoxBooleanSchema<T>
    | BoxDateSchema<T>
    | BoxFunctionSchema<T>
    | BoxArraySchema<T>
    | BoxObjectSchema<T>
    | BoxAlternativesSchema<T>;

  interface BoxAnySchema<N extends Box<any, boolean>> extends joi.AnySchema {
    __schemaTypeLiteral: "BoxAnySchema";

    default(value: any, description?: string): BoxAnySchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxAnySchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxAnySchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T>(...values: T[]): BoxAnySchema<BoxType<N, T>>;
    valid<T>(values: T[]): BoxAnySchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxAnySchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxAnySchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxAnySchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxStringSchema<N extends BoxSchema> extends joi.StringSchema {
    __schemaTypeLiteral: "BoxStringSchema";

    default(value: string, description?: string): BoxStringSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxStringSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxStringSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxStringSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxStringSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxStringSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxStringSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxStringSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxNumberSchema<N extends BoxSchema> extends joi.NumberSchema {
    __schemaTypeLiteral: "BoxNumberSchema";

    default(value: number, description?: string): BoxNumberSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxNumberSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxNumberSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxNumberSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxNumberSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxNumberSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxNumberSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxNumberSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxBooleanSchema<N extends BoxSchema> extends joi.BooleanSchema {
    __schemaTypeLiteral: "BoxBooleanSchema";

    default(value: boolean, description?: string): BoxBooleanSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxBooleanSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxBooleanSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxBooleanSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxBooleanSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxBooleanSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxBooleanSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxBooleanSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxDateSchema<N extends BoxSchema> extends joi.DateSchema {
    __schemaTypeLiteral: "BoxDateSchema";

    default(value: Date, description?: string): BoxDateSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxDateSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxDateSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    valid<T extends string>(...values: T[]): BoxDateSchema<BoxType<N, T>>;
    valid<T extends string>(values: T[]): BoxDateSchema<BoxType<N, T>>;
    valid(...values: any[]): this;
    valid(values: any[]): this;

    required(): BoxDateSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxDateSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxDateSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxFunctionSchema<N extends BoxSchema> extends joi.FunctionSchema {
    __schemaTypeLiteral: "BoxFunctionSchema";

    allow<T>(...values: T[]): BoxFunctionSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxFunctionSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    required(): BoxFunctionSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxFunctionSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxFunctionSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxArraySchema<N extends BoxSchema> extends joi.ArraySchema {
    __schemaTypeLiteral: "BoxArraySchema";

    default(value: N["T"][], description?: string): BoxArraySchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxArraySchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxArraySchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    items<T extends mappedSchema>(type: T): BoxArraySchema<BoxUnion<N, extractType<T>>>;

    items(...types: joi.SchemaLike[]): this;
    items(types: joi.SchemaLike[]): this;

    required(): BoxArraySchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxArraySchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxArraySchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxObjectSchema<N extends BoxSchema> extends joi.ObjectSchema {
    __schemaTypeLiteral: "BoxObjectSchema";

    default(value: N["T"], description?: string): BoxObjectSchema<BoxReq<N, true>>;
    /** @deprecated */
    default(): this;

    allow<T>(...values: T[]): BoxObjectSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxObjectSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    keys<T extends mappedSchemaMap>(schema: T): BoxObjectSchema<BoxIntersection<N, extractMap<T>>>;
    keys(schema?: joi.SchemaMap): this;

    append<T extends mappedSchemaMap>(schema: T): BoxObjectSchema<BoxIntersection<N, extractMap<T>>>;
    append(schema?: joi.SchemaMap): this;

    pattern<S extends BoxStringSchema<any>, T extends mappedSchema>(
      pattern: S,
      schema: T
    ): BoxObjectSchema<BoxIntersection<N, extractMap<{ [key in extractType<S>]: T }>>>;

    pattern<T extends mappedSchema>(
      pattern: RegExp,
      schema: T
    ): BoxObjectSchema<BoxIntersection<N, extractMap<{ [key: string]: T }>>>;

    pattern(pattern: RegExp | joi.SchemaLike, schema: joi.SchemaLike): this;

    required(): BoxObjectSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxObjectSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxObjectSchema<BoxReq<N, false>>;
    optional(): this;
  }

  interface BoxAlternativesSchema<N extends BoxSchema> extends joi.AlternativesSchema {
    __schemaTypeLiteral: "BoxAlternativesSchema";

    allow<T>(...values: T[]): BoxAlternativesSchema<BoxUnion<N, T>>;
    allow<T>(values: T[]): BoxAlternativesSchema<BoxUnion<N, T>>;
    allow(...values: any[]): this;
    allow(values: any[]): this;

    try<T extends mappedSchema[]>(
      ...values: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T>>>
      : BoxAlternativesSchema<Box<extractType<T>, false>>;

    try<T extends mappedSchema[]>(
      values: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T>>>
      : BoxAlternativesSchema<Box<extractType<T>, false>>;

    try(...types: joi.SchemaLike[]): this;
    try(types: joi.SchemaLike[]): this;

    required(): BoxAlternativesSchema<BoxReq<N, true>>;
    required(): this;
    exist(): BoxAlternativesSchema<BoxReq<N, true>>;
    exist(): this;
    optional(): BoxAlternativesSchema<BoxReq<N, false>>;
    optional(): this;

    when<R, T1 extends mappedSchema, T2 extends mappedSchema, T extends { then: T1; otherwise: T2 }>(
      ref: R,
      defs: T
    ): N extends Box<infer oT, infer oR>
      ? BoxAlternativesSchema<BoxType<N, oT | extractType<T["then"]> | extractType<T["otherwise"]>>>
      : BoxAlternativesSchema<Box<extractType<T["then"]> | extractType<T["otherwise"]>, false>>;

    when(ref: string | joi.Reference, options: joi.WhenOptions): this;
    when(ref: joi.Schema, options: joi.WhenSchemaOptions): this;
  }

  type maybeExtractBox<T> = T extends Box<infer O, infer R> ? (R extends true ? O : O | undefined) : T;

  type Required<T, K = keyof T> = {
    [j in K extends keyof T ? (T[K] extends BoxedPrimitive<infer B> ? (B["R"] extends true ? K : never) : never) : never]: true;
  };

  type Optional<T, K = keyof T> = {
    [j in K extends keyof T ? (T[K] extends BoxedPrimitive<infer B> ? (B["R"] extends false ? K : never) : never) : never]: true;
  };

  type extractMap<T extends mappedSchemaMap> = { [K in keyof Optional<T>]?: extractType<T[K]> } & {
    [K in keyof Required<T>]: extractType<T[K]>;
  };

  type extractOne<T extends mappedSchema> =
    /** Primitive types */
    T extends primitiveType
      ? T
      : /** Holds the extracted type */
      T extends BoxAnySchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxBooleanSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxStringSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxNumberSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxDateSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxFunctionSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxArraySchema<infer O>
      ? maybeExtractBox<O>[]
      : T extends BoxObjectSchema<infer O>
      ? maybeExtractBox<O>
      : T extends BoxAlternativesSchema<infer O>
      ? maybeExtractBox<O>
      : T extends joi.AnySchema
      ? any
      : any;

  type extractType<T extends mappedSchema> =
    /**
     * Hack to support [Schema1, Schema2, ...N] alternatives notation
     * Can't use extractType directly here because of cycles:
     * ```
     * T extends Array<infer O> ? extractType<O> :
     *                            ^ cycle
     * ```
     */
    T extends Array<infer O>
      ? O extends joi.SchemaLike
        ? extractOne<O>
        : O extends BoxedPrimitive
        ? extractOne<O>
        : O
      : /**
       * Handle Objects as schemas, without Joi.object at the root.
       * It needs to come first than mappedSchema.
       * It is difficult to avoid it to be inferred from extends clause.
       */
      T extends mappedSchemaMap
      ? extractMap<T>
      : /**
       * This is the base case for every schema implemented
       */
      T extends joi.SchemaLike
      ? extractOne<T>
      : T extends BoxedPrimitive
      ? extractOne<T>
      : /**
         * Default case to handle primitives and schemas
         */
        extractOne<T>;
}

How can this be used in project upgrading from @hapi/joi to new [email protected]? Do I manually overwrite index.d.ts in node_modules/joi-extract-typedist/index.d.ts with this file contents?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment