tsminilens
Version:
mini Lens for TypeScript
137 lines (112 loc) • 4.22 kB
text/typescript
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>(); }