panwauu
panwauu

Reputation: 47

TypeScript: Remove keys from object by value

I have a object with keys and functions as values. Now I want to create a function where you can only assign the keys where the corresponding key includes a callback function as last function parameter.

The callback may only have one argument and return type void.

interface Events {
  'valid0': (data: string, cb: () => void) => void, // valid
  'valid1': (data: number, cb: (data: string) => void) => void, // valid
  'invalid0': (data: string, cb: () => string) => void, // invalid return type of callback
  'invalid1': (data: string, cb: (string: string, number: number) => void) => void, // invalid number of callback arguments
}

type EventsWithCallback<E> = ???

type testFunction<Events> = (EventName: EventsWithCallback<Events>) => void

I am having problems to define this EventsWithCallback type. With the following I get the error: Type 'T[P]' does not satisfy the constraint '(...args: any[]) => void'. This is somehow logical. I tried typing T as Record<string, (...args: any) => void> but then I match all string.

type Last<T extends any[]> = T extends [...any, infer Last] ? Last : any;
type EventsWithCallback<T> = keyof { [P in keyof T as Last<Parameters<T[P]>> extends Function ? P : never]: T[P] };

Also extends Function matches any function and also any type.

Thanks for any kind of help. I hope the problem is understandable.

Upvotes: 1

Views: 368

Answers (1)

Rubydesic
Rubydesic

Reputation: 3476

Does this work? Playground Link

interface Events {
  'valid0': (data: string, cb: () => void) => void, // valid
  'valid1': (data: number, cb: (data: string) => void) => void, // valid
  'invalid0': (data: string, cb: () => string) => void, // invalid return type of callback
  'invalid1': (data: string, cb: (string: string, number: number) => void) => void, // invalid number of callback arguments
}

type Last<T extends any[]> = T extends [...any, infer Last] ? Last : any

type EventsWithCallback<T> = { 
  [P in keyof T]: 
    T[P] extends (...args: infer A) => void // Check if it is a function
      ? Last<A> extends (arg: any) => any // Check if the last parameter is a function with single parameter
        ? ReturnType<Last<A>> extends void // Check if it returns void
          ? P // return the parameter name
          : never
        : never
      : never
}[keyof T]

const testFunction = (eventName: EventsWithCallback<Events>) => {
  eventName = 'invalid0' // error
  eventName = 'invalid1' // error
  eventName = 'valid0'   // no error
  eventName = 'valid1'   // no error
}

Upvotes: 1

Related Questions