How can you type a functional mixin reducer?

I’m learning typescript and struggling to figure out how to appropriately type a reducer function that reduces functional mixins.

Given two functional mixins like:

type FooComposable = {
  foo: string;
};
const withFoo = (composable): FooComposable => {
  composable.foo  = 'foo';

  return composable;
};

type BarComposable = {
  bar: string;
};
const withBar = (composable): BarComposable => {
  composable.bar = 'bar';

  return composable;
};

I have a reducer function that will reduce all provided functional mixins:

const reduce(...fns) = fns.reduce((acc, fn) => fn(acc), {}));

reduce(withFoo); // -> { foo: 'foo' }
reduce(withBar); // -> { bar: 'bar' }
reduce(withFoo, withBar); // -> { foo: 'foo', bar: 'bar' }

How can I add typings to the reduce() function (and functional mixins) such that a resultant reduced composable has expected type inferences?

type FooComposable = {
  foo: string;
};
const withFoo = <T extends FooComposable>(composable: T): FooComposable => {
  composable.foo = 'foo';

  return composable;
};

type BarComposable = {
  bar: string;
};
const withBar = <T extends BarComposable>(composable: T): BarComposable => {
  composable.bar = 'bar';

  return composable;
};

type FunctionalMixin<T extends {}> = (composable: T) => T;
const reduce = <T extends {}>(...fns: FunctionalMixin<T>[]): T =>
  fns.reduce((acc, fn) => fn(acc), {});
/* Type '{}' is not assignable to type 'T'.
  '{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.  */

reduce<FooComposable & BarComposable>(withFoo, withBar); // -> { foo: 'foo', bar: 'bar' }
/* Argument of type '<T extends FooComposable>(composable: T) => FooComposable' is not assignable to parameter of type 'FunctionalMixin<FooComposable & BarComposable>'.
  Type 'FooComposable' is not assignable to type 'FooComposable & BarComposable'.
    Property 'bar' is missing in type 'FooComposable' but required in type 'BarComposable'. */


Answer

How can I add typings to the reduce() function (and functional mixins) such that a resultant reduced composable has expected type inferences?

In the following example, result has the correctly inferred type Foo & Bar.

Playground

type Foo = {
    foo: string;
};

const withFoo: Mixin<Foo> = (a) => ({ ...a, foo: 'foo' });

type Bar = {
    bar: string;
};

const withBar: Mixin<Bar> = (a) => ({ ...a, bar: 'bar' });

type Mixin<B> = <A extends object>(a: A) => A & B;

type Reduce<A extends object, T extends unknown[]> =
    T extends [] ? A :
    T extends [Mixin<infer B>, ...infer C] ? Reduce<A & B, C> :
    never;

const reduce = <T extends Mixin<unknown>[]>(...fns: T): Reduce<{}, T> =>
    fns.reduce((a, fn) => fn(a), {}) as Reduce<{}, T>;

const result = reduce(withFoo, withBar);