Proxy Observe

Proxserve is a lightweight and very fast javascript library for observing any changes made to an object.
It was initially created for the OH! library but has grown so much that it is now a full-blown library by itself.
With Proxserve you are using plain old javascript objects with the addition of event listeners directly on properties, thus monitoring any changes made to these properties and sub-objects.

Installation

Proxserve runs on both client side and server side (Node.js)

Server side

Run the following command in the terminal

npm install proxserve --save

Then require it:

const Proxserve = require('proxserve');

Client side

Simplest way is to use a CDN
Don't forget to replace "1.x.x" with the desired version (recent version is 1.x.x)

<script src="https://cdn.jsdelivr.net/npm/proxserve@1.x.x/dist/proxserve.min.js"></script> or <script src="https://cdn.jsdelivr.net/npm/proxserve/dist/proxserve.min.js"></script>

If installed on your server, you may serve Proxserve on your own.
The files for distribution are located at "node_modules/proxserve/dist/".
You may choose between an unminified (proxserve.js) and minified (proxserve.min.js) versions

<script src="http://your-domain.com/path-to/proxserve/dist/proxserve.min.js"></script>

Main Concepts

Events

The heart of Proxserve is emitting events for every change made. Proxserve treats the object as a tree and emits the event up the tree. Each event is an object emitted to whoever listens to it, holding a property of a respective path.
Let's say you have an object like this:

const main = new Proxserve({ subObj: { arr: [0, 1, 2] } });

Altering any of the three cells of the array will emit an "update" event to "arr", to "subObj" and to "main".
Now let's say you have attached a listener to all objects and then changed the value of "main.subObj.arr[1]" to 99.
"arr" listener will be called with a "change object" that looks like this:

{ path: '[1]', value: 99, oldValue: 1, type: 'update' }

"subObj" listener will be called with a similar change-object but with a path like this: ".arr[1]"
and "main" listener will be called with a change-object with a path like this: ".subObj.arr[1]"

Event Cycle

Proxserve by default has its own "event loop".
It waits and accumulates changes before it fires the events in order to let you decide what data change do you want to handle and what not. This makes both, Proxserve and your own code, very efficient and fast.
The time of a cycle is controlled via the "delay" option. This cycle can be disabled.
An example for a case like this is Array.splice(). This method causes javascript to generate many many changes (javascript moves cells in the array one by one). Instead of having your listener invoked many times, it will be invoked once with an array of all changes. You will probably want to ignore all of the changes and refer just to the current and final state of the array.

Garbage Collection

Proxserve handles memory very efficiently but also very aggressively.
By default any sub-object that has been detached from its parent object (by delete/splice of it or its parents) will enter the "destroy" cycle.
This cycle runs once every "delay + 1000" milliseconds. This lets all scripts who might still point to the object finish their business.
At the end of the cycle's time all internal references of the sub-object are deleted and then the sub-object (which is a proxy-object) gets revoked - javascript deletes the reference of the proxy-object to the original target object, allowing the garbage collector to collect it.
Caveat: handling objects outside of the proxerve object might lead to pointing to revoked proxies (destroyed objects). This can throw unexpected errors. Consider this example:

const users = new Proxserve([ { name: 'Will Smith', age: 52 }, { name: 'Brad Pitt', age: 56 } ]); let willSmith = users[0]; users.splice(0, 1); console.log(willSmith); //works because the proxy wasn't destroyed yet setTimeout(() => { console.log(willSmith); //throws an error. "willSmith" is now a revoked proxy }, 1500);

Solutions for this situation are at the API page

Basic usage

Note: for those who haven't worked with proxy objects before - Proxserve object actually masks a target object. Altering the proxy object allows it to trap your handlers (set, get, etc.) and do custom operations on the target object that is behind it

Initialization

//verbose way const originalObj = { ... }; const smartObj = new Proxserve(originalObj); //short way const smartObj = new Proxserve({ ... });

Caution: after initiating a Proxserve do not refer to the original object. This will bypass Proxserve and will not emit events

const Proxserve = require('proxserve'); const userData = { name: 'John', last_name: 'Doe', age: 30 }; const user = new Proxserve(userData); userData.age = 40; //bad practice user.age = 40; //good practice

Basic Event Listeners

There are 3 events - "create", "update" & "delete". You may listen to one of them or to all of them with the special "change" event.
Whenever you instantiate a proxserve it has "hidden" methods attached to the object and sub-objects. one of them is "on()" or the alias "$on()"

const pObj = new Proxserve({ subObj: [] }); pObj.on('change', function(changesArray) { console.log(changesArray); }); pObj.subObj.on('create', function(change) { console.log(change); }); pObj.subObj.push(123, 345); //will trigger subObj's listener twice pObj.on('delete', function(change) { console.log(change); }); delete pObj.subObj; //will trigger pObj's "delete" listener once //And now pObj's "change" listener will trigger once, with an array of 3 changes

Advanced usage

Advanced Event Listeners

Adding event listeners to properties not yet created, Listeners adding more listeners and more.
Watch out for race conditions

const Proxserve = require('./dist/proxserve.js'); const game = new Proxserve({ total: 0 }); function updateTotalScore(change) { let oldValue = !change.oldValue ? 0 : change.oldValue; game.total += change.value - oldValue; } game.once('create', '.players', function(change) { //adding a listener to a path not yet existing print_ui_for_players(change); for(let player of game.players) player.on(['create','update'], '.score', updateTotalScore); game.players.on('change', (changes) => { //adding another listener in the middle of the event emitting for(let change of changes) { let pathArr = Proxserve.splitPath(change.path); if(Number.isInteger(pathArr[0])) { if(pathArr[0] >= 4) { //don't listen to any more changes after player number 4 game.players.removeListener('playersListenerID'); return; } //attach a listener to the currently created player this[ pathArr[0] ].on(['create','update'], '.score', updateTotalScore); update_ui_for_players(change); } } }, 'playersListenerID'); }); game.players = [ { name: 'John', age: 30 } ]; setTimeout(() => { game.players.push({ name: 'Jane', age: 20 }, { name: 'Tim', age: 25 }, { name: 'Tanya', age: 35 }); game.players.push({ name: 'Samuel', age: 40 }, { name: 'Samantha', age: 45 }); //5th & 6th players won't be handled }, 100); //simulate some CPU time setTimeout(() => { game.players[0].score = 5; game.players[1].score = 1; game.players[2].score = 4; game.players[3].score = 3; game.players[0].score = 1; }, 200); //simulate some CPU time game.on('change', '.total', function(changes) { console.log(game.total); //will output 9 });