Defining object types and their properties

An Avers object is a plain JavaScript class which is observable by Avers. The only thing you have to do to make your class an Avers object is to define the desired properties.

The constructor should not take any arguments. You can do basic initialization like setting non-Avers properties, but should not do anything more in the constructor.

Currently Avers objects must be JavaScript classes. In the future we'll also make it possible to use POJOs.
function User() {} Avers.definePrimitive(User, 'name', 'John Doe'); Avers.defineObject(User, 'address', Address);

Property types

There are four principal types of properties which can be registered:

You can define the properties with one of the define* functions.

Creating objects

To create objects, you should use the functions provided by Avers. It is not recommended to create instances manually using the JavaScript new operator.

The functions take the constructor and a JSON object describing the properties as arguments, and return a new instance of the type.

var user1 = Avers.parseJSON(User, {}); var user2 = Avers.mk(User, {})

Serializing objects to JSON

Because Avers knows about all the properties, it can automatically serialize objects to JSON. The toJSON function is a complement to parseJSON.

var json = Avers.toJSON(user);

Listening to changes

Whenever one of the defined properties is set to a new value, Avers will generate a change event and propagate it to the root. You can set up callbacks and listen for these changes.

The callback is called with two arguments: the path at which the change happened and a description of the change.

The change operation is a JSON description of what was changed at that path. In the future Avers will be able to undo individual operations.

var user = Avers.mk(User, {}); user.on('change', function(path, op) { console.log('Change at path:', path); }); user.name = 'Fry'; // A message should show up in the log: // Change at path: name
In the near future, Avers will stop polluting the class prototype with its own functions. Then you'll have to use Avers.on(obj, 'change', function(){}) to listen to changes.

Incremental updates to objects

Once you have a change operation, you can apply it to another object. Use applyOperation to do that.

The changes

var user1 = Avers.mk(User, {}) , user2 = Avers.mk(User, {}); user1.on('change', function(path, op) { Avers.applyOperation(user2, path, op); }); user1.name = 'Fry'; // You need to flush the changes so that the callback is invoked. Avers.deliverChangeRecords(); assert(user1.name === user2.name);

API

Defining properties

definePrimitive<T>(obj :: any, name :: string, default :: T) :: ()

This function registers a new primitive property on the given object.

Avers.definePrimitive(User, 'name', 'John Doe');
defineObject<T>(obj :: any, name :: string, ctor :: new<T>(), default :: T) :: ()

Define a object property. ctor is the constructor. The default value is optional. If not provided, the property will not be initialized during migration.

Avers.defineObject(Book, 'author', Author);
defineCollection<T>(obj :: any, name :: string, ctor :: new<T>()) :: ()

Define a collection property. In addition to your own types, you can also use String and Number as constructor.

Avers.defineCollection(Book, 'tags', String);
defineVariant<T>(obj :: any, name :: string, typeField :: string, typeMap :: { string => new<T>() }, default :: T) :: ()

A variant (sum type) field can hold objects of multiple types. The type is a string, stored in the typeField property on the object.

The typeMap defines how types are mapped to constructors. When serializing to JSON, Avers will automatically set the type field according to the property type.

var typeMap = { book: Book, magazine: Magazine }; Avers.defineVariant(Item, 'content', 'type', typeMap);

Creating new objects

parseJSON<T>(ctor :: new<T>(), json: any) :: T

Creates a new instance of the given class and initializes properties from the json object. Any properties which are registered with the class, but absent from the json are left undefined.

var user = Avers.parseJSON(User, {});
mk<T>(ctor :: new<T>(), json: any) :: T

mk is a convenience function which combines parseJSON and migrateObject.

var user = Avers.mk(User, {});
migrateObject(obj :: any) :: ()

Initialize properties which are not set to their default value. If a property does not have a default value, it will be left blank.

var user = Avers.parseJSON(User, {}); Avers.migrateObject(user);
attachChangeListener(obj :: any, fn: ChangeCallback) :: ()

Attach a function which will be called when a property changes. The function will be given two arguments: the path to the property which has changed and the description of the change (Operation).

var user = Avers.mk(User, {}); Avers.attachChangeListener(user, function(path, op) { console.log('Change at', path); });
detachChangeListener(obj :: any, fn: ChangeCallback) :: ()

Detach an change listener from the object. The function must be the same one you used in attachChangeListener.

var user = Avers.mk(User, {}); Avers.attachChangeListener(user, function changeCallback(path, op) { Avers.detachChangeListener(user, changeCallback); console.log('Change at', path); });