mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-15 19:08:17 +00:00
feat(helpers,types)!: slash command localization (#2129)
* feat(helpers,types): slash command localization Discord has documented the slash command localization feature now. This adds the full functionality for those to Discordeno. Additionally a `Locales` has also been added to allow for better typing. Reference: https://github.com/discord/discord-api-docs/pull/4653 * suggestion * better locales type * f * fix serializing * stupid direct pushes * b
This commit is contained in:
@@ -69,6 +69,7 @@ import { transformStageInstance } from "./transformers/stageInstance.ts";
|
|||||||
import { StickerPack, transformSticker, transformStickerPack } from "./transformers/sticker.ts";
|
import { StickerPack, transformSticker, transformStickerPack } from "./transformers/sticker.ts";
|
||||||
import { GetGatewayBot, transformGatewayBot } from "./transformers/gatewayBot.ts";
|
import { GetGatewayBot, transformGatewayBot } from "./transformers/gatewayBot.ts";
|
||||||
import {
|
import {
|
||||||
|
DiscordApplicationCommandOptionChoice,
|
||||||
DiscordEmoji,
|
DiscordEmoji,
|
||||||
DiscordGatewayPayload,
|
DiscordGatewayPayload,
|
||||||
DiscordInteractionDataOption,
|
DiscordInteractionDataOption,
|
||||||
@@ -127,6 +128,10 @@ import { VoiceRegions } from "./transformers/voiceRegion.ts";
|
|||||||
import { GuildWidget } from "./transformers/widget.ts";
|
import { GuildWidget } from "./transformers/widget.ts";
|
||||||
import { StageInstance } from "./transformers/stageInstance.ts";
|
import { StageInstance } from "./transformers/stageInstance.ts";
|
||||||
import { Sticker } from "./transformers/sticker.ts";
|
import { Sticker } from "./transformers/sticker.ts";
|
||||||
|
import {
|
||||||
|
ApplicationCommandOptionChoice,
|
||||||
|
transformApplicationCommandOptionChoice,
|
||||||
|
} from "./transformers/applicationCommandOptionChoice.ts";
|
||||||
import { transformEmbedToDiscordEmbed } from "./transformers/reverse/embed.ts";
|
import { transformEmbedToDiscordEmbed } from "./transformers/reverse/embed.ts";
|
||||||
import { transformComponentToDiscordComponent } from "./transformers/reverse/component.ts";
|
import { transformComponentToDiscordComponent } from "./transformers/reverse/component.ts";
|
||||||
|
|
||||||
@@ -421,6 +426,10 @@ export interface Transformers {
|
|||||||
stageInstance: (bot: Bot, payload: DiscordStageInstance) => StageInstance;
|
stageInstance: (bot: Bot, payload: DiscordStageInstance) => StageInstance;
|
||||||
sticker: (bot: Bot, payload: DiscordSticker) => Sticker;
|
sticker: (bot: Bot, payload: DiscordSticker) => Sticker;
|
||||||
stickerPack: (bot: Bot, payload: DiscordStickerPack) => StickerPack;
|
stickerPack: (bot: Bot, payload: DiscordStickerPack) => StickerPack;
|
||||||
|
applicationCommandOptionChoice: (
|
||||||
|
bot: Bot,
|
||||||
|
payload: DiscordApplicationCommandOptionChoice,
|
||||||
|
) => ApplicationCommandOptionChoice;
|
||||||
template: (bot: Bot, payload: DiscordTemplate) => Template;
|
template: (bot: Bot, payload: DiscordTemplate) => Template;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,6 +476,7 @@ export function createTransformers(options: Partial<Transformers>) {
|
|||||||
sticker: options.sticker || transformSticker,
|
sticker: options.sticker || transformSticker,
|
||||||
stickerPack: options.stickerPack || transformStickerPack,
|
stickerPack: options.stickerPack || transformStickerPack,
|
||||||
gatewayBot: options.gatewayBot || transformGatewayBot,
|
gatewayBot: options.gatewayBot || transformGatewayBot,
|
||||||
|
applicationCommandOptionChoice: options.applicationCommandOptionChoice || transformApplicationCommandOptionChoice,
|
||||||
template: options.template || transformTemplate,
|
template: options.template || transformTemplate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Bot } from "../../../bot.ts";
|
import type { Bot } from "../../../bot.ts";
|
||||||
import { ApplicationCommandOption, ApplicationCommandTypes } from "../../../mod.ts";
|
import { ApplicationCommandOption, ApplicationCommandTypes, Localization } from "../../../mod.ts";
|
||||||
import { DiscordApplicationCommand, DiscordApplicationCommandOption } from "../../../types/discord.ts";
|
import { DiscordApplicationCommand, DiscordApplicationCommandOption } from "../../../types/discord.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,9 +24,13 @@ export async function createApplicationCommand(
|
|||||||
guildId
|
guildId
|
||||||
? bot.constants.endpoints.COMMANDS_GUILD(bot.applicationId, guildId)
|
? bot.constants.endpoints.COMMANDS_GUILD(bot.applicationId, guildId)
|
||||||
: bot.constants.endpoints.COMMANDS(bot.applicationId),
|
: bot.constants.endpoints.COMMANDS(bot.applicationId),
|
||||||
isContextApplicationCommand(options) ? { name: options.name, type: options.type } : {
|
isContextApplicationCommand(options)
|
||||||
|
? { name: options.name, name_localizations: options.nameLocalizations, type: options.type }
|
||||||
|
: {
|
||||||
name: options.name,
|
name: options.name,
|
||||||
|
name_localizations: options.nameLocalizations,
|
||||||
description: options.description,
|
description: options.description,
|
||||||
|
description_localizations: options.descriptionLocalizations,
|
||||||
type: options.type,
|
type: options.type,
|
||||||
options: options.options ? makeOptionsForCommand(options.options) : undefined,
|
options: options.options ? makeOptionsForCommand(options.options) : undefined,
|
||||||
},
|
},
|
||||||
@@ -39,9 +43,15 @@ export function makeOptionsForCommand(options: ApplicationCommandOption[]): Disc
|
|||||||
return options.map((option) => ({
|
return options.map((option) => ({
|
||||||
type: option.type,
|
type: option.type,
|
||||||
name: option.name,
|
name: option.name,
|
||||||
|
name_localizations: option.nameLocalizations,
|
||||||
description: option.description,
|
description: option.description,
|
||||||
|
description_localizations: option.descriptionLocalizations,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
choices: option.choices,
|
choices: option.choices?.map((choice) => ({
|
||||||
|
name: choice.name,
|
||||||
|
name_localizations: choice.nameLocalizations,
|
||||||
|
value: choice.value,
|
||||||
|
})),
|
||||||
options: option.options ? makeOptionsForCommand(option.options) : undefined,
|
options: option.options ? makeOptionsForCommand(option.options) : undefined,
|
||||||
channel_types: option.channelTypes,
|
channel_types: option.channelTypes,
|
||||||
autocomplete: option.autocomplete,
|
autocomplete: option.autocomplete,
|
||||||
@@ -50,12 +60,16 @@ export function makeOptionsForCommand(options: ApplicationCommandOption[]): Disc
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command-json-params */
|
/** https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params */
|
||||||
export interface CreateApplicationCommand {
|
export interface CreateApplicationCommand {
|
||||||
/** 1-31 character name matching lowercase `^[\w-]{1,32}$` */
|
/** 1-31 character name matching lowercase `^[\w-]{1,32}$` */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
nameLocalizations?: Localization;
|
||||||
/** 1-100 character description */
|
/** 1-100 character description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Localization object for the `description` field. Values follow the same restrictions as `description` */
|
||||||
|
descriptionLocalizations?: Localization;
|
||||||
/** The type of the command */
|
/** The type of the command */
|
||||||
type?: ApplicationCommandTypes;
|
type?: ApplicationCommandTypes;
|
||||||
/** The parameters for the command */
|
/** The parameters for the command */
|
||||||
@@ -64,10 +78,12 @@ export interface CreateApplicationCommand {
|
|||||||
defaultPermission?: boolean;
|
defaultPermission?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command-json-params */
|
/** https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params */
|
||||||
export interface CreateContextApplicationCommand {
|
export interface CreateContextApplicationCommand {
|
||||||
/** 1-31 character name matching lowercase `^[\w-]{1,32}$` */
|
/** 1-31 character name matching lowercase `^[\w-]{1,32}$` */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
nameLocalizations?: Localization;
|
||||||
/** The type of the command */
|
/** The type of the command */
|
||||||
type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User;
|
type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,30 @@ import type { Bot } from "../../../bot.ts";
|
|||||||
import { DiscordApplicationCommand } from "../../../types/discord.ts";
|
import { DiscordApplicationCommand } from "../../../types/discord.ts";
|
||||||
|
|
||||||
/** Fetches the global command for the given Id. If a guildId is provided, the guild command will be fetched. */
|
/** Fetches the global command for the given Id. If a guildId is provided, the guild command will be fetched. */
|
||||||
export async function getApplicationCommand(bot: Bot, commandId: bigint, guildId?: bigint) {
|
export async function getApplicationCommand(bot: Bot, commandId: bigint, options?: GetApplicationCommand) {
|
||||||
|
let url = `${
|
||||||
|
options?.guildId
|
||||||
|
? bot.constants.endpoints.COMMANDS_GUILD_ID(bot.applicationId, options.guildId, commandId)
|
||||||
|
: bot.constants.endpoints.COMMANDS_ID(bot.applicationId, commandId)
|
||||||
|
}?`;
|
||||||
|
|
||||||
|
if (options?.withLocalizations !== undefined) {
|
||||||
|
url += `with_localizations=${options.withLocalizations}`;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await bot.rest.runMethod<DiscordApplicationCommand>(
|
const result = await bot.rest.runMethod<DiscordApplicationCommand>(
|
||||||
bot.rest,
|
bot.rest,
|
||||||
"get",
|
"get",
|
||||||
guildId
|
url,
|
||||||
? bot.constants.endpoints.COMMANDS_GUILD_ID(bot.applicationId, guildId, commandId)
|
|
||||||
: bot.constants.endpoints.COMMANDS_ID(bot.applicationId, commandId),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return bot.transformers.applicationCommand(bot, result);
|
return bot.transformers.applicationCommand(bot, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** https://discord.com/developers/docs/interactions/application-commands#endpoints-query-string-params */
|
||||||
|
export interface GetApplicationCommand {
|
||||||
|
/** Guild ID of the guild in which the command is available if it is a guild-specific command */
|
||||||
|
guildId?: bigint;
|
||||||
|
/** Whether to include full localization object (`name_localizations` and `description_localizations`) in the returned objects, instead of the `name_localized` and `description_localized` fields. Default false */
|
||||||
|
withLocalizations?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ export function transformApplicationCommand(bot: Bot, payload: DiscordApplicatio
|
|||||||
applicationId: bot.transformers.snowflake(payload.application_id),
|
applicationId: bot.transformers.snowflake(payload.application_id),
|
||||||
guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined,
|
guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined,
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
|
nameLocalizations: payload.name_localizations,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
|
descriptionLocalizations: payload.description_localizations,
|
||||||
defaultPermission: payload.default_permission ?? false,
|
defaultPermission: payload.default_permission ?? false,
|
||||||
type: payload.type,
|
type: payload.type,
|
||||||
version: payload.version,
|
version: payload.version,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Bot } from "../bot.ts";
|
import { Bot } from "../bot.ts";
|
||||||
import { DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice } from "../types/discord.ts";
|
import { DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice } from "../types/discord.ts";
|
||||||
import { ApplicationCommandOptionTypes, ChannelTypes } from "../types/shared.ts";
|
import { ApplicationCommandOptionTypes, ChannelTypes, Localization } from "../types/shared.ts";
|
||||||
import { Optionalize } from "../types/shared.ts";
|
import { Optionalize } from "../types/shared.ts";
|
||||||
|
import { ApplicationCommandOptionChoice } from "./applicationCommandOptionChoice.ts";
|
||||||
|
|
||||||
export function transformApplicationCommandOption(
|
export function transformApplicationCommandOption(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
@@ -10,9 +11,11 @@ export function transformApplicationCommandOption(
|
|||||||
return {
|
return {
|
||||||
type: payload.type,
|
type: payload.type,
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
|
nameLocalizations: payload.name_localizations,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
|
descriptionLocalizations: payload.description_localizations,
|
||||||
required: payload.required ?? false,
|
required: payload.required ?? false,
|
||||||
choices: payload.choices,
|
choices: payload.choices?.map((choice) => bot.transformers.applicationCommandOptionChoice(bot, choice)),
|
||||||
autocomplete: payload.autocomplete,
|
autocomplete: payload.autocomplete,
|
||||||
channelTypes: payload.channel_types,
|
channelTypes: payload.channel_types,
|
||||||
minValue: payload.min_value,
|
minValue: payload.min_value,
|
||||||
@@ -29,12 +32,16 @@ export interface ApplicationCommandOption {
|
|||||||
type: ApplicationCommandOptionTypes;
|
type: ApplicationCommandOptionTypes;
|
||||||
/** 1-32 character name matching lowercase `^[\w-]{1,32}$` */
|
/** 1-32 character name matching lowercase `^[\w-]{1,32}$` */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
nameLocalizations?: Localization;
|
||||||
/** 1-100 character description */
|
/** 1-100 character description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Localization object for the `description` field. Values follow the same restrictions as `description` */
|
||||||
|
descriptionLocalizations?: Localization;
|
||||||
/** If the parameter is required or optional--default `false` */
|
/** If the parameter is required or optional--default `false` */
|
||||||
required: boolean;
|
required: boolean;
|
||||||
/** Choices for `string` and `int` types for the user to pick from */
|
/** Choices for `string` and `int` types for the user to pick from */
|
||||||
choices?: DiscordApplicationCommandOptionChoice[];
|
choices?: ApplicationCommandOptionChoice[];
|
||||||
/** If the option is a subcommand or subcommand group type, this nested options will be the parameters */
|
/** If the option is a subcommand or subcommand group type, this nested options will be the parameters */
|
||||||
options?: ApplicationCommandOption[];
|
options?: ApplicationCommandOption[];
|
||||||
/** if autocomplete interactions are enabled for this `String`, `Integer`, or `Number` type option */
|
/** if autocomplete interactions are enabled for this `String`, `Integer`, or `Number` type option */
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Bot } from "../bot.ts";
|
||||||
|
import { DiscordApplicationCommandOptionChoice } from "../types/discord.ts";
|
||||||
|
import { Camelize, Localization, Optionalize } from "../types/shared.ts";
|
||||||
|
|
||||||
|
export function transformApplicationCommandOptionChoice(bot: Bot, payload: DiscordApplicationCommandOptionChoice) {
|
||||||
|
const applicationCommandChoice = {
|
||||||
|
name: payload.name,
|
||||||
|
nameLocalizations: payload.name_localizations,
|
||||||
|
value: payload.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return applicationCommandChoice as Optionalize<typeof applicationCommandChoice>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApplicationCommandOptionChoice extends ReturnType<typeof transformApplicationCommandOptionChoice> {}
|
||||||
+16
-4
@@ -16,6 +16,8 @@ import {
|
|||||||
GuildNsfwLevel,
|
GuildNsfwLevel,
|
||||||
IntegrationExpireBehaviors,
|
IntegrationExpireBehaviors,
|
||||||
InteractionTypes,
|
InteractionTypes,
|
||||||
|
Locales,
|
||||||
|
Localization,
|
||||||
MessageActivityTypes,
|
MessageActivityTypes,
|
||||||
MessageComponentTypes,
|
MessageComponentTypes,
|
||||||
MessageTypes,
|
MessageTypes,
|
||||||
@@ -1633,7 +1635,7 @@ export interface DiscordInviteStageInstance {
|
|||||||
topic: string;
|
topic: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommand */
|
/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure */
|
||||||
export interface DiscordApplicationCommand {
|
export interface DiscordApplicationCommand {
|
||||||
/** Unique id of the command */
|
/** Unique id of the command */
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1643,8 +1645,12 @@ export interface DiscordApplicationCommand {
|
|||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
/** 1-32 character name matching */
|
/** 1-32 character name matching */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
name_localizations?: Localization;
|
||||||
/** 1-100 character description */
|
/** 1-100 character description */
|
||||||
description?: string;
|
description: string;
|
||||||
|
/** Localization object for the `description` field. Values follow the same restrictions as `description` */
|
||||||
|
description_localizations?: Localization;
|
||||||
/** The parameters for the command */
|
/** The parameters for the command */
|
||||||
options?: DiscordApplicationCommandOption[];
|
options?: DiscordApplicationCommandOption[];
|
||||||
/** Whether the command is enbaled by default when the app is added to a guild */
|
/** Whether the command is enbaled by default when the app is added to a guild */
|
||||||
@@ -1655,14 +1661,18 @@ export interface DiscordApplicationCommand {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption */
|
/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure */
|
||||||
export interface DiscordApplicationCommandOption {
|
export interface DiscordApplicationCommandOption {
|
||||||
/** Value of Application Command Option Type */
|
/** Value of Application Command Option Type */
|
||||||
type: ApplicationCommandOptionTypes;
|
type: ApplicationCommandOptionTypes;
|
||||||
/** 1-32 character name matching lowercase `^[\w-]{1,32}$` */
|
/** 1-32 character name matching lowercase `^[\w-]{1,32}$` */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
name_localizations?: Localization;
|
||||||
/** 1-100 character description */
|
/** 1-100 character description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Localization object for the `description` field. Values follow the same restrictions as `description` */
|
||||||
|
description_localizations?: Localization;
|
||||||
/** If the parameter is required or optional--default `false` */
|
/** If the parameter is required or optional--default `false` */
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
/** Choices for `string` and `int` types for the user to pick from */
|
/** Choices for `string` and `int` types for the user to pick from */
|
||||||
@@ -1679,10 +1689,12 @@ export interface DiscordApplicationCommandOption {
|
|||||||
max_value?: number;
|
max_value?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */
|
/** https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure */
|
||||||
export interface DiscordApplicationCommandOptionChoice {
|
export interface DiscordApplicationCommandOptionChoice {
|
||||||
/** 1-100 character choice name */
|
/** 1-100 character choice name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
||||||
|
name_localizations?: Localization;
|
||||||
/** Value of the choice, up to 100 characters if string */
|
/** Value of the choice, up to 100 characters if string */
|
||||||
value: string | number;
|
value: string | number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1240,6 +1240,41 @@ export enum Errors {
|
|||||||
YOU_CAN_NOT_DM_THE_BOT_ITSELF = "YOU_CAN_NOT_DM_THE_BOT_ITSELF",
|
YOU_CAN_NOT_DM_THE_BOT_ITSELF = "YOU_CAN_NOT_DM_THE_BOT_ITSELF",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Locales {
|
||||||
|
Danish = "da",
|
||||||
|
German = "de",
|
||||||
|
EnglishUk = "en-GB",
|
||||||
|
EnglishUs = "en-US",
|
||||||
|
Spanish = "es-ES",
|
||||||
|
French = "fr",
|
||||||
|
Croatian = "hr",
|
||||||
|
Italian = "it",
|
||||||
|
Lithuanian = "lt",
|
||||||
|
Hungarian = "hu",
|
||||||
|
Dutch = "nl",
|
||||||
|
Norwegian = "no",
|
||||||
|
Polish = "pl",
|
||||||
|
PortugueseBrazilian = "pt-BR",
|
||||||
|
RomanianRomania = "ro",
|
||||||
|
Finnish = "fi",
|
||||||
|
Swedish = "sv-SE",
|
||||||
|
Vietnamese = "vi",
|
||||||
|
Turkish = "tr",
|
||||||
|
Czech = "cs",
|
||||||
|
Greek = "el",
|
||||||
|
Bulgarian = "bg",
|
||||||
|
Russian = "ru",
|
||||||
|
Ukrainian = "uk",
|
||||||
|
Hindi = "hi",
|
||||||
|
Thai = "th",
|
||||||
|
ChineseChina = "zh-CN",
|
||||||
|
Japanese = "ja",
|
||||||
|
ChineseTaiwan = "zh-TW",
|
||||||
|
Korean = "ko",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Localization = Partial<Record<Locales, string>>;
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
|
|
||||||
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||||
|
|||||||
Reference in New Issue
Block a user