0.1.31 • Published 1 year ago
tinytrpc v0.1.31
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
- Want structure + intelisense for handling complex interactions
- Want to persist data within discord
- 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