How can I do typed metaprogramming with TypeScript?

Gabriel Grubba

I'm creating a function that receives multiple keys and values and should return an object having those keys and their respective values. The Value types should match the values I passed when I called the function.

Currently, the code works, but the typings are not exact.

I tried using the Factory way, hoping that typescript could infer something for me.

Here is the code for the factory. Also, there is a playground here.

const maker = (subModules?: Record<string, unknown>) => {
  const add = <Name extends string, Q>(key: Name, value: Q) => {
    const obj = {[key]: value};
    return maker({
      ...subModules,
      ...obj
    })
  }
  const build = () => {
    return subModules
  }

  return {
    add,
    build
  }
}

const m2 = maker()
  .add('fn', ({a, b}: { a: number, b: number }) => a + b)
  .add('foo', 1)
  .add('bar', 'aaaa')
  .build()
// m2.foo -> 1
// m2.bar -> 'aaaa'
// m2.fn({a: 1, b: 2}) -> 3
m2

Also there is the option for pipeline(playground) maybe this one could be easier:


type I = <T extends any[]>(...obj: T) => { [P in T[number]['key']]:  T[number]['value'] }
const metaMaker: I = <T extends any[]>(...subModules: T) => {
  return subModules.reduce((acc, curr) => {
    const op = {[curr.key]: curr.value}
    return {
      ...acc,
      ...op
    }
  }, {}) as { [P in T[number]['key']]: T[number]['value'] }
}
const m = metaMaker(
  {key: 'fn', value: ({a, b}: { a: number, b: number }) => a + b},
  {key: 'foo', value: 1},
  {key: 'bar', value: 'aaaa'},
)
// m.foo -> 1 
// m.bar -> 'aaaa'
// m.fn({a: 1, b: 2}) -> 3
// m
kelly

Similar to @yeahwhat's solution, but this one has added something extra in build. Since you are returning an intersection of many records, it can get messy pretty fast. This extends infer O bit "collapses" the intersection into a single type.

const maker = <Submodules extends Record<string, unknown> = {}>(subModules?: Submodules) => {
  const add = <Name extends string, Q>(key: Name, value: Q) => {
    const obj = {[key]: value};
    return maker<Submodules & Record<Name, Q>>({
      ...subModules,
      ...obj
    } as Submodules & Record<Name, Q>)
  }
  
  const build = () => {
    return subModules as Submodules extends infer O ? { [K in keyof O]: O[K] } : never;
  }

  return {
    add,
    build
  }
}

I have also made the second option:

type Narrow<T> =
    | (T extends infer U ? U : never)
    | Extract<T, number | string | boolean | bigint | symbol | null | undefined | []>
    | ([T] extends [[]] ? [] : { [K in keyof T]: Narrow<T[K]> });

const metaMaker = <T extends { key: string; value: any }[]>(...subModules: Narrow<T>) => {
  return (subModules as T).reduce((acc, curr) => {
    const op = {[curr.key]: curr.value}
    return {
      ...acc,
      ...op
    }
  }, {}) as { [P in T[number]['key']]: Extract<T[number], { key: P }>['value'] }
}

You were pretty close with your original solution, but I have used a special type to narrow the type of the input given, without the need for using as const. The piece you were missing is Extract<T[number], { key: P }> to get only the specific value for that key. Before you were giving each key all the values.

Playground (contains both)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How can I use typed variables as types with same name in Typescript?

How can I implement a typed "both" function in TypeScript?

How can I specify a typed object literal in TypeScript?

How do I limit what characters can be typed

How do I create a dynamic list in template metaprogramming?

In TypeScript's type system how can I check for freshness for typed parameters?

How do I add a TypeScript definitely typed definition in ASP.NET 5?

How do I map my Union typed GraphQL response Array in React and Typescript

How can I order a not typed array?

How can I destructure tuples into typed variables?

How can I extend typed Arrays in Swift?

How can i push to an custom typed array

How do I make a strongly typed AuthorizeAttribute

How can I do an `if` check if the typed word is equal to some word in a string list in C?

How might I express a properly typed message queue in TypeScript?

How do I reply to a message outside of the channel I typed in?

How can I do compile-time addition in TypeScript?

How can I do constructor overloading in a derived class in TypeScript?

How can I do refactoring of CDK code using TypeScript?

How can I decrease compile time of TMP & macro preprocessor metaprogramming in C++?

How do I use Ruby metaprogramming to dynamically create a class with an initialize method?

How to initialize a typed array in Typescript

How can i compare the typed word with the word list?

How can I listen to a TAB key pressed/typed in Java?

how can I clear wrong typed wireless information?

How can I implement a custom typed header for use with Hyper?

What is a typed table in PostgreSQL? How can I make such table?

How can I count the entries the person typed that end in "im" in java?

How can I convert X11 keysym to the string that will be typed?