types(util): add case utility types (#683)

* types(util): add case utility types

* Rename *ID to *Id
This commit is contained in:
ayntee
2021-03-23 19:04:46 +04:00
committed by GitHub
parent 6ff4654686
commit 10cb9d7eb9
10 changed files with 161 additions and 48 deletions
+2 -2
View File
@@ -66,9 +66,9 @@ Examples of bad PR title:
Example:
```ts
// Discordeno has utility type Camelize<T>, where T is an interface with keys in snake case.
// Discordeno has utility type CamelCaseProps<T>, where T is an interface with keys in snake case.
// It can be used to "generate" corresponding "Discordeno type" from "Discord type".
// Example: export type BanOptions = Camelize<DiscordBanOptions>
// Example: export type BanOptions = CamelCaseProps<DiscordBanOptions>
export interface EditMemberOptions {
/** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */
@@ -5,14 +5,14 @@ export function handleApplicationCommandCreate(
data: DiscordPayload,
) {
const {
guild_id: guildID,
application_id: applicationID,
guild_id: guildId,
application_id: applicationId,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandCreate?.({
...rest,
guildID,
applicationID,
guildId,
applicationId,
});
}
@@ -3,14 +3,14 @@ import { ApplicationCommandEvent, DiscordPayload } from "../../types/mod.ts";
export function handleApplicationCommandDelete(data: DiscordPayload) {
const {
application_id: applicationID,
guild_id: guildID,
application_id: applicationId,
guild_id: guildId,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandDelete?.({
...rest,
guildID,
applicationID,
guildId,
applicationId,
});
}
@@ -3,14 +3,14 @@ import { ApplicationCommandEvent, DiscordPayload } from "../../types/mod.ts";
export function handleApplicationCommandUpdate(data: DiscordPayload) {
const {
application_id: applicationID,
guild_id: guildID,
application_id: applicationId,
guild_id: guildId,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandUpdate?.({
...rest,
guildID,
applicationID,
guildId,
applicationId,
});
}
@@ -8,24 +8,24 @@ export function handleIntegrationCreate(
data: DiscordPayload,
) {
const {
guild_id: guildID,
guild_id: guildId,
enable_emoticons: enableEmoticons,
expire_behavior: expireBehavior,
expire_grace_period: expireGracePeriod,
subscriber_count: subscriberCount,
role_id: roleID,
role_id: roleId,
synced_at: syncedAt,
...rest
} = data.d as IntegrationCreateUpdateEvent;
eventHandlers.integrationCreate?.({
...rest,
guildID,
guildId,
enableEmoticons,
expireBehavior,
expireGracePeriod,
syncedAt,
subscriberCount,
roleID,
roleId,
});
}
@@ -3,14 +3,14 @@ import { DiscordPayload, IntegrationDeleteEvent } from "../../types/mod.ts";
export function handleIntegrationDelete(data: DiscordPayload) {
const {
guild_id: guildID,
application_id: applicationID,
guild_id: guildId,
application_id: applicationId,
...rest
} = data.d as IntegrationDeleteEvent;
eventHandlers.integrationDelete?.({
...rest,
applicationID,
guildID,
applicationId,
guildId,
});
}
@@ -9,20 +9,20 @@ export function handleIntegrationUpdate(data: DiscordPayload) {
enable_emoticons: enableEmoticons,
expire_behavior: expireBehavior,
expire_grace_period: expireGracePeriod,
role_id: roleID,
role_id: roleId,
subscriber_count: subscriberCount,
synced_at: syncedAt,
guild_id: guildID,
guild_id: guildId,
...rest
} = data.d as IntegrationCreateUpdateEvent;
eventHandlers.integrationUpdate?.({
...rest,
guildID,
guildId,
subscriberCount,
enableEmoticons,
expireGracePeriod,
roleID,
roleId,
expireBehavior,
syncedAt,
});
+4 -4
View File
@@ -5,10 +5,10 @@ export function handleInviteCreate(payload: DiscordPayload) {
if (payload.t !== "INVITE_CREATE") return;
//TODO: replace with tocamelcase
const {
channel_id: channelID,
channel_id: channelId,
created_at: createdAt,
max_age: maxAge,
guild_id: guildID,
guild_id: guildId,
target_user: targetUser,
target_user_type: targetUserType,
max_uses: maxUses,
@@ -17,8 +17,8 @@ export function handleInviteCreate(payload: DiscordPayload) {
eventHandlers.inviteCreate?.({
...rest,
channelID,
guildID,
channelId,
guildId,
maxAge,
targetUser,
targetUserType,
+15 -9
View File
@@ -25,7 +25,7 @@ import {
PartialMessage,
ReactionPayload,
} from "./message.ts";
import { Camelize } from "./util.ts";
import { CamelCaseProps } 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: CamelCaseProps<ApplicationCommandEvent>,
) => unknown;
/** Sent when a Slash Command relevant to the current user is updated. */
applicationCommandUpdate?: (
data: Camelize<ApplicationCommandEvent>,
data: CamelCaseProps<ApplicationCommandEvent>,
) => unknown;
/** Sent when a Slash Command relevant to the current user is deleted. */
applicationCommandDelete?: (
data: Camelize<ApplicationCommandEvent>,
data: CamelCaseProps<ApplicationCommandEvent>,
) => unknown;
/** Sent when properties about the user change. */
botUpdate?: (user: UserPayload) => unknown;
@@ -242,15 +242,21 @@ 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: CamelCaseProps<IntegrationCreateUpdateEvent>,
) => unknown;
/** Sent when an integration is updated. */
integrationUpdate?: (data: Camelize<IntegrationCreateUpdateEvent>) => unknown;
integrationUpdate?: (
data: CamelCaseProps<IntegrationCreateUpdateEvent>,
) => unknown;
/** Sent when an integration is deleted. */
integrationDelete?: (data: Camelize<IntegrationDeleteEvent>) => undefined;
integrationDelete?: (
data: CamelCaseProps<IntegrationDeleteEvent>,
) => undefined;
/** Sent when a new invite to a channel is created. */
inviteCreate?: (data: Camelize<InviteCreateEvent>) => unknown;
inviteCreate?: (data: CamelCaseProps<InviteCreateEvent>) => unknown;
/** Sent when an invite is deleted. */
inviteDelete?: (data: Camelize<InviteDeleteEvent>) => unknown;
inviteDelete?: (data: CamelCaseProps<InviteDeleteEvent>) => unknown;
}
/** https://discord.com/developers/docs/topics/gateway#list-of-intents */
+116 -9
View File
@@ -1,10 +1,117 @@
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 CamelCaseProps<T> = {
[K in keyof T as CamelCase<K>]: T[K];
};
export type SnakeCaseProps<T> = {
[K in keyof T as SnakeCase<K>]: T[K];
};