Synchronized entity store
Homepage Repository npm JavaScript Download
npm install synchronized-entity-store@1.0.0
Synchronized key-value stores with RPCs and pub/sub events. Works over sockets (try it with Sockhop!)
Basic use:
let e=new Entangld();
// Simple set/get
e.set("number.six",6);
e.get("number.six").then((val)=>{}); // val==6
// Functions as values
e._deref_mode=true;
e.set("number.seven",()=>{ return 7;});
e.get("number").then((val)=>{}); // val => { six:6, seven, 7}
// Promises from functions
e.set("number.eight",()=>{ return new Promise((resolve)=>{ resolve(8); }); });
e.get("number.eight").then((val)=>{}); // val==8
// Even dereference beneath functions
e.set("eenie.meenie",()=>{ return {"miney": "moe"}; });
e.get("eenie.meenie.miney").then((val)=>{}); // val=="moe"
Pairing two data stores together:
let parent=new Entangld();
let child=new Entangld();
// Attach child namespace
s.attach("child",child);
// Configure communications
parent.transmit((msg, store)=>store.receive(msg)); // store === child in this example
child.transmit((msg)=>parent.receive(msg, child));
// Set something in the child...
child.set("system.voltage",33);
// Get it back in the parent
parent.get("child.system.voltage"); // == 33
Using getter functions as RPC:
// Assign a function to a child key
child.set("double.me",(param=0)=>param*2); // Or we could return a Promise instead of a value, if we wanted to!
// Call the RPC from the parent
parent.get("child.double.me", 2).then((val)=>{
// val == 4
});
Note in this example how we set a default value for this getter function (0). This is because when _deref_mode is true
this getter will be called without any arguments.
Pub/sub (remote events):
// Assign an event callback
parent.subscribe("child.system.voltage",(path, val)=>{
// path=="child.system.voltage"
// val==21
});
// Trigger an event
child.set("system.voltage",21);
Over sockets:
const Sockhop=require("sockhop");
const Entangld=require("entangld");
var parent=new Entangld();
/**
* Parent / server setup
*/
let parent=new Entangld();
let server=new Sockhop.server();
// Connect server to parent store
parent.transmit((msg, store)=>server.send(store, msg));
server
.on("receive",(data, meta)=>parent.receive(data, meta.sock)) // Use the socket as the data store handle
.on('connect',(sock)=>{
parent.attach("client", sock); // "client" works for one client. Normally use uuid() or something
parent.get("client.my.name")
.then((val)=>{
console.log("Client's name is "+val);
server.close();
});
})
.on('disconnect', (sock)=>parent.detach(null, sock))
.on('error', (e)=>console.log("Sockhop error: "+e))
.listen();
/**
* Child / client setup
*/
let child=new Entangld();
let client=new Sockhop.client();
// Connect client to child store
child.transmit((msg)=>client.send(msg));
client
.on("receive", (data, meta)=>child.receive(data))
.on("connect", ()=>{
// attach() to parent is optional, if we plan to get() parent items
})
.on("error", (e)=>console.log("Sockhop error: "+e))
.connect();
child.set("my.name", "Entangld");
Any object can store values. And a Map can store values keyed to objects. But what if you want to....
parent.attach("child.goes.here", child);
child.set("",{"child" : "data" });
parent.set("",{"parent" : "data"});
parent.attach("child", child);
parent.get(""); // Returns { "parent" : "data", "child" : {} }
parent.get("child"); // Returns {"child" : "data"}
This is because we would have to perform recursive child queries to show you a complete tree. This is left for a future version.
.set()
a function, that function may return a value or a Promise. If it returns a promise, that promise will be returned directly to you when you call .get()
.subscribe("a.b.c", callback)
we should use .on("path.a.b.c", callback)
If you attach a key to a getter function instead of a value, that function would never be called until you request that key directly (i.e. querying the parent of that key would not reveal that that key exists). This changed in 1.2.1, when _deref_mode was introduced. If you set _deref_mode to true, it will iterate all leaves and try to call all functions. Those that return Promise will have their Promise resolved before the result is actually returned.
This is pretty cool, and after consideration it is probably the way this thing should work all the time. However it also introduces two problems which are not yet resolved (//TODO):
First, in an effort to not accidentally mutate the original data set, a copy is made. This is somewhat inefficient. Second, when the copy is made, JSON.parse/JSON.stringify are used. This means that leaves consisting of Map() or the like are just erased.
If these two issues can be resolved at some point, _deref_mode will probably be turned on permanently. Honestly, for remote stores operating over sockets it's probably not a huge issue. More to the point are local stores where the user might be storing non JSON-compatible items.
Message class for Entangld
EventEmitter
Synchronized Event Store
Promise
Deep copy an object
Uses JSON parse methods so things that don't work in JSON will disappear, with the special exception of functions which
are replaced by their return values (or if the function returns a promise, the value that it resolves to).
If you pass undefined, it will return a promise resolving to undefined.
Message class for Entangld
EventEmitter
Synchronized Event Store
Kind: global class
Extends: EventEmitter
EventEmitter
array
string
Promise
boolean
array
Get namespaces
Kind: instance property of Entangld
Returns: array
- namespaces an array of attached namespaces
Read only: true
string
Get namespace for a store
Kind: instance method of Entangld
Returns: string
- namespace for the given store
Read only: true
Attach a namespace and a store
Kind: instance method of Entangld
Throws:
Error
Error will be thrown if you try to attach a namespace twiceParam | Type | Description |
---|---|---|
namespace | string |
a namespace for this store |
store | object |
an object that will be sent along with "transmit" callbacks when we need something from this store |
Detach a namespace / store pair
If you only pass a namespace or a store, it will find the missing item before detaching
Kind: instance method of Entangld
Throws:
Error
Error will be thrown if you don't pass at least one parameterParam | Type | Description |
---|---|---|
[namespace] | string |
the namespace |
[store] | object |
the store |
Transmit
Specify a callback to be used so we can transmit data to another store Callback will be passed (msg, store) where msg is an object and store is the Entangld store that should receive() it
Kind: instance method of Entangld
Param | Type | Description |
---|---|---|
f | function |
the callback function |
Receive
Call this function with the data that was sent via the transmit() callback
Kind: instance method of Entangld
Param | Type | Description |
---|---|---|
msg | object |
the message that was given to the callback |
[store] | object |
the Entangld store that sent the message |
Push an object into an array in the store
Convenience method for set(path, o, "push")
Kind: instance method of Entangld
Throws:
Error
Param | Type | Description |
---|---|---|
path | string |
the path to set (like "system.fan.voltage") |
object | object |
the object or function you want to store at that path |
Set an object into the store
Kind: instance method of Entangld
Throws:
Error
Param | Type | Default | Description |
---|---|---|---|
path | string |
the path to set (like "system.fan.voltage") | |
object | object |
the object or function you want to store at that path | |
[operation_type] | string |
""set"" |
whether to set or push the new data (push only works if the data item exists and is an array) |
Promise
Get an object from the store
Note: using max_depth, especially large max_depth, involves a lot of recursion and may be expensive
Kind: instance method of Entangld
Returns: Promise
- promise resolving to the object living at that path
Throws:
Errror
throws errorParam | Type | Description |
---|---|---|
path | string |
the path to query (like "system.voltage") |
[params | max_depth] |
Subscribe to change events for a path
If objects at or below this path change, you will get a callback
Kind: instance method of Entangld
Throws:
Error
error thrown on empty pathParam | Type | Description |
---|---|---|
path | string |
the path to watch |
f | function |
the callback - will be of the form (path, value) |
boolean
Check for subscription
Are we subscribed to a particular remote path?
Kind: instance method of Entangld
Param | Type | Description |
---|---|---|
subscription | string |
the subscription to check for |
Unubscribe to change events for a remote path
Note that this will unsubscribe from all paths that might cause events to fire for it (all paths above). For example, unsubscribe("a.cars.red.doors") will remove previous subscriptions to "a.cars.red" and "a.cars".
Kind: instance method of Entangld
Throws:
Error
Param | Type | Description |
---|---|---|
path | string |
the path to watch |
Promise
Deep copy an object
Uses JSON parse methods so things that don't work in JSON will disappear, with the special exception of functions which
are replaced by their return values (or if the function returns a promise, the value that it resolves to).
If you pass undefined, it will return a promise resolving to undefined.
Kind: global function
Returns: Promise
- result a promise resolving to a completely new, de-referenced object only containing objects and values
Param | Type | Description |
---|---|---|
o | object |
the object to copy. |
MIT