backendless-coderunner
Version:
Backendless CodeRunner for Node.js
352 lines (288 loc) • 8.5 kB
JavaScript
'use strict'
const Backendless = require('backendless')
const isPersistable = value => value instanceof PersistenceItem
const isArray = value => Array.isArray(value)
const isEmptyArray = value => isArray(value) && value.length === 0
const isArrayOfPersistable = value => {
if (!isArray(value) || value.length === 0) {
return false
}
for (let i = 0; i < value.length; i++) {
if (!isPersistable(value[i])) {
return false
}
}
return true
}
const trimRelations = item => {
const result = {}
Object.keys(item).forEach(key => {
const value = item[key]
if (isPersistable(value) || isArray(value)) {
return
}
result[key] = value
})
return result
}
const SYSTEM_PROPS = ['objectId', 'ownerId', 'created', 'updated', '___class', '___jsonclass']
const queryToBuilder = query => {
if (query instanceof Backendless.DataQueryBuilder) {
return query
}
if (typeof query === 'string') {
return Backendless.DataQueryBuilder.create().setWhereClause(query)
}
const result = Backendless.DataQueryBuilder.create()
if (query) {
const { condition, properties, options } = query
condition && (result.setWhereClause(condition))
properties && (result.setProperties(properties))
if (options) {
options.pageSize && (result.setPageSize(options.pageSize))
options.offset && (result.setOffset(options.offset))
options.relations && (result.setRelated(options.relations))
options.sortBy && (result.setSortBy(options.sortBy))
}
}
return result
}
/**
* @typedef {Object} PersistenceIten
* @property {String} ownerId
* @property {String} objectId
* @property {Number} created
* @property {Number} updated
*/
class PersistenceItem {
constructor(args) {
this.___class = this.constructor.name
if (typeof args === 'string') {
args = { objectId: args }
}
Object.assign(this, args)
}
/**
* @param {String[]} [relations]
* @param {String[]} [properties]
* @returns {Promise.<PersistenceItem>}
*/
fetch(relations, properties) {
return this.constructor.findById(this.objectId, relations, properties)
}
/**
* @returns {Promise}
*/
save() {
const payload = trimRelations(this)
const hasCustomProps = !!Object.keys(payload).find(key =>
payload[key] !== undefined && !SYSTEM_PROPS.includes(key))
if (!payload.objectId || hasCustomProps || payload.ownerId !== undefined) {
return this.dataStore.save(payload).then(saved => Object.assign(this, saved))
}
return Promise.resolve(this)
}
/**
* @param {{stale: String[]}} [options]
* @returns {Promise.<PersistenceItem>}
*/
saveWithRelations(options = {}) {
const { stale = [], deletable = [] } = options
const childPath = (key, path) => {
const prefix = `${key}.`
return path
.filter(s => s.startsWith(prefix))
.map(s => s.substr(prefix.length))
}
return this.save().then(saved => {
// eslint-disable-next-line no-shadow
const saveItem = (item, stale, deletable) => {
if (item instanceof PersistenceItem) {
return item.saveWithRelations({ stale, deletable })
}
return item
}
const processField = (name, value) => {
const setRelation = relation => {
saved[name] = relation
return saved.setRelation(name, relation)
}
if (isPersistable(value)) {
return Promise.resolve()
.then(() => {
return stale.includes(name)
? value
: saveItem(value, childPath(name, stale), childPath(name, deletable))
})
.then(setRelation)
}
if (isArrayOfPersistable(value)) {
return Promise.resolve()
.then(() => {
return stale.includes(name)
? value
: Promise.all(value.map(item => saveItem(item, childPath(name, stale), childPath(name, deletable))))
})
.then(setRelation)
}
if (deletable.includes(name) && (isEmptyArray(value) || value === null)) {
return saved.deleteRelation(name, 'objectId is not null')
}
}
return Promise.all(Object.keys(this).map(key => processField(key, this[key])))
.then(() => saved)
})
}
/**
* Returns a new class instance with objectId and filtered set of properties
*
* @param {...String} propsToInclude - properties to include in the response
* @returns {PersistenceItem}
*/
ref(...propsToInclude) {
const result = new this.constructor({ objectId: this.objectId })
if (propsToInclude) {
propsToInclude.forEach(propName => {
result[propName] = this[propName]
})
}
return result
}
/**
* @returns {Promise}
*/
remove() {
return this.dataStore.remove(this)
}
/**
* @param {String} columnName - A name of the column identifying the relation
* @param {PersistenceItem|PersistenceItem[]|String} childrenOrWhereClause
* @returns {Promise}
*/
setRelation(columnName, childrenOrWhereClause) {
if (isPersistable(childrenOrWhereClause)) {
childrenOrWhereClause = [childrenOrWhereClause]
}
return this.dataStore.setRelation(this, columnName, childrenOrWhereClause)
}
/**
* @param {String} columnName - A name of the column identifying the relation
* @param {PersistenceItem|PersistenceItem[]|String} childrenOrWhereClause
* @returns {Promise}
*/
addRelation(columnName, childrenOrWhereClause) {
if (isPersistable(childrenOrWhereClause)) {
childrenOrWhereClause = [childrenOrWhereClause]
}
return this.dataStore.addRelation(this, columnName, childrenOrWhereClause)
}
/**
* @param {String} columnName - A name of the column identifying the relation
* @param {PersistenceItem|PersistenceItem[]|String} childrenOrWhereClause
* @returns {Promise}
*/
deleteRelation(columnName, childrenOrWhereClause) {
if (isPersistable(childrenOrWhereClause)) {
childrenOrWhereClause = [childrenOrWhereClause]
}
return this.dataStore.deleteRelation(this, columnName, childrenOrWhereClause)
}
/**
* @private
*/
get dataStore() {
return Backendless.Data.of(this.constructor)
}
/**
* @private
*/
static get dataStore() {
return Backendless.Data.of(this)
}
/**
* @param {String} condition
* @returns {Promise}
*/
static count(condition) {
return this.dataStore.getObjectCount(condition)
}
/**
* @param {Object} obj
* @returns {Promise.<PersistenceItem>}
*/
static save(obj) {
return new this(obj).save()
}
/**
* @param {Object} obj
* @param {Object} [opts]
* @returns {Promise.<PersistenceItem>}
*/
static saveWithRelations(obj, opts) {
return new this(obj).saveWithRelations(opts)
}
/**
* @param {Backendless.DataQuery|Object|String} query
* @returns {Promise.<PersistenceItem[]>}
*/
static find(query) {
return this.dataStore.find(queryToBuilder(query))
}
/**
* @param {String} objectId
* @param {String[]} [relations]
* @param {String[]} [properties]
* @returns {Promise.<PersistenceItem>}
*/
static findById(objectId, relations, properties) {
if (!objectId) {
return Promise.reject(new Error('objectId is not defined'))
}
return this.dataStore.findById(objectId, {
relations,
properties
})
}
/**
* @returns {Promise}
*/
static findFirst() {
return this.dataStore.findFirst()
}
/**
* @returns {Promise}
*/
static findLast() {
return this.dataStore.findLast()
}
/**
* @param {Object|String} obj An object or objectId for deletion
* @returns {Promise}
*/
static remove(obj) {
return this.dataStore.remove(obj)
}
/**
* @param {Array<Object>} items
* @returns {Promise}
*/
static bulkCreate(items) {
return this.dataStore.bulkCreate(items)
}
/**
* @param {String} whereClause
* @param {Object} changes
* @returns {Promise}
*/
static bulkUpdate(whereClause, changes) {
return this.dataStore.bulkUpdate(whereClause, changes)
}
/**
* @param {String|Array<Object|String>} whereClause whereClause or an array of objects/objectIds for deletion
* @returns {Promise}
*/
static bulkDelete(whereClause) {
return this.dataStore.bulkDelete(whereClause)
}
}
module.exports = PersistenceItem