UNPKG

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
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) } } } }