@focuson/lens
Version:
A simple implementation of lens using type script
230 lines (228 loc) • 14.7 kB
TypeScript
import { NameAnd } from "@focuson/utils";
export declare const identityOptics: <State>(description?: string) => Iso<State, State>;
export interface GetOptioner<Main, Child> {
getOption: (m: Main) => Child | undefined;
}
export interface SetOptioner<Main, Child> {
setOption: (m: Main, c: Child) => Main | undefined;
}
export interface Getter<Main, Child> {
get: (m: Main) => Child;
}
export interface Setter<Main, Child> {
set: (m: Main, c: Child) => Main;
}
export interface HasOptional<Main, T> {
optional: Optional<Main, T>;
}
/** An Optional is like a lens, except that it is not guaranteed to 'work'. Specifically if you ask for a child... maybe that child isn't there.
*
* This is great for things like 'optional values' which are often written as 'name?: type' in typescript.
*
* It is rare that you create one directly. Usually it is created using 'focusQuery' on a lens
*/
export declare class Optional<Main, Child> implements GetOptioner<Main, Child>, SetOptioner<Main, Child> {
getOption: (m: Main) => Child | undefined;
setOption: (m: Main, c: Child) => Main | undefined;
description: string;
constructor(getOption: (m: Main) => (Child | undefined), optionalSet: (m: Main, c: Child) => Main | undefined, description?: string);
set: (m: Main, c: Child) => Main;
map: (m: Main, fn: (c: Child | undefined) => Child) => Main;
mapDefined: (m: Main, fn: (c: Child) => Child) => Main;
/** This is identical to this.setOption(m, undefined) */
clearJson(m: Main): Main;
/** Allows us to change the focuson-ed child based on it's existing value
* @fn a function that will be given the old value and will calculate the new
* @returns a function that given a Main will return a new main with the child transformed as per 'fn' */
transform(fn: (oldChild: Child) => Child): (m: Main) => Main;
/** This is used when the 'parameter' points to definite value. i.e. it isn't 'x: X | undefined' or 'x?: X'. If you want to
* walk through those you probably want to use 'focusQuery'
*
* If the type system is complaining and you are sure that it should be OK, check if the previous focusOn should be a focusQuery
* @param k
*/
focusOn<K extends keyof Child>(k: K): Optional<Main, Child[K]>;
/** Used to focus onto a child that might not be there. If you don't use this, then the type system is likely to complain if you try and carry on focusing. */
focusQuery<K extends keyof Child, Req extends Required<Child>>(k: K): Optional<Main, Req[K]>;
chain<T>(o: Optional<Child, T>): Optional<Main, T>;
combine<OtherChild>(other: Optional<Main, OtherChild>): Optional<Main, [Child, OtherChild]>;
/** If you desire to change the description this will do that. It is rarely called outside the Lens code itself */
withDescription(description: string): Optional<Main, Child>;
combineAs<OtherChild, NewChild>(other: Optional<Main, OtherChild>, iso: Iso<[Child, OtherChild], NewChild>): Optional<Main, NewChild>;
chainIntoArray(a: string[]): Optional<Main, string>;
chainNthFromPath(pathL: Optional<Main, number>): Optional<Main, any>;
chainCalc(pathL: Optional<Main, keyof Child>): Optional<Main, Child[keyof Child]>;
toString(): string;
}
export declare function optional<Main, Child>(getOption: (m: Main) => Child | undefined, setOption: (m: Main, c: Child) => Main | undefined, description?: string): Optional<Main, Child>;
export declare function identityOptional<Main>(): Optional<Main, Main>;
export declare function orUndefined<T>(description?: string): Optional<T | undefined, T>;
export declare function castIfOptional<T, T1>(cond: (t: T) => boolean, description?: string): Optional<T, T1>;
/**
* Creates a lens with two generics. Lens<Main,Child>. Main is the main 'object' that we start with, and Child is the part of Main that the lens is focuson-ed
* @param get should be a sideeffect free function that goes from 'Main' to the focuson-ed child. When called it 'gets' the Child from the Main
* @param set should be a sideeffect free function that creates a new Main out of an old main and a new child. It returns the old main with the 'focuson-ed' part replaced by the new child
* @param description should probably be the string representation of the class 'Main'. If the main object is of type Dragon, this could be the string 'dragon'.
*
* Usually these are created by code like
*
* identityOptics<Dragon>().focuson('head')
*/
export declare function lens<Main, Child>(get: (main: Main) => Child, set: (main: Main, newChild: Child) => Main, description?: string): Lens<Main, Child>;
/** This is the class that represents a Lens<Main,Child> which focuses on Child which is a part of the Main */
export declare class Lens<Main, Child> extends Optional<Main, Child> implements Getter<Main, Child>, Setter<Main, Child> {
set: (m: Main, c: Child) => Main;
get: (m: Main) => Child;
constructor(get: (m: Main) => (Child), set: (m: Main, c: Child) => Main, description: string);
/** this is the 'normal' focuson. We use it when we know that the result is there. i.e. if we have
*
* interface AB{
* a: string,
* b?: SomeInterface | undefined
* }
*
* In this case focusOn('a') will give us a Lens<AB,string> but focusOn('b') will give a lens<AB, SomeInterface|undefined>.
* @param k
*/
focusOn<K extends keyof Child>(k: K): Lens<Main, Child[K]>;
/** interface AB{
* a: string,
* b?: SomeInterface | undefined
* }
*
* In this case it would be redundant to have focusWithDefault('a', "someA") because 'a' should never be undefined.
* However (if someValue is a SomeInterface) focusWithDfeault('b', someValue) return Lens<AB,SomeInterface> and if we do a get, and b was undefined, we use 'someValue'
* @param k
*/
focusWithDefault<K extends keyof Child, Req extends Required<Child>>(k: K, def: Child[K]): Lens<Main, Req[K]>;
chainLens: <T>(o: Lens<Child, T>) => Lens<Main, T>;
/** @deprecated */
chainWith: <T>(o: Lens<Child, T>) => Lens<Main, T>;
combineLens<OtherChild>(other: Lens<Main, OtherChild>): Lens<Main, [Child, OtherChild]>;
/** If you desire to change the description this will do that. It is rarely called outside the Lens code itself */
withDescription(description: string): Lens<Main, Child>;
toString(): string;
}
/** A factory class that allows us to create new Lens. Every method on it is static, so you would never create one of these
*
* This class will be removed and replaced with just 'plain functions'
* */
export declare class Lenses {
/** This is a the normal way to generate lens. It create a link that goes from Main to itself */
static build<Main>(description: string): Lens<Main, Main>;
/** Given a main which is an object, with a field name, this returns a lens that goes from the Main to the contents of the field name */
static field: <Main, K extends keyof Main>(fieldName: K) => Lens<Main, Main[K]>;
/** Given a main which is an object, with a field name, this returns a lens that goes from the Main to the contents of the field name */
static identity<M>(description?: string): Iso<M, M>;
/**This should no longer be needed. It was in fact the need for this method that drove the rewrite using Optionals/Prisms and Isos.
*
* Nowadays we can use focusQuery
*
* It should only be used when we 'know' that a Lens<Main,Child|undefined> is really a Lens<Main,Child>.
* @deprecated */
static define<T>(): Lens<T | undefined, T>;
/** This returns a lens from an array of T to the last item of the array */
static last<T>(): Lens<T[], T>;
/** This returns a lens from an array of T to the next item of the array */
static append<T>(): Lens<T[], T>;
/** This returns a lens from an array of T to the nth member of the array */
static nth<T>(n: number): Optional<T[], T>;
static chainNthFromOptionalFn<From, To, NewTo>(lens: Optional<From, To>, newToFnL: (f: From) => Optional<To, NewTo>, newFragDescription: string): Optional<From, NewTo>;
static chainNthRef<From, T>(lens: Optional<From, T[]>, lookup: (name: string) => any, name: string, description?: string): Optional<From, T>;
/** Used to to go from ids to values and back again. Obvious if the mapping isn't one to one there will be loss of data */
static chainLookup<Main, V extends string | number | boolean>(opt: Optional<Main, string>, lookupL: Optional<Main, NameAnd<V>>): Optional<Main, V>;
/** Used to to go from ids to values and back again. Obvious if the mapping isn't one to one there will be loss of data */
static chainLookupTable<Main, L, K extends keyof L, V extends keyof L>(opt: Optional<Main, string>, lookupL: Optional<Main, L[]>, idName: K, valueName: V): Optional<Main, L[V]>;
static constant: <Main, Child>(value: Child, description?: string) => Lens<Main, Child>;
static safeList<T>(): Lens<T[] | undefined, T[]>;
static if<Main, T>(cond: Optional<Main, boolean>, trueL: Optional<Main, T>, falseL: Optional<Main, T>): Optional<Main, T>;
static condition<Main, Cond, T>(cond: Optional<Main, Cond>, fn: (c: Cond) => boolean, trueL: Optional<Main, T>, falseL: Optional<Main, T>): Optional<Main, T>;
static calculatedNth<Main, T>(nL: Optional<Main, number>, opt: Optional<Main, T[]>): Optional<Main, T>;
}
export type FocusOnPathItem = string | FocusOnPathSimpleItem | FocusOnPathNthItem | FocusOnPathPathItem;
export interface FocusOnPathSimpleItem {
action: '$last' | '$append';
}
export declare function isFOPSingle(f: FocusOnPathItem): f is FocusOnPathSimpleItem;
export interface FocusOnPathNthItem {
action: '[n]';
n: number;
}
export declare function isFOPNth(f: FocusOnPathItem): f is FocusOnPathNthItem;
export interface FocusOnPathPathItem {
action: '[path]';
path: FocusOnPathItem[];
}
export declare function isFOPPath(f: FocusOnPathItem): f is FocusOnPathPathItem;
export declare function prism<Main, Child>(getOption: (m: Main) => Child | undefined, reverseGet: (c: Child) => Main, description?: string): Prism<Main, Child>;
export declare function dirtyPrism<Main, Child>(getOption: (m: Main) => Child | undefined, reverseGet: (c: Child) => Main, description?: string): DirtyPrism<Main, Child>;
export declare class DirtyPrism<Main, Child> extends Optional<Main, Child> {
reverseGet: (c: Child) => Main;
constructor(getOption: (m: Main) => (Child | undefined), reverseGet: (c: Child) => Main, description: string);
toString(): string;
}
export declare class Prism<Main, Child> extends DirtyPrism<Main, Child> {
constructor(getOption: (m: Main) => (Child | undefined), reverseGet: (c: Child) => Main, description: string);
toString(): string;
}
export declare function iso<Main, Child>(get: (m: Main) => Child, reverseGet: (c: Child) => Main, description?: string): Iso<Main, Child>;
export declare class Iso<Main, Child> extends Lens<Main, Child> {
reverseGet: (c: Child) => Main;
optional: Optional<Main, Child>;
constructor(get: (m: Main) => Child, reverseGet: (c: Child) => Main, description: string);
toString(): string;
}
/** This 'changes' two parts of Main simultaneously.
*
* @param lens1 This is focused in on a part of main that we want to change
* @param lens2 This is focused in on a second part of main that we want to change
* @param fn1 Given the old values that lens1 and lens2 are focused on, this gives us a new value for the part of main that lens1 is focused on
* @param fn2 Given the old values that lens1 and lens2 are focused on, this gives us a new value for the part of main that lens2 is focused on
* @returns a function that given a Main will return a new main with the two functions used to modify the parts of Main that the two lens are focused in on
*
*/
export declare const transformTwoValues: <Main, C1, C2>(lens1: Optional<Main, C1>, lens2: Optional<Main, C2>) => (fn1: (c1: C1, c2: C2) => C1, fn2: (c1: C1, c2: C2) => C2) => (main: Main) => Main;
/** This 'changes' two parts of Main simultaneously.
*
* @param lens1 This is focused in on a part of main that we want to change
* @param lens2 This is focused in on a second part of main that we want to change
* @param main A value that is to be 'changed' by the method. Changed means that we will make a copy of it with changes
* @param c1 The new value for the part that lens1 is focused on
* @param c2 The new value for the part that lens2 is focused on
* @returns a new main with the parts the two lens are focused on changed by the new values
*
*/
export declare const updateTwoValues: <Main, C1, C2>(lens1: Optional<Main, C1>, lens2: Optional<Main, C2>) => (main: Main, c1: C1, c2: C2) => Main;
/** This 'changes' three parts of Main simultaneously.
*
* @param lens1 This is focused in on a part of main that we want to change
* @param lens2 This is focused in on a second part of main that we want to change
* @param lens3 This is focused in on a third part of main that we want to change
* @param main A value that is to be 'changed' by the method. Changed means that we will make a copy of it with changes
* @param c1 The new value for the part that lens1 is focused on
* @param c2 The new value for the part that lens2 is focused on
* @param c3 The new value for the part that lens3 is focused on
* @returns a new main with the parts the three lens are focused on changed by the new values
*
*/
export declare const updateThreeValues: <Main, C1, C2, C3>(lens1: Optional<Main, C1>, lens2: Optional<Main, C2>, lens3: Optional<Main, C3>) => (main: Main, c1: C1, c2: C2, c3: C3) => Main;
export declare function nthItem<T>(n: number): Optional<T[], T>;
export declare function firstIn2<T1, T2>(): Optional<[T1, T2], T1>;
export declare function secondIn2<T1, T2>(): Optional<[T1, T2], T2>;
export type Transform<Main, Child> = [Optional<Main, Child>, (c: Child | undefined) => Child];
interface DisplayTransform {
opt: string;
value: any;
}
export declare function displayTransformsInState<S>(main: S, txs: Transform<S, any>[]): DisplayTransform[];
export declare function massTransform<Main>(main: Main, ...transforms: Transform<Main, any>[]): Main;
export declare function nameLensFn<S, T extends NameAnd<any>>(lens: Optional<S, T>): GetNameFn<S, any>;
export declare function asGetNameFn<S, T>(nl: NameAndLens<S>): GetNameFn<S, T>;
export type GetNameFn<Main, T> = (name: string) => GetOptioner<Main, T>;
export interface NameAndLens<S> {
[name: string]: Optional<S, any>;
}
export interface NameAndLensFn<S> {
[name: string]: (o: Iso<S, S>) => Optional<S, any>;
}
export {};