mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-03 01:10:07 +00:00
feat(types/util): add case utility types
This commit is contained in:
@@ -1,166 +0,0 @@
|
||||
# Discordeno Typings Guidelines / Explanations
|
||||
|
||||
Discordeno has a certain standard guidelines for our typings. Please follow this
|
||||
as best as possible when contributing to the library.
|
||||
|
||||
1. Discordeno Types
|
||||
|
||||
These types will be specifically used by the end user for functions inside
|
||||
Discordeno.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
interface EditMemberOptions {
|
||||
/** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */
|
||||
nick?: string;
|
||||
/** Array of role ids the member will have after this edit. Useful for adding/removing multiple roles in 1 API call. Requires MANAGE_ROLES permission. */
|
||||
roles?: string[];
|
||||
/** Whether the user is muted in voice channels. Requires MUTE_MEMBERS permission. */
|
||||
mute?: boolean;
|
||||
/** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */
|
||||
deaf?: boolean;
|
||||
/** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */
|
||||
channelID?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Camel Case
|
||||
- Do not allow `null`
|
||||
- Everything should have a comment explaining it
|
||||
- These typings should be kept in the file with the function.
|
||||
- Example: EditMemberOptions is at the bottom of the file where editMember()
|
||||
is declared.
|
||||
|
||||
2. Discord Types Incoming
|
||||
|
||||
These types are meant for the payloads that we receive from Discord, whether
|
||||
through gateway or REST.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export interface MemberCreatePayload {
|
||||
/** The user this guild member represents */
|
||||
user: UserPayload;
|
||||
/** The user's guild nickname if one is set. */
|
||||
nick?: string;
|
||||
/** Array of role ids that the member has */
|
||||
roles: string[];
|
||||
/** When the user joined the guild. */
|
||||
joined_at: string;
|
||||
/** When the user used their nitro boost on the server. */
|
||||
premium_since?: string;
|
||||
/** Whether the user is deafened in voice channels */
|
||||
deaf: boolean;
|
||||
/** Whether the user is muted in voice channels */
|
||||
mute: boolean;
|
||||
/** Whether the user has passed the guild's Membership Screening requirements */
|
||||
pending?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Snake case (Or whatever discord uses. Everything here should be letter to
|
||||
letter in accordance with discord's docs.)
|
||||
- Everything should have a comment explaining it
|
||||
- Kept in the src/types/api/incoming folder
|
||||
|
||||
3. Discord Types Outgoing
|
||||
|
||||
These types are meant **US** as we develop Discordeno. These will help us
|
||||
prevent bugs when we are sending payloads to Discord whether through gateway or
|
||||
REST.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export interface EditMemberPayload {
|
||||
/** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */
|
||||
nick?: string;
|
||||
/** Array of role ids the member is assigned. Requires MANAGE_ROLES permission. */
|
||||
roles?: string[];
|
||||
/** Whether the user is muted in voice channels. Requires MUTE_MEMBERS permission. */
|
||||
mute?: boolean;
|
||||
/** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */
|
||||
deaf?: boolean;
|
||||
/** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */
|
||||
channel_id?: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Snake case (Or whatever discord uses. Everything here should be letter to
|
||||
letter in accordance with discord's docs.)
|
||||
- Everything should have a comment explaining it
|
||||
- Kept in the src/types/api/outgoing folder
|
||||
|
||||
## Minimalistic
|
||||
|
||||
Since Discord uses `snake_case` but we use `camelCase` we will end up with
|
||||
redundant typings. In order to solve this, we have the `Camelize<T>` type which
|
||||
helps create a type using the snake case.
|
||||
|
||||
If we have the base payload for Discord's `User` as follows:
|
||||
|
||||
```ts
|
||||
export interface DiscordUserPayload {
|
||||
/** The user's id */
|
||||
id: string;
|
||||
/** the user's username, not unique across the platform */
|
||||
username: string;
|
||||
/** The user's 4 digit discord tag */
|
||||
discriminator: string;
|
||||
/** The user's avatar hash */
|
||||
avatar: string | null;
|
||||
/** Whether the user is a bot */
|
||||
bot?: boolean;
|
||||
/** Whether the user is an official discord system user (part of the urgent message system.) */
|
||||
system?: boolean;
|
||||
/** Whether the user has two factor enabled on their account */
|
||||
"mfa_enabled"?: boolean;
|
||||
/** the user's chosen language option */
|
||||
locale?: string;
|
||||
/** Whether the email on this account has been verified */
|
||||
verified?: boolean;
|
||||
/** The user's email */
|
||||
email?: string;
|
||||
/** The flags on a user's account. */
|
||||
flags?: number;
|
||||
/** The type of Nitro subscription on a user's account. */
|
||||
premium_type?: number;
|
||||
}
|
||||
```
|
||||
|
||||
To create the Discordeno version for this it would be done as:
|
||||
|
||||
```ts
|
||||
export interface UserPayload extends Camelize<DiscordUserPayload> {}
|
||||
```
|
||||
|
||||
Now we have 2 unique interfaces, without having all the extra work or headaches
|
||||
of maintaing 2 different interfaces.
|
||||
|
||||
Similarily, for any outgoing Discord types, we can also camelize them to have
|
||||
better user experience for users.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export interface DiscordBanOptions {
|
||||
/** number of days to delete messages for (0-7) */
|
||||
delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
/** The reason for the ban. */
|
||||
reason?: string;
|
||||
}
|
||||
```
|
||||
|
||||
To create the Discordeno version for this it would be done as:
|
||||
|
||||
```ts
|
||||
export interface BanOptions extends Camelize<DiscordBanOptions> {}
|
||||
```
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
PartialMessage,
|
||||
ReactionPayload,
|
||||
} from "./message.ts";
|
||||
import { Camelize } from "./util.ts";
|
||||
import { CamelCase } from "./util.ts";
|
||||
|
||||
export interface BotConfig {
|
||||
token: string;
|
||||
@@ -93,15 +93,15 @@ export interface EventHandlers {
|
||||
rateLimit?: (data: RateLimitData) => unknown;
|
||||
/** Sent when a new Slash Command is created, relevant to the current user. */
|
||||
applicationCommandCreate?: (
|
||||
data: Camelize<ApplicationCommandEvent>,
|
||||
data: CamelCase<ApplicationCommandEvent>,
|
||||
) => unknown;
|
||||
/** Sent when a Slash Command relevant to the current user is updated. */
|
||||
applicationCommandUpdate?: (
|
||||
data: Camelize<ApplicationCommandEvent>,
|
||||
data: CamelCase<ApplicationCommandEvent>,
|
||||
) => unknown;
|
||||
/** Sent when a Slash Command relevant to the current user is deleted. */
|
||||
applicationCommandDelete?: (
|
||||
data: Camelize<ApplicationCommandEvent>,
|
||||
data: CamelCase<ApplicationCommandEvent>,
|
||||
) => unknown;
|
||||
/** Sent when properties about the user change. */
|
||||
botUpdate?: (user: UserPayload) => unknown;
|
||||
@@ -242,15 +242,19 @@ export interface EventHandlers {
|
||||
/** Sent when a member has passed the guild's Membership Screening requirements */
|
||||
membershipScreeningPassed?: (guild: Guild, member: Member) => unknown;
|
||||
/** Sent when an integration is created on a server such as twitch, youtube etc.. */
|
||||
integrationCreate?: (data: Camelize<IntegrationCreateUpdateEvent>) => unknown;
|
||||
integrationCreate?: (
|
||||
data: CamelCase<IntegrationCreateUpdateEvent>,
|
||||
) => unknown;
|
||||
/** Sent when an integration is updated. */
|
||||
integrationUpdate?: (data: Camelize<IntegrationCreateUpdateEvent>) => unknown;
|
||||
integrationUpdate?: (
|
||||
data: CamelCase<IntegrationCreateUpdateEvent>,
|
||||
) => unknown;
|
||||
/** Sent when an integration is deleted. */
|
||||
integrationDelete?: (data: Camelize<IntegrationDeleteEvent>) => undefined;
|
||||
integrationDelete?: (data: CamelCase<IntegrationDeleteEvent>) => undefined;
|
||||
/** Sent when a new invite to a channel is created. */
|
||||
inviteCreate?: (data: Camelize<InviteCreateEvent>) => unknown;
|
||||
inviteCreate?: (data: CamelCase<InviteCreateEvent>) => unknown;
|
||||
/** Sent when an invite is deleted. */
|
||||
inviteDelete?: (data: Camelize<InviteDeleteEvent>) => unknown;
|
||||
inviteDelete?: (data: CamelCase<InviteDeleteEvent>) => unknown;
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/topics/gateway#list-of-intents */
|
||||
|
||||
@@ -1,10 +1,123 @@
|
||||
export type CamelizeString<T extends PropertyKey> = T extends string
|
||||
? string extends T ? string
|
||||
: T extends `${infer F}_${infer R}`
|
||||
? `${F}${T extends `${infer F}_id` ? Uppercase<R>
|
||||
: Capitalize<CamelizeString<R>>}`
|
||||
: T
|
||||
: T;
|
||||
type UpperCaseCharacters =
|
||||
| "A"
|
||||
| "B"
|
||||
| "C"
|
||||
| "D"
|
||||
| "E"
|
||||
| "F"
|
||||
| "G"
|
||||
| "H"
|
||||
| "I"
|
||||
| "J"
|
||||
| "K"
|
||||
| "L"
|
||||
| "M"
|
||||
| "N"
|
||||
| "O"
|
||||
| "P"
|
||||
| "Q"
|
||||
| "R"
|
||||
| "S"
|
||||
| "T"
|
||||
| "U"
|
||||
| "V"
|
||||
| "W"
|
||||
| "X"
|
||||
| "Y"
|
||||
| "Z";
|
||||
|
||||
// deno-fmt-ignore
|
||||
export type Camelize<T> = { [K in keyof T as CamelizeString<K>]: T[K] };
|
||||
type WordSeparators = "-" | "_" | " ";
|
||||
|
||||
type SplitIncludingDelimiters<
|
||||
Source extends string,
|
||||
Delimiter extends string,
|
||||
> = Source extends "" ? []
|
||||
: Source extends `${infer FirstPart}${Delimiter}${infer SecondPart}` ? (
|
||||
Source extends `${FirstPart}${infer UsedDelimiter}${SecondPart}`
|
||||
? UsedDelimiter extends Delimiter
|
||||
? Source extends `${infer FirstPart}${UsedDelimiter}${infer SecondPart}`
|
||||
? [
|
||||
...SplitIncludingDelimiters<FirstPart, Delimiter>,
|
||||
UsedDelimiter,
|
||||
...SplitIncludingDelimiters<SecondPart, Delimiter>,
|
||||
]
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
)
|
||||
: [Source];
|
||||
|
||||
type StringPartToDelimiterCase<
|
||||
StringPart extends string,
|
||||
UsedWordSeparators extends string,
|
||||
UsedUpperCaseCharacters extends string,
|
||||
Delimiter extends string,
|
||||
> = StringPart extends UsedWordSeparators ? Delimiter
|
||||
: StringPart extends UsedUpperCaseCharacters
|
||||
? `${Delimiter}${Lowercase<StringPart>}`
|
||||
: StringPart;
|
||||
|
||||
type StringArrayToDelimiterCase<
|
||||
Parts extends any[],
|
||||
UsedWordSeparators extends string,
|
||||
UsedUpperCaseCharacters extends string,
|
||||
Delimiter extends string,
|
||||
> = Parts extends [`${infer FirstPart}`, ...infer RemainingParts]
|
||||
? `${StringPartToDelimiterCase<
|
||||
FirstPart,
|
||||
UsedWordSeparators,
|
||||
UsedUpperCaseCharacters,
|
||||
Delimiter
|
||||
>}${StringArrayToDelimiterCase<
|
||||
RemainingParts,
|
||||
UsedWordSeparators,
|
||||
UsedUpperCaseCharacters,
|
||||
Delimiter
|
||||
>}`
|
||||
: "";
|
||||
|
||||
type DelimiterCase<Value, Delimiter extends string> = Value extends string
|
||||
? StringArrayToDelimiterCase<
|
||||
SplitIncludingDelimiters<Value, WordSeparators | UpperCaseCharacters>,
|
||||
WordSeparators,
|
||||
UpperCaseCharacters,
|
||||
Delimiter
|
||||
>
|
||||
: Value;
|
||||
|
||||
type InnerCamelCaseStringArray<Parts extends any[], PreviousPart> =
|
||||
Parts extends [`${infer FirstPart}`, ...infer RemainingParts]
|
||||
? FirstPart extends undefined ? ""
|
||||
: FirstPart extends ""
|
||||
? InnerCamelCaseStringArray<RemainingParts, PreviousPart>
|
||||
: `${PreviousPart extends "" ? FirstPart
|
||||
: Capitalize<FirstPart>}${InnerCamelCaseStringArray<
|
||||
RemainingParts,
|
||||
FirstPart
|
||||
>}`
|
||||
: "";
|
||||
|
||||
type CamelCaseStringArray<Parts extends string[]> = Parts extends
|
||||
[`${infer FirstPart}`, ...infer RemainingParts] ? Uncapitalize<
|
||||
`${FirstPart}${InnerCamelCaseStringArray<RemainingParts, FirstPart>}`
|
||||
>
|
||||
: never;
|
||||
|
||||
type Split<S extends string, D extends string> = string extends S ? string[]
|
||||
: S extends "" ? []
|
||||
: S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>]
|
||||
: [S];
|
||||
|
||||
export type SnakeCase<Value> = DelimiterCase<Value, "_">;
|
||||
|
||||
export type CamelCase<K> = K extends string
|
||||
? CamelCaseStringArray<Split<K, WordSeparators>>
|
||||
: K;
|
||||
|
||||
export type CamelCasedProps<T> = {
|
||||
[K in keyof T as CamelCase<K>]: T[K];
|
||||
};
|
||||
|
||||
export type SnakeCasedProps<T> = {
|
||||
[K in keyof T as SnakeCase<K>]: T[K];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user