Expanding Templates
Relevant source files
The following files were used as context for generating this wiki page:
Expanding templates is the process of resolving abstract TemplatePath objects—which contain wildcards like * (collection wildcard) or ** (deep wildcard)—into an array of concrete Path objects based on actual data structures. This mechanism allows for bulk data operations, such as reading from or writing to multiple nested properties simultaneously.
Sources: src/impl/template-path-impl.ts:59-65, README.md:144-150, skills/data-path/SKILL.md:55-60
Core Architecture
The template system is built upon the TemplatePathImpl class, which extends the standard PathImpl. While a standard path represents a single, specific location in a data object, a template path represents a pattern that can match multiple locations.
Template Hierarchy and Relationships
The following diagram illustrates the relationship between standard paths and template paths within the system:
The TemplatePathImpl overrides data access methods to handle multiple results and introduces the expand logic to bridge the gap between patterns and concrete data.
Sources: src/impl/template-path-impl.ts:11-15, src/impl/path-impl.ts:25-27, src/types.ts:162-170
The Expansion Logic
The heart of the template system is the expand(data) method. It uses a recursive "walk" algorithm to traverse the provided data object and identify all paths that match the template's internal segments.
The Walk Algorithm
The walk function operates by maintaining a pointer to the current data node, the current segment index in the template, and the concrete path segments accumulated so far.
The algorithm handles three types of segments:
- Wildcard (
*): Matches every key in the current object or every index in the current array. - Deep Wildcard (
**): Matches the current node and recursively explores all descendants at any depth. - Static Segments: Matches specific property names or numeric indices.
Sources: src/impl/template-path-impl.ts:66-114, src/tests/templates.spec.ts:114-129
Bulk Operations
Once expanded, template paths leverage the resulting array of concrete paths to perform bulk operations.
Data Retrieval (get)
When get(data) is called on a TemplatePath, it internally calls expand(data) and then maps over the resulting concrete paths to extract an array of values.
| Method | Input | Output | Behavior |
|---|---|---|---|
expand | Data Object | Path[] | Returns all valid concrete paths |
get | Data Object | V[] | Returns all values matching the pattern |
fn | (Getter) | (data) => V[] | Returns an accessor for bulk extraction |
Sources: src/impl/template-path-impl.ts:121-137, src/types.ts:219-231
Immutable Bulk Update (set)
The set operation on a template path ensures that all matching locations are updated while maintaining structural sharing and immutability.
The implementation iterates through all concrete paths discovered during expansion and sequentially applies the set operation. Each intermediate step produces a new structural clone, ensuring the original data remains untouched.
Sources: src/impl/template-path-impl.ts:144-152, src/tests/templates.spec.ts:167-181
Wildcard Definitions
Templates are constructed using specific methods that inject wildcard markers into the path segments.
| Method | Segment Marker | Constant | Description |
|---|---|---|---|
.each() | * | WILDCARD | Matches one level of a collection (Array or Record). |
.deep() | ** | DEEP_WILDCARD | Matches any property at any depth recursively. |
Sources: src/impl/template-path-impl.ts:25-31, src/impl/template-path-impl.ts:49-55, src/types.ts:233-247
Example Construction
// Template: "users.*.name"
const template = path<AppData>(p => p.users).each(u => u.name);
// Template: "**.id"
const deepTemplate = path<AppData>().deep(node => node.id);
Sources: README.md:154-173, src/tests/templates.spec.ts:20-25
Summary
Expanding templates provides a robust way to bridge abstract path patterns with concrete data instances. By resolving wildcards into specific Path objects, the system enables powerful bulk-read and bulk-write capabilities while strictly adhering to TypeScript's type safety and the principles of immutability.