-
-
Notifications
You must be signed in to change notification settings - Fork 45
/
utils.types.ts
396 lines (357 loc) · 10 KB
/
utils.types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
import { z, ZodType } from "zod";
/**
* filter an array type by a predicate value
* @param T - array type
* @param C - predicate object to match
* @details - this is using tail recursion type optimization from typescript 4.5
*/
export type FilterArrayByValue<
T extends unknown[] | undefined,
C,
Acc extends unknown[] = []
> = T extends [infer Head, ...infer Tail]
? Head extends C
? FilterArrayByValue<Tail, C, [...Acc, Head]>
: FilterArrayByValue<Tail, C, Acc>
: Acc;
/**
* filter an array type by key
* @param T - array type
* @param K - key to match
* @details - this is using tail recursion type optimization from typescript 4.5
*/
export type FilterArrayByKey<
T extends unknown[],
K extends string,
Acc extends unknown[] = []
> = T extends [infer Head, ...infer Tail]
? Head extends { [Key in K]: unknown }
? FilterArrayByKey<Tail, K, [...Acc, Head]>
: FilterArrayByKey<Tail, K, Acc>
: Acc;
/**
* filter an array type by removing undefined values
* @param T - array type
* @details - this is using tail recursion type optimization from typescript 4.5
*/
export type DefinedArray<
T extends unknown[],
Acc extends unknown[] = []
> = T extends [infer Head, ...infer Tail]
? Head extends undefined
? DefinedArray<Tail, Acc>
: DefinedArray<Tail, [Head, ...Acc]>
: Acc;
type Try<A, B, C> = A extends B ? A : C;
type NarrowRaw<T> =
| (T extends Function ? T : never)
| (T extends string | number | bigint | boolean ? T : never)
| (T extends [] ? [] : never)
| {
[K in keyof T]: K extends "description" ? T[K] : NarrowNotZod<T[K]>;
};
type NarrowNotZod<T> = Try<T, ZodType, NarrowRaw<T>>;
/**
* Utility to infer the embedded primitive type of any type
* Same as `as const` but without setting the object as readonly and without needing the user to use it
* @param T - type to infer the embedded type of
* @see - thank you tannerlinsley for this idea
*/
export type Narrow<T> = Try<T, [], NarrowNotZod<T>>;
/**
* merge all union types into a single type
* @param T - union type
*/
export type MergeUnion<T> = (
T extends unknown ? (k: T) => void : never
) extends (k: infer I) => void
? { [K in keyof I]: I[K] }
: never;
/**
* get all required properties from an object type
* @param T - object type
*/
export type RequiredProps<T> = Omit<
T,
{
[P in keyof T]-?: undefined extends T[P] ? P : never;
}[keyof T]
>;
/**
* get all optional properties from an object type
* @param T - object type
*/
export type OptionalProps<T> = Pick<
T,
{
[P in keyof T]-?: undefined extends T[P] ? P : never;
}[keyof T]
>;
/**
* get all properties from an object type that are not undefined or optional
* @param T - object type
* @returns - union type of all properties that are not undefined or optional
*/
export type RequiredKeys<T> = {
[P in keyof T]-?: undefined extends T[P] ? never : P;
}[keyof T];
/**
* Simplify a type by merging intersections if possible
* @param T - type to simplify
*/
export type Simplify<T> = T extends unknown ? { [K in keyof T]: T[K] } : T;
/**
* Merge two types into a single type
* @param T - first type
* @param U - second type
*/
export type Merge<T, U> = Simplify<T & U>;
/**
* transform possible undefined properties from a type into optional properties
* @param T - object type
*/
export type UndefinedToOptional<T> = Merge<
RequiredProps<T>,
Partial<OptionalProps<T>>
>;
/**
* remove all the never properties from a type object
* @param T - object type
*/
export type PickDefined<T> = Pick<
T,
{ [K in keyof T]: T[K] extends never ? never : K }[keyof T]
>;
/**
* check if two types are equal
*/
export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T
? 1
: 2) extends <G>() => G extends U ? 1 : 2
? Y
: N;
/**
* get never if empty type
* @param T - type
* @example
* ```ts
* type A = {};
* type B = NotEmpty<A>; // B = never
*/
export type NeverIfEmpty<T> = IfEquals<T, {}, never, T>;
/**
* get undefined if empty type
* @param T - type
* @example
* ```ts
* type A = {};
* type B = NotEmpty<A>; // B = never
*/
export type UndefinedIfEmpty<T> = IfEquals<T, {}, undefined, T>;
export type UndefinedIfNever<T> = IfEquals<T, never, undefined, T>;
type RequiredChildProps<T> = {
[K in keyof T]: IfEquals<T[K], OptionalProps<T[K]>, never, K>;
}[keyof T];
export type OptionalChildProps<T> = {
[K in keyof T]: IfEquals<T[K], OptionalProps<T[K]>, K, never>;
}[keyof T];
/**
* set properties to optional if their child properties are optional
* @param T - object type
*/
export type SetPropsOptionalIfChildrenAreOptional<T> = Merge<
Pick<Partial<T>, OptionalChildProps<T>>,
Pick<T, RequiredChildProps<T>>
>;
/**
* transform an array type into a readonly array type
* @param T - array type
*/
interface ReadonlyArrayDeep<T> extends ReadonlyArray<ReadonlyDeep<T>> {}
/**
* transform an object type into a readonly object type
* @param T - object type
*/
export type DeepReadonlyObject<T> = {
readonly [P in keyof T]: ReadonlyDeep<T[P]>;
};
/**
* transform a type into a readonly type
* @param T - type
*/
export type ReadonlyDeep<T> = T extends (infer R)[]
? ReadonlyArrayDeep<R>
: T extends Function
? T
: T extends object
? DeepReadonlyObject<T>
: T;
export type MaybeReadonly<T> = T | ReadonlyDeep<T>;
/**
* Map a type an api description parameter to a zod infer type
* @param T - array of api description parameters
* @details - this is using tail recursion type optimization from typescript 4.5
*/
export type MapSchemaParameters<
T,
Frontend extends boolean = true,
Acc = {}
> = T extends [infer Head, ...infer Tail]
? Head extends {
name: infer Name;
schema: infer Schema;
}
? Name extends string
? MapSchemaParameters<
Tail,
Frontend,
Merge<
{
[Key in Name]: Schema extends z.ZodType<any, any, any>
? Frontend extends true
? z.input<Schema>
: z.output<Schema>
: never;
},
Acc
>
>
: Acc
: Acc
: Acc;
/**
* Split string into a tuple, using a simple string literal separator
* @description - This is a simple implementation of split, it does not support multiple separators
* A more complete implementation is built on top of this one
* @param Str - String to split
* @param Sep - Separator, must be a string literal not a union of string literals
* @returns Tuple of strings
*/
export type Split<
Str,
Sep extends string,
Acc extends string[] = []
> = Str extends ""
? Acc
: Str extends `${infer T}${Sep}${infer U}`
? Split<U, Sep, [...Acc, T]>
: [...Acc, Str];
type ConcatSplits<
Parts extends string[],
Seps extends string[],
Acc extends string[] = []
> = Parts extends [infer First extends string, ...infer Rest extends string[]]
? ConcatSplits<Rest, Seps, [...Acc, ...SplitMany<First, Seps>]>
: Acc;
/**
* Split a string into a tuple.
* @param Str - The string to split.
* @param Sep - The separators to split on, a tuple of strings with one or more characters.
* @returns The tuple of each split. if sep is an empty string, returns a tuple of each character.
*/
export type SplitMany<
Str extends string,
Sep extends string[],
Acc extends string[] = []
> = Sep extends [
infer FirstSep extends string,
...infer RestSep extends string[]
]
? ConcatSplits<Split<Str, FirstSep>, RestSep>
: [Str, ...Acc];
type PathSeparator = ["/", "?", "&", "#", "=", "(", ")", "[", "]", "%"];
type FilterParams<Params, Acc extends string[] = []> = Params extends [
infer First,
...infer Rest
]
? First extends `${string}:${infer Param}`
? FilterParams<Rest, [...Acc, ...Split<Param, ":">]>
: FilterParams<Rest, Acc>
: Acc;
/**
* Extract Path Params from a path
* @param Path - Path to extract params from
* @returns Path params
* @example
* ```ts
* type Path = "/users/:id/posts/:postId"
* type PathParams = ApiPathToParams<Path>
* // output: ["id", "postId"]
* ```
*/
export type ApiPathToParams<Path extends string> = FilterParams<
SplitMany<Path, PathSeparator>
>;
/**
* get all parameters from an API path
* @param Path - API path
* @details - this is using tail recursion type optimization from typescript 4.5
*/
export type PathParamNames<Path> = Path extends string
? ApiPathToParams<Path>[number]
: never;
/**
* Check if two type are equal else generate a compiler error
* @param T - type to check
* @param U - type to check against
* @returns true if types are equal else a detailed compiler error
*/
export type Assert<T, U> = IfEquals<
T,
U,
true,
{ error: "Types are not equal"; type1: T; type2: U }
>;
export type PickRequired<T, K extends keyof T> = Merge<T, { [P in K]-?: T[P] }>;
/**
* Flatten a tuple type one level
* @param T - tuple type
* @returns flattened tuple type
*
* @example
* ```ts
* type T0 = TupleFlat<[1, 2, [3, 4], 5]>; // T0 = [1, 2, 3, 4, 5]
* ```
*/
export type TupleFlat<T, Acc extends unknown[] = []> = T extends [
infer Head,
...infer Tail
]
? Head extends unknown[]
? TupleFlat<Tail, [...Acc, ...Head]>
: TupleFlat<Tail, [...Acc, Head]>
: Acc;
/**
* trick to combine multiple unions of objects into a single object
* only works with objects not primitives
* @param union - Union of objects
* @returns Intersection of objects
*/
export type UnionToIntersection<union> = (
union extends any ? (k: union) => void : never
) extends (k: infer intersection) => void
? intersection
: never;
/**
* get last element of union
* @param Union - Union of any types
* @returns Last element of union
*/
type GetUnionLast<Union> = UnionToIntersection<
Union extends any ? () => Union : never
> extends () => infer Last
? Last
: never;
/**
* Convert union to tuple
* @param Union - Union of any types, can be union of complex, composed or primitive types
* @returns Tuple of each elements in the union
*/
export type UnionToTuple<Union, Tuple extends unknown[] = []> = [
Union
] extends [never]
? Tuple
: UnionToTuple<
Exclude<Union, GetUnionLast<Union>>,
[GetUnionLast<Union>, ...Tuple]
>;