diff --git a/packages/builders/src/components/Component.ts b/packages/builders/src/components/Component.ts index 9a9a59d12b3d..4efb352e77b4 100644 --- a/packages/builders/src/components/Component.ts +++ b/packages/builders/src/components/Component.ts @@ -1,5 +1,6 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIBaseComponent, ComponentType } from 'discord-api-types/v10'; +import { Refineable } from '../mixins/Refineable.js'; export interface ComponentBuilderBaseData { id?: number | undefined; @@ -11,6 +12,7 @@ export interface ComponentBuilderBaseData { * @typeParam Component - The type of API data that is stored within the builder */ export abstract class ComponentBuilder> + extends Refineable implements JSONEncodable { /** diff --git a/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts index de024c23e7ed..2af68d61a250 100644 --- a/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts +++ b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts @@ -1,12 +1,13 @@ import type { JSONEncodable } from '@discordjs/util'; -import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10'; +import type { APISelectMenuOption, APIMessageComponentEmoji } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { validate } from '../../util/validation.js'; import { selectMenuStringOptionPredicate } from '../Assertions.js'; /** * A builder that creates API-compatible JSON data for string select menu options. */ -export class StringSelectMenuOptionBuilder implements JSONEncodable { +export class StringSelectMenuOptionBuilder extends Refineable implements JSONEncodable { private readonly data: Partial; /** @@ -32,6 +33,7 @@ export class StringSelectMenuOptionBuilder implements JSONEncodable = {}) { + super(); this.data = structuredClone(data); } diff --git a/packages/builders/src/components/v2/MediaGalleryItem.ts b/packages/builders/src/components/v2/MediaGalleryItem.ts index e6762009188b..0c820b2a28b1 100644 --- a/packages/builders/src/components/v2/MediaGalleryItem.ts +++ b/packages/builders/src/components/v2/MediaGalleryItem.ts @@ -1,9 +1,10 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIMediaGalleryItem } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { validate } from '../../util/validation.js'; import { mediaGalleryItemPredicate } from './Assertions.js'; -export class MediaGalleryItemBuilder implements JSONEncodable { +export class MediaGalleryItemBuilder extends Refineable implements JSONEncodable { private readonly data: Partial; /** @@ -32,6 +33,7 @@ export class MediaGalleryItemBuilder implements JSONEncodable = {}) { + super(); this.data = structuredClone(data); } diff --git a/packages/builders/src/interactions/commands/Command.ts b/packages/builders/src/interactions/commands/Command.ts index 89016d18bf0a..b06cf5ba3d37 100644 --- a/packages/builders/src/interactions/commands/Command.ts +++ b/packages/builders/src/interactions/commands/Command.ts @@ -5,6 +5,7 @@ import type { Permissions, RESTPostAPIApplicationCommandsJSONBody, } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import type { RestOrArray } from '../../util/normalizeArray.js'; import { normalizeArray } from '../../util/normalizeArray.js'; @@ -20,6 +21,7 @@ export interface CommandData * The base class for all command builders. */ export abstract class CommandBuilder + extends Refineable implements JSONEncodable { /** diff --git a/packages/builders/src/interactions/commands/SharedName.ts b/packages/builders/src/interactions/commands/SharedName.ts index c9625bd180bb..6d3a2dd2eb91 100644 --- a/packages/builders/src/interactions/commands/SharedName.ts +++ b/packages/builders/src/interactions/commands/SharedName.ts @@ -1,4 +1,5 @@ import type { Locale, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; export interface SharedNameData extends Partial> {} @@ -6,7 +7,7 @@ export interface SharedNameData /** * This mixin holds name and description symbols for chat input commands. */ -export class SharedName { +export class SharedName extends Refineable { /** * @internal */ diff --git a/packages/builders/src/interactions/modals/Modal.ts b/packages/builders/src/interactions/modals/Modal.ts index 882125fe751b..755b8b0fed01 100644 --- a/packages/builders/src/interactions/modals/Modal.ts +++ b/packages/builders/src/interactions/modals/Modal.ts @@ -9,6 +9,7 @@ import type { AnyModalComponentBuilder } from '../../components/Components.js'; import { createComponentBuilder } from '../../components/Components.js'; import { LabelBuilder } from '../../components/label/Label.js'; import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js'; +import { Refineable } from '../../mixins/Refineable.js'; import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; import { resolveBuilder } from '../../util/resolveBuilder.js'; import { validate } from '../../util/validation.js'; @@ -21,7 +22,7 @@ export interface ModalBuilderData extends Partial { +export class ModalBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this modal. */ @@ -40,6 +41,7 @@ export class ModalBuilder implements JSONEncodable = {}) { + super(); const { components = [], ...rest } = data; this.data = { diff --git a/packages/builders/src/messages/AllowedMentions.ts b/packages/builders/src/messages/AllowedMentions.ts index 9a094a3f06ae..6e3f70dd55ad 100644 --- a/packages/builders/src/messages/AllowedMentions.ts +++ b/packages/builders/src/messages/AllowedMentions.ts @@ -1,5 +1,6 @@ import type { JSONEncodable } from '@discordjs/util'; import type { AllowedMentionsTypes, APIAllowedMentions, Snowflake } from 'discord-api-types/v10'; +import { Refineable } from '../mixins/Refineable.js'; import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js'; import { validate } from '../util/validation.js'; import { allowedMentionPredicate } from './Assertions.js'; @@ -7,7 +8,7 @@ import { allowedMentionPredicate } from './Assertions.js'; /** * A builder that creates API-compatible JSON data for allowed mentions. */ -export class AllowedMentionsBuilder implements JSONEncodable { +export class AllowedMentionsBuilder extends Refineable implements JSONEncodable { /** * The API data associated with these allowed mentions. */ @@ -19,7 +20,8 @@ export class AllowedMentionsBuilder implements JSONEncodable * @param data - The API data to create this allowed mentions with */ public constructor(data: Partial = {}) { - this.data = structuredClone(data); + super(); + this.data = { ...structuredClone(data) }; } /** diff --git a/packages/builders/src/messages/Attachment.ts b/packages/builders/src/messages/Attachment.ts index a6cdb58b798a..450cbc2f91ee 100644 --- a/packages/builders/src/messages/Attachment.ts +++ b/packages/builders/src/messages/Attachment.ts @@ -1,12 +1,13 @@ import type { JSONEncodable } from '@discordjs/util'; import type { RESTAPIAttachment, Snowflake } from 'discord-api-types/v10'; +import { Refineable } from '../mixins/Refineable.js'; import { validate } from '../util/validation.js'; import { attachmentPredicate } from './Assertions.js'; /** * A builder that creates API-compatible JSON data for attachments. */ -export class AttachmentBuilder implements JSONEncodable { +export class AttachmentBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this attachment. */ @@ -15,10 +16,11 @@ export class AttachmentBuilder implements JSONEncodable { /** * Creates a new attachment builder. * - * @param data - The API data to create this attachment with + * @param attachment - The attachment to build from */ - public constructor(data: Partial = {}) { - this.data = structuredClone(data); + public constructor(attachment: Partial = {}) { + super(); + this.data = { ...structuredClone(attachment) }; } /** diff --git a/packages/builders/src/messages/Message.ts b/packages/builders/src/messages/Message.ts index 1c7521e8a20a..dfddef06f1c3 100644 --- a/packages/builders/src/messages/Message.ts +++ b/packages/builders/src/messages/Message.ts @@ -28,6 +28,7 @@ import { MediaGalleryBuilder } from '../components/v2/MediaGallery.js'; import { SectionBuilder } from '../components/v2/Section.js'; import { SeparatorBuilder } from '../components/v2/Separator.js'; import { TextDisplayBuilder } from '../components/v2/TextDisplay.js'; +import { Refineable } from '../mixins/Refineable.js'; import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js'; import { resolveBuilder } from '../util/resolveBuilder.js'; import { validate } from '../util/validation.js'; @@ -56,7 +57,7 @@ export interface MessageBuilderData /** * A builder that creates API-compatible JSON data for messages. */ -export class MessageBuilder implements JSONEncodable { +export class MessageBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this message. */ @@ -89,6 +90,7 @@ export class MessageBuilder implements JSONEncodable = {}) { + super(); const { attachments = [], embeds = [], components = [], message_reference, poll, allowed_mentions, ...rest } = data; this.data = { diff --git a/packages/builders/src/messages/MessageReference.ts b/packages/builders/src/messages/MessageReference.ts index 5a026f5e7f43..c5914778fbf1 100644 --- a/packages/builders/src/messages/MessageReference.ts +++ b/packages/builders/src/messages/MessageReference.ts @@ -1,12 +1,13 @@ import type { JSONEncodable } from '@discordjs/util'; import type { MessageReferenceType, RESTAPIMessageReference, Snowflake } from 'discord-api-types/v10'; +import { Refineable } from '../mixins/Refineable.js'; import { validate } from '../util/validation.js'; import { messageReferencePredicate } from './Assertions.js'; /** * A builder that creates API-compatible JSON data for message references. */ -export class MessageReferenceBuilder implements JSONEncodable { +export class MessageReferenceBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this message reference. */ @@ -18,6 +19,7 @@ export class MessageReferenceBuilder implements JSONEncodable = {}) { + super(); this.data = structuredClone(data); } diff --git a/packages/builders/src/messages/embed/Embed.ts b/packages/builders/src/messages/embed/Embed.ts index 9fbaf2699d5d..7ab229a2f7a8 100644 --- a/packages/builders/src/messages/embed/Embed.ts +++ b/packages/builders/src/messages/embed/Embed.ts @@ -1,5 +1,6 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import type { RestOrArray } from '../../util/normalizeArray.js'; import { normalizeArray } from '../../util/normalizeArray.js'; import { resolveBuilder } from '../../util/resolveBuilder.js'; @@ -21,7 +22,7 @@ export interface EmbedBuilderData extends Omit { +export class EmbedBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this embed. */ @@ -40,6 +41,7 @@ export class EmbedBuilder implements JSONEncodable { * @param data - The API data to create this embed with */ public constructor(data: Partial = {}) { + super(); const { author, fields = [], footer, ...rest } = data; this.data = { diff --git a/packages/builders/src/messages/embed/EmbedAuthor.ts b/packages/builders/src/messages/embed/EmbedAuthor.ts index e39e1ed20e45..7faeeb400041 100644 --- a/packages/builders/src/messages/embed/EmbedAuthor.ts +++ b/packages/builders/src/messages/embed/EmbedAuthor.ts @@ -1,24 +1,26 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIEmbedAuthor } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { validate } from '../../util/validation.js'; import { embedAuthorPredicate } from './Assertions.js'; /** - * A builder that creates API-compatible JSON data for the embed author. + * A builder that creates API-compatible JSON data for embed authors. */ -export class EmbedAuthorBuilder implements JSONEncodable { +export class EmbedAuthorBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this embed author. */ private readonly data: Partial; /** - * Creates a new embed author. + * Creates a new embed author builder. * * @param data - The API data to create this embed author with */ public constructor(data: Partial = {}) { - this.data = structuredClone(data); + super(); + this.data = { ...structuredClone(data) }; } /** diff --git a/packages/builders/src/messages/embed/EmbedField.ts b/packages/builders/src/messages/embed/EmbedField.ts index 9466ca267968..76a4a91afac4 100644 --- a/packages/builders/src/messages/embed/EmbedField.ts +++ b/packages/builders/src/messages/embed/EmbedField.ts @@ -1,24 +1,26 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIEmbedField } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { validate } from '../../util/validation.js'; import { embedFieldPredicate } from './Assertions.js'; /** * A builder that creates API-compatible JSON data for embed fields. */ -export class EmbedFieldBuilder implements JSONEncodable { +export class EmbedFieldBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this embed field. */ private readonly data: Partial; /** - * Creates a new embed field. + * Creates a new embed field builder. * * @param data - The API data to create this embed field with */ public constructor(data: Partial = {}) { - this.data = structuredClone(data); + super(); + this.data = { ...structuredClone(data) }; } /** diff --git a/packages/builders/src/messages/embed/EmbedFooter.ts b/packages/builders/src/messages/embed/EmbedFooter.ts index 13da0d635e83..d357f8770195 100644 --- a/packages/builders/src/messages/embed/EmbedFooter.ts +++ b/packages/builders/src/messages/embed/EmbedFooter.ts @@ -1,24 +1,26 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIEmbedFooter } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { validate } from '../../util/validation.js'; import { embedFooterPredicate } from './Assertions.js'; /** - * A builder that creates API-compatible JSON data for the embed footer. + * A builder that creates API-compatible JSON data for embed footers. */ -export class EmbedFooterBuilder implements JSONEncodable { +export class EmbedFooterBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this embed footer. */ private readonly data: Partial; /** - * Creates a new embed footer. + * Creates a new embed footer builder. * * @param data - The API data to create this embed footer with */ public constructor(data: Partial = {}) { - this.data = structuredClone(data); + super(); + this.data = { ...structuredClone(data) }; } /** diff --git a/packages/builders/src/messages/poll/Poll.ts b/packages/builders/src/messages/poll/Poll.ts index 24549c69c75f..7766d8641663 100644 --- a/packages/builders/src/messages/poll/Poll.ts +++ b/packages/builders/src/messages/poll/Poll.ts @@ -1,5 +1,6 @@ import type { JSONEncodable } from '@discordjs/util'; import type { RESTAPIPoll, APIPollMedia, PollLayoutType, APIPollAnswer } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; import { resolveBuilder } from '../../util/resolveBuilder.js'; import { validate } from '../../util/validation.js'; @@ -15,7 +16,7 @@ export interface PollData extends Omit { /** * A builder that creates API-compatible JSON data for polls. */ -export class PollBuilder implements JSONEncodable { +export class PollBuilder extends Refineable implements JSONEncodable { /** * The API data associated with this poll. */ @@ -34,6 +35,7 @@ export class PollBuilder implements JSONEncodable { * @param data - The API data to create this poll with */ public constructor(data: Partial = {}) { + super(); const { question, answers = [], ...rest } = data; this.data = { diff --git a/packages/builders/src/messages/poll/PollAnswer.ts b/packages/builders/src/messages/poll/PollAnswer.ts index c0574d294017..f92507a6f3cf 100644 --- a/packages/builders/src/messages/poll/PollAnswer.ts +++ b/packages/builders/src/messages/poll/PollAnswer.ts @@ -1,5 +1,6 @@ import type { JSONEncodable } from '@discordjs/util'; import type { APIPollAnswer, APIPollMedia } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; import { resolveBuilder } from '../../util/resolveBuilder'; import { validate } from '../../util/validation'; import { pollAnswerPredicate } from './Assertions'; @@ -12,7 +13,7 @@ export interface PollAnswerData extends Omit> { +export class PollAnswerBuilder extends Refineable implements JSONEncodable> { /** * The API data associated with this poll answer. */ @@ -24,6 +25,7 @@ export class PollAnswerBuilder implements JSONEncodable> = {}) { + super(); const { poll_media, ...rest } = data; this.data = { diff --git a/packages/builders/src/messages/poll/PollMedia.ts b/packages/builders/src/messages/poll/PollMedia.ts index 30f11c7ac4d7..ea2119d8aed8 100644 --- a/packages/builders/src/messages/poll/PollMedia.ts +++ b/packages/builders/src/messages/poll/PollMedia.ts @@ -1,9 +1,10 @@ import type { APIPollMedia } from 'discord-api-types/v10'; +import { Refineable } from '../../mixins/Refineable.js'; /** * The base poll media builder that contains common symbols for poll media builders. */ -export abstract class PollMediaBuilder { +export abstract class PollMediaBuilder extends Refineable { /** * The API data associated with this poll media. * @@ -17,6 +18,7 @@ export abstract class PollMediaBuilder { * @param data - The API data to create this poll media with */ public constructor(data: Partial = {}) { + super(); this.data = structuredClone(data); } diff --git a/packages/builders/src/mixins/Refineable.ts b/packages/builders/src/mixins/Refineable.ts new file mode 100644 index 000000000000..075b8c4356c2 --- /dev/null +++ b/packages/builders/src/mixins/Refineable.ts @@ -0,0 +1,28 @@ +/** + * Mixin used to provide {@link Refineable.refine} + */ +export class Refineable { + /** + * Refines this builder by applying the provided function to itself. + * Useful for conditionally modifying the builder without breaking the method chain, while also isolating + * the scope of temporary variables. + * + * @example + * ```ts + * const builder = new EmbedBuilder() + * .setTitle('Hello World') + * .setDescription('This is a description') + * .refine((b) => { + * const intermediateValue = computeSomething(); + * b.setFooter(`Computed value: ${intermediateValue}`); + * if (externalVariable) { + * b.setColor(0xff0000); + * } + * }); + * ``` + */ + public refine(fn: (builder: this) => void): this { + fn(this); + return this; + } +}