0.1.2 #9

Merged
Thilawyn merged 8 commits from next into master 2024-01-27 23:40:19 +01:00
6 changed files with 220 additions and 236 deletions
Showing only changes of commit 1722372a48 - Show all commits

84
src/expresses.ts Normal file
View File

@@ -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<bigint>(),
* Permissible,
* ) {
* readonly id: bigint
*
* constructor(id: bigint) {
* super()
* this.id = id
* }
* }
* ```
*/
export function extendsAndExpresses<
C extends AbstractClass<any>,
Traits extends readonly Trait<any>[],
>(
extend: C,
...traits: Traits
) {
return traits.reduce(
(previous, trait) => trait(previous),
extend as Opaque<C, TraitApplierSuperTag>,
) as unknown as (
AbstractClass<
MergeInheritanceTreeWithoutOverriding<[
InstanceType<C>,
...ClassesInstances<
TraitsClasses<Traits>
>,
]>,
ConstructorParameters<C>
> &
MergeInheritanceTree<[
StaticMembers<C>,
...ClassesStaticMembers<
TraitsClasses<Traits>
>,
]>
)
}
/**
* 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<bigint>(), Permissible) {
* readonly id: bigint
*
* constructor(id: bigint) {
* super()
* this.id = id
* }
* }
* ```
*/
export function expresses<
Traits extends readonly Trait<any>[],
>(
...traits: Traits
) {
return extendsAndExpresses(Object, ...traits)
}

View File

@@ -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<C> = {
[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<any>
> = Opaque<
TraitApplier<C>,
"@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<any>
> =
(Parent: Opaque<AbstractConstructor<object>, TraitApplierTag>) => Opaque<C, TraitApplierTag>
/**
* 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
/**
* 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 = <ID>() =>
* 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 = <ID>(defaultID: ID) =>
* trait(Parent => {
* abstract class ImplementsIdentifiable extends extendsAndExpresses(
* Parent,
* Identifiable<ID>(),
* ) {
* id: ID = defaultID
*
* // Optional
* constructor(...args: any[]) {
* super(...args)
* }
* }
*
* return ImplementsIdentifiable
* })
* ```
*/
export function trait<
C extends AbstractClass<any>
>(
applier: TraitApplier<C>
) {
return applier as Trait<C>
}
/**
* 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<bigint>(),
* Permissible,
* ) {
* readonly id: bigint
*
* constructor(id: bigint) {
* super()
* this.id = id
* }
* }
* ```
*/
export function extendsAndExpresses<
C extends AbstractClass<any>,
Traits extends readonly Trait<any>[],
>(
extend: C,
...traits: Traits
) {
return traits.reduce(
(previous, trait) => trait(previous),
extend as Opaque<C, TraitApplierTag>,
) as (
AbstractClass<
InstanceType<C> &
UnionToIntersection<
TraitInstance<
Traits[number]
>
>,
ConstructorParameters<C>
> &
StaticMembers<C> &
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<bigint>(), Permissible) {
* readonly id: bigint
*
* constructor(id: bigint) {
* super()
* this.id = id
* }
* }
* ```
*/
export function expresses<
Traits extends readonly Trait<any>[],
>(
...traits: Traits
) {
return extendsAndExpresses(Object, ...traits)
}
export * from "./expresses"
export * from "./trait"

View File

@@ -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 = <ID>() =>
@@ -93,33 +93,8 @@ class Test3 {
}
type ExtendClasses<Classes extends readonly AbstractClass<any>[]> = (
AbstractClass<
// Extend<ClassesInstances<Classes>>
MergeInheritanceTreeWithoutOverriding<
type CleanupInheritanceTreeProperties<Classes extends readonly AbstractClass<any>[]> = (
MergeInheritanceTree<
ClassesInstances<Classes>
>
> &
MergeInheritanceTree<
ClassesStaticMembers<Classes>
>
)
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<TestTrait2, TestTrait1>,
// any[]
// > & Extend2<StaticMembers<typeof TestTrait2>, StaticMembers<typeof TestTrait1>>

117
src/trait.ts Normal file
View File

@@ -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<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>
> = (
(Super: Opaque<AbstractConstructor<object>, TraitApplierSuperTag>) => Opaque<C, TraitApplierSuperTag>
)
/**
* 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 function trait<
C extends AbstractClass<any>
>(
applier: TraitApplier<C>
) {
return applier as Trait<C>
}
/**
* 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
)

View File

@@ -1,2 +1,3 @@
export * from "./class"
export * from "./inheritance"
export * from "./trait"

12
src/util/trait.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Trait, TraitClass } from ".."
export type TraitsClasses<Traits extends readonly Trait<any>[]> = (
Traits extends [infer T, ...infer Rest]
? T extends Trait<any>
? Rest extends Trait<any>[]
? [TraitClass<T>, ...TraitsClasses<Rest>]
: never
: never
: []
)