sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.56k stars 148 forks source link

Dynamic Template Literals can't be Mapped #913

Closed MatthewAry closed 2 weeks ago

MatthewAry commented 2 weeks ago

I'm trying to do some meta-programming for making APIs on Elysia. Using TS 5.5.2

Getting an interesting problem

function baseNumericComparableLiteral<T extends string>(key: T) {
  const comparables = Type.TemplateLiteral([Type.Union([
    Type.Literal('gt'),
    Type.Literal('gte'),
    Type.Literal('lt'),
    Type.Literal('lte'),
    Type.Literal('eq'),
    Type.Literal('ne'),
  ])])
  return Type.TemplateLiteral([Type.Literal(key), comparables])
}

const literals = baseNumericComparableLiteral('key')
type literalsType = Static<typeof literals> // type literalsType = "keygt" | "keygte" | "keylt" | "keylte" | "keyeq" | "keyne"

const mapping = Type.Mapped(literals, K => Type.Date())
type mappingType = Static<typeof mapping> // type mappingType = { key: Date; }

On mappingType, I expected the result to match to:

type mappingType = {
  "keygt": Date
  "keygte": Date
  "keylt": Date
  "keylte": Date
  "keyeq": Date
  "keyne": Date
}

Am I missing something?

sinclairzx81 commented 2 weeks ago

@MatthewAry Hi, thanks for reporting!

On mappingType, I expected the result to match to:

You can get this to work with the following.

TypeScript Link Here

function baseNumericComparableLiteral<T extends string>(key: T) {
  // avoid embedding template literal in template literal
  return Type.TemplateLiteral([Type.Literal(key), Type.Union([ 
    Type.Literal('gt'),
    Type.Literal('gte'),
    Type.Literal('lt'),
    Type.Literal('lte'),
    Type.Literal('eq'),
    Type.Literal('ne'),
  ])])
}

const literals = baseNumericComparableLiteral('key')
type literalsType = Static<typeof literals> // type literalsType = "keygt" | "keygte" | "keylt" | "keylte" | "keyeq" | "keyne"

const mapping = Type.Mapped(literals, K => Type.Date())
type mappingType = Static<typeof mapping> // {
                                          //  "keygt": Date
                                          //  "keygte": Date
                                          //  "keylt": Date
                                          //  "keylte": Date
                                          //  "keyeq": Date
                                          //  "keyne": Date
                                          // }

However I would expect the embedded template literal (as per your example), to correctly map also. I will take a look at this.

Let me know if the above helps Cheers S

sinclairzx81 commented 2 weeks ago

@MatthewAry Hiya,

Have pushed a fix for this on 0.32.34. You should be able to embed template within templates as per your original example. The following should map as you expect (note: you may need to clear your browser cache)

TypeScript Link Here

This issue was specific to the inference, where the runtime mapping was working as expected. TypeBox is due to revisit the template literal implementation under the revised inference architecture (which was added on 0.32.x), there's are still known issues with embedding unions in unions in template literals in unions (or some nested permutation of) (which is technically possible in TS, but quite challenging to map), This said, supporting embedded template literals in template literals was envisioned for the first pass of this feature (so thanks for finding this)

If you run into any issues, feel free to ping on this thread. Cheers S