@arktype/util
Advanced tools
Comparing version 0.0.10 to 0.0.11
@@ -72,2 +72,68 @@ import { attest } from "@arktype/attest" | ||
}) | ||
it("works with subclasses", () => { | ||
abstract class Foo extends Boundable<number> { | ||
getFoo() { | ||
return "foo" | ||
} | ||
} | ||
class Bar extends compose(Foo) { | ||
sizeOf(data: number) { | ||
return data | ||
} | ||
} | ||
const b = new Bar({ limit: 2 }) | ||
attest(b.check(1)).equals(true) | ||
attest(b.check(3)).equals(false) | ||
attest(b.getFoo()).equals("foo") | ||
}) | ||
it("preserves static", () => { | ||
abstract class A extends Trait { | ||
static readonly a = "a" | ||
readonly a = "a" | ||
} | ||
abstract class B extends Trait { | ||
static readonly b = "b" | ||
readonly b = "b" | ||
} | ||
class C extends compose(A, B) { | ||
static readonly c = "c" | ||
readonly c = "c" | ||
} | ||
attest<"a">(C.a).equals("a") | ||
attest<"b">(C.b).equals("b") | ||
attest<"c">(C.c).equals("c") | ||
const c = new C() | ||
attest<"a">(c.a).equals("a") | ||
attest<"b">(c.b).equals("b") | ||
attest<"c">(c.c).equals("c") | ||
}) | ||
it("trait from trait", () => { | ||
abstract class A extends Trait { | ||
readonly a = "a" | ||
} | ||
abstract class B extends Trait { | ||
readonly b = "b" | ||
} | ||
class C extends compose(A, B) { | ||
readonly c = "c" | ||
} | ||
class D extends Trait { | ||
readonly d = "d" | ||
} | ||
class E extends compose(C, D) { | ||
readonly e = "e" | ||
} | ||
const e = new E() | ||
attest<"a">(e.a).equals("a") | ||
attest<"b">(e.b).equals("b") | ||
attest<"c">(e.c).equals("c") | ||
attest<"d">(e.d).equals("d") | ||
attest<"e">(e.e).equals("e") | ||
attest(e.traitsOf()).equals([A, B, C, D]) | ||
}) | ||
}) |
import type { intersectParameters } from "./intersections.js"; | ||
import type { Constructor } from "./objectKinds.js"; | ||
export type TraitComposition = <traits extends readonly Constructor[]>(...traits: traits) => compose<traits>; | ||
export declare const traitsOf: unique symbol; | ||
export declare abstract class Trait { | ||
static [Symbol.hasInstance](o: any): boolean; | ||
static [Symbol.hasInstance](o: object): boolean; | ||
traitsOf(): readonly Function[]; | ||
} | ||
export declare const compose: TraitComposition; | ||
export type compose<traits extends readonly Constructor[]> = composeRecurse<traits, [ | ||
], {}>; | ||
export type composeRecurse<traits extends readonly unknown[], parameters extends readonly unknown[], instance extends {}> = traits extends readonly [ | ||
], {}, {}>; | ||
export type composeRecurse<traits extends readonly unknown[], parameters extends readonly unknown[], statics extends {}, instance extends {}> = traits extends readonly [ | ||
abstract new (...args: infer nextArgs) => infer nextInstance, | ||
...infer tail | ||
] ? composeRecurse<tail, intersectParameters<parameters, nextArgs>, instance & nextInstance> : abstract new (...args: parameters) => instance; | ||
] ? composeRecurse<tail, intersectParameters<parameters, nextArgs>, statics & { | ||
[k in keyof traits[0]]: traits[0][k]; | ||
}, instance & nextInstance> : statics & (abstract new (...args: parameters) => instance); | ||
//# sourceMappingURL=traits.d.ts.map |
@@ -1,6 +0,14 @@ | ||
export const traitsOf = Symbol("hasTraits"); | ||
// even though the value we attach will be identical, we use this so classes | ||
// won't be treated as instanceof a Trait | ||
const implementedTraits = Symbol("implementedTraits"); | ||
export class Trait { | ||
static [Symbol.hasInstance](o) { | ||
return Array.isArray(o[traitsOf]) && o[traitsOf].includes(this); | ||
return (implementedTraits in o.constructor && | ||
o.constructor[implementedTraits].includes(this)); | ||
} | ||
traitsOf() { | ||
return implementedTraits in this.constructor | ||
? this.constructor[implementedTraits] | ||
: []; | ||
} | ||
} | ||
@@ -18,13 +26,38 @@ const collectPrototypeDescriptors = (trait) => { | ||
export const compose = ((...traits) => { | ||
if (traits.length === 0) { | ||
return Object; | ||
} | ||
if (traits.length === 1) { | ||
return traits[0]; | ||
} | ||
const base = function (...args) { | ||
for (const trait of traits) { | ||
Object.assign(this, Reflect.construct(trait, args, this.constructor)); | ||
const instance = Reflect.construct(trait, args, this.constructor); | ||
Object.assign(this, instance); | ||
} | ||
Object.defineProperty(this, traitsOf, { value: traits, enumerable: false }); | ||
}; | ||
const flatImplementedTraits = []; | ||
for (const trait of traits) { | ||
// copy static properties | ||
Object.assign(base, trait); | ||
// flatten and copy prototype | ||
Object.defineProperties(base.prototype, collectPrototypeDescriptors(trait)); | ||
if (implementedTraits in trait) { | ||
// add any ancestor traits from which the current trait was composed | ||
for (const innerTrait of trait[implementedTraits]) { | ||
if (!flatImplementedTraits.includes(innerTrait)) { | ||
flatImplementedTraits.push(innerTrait); | ||
} | ||
} | ||
} | ||
if (!flatImplementedTraits.includes(trait)) { | ||
flatImplementedTraits.push(trait); | ||
} | ||
} | ||
Object.defineProperty(base, implementedTraits, { | ||
value: flatImplementedTraits, | ||
enumerable: false | ||
}); | ||
return base; | ||
}); | ||
//# sourceMappingURL=traits.js.map |
{ | ||
"name": "@arktype/util", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"author": { | ||
@@ -5,0 +5,0 @@ "name": "David Blass", |
@@ -8,8 +8,19 @@ import type { intersectParameters } from "./intersections.js" | ||
export const traitsOf = Symbol("hasTraits") | ||
// even though the value we attach will be identical, we use this so classes | ||
// won't be treated as instanceof a Trait | ||
const implementedTraits = Symbol("implementedTraits") | ||
export abstract class Trait { | ||
static [Symbol.hasInstance](o: any) { | ||
return Array.isArray(o[traitsOf]) && o[traitsOf].includes(this) | ||
static [Symbol.hasInstance](o: object) { | ||
return ( | ||
implementedTraits in o.constructor && | ||
(o.constructor[implementedTraits] as Function[]).includes(this) | ||
) | ||
} | ||
traitsOf(): readonly Function[] { | ||
return implementedTraits in this.constructor | ||
? (this.constructor[implementedTraits] as Function[]) | ||
: [] | ||
} | ||
} | ||
@@ -30,12 +41,36 @@ | ||
export const compose = ((...traits: Function[]) => { | ||
if (traits.length === 0) { | ||
return Object | ||
} | ||
if (traits.length === 1) { | ||
return traits[0] | ||
} | ||
const base: any = function (this: any, ...args: any[]) { | ||
for (const trait of traits) { | ||
Object.assign(this, Reflect.construct(trait, args, this.constructor)) | ||
const instance = Reflect.construct(trait, args, this.constructor) | ||
Object.assign(this, instance) | ||
} | ||
Object.defineProperty(this, traitsOf, { value: traits, enumerable: false }) | ||
} | ||
const flatImplementedTraits: Function[] = [] | ||
for (const trait of traits) { | ||
// copy static properties | ||
Object.assign(base, trait) | ||
// flatten and copy prototype | ||
Object.defineProperties(base.prototype, collectPrototypeDescriptors(trait)) | ||
if (implementedTraits in trait) { | ||
// add any ancestor traits from which the current trait was composed | ||
for (const innerTrait of trait[implementedTraits] as Function[]) { | ||
if (!flatImplementedTraits.includes(innerTrait)) { | ||
flatImplementedTraits.push(innerTrait) | ||
} | ||
} | ||
} | ||
if (!flatImplementedTraits.includes(trait)) { | ||
flatImplementedTraits.push(trait) | ||
} | ||
} | ||
Object.defineProperty(base, implementedTraits, { | ||
value: flatImplementedTraits, | ||
enumerable: false | ||
}) | ||
return base | ||
@@ -47,2 +82,3 @@ }) as TraitComposition | ||
[], | ||
{}, | ||
{} | ||
@@ -54,2 +90,3 @@ > | ||
parameters extends readonly unknown[], | ||
statics extends {}, | ||
instance extends {} | ||
@@ -63,4 +100,5 @@ > = traits extends readonly [ | ||
intersectParameters<parameters, nextArgs>, | ||
statics & { [k in keyof traits[0]]: traits[0][k] }, | ||
instance & nextInstance | ||
> | ||
: abstract new (...args: parameters) => instance | ||
: statics & (abstract new (...args: parameters) => instance) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
181321
2632