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:- render(ops): called when there are updates from data source.
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:
- implement
ops-in
function to render / update your application. note:- ops has been applied to
data
member before yourops-in
is called. - however any data kept locally should still be updated based on ops.
- ops has been applied to
- watch
open
/close
event or usestate
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, defaultdoc
if omitted.initConnect
: default true. if true, auto connect to sharedb ifid
is given.create()
: empty object creator function. should return an object for initial value.- when omitted, a creator function returing an empty object is used.
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:- omitted (undefined):
connect()
will use storedid
andcollection
to (re)connect. - a string for id of the sharedb doc to connect. In this case,
collection
will be set todoc
. - an object with fields:
id
: sharedb id. use storedid
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 ifid
/collection
is the same.- when
opt
is a string or omitted,force
will also be true.
- when
- omitted (undefined):
- if
id
/collection
are provided inopt
, they will be stored internal for future use.
disconnect()
: disconnect current doc from sharedb. return Promise, resolved when disconnected.- this will be automatically called when internal
sdb-client
is closed.
- this will be automatically called when internal
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 whendisconnect
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).- fields of
o
:get()
: a function returning a full snapshot of data.ops-out(ops)
: a function to handle outward ops.
- a source hub is responsible to call
ops-in(ops)
when there are data changes from source (inward ops)
- fields of
as-des(o)
: set this hub as destination hub ( handle inward events by such as rendering )- fields of
o
:ops-in(ops)
: a function to handle inward ops.
- a destination hub is responsible to call
ops-out(ops)
when there are data changes to source (outward ops)
- fields of
get()
: return a snapshot of source datapipe(hub)
: pipe down events tohub
.addon(ops)
: prependops
to create node for ops accessing non-existed pathcut(hub)
: removehub
from current object's subscriber list.state(s)
: change state. s can be eitheropened
orclosed
.- state propagates automatically. Should only be used by source hub.
- des hub should always call
get
to retrieve a new object and reinitialize if needed after eachopen
event.
as(constructor)
: wrapconstructor
as a hub that can be piped.- constructor options depends on how
constructor
is implemented, with following additional fields:scope
: thescope
used by hub, as defined above.
- constructor options depends on how
MIT