Skip to content

Instantly share code, notes, and snippets.

@cassiozen
Created September 19, 2022 17:51
Show Gist options
  • Save cassiozen/7e12c0d3212d20aeb9650fcbc6b3da37 to your computer and use it in GitHub Desktop.
Save cassiozen/7e12c0d3212d20aeb9650fcbc6b3da37 to your computer and use it in GitHub Desktop.
Typescript Type Utilities (by https://github.com/devanshj)
export namespace TypeUtils {
/**
* Casts a type (unless it's already narrower)
* @example
* // returns 'hey'
* Cast<'hey', string>
* @example
* // returns string
* Cast<unknown, string>
*/
export type Cast<TType, TBroadType> = TType extends TBroadType ? TType : TBroadType;
/**
* Gets a subtype of a collection type (object, array, tuple)
* @example
* Get<{name: string, pronoums: ['they','them']}, 'pronoums'> //['they','them']
*
* You can also get within a nested collection by passing a tuple
* @example
* Get<{user: { name: string, pronoums: ['they','them']}}, ['user', 'pronoums', 0]> //'they'
*/
export type Get<TCollection, TKeys, TFallback = undefined> = TKeys extends any[]
? TKeys extends []
? TCollection extends undefined
? TFallback
: TCollection
: TKeys extends [infer K1, ...infer Kr]
? K1 extends keyof TCollection
? Get<TCollection[K1], Kr, TFallback> extends infer X
? Cast<X, any>
: never
: TFallback
: never
: Get<TCollection, [TKeys], TFallback>;
// Compares if two types A and B are equal. Returns the type literal 'true' or 'false'
export type AreEqual<A, B> = (<T>() => T extends B ? 1 : 0) extends <T>() => T extends A ? 1 : 0 ? true : false;
// Checks if type A extends type B. Returns the type literal 'true' or 'false'
export type DoesExtend<A, B> = A extends B ? true : false;
// Checks if a type is an plain object (not a function)
export type IsPlainObject<TType> = Bool.And<DoesExtend<TType, A.Object>, Bool.Not<DoesExtend<TType, A.Function>>>;
export namespace A {
/**
* Aliases for common generic types
*/
export type Function = (...args: any[]) => any;
export type Tuple<T = any> = T[] | [T];
export type TupleOrUnit<T> = T | Tuple<T>;
export type Object = object;
export type String = string;
export type Number = number;
export type Universal = string | number | boolean | undefined | null | bigint | object | ((...a: any[]) => any);
}
export namespace Obj {
/**
* Object Type utilities
*/
// Casts TType to object type
export type Assert<TType> = Cast<TType, A.Object>;
// Apply readonly recursively
export type DeepReadonly<TObj> = {
readonly [TKey in keyof TObj]: TObj[TKey] extends any
? TObj[TKey] extends A.Function
? TObj[TKey]
: TObj[TKey] extends A.Object
? DeepReadonly<TObj[TKey]>
: TObj[TKey]
: never;
};
// Returns the first key in TObj whose type matches TValueType
// Ex: KeyWithValue<{name: string, age: number}, number> //"age"
export type KeyWithValue<TObj, TValueType> = {
[Key in keyof TObj]: TObj[Key] extends TValueType ? Key : never;
}[keyof TObj];
// Returns an object with all its keys and values.
// Can be used as a trick to force Typescript to express
// an union of objects as a merged object
// Ex: type T = {name: string} & {age: number} //{name: string} & {age: number}
// Ex: Mergify<T> //{name: string, age: number}
export type Mergify<TObj> = {
[Key in keyof TObj]: TObj[Key];
};
// Updates the type of a given key within an object type
// Ex: type T = {name: string, age: string} //{name: string, age: string}
// Ex: Update<T, {age: number}> //{name: string, age: number}
export type Update<A, B> = Mergify<
{
[K in Union.Exclude<keyof A, keyof B>]: A[K];
} & {
[K in keyof B]: B[K];
}
>;
}
export namespace List {
/**
* Array and Tuple Type utilities
*/
// Casts TType to tuple type
export type Assert<TType> = Cast<TType, A.Tuple>;
// Concatenate two tuple types A and B
export type Concat<A, B> = [...List.Assert<A>, ...List.Assert<B>];
// Returns a new tuple type with type TType pushed into tuple type TTuple
// Ex.: Pushed<[string, string], number> //[string, string, number]
export type Pushed<TTuple, TType> = [...List.Assert<TTuple>, TType];
// Returns a new tuple type with the last type removed from TTuple
// Ex.: Popped<[string, string, number]> //[string, string]
export type Popped<TTuple> = TTuple extends [] ? [] : TTuple extends [...infer Popped, any] ? Popped : never;
// Returns the last type from tuple TTuple
// Ex.: Pop<[string, string, number]> //number
export type Pop<TTuple> = TTuple extends []
? undefined
: TTuple extends [...List.Popped<TTuple>, infer X]
? X
: never;
// Returns a new tuple type with the first type removed from TTuple
// Ex.: Shifted<[string, string, number]> //[string, number]
export type Shifted<TTuple> = TTuple extends [] ? [] : TTuple extends [any, ...infer Shifted] ? Shifted : never;
// Returns the first type from tuple TTuple
// Ex.: Shift<[string, string, number]> //string
export type Shift<TTuple> = TTuple extends [] ? undefined : TTuple extends [infer X, ...infer _] ? X : never;
// Concatenate multiple tuples
// Ex.: ConcatAll<[[string, number], [boolean], [10]]> //[string, number, boolean, 10]
export type ConcatAll<TTuples> = TTuples extends []
? []
: TTuples extends [infer A]
? A
: TTuples extends [infer A, infer B, ...infer X]
? ConcatAll<[List.Concat<A, B>, ...X]>
: never;
// Joins a tuple type TTuple into a string literal or template literal
// Ex.: Join<['Chocolate', 'Strawberry'], ', '> //"Chocolate, Strawberry"
// Ex.: Join<[string, number], ', '> //`${string}, ${number}`
export type Join<TTuple, TDelimeter> = TTuple extends []
? ""
: TTuple extends [infer H, ...infer T]
? T extends []
? H
: `${String.Assert<H>}${String.Assert<TDelimeter>}${Join<T, TDelimeter>}`
: string;
// Checks if tuple type TTuple includes type TValue. Returns the type literal 'true' or 'false'
// Ex.: Includes<[1,2,3], 2> //true
export type Includes<TTuple, TValue> = TTuple extends []
? false
: TValue extends Get<TTuple, number>
? true
: false;
// Returns a new tuple type with the type TValue filtered out
// Ex.: Filter<[10,20,30], 20> //[10, 30]
export type Filter<TTuple, TValue = Filter.Out> = TTuple extends []
? []
: TTuple extends [infer H, ...infer T]
? H extends TValue
? Filter<T, TValue>
: [H, ...Filter<T, TValue>]
: never;
export namespace Filter {
declare const Out: unique symbol;
export type Out = typeof Out;
}
}
export namespace Union {
/**
* Union type utilities
*/
// Checks if type is an union. Returns the type literal 'true' or 'false'
// Ex.: IsUnit<'hello'> //false
// Ex.: IsUnit<'hello' | 'goodbye'> //true
export type IsUnit<T> = [Union.Popped<T>] extends [never] ? true : false;
// Returns a new type with the last type removed from union TUnion
// Ex.: Popped<'morning' | 'afternoon' | 'night'> //'morning' | 'afternoon'
export type Popped<TUnion> = Union.Exclude<TUnion, Union.Pop<TUnion>>;
// Returns a new type with the type TValue excluded from union TUnion
// Ex.:Exclude<'morning' | 'afternoon' | 'night', 'morning'> //'afternoon' | 'night'
export type Exclude<TUnion, TValue> = TUnion extends TValue ? never : TUnion;
// Returns a new type with the type TValue extracted from union TUnion
// Ex.:Extract<'morning' | 'afternoon' | 'night', 'morning'> //'morning'
export type Extract<TUnion, TValue> = TUnion extends TValue ? TUnion : never;
// Returns the last type from tuple TTuple
// Ex.: Pop<'clubs' | 'diamonds' | 'hearts' | 'spades'> //'spades'
export type Pop<TUnion> = ToIntersection<TUnion extends unknown ? (x: TUnion) => void : never> extends (
x: infer P
) => void
? P
: never;
// Returns the intersection between types in an union
// Ex.: ToIntersection<number | number> //number
export type ToIntersection<TUnion> = (TUnion extends unknown ? (k: TUnion) => void : never) extends (
k: infer I
) => void
? I
: never;
// Returns a tuple of all the types in the union
// ToTuple<'clubs' | 'diamonds' | 'hearts' | 'spades'> //["clubs", "diamonds", "hearts", "spades"]
export type ToTuple<TUnion> = [TUnion] extends [never]
? []
: [...Union.ToTuple<Union.Popped<TUnion>>, Union.Pop<TUnion>];
}
export namespace Bool {
/**
* Boolean Type utilities
*/
export type Not<B> = B extends true ? false : true;
export type And<A, B> = AreEqual<Union.ToIntersection<Get<[A, B], number>>, true>;
}
export namespace String {
/**
* String type utilities
*/
// Casts TType to string type
export type Assert<TType> = Cast<TType, A.String>;
// Checks if TType is a string literal. Returns the type literal 'true' or 'false'
export type IsLiteral<TType> = TType extends A.String ? (A.String extends TType ? false : true) : false;
// Checks if string literal TString starts with TValue.
// Ex.: DoesStartWith<'order status ticket', 'order'> //true
// Ex.: DoesStartWith<'order status ticket', 'status'> //false
export type DoesStartWith<TString, TValue> = TString extends TValue
? true
: TString extends `${String.Assert<TValue>}${infer _}`
? true
: false;
// Checks if string literal TString contains TValue.
// Ex.: DoesContain<'order status ticket', 'order'> //true
// Ex.: DoesContain<'order status ticket', 'status'> //true
export type DoesContain<TString, TValue> = TString extends TValue
? true
: TString extends `${infer _}${String.Assert<TValue>}${infer __}`
? true
: false;
// Splits a string literal type TString into a tuple using delimiter TDelimiter
// Ex.: Split<"chocolate, strawberry, cherry, vanilla", ", "> // ["chocolate", "strawberry", "cherry", "vanilla"]
export type Split<TString, TDelimiter> = TString extends `${infer H}${String.Assert<TDelimiter>}${infer T}`
? [H, ...Split<T, TDelimiter>]
: [TString];
// Given a string literal type TString, returns a new string literal replacing all occurencies of TWhat for TWith
// Ex.: Replace<"Hello World", "World", "Universe"> // "Hello Universe"
export type Replace<TString, TWhat, TWith> = TString extends `${infer P}${String.Assert<TWhat>}${infer S}`
? `${P}${String.Assert<TWith>}${Replace<S, TWhat, TWith>}`
: TString;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment