Access scoped data via piped operational transformation.
Access scoped data via piped operational transformation.
npm install @plotdb/datahub
after including datahub.bundle.min.js
:
# this is our data source.
src = new datahub.src do
ops-out: (ops) -> # update data src by incoming ops
get: -> # return complete data. raw data is returned, users should make their own copy if to use.
# this is our view controller
des = new datahub.des do
ops-in (ops): -> # update ui / widget
# data through pipe, scoped under "my-view"
view-hub = new datahub {scope: ["my-view"]}
# ... from srchub / through view-hub / to deshub
src.pipe view-hub .pipe des
# notify all users (deshub) about data changed
src.ops-in [... /* ops */ ...]
# update data source (srchub)
des.ops-out [... /* ops */ ...]
Datahub also provides auxiliary hubs for quick testing and evaluation.
memhub
- data source hub. store data in memory.usrhub
- simple destination hub. constructor options:A sample usage of memhub
and usrhub
as follows:
mhub = new datahub.mem!
uhub = new datahub.usr {render: -> console.log('ok'); }
mhub.pipe uhub
document.querySelector('textarea').addEventListener \input, ->
uhub.ops-out [
{ p: ['str',0], si: @value[@value.length - 1] }
]
Instead of userhub
, you can use datahub.as
to wrap your constructor to support hub mechanism directly with your own class:
someClass = datahub.as (opt = {}) -> @
someClass.prototype <<< { ... }
You can pipe source hub to the constructed hub object:
hub = new myHub();
src.pipe(hub);
Wrapped hub only provides following methods:
state()
on(name, cb)
fire(name, ...args)
ops-out(ops)
get()
And following member variables to use:
hub
: the actual hub object used to accept source data.data
: data object from source.As a wrapped hub user, you should do the following:
ops-in
function to render / update your application. note:data
member before your ops-in
is called.open
/ close
event or use state
method to track data stream status.Following is a sample wrapped hub:
form = datahub.as (o = {}) ->
# udate based on a input element change
o.node.addEventListener \input, (evt = {}) ~> @update evt.target.value
# keep a local _data to diff original data for ops
@on \open, ~> @_data = JSON.parse(JSON.stringify(@data)); @render!
@ <<< o{name}
form.prototype <<<
# re-render on every ops-in
ops-in: (ops) ->
json0.type.apply @_data, ops
@render!
render: -> console.log @_data
update: (v) ->
if @state! == \closed => return
@_data[@name] = v
@ops-out json0.diff(@data, @_data)
For a live, working example, please check web/src/pug/as.pug
.
Sharehub provides a simple interface and implementation reference for adopting ShareDB with data hub to keep edited data in database:
uhub = new datahub.usr!
shub = new sharehub({
id: 'my-sharedb-doc-id'
create: -> {} # init obj if doc not found.
})
shub.init!
.then -> shub.pipe uhub
Sharehub is in a standalone JS file. include sharehub.js
/ sharehub.min.js
/ sharehub.bundle.min.js
if you want to use it.
Constructor options:
id
: id of the sharedb doc to connect. optionalcollection
: collection of the sharedb collection to connect. optional, default doc
if omitted.initConnect
: default true. if true, auto connect to sharedb if id
is given.create()
: empty object creator function. should return an object for initial value.watch(ops, src)
: watcher function. optionalews
: @plotdb/ews
instance to use. required.APIs:
connect(opt)
: connect to sharedb. return Promise, resolved when connected.opt
is either:connect()
will use stored id
and collection
to (re)connect.collection
will be set to doc
.id
: sharedb id. use stored id
if omitted. this id will be stored for future use. (e.g., auto-reconnect)collection
: doc
if omitted.force
: default true. when true, force to reconnect even if id
/ collection
is the same.opt
is a string or omitted, force
will also be true.id
/ collection
are provided in opt
, they will be stored internal for future use.disconnect()
: disconnect current doc from sharedb. return Promise, resolved when disconnected.sdb-client
is closed.config(opt)
: update configuration. opt has following fields:id
: sharedb id.collection
: doc
if omitted.Additionally, sharehub
fire following events
open
: fired when a connect is successfully done.close
: fired when disconnect
is called.error
: fired when internal sdb-client object fires error events.Passing scope
option ( an array of strings / numbers ) into datahub constructor to filter incoming op and data based on the specified scope. For example, assume our data source keeps data in datasrc
variable, then following hub:
new hub({scope: ["users", "deleted"]})
will only pass ops that affect object in datasrc.users.deleted
. Furthermore, data get from this hub will only be the subtree in datasrc.users.deleted
.
You can pipe data source to a hub that is scoped, and pipe this scoped hub to the destination hubs that use the same subtree.
ops-in(ops)
: send ops from this hub to subscribers. ( inward / down to client )ops-out(ops)
: send ops from this hub to src. ( outward / up to server ) as-src(o)
: set this hub as a source hub (provide data and handle outward request).o
:get()
: a function returning a full snapshot of data.ops-out(ops)
: a function to handle outward ops.ops-in(ops)
when there are data changes from source (inward ops)as-des(o)
: set this hub as destination hub ( handle inward events by such as rendering )o
:ops-in(ops)
: a function to handle inward ops.ops-out(ops)
when there are data changes to source (outward ops)get()
: return a snapshot of source datapipe(hub)
: pipe down events to hub
.addon(ops)
: prepend ops
to create node for ops accessing non-existed pathcut(hub)
: remove hub
from current object’s subscriber list.state(s)
: change state. s can be either opened
or closed
.get
to retrieve a new object and reinitialize if needed after each open
event.as(constructor)
: wrap constructor
as a hub that can be piped.constructor
is implemented, with following additional fields:scope
: the scope
used by hub, as defined above.MIT