Make it work at run time

Photo by lucas Favre on Unsplash

Make it work at run time

Typescript builder tutorial [6/6]

So we took a lot of time making our generic builder to be as dynamic as possible, non raising erroneous error, etc... It is now time to test our code in a unit test : Capture d’écran 2022-07-03 à 14.50.16.png And run it Capture d’écran 2022-07-03 à 14.08.13.png Uh ho... it dit not work as expected. The javascript runtime does not seem to know the withKind method that we declared dynamically in our usage of generics and advance typing in typescript Why is that ? In fact, when manipulating the types in typescript, the change are made at build time. At run time, when doing advanced types manipulation we might not retrieve objets that match their build time definition. This is what happened here.

So we need to make our code match at run time what we defined at build time. We should be able to have a getA method that at run time have the same with* methods that we declared at build time. What we need is a way to build an object that has properties and functions that derive dynamically from properties of an another JS Object. Let's have a look at the definition of the Proxy from msdn .

The Proxy object allows you to create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties.

That is exactly what we need for our use case, let see how we can leverage the proxy functionality for our generic entity builder.

Proxy mascarade

In order to be able to dynamically compute with* methods based on the provided type, we first need to make the getA method returns a proxy instead of the EntityBuilder class. Let see how it can be done.

Instead of this :

public static getA<StaticBuiltEntityType>(TCreator: new () => StaticBuiltEntityType) {
    return new EntityBuilder(TCreator) as unknown as BuilderWithTypeMethods<PartialProps<StaticBuiltEntityType>, StaticBuiltEntityType>
  }

we will create a proxy from the instance of EntityBuilder and returning it, allowing us to return an object similar to an instance of EntityBuilder but with the possibility to later on modify dynamically the behavior of that object.

public static getA<StaticBuiltEntityType>(TCreator: new () => StaticBuiltEntityType) {
. 
const proxy: EntityBuilder<StaticBuiltEntityType, StaticBuiltEntityType> = new Proxy(new EntityBuilder(TCreator), {})

  return proxy as unknown as BuilderWithTypeMethods<PartialProps<StaticBuiltEntityType>, StaticBuiltEntityType>
}

Dynamic methods

Great ! We now have a static getA method that is returning a proxy. We will be able to modify dynamically the methods that are defined, thanks to the get accessor overriding allowed by the proxy object.

Here how is it done :

public static getA<StaticBuiltEntityType>(TCreator: new () => StaticBuiltEntityType) {
    const proxy: EntityBuilder<StaticBuiltEntityType, StaticBuiltEntityType> = new Proxy(new EntityBuilder(TCreator), {
      get(target, propKey: keyof EntityBuilder<StaticBuiltEntityType, StaticBuiltEntityType>) {
        if (propKey.startsWith('with')) {
          const prop = _.camelCase(propKey.replace('with', '')) as keyof StaticBuiltEntityType
          return (arg: any) => {
            target.builtEntity[prop] = arg
            return proxy
          }
        } else {
          return target[propKey]
        }
      },
    })
    return proxy as unknown as BuilderWithTypeMethods<PartialProps<StaticBuiltEntityType>, StaticBuiltEntityType>
  }

Here we provide a handler object with a get overriding function. In this function, we simply check if the propKey starts with with, if so we infer the name of the property from the name of the method and returns a new function that will set the value from the provided input on the inferred property of the entity being built.

Let's see if our tests behave better now : Capture d’écran 2022-07-03 à 15.48.18.png

Great !! It works as expected. Capture d’écran 2022-07-03 à 15.52.12.png

Conclusion

This sequence of article demonstrate the power and the potential of typescript to construct deeply dynamic code infrastructure while still enforcing type safety. We were able to build this generic entity builder, thanks to the following functionalities of typescript & javascript :

I hope you enjoyed reading this course and that you found along the way one or two interesting typescript tricks.

See you next time ! 😉