UNPKG

tsminilens

Version:
137 lines (112 loc) 4.22 kB
import { ChainedLensMaker, ChainedLensMakerAlias, LensMaker, LensMakerAlias } from "./lensMaker"; function mapNullable (f, n) { return n == null ? n : f(n); } export abstract class Lens<T, TField> { set<Tx extends T = T>(obj: Tx, val: TField): Tx { return this.over(obj, _ => val); } castIf<TField1 extends TField>( predicate: (o: TField) => o is TField1 ): Lens<T, TField1> { return castIf(this, predicate); } chain<TField1, TFieldComp extends TField = TField>( lens1: Lens<TFieldComp, TField1> ): Lens<T, TField1> { return chain(this, lens1) as any; } /* chain lenses in a slightly more fluent way than chain() */ get then(): ChainedLensMaker<T, TField> & ChainedLensMakerAlias<T, TField> { return <any>{ withPath: (...ps: string[]) => chain(this, new SimpleLens(ps) as any), to: (...ps: string[]) => chain(this, new SimpleLens(ps) as any), }; } abstract view(obj: T): TField; abstract over<Tx extends T = T>(obj: Tx, func: (val: TField) => TField): Tx; } export class SimpleLens<T, TField> extends Lens<T, TField> { constructor( private fields: string[] ) { super(); } view(obj: T): TField { return this.fields.reduce((st, cur) => mapNullable(_ => st[cur], st), obj); } set<Tx extends T = T>(obj: Tx, val: TField): Tx { return this.over(obj, _ => val); } over<Tx extends T = T>(obj: Tx, func: (val: TField) => TField): Tx { const setArrayAware = (o, fld, val) => o instanceof Array ? o.map((v, idx) => idx === fld ? val : v) : { ...o, [fld]: val }; return this.fields.reduceRight( (st, cur) => next => mapNullable(_ => setArrayAware(next, cur, st(next[cur])), next), func )(obj as any); } } export class CastLens<T, TField, TField1 extends TField> extends Lens<T, TField1> { constructor ( private inner: Lens<T, TField>, private predicate: (o: TField) => o is TField1 ) { super(); } view(obj: T): TField1 { return mapNullable( v => this.predicate(v) ? v : undefined, this.inner.view(obj) ); } set<Tx extends T = T>(obj: Tx, val: TField1): Tx { return this.over(obj, _ => val); } over<Tx extends T = T>(obj: Tx, func: (val: TField1) => TField1): Tx { // over/set should allow acting on null, or null prevents set / override from happening // because it won't satisfy the predicate return this.inner.over(obj, v => null == v || this.predicate(v) ? func(v as any) : v); } } export function castIf<T, TField, TField1 extends TField>( lens: Lens<T, TField>, predicate: (o: TField) => o is TField1 ): Lens<T, TField1> { return new CastLens<T, TField, TField1>(lens, predicate); } export class ChainedLens<TRoot, TField, TField1, TFieldComp extends TField = TField> extends Lens<TRoot, TField1> { constructor ( private inner1: Lens<TRoot, TField>, private inner2: Lens<TFieldComp, TField1> ) { super(); } view(obj: TRoot): TField1 { return mapNullable(v => this.inner2.view(v), this.inner1.view(obj)); } set<Tx extends TRoot = TRoot>(obj: Tx, val: TField1): Tx { return this.over(obj, _ => val); } over<Tx extends TRoot = TRoot>(obj: Tx, func: (val: TField1) => TField1): Tx { return this.inner1.over(obj, (fld: TFieldComp) => this.inner2.over(fld, func)); } } export function chain<T, TField, TField1, TFieldComp extends TField = TField>( lens1: Lens<T, TField>, lens2: Lens<TFieldComp, TField1> ): ChainedLens<T, TField, TField1, TFieldComp> { return new ChainedLens(lens1, lens2); } export function lensFor<T>(): LensMaker<T> { return <any>{ withPath(...ps: string[]) { return new SimpleLens(ps); } }; } // alias for lensFor<T>().withPath() export function lensFrom<T>(): LensMakerAlias<T> { return { to: lensFor<T>().withPath }; } // an even terser alias export function L<T>(): LensMakerAlias<T> { return lensFrom<T>(); }