Skip to content

Commit

Permalink
refactor: add partial type parameters to structures (#116)
Browse files Browse the repository at this point in the history
* refactor: add partial type parameters to structures

* fix: correct some getter return types

* chore: add changeset
  • Loading branch information
apteryxxyz committed Sep 18, 2024
1 parent d9c2dda commit a0074f0
Show file tree
Hide file tree
Showing 23 changed files with 1,004 additions and 636 deletions.
5 changes: 5 additions & 0 deletions .changeset/tidy-chicken-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@buape/carbon": patch
---

refactor: add partial type parameters to structures to improve field types
87 changes: 52 additions & 35 deletions packages/carbon/src/abstracts/BaseChannel.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,79 @@
import {
type APIChannel,
type ChannelFlags,
type ChannelType,
Routes
} from "discord-api-types/v10"
import type { Client } from "../classes/Client.js"
import type { IfPartial } from "../utils.js"
import { Base } from "./Base.js"

export abstract class BaseChannel<Type extends ChannelType> extends Base {
/**
* The id of the channel.
*/
id: string
/**
* Whether the channel is a partial channel (meaning it does not have all the data).
* If this is true, you should use {@link BaseChannel.fetch} to get the full data of the channel.
*/
partial: boolean
/**
* The type of the channel.
*/
type?: Type
/**
* The flags of the channel in a bitfield.
* @see https://discord.com/developers/docs/resources/channel#channel-object-channel-flags
*/
flags?: number | null
/**
* The raw data of the channel.
*/
protected rawData: Extract<APIChannel, { type: Type }> | null = null

export abstract class BaseChannel<
Type extends ChannelType,
IsPartial extends boolean = false
> extends Base {
constructor(
client: Client,
rawDataOrId: Extract<APIChannel, { type: Type }> | string
rawDataOrId: IsPartial extends true
? string
: Extract<APIChannel, { type: Type }>
) {
super(client)
if (typeof rawDataOrId === "string") {
this.id = rawDataOrId
this.partial = true
} else {
this.rawData = rawDataOrId
this.rawData = rawDataOrId as never
this.id = rawDataOrId.id
this.partial = false
this.setData(rawDataOrId)
this.setData(rawDataOrId as never)
}
}

/**
* The raw data of the channel.
*/
protected rawData: Extract<APIChannel, { type: Type }> | null = null
protected setData(data: Extract<APIChannel, { type: Type }>) {
if (!data) throw new Error("Cannot set data without having data... smh")
this.rawData = data
this.type = data.type
this.partial = false
this.setSpecificData(data)
}
protected setField(
field: keyof Extract<APIChannel, { type: Type }>,
value: unknown
) {
if (!this.rawData)
throw new Error("Cannot set field without having data... smh")
this.rawData[field] = value
}

/**
* The id of the channel.
*/
readonly id: string

/**
* Whether the channel is a partial channel (meaning it does not have all the data).
* If this is true, you should use {@link BaseChannel.fetch} to get the full data of the channel.
*/
get partial(): IsPartial {
return (this.rawData === null) as never
}

/**
* The type of the channel.
*/
get type(): IfPartial<IsPartial, Type> {
if (!this.rawData) return undefined as never
return this.rawData.type
}

protected abstract setSpecificData(
data: Extract<APIChannel, { type: Type }>
): void
/**
* The flags of the channel in a bitfield.
* @see https://discord.com/developers/docs/resources/channel#channel-object-channel-flags
*/
get flags(): IfPartial<IsPartial, ChannelFlags | undefined> {
if (!this.rawData) return undefined as never
return this.rawData.flags
}

/**
* Fetches the channel from the API.
Expand Down
65 changes: 39 additions & 26 deletions packages/carbon/src/abstracts/BaseGuildChannel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
type APIChannel,
type APIGuildChannel,
type APIMessage,
type GuildChannelType,
Expand All @@ -10,49 +9,63 @@ import {
} from "discord-api-types/v10"
import { Guild } from "../structures/Guild.js"
import type { GuildCategoryChannel } from "../structures/GuildCategoryChannel.js"
import type { IfPartial } from "../utils.js"
import { BaseChannel } from "./BaseChannel.js"

export abstract class BaseGuildChannel<
Type extends GuildChannelType
> extends BaseChannel<Type> {
Type extends GuildChannelType,
IsPartial extends boolean = false
> extends BaseChannel<Type, IsPartial> {
// @ts-expect-error
declare rawData: APIGuildChannel<Type> | null

/**
* The name of the channel.
*/
name?: string
get name(): IfPartial<IsPartial, string> {
if (!this.rawData) return undefined as never
return this.rawData.name as never
}

/**
* The ID of the guild this channel is in
*/
guildId?: string
get guildId(): IfPartial<IsPartial, string> {
if (!this.rawData) return undefined as never
return this.rawData.guild_id as never
}

/**
* The position of the channel in the channel list.
*/
position?: number
get position(): IfPartial<IsPartial, number> {
if (!this.rawData) return undefined as never
return this.rawData.position
}

/**
* The ID of the parent category for the channel.
*/
parentId?: string | null
get parentId(): IfPartial<IsPartial, string | null> {
if (!this.rawData) return undefined as never
return this.rawData.parent_id ?? null
}

/**
* Whether the channel is marked as nsfw.
*/
nsfw?: boolean
get nsfw(): IfPartial<IsPartial, boolean> {
if (!this.rawData) return undefined as never
return this.rawData.nsfw ?? false
}

/**
* The guild this channel is in
*/
get guild() {
get guild(): IfPartial<IsPartial, Guild<true>> {
if (!this.rawData) return undefined as never
if (!this.guildId) throw new Error("Cannot get guild without guild ID")
return new Guild(this.client, this.guildId)
}

protected override setData(data: APIGuildChannel<Type>): void {
this.rawData = data as Extract<APIChannel, { type: Type }> | null
this.partial = false
this.name = data.name
this.guildId = data.guild_id
this.position = data.position
this.parentId = data.parent_id
this.nsfw = data.nsfw
this.setSpecificData(data as Extract<APIChannel, { type: Type }>)
return new Guild<true>(this.client, this.guildId)
}

/**
Expand All @@ -65,7 +78,7 @@ export abstract class BaseGuildChannel<
name
}
})
this.name = name
this.setField("name", name)
}

/**
Expand All @@ -78,7 +91,7 @@ export abstract class BaseGuildChannel<
position
}
})
this.position = position
this.setField("position", position)
}

/**
Expand All @@ -92,14 +105,14 @@ export abstract class BaseGuildChannel<
parent_id: parent
}
})
this.parentId = parent
this.setField("parent_id", parent)
} else {
await this.client.rest.patch(Routes.channel(this.id), {
body: {
parent_id: parent.id
}
})
this.parentId = parent.id
this.setField("parent_id", parent.id)
}
}

Expand All @@ -113,7 +126,7 @@ export abstract class BaseGuildChannel<
nsfw
}
})
this.nsfw = nsfw
this.setField("nsfw", nsfw)
}

/**
Expand Down
37 changes: 21 additions & 16 deletions packages/carbon/src/abstracts/BaseGuildTextChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,40 @@ import {
} from "discord-api-types/v10"
import { GuildThreadChannel } from "../structures/GuildThreadChannel.js"
import { Message } from "../structures/Message.js"
import type { IfPartial } from "../utils.js"
import { BaseGuildChannel } from "./BaseGuildChannel.js"

export abstract class BaseGuildTextChannel<
Type extends GuildTextChannelType
> extends BaseGuildChannel<Type> {
Type extends GuildTextChannelType,
IsPartial extends boolean = false
> extends BaseGuildChannel<Type, IsPartial> {
declare rawData: APIGuildTextChannel<Type> | null

/**
* The ID of the last message sent in the channel.
*
* @remarks
* This might not always resolve to a message. The ID still stays a part of the channel's data, even if the message is deleted.
*/
lastMessageId?: string | null
get lastMessageId(): IfPartial<IsPartial, string | null> {
if (!this.rawData) return undefined as never
return this.rawData.last_message_id ?? null
}
/**
* The timestamp of the last pin in the channel.
*/
lastPinTimestamp?: string | null
get lastPinTimestamp(): IfPartial<IsPartial, string | null> {
if (!this.rawData) return undefined as never
return this.rawData.last_pin_timestamp ?? null
}
/**
* The rate limit per user for the channel, in seconds.
*/
rateLimitPerUser?: number | null

protected setSpecificData(data: APIGuildTextChannel<Type>): void {
this.lastMessageId = data.last_message_id
this.lastPinTimestamp = data.last_pin_timestamp
this.rateLimitPerUser = data.rate_limit_per_user
this.setMoreSpecificData(data)
get rateLimitPerUser(): IfPartial<IsPartial, number | undefined> {
if (!this.rawData) return undefined as never
return this.rawData.rate_limit_per_user
}

protected abstract setMoreSpecificData(data: APIGuildTextChannel<Type>): void

/**
* The last message sent in the channel.
*
Expand All @@ -46,11 +50,12 @@ export abstract class BaseGuildTextChannel<
* This will always return a partial message, so you can use {@link Message.fetch} to get the full message data.
*
*/
get lastMessage() {
get lastMessage(): IfPartial<IsPartial, Message<true> | null> {
if (!this.rawData) return undefined as never
if (!this.lastMessageId) return null
return new Message(this.client, {
return new Message<true>(this.client, {
id: this.lastMessageId,
channel_id: this.id
channelId: this.id
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/carbon/src/abstracts/BaseInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ export abstract class BaseInteraction<T extends APIInteraction> extends Base {
return new Message(this.client, this.rawData.message)
}

get guild(): Guild | null {
get guild(): Guild<true> | null {
if (!this.rawData.guild_id) return null
return new Guild(this.client, this.rawData.guild_id)
return new Guild<true>(this.client, this.rawData.guild_id)
}

get user(): User | null {
Expand Down
Loading

0 comments on commit a0074f0

Please sign in to comment.