nativescript-couchbase-vuex-orm
by Jay Edwards | v1.3.2
a plugin for vue nativescript to generate vuex ORM-style actions with Couchbase at runtime.
npm i --save nativescript-couchbase-vuex-orm

Nativescript Couchbase Vuex ORM

nativescript-couchbase-vuex-orm

A module that auto-generates conventional ORM-style modular vuex actions using the nativescript-couchbase-plugin. Ideally suited for Vue NativeScript projects. e.g.


Example

given a couple of "Model" classes...

export class Document {
constructor () {
this.hasMany(['ChildDocument']),
this.properties({
name: 'Default Document Name'
})
}
}

export class ChildDocument {
constructor () {
this.belongsTo = 'Document'
}
}

...and a bit of store setup (see further below), the following actions will automatically become available to dispatch on your vue template!

import { mapGetters, mapActions } from 'vuex'
...
export default {
methods () {
...mapActions[
'Document/getById',
'Document/create',
'Document/update',
'Document/addChildDocument',
'Document/removeChildDocument',
'Document/reorderChildDocument',
'Document/delete',
'ChildDocument/create',
'ChildDocument/update'
/* etc. */]
}
}

Thus, after a few dispatches, the data in your Couchbase-lite database would look something like the following:

`select * from Document` [{
_type: 'Document',
childDocuments: [
'6f0a1418-ea43-4cd2-a652-5202f4f251fb',
'6f0a1418-ea43-4cd2-a652-5202f4f251fc'
],
id: '6f0a1418-ea43-4cd2-a652-5202f4f251fa',
name: 'Default Document Name'
}]

`select * from ChildDocument` [
{
_parentId: '6f0a1418-ea43-4cd2-a652-5202f4f251fa',
_type: 'ChildDocument',
id: '6f0a1418-ea43-4cd2-a652-5202f4f251fb'
},
{
_parentId: '6f0a1418-ea43-4cd2-a652-5202f4f251fa',
_type: 'ChildDocument',
id: '6f0a1418-ea43-4cd2-a652-5202f4f251fc'
}
]

Requirements


Installation

to install with NPM, enter the following command in the desired project directory.

npm install --save nativescript-couchbase-vuex-orm

your store must have $db in it's root state object with a Couchbase-lite DB. Your "model" classes are supplied as below (we continue with the Document and ChildDocument examples from above):

import { Couchbase } from 'nativescript-couchbase-plugin'
import { ORM } from 'nativescript-couchbase-vuex-orm'

import { Document, ChildDocument } from './Example.js'

const store = {
state: {
$db: new Couchbase('<your-db-name-here>'),
},
modules: {
Document: new ORM(Document).build(),
ChildDocument: new ORM(ChildDocument).build()
}
}

NB. Model classes will not work within other Model classes as vuex sub-modules (though ambitious developers are welcome to fork this project should they seek this functionality!) If you wish to create internal objects with a collection, add an object literal to your Model's properties object.


Model Abstract Class

Model classes should appear as below (here I use the example of a WorkFlow which can have many Stories):

class WorkFlow {
constructor () {
// a collection can only belong to one parent but can be featured in the hasMany of multiple instance types. A Model's `belongsTo` property merely enforces a `_parentId` property is present on the child collections of a given parent.
this.belongsTo = 'Organisation'

this.hasMany = [
// NB. delete ALWAYS cascades by default

// - EITHER -
// String ChildClassName
'Story',

// (v^1.2.0)
// - OR -
// Model ChildClass
// NB. to eager load children, a Model MUST be supplied
Story

// - OR -
// Object ChildModel
{
// --------------
// - EITHER -
// String ChildClass i.e. 'class ChildClass { ... }'
name: 'Story',

// (v^1.2.0)
// - OR -
// Model ChildClass
// NB. to eager load children, a Model MUST be supplied
name: Story,
// --------------

// (optional) a plural name to use instead of bolting an "s" onto the name property (the default)
pluralName: 'Stories',

// override cascade behaviour with explicit property
cascade: false
}
],

// default model properties initialised as below:
this.properties = {
someString: 'Foo',
someNumber: 1234,
someObject: {
foo: 'bar'
}
}

// (v^1.3.0)
// Still want to use the module as a normal part of your vue store as well?
// You can optionally add state, mutations and getters too!
this.state = {
foo: 'test',
bar: 1234
}
this.mutations = {
setFooBar (state, { foo, bar }) {
state.foo = foo
state.bar = bar
}
}
// all states are automatically mapped to default getters.
// e.g. `store.getters[Workflow/foo] = state => state.someStoreVariable`
// you can optionally add custom (or override) getters as below.
this.getters = {
getFooBar: state => state.foo + state.bar
}
}

// custom actions are added outside of your constructor
async customAction ({ dispatch, rootState }, args) {
// e.g.
// 1) call an instance modeled on the local class
const yourModelName = await dispatch('getById', args.id)
// 2) call parent using the retrieved parent Id
const parentModel = await dispatch(
'SomeParentModel/getById',
yourModelName._parentId,
{ root: true })
// 3) return the name property
return parentModel.name
}
}

API

Remember all calls use await/async or Promise.then() format

getById(String id || { String id, Boolean lazy, Number maxDepth })Object<CouchbaseCollection> collection

  • return a collection based on the supplied hash ID.
  • ID must be of the same type as the given Model
  • (as of v^1.2.0) If an object is supplied instead of a string ID:
    • Set the lazy prop equal to false to eager load the children in place of their ID references (NB, only children that were declared as Model classes in their parent's hasMany prop can be eager loaded).
    • (optionally) define a maxDepth prop to specify how many levels of child down you would like the ORM to inject

create(Object properties)String id

  • create a new collection and populate it with the supplied properties
  • As this is a NoSQL DB, fields are not enforced

update({ String id, Object props })Boolean success

  • update the given collection
  • ID must be of the same type as the given Model

delete(String id)Boolean success

  • delete the given collection
  • cascade the delete into the supplied child collections
  • NB deleting an item is not enforced and will not remove references from an associated parent (or any instance with an associated hasMany array)

add[Child](String id | { String id, Object child: {String id | Object props } })Number indexNumber

  • add either a new or existing child to a collection
  • if a parent ID is supplied as the argument, a new item will be created with default arguments
  • if an object is supplied with a parent ID and a child object containing a valid child ID, the child ID will be added to the parent collection (the _parentId property of the child will be updated accordingly if it belongsTo the given parent)
  • if an object is supplied with a parent ID and a child object containing a props object, a new child will be created using the supplied properties.

example:

given a "Deck" model that hasMany "Card", create a new card and add it to a deck with the supplied foo property

await Deck.addCard({ id: "0xparentId", child: { props: { foo: 'bar' } }})

remove[Child](String parentId | { String id, Object child: {String id, Boolean delete } })Boolean success

  • remove and (optionally) delete a child in the collection
  • if a parent ID is supplied as the argument, the last child will be popped from the parent's hasMany collection but not deleted.
  • if an object is supplied with a parent ID and a child object containing an id then the given child will be removed from the parent's hasMany collection.
  • if an object is supplied with a parent ID and a child object containing an id property and a delete property marked as true, then delete the object altogether.

example:

given a "Deck" model that hasMany "Card", remove a card and delete it

await Deck.removeCard({ id: "0xparentId", child: { id: "0xchildId", delete: true }})

reorder[Child]({ String id, String fromIndex, String toIndex })Boolean success

  • reorder the index of a child within a parent (or any hasMany)
  • doesn't touch the child collection.

example:

given a "Deck" model that hasMany "Card", move a card's index from 0 to 5

Deck.reorderCard({ id: "0xparentId", fromIndex: 0, toIndex: 5 }})

Questions

Here are some questions I can imagine myself having were I discovering this project for the first time:

are the items in the state object stored in the database?

No. Only properties will be added to the database. vuex state, mutations and getters are a separate concern from the database.

are the database properties visible in the module's state object or by vuex getters?

No. All database transactions including retrieval of data should be done using the supplied actions.

can I access state, mutations and getters in my custom ORM actions?

Yes! This is the recommended way to communicate between actions within your database and state within your application.

When I getById with a valid ID I don't get anything back, why?

A common cause is you're using the wrong module's getById. The ORM is type sensitive. Be wary of defining reserved ORM properties (e.g. _type, id, _parentId). If all else fails, the ORM really isn't that massive or difficult to read through. so if something feels weird, it shouldn't be too hard to spot the cause in my source. All of the conventions I introduce above are very strict so be sure to type everything precisely as instructed.

Do you unit test this stuff?

Yes, I have about 99.9% coverage in fact, but because I use an integrated version of this code in a private project, I can't release the tests I'm afraid because they all stub off my existing source code. you'll just have to take my word for it. Suffice it to say, I use this stuff and it all works correctly for my build.

Do you have a roadmap for this project?

Not right now, it's sort of organically growing along side the code I'm working on. When I spot something I'd like it to do, I figure others will be equally eager! That said, I'm receptive to suggestions (though I may not deal with them immediately!)