Lambda Execution and Internals
Relevant source files
The following files were used as context for generating this wiki page:
The data-path library uses a Proxy-based mechanism to transform TypeScript lambda expressions into structured path objects. Instead of parsing string literals or manually defining arrays, developers interact with their actual data types through a function, which the library intercepts at runtime to determine the intended property sequence.
This system provides a bridge between compile-time type safety and runtime data access. By executing the lambda once during path creation, the library captures property keys and array indices, storing them as a list of segments used for immutable state updates and deep data retrieval.
Path Construction Mechanism
The core of the library is the path() function. It operates by passing a specialized Proxy object into the user-provided lambda expression.
Proxy-Based Interception
When path<T>(p => p.a.b[0]) is called, the library creates a recursive Proxy. As the lambda executes, every property access (like .a or .b) and every index access (like [0]) is intercepted and recorded. This recording is stored internally, typically associated with a specific symbol such as PATH_SEGMENTS.
The logic for creating the proxy and extracting these segments is found in the entry point and internal utility calls.
Sources: [src/path.ts:44-52], [src/impl/path-impl.ts:213-217]
Construction Overloads
The path function supports multiple signatures to allow for root path creation, direct lambda construction, or extending existing paths.
| Signature | Use Case | Resulting Internal State |
|---|---|---|
path<T>() | Root Path | segments: [] |
path<T>(expr) | Lambda Path | segments: [result of expr] |
path(base, expr) | Extension | segments: [...base, ...expr] |
Sources: [src/path.ts:31-41], [src/types.ts:246-255]
Data Operations Internals
Once a path is constructed, it is primarily used for two operations: Reading (get) and Writing (set).
Safe Data Retrieval (get)
The get method iterates through the internal segments array. It performs a "safe" traversal, meaning that if any intermediate property in the data object is null or undefined, the method immediately returns undefined rather than throwing a runtime error.
Sources: [src/impl/path-impl.ts:69-76], [src/types.ts:153-159]
Immutable State Updates (set)
The set operation is designed to work with immutable state managers. It does not mutate the original object. Instead, it performs a structural clone along the path of the update, reusing references for branches of the object tree that were not modified (structural sharing).
Key implementation details of set:
- Object/Array detection: If a segment is a number, the implementation assumes an array and creates a new array reference.
- Recursive cloning: The
setAthelper function recursively rebuilds the object tree from the leaf up to the root. - Missing path creation: If intermediate objects or arrays do not exist in the source data,
setautomatically instantiates them based on the segment type (numeric indices create[], string keys create{}).
Sources: [src/impl/path-impl.ts:98-132], [src/types.ts:168-176]
Template and Wildcard Processing
Templates allow paths to represent multiple locations (e.g., all items in an array) using wildcards like * (WILDCARD) and ** (DEEP_WILDCARD).
Wildcard Recording
When .each() or .deep() is called, the library appends the respective wildcard segment to the current path. If a lambda is passed to these methods, it is executed against a new proxy to capture the "tail" of the path.
Sources: [src/impl/template-path-impl.ts:24-42], [src/impl/template-path-impl.ts:48-66]
Template Expansion
The expand(data) method is the core of template execution. It performs a recursive "walk" through the provided data object, matching the wildcard segments against actual runtime keys.
*(WILDCARD): Matches all keys of the current object or indices of the current array.**(DEEP_WILDCARD): Recursively matches the current node and all its descendants until the next segment in the path is found.
Sources: [src/impl/template-path-impl.ts:74-129], [src/types.ts:233-241]
Path Interaction and Algebra
The library implements relational algebra to compare paths. This is handled by comparing the internal segments arrays.
Match Relations
The match(other) method evaluates how two paths relate in a hierarchy.
| Relation | Logic |
|---|---|
equals | Segments are identical in value and order. |
child | Current path starts with other segments and is longer. |
parent | other starts with current segments and is longer. |
includes | Current path contains wildcards that cover the other path. |
Sources: [src/impl/path-impl.ts:233-261], [src/types.ts:19-27]
Smart Merging
The merge(other) operation does not perform simple concatenation. It looks for the longest suffix-prefix overlap between the two paths to avoid redundant segments (e.g., merging users and users.0 results in users.0, not users.users.0).
Sources: [src/impl/path-impl.ts:271-288], [src/types.ts:208-214]
Internal Path Implementation Summary
The project distinguishes between standard paths and template paths through two primary classes, with TemplatePathImpl extending the standard implementation to add bulk-operation capabilities.
Sources: [src/impl/path-impl.ts:22], [src/impl/template-path-impl.ts:13], [src/types.ts:121]
The library relies on a singleton-style injection for the TemplatePathCtor to avoid circular dependency issues between the base path implementation and the template implementation.
Sources: [src/path.ts:7], [src/impl/path-impl.ts:17-20]