nativescript-couchbase-vuex-orm
Version:
a plugin for vue nativescript to generate vuex ORM-style actions with Couchbase at runtime.
338 lines (321 loc) • 11.6 kB
JavaScript
async function createOrGetChild (args, dispatch, name) {
let childId = typeof args === 'object'
? (args.child ? args.child.id : undefined)
: undefined
if (!childId || args.create) {
childId =
await dispatch(
`${name}/create`,
{
...(args.child ? args.child.props || {} : {}),
_parentId: (args.id || args)
})
}
return childId
}
async function forEach (items, fn) {
if (items && items.length) {
await Promise.all(
items.map(async function (item) {
await fn(item)
}))
}
}
async function reduce (items, fn, initialValue) {
await forEach(
items, async function (item) {
initialValue = await fn(initialValue, item)
})
return initialValue;
}
async function createOrGetChild (args, dispatch, name) {
let childId = typeof args === 'object'
? (args.child ? args.child.id : undefined)
: undefined
if (!childId || args.create) {
childId =
await dispatch(
`${name}/create`,
{
...(args.child ? args.child.props || {} : {}),
_parentId: (args.id || args)
},
{ root: true })
}
return childId
}
function formatName (child) {
return typeof child === 'string'
? child
: (typeof child === 'function'
? child.name
: (child.name === 'function' ? child.name.name : child.name))
}
function formatProp (string) {
return string[0].toLowerCase() + string.substring(1) + 's'
}
function getORMClass (hasManyItem) {
if (typeof hasManyItem) {
return typeof hasManyItem === 'function'
? new ORM(hasManyItem)
: (typeof hasManyItem === 'object'
? (typeof hasManyItem.name === 'function'
? new ORM(hasManyItem.name)
: null)
: null)
} else return null
}
function getClientMethodsAndOverrides (_this) {
return Object.getOwnPropertyNames(
Object.getPrototypeOf(_this.model))
.reduce(function (obj, it) {
if (it !== 'constructor') obj[it] = _this.model[it]
return obj
}, {})
}
// { id, child: { id || args } }
function modelAddToMany (_this) {
const hasMany = {}
if (_this.model.hasMany) {
_this.model.hasMany.forEach(function (child) {
const name = formatName(child)
const prop = formatProp(child.pluralName || name)
Object.defineProperty(hasMany, `add${name.replace(/s$/, '')}`, {
configurable: false, enumerable: true, writable: false,
value:
async function ({ rootState, dispatch }, args) {
const childId = await createOrGetChild(args, dispatch, name)
return new Promise(function (resolve, reject) {
try {
const parent = rootState.$db.getDocument(args.id || args)
if (parent._type === _this.type) {
if (!parent[prop]) parent[prop] = []
const count = parent[prop].push(childId)
rootState.$db.updateDocument(parent.id, { [prop]: parent[prop] })
const belongsTo = rootState.$db.getDocument(childId).belongsTo
if (belongsTo === _this.type) {
rootState.$db.updateDocument(childId, { _parentId: parent.id })
}
resolve(count - 1) // return index number
} else throw new Error('parent type is not valid')
} catch (e) {
reject(`failed to add a ${name.replace(/s$/, '')} to a ${_this.type} "${args.id}": ${e.message}`)
}
})
}
})
})
}
return hasMany
}
function modelCreate (_this) {
return async function ({ rootState }, args = {}) {
return new Promise(function (resolve, reject) {
try {
if (!_this.model.belongsTo ||
typeof args !== 'object' ||
!!Object.keys(args).filter(key => key === '_parentId').length) {
const hasMany = (_this.model.hasMany)
? _this.model.hasMany.reduce((obj, it) => {
obj[formatProp(formatName(it))] = []
return obj
}, {})
: {}
resolve(
rootState.$db.createDocument(
typeof args === 'object'
? { _type: _this.type, ...hasMany, ..._this.model.properties, ...args }
: { _type: _this.type, ...hasMany, ..._this.model.properties, _parentId: args }))
} else throw new Error('Object is a child and should have a _parentId')
} catch (e) {
reject(`failed to create a ${_this.type}: ${e.message || e}`)
}
})
}
}
function modelDelete (_this) {
return async function ({ rootState, dispatch }, id) {
if (_this.model.hasMany && _this.model.hasMany.length) {
const collection = await dispatch(`${_this.type}/getById`, id, { root: true })
await forEach(_this.model.hasMany, async function (child) {
if (typeof child !== 'object' || (child.hasOwnProperty('cascade') && child.cascade)) {
const name = formatName(child)
const prop = formatProp(child.pluralName || name)
if (collection && collection.hasOwnProperty(prop) && collection[prop].length) {
await forEach(collection[prop], async function (childId) {
await dispatch(`${name.replace(/s$/, '')}/delete`, childId, { root: true })
})
}
}
})
}
return new Promise(function (resolve, reject) {
try {
rootState.$db.deleteDocument(id)
resolve(true)
} catch (e) {
reject(`failed to delete a ${_this.type}: ${e.message || e}`)
}
})
}
}
async function mapModelDependencies (_this, model, rootState, maxDepth, level = 0) {
return reduce(_this.model.hasMany, async function (parent, child) {
const name = formatName(child)
const prop = formatProp(child.pluralName || name)
const _thisChild = getORMClass(child)
const dependencies = await reduce(
parent[prop],
async function (list, childId) {
const childModel = rootState.$db.getDocument(childId)
if (_thisChild && (!maxDepth || level < maxDepth)) {
await mapModelDependencies(
_thisChild, childModel, rootState, maxDepth, ++level)
}
list.push(childModel)
return list
}, [])
parent[prop] = dependencies.length ? dependencies : parent[prop]
return parent
}, model)
}
function modelGetById (_this) {
return async function ({ rootState }, args) {
return new Promise(async function (resolve, reject) {
try {
const id = typeof args === 'object' ? args.id : args
let result = rootState.$db.query({
select: [],
where: [
{ property: '_id', comparison: 'equalTo', value: id },
{ property: '_type', logical: 'and', comparison: 'equalTo', value: _this.type }],
order: [{ property: 'id', direction: 'desc' }],
limit: 1
});
result = result && result.length ? result[0] : null
if (result && _this.model.hasMany && typeof args === 'object' && !args.lazy) {
const model = await mapModelDependencies(_this, result, rootState, args.maxDepth)
resolve(model || null)
} else {
resolve(result)
}
}
catch (e) {
reject(`failed to get ${_this._type}: ${e.message || e}`)
}
})
}
}
// { id, child: { id, delete } }
function modelRemoveFromMany (_this) {
const hasMany = {}
if (_this.model.hasMany) {
_this.model.hasMany.forEach(function (child) {
const name = formatName(child)
const prop = formatProp(child.pluralName || name)
Object.defineProperty(hasMany, `remove${name.replace(/s$/, '')}`, {
configurable: false, enumerable: true, writable: false,
value:
async function ({ dispatch, rootState }, args) {
let childId = typeof args === 'object'
? (args.child ? args.child.id : undefined)
: undefined
let id = args.id || args
if (args.child && args.child.delete) await dispatch(`${name}/delete`, args.child.id, { root: true })
return new Promise(function (resolve, reject) {
try {
const parent = rootState.$db.getDocument(id)
if (parent._type === _this.type) {
if (!childId) childId = parent[prop].pop()
const arr =
rootState.$db.updateDocument(
id, {
[prop]: childId
? parent[prop].filter(c => c != childId)
: parent[prop]
})
const testBelongs = rootState.$db.getDocument(childId)
if (testBelongs && testBelongs.belongsTo === _this.type) {
rootState.$db.updateDocument(childId, { _parentId: undefined })
}
resolve(true)
} else throw new Error('parent type is not valid')
} catch (e) {
reject(`adding a ${name.replace(/s$/, '')} to a ${_this.type} "${args.id || args}": ${e.message}`)
}
})
}
})
})
}
return hasMany
}
// { id, fromIndex, toIndex }
function modelReorderItemInMany (_this) {
const hasMany = {}
if (_this.model.hasMany) {
_this.model.hasMany.forEach(function (child) {
const name = formatName(child)
const prop = formatProp(child.pluralName || name)
Object.defineProperty(hasMany, `reorder${name.replace(/s$/, '')}`, {
configurable: false, enumerable: true, writable: false,
value: async function ({ rootState }, { id, fromIndex, toIndex }) {
return new Promise(function (resolve, reject) {
try {
const parent = rootState.$db.getDocument(id)
if (parent._type === _this.type) {
parent[prop].splice(toIndex, 0, parent[prop].splice(fromIndex, 1)[0])
rootState.$db.updateDocument(id, { [prop]: parent[prop] })
resolve(true)
} else throw new Error('parent type is not valid')
} catch (e) {
reject(`failed to reorder a ${name} in ${_this.type} "${id}": ${e.message}`)
}
})
}
})
})
}
return hasMany
}
function modelUpdate (_this) {
return async function ({ rootState }, { id, props }) {
return new Promise(function (resolve, reject) {
try { resolve(rootState.$db.updateDocument(id, props)) }
catch (e) {
reject(`failed to update ${_this._type} "${id || 'unknown!'}": ${e.message || e}`)
}
})
}
}
export class ORM {
constructor(Model) {
this.model = new Model()
const _this = this
this.type = Model.name
}
build () {
return {
namespaced: true,
state: this.model.state || {},
mutations: this.model.mutations || {},
getters: this.model.state
? Object.keys(this.model.state).reduce((obj, key) => {
// default getter format: `state => state.someStoreVariable`
if (!obj.hasOwnProperty(key)) obj[key] = state => state[key]
return obj
}, this.model.getters || {})
: {},
actions: {
...modelAddToMany(this),
...modelRemoveFromMany(this),
...modelReorderItemInMany(this),
create: modelCreate(this),
delete: modelDelete(this),
getById: modelGetById(this),
update: modelUpdate(this),
...getClientMethodsAndOverrides(this)
}
}
}
}