mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-13 01:53:30 +01:00
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:
144
packages/structures/src/Structure.ts
Normal file
144
packages/structures/src/Structure.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user