Last active
August 8, 2024 00:25
-
-
Save ClickerMonkey/a081b990b9b14215141fb6248cef4dc4 to your computer and use it in GitHub Desktop.
Typescript Helper Types
This file contains 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>) {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment