agiledigital / typed-redux-saga

An attempt to bring better TypeScript typing to redux-saga.
MIT License
315 stars 33 forks source link

WIP: infer `call` return type based on args #665

Open chrissantamaria opened 2 years ago

chrissantamaria commented 2 years ago

👋 big fan of this project! One issue I've run into is invoking functions will call where the return type is dependent on arguments passed. For example, an identity function is likely the simplest example:

const identity = <T>(x: T): T => x;

// $ExpectType "foo"
identity("foo" as const);

However, this currently fails when using call. The root case seems come from the SagaReturnType helper from @redux-saga/core:

export type SagaReturnType<S extends Function> =
  S extends (...args: any[]) => SagaIterator<infer RT> ? RT :
  S extends (...args: any[]) => Promise<infer RT> ? RT :
  S extends (...args: any[]) => infer RT ? RT :
  never;

Inferring a return type without knowing the arguments used for invocation will cause TypeScript to evaluate this as unknown for some functions.

Fortunately, the arguments can be considered. This PR proposes a new type for call:

export function call<Args extends unknown[], Return>(
  fn: (...args: Args) => Return,
  ...args: Args
): SagaGenerator<
  ExtractReturnValue<Return>,
  CallEffect<ExtractReturnValue<Return>>
>;

To handle Return potentially being a SagaIterator or Promise, I've created a new ExtractReturnValue helper similar to SagaReturnType:

export type ExtractReturnValue<ReturnType> = ReturnType extends SagaIterator<
  infer SagaReturnType
>
  ? SagaReturnType
  : ReturnType extends Promise<infer PromiseReturnType>
  ? PromiseReturnType
  : ReturnType;

This works great for simple use cases like call(fn, ...args), though I'm struggling to implement it for all other call signatures of call. I've committed an attempt at fixing call([context, fnName], ...args) with a failing test, though I'm not sure exactly where it's falling apart. Opening a PR to get some thoughts and hopefully arrive at a good solution for all call signatures!

Related issues: