UNPKG

tsminilens

Version:
111 lines (76 loc) 3.79 kB
# TsMiniLens: Type-safe mini Lens for TypeScript ``` npm i tsminilens ``` A type-safe and idiomatic way to navigate through nested JSON objects. Written in TypeScript so enjoy intellisense and compiler errors! (instead of null reference errors at run time). ![demo](./demo.gif) ## Releases ### 2.0.1-alpha This release adds automatic selection from union types. Consider, ```TypeScript type T1 = { kind: 't1', foo: string }; type T2 = { kind: 't2', bar: number }; type U = { element: T1|T2 }; // I want to dot into the fields of T1 or T2 directly // this used to fail but will now work const l = L<U>().to('element', 'foo'); ``` The library is able to detect that field name `foo` identifies the type of `element` to be `T1`. Previously this requires a type guard e.g. `L<U>().to('element').castIf(isT1)`. `castIf` should now be obsolete, although it's kept for backward compatibility. ## Use cases ### Given ```TypeScript interface Address { city?: string; street: string }; interface Person { name?: string; address: Address }; const lensPerson2Street = lensFrom<Person>().to('address', 'street'); // since 1.1.16 const lensPerson2Street = L<Person>().to('address', 'street'); ``` ### view() to navigate safely UPDATE: for viewing only, [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) would be a better solution. Lens is more useful for updating / modifying data, see below. We all know the dreaded null reference exception (Law of demeter applies) ```TypeScript const street = person.address.street; // error if address is null! ``` with lens this never happens, in the following case, if address is null then view() returns null instead of erroring out ```TypeScript const street = lensPerson2Street.view(person); // safe! ``` ### set() or over() to update easily If immutability is a concern, then updating a nested data structure can be tedious. ```TypeScript const updatedPerson = { ...person, address: { ...person.address, street: 'new street' } }; // imagine more nesting! :( ``` with ``set()`` this becomes a breeze. It does a CoW (Copy on Write) to support immutability. ``` const personRelocated = lensPerson2Street.set(person, 'new street'); ``` Note ``personRelocated`` is a different object than ``person``, or, ``person !== personRelocated``. ``over()`` is handy if we are to modify (but not replace) the current street, ``` const updatedPerson = lensPerson2Street.over(person, street => 'Level 2' + street); ``` Quiz: how to implement ``set()`` in terms of ``over()``? ### chain() and castIf() It's also possible to chain lenses with ``lens1.chain(lens2)`` or more fluently, ``lens1.then.to('level1', 'level2')`` ``lens.castIf(typeGuard)`` supports navigating through union types safely with [type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types). ### arrays Operations on arrays are supported as arrays work similar to objects. ```TypeScript lensFrom<string[]>().to(1).view([ 'aaa', 'bbb', 'ccc' ]); // 'bbb' ``` ## Caveats Copy-on-write is implemented with the spread operator, e.g. `{ ...foo, bar: baz }`. This works for plain objects and arrays, but is not safe for complex types such as Map, Set, or class etc. There are friendly requests to add support for `view` / `set` of array elements. The challenge is to not to disrupt the current interfaces too much so my guess it will be work in progress for a (long) while. In the mean time, it's practical to operate on arrays with the likes of `map` / `filter` (as one would normally do) over `set` / `view`. ## Remember it's mini Bear in mind it's mini indeed - there is absolutely no parity with lens proper as in Haskell.