0

This is a followup to this question and this question.

// return type
interface DataA {
  value: number;
}

// return type
interface DataB {
  text: string;
}

// return type
interface DataC {
  has: boolean;
}

// correct "resolver" has at least one function with a return type
interface ResolverA {
  // 'callA' should be suggested because it is a function with a return type
  // payload is DataA
  callA(...args: any): DataA;
  // 'callB' should be suggested because it is a function with a return type
  // payload is DataB
  callB(): DataB;
}

// correct "resolver" has at least one function with a return type
interface ResolverB {
  // 'value' should not be suggested because it is a number
  value: number;

  // 'resolve' should be suggested because it is a function with a return type
  // payload is DataB
  resolve(): DataC;
}

// incorrect "resolver" has at least one function with a return typpe
interface ResolverC {
  // 'text' should not be suggested because it is a number
  text: string;
}

// should be ok because two properties have a correct "resolver" type
interface ResolversShouldWork {
  // 'has' should not be suggested because it is a number
  has: boolean;
  // 'resolverA' should be suggested because ResolverA has functions with an return type.
  resolverA: ResolverA;
  // 'resolverB' should be suggested because ResolverB has functions with an return type.
  resolverB: ResolverB;
  // 'resolverC' should not be suggested because Resolver C has no function with an return type.
  resolverC: ResolverC;
}

// should be error because no property has a correct "resolver" type
interface ResolversShouldntWorkA {
  // 'item' should not be suggested because it is a number
  item: object;
}

// should be error because no property has a correct "resolver" type
interface ResolversShouldntWorkB {
  // 'resolverC' should not be suggested because Resolver C has no function with an return type.
  resolverC: ResolverC;
}

type PickByValue<T, V> = { [K in keyof T as T[K] extends V ? K : never]: T[K] };
type HasProp<T, D = never> = T extends (keyof T extends never ? never : unknown)
  ? T
  : D;

type AcceptableHandler<T> = HasProp<
  PickByValue<T, (...args: any) => any>,
  { needsAtLeastOneMethod(): any }
>;

class Handler<
  TResolvers extends {
    [TResolver in keyof TResolvers]: AcceptableHandler<TResolver>;
  },
> {
  public send<
    TResolver extends Extract<keyof TResolvers, string>,
    TMethod extends Extract<keyof TResolvers[TResolver], string>,
    TPayload extends TResolvers[TResolver][TMethod] extends (
      ...args: any
    ) => infer R
      ? R
      : never,
  >(resolver: TResolver, method: TMethod, payload: TPayload) {
    //
  }
}

const handlerA = new Handler<ResolversShouldWork>(); // should be ok
const handlerB = new Handler<ResolversShouldntWorkA>(); // should be error
const handlerC = new Handler<ResolversShouldntWorkB>(); // should be error

handlerA.send('has', null, null); // 'has' should be error
handlerA.send('resolverA', 'callA', { value: 5 }); // should be ok, payload should be 'DataA'
handlerA.send('resolverA', 'callB', { text: 'hi' }); // should be ok, payload should be 'DataB'
handlerA.send('resolverB', 'resolve', { has: true }); // should be ok, payload should be 'DataC'
handlerA.send('resolverB', 'value', null); // 'value' should be error
handlerA.send('resolverC', null, null); // 'resolverC' should be error

Here is a playground.

I would like Handler to only accept interfaces with properties of a type having functions with a return type for a payload. It doesn't matter if the functions have parameters or not. So if you create a new interface having properties with types having at least one function with a return type it should work as a generic parameter for Handler as well.

There are some suggestions that should be autocompleted, some not. The first parameter of handlerA's function send should be choosable between resolverA and resolverB. If resolverA is selected the second parameter should allow callA and callB. If callA is selected the third parameter payload should expect type DataA. If callB is selected payload should expect type DataB. If resolverB is selected the second parameter should only allow resolve. If selected payload should expect type DataC.

Mat
  • 202,337
  • 40
  • 393
  • 406
Kleywalker
  • 49
  • 5
  • Like [this maybe](https://tsplay.dev/w225zw)? Frankly I'm getting tired of seeing minor variations of this question cropping up and I don't think I've got enough stamina to write up an answer for it. I'm going to disengage and maybe come back later if nobody else posts, but I think in any case I probably can't commit to continue for any future versions of this same thing. – jcalz Apr 05 '23 at 18:25
  • Oop, I got to a pretty [similar conclusion](https://tsplay.dev/NB8J4W) – kelsny Apr 05 '23 at 18:34
  • Sorry for tiring you. Even more thank you then for solving my problem! This is exactly what I was hoping for. Could you explain in detail how `resolverC` is excluded? – Kleywalker Apr 05 '23 at 18:42
  • @pink could you explain the exact difference of your conclusion and why it works anyway? – Kleywalker Apr 05 '23 at 19:00
  • It does the exact same thing as jcalz's but achieves it in a different manner. The idea is the same; only the execution is different. To exclude `resolverC`, we check if the value of the property is an object, and if the values of that object contain a function. If it isn't an object, or if it doesn't contain a function, we exclude the key. – kelsny Apr 05 '23 at 19:05

0 Answers0