Search code examples
typescriptvisual-studio-codetelegraf.js

Can I get suggestions of all possible properties of an object in TypeScript?


I'm trying to use Telegraf (4.6.0) types and I'm having an issue with exploring possible message properties.

Here's what I do now:

import { Telegraf, Context } from 'telegraf'

const myBot = new Telegraf(myToken)
listenerBot.on('message', (ctx: Context) => {
    const {
        text,
        forward_from_chat, forward_from_message_id,
        photo, caption, caption_entities,
        // what it will contain if there's a video? audio? document? ...
    } = ctx.message as any
    // do stuff with message
}

Since messages can be of various types (in both non-TS and TS sense), when I type ctx.message. in IDE (VS Code in my case) I'm only suggested the props that are always in message object (like message_id). Yes, I can do things like

if('text' in ctx.message) {
    // do stuff with ctx.message.text
}

but this doesn't help me explore what props can ctx.message hold. I can imagine a hacky way like

class ExploredContext = ExploreProps<Context> → gives a class similar to Context,
  but all possible props are non-optional

...
(ctx as ExploredContext).message._ // cursor here, IDE shows possilbe props
(ctx.message as ExploredMessage)._ // or like this

but neither I know how to implement things like ExploreProps helper (I'm only aware of utility types) nor I know any better, non-hacky ways to get this (like some configuration of typescript and/or IDE).

Can you suggest a way to implement ExploreProps or a better way to explore possible props?

(in Telegraf context, I've also asked at in an issue, but a consistent solution would be helpful without regard to Telegraf itself)


Solution

  • You can flatten a union using StrictUnion as defined here This type will basically add the missing members to all union constituents with the type undefined. This will allow de-structuring to suggest all members from any constituent, but each member that is not present in all union constituents will also contain undefined (which is probably for the best from a type-safety perspective)

    import { Telegraf, Context } from 'telegraf'
    
    type UnionKeys<T> = T extends T ? keyof T : never;
    type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>> : never;
    type StrictUnion<T> = StrictUnionHelper<T, T>
    
    
    const myToken = ""
    const myBot = new Telegraf(myToken)
    myBot.on('message', (ctx: Context) => {
        const {
            text,
            forward_from_chat, forward_from_message_id,
            photo, caption, caption_entities,
            
        } = ctx.message as StrictUnion<Context['message']>
    })
    

    Playground Link