Installations
npm install @nx-js/observer-util
Developer
nx-js
Developer Guide
Module System
CommonJS
Min. Node Version
>=6.5.0
Typescript Support
Yes
Node Version
10.7.0
NPM Version
6.2.0
Statistics
1,201 Stars
259 Commits
94 Forks
25 Watching
12 Branches
7 Contributors
Updated on 04 Nov 2024
Languages
JavaScript (99.96%)
HTML (0.04%)
Total Downloads
Cumulative downloads
Total Downloads
1,181,058
Last day
-1.2%
1,279
Compared to previous day
Last week
-1.3%
6,309
Compared to previous week
Last month
3.3%
25,933
Compared to previous month
Last year
41.2%
312,063
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dev Dependencies
29
The Observer Utility
Transparent reactivity with 100% language coverage. Made with :heart: and ES6 Proxies.
Table of Contents
Motivation
Popular frontend frameworks - like Angular, React and Vue - use a reactivity system to automatically update the view when the state changes. This is necessary for creating modern web apps and staying sane at the same time.
The Observer Utililty is a similar reactivity system, with a modern twist. It uses ES6 Proxies to achieve true transparency and a 100% language coverage. Ideally you would like to manage your state with plain JS code and expect the view to update where needed. In practice some reactivity systems require extra syntax - like React's setState
. Others have limits on the language features, which they can react on - like dynamic properties or the delete
keyword. These are small nuisances, but they lead to long hours lost among special docs and related issues.
The Observer Utility aims to eradicate these edge cases. It comes with a tiny learning curve and with a promise that you won't have to dig up hidden docs and issues later. Give it a try, things will just work.
Bindings
This is a framework independent library, which powers the reactivity system behind other state management solutions. These are the currently available bindings.
- React Easy State is a state management solution for React with a minimal learning curve.
- preact-ns-observer provides a simple
@observable
decorator that makes Preact components reactive.
Installation
$ npm install @nx-js/observer-util
Usage
The two building blocks of reactivity are observables and reactions. Observable objects represent the state and reactions are functions, that react to state changes. In case of transparent reactivity, these reactions are called automatically on relevant state changes.
Observables
Observables are transparent Proxies, which can be created with the observable
function. From the outside they behave exactly like plain JS objects.
1import { observable } from '@nx-js/observer-util'; 2 3const counter = observable({ num: 0 }); 4 5// observables behave like plain JS objects 6counter.num = 12;
Reactions
Reactions are functions, which use observables. They can be created with the observe
function and they are automatically executed whenever the observables - used by them - change.
Vanilla JavaScript
1import { observable, observe } from '@nx-js/observer-util'; 2 3const counter = observable({ num: 0 }); 4const countLogger = observe(() => console.log(counter.num)); 5 6// this calls countLogger and logs 1 7counter.num++;
React Component
1import { store, view } from 'react-easy-state'; 2 3// this is an observable store 4const counter = store({ 5 num: 0, 6 up() { 7 this.num++; 8 } 9}); 10 11// this is a reactive component, which re-renders whenever counter.num changes 12const UserComp = view(() => <div onClick={counter.up}>{counter.num}</div>);
Preact Component
1import { observer } from "preact-nx-observer"; 2 3let store = observable({ title: "This is foo's data"}); 4 5@observer // Component will now re-render whenever store.title changes. 6class Foo extends Component { 7 render() { 8 return <h1>{store.title}</h1> 9 } 10}
More examples
Dynamic properties
1import { observable, observe } from '@nx-js/observer-util'; 2 3const profile = observable(); 4observe(() => console.log(profile.name)); 5 6// logs 'Bob' 7profile.name = 'Bob';
Nested properties
1import { observable, observe } from '@nx-js/observer-util'; 2 3const person = observable({ 4 name: { 5 first: 'John', 6 last: 'Smith' 7 }, 8 age: 22 9}); 10 11observe(() => console.log(`${person.name.first} ${person.name.last}`)); 12 13// logs 'Bob Smith' 14person.name.first = 'Bob';
Getter properties
1import { observable, observe } from '@nx-js/observer-util'; 2 3const person = observable({ 4 firstName: 'Bob', 5 lastName: 'Smith', 6 get name() { 7 return `${this.firstName} ${this.lastName}`; 8 } 9}); 10 11observe(() => console.log(person.name)); 12 13// logs 'Ann Smith' 14person.firstName = 'Ann';
Conditionals
1import { observable, observe } from '@nx-js/observer-util'; 2 3const person = observable({ 4 gender: 'male', 5 name: 'Potato' 6}); 7 8observe(() => { 9 if (person.gender === 'male') { 10 console.log(`Mr. ${person.name}`); 11 } else { 12 console.log(`Ms. ${person.name}`); 13 } 14}); 15 16// logs 'Ms. Potato' 17person.gender = 'female';
Arrays
1import { observable, observe } from '@nx-js/observer-util'; 2 3const users = observable([]); 4 5observe(() => console.log(users.join(', '))); 6 7// logs 'Bob' 8users.push('Bob'); 9 10// logs 'Bob, John' 11users.push('John'); 12 13// logs 'Bob' 14users.pop();
ES6 collections
1import { observable, observe } from '@nx-js/observer-util'; 2 3const people = observable(new Map()); 4 5observe(() => { 6 for (let [name, age] of people) { 7 console.log(`${name}, ${age}`); 8 } 9}); 10 11// logs 'Bob, 22' 12people.set('Bob', 22); 13 14// logs 'Bob, 22' and 'John, 35' 15people.set('John', 35);
Inherited properties
1import { observable, observe } from '@nx-js/observer-util'; 2 3const defaultUser = observable({ 4 name: 'Unknown', 5 job: 'developer' 6}); 7const user = observable(Object.create(defaultUser)); 8 9// logs 'Unknown is a developer' 10observe(() => console.log(`${user.name} is a ${user.job}`)); 11 12// logs 'Bob is a developer' 13user.name = 'Bob'; 14 15// logs 'Bob is a stylist' 16user.job = 'stylist'; 17 18// logs 'Unknown is a stylist' 19delete user.name;
Reaction scheduling
Reactions are scheduled to run whenever the relevant observable state changes. The default scheduler runs the reactions synchronously, but custom schedulers can be passed to change this behavior. Schedulers are usually functions which receive the scheduled reaction as argument.
1import { observable, observe } from '@nx-js/observer-util'; 2 3// this scheduler delays reactions by 1 second 4const scheduler = reaction => setTimeout(reaction, 1000); 5 6const person = observable({ name: 'Josh' }); 7observe(() => console.log(person.name), { scheduler }); 8 9// this logs 'Barbie' after a one second delay 10person.name = 'Barbie';
Alternatively schedulers can be objects with an add
and delete
method. Check out the below examples for more.
More examples
React Scheduler
The React scheduler simply calls setState
on relevant observable changes. This delegates the render scheduling to React Fiber. It works roughly like this.
1import { observe } from '@nx-js/observer-util'; 2 3class ReactiveComp extends BaseComp { 4 constructor() { 5 // ... 6 this.render = observe(this.render, { 7 scheduler: () => this.setState() 8 }); 9 } 10}
Batched updates with ES6 Sets
Schedulers can be objects with an add
and delete
method, which schedule and unschedule reactions. ES6 Sets can be used as a scheduler, that automatically removes duplicate reactions.
1import { observable, observe } from '@nx-js/observer-util'; 2 3const reactions = new Set(); 4const person = observable({ name: 'Josh' }); 5observe(() => console.log(person), { scheduler: reactions }); 6 7// this throttles reactions to run with a minimal 1 second interval 8setInterval(() => { 9 reactions.forEach(reaction => reaction()); 10}, 1000); 11 12// these will cause { name: 'Barbie', age: 30 } to be logged once 13person.name = 'Barbie'; 14person.age = 87;
Batched updates with queues
Queues from the Queue Util can be used to implement complex scheduling patterns by combining automatic priority based and manual execution.
1import { observable, observe } from '@nx-js/observer-util'; 2import { Queue, priorities } from '@nx-js/queue-util'; 3 4const scheduler = new Queue(priorities.LOW); 5const person = observable({ name: 'Josh' }); 6observe(() => console.log(person), { scheduler }); 7 8// these will cause { name: 'Barbie', age: 30 } to be logged once 9// when everything is idle and there is free time to do it 10person.name = 'Barbie'; 11person.age = 87;
Queues are automatically scheduling reactions - based on their priority - but they can also be stopped, started and cleared manually at any time. Learn more about them here.
API
Proxy = observable(object)
Creates and returns a proxied observable object, which behaves just like the originally passed object. The original object is not modified.
- If no argument is provided, it returns an empty observable object.
- If an object is passed as argument, it wraps the passed object in an observable.
- If an observable object is passed, it returns the passed observable object.
boolean = isObservable(object)
Returns true if the passed object is an observable, returns false otherwise.
reaction = observe(function, config)
Wraps the passed function with a reaction, which behaves just like the original function. The reaction is automatically scheduled to run whenever an observable - used by it - changes. The original function is not modified.
observe
also accepts an optional config object with the following options.
-
lazy
: A boolean, which controls if the reaction is executed when it is created or not. If it is true, the reaction has to be called once manually to trigger the reactivity process. Defaults to false. -
scheduler
: A function, which is called with the reaction when it is scheduled to run. It can also be an object with anadd
anddelete
method - which schedule and unschedule reactions. The default scheduler runs the reaction synchronously on observable mutations. You can learn more about reaction scheduling in the related docs section. -
debugger
: An optional function. It is called with contextual metadata object on basic operations - like set, get, delete, etc. The metadata object can be used to determine why the operation wired or scheduled the reaction and it always has enough data to reverse the operation. The debugger is always called before the scheduler.
unobserve(reaction)
Unobserves the passed reaction. Unobserved reactions won't be automatically run anymore.
1import { observable, observe, unobserve } from '@nx-js/observer-util'; 2 3const counter = observable({ num: 0 }); 4const logger = observe(() => console.log(counter.num)); 5 6// after this the logger won't be automatically called on counter.num changes 7unobserve(logger);
obj = raw(observable)
Original objects are never modified, but transparently wrapped by observable proxies. raw
can access the original non-reactive object. Modifying and accessing properties on the raw object doesn't trigger reactions.
Using raw
at property access
1import { observable, observe, raw } from '@nx-js/observer-util'; 2 3const person = observable(); 4const logger = observe(() => console.log(person.name)); 5 6// this logs 'Bob' 7person.name = 'Bob'; 8 9// `name` is used from the raw non-reactive object, this won't log anything 10raw(person).name = 'John';
Using raw
at property mutation
1import { observable, observe, raw } from '@nx-js/observer-util'; 2 3const person = observable({ age: 20 }); 4observe(() => console.log(`${person.name}: ${raw(person).age}`)); 5 6// this logs 'Bob: 20' 7person.name = 'Bob'; 8 9// `age` is used from the raw non-reactive object, this won't log anything 10person.age = 33;
Platform support
- Node: 6.5 and above
- Chrome: 49 and above
- Firefox: 38 and above
- Safari: 10 and above
- Edge: 12 and above
- Opera: 36 and above
- IE is not supported
Alternative builds
This library detects if you use ES6 or commonJS modules and serve the right format to you. The exposed bundles are transpiled to ES5 to support common tools - like UglifyJS minifying. If you would like a finer control over the provided build, you can specify them in your imports.
@nx-js/observer-util/dist/es.es6.js
exposes an ES6 build with ES6 modules.@nx-js/observer-util/dist/es.es5.js
exposes an ES5 build with ES6 modules.@nx-js/observer-util/dist/cjs.es6.js
exposes an ES6 build with commonJS modules.@nx-js/observer-util/dist/cjs.es5.js
exposes an ES5 build with commonJS modules.
If you use a bundler, set up an alias for @nx-js/observer-util
to point to your desired build. You can learn how to do it with webpack here and with rollup here.
Contributing
Contributions are always welcomed! Just send a PR for fixes and doc updates and open issues for new features beforehand. Make sure that the tests and the linter pass and that the coverage remains high. Thanks!
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE.md:0
- Info: FSF or OSI recognized license: MIT License: LICENSE.md:0
Reason
Found 5/30 approved changesets -- score normalized to 1
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 5 are checked with a SAST tool
Reason
117 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92
- Warn: Project is vulnerable to: GHSA-6chw-6frg-f759
- Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw
- Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw
- Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25
- Warn: Project is vulnerable to: GHSA-42xw-2xvc-qx8m
- Warn: Project is vulnerable to: GHSA-4w2v-q235-vp99
- Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x
- Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx
- Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r
- Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7
- Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx
- Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-c6rq-rjc2-86v2
- Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq
- Warn: Project is vulnerable to: GHSA-9j49-mfvp-vmhm
- Warn: Project is vulnerable to: GHSA-j4f2-536g-r55m
- Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w
- Warn: Project is vulnerable to: GHSA-qrmc-fj45-qfc2
- Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q
- Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c
- Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc
- Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp
- Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3
- Warn: Project is vulnerable to: MAL-2023-462
- Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5
- Warn: Project is vulnerable to: GHSA-q42p-pg8m-cqh6
- Warn: Project is vulnerable to: GHSA-w457-6q6x-cgp9
- Warn: Project is vulnerable to: GHSA-62gr-4qp9-h98f
- Warn: Project is vulnerable to: GHSA-f52g-6jhx-586p
- Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv
- Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8
- Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65
- Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh
- Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44
- Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988
- Warn: Project is vulnerable to: GHSA-44pw-h2cw-w3vq
- Warn: Project is vulnerable to: GHSA-jp4x-w63m-7wgm
- Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp
- Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj
- Warn: Project is vulnerable to: GHSA-6x33-pw7p-hmpq
- Warn: Project is vulnerable to: GHSA-pc5p-h8pf-mvwp
- Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37
- Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22
- Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp
- Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546
- Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx
- Warn: Project is vulnerable to: GHSA-896r-f27r-55mw
- Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h
- Warn: Project is vulnerable to: GHSA-282f-qqgm-c34q
- Warn: Project is vulnerable to: GHSA-7x7c-qm48-pq9c
- Warn: Project is vulnerable to: GHSA-rc3x-jf5g-xvc5
- Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp
- Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574
- Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm
- Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695
- Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw
- Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9
- Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm
- Warn: Project is vulnerable to: GHSA-82v2-mx6x-wq7q
- Warn: Project is vulnerable to: GHSA-4xcv-9jjx-gfj3
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-hxm2-r34f-qmc5
- Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3
- Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m / GHSA-xvch-5gv4-984h
- Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9
- Warn: Project is vulnerable to: GHSA-4c7m-wxvm-r7gc / GHSA-pch5-whg9-qr2r
- Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g
- Warn: Project is vulnerable to: GHSA-48ww-j4fc-435p
- Warn: Project is vulnerable to: GHSA-hwqf-gcqm-7353
- Warn: Project is vulnerable to: GHSA-9h6g-pr28-7cqp
- Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9
- Warn: Project is vulnerable to: GHSA-g6ww-v8xp-vmwg
- Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp
- Warn: Project is vulnerable to: GHSA-35q2-47q7-3pc3
- Warn: Project is vulnerable to: GHSA-36m4-6v6m-4vpr
- Warn: Project is vulnerable to: GHSA-q22g-8fr4-qpj4
- Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6
- Warn: Project is vulnerable to: GHSA-hjp8-2cm3-cc45
- Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-jv35-xqg7-f92r
- Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp
- Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr
- Warn: Project is vulnerable to: GHSA-4rq4-32rv-6wp6
- Warn: Project is vulnerable to: GHSA-64g7-mvw6-v9qj
- Warn: Project is vulnerable to: GHSA-fxwf-4rqh-v8g3
- Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj
- Warn: Project is vulnerable to: GHSA-xfhh-g9f5-x4m4
- Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq
- Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9
- Warn: Project is vulnerable to: GHSA-j44m-qm6p-hp7m
- Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9
- Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw
- Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc
- Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh
- Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p
- Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36
- Warn: Project is vulnerable to: GHSA-f523-2f5j-gfcg
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
- Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v
- Warn: Project is vulnerable to: GHSA-xc7v-wxcw-j472
- Warn: Project is vulnerable to: GHSA-662x-fhqg-9p8v
- Warn: Project is vulnerable to: GHSA-394c-5j6w-4xmx
- Warn: Project is vulnerable to: GHSA-78cj-fxph-m83p
- Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3
- Warn: Project is vulnerable to: GHSA-cf4h-3jhx-xvhq
- Warn: Project is vulnerable to: GHSA-v2p6-4mp7-3r9v
- Warn: Project is vulnerable to: GHSA-mgfv-m47x-4wqp
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
- Warn: Project is vulnerable to: GHSA-72mh-269x-7mh5
- Warn: Project is vulnerable to: GHSA-h4j5-c7cj-74xg
- Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh
- Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp
Score
2.1
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More