From 56ffae27f8ef447953df4d0cb2cb58306a5c897f Mon Sep 17 00:00:00 2001 From: Derrick Beining Date: Tue, 11 Dec 2018 01:38:55 -0500 Subject: [PATCH] perf: split functions into separate modules --- docs/assets/js/search.js | 2 +- docs/classes/atom.html | 2 +- docs/globals.html | 11 +- src/atom.ts | 207 ++----------------------------------- src/deref.ts | 25 +++++ src/getValidator.ts | 27 +++++ src/index.ts | 7 +- src/internal-state.ts | 30 ++++++ src/set.ts | 42 ++++++++ src/setValidator.ts | 40 +++++++ src/swap.ts | 41 ++++++++ src/test/set.spec.tsx | 2 +- src/test/validator.spec.ts | 3 +- src/utils.ts | 10 +- 14 files changed, 234 insertions(+), 215 deletions(-) create mode 100644 src/deref.ts create mode 100644 src/getValidator.ts create mode 100644 src/internal-state.ts create mode 100644 src/set.ts create mode 100644 src/setValidator.ts create mode 100644 src/swap.ts diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js index d67198b..d8e0d4b 100644 --- a/docs/assets/js/search.js +++ b/docs/assets/js/search.js @@ -1,3 +1,3 @@ var typedoc = typedoc || {}; typedoc.search = typedoc.search || {}; - typedoc.search.data = {"kinds":{"64":"Function","128":"Class","256":"Interface","2048":"Method","4194304":"Type alias"},"rows":[{"id":0,"kind":256,"name":"AtomConstructorOptions","url":"interfaces/atomconstructoroptions.html","classes":"tsd-kind-interface tsd-has-type-parameter"},{"id":1,"kind":2048,"name":"validator","url":"interfaces/atomconstructoroptions.html#validator","classes":"tsd-kind-method tsd-parent-kind-interface","parent":"AtomConstructorOptions"},{"id":2,"kind":4194304,"name":"AtomState","url":"globals.html#atomstate","classes":"tsd-kind-type-alias tsd-has-type-parameter"},{"id":3,"kind":4194304,"name":"DeepImmutable","url":"globals.html#deepimmutable","classes":"tsd-kind-type-alias tsd-has-type-parameter"},{"id":4,"kind":128,"name":"Atom","url":"classes/atom.html","classes":"tsd-kind-class tsd-has-type-parameter"},{"id":5,"kind":2048,"name":"of","url":"classes/atom.html#of","classes":"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter tsd-is-static","parent":"Atom"},{"id":6,"kind":64,"name":"deref","url":"globals.html#deref","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":7,"kind":64,"name":"swap","url":"globals.html#swap","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":8,"kind":64,"name":"set","url":"globals.html#set","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":9,"kind":64,"name":"getValidator","url":"globals.html#getvalidator","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":10,"kind":64,"name":"setValidator","url":"globals.html#setvalidator","classes":"tsd-kind-function tsd-has-type-parameter"}]}; \ No newline at end of file + typedoc.search.data = {"kinds":{"64":"Function","128":"Class","256":"Interface","2048":"Method","4194304":"Type alias"},"rows":[{"id":0,"kind":256,"name":"AtomConstructorOptions","url":"interfaces/atomconstructoroptions.html","classes":"tsd-kind-interface tsd-has-type-parameter"},{"id":1,"kind":2048,"name":"validator","url":"interfaces/atomconstructoroptions.html#validator","classes":"tsd-kind-method tsd-parent-kind-interface","parent":"AtomConstructorOptions"},{"id":2,"kind":4194304,"name":"AtomState","url":"globals.html#atomstate","classes":"tsd-kind-type-alias tsd-has-type-parameter"},{"id":3,"kind":4194304,"name":"DeepImmutable","url":"globals.html#deepimmutable","classes":"tsd-kind-type-alias tsd-has-type-parameter"},{"id":4,"kind":128,"name":"Atom","url":"classes/atom.html","classes":"tsd-kind-class tsd-has-type-parameter"},{"id":5,"kind":2048,"name":"of","url":"classes/atom.html#of","classes":"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter tsd-is-static","parent":"Atom"},{"id":6,"kind":64,"name":"deref","url":"globals.html#deref","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":7,"kind":64,"name":"getValidator","url":"globals.html#getvalidator","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":8,"kind":64,"name":"set","url":"globals.html#set","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":9,"kind":64,"name":"setValidator","url":"globals.html#setvalidator","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":10,"kind":64,"name":"swap","url":"globals.html#swap","classes":"tsd-kind-function tsd-has-type-parameter"}]}; \ No newline at end of file diff --git a/docs/classes/atom.html b/docs/classes/atom.html index 8a9ac2e..ce9a39c 100644 --- a/docs/classes/atom.html +++ b/docs/classes/atom.html @@ -113,7 +113,7 @@

Static of

  • diff --git a/docs/globals.html b/docs/globals.html index a5fd532..f2e7ed1 100644 --- a/docs/globals.html +++ b/docs/globals.html @@ -148,7 +148,7 @@

    deref

  • @@ -196,7 +196,7 @@

    getValidator

  • @@ -244,7 +244,7 @@

    set

  • @@ -302,7 +302,7 @@

    setValidator

  • @@ -317,6 +317,7 @@

    setValidator

    example
    
     import {Atom, deref, setValidator, set} from '@libre/atom'
    +import { _setValidator } from './internal-state';
     
     const atom = Atom.of({ count: 0 }, {validator: (state) => isNumber(state.count) })
     setValidator(atom, (state) => isOdd(state.count)) // Error; new validator rejected
    @@ -359,7 +360,7 @@ 

    swap

  • diff --git a/src/atom.ts b/src/atom.ts index d65ac41..68476a2 100644 --- a/src/atom.ts +++ b/src/atom.ts @@ -1,23 +1,6 @@ -import * as ErrorMsgs from "./error-messages"; +import { _getState, _setState, _setValidator, _useNextAtomId } from "./internal-state"; import { AtomConstructorOptions, DeepImmutable } from "./internal-types"; -import { prettyPrint, throwIfNotAtom } from "./utils"; -// ------------------------------------------------------------------------------------------ // -// ---------------------------------- INTERNAL STATE ---------------------------------------- // -// ------------------------------------------------------------------------------------------ // - -let nextAtomUid = 0; - -const stateByAtomId: Record> = Object.create(null); -const validatorByAtomId: Record["validator"]>> = Object.create(null); - -/** @ignore */ -export function getState(atom: Atom): DeepImmutable { - return stateByAtomId[atom["$$id"]]; -} - -// ------------------------------------------------------------------------------------------ // -// -------------------------------------- PUBLIC API ---------------------------------------- // -// ------------------------------------------------------------------------------------------ // +import { _prettyPrint } from "./utils"; /** * A data structure useful for providing a controlled, predictable mechanism for mutability. @@ -27,10 +10,6 @@ export function getState(atom: Atom): DeepImmutable { * */ -// -// ======================================= ATOM ============================================== -// - export class Atom { /** * Constructs a new instance of [[Atom]] with its internal state @@ -58,7 +37,7 @@ const a3 = Atom.of({ count: 0 }) private constructor(state: S, { validator }: AtomConstructorOptions = {}) { validator = validator || (() => true); if (!validator(state as DeepImmutable)) { - const errMsg = `Atom initialized with invalid state:\n\n${prettyPrint( + const errMsg = `Atom initialized with invalid state:\n\n${_prettyPrint( state )}\n\naccording to validator function:\n${validator}\n\n`; const err = Error(errMsg); @@ -66,189 +45,17 @@ const a3 = Atom.of({ count: 0 }) throw err; } - Object.defineProperty(this, "$$id", { value: nextAtomUid++ }); - stateByAtomId[this["$$id"]] = state; - validatorByAtomId[this["$$id"]] = validator; + Object.defineProperty(this, "$$id", { value: _useNextAtomId() }); + _setState(this, state); + _setValidator(this, validator); return this; } /** @ignore */ public toString(): string { - return `Atom<${prettyPrint(getState(this))}>`; + return `Atom<${_prettyPrint(_getState(this))}>`; } /** @ignore */ public inspect(): string { return this.toString(); } } - -// -// ======================================= DEREF ============================================== -// - -/** - * Dereferences (i.e. "*reads*") the current state of an [[Atom]]. The dereferenced value - * should ___not___ be mutated. - * - * @param the type of `atom`'s inner state - * - * @example -```js - -import {Atom, deref} from '@libre/atom' - -const stateAtom = Atom.of({ count: 0 }) - -deref(stateAtom) // => { count: 0 } -``` - */ -export function deref(atom: Atom): DeepImmutable { - throwIfNotAtom(atom); - return getState(atom); -} - -// -// ======================================= SWAP ============================================== -// -/** - * Swaps `atom`'s state with the value returned from applying `updateFn` to `atom`'s - * current state. `updateFn` should be a pure function and ___not___ mutate `state`. - * - * @param the type of `atom`'s inner state - * @param atom an instance of [[Atom]] - * @param updateFn a pure function that takes the current state and returns the next state; the next state should be of the same type/interface as the current state; - * - * @example - * ```jsx - * - *import {Atom, swap} from '@libre/atom' - * - *const stateAtom = Atom.of({ count: 0 }) - *const increment = () => swap(stateAtom, (state) => ({ - * count: state.count + 1 - *})); - * ``` - */ -export function swap(atom: Atom, updateFn: (state: DeepImmutable) => S): void { - throwIfNotAtom(atom); - const nextState = updateFn(getState(atom)); - const validator = getValidator(atom); - const didValidate = validator(nextState); - if (!didValidate) { - const errMsg = `swap updateFn\n${updateFn}\n\nattempted to swap the state of\n\n${atom}\n\nwith:\n\n${prettyPrint( - nextState - )}\n\nbut it did not pass validator:\n${validator}\n\n`; - const err = Error(errMsg); - err.name = "AtomInvalidStateError"; - - throw err; - } else { - stateByAtomId[atom["$$id"]] = nextState; - } -} - -// -// ======================================= SET ============================================== -// - -/** - * Sets `atom`s state to `nextState`. - * - * It is equivalent to `swap(atom, () => newState)`. - * - * @param the type of `atom`'s inner state - * @param atom an instance of [[Atom]] - * @param nextState the value to which to set the state; it should be the same type/interface as current state - * - * @example -```js - -import {Atom, deref, set} from '@libre/atom' - -const atom = Atom.of({ count: 0 }) - -set(atom, { count: 100 }) -deref(atom) // => { count: 100 } -``` - */ - -export function set(atom: Atom, nextState: S): void { - throwIfNotAtom(atom); - const validator = getValidator(atom); - const didValidate = validator(nextState); - if (!didValidate) { - const errMsg = `Attempted to set the state of\n\n${atom}\n\nwith:\n\n${prettyPrint( - nextState - )}\n\nbut it did not pass validator:\n${validator}\n\n`; - const err = Error(errMsg); - err.name = "AtomInvalidStateError"; - - throw err; - } else { - stateByAtomId[atom["$$id"]] = nextState; - } -} - -// -// ======================================= GETVALIDATOR ============================================== -// - -/** - * Gets `atom`'s validator function - * - * @param the type of `atom`'s inner state - * - * @example -```js - -import {Atom, deref, getValidator, swap} from '@libre/atom' - -const atom = Atom.of({ count: 0 }, { validator: (state) => isEven(state.count) }) -const validator = getValidator(atom) -validator({ count: 3 }) // => false -validator({ count: 2 }) // => true -``` - */ - -export function getValidator(atom: Atom): NonNullable["validator"]> { - throwIfNotAtom(atom); - return validatorByAtomId[atom["$$id"]]; -} - -// -// ======================================= SETVALIDATOR ============================================== -// - -/** - * Sets the `validator` for `atom`. `validator` must be a pure function of one argument, - * which will be passed the intended new state on any state change. If the new state is - * unacceptable, `validator` should return false or throw an exception. If the current state - * is not acceptable to the new validator, an exception will be thrown and the validator will - * not be changed. - * - * @param the type of `atom`'s inner state - * - * @example -```js - -import {Atom, deref, setValidator, set} from '@libre/atom' - -const atom = Atom.of({ count: 0 }, {validator: (state) => isNumber(state.count) }) -setValidator(atom, (state) => isOdd(state.count)) // Error; new validator rejected -set(atom, {count: "not number"}) // Error; new state not set -setValidator(atom, (state) => isEven(state.count)) // All good -set(atom, {count: 2}) // All good - -``` - */ - -export function setValidator(atom: Atom, validator: NonNullable["validator"]>): void { - throwIfNotAtom(atom); - if (!validator(getState(atom))) { - const errMsg = `Could not set validator on\n\n${atom}\n\nbecause current state would be invalid according to new validator:\n${validator}\n\n`; - const err = Error(errMsg); - err.name = "AtomInvalidStateError"; - throw err; - } else { - validatorByAtomId[atom["$$id"]] = validator; - } -} diff --git a/src/deref.ts b/src/deref.ts new file mode 100644 index 0000000..e2441ef --- /dev/null +++ b/src/deref.ts @@ -0,0 +1,25 @@ +import { Atom } from "./atom"; +import { _getState } from "./internal-state"; +import { DeepImmutable } from "./internal-types"; +import { _throwIfNotAtom } from "./utils"; + +/** + * Dereferences (i.e. "*reads*") the current state of an [[Atom]]. The dereferenced value + * should ___not___ be mutated. + * + * @param the type of `atom`'s inner state + * + * @example +```js + +import {Atom, deref} from '@libre/atom' + +const stateAtom = Atom.of({ count: 0 }) + +deref(stateAtom) // => { count: 0 } +``` + */ +export function deref(atom: Atom): DeepImmutable { + _throwIfNotAtom(atom); + return _getState(atom); +} diff --git a/src/getValidator.ts b/src/getValidator.ts new file mode 100644 index 0000000..b37b2bb --- /dev/null +++ b/src/getValidator.ts @@ -0,0 +1,27 @@ +import { Atom } from "./atom"; +import { _getValidator } from "./internal-state"; +import { AtomConstructorOptions } from "./internal-types"; + +import { _throwIfNotAtom } from "./utils"; + +/** + * Gets `atom`'s validator function + * + * @param the type of `atom`'s inner state + * + * @example +```js + +import {Atom, deref, getValidator, swap} from '@libre/atom' + +const atom = Atom.of({ count: 0 }, { validator: (state) => isEven(state.count) }) +const validator = getValidator(atom) +validator({ count: 3 }) // => false +validator({ count: 2 }) // => true +``` + */ + +export function getValidator(atom: Atom): NonNullable["validator"]> { + _throwIfNotAtom(atom); + return _getValidator(atom); +} diff --git a/src/index.ts b/src/index.ts index 15ba092..f672d0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,7 @@ -export { Atom, deref, swap, set, setValidator, getValidator } from "./atom"; +export { Atom } from "./atom"; export { AtomState } from "./internal-types"; +export { deref } from "./deref"; +export { getValidator } from "./getValidator"; +export { set } from "./set"; +export { setValidator } from "./setValidator"; +export { swap } from "./swap"; diff --git a/src/internal-state.ts b/src/internal-state.ts new file mode 100644 index 0000000..7198452 --- /dev/null +++ b/src/internal-state.ts @@ -0,0 +1,30 @@ +import { Atom } from "./atom"; +import { AtomConstructorOptions, DeepImmutable } from "./internal-types"; + +let nextAtomUid = 0; +const stateByAtomId: Record> = Object.create(null); +const validatorByAtomId: Record["validator"]>> = Object.create(null); + +/** @ignore */ +export function _useNextAtomId() { + return nextAtomUid++; +} + +/** @ignore */ +export function _getState(atom: Atom): DeepImmutable { + return stateByAtomId[atom["$$id"]]; +} +/** @ignore */ +export function _setState(atom: Atom, state: S): void { + stateByAtomId[atom["$$id"]] = state; +} + +/** @ignore */ +export function _getValidator(atom: Atom): NonNullable["validator"]> { + return validatorByAtomId[atom["$$id"]]; +} + +/** @ignore */ +export function _setValidator(atom: Atom, validator: NonNullable["validator"]>): void { + validatorByAtomId[atom["$$id"]] = validator; +} diff --git a/src/set.ts b/src/set.ts new file mode 100644 index 0000000..8de2756 --- /dev/null +++ b/src/set.ts @@ -0,0 +1,42 @@ +import { Atom } from "./atom"; +import { _getValidator, _setState } from "./internal-state"; +import { DeepImmutable } from "./internal-types"; +import { _prettyPrint, _throwIfNotAtom } from "./utils"; + +/** + * Sets `atom`s state to `nextState`. + * + * It is equivalent to `swap(atom, () => newState)`. + * + * @param the type of `atom`'s inner state + * @param atom an instance of [[Atom]] + * @param nextState the value to which to set the state; it should be the same type/interface as current state + * + * @example +```js + +import {Atom, deref, set} from '@libre/atom' + +const atom = Atom.of({ count: 0 }) + +set(atom, { count: 100 }) +deref(atom) // => { count: 100 } +``` + */ + +export function set(atom: Atom, nextState: S): void { + _throwIfNotAtom(atom); + const validator = _getValidator(atom); + const didValidate = validator(nextState as DeepImmutable); + if (!didValidate) { + const errMsg = `Attempted to set the state of\n\n${atom}\n\nwith:\n\n${_prettyPrint( + nextState + )}\n\nbut it did not pass validator:\n${validator}\n\n`; + const err = Error(errMsg); + err.name = "AtomInvalidStateError"; + + throw err; + } else { + _setState(atom, nextState); + } +} diff --git a/src/setValidator.ts b/src/setValidator.ts new file mode 100644 index 0000000..5b59d0e --- /dev/null +++ b/src/setValidator.ts @@ -0,0 +1,40 @@ +import { Atom } from "./atom"; +import { _getState, _setValidator } from "./internal-state"; +import { AtomConstructorOptions } from "./internal-types"; +import { _throwIfNotAtom } from "./utils"; + +/** + * Sets the `validator` for `atom`. `validator` must be a pure function of one argument, + * which will be passed the intended new state on any state change. If the new state is + * unacceptable, `validator` should return false or throw an exception. If the current state + * is not acceptable to the new validator, an exception will be thrown and the validator will + * not be changed. + * + * @param the type of `atom`'s inner state + * + * @example +```js + +import {Atom, deref, setValidator, set} from '@libre/atom' +import { _setValidator } from './internal-state'; + +const atom = Atom.of({ count: 0 }, {validator: (state) => isNumber(state.count) }) +setValidator(atom, (state) => isOdd(state.count)) // Error; new validator rejected +set(atom, {count: "not number"}) // Error; new state not set +setValidator(atom, (state) => isEven(state.count)) // All good +set(atom, {count: 2}) // All good + +``` + */ + +export function setValidator(atom: Atom, validator: NonNullable["validator"]>): void { + _throwIfNotAtom(atom); + if (!validator(_getState(atom))) { + const errMsg = `Could not set validator on\n\n${atom}\n\nbecause current state would be invalid according to new validator:\n${validator}\n\n`; + const err = Error(errMsg); + err.name = "AtomInvalidStateError"; + throw err; + } else { + _setValidator(atom, validator); + } +} diff --git a/src/swap.ts b/src/swap.ts new file mode 100644 index 0000000..dc0fa33 --- /dev/null +++ b/src/swap.ts @@ -0,0 +1,41 @@ +import { Atom } from "./atom"; +import { _getState, _getValidator, _setState } from "./internal-state"; +import { DeepImmutable } from "./internal-types"; +import { _prettyPrint, _throwIfNotAtom } from "./utils"; + +/** + * Swaps `atom`'s state with the value returned from applying `updateFn` to `atom`'s + * current state. `updateFn` should be a pure function and ___not___ mutate `state`. + * + * @param the type of `atom`'s inner state + * @param atom an instance of [[Atom]] + * @param updateFn a pure function that takes the current state and returns the next state; the next state should be of the same type/interface as the current state; + * + * @example + * ```jsx + * + *import {Atom, swap} from '@libre/atom' + * + *const stateAtom = Atom.of({ count: 0 }) + *const increment = () => swap(stateAtom, (state) => ({ + * count: state.count + 1 + *})); + * ``` + */ +export function swap(atom: Atom, updateFn: (state: DeepImmutable) => S): void { + _throwIfNotAtom(atom); + const nextState = updateFn(_getState(atom)); + const validator = _getValidator(atom); + const didValidate = validator(nextState as DeepImmutable); + if (!didValidate) { + const errMsg = `swap updateFn\n${updateFn}\n\nattempted to swap the state of\n\n${atom}\n\nwith:\n\n${_prettyPrint( + nextState + )}\n\nbut it did not pass validator:\n${validator}\n\n`; + const err = Error(errMsg); + err.name = "AtomInvalidStateError"; + + throw err; + } else { + _setState(atom, nextState); + } +} diff --git a/src/test/set.spec.tsx b/src/test/set.spec.tsx index b81d305..a1d66ff 100644 --- a/src/test/set.spec.tsx +++ b/src/test/set.spec.tsx @@ -1,4 +1,4 @@ -import { Atom, deref, set } from "../../src/atom"; +import { Atom, deref, set } from "../../src"; describe("set function", () => { it("is a function", () => { diff --git a/src/test/validator.spec.ts b/src/test/validator.spec.ts index 3ad7f11..a040648 100644 --- a/src/test/validator.spec.ts +++ b/src/test/validator.spec.ts @@ -1,5 +1,4 @@ -import { Atom, deref, set, swap } from "../../src"; -import { getValidator, setValidator } from "../atom"; +import { Atom, deref, getValidator, set, setValidator, swap } from "../../src"; describe("Atom state validation", () => { describe("Atom.of(state, { validator })", () => { diff --git a/src/utils.ts b/src/utils.ts index 7f19f3f..5bf69ff 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,11 +2,13 @@ import { Atom } from "./atom"; import * as ErrorMsgs from "./error-messages"; /** @ignore */ -export const prettyPrint = (val: any): string => JSON.stringify(val, null, " "); +export function _prettyPrint(val: any): string { + return JSON.stringify(val, null, " "); +} /** @ignore */ -export const throwIfNotAtom = (atom: Atom): void | never => { +export function _throwIfNotAtom(atom: Atom): void | never { if (!(atom instanceof Atom)) { - throw TypeError(`${ErrorMsgs.expectedAtomButGot}\n\n${prettyPrint(atom)}`); + throw TypeError(`${ErrorMsgs.expectedAtomButGot}\n\n${_prettyPrint(atom)}`); } -}; +}