Dynamic and Unsafe Paths
Relevant source files
The following files were used as context for generating this wiki page:
The data-path library primarily focuses on type-safe path creation via lambda expressions. However, it provides the unsafePath utility and dynamic construction mechanisms to handle scenarios where paths are provided as raw strings or determined at runtime (e.g., from an API response, database, or validation error). While these methods bypass some compile-time checks, they still produce standard Path objects that integrated into the library's relational algebra and data access systems.
Dynamic path construction is essential for integrating with external tools like Zod or form libraries that represent nested structures as dot-separated strings. By using these utilities, developers can bridge the gap between opaque string literals and the library's structured path manipulation features.
Sources: README.md:104-108, src/path.ts:75-81
The Unsafe Path Utility
The unsafePath<T>(raw: string) function allows for the creation of a path from a dot-separated string. This is explicitly designed for dynamic contexts where the path structure is not known until runtime. Unlike the standard path() function, unsafePath bypasses type checking on individual segments.
Sources: src/path.ts:75-81, src/tests/unsafe.spec.ts:12-16
Parsing Logic
When a string is passed to unsafePath, the system performs a "best-effort" parse by splitting the string by the . delimiter. Each resulting segment is processed to determine if it should be treated as a string key or a numeric index.
The diagram above illustrates the transformation process from a raw string to a structured PathImpl instance.
Sources: src/path.ts:82-87, src/utils.ts:10-15
Canonical Array Indices
To ensure data integrity, the library distinguishes between numeric indices and numeric-looking string keys. A segment is converted to a number only if it is a canonical representation of a non-negative integer.
| Segment Input | Parsed Type | Resulting Segment | Reason |
|---|---|---|---|
"0" | number | 0 | Canonical integer |
"123" | number | 123 | Canonical integer |
"01" | string | "01" | Non-canonical (leading zero) |
"1e3" | string | "1e3" | Scientific notation |
"" | string | "" | Empty segment from leading/double dots |
Sources: src/utils.ts:10-15, src/tests/unsafe.spec.ts:25-30, src/tests/unsafe.spec.ts:46-55
Dynamic Path Construction
Beyond raw strings, paths can be constructed dynamically using runtime variables or by composing existing paths. This allows for flexible path generation within loops or generic components.
Runtime Variables in Lambdas
The library's Proxy-based builder captures property accesses even when indices are provided via variables. This is the recommended way to create "dynamic" paths that still maintain type safety.
function getUserPropertyPath(userIdx: number, property: string) {
// Capture function arguments directly in the path!
return path<AppData>(p => p.users[userIdx][property]);
}
Sources: README.md:195-201, src/tests/path.spec.ts:20-25
Path Composition and Algebra
Dynamic paths can be manipulated using algebraic methods. This is useful when a component receives a base path and needs to append a dynamic suffix.
- Merge: Appends another path, intelligently deduplicating overlapping segments using the longest suffix-prefix overlap.
- Subtract: Removes a prefix or suffix from a path, returning a relative path or
null. - Slice: Extracts a specific range of segments using
Array.prototype.slicesemantics.
Sources: src/impl/path-impl.ts:227-245, src/impl/path-impl.ts:254-269, src/types.ts:219-224
Integration with External Systems
Dynamic and unsafe paths are frequently used to bridge data-path with external validation or state management libraries.
Zod Validation Mapping
When Zod returns a validation error, it provides a path as an array of segments. These can be converted to an unsafe path to compare against known, type-safe paths in the application.
The sequence diagram shows how raw path data from external validators is converted into a comparable Path object.
Sources: README.md:310-323, skills/data-path/SKILL.md:65-68
Summary of Unsafe Path Characteristics
Unsafe paths, while lacking the initial compile-time validation of lambda-based paths, share the same underlying implementation and capabilities:
| Feature | Standard Path | Unsafe Path |
|---|---|---|
| Creation Method | path(p => p.a.b) | unsafePath("a.b") |
| Type Safety | 100% Compiler Checked | Bypassed (returns Path<T, unknown>) |
| Data Access | .get(data), .set(data, v) | .get(data), .set(data, v) |
| Relational Algebra | Full support | Full support |
| String Output | Available via .$ | Available via .$ |
Sources: src/types.ts:289-291, src/path.ts:75-87, README.md:65-71
Dynamic and unsafe paths provide the necessary escape hatch for handling runtime data while still benefiting from the library's robust data manipulation and relational logic. They are best used when bridging external data sources or building generic path-handling utilities.