Anton
Anton

Reputation: 2341

Typescript: type assist for function argument

I've written a little library and now I'm trying to add type support for it through creating custom d.ts files.

To give an example (my library is called layerCompose):

const C = layerCompose(
  {
     _($) { /* function body */ },
     execute($) { /* function body */ }
  }
)
const c = C()
c._()

You will notice that both _ and execute function take a parameter $. This parameter (in our example) is an object with _ and execute properties (similar to this within a class)

So, I'd like to add type support to this $ parameter. Through experimenting I found that having this definition inside my d.ts file gives type support within the _ function.

export function layerCompose<
  T extends [
    A extends {} 
      ? {
        _($: {test: () => void})
      } 
      : never
  ]
  , A
>(...layers: T): lcConstructor<Spread<T>>

In other words Webstorm can see that $ parameter in _ function has type {test: () => void}

Obviously, that's not what I'm looking for. However, changing the signature to (what seems proper to me)

export function layerCompose<
  T extends [
    A extends {} 
      ? { 
        [K in keyof A]: ($: {test: () => void}) => any 
      } 
      : never
  ]
  , A
>(...layers: T): lcConstructor<Spread<T>>

and Webstorm looses the ability to tell that $ is an object with properties _ and execute.


EDIT: Included an example on typescript playground. It's missing the return type (non-trivial to set that up quickly), but otherwise gives an insight into what layerCompose approximately does.


EDIT 2:

Playing around further, having simplified to 1 parameter scenario, this works:

export function layerCompose<L1>(l1: {_: ($: {test: () => void}) => void} & L1): void

yet this does not:

export function layerCompose<L1>(l1: {[K in keyof L1]: ($: {test: () => void}) => void} & L1): void

Upvotes: 9

Views: 166

Answers (2)

chinesedfan
chinesedfan

Reputation: 449

According personal experiences, find something as generic type first, to avoid circular dependencies. Please check following definitions.

type Layer<K extends string> = Partial<Record<K, ($: Record<K, Function>) => void>>

function layerCompose<K extends string>(...layers: Layer<K>[]): Record<K, Function>
  // ignore how do you implement to match types
  return {} as Record<K, Function>
}

Upvotes: 2

Olian04
Olian04

Reputation: 6872

Is this what you're looking for?

export function layerCompose(...layers: Layer[]): lcConstructor<Spread<T>>;

Since you haven't defined neither lcConstructor or Spread I can't make any real guesses as to what the return type of layerCompose is supposed to be. So for the purpose of this answer those types have been replaced with the identity type.

// See playground below
type ComposedLayer = {
  _(): void;
  execute(): void;
}
type Layer = {
  _(v: Layer): void;
  execute(v: Layer): void;
}

/* dummy type */ type lcConstructor<T> = ComposedLayer;
/* dummy type */ type Spread<T> = T;

declare function layerCompose(...layers: Layer[]): lcConstructor<Spread<Layer>>;

const C = layerCompose(
  {
     _($) {},
     execute($) {
       $._({
         _($) {},
         execute($) {}
       })
     }
  }
);

C._();

playground

Upvotes: 2

Related Questions