1 | import { 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 | */
|
6 | export type PathString = string;
|
7 | /**
|
8 | * Type which can be traversed through with a {@link PathString}.
|
9 | * I.e. objects, arrays, and tuples
|
10 | */
|
11 | export 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 | */
|
21 | export 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 | */
|
25 | export type ArrayKey = number;
|
26 | /**
|
27 | * Type which can be used to index an object.
|
28 | */
|
29 | export 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 | */
|
34 | export 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 | */
|
39 | export 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 | */
|
44 | export 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 | */
|
49 | export 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 | */
|
60 | export 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 | */
|
72 | type 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 | */
|
80 | type 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 | */
|
93 | export 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 | */
|
100 | type 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 | */
|
111 | export 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 | */
|
123 | type 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 | */
|
140 | type 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 | */
|
151 | type 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 | */
|
163 | export 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 | */
|
176 | export 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 | */
|
188 | export 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 | */
|
197 | type 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 | */
|
210 | export 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}> = 'foo' | 'bar'
|
218 | * ObjectKeys<{foo: string, bar: number} | { foo: string }> = 'foo'
|
219 | * ```
|
220 | */
|
221 | export 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 | */
|
235 | export 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 | */
|
246 | export 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 | */
|
251 | type 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 | */
|
268 | export 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 | */
|
282 | export 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 | */
|
290 | type 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 | */
|
302 | export 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 | */
|
314 | export type HasPath<T, PT extends PathTuple> = ValidPathPrefix<T, PT> extends PT ? true : false;
|
315 | export {};
|
316 | //# sourceMappingURL=common.d.ts.map |
\ | No newline at end of file |