Typescript Utilities
12 December 2022
Updated: 14 July 2024
Here’s a collection of some utility types I’ve put together:
Functions
1import { Reverse } from './arrays'2
3/**4 * A function with defined input and output5 */6export type Fn<TParams, TResult> = (params: TParams) => TResult7
8/**9 * A function that returns a result or undefined10 */11export type OptionFn<TParams, TResult> = Fn<TParams, TResult | undefined>12
13/**14 * A function that returns void15 */16export type VoidFn<TParams> = (params: TParams) => void17
18/**19 * Convert a function type into an async version of that function20 */21export type Async<TFn extends (...args: any[]) => any> = (22 ...args: Parameters<TFn>23) => Promise<ReturnType<TFn>>24
25/**26 * Create an async version of `Fn`27 */28export type AsyncFn<TParams, TResult> = Async<Fn<TParams, TResult>>29
30/**31 * Create an async version of `OptionFn`32 */33export type AsyncOptionFn<TParams, TResult> = Async<OptionFn<TParams, TResult>>34
35/**36 * Create an async version of `VoidFn`37 */38export type AsyncVoidFn<TParams> = Async<VoidFn<TParams>>39
40/**41 * Create a version of a function that may either be sync or async42 */43export type SyncOrAsync<TFn extends (...args: any[]) => any> = (44 ...args: Parameters<TFn>45) => ReturnType<TFn> | Promise<ReturnType<TFn>>46
47/**48 * Create a version of `Fn` that may either be sync or async49 */50export type SyncOrAsyncFn<TParams, TResult> = SyncOrAsync<Fn<TParams, TResult>>51
52/**53 * Create a version of `OptionFn` that may either be sync or async54 */55export type SyncOrAsyncOptionFn<TParams, TResult> = SyncOrAsync<56 OptionFn<TParams, TResult>57>58
59/**60 * Create a version of `VoidFn` that may either be sync or async61 */62export type SyncOrAsyncVoidFn<TParams> = SyncOrAsync<VoidFn<TParams>>63
64/**65 * Returns a function with the arguments reversed66 */67export type ReverseArguments<T extends (...args: any[]) => any> = (68 ...args: Reverse<Parameters<T>>69) => ReturnType<T>
Arrays
1/**2 * An array with at least 1 item in it3 */4export type NonEmptyArray<T> = [T, ...T[]]5
6/**7 * Array filter type, used to filter an array via the `.filter` method8 */9export type ArrayFilter<T> = (value: T, index: number, array: T[]) => boolean10
11/**12 * Array map type, used to map an array via the `.map` method13 */14export type ArrayMap<T, U> = (value: T, index: number, array: T[]) => U15
16/**17 * Array reduce type, used to reduce an array via the `.reduce` method18 */19export type ArrayReducer<T, U> = (20 previousValue: U,21 currentValue: T,22 currentIndex: number,23 array: T[],24) => U25
26/**27 * Defines a step type that may contain different data types as an array such28 * that each step adds or removes from the current input but should always29 * result in the same final object that can be validated by just verifying the30 * final length assuming all steps have been validated prior31 */32export type StepsOf<Final extends Readonly<any[]>> = Final extends [33 ...infer Next,34 infer _,35]36 ? StepsOf<Next> | Final37 : []38
39/**40 * Reverses the input tuple41 */42export type Reverse<T extends Array<unknown>> = T extends [43 infer First,44 ...infer Rest,45]46 ? [...Reverse<Rest>, First]47 : []48
49/**50 * Remove first value from array51 */52export type Eat<T extends unknown[]> = T extends [infer _, ...infer R]53 ? R54 : never55
56/**57 * Remove last value from array58 */59export type EatLast<T extends unknown[]> = T extends [...infer R, infer _]60 ? R61 : never
Objects
1/**2 * Definition for a type guard that checks if a value is of a specific type3 */4export type Guard<TResult, TOther = unknown> = (5 value: TResult | TOther,6) => value is TResult7
8/**9 * Create a type where the provided keys are optional10 *11 * @param T the base type12 * @param O the keys to make optional13 */14export type WithOptional<T extends {}, O extends keyof T> = Omit<T, O> &15 Partial<Pick<T, O>>16
17/**18 * Create a type where the provided keys are required19 *20 * @param T the base type21 * @param R the keys to make required22 */23export type WithRequired<T extends {}, R extends keyof T> = T &24 Required<Pick<T, R>>25
26/**27 * Create a type where all all properties and sub-properties are recursively partial unless they are28 * of the type specified in TKeep29 *30 * @param T the base type31 * @param TKeep types to not make partial32 */33export type DeepPartial<T, TKeep = never> = T extends TKeep34 ? T35 : T extends object36 ? {37 [P in keyof T]?: DeepPartial<T[P], TKeep>38 }39 : T40
41/**42 * Returns a specific subset of `keyof T`43 *44 * The resulting types can be used with utilities like `Omit` or `Pick` in a reusable manner45 *46 * @param T the base type47 * @param K keys of T48 */49export type Keys<T, K extends keyof T> = K50
51export type Primitive = string | number | boolean | Symbol | Date52
53/**54 * Create a type where all direct properties are optional if they're Primitive otherwise it will be55 * a partial of the property56 *57 * @param T the base type58 * @param TKeep the types to not partialize59 */60export type FlatPartial<T, TKeep = Primitive> = {61 [K in keyof T]: T[K] extends TKeep ? T[K] | undefined : Partial<T[K]>62}63
64/**65 * Gets all keys of an object where the property at that key in the object extends a condition66 *67 * @param Cond the condition that properties need to extend68 * @param T the object with the keys of interest69 */70type ConditionalKeys<Cond, T> = {71 [K in keyof T]: T[K] extends Cond ? K : never72}[keyof T]73
74/**75 * Get the primitive keys of a given object76 */77type PrimitiveKeys<T> = ConditionalKeys<Primitive, T>78
79/**80 * Pick the keys of an object where the property value is a primitive81 */82type PickPrimitive<T> = Pick<T, PrimitiveKeys<T>>83
84/**85 * Pick the keys of an object where the property value is not primitive86 */87type ObjectKeys<T> = Exclude<keyof T, PrimitiveKeys<T>>88
89/**90 * Join two keys using a separator91 */92type JoinKeys<93 TSep extends string,94 P extends string,95 K extends string,96> = `${P}${TSep}${K}`97
98/**99 * Create an object with all keys prepended with some prefix100 *101 * @param T the type with the keys of interest102 * @param P the prefix to prepend keys with103 * @param Sep the separator to use for nesting keys104 */105type ExpandPathsRec<T, P extends string, Sep extends string> = {106 [K in keyof T & string]: T[K] extends Primitive107 ? {108 [key in JoinKeys<Sep, P, K>]: T[K]109 }110 : ExpandPathsRec<T[K], JoinKeys<Sep, P, K>, Sep>111}[keyof T & string]112
113/**114 * Create the resultant nested object using the given keys of the input object115 *116 * @param T the object to un-nest117 * @param P the keys to keep when un-nesting118 */119type ExpandPaths<T, P extends keyof T, Sep extends string> = P extends string120 ? ExpandPathsRec<T[P], P, Sep>121 : {}122
123/**124 * Bring all object properties to the top-level recursively125 *126 * @param T the object to expand127 * @param Sep the separator to use when expanding128 */129type Pathify<T, Sep extends string> = PickPrimitive<T> &130 ExpandPaths<T, ObjectKeys<T>, Sep>131
132/**133 * Get object with only keys that are an array134 */135export type PickArrays<T> = {136 [K in keyof T as T[K] extends Array<any> ? K : never]: T[K]137}138
139/**140 * Get the keys of the object `Obj` that start with the given string `P`141 *142 * @param P the string that a key should be prefixed with143 * @param Obj the object from which to take the matching keys144 */145export type KeysStartingWith<P extends string, Obj> = keyof Obj &146 `${P}${string}`147
148/**149 * Get the keys of the object `Obj` that end with the given string `S`150 *151 * @param S the string that a key should be suffixed with152 * @param Obj the object from which to take the matching keys153 */154export type KeysEndingWith<S extends string, Obj> = keyof Obj & `${string}${S}`155
156/**157 * Get a type or else a fallback if it is `never`158 *159 * @param T the base type160 * @param F the fallback to use if `T extends never`161 */162export type OrElse<T, F> = T extends never ? F : T163
164/**165 * Check if two types are exactly equal166 *167 * This is a direct copy from type-challenges:168 * https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts169 */170export type Equal<X, Y> =171 (<T>() => T extends X ? 1 : 2) extends172 (<T>() => T extends Y ? 1 : 2) ? true : false
Strings
1import { NonEmptyArray, Reverse } from './arrays'2
3/**4 * Types that can be cleanly/predictably converted into a string5 */6export type Stringable = Exclude<string | number, ''>7
8/**9 * String type that will show suggestions but accept any string10 *11 * @param TSugg the strings that you want to appear as IDE suggestions12 */13export type SuggestedStrings<TSugg> = TSugg | (string & {})14
15/**16 * Joins stringable members into a single, typed, string17 */18export type Join<TSep extends string, T extends Array<Stringable>> = T extends [19 infer El,20 ...infer Rest,21]22 ? El extends Stringable23 ? Rest extends NonEmptyArray<Stringable>24 ? `${El}${TSep}${Join<TSep, Rest>}`25 : El26 : ''27 : ''28
29/**30 * Split a strongly typed string into its parts31 */32export type Split<33 TSep extends string,34 TStr extends string,35 TBase extends string[] = [],36> = TStr extends `${infer El}${TSep}${infer Rest}`37 ? [...TBase, El, ...Split<TSep, Rest>]38 : [TStr]39
40/**41 * Get the chars of a strongly typed string as an array42 */43export type Chars<44 TStr extends string,45 TBase extends string[] = [],46> = TStr extends `${infer El}${''}${infer Rest}`47 ? Rest extends ''48 ? [TStr]49 : [...TBase, El, ...Chars<Rest>]50 : never51
52/**53 * Get the length of a string54 */55export type Length<TStr extends string> = Chars<TStr>['length']56
57/**58 * Get the first character of a string59 */60export type First<T extends string> = T extends `${infer F}${string}`61 ? F62 : never63
64/**65 * Get the last character of a string66 */67export type Last<TStr extends string> = Reverse<Chars<TStr>>[0]68
69/**70 * Get a string with the first character omitted71 */72export type Eat<T extends string> = T extends `${string}${infer E}` ? E : never
JSON Schema
Type for inferring a simple JSON Schema into a TypeScript type
1/**2 * Relevant schema structure for inferring type definition3 */4interface PartialJSONSchema<T extends Properties = Properties> {5 type: 'object'6 properties: T7 required?: Array<keyof T>8}9
10type TypeMap = {11 string: string12 boolean: boolean13}14
15type Type = TypeMap[keyof TypeMap]16
17type Properties = Record<string, { type: keyof TypeMap; default?: unknown }>18
19/**20 * Create a type where the provided keys are required21 *22 * @param T the base type23 * @param R the keys to make required24 */25type WithRequired<T, R extends keyof T> = T & Required<Pick<T, R>>26
27/**28 * Keys that have been defaulted by the29 */30type DefaultedKeys<T extends Properties> = {31 [K in keyof T]: T[K]['default'] extends Type ? K : never32}[keyof T]33
34/**35 * A field is required if its default value is in the list of required keys36 */37type Structure<T extends PartialJSONSchema> = WithRequired<38 {39 [K in keyof T['properties']]?: TypeMap[T['properties'][K]['type']]40 },41 DefaultedKeys<T['properties']>42>43
44/**45 * Extracts inner resultant type to make IDE inferrence/display better46 */47type Simplify<T> = {48 [KeyType in keyof T]: T[KeyType]49}50
51/**52 * Get the typescript type inferred by a JSON Schema53 */54export type FromJSONSchema<T extends PartialJSONSchema> = Simplify<55 T['required'] extends Array<keyof T['properties']>56 ? WithRequired<Structure<T>, T['required'][number]>57 : Structure<T>58>