Typescript/xstate : Split Object to discriminated Unions

234 Views Asked by At

I have this type :

type FetcherEvents = {
    signInWithEmailAndPassword: [email: string, password: string];
    signUpWithEmailAndPassword: [email: string, password: string, ...data: any[]];
    signWithEmail: [email: string, code: any];
}

And I want to convert each property to an object and creates an discriminated union.

So it becomes this :

type Resolved =
  | {
      type: "signInWithEmailAndPassword";
      value: [email: string, password: string];
    }
  | {
      type: "signUpWithEmailAndPassword";
      value: [email: string, password: string, ...data: any[]];
    }
  | {
      type: "signWithEmail";
      value: [email: string, code: any];
    };

Can you help me.

1

There are 1 best solutions below

0
On

I find the solution :

First create an TuplifyUnion

To transform the union keyof 'some Type' into an Array (or tuple)

type _UnionToIntersection<U> = (
  U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never;

type _LastOf<T> = _UnionToIntersection<
  T extends unknown ? () => T : never
> extends () => infer R
  ? R
  : never;

type _Push<T extends unknown[], V> = [...T, V];

type _TuplifyUnionBoolean<T> = [T] extends [never]
  ? true
  : false;

// TS4.1+
export type TuplifyUnion<T> =
  true extends _TuplifyUnionBoolean<T>
    ? []
    : _Push<
        TuplifyUnion<Exclude<T, _LastOf<T>>>,
        _LastOf<T>
      >;

// #endregion

Don't ask me how TuplifyUnion is done, I copy it from another stackoverflow.

Second step you create ...Rest functions for Array and Objects.

Like this :

type RestArrayOf<T extends any[]> = T extends [any, ...infer U] ? U : never;

type RestObjectOf<T> = RestArrayOf<
  TuplifyUnion<keyof T>
>[number] extends keyof T
  ? { [key in RestArrayOf<TuplifyUnion<keyof T>>[number]]: T[key] }
  : never;

And the last step...

Your beautifull type to unionyse or eventyse (for xstate) your Interface :

type Eventyse<T> = TuplifyUnion<keyof T>[0] extends keyof T
  ? TuplifyUnion<keyof T>[1] extends keyof T
    ?
        | {
            type: TuplifyUnion<keyof T>[0];
            value: T[TuplifyUnion<keyof T>[0]];
          }
        | Eventyse<RestObjectOf<T>>
    : {
        type: TuplifyUnion<keyof T>[0];
        value: T[TuplifyUnion<keyof T>[0]];
      }
  : never;

So...

The the resolved type :

type Resolved= Eventyse<FetcherEvents>;

outputs:


  | {
      type: "signInWithEmailAndPassword";
      value: [email: string, password: string];
    }
  | {
      type: "signUpWithEmailAndPassword";
      value: [email: string, password: string, ...data: any[]];
    }
  | {
      type: "signWithEmail";
      value: [email: string, code: any];
    };

Thanks you. TypeScript is the best...