Skip to content

Commit

Permalink
feat: improve support for union input types
Browse files Browse the repository at this point in the history
  • Loading branch information
dextertanyj committed May 4, 2023
1 parent 3ff55da commit 1dca246
Show file tree
Hide file tree
Showing 2 changed files with 1,754 additions and 10 deletions.
37 changes: 27 additions & 10 deletions src/types/transformed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type GetFieldType<T, Path extends string> = NormalizePath<Path> extends ""
? GetArrayFieldType<T, NormalizePath<Path>>
: NormalizePath<Path> extends `${string}`
? GetRecordFieldType<T, NormalizePath<Path>>
: never;
: never; // Invalid Path.

/**
* Returns the type of the field defined by `Path`, resolved with respect to the record `T`.
Expand All @@ -38,14 +38,16 @@ type GetFieldType<T, Path extends string> = NormalizePath<Path> extends ""
type GetRecordFieldType<T, Path> = T extends Record<string, unknown> | undefined | null
? Path extends "" // Preserves original optionality of T.
? T
: [T] extends [undefined | null] // Short circuit if T is only undefined or null.
? undefined
: Path extends keyof Defined<T>
? OptionalWrapper<T, Defined<T>[Path]>
: Path extends `${infer Key extends keyof T & string}${"." | `[${number | "*"}]`}${string}`
? Path extends `${Key}${infer Rest extends string}`
? OptionalWrapper<T, GetFieldType<T[Key], NormalizePath<Rest>>>
: never
: never
: never;
: never // Invalid Path.
: undefined // Key is not keyof T.
: undefined; // T is not a Record.

/**
* Returns the type of the field defined by `Path`, resolved with respect to the array `Array`.
Expand All @@ -55,17 +57,29 @@ type GetRecordFieldType<T, Path> = T extends Record<string, unknown> | undefined
* @typeparam `Array` - The current array type.
* @typeparam `Path` - The path to the field.
*/
type GetArrayFieldType<Array, Path extends string> = Array extends unknown[] | undefined | null
type GetArrayFieldType<T, Path extends string> = T extends unknown[] | undefined | null
? Path extends ""
? Array
? T
: [T] extends [undefined | null] // Short circuit if T is only undefined or null.
? undefined
: Path extends `[${infer Index extends number | "*"}]${infer Next}`
? Defined<Array>[number] extends infer Element
? Defined<T>[number] extends infer Element
? GetFieldType<Element, Next> extends infer Result
? WrapArrayResult<Array, Result, Index, Next>
? [Result] extends [undefined]
? // Element is guaranteed to not be never since T is not undefined or null,
// but the field cannot exist on any Element.
//
// Return never instead of undefined since the field must on elements of some
// other sibling T (otherwise the Path should not pass typechecking).
// At runtime, this T and its sibling arrays cannot be fully differentiated
// since optional and undefined properties of nested objects cannot be
// differentiated from invalid properties.
never
: WrapArrayResult<T, Result, Index, Next> // Field possibly exists on Element.
: never
: never
: never
: never;
: undefined; // T is not an Array.

/**
* Returns `Result` wrapped in an array if Index is a wildcard and no other wildcards are present in `Path`.
Expand All @@ -76,7 +90,10 @@ type WrapArrayResult<Array, Result, Index, Path extends string> = Index extends
: NormalizePath<Path> extends ""
? OptionalWrapper<Array, ArrayOf<Result>>
: Contains<NormalizePath<Path>, "[*]"> extends true
? OptionalWrapper<Array, Exclude<Result, undefined>>
? // Merge flattened array types for results that contain union types.
Exclude<Result, undefined> extends (infer T)[]
? OptionalWrapper<Array, ArrayOf<T>>
: never
: OptionalWrapper<Array, ArrayOf<Result>>;

type DeclaredArrayTransformed<T, ArrayPath extends string, Value> = GetFieldType<
Expand Down
Loading

0 comments on commit 1dca246

Please sign in to comment.