UNPKG

backendless

Version:

Backendless JavaScript SDK for Node.js and the browser

663 lines (529 loc) 16.9 kB
import Utils from '../utils' import DataQueryBuilder from '../data/data-query-builder' import { OperationType, IsolationLevelEnum } from './constants' import { OperationJSONAdapter } from './json-adapter' import { OpResult } from './op-result' import { OpResultValueReference } from './op-result-value-reference' class TransactionOperationError extends Error { constructor(error, operation) { super(error.message) this.code = error.code this.operation = operation } toJSON() { return { message : this.message, operation: this.operation, } } } class UnitOfWorkResult { constructor({ success, error, results }) { this.success = success this.error = error this.results = results } isSuccess() { return this.success } getError() { return this.error } getResults() { return this.results } } class UnitOfWork { static IsolationLevelEnum = IsolationLevelEnum static OpResult = OpResult static OpResultValueReference = OpResultValueReference constructor(isolationLevelEnum, app) { this.app = app this.payload = { isolationLevelEnum, operations: [] } this.usedOpIds = {} } getOpResultById(opResultId) { const operation = this.payload.operations.find(opResult => opResult.meta.opResult.getOpResultId() === opResultId) return operation.meta.opResult } setIsolationLevel(isolationLevelEnum) { this.payload.isolationLevelEnum = isolationLevelEnum } getOpStackName(operationType, table) { return `${operationType.toLowerCase()}${table}` } getNextOpResultIndex(stackName) { if (!this.usedOpIds[stackName]) { this.usedOpIds[stackName] = 0 } return ++this.usedOpIds[stackName] } addOperations(operationType, table, payload) { if (Array.isArray(payload)) { payload = payload.map(item => { delete item.___jsonclass return item }) } else { delete payload.___jsonclass } const opResult = new OpResult(this, { operationType, table, payload }) this.payload.operations.push({ operationType, table, payload, meta: { opResult } }) return opResult } composePayload() { return { ...this.payload, operations: this.payload.operations.map(({ meta, ...operation }) => { return { ...operation, opResultId: meta.opResult.getOpResultId(), } }) } } async execute() { const result = await this.app.request.post({ url : this.app.urls.transactions(), data: this.composePayload(), }) return this.setResult(result) } setResult(result){ if (result.results) { this.payload.operations.forEach(operation => { const opResultId = operation.meta.opResult.getOpResultId() if (result.results[opResultId]) { operation.meta.opResult.setResult(result.results[opResultId].result) } }) } if (result.error) { const operation = this.payload.operations.find(op => { return result.error.operation.opResultId === op.meta.opResult.getOpResultId() }) result.error = new TransactionOperationError(result.error, operation.meta.opResult) operation.meta.opResult.setError(result.error) } return new UnitOfWorkResult(result) } find(tableName, queryBuilder) { const query = (queryBuilder instanceof DataQueryBuilder) ? queryBuilder.toJSON() : (queryBuilder || {}) if (!tableName || typeof tableName !== 'string') { throw new Error('Invalid arguments') } if (typeof query !== 'object' || Array.isArray(query)) { throw new Error('Invalid arguments') } const payload = { queryOptions: {} } if (query.pageSize > 0) { payload.pageSize = query.pageSize } if (query.offset > 0) { payload.offset = query.offset } if (query.properties) { payload.properties = query.properties } if (query.excludeProps) { payload.excludeProps = query.excludeProps } if (query.excludeProps) { payload.excludeProps = query.excludeProps } if (query.where) { payload.whereClause = query.where } if (query.having) { payload.havingClause = query.having } if (query.groupBy) { payload.groupBy = query.groupBy } if (query.sortBy) { payload.queryOptions.sortBy = query.sortBy } if (query.relations) { payload.queryOptions.related = query.relations } if (query.relationsDepth) { payload.queryOptions.relationsDepth = query.relationsDepth } if (query.relationsPageSize > 0) { payload.queryOptions.relationsPageSize = query.relationsPageSize } return this.addOperations(OperationType.FIND, tableName, payload) } /** * upsert(object: object): OpResult; * upsert(tableName: string, object: object): OpResult; * **/ upsert(...args) { let tableName let changes if (args.length === 1) { tableName = Utils.getClassName(args[0]) changes = args[0] } else if (args.length === 2) { tableName = args[0] changes = args[1] } else { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Invalid arguments') } if (!changes || typeof changes !== 'object' || Array.isArray(changes)) { throw new Error('Invalid arguments') } return this.addOperations(OperationType.UPSERT, tableName, changes) } /** * create(object: object): OpResult; * create(tableName: string, object: object): OpResult; * **/ create(...args) { let tableName let changes if (args.length === 1) { tableName = Utils.getClassName(args[0]) changes = args[0] } else if (args.length === 2) { tableName = args[0] changes = args[1] } else { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Invalid arguments') } if (!changes || typeof changes !== 'object' || Array.isArray(changes)) { throw new Error('Invalid arguments') } return this.addOperations(OperationType.CREATE, tableName, changes) } /** * update(object: object): OpResult; * update(tableName: string, object: object): OpResult; * update(opResult: OpResult | OpResultValueReference, object: object): OpResult; * * update( * opResult: OpResult | OpResultValueReference, * propertyName: string, * propertyValue: OpResultValueReference): OpResult; * * update( * opResult: OpResult | OpResultValueReference, * propertyName: string, * propertyValue: number | string | boolean): OpResult; * **/ update(...args) { let tableName let payload if (args.length === 1) { tableName = Utils.getClassName(args[0]) payload = args[0] } else if (args.length === 2 && typeof args[0] === 'string') { tableName = args[0] payload = args[1] } else if (args[0] instanceof OpResult || args[0] instanceof OpResultValueReference) { tableName = args[0].getTableName() payload = { objectId: args[0] } if (args.length === 3 && typeof args[1] === 'string') { payload[args[1]] = args[2] } else if (args.length === 2) { payload = { ...payload, ...args[1] } } else { throw new Error('Invalid arguments') } } else { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Invalid arguments') } return this.addOperations(OperationType.UPDATE, tableName, payload) } /** * delete(opResult: OpResult | OpResultValueReference): OpResult; * delete(object: object): OpResult; * delete(tableName: string, object: object): OpResult; * delete(tableName: string, objectId: string): OpResult; * **/ delete(...args) { let tableName let object if (args.length === 1) { if (args[0] instanceof OpResult || args[0] instanceof OpResultValueReference) { tableName = args[0].getTableName() object = args[0] } else if (args[0] && typeof args[0] === 'object' && !Array.isArray(args[0])) { tableName = Utils.getClassName(args[0]) object = args[0].objectId } } else if (args.length === 2) { tableName = args[0] object = args[1] && args[1].objectId || args[1] } else { throw new Error('Invalid arguments') } if (!object || Array.isArray(object) || (typeof object !== 'string' && typeof object !== 'object')) { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } return this.addOperations(OperationType.DELETE, tableName, object) } /** * bulkUpsert(tableName: string, objects: object[]): OpResult; * bulkUpsert(objects: object[]): OpResult; * **/ bulkUpsert(tableName, objects) { if (Array.isArray(tableName)) { objects = tableName tableName = Utils.getClassName(objects[0]) } if (!objects || !Array.isArray(objects)) { throw new Error('Objects must be an array of objects.') } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } return this.addOperations(OperationType.UPSERT_BULK, tableName, objects) } /** * bulkCreate(tableName: string, objects: object[]): OpResult; * bulkCreate(objects: object[]): OpResult; * **/ bulkCreate(tableName, objects) { if (Array.isArray(tableName)) { objects = tableName tableName = Utils.getClassName(objects[0]) } if (!objects || !Array.isArray(objects)) { throw new Error('Objects must be an array of objects.') } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } return this.addOperations(OperationType.CREATE_BULK, tableName, objects) } /** * bulkUpdate(opResult: OpResult, changes: object): OpResult; * * bulkUpdate(tableName: string, whereClause: string, changes: object): OpResult; * bulkUpdate(tableName: string, objectIds: string[], changes: object): OpResult; * bulkUpdate(tableName: string, objects: object[], changes: object): OpResult; * **/ bulkUpdate(...args) { let tableName const payload = {} if (args.length === 2) { payload.changes = args[1] if (typeof args[0] === 'string') { tableName = Utils.getClassName(args[1]) payload.conditional = args[0] } else if (args[0] instanceof OpResult) { tableName = args[0].getTableName() payload.unconditional = args[0] } } else if (args.length === 3) { tableName = args[0] payload.changes = args[2] if (typeof args[1] === 'string') { payload.conditional = args[1] } else if (Array.isArray(args[1])) { payload.unconditional = args[1].map(o => o.objectId || o) } else { throw new Error('Invalid arguments') } } else { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } return this.addOperations(OperationType.UPDATE_BULK, tableName, payload) } /** * bulkDelete(opResult: OpResult): OpResult; * bulkDelete(objects: object[]): OpResult; * * bulkDelete(tableName: string, objects: object[]): OpResult; * bulkDelete(tableName: string, objectIds: string[]): OpResult; * bulkDelete(tableName: string, whereClause: string): OpResult; * **/ bulkDelete(...args) { const payload = {} let tableName if (args.length === 1) { if (args[0] instanceof OpResult) { tableName = args[0].getTableName() payload.unconditional = args[0] } else if (Array.isArray(args[0])) { tableName = Utils.getClassName(args[0][0]) payload.unconditional = args[0].map(o => o.objectId) } else { throw new Error('Invalid arguments') } } else if (args.length === 2) { tableName = args[0] if (typeof args[1] === 'string') { payload.conditional = args[1] } else if (Array.isArray(args[1])) { payload.unconditional = args[1].map(o => o.objectId || o) } else { throw new Error('Invalid arguments') } } else { throw new Error('Invalid arguments') } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } return this.addOperations(OperationType.DELETE_BULK, tableName, payload) } addToRelation(...args) { return this.relationsOperation(OperationType.ADD_RELATION, args) } setRelation(...args) { return this.relationsOperation(OperationType.SET_RELATION, args) } deleteRelation(...args) { return this.relationsOperation(OperationType.DELETE_RELATION, args) } /** * * uow.[...]Relation( * parentObject: OpResult|OpResultValue|Class, * columnName: String, * children: String|Class|OpResult|OpResultValue|List<String|Class|OpResult|OpResultValue> * ) * * uow.[...]Relation( * tableName: String, * parentObject: String|Class|Object, * columnName: String, * children: String|Class|OpResult|OpResultValue|List<String|Class|OpResult|OpResultValue> * ) * * */ relationsOperation(operationType, args) { let tableName let relationColumn let parentObject let conditional let unconditional let children if (args.length === 3) { parentObject = args[0] relationColumn = args[1] children = args[2] if (parentObject instanceof OpResult || parentObject instanceof OpResultValueReference) { tableName = parentObject.getTableName() } else if (parentObject && typeof parentObject === 'object') { tableName = Utils.getClassName(parentObject) } else { throw new Error( 'Invalid the first argument, it must be an instance of [OpResult|OpResultValueReference|Object]' ) } } else if (args.length === 4) { tableName = args[0] relationColumn = args[2] children = args[3] if (typeof args[1] === 'string') { parentObject = args[1] } else if (args[1] && typeof args[1] === 'object') { parentObject = args[1] } else { throw new Error('Invalid the second argument, it must be an Object or objectId') } } else { throw new Error('Invalid arguments') } if (parentObject && parentObject.objectId) { parentObject = parentObject.objectId } if (typeof children === 'string') { conditional = children } else if (children instanceof OpResult && children.isCollectionRef()) { unconditional = children } else { if (!Array.isArray(children)) { children = [children] } unconditional = children.map(child => { if (child) { if (child instanceof OpResult || child instanceof OpResultValueReference) { return child } if (typeof child === 'string') { return child } if (child.objectId) { return child.objectId } } throw new Error( 'Invalid child argument, it must be an instance of [OpResult|OpResultValueReference|Object] or objectId' ) }) } if (!relationColumn || typeof relationColumn !== 'string') { throw new Error( 'Invalid "relationColumn" parameter, check passed arguments' ) } if (!unconditional && !conditional) { throw new Error( 'Neither "unconditional" nor "conditional" parameter is specified, check passed arguments' ) } if (!tableName || typeof tableName !== 'string') { throw new Error('Table Name must be a string.') } const payload = { parentObject, relationColumn } if (conditional) { payload.conditional = conditional } else { payload.unconditional = unconditional } return this.addOperations(operationType, tableName, payload) } } export default function UnitOfWorkService(app) { return class extends UnitOfWork { static initFromJSON(data) { const uow = new this(data.transactionIsolation) data.operations.forEach(op => { const opResult = OperationJSONAdapter[op.operationType](uow, op) opResult.setOpResultId(op.opResultId) }) return uow } constructor(isolationLevelEnum) { super(isolationLevelEnum, app) } } }