Trait reimplementation work

This commit is contained in:
Julien Valverdé
2024-01-28 19:56:12 +01:00
parent eaaabeec49
commit f5ab00c34a

View File

@@ -1,117 +1,56 @@
import { AbstractClass, AbstractConstructor, Opaque } from "type-fest"
import { AbstractClass, Class, Opaque } from "type-fest"
import { StaticMembers } from "./util"
/**
* Represents a trait that can be applied to a class.
* @template C - The abstract class type.
*/
export type Trait<
C extends AbstractClass<any>
> = Opaque<
TraitApplier<C>,
"@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<any>
type RemoveAbstractMembersFromImpl<
ImplWithAbstractMembers extends Class<AbstractMembers, []>,
AbstractMembers extends object,
> = (
(Super: Opaque<AbstractConstructor<object>, TraitApplierSuperTag>) => Opaque<C, TraitApplierSuperTag>
Class<
Omit<InstanceType<ImplWithAbstractMembers>, keyof AbstractMembers>,
ConstructorParameters<ImplWithAbstractMembers>
> &
StaticMembers<ImplWithAbstractMembers>
)
/**
* 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 = <ID>() =>
* 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 = <ID>(defaultID: ID) =>
* trait(Super => {
* abstract class ImplementsIdentifiable extends extendsAndExpresses(
* Super,
* Identifiable<ID>(),
* ) {
* id: ID = defaultID
*
* // Optional
* constructor(...args: any[]) {
* super(...args)
* }
* }
*
* return ImplementsIdentifiable
* })
* ```
*/
export type TraitTag = "@thilawyn/traitify-ts/Trait"
export type Trait<
AbstractMembers extends object,
Impl extends Class<any, []>,
> = (
Opaque<{
readonly AbstractMembers: AbstractMembers
readonly Impl: Impl
}, TraitTag>
)
export type TraitApplierSuperTag = "@thilawyn/traitify-ts/Super"
export type TraitApplier<
AbstractMembers extends object,
ImplWithAbstractMembers extends Class<AbstractMembers, []>,
> = (
(Super: Opaque<AbstractClass<AbstractMembers>, TraitApplierSuperTag>) => (
Opaque<ImplWithAbstractMembers, TraitApplierSuperTag>
)
)
export function trait<
C extends AbstractClass<any>
>(
applier: TraitApplier<C>
AbstractMembers extends object = {}
>(): (
<ImplWithAbstractMembers extends Class<AbstractMembers, []>>(
applier: TraitApplier<AbstractMembers, ImplWithAbstractMembers>
) => Trait<
AbstractMembers,
RemoveAbstractMembersFromImpl<
ImplWithAbstractMembers,
AbstractMembers
>
>
) {
return applier as Trait<C>
return (applier) => applier()
}
/**
* Returns the class type of a trait.
* @template T - The trait type.
*/
export type TraitClass<T> = (
T extends Trait<infer C>
? C
: never
)
/**
* Returns the instance type of a trait.
* @template T - The trait type.
*/
export type TraitInstance<T> = (
T extends Trait<infer C>
? InstanceType<C>
: never
)