mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 08:33:30 +01:00
* 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>
180 lines
6.7 KiB
TypeScript
180 lines
6.7 KiB
TypeScript
import { DataTemplatePropertyName, OptimizeDataPropertyName, type Structure } from './Structure.js';
|
|
import { kMixinConstruct, kMixinToJSON } from './utils/symbols.js';
|
|
|
|
export type Mixinable<ClassType> = new (...args: unknown[]) => ClassType;
|
|
|
|
export type MixinBase<BaseClass extends Structure<{}>> =
|
|
BaseClass extends Structure<infer DataType, infer Omitted> ? Structure<DataType, Omitted> : never;
|
|
|
|
/**
|
|
* Copies the prototype (getters, setters, and methods) of all mixins to the destination class.
|
|
* For type information see {@link MixinTypes}
|
|
*
|
|
* @param destination - The class to apply the mixins to, must extend the base that the mixins expect it to.
|
|
* @param mixins - Classes that contain "pure" prototypes to be copied on top of the destination class prototype
|
|
* @remarks All mixins should be "pure" in that they only contain getters, setters, and methods.
|
|
* The runtime code will only copy these, and adding properties to the class only results
|
|
* in the types of the mixed class being wrong.
|
|
* @example
|
|
* ```
|
|
* // Interface merging on the mixin to give type access to props on the base and kData that are available once copied
|
|
* interface TextMixin extends Channel {}
|
|
* class TextMixin {
|
|
* // Methods / getters
|
|
* }
|
|
*
|
|
* // Interface merging on the mixed class to give it accurate type information within the declaration and when instantiated
|
|
* interface TextChannel extends MixinTypes<Channel, [TextMixin]> {}
|
|
* class TextChannel extends Channel {}
|
|
*
|
|
* // Apply for runtime
|
|
* Mixin(TextChannel, [TextMixin])
|
|
* ```
|
|
* @typeParam DestinationClass - The class to be mixed, ensures that the mixins provided can be used with this destination
|
|
*/
|
|
export function Mixin<DestinationClass extends typeof Structure<{}>>(
|
|
destination: DestinationClass,
|
|
mixins: Mixinable<MixinBase<DestinationClass['prototype']>>[],
|
|
) {
|
|
const dataTemplates: Record<string, unknown>[] = [];
|
|
const dataOptimizations: ((data: unknown) => void)[] = [];
|
|
const enrichToJSONs: ((data: Partial<unknown>) => void)[] = [];
|
|
const constructors: ((data: Partial<unknown>) => void)[] = [];
|
|
|
|
for (const mixin of mixins) {
|
|
// The entire prototype chain, in reverse order, since we want to copy it all
|
|
const prototypeChain: MixinBase<DestinationClass['prototype']>[] = [];
|
|
let extendedClass = mixin;
|
|
while (extendedClass.prototype !== undefined) {
|
|
if (
|
|
DataTemplatePropertyName in extendedClass &&
|
|
typeof extendedClass.DataTemplate === 'object' &&
|
|
// eslint-disable-next-line no-eq-null, eqeqeq
|
|
extendedClass.DataTemplate != null
|
|
) {
|
|
dataTemplates.push(extendedClass.DataTemplate as Record<string, unknown>);
|
|
}
|
|
|
|
prototypeChain.unshift(extendedClass.prototype);
|
|
extendedClass = Object.getPrototypeOf(extendedClass);
|
|
}
|
|
|
|
for (const prototype of prototypeChain) {
|
|
// Symboled data isn't traversed by Object.entries, we can handle it here
|
|
if (prototype[kMixinConstruct]) {
|
|
constructors.push(prototype[kMixinConstruct]);
|
|
}
|
|
|
|
if (prototype[kMixinToJSON]) {
|
|
enrichToJSONs.push(prototype[kMixinToJSON]);
|
|
}
|
|
|
|
// Copy instance methods and setters / getters
|
|
const originalDescriptors = Object.getOwnPropertyDescriptors(prototype);
|
|
const usingDescriptors: { [prop: string]: PropertyDescriptor } = {};
|
|
for (const [prop, descriptor] of Object.entries(originalDescriptors)) {
|
|
// Drop constructor
|
|
if (['constructor'].includes(prop)) {
|
|
continue;
|
|
}
|
|
|
|
// Special case for optimize function, we want to combine these
|
|
if (prop === OptimizeDataPropertyName) {
|
|
if (typeof descriptor.value !== 'function')
|
|
throw new RangeError(`Expected ${prop} to be a function, received ${typeof descriptor.value} instead.`);
|
|
dataOptimizations.push(descriptor.value);
|
|
continue;
|
|
}
|
|
|
|
// Shouldn't be anything other than these without being instantiated, but just in case
|
|
if (
|
|
typeof descriptor.get !== 'undefined' ||
|
|
typeof descriptor.set !== 'undefined' ||
|
|
typeof descriptor.value === 'function'
|
|
) {
|
|
usingDescriptors[prop] = descriptor;
|
|
}
|
|
}
|
|
|
|
Object.defineProperties(destination.prototype, usingDescriptors);
|
|
}
|
|
}
|
|
|
|
// Set the function to call any mixed constructors
|
|
if (constructors.length > 0) {
|
|
Object.defineProperty(destination.prototype, kMixinConstruct, {
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
// eslint-disable-next-line func-name-matching
|
|
value: function _mixinConstructors(data: Partial<unknown>) {
|
|
for (const construct of constructors) {
|
|
construct.call(this, data);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
// Combine all optimizations into a single function
|
|
const baseOptimize = Object.getOwnPropertyDescriptor(destination, OptimizeDataPropertyName);
|
|
if (baseOptimize && typeof baseOptimize.value === 'function') {
|
|
// call base last (mimic constructor behavior)
|
|
dataOptimizations.push(baseOptimize.value);
|
|
}
|
|
|
|
const superOptimize = Object.getOwnPropertyDescriptor(
|
|
Object.getPrototypeOf(destination).prototype,
|
|
OptimizeDataPropertyName,
|
|
);
|
|
// the mixin base optimize should call super, so we can ignore the super in that case
|
|
if (!baseOptimize && superOptimize && typeof superOptimize.value === 'function') {
|
|
// call super first (mimic constructor behavior)
|
|
dataOptimizations.unshift(superOptimize.value);
|
|
}
|
|
|
|
// If there's more than one optimization or if there's an optimization that isn't on the destination (base)
|
|
if (dataOptimizations.length > 1 || (dataOptimizations.length === 1 && !baseOptimize)) {
|
|
Object.defineProperty(destination.prototype, OptimizeDataPropertyName, {
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
// eslint-disable-next-line func-name-matching
|
|
value: function _mixinOptimizeData(data: unknown) {
|
|
for (const optimization of dataOptimizations) {
|
|
optimization.call(this, data);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
if (enrichToJSONs.length > 0) {
|
|
Object.defineProperty(destination.prototype, kMixinToJSON, {
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
// eslint-disable-next-line func-name-matching
|
|
value: function _mixinToJSON(data: Partial<unknown>) {
|
|
for (const enricher of enrichToJSONs) {
|
|
enricher.call(this, data);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
// Copy the properties (setters) of each mixins template to the destinations template
|
|
if (dataTemplates.length > 0) {
|
|
if (!Object.getOwnPropertyDescriptor(destination, DataTemplatePropertyName)) {
|
|
Object.defineProperty(destination, DataTemplatePropertyName, {
|
|
value: Object.defineProperties({}, Object.getOwnPropertyDescriptors(destination[DataTemplatePropertyName])),
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
});
|
|
}
|
|
|
|
for (const template of dataTemplates) {
|
|
Object.defineProperties(destination[DataTemplatePropertyName], Object.getOwnPropertyDescriptors(template));
|
|
}
|
|
}
|
|
}
|