router!: chain .required() and .optional() rather than having required in options
vi did:web:vt3e.cat
Wed, 08 Jul 2026 23:01:16 +0100
3 files changed,
41 insertions(+),
22 deletions(-)
M
.gitignore
→
.gitignore
@@ -1,5 +1,6 @@
# dependencies (bun install) node_modules +local # output out
M
pkgs/router/GET-STARTED.md
→
pkgs/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.ts
→
pkgs/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 = {