Typescript Utilities

12 December 2022

Updated: 14 July 2024

Here’s a collection of some utility types I’ve put together:

Functions

ts-utils/functions.ts
1
import { Reverse } from './arrays'
2
3
/**
4
* A function with defined input and output
5
*/
6
export type Fn<TParams, TResult> = (params: TParams) => TResult
7
8
/**
9
* A function that returns a result or undefined
10
*/
11
export type OptionFn<TParams, TResult> = Fn<TParams, TResult | undefined>
12
13
/**
14
* A function that returns void
15
*/
16
export type VoidFn<TParams> = (params: TParams) => void
17
18
/**
19
* Convert a function type into an async version of that function
20
*/
21
export 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
*/
28
export type AsyncFn<TParams, TResult> = Async<Fn<TParams, TResult>>
29
30
/**
31
* Create an async version of `OptionFn`
32
*/
33
export type AsyncOptionFn<TParams, TResult> = Async<OptionFn<TParams, TResult>>
34
35
/**
36
* Create an async version of `VoidFn`
37
*/
38
export type AsyncVoidFn<TParams> = Async<VoidFn<TParams>>
39
40
/**
41
* Create a version of a function that may either be sync or async
42
*/
43
export 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 async
49
*/
50
export type SyncOrAsyncFn<TParams, TResult> = SyncOrAsync<Fn<TParams, TResult>>
51
52
/**
53
* Create a version of `OptionFn` that may either be sync or async
54
*/
55
export type SyncOrAsyncOptionFn<TParams, TResult> = SyncOrAsync<
56
OptionFn<TParams, TResult>
57
>
58
59
/**
60
* Create a version of `VoidFn` that may either be sync or async
61
*/
62
export type SyncOrAsyncVoidFn<TParams> = SyncOrAsync<VoidFn<TParams>>
63
64
/**
65
* Returns a function with the arguments reversed
66
*/
67
export type ReverseArguments<T extends (...args: any[]) => any> = (
68
...args: Reverse<Parameters<T>>
69
) => ReturnType<T>

Arrays

ts-utils/arrays.ts
1
/**
2
* An array with at least 1 item in it
3
*/
4
export type NonEmptyArray<T> = [T, ...T[]]
5
6
/**
7
* Array filter type, used to filter an array via the `.filter` method
8
*/
9
export type ArrayFilter<T> = (value: T, index: number, array: T[]) => boolean
10
11
/**
12
* Array map type, used to map an array via the `.map` method
13
*/
14
export type ArrayMap<T, U> = (value: T, index: number, array: T[]) => U
15
16
/**
17
* Array reduce type, used to reduce an array via the `.reduce` method
18
*/
19
export type ArrayReducer<T, U> = (
20
previousValue: U,
21
currentValue: T,
22
currentIndex: number,
23
array: T[],
24
) => U
25
26
/**
27
* Defines a step type that may contain different data types as an array such
28
* that each step adds or removes from the current input but should always
29
* result in the same final object that can be validated by just verifying the
30
* final length assuming all steps have been validated prior
31
*/
32
export type StepsOf<Final extends Readonly<any[]>> = Final extends [
33
...infer Next,
34
infer _,
35
]
36
? StepsOf<Next> | Final
37
: []
38
39
/**
40
* Reverses the input tuple
41
*/
42
export 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 array
51
*/
52
export type Eat<T extends unknown[]> = T extends [infer _, ...infer R]
53
? R
54
: never
55
56
/**
57
* Remove last value from array
58
*/
59
export type EatLast<T extends unknown[]> = T extends [...infer R, infer _]
60
? R
61
: never

Objects

ts-utils/objects.ts
1
/**
2
* Definition for a type guard that checks if a value is of a specific type
3
*/
4
export type Guard<TResult, TOther = unknown> = (
5
value: TResult | TOther,
6
) => value is TResult
7
8
/**
9
* Create a type where the provided keys are optional
10
*
11
* @param T the base type
12
* @param O the keys to make optional
13
*/
14
export 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 required
19
*
20
* @param T the base type
21
* @param R the keys to make required
22
*/
23
export 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 are
28
* of the type specified in TKeep
29
*
30
* @param T the base type
31
* @param TKeep types to not make partial
32
*/
33
export type DeepPartial<T, TKeep = never> = T extends TKeep
34
? T
35
: T extends object
36
? {
37
[P in keyof T]?: DeepPartial<T[P], TKeep>
38
}
39
: T
40
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 manner
45
*
46
* @param T the base type
47
* @param K keys of T
48
*/
49
export type Keys<T, K extends keyof T> = K
50
51
export type Primitive = string | number | boolean | Symbol | Date
52
53
/**
54
* Create a type where all direct properties are optional if they're Primitive otherwise it will be
55
* a partial of the property
56
*
57
* @param T the base type
58
* @param TKeep the types to not partialize
59
*/
60
export 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 condition
66
*
67
* @param Cond the condition that properties need to extend
68
* @param T the object with the keys of interest
69
*/
70
type ConditionalKeys<Cond, T> = {
71
[K in keyof T]: T[K] extends Cond ? K : never
72
}[keyof T]
73
74
/**
75
* Get the primitive keys of a given object
76
*/
77
type PrimitiveKeys<T> = ConditionalKeys<Primitive, T>
78
79
/**
80
* Pick the keys of an object where the property value is a primitive
81
*/
82
type PickPrimitive<T> = Pick<T, PrimitiveKeys<T>>
83
84
/**
85
* Pick the keys of an object where the property value is not primitive
86
*/
87
type ObjectKeys<T> = Exclude<keyof T, PrimitiveKeys<T>>
88
89
/**
90
* Join two keys using a separator
91
*/
92
type 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 prefix
100
*
101
* @param T the type with the keys of interest
102
* @param P the prefix to prepend keys with
103
* @param Sep the separator to use for nesting keys
104
*/
105
type ExpandPathsRec<T, P extends string, Sep extends string> = {
106
[K in keyof T & string]: T[K] extends Primitive
107
? {
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 object
115
*
116
* @param T the object to un-nest
117
* @param P the keys to keep when un-nesting
118
*/
119
type ExpandPaths<T, P extends keyof T, Sep extends string> = P extends string
120
? ExpandPathsRec<T[P], P, Sep>
121
: {}
122
123
/**
124
* Bring all object properties to the top-level recursively
125
*
126
* @param T the object to expand
127
* @param Sep the separator to use when expanding
128
*/
129
type Pathify<T, Sep extends string> = PickPrimitive<T> &
130
ExpandPaths<T, ObjectKeys<T>, Sep>
131
132
/**
133
* Get object with only keys that are an array
134
*/
135
export 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 with
143
* @param Obj the object from which to take the matching keys
144
*/
145
export 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 with
152
* @param Obj the object from which to take the matching keys
153
*/
154
export 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 type
160
* @param F the fallback to use if `T extends never`
161
*/
162
export type OrElse<T, F> = T extends never ? F : T

Strings

ts-utils/strings.ts
1
import { NonEmptyArray, Reverse } from './arrays'
2
3
/**
4
* Types that can be cleanly/predictably converted into a string
5
*/
6
export type Stringable = Exclude<string | number, ''>
7
8
/**
9
* String type that will show suggestions but accept any string
10
*
11
* @param TSugg the strings that you want to appear as IDE suggestions
12
*/
13
export type SuggestedStrings<TSugg> = TSugg | (string & {})
14
15
/**
16
* Joins stringable members into a single, typed, string
17
*/
18
export type Join<TSep extends string, T extends Array<Stringable>> = T extends [
19
infer El,
20
...infer Rest,
21
]
22
? El extends Stringable
23
? Rest extends NonEmptyArray<Stringable>
24
? `${El}${TSep}${Join<TSep, Rest>}`
25
: El
26
: ''
27
: ''
28
29
/**
30
* Split a strongly typed string into its parts
31
*/
32
export 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 array
42
*/
43
export 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
: never
51
52
/**
53
* Get the length of a string
54
*/
55
export type Length<TStr extends string> = Chars<TStr>['length']
56
57
/**
58
* Get the first character of a string
59
*/
60
export type First<T extends string> = T extends `${infer F}${string}`
61
? F
62
: never
63
64
/**
65
* Get the last character of a string
66
*/
67
export type Last<TStr extends string> = Reverse<Chars<TStr>>[0]
68
69
/**
70
* Get a string with the first character omitted
71
*/
72
export 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

ts-utils/json-schema.ts
1
/**
2
* Relevant schema structure for inferring type definition
3
*/
4
interface PartialJSONSchema<T extends Properties = Properties> {
5
type: 'object'
6
properties: T
7
required?: Array<keyof T>
8
}
9
10
type TypeMap = {
11
string: string
12
boolean: boolean
13
}
14
15
type Type = TypeMap[keyof TypeMap]
16
17
type Properties = Record<string, { type: keyof TypeMap; default?: unknown }>
18
19
/**
20
* Create a type where the provided keys are required
21
*
22
* @param T the base type
23
* @param R the keys to make required
24
*/
25
type WithRequired<T, R extends keyof T> = T & Required<Pick<T, R>>
26
27
/**
28
* Keys that have been defaulted by the
29
*/
30
type DefaultedKeys<T extends Properties> = {
31
[K in keyof T]: T[K]['default'] extends Type ? K : never
32
}[keyof T]
33
34
/**
35
* A field is required if its default value is in the list of required keys
36
*/
37
type 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 better
46
*/
47
type Simplify<T> = {
48
[KeyType in keyof T]: T[KeyType]
49
}
50
51
/**
52
* Get the typescript type inferred by a JSON Schema
53
*/
54
export 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
>