Home > database >  Typescript: How to transform function chain to Tagged Template literal?
Typescript: How to transform function chain to Tagged Template literal?

Time:10-18

Till now I have this (missing implementation is not important here):


interface Context {
  User:     "OK"      | "KO"                    //  | "..."
  Password: "valid"   | "invalid"               //  | "..."
  Message:  "Welcome" | "Invalid credentials"   //  | "..."
  // ...
}

// I = Input, O = Output, H = Header
declare var I :Context , O :Context

interface Examples<H extends unknown[]> { // by @jcalz :)
  (...rows: H): Examples<H>
}


// this syntax is function chain with type safety and autocomplete
declare function Examples<H extends unknown[]>(...rows: H): Examples<H>

Examples
( I.User , I.Password , O.Message             )
( "OK"   , "valid"    , "Welcome"             )
( "KO"   , "invalid"  , "Invalid credentials" )
  
 
declare var ExamplesTTL_ideal: any 

// and this is ideal syntax sample with TTL - where first param is header and rest should be rows of type header

ExamplesTTL_ideal `
${[ I.User , I.Password , O.Message              ]}
${[ "OK"   , "valid"    , "Welcome"              ]}
${[ "KO"   , "invalid"  , "Invalid credentials"  ]}
`

Here it is all in Typescript Playground

The question is - what should be declaration of ExampleTTL_ideal instead of :any ?

Regards.

CodePudding user response:

My approach here would look like this:

declare function ExamplesTTL<H extends string[]>(
  text: TemplateStringsArray, ...rows: [...H][]): ExamplesTTL<H>;

interface ExamplesTTL<H extends string[]> {
  (text: TemplateStringsArray, ...rows: [...H][]): ExamplesTTL<H>;
}

The function is generic in H, intended to be a tuple of string literal types (or unions of string literals). We want rows to be an array of such tuples. If we're lucky, the compiler will use the first element in the rows array to infer H, and then check that subsequent elements in the array conform to it.

Note that in order to give the compiler a hint that H should be a tuple, we use the variadic tuple type [...H].

Since you want the function to return a function of the same type except that H is no longer generic, I defined the ExamplesTTL<H> generic type. Conceptually, the ExamplesTTL<H> type is equivalent to typeof ExamplesTTL<H> instantiation expression, but I've given it a new name so as to avoid a vicious circularity (see ms/TS#51202).


Let's test it out:

const x = ExamplesTTL`
${[I.User, I.Password, O.Message]}
${["OK", "valid", "Welcome"]}
${["KO", "invalid", "Invalid credentials"]}
`;
// const x: ExamplesTTL<["OK" | "KO", "valid" | "invalid", 
//   "Welcome" | "Invalid credentials"]>

x`${["OK", "valid", "Welcome"]} ${["oops"]}`; // error!
// ------------------------------> ~~~~~~
//Type '"oops"' is not assignable to type '"OK" | "KO"'.(2322)

const y = ExamplesTTL`
${["a", "b", "c"]}${["a", "b", "d"]}`; // error!
// --------------------------> ~~~
// Type '"d"' is not assignable to type '"c"'.(2322)

Looks good. The type of x is inferred as ExamplesTTL<["OK" | "KO", "valid" | "invalid", "Welcome" | "Invalid credentials"]> as desired, and thus subsequent calls to x constrain its input to that type (so ["oops"] is bad). And if you try to call ExamplesTTL with inconsistent tuple types, any inconsistency in the subsequent array entries are marked as an error.

Playground link to code

  • Related