all repos — kitten @ cc55514091d8d0ea86ffe7cf25645fee1768a697

router!: chain .required() and .optional() rather than having required in options
vi did:web:vt3e.cat
Wed, 08 Jul 2026 23:01:16 +0100
commit

cc55514091d8d0ea86ffe7cf25645fee1768a697

parent

8a511d99dc35b383139b6948bc1f4c265aff202c

3 files changed, 41 insertions(+), 22 deletions(-)

jump to
M .gitignore.gitignore

@@ -1,5 +1,6 @@

# dependencies (bun install) node_modules +local # output out
M pkgs/router/GET-STARTED.mdpkgs/router/GET-STARTED.md

@@ -57,8 +57,8 @@ ```ts

const whoisCommand = kitten.command("whois", { description: "get information about a user", options: { - user: option.user("the user to get information about", { required: true }), - ephemeral: option.boolean("whether the response should be ephemeral", { required: false }), + user: option.user.required("the user to get information about"), + ephemeral: option.boolean.optional("whether the response should be ephemeral"), }, async run(interaction, { user, ephemeral }) { await interaction.reply({

@@ -84,8 +84,8 @@

configCommand.subcommand("set", { description: "Set a configuration value", options: { - key: option.string("the setting key", { required: true }), - value: option.string("the value", { required: true }), + key: option.string.required("the setting key"), + value: option.string.required("the value"), }, async run(interaction, { key, value }) { await interaction.reply(`set ${key} to ${value}`);

@@ -179,7 +179,7 @@ return matches.map((key) => ({ name: key, value: key }));

}, }), - value: option.string("the value", { required: true }), + value: option.string.required("the value"), }, async run(interaction, { key, value }) { await interaction.reply(`set ${key} to ${value}`);

@@ -227,7 +227,7 @@ ```ts

const banCommand = authorised.command("ban", { description: "ban a user", options: { - user: option.user("the user to ban", { required: true }), + user: option.user.required("the user to ban"), }, async run(interaction, args, context) { // context: { user: { id: string; moderator: boolean; ... } }
M pkgs/router/src/options.tspkgs/router/src/options.ts

@@ -50,11 +50,33 @@ interaction: AutocompleteInteraction,

value: T, ) => Promise<ApplicationCommandOptionChoiceData[]> | ApplicationCommandOptionChoiceData[]; -const createOption = <T>(type: OptionType) => { - return (( +export interface OptionBuilder<T> { + ( + description: string, + config: { required: true; autocomplete?: AutocompleteFn<T> }, + ): CommandOption<T, true>; + ( + description: string, + config?: { required?: false; autocomplete?: AutocompleteFn<T> }, + ): CommandOption<T, false>; + (config: { required: true; autocomplete?: AutocompleteFn<T> }): Option<T, true>; + (config?: { required?: false; autocomplete?: AutocompleteFn<T> }): Option<T, false>; + + required( + description: string, + config?: { autocomplete?: AutocompleteFn<T> }, + ): CommandOption<T, true>; + optional( + description: string, + config?: { autocomplete?: AutocompleteFn<T> }, + ): CommandOption<T, false>; +} + +const createOption = <T>(type: OptionType): OptionBuilder<T> => { + const fn = ( descriptionOrConfig?: string | { required?: boolean; autocomplete?: AutocompleteFn<T> }, config?: { required?: boolean; autocomplete?: AutocompleteFn<T> }, - ): Option<T, any> => { + ): any => { const hasDescription = typeof descriptionOrConfig === "string"; const description = hasDescription ? descriptionOrConfig : undefined; const actualConfig = hasDescription ? config : descriptionOrConfig;

@@ -65,21 +87,17 @@ description,

required: actualConfig?.required ?? false, autocomplete: actualConfig?.autocomplete, }; - }) as { - // w/ description for commands - ( - description: string, - config: { required: true; autocomplete?: AutocompleteFn<T> }, - ): CommandOption<T, true>; - ( - description: string, - config?: { required?: false; autocomplete?: AutocompleteFn<T> }, - ): CommandOption<T, false>; + }; + + fn.required = (description: string, config?: { autocomplete?: AutocompleteFn<T> }) => { + return fn(description, { required: true, autocomplete: config?.autocomplete }); + }; - // w/out description for components - (config: { required: true; autocomplete?: AutocompleteFn<T> }): Option<T, true>; - (config?: { required?: false; autocomplete?: AutocompleteFn<T> }): Option<T, false>; + fn.optional = (description: string, config?: { autocomplete?: AutocompleteFn<T> }) => { + return fn(description, { required: false, autocomplete: config?.autocomplete }); }; + + return fn as OptionBuilder<T>; }; export const option = {