Skip to content

Instantly share code, notes, and snippets.

@oirodolfo
Forked from ClickerMonkey/types.ts
Created November 11, 2021 19:14
Show Gist options
  • Save oirodolfo/23da6e031f964a987a4dfed5f8391f41 to your computer and use it in GitHub Desktop.
Save oirodolfo/23da6e031f964a987a4dfed5f8391f41 to your computer and use it in GitHub Desktop.
Typescript Helper Types
// 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