Are there anyway to improve typing for this module?

Hello!
I recently create a module that post Discord’s Webhook with strict typing,

However, After making it halfway, i notice that there’s many repeated element in type,
Are there something like extending type like in Typescript?

Typescript extending type
interface Shape {
  color: string;
}

// What i meant by extending type
interface Square extends Shape {
  sideLength: number;
}
My Code
local HttpService = game:GetService("HttpService")

local DiscordWebhook = {}

-- TYPE --
export type EmbedType = "rich" | "image" | "video" | "gifv" | "article" | "link"

export type EmbedThumbnail = {
	url: string,
	proxy_url: string?,
	height: number?,
	width: number?
}

export type EmbedVideo = {
	url: string?,
	proxy_url: string?,
	height: number?,
	width: number?
}

export type EmbedImage = {
	url: string,
	proxy_url: string?,
	height: number?,
	width: number?
}

export type EmbedProvider = {
	name: string?,
	url: string?
}

export type EmbedAuthor = {
	name: string,
	url: string?,
	icon_url: string?,
	proxy_icon_url: string?
}

export type EmbedFooter = {
	text: string,
	icon_url: string?,
	proxy_icon_url: string?
}

export type EmbedField = {
	name: string,
	value: string,
	inline: boolean?
}

export type Embed = {
	title: string?,
	type: EmbedType?,
	description: string?,
	url: string?,
	timestamp: string?,
	color: number?,
	footer: EmbedFooter?,
	image: EmbedImage?,
	thumbnail: EmbedThumbnail?,
	video: EmbedVideo?,
	provider: EmbedProvider?,
	author: EmbedAuthor?,
	fields: {[number]: EmbedField}?
}


export type AllowedMentions = {
	parse: {[string]: "users" | "roles" | "everyone"},
	roles: {[number]: string},
	users: {[number]: string},
	replied_user: boolean?
}

export type WebhookRequest = {
	content: string,
	username: string?,
	avatar_url: string?,
	tts: boolean?,
	embeds: {[number]: Embed}?,
	allowed_mentions: {[number]: AllowedMentions}?,
	components: any?, --TODO
	payload_json: string?,
	attachments: {[number]: any}?,
	flags: number?,
	thread_name: string?,
	[string]: any
}

export type User = {
	id: string,
	username: string,
	discriminator: string,
	avatar: string?,
	bot: boolean?,
	system: boolean?,
	mfa_enabled: boolean?,
	banner: string?,
	accent_color: number?,
	locale: string?,
	verified: boolean?,
	email: string?,
	flags: number?,
	premium_type: number?,
	public_flags: number?
}

export type MessageReference = {
	message_id: string?,
	channel_id: string?,
	guild_id: string?,
	fail_if_not_exists: boolean?
}

export type StickerItem = {
	id: string,
	name: string,
	format_type: number
}

export type Sticker = {
	id: string,
	pack_id: string?,
	name: string,
	description: string?,
	tags: string,
	asset: string?,
	type: number,
	format_type: number,
	available: boolean?,
	guild_id: string,
	user: User?,
	sort_value: number?
}

export type Attachment = {
	id: string,
	filename: string,
	description: string?,
	content_type: string?,
	size: number,
	url: string,
	proxy_url: string,
	height: number?,
	width: number?,
	ephemeral: boolean?,
}

export type GuildMember = {
	user: User?,
	nick: string?,
	avatar: string?,
	roles: {[number]: string},
	joined_at: string?,
	premium_since: string?,
	deaf: boolean?,
	mute: boolean?,
	pending: boolean?,
	permissions: string?,
	communication_disabled_until: string?
}

export type MessageMentionUser = {
	id: string,
	username: string,
	discriminator: string,
	avatar: string?,
	bot: boolean?,
	system: boolean?,
	mfa_enabled: boolean?,
	banner: string?,
	accent_color: number?,
	locale: string?,
	verified: boolean?,
	email: string?,
	flags: number?,
	premium_type: number?,
	public_flags: number?,
	member: GuildMember
}

export type MessageActivity = {
	type: number,
	party_id: string?
}

export type Emoji = {
	id: string?,
	name: string,
	roles: {[number]: string},
	user: User?,
	require_colons: boolean?,
	managed: boolean?,
	animated: boolean?,
	available: boolean?
}

export type Reaction = {
	count: number,
	me: boolean,
	emoji: Emoji
}

export type ChannelMention = {
	id: string,
	guild_id: string,
	type: number,
	name: string
}

export type MessageInteraction = {
	id: string,
	type: number,
	name: string,
	user: User,
	member: GuildMember?
}

export type ThreadMember = {
	id: string?,
	user_id: string?,
	join_timestamp: string,
	flags: number
}

export type ThreadMetadata = {
	archived: boolean,
	auto_archive_duration: number,
	archive_timestamp: string,
	locked: boolean,
	invitable: boolean?,
	create_timestamp: string?
}

export type Overwrite = {
	id: string,
	type: number,
	allow: string,
	deny: string
}

export type Channel = {
	id: string,
	type: number,
	guild_id: number?,
	position: number?,
	permission_overwrites: {[number]: Overwrite}?,
	name: string?,
	topic: string?,
	nsfw: boolean?,
	last_message_id: string?,
	bitrate: number?,
	user_limit: number?,
	rate_limit_per_user: number?,
	recipients: {[number]: User}?,
	icon: string?,
	owner_id: string?,
	application_id: string?,
	parent_id: string?,
	last_pin_timestamp: string?,
	rtc_region: string?,
	video_quality_mode: number?,
	message_count: number?,
	member_count: number?,
	thread_metadata: ThreadMetadata?,
	member: ThreadMember?,
	default_auto_archive_duration: number?,
	permissions: string?,
	flags: number?
}

export type InstallParams = {
	scopes: {[number]: string},
	permissions: string
}

export type TeamMember = {
	membership_state: number,
	permissions: {[number]: string},
	team_id: string,
	user: User
}

export type Team = {
	icon: string?,
	id: string,
	members: {[number]: TeamMember},
	name: string,
	owner_user_id: string
}

export type Application = {
	id: string,
	name: string,
	icon: string?,
	description: string,
	rpc_origins: {[number]: string},
	bot_public: boolean,
	bot_require_code_grant: boolean,
	terms_of_service_url: string?,
	privacy_policy_url: string?,
	owner: User?,
	summary: string, -- deprecated
	verify_key: string,
	team: Team?,
	guild_id: string?,
	primary_sku_id: string?,
	slug: string?,
	cover_image: string?,
	flags: number?,
	tags: {[number]: string}?,
	install_params: InstallParams?,
	custom_install_url: string?
}

export type Message = {
	id: string,
	channel_id: string,
	guild_id: string?,
	author: User,
	member: GuildMember?,
	content: string,
	timestamp: number,
	edited_timestamp: number?,
	tts: boolean,
	mention_everyone: boolean,
	mentions: {[number]: MessageMentionUser},
	mention_roles: {[number]: string},
	mention_channels: {[number]: ChannelMention},
	attachments: {[number]: Attachment},
	embeds: {[number]: Embed},
	reactions: {[string]: Reaction},
	pinned: boolean,
	webhook_id: string?,
	type: number,
	activity: MessageActivity?,
	application: Application?,
	message_reference: MessageReference?,
	flags: number?,
	referenced_message: Message?,
	interaction: MessageInteraction?,
	thread: Channel?,
	components: {[number]: any}?, --TODO
	sticker_items: {[number]: StickerItem}?,
	stickers: {[number]: Sticker}?
}

---------------------------------

function DiscordWebhook.new(id: string?, token: string?, version: number?)
	local self = setmetatable({
		id = id,
		token = token,
		version = version or 10
	}, DiscordWebhook)
	return self
end

function DiscordWebhook:Post(data: WebhookRequest, url: string?): Message?
	url = url or "https://discord.com/api/v" .. self.version .. "/webhooks/" .. self.id .. "/" .. self.token

	local success, response = pcall(function()
		return HttpService:PostAsync(url, HttpService:JSONEncode(data))
	end)

	return success and (HttpService:JSONDecode(response) :: Message) or nil
end

return DiscordWebhook

Thank you!

2 Likes

Yep!

In luau’s type checking you can use & to extend stuff

--!strict
type basic = {
	test:string
}

type advanced = basic & { 
	anotherTest:string 
}

local thing:advanced = {
	test = "hello",
	anotherTest = "world"
}

This also allows things like this which can be useful :slight_smile:

type Character = Model & {Head:BasePart, Humanoid:Humanoid, HumanoidRootPart:BasePart}
2 Likes