yarn add @citygro/vdata
if you are building awebpack
orbabel
project you can make@citygro/vdata
smaller (via tree shaking and chunking) by aliasing this package tonode_modules/@citygro/vdata/src/index.js
rather than using our commonjs bundle.
Modules
Constants
- handleChange
- handleKeyChange
- handleArrayChange
- handleArrayKeyChange
- pushToArray
- pushToArrayKey
- removeFromArray
- removeFromArrayKey
Functions
- createDataFlowMixin(valueProp)
create a dataflow mixin for a given value prop.
a 'value' dataflow implements the
v-model
interface.custom dataflows follow a pattern: methods are prefixed with the
valueProp
name andupdate:${valueProp}
is emitted.- add(vm)
register handlers that will run on datastore events
- createMixinForItemById(options) ⇒
object
create a mixin that configures a vm to manipulate a single record. you can use a prop to ask for a record by id or specify a template to create a new record that is pre-populated with some initial state.
// @/queries/UserById.js import {createMixinForItemById} from '@citygro/vdata'
export default { mixins:
}createMixinForItemById({ idPropertyName: 'userId', collectionName: 'users', localPropertyName: 'user', requestOptions: { capture: false } })
a vm which consumes this mixin will have the following props, methods, data, &c. it will also be configured to react to changes to data in the store and update itself accordingly.
{ props: {
}, data: {userid: String, userRequestOptionsOverride: Object
}, methods: {user: Object,
}, computed: {userSave: Function,
} }asyncLoading: Boolean, userLoading: Boolean, userHasChanges: Boolean
@/queries/UserById
defines a query that fetches and captures the initial state for a user record. lets say we have a particular editor that provides read-only access to a particular resource for some users and read/write access for others.for the case where the editor should be read/write we can default some props in the vm to change its behavior depending on the permissions of the current user.
// UserEditor.js import UserById from '@/queries/UserById'
export default { mixins:
, props: {UserById
} // ... }userRequestOptionsOverride: { default () { return { capture: this.$session.hasPermissionToEditUsers() } } }
- createStore(options) ⇒
Store
- nullify(object) ⇒
Object
replace all values in an object with
null
. used to generate the ORSet for diffing operations.- difference(base, object) ⇒
object
- flattenMixinTree(mixins)
- toQueryString(o, prefix)
createMixinForItemById
Store
handleChange
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| diff | object
| handleKeyChange
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| key | string
|
| diff | object
| handleArrayChange
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| i | number
|
| diff | object
| handleArrayKeyChange
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| i | number
|
| key | string
|
| diff | object
| pushToArray
Kind: global constant| Param | Type | | --- | --- | | value |
array
|
| diff | object
| pushToArrayKey
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| key | string
|
| diff | object
| removeFromArray
Kind: global constant| Param | Type | | --- | --- | | value |
array
|
| i | number
| removeFromArrayKey
Kind: global constant| Param | Type | | --- | --- | | value |
object
|
| i | number
|
| key | string
| createDataFlowMixin(valueProp)
create a dataflow mixin for a given value prop.a 'value' dataflow implements the
v-model
interface.custom dataflows follow a pattern: methods are prefixed with the
valueProp
name and update:${valueProp}
is emitted.Kind: global function
| Param | Type | Description | | --- | --- | --- | | valueProp |
string
| bind dataflow to this prop |add(vm)
register handlers that will run on datastore eventsKind: global function
| Param | Type | | --- | --- | | vm |
Vue.Component
| createMixinForItemById(options) ⇒ object
create a mixin that configures a vm to manipulate a single record. you can
use a prop to ask for a record by id or specify a template to create a new
record that is pre-populated with some initial state.// @/queries/UserById.js
import {createMixinForItemById} from '@citygro/vdata'
export default {
mixins: [
createMixinForItemById({
idPropertyName: 'userId',
collectionName: 'users',
localPropertyName: 'user',
requestOptions: {
capture: false
}
})
]
}
a vm which consumes this mixin will have the following props, methods, data, &c. it will also be configured to react to changes to data in the store and update itself accordingly.
{
props: {
userid: String,
userRequestOptionsOverride: Object
},
data: {
user: Object,
},
methods: {
userSave: Function,
},
computed: {
asyncLoading: Boolean,
userLoading: Boolean,
userHasChanges: Boolean
}
}
@/queries/UserById
defines a query that fetches and captures the initial state
for a user record. lets say we have a particular editor that provides read-only
access to a particular resource for some users and read/write access for
others.for the case where the editor should be read/write we can default some props in the vm to change its behavior depending on the permissions of the current user.
// UserEditor.js
import UserById from '@/queries/UserById'
export default {
mixins: [
UserById
],
props: {
userRequestOptionsOverride: {
default () {
return {
capture: this.$session.hasPermissionToEditUsers()
}
}
}
} // ...
}
Kind: global function
Returns:
object
- item-by-id query mixin| Param | Type | Default | Description | | --- | --- | --- | --- | | options |
object
| | |
| options.collectionName | string
| | |
| options.localPropertyName | string
| | the vm data where the result of the query will be stored |
| options.idPropertyName | string
| "id"
| the name of the prop you will use to specify the id of the requested record |
| options.requestOptions | object
| | control some of the behavior of the query |
| options.requestOptions.force | boolean
| false
| always fetch the latest record |
| options.requestOptions.capture | boolean
| false
| capture the initial state of the record, implies force = true
|
| options.template | object
| {}
| the default template for this query |createStore(options) ⇒ Store
Kind: global functionReturns:
Store
- a vdata store instance| Param | Type | Default | Description | | --- | --- | --- | --- | | options |
Object
| | |
| options.models | Object
| | |
| options.basePath | String
| ''
| default prefix for http requests |
| options.adapter | function
| | a custom fetch |
| options.deserialize | function
| | request post-processing |- createStore(options) ⇒
Store
* [~Store](#createStore..Store)
* [.createRecord(collection, [data])](#createStore..Store+createRecord) ⇒ <code>Object</code>
* [.get(collectionName, pkOrId)](#createStore..Store+get) ⇒ <code>Object</code>
* [.getList(collectionName, [keys])](#createStore..Store+getList) ⇒ <code>Array.<object></code>
* [.remove(collectionName, pkOrId, options)](#createStore..Store+remove) ⇒ <code>Object</code>
* [.removeList(collectionName, keys)](#createStore..Store+removeList) ⇒ <code>Array.<object></code>
* [.clear()](#createStore..Store+clear)
* [.rebase(collection, data)](#createStore..Store+rebase) ⇒ <code>Object</code>
* [.add(collection, data, options)](#createStore..Store+add) ⇒ <code>Object</code>
* [.addList(collectionName, data)](#createStore..Store+addList) ⇒ <code>Array.<Object></code>
* [.hasChanges(collectionName, data)](#createStore..Store+hasChanges) ⇒ <code>Boolean</code>
* [.destroy(collectionName, data, options)](#createStore..Store+destroy) ⇒ <code>Promise.<Object></code>
* [.save(collection, data, options)](#createStore..Store+save) ⇒ <code>Promise.<Object></code>
* [.find(collection, [query], [options])](#createStore..Store+find) ⇒ <code>Promise.<Object></code>
* [.findAll(collection, [query], [options])](#createStore..Store+findAll) ⇒ <code>Promise.<Array.<Object>></code>
* [.on(event, handler)](#createStore..Store+on)
* [.off(event, handler)](#createStore..Store+off)
* [.emit(event, payload)](#createStore..Store+emit)
* [.getBasePath(collectionName)](#createStore..Store+getBasePath) ⇒ <code>String</code>
* [.isValidId(id)](#createStore..Store+isValidId) ⇒ <code>Boolean</code>
createStore~Store
Kind: inner class ofcreateStore
* [.createRecord(collection, [data])](#createStore..Store+createRecord) ⇒ <code>Object</code>
* [.get(collectionName, pkOrId)](#createStore..Store+get) ⇒ <code>Object</code>
* [.getList(collectionName, [keys])](#createStore..Store+getList) ⇒ <code>Array.<object></code>
* [.remove(collectionName, pkOrId, options)](#createStore..Store+remove) ⇒ <code>Object</code>
* [.removeList(collectionName, keys)](#createStore..Store+removeList) ⇒ <code>Array.<object></code>
* [.clear()](#createStore..Store+clear)
* [.rebase(collection, data)](#createStore..Store+rebase) ⇒ <code>Object</code>
* [.add(collection, data, options)](#createStore..Store+add) ⇒ <code>Object</code>
* [.addList(collectionName, data)](#createStore..Store+addList) ⇒ <code>Array.<Object></code>
* [.hasChanges(collectionName, data)](#createStore..Store+hasChanges) ⇒ <code>Boolean</code>
* [.destroy(collectionName, data, options)](#createStore..Store+destroy) ⇒ <code>Promise.<Object></code>
* [.save(collection, data, options)](#createStore..Store+save) ⇒ <code>Promise.<Object></code>
* [.find(collection, [query], [options])](#createStore..Store+find) ⇒ <code>Promise.<Object></code>
* [.findAll(collection, [query], [options])](#createStore..Store+findAll) ⇒ <code>Promise.<Array.<Object>></code>
* [.on(event, handler)](#createStore..Store+on)
* [.off(event, handler)](#createStore..Store+off)
* [.emit(event, payload)](#createStore..Store+emit)
* [.getBasePath(collectionName)](#createStore..Store+getBasePath) ⇒ <code>String</code>
* [.isValidId(id)](#createStore..Store+isValidId) ⇒ <code>Boolean</code>
store.createRecord(collection, data) ⇒ Object
tag a javascript object with metadata that allows it to be tracked by the vdata store.
__tmp_id
and the idAttribute
configured for the given collection are both used to
identify the object. editing either of these will cause vdata to see the resulting
object as something new that needs to be tracked separately from the original object.Kind: instance method of
Store
| Param | Type | Default | | --- | --- | --- | | collection |
String
| |
| data | Object
| {}
| store.get(collectionName, pkOrId) ⇒ Object
get a particular object from the store using the primary key provided by
your api server, or the temporary local id that vdata uses internally to
track records.Kind: instance method of
Store
| Param | Type | | --- | --- | | collectionName |
String
|
| pkOrId | String
| store.getList(collectionName, keys) ⇒ Array.<object>
get all of the records in collectionName
. if you include a keys
parameter, this method returns all of the records that match the ids
listed.Kind: instance method of
Store
| Param | Type | | --- | --- | | collectionName |
String
|
| keys | Array.<string>
| store.remove(collectionName, pkOrId, options) ⇒ Object
remove a record from the store, identified by public key or temporary id.Kind: instance method of
Store
Emits:
Store#event:remove
| Param | Type | | --- | --- | | collectionName |
String
|
| pkOrId | String
|
| options | Object
|
| options.quiet | Boolean
| store.removeList(collectionName, keys) ⇒ Array.<object>
remove all of the records in collectionName
or all of the records that match the ids passed into keys
.Kind: instance method of
Store
Emits:
Store#event:remove-list
| Param | Type | | --- | --- | | collectionName |
String
|
| keys | Array.<string>
| store.clear()
remove all records from all collectionsKind: instance method of
Store
Emits:
Store#event:remove-list
store.rebase(collection, data) ⇒ Object
vdata automatically tracks all of the versions that are created for every
record that it tracks. this version tracking is how Store#rebase
is able
to implement a simple Observed-Remove Set (ORSet) that enables vdata to
deterministically merge all of the changes to a particular record.given
data
with a particular __sym_id
and the current version of the
same record at data[idAttribute]
, return a merged record containing all
changes, applied to the base record at __sym_id
in the following order,
diff'd against base
:- current
- data
at CityGro we use the ORSet implementation in vdata to power the real-time features of our customer portal application. in most cases, the core diffing algorithm is able to generate merged outputs with intuitive results. however, it is important to note the rules that we use to resolve certain edge cases.
- Last-write (from the perspective of the writer) wins. in our
experience, this produces the least surprising results for our users.
- Array mutations are all-or-nothing. we currently don't have an
acceptable solution to merging arrays with arbitrary mutations.
following rule #1, we opt to *replace* any previous values with the
latest version of the array. if you have thoughts on this, please open
a ticket on [GitLab](https://gitlab.com/citygro/vdata/issues).
Kind: instance method of Store
| Param | Type | | --- | --- | | collection |
String
|
| data | Object
| store.add(collection, data, options) ⇒ Object
add a record to the store. you do not need to pass your data to
Store#createRecord
before adding it.Kind: instance method of
Store
Emits:
Store#event:add
See: {Store.rebase}
| Param | Type | Default | Description | | --- | --- | --- | --- | | collection |
String
| | |
| data | Object
| | |
| options | Object
| | |
| options.quiet | Boolean
| false
| silence store events for this invocation |store.addList(collectionName, data) ⇒ Array.<Object>
add all of the records in data
to colectionName
in a single operation.Kind: instance method of
Store
Emits:
Store#event:add-list
| Param | Type | | --- | --- | | collectionName |
String
|
| data | Array.<Object>
| store.hasChanges(collectionName, data) ⇒ Boolean
check if data
differs from the current version of the corresponding
record in the store.Kind: instance method of
Store
| Param | Type | | --- | --- | | collectionName |
String
|
| data | Object
| store.destroy(collectionName, data, options) ⇒ Promise.<Object>
send a DELETE
request to the endpoint configured for collectionName
and remove the corresponding record from the store.Kind: instance method of
Store
Emits:
Store#event:remove
| Param | Type | | --- | --- | | collectionName |
String
|
| data | Object
|
| options | Object
| store.save(collection, data, options) ⇒ Promise.<Object>
persist data
using the endpoint configured for collectonName
. if
data
is only identified by a local temporary id send a POST
request to
/:basePath/:collectionName
. if data
has a primary key send a PUT
request to /:basePath/:collectionName/:primaryKey
when updating an existing record, this methods calls Store#rebase. this gives vdata some important super-powers that you can use to build real-time applications. check the method's docs for details.
Kind: instance method of
Store
Emits:
Store#event:add
| Param | Type | | --- | --- | | collection |
String
|
| data | Object
|
| options | Object
| store.find(collection, query, options) ⇒ Promise.<Object>
fetch a particular record from /:basePath/:collectionName/:primaryKey
.
if force === false
immediately return the cached record if present.Kind: instance method of
Store
| Param | Type | Default | | --- | --- | --- | | collection |
String
| |
| query | Object
| |
| options | Object
| |
| options.force | Boolean
| false
| store.findAll(collection, query, options) ⇒ Promise.<Array.<Object>>
fetch all of the records from the api that match the parameters specified
in query
. these are sent along with the request as query parameters.
if force === false
immediately return a cached response if one exists.Kind: instance method of
Store
| Param | Type | | --- | --- | | collection |
String
|
| query | Object
|
| options | Object
| store.on(event, handler)
bind an event listener to the storeKind: instance method of
Store
| Param | Type | | --- | --- | | event |
String
|
| handler | function
| store.off(event, handler)
unbind an event listener to the storeKind: instance method of
Store
| Param | Type | | --- | --- | | event |
String
|
| handler | function
| store.emit(event, payload)
manually emit a message using the store's event busKind: instance method of
Store
| Param | Type | | --- | --- | | event |
String
|
| payload | \*
| store.getBasePath(collectionName) ⇒ String
get the base path for collectionName
Kind: instance method of
Store
| Param | Type | | --- | --- | | collectionName |
String
| store.isValidId(id) ⇒ Boolean
check if the given value is a valid idKind: instance method of
Store
| Param | Type | | --- | --- | | id |
\*
| nullify(object) ⇒ Object
replace all values in an object with null
. used to generate the ORSet for
diffing operations.Kind: global function
| Param | Type | | --- | --- | | object |
Object
| difference(base, object) ⇒ object
Kind: global function| Param | Type | | --- | --- | | base |
object
|
| object | object
| flattenMixinTree(mixins)
Kind: global function| Param | Type | | --- | --- | | mixins |
Array.<Object>
| toQueryString(o, prefix)
Kind: global function| Param | Type | | --- | --- | | o |
object
|
| prefix | string
|