In my current project, I am working on enhancing a TypeScript process that is in place. The goal is to make it more strongly typed for better scalability and accuracy.
The structure of the existing tree under consideration is as follows:
interface Node {
[name: string]: Leaf | Node | Node[] | undefined;
}
This tree is simplistic, with each node housing either a leaf node, another node, a list of nodes, or being undefined, all categorized by property names.
Rules can be applied to this tree through a specific mechanism. These rules are defined as shown below:
interface Rule<T extends Node, K = keyof T> {
name: K;
optional?: boolean;
data?: {};
list?: boolean;
process?(processor: RuleProcessor): Node;
// additional rule-specific flags
}
To increase flexibility, I aim to segment these rules based on the type of children they apply to:
optional
exclusive to potentially undefined childrendata
tailored for child leaves onlylist
specifically for child listsprocess()
designed for child nodes or lists of nodes exclusively
This is the envisioned plan:
type Child = Leaf | Node | Node[];
interface Node {
[name: string]: Child | undefined;
}
// Separate rule types based on child categories
interface OptionalRule<T extends Node, K extends keyof T = keyof T> extends Rule<T, K, Child | undefined> {
optional: true;
}
interface RequiredRule<T extends Node, K extends keyof T = keyof T, V extends Child = Child> extends Rule<T, K, V> {}
interface LeafRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Leaf> {
data: {};
}
interface ListRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Node[]> {
list: true;
}
interface ProcessRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Node | Node[]> {
process(processor: RuleProcessor): Node;
}
type AnyRule<T extends Node> = OptionalRule<T> | LeafRule<T> | ListRule<T> | ProcessRule<T>;
function process<T extends Node>(node: T, rules: AnyRule<T>[]) {
// logic
}
The objective is to have specific rule types that would verify against the property type if a rule is specified for a particular property.
For instance, given a node:
const node: Node = { a: new Leaf(), b: [] };
And running processing on it:
process(node, [{ name: 'a', data: {} }, { b: list: true }]);
I expect that utilizing data
will confirm whether node['a']
is indeed a Leaf
, triggering an error if not. Similarly, the second rule should ensure that node['b']
is a Node[]
.
Currently, when changing data: {}
to list: true
on a
, no error prompts, even though node['a']
isn't an array. Realizing there might be an issue from the start, I wonder if such verification is plausible in TypeScript considering features like keyof T
and T[K]
open up complex type descriptions.
The core aspiration is to enforce compile-time checks preventing developer errors since at runtime, types are stripped away.
EDIT: The challenge could be resolved if a constraint existed on V = T[K]
dictating that V
must adhere to a specific type.