forked from firebase/firebase-tools
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmetaprogramming.ts
118 lines (102 loc) · 3.97 KB
/
metaprogramming.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
// eslint-disable-next-line @typescript-eslint/ban-types
type Primitive = number | string | null | undefined | Date | Function;
/**
* Statically verify that one type implements another.
* This is very useful to say assertImplements<fieldMasks, RecursiveKeyOf<T>>();
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
export function assertImplements<Test extends MaybeBase, MaybeBase>(): void {}
/**
* RecursiveKeyOf is a type for keys of an objet usind dots for subfields.
* For a given object: {a: {b: {c: number}}, d } the RecursiveKeysOf are
* 'a' | 'a.b' | 'a.b.c' | 'd'
*/
export type RecursiveKeyOf<T> = T extends Primitive
? never
: T extends (infer Elem)[]
? RecursiveSubKeys<Elem, keyof Elem & string>
:
| (keyof T & string)
| {
[P in keyof Required<T> & string]: RecursiveSubKeys<Required<T>, P>;
}[keyof T & string];
type RecursiveSubKeys<T, P extends keyof T & string> = T[P] extends (infer Elem)[]
? `${P}.${RecursiveKeyOf<Elem>}`
: T[P] extends object
? `${P}.${RecursiveKeyOf<T[P]>}`
: never;
export type DeepExtract<RecursiveKeys extends string, Select extends string> = [
RecursiveKeys extends `${infer Head}.${infer Rest}`
? Head extends Select
? Head
: DeepExtract<TailsOf<RecursiveKeys, Head>, Rest>
: Extract<RecursiveKeys, Select>,
][number];
/**
* SameType is used in testing to verify that two types are the same.
* Usage:
* const test: SameType<A, B> = true.
* The assigment will fail if the types are different.
*/
export type SameType<T, V> = T extends V ? (V extends T ? true : false) : false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type HeadOf<T extends string> = [T extends `${infer Head}.${infer Tail}` ? Head : T][number];
type TailsOf<T extends string, Head extends string> = [
T extends `${Head}.${infer Tail}` ? Tail : never,
][number];
type RequiredFields<T> = {
[K in keyof T as (object extends Pick<T, K> ? never : K) & string]: T[K];
};
type OptionalFields<T> = {
[K in keyof T as (object extends Pick<T, K> ? K : never) & string]?: T[K];
};
/**
* DeepOmit allows you to omit fields from a nested structure using recursive keys.
*/
export type DeepOmit<T extends object, Keys extends RecursiveKeyOf<T>> = DeepOmitUnsafe<T, Keys>;
type DeepOmitUnsafe<T, Keys extends string> = T extends (infer Elem)[]
? Array<DeepOmitUnsafe<Elem, Keys>>
: {
[Key in Exclude<keyof RequiredFields<T>, Keys>]: Key extends HeadOf<Keys>
? DeepOmitUnsafe<T[Key], TailsOf<Keys, Key>>
: T[Key];
} & {
[Key in Exclude<keyof OptionalFields<T>, Keys>]?: Key extends HeadOf<Keys>
? DeepOmitUnsafe<T[Key], TailsOf<Keys, Key>>
: T[Key];
};
export type DeepPick<T extends object, Keys extends RecursiveKeyOf<T>> = DeepPickUnsafe<T, Keys>;
type DeepPickUnsafe<T, Keys extends string> = T extends (infer Elem)[]
? Array<DeepOmitUnsafe<Elem, Keys>>
: {
[Key in Extract<keyof RequiredFields<T>, HeadOf<Keys>>]: Key extends Keys
? T[Key]
: DeepPickUnsafe<T[Key], TailsOf<Keys, Key>>;
} & {
[Key in Extract<keyof OptionalFields<T>, HeadOf<Keys>>]?: Key extends Keys
? T[Key]
: DeepPickUnsafe<T[Key], TailsOf<Keys, Key>>;
};
/**
* Make properties of an object required.
*
* type Foo = {
* a?: string
* b?: number
* c?: object
* }
*
* type Bar = RequireKeys<Foo, "a" | "b">
* // Property "a" and "b" are now required.
*/
export type RequireKeys<T extends object, Keys extends keyof T> = T & Required<Pick<T, Keys>>;
/** In the array LeafElems<[[["a"], "b"], ["c"]]> is "a" | "b" | "c" */
export type LeafElems<T> =
T extends Array<infer Elem> ? (Elem extends unknown[] ? LeafElems<Elem> : Elem) : T;
/**
* In the object {a: number, b: { c: string } },
* LeafValues is number | string
*/
export type LeafValues<T extends object> = {
[Key in keyof T & string]: T[Key] extends object ? LeafValues<T[Key]> : T[Key];
}[keyof T & string];