Reputation: 26
Im trying to get each Field
in ObjectType
to have its own generic, so the resolve
fn of each Field has to return the type defined in the type
property.
type Scalars = {
ID: string;
Int: number;
Float: number;
String: string;
Boolean: boolean;
};
type Field<T extends keyof Scalars> = {
type: T,
resolve: () => Scalars[T];
}
type ObjectType<T extends keyof Scalars> = {
[key: string]: Field<T>
}
const objectType = <T extends keyof Scalars>(config: ObjectType<T>): any => 'todo';
objectType({
id: {
type: 'Int',
// type of `resolve` is `() => (string | number)`, should be `() => number`
resolve: () => '123',
},
name: {
type: 'String',
// should be `() => string`
resolve: () => 123,
},
});
After investigating some other SO questions I found a solution that almost solves my problem in https://stackoverflow.com/a/51547767/11042710
Here's the TS playground where I attempt what was suggested above TS playground.
But it fails when I try to pass the infer P
type to a generic that extends keyof Scalars
Upvotes: 0
Views: 722
Reputation: 327654
It looks like your Field<..>
types can form a discriminated union like this:
type Fields = { [K in keyof Scalars]: Field<K> }[keyof Scalars];
Here the type Fields
is equivalent to
type Fields = Field<"ID"> | Field<"Int"> | Field<"Float"> |
Field<"String"> | Field<"Boolean">
where all terms in the union have a common type
filed of a string literal type that can be used to discriminate between them. That's good, because it means that your objectType()
method can involve a lot less generic hoop-jumping, since the config
property values can be constrained to the specific type Fields
:
const objectType = <T extends Record<keyof T, Fields>>(config: T): any => 'todo';
Now all we're doing is constraining config
to be an object with any keys (Record<keyof T, ...>
) and whose values are all Fields
. Let's see if it works. This is is the intended use:
objectType({
id: {
type: 'String',
resolve: () => "123",
},
ide: {
type: 'Int',
resolve: () => 123,
},
});
and this is what happens when you do it wrong:
objectType({
id: { // error! number is not assignable to string
type: 'String',
resolve: () => 123,
},
ide: { // error! string is not assignable to boolean
type: 'Boolean',
resolve: () => "123",
},
});
Those are the errors you expect, right?
Upvotes: 2