@arktype/util
Advanced tools
Comparing version 0.0.26 to 0.0.27
@@ -48,3 +48,3 @@ import { attest } from "@arktype/attest" | ||
constructor(attach: attach) { | ||
super(() => 0, attach) | ||
super(() => 0, { attach }) | ||
} | ||
@@ -55,2 +55,6 @@ | ||
} | ||
getAttached<k extends keyof attach>(k: k): attach[k] { | ||
return (this as any)[k] | ||
} | ||
} | ||
@@ -67,3 +71,31 @@ | ||
attest<2>(foo.b()).snap(2) | ||
// can access attached on methods | ||
attest<1>(foo.getAttached("a")).snap(1) | ||
}) | ||
it("can access attached properties and prototype methods", () => { | ||
class GetAttached<attach extends object> extends Callable< | ||
<k extends keyof attach>(k: k) => attach[k], | ||
attach | ||
> { | ||
constructor(attach: attach) { | ||
super(<k extends keyof attach>(k: k) => this.protoGetAttached(k), { | ||
attach | ||
}) | ||
} | ||
protoGetAttached(k: PropertyKey) { | ||
return (this as any)[k] | ||
} | ||
getAttached<k extends keyof attach>(k: k): attach[k] { | ||
return this(k) | ||
} | ||
} | ||
const foo = new GetAttached({ a: 1 } as const) | ||
attest<1>(foo.a).equals(1) | ||
attest<1>(foo.getAttached("a")).equals(1) | ||
attest<1>(foo("a")).equals(1) | ||
}) | ||
}) |
import { attest } from "@arktype/attest" | ||
import type { conform, Hkt, List } from "@arktype/util" | ||
import { Hkt, type conform, type evaluate, type List } from "@arktype/util" | ||
@@ -22,2 +22,39 @@ describe("hkt", () => { | ||
}) | ||
const AddB = new (class AddB extends Hkt.UnaryKind { | ||
f = ( | ||
args: conform<this[Hkt.key], { a: number }> | ||
): evaluate<typeof args & { b: (typeof args)["a"] }> => | ||
Object.assign(args, { b: args.a } as const) | ||
})() | ||
const AddC = new (class extends Hkt.UnaryKind { | ||
f = ( | ||
args: conform<this[Hkt.key], { a: number; b: number }> | ||
): evaluate< | ||
typeof args & { c: [(typeof args)["a"], (typeof args)["b"]] } | ||
> => Object.assign(args, { c: [args.a, args.b] } as const) as never | ||
})() | ||
it("pipe", () => { | ||
type result1 = Hkt.apply<typeof AddB, { a: 1 }> | ||
attest<{ a: 1; b: 1 }, result1>() | ||
const addAB = Hkt.pipe(AddB, AddC) | ||
const result = addAB({ a: 1 as const }) | ||
attest<{ a: 1; b: 1; c: [1, 1] }>(result).equals({ a: 1, b: 1, c: [1, 1] }) | ||
}) | ||
it("initial parameter", () => { | ||
const addAB = Hkt.pipe(AddB, AddC) | ||
// @ts-expect-error | ||
attest(() => addAB({})).type.errors.snap() | ||
}) | ||
it("validates pipeable", () => { | ||
const AddD = new (class AddD extends Hkt.UnaryKind { | ||
f = ( | ||
args: conform<this[Hkt.key], { c: number }> | ||
): evaluate<typeof args & { d: (typeof args)["c"] }> => { | ||
return Object.assign(args, { d: args.c } as const) | ||
} | ||
})() | ||
// @ts-expect-error | ||
attest(() => Hkt.pipe(AddB, AddD)).type.errors.snap() | ||
}) | ||
}) |
@@ -1,2 +0,2 @@ | ||
import { Trait, compose } from "@arktype/util" | ||
import { Trait, implement } from "../main.js" | ||
@@ -22,3 +22,10 @@ // Declare a trait just like a normal class | ||
// Pass an object to the generic parameter to declare abstract methods | ||
export class Rhombus extends Trait<{ largestAngle: number; area(): number }> { | ||
export class Rhombus extends Trait<{ | ||
abstractMethods: { | ||
calculateArea(): number | ||
} | ||
abstractProps: { | ||
largestAngle: number | ||
} | ||
}> { | ||
constructor(public side: number) { | ||
@@ -34,12 +41,12 @@ super() | ||
// Use compose to implement a type-safe set of Traits | ||
class Square extends compose(Rectangle, Rhombus)( | ||
class Square extends implement( | ||
Rectangle, | ||
Rhombus, | ||
// Here we have to implement any declared abstract methods. | ||
// Notice we don't need to reimplement area() since it can be derived from Rectangle! | ||
{ | ||
largestAngle: 90 | ||
}, | ||
// Methods with the same name must be disambiguated by specifying which | ||
// Trait to use. If there are no overlaps, this parameter is optional. | ||
{ | ||
perimeter: Rhombus | ||
calculateArea() { | ||
return this.side ** 2 | ||
}, | ||
construct: () => ({ largestAngle: 90 }) | ||
} | ||
@@ -46,0 +53,0 @@ ) { |
import { attest } from "@arktype/attest" | ||
import { Trait, compose } from "@arktype/util" | ||
import { Trait, compose, implement } from "@arktype/util" | ||
export class Describable extends Trait<{ writeDefaultDescription(): string }> { | ||
export class Describable extends Trait<{ | ||
abstractMethods: { | ||
writeDefaultDescription(): string | ||
} | ||
}> { | ||
description: string | ||
@@ -13,3 +17,5 @@ | ||
export class Boundable<data> extends Trait<{ sizeOf(data: data): number }> { | ||
export class Boundable<data> extends Trait<{ | ||
abstractMethods: { sizeOf(data: data): number } | ||
}> { | ||
limit: number | undefined | ||
@@ -29,8 +35,5 @@ | ||
it("compose", () => { | ||
class StringChecker extends compose( | ||
Describable, | ||
Boundable<string> | ||
)({ | ||
sizeOf: (data: string) => data.length, | ||
writeDefaultDescription: () => "foo" | ||
class StringChecker extends implement(Describable, Boundable<string>, { | ||
writeDefaultDescription: () => "foo", | ||
sizeOf: (data: string) => data.length | ||
}) {} | ||
@@ -74,3 +77,3 @@ | ||
} | ||
class Bar extends compose(Foo)({ | ||
class Bar extends implement(Foo, { | ||
sizeOf: (data: number) => data | ||
@@ -89,3 +92,7 @@ }) {} | ||
} | ||
class B extends Trait { | ||
class B extends Trait<{ | ||
abstractStatics: { | ||
foo: "Bar" | ||
} | ||
}> { | ||
static readonly b = "b" | ||
@@ -95,3 +102,4 @@ | ||
} | ||
class C extends compose(A, B)({}) { | ||
class C extends compose(A, B) { | ||
static readonly c = "c" | ||
@@ -118,3 +126,3 @@ | ||
} | ||
class C extends compose(A, B)({}) { | ||
class C extends compose(A, B) { | ||
readonly c = "c" | ||
@@ -125,3 +133,3 @@ } | ||
} | ||
class E extends compose(C, D)({}) { | ||
class E extends compose(C, D) { | ||
readonly e = "e" | ||
@@ -139,47 +147,9 @@ } | ||
it("requires abstract properties be implemented", () => { | ||
class A extends Trait<{ a(): number }> {} | ||
class B extends Trait<{ b(): number }> {} | ||
class A extends Trait<{ abstractMethods: { a(): number } }> {} | ||
class B extends Trait<{ abstractMethods: { b(): number } }> {} | ||
// @ts-expect-error | ||
attest(class C extends compose(A, B)({}) {}).type.errors( | ||
attest(class C extends implement(A, B, {}) {}).type.errors( | ||
"Type '{}' is missing the following properties from type '{ a: () => number; b: () => number; }': a, b" | ||
) | ||
}) | ||
it("can disambiguate conflicting implementations", () => { | ||
class A1 extends Trait { | ||
a() { | ||
return 1 | ||
} | ||
} | ||
class A2 extends Trait { | ||
a() { | ||
return 2 | ||
} | ||
} | ||
// @ts-expect-error | ||
attest(class A3 extends compose(A1, A2)({}) {}).type.errors( | ||
"Expected 2 arguments, but got 1." | ||
) | ||
// you can disambiguate by implementing the method yourself | ||
class A4 extends compose( | ||
A1, | ||
A2 | ||
)({ | ||
a: () => 4 as const | ||
}) {} | ||
attest<4>(new A4().a()).equals(4) | ||
// or you can disambiguate by specifying a disambiguation param | ||
class A5 extends compose(A1, A2)({}, { a: A1 }) {} | ||
attest<number>(new A5().a()).equals(1) | ||
class A6 extends compose(A1, A2)({}, { a: A2 }) {} | ||
attest<number>(new A6().a()).equals(2) | ||
}) | ||
}) |
@@ -54,3 +54,3 @@ import type { evaluate } from "./generics.js" | ||
export const domainOf = <data>(data: data) => { | ||
export const domainOf = <data>(data: data): domainOf<data> => { | ||
const builtinType = typeof data | ||
@@ -84,3 +84,3 @@ return ( | ||
/** Each domain's completion for the phrase "Must be _____" */ | ||
/** Each domain's completion for the phrase "must be _____" */ | ||
export const domainDescriptions = { | ||
@@ -87,0 +87,0 @@ ...nonEnumerableDomainDescriptions, |
@@ -13,3 +13,5 @@ export class InternalArktypeError extends Error {} | ||
export class ParseError extends Error {} | ||
export class ParseError extends Error { | ||
name = "ParseError" | ||
} | ||
@@ -16,0 +18,0 @@ export const throwParseError: (message: string) => never = (message) => |
import { throwInternalError } from "./errors.js" | ||
import { NoopBase, type Dict } from "./records.js" | ||
import { NoopBase } from "./records.js" | ||
export const cached = <T>(thunk: () => T) => { | ||
export const cached = <T>(thunk: () => T): (() => T) => { | ||
let isCached = false | ||
@@ -71,2 +71,11 @@ let result: T | undefined | ||
) | ||
// const proto = opts?.bind ?? this.constructor.prototype | ||
// const self = Object.create( | ||
// proto, | ||
// Object.getOwnPropertyDescriptors(opts?.attach) | ||
// ) | ||
// return Object.assign( | ||
// Object.setPrototypeOf(f.bind(self), proto), | ||
// opts?.attach | ||
// ) | ||
} | ||
@@ -73,0 +82,0 @@ } |
@@ -100,3 +100,3 @@ import type { Primitive } from "./domain.js" | ||
] | ||
? [head, ...narrowTuple<tail>] | ||
? readonly [head, ...narrowTuple<tail>] | ||
: [] | ||
@@ -110,2 +110,2 @@ | ||
export const narrow = <t>(t: narrow<t>) => t | ||
export const narrow = <t>(t: narrow<t>): t => t as t |
53
hkt.ts
@@ -35,2 +35,55 @@ import type { conform } from "./generics.js" | ||
) => Hkt.apply<hkt, In> | ||
export abstract class UnaryKind< | ||
f extends (In: never) => unknown = (In: any) => unknown | ||
> { | ||
declare readonly [key]: unknown | ||
abstract readonly f: f | ||
} | ||
type validatePipedKinds< | ||
kinds extends UnaryKind[], | ||
out = Parameters<kinds[0]["f"]>[0] | ||
> = kinds extends readonly [ | ||
infer head extends UnaryKind, | ||
...infer tail extends UnaryKind[] | ||
] | ||
? out extends Parameters<head["f"]>[0] | ||
? [kinds[0], ...validatePipedKinds<tail, Hkt.apply<head, out>>] | ||
: [Kind<(In: out) => unknown>, ...tail] | ||
: [] | ||
type inferPipedReturn< | ||
kinds extends UnaryKind[], | ||
out | ||
> = kinds extends readonly [ | ||
infer head extends UnaryKind, | ||
...infer tail extends UnaryKind[] | ||
] | ||
? inferPipedReturn<tail, Hkt.apply<head, out>> | ||
: out | ||
export type pipe<kinds extends UnaryKind[]> = < | ||
In extends Parameters<kinds[0]["f"]>[0] | ||
>( | ||
In: In | ||
) => inferPipedReturn<kinds, In> | ||
export const pipe = | ||
< | ||
kinds extends UnaryKind[], | ||
validatedKinds extends UnaryKind[] = validatePipedKinds<kinds> | ||
>( | ||
...kinds: { | ||
[i in keyof kinds]: conform< | ||
kinds[i], | ||
validatedKinds[i & keyof validatedKinds] | ||
> | ||
} | ||
): pipe<kinds> => | ||
(In) => | ||
kinds.reduce( | ||
(out, kind) => (kind as Hkt.UnaryKind<(_: any) => any>).f(out), | ||
In | ||
) | ||
} |
import type { evaluate } from "./generics.js" | ||
import type { List, listable } from "./lists.js" | ||
import type { Entry, entryOf, fromEntries } from "./records.js" | ||
import type { Entry, Key, entryOf, fromEntries } from "./records.js" | ||
import type { intersectUnion } from "./unionToTuple.js" | ||
@@ -49,3 +49,3 @@ | ||
export type MappedEntry = listable<Entry<string | symbol> | Entry<number>> | ||
export type MappedEntry = listable<Entry<Key> | Entry<number>> | ||
@@ -52,0 +52,0 @@ export type fromMappedEntries<transformed extends MappedEntry> = [ |
@@ -30,3 +30,5 @@ import { throwParseError } from "./errors.js" | ||
export const isWellFormedNumber = wellFormedNumberMatcher.test | ||
export const isWellFormedNumber = wellFormedNumberMatcher.test.bind( | ||
wellFormedNumberMatcher | ||
) | ||
@@ -38,10 +40,12 @@ const numberLikeMatcher = /^-?\d*\.?\d*$/ | ||
* Matches a well-formatted integer according to the following rules: | ||
* 1. Must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 1. must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 2. The value may not be "-0" | ||
*/ | ||
export const wellFormedIntegerMatcher = /^(?:0|(?:-?[1-9]\d*))$/ | ||
export const isWellFormedInteger = wellFormedIntegerMatcher.test | ||
export const isWellFormedInteger = wellFormedIntegerMatcher.test.bind( | ||
wellFormedIntegerMatcher | ||
) | ||
const integerLikeMatcher = /^-?\d+$/ | ||
const isIntegerLike = (s: string) => integerLikeMatcher.test(s) | ||
const isIntegerLike = integerLikeMatcher.test.bind(integerLikeMatcher) | ||
@@ -48,0 +52,0 @@ type NumericLiteralKind = "number" | "bigint" | "integer" |
import { domainOf, type Domain, type domainDescriptions } from "./domain.js" | ||
import type { evaluate } from "./generics.js" | ||
import type { List } from "./lists.js" | ||
import { isKeyOf } from "./records.js" | ||
import { isKeyOf, type Key } from "./records.js" | ||
@@ -111,3 +110,3 @@ // Built-in object constructors based on a subset of: | ||
/** Each defaultObjectKind's completion for the phrase "Must be _____" */ | ||
/** Each defaultObjectKind's completion for the phrase "must be _____" */ | ||
export const objectKindDescriptions = { | ||
@@ -170,5 +169,11 @@ Array: "an array", | ||
export type normalizedKeyOf<t> = keyof t extends infer k | ||
? k extends number | ||
? `${k}` | ||
: k | ||
: never | ||
/** Mimics output of TS's keyof operator at runtime */ | ||
export const prototypeKeysOf = <t>(value: t): evaluate<keyof t>[] => { | ||
const result: (string | symbol)[] = [] | ||
export const prototypeKeysOf = <t>(value: t): normalizedKeyOf<t>[] => { | ||
const result: Key[] = [] | ||
while (value !== Object.prototype && value !== null && value !== undefined) { | ||
@@ -187,6 +192,6 @@ for (const k of Object.getOwnPropertyNames(value)) { | ||
} | ||
return result as evaluate<keyof t>[] | ||
return result as never | ||
} | ||
const baseKeysByDomain: Record<Domain, readonly PropertyKey[]> = { | ||
const baseKeysByDomain: Record<Domain, readonly Key[]> = { | ||
bigint: prototypeKeysOf(0n), | ||
@@ -205,3 +210,3 @@ boolean: prototypeKeysOf(false), | ||
domain: domain | ||
): PropertyKey[] => [...baseKeysByDomain[domain]] | ||
): Key[] => [...baseKeysByDomain[domain]] | ||
@@ -208,0 +213,0 @@ export const constructorExtends = ( |
@@ -30,3 +30,3 @@ import type { evaluate } from "./generics.js"; | ||
export type NonEnumerableDomain = keyof typeof nonEnumerableDomainDescriptions; | ||
/** Each domain's completion for the phrase "Must be _____" */ | ||
/** Each domain's completion for the phrase "must be _____" */ | ||
export declare const domainDescriptions: { | ||
@@ -33,0 +33,0 @@ boolean: "boolean"; |
@@ -24,3 +24,3 @@ export const hasDomain = (data, kind) => domainOf(data) === kind; | ||
}; | ||
/** Each domain's completion for the phrase "Must be _____" */ | ||
/** Each domain's completion for the phrase "must be _____" */ | ||
export const domainDescriptions = { | ||
@@ -27,0 +27,0 @@ ...nonEnumerableDomainDescriptions, |
@@ -6,2 +6,3 @@ export declare class InternalArktypeError extends Error { | ||
export declare class ParseError extends Error { | ||
name: string; | ||
} | ||
@@ -8,0 +9,0 @@ export declare const throwParseError: (message: string) => never; |
@@ -8,4 +8,5 @@ export class InternalArktypeError extends Error { | ||
export class ParseError extends Error { | ||
name = "ParseError"; | ||
} | ||
export const throwParseError = (message) => throwError(message, ParseError); | ||
//# sourceMappingURL=errors.js.map |
import { NoopBase } from "./records.js"; | ||
export declare const cached: <T>(thunk: () => T) => () => T; | ||
export declare const cached: <T>(thunk: () => T) => (() => T); | ||
export declare const isThunk: <value>(value: value) => value is Extract<value, Thunk<unknown>> extends never ? value & Thunk<unknown> : Extract<value, Thunk<unknown>>; | ||
@@ -4,0 +4,0 @@ export type Thunk<ret = unknown> = () => ret; |
@@ -36,4 +36,13 @@ import { throwInternalError } from "./errors.js"; | ||
return Object.assign(Object.setPrototypeOf(f.bind(opts?.bind ?? this), this.constructor.prototype), opts?.attach); | ||
// const proto = opts?.bind ?? this.constructor.prototype | ||
// const self = Object.create( | ||
// proto, | ||
// Object.getOwnPropertyDescriptors(opts?.attach) | ||
// ) | ||
// return Object.assign( | ||
// Object.setPrototypeOf(f.bind(self), proto), | ||
// opts?.attach | ||
// ) | ||
} | ||
} | ||
//# sourceMappingURL=functions.js.map |
@@ -43,8 +43,8 @@ import type { Primitive } from "./domain.js"; | ||
...infer tail | ||
] ? [head, ...narrowTuple<tail>] : []; | ||
] ? readonly [head, ...narrowTuple<tail>] : []; | ||
export type narrow<t> = t extends Primitive ? t : t extends readonly unknown[] ? narrowTuple<t> : { | ||
[k in keyof t]: narrow<t[k]>; | ||
}; | ||
export declare const narrow: <t>(t: narrow<t>) => narrow<t>; | ||
export declare const narrow: <t>(t: narrow<t>) => t; | ||
export {}; | ||
//# sourceMappingURL=generics.d.ts.map |
import type { conform } from "./generics.js"; | ||
/** A small set of HKT utility types based on https://github.com/poteat/hkt-toolbelt */ | ||
export declare namespace Hkt { | ||
const key: unique symbol; | ||
type key = typeof key; | ||
abstract class Kind<f extends (...args: any[]) => unknown = (...args: any[]) => unknown> { | ||
export const key: unique symbol; | ||
export type key = typeof key; | ||
export abstract class Kind<f extends (...args: any[]) => unknown = (...args: any[]) => unknown> { | ||
readonly [key]: unknown; | ||
abstract readonly f: f; | ||
} | ||
type apply<hkt extends Kind, args extends Parameters<hkt["f"]>[0]> = ReturnType<(hkt & { | ||
export type apply<hkt extends Kind, args extends Parameters<hkt["f"]>[0]> = ReturnType<(hkt & { | ||
readonly [key]: args; | ||
})["f"]>; | ||
interface Reify extends Kind { | ||
export interface Reify extends Kind { | ||
f(In: conform<this[key], Kind>): reify<typeof In>; | ||
} | ||
const reify: <def extends Kind<(...args: any[]) => unknown>>(def: def) => reify<def>; | ||
type reify<hkt extends Kind> = <const In extends Parameters<hkt["f"]>[0]>(In: In) => Hkt.apply<hkt, In>; | ||
export const reify: <def extends Kind<(...args: any[]) => unknown>>(def: def) => reify<def>; | ||
export type reify<hkt extends Kind> = <const In extends Parameters<hkt["f"]>[0]>(In: In) => Hkt.apply<hkt, In>; | ||
export abstract class UnaryKind<f extends (In: never) => unknown = (In: any) => unknown> { | ||
readonly [key]: unknown; | ||
abstract readonly f: f; | ||
} | ||
type validatePipedKinds<kinds extends UnaryKind[], out = Parameters<kinds[0]["f"]>[0]> = kinds extends readonly [ | ||
infer head extends UnaryKind, | ||
...infer tail extends UnaryKind[] | ||
] ? out extends Parameters<head["f"]>[0] ? [kinds[0], ...validatePipedKinds<tail, Hkt.apply<head, out>>] : [Kind<(In: out) => unknown>, ...tail] : []; | ||
type inferPipedReturn<kinds extends UnaryKind[], out> = kinds extends readonly [ | ||
infer head extends UnaryKind, | ||
...infer tail extends UnaryKind[] | ||
] ? inferPipedReturn<tail, Hkt.apply<head, out>> : out; | ||
export type pipe<kinds extends UnaryKind[]> = <In extends Parameters<kinds[0]["f"]>[0]>(In: In) => inferPipedReturn<kinds, In>; | ||
export const pipe: <kinds extends UnaryKind<(In: any) => unknown>[], validatedKinds extends UnaryKind<(In: any) => unknown>[] = validatePipedKinds<kinds, Parameters<kinds[0]["f"]>[0]>>(...kinds: { [i in keyof kinds]: conform<kinds[i], validatedKinds[i & keyof validatedKinds]>; }) => pipe<kinds>; | ||
export {}; | ||
} | ||
//# sourceMappingURL=hkt.d.ts.map |
@@ -8,3 +8,7 @@ /** A small set of HKT utility types based on https://github.com/poteat/hkt-toolbelt */ | ||
Hkt.reify = (def) => def.f; | ||
class UnaryKind { | ||
} | ||
Hkt.UnaryKind = UnaryKind; | ||
Hkt.pipe = (...kinds) => (In) => kinds.reduce((out, kind) => kind.f(out), In); | ||
})(Hkt || (Hkt = {})); | ||
//# sourceMappingURL=hkt.js.map |
@@ -22,3 +22,3 @@ import type { isDisjoint } from "./intersections.js"; | ||
export declare const spliterate: <item, included extends item>(list: readonly item[], by: (item: item) => item is included) => [included: included[], excluded: Exclude<item, included>[]]; | ||
export declare const ReadonlyArray: new <T>(...args: T[]) => readonly T[]; | ||
export declare const ReadonlyArray: new <T>(...args: ConstructorParameters<typeof Array<T>>) => ReadonlyArray<T>; | ||
export declare const includes: <array extends List<unknown>>(array: array, element: unknown) => element is array[number]; | ||
@@ -25,0 +25,0 @@ export declare const range: (length: number, offset?: number) => number[]; |
import type { evaluate } from "./generics.js"; | ||
import type { List, listable } from "./lists.js"; | ||
import type { Entry, entryOf, fromEntries } from "./records.js"; | ||
import type { Entry, Key, entryOf, fromEntries } from "./records.js"; | ||
import type { intersectUnion } from "./unionToTuple.js"; | ||
@@ -16,3 +16,3 @@ type objectFromListableEntries<transformed extends readonly Entry[]> = evaluate<intersectUnion<fromEntries<transformed>>>; | ||
}[number]; | ||
export type MappedEntry = listable<Entry<string | symbol> | Entry<number>>; | ||
export type MappedEntry = listable<Entry<Key> | Entry<number>>; | ||
export type fromMappedEntries<transformed extends MappedEntry> = [ | ||
@@ -19,0 +19,0 @@ transformed |
@@ -24,3 +24,3 @@ export type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; | ||
* Matches a well-formatted integer according to the following rules: | ||
* 1. Must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 1. must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 2. The value may not be "-0" | ||
@@ -43,6 +43,6 @@ */ | ||
}[kind]} but could not be narrowed to a literal value. Avoid unnecessary leading or trailing zeros and other abnormal notation`; | ||
export declare const tryParseNumber: <errorOnFail extends string | boolean>(token: string, options?: NumericParseOptions<errorOnFail> | undefined) => errorOnFail extends string | true ? number : number | undefined; | ||
export declare const tryParseNumber: <errorOnFail extends string | boolean>(token: string, options?: NumericParseOptions<errorOnFail>) => errorOnFail extends true | string ? number : number | undefined; | ||
export type tryParseNumber<token extends string, messageOnFail extends string> = token extends NumberLiteral<infer value> ? number extends value ? writeMalformedNumericLiteralMessage<token, "number"> : value : messageOnFail; | ||
export type parseNumber<token extends string> = token extends NumberLiteral<infer value> ? value : never; | ||
export declare const tryParseInteger: <errorOnFail extends string | boolean>(token: string, options?: NumericParseOptions<errorOnFail> | undefined) => errorOnFail extends string | true ? number : number | undefined; | ||
export declare const tryParseInteger: <errorOnFail extends string | boolean>(token: string, options?: NumericParseOptions<errorOnFail>) => errorOnFail extends true | string ? number : number | undefined; | ||
export type tryParseInteger<token extends string, messageOnFail extends string> = token extends IntegerLiteral<infer value> ? bigint extends value ? writeMalformedNumericLiteralMessage<token, "integer"> : `${value}` extends NumberLiteral<infer valueAsNumber> ? valueAsNumber : never : messageOnFail; | ||
@@ -49,0 +49,0 @@ export type parseInteger<token extends string> = token extends IntegerLiteral<infer value> ? `${value}` extends NumberLiteral<infer valueAsNumber> ? valueAsNumber : never : never; |
@@ -18,3 +18,3 @@ import { throwParseError } from "./errors.js"; | ||
export const wellFormedNumberMatcher = /^(?!^-0$)-?(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/; | ||
export const isWellFormedNumber = wellFormedNumberMatcher.test; | ||
export const isWellFormedNumber = wellFormedNumberMatcher.test.bind(wellFormedNumberMatcher); | ||
const numberLikeMatcher = /^-?\d*\.?\d*$/; | ||
@@ -24,9 +24,9 @@ const isNumberLike = (s) => s.length !== 0 && numberLikeMatcher.test(s); | ||
* Matches a well-formatted integer according to the following rules: | ||
* 1. Must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 1. must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0 | ||
* 2. The value may not be "-0" | ||
*/ | ||
export const wellFormedIntegerMatcher = /^(?:0|(?:-?[1-9]\d*))$/; | ||
export const isWellFormedInteger = wellFormedIntegerMatcher.test; | ||
export const isWellFormedInteger = wellFormedIntegerMatcher.test.bind(wellFormedIntegerMatcher); | ||
const integerLikeMatcher = /^-?\d+$/; | ||
const isIntegerLike = (s) => integerLikeMatcher.test(s); | ||
const isIntegerLike = integerLikeMatcher.test.bind(integerLikeMatcher); | ||
const numericLiteralDescriptions = { | ||
@@ -33,0 +33,0 @@ number: "a number", |
import { domainOf, type Domain, type domainDescriptions } from "./domain.js"; | ||
import type { evaluate } from "./generics.js"; | ||
import type { List } from "./lists.js"; | ||
import { type Key } from "./records.js"; | ||
export declare const builtinObjectKinds: { | ||
@@ -77,3 +77,3 @@ readonly Array: ArrayConstructor; | ||
export declare const isArray: (data: unknown) => data is List; | ||
/** Each defaultObjectKind's completion for the phrase "Must be _____" */ | ||
/** Each defaultObjectKind's completion for the phrase "must be _____" */ | ||
export declare const objectKindDescriptions: { | ||
@@ -105,7 +105,8 @@ readonly Array: "an array"; | ||
export declare const ancestorsOf: (o: object) => Function[]; | ||
export type normalizedKeyOf<t> = keyof t extends infer k ? k extends number ? `${k}` : k : never; | ||
/** Mimics output of TS's keyof operator at runtime */ | ||
export declare const prototypeKeysOf: <t>(value: t) => (keyof t)[]; | ||
export declare const getBaseDomainKeys: <domain extends Domain>(domain: domain) => PropertyKey[]; | ||
export declare const prototypeKeysOf: <t>(value: t) => normalizedKeyOf<t>[]; | ||
export declare const getBaseDomainKeys: <domain extends Domain>(domain: domain) => Key[]; | ||
export declare const constructorExtends: (constructor: Constructor, base: Constructor) => boolean; | ||
export {}; | ||
//# sourceMappingURL=objectKinds.d.ts.map |
@@ -39,3 +39,3 @@ import { domainOf } from "./domain.js"; | ||
export const isArray = (data) => Array.isArray(data); | ||
/** Each defaultObjectKind's completion for the phrase "Must be _____" */ | ||
/** Each defaultObjectKind's completion for the phrase "must be _____" */ | ||
export const objectKindDescriptions = { | ||
@@ -42,0 +42,0 @@ Array: "an array", |
@@ -94,3 +94,3 @@ import type { defined, evaluate } from "./generics.js"; | ||
export declare const isEmptyObject: (o: object) => o is EmptyObject; | ||
export declare const stringAndSymbolicEntriesOf: (o: Record<string | symbol, unknown>) => Entry<string | symbol>[]; | ||
export declare const stringAndSymbolicEntriesOf: (o: Record<Key, unknown>) => Entry<Key>[]; | ||
export type Key = string | symbol; | ||
@@ -97,0 +97,0 @@ export type invert<t extends Record<PropertyKey, PropertyKey>> = { |
@@ -14,3 +14,3 @@ import { type inferDomain, type Primitive } from "./domain.js"; | ||
export type JsonData = string | boolean | number | null | Json; | ||
export declare const snapshot: <t>(data: t, opts?: SerializationOptions) => snapshot<t, []>; | ||
export declare const snapshot: <t>(data: t, opts?: SerializationOptions) => snapshot<t>; | ||
export type snapshot<t, depth extends 1[] = []> = unknown extends t ? unknown : t extends Primitive ? snapshotPrimitive<t> : t extends Function ? `(function${string})` : t extends Date ? string : depth["length"] extends 10 ? unknown : t extends List<infer item> ? List<snapshot<item, [...depth, 1]>> : { | ||
@@ -17,0 +17,0 @@ [k in keyof t]: snapshot<t[k], [...depth, 1]>; |
@@ -1,28 +0,22 @@ | ||
import type { conform, evaluate } from "./generics.js"; | ||
import type { conform, evaluate, satisfy } from "./generics.js"; | ||
import type { intersectParameters } from "./intersections.js"; | ||
import type { List } from "./lists.js"; | ||
import { type Constructor } from "./objectKinds.js"; | ||
import { NoopBase, type optionalizeKeys } from "./records.js"; | ||
export type TraitComposition = <traits extends readonly TraitConstructor[]>(...traits: traits) => TraitImplementation<traits, compose<traits>>; | ||
type TraitImplementation<traits extends readonly TraitConstructor[], composed extends ComposedTraits> = <implementation>(implementation: conform<implementation, baseImplementationOf<composed>> & ThisType<implementation & composed["implemented"]>, ...disambiguation: baseDisambiguationOf<traits, implementation, composed> extends infer disambiguation ? {} extends disambiguation ? [] : [disambiguation] : never) => TraitsBase<composed, implementation>; | ||
type TraitsBase<composed extends ComposedTraits, implementation> = composed["statics"] & (new (...args: composed["params"]) => evaluate<intersectImplementations<implementation, composed["implemented"]>>); | ||
type optionalizeSatisfied<base> = optionalizeKeys<base, { | ||
[k in keyof base]: undefined extends base[k] ? k : never; | ||
}[keyof base]>; | ||
type baseImplementationOf<composed extends ComposedTraits> = optionalizeSatisfied<{ | ||
[k in keyof composed["abstracted"]]: k extends keyof composed["implemented"] ? composed["implemented"][k] extends composed["abstracted"][k] ? composed["implemented"][k] | undefined : composed["implemented"][k] : composed["abstracted"][k]; | ||
}>; | ||
type omitUnambiguous<base> = Omit<base, { | ||
[k in keyof base]: undefined extends base[k] ? k : never; | ||
}[keyof base]>; | ||
type baseDisambiguationOf<traits extends readonly TraitConstructor[], implementation, composed extends ComposedTraits> = omitUnambiguous<{ | ||
[k in keyof composed["implemented"]]: k extends keyof implementation ? undefined : k extends keyof Trait ? undefined : traitsImplementingKey<traits, k> extends infer implementations extends TraitConstructor[] ? implementations["length"] extends 1 ? undefined : implementations[number] : never; | ||
}>; | ||
type traitsImplementingKey<traits extends List, k, result extends unknown[] = []> = traits extends readonly [ | ||
TraitConstructor<any[], infer instance, infer abstracts>, | ||
...infer tail | ||
] ? traitsImplementingKey<tail, k, k extends Exclude<keyof instance, keyof abstracts> ? [...result, traits[0]] : result> : result; | ||
import { NoopBase } from "./records.js"; | ||
export type TraitImplementation = <traits extends TraitConstructor[], implementation extends implementationOf<s>, s extends CompositionState = composeTraits<[ | ||
...traits, | ||
implementation | ||
], "implementation">, cls extends TraitConstructor = TraitConstructor<s["params"], s["implemented"], s["statics"], s["abstractMethods"], s["abstractProps"], s["abstractStatics"]>>(...args: [...traits, implementation & ThisType<InstanceType<cls>>]) => cls; | ||
export type TraitComposition = <traits extends TraitConstructor[], s extends CompositionState = composeTraits<traits, "abstract">>(...traits: conform<traits, s["validated"]>) => TraitConstructor<s["params"], s["implemented"], s["statics"], s["abstractMethods"], s["abstractProps"], s["abstractStatics"]>; | ||
export declare const hasTrait: (traitClass: Constructor) => (o: unknown) => boolean; | ||
export declare abstract class Trait<abstracted extends object = {}, implemented extends object = {}> extends NoopBase<abstracted & implemented> { | ||
$abstracted: abstracted; | ||
export type TraitDeclaration = { | ||
abstractMethods?: object; | ||
abstractProps?: object; | ||
abstractStatics?: object; | ||
dynamicBase?: object; | ||
}; | ||
export declare abstract class Trait<d extends TraitDeclaration = {}, abstractMethods extends object = d["abstractMethods"] & {}, abstractProps extends object = d["abstractProps"] & {}, abstractStatics extends object = d["abstractStatics"] & {}, dynamicBase extends object = d["dynamicBase"] & {}> extends NoopBase<abstractMethods & abstractProps & dynamicBase> { | ||
abstractMethods: abstractMethods; | ||
abstractProps: abstractProps; | ||
abstractStatic: abstractStatics; | ||
static get [Symbol.hasInstance](): (o: unknown) => boolean; | ||
@@ -32,25 +26,65 @@ traitsOf(): readonly TraitConstructor[]; | ||
export declare const compose: TraitComposition; | ||
export type TraitConstructor<params extends List = any[], instance = {}, abstracted = {}, statics = {}> = statics & (new (...args: params) => { | ||
$abstracted: abstracted; | ||
} & instance); | ||
export type ComposedTraits = { | ||
export declare const implement: TraitImplementation; | ||
export type TraitConstructor<params extends List = any[], instance extends object = {}, statics = {}, abstractMethods extends object = {}, abstractProps extends object = {}, abstractStatics extends object = {}> = statics & (new (...args: params) => Trait<{ | ||
abstractMethods: abstractMethods; | ||
abstractProps: abstractProps; | ||
abstractStatics: abstractStatics; | ||
}> & instance); | ||
type CompositionState = { | ||
validated: List; | ||
remaining: List; | ||
params: List; | ||
implemented: unknown; | ||
abstracted: unknown; | ||
statics: unknown; | ||
kind: TraitCompositionKind; | ||
implemented: object; | ||
abstractMethods: object; | ||
abstractProps: object; | ||
abstractStatics: object; | ||
statics: object; | ||
}; | ||
export type compose<traits extends readonly TraitConstructor[]> = composeRecurse<traits, [], {}, {}, {}>; | ||
export type TraitCompositionKind = "abstract" | "implementation"; | ||
export type composeTraits<traits extends List, kind extends TraitCompositionKind> = composeRecurse<{ | ||
validated: []; | ||
remaining: traits; | ||
kind: kind; | ||
params: []; | ||
implemented: {}; | ||
abstractMethods: {}; | ||
abstractProps: {}; | ||
abstractStatics: {}; | ||
statics: {}; | ||
}>; | ||
type intersectImplementations<l, r> = { | ||
[k in keyof l]: k extends keyof r ? l[k] extends (...args: infer lArgs) => infer lReturn ? r[k] extends (...args: infer rArgs) => infer rReturn ? (...args: intersectParameters<lArgs, rArgs>) => lReturn & rReturn : l[k] & r[k] : l[k] & r[k] : l[k]; | ||
} & Omit<r, keyof l>; | ||
type composeRecurse<traits extends List, params extends List, implemented, abstracted, statics> = traits extends readonly [ | ||
TraitConstructor<infer nextParams, infer nextInstance, infer nextAbstracted, infer nextStatics>, | ||
type composeRecurse<s extends CompositionState> = s["remaining"] extends readonly [ | ||
TraitConstructor<infer params, infer instance, infer statics, infer abstractMethods, infer abstractProps, infer abstractStatics>, | ||
...infer tail | ||
] ? composeRecurse<tail, intersectParameters<params, nextParams>, intersectImplementations<implemented, Omit<nextInstance, keyof nextAbstracted>>, intersectImplementations<abstracted, nextAbstracted>, intersectImplementations<statics, nextStatics>> : { | ||
params: params; | ||
implemented: evaluate<implemented>; | ||
abstracted: evaluate<abstracted>; | ||
statics: evaluate<Omit<statics, keyof typeof Trait>>; | ||
}; | ||
] ? composeRecurse<{ | ||
validated: [...s["validated"], s["remaining"][0]]; | ||
remaining: tail; | ||
kind: s["kind"]; | ||
params: intersectParameters<s["params"], params>; | ||
implemented: intersectImplementations<s["implemented"], Omit<instance, keyof abstractMethods | keyof abstractProps>>; | ||
statics: intersectImplementations<s["statics"], Omit<statics, keyof abstractStatics>>; | ||
abstractMethods: intersectImplementations<s["abstractMethods"], abstractMethods>; | ||
abstractProps: intersectImplementations<s["abstractProps"], abstractProps>; | ||
abstractStatics: intersectImplementations<s["abstractStatics"], abstractStatics>; | ||
}> : finalizeState<s>; | ||
type finalizeState<s extends CompositionState> = satisfy<CompositionState, { | ||
params: s["params"]; | ||
validated: s["validated"]; | ||
remaining: s["remaining"]; | ||
kind: s["kind"]; | ||
implemented: evaluate<s["implemented"]>; | ||
statics: evaluate<Omit<s["statics"], keyof typeof Trait>>; | ||
abstractMethods: evaluate<Omit<s["abstractMethods"], keyof s["implemented"]>>; | ||
abstractProps: evaluate<Omit<s["abstractProps"], keyof s["implemented"]>>; | ||
abstractStatics: evaluate<Omit<s["abstractStatics"], keyof s["statics"]>>; | ||
}>; | ||
export type implementationOf<s extends CompositionState> = s["abstractMethods"] & ({} extends s["abstractProps"] ? {} : { | ||
construct: (...args: s["params"]) => s["abstractProps"]; | ||
}) & ({} extends s["abstractStatics"] ? {} : { | ||
statics: s["abstractStatics"]; | ||
}); | ||
export {}; | ||
//# sourceMappingURL=traits.d.ts.map |
@@ -18,3 +18,3 @@ import { hasDomain } from "./domain.js"; | ||
}; | ||
// @ts-expect-error allow abstract property access | ||
// @ts-expect-error | ||
export class Trait extends NoopBase { | ||
@@ -30,3 +30,3 @@ static get [Symbol.hasInstance]() { | ||
} | ||
const collectPrototypeDescriptors = (trait, disambiguation) => { | ||
const collectPrototypeDescriptors = (trait) => { | ||
let proto = trait.prototype; | ||
@@ -39,11 +39,5 @@ let result = {}; | ||
} while (proto !== Object.prototype && proto !== null); | ||
for (const k in disambiguation) { | ||
if (disambiguation[k] !== trait) { | ||
// remove keys disambiguated to resolve to other traits | ||
delete result[k]; | ||
} | ||
} | ||
return result; | ||
}; | ||
export const compose = ((...traits) => (implementation, disambiguation = {}) => { | ||
export const compose = ((...traits) => { | ||
const base = function (...args) { | ||
@@ -60,3 +54,3 @@ for (const trait of traits) { | ||
// flatten and copy prototype | ||
Object.defineProperties(base.prototype, collectPrototypeDescriptors(trait, disambiguation)); | ||
Object.defineProperties(base.prototype, collectPrototypeDescriptors(trait)); | ||
if (implementedTraits in trait) { | ||
@@ -78,6 +72,14 @@ // add any ancestor traits from which the current trait was composed | ||
}); | ||
return base; | ||
}); | ||
export const implement = (...args) => { | ||
if (args.at(-1) instanceof Trait) { | ||
return compose(...args); | ||
} | ||
const implementation = args.at(-1); | ||
const base = compose(...args.slice(0, -1)); | ||
// copy implementation last since it overrides traits | ||
Object.defineProperties(base.prototype, Object.getOwnPropertyDescriptors(implementation)); | ||
return base; | ||
}); | ||
}; | ||
//# sourceMappingURL=traits.js.map |
{ | ||
"name": "@arktype/util", | ||
"version": "0.0.26", | ||
"version": "0.0.27", | ||
"author": { | ||
@@ -5,0 +5,0 @@ "name": "David Blass", |
@@ -214,4 +214,4 @@ import type { defined, evaluate } from "./generics.js" | ||
export const stringAndSymbolicEntriesOf = ( | ||
o: Record<string | symbol, unknown> | ||
): Entry<string | symbol>[] => [ | ||
o: Record<Key, unknown> | ||
): Entry<Key>[] => [ | ||
...Object.entries(o), | ||
@@ -218,0 +218,0 @@ ...Object.getOwnPropertySymbols(o).map((k) => [k, o[k]] as const) |
382
traits.ts
import { hasDomain } from "./domain.js" | ||
import type { conform, evaluate } from "./generics.js" | ||
import type { conform, evaluate, satisfy } from "./generics.js" | ||
import type { intersectParameters } from "./intersections.js" | ||
import type { List } from "./lists.js" | ||
import { ancestorsOf, type Constructor } from "./objectKinds.js" | ||
import { NoopBase, type optionalizeKeys } from "./records.js" | ||
import { NoopBase } from "./records.js" | ||
export type TraitComposition = <traits extends readonly TraitConstructor[]>( | ||
...traits: traits | ||
) => TraitImplementation<traits, compose<traits>> | ||
export type TraitImplementation = < | ||
traits extends TraitConstructor[], | ||
implementation extends implementationOf<s>, | ||
s extends CompositionState = composeTraits< | ||
[...traits, implementation], | ||
"implementation" | ||
>, | ||
cls extends TraitConstructor = TraitConstructor< | ||
s["params"], | ||
s["implemented"], | ||
s["statics"], | ||
s["abstractMethods"], | ||
s["abstractProps"], | ||
s["abstractStatics"] | ||
> | ||
>( | ||
...args: [...traits, implementation & ThisType<InstanceType<cls>>] | ||
) => cls | ||
type TraitImplementation< | ||
traits extends readonly TraitConstructor[], | ||
composed extends ComposedTraits | ||
> = <implementation>( | ||
implementation: conform<implementation, baseImplementationOf<composed>> & | ||
ThisType<implementation & composed["implemented"]>, | ||
...disambiguation: baseDisambiguationOf< | ||
traits, | ||
implementation, | ||
composed | ||
> extends infer disambiguation | ||
? {} extends disambiguation | ||
? [] | ||
: [disambiguation] | ||
: never | ||
) => TraitsBase<composed, implementation> | ||
type TraitsBase< | ||
composed extends ComposedTraits, | ||
implementation | ||
> = composed["statics"] & | ||
(new ( | ||
...args: composed["params"] | ||
) => evaluate< | ||
intersectImplementations<implementation, composed["implemented"]> | ||
>) | ||
type optionalizeSatisfied<base> = optionalizeKeys< | ||
base, | ||
{ | ||
[k in keyof base]: undefined extends base[k] ? k : never | ||
}[keyof base] | ||
export type TraitComposition = < | ||
traits extends TraitConstructor[], | ||
s extends CompositionState = composeTraits<traits, "abstract"> | ||
>( | ||
...traits: conform<traits, s["validated"]> | ||
) => TraitConstructor< | ||
s["params"], | ||
s["implemented"], | ||
s["statics"], | ||
s["abstractMethods"], | ||
s["abstractProps"], | ||
s["abstractStatics"] | ||
> | ||
type baseImplementationOf<composed extends ComposedTraits> = | ||
optionalizeSatisfied<{ | ||
[k in keyof composed["abstracted"]]: k extends keyof composed["implemented"] | ||
? composed["implemented"][k] extends composed["abstracted"][k] | ||
? composed["implemented"][k] | undefined | ||
: composed["implemented"][k] | ||
: composed["abstracted"][k] | ||
}> | ||
type omitUnambiguous<base> = Omit< | ||
base, | ||
{ | ||
[k in keyof base]: undefined extends base[k] ? k : never | ||
}[keyof base] | ||
> | ||
type baseDisambiguationOf< | ||
traits extends readonly TraitConstructor[], | ||
implementation, | ||
composed extends ComposedTraits | ||
> = omitUnambiguous<{ | ||
[k in keyof composed["implemented"]]: k extends keyof implementation | ||
? undefined | ||
: k extends keyof Trait | ||
? undefined | ||
: traitsImplementingKey<traits, k> extends infer implementations extends | ||
TraitConstructor[] | ||
? implementations["length"] extends 1 | ||
? undefined | ||
: implementations[number] | ||
: never | ||
}> | ||
type traitsImplementingKey< | ||
traits extends List, | ||
k, | ||
result extends unknown[] = [] | ||
> = traits extends readonly [ | ||
TraitConstructor<any[], infer instance, infer abstracts>, | ||
...infer tail | ||
] | ||
? traitsImplementingKey< | ||
tail, | ||
k, | ||
k extends Exclude<keyof instance, keyof abstracts> | ||
? [...result, traits[0]] | ||
: result | ||
> | ||
: result | ||
// even though the value we attach will be identical, we use this so classes | ||
@@ -116,8 +61,21 @@ // won't be treated as instanceof a Trait | ||
// @ts-expect-error allow abstract property access | ||
export type TraitDeclaration = { | ||
abstractMethods?: object | ||
abstractProps?: object | ||
abstractStatics?: object | ||
dynamicBase?: object | ||
} | ||
// @ts-expect-error | ||
export abstract class Trait< | ||
abstracted extends object = {}, | ||
implemented extends object = {} | ||
> extends NoopBase<abstracted & implemented> { | ||
declare $abstracted: abstracted | ||
d extends TraitDeclaration = {}, | ||
// we have to enumerate these for TS to understand extending their intersection | ||
abstractMethods extends object = d["abstractMethods"] & {}, | ||
abstractProps extends object = d["abstractProps"] & {}, | ||
abstractStatics extends object = d["abstractStatics"] & {}, | ||
dynamicBase extends object = d["dynamicBase"] & {} | ||
> extends NoopBase<abstractMethods & abstractProps & dynamicBase> { | ||
declare abstractMethods: abstractMethods | ||
declare abstractProps: abstractProps | ||
declare abstractStatic: abstractStatics | ||
@@ -135,8 +93,3 @@ static get [Symbol.hasInstance](): (o: unknown) => boolean { | ||
type Disambiguation = Record<string, TraitConstructor> | ||
const collectPrototypeDescriptors = ( | ||
trait: TraitConstructor, | ||
disambiguation: Disambiguation | ||
) => { | ||
const collectPrototypeDescriptors = (trait: TraitConstructor) => { | ||
let proto = trait.prototype | ||
@@ -149,74 +102,95 @@ let result: PropertyDescriptorMap = {} | ||
} while (proto !== Object.prototype && proto !== null) | ||
for (const k in disambiguation) { | ||
if (disambiguation[k] !== trait) { | ||
// remove keys disambiguated to resolve to other traits | ||
delete result[k] | ||
} | ||
} | ||
return result | ||
} | ||
export const compose = ((...traits: TraitConstructor[]) => | ||
(implementation: object, disambiguation: Disambiguation = {}) => { | ||
const base: any = function (this: any, ...args: any[]) { | ||
for (const trait of traits) { | ||
const instance = Reflect.construct(trait, args, this.constructor) | ||
Object.assign(this, instance) | ||
} | ||
export const compose: TraitComposition = ((...traits: TraitConstructor[]) => { | ||
const base: any = function (this: Trait, ...args: any[]) { | ||
for (const trait of traits) { | ||
const instance = Reflect.construct(trait, args, this.constructor) | ||
Object.assign(this, instance) | ||
} | ||
const flatImplementedTraits: TraitConstructor[] = [] | ||
for (const trait of traits) { | ||
// copy static properties | ||
Object.assign(base, trait) | ||
// flatten and copy prototype | ||
Object.defineProperties( | ||
base.prototype, | ||
collectPrototypeDescriptors(trait, disambiguation) | ||
) | ||
if (implementedTraits in trait) { | ||
// add any ancestor traits from which the current trait was composed | ||
for (const innerTrait of trait[ | ||
implementedTraits | ||
] as TraitConstructor[]) { | ||
if (!flatImplementedTraits.includes(innerTrait)) { | ||
flatImplementedTraits.push(innerTrait) | ||
} | ||
} | ||
const flatImplementedTraits: TraitConstructor[] = [] | ||
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 TraitConstructor[]) { | ||
if (!flatImplementedTraits.includes(innerTrait)) { | ||
flatImplementedTraits.push(innerTrait) | ||
} | ||
} | ||
if (!flatImplementedTraits.includes(trait)) { | ||
flatImplementedTraits.push(trait) | ||
} | ||
} | ||
Object.defineProperty(base, implementedTraits, { | ||
value: flatImplementedTraits, | ||
enumerable: false | ||
}) | ||
// copy implementation last since it overrides traits | ||
Object.defineProperties( | ||
base.prototype, | ||
Object.getOwnPropertyDescriptors(implementation) | ||
) | ||
return base | ||
}) as TraitComposition | ||
if (!flatImplementedTraits.includes(trait)) { | ||
flatImplementedTraits.push(trait) | ||
} | ||
} | ||
Object.defineProperty(base, implementedTraits, { | ||
value: flatImplementedTraits, | ||
enumerable: false | ||
}) | ||
return base as never | ||
}) as TraitComposition | ||
export const implement: TraitImplementation = (...args) => { | ||
if (args.at(-1) instanceof Trait) { | ||
return compose(...(args as never)) as never | ||
} | ||
const implementation = args.at(-1) | ||
const base = compose(...(args.slice(0, -1) as never)) | ||
// copy implementation last since it overrides traits | ||
Object.defineProperties( | ||
base.prototype, | ||
Object.getOwnPropertyDescriptors(implementation) | ||
) | ||
return base as never | ||
} | ||
export type TraitConstructor< | ||
params extends List = any[], | ||
instance = {}, | ||
abstracted = {}, | ||
statics = {} | ||
instance extends object = {}, | ||
statics = {}, | ||
abstractMethods extends object = {}, | ||
abstractProps extends object = {}, | ||
abstractStatics extends object = {} | ||
> = statics & | ||
(new (...args: params) => { | ||
$abstracted: abstracted | ||
} & instance) | ||
(new (...args: params) => Trait<{ | ||
abstractMethods: abstractMethods | ||
abstractProps: abstractProps | ||
abstractStatics: abstractStatics | ||
}> & | ||
instance) | ||
export type ComposedTraits = { | ||
type CompositionState = { | ||
validated: List | ||
remaining: List | ||
params: List | ||
implemented: unknown | ||
abstracted: unknown | ||
statics: unknown | ||
kind: TraitCompositionKind | ||
implemented: object | ||
abstractMethods: object | ||
abstractProps: object | ||
abstractStatics: object | ||
statics: object | ||
} | ||
export type compose<traits extends readonly TraitConstructor[]> = | ||
composeRecurse<traits, [], {}, {}, {}> | ||
export type TraitCompositionKind = "abstract" | "implementation" | ||
export type composeTraits< | ||
traits extends List, | ||
kind extends TraitCompositionKind | ||
> = composeRecurse<{ | ||
validated: [] | ||
remaining: traits | ||
kind: kind | ||
params: [] | ||
implemented: {} | ||
abstractMethods: {} | ||
abstractProps: {} | ||
abstractStatics: {} | ||
statics: {} | ||
}> | ||
type intersectImplementations<l, r> = { | ||
@@ -233,32 +207,70 @@ [k in keyof l]: k extends keyof r | ||
type composeRecurse< | ||
traits extends List, | ||
params extends List, | ||
implemented, | ||
abstracted, | ||
statics | ||
> = traits extends readonly [ | ||
TraitConstructor< | ||
infer nextParams, | ||
infer nextInstance, | ||
infer nextAbstracted, | ||
infer nextStatics | ||
>, | ||
...infer tail | ||
] | ||
? composeRecurse< | ||
tail, | ||
intersectParameters<params, nextParams>, | ||
intersectImplementations< | ||
implemented, | ||
Omit<nextInstance, keyof nextAbstracted> | ||
>, | ||
intersectImplementations<abstracted, nextAbstracted>, | ||
intersectImplementations<statics, nextStatics> | ||
> | ||
: { | ||
params: params | ||
implemented: evaluate<implemented> | ||
abstracted: evaluate<abstracted> | ||
statics: evaluate<Omit<statics, keyof typeof Trait>> | ||
} | ||
type composeRecurse<s extends CompositionState> = | ||
s["remaining"] extends readonly [ | ||
TraitConstructor< | ||
infer params, | ||
infer instance, | ||
infer statics, | ||
infer abstractMethods, | ||
infer abstractProps, | ||
infer abstractStatics | ||
>, | ||
...infer tail | ||
] | ||
? composeRecurse<{ | ||
validated: [...s["validated"], s["remaining"][0]] | ||
remaining: tail | ||
kind: s["kind"] | ||
params: intersectParameters<s["params"], params> | ||
implemented: intersectImplementations< | ||
s["implemented"], | ||
Omit<instance, keyof abstractMethods | keyof abstractProps> | ||
> | ||
statics: intersectImplementations< | ||
s["statics"], | ||
Omit<statics, keyof abstractStatics> | ||
> | ||
abstractMethods: intersectImplementations< | ||
s["abstractMethods"], | ||
abstractMethods | ||
> | ||
abstractProps: intersectImplementations< | ||
s["abstractProps"], | ||
abstractProps | ||
> | ||
abstractStatics: intersectImplementations< | ||
s["abstractStatics"], | ||
abstractStatics | ||
> | ||
}> | ||
: finalizeState<s> | ||
type finalizeState<s extends CompositionState> = satisfy< | ||
CompositionState, | ||
{ | ||
params: s["params"] | ||
validated: s["validated"] | ||
remaining: s["remaining"] | ||
kind: s["kind"] | ||
implemented: evaluate<s["implemented"]> | ||
statics: evaluate<Omit<s["statics"], keyof typeof Trait>> | ||
abstractMethods: evaluate< | ||
Omit<s["abstractMethods"], keyof s["implemented"]> | ||
> | ||
abstractProps: evaluate<Omit<s["abstractProps"], keyof s["implemented"]>> | ||
abstractStatics: evaluate<Omit<s["abstractStatics"], keyof s["statics"]>> | ||
} | ||
> | ||
export type implementationOf<s extends CompositionState> = | ||
s["abstractMethods"] & | ||
({} extends s["abstractProps"] | ||
? {} | ||
: { | ||
construct: (...args: s["params"]) => s["abstractProps"] | ||
}) & | ||
({} extends s["abstractStatics"] | ||
? {} | ||
: { | ||
statics: s["abstractStatics"] | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
226366
4216