import { __DEVBUILD__, assert, GENERIC_ERROR_MSG } from "./_assert" import { isInTx, queueToEndOfTx, sendNextInTx, Source } from "./_core" import { MutableSource } from "./_mutable" import { MAX_PRIORITY } from "./_priority" import { Operation, Transaction } from "./_tx" import { curry2, is } from "./_util" import { dispatcherOf } from "./Observable" import { Operator } from "./operators/_base" import { Property, PropertyDispatcher } from "./Property" export type Lens = ObjectSetLens | ObjectUpdateLens | VanLaarhovenLens export interface SetOp { (atom: Atom, value: T): void (atom: Atom): (value: T) => void } export interface ModifyOp { (atom: Atom, f: (state: T) => T): void (atom: Atom): (f: (state: T) => T) => void } export interface ViewOp { (atom: Atom, prop: Prop): Atom (atom: Atom): (prop: Prop) => Atom (atom: Atom, lens: Lens): Atom (atom: Atom): (lens: Lens) => Atom } export function atom(initialValue: T): Atom { return new RootAtom(initialValue) } export const get = _get export const set: SetOp = curry2(_set) export const modify: ModifyOp = curry2(_modify) export const view: ViewOp = curry2(_view) export abstract class Atom extends Property {} function _get(a: Atom): T { return atomDispatcherOf(a).state() } function _modify(a: Atom, f: (state: T) => T): void { atomDispatcherOf(a).modify(f) } function _set(a: Atom, value: T): void { _modify(a, (_: T) => value) } function _view(a: Atom, lens: Lens | string): Atom { return new Cursor(new AtomDispatcher(new View(atomDispatcherOf(a), toObjectLens(lens)))) } class RootAtom extends Atom { constructor(val: T) { super(new AtomDispatcher(new AtomSource(val))) } } class Cursor extends Atom {} class AtomSource extends MutableSource { private q: boolean = false constructor(private v: T) { super() } public activate(initialNeeded: boolean): void { super.activate(false) if (initialNeeded) { this._sendValue(this.v) } } public state(): T { return this.v } public modify(f: (state: T) => T): void { const prev = this.v const next = (this.v = f(prev)) if (next !== prev && this.active) { this._sendValue(next) } } public _sendLatest(tx: Transaction): void { this.q = false this.active && this.s.next(tx, this.v) } private _sendValue(value: T): void { if (isInTx() && !this.q) { this.q = true queueToEndOfTx(new SendLatestState(this)) } else { sendNextInTx(this.s, value) } } } class View extends Operator implements AtomSrc { constructor(source: Source & AtomSrc, private l: ObjectUpdateLens) { super(source) } public next(tx: Transaction, val: S): void { const { get: getter } = this.l this.sink.next(tx, getter(val)) } public state(): A { const state = ((this.source as any) as AtomSrc).state() const { get: getter } = this.l return getter(state) } public modify(f: (state: A) => A): void { const s = (this.source as any) as AtomSrc const { update } = this.l s.modify((state: S) => update(state, f)) } } class AtomDispatcher extends PropertyDispatcher implements AtomSrc { public next(tx: Transaction, val: T): void { const { val: prev } = this this.has = true if (prev !== val) { this.sink.next(tx, (this.val = val)) } } public state(): T { return ((this.source as any) as AtomSrc).state() } public modify(f: (state: T) => T): void { // tslint:disable-next-line:whitespace semicolon ;((this.source as any) as AtomSrc).modify(f) } } class SendLatestState implements Operation { public readonly priority: number = MAX_PRIORITY constructor(private a: AtomSource | null) {} public exec(tx: Transaction): void { const atomSource = this.a if (atomSource !== null) { atomSource._sendLatest(tx) } } public abort(): void { this.a = null } } interface Functor { fmap(fn: (a: A) => B): Functor } interface AtomSrc { state(): T modify(f: (state: T) => T): void } function toObjectLens(lens: Lens | string): ObjectUpdateLens { if (typeof lens === "string") { const propGet = (s: S): A => (s as any)[lens] const propUpdate = (s: S, f: (a: A) => A): S => (s === undefined ? { [lens]: f((undefined as any) as A) } : Object.assign({}, s, { [lens]: f((s as any)[lens]) })) as any return { get: propGet, update: propUpdate, } } else if (typeof lens === "function") { const lensGet = (s: S): A => ((lens(constant)(s) as any) as VFunctor).val const lensUpdate = (s: S, f: (a: A) => A): S => ((lens((a: A) => id(f(a)))(s) as any) as VFunctor).val return { get: lensGet, update: lensUpdate, } } else if ("update" in lens) { if (__DEVBUILD__) { assert(typeof lens.update === "function", "'lens.update' must be a function") assert(typeof lens.get === "function", "'lens.get' must be a function") } return lens } else { if (__DEVBUILD__) { assert(typeof lens.set === "function", "'lens.set' must be a function") assert(typeof lens.get === "function", "'lens.get' must be a function") } const { get: lGet, set: lSet } = lens return { get: lGet, update: (s: S, f: (a: A) => A): S => lSet(f(lGet(s)), s), } } } interface ObjectSetLens { get(s: S): A set(a: A, s: S): S } interface ObjectUpdateLens { get(s: S): A update(s: S, f: (a: A) => A): S } type VanLaarhovenLens = (a2f: (a: A) => Functor) => (s: S) => Functor interface VFunctor extends Functor { val: T } function constant(val: T): VFunctor { return new Const(val) } function id(val: T): VFunctor { return new Id(val) } class Const implements VFunctor { constructor(public val: A) {} public fmap(fn: (a: A) => B): VFunctor { return (this as any) as VFunctor } public map(fn: (a: A) => B): VFunctor { return this.fmap(fn) } public ["fantasy-land/map"](fn: (a: A) => B): VFunctor { return this.fmap(fn) } } class Id implements VFunctor { constructor(public val: A) {} public fmap(fn: (a: A) => B): VFunctor { return new Id(fn(this.val)) } public map(fn: (a: A) => B): VFunctor { return this.fmap(fn) } public ["fantasy-land/map"](fn: (a: A) => B): VFunctor { return this.fmap(fn) } } function atomDispatcherOf(a: Atom): AtomDispatcher { checkAtom(a) return (dispatcherOf(a) as any) as AtomDispatcher } function checkAtom(x: any): void { const ok = is(x, Atom) assert(ok, __DEVBUILD__ ? (!ok ? `Expected an Atom but got ${x}` : "") : GENERIC_ERROR_MSG) }