I'm struggling to find a way of displaying a type parameter in an error message. The idea is to prevent passing dependencies that were already injected, and check this during compile time.
This is how I solved it:
export type TCons<T> = new (...args: any[]) => T
export interface Has<K extends string, T> {
get: (k: K, v: TCons<T>) => T
}
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
export interface Application {
withSearchProvider: CombineExclusive<this, "SearchProvider", SearchProvider>
}
If used like this:
const liveApplication = (app: Application) =>
app
.withSearchProvider(new A())
.withSearchProvider(new B())
.withSearchProvider(new A())
You will get an error on the last line which looks like this: SearchProvider already exists.
I want to improve it a little bit: SearchProvider A already exists and here is where I started to struggle:
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} ${P} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
I cannot reference P in the template literal since it creates a circular constraint. Another way might be to reference "original" P, but I don't know how, or whether it is possible. So, my task is to create a type constraint which error message displays parameter type P. Any ideas?
Link to ts playground: playground