Reputation: 858
I have the following implementation
export class DbService<T extends object> {
addOne<TKey extends keyof T>(table: TKey, object: T[TKey]) {
return this.db.table(String(table)).add(object);
}
}
It works fine when you give a valid interface as T:
interface DbSchema {
users: User
}
var db = new DbService<DbSchema>();
// working fine here.
// It asks for 'users' key that matches the DbSchema
// And asks for User object implementation
db.addOne('users', { name: 'foo' });
But now I wish that DbSchema
interface could be optional and you could give any key as table. So I tried the following overload method:
addOne<TKey extends keyof T>(table: TKey, object: T[TKey]): T[TKey];
addOne<O extends object>(table: string, object: O): O;
addOne(table: any, object: any) {
return this.db.table(String(table)).add(object);
}
I can say that it partially worked, but typescript stopped from help me with the object implementation even when the key matches with the DbSchema
.
It starts well, showing keys from DbSchema
:
But when you start to write the object it loses the track and jumps to second overload:
Is there anything I can do to help it?
Upvotes: 0
Views: 221
Reputation: 328262
I think your problem is that once you type {}
, that's an empty object that doesn't match the User
type, and the compiler immediately switches to the other overload, since "users"
matches string
and {}
matches object
.
One possible solution here is to make sure that the second overload does not accept known keys, like this:
addOne<TKey extends keyof T>(table: TKey, object: T[TKey]): T[TKey];
addOne<S extends string, O extends object>(table: S & Exclude<S, keyof T>, object: O): O;
addOne(table: any, object: any) {
return null!;
}
Now the second overload has the table
parameter as type S & Exclude<S, keyof T>
, where Exclude<A, B>
is a utility type that takes a union type A
and removes any members that are assignable to B
. If S
is a string distinct from keyof T
, then S & Exclude<S, keyof T>
will be S & S
which is just S
. But if S
is itself assignable to keyof T
, then S & Exclude<S, keyof T>
will be S & never
, which is never
.
The parameter S
will be inferred as the string literal value passed in for table
. That means as long as table
is not in keyof T
, then table
will be checked against S
, which is fine. But if table
is in keyof T
, then table
will be checked against never
, which is an error. So as soon as you pass something like "users"
in for table
, the compiler will decide that it does not match the second overload. And therefore when you are in the middle of typing {}
, the compiler has no reason to switch to the second overload, and you get the desired IntelliSense:
Hope this works for you or at least gives you some ideas. Good luck!
Upvotes: 1