0.1.31 • Published 1 year ago

tinytrpc v0.1.31

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

TinyTrpc

Typed discord UIs. Command-click to a component's handler.

It's this but cooler

// creation
new ButtonBuilder({
   customId: buttonName + "-" + pageNumber,
});

// handling
const [buttonName, pageNumber] = interaction.customId.split("-")

Full demo

init.

import { flare } from "tinytrpc";

// Simple "middleware"
const adminOnly = (interaction: Interaction) => interaction.memberPermissions?.has("Administrator") ?? false;

// Optional context for handler
type Context = ButtonInteraction<"raw" | "cached">;
// optional but convenient shortcut for combining routers
const lens = flare<Context>();

Basic router with context + middleware checkpoint

const adminPageScope = lens
   // Never miss a permissions check. Useful in nested routers.
   .lock(adminOnly)
   // Your methods
   .scope({
      // TS requires first param to be context
      async delete(interaction: Context, page: number) {
         // "page" comes from component's customId payload
         await deletePage(interaction.guildId, page);
      },
   });

Nesting routers

const { router, handler } = lens.scope({
   page: {
      admin: adminPageScope, // (uses "_internal" prop for nesting)

      // More methods, any shape
      async open(interaction: Context, page: number) {
         const pageData = await getPageData(interaction.guildId, page);

         // Example use
         const buttonRow = new ActionRowBuilder<ButtonBuilder>();
         buttonRow.addComponents(
            new ButtonBuilder({
               label: "next",
               // generate customId with next page as payload
               // context is provided later by handler
               customId: router.page.open(page + 1),
            }),
            new ButtonBuilder({
               label: "delete",
               // You can go to handler's definition via command-click!
               customId: router.page.admin.delete(page),
            }),
         );

         await interaction.update({ components: [buttonRow] });
      },
   },
});

Generic interaction handler

async function resolveAnyInteraction(interaction: Interaction) {
   if (!interaction.isButton()) return;
   if (!interaction.inGuild()) return;
   // handler second param is conditionally required if you use context
   await handler(interaction.customId, interaction);
}

Disclaimer

Keep component payloads tiny, don't stuff it.

  • Discord caps customId length at 100 characters - 10 characters are used for matching the method ID
  • You payload is JSON.stringified - If your payload doesn't fit, string compression is attempted
  • No runtime type validation. Not zod. - fn.length (num of JSON.parsed params) must match method

Why does this exist?

  • Why not store interaction data in a database?
  • Why not just do
// creating component
db.set(customId, payload)
// handling component interaction
db.get(customId)

Because you might

  1. Want structure + intelisense for handling complex interactions
  2. Want to persist data within discord
  3. Have a value too tiny for a db. Why waste a roundtrip?
0.1.30

1 year ago

0.1.31

1 year ago

0.1.27

1 year ago

0.1.28

1 year ago

0.1.29

1 year ago

0.1.23

1 year ago

0.1.24

1 year ago

0.1.25

1 year ago

0.1.26

1 year ago

0.1.21

2 years ago

0.1.22

2 years ago

0.1.20

2 years ago

0.1.19

2 years ago

0.1.18

2 years ago

0.1.17

2 years ago

0.1.16

2 years ago

0.1.15

2 years ago

0.1.14

2 years ago

0.1.13

2 years ago

0.1.12

2 years ago

0.1.11

2 years ago

0.1.10

2 years ago

0.1.9

2 years ago

0.1.8

2 years ago

0.1.7

2 years ago

0.1.6

2 years ago

0.1.5

2 years ago

0.1.4

2 years ago

0.1.3

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago