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 :
And run it
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 fundamentalObject
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 :
Great !! It works as expected.
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
:
- class extension - TS Step 1
- instance generator - TS - Step 1
- static methods - TS - Step 2
- type operators - TS (
&
,keyof
,in
,extends
,as
) - Step 3, Step 4 - generics - TS - Step 3, Step 4
- standard types (
Partial
,NonNullable
,Pick
,Capitalize
) - TS - Step 3, Step 4 - Proxy - JS - Step 5
I hope you enjoyed reading this course and that you found along the way one or two interesting typescript tricks.
See you next time ! 😉