UNPKG

@aurbi/tiny-composite-builder

Version:

Small framework for taking base objects and adding extensions using a builder pattern, but with full and strict compile-time type support.

148 lines (134 loc) 4.99 kB
import { IExtension } from "./IExtension"; import { Builder } from "./Builder"; import { IBaseHint } from "./IBase"; /** * Represent a union (A|B) as an intersection (A&B) */ export type UnionToIntersection<U> = // this is a little above my pay grade. thanks internet. // i think.. that because it's inferring I, it has to break apart every possible // union member to get at the type to perform the conditional check, which is // then separately represented. // but, because all of them are required to satisfy the argument constraint, it // knows to intersect them? (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never; /** * Represents all the public methods of ALL the Exts provided. */ export type MethodsOfExts<TBase extends IBaseHint, EE> = EE extends Array<IExtension<TBase>> ? UnionToIntersection< // {a(): void}|{b(): void} => {a(): void}&{b(): void} MethodsOfExt<TBase, ({ [k in keyof EE as // for every property of the array... k extends number // get the real values only (numerically indexed)... ? (EE[k] extends IExtension<TBase> ? k : never) // and if they're extensions... : never ]: EE[k] // snag it! })[0] // TODO: figure out why i need this > > : {}; // no methods, because there we no exts. (TODO: verify? was 'never') /** * Represents the prepared extensions of a particular Builder type */ export type ExtConsOfBuilder<TBase> = TBase extends Builder<infer T, infer EE> ? ( EE extends Array<any> ? EE // builder has extensions: these are them! : [] // builder doesn't have any extensions yet. blank list. ) : never; /** * Represents a function... or a method, in our case. * (Also Function is a reserved global type) * It returns type T and has arguments U. */ export type Method<T = any, U extends [] = any> = (...args: U) => T; /** * Represents an uninstantiated class (a constructor) that constructs T, with arguments U */ export type Constructor<T = any, U extends [] = any> = new (...args: U) => T; /** * Represents the type that's constructed by a given constructor T */ export type ConstructedType<T> = T extends Constructor<infer U> ? U : never; /** * Get all public methods of a given extension class */ export type MethodsOfExt<TBase extends IBaseHint, E> = E extends IExtension<TBase> // if this is an extension of the same base interface type... ? ( // a value cannot exist without a valid key type. never is ... never valid. // so, we check if each key (method or property) of E matches a permissive method // and if so, we index it. { [k in keyof E as E[k] extends Method ? k extends `$${infer U}` ? U : never : never]: E[k] } ) : never; /** * Represents an existing builder that may have extensions, but with another extension type * added, too. */ export type BuilderWithExt< TBase extends IBaseHint, TBuilder, TNewExt extends IExtension<TBase> > = TBuilder extends IfEq< // If TBuilder ... TBase, // ... has the same base interface type as ... BaseOfExt<TNewExt>, // ... the extension we're adding .... Builder<TBase, infer EE> // ... continue, and infer the current extension list EE > ? ( EE extends Array<ExtensionConstructionElement<TBase>> // if EE is an array, we have existing extensions. ? Builder<TBase, [...EE, ExtensionConstructionElement<TBase, TNewExt>]> // add the new one to the existing list. : Builder<TBase, [ExtensionConstructionElement<TBase, TNewExt>]> // ... otherwise, make a new list with this ext. ) : never; /** * If A and B are exactly the same type, return T, otherwise never */ export type IfEq<A, B, T> = // (A extends B) and (B extends A) can only be true if they are identical. A extends B ? ( B extends A ? T : never ) : never; /** * Extract the base interface type of a given extension TExt */ export type BaseOfExt<TExt> = TExt extends IExtension<infer TBase> ? TBase : never; /** * Element of a builder's extension construction array */ export type ExtensionConstructionElement< TBase extends IBaseHint, TExt extends IExtension<TBase> = IExtension<TBase> > = [Constructor<TExt>, ConstructorParameters<Constructor<TExt>>]; /** * A Builder's extension construction array */ export type ExtensionConstructionArray<TBase extends IBaseHint> = Array<ExtensionConstructionElement<TBase>> /** * Given an extension construction type array, represent the array of extensions it'd build */ export type ConstructedExtensions< TBase extends IBaseHint, EE > = EE extends ExtensionConstructionArray<TBase> ? ( { [k in keyof EE]: EE[k] extends [Constructor<infer E>, any] ? E : never } ) : never; /** * Given a ConstructorParameters<TClass> as the single type argument, * returns the ConstructorParameters list but without the first entry. */ export type DropFirst<T extends any[]> = T extends [any, ...infer U] ? U : never;