Skip to content

Instantly share code, notes, and snippets.

@ClickerMonkey
Last active August 8, 2024 00:25
Show Gist options
  • Save ClickerMonkey/a081b990b9b14215141fb6248cef4dc4 to your computer and use it in GitHub Desktop.
Save ClickerMonkey/a081b990b9b14215141fb6248cef4dc4 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>) {}
@Schokokex
Copy link

/**
 * 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]> };

@schontz
Copy link

schontz commented Jan 14, 2021

What is the license of this gist?

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