UNPKG

@platform/state

Version:

A small, simple, strongly typed, [rx/observable] state-machine.

113 lines (112 loc) 3.62 kB
import { id } from '@platform/util.value'; import { enablePatches, isDraft, original, produceWithPatches, setAutoFreeze } from 'immer'; import { Subject } from 'rxjs'; import { share } from 'rxjs/operators'; import { is } from '../common'; import { Patch } from '../Patch'; import * as events from './StateObject.events'; import * as merge from './StateObject.merge'; if (typeof setAutoFreeze === 'function') { setAutoFreeze(false); } if (typeof enablePatches === 'function') { enablePatches(); } export class StateObject { constructor(args) { this._dispose$ = new Subject(); this.dispose$ = this._dispose$.pipe(share()); this._event$ = new Subject(); this.event = events.create(this._event$, this._dispose$); this.change = (fn, options = {}) => { const cid = id.cuid(); const type = (options.action || '').trim(); const from = this.state; const { to, op, patches } = next(from, fn); if (Patch.isEmpty(patches)) { return { op, cid, patches }; } const changing = { op, cid, from, to, patches, cancelled: false, cancel: () => (changing.cancelled = true), action: type, }; this.fire({ type: 'StateObject/changing', payload: changing }); const cancelled = changing.cancelled ? changing : undefined; if (cancelled) { this.fire({ type: 'StateObject/cancelled', payload: cancelled }); } const changed = cancelled ? undefined : { op, cid, from, to, patches, action: type }; if (changed) { this._state = to; this.fire({ type: 'StateObject/changed', payload: changed }); } return { op, cid, changed, cancelled, patches, }; }; this.fire = (e) => this._event$.next(e); this._state = Object.assign({}, args.initial); this.original = this.state; } static create(initial) { return new StateObject({ initial }); } static readonly(obj) { return obj; } static toObject(input) { return isDraft(input) ? original(input) : input; } static isStateObject(input) { return is.stateObject(input); } dispose() { if (!this.isDisposed) { this.fire({ type: 'StateObject/disposed', payload: { original: this.original, final: this.state }, }); this._dispose$.next(); this._dispose$.complete(); } } get isDisposed() { return this._dispose$.isStopped; } get state() { return this._state; } get readonly() { return this; } } StateObject.merge = merge.create(StateObject.create); const next = (from, fn) => { if (typeof fn === 'function') { const [to, forward, backward] = produceWithPatches(from, (draft) => { fn(draft); return undefined; }); const patches = Patch.toPatchSet(forward, backward); const op = 'update'; return { op, to, patches }; } else { const [to, forward, backward] = produceWithPatches(from, () => fn); const patches = Patch.toPatchSet(forward, backward); const op = 'replace'; return { op, to, patches }; } };