0

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)

YakovL
  • 7,557
  • 12
  • 62
  • 102
  • please provide reproducible example. It might be a good idea to add a link to typescript playground with appropriate comments: `expected error because ....` or `should be ok because ....` – captain-yossarian from Ukraine Feb 17 '22 at 08:41
  • @captain-yossarian sorry, I don't see how I haven't provided an MCVE, all the necessary bits are in the post, even Telegraf version. It was also enough for Titian to answer. Do you propose to reduce Telegraf to some types defined in the question? – YakovL Feb 17 '22 at 12:52

1 Answers1

2

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

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • awesome! In term of TS hack, this is exactly what I was asking for. I'll try to decompose this myself, but could you explain why `UnionKeys` is defined with ternary operator and not as just `keyof T`? What does `T extends T` bit check? – YakovL Feb 17 '22 at 12:49