access scoped data via piped operational transformation
npm install @plotdb/datahubAccess 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 your ops-in is called.
- however any data kept locally should still be updated based on ops.
- watch 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. optional
- collection: 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.
- when omitted, a creator function returing an empty object is used.
- watch(ops, src): watcher function. optional
- ews: @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 stored id and collection to (re)connect.
- a string for id of the sharedb doc to connect. In this case, collection will be set to doc.
- an object with fields:
- 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.
- when opt is a string or omitted, force will also be true.
- if 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.
- this will be automatically called when internal 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).
- 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)
- 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)
- get(): return a snapshot of source data
- pipe(hub): pipe down events to hub.
- addon(ops): prepend ops to create node for ops accessing non-existed path
- cut(hub): remove hub from current object's subscriber list.
- state(s): change state. s can be either opened or closed.
- 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 each open event.
- as(constructor): wrap constructor as a hub that can be piped.
- constructor options depends on how constructor is implemented, with following additional fields:
- scope: the scope used by hub, as defined above.
Tabhub is a local-based hub that provides data sharing mechanism across browser tabs within a computer.
Constructor options include all datahub's options, plus:
- channelName: optional channel name. default default-channel
- initialData: initial data. will be ignored if live data is available in broadcast channel.
APIs: include all datahub's APIs, plus:
- connect(): connect to local tab group by the given channel name.
Sample usage:
hub = new tabhub({channelName: 'tabhub-test', initialData: content: "some simple text"})
<- hub.connect!then _
hub.as-des ops-in: -> / ... call renderer to render content somewhere ... /
something.on 'update', -> hub.ops-out json0.diff ... / try diff a ops to send /
For demo check web/static/tabhub/.
MIT