Modular Types
Type modularization refers to the practice of organizing and managing types in a way that promotes reusability and maintainability, often through the use of import and export statements. To facilitate this process, the @ibnlanre/builder package provides ways to access the type of values stored within the builder, or the type of a nested property, without the need for manual type declarations.
Accessing types
Although it is possible to inspect the type of the register by hovering over the builder object in an IDE, having a way to access the type programmatically is essential.
The @ibnlanre/builder package provides three primary ways to retrieve the type of the values stored within the register. Each method has its unique characteristics and use cases. This guide explains the various methods in detail, and which is best suited for a given scenario.
An example of a builder object created from a register is shown below:
import { createBuilder } from '@ibnlanre/builder';
const register = {
user: {
name: 'John Doe',
age: 42,
},
};
const builder = createBuilder(register);
Checking the register
The type of the values stored in the register can be accessed directly using the typeof (opens in a new tab) operator. This is useful when the register exists within the location of use. In scenarios where the type of the register is required outside the file it is defined in, exporting the register and importing it in the desired location is not advised. A better approach is to access the type of the register directly from the builder object.
// Access the type of the register directly.
type UserAge = typeof register.user.age;
// ^? number
Using the use
method
The $use
property is a read-only attribute on the root node of the builder object, that returns the inferred type of the register. Unlike the $use
and $get
methods on other nodes, it is not a function and cannot be invoked. The $use
property not only represents the register but also yields the anticipated result when accessed, ensuring the integrity of the builder's inferred type.
The following code snippet demonstrates how to retrieve the type of the register using the $use
property:
// Access the type of the register using the `$use` property.
type Register = typeof builder.$use;
// ^? { name: string; age: number; }
// Access the type of a nested property using the `typeof` operator.
type UserName = typeof builder.$use.user.name;
// ^? string
Had the $use
property on the root node been a function, accessing the type of the register would require the use of the typeof (opens in a new tab) operator coupled with the ReturnType (opens in a new tab) utility type. Accessing nested types would also require the use of bracket notation (opens in a new tab) which is a chore, as opposed to using dot notation (opens in a new tab), which is more convenient.
The following code snippet highlights the differences between using the dot (opens in a new tab) and bracket (opens in a new tab) notations:
// $use as a method on the builder
type Bar = ReturnType<typeof builder.$use>['foo']['bar'];
// $use as a property of the builder
type Bar = typeof builder.$use.foo.bar;
// ^? number
Addendum: Root node
In summary, the root node uniquely returns the register when the $use
property is accessed. While the $get
method, in contrast, yields the prefixes used in the creation of the builder object when it is called without arguments, and returns a string when it is called with arguments. This behavior diverges from that of other nodes, which generate keys from the nested keys within the register. Given its distinct characteristics, comprehending how the root node works is crucial.
For a more detailed review of the root node, refer to the usage page.