import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type' // A base type that roughly describes the dependencies of a single factory // for implementations of one operation. It is an object whose keys are the // identifiers are dependencies, and whose values describe that dependency. // In the value for a given key, the 'is' property gives the name of the // operation that dependency should be an instance of, defaulting to the key // itself when not present, and the 'sig' property gives the desired // signature for that operation. When the 'sig' property is not present, // the signature will default to some ambient ensemble of signatures. export type RawDependencies = Record // The following type transform fills in any unspecified signatures in RD // with the corresponding signatures from SomeSigs: type PatchedDepSpec = { [K in keyof RD]: RD[K] extends {sig: AnyFunc} ? RD[K] : K extends keyof SomeSigs ? (RD[K] & {sig: SomeSigs[K]}) : RD[K] } // A factory for building dependency specifications from the ensemble of // common signatures for a specific type (and perhaps auxiliary type). This // is typically used when describing implementation factories for one type // that depend on the common signatures for a *different* type. export function commonSpecs< T, Aux = T, CommonSigs extends GenSigs = CommonSignature >() { return ( rd: RD ): PatchedDepSpec => Object.fromEntries( Object.keys(rd).map(k => [ k, 'sig' in rd[k] ? rd[k] : {...rd[k], sig: (() => undefined)} ]) ) as PatchedDepSpec } // Further constraint on a dependency specification that means it is ready // to use with a given set of signatures: type DepSpec = { [K in Needs]: K extends keyof Signatures ? {sig?: AnyFunc} : {is: keyof Signatures, sig: AnyFunc} } // Just checks if an RawDependencies is really a DepSpec, and blanks it out if not type DepCheck< RD extends RawDependencies, Signatures extends GenSigs, Needs extends string = keyof RD & string > = RD extends DepSpec ? RD : { [K in Needs]: K extends keyof Signatures ? {} : {is: never, sig: (q: boolean) => void} } // The actual type of a dependency, given a dependency specification type DepType< Signatures extends GenSigs, DS extends DepSpec > = {[K in keyof DS]: DS[K] extends {sig: AnyFunc} ? DS[K]['sig'] : K extends keyof Signatures ? Signatures[K] : never } // A collection of dependency specifications for some of the operations in // an ensemble of Signatures: type Specifications< Signatures extends GenSigs, NeedKeys extends keyof Signatures & string, NeedList extends Record > = {[K in NeedKeys]: DepSpec} // The type of a factory function for implementations of a dependent operation, // given a dependency specification: type FactoryType< Signatures extends GenSigs, K extends (keyof Signatures) & string, DS extends DepSpec > = (dep: DepType) => Signatures[K] // The type of an implementation specification for an operation given its // dependency specification: either directly the implementation if there // are actually no dependencies, or a factory function and collection of // dependency names otherwise: type ImpType< Signatures extends GenSigs, K extends (keyof Signatures) & string, DS extends DepSpec > = DS extends null ? {implementation: Signatures[K]} : {factory: FactoryType, dependencies: DS} // A collection of implementations for some operations of an ensemble of // Signatures, matching a given collection of dependency specifications type Implementations< Signatures extends GenSigs, NeedKeys extends keyof Signatures & string, NeedList extends Record, Specs extends Specifications > = {[K in NeedKeys]: ImpType} // The builder interface that lets us assemble narrowly-typed Implementations: interface ImplementationBuilder< Signatures extends GenSigs, NeedKeys extends keyof Signatures & string, NeedList extends Record, Specs extends Specifications > { independent( independentImps: {[K in NewKeys]: Signatures[K]} ): ImplementationBuilder< Signatures, NeedKeys | NewKeys, NeedList & {[K in NewKeys]: never}, Specs & {[K in NewKeys]: null} > dependent< RD extends RawDependencies, // Easier to infer NewKeys extends (keyof Signatures) & string, DepKeys extends string = keyof RD & string >( depSpec: RD, imps: { [K in NewKeys]: FactoryType> } ): ImplementationBuilder< Signatures, NeedKeys | NewKeys, NeedList & {[K in NewKeys]: DepKeys}, Specs & {[K in NewKeys]: DepCheck} > ship(): Implementations } // And a function that actually provides the builder interface: function impBuilder< Signatures extends GenSigs, NeedKeys extends keyof Signatures & string, NeedList extends Record, Specs extends Specifications >( sofar: Implementations ): ImplementationBuilder { return { independent( imps: {[K in NewKeys]: Signatures[K]}) { return impBuilder({ ...sofar, ...Object.fromEntries(Object.keys(imps).map(k => [k, { implementation: imps[k] }])) } as Implementations< Signatures, NeedKeys | NewKeys, NeedList & {[K in NewKeys]: never}, Specs & {[K in NewKeys]: null} >) }, dependent< RD extends RawDependencies, NewKeys extends (keyof Signatures) & string, DepKeys extends string = keyof RD & string >( depSpec: RD, imps: { [K in NewKeys]: FactoryType> } ) { return impBuilder({ ...sofar, ...Object.fromEntries(Object.keys(imps).map(k => [k, { factory: imps[k], dependencies: depSpec }])) }) as unknown as ImplementationBuilder< Signatures, NeedKeys | NewKeys, NeedList & {[K in NewKeys]: DepKeys}, Specs & {[K in NewKeys]: DepCheck} > }, ship() { return (sofar as Implementations) } } } // A convenience function that gives you an implementation builder: export function implementations( ): ImplementationBuilder { return impBuilder({}) }