diff --git a/src/expresses.ts b/src/expresses.ts new file mode 100644 index 0000000..5784e56 --- /dev/null +++ b/src/expresses.ts @@ -0,0 +1,84 @@ +import { AbstractClass, Opaque } from "type-fest" +import { Trait, TraitApplierSuperTag } from "." +import { ClassesInstances, ClassesStaticMembers, MergeInheritanceTree, MergeInheritanceTreeWithoutOverriding, StaticMembers, TraitsClasses } from "./util" + + +/** + * Extends a class with the given traits and expresses their combined functionality. + * @template C - The abstract class type. + * @template Traits - An array of traits. + * @param extend - The class to extend. + * @param traits - An array of traits to apply. + * @returns A new class type expressing the combined functionality of the base class and traits. + * @example + * Extends a superclass and applies traits: + * ```ts + * class User extends extendsAndExpresses(Entity, + * Identifiable(), + * Permissible, + * ) { + * readonly id: bigint + * + * constructor(id: bigint) { + * super() + * this.id = id + * } + * } + * ``` + */ +export function extendsAndExpresses< + C extends AbstractClass, + Traits extends readonly Trait[], +>( + extend: C, + ...traits: Traits +) { + return traits.reduce( + (previous, trait) => trait(previous), + extend as Opaque, + ) as unknown as ( + AbstractClass< + MergeInheritanceTreeWithoutOverriding<[ + InstanceType, + ...ClassesInstances< + TraitsClasses + >, + ]>, + + ConstructorParameters + > & + + MergeInheritanceTree<[ + StaticMembers, + ...ClassesStaticMembers< + TraitsClasses + >, + ]> + ) +} + +/** + * Expresses the combined functionality of multiple traits. + * @template Traits - An array of trait. + * @param traits - An array of trait to apply. + * @returns A new class type expressing the combined functionality of the traits. + * @example + * Applies traits to a class: + * ```ts + * class User extends expresses(Identifiable(), Permissible) { + * readonly id: bigint + * + * constructor(id: bigint) { + * super() + * this.id = id + * } + * } + * ``` + */ +export function expresses< + Traits extends readonly Trait[], +>( + ...traits: Traits +) { + return extendsAndExpresses(Object, ...traits) +} diff --git a/src/index.ts b/src/index.ts index ea9c1b5..ae86066 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,207 +1,2 @@ -import { AbstractClass, AbstractConstructor, Opaque, UnionToIntersection } from "type-fest" - - -/** - * Represents the static members of a class. - * @template C - The class type. - */ -type StaticMembers = { - [Key in keyof C as Key extends "prototype" ? never : Key]: C[Key] -} - - -/** - * Represents a trait that can be applied to a class. - * @template C - The abstract class type. - */ -export type Trait< - C extends AbstractClass -> = Opaque< - TraitApplier, - "@thilawyn/thilatrait/Trait" -> - -export type TraitApplierTag = "@thilawyn/thilatrait/Parent" - -/** - * Represents the function signature for applying a trait to a parent class. - * @template C - The abstract class type. - */ -export type TraitApplier< - C extends AbstractClass -> = - (Parent: Opaque, TraitApplierTag>) => Opaque - -/** - * Returns the class type of a trait. - * @template T - The trait type. - */ -export type TraitClass = - T extends Trait - ? C - : never - -/** - * Returns the instance type of a trait. - * @template T - The trait type. - */ -export type TraitInstance = - T extends Trait - ? InstanceType - : never - - -/** - * Creates a trait using the provided trait applier function. - * @template C - The abstract class type. - * @param applier - The trait applier function. - * @returns A trait. - * @example - * Creates a trait: - * ```ts - * const Permissible = trait(Parent => { - * abstract class Permissible extends Parent { - * static readonly defaultPermissions: string[] = [] - * permissions: string[] = [] - * - * // Constructor is optional - * // If you wish to use it, make sure it takes any[] as an args array and passes it to the super call. This is necessary for inheritance to work properly. - * // Trait constructors cannot have typed arguments of their own, they only serve to run logic during object instantiation. - * constructor(...args: any[]) { - * super(...args) - * } - * } - * - * return Permissible - * }) - * ``` - * Creates a generic trait: - * ```ts - * const Identifiable = () => - * trait(Parent => { - * abstract class Identifiable extends Parent { - * abstract readonly id: ID - * - * equals(el: Identifiable) { - * return this.id === el.id - * } - * - * // Optional - * constructor(...args: any[]) { - * super(...args) - * } - * } - * - * return Identifiable - * }) - * ``` - * Creates a subtrait: - * ```ts - * const ImplementsIdentifiable = (defaultID: ID) => - * trait(Parent => { - * abstract class ImplementsIdentifiable extends extendsAndExpresses( - * Parent, - * Identifiable(), - * ) { - * id: ID = defaultID - * - * // Optional - * constructor(...args: any[]) { - * super(...args) - * } - * } - * - * return ImplementsIdentifiable - * }) - * ``` - */ -export function trait< - C extends AbstractClass ->( - applier: TraitApplier -) { - return applier as Trait -} - - -/** - * Extends a class with the given traits and expresses their combined functionality. - * @template C - The abstract class type. - * @template Traits - An array of traits. - * @param extend - The class to extend. - * @param traits - An array of traits to apply. - * @returns A new class type expressing the combined functionality of the base class and traits. - * @example - * Extends a superclass and applies traits: - * ```ts - * class User extends extendsAndExpresses(Entity, - * Identifiable(), - * Permissible, - * ) { - * readonly id: bigint - * - * constructor(id: bigint) { - * super() - * this.id = id - * } - * } - * ``` - */ -export function extendsAndExpresses< - C extends AbstractClass, - Traits extends readonly Trait[], ->( - extend: C, - ...traits: Traits -) { - return traits.reduce( - (previous, trait) => trait(previous), - extend as Opaque, - ) as ( - AbstractClass< - InstanceType & - UnionToIntersection< - TraitInstance< - Traits[number] - > - >, - - ConstructorParameters - > & - - StaticMembers & - StaticMembers< - UnionToIntersection< - TraitClass< - Traits[number] - > - > - > - ) -} - -/** - * Expresses the combined functionality of multiple traits. - * @template Traits - An array of trait. - * @param traits - An array of trait to apply. - * @returns A new class type expressing the combined functionality of the traits. - * @example - * Applies traits to a class: - * ```ts - * class User extends expresses(Identifiable(), Permissible) { - * readonly id: bigint - * - * constructor(id: bigint) { - * super() - * this.id = id - * } - * } - * ``` - */ -export function expresses< - Traits extends readonly Trait[], ->( - ...traits: Traits -) { - return extendsAndExpresses(Object, ...traits) -} +export * from "./expresses" +export * from "./trait" diff --git a/src/tests.ts b/src/tests.ts index 87609f0..2930e45 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -1,6 +1,6 @@ -import { AbstractClass, UnionToIntersection } from "type-fest" +import { AbstractClass } from "type-fest" import { expresses, extendsAndExpresses, trait } from "." -import { ClassesInstances, ClassesInstancesIntersection, ClassesStaticMembers, MergeInheritanceTree, MergeInheritanceTreeWithoutOverriding } from "./util" +import { ClassesInstances, MergeInheritanceTree } from "./util" const Identifiable = () => @@ -93,33 +93,8 @@ class Test3 { } -type ExtendClasses[]> = ( - AbstractClass< - // Extend> - MergeInheritanceTreeWithoutOverriding< - ClassesInstances - > - > & +type CleanupInheritanceTreeProperties[]> = ( MergeInheritanceTree< - ClassesStaticMembers + ClassesInstances > ) - - -type MixedTestProto = ExtendClasses<[ - typeof Test1, - typeof Test2, - typeof Test3, -]> -declare const MixedTestProto: MixedTestProto - -class MixedTest extends MixedTestProto { -} - -MixedTest.prototype -new MixedTest().status.expiresAt - -// type MixedTestTraitsProto = Class< -// Extend2, -// any[] -// > & Extend2, StaticMembers> diff --git a/src/trait.ts b/src/trait.ts new file mode 100644 index 0000000..21fd00c --- /dev/null +++ b/src/trait.ts @@ -0,0 +1,117 @@ +import { AbstractClass, AbstractConstructor, Opaque } from "type-fest" + + +/** + * Represents a trait that can be applied to a class. + * @template C - The abstract class type. + */ +export type Trait< + C extends AbstractClass +> = Opaque< + TraitApplier, + "@thilawyn/thilatrait/Trait" +> + +export type TraitApplierSuperTag = "@thilawyn/thilatrait/Super" + +/** + * Represents the function signature for applying a trait to a parent class. + * @template C - The abstract class type. + */ +export type TraitApplier< + C extends AbstractClass +> = ( + (Super: Opaque, TraitApplierSuperTag>) => Opaque +) + +/** + * Creates a trait using the provided trait applier function. + * @template C - The abstract class type. + * @param applier - The trait applier function. + * @returns A trait. + * @example + * Creates a trait: + * ```ts + * const Permissible = trait(Super => { + * abstract class Permissible extends Super { + * static readonly defaultPermissions: string[] = [] + * permissions: string[] = [] + * + * // Constructor is optional + * // If you wish to use it, make sure it takes any[] as an args array and passes it to the super call. This is necessary for inheritance to work properly. + * // Trait constructors cannot have typed arguments of their own, they only serve to run logic during object instantiation. + * constructor(...args: any[]) { + * super(...args) + * } + * } + * + * return Permissible + * }) + * ``` + * Creates a generic trait: + * ```ts + * const Identifiable = () => + * trait(Super => { + * abstract class Identifiable extends Super { + * abstract readonly id: ID + * + * equals(el: Identifiable) { + * return this.id === el.id + * } + * + * // Optional + * constructor(...args: any[]) { + * super(...args) + * } + * } + * + * return Identifiable + * }) + * ``` + * Creates a subtrait: + * ```ts + * const ImplementsIdentifiable = (defaultID: ID) => + * trait(Super => { + * abstract class ImplementsIdentifiable extends extendsAndExpresses( + * Super, + * Identifiable(), + * ) { + * id: ID = defaultID + * + * // Optional + * constructor(...args: any[]) { + * super(...args) + * } + * } + * + * return ImplementsIdentifiable + * }) + * ``` + */ +export function trait< + C extends AbstractClass +>( + applier: TraitApplier +) { + return applier as Trait +} + +/** + * Returns the class type of a trait. + * @template T - The trait type. + */ +export type TraitClass = ( + T extends Trait + ? C + : never +) + +/** + * Returns the instance type of a trait. + * @template T - The trait type. + */ +export type TraitInstance = ( + T extends Trait + ? InstanceType + : never +) diff --git a/src/util/index.ts b/src/util/index.ts index 05e8a4a..c5faea6 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,2 +1,3 @@ export * from "./class" export * from "./inheritance" +export * from "./trait" diff --git a/src/util/trait.ts b/src/util/trait.ts new file mode 100644 index 0000000..97a2571 --- /dev/null +++ b/src/util/trait.ts @@ -0,0 +1,12 @@ +import { Trait, TraitClass } from ".." + + +export type TraitsClasses[]> = ( + Traits extends [infer T, ...infer Rest] + ? T extends Trait + ? Rest extends Trait[] + ? [TraitClass, ...TraitsClasses] + : never + : never + : [] +)