Created
September 19, 2022 17:51
-
-
Save cassiozen/7e12c0d3212d20aeb9650fcbc6b3da37 to your computer and use it in GitHub Desktop.
Typescript Type Utilities (by https://github.com/devanshj)
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
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