Two key problems arise in the above approach:
T[number]
, known as an indexed access type, results in a union of all types within the T
tuple/array; however, TypeScript only retains shared keys (intersection) when dealing with variables that are unions. In this case, since A | B
lacks common keys, keyof (A | B)
is essentially already never
, even without method filtering.
- When evaluating
typeof testResolvers
, we actually get readonly [typeof A, typeof B]
, which assigns the type of the class constructor rather than the actual classes themselves. Consequently, accessing their keys using keyof typeof A
, for instance, merely yields "prototype"
.
Regarding problem 1: instead of a union, what we seek is an intersection wherein TypeScript preserves unioned keys (all keys from any element). While building such an intersection directly from a tuple isn't straightforward, it's feasible with an example like below:
// Transforming a tuple into an intersection of each contained type
type TupleToIntersection<T> =
T extends readonly never[]
? unknown : (
T extends readonly [infer First, ...infer Rest]
? First & TupleToIntersection<Rest>
: never)
type intersection = TupleToIntersection<typeof testResolvers>
// ^? type intersection = typeof A & typeof B
In terms of problem 2: to retrieve the class type from its constructor type, one simply needs to access the "prototype"
property, generally via indexed access once more. An assistive type could be developed for conditional conversion if needed:
// Extracting the class (prototype) from its constructor if possible
type ConstructorToPrototype<C> = C extends { prototype: infer P } ? P : C
type AClass = ConstructorToPrototype<typeof A>
// ^? type AClass = A
Subsequently, employing these auxiliary types in line enables us to obtain the desired outcome of extracting the union of methods across all class instances present in the tuple:
// 1. Converting the tuple type into an intersection
// 2. Acquiring the classes over the constructor types
// 3. Retrieving keys with function values (i.e., methods)
type ValuesOf<T extends readonly unknown[]> = ExtractFunctionKeys<ConstructorToPrototype<TupleToIntersection<T>>>;
type Foo = ValuesOf<typeof testResolvers>;
// ^? type Foo = "getBooks" | "getBook" | "getUsers" | "getUser" | "getBooksOfUser"
Playground Link