gvergnaud/ts-pattern

a way to split long match blocks

phaux opened this issue · 0 comments

phaux commented

Is your feature request related to a problem? Please describe.

I'm making a telegram bot and the main part of it is a function like this (I'm not using any library, just typed jsons):

async function handleUpdate(update: TgUpdate) {
  await match(update)
    // bot added to group
    .with({
      my_chat_member: {
        chat: { type: P.union("group", "supergroup") },
        new_chat_member: { status: P.union("member", "administrator") },
      },
    }, async (update) => {
      await handleGroupJoin(update.my_chat_member)
    })
    // command /set_threshold in group chat
    .with({
      message: {
        text: P.string.startsWith("/set_threshold"),
        entities: [{ type: "bot_command", offset: 0 }, ...P.array()],
        chat: { type: P.union("group", "supergroup") },
      },
    }, async (update) => {
      await handleSetThresholdCommand(update.message)
    })
    // reply to command /set_threshold
    .with({
      message: {
        reply_to_message: {
          text: P.string.includes("Tell me the threshold"),
          from: { is_bot: true },
        },
        text: P.string,
      },
    }, async (update) => {
      await handleSetThresholdReply(update.message)
    })
    // message_reaction
    .with({
      message_reaction: {
        chat: { type: P.union("group", "supergroup", "channel") },
      },
    }, async (update) => {
      await handleReaction(update.message_reaction)
    })
    
    // many more match arms later...

    .otherwise(async () => { /* ignore */ })
}

Describe the solution you'd like

I wish there was a way to split the long match block into smaller parts. For example:

export const matchThresholdCommand = (match: _) => match
  // command /set_threshold in group chat
  .with({
    ...
  }, async (update) => {
    ...
  })
  // reply to command /set_threshold
  .with({
    ...
  }, async (update) => {
    ...
  })
async function handleUpdate(update: TgUpdate) {
  await match(update)
    .use(matchThresholdCommand)
    .use(matchOtherCommand)
    // etc
    .otherwise(async () => { /* ignore */ })
}

#209 could improve the api further.

Describe alternatives you've considered

I can match the updates by their type first (all messages, replies, reactions, joins) and then do inner match for each (e.g. for messages, what command it contains), but I would like to split them based on what functionality they provide, not what type they are.

Additional context

I wonder if its possible without any changes to the library and only using the Match type somehow? I tried it but couldn't get the types right:

async function handleUpdate(update: TgUpdate) {
  await withBazCommand(withBarCommand(withFooCommand(match(update))))
    .otherwise(async () => { /* ignore */ })
}

const withFooCommand = <_>(match: Match<_>): Match<_> => match
  .with({...}, async (update) => {...})
  .with({...}, async (update) => {...})

Maybe exporting some helper types for this use case would be enough.