Valid Typescript Data Guaranteed

My Aim
When writing back-end NodeJS APIs in Typescript, I don’t want to worry about the validity of the data my services are consuming or producing. Given I have correctly captured the business rules and the data validity criteria, I want to be able to trust that the data I am working with is always valid.
Tools
The basic tool I will use here are Joi and a set of abstractions I will present in coming paragraphs :

Joi provides an API to describe your data as well as the validation rules that go with.
For example, say I wanted to describe and be able to validate a person object. Each person object having a name and an age properties. The name must be a non-empty string of minimum 3 characters, while the age is a number that cannot be less than 1. With Joi, this would be described as:
Validation would happen such as in:
Before going further, you can find the code mentioned below at https://github.com/kanian/ts-valid-object . Now let’s talk about the abstractions I need now.
The Abstractions
We can envision the data the application uses as, either primitives or compound objects. Compound objects are made of primitives or other compound objects.
The Primitive Abstraction
In Typescript, The most basic piece of data I could ever come across is a literal that is either a string, a boolean, a number or a symbol. We could summarize that into a union type type PrimitiveType = boolean | string | number | symbol.
The abstraction, which I call a Primitive
, hosts a PrimitiveType.
If the value of the PrimitiveType
does not follow the validation rules stipulated by its schema, then the creation of the Primitive
must fail:
Now, to go back to our previous example, a primitive for a person’s age would be:
With const personAgeSchema = Joi.number().min(1).
Now we have:
The ValidObject Abstraction
The abstraction used to model compound objects is calledValidObject
:
The constructor takes a JoiSchema
useful for validation. It also takes a variable number of ArgumentDescriptors
that define the properties that exist on the soon to be created object. The properties described can extend Primitive<PrimitiveType>
orValidObject
:
In the body of the constructor, there’s a call to this._init
:
Here, I set the properties of the object, based on the given descriptors:
this.setProperties(...x)
Then, I generate an object that is the one that will be validated by Joi:
this._value= this.getValidable()
.
The feisty lookingthis.setProperties
is the heart of ValidObject:
In a nutshell, for each ArgumentDescriptor
, I initialize a private counterpart and I give it the appropriate getter
and setter
. The setter
will consume a PrimitiveType
or a ValidObject
, and resync the value used for validation by Joi.
It is worth noting that when the setter’s value is a validObject, then, I recursively build that ValidObject. This ensures that the whole structure is valid:
Example Usage
Conclusion
The best way for me to use this approach is to first define what my data types are and the validation rules that constrain these data types. Joi let’s us do that easily. I would then build factory functions for all the primitive and compound objects I need. Then usage can be exemplified in the following diagram:

Thanks for reading ! You can find the git repo here.