Sanjay Idpuganti
Sanjay Idpuganti

Reputation: 320

How to narrow down the Template Literal Type when used twice

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

Answers (1)

jcalz
jcalz

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!

Playground link to code

Upvotes: 2

Related Questions