[Typescript] Get full type safe for discriminatedUnion type with 'type' & 'subtype' (2 levels)

// print
type PrintStart = {
    type: "print";
    subtype: "start";
    attributes: {
        controlsId: string;
        tabId: number;
    };
}

type PrintAbort = {
    type: "print";
    subtype: "abort";
    attributes: {
        controlsId: string;
    };
}

type PrintLoading = {
    type: "print";
    subtype: "loading";
    attributes: {
        controlsId: string;
    };
}

type Print = PrintLoading | PrintAbort | PrintStart

// chat
type ChatConnected = {
    timestamp?: number | undefined;
    type: "chat";
    subtype: "connected";
    attributes: {
        controlsId: string;
        component: string;
    };
}

type ChatLoading = {
    timestamp?: number | undefined;
    type: "chat";
    subtype: "loading";
    attributes: {
        controlsId: string;
        component: string;
    };
}

type ChatUpload = {
    timestamp?: number | undefined;
    type: "chat";
    subtype: "unload";
    attributes: {
        controlsId: string;
        component: string;
    };
}

type Chat = ChatUpload | ChatLoading | ChatConnected

// Union of message
type Message = Print | Chat

 

What we want is that

function sendEncodeMessage(type, subtype, msg) {}

When we give `type` and `subtype`, the `msg` should be narrow down to the exact type

const printStart: PrintStart = {
  type: 'print',
  subtype: 'start',
  attributes: {
    tabId: 123,
    controlsId: '123',
  },
}

function sendEncodeMessage<
  Type extends Message['type'],
  Sub extends GetMessageSubtypes<Type>,
  Msg extends GetMessage<Type, Sub>,
>(type: Type, subtype: Sub, msg: Msg) {}

sendEncodeMessage('print', 'start', printStart) // works

 

Helpers

export type GetMessagesByType<Type extends Message['type']> = Message extends infer Msg
  ? Msg extends { type: Type }
    ? Message
    : never
  : never

export type GetMessageSubtypes<Type extends Message['type']> = Type extends Message['type']
  ? GetMessagesByType<Type>['subtype']
  : never

export type GetMessage<
  Type extends Message['type'],
  Subtype extends GetMessageSubtypes<Type>,
> = Message extends infer Msg ? (Msg extends { type: Type; subtype: Subtype } ? Message : never) : never

export type GetMessageAttr<Type extends Message['type'], Subtype extends GetMessageSubtypes<Type>> = GetMessage<
  Type,
  Subtype
> extends {
  attributes: infer Attr
}
  ? Attr
  : never

 

posted @ 2023-01-04 03:46  Zhentiw  阅读(12)  评论(0编辑  收藏  举报