UNPKG

miter

Version:

A typescript web framework based on ExpressJs based loosely on SailsJs

668 lines 32.9 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const Sql = require("sequelize"); const _ = require("lodash"); const transaction_decorator_1 = require("../../decorators/orm/transaction.decorator"); const name_decorator_1 = require("../../decorators/services/name.decorator"); const prop_1 = require("../../metadata/orm/prop"); const model_1 = require("../../metadata/orm/model"); const belongs_to_1 = require("../../metadata/orm/associations/belongs-to"); const has_one_1 = require("../../metadata/orm/associations/has-one"); const has_many_1 = require("../../metadata/orm/associations/has-many"); const orm_1 = require("../../decorators/orm"); const logger_1 = require("../../services/logger"); const sequelize_1 = require("../sequelize"); const transaction_service_1 = require("../../services/transaction.service"); const transaction_impl_1 = require("./transaction-impl"); let DbImpl = DbImpl_1 = class DbImpl { constructor(modelFn, model, sequelize, logger, transactionService) { this.modelFn = modelFn; this.model = model; this.sequelize = sequelize; this.logger = logger; this.transactionService = transactionService; this.transforms = []; this.createCopyValsFn(); this.createTransformQuery(); } getSqlTransact(transaction) { transaction = transaction || this.transactionService.current; if (transaction) this.logger.verbose(`Using transaction: ${transaction.fullName}`); return transaction && transaction.sync(); } transaction(name, transaction) { return __awaiter(this, void 0, void 0, function* () { return yield this.sequelize.transaction(name, transaction); }); } create(t, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (t instanceof Array) { t = _.cloneDeep(t); t.forEach((t, idx, arr) => { [arr[idx], implicitIncludes] = this.transformQueryWhere(t); if (implicitIncludes.length) throw new Error(`Cannot have implicit includes in where clause in create query.`); }); let results = yield this.model.bulkCreate(t, _.merge({}, { transaction: sqlTransact })); return true; } else { [t, implicitIncludes] = this.transformQueryWhere(t); if (implicitIncludes.length) throw new Error(`Cannot have implicit includes in where clause in create query.`); let result = yield this.model.create(t, _.merge({}, { transaction: sqlTransact })); return this.wrapResult(result, implicitIncludes); } }); } findById(id, options, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (options) [options, implicitIncludes] = this.transformQuery(options); if (options && typeof options.limit !== 'undefined') throw new Error(`Cannot include limit in findById query.`); let result = yield this.model.findById(id, _.merge({}, { transaction: sqlTransact }, options)); return result && this.wrapResult(result, implicitIncludes); }); } findOne(query, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; [query, implicitIncludes] = this.transformQuery(query); let limit = query && query.limit; if (implicitIncludes.length) { let results = yield this.model.findAll(_.merge({}, { transaction: sqlTransact }, query)); let result = results[0] || null; return result && this.wrapResult(result, implicitIncludes); } else { let result = yield this.model.findOne(_.merge({}, { transaction: sqlTransact }, query)); return result && this.wrapResult(result, implicitIncludes); } }); } findOrCreate(query, defaults, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; [query, implicitIncludes] = this.transformQueryWhere(query); let defaultImplicitIncludes = []; [defaults, defaultImplicitIncludes] = this.transformQueryWhere(defaults); if (defaultImplicitIncludes.length) { this.logger.error(`findOrCreate. query:`, query); this.logger.error(`defaults:`, defaults); this.logger.error(`defaultImplicitIncludes:`, defaultImplicitIncludes); throw new Error(`Cannot have implicit includes in default values in findOrCreate query.`); } let findOrCreateOpts = _.merge({}, { where: query, include: implicitIncludes, defaults: defaults || {}, transaction: sqlTransact }); let [result, created] = yield this.model.findOrCreate(findOrCreateOpts); return [result && this.wrapResult(result, implicitIncludes), created]; }); } findAndCountAll(query, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (query) [query, implicitIncludes] = this.transformQuery(query); let limitAfter = false; let limit = query && query.limit; if (implicitIncludes.length && limit) { limitAfter = true; delete query.limit; } let results = yield this.model.findAndCountAll(_.merge({}, { transaction: sqlTransact }, query)); if (limitAfter) results.rows = results.rows.slice(0, limit); return { count: results.count, results: this.wrapResults(results.rows, implicitIncludes) }; }); } findAll(query, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (query) [query, implicitIncludes] = this.transformQuery(query); let limitAfter = false; let limit = query && query.limit; if (implicitIncludes.length && limit) { limitAfter = true; delete query.limit; } let results = yield this.model.findAll(_.merge({}, { transaction: sqlTransact }, query)); if (limitAfter) results = results.slice(0, limit); return this.wrapResults(results, implicitIncludes); }); } all(query, transaction) { return __awaiter(this, void 0, void 0, function* () { return yield this.findAll(query, transaction); }); } count(query, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (query) [query, implicitIncludes] = this.transformQuery(query); return yield this.model.count(_.merge({}, { transaction: sqlTransact }, query)); }); } max(field, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); return yield this.model.max(field, _.merge({}, { transaction: sqlTransact })); }); } min(field, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); return yield this.model.min(field, _.merge({}, { transaction: sqlTransact })); }); } sum(field, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); return yield this.model.sum(field, _.merge({}, { transaction: sqlTransact })); }); } save(t, transaction) { return __awaiter(this, void 0, void 0, function* () { let result; let _; if (!t.id) result = yield this.create(t, transaction); else [_, result] = yield this.update(t.id, t, true, transaction); return result; }); } update(query, replace, returning = false, transaction) { return __awaiter(this, void 0, void 0, function* () { if (!query) { throw new Error(`Db.update query parameter was falsey: ${query}`); } let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (this.isId(query)) query = { where: { id: query } }; else if (this.isT(query)) query = { where: { id: query.id } }; else { let include = query.include; if (include && include.length && !returning) throw new Error(`Cannot have explicit includes in update when returning = false.`); [query, implicitIncludes] = this.transformQuery(query); let limit = query && query.limit; let filterAfter = false; if (implicitIncludes && implicitIncludes.length && limit) { filterAfter = true; delete query.limit; } if (returning || filterAfter) { let results = yield this.model.findAll(query); if (filterAfter) results = results.slice(0, limit); let ids = results.map((result) => result.id); query = { where: { id: { $in: ids } } }; } } let replaceImplicitIncludes = []; [replace, replaceImplicitIncludes] = this.transformQueryWhere(replace); if (replaceImplicitIncludes.length) { this.logger.error(`update. query:`, query); this.logger.error(`replace:`, replace); this.logger.error(`replaceImplicitIncludes:`, replaceImplicitIncludes); throw new Error(`Cannot have implicit includes in replace values in update query.`); } let [affected, results] = yield this.model.update(replace, _.merge({}, { transaction: sqlTransact }, query)); if (returning) { let returningResults = yield this.model.findAll(_.merge({}, { transaction: sqlTransact }, query)); results = this.wrapResults(returningResults, implicitIncludes); } return [affected, results]; }); } updateOrCreate(query, defaults, transaction) { return __awaiter(this, void 0, void 0, function* () { let [result, created] = yield this.findOrCreate(query, defaults); if (!created) { let worked = yield this.update({ where: query }, defaults, false); if (!worked) throw new Error("Failed to update or create a model."); let resultOrNull = yield this.findOne(_.merge({}, { where: query }, defaults)); if (!resultOrNull) throw new Error("Updated row, but could not find it afterwards."); result = resultOrNull; } return [result, created]; }); } destroy(query, transaction) { return __awaiter(this, void 0, void 0, function* () { let sqlTransact = this.getSqlTransact(transaction); let implicitIncludes = []; if (this.isId(query)) query = { where: { id: query } }; else if (this.isT(query)) query = { where: { id: query.id } }; else [query, implicitIncludes] = this.transformQuery(query); if (implicitIncludes.length && query && query.limit) throw new Error(`Model.destroy with limit and with a query containing implicit includes is not implemented`); return yield this.model.destroy(_.merge({}, { transaction: sqlTransact }, query)); }); } isId(query) { return typeof query === 'number' || typeof query == 'string'; } isT(query) { return !!query.id; } createCopyValsFn() { let allProps = []; let directTransformFn = (val) => val; let props = Reflect.getOwnMetadata(model_1.ModelPropertiesSym, this.modelFn.prototype) || []; for (let q = 0; q < props.length; q++) { let propName = props[q]; let propMeta = Reflect.getOwnMetadata(prop_1.PropMetadataSym, this.modelFn.prototype, propName); if (!propMeta) throw new Error(`Could not find model property metadata for property ${this.modelFn.name || this.modelFn}.${propName}.`); let transformFn = directTransformFn; if (propMeta.type == orm_1.Types.DATE) transformFn = (dateStr) => new Date(dateStr); allProps.push({ columnName: propMeta.columnName || propName, propertyName: propName, transformFn: transformFn }); } this.copyVals = function (sql, t) { for (let q = 0; q < allProps.length; q++) { let propName = allProps[q].propertyName; t[propName] = allProps[q].transformFn(sql[propName]); } }; } static getForeignModelDbImpl(foreignModel) { if (!foreignModel || !("db" in foreignModel)) throw new Error("Cannot get DbImpl from ForeignModel that has not been resolved"); let staticModel = foreignModel; return staticModel.db; } createTransformQuery() { let transforms = this.transforms = this.getTransforms(); this.createTransformQueryWhere(transforms); this.createTransformQueryInclude(); this.createTransformResult(transforms); } transformQuery(query) { let result = _.clone(query); let implicitIncludes = []; if (query.where) [result.where, implicitIncludes] = this.transformQueryWhere(query.where, implicitIncludes); if (!query.include) query.include = []; for (let q = 0; q < query.include.length; q++) { let alias = query.include[q]; let idx = implicitIncludes.indexOf(alias); if (idx !== -1) implicitIncludes.splice(idx, 1); } let include = [...query.include, ...implicitIncludes]; result.include = this.transformQueryInclude(include); return [result, implicitIncludes]; } getTransforms() { let transforms = []; let belongsTo = Reflect.getOwnMetadata(belongs_to_1.ModelBelongsToAssociationsSym, this.modelFn.prototype) || []; for (let q = 0; q < belongsTo.length; q++) { let propName = belongsTo[q]; let propMeta = Reflect.getOwnMetadata(belongs_to_1.BelongsToMetadataSym, this.modelFn.prototype, propName); if (!propMeta) throw new Error(`Could not find model belongs-to metadata for property ${this.modelFn.name || this.modelFn}.${propName}.`); let fkey = propMeta.foreignKey; if (fkey && typeof fkey !== 'string') fkey = fkey.name; if (!fkey) throw new Error(`Could not get foreign key for belongs-to property ${this.modelFn.name || this.modelFn}.${propName}`); transforms.push({ type: 'belongs-to', columnName: fkey, fieldName: propName, foreignPkName: 'id', foreignDb: () => DbImpl_1.getForeignModelDbImpl(propMeta.foreignModel) }); } let hasOne = Reflect.getOwnMetadata(has_one_1.ModelHasOneAssociationsSym, this.modelFn.prototype) || []; for (let q = 0; q < hasOne.length; q++) { let propName = hasOne[q]; let propMeta = Reflect.getOwnMetadata(has_one_1.HasOneMetadataSym, this.modelFn.prototype, propName); if (!propMeta) throw new Error(`Could not find model has-one metadata for property ${this.modelFn.name || this.modelFn}.${propName}.`); let fkey = propMeta.foreignKey; if (fkey && typeof fkey !== 'string') fkey = fkey.name; if (!fkey) throw new Error(`Could not get foreign key for has-one property ${this.modelFn.name || this.modelFn}.${propName}`); transforms.push({ type: 'has-one', foreignColumnName: fkey, fieldName: propName, pkName: 'id', foreignDb: () => DbImpl_1.getForeignModelDbImpl(propMeta.foreignModel) }); } let hasMany = Reflect.getOwnMetadata(has_many_1.ModelHasManyAssociationsSym, this.modelFn.prototype) || []; for (let q = 0; q < hasMany.length; q++) { let propName = hasMany[q]; let propMeta = Reflect.getOwnMetadata(has_many_1.HasManyMetadataSym, this.modelFn.prototype, propName); if (!propMeta) throw new Error(`Could not find model has-many metadata for property ${this.modelFn.name || this.modelFn}.${propName}.`); let fkey = propMeta.foreignKey; if (fkey && typeof fkey !== 'string') fkey = fkey.name; if (!fkey) throw new Error(`Could not get foreign key for has-many property ${this.modelFn.name || this.modelFn}.${propName}`); transforms.push({ type: 'has-many', foreignColumnName: fkey, fieldName: propName, pkName: 'id', foreignDb: () => DbImpl_1.getForeignModelDbImpl(propMeta.foreignModel) }); } return transforms; } composeAnd(query, $and) { if (!query) throw new Error(`Cannot compose $and. Invalid query: ${query}.`); if (Array.isArray(query)) query.push({ $and: $and }); else if (!query.$and) query.$and = $and; else if (Array.isArray(query.$and)) query.$and.push($and); else query.$and = [query.$and, $and]; } addPrefix(query, prefix) { if (!query) throw new Error(`Cannot add prefix to query. Invalid query: ${query}.`); let newQuery = {}; for (let key in query) { if (key === '$and' || key === '$or') newQuery[key] = query[key]; else newQuery[`$${prefix}.${key}$`] = query[key]; } return newQuery; } createTransformQueryWhere(transforms) { this.transformQueryWhere = (function (query, implicitIncludes = [], prefix = '') { if (!query) return [query, implicitIncludes]; query = _.clone(query); if (query['$and']) { let andVal = query['$and']; if (Array.isArray(andVal)) { for (let q = 0; q < andVal.length; q++) { if (typeof andVal[q] !== 'object') throw new Error(`Invalid $and query: [..., ${andVal[q]}, ...]`); [andVal[q], implicitIncludes] = this.transformQueryWhere(andVal[q], implicitIncludes, prefix); } } else if (typeof andVal === 'object') [andVal, implicitIncludes] = this.transformQueryWhere(andVal, implicitIncludes, prefix); else throw new Error(`Invalid $and query: ${andVal}`); query['$and'] = andVal; } if (query['$or']) { let orVal = query['$or']; if (Array.isArray(orVal)) { for (let q = 0; q < orVal.length; q++) { if (typeof orVal[q] !== 'object') throw new Error(`Invalid $or query: [..., ${orVal[q]}, ...]`); [orVal[q], implicitIncludes] = this.transformQueryWhere(orVal[q], implicitIncludes, prefix); } } else if (typeof orVal === 'object') [orVal, implicitIncludes] = this.transformQueryWhere(orVal, implicitIncludes, prefix); else throw new Error(`Invalid $or query: ${orVal}`); query['$or'] = orVal; } for (let q = 0; q < transforms.length; q++) { let transform = transforms[q]; let fieldVal = query[transform.fieldName]; let transformedPrefix = (prefix ? `${prefix}.` : '') + transform.fieldName; switch (transform.type) { case 'belongs-to': if (typeof fieldVal !== 'undefined') { if (fieldVal && fieldVal[transform.foreignPkName]) fieldVal = fieldVal[transform.foreignPkName]; if (fieldVal === null || typeof fieldVal === 'string' || typeof fieldVal === 'number') { query[transform.columnName] = fieldVal; delete query[transform.fieldName]; } else { let foreignDb = transform.foreignDb(); let $and; if (Array.isArray(fieldVal)) { let ids = []; for (let val of fieldVal) { if (typeof val === 'number') ids.push(val); else if (typeof val === 'object' && typeof val.id === 'number') ids.push(val.id); else throw new Error(`Failed to parse $in array for element: ${val}`); } $and = { [transform.columnName]: { $in: ids } }; } else { if (implicitIncludes.indexOf(transformedPrefix) === -1) implicitIncludes.push(transformedPrefix); [$and, implicitIncludes] = foreignDb.transformQueryWhere(fieldVal, implicitIncludes, transformedPrefix); } delete query[transform.fieldName]; this.composeAnd(query, $and); } } break; case 'has-one': case 'has-many': if (typeof fieldVal !== 'undefined') { if (implicitIncludes.indexOf(transformedPrefix) === -1) implicitIncludes.push(transformedPrefix); let foreignDb = transform.foreignDb(); let $and; if (typeof fieldVal === 'number') fieldVal = [fieldVal]; if (Array.isArray(fieldVal)) { let ids = []; for (let val of fieldVal) { if (typeof val === 'number') ids.push(val); else if (typeof val === 'object' && typeof val.id === 'number') ids.push(val.id); else throw new Error(`Failed to parse $in array for element: ${val}`); } fieldVal = { [transform.pkName]: ids }; } [$and, implicitIncludes] = foreignDb.transformQueryWhere(fieldVal, implicitIncludes, transformedPrefix); delete query[transform.fieldName]; this.composeAnd(query, $and); } break; default: throw new Error(`WTF? How did you get here?`); } } let transformed = prefix ? this.addPrefix(query, prefix) : query; return [transformed, implicitIncludes]; }).bind(this); } createTransformQueryInclude() { function getForeignDb(impl, field) { for (let i = 0; i < impl.transforms.length; i++) { if (field == impl.transforms[i].fieldName) return impl.transforms[i].foreignDb(); } return null; } this.transformQueryInclude = (function (fields) { if (!fields) return fields; let newFieldMap = new Map(); let newFields = []; let self = this; function addInclude(field) { if (newFieldMap.has(field)) return newFieldMap.get(field); let lastIdx = field.lastIndexOf('.'); if (lastIdx === -1) { let fdb = getForeignDb(self, field); if (!fdb) { self.logger.error('fields:', fields); self.logger.error('newFieldMap:', newFieldMap); self.logger.error('newFields:', newFields); throw new Error(`Cannot find field ${field} from include query`); } let val = { model: fdb.model, as: field }; newFields.push(val); newFieldMap.set(field, [fdb, val]); return [fdb, val]; } else { let assocName = field.substr(lastIdx + 1); let prevName = field.substr(0, lastIdx); let [prevDb, prevInclude] = addInclude(prevName); let fdb = getForeignDb(prevDb, assocName); if (!fdb) { self.logger.error('fields:', fields); self.logger.error('newFieldMap:', newFieldMap); self.logger.error('newFields:', newFields); throw new Error(`Cannot find field ${field} from include query`); } let val = { model: fdb.model, as: assocName }; if (!prevInclude.include) prevInclude.include = [val]; else prevInclude.include.push(val); newFieldMap.set(field, [fdb, val]); return [fdb, val]; } } fields.forEach(addInclude); return newFields; }).bind(this); } createTransformResult(transforms) { this.transformResult = (function (sql, result, implicitIncludes = []) { if (result === null) return result; else if (typeof result === 'undefined') throw new Error(`Result was undefined in DbImpl#transformResult!`); result = _.clone(result); for (let q = 0; q < transforms.length; q++) { let transform = transforms[q]; let foreignDb = transform.foreignDb(); let fieldVal = sql[transform.fieldName]; switch (transform.type) { case 'belongs-to': if (typeof fieldVal !== 'undefined') { let t = null; if (fieldVal) { t = foreignDb.wrapResult(fieldVal, []); } result[transform.fieldName] = transform.foreignDb().transformResult(fieldVal, t); } else if (typeof sql[transform.columnName] !== 'undefined') { result[transform.fieldName] = sql[transform.columnName]; delete result[transform.columnName]; } break; case 'has-one': if (typeof fieldVal !== 'undefined') { let t = null; if (fieldVal) { t = foreignDb.wrapResult(fieldVal, []); } result[transform.fieldName] = foreignDb.transformResult(fieldVal, t); } break; case 'has-many': if (typeof fieldVal !== 'undefined') { let ts = []; if (fieldVal) { ts = foreignDb.wrapResults(fieldVal, []); } result[transform.fieldName] = ts.map(t => foreignDb.transformResult(fieldVal, t)); } break; default: throw new Error(`WTF? How did you get here?`); } } return result; }).bind(this); } fromJson(json) { return this.wrapResult(json, []); } wrapResult(result, implicitIncludes) { let t = new this.modelFn(); this.copyVals(result, t); return this.transformResult(result, t, implicitIncludes); } wrapResults(results, implicitIncludes) { return results.map(result => this.wrapResult(result, implicitIncludes)); } }; __decorate([ transaction_decorator_1.Transaction(), __metadata("design:type", Function), __metadata("design:paramtypes", [typeof (_a = (typeof Sql !== "undefined" && Sql).WhereOptions) === "function" && _a || Object, Object, transaction_impl_1.TransactionImpl]), __metadata("design:returntype", Promise) ], DbImpl.prototype, "updateOrCreate", null); DbImpl = DbImpl_1 = __decorate([ name_decorator_1.Name('db-impl'), __metadata("design:paramtypes", [Object, typeof (_b = (typeof Sql !== "undefined" && Sql).Model) === "function" && _b || Object, sequelize_1.Sequelize, logger_1.Logger, transaction_service_1.TransactionService]) ], DbImpl); exports.DbImpl = DbImpl; var DbImpl_1, _a, _b; //# sourceMappingURL=db-impl.js.map