UNPKG

11.8 kBTypeScriptView Raw
1import { IsAny, IsNever } from '../utils';
2/**
3 * Type alias to `string` which describes a lodash-like path through an object.
4 * E.g. `'foo.bar.0.baz'`
5 */
6export type PathString = string;
7/**
8 * Type which can be traversed through with a {@link PathString}.
9 * I.e. objects, arrays, and tuples
10 */
11export type Traversable = object;
12/**
13 * Type to query whether an array type T is a tuple type.
14 * @typeParam T - type which may be an array or tuple
15 * @example
16 * ```
17 * IsTuple<[number]> = true
18 * IsTuple<number[]> = false
19 * ```
20 */
21export type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;
22/**
23 * Type which can be used to index an array or tuple type.
24 */
25export type ArrayKey = number;
26/**
27 * Type which can be used to index an object.
28 */
29export type Key = string;
30/**
31 * Type to assert that a type is a {@link Key}.
32 * @typeParam T - type which may be a {@link Key}
33 */
34export type AsKey<T> = Extract<T, Key>;
35/**
36 * Type to convert a type to a {@link Key}.
37 * @typeParam T - type which may be converted to a {@link Key}
38 */
39export type ToKey<T> = T extends ArrayKey ? `${T}` : AsKey<T>;
40/**
41 * Type which describes a path through an object
42 * as a list of individual {@link Key}s.
43 */
44export type PathTuple = Key[];
45/**
46 * Type to assert that a type is a {@link PathTuple}.
47 * @typeParam T - type which may be a {@link PathTuple}
48 */
49export type AsPathTuple<T> = Extract<T, PathTuple>;
50/**
51 * Type to intersect a union type.
52 * See https://fettblog.eu/typescript-union-to-intersection/
53 * @typeParam U - union
54 * @example
55 * ```
56 * UnionToIntersection<{ foo: string } | { bar: number }>
57 * = { foo: string; bar: number }
58 * ```
59 */
60export type UnionToIntersection<U> = (U extends any ? (_: U) => any : never) extends (_: infer I) => any ? I : never;
61/**
62 * Type which appends a {@link Key} to the {@link PathTuple} only if it is not
63 * blank, i.e. not the empty string.
64 * @typeParam PT - path
65 * @typeParam K - key
66 * @example
67 * ```
68 * AppendNonBlankKey<['foo'], 'bar'> = ['foo', 'bar']
69 * AppendNonBlankKey<['foo'], ''> = ['foo']
70 * ```
71 */
72type AppendNonBlankKey<PT extends PathTuple, K extends Key> = K extends '' ? PT : [...PT, K];
73/**
74 * Type to implement {@link SplitPathString} tail recursively.
75 * @typeParam PS - remaining {@link PathString} which should be split into its
76 * individual {@link Key}s
77 * @typeParam PT - accumulator of the {@link Key}s which have been split from
78 * the original {@link PathString} already
79 */
80type SplitPathStringImpl<PS extends PathString, PT extends PathTuple> = PS extends `${infer K}.${infer R}` ? SplitPathStringImpl<R, AppendNonBlankKey<PT, K>> : AppendNonBlankKey<PT, PS>;
81/**
82 * Type to split a {@link PathString} into a {@link PathTuple}.
83 * The individual {@link Key}s may be empty strings.
84 * @typeParam PS - {@link PathString} which should be split into its
85 * individual {@link Key}s
86 * @example
87 * ```
88 * SplitPathString<'foo'> = ['foo']
89 * SplitPathString<'foo.bar.0.baz'> = ['foo', 'bar', '0', 'baz']
90 * SplitPathString<'.'> = []
91 * ```
92 */
93export type SplitPathString<PS extends PathString> = SplitPathStringImpl<PS, [
94]>;
95/**
96 * Type to implement {@link JoinPathTuple} tail-recursively.
97 * @typeParam PT - remaining {@link Key}s which needs to be joined
98 * @typeParam PS - accumulator of the already joined {@link Key}s
99 */
100type JoinPathTupleImpl<PT extends PathTuple, PS extends PathString> = PT extends [infer K, ...infer R] ? JoinPathTupleImpl<AsPathTuple<R>, `${PS}.${AsKey<K>}`> : PS;
101/**
102 * Type to join a {@link PathTuple} to a {@link PathString}.
103 * @typeParam PT - {@link PathTuple} which should be joined.
104 * @example
105 * ```
106 * JoinPathTuple<['foo']> = 'foo'
107 * JoinPathTuple<['foo', 'bar', '0', 'baz']> = 'foo.bar.0.baz'
108 * JoinPathTuple<[]> = never
109 * ```
110 */
111export type JoinPathTuple<PT extends PathTuple> = PT extends [
112 infer K,
113 ...infer R
114] ? JoinPathTupleImpl<AsPathTuple<R>, AsKey<K>> : never;
115/**
116 * Type which converts all keys of an object to {@link Key}s.
117 * @typeParam T - object type
118 * @example
119 * ```
120 * MapKeys<{0: string}> = {'0': string}
121 * ```
122 */
123type MapKeys<T> = {
124 [K in keyof T as ToKey<K>]: T[K];
125};
126/**
127 * Type to access a type by a key.
128 * - Returns undefined if it can't be indexed by that key.
129 * - Returns null if the type is null.
130 * - Returns undefined if the type is not traversable.
131 * @typeParam T - type which is indexed by the key
132 * @typeParam K - key into the type
133 * ```
134 * TryAccess<{foo: string}, 'foo'> = string
135 * TryAccess<{foo: string}, 'bar'> = undefined
136 * TryAccess<null, 'foo'> = null
137 * TryAccess<string, 'foo'> = undefined
138 * ```
139 */
140type TryAccess<T, K> = K extends keyof T ? T[K] : T extends null ? null : undefined;
141/**
142 * Type to access an array type by a key.
143 * Returns undefined if the key is non-numeric.
144 * @typeParam T - type which is indexed by the key
145 * @typeParam K - key into the type
146 * ```
147 * TryAccessArray<string[], '0'> = string
148 * TryAccessArray<string[], 'foo'> = undefined
149 * ```
150 */
151type TryAccessArray<T extends ReadonlyArray<any>, K extends Key> = K extends `${ArrayKey}` ? T[number] : TryAccess<T, K>;
152/**
153 * Type to evaluate the type which the given key points to.
154 * @typeParam T - type which is indexed by the key
155 * @typeParam K - key into the type
156 * @example
157 * ```
158 * EvaluateKey<{foo: string}, 'foo'> = string
159 * EvaluateKey<[number, string], '1'> = string
160 * EvaluateKey<string[], '1'> = string
161 * ```
162 */
163export type EvaluateKey<T, K extends Key> = T extends ReadonlyArray<any> ? IsTuple<T> extends true ? TryAccess<T, K> : TryAccessArray<T, K> : TryAccess<MapKeys<T>, K>;
164/**
165 * Type to evaluate the type which the given path points to.
166 * @typeParam T - deeply nested type which is indexed by the path
167 * @typeParam PT - path into the deeply nested type
168 * @example
169 * ```
170 * EvaluatePath<{foo: {bar: string}}, ['foo', 'bar']> = string
171 * EvaluatePath<[number, string], ['1']> = string
172 * EvaluatePath<number, []> = number
173 * EvaluatePath<number, ['foo']> = undefined
174 * ```
175 */
176export type EvaluatePath<T, PT extends PathTuple> = PT extends [
177 infer K,
178 ...infer R
179] ? EvaluatePath<EvaluateKey<T, AsKey<K>>, AsPathTuple<R>> : T;
180/**
181 * Type which given a tuple type returns its own keys, i.e. only its indices.
182 * @typeParam T - tuple type
183 * @example
184 * ```
185 * TupleKeys<[number, string]> = '0' | '1'
186 * ```
187 */
188export type TupleKeys<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;
189/**
190 * Type which extracts all numeric keys from an object.
191 * @typeParam T - type
192 * @example
193 * ```
194 * NumericObjectKeys<{0: string, '1': string, foo: string}> = '0' | '1'
195 * ```
196 */
197type NumericObjectKeys<T extends Traversable> = ToKey<Extract<keyof T, ArrayKey | `${ArrayKey}`>>;
198/**
199 * Type which extracts all numeric keys from an object, tuple, or array.
200 * If a union is passed, it evaluates to the overlapping numeric keys.
201 * @typeParam T - type
202 * @example
203 * ```
204 * NumericKeys<{0: string, '1': string, foo: string}> = '0' | '1'
205 * NumericKeys<number[]> = `${number}`
206 * NumericKeys<[string, number]> = '0' | '1'
207 * NumericKeys<{0: string, '1': string} | [number] | number[]> = '0'
208 * ```
209 */
210export type NumericKeys<T extends Traversable> = UnionToIntersection<T extends ReadonlyArray<any> ? IsTuple<T> extends true ? [TupleKeys<T>] : [ToKey<ArrayKey>] : [NumericObjectKeys<T>]>[never];
211/**
212 * Type which extracts all keys from an object.
213 * If a union is passed, it evaluates to the overlapping keys.
214 * @typeParam T - object type
215 * @example
216 * ```
217 * ObjectKeys<{foo: string, bar: string}, string> = 'foo' | 'bar'
218 * ObjectKeys<{foo: string, bar: number}, string> = 'foo'
219 * ```
220 */
221export type ObjectKeys<T extends Traversable> = Exclude<ToKey<keyof T>, `${string}.${string}` | ''>;
222/**
223 * Type to check whether a type's property matches the constraint type
224 * and return its key. Converts the key to a {@link Key}.
225 * @typeParam T - type whose property should be checked
226 * @typeParam K - key of the property
227 * @typeParam U - constraint type
228 * @example
229 * ```
230 * CheckKeyConstraint<{foo: string}, 'foo', string> = 'foo'
231 * CheckKeyConstraint<{foo: string}, 'foo', number> = never
232 * CheckKeyConstraint<string[], number, string> = `${number}`
233 * ```
234 */
235export type CheckKeyConstraint<T, K extends Key, U> = K extends any ? EvaluateKey<T, K> extends U ? K : never : never;
236/**
237 * Type which evaluates to true when the type is an array or tuple or is a union
238 * which contains an array or tuple.
239 * @typeParam T - type
240 * @example
241 * ```
242 * ContainsIndexable<{foo: string}> = false
243 * ContainsIndexable<{foo: string} | number[]> = true
244 * ```
245 */
246export type ContainsIndexable<T> = IsNever<Extract<T, ReadonlyArray<any>>> extends true ? false : true;
247/**
248 * Type to implement {@link Keys} for non-nullable values.
249 * @typeParam T - non-nullable type whose property should be checked
250 */
251type KeysImpl<T> = [T] extends [Traversable] ? ContainsIndexable<T> extends true ? NumericKeys<T> : ObjectKeys<T> : never;
252/**
253 * Type to find all properties of a type that match the constraint type
254 * and return their keys.
255 * If a union is passed, it evaluates to the overlapping keys.
256 * @typeParam T - type whose property should be checked
257 * @typeParam U - constraint type
258 * @example
259 * ```
260 * Keys<{foo: string, bar: string}, string> = 'foo' | 'bar'
261 * Keys<{foo?: string, bar?: string}> = 'foo' | 'bar'
262 * Keys<{foo: string, bar: number}, string> = 'foo'
263 * Keys<[string, number], string> = '0'
264 * Keys<string[], string> = `${number}`
265 * Keys<{0: string, '1': string} | [number] | number[]> = '0'
266 * ```
267 */
268export type Keys<T, U = unknown> = IsAny<T> extends true ? Key : IsNever<T> extends true ? Key : IsNever<NonNullable<T>> extends true ? never : CheckKeyConstraint<T, KeysImpl<NonNullable<T>>, U>;
269/**
270 * Type to check whether a {@link Key} is present in a type.
271 * If a union of {@link Key}s is passed, all {@link Key}s have to be present
272 * in the type.
273 * @typeParam T - type which is introspected
274 * @typeParam K - key
275 * @example
276 * ```
277 * HasKey<{foo: string}, 'foo'> = true
278 * HasKey<{foo: string}, 'bar'> = false
279 * HasKey<{foo: string}, 'foo' | 'bar'> = false
280 * ```
281 */
282export type HasKey<T, K extends Key> = IsNever<Exclude<K, Keys<T>>>;
283/**
284 * Type to implement {@link ValidPathPrefix} tail recursively.
285 * @typeParam T - type which the path should be checked against
286 * @typeParam PT - path which should exist within the given type
287 * @typeParam VPT - accumulates the prefix of {@link Key}s which have been
288 * confirmed to exist already
289 */
290type ValidPathPrefixImpl<T, PT extends PathTuple, VPT extends PathTuple> = PT extends [infer K, ...infer R] ? HasKey<T, AsKey<K>> extends true ? ValidPathPrefixImpl<EvaluateKey<T, AsKey<K>>, AsPathTuple<R>, AsPathTuple<[...VPT, K]>> : VPT : VPT;
291/**
292 * Type to find the longest path prefix which is still valid,
293 * i.e. exists within the given type.
294 * @typeParam T - type which the path should be checked against
295 * @typeParam PT - path which should exist within the given type
296 * @example
297 * ```
298 * ValidPathPrefix<{foo: {bar: string}}, ['foo', 'bar']> = ['foo', 'bar']
299 * ValidPathPrefix<{foo: {bar: string}}, ['foo', 'ba']> = ['foo']
300 * ```
301 */
302export type ValidPathPrefix<T, PT extends PathTuple> = ValidPathPrefixImpl<T, PT, [
303]>;
304/**
305 * Type to check whether a path through a type exists.
306 * @typeParam T - type which the path should be checked against
307 * @typeParam PT - path which should exist within the given type
308 * @example
309 * ```
310 * HasPath<{foo: {bar: string}}, ['foo', 'bar']> = true
311 * HasPath<{foo: {bar: string}}, ['foo', 'ba']> = false
312 * ```
313 */
314export type HasPath<T, PT extends PathTuple> = ValidPathPrefix<T, PT> extends PT ? true : false;
315export {};
316//# sourceMappingURL=common.d.ts.map
\No newline at end of file