Reputation: 320
Is it possible to narrow down the Template Literal in the following case.
type Table = 'Name' | 'Age'
type Separated<S extends string> = `${S}${"" | `, ${S}`}`;
type Expression<T extends string> = `${T}: ${Uncapitalize<T>}`;
type UpdateExpression<T extends string> = `SET ${Separated<Expression<T>>}`
let expression: UpdateExpression<Table> = 'SET Age: age, Name: name'; // - This should be possible
let expression: UpdateExpression<Table> = 'SET Age: age, Name: age'; // - This should not be possible
When I enter Age:
. The instellisense should suggest the value of UnCapitalize<Age>
which results in age
. It should not compile if any other values are entered.
Is it possible to do in typescript. ?
The solution should satisfy arbitrary number of Table
unions.
Upvotes: 1
Views: 332
Reputation: 327634
The problem here is that Expression<T>
does not distribute `${T}: ${Uncapitalize<T>}`
across unions in T
; ideally you want Expression<"a" | "b">
to be equivalent to Expression<"a"> | Expression<"b">
. Luckily, you can use a distributive conditional type to get this behavior:
type Expression<T extends string> =
T extends any ? `${T}: ${Uncapitalize<T>}` : never;
By wrapping the original expression in T extends any ? ... : never
, we've told the compiler to break any unions in T
into individual members, evaluate the type for each member, and then unite it back into a single union.
With that change, UpdateExpression<Table>
becomes:
type UET = UpdateExpression<Table>;
/* type UET =
"SET Name: name" | "SET Age: age" | "SET Name: name, Name: name" |
"SET Name: name, Age: age" | "SET Age: age, Name: name" |
"SET Age: age, Age: age" */
which only has valid pairs, as desired. (I'm not considering out-of-scope issues like repeated fields and support for only up to two fields.)
And so this does what you want:
let expression: UpdateExpression<Table> = 'SET Age: age, Name: name'; // okay
let expression2: UpdateExpression<Table> = 'SET Age: age, Name: age'; // error!
Upvotes: 2