Skip to main content

function

ReturnTypeSafe, ParametersSafe, Curried, Promisify.

import type {
  ReturnTypeSafe, ParametersSafe, Curried, Promisify,
} from '@gentleduck/ttest/function'

Safer variants of the built-in ReturnType / Parameters (they degrade to never instead of erroring on non-callable input) plus two function transforms. For class constructors see class; for async-aware return types see async.

ReturnTypeSafe

type ReturnTypeSafe<F> = F extends (...args: any[]) => infer R ? R : never

Return type of F, or never for non-functions. Unlike the built-in ReturnType<T>, this does not require T extends (...args: any) => any at the call site, so it composes safely inside larger conditionals.

type a = ReturnTypeSafe<() => string>           // string
type b = ReturnTypeSafe<(n: number) => boolean> // boolean
type c = ReturnTypeSafe<string>                 // never
type d = ReturnTypeSafe<undefined>              // never
// Compose without prior callable check.
type ResultOf<F> = ReturnTypeSafe<F> | 'no-fn'
type a = ResultOf<() => 1>    // 1 | 'no-fn'
type b = ResultOf<number>     // 'no-fn'   (never collapses out of the union)

ParametersSafe

type ParametersSafe<F> = F extends (...args: infer P) => any ? P : never

Parameter tuple, or never for non-functions.

type a = ParametersSafe<(x: number, y: string) => void>  // [x: number, y: string]
type b = ParametersSafe<() => 1>                          // []
type c = ParametersSafe<{ kind: 'data' }>                 // never

Edge case: overloaded function types collapse to the last overload's parameters — this matches the built-in Parameters behavior.

Curried

type Curried<F>

Curried form: an n-ary function becomes a chain of unary functions.

type f = (a: number, b: string, c: boolean) => Date
type c = Curried<f>
//   ^? (a: number) => (b: string) => (c: boolean) => Date
// Zero-arg and unary functions are pass-through.
type a = Curried<() => 1>            // () => 1
type b = Curried<(x: number) => 1>   // (x: number) => 1

Promisify

type Promisify<F> = F extends (...args: infer P) => infer R ? (...args: P) => Promise<R> : never

Wrap the return type of F in Promise. Use to derive the async signature of a sync API surface, or to type a .then-able adapter.

type f = (n: number) => string
type p = Promisify<f>  // (n: number) => Promise<string>
type Sync  = { read(): string; write(s: string): void }
type Async = { [K in keyof Sync]: Promisify<Sync[K]> }
//          ^? { read: () => Promise<string>; write: (s: string) => Promise<void> }

Edge case: Promisify<F> on a function that already returns Promise<T> produces (...args: P) => Promise<Promise<T>>. Use Awaited or DeepAwaited on the result if you need flat promises.