@libre/atom
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -1,24 +0,42 @@ | ||
/** @ignore */ | ||
const derefArgMustBeAtom = "deref only accepts Atom instances, but got:"; | ||
// ------------------------------------------------------------------------------------------ // | ||
// ---------------------------------- INTERNAL STATE ---------------------------------------- // | ||
// ------------------------------------------------------------------------------------------ // | ||
let nextAtomUid = 0; | ||
const stateByAtomId = Object.create(null); | ||
const validatorByAtomId = Object.create(null); | ||
/** @ignore */ | ||
function getState(atom) { | ||
function _useNextAtomId() { | ||
return nextAtomUid++; | ||
} | ||
/** @ignore */ | ||
function _getState(atom) { | ||
return stateByAtomId[atom["$$id"]]; | ||
} | ||
// ------------------------------------------------------------------------------------------ // | ||
// -------------------------------------- PUBLIC API ---------------------------------------- // | ||
// ------------------------------------------------------------------------------------------ // | ||
/** @ignore */ | ||
function _setState(atom, state) { | ||
stateByAtomId[atom["$$id"]] = state; | ||
} | ||
/** @ignore */ | ||
function _getValidator(atom) { | ||
return validatorByAtomId[atom["$$id"]]; | ||
} | ||
/** @ignore */ | ||
function _setValidator(atom, validator) { | ||
validatorByAtomId[atom["$$id"]] = validator; | ||
} | ||
/** @ignore */ | ||
const expectedAtomButGot = "Expected an Atom instances, but got:"; | ||
/** @ignore */ | ||
function _prettyPrint(val) { | ||
return JSON.stringify(val, null, " "); | ||
} | ||
/** @ignore */ | ||
function _throwIfNotAtom(atom) { | ||
if (!(atom instanceof Atom)) { | ||
throw TypeError(`${expectedAtomButGot}\n\n${_prettyPrint(atom)}`); | ||
} | ||
} | ||
/** | ||
* `@libre/atom` provides a data type called `Atom` and a few functions for working with `Atom`s. | ||
* It is heavily inspired by `atom`s in Clojure(Script). | ||
* | ||
* Atoms provide a predictable way to manage state that is shared by multiple components of a | ||
* program as that state changes over time. They are particularly useful in the functional and reactive | ||
* programming paradigms, where most components of a program are pure functions operating on | ||
* immutable data. In this context, Atoms provide a form of mutability that is controlled in such | ||
* A data structure useful for providing a controlled, predictable mechanism for mutability. | ||
* Allows multiple components of a program to share read/write access to some state in such | ||
* a way that no component can mutate another component's current reference to the state in | ||
@@ -28,10 +46,15 @@ * the middle of some process or asynchronous operation. | ||
*/ | ||
// | ||
// ======================================= ATOM ============================================== | ||
// | ||
class Atom { | ||
/** @ignore */ | ||
constructor(state) { | ||
Object.defineProperty(this, "$$id", { value: nextAtomUid++ }); | ||
stateByAtomId[this["$$id"]] = state; | ||
constructor(state, { validator } = {}) { | ||
validator = validator || (() => true); | ||
if (!validator(state)) { | ||
const errMsg = `Atom initialized with invalid state:\n\n${_prettyPrint(state)}\n\naccording to validator function:\n${validator}\n\n`; | ||
const err = Error(errMsg); | ||
err.name = "AtomInvalidStateError"; | ||
throw err; | ||
} | ||
Object.defineProperty(this, "$$id", { value: _useNextAtomId() }); | ||
_setState(this, state); | ||
_setValidator(this, validator); | ||
return this; | ||
@@ -54,11 +77,8 @@ } | ||
*/ | ||
static of(state) { | ||
return new Atom(state); | ||
static of(state, options) { | ||
return new Atom(state, options); | ||
} | ||
/** @ignore */ | ||
toString() { | ||
return `Atom ${JSON.stringify({ | ||
$$id: this["$$id"], | ||
"[[inner_state]]": getState(this) | ||
}, null, " ")}`; | ||
return `Atom<${_prettyPrint(_getState(this))}>`; | ||
} | ||
@@ -70,7 +90,5 @@ /** @ignore */ | ||
} | ||
// | ||
// ======================================= DEREF ============================================== | ||
// | ||
/** | ||
* Reads (i.e. "*dereferences*") the current state of an [[Atom]]. The dereferenced value | ||
* Dereferences (i.e. "*reads*") the current state of an [[Atom]]. The dereferenced value | ||
* should ___not___ be mutated. | ||
@@ -91,35 +109,27 @@ * | ||
function deref(atom) { | ||
if (!(atom instanceof Atom)) { | ||
const arg = JSON.stringify(atom, null, " "); | ||
throw TypeError(`${derefArgMustBeAtom}\n${arg}`); | ||
} | ||
return getState(atom); | ||
_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`. | ||
* Gets `atom`'s validator function | ||
* | ||
* @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; | ||
* @param <S> the type of `atom`'s inner state | ||
* | ||
* @example | ||
* ```jsx | ||
* | ||
*import {Atom, swap} from '@libre/atom' | ||
* | ||
*const stateAtom = Atom.of({ count: 0 }) | ||
*const increment = () => swap(stateAtom, (state) => ({ | ||
* count: state.count + 1 | ||
*})); | ||
* ``` | ||
```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 | ||
``` | ||
*/ | ||
function swap(atom, updateFn) { | ||
stateByAtomId[atom["$$id"]] = updateFn(getState(atom)); | ||
function getValidator(atom) { | ||
_throwIfNotAtom(atom); | ||
return _getValidator(atom); | ||
} | ||
// | ||
// ======================================= SET ============================================== | ||
// | ||
/** | ||
@@ -130,2 +140,3 @@ * Sets `atom`s state to `nextState`. | ||
* | ||
* @param <S> the type of `atom`'s inner state | ||
* @param atom an instance of [[Atom]] | ||
@@ -137,4 +148,3 @@ * @param nextState the value to which to set the state; it should be the same type/interface as current state | ||
import {Atom, useAtom, set} from '@libre/atom' | ||
import { DeepImmutable } from './internal-types'; | ||
import {Atom, deref, set} from '@libre/atom' | ||
@@ -148,6 +158,88 @@ const atom = Atom.of({ count: 0 }) | ||
function set(atom, nextState) { | ||
swap(atom, () => nextState); | ||
_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 { | ||
_setState(atom, nextState); | ||
} | ||
} | ||
export { Atom, deref, swap, set }; | ||
/** | ||
* 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 <S> 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 | ||
``` | ||
*/ | ||
function setValidator(atom, validator) { | ||
_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); | ||
} | ||
} | ||
/** | ||
* 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 <S> 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 | ||
*})); | ||
* ``` | ||
*/ | ||
function swap(atom, updateFn) { | ||
_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 { | ||
_setState(atom, nextState); | ||
} | ||
} | ||
export { Atom, deref, getValidator, set, setValidator, swap }; | ||
//# sourceMappingURL=index.esm.js.map |
@@ -1,2 +0,2 @@ | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t["@libre/atom"]={})}(this,function(t){"use strict";var e=0,i=Object.create(null);function r(t){return i[t.$$id]}var o=function(){function n(t){return Object.defineProperty(this,"$$id",{value:e++}),i[this.$$id]=t,this}return n.of=function(t){return new n(t)},n.prototype.toString=function(){return"Atom "+JSON.stringify({$$id:this.$$id,"[[inner_state]]":r(this)},null," ")},n.prototype.inspect=function(){return this.toString()},n}();function u(t,n){i[t.$$id]=n(r(t))}t.Atom=o,t.deref=function(t){if(t instanceof o)return r(t);var n=JSON.stringify(t,null," ");throw TypeError("deref only accepts Atom instances, but got:\n"+n)},t.swap=u,t.set=function(t,n){u(t,function(){return n})},Object.defineProperty(t,"__esModule",{value:!0})}); | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t["@libre/atom"]={})}(this,function(t){"use strict";var i=0,r=Object.create(null),e=Object.create(null);function a(t){return r[t.$$id]}function u(t,n){r[t.$$id]=n}function d(t){return e[t.$$id]}function f(t,n){e[t.$$id]=n}var n="Expected an Atom instances, but got:";function c(t){return JSON.stringify(t,null," ")}function s(t){if(!(t instanceof o))throw TypeError(n+"\n\n"+c(t))}var o=function(){function r(t,n){var r=(void 0===n?{}:n).validator;if((r=r||function(){return!0})(t))return Object.defineProperty(this,"$$id",{value:i++}),u(this,t),f(this,r),this;var e="Atom initialized with invalid state:\n\n"+c(t)+"\n\naccording to validator function:\n"+r+"\n\n",o=Error(e);throw o.name="AtomInvalidStateError",o}return r.of=function(t,n){return new r(t,n)},r.prototype.toString=function(){return"Atom<"+c(a(this))+">"},r.prototype.inspect=function(){return this.toString()},r}();t.Atom=o,t.deref=function(t){return s(t),a(t)},t.getValidator=function(t){return s(t),d(t)},t.set=function(t,n){s(t);var r=d(t);if(!r(n)){var e="Attempted to set the state of\n\n"+t+"\n\nwith:\n\n"+c(n)+"\n\nbut it did not pass validator:\n"+r+"\n\n",o=Error(e);throw o.name="AtomInvalidStateError",o}u(t,n)},t.setValidator=function(t,n){if(s(t),!n(a(t))){var r=Error("Could not set validator on\n\n"+t+"\n\nbecause current state would be invalid according to new validator:\n"+n+"\n\n");throw r.name="AtomInvalidStateError",r}f(t,n)},t.swap=function(t,n){s(t);var r=n(a(t)),e=d(t);if(!e(r)){var o="swap updateFn\n"+n+"\n\nattempted to swap the state of\n\n"+t+"\n\nwith:\n\n"+c(r)+"\n\nbut it did not pass validator:\n"+e+"\n\n",i=Error(o);throw i.name="AtomInvalidStateError",i}u(t,r)},Object.defineProperty(t,"__esModule",{value:!0})}); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -1,12 +0,5 @@ | ||
import { DeepImmutable } from "./internal-types"; | ||
/** @ignore */ | ||
export declare function getState<S>(atom: Atom<S>): DeepImmutable<S>; | ||
import { AtomConstructorOptions } from "./internal-types"; | ||
/** | ||
* `@libre/atom` provides a data type called `Atom` and a few functions for working with `Atom`s. | ||
* It is heavily inspired by `atom`s in Clojure(Script). | ||
* | ||
* Atoms provide a predictable way to manage state that is shared by multiple components of a | ||
* program as that state changes over time. They are particularly useful in the functional and reactive | ||
* programming paradigms, where most components of a program are pure functions operating on | ||
* immutable data. In this context, Atoms provide a form of mutability that is controlled in such | ||
* A data structure useful for providing a controlled, predictable mechanism for mutability. | ||
* Allows multiple components of a program to share read/write access to some state in such | ||
* a way that no component can mutate another component's current reference to the state in | ||
@@ -32,3 +25,3 @@ * the middle of some process or asynchronous operation. | ||
*/ | ||
static of<S>(state: S): Atom<S>; | ||
static of<S>(state: S, options?: AtomConstructorOptions<S>): Atom<S>; | ||
/** @ignore */ | ||
@@ -43,58 +36,1 @@ readonly ["$$id"]: number; | ||
} | ||
/** | ||
* Reads (i.e. "*dereferences*") the current state of an [[Atom]]. The dereferenced value | ||
* should ___not___ be mutated. | ||
* | ||
* @param <S> 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 declare function deref<S>(atom: Atom<S>): DeepImmutable<S>; | ||
/** | ||
* 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 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 declare function swap<S>(atom: Atom<S>, updateFn: (state: DeepImmutable<S>) => S): void; | ||
/** | ||
* Sets `atom`s state to `nextState`. | ||
* | ||
* It is equivalent to `swap(atom, () => newState)`. | ||
* | ||
* @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, useAtom, set} from '@libre/atom' | ||
import { DeepImmutable } from './internal-types'; | ||
const atom = Atom.of({ count: 0 }) | ||
set(atom, { count: 100 }) | ||
deref(atom) // => { count: 100 } | ||
``` | ||
*/ | ||
export declare function set<S>(atom: Atom<S>, nextState: S): void; |
/** @ignore */ | ||
export declare const derefArgMustBeAtom = "deref only accepts Atom instances, but got:"; | ||
export declare const expectedAtomButGot = "Expected an Atom instances, but got:"; |
@@ -1,2 +0,7 @@ | ||
export { Atom, deref, swap, set } 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"; |
import { Atom } from "./atom"; | ||
/** | ||
* Optional paramaters accepted by [[Atom.of]] | ||
* | ||
* @param <S> the type of the [[Atom]]'s inner state | ||
*/ | ||
export interface AtomConstructorOptions<S> { | ||
/** | ||
* Validates the next state of an [[Atom]] during [[Atom.of]], [[swap]], | ||
* and [[set]]. It should either return a `boolean` or throw an error. If it | ||
* returns `false`, then an Error is thrown and the new state is not committed. | ||
* | ||
* @default `() => true` | ||
*/ | ||
validator?(state: DeepImmutable<S>): boolean; | ||
} | ||
/** | ||
* Extracts the type info of an [[Atom]]'s inner state | ||
@@ -4,0 +19,0 @@ * |
@@ -91,3 +91,3 @@ { | ||
"unpkg": "https://unpkg.com/@libre/atom", | ||
"version": "1.0.2" | ||
"version": "1.1.0" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
49814
20
429