feat: @discordjs/structures (#10900)

* chore: init /structures

* feat: base structure

* feat: initial structures design attempt

* refactor(Structure): use unknown to store in kData

* feat(Structure): add Invite

refactor(Structure): patch to _patch

* refactor: symbol names and override location

* fix: don't possibly return 0 if discord borks

Co-authored-by: Synbulat Biishev <signin@syjalo.dev>

* refactor: use getter value instead of api

Co-authored-by: Synbulat Biishev <signin@syjalo.dev>

* refactor: cache createdTimestamp value

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* docs: better docs for what's done so far

* feat: add Mixin

* refactor(User): remove bitfield getters and add displayName

* feat(structures): add Connection

* feat(structures): add Channel base

* refactor(Mixin): trace prototype chain, allow construction

* fix(structures): fix mixin behavior

* fix(structures): data optimization call behavior from perf testing

* feat: channel mixins

* chore: update deps

* feat: channels and mixins

* chore: more typeguard tests

* fix: tests and some other issues

* feat: add ChannelWebhookMixin

* fix: more tests

* chore: tests and docs

* chore: docs

* fix: remove unneccessary omitted

* chore: apply code suggestions

* refactor: change how extended invite works

* fix: type imports

* Apply suggestions from code review

Co-authored-by: Almeida <github@almeidx.dev>

* fix: tests

* chore: add jsdoc

* refactor: apply code suggestions

* fix: don't instantiate sub-structures

* fix: don't do null default twice

* chore: use formatters, add _cache

* chore: lockfile

* chore: move MixinTypes to declaratiion file

* fix: tests

* fix: don't include source d.ts files for docs

* feat: bitfields

* feat: more bitfields

* refactor: remove DirectoryChannel structure

* chore: apply suggestions from code review

* chore: remove unused import

* refactor: use symbol for mixin toJSON, remove _ prefix

* chore: apply suggestions from code review

* refactor: remove bitfield casts

* refactor: remove special case for threadchannel types

* fix: apply code review suggestions

* refactor: bitfields always store bigint

* fix: tests

* chore: apply suggestions from code review

* fix: lint

* refactor: conditional structuredClone

* Apply suggestions from code review

Co-authored-by: ckohen <chaikohen@gmail.com>

* fix: code review errors

* fix: lint

* chore: bump dtypes

* Update packages/structures/cliff.toml

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>

* docs: link to VideoQualityMode

* chore: typo in comment

* chore: small nits in docs links

* chore: small nits

* docs: forgot one

* chore: update template

* chore: typos and things

* chore: apply suggestions from code review

* fix: tests and typeguards

* chore: don't clone appliedTags

* refactor: use a symbol for patch method

* fix: add missing readonly

* chore: remove todo comment

* refactor: use symbol for clone

* fix: add constraint to DataType

* chore: apply suggestions

* fix: dtypes bump

* chore: fix comment

* chore: add todo comment

* chore: mark bitfield as todo
chore: mark bit field as todo and edit readme

---------

Co-authored-by: ckohen <chaikohen@gmail.com>
Co-authored-by: Synbulat Biishev <signin@syjalo.dev>
Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Qjuh
2025-07-12 20:24:30 +02:00
committed by GitHub
parent 591668099e
commit 3cff4d7412
89 changed files with 4593 additions and 15 deletions

View File

@@ -0,0 +1,203 @@
import type { EnumLike, NonAbstract, RecursiveReadonlyArray } from '../utils/types.js';
// TODO: this currently is mostly copied from mainlib discord.js v14 and definitely needs a refactor in a later iteration
/**
* Data that can be resolved to give a bit field. This can be:
* A bit number (this can be a number literal or a value taken from {@link (BitField:class).Flags})
* A string bit number
* An instance of BitField
* An Array of BitFieldResolvable
*/
export type BitFieldResolvable<Flags extends string> =
| Flags
| Readonly<BitField<Flags>>
| RecursiveReadonlyArray<Flags | Readonly<BitField<Flags>> | bigint | number | `${bigint}`>
| bigint
| number
| `${bigint}`;
/**
* Data structure that makes it easy to interact with a bit field.
*/
export abstract class BitField<Flags extends string> {
/**
* Numeric bit field flags.
*
* @remarks Defined in extension classes
*/
public static readonly Flags: EnumLike<unknown, bigint | number> = {};
public static readonly DefaultBit: bigint = 0n;
/**
* Bitfield of the packed bits
*/
public bitField: bigint;
declare public ['constructor']: NonAbstract<typeof BitField<Flags>>;
/**
* @param bits - Bit(s) to read from
*/
public constructor(bits: BitFieldResolvable<Flags> = this.constructor.DefaultBit) {
this.bitField = this.constructor.resolve(bits);
}
/**
* Checks whether the bit field has a bit, or any of multiple bits.
*
* @param bit - Bit(s) to check for
* @returns Whether the bit field has the bit(s)
*/
public any(bit: BitFieldResolvable<Flags>) {
return (this.bitField & this.constructor.resolve(bit)) !== this.constructor.DefaultBit;
}
/**
* Checks if this bit field equals another
*
* @param bit - Bit(s) to check for
* @returns Whether this bit field equals the other
*/
public equals(bit: BitFieldResolvable<Flags>) {
return this.bitField === this.constructor.resolve(bit);
}
/**
* Checks whether the bit field has a bit, or multiple bits.
*
* @param bit - Bit(s) to check for
* @returns Whether the bit field has the bit(s)
*/
public has(bit: BitFieldResolvable<Flags>, ..._hasParams: unknown[]) {
const resolvedBit = this.constructor.resolve(bit);
return (this.bitField & resolvedBit) === resolvedBit;
}
/**
* Gets all given bits that are missing from the bit field.
*
* @param bits - Bit(s) to check for
* @param hasParams - Additional parameters for the has method, if any
* @returns A bit field containing the missing bits
*/
public missing(bits: BitFieldResolvable<Flags>, ...hasParams: readonly unknown[]) {
return new this.constructor(bits).remove(this).toArray(...hasParams);
}
/**
* Freezes these bits, making them immutable.
*
* @returns This bit field but frozen
*/
public freeze() {
return Object.freeze(this);
}
/**
* Adds bits to these ones.
*
* @param bits - Bits to add
* @returns These bits or new BitField if the instance is frozen.
*/
public add(...bits: BitFieldResolvable<Flags>[]) {
let total = this.constructor.DefaultBit;
for (const bit of bits) {
total |= this.constructor.resolve(bit);
}
if (Object.isFrozen(this)) return new this.constructor(this.bitField | total);
this.bitField |= total;
return this;
}
/**
* Removes bits from these.
*
* @param bits - Bits to remove
* @returns These bits or new BitField if the instance is frozen.
*/
public remove(...bits: BitFieldResolvable<Flags>[]) {
let total = this.constructor.DefaultBit;
for (const bit of bits) {
total |= this.constructor.resolve(bit);
}
if (Object.isFrozen(this)) return new this.constructor(this.bitField & ~total);
this.bitField &= ~total;
return this;
}
/**
* Gets an object mapping field names to a boolean indicating whether the bit is available.
*
* @param hasParams - Additional parameters for the has method, if any
* @returns An object mapping field names to a boolean indicating whether the bit is available
*/
public serialize(...hasParams: readonly unknown[]) {
const serialized: Partial<Record<keyof Flags, boolean>> = {};
for (const [flag, bit] of Object.entries(this.constructor.Flags)) {
if (Number.isNaN(Number(flag))) serialized[flag as keyof Flags] = this.has(bit as bigint | number, ...hasParams);
}
return serialized;
}
/**
* Gets an Array of bit field names based on the bits available.
*
* @param hasParams - Additional parameters for the has method, if any
* @returns An Array of bit field names
*/
public toArray(...hasParams: readonly unknown[]) {
return [...this[Symbol.iterator](...hasParams)];
}
public toJSON(asNumber?: boolean) {
if (asNumber) {
if (this.bitField > Number.MAX_SAFE_INTEGER) {
throw new RangeError(
`Cannot convert bitfield value ${this.bitField} to number, as it is bigger than ${Number.MAX_SAFE_INTEGER} (the maximum safe integer)`,
);
}
return Number(this.bitField);
}
return this.bitField.toString();
}
public valueOf() {
return this.bitField;
}
public *[Symbol.iterator](...hasParams: unknown[]) {
for (const bitName of Object.keys(this.constructor.Flags)) {
if (Number.isNaN(Number(bitName)) && this.has(bitName as Flags, ...hasParams)) yield bitName as Flags;
}
}
/**
* Resolves bit fields to their numeric form.
*
* @param bit - bit(s) to resolve
* @returns the numeric value of the bit fields
*/
public static resolve<Flags extends string = string>(bit: BitFieldResolvable<Flags>): bigint {
const DefaultBit = this.DefaultBit;
if (typeof bit === 'bigint' && bit >= DefaultBit) return bit;
if (typeof bit === 'number' && BigInt(bit) >= DefaultBit) return BigInt(bit);
if (bit instanceof BitField) return bit.bitField;
if (Array.isArray(bit)) {
return bit.map((bit_) => this.resolve(bit_)).reduce((prev, bit_) => prev | bit_, DefaultBit);
}
if (typeof bit === 'string') {
if (!Number.isNaN(Number(bit))) return BigInt(bit);
if (bit in this.Flags) return this.Flags[bit as keyof typeof this.Flags];
}
throw new Error(`BitFieldInvalid: ${JSON.stringify(bit)}`);
}
}

View File

@@ -0,0 +1,16 @@
import { ChannelFlags } from 'discord-api-types/v10';
import { BitField } from './BitField.js';
/**
* Data structure that makes it easy to interact with a {@link (Channel:class).flags} bitfield.
*/
export class ChannelFlagsBitField extends BitField<keyof ChannelFlags> {
/**
* Numeric guild channel flags.
*/
public static override readonly Flags = ChannelFlags;
public override toJSON() {
return super.toJSON(true);
}
}

View File

@@ -0,0 +1,76 @@
/* eslint-disable unicorn/consistent-function-scoping */
import { PermissionFlagsBits } from 'discord-api-types/v10';
import type { BitFieldResolvable } from './BitField.js';
import { BitField } from './BitField.js';
/**
* Data structure that makes it easy to interact with a permission bit field. All {@link GuildMember}s have a set of
* permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrite}s for the member
* that override their default permissions.
*/
export class PermissionsBitField extends BitField<keyof typeof PermissionFlagsBits> {
/**
* Numeric permission flags.
*
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
*/
public static override Flags = PermissionFlagsBits;
/**
* Bit field representing every permission combined
*/
public static readonly All = Object.values(PermissionFlagsBits).reduce((all, perm) => all | perm, 0n);
/**
* Bit field representing the default permissions for users
*/
public static readonly Default = 104_324_673n;
/**
* Bit field representing the permissions required for moderators of stage channels
*/
public static readonly StageModerator =
PermissionFlagsBits.ManageChannels | PermissionFlagsBits.MuteMembers | PermissionFlagsBits.MoveMembers;
/**
* Gets all given bits that are missing from the bit field.
*
* @param bits - Bit(s) to check for
* @param checkAdmin - Whether to allow the administrator permission to override
* @returns A bit field containing the missing permissions
*/
public override missing(bits: BitFieldResolvable<keyof typeof PermissionFlagsBits>, checkAdmin = true) {
return checkAdmin && this.has(PermissionFlagsBits.Administrator) ? [] : super.missing(bits);
}
/**
* Checks whether the bit field has a permission, or any of multiple permissions.
*
* @param permission - Permission(s) to check for
* @param checkAdmin - Whether to allow the administrator permission to override
* @returns Whether the bit field has the permission(s)
*/
public override any(permission: BitFieldResolvable<keyof typeof PermissionFlagsBits>, checkAdmin = true) {
return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.any(permission);
}
/**
* Checks whether the bit field has a permission, or multiple permissions.
*
* @param permission - Permission(s) to check for
* @param checkAdmin - Whether to allow the administrator permission to override
* @returns Whether the bit field has the permission(s)
*/
public override has(permission: BitFieldResolvable<keyof typeof PermissionFlagsBits>, checkAdmin = true) {
return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.has(permission);
}
/**
* Gets an Array of bitfield names based on the permissions available.
*
* @returns An Array of permission names
*/
public override toArray() {
return super.toArray(false);
}
}

View File

@@ -0,0 +1,4 @@
export * from './BitField.js';
export * from './ChannelFlagsBitField.js';
export * from './PermissionsBitField.js';