Files
discord.js/packages/structures/src/Structure.ts
Qjuh 3cff4d7412 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>
2025-07-12 18:24:30 +00:00

145 lines
5.8 KiB
TypeScript

import { kClone, kData, kMixinConstruct, kMixinToJSON, kPatch } from './utils/symbols.js';
import type { ReplaceOmittedWithUnknown } from './utils/types.js';
export const DataTemplatePropertyName = 'DataTemplate';
export const OptimizeDataPropertyName = 'optimizeData';
/**
* Represents a data model from the Discord API
*
* @privateRemarks
* Explanation of the type complexity surround Structure:
*
* There are two layers of Omitted generics, one here, which allows omitting things at the library level so we do not accidentally
* access them, in addition to whatever the user does at the layer above.
*
* The second layer, in the exported structure is effectively a type cast that allows the getters types to match whatever data template is used
*
* In order to safely set and access this data, the constructor and patch take data as "partial" and forcibly assigns it to kData. To accommodate this,
* kData stores properties as `unknown` when it is omitted, which allows accessing the property in getters even when it may not actually be present.
* This is the most technically correct way of representing the value, especially since there is no way to guarantee runtime matches the "type cast."
*/
export abstract class Structure<DataType extends {}, Omitted extends keyof DataType | '' = ''> {
/**
* A construct function used when mixing to allow mixins to set optimized property defaults
*
* @internal
* @remarks This should only be used to set defaults, setting optimized values should be done
* in the mixins `optimizeData` method, which will be called automatically.
* @param data - The full API data received by the Structure
*/
protected [kMixinConstruct]?(data: Partial<DataType>): void;
/**
* A function used when mixing to allow mixins to add properties to the result of toJSON
*
* @internal
* @remarks This should only be used to add properties that the mixin optimizes, if the raw
* JSON data is unchanged the property will already be returned.
* @param data - The result of the base class toJSON Structure before it gets returned
*/
protected [kMixinToJSON]?(data: Partial<DataType>): void;
/**
* The template used for removing data from the raw data stored for each Structure.
*
* @remarks This template should be overridden in all subclasses to provide more accurate type information.
* The template in the base {@link Structure} class will have no effect on most subclasses for this reason.
*/
protected static readonly DataTemplate: Record<string, unknown> = {};
/**
* @returns A cloned version of the data template, ready to create a new data object.
*/
private getDataTemplate() {
return Object.create((this.constructor as typeof Structure).DataTemplate);
}
/**
* The raw data from the API for this structure
*
* @internal
*/
protected [kData]: Readonly<ReplaceOmittedWithUnknown<Omitted, DataType>>;
/**
* Creates a new structure to represent API data
*
* @param data - the data from the API that this structure will represent
* @remarks To be made public in subclasses
* @internal
*/
public constructor(data: Readonly<Partial<DataType>>, ..._rest: unknown[]) {
this[kData] = Object.assign(this.getDataTemplate(), data);
this[kMixinConstruct]?.(data);
}
/**
* Patches the raw data of this object in place
*
* @param data - the updated data from the API to patch with
* @remarks To be made public in subclasses
* @returns this
* @internal
*/
protected [kPatch](data: Readonly<Partial<DataType>>): this {
this[kData] = Object.assign(this.getDataTemplate(), this[kData], data);
this.optimizeData(data);
return this;
}
/**
* Creates a clone of this structure
*
* @returns a clone of this
* @internal
*/
protected [kClone](patchPayload?: Readonly<Partial<DataType>>): typeof this {
const clone = this.toJSON();
// @ts-expect-error constructor is of abstract class is unknown
return new this.constructor(
// Ensure the ts-expect-error only applies to the constructor call
patchPayload ? Object.assign(clone, patchPayload) : clone,
);
}
/**
* Function called to ensure stored raw data is in optimized formats, used in tandem with a data template
*
* @example created_timestamp is an ISO string, this can be stored in optimized form as a number
* @param _data - the raw data received from the API to optimize
* @remarks Implementation to be done in subclasses and mixins where needed.
* For typescript users, mixins must use the closest ancestors access modifier.
* @remarks Automatically called in Structure[kPatch] but must be called manually in the constructor
* of any class implementing this method.
* @remarks Additionally, when implementing, ensure to call `super._optimizeData` if any class in the super chain aside
* from Structure contains an implementation.
* Note: mixins do not need to call super ever as the process of mixing walks the prototype chain.
* @virtual
* @internal
*/
protected optimizeData(_data: Partial<DataType>) {}
/**
* Transforms this object to its JSON format with raw API data (or close to it),
* automatically called by `JSON.stringify()` when this structure is stringified
*
* @remarks
* The type of this data is determined by omissions at runtime and is only guaranteed for default omissions
* @privateRemarks
* When omitting properties at the library level, this must be overridden to re-add those properties
*/
public toJSON(): DataType {
// This will be DataType provided nothing is omitted, when omits occur, subclass needs to overwrite this.
const data =
// Spread is way faster than structuredClone, but is shallow. So use it only if there is no nested objects
(
Object.values(this[kData]).some((value) => typeof value === 'object' && value !== null)
? structuredClone(this[kData])
: { ...this[kData] }
) as DataType;
this[kMixinToJSON]?.(data);
return data;
}
}