Entanglement Javascript Support
Entanglement has a Javascript interface that allows websocket clients to connect to a Python server and provides two-way synchronization of objects.
The javascript schema generator is used to export the Python schema to Javascript.
This schema is imported into a javascript project typically built with Webpack.
The schema is attached to a
SyncRegistry()Persistence is set up.
filters and relations are used to be notified of synchronized objects.
Javascript Schema Generator
The set of primary keys and attributes associated with Synchronizable classes is exported from the Python code to Javascript.
If a class has no special behavior in Javascript, then Entanglement will automatically generate a Javascript class to represent the Synchronizable. In typical usage, javascript classes are required to customize the behavior of the synchronized object. The automatically generated class can serve as a base class to set up basic properties and primary keys. Unfortunately, because of a bug, the automatically generated base classes will not work for persistent objects unless a custom class extending PersistentSynchronizable is created for each synchronized class.
First, in your Python code, attach a SyncRegistry to a javascript schema.
from entanglement.javascript_schema import javascript_registry
from entanglement import SyncRegistry, Synchronizable
registry = SyncRegistry()
class SomeSync(Synchronizable):
sync_registry = registry
# some code
javascript_registry(registry, "our_registry.js")
- entanglement.javascript_schema.javascript_registry(registry: SyncRegistry, schema: str)
- Parameters
registry – the
SyncRegistrythat should be made available in javascriptschema – The filename to write the javascript schema to.
Then, run the schema generator:
python3 -m entanglement.javascript_schema packages_containaing_registries
For example if project.entanglement.schema contains several sync registries, you might run:
python3 -mentanglement.javascript_schema project.entanglement.schema
That would produce schema for any call to javascript_registry() in the project.entanglement.schema package. Generally, Entanglement Javascript projects also require the sql_meta.js schema produced by entanglement.sql.internal.
Using a Schema
Assume that entanglement schemas are stored in ./entanglement_schemas.. There is one schema called project.js and the sql_meta.js schema.
A project using Entanglement might look something like:
import {SyncManager, SyncRegistry} from "entanglement";
import {SyncOwner, relationship, setupPersistence} from "entanglement/persistence";
import {filter, FilterBase} from "entanglement/filter";
import MetaSchema from "../entanglement_schemas/sql_meta";
import Schema from "../entanglement_schemas/project"
Each schema exports a default function called register_schema:
- register_schema(registry)
- Arguments
registry – The
SyncRegistry()to attach this schema to.
Typical usage is to import the function and call it on a registry:
import schema from "./entanglement_schemas/schema" const registry = new SyncRegistry() schema(registry)
So, going back to our sample project, we would register the two schemas:
registry = new SyncRegistry()
MetaSchema(registry)
Schema(registry)
And then set up persistence:
setupPersistence(registry)
- setupPersistence(registry)
- Arguments
registry – a
SyncRegistry()on which to enable persistence
After the
sql_metaschema is attached to a registry, setupPersistence must be called to attach javascript implementations to classes defined in the schema.
Registering Custom Classes
To define custom behavior, define a Javascript class with the same name as the Python class. Ideally this class extends PersistentSynchronizable():
class SomeSynchronizable extends PersistentSynchronizable {
// Extra methods can go here
}
// and register the class
registry.register(SomeSynchronizable)
If a class is registered that is in a schema attached to the registry, then:
Attributes are made available for all sync attributes.
Primary keys are handled.
Warning
There is support for schema classes that are auto generated rather than calling register explicitly. Also, there is support for using an application specific base hierarchy so classes do not need to extend PersistentSynchronizable. Unfortunately both items are buggy. In particular, if a class does not extend PersistentSynchronizable, then only the methods of Synchronizable will be included. As a result, each time an instance is synchronized, a new instance will be generated. All the filters will fail. Also methods required for updating, creating and deleting objects will not be present.
Filters
Filters provide a way to be notified about changes, additions or removals of PersistentSynchronisables matching certain criteria. For example foreign key relationships are implemented using filters.
- class FilterBase(options)
A FilterBase is a generic categorized filter. It takes a filter function that maps objects into categories. In simple usage, the filter function can return true for objects that the filter wants to track or false/undefined for objects that should be ignored. But for example a foreign key relationship could categorize child objects based on which parent they belong to.
The FilterBase calls an add function for the new category when an object is added to a category, and the remove function for the old category. Internally the category is not used for anything else. For example
filter()implements a single-category filter that stores interesting objects in a list. Its add function adds objects to the list; the remove function removes them from a list.FilterBase’s constructor takes an object containing the following possible options:
- Arguments
target – The
PersistentSynchronizable()that this FilterBase tracks. Event listeners will be added to the target to track new and changed objects.filter –
function filter(obj) {}Returns the category for a given object. Ifundefined/falseis returned, the object is ignored.add –
function add(obj, category) {}A function called when an object is added to a category. Objects are never added to the undefined category; when filter returns undefined, objects are ignored.remove –
function remove(obj, category) { }Called when an object is removed from a category.include_transitions – If true, then the filter tracks Entanglement transition operations.
debug – If true, log debugging state to console
- FilterBase.close()
Shuts down the filter and removes event listeners from the target.
- FilterBase.onObjectChange(obj)
Called whenever an object changes, even if it does not change categories.
- FilterBase.onAdd(obj, category)
Called when an object is added to a category.
- FilterBase.onRemove(obj, category)
Called when an object is removed from a category.
- FilterBase.onChange(obj, old_category, new_category)
Called when an object’s category changes.
- filter(options)
- Returns
A
FilterBase()that stores objects matching the filter function in a list. The returned FilterBase will have a result property which is a list containing the current objects.- Arguments
filter – A filter function returning true for objects that should be tracked. It is important this function always return the same value for objects that are interesting.
target – The
PersistentSynchronizable()that this filter applies to.order –
function(a,b) {}A function that is a sort comparitor. If supplied, whenever the result element is accessed, it will be sorted if needed.
Other options are also passed through to
FilterBase(), but add and remove should not be set.Note
For Vue3 it’s probably desirable that the eventual result list be something that can be passed in as an option so that a reactive list can be used when desired. For vue2 it is sufficient to call $vue.observer on the result, since that mutates the underlying object.
- mapFilter(options)
Like filter except for maps instead of lists. It’s kind of complicated and we’ll document later. The map key is the category; the map value can either be a list or a singleton depending on configuration.
- relationship(local, remote, options)
Sets up a foreign key relationship between two
PersistentSynchronizable()classes. Even supports promises when a child is received before a parent.- Arguments
local – The child side of the relationship (the one with the foreign key constraint in a database)
remote – The
PersistentSynchronizable()class that is the parent side of the relationship.options –
Options to configure the relationship:
- keys
A list of attribute names corresponding to
remote.syncPrimaryKeys. Must be the same length assyncPrimaryKeys.- use_list
If true, then remote_prop is a list; there can be many local objects for each remote object. If false, then this is a one-to-one relationship.
- local_prop
The name of the property on local that will be added to refer to the parent remote object.
- remote_prop
The name of the property to be added to remote. If use_list is true, this contains a list of local objects; otherwise it contains a single local object.
If not specified, the default name is local.name with the first letter downcased; if use_list is true, a
sis added to the name.- missing_node
function(key, local_obj){}A function returning the value of local_prop when no remote is found with key key. This can be used for example to create a proxy UI object when some objects are still being loaded. This could even be used to subscribe to a remote object outside of the current view. Whatever is returned, the loadedPromise property will be set to a promise that will be resolved if a remote with key key is ever synchronized. If this option is not set, local_prop will beundefinedand missing nodes will not be tracked.
Javascript API
- class PersistentSynchronizable()
- PersistentSynchronizable.set syncStorageMap()
Sets the map in which objects of this class are stored. By default classes generate their own, but it may be desirable to have all classes below a certain point in the inheritance tree stored in the same map. If this is done, then foreign key constraints can refer to any class implementing the right interface.
- PersistentSynchronizable.syncModified()
- Returns
A set of attributes modified since the object was received.
- PersistentSynchronizable.syncUpdate(manager)
Send a forward of this object toward its owner using manager. I.E. Save local modifications.
- Returns
A promise that resolves to the result of the forward operation. Assuming
syncConstruct()is working typically, on success, the promise will resolve to this. On failure the promise resolves to some SyncError.
- PersistentSynchronizable.syncCreate(manager)
Creates this object on manager. _sync_owner must already be set.
- Returns
A promise that resolves to the created object. Unlike for updates, that object is not typically this.
- PersistentSynchronizable.syncDelete(manager)
Synchronizes a delete of this to manager.