Proxserve Class

Constructor

Construct a new proxserve instance.
Parameters:

Name Type Default value Description
target Object|Array The target for proxserve to observe
options Object {...} See the constructor's options section
options.delay Number 10
options.strict Boolean true
options.emitReference Boolean false

Returns: Object
Example:

const myTarget = { some: 'property' }; const options = { delay: 10, strict: true, emitReference: false }; const pObj = new Proxserve(myTarget, options);

Constructor's Options

Proxserve has 3 options. Each is very important and affects the core of the library.

Name Type Default value
delay Number 10

The delay option controls the delay of the event cycles. Whenever a change is made to the object it triggers a cycle that accumulates all changes for several milliseconds and then fires them all together. This behavior has several benefits which you can read about at the concepts section. The delay makes listening and responding to changes asynchronous
This event cycle can be disabled and turned synchronous by setting the delay to 0 (or lower, which will affect the automatic destroy for garbage collection) if you need to respond to changes immediately (at the same event loop cycle if speaking in Nodejs terms).
Example:

const objOne = new Proxserve({ score: 0 }, { delay: 25 }); objOne.on('update', function(change) { let oldValue = change.oldValue, wasChangedTo = change.value, currentValue = this.score; console.log(oldValue, wasChangedTo, currentValue); }); objOne.score++; //triggers the change-event loop cycle objOne.score++; //adds an event objOne.score++; //adds another event //after 25ms the events are fired and now the console will log "0 1 3", "1 2 3", "2 3 3" const objTwo = new Proxserve({ score: 0 }, { delay: 0 }); objTwo.on('update', function(change) { let oldValue = change.oldValue, wasChangedTo = change.value, currentValue = this.score; console.log(oldValue, wasChangedTo, currentValue); }); objTwo.score++; //immediately logs "0 1 1" objTwo.score++; //immediately logs "1 2 2" objTwo.score++; //immediately logs "2 3 3"
Name Type Default value
strict Boolean true

The strict option determines whether Proxserve will destroy deleted/detached sub-objects automatically. Whenever a property gets deleted, Proxserve will revoke the proxy-object of this property and will destroy all internal metadata used by the library. This ensures the garbage collector will collect the object (assuming there are no other references to this object). Objects will keep on living for delay + 1000ms before destroyed in order to let all other code that might still use it finish without errors. Another benefit of the delay is that this cpu intensive operation will run at another time, making the overall performance feel smoother.
Disabling the strict mode will not revoke the deleted proxy-objects and will not delete its metadata. Deleting can still lead to zero references to the object and thus still being garbage collected but because of circular references in the metadata there are chances of objects and child objects not being garbage collected and leading to unexpected behaviour like memory overflow. In cases like this you can destroy objects manually.
Example:

const target = { sample: [1,2] }; const obj = new Proxserve(target, { strict: true }); //'obj.sample' is a proxy-object for 'target.sample' const someRef = obj.sample; //'someRef' is a reference to the same proxy-object as 'obj.sample' obj.sampleTwo = obj.sample; //'obj.sampleTwo' is a new (different) proxy-object for 'target.sample' delete obj.sample; //the property is deleted and the proxy-object it used to refer to will be destroyed setTimeout(() => { console.log(obj.sampleTwo); //logs 'a' console.log(obj.sample); //logs 'undefined' try { console.log(someRef); } //throws an error because it can't access a revoked proxy! catch(err) { console.log(err); } }, 1500); //waiting for the destroy cycle
Name Type Default value
emitReference Boolean false

This option is a tricky one. The emitReference option controls whether a reference is emitted along with the change-event object. Emitting a reference is much faster (because it doesn't require a deep copy) but will lead to very confusing results. For example the change.value may reference to an object that was changed several times by the time your listener is invoked, meaning you'll receive a different change.value than you expected. Although by default this option is off, it is recommended to turn it on in cases where you don't care about change.oldValue, change.value or in cases you simply monitor primitives only.
Example:

const objOne = new Proxserve({ users: [] }, { emitReference: true }); objOne.users.on('create', function(change) { console.log(change.value); //logs "{ name: 'Sam' }" even though on creation it was 'John' }); objOne.users.push({name: 'John'}); //prepares an event with a reference to the new user objOne.users[0].name = 'Sam'; //updates the new user //...fires all events const objTwo = new Proxserve({ users: [] }, { emitReference: false }); objTwo.users.on('create', function(change) { console.log(change.value); //logs "{ name: 'John' }" }); objTwo.users.push({name: 'John'}); //prepares an event with a copy of the new user objTwo.users[0].name = 'Sam'; //updates the new user //...fires all events

Caution: when emittin a reference, the change.value will refer to the proxy-object but the change.oldValue will refer to the original target object (the one behind the proxy) because by the time the listener is called, the older proxy-object might have been destroyed (revoked) and refering to it will throw an error.

Destroy

Destroys (revokes) a proxserve object or sub-object along with all of its children and internal references.
It does not delete the property off the parent object for you. If you will not delete a destroyed property it will refer to the original target object.
Parameters:

Name Type Description
proxy Proxy-Object The proxserve's main object or sub-object

Example:

const target = { lists: { fruits: ['apple', 'cherry'], vegetables: ['cucumber', 'carrot'] } }; const pObj = new Proxserve(target, { strict: false }); const fruits = pObj.lists.fruits; //reference to the 'fruits' proxy-object Proxserve.destroy(pObj.lists.fruits); //revokes proxy and children console.log(pObj.lists.fruits); //logs the original target object (target.lists.fruits) try { console.log(fruits); } //throws an error! 'fruits' proxy-object was revoked catch(err) { console.error(err); } delete pObj.lists.fruits; //property is deleted and now the process is complete

splitPath

Key component of the whole events system is the path of each change. In order to analyze this string you will want to break it to an array of object keys and array indexes. The splitPath method does that very efficiently.
Parameters:

Name Type Description
path String The path of the emitted change-event

Returns: Array
Example:

const pObj = new Proxserve({ users: { admins: ['John', 'Sean'] } }); pObj.on('update', function(change) { console.log(change.path); //function calls will log ".users.admins[0]" and then ".users.admins[1]" let splitted = Proxserve.splitPath(change.path); console.log(splitted); //function calls will log [ 'users', 'admins', 0 ] and then [ 'users', 'admins', 1 ] }); pObj.users.on('change', function(changes) { for(let change of changes) { console.log(change.path); //iterations will log ".admins[0]" and then ".admins[1]" let splitted = Proxserve.splitPath(change.path); console.log(splitted); //iterations will log [ 'admins', 0 ] and then [ 'admins', 1 ] } }); pObj.users.admins[0] = 'Jenny'; pObj.users.admins[1] = 'Sam';

evalPath

Evaluates a path according to an object. Returns the evaluated sub-object, property and the actual value.
Parameters:

Name Type Description
obj Object The object to evaluate the path for
path String A path of an emitted change-event

Returns: Object - {object, property, value}
Example:

const school = new Proxserve({ name: 'Philly High', classes: [ { teacher: 'John', students: ['Mary', 'Jane'] }, { teacher: 'Bob', students: ['David', 'Lisa'] } ] }, { delay: 0 }); school.once('update', function(change) { //"this" = "school" //"change.path" = ".classes[1].teacher" let ev = Proxserve.evalPath(this, change.path); //evaluated to: { object: "school.classes[1]", property: "teacher", value: "Betty" } console.log(ev.object[ ev.property ] === ev.value); }); school.classes[1].teacher = 'Betty'; school.classes.once('update', function(change) { //"this" = "school.classes" //"change.path" = "[0].students[1]" let ev = Proxserve.evalPath(this, change.path); //evaluated to: { object: "school.classes[0].students", property: "1", value: "Tom" } console.log(ev.object[ ev.property ] === ev.value); }); school.classes[0].students[1] = 'Tom';

Object Methods

Every proxserve object and sub-objects have special methods attached to them. You may overwrite the methods property names if needed and use the alias instead.

on (alias: $on)

Attach an event listener to the object, listening to any changes made to it or to its sub-objects.
Accepted values: "create", "update", "delete" and special value "change".
Parameters:

Name Type Optional Description
events String|Array The event or events to listen for
path String Yes Path selector. Useful for objects not yet created
listener Function The listener function to invoke
id Number|String Yes An identifier for removeListener

Hint: use the "path" parameter in order to attach listeners to primitives or to objects not yet created.
Examples:

const pObj = new Proxserve({ person: 'Paul' }); pObj.on('delete', function(change) { ... }); //listen to one event type pObj.on(['create','update'], function(change) { ... }); //listen to several event types pObj.on('create', '.arr[0]', function(change) { ... }); //listen on a property not yet created pObj.arr = ['Ronald']; pObj.arr.on('change', function(changes) { //listen to all events as an ordered array for(let change of changes) { ... } }); pObj.arr.push('Emily','Frank','Anna');

once (alias: $once)

Attach an event listener that runs only once.
Parameters: same as on

Example:

const pObj = new Proxserve({ person: 'Amanda' }); pObj.once('update', function(change) { console.log(change); }); pObj.person = 'Gary'; //logs Gary's change pObj.person = 'Eric'; //does nothing

removeListener (alias: $removeListener)

removes a listener from a path by an identifier or by the listener's function.
Parameters:

Name Type Optional Description
path String yes Path to a sub-property
id Number|String|Function The identifier or function of the listener

Example:

const pObj = new Proxserve({ person: { name: 'Brandon' } }, { delay: 0 }); function myListener(change) { console.log(change.value); } pObj.on('update', myListener); pObj.person.name = 'Emma'; //logs "Emma" pObj.removeListener(myListener); pObj.person.name = 'Jack'; //nothing happens pObj.person.on('update', myListener, 'id123'); pObj.person.name = 'Rachel'; //logs "Rachel" pObj.removeListener('.person', 'id123'); pObj.person.name = 'Dennis'; //nothing happens

removeAllListeners (alias: $removeAllListeners)

removes all listener from a path.
Parameters:

Name Type Optional Description
path String yes Path to a sub-property

Example:

const pObj = new Proxserve({ arr: [], num: 2 }, { delay: 0 }); pObj.arr.on('create', function(change) { ... }); pObj.on('create', '.arr', function(change) { ... }); pObj.arr.removeAllListeners(); pObj.on('update', '.num', function(change) { ... }); pObj.on('change', '.num', function(changes) { ... }); pObj.removeAllListeners('.num');

stop

Stops the object and children from emitting change events.
Note: calling "stop" overrides "block" status
Example:

const pObj = new Proxserve({ sub: { sub2: 'abc' } }, { delay: 10 }); pObj.on('change', function(changes) { console.log(changes); }); pObj.sub.sub2 = 'def'; //will get in queue and log the change in 10ms pObj.sub.stop(); pObj.sub.sub2 = 'ghi'; //will not emit any event

block

Blocks the object and children from changes. You can't alter or delete any property.
Note: calling "block" overrides "stop" status
Example:

const pObj = new Proxserve({ sub: { sub2: 123 } }); pObj.sub.sub2 = 456; //"sub2" is now 456 pObj.sub.block(); pObj.sub.sub2 = 789; //"sub2" is still 456 and an error is logged to the console

activate

Resume default behavior of mutability and emitting change events, Meaning the state is inherited from parent (unless "force" parameter is used).
Parameters:

Name Type Default value Description
force Boolean false Force being active regardless of parent's state

Example:

const granddad = new Proxserve({ dad: { child: { grandson: 'Henry', granddaughter: 'Julie' } } }); granddad.block(); granddad.dad.child.grandson = 'Adam'; //fail granddad.activate(); granddad.dad.child.grandson = 'Douglas'; //success granddad.stop(); granddad.on('change', function(changes) { console.log('root object is listening'); }); granddad.dad.child.on('change', function(changes) { console.log('child object is listening'); }); granddad.dad.child.activate(true); granddad.dad.child.granddaughter = 'Victoria'; //root object won't log but child object will

getOriginalTarget

Special built-in method to get the target object behind the proxserve object.
Hint: also useful for checking if an object is a proxserve object
Example:

const target = { players: [{ name: 'Peter', score: 50 }] }; const game = new Proxserve(target); console.log(game.players[0] === target.players[0]); //false console.log(game.players[0].getOriginalTarget() === target.players[0]); //true

Caution: altering the target object behind the proxserve will not emit events