Layer Patch Javascript Utility
For more information about why Layer-Patch, read the Layer-Patch Format spec.
The goal of this utility is to take as input
- Layer Patch Operations Arrays
- An object to modify
Installation
NPM
The recommended approach for installation is npm:
npm install layer-patch
Your initialization code will then look like:
var LayerPatchParser = ;var parser = {};
Github
You can directly download the file layer-patch.js and load that from a script tag. If you load it this way, your initialization code will look like:
var parser = {};
You can download and build the repo itself via:
> git clone git@github.com:layerhq/node-layer-patch.git
> cd node-layer-patch
> npm install
> npm test
Basic Example
var parser = {}; var testObj = "a": "Hello" "b": "There";parser;
The above example transforms testObj
to
Layer Websocket Example
This example shows using this library for receiving operations from the Layer Platform's Websocket API.
It depends upon the getObjectCallback and changeCallbacks documented below.
// Setup an object cache into which we will write new objectsvar objectCache = {}; // This example does not define what an EventManager is, but assumes it has a trigger method.var EventManager = ; // Create the parservar parser = { return objectCacheid } // This callback would typically instantiate a new instance rather than just directly use the value // from the operation { objectCacheid = value; return value; } changeCallbacks: Message: { console; EventManager; } Conversation: { console; EventManager; } ; // Assumes the websocket is already initialized and just needs an onMessage event handlersocket;
Library Properties
The parser takes a number of optional parameters when initializing it. Many of these depend upon
the type
parameter.
Every call to the parse
method has an input of type
:
parser;
This type
value is used as an index into many of the configuration properties shown below.
Note that subproperty names are NOT supported in any of these configurations. For example, if you have a property called "metadata" you can use any of these configuration parameters to affect "metadata", but if an operation were to set "metadata.age", configurations on "metadata" would continue to apply, but you can not add configurations for the "age" subproperty.
getObjectCallback
The getObjectCallback allows the parser to handle operations such as
As the operation is setting by id rather than by value, the parser needs a way to lookup the object identified by "fred". The parser will use the getObjectCallback
method provided to find the object specified by "fred" and use that as the value.
var objectCache = "fred": "firstName": "fred" "lastName": "flinstone" "status": "stoneAged" ; /** * @method * @param * @return */var { return objectCacheid;} var parser = getObjectCallback: getObjectCallback; var testObj = "a": "Hello" "b": "There" "friend": null;parser;
The above operation will result in a final state for testObj:
doesObjectMatchIdCallback
When adding or removing objects to a set, a way of comparing objects is needed. While adding/removing objects is only allowed by passing in an id rather than object, we need a way to compare that id to the objects in the set. The doesObjectMatchIdCallback method will be called on each object in the set and returns true if its a match. If its a match, an add
operation will determine that the object is already present and does not need adding; a remove
operation will remove the matching entry.
Note that if using the Layer Platform Websocket, this method is not required; sets managed by Layer do not contain objects.
/** * @method * @param * @param * @return */ { return objid == id;}
createObjectCallback
The createObjectCallback allows the parser to handle operations such as
As the operation is setting by id rather than by value, the parser needs a way to lookup the object identified by "fred". The parser will use the getObjectCallback
method provided to find the object specified by "fred" and use that as the value. But what happens if "fred" is not found? Either one must do an asynchronous lookup to get the value... or have the value
provided as is done in the above structure. The createObjectCallback
allows you to take that value, create and return an instance or object, and to register the object for future calls to getObjectCallback
.
var objectCache = "wilma": "firstName": "wilma" "lastName": "flinstone" "status": "stoneAged" ; /** * @method * @param * @return */var { return objectCacheid;} /** * @method * @param * @param * @return */var { objectCacheid = obj; return objectCacheid;} var parser = getObjectCallback: getObjectCallback createObjectCallback: createObjectCallback; var testObj = "a": "Hello" "b": "There" "friend": null;parser;
The above operation will result in a final state for testObj:
And a final state for objectCache
:
var objectCache = "wilma": "firstName": "wilma" "lastName": "flinstone" "status": "stoneAged" "fred": "firstName": "fred" "lastName": "flinstone" "status": "stoneAged" ;
camelCase
If true, camelCase says take any uncamel cased property names in the layer-patch operations array, and assume that the local copy uses the camelCased equivalent.
var parser = camelCase: true; var testObj = "isAFriend": true "myEnemy": "fred";parser;
The above operation will result in a final state for testObj:
propertyNameMap
The Property Name Map: Allows us to map a property name received from a
remote client/server to our local object models which may have different property names.
This is similar to the camelCase
property but provides fine grained control.
The map is organized by object type.
var propertyNameMap = "Person": "age": "year_count" "Dog": "breed": "dog_type" ; var parser = "propertyNameMap": propertyNameMap; var testObj = "year_count": 50 "name": "fred";parser;
The above operation will result in a final state for testObj:
changeCallbacks
The Change Event Handler allows side effects and events to be fired based on a change executed by the parser. The changeCallback parameter should be broken down by object type, and each object type can either contain an "all" function or individual functions for each property name.
/** * @param * @param * @param * @param * * Note that the paths array typically contains only a single element. * The only time it contains multiple elements is if subproperties are changed, * in which case it groups all changes to the same property in a single call. */var changeCallbacks = Person: { ; } { ; } Dog: { ; } var parser = changeCallbacks: changeCallbacks; var testPerson = "year_count": 50 "name": "fred" "metadata": "nickname": "Freaky Fred" "last_nickname": "Friendly Fred" ; var testDog = "breed": "poodle" "attitude": "hostile" "preferred_food": "zombie"; parser; parser;
The two parse calls above will result in the following events:
- year_count callback called with (testPerson, 50, 51, ["year_count"])
- metadata callback called with (testPerson, {nickname: "Freaky Fred", last_nickname: "Friendly Fred"}, {nickname: "Freaky Frodo", last_nickname: "Freaky Fred"}, ["metadata.nickname", "metadata.last_nickname"])
- all callback called with (testDog, "zombie", "Frankenstein", ["preferred_food"])
abortCallback
The Abort Event Handler allows an operation to be rejected before its performed. The abortCallback parameter should be broken down by object type, and each object type can either contain an "all" function or individual functions for each property name.
Each function should return true or a truthy value to abort the change; a falsy value will allow the change to procede.
/** * @param * @param * @param * @return */var abortCallbacks = Person: { // System should reject negative years; all else is good. if operation == "set" && value < 0 return true; } Dog: { // Reject changes to any field whose name ends in _at but // whose value doesn't parse to date/time. if operation == "set" && property var d = value; if return true; } ; var parser = abortCallbacks: abortCallbacks; var testPerson = year_count: 50 name: "fred"; var testDog = breed: "poodle" attitude: "hostile" preferred_food: "zombie" ate_zombie_at: "10/10/2010"; parser; parser;
The two parse calls above will result in the following objects:
var testPerson = year_count: 52 name: "fred"; var testDog = breed: "poodle" attitude: "hostile" preferred_food: "Bad Dates" ate_zombie_at: "10/10/2010";
returnIds
When setting values by ID, proper behavior when the object associated with that ID is not well defined by the Layer Patch specification. The default behavior is to set the property to null if the ID is not found. Setting the returnIds property to true will set the property to the string ID if the object is not found.
Testing
To run unit tests use the following command:
npm test
Contributing
Layer Patch Javascript Utility is an Open Source project maintained by Layer, inc. Feedback and contributions are always welcome and the maintainers try to process patches as quickly as possible. Feel free to open up a Pull Request or Issue on Github.
Contact
Layer Web SDK was developed in San Francisco by the Layer team. If you have any technical questions or concerns about this project feel free to reach out to engineers responsible for the development: