-
-
Save MrChocolatine/6cfea14b6124f5660287cb9c78e55b2a to your computer and use it in GitHub Desktop.
TypeScript – Collection of utility types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// when T is any|unknown, Y is returned, otherwise N | |
type IsAnyUnknown<T, Y, N> = unknown extends T ? Y : N; | |
// when T is never, Y is returned, otherwise N | |
type IsNever<T, Y = true, N = false> = [T] extends [never] ? Y : N; | |
// when T is a tuple, Y is returned, otherwise N | |
// valid tuples = [string], [string, boolean], | |
// invalid tuples = [], string[], (string | number)[] | |
type IsTuple<T, Y = true, N = false> = T extends [any, ...any[]] ? Y : N; | |
// empty object | |
type EmptyObject = { [key: string]: never }; | |
// is a type an empty object? | |
type IsEmpty<T, Y = true, N = false> = T extends EmptyObject ? Y : N; | |
// returns the value type for T[K] without having to do "K extends keyof T ? T[K] : never" | |
type Lookup<T, K, N = never> = T extends any ? K extends keyof T ? T[K] : N : N; | |
// sometimes you have a type with never values, this removes those keys from T | |
type StripNever<T> = Pick<T, { [K in keyof T]: IsNever<T[K], never, K> }[keyof T]>; | |
// sometimes something is an expected type, but TypeScript has problem recognizing it. | |
// This can ensure the expected type is being used. | |
type Cast<T, AS> = T extends AS ? T : never; | |
// Returns the result of { ...A, ...B } | |
type MergeObjects<A, B> = { | |
[K in keyof B]: undefined extends B[K] | |
? K extends keyof A | |
? Exclude<B[K], undefined> | A[K] | |
: B[K] | |
: B[K] | |
} & { | |
[K in keyof A]: K extends keyof B | |
? undefined extends B[K] | |
? Exclude<B[K], undefined> | A[K] | |
: B[K] | |
: A[K] | |
}; | |
// Which keys in T are undefined | |
type UndefinedKeys<T> = { | |
[P in keyof T]-?: undefined extends T[P] ? P : never | |
}[keyof T]; | |
// When a type is really deep and has retained an unnessecary amount of type information, | |
// this flattens it to a single array/object/value. | |
type Simplify<T> = | |
T extends (object | any[]) | |
? { [K in keyof T]: T[K] } | |
: T; | |
// Converts { x: string | undefined } to { x?: string | undefined } | |
type UndefinedToOptional<T> = Simplify< | |
Pick<T, Exclude<keyof T, UndefinedKeys<T>>> & | |
Partial<Pick<T, UndefinedKeys<T>>> | |
>; | |
// Converts { x: string } | { y: number } to { x: string, y: number } | |
type UnionToIntersection<T> = | |
(T extends any ? (x: T) => any : never) extends | |
(x: infer R) => any ? R : never; | |
// Converts string | number | boolean to [string, number, boolean] | |
type UnionToTuple<T> = ( | |
( | |
( | |
T extends any | |
? (t: T) => T | |
: never | |
) extends infer U | |
? (U extends any | |
? (u: U) => any | |
: never | |
) extends (v: infer V) => any | |
? V | |
: never | |
: never | |
) extends (_: any) => infer W | |
? [...UnionToTuple<Exclude<T, W>>, W] | |
: [] | |
); | |
// Converts (string | number)[] to [string, number] | |
type ArrayToTuple<T> = | |
T extends Array<infer E> | |
? UnionToTuple<E> | |
: T | |
; | |
// Merges objects and changes undefined to optional if any exist. | |
type AppendObjects<A, B> = | |
UndefinedToOptional<MergeObjects<A, B>> | |
; | |
// AppendTuples<[string, boolean], [string, number]> = [string, boolean, string, number] | |
type AppendTuples<A extends any[], B extends any[]> = | |
Simplify<[...A, ...B]> | |
; | |
// JSON | |
type JsonScalar = number | string | boolean | null; | |
type JsonObject = { [key: string]: JsonScalar | JsonObject | JsonArray; }; | |
type JsonArray = Array<JsonScalar | JsonObject | JsonArray>; | |
type Json = JsonScalar | JsonObject | JsonArray; | |
// ObjectKeys<{ x: string, y: number }> = ["x", "y"] | |
type ObjectKeys<T> = | |
UnionToTuple<keyof T> extends Array<keyof T> | |
? UnionToTuple<keyof T> | |
: never; | |
// The properties of T should be Partialed | |
type PartialChildren<T> = { | |
[K in keyof T]: Partial<T[K]> | |
}; | |
// JoinTuples<[[string], [], [boolean, number], [string, Date]]> = [string, boolean, number, string, Date] | |
type JoinTuples<T extends any[]> = | |
T extends [infer A] | |
? ToTuple<A> | |
: T extends [infer B, ...infer C] | |
? [...ToTuple<B>, ...JoinTuples<C>] | |
: []; | |
// Converts anything to a tuple or array, unless it aleady is. | |
type ToTuple<T extends any> = T extends any[] ? T : [T]; | |
// A extends B is not good enough sometimes, especially when never is involved. | |
// Also order matters when doing extends, so this does both directions. | |
type Extends<A, B, T = true, F = false> = [A] extends [B] ? [B] extends [A] ? T : F : F; | |
/** | |
* forbids manipulation for the wrapped type (recursive) | |
* but i guess its not hard enough against typecasting | |
* | |
* | |
* credits and thanks to https://templecoding.com/blog/real-immutable-types-with-typescript | |
* --> original | |
* fails to be compatible to "Date"-Objects | |
* | |
* const immDate: Immutable<Date> = new Date(1); | |
* const date: Date = immDate; // Error | |
* | |
* --> workaround | |
* adding "T &" {... | |
*/ | |
export type Immutable<T> = T & { readonly [K in keyof T]: Immutable<T[K]> }; | |
// Testing return types. | |
// If the input doesn't match T there will be a TS error. | |
// | |
// expectType<string>('hello') | |
function expectType<Expected>(type: Expected) {} | |
// Testing types against each other (used to unit test the above types) | |
// If the types don't match there will be a TS error. | |
// | |
// expectTypeMatch<["x", "y"], ObjectKeys<{ x: string, y: number }>>(true); | |
// | |
// If you want to test they are NOT equivalent, pass false to function. | |
function expectTypeMatch<A, B>(truthy: Extends<A, B>) {} | |
// ------------------- | |
/** | |
* Construct a mutable tuple by excluding from an initial tuple all members that are assignable to | |
* the list of values provided in the second parameter. | |
* Inspired by: https://stackoverflow.com/questions/54607400/typescript-remove-entries-from-tuple-type/64034671#64034671 | |
* @param Tuple Initial tuple to exclude the values from. | |
* @param ValuesToExclude A union of values to exclude | |
* @return A new tuple without the excluded values | |
*/ | |
type ExcludeFromTuple<Tuple extends ReadonlyArray<any>, ValuesToExclude> = | |
Tuple extends [] | |
? [] | |
: Tuple extends readonly [ infer Value, ...infer RestOfValues ] | |
? Value extends ValuesToExclude | |
? ExcludeFromTuple<RestOfValues, ValuesToExclude> | |
: [ Value, ...ExcludeFromTuple<RestOfValues, ValuesToExclude> ] | |
: never | |
// Some tests for the util `ExcludeFromTuple` | |
// To be removed after pair-review | |
// type SomeTuple = [undefined, null, 1, 'toto', true] | |
// type SomeTupleRO = readonly [undefined, null, 1, 'toto', true] | |
// let truc: ExcludeFromTuple<SomeTuple, null | undefined> | |
// let truc: ExcludeFromTuple<SomeTuple, 1> | |
// let truc: ExcludeFromTuple<SomeTuple, 'toto'> | |
// let truc: ExcludeFromTuple<SomeTuple, null | 1> | |
// let truc: ExcludeFromTuple<SomeTuple, 'toto' | true | undefined | null> | |
// let truc: ExcludeFromTuple<SomeTupleRO, null | undefined> | |
// let truc: ExcludeFromTuple<SomeTupleRO, 1> | |
// let truc: ExcludeFromTuple<SomeTupleRO, 'toto'> | |
// let truc: ExcludeFromTuple<SomeTupleRO, null | 1> | |
// let truc: ExcludeFromTuple<SomeTupleRO, 'toto' | true | undefined | null> | |
// let truc: ExcludeFromTuple<[], 1> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment