Typescript Utilities

12 December 2022

Updated: 24 March 2024

Here’s a collection of some typescript utility functions I’ve put together

Functions

1
/**
2
* A function with defined input and output
3
*/
4
export type Fn<TParams, TResult> = (params: TParams) => TResult
5
6
/**
7
* A function that returns a result or undefined
8
*/
9
export type OptionFn<TParams, TResult> = Fn<TParams, TResult | undefined>
10
11
/**
12
* A function that returns void
13
*/
14
export type VoidFn<TParams> = (params: TParams) => void
15
16
/**
17
* Convert a function type into an async version of that function
18
*/
19
export type Async<TFn extends (...args: any[]) => any> = (
20
...args: Parameters<TFn>
21
) => Promise<ReturnType<TFn>>
22
23
/**
24
* Create an async version of `Fn`
25
*/
26
export type AsyncFn<TParams, TResult> = Async<Fn<TParams, TResult>>
27
28
/**
29
* Create an async version of `OptionFn`
30
*/
31
export type AsyncOptionFn<TParams, TResult> = Async<OptionFn<TParams, TResult>>
32
33
/**
34
* Create an async version of `VoidFn`
35
*/
36
export type AsyncVoidFn<TParams> = Async<VoidFn<TParams>>
37
38
/**
39
* Create a version of a function that may either be sync or async
40
*/
41
export type SyncOrAsync<TFn extends (...args: any[]) => any> = (
42
...args: Parameters<TFn>
43
) => ReturnType<TFn> | Promise<ReturnType<TFn>>
44
45
/**
46
* Create a version of `Fn` that may either be sync or async
47
*/
48
export type SyncOrAsyncFn<TParams, TResult> = SyncOrAsync<Fn<TParams, TResult>>
49
50
/**
51
* Create a version of `OptionFn` that may either be sync or async
52
*/
53
export type SyncOrAsyncOptionFn<TParams, TResult> = SyncOrAsync<
54
OptionFn<TParams, TResult>
55
>
56
57
/**
58
* Create a version of `VoidFn` that may either be sync or async
59
*/
60
export type SyncOrAsyncVoidFn<TParams> = SyncOrAsync<VoidFn<TParams>>

Arrays

1
/**
2
* Array filter type, used to filter an array via the `.filter` method
3
*/
4
export type ArrayFilter<T> = (value: T, index: number, array: T[]) => boolean
5
6
/**
7
* Array map type, used to map an array via the `.map` method
8
*/
9
export type ArrayMap<T, U> = (value: T, index: number, array: T[]) => U
10
11
/**
12
* Array reduce type, used to reduce an array via the `.reduce` method
13
*/
14
export type ArrayReducer<T, U> = (
15
previousValue: U,
16
currentValue: T,
17
currentIndex: number,
18
array: T[]
19
) => U
20
21
/**
22
* Defines a step type that may contain different data types as an array such
23
* that each step adds or removes from the current input but should always
24
* result in the same final object that can be validated by just verifying the
25
* final length assuming all steps have been validated prior
26
*/
27
export type StepsOf<Final extends Readonly<any[]>> = Final extends [
28
...infer Next,
29
infer _,
30
]
31
? StepsOf<Next> | Final
32
: [];

Objects

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
}

Strings

1
/**
2
* Types that can be cleanly/predictably converted into a string
3
*/
4
type Stringable = Exclude<string | number, ''>
5
6
type NonEmptyArray<T> = [T, ...T[]]
7
8
/**
9
* Joins stringable members into a single, typed, string
10
*/
11
export type Join<TSep extends string, T extends Array<Stringable>> = T extends [
12
infer El,
13
...infer Rest
14
]
15
? El extends Stringable
16
? Rest extends NonEmptyArray<Stringable>
17
? `${El}${TSep}${Join<TSep, Rest>}`
18
: El
19
: ''
20
: ''
21
22
/**
23
* Split a strongly typed string into its parts
24
*/
25
export type Split<
26
TSep extends string,
27
TStr extends string,
28
TBase extends string[] = []
29
> = TStr extends `${infer El}${TSep}${infer Rest}`
30
? [...TBase, El, ...Split<TSep, Rest>]
31
: [TStr]