1 | import type {
|
2 | UnknownFunction,
|
3 | Expand,
|
4 | TuplifyUnion,
|
5 | Has,
|
6 | List,
|
7 | IsTuple
|
8 | } from '../types'
|
9 |
|
10 | /** Given a set of input selectors, extracts the intersected parameters to determine
|
11 | * what values can actually be passed to all of the input selectors at once
|
12 | * WARNING: "you are not expected to understand this" :)
|
13 | */
|
14 | export type MergeParameters<
|
15 | // The actual array of input selectors
|
16 | T extends readonly UnknownFunction[],
|
17 | // Given those selectors, we do several transformations on the types in sequence:
|
18 | // 1) Extract "the type of parameters" for each input selector, so that we now have
|
19 | // a tuple of all those parameters
|
20 | ParamsArrays extends readonly any[][] = ExtractParams<T>,
|
21 | // 2) Transpose the parameter tuples.
|
22 | // Originally, we have nested arrays with "all params from input", "from input 2", etc:
|
23 | // `[ [i1a, i1b, i1c], [i2a, i2b, i2c], [i3a, i3b, i3c] ],
|
24 | // In order to intersect the params at each index, we need to transpose them so that
|
25 | // we have "all the first args", "all the second args", and so on:
|
26 | // `[ [i1a, i2a, i3a], [i1b, i2b, i3b], [i1c, i2c, i3c] ]
|
27 | // Unfortunately, this step also turns the arrays into a union, and weirder, it is
|
28 | // a union of all possible combinations for all input functions, so there's duplicates.
|
29 | TransposedArrays = Transpose<ParamsArrays>,
|
30 | // 3) Turn the union of arrays back into a nested tuple. Order does not matter here.
|
31 | TuplifiedArrays extends any[] = TuplifyUnion<TransposedArrays>,
|
32 | // 4) Find the longest params array out of the ones we have.
|
33 | // Note that this is actually the _nested_ data we wanted out of the transpose step,
|
34 | // so it has all the right pieces we need.
|
35 | LongestParamsArray extends readonly any[] = LongestArray<TuplifiedArrays>
|
36 | > =
|
37 | // After all that preparation work, we can actually do parameter extraction.
|
38 | // These steps work somewhat inside out (jump ahead to the middle):
|
39 | // 11) Finally, after all that, run a shallow expansion on the values to make the user-visible
|
40 | // field details more readable when viewing the selector's type in a hover box.
|
41 | ExpandItems<
|
42 | // 10) Tuples can have field names attached, and it seems to work better to remove those
|
43 | RemoveNames<{
|
44 | // 5) We know the longest params array has N args. Loop over the indices of that array.
|
45 | // 6) For each index, do a check to ensure that we're _only_ checking numeric indices,
|
46 | // not any field names for array functions like `slice()`
|
47 | [index in keyof LongestParamsArray]: LongestParamsArray[index] extends LongestParamsArray[number]
|
48 | ? // 9) Any object types that were intersected may have had
|
49 | IgnoreInvalidIntersections<
|
50 | // 8) Then, intersect all of the parameters for this arg together.
|
51 | IntersectAll<
|
52 | // 7) Since this is a _nested_ array, extract the right sub-array for this index
|
53 | LongestParamsArray[index]
|
54 | >
|
55 | >
|
56 | : never
|
57 | }>
|
58 | >
|
59 |
|
60 | /*
|
61 | *
|
62 | * Reselect Internal Utility Types
|
63 | *
|
64 | */
|
65 |
|
66 | /*
|
67 | *
|
68 | * Reselect Internal Utility Types
|
69 | *
|
70 | */
|
71 |
|
72 | /** An object with no fields */
|
73 | type EmptyObject = {
|
74 | [K in any]: never
|
75 | }
|
76 |
|
77 | type IgnoreInvalidIntersections<T> = T extends EmptyObject ? never : T
|
78 |
|
79 | /** Extract the parameters from all functions as a tuple */
|
80 | export type ExtractParams<T extends readonly UnknownFunction[]> = {
|
81 | [index in keyof T]: T[index] extends T[number] ? Parameters<T[index]> : never
|
82 | }
|
83 |
|
84 | /** Recursively expand all fields in an object for easier reading */
|
85 | export type ExpandItems<T extends readonly unknown[]> = {
|
86 | [index in keyof T]: T[index] extends T[number] ? Expand<T[index]> : never
|
87 | }
|
88 |
|
89 | /** Select the longer of two arrays */
|
90 | export type Longest<L extends List, L1 extends List> = L extends unknown
|
91 | ? L1 extends unknown
|
92 | ? { 0: L1; 1: L }[Has<keyof L, keyof L1>]
|
93 | : never
|
94 | : never
|
95 |
|
96 | /** Recurse over a nested array to locate the longest one.
|
97 | * Acts like a type-level `reduce()`
|
98 | */
|
99 | export type LongestArray<S extends readonly any[][]> =
|
100 | // If this isn't a tuple, all indices are the same, we can't tell a difference
|
101 | IsTuple<S> extends '0'
|
102 | ? // so just return the type of the first item
|
103 | S[0]
|
104 | : // If there's two nested arrays remaining, compare them
|
105 | S extends [any[], any[]]
|
106 | ? Longest<S[0], S[1]>
|
107 | : // If there's more than two, extract their types, treat the remainder as a smaller array
|
108 | S extends [any[], any[], ...infer Rest]
|
109 | ? // then compare those two, recurse through the smaller array, and compare vs its result
|
110 | Longest<
|
111 | Longest<S[0], S[1]>,
|
112 | Rest extends any[][] ? LongestArray<Rest> : []
|
113 | >
|
114 | : // If there's one item left, return it
|
115 | S extends [any[]]
|
116 | ? S[0]
|
117 | : never
|
118 |
|
119 | /** Recursive type for intersecting together all items in a tuple, to determine
|
120 | * the final parameter type at a given argument index in the generated selector. */
|
121 | export type IntersectAll<T extends any[]> = IsTuple<T> extends '0'
|
122 | ? T[0]
|
123 | : _IntersectAll<T>
|
124 |
|
125 | type IfJustNullish<T, True, False> = [T] extends [undefined | null]
|
126 | ? True
|
127 | : False
|
128 |
|
129 | /** Intersect a pair of types together, for use in parameter type calculation.
|
130 | * This is made much more complex because we need to correctly handle cases
|
131 | * where a function has fewer parameters and the type is `undefined`, as well as
|
132 | * optional params or params that have `null` or `undefined` as part of a union.
|
133 | *
|
134 | * If the next type by itself is `null` or `undefined`, we exclude it and return
|
135 | * the other type. Otherwise, intersect them together.
|
136 | */
|
137 | type _IntersectAll<T, R = unknown> = T extends [infer First, ...infer Rest]
|
138 | ? _IntersectAll<Rest, IfJustNullish<First, R, R & First>>
|
139 | : R
|
140 |
|
141 | /*
|
142 | *
|
143 | * External/Copied Utility Types
|
144 | *
|
145 | */
|
146 |
|
147 | /**
|
148 | * Removes field names from a tuple
|
149 | * Source: https://stackoverflow.com/a/63571175/62937
|
150 | */
|
151 | type RemoveNames<T extends readonly any[]> = [any, ...T] extends [
|
152 | any,
|
153 | ...infer U
|
154 | ]
|
155 | ? U
|
156 | : never
|
157 |
|
158 | /**
|
159 | * Transposes nested arrays
|
160 | * Source: https://stackoverflow.com/a/66303933/62937
|
161 | */
|
162 | type Transpose<T> = T[Extract<
|
163 | keyof T,
|
164 | T extends readonly any[] ? number : unknown
|
165 | >] extends infer V
|
166 | ? {
|
167 | [K in keyof V]: {
|
168 | [L in keyof T]: K extends keyof T[L] ? T[L][K] : undefined
|
169 | }
|
170 | }
|
171 | : never
|