Design Philosophy
Relevant source files
The following files were used as context for generating this wiki page:
data-path is designed as a modern, zero-dependency library for building and manipulating object property paths using TypeScript lambda expressions. Its primary goal is to replace fragile string-based paths (e.g., "users[0].name") with type-safe, refactorable, and IDE-discoverable path objects. By utilizing Proxy-based captures, the library ensures that paths are verified by the TypeScript compiler against the actual data structures they represent.
The project adheres to several core principles: being stack-agnostic, maintaining immutability in data operations, and providing a rich relational algebra for path comparison. These design choices make it suitable for deep state management in frameworks like React or Zustand, as well as complex form bindings and validation mapping.
Sources: README.md:1-24, skills/data-path/SKILL.md:1-15
Type Safety and Proxy-Based Capture
The foundational design of data-path revolves around the use of a Proxy-based builder. Instead of parsing strings, the library provides a proxy object to a lambda expression. As the user accesses properties on this proxy, the library records the keys and indices as path segments.
This diagram illustrates how the proxy captures property access to build a concrete list of segments.
Sources: src/path.ts:39-46, src/utils.ts:28-41
Key Benefits of this Approach
- Compile-time Validation: If a property name changes in the underlying type, the TypeScript compiler will flag the lambda expression as an error.
- IDE Autocomplete: Since the lambda receives a typed proxy, developers get full intellisense for deep object structures.
- Segment Normalization: The library distinguishes between numeric indices and string keys. It uses "canonical array indices" to ensure
users[0]results in a numeric segment0, while preserving non-canonical strings like"01"as string segments.
Sources: README.md:46-55, src/utils.ts:8-14
Immutable Data Manipulation
A core tenet of the library's design is immutability. All write operations performed via .set(data, value) return a structural clone of the original data. This is achieved through recursive spreading, ensuring that only the modified branch of the object tree receives new references, while unchanged branches are preserved (structural sharing).
| Operation | Logic | Result |
|---|---|---|
.get(data) | Traverses segments; returns undefined if any intermediate is missing. | Null-safe read. |
.set(data, value) | Creates shallow copies of objects/arrays along the path. | Immutable write. |
| Auto-initialization | If .set() encounters a missing intermediate, it creates {} or [] based on the segment type. | Self-healing structure. |
Sources: src/impl/path-impl.ts:70-113, src/types.ts:143-152
Path Algebra and Relational Logic
data-path treats paths as mathematical entities that can be merged, subtracted, or compared. This design allows for complex logic where the relationship between two paths determines application behavior (e.g., permission checks or validation error mapping).
Relational API
The library defines specific relations between paths, allowing developers to programmatically determine hierarchy:
This relational model is used to check if one path is a prefix of another or if they overlap.
Sources: src/types.ts:16-25, src/impl/path-impl.ts:184-211
Compositional Operations
The library supports merging paths with intelligent deduplication of overlapping segments. If Path A ends with the same segments that Path B starts with, the overlap is collapsed during the merge.
Sources: src/impl/path-impl.ts:219-237, src/types.ts:211-218
Template and Wildcard Architecture
For bulk operations, the library introduces TemplatePath. This extends the base design with support for wildcards (* for single levels and ** for deep scans).
- Chaining: Traversal methods like
.each()and.deep()return aTemplatePath, allowing for fluent construction of complex patterns (e.g.,path<T>().users.each().profile.firstName). - Expansion: Unlike standard paths, template paths can be "expanded" against real data. This resolves wildcards into a concrete array of
Pathobjects based on the keys and indices actually present in the data.
Sources: src/types.ts:269-317, src/impl/template-path-impl.ts:57-114
Summary of Design Goals
The design of data-path prioritizes developer experience (DX) and reliability in complex TypeScript environments. By moving away from string literals and toward a functional, Proxy-based model, it provides a robust framework for data access that scales with the complexity of the application's data models.
Sources: README.md:12-25, CONTRIBUTING.md:12-16