feat: Narrow tsc typing of operation dependencies/implementations
This commit is contained in:
parent
90b66dc863
commit
f575582879
19 changed files with 912 additions and 111 deletions
4
src/core/Configuration.ts
Normal file
4
src/core/Configuration.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type Configuration = {
|
||||
epsilon: number
|
||||
predictable: boolean
|
||||
}
|
201
src/core/Dispatcher.ts
Normal file
201
src/core/Dispatcher.ts
Normal file
|
@ -0,0 +1,201 @@
|
|||
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<string, {is?: string, sig?: AnyFunc}>
|
||||
|
||||
// The following type transform fills in any unspecified signatures in RD
|
||||
// with the corresponding signatures from SomeSigs:
|
||||
type PatchedDepSpec<RD extends RawDependencies, SomeSigs extends GenSigs> = {
|
||||
[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<T, Aux>
|
||||
>() {
|
||||
return <RD extends RawDependencies>(
|
||||
rd: RD
|
||||
): PatchedDepSpec<RD, CommonSigs> => Object.fromEntries(
|
||||
Object.keys(rd).map(k => [
|
||||
k, 'sig' in rd[k] ? rd[k]
|
||||
: {...rd[k], sig: (() => undefined)}
|
||||
])
|
||||
) as PatchedDepSpec<RD, CommonSigs>
|
||||
}
|
||||
|
||||
// Further constraint on a dependency specification that means it is ready
|
||||
// to use with a given set of signatures:
|
||||
type DepSpec<Signatures extends GenSigs, Needs extends string>
|
||||
= {
|
||||
[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<Signatures, Needs> ? 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<Signatures, (keyof DS & string)>
|
||||
> = {[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<NeedKeys, string>
|
||||
> = {[K in NeedKeys]: DepSpec<Signatures, NeedList[K]>}
|
||||
|
||||
// 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<Signatures, (keyof DS & string)>
|
||||
> = (dep: DepType<Signatures, DS>) => 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<Signatures, (keyof DS & string)>
|
||||
> = DS extends null ? {implementation: Signatures[K]}
|
||||
: {factory: FactoryType<Signatures, K, DS>, 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<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
||||
|
||||
// The builder interface that lets us assemble narrowly-typed Implementations:
|
||||
interface ImplementationBuilder<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
> {
|
||||
independent<NewKeys extends (keyof Signatures) & string>(
|
||||
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<Signatures, K, DepCheck<RD, Signatures>>
|
||||
}
|
||||
): ImplementationBuilder<
|
||||
Signatures,
|
||||
NeedKeys | NewKeys,
|
||||
NeedList & {[K in NewKeys]: DepKeys},
|
||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
|
||||
>
|
||||
|
||||
ship(): Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||
}
|
||||
|
||||
// And a function that actually provides the builder interface:
|
||||
function impBuilder<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
>(
|
||||
sofar: Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||
): ImplementationBuilder<Signatures, NeedKeys, NeedList, Specs> {
|
||||
return {
|
||||
independent<NewKeys extends (keyof Signatures) & string>(
|
||||
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<Signatures, K, DepCheck<RD, Signatures, DepKeys>>
|
||||
}
|
||||
) {
|
||||
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<RD, Signatures, DepKeys>}
|
||||
>
|
||||
},
|
||||
ship() {
|
||||
return (sofar as
|
||||
Implementations<Signatures, NeedKeys, NeedList, Specs>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A convenience function that gives you an implementation builder:
|
||||
export function implementations<Signatures extends GenSigs>(
|
||||
): ImplementationBuilder<Signatures, never, {}, {}> {
|
||||
return impBuilder({})
|
||||
}
|
7
src/core/type.ts
Normal file
7
src/core/type.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import type {TypeName} from '@/interfaces/type'
|
||||
|
||||
// Dummy implementation for now
|
||||
export function joinTypes(a: TypeName, b: TypeName) {
|
||||
if (a === b) return a
|
||||
return 'any'
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue