UNPKG

tabel

Version:

A simple orm for PostgreSQL which works with simple javascript objects and arrays

1,707 lines (1,439 loc) 67.9 kB
'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /* { // the table's name, is required name: null, // table properties props: { key: 'id', // default key column, can be ['user_id', 'post_id'] for composite keys uuid: false, // by default we don't assume that you use an auto generated db id perPage: 25, // standard batch size per page used by `forPage` method // forPage method uses offset // avoid that and use a keyset in prod (http://use-the-index-luke.com/no-offset) timestamps: false // set to `true` if you want auto timestamps or // timestamps: ['created_at', 'updated_at'] (these are defaults when `true`) // will be assigned in this order only }, // predefined scopes on the table scopes: {}, // predefined joints on the table joints: {}, // relations definitions for the table relations: {}, // table methods defintions methods: {} } */ var md5 = require('md5'); var uuid = require('uuid'); var isUsableObject = require('isusableobject'); var _require = require('lodash'), isArray = _require.isArray, isString = _require.isString, isDate = _require.isDate, isNumber = _require.isNumber, isFunction = _require.isFunction, assign = _require.assign, merge = _require.merge, toPlainObject = _require.toPlainObject; var Scope = require('./Scope'); var Scoper = require('./Scoper'); var Track = require('./Track'); var HasOne = require('./relations/HasOne'); var HasMany = require('./relations/HasMany'); var HasManyThrough = require('./relations/HasManyThrough'); var BelongsTo = require('./relations/BelongsTo'); var ManyToMany = require('./relations/ManyToMany'); var MorphOne = require('./relations/MorphOne'); var MorphMany = require('./relations/MorphMany'); var MorphTo = require('./relations/MorphTo'); var Table = function () { function Table(orm) { _classCallCheck(this, Table); this.orm = orm; this.scopeTrack = new Track(); if (this.orm.cache) { this.cache = this.orm.cache.hash('tabel__' + this.tableName()); } } /** * get the tablename * @return {string} the tableName */ _createClass(Table, [{ key: 'tableName', value: function tableName() { return this.name; } /** * knex.raw helper * @param {string} expr raw expression * @param {Array} bindings bindings for the expression * @return {knex.raw} raw expr */ }, { key: 'raw', value: function raw(expr) { var bindings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; return this.orm.raw(expr, bindings); } /** * just return a raw knex query for this table * @return {knex.query} a fresh knex query for table */ }, { key: 'rawQuery', value: function rawQuery() { return this.orm.knex(this.tableName()); } /** * get a new query instance for this table, with a few flags * set on the query object used by the orm * @return {knex.query} a fresh knex query for table with orm flags */ }, { key: 'newQuery', value: function newQuery() { return this.attachOrmNSToQuery(this.orm.knex(this.tableName())); } /** * attach _orm to knex query with required flags * @param {knex.query} q knex query * @return {knex.query} query with namespace */ }, { key: 'attachOrmNSToQuery', value: function attachOrmNSToQuery(q) { q._orm = { // used by cache processor cacheEnabled: false, cacheLifetime: null, // transaction being used on the query trx: null, // relations to be eagerloaded on the query eagerLoads: {}, // map to be applied to the query result maps: [] }; return q; } /** * get a fully scoped query, with flags for whether this is a count query or not. * the counting function sets columns it counts on smartly * @return {knex.query} table's query object with scopeTrack applied */ }, { key: 'query', value: function query() { var q = this.newQuery(); // apply the scopeTrack on the query this.scopeTrack.apply(q); if (this.scopeTrack.hasScope('select')) { return q; } else { return q.select(this.c('*')); } } /** * load columns of the table * @return {Promise} a promise which resolves when table columns have loaded */ }, { key: 'load', value: function load() { var _this = this; return this.newQuery().columnInfo().then(function (columns) { _this.orm.tableColumns.set(_this.tableName(), columns); return _this; }); } /** * get a promise of columns in the table * @return {Promise} promise containing array of table's columnNames */ }, { key: 'columns', value: function columns() { var _this2 = this; if (this.orm.tableColumns.has(this.tableName())) { return Promise.resolve(Object.keys(this.orm.tableColumns.get(this.tableName()))); } else { return this.load().then(function () { return _this2.columns(); }); } } /** * qualified column name helper * @param {mixed} col column or [columns] * @return {mixed} qualified column name(s) */ }, { key: 'c', value: function c(col) { var _this3 = this; if (isArray(col)) { return col.map(function (c) { return _this3.c(c); }); } if (isString(col)) { return col.indexOf('.') > -1 ? col : this.tableName() + '.' + col; } return col; } /** * get the key of the table * @return {mixed} get the key property */ }, { key: 'key', value: function key() { return this.props.key; } /** * qualified column of key(s) * @return {mixed} qualified key column(s) */ }, { key: 'keyCol', value: function keyCol() { return this.c(this.key()); } /** * chain new scopes to the table's scopeTrack * @param {function} closure op to be applied on the query * @param {string} label label of the scope, optional * @return {this} current instance */ }, { key: 'scope', value: function scope(closure) { var label = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'scope'; this.scopeTrack.push(new Scope(closure, label)); return this; } /** * chain a new joint to the table's scopeTrack. * will be run only once, if another with same label has run before * @param {function} closure op to be applied on the query * @param {string} label label of the joint, optional * @return {this} current instance */ }, { key: 'joint', value: function joint(closure) { var label = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'joint'; this.scopeTrack.push(new Scope(closure, label, true)); return this; } /** * fork the table and its scopes so that different scopes can be applied * to both instances further * @return {this.constructor} forked table instance */ }, { key: 'fork', value: function fork() { var forkedTable = new this.constructor(this.orm); forkedTable.scopeTrack = this.scopeTrack.fork(); return forkedTable; } /** * fork the table free of scopes * @return {this.constructor} forked and clean table instance */ }, { key: 'fresh', value: function fresh() { return new this.constructor(this.orm); } /** * helper to refer to other tables. carries over transaction * and settings * @param {string} tableName name of table you want * @return {Table} table instance for tableName */ }, { key: 'table', value: function table(tableName) { var q = this.query(); var tbl = this.orm.table(tableName); if (q._orm.trx !== null) { tbl.transacting(q._orm.trx); } return tbl; } /** * shorthand for table * @param {string} tableName name of table you want * @return {Table} table instance for tableName */ }, { key: 'tbl', value: function tbl(tableName) { return this.table(tableName); } /** * don't scope any rows * @return {this} current instance */ }, { key: 'whereFalse', value: function whereFalse() { return this.scope(function (q) { return q.whereRaw('?', [false]); }, 'whereFalse'); } /** * apply a where condition on the key(s) with scopes as planned * @param {mixed} val value(s) to match the key(s) against * @return {this} current instance * * whereKey(1) * whereKey({id: 1}) * whereKey({post_id: 1, tag_id: 2}) * whereKey([1,2,3,4]); * whereKey([{post_id: 1, tag_id: 2}, {post_id: 1, tag_id:2}]) */ }, { key: 'whereKey', value: function whereKey(val) { if (isArray(val)) { return this.whereIn(this.key(), val); } else { if (isArray(this.key())) { if (isUsableObject(val)) { val = toPlainObject(val); return this.where(this.key().reduce(function (conditions, k) { return assign(conditions, _defineProperty({}, k, val[k])); }, {})); } else { return this.where(this.key().reduce(function (conditions, k) { return assign(conditions, _defineProperty({}, k, val)); }, {})); } } else { return this.where(_defineProperty({}, this.key(), val)); } } } /** * apply an orWhere condition on the key(s) with scopes as planned * @param {mixed} val value(s) to match the key(s) against * @return {this} current instance * * orWhereKey(1) * orWhereKey({id: 1}) * orWhereKey({post_id: 1, tag_id: 2}) * orWhereKey([1,2,3,4]); * orWhereKey([{post_id: 1, tag_id: 2}, {post_id: 1, tag_id:2}]) */ }, { key: 'orWhereKey', value: function orWhereKey(val) { if (isArray(val)) { return this.orWhereIn(this.key(), val); } else { if (isArray(this.key())) { if (isUsableObject(val)) { val = toPlainObject(val); return this.orWhere(this.key().reduce(function (conditions, k) { return assign(conditions, _defineProperty({}, k, val[k])); }, {})); } else { return this.orWhere(this.key().reduce(function (conditions, k) { return assign(conditions, _defineProperty({}, k, val)); }, {})); } } else { return this.orWhere(_defineProperty({}, this.key(), val)); } } } /** * scope a where condition * @param {mixed} args conditions * @return {this} current instance */ }, { key: 'where', value: function where() { var _this4 = this; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (args.length === 1) { if (isUsableObject(args[0])) { var conditions = toPlainObject(args[0]); return Object.keys(conditions).reduce(function (table, field) { return table.where(field, '=', conditions[field]); }, this); } else if (isFunction(args[0])) { return this.scope(function (q) { return q.where(args[0]); }, 'where'); } } if (args.length === 2) { var field = args[0], val = args[1]; return this.where(field, '=', val); } if (args.length === 3) { var _field = args[0], op = args[1], _val = args[2]; switch (op.toLowerCase()) { case 'in': return this.whereIn(_field, _val); case 'not in': return this.whereNotIn(_field, _val); case 'between': return this.whereBetween(_field, _val); case 'not between': return this.whereNotBetween(_field, _val); default: return this.scope(function (q) { return q.where(_this4.c(_field), op, _val); }, 'where'); } } return this; } /** * scope an orWhere condition * @param {mixed} args conditions * @return {this} current instance */ }, { key: 'orWhere', value: function orWhere() { var _this5 = this; for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } if (args.length === 1) { if (isUsableObject(args[0])) { var conditions = toPlainObject(args[0]); return Object.keys(conditions).reduce(function (table, field) { return table.orWhere(field, '=', conditions[field]); }, this); } else if (isFunction(args[0])) { return this.scope(function (q) { return q.orWhere(args[0]); }, 'where'); } } if (args.length === 2) { var field = args[0], val = args[1]; return this.where(field, '=', val); } if (args.length === 3) { var _field2 = args[0], op = args[1], _val2 = args[2]; switch (op.toLowerCase()) { case 'in': return this.orWhereIn(_field2, _val2); case 'not in': return this.orWhereNotIn(_field2, _val2); case 'between': return this.orWhereBetween(_field2, _val2); case 'not between': return this.orWhereNotBetween(_field2, _val2); default: return this.scope(function (q) { return q.orWhere(_this5.c(_field2), op, _val2); }, 'orWhere'); } } return this; } /** * scope a whereNot condition * @param {mixed} args conditions * @return {this} current instance */ }, { key: 'whereNot', value: function whereNot() { var _this6 = this; for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } if (args.length === 1) { if (isUsableObject(args[0])) { var conditions = toPlainObject(args[0]); return Object.keys(conditions).reduce(function (table, field) { return table.whereNot(field, '=', conditions[field]); }, this); } else if (isFunction(args[0])) { return this.scope(function (q) { return q.whereNot(args[0]); }, 'whereNot'); } } if (args.length === 2) { var field = args[0], val = args[1]; return this.whereNot(field, '=', val); } if (args.length === 3) { var _field3 = args[0], op = args[1], _val3 = args[2]; switch (op.toLowerCase()) { case 'in': return this.whereNotIn(_field3, _val3); case 'not in': return this.whereIn(_field3, _val3); case 'between': return this.whereNotBetween(_field3, _val3); case 'not between': return this.whereBetween(_field3, _val3); default: return this.scope(function (q) { return q.whereNot(_this6.c(_field3), op, _val3); }, 'whereNot'); } } return this; } /** * scope an orWhereNot condition * @param {mixed} args conditions * @return {this} current instance */ }, { key: 'orWhereNot', value: function orWhereNot() { var _this7 = this; for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { args[_key4] = arguments[_key4]; } if (args.length === 1) { if (isUsableObject(args[0])) { var conditions = toPlainObject(args[0]); return Object.keys(conditions).reduce(function (table, field) { return table.orWhereNot(field, '=', conditions[field]); }, this); } else if (isFunction(args[0])) { return this.scope(function (q) { return q.orWhereNot(args[0]); }, 'orWhereNot'); } } if (args.length === 2) { var field = args[0], val = args[1]; return this.orWhereNot(field, '=', val); } if (args.length === 3) { var _field4 = args[0], op = args[1], _val4 = args[2]; switch (op.toLowerCase()) { case 'in': return this.orWhereNotIn(_field4, _val4); case 'not in': return this.orWhereIn(_field4, _val4); case 'between': return this.orWereNotBetween(_field4, _val4); case 'not between': return this.orWhereBetween(_field4, _val4); default: return this.scope(function (q) { return q.orWhereNot(_this7.c(_field4), op, _val4); }, 'orWhere'); } } return this; } /** * scope a whereIn condition * @param {string|[string]} field field name * @param {[mixed]} vals values to match against * @return {this} current instance * * whereIn('id', [1,2,3,4]) * whereIn(['user_id', 'post_id'], [{user_id: 1, post_id: 2}, {user_id: 3, post_id: 5}]) */ }, { key: 'whereIn', value: function whereIn(field) { var _this8 = this; var vals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; if (vals.length === 0) { return this.whereFalse(); } else { if (isArray(field)) { return this.whereRaw('(' + this.c(field).join(',') + ') in (' + vals.map(function () { return '(' + field.map(function () { return '?'; }).join(',') + ')'; }).join(',') + ')', vals.map(function (v) { return field.map(function (f) { return v[f]; }); }).reduce(function (all, item) { return all.concat(item); }, [])); } else { return this.scope(function (q) { return q.whereIn(_this8.c(field), vals); }, 'whereIn'); } } } /** * scope an orWhereIn condition * @param {string|[string]} field field name * @param {[mixed]} vals values to match against * @return {this} current instance * * orWhereIn('id', [1,2,3,4]) * orWhereIn(['user_id', 'post_id'], [{user_id: 1, post_id: 2}, {user_id: 3, post_id: 5}]) * */ }, { key: 'orWhereIn', value: function orWhereIn(field) { var _this9 = this; var vals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; if (vals.length === 0) { return this; } else { if (isArray(field)) { return this.orWhereRaw('(' + this.c(field).join(',') + ') in (' + vals.map(function () { return '(' + field.map(function () { return '?'; }).join(',') + ')'; }).join(',') + ')', vals.map(function (v) { return field.map(function (f) { return v[f]; }); }).reduce(function (all, item) { return all.concat(item); }, [])); } else { return this.scope(function (q) { return q.orWhereIn(_this9.c(field), vals); }, 'orWhereIn'); } } } /** * scope a whereNotIn condition * @param {string|[string]} field field name * @param {[mixed]} vals values to match against * @return {this} current instance */ }, { key: 'whereNotIn', value: function whereNotIn(field) { var _this10 = this; var vals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; if (vals.length === 0) { return this; } else { if (isArray(field)) { return this.whereRaw('(' + this.c(field).join(',') + ') not in (' + vals.map(function () { return '(' + field.map(function () { return '?'; }).join(',') + ')'; }).join(',') + ')', vals.map(function (v) { return field.map(function (f) { return v[f]; }); }).reduce(function (all, item) { return all.concat(item); }, [])); } else { return this.scope(function (q) { return q.whereNotIn(_this10.c(field), vals); }, 'whereNotIn'); } } } /** * scope a whereNotIn condition * @param {string|[string]} field field name * @param {[mixed]} vals values to match against * @return {this} current instance */ }, { key: 'orWhereNotIn', value: function orWhereNotIn(field) { var _this11 = this; var vals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; if (vals.length === 0) { return this; } else { if (isArray(field)) { return this.orWhereRaw('(' + this.c(field).join(',') + ') not in (' + vals.map(function () { return '(' + field.map(function () { return '?'; }).join(',') + ')'; }).join(',') + ')', vals.map(function (v) { return field.map(function (f) { return v[f]; }); }).reduce(function (all, item) { return all.concat(item); }, [])); } else { return this.scope(function (q) { return q.orWhereNotIn(_this11.c(field), vals); }, 'orWhereNotIn'); } } } /** * scope a whereNull condition * @param {string} field field name * @return {this} current instance */ }, { key: 'whereNull', value: function whereNull(field) { var _this12 = this; return this.scope(function (q) { return q.whereNull(_this12.c(field)); }, 'whereNull'); } /** * scope an orWhereNull condition * @param {string} field field name * @return {this} current instance */ }, { key: 'orWhereNull', value: function orWhereNull(field) { var _this13 = this; return this.scope(function (q) { return q.orWhereNull(_this13.c(field)); }, 'orWhereNull'); } /** * scope a whereNotNull condition * @param {string} field field name * @return {this} current instance */ }, { key: 'whereNotNull', value: function whereNotNull(field) { var _this14 = this; return this.scope(function (q) { return q.whereNotNull(_this14.c(field)); }, 'whereNotNull'); } /** * scope an orWhereNotNull condition * @param {string} field field name * @return {this} current instance */ }, { key: 'orWhereNotNull', value: function orWhereNotNull(field) { var _this15 = this; return this.scope(function (q) { return q.whereNotNull(_this15.c(field)); }, 'orWhereNotNull'); } /** * scope a whereBetween condition * @param {string} field field name * @param {[mixed]} range range of vals * @return {this} current instance */ }, { key: 'whereBetween', value: function whereBetween(field, _ref) { var _this16 = this; var _ref2 = _slicedToArray(_ref, 2), min = _ref2[0], max = _ref2[1]; return this.scope(function (q) { return q.whereBetween(_this16.c(field), [min, max]); }, 'whereBetween'); } /** * scope a orWhereBetween condition * @param {string} field field name * @param {[mixed]} range range of vals * @return {this} current instance */ }, { key: 'orWhereBetween', value: function orWhereBetween(field, _ref3) { var _this17 = this; var _ref4 = _slicedToArray(_ref3, 2), min = _ref4[0], max = _ref4[1]; return this.scope(function (q) { return q.orWhereBetween(_this17.c(field), [min, max]); }, 'orWhereBetween'); } /** * scope a whereNotBetween condition * @param {string} field field name * @param {[mixed]} range range of vals * @return {this} current instance */ }, { key: 'whereNotBetween', value: function whereNotBetween(field, _ref5) { var _this18 = this; var _ref6 = _slicedToArray(_ref5, 2), min = _ref6[0], max = _ref6[1]; return this.scope(function (q) { return q.whereNotBetween(_this18.c(field), [min, max]); }, 'whereNotBetween'); } /** * scope a orWhereNotBetween condition * @param {string} field field name * @param {[mixed]} range range of vals * @return {this} current instance */ }, { key: 'orWhereNotBetween', value: function orWhereNotBetween(field, _ref7) { var _this19 = this; var _ref8 = _slicedToArray(_ref7, 2), min = _ref8[0], max = _ref8[1]; return this.scope(function (q) { return q.orWhereNotBetween(_this19.c(field), [min, max]); }, 'orWhereNotBetween'); } /** * scope a whereRaw condition * @param {string} condition raw where condition * @param {[mixed]} bindings condition bindings * @return {this} current instance */ }, { key: 'whereRaw', value: function whereRaw(condition, bindings) { return this.scope(function (q) { return q.whereRaw(condition, bindings); }, 'whereRaw'); } /** * scope a orWhereRaw condition * @param {string} condition raw where condition * @param {[mixed]} bindings condition bindings * @return {this} current instance */ }, { key: 'orWhereRaw', value: function orWhereRaw(condition, bindings) { return this.scope(function (q) { return q.orWhereRaw(condition, bindings); }, 'orWhereRaw'); } /** * scope a transaction * @param {knex.transaction} trx the ongoing transaction * @return {this} current instance */ }, { key: 'transacting', value: function transacting(trx) { return this.scope(function (q) { q._orm.trx = trx;q.transacting(trx); }, 'transacting'); } /** * scope for a page number * @param {int} page page number * @param {int} perPage records per page * @return {this} current instance */ }, { key: 'forPage', value: function forPage(page, perPage) { page = parseInt(page, 10); page = page < 1 ? 1 : page; perPage = isNumber(perPage) && perPage > 0 ? perPage : this.props.perPage; var limit = perPage, offset = (page - 1) * perPage; return this.limit(limit).offset(offset); } /** * apply a scope which sets an offset * @param {int} offset offset to be set on the query * @return {this} current instance */ }, { key: 'offset', value: function offset(_offset) { return this.scope(function (q) { return q.offset(_offset); }, 'offset'); } /** * apply a scope which sets a limit on the query * @param {int} limit limit to be set on the query * @return {this} current instance */ }, { key: 'limit', value: function limit(_limit) { return this.scope(function (q) { return q.limit(_limit); }, 'limit'); } /** * apply a scope which sets an order on the query * @param {string} field column by which to order * @param {string} direction should be 'asc', 'desc' * @return {this} current instance */ }, { key: 'orderBy', value: function orderBy(field, direction) { var _this20 = this; return this.scope(function (q) { return q.orderBy(_this20.c(field), direction); }, 'orderBy'); } /** * apply a scope which sets an orderByRaw on the query * @param {string} sql sql for the order by * @param {array} bindings bindings for orderByRaw * @return {this} current instance */ }, { key: 'orderByRaw', value: function orderByRaw(sql, bindings) { return this.scope(function (q) { return q.orderByRaw(sql, bindings); }, 'orderByRaw'); } /** * apply a scope which sets a groupBy on the query * @param {...string} args columns to group by * @return {this} current instance */ }, { key: 'groupBy', value: function groupBy() { var _this21 = this; for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { args[_key5] = arguments[_key5]; } return this.scope(function (q) { return q.groupBy.apply(q, _toConsumableArray(_this21.c(args))); }, 'groupBy'); } /** * apply a scope which sets a groupByRaw on the query * @param {string} sql sql for the group by * @param {array} bindings bindings for groupBy * @return {this} current instance */ }, { key: 'groupByRaw', value: function groupByRaw(sql, bindings) { return this.scope(function (q) { return q.groupByRaw(sql, bindings); }, 'groupByRaw'); } /** * apply a scope which sets a having clause on the query * @param {string} col column * @param {op} op operator * @param {val} val value * @return {this} current instance */ }, { key: 'having', value: function having(col, op, val) { return this.scope(function (q) { return q.having(col, op, val); }, 'having'); } /** * apply a scope which sets a having clause on the query * @param {string} sql sql string for the having clause * @param {array} bindings bindings for the sql * @return {this} current instance */ }, { key: 'havingRaw', value: function havingRaw(sql, bindings) { return this.scope(function (q) { return q.havingRaw(sql, bindings); }, 'havingRaw'); } /** * apply a scope which sets a distinct clause on the query * @return {this} current instance */ }, { key: 'distinct', value: function distinct() { return this.scope(function (q) { return q.distinct(); }, 'distinct'); } /** * apply a scope to select some columns * @param {mixed} cols the columns to select * @return {this} current instance */ }, { key: 'select', value: function select() { var _this22 = this; for (var _len6 = arguments.length, cols = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { cols[_key6] = arguments[_key6]; } return this.scope(function (q) { q.select(_this22.c(cols)); }, 'select'); } /** * apply a scope to join a table with this Table * @param {string} tableName to join * @param {...mixed} args join conditions * @return {this} current instance */ }, { key: 'join', value: function join(tableName) { for (var _len7 = arguments.length, args = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) { args[_key7 - 1] = arguments[_key7]; } if (isFunction(args[0])) { var joiner = args[0]; return this.joint(function (q) { q.join(tableName, joiner); }, 'join' + tableName + md5(joiner.toString())); } else { return this.joint(function (q) { q.join.apply(q, [tableName].concat(args)); }, 'join' + tableName + md5(args.toString())); } } /** * apply a scope to leftJoin a table with this Table * @param {string} tableName to join * @param {...mixed} args join conditions * @return {this} current instance */ }, { key: 'leftJoin', value: function leftJoin(tableName) { for (var _len8 = arguments.length, args = Array(_len8 > 1 ? _len8 - 1 : 0), _key8 = 1; _key8 < _len8; _key8++) { args[_key8 - 1] = arguments[_key8]; } if (isFunction(args[0])) { var joiner = args[0]; return this.joint(function (q) { q.leftJoin(tableName, joiner); }, 'join' + tableName + md5(joiner.toString())); } else { return this.joint(function (q) { q.leftJoin.apply(q, [tableName].concat(args)); }, 'join' + tableName + md5(args.toString())); } } /** * apply a scope which enables a cache on the current query * @param {int} lifetime lifetime in milliseconds * @return {this} current instance */ }, { key: 'remember', value: function remember(lifetime) { return this.scope(function (q) { q._orm.cacheEnabled = true; q._orm.cacheLifetime = lifetime; }, 'cache'); } /** * apply a debug scope on the query * @param {Boolean} flag true/false * @return {this} current instance */ }, { key: 'debug', value: function debug() { var flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return this.scope(function (q) { return q.debug(flag); }); } /** * add a scope to eager-load various relations * @param {...mixed} eagerLoads relations to eager-load with constraints * @return {this} current instance */ }, { key: 'eagerLoad', value: function eagerLoad() { for (var _len9 = arguments.length, eagerLoads = Array(_len9), _key9 = 0; _key9 < _len9; _key9++) { eagerLoads[_key9] = arguments[_key9]; } eagerLoads = this.parseEagerLoads(eagerLoads); return this.scope(function (q) { assign(q._orm.eagerLoads, eagerLoads); }, 'eagerLoad'); } /** * parse and eagerLoads argument * @param {mixed} eagerLoads eagerLoads to be parsed, {} or [] * @return {this} current instance */ }, { key: 'parseEagerLoads', value: function parseEagerLoads(eagerLoads) { // if eagerLoads is of the form ['foo', 'foo.bar', {'foo.baz': (t) => { t.where('active', true); }}] // then use a place-holder constraint for 'foo' & 'foo.bar' // and reduce to form {'rel1': constraint1, 'rel2': constraint2} if (isArray(eagerLoads)) { return this.parseEagerLoads(eagerLoads.map(function (eagerLoad) { if (isUsableObject(eagerLoad)) { return toPlainObject(eagerLoad); } else { return _defineProperty({}, eagerLoad, function () {}); } }).reduce(function (eagerLoadsObject, eagerLoad) { return assign(eagerLoadsObject, eagerLoad); }, {})); } // processing the object form of eagerLoads return Object.keys(eagerLoads).reduce(function (allRelations, relation) { if (relation.indexOf('.') === -1) { return allRelations.concat([relation]); } else { // foo.bar.baz // -> // foo // foo.bar // foo.bar.baz return allRelations.concat(relation.split('.').reduce(function (relationParts, part) { return relationParts.concat([relationParts.slice(-1).concat([part]).join('.')]); }, [])); } }, []).reduce(function (parsedEagerLoads, relation) { if (relation in eagerLoads) { return assign(parsedEagerLoads, _defineProperty({}, relation, eagerLoads[relation])); } else { return assign(parsedEagerLoads, _defineProperty({}, relation, function () {})); } }, {}); } /** * clear table's cache * @return {Promise} promise for clearing table's cache */ }, { key: 'clearCache', value: function clearCache() { var _this23 = this; return this.cache.clear().then(function () { return _this23; }); } /** * uncache the query chain * @param {options} options whether we are forgetting count or rows * @return {Promise} current instance */ }, { key: 'forget', value: function forget() { var _this24 = this; var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { count: false }; var q = options.count ? this.countQuery() : this.query(); var key = this.queryCacheKey(q); return this.cache.del(key).then(function () { return _this24; }); } /** * process the result of a query, strip table's name, * replace '.' with '__' in columns with different table-prefix, * parse count if the query is a count query * @param {mixed} result result fetched for the query * @param {options} options whether we are fetching count results * @return {mixed} the processed result */ }, { key: 'processResult', value: function processResult(result, options) { var _this25 = this; var _ref10 = isUsableObject(options) ? toPlainObject(options) : { count: false }, count = _ref10.count; if (count === true) { // result[0].count is how knex gives count query results if (isPostgres(this.orm)) { return parseInt(result[0].count, 10); } else if (isMysql(this.orm)) { return result[0]['count(*)']; } else { throw new UnsupportedDbError(); } } else if (isArray(result)) { // processing an array of response return result.map(function (row) { return _this25.processResult(row, { count: count }); }); } else if (isUsableObject(result)) { // processing individual model results result = toPlainObject(result); return Object.keys(result).reduce(function (processed, key) { if (key.indexOf('.') > -1) { if (key.indexOf(_this25.tableName()) === 0) { return assign(processed, _defineProperty({}, key.split('.')[1], result[key])); } else { return assign(processed, _defineProperty({}, key.split('.').join('__'), result[key])); } } else { return assign(processed, _defineProperty({}, key, result[key])); } }, {}); } else { // processing other random values return result; } } /** * eager load relations for an array of models * @param {array} models of models * @param {object} eagerLoads a processed eagerLoads with constraints * @return {Promise} promise which resilves when all relations have loaded */ }, { key: 'loadRelations', value: function loadRelations(models, eagerLoads) { var _this26 = this; if (isArray(models) && models.length === 0) { // don't do anything for empty values return Promise.resolve(models); } return Promise.all(Object.keys(eagerLoads) // filter all top level relations .filter(function (relation) { return relation.indexOf('.') === -1; }).map(function (relation) { // check for the relation actually being there if (!_this26.definedRelations.has(relation)) { throw new Error('invalid relation ' + relation); } return _this26[relation]().eagerLoad(_this26.subEagerLoads(relation, eagerLoads)).constrain(eagerLoads[relation]).load(models); })).then(function (results) { return results.reduce(function (mergedResult, result) { return merge(mergedResult, result); }, models); }); } /** * get the subEagerLoads for a relation, given a set of eagerLoads * @param {string} relation name of the relation * @param {object} eagerLoads {relationName: cosntraint} form eagerLoads * @return {object} subEagerLoads of relation with constraints */ }, { key: 'subEagerLoads', value: function subEagerLoads(relation, eagerLoads) { return Object.keys(eagerLoads).filter(function (relationName) { return relationName.indexOf(relation) === 0 && relationName !== relation; }).map(function (relationName) { return relationName.split('.').slice(1).join('.'); }).reduce(function (subEagerLoads, subRelationName) { return assign(subEagerLoads, _defineProperty({}, subRelationName, eagerLoads[relation + '.' + subRelationName])); }, {}); } /** * returns key which will be used to cache a query's result * @param {knex.query} q the query which is being used to fetch result * @return {string} md5 hash of the query */ }, { key: 'queryCacheKey', value: function queryCacheKey(q) { var queryStr = q.toString(); var eagerLoadsStr = Object.keys(q._orm.eagerLoads).map(function (rel) { return { rel: rel, constraint: q._orm.eagerLoads[rel] }; }).slice(0).sort(function (a, b) { return a.rel < b.rel ? -1 : 1; }).map(function (_ref11) { var rel = _ref11.rel, constraint = _ref11.constraint; return rel + ':' + constraint.toString(); }).join('-'); return [queryStr, eagerLoadsStr].map(function (s) { return md5(s); }).join('-'); } /** * get the first row for the scoped query * @param {...mixed} args conditions for scoping the query * @return {Promise} promise which resolves the result */ }, { key: 'first', value: function first() { var _limit2; return (_limit2 = this.limit(1)).all.apply(_limit2, arguments).then(function (result) { return result.length > 0 ? result[0] : null; }); } /** * get all rows from the scoped query * @param {...mixed} args conditions for scoping the query * @return {Promise} promise which resolves the result */ }, { key: 'all', value: function all() { var _this27 = this; if (arguments.length === 1) { return this.where(arguments.length <= 0 ? undefined : arguments[0]).all(); } else if (arguments.length >= 2) { return this.where.apply(this, arguments).all(); } var q = this.query(); // 1. Try to fetch results from cache // 2. If there are any cached results, return results from cache, apply map to them, and return // 3. If there aren't any cached results, run the query, get results, load relations // 4. Cache the resulting dataset if needed // 5. Apply maps to resulting dataset, and return if (q._orm.cacheEnabled) { var cacheKey = this.queryCacheKey(q); return this.cache.get(cacheKey, null).then(function (models) { if (models === null) { return q.then(function (models) { return _this27.processResult(models); }).then(function (models) { return _this27.loadRelations(models, q._orm.eagerLoads); }).then(function (models) { return _this27.cache.set(cacheKey, models, q._orm.cacheLifetime).then(function () { return models; }); }).then(function (models) { return models.map(function (m) { return q._orm.maps.reduce(function (m, map) { return map(m); }, m); }); }); } else { return models.map(function (m) { return q._orm.maps.reduce(function (m, map) { return map(m); }, m); }); } }); } else { return q.then(function (models) { return _this27.processResult(models); }).then(function (models) { return _this27.loadRelations(models, q._orm.eagerLoads); }).then(function (models) { return models.map(function (m) { return q._orm.maps.reduce(function (m, map) { return map(m); }, m); }); }); } } /** * get query that'll be executed for counts * @return {knex.query} query to be executed to get count of scoped rows */ }, { key: 'countQuery', value: function countQuery() { var _this28 = this; return this.attachOrmNSToQuery(this.orm.knex.count('*').from(function (q) { q.from(_this28.tableName()); _this28.scopeTrack.apply(q); if (!_this28.scopeTrack.hasScope('select')) { q.select(_this28.c('*')); } q.as('t1'); })); } /** * get count of the scoped result set. works well * even when you have groupBy etc in your queries * @param {...mixed} args conditions for scoping the query * @return {int} count of the result set */ }, { key: 'count', value: function count() { var _this29 = this; if (arguments.length === 1) { return this.where(arguments.length <= 0 ? undefined : arguments[0]).count(); } else if (arguments.length >= 2) { return this.where.apply(this, arguments).count(); } var q = this.countQuery(); if (q._orm.cacheEnabled) { var cacheKey = this.queryCacheKey(q); return this.cache.get(cacheKey, null).then(function (result) { if (result === null) { return q.then(function (result) { return _this29.cache.set(cacheKey, result, q._orm.cacheLifetime).then(function () { return result; }); }).then(function (result) { return _this29.processResult(result, { count: true }); }); } else { return _this29.processResult(result, { count: true }); } }); } else { return q.then(function (result) { return _this29.processResult(result, { count: true }); }); } } /** * check whether an 'orderBy' or an 'orderByRaw' clause exists * in the this.scopeTrack. If not, return a fork with an orderBy clause * based on key-columns * @return {Table} ordered fork of current table */ }, { key: 'orderedFork', value: function orderedFork() { var _this30 = this; var labels = this.scopeTrack.scopes.map(function (_ref12) { var label = _ref12.label; return label; }); if (labels.indexOf('orderBy') > -1 || labels.indexOf('orderByRaw') > -1) { return this.fork(); } else { if (isPostgres(this.orm)) { return this.fork().orderByRaw('(' + (isArray(this.key()) ? this.key() : [this.key()]).map(function (c) { return _this30.c(c) + '::text'; }).join(' || \'-\' || ') + ') desc'); } else if (isMysql(this.orm)) { return this.fork().orderByRaw('concat(' + (isArray(this.key()) ? this.key() : [this.key()]).ma