4
function Middleware<T extends Function>(middleware: Array<{ funcs: Array<keyof T['prototype']>, class: T }>) {

}

class TestValidation {
  Test() { }
}

class FilterValidation {
  Filter() { }
}

I am defining Middleware fonkisyon and I want to use giving class functions as string. But not working.

example usage

Middleware([
    { funcs: ['Test'], class: TestValidation },
    { funcs: ['Filter'], class: FilterValidation}
  ]) 

this code givin error Type '"Filter"' is not assignable to type 'keyof TestValidation'

how can I fix this problem?

Erdem Ün
  • 204
  • 1
  • 7

1 Answers1

1

One approach is to make Middleware generic in a type parameter T corresponding to the tuple type of instance types constructed by the class property of the middleware array. So if you call Middleware([{funcs: [], class: Class1}, {funcs: [], class: Class2}]), where Class1 and Class2 are class constructors of types named Class1 and Class2 respectively, then T would be the type [Class1, Class2].

And we'd make the middleware parameter be of a mapped type over T, such that each element of middleware takes on a type related to the corresponding element of T. For each numeric index I from the keys of T, the class property should be a construct signature for T[I] (the element of T at key I), while the funcs property should be an array of keys of T[I] whose properties are functions.

We can write it like this:

function Middleware<T extends readonly object[]>(
  middleware: [...{ [I in keyof T]: {
    funcs: ReadonlyArray<KeysMatching<T[I], Function>>
    class: new () => T[I]
  } }]
) {}

where KeysMatching<T, V> is a utility type to get the property keys of T whose property values are assignable to V. One possible definition is

type KeysMatching<T, V> = keyof
  { [K in keyof T as T[K] extends V ? K : never]: any }

See In TypeScript, how to get the keys of an object type whose values are of a given type? for more information about how KeysMatching works.

Oh, and the middleware parameter's type is wrapped in a variadic tuple type like [...+] to give the compiler a hint that we want T to be inferred as a tuple and not an unordered array.


Okay, let's test it:

class TestValidation { Test() { } }
class FilterValidation { Filter() { } }

Middleware([
  { funcs: ['Test'], class: TestValidation },
  { funcs: ['Filter'], class: FilterValidation }
]); // okay

Looks good. Let's test it on an example that fails:

class Foo {
  a = 1;
  b = 2;
  c() { }
}
Middleware([{
  class: Foo, funcs: ["a", "b", "c"]; // error!
  // ---------------> ~~~  ~~~
}])

That fails because "a" and "b" are keys of Foo whose values are not functions. Only "c" is accepted. So this solution works for both accepting desirable inputs and rejecting undesirable ones.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360