UNPKG

objection

Version:
1,416 lines (1,185 loc) 32.3 kB
import _ from 'lodash'; import AjvValidator from './AjvValidator'; import QueryBuilder from '../queryBuilder/QueryBuilder'; import inheritModel from './inheritModel'; import RelationExpression from '../queryBuilder/RelationExpression'; import {visitModels} from './modelVisitor'; import {inherits} from '../utils/classUtils'; import {inheritHiddenData} from '../utils/hiddenData'; import hiddenData from '../utils/decorators/hiddenData'; import memoize from '../utils/decorators/memoize'; import Relation from '../relations/Relation'; import HasOneRelation from '../relations/hasOne/HasOneRelation'; import HasManyRelation from '../relations/hasMany/HasManyRelation'; import ManyToManyRelation from '../relations/manyToMany/ManyToManyRelation'; import BelongsToOneRelation from '../relations/belongsToOne/BelongsToOneRelation'; import HasOneThroughRelation from '../relations/hasOneThrough/HasOneThroughRelation'; import InstanceFindOperation from '../queryBuilder/operations/InstanceFindOperation'; import InstanceInsertOperation from '../queryBuilder/operations/InstanceInsertOperation'; import InstanceUpdateOperation from '../queryBuilder/operations/InstanceUpdateOperation'; import InstanceDeleteOperation from '../queryBuilder/operations/InstanceDeleteOperation'; import JoinEagerOperation from '../queryBuilder/operations/JoinEagerOperation'; import WhereInEagerOperation from '../queryBuilder/operations/WhereInEagerOperation'; const JoinEagerAlgorithm = () => { return new JoinEagerOperation('eager'); }; const WhereInEagerAlgorithm = () => { return new WhereInEagerOperation('eager'); }; /** * @typedef {Object} ModelOptions * * @property {boolean} [patch] * @property {boolean} [skipValidation] * @property {Model} [old] */ export default class Model { static QueryBuilder = QueryBuilder; static RelatedQueryBuilder = QueryBuilder; static HasOneRelation = HasOneRelation; static HasManyRelation = HasManyRelation; static ManyToManyRelation = ManyToManyRelation; static BelongsToOneRelation = BelongsToOneRelation; static HasOneThroughRelation = HasOneThroughRelation; static JoinEagerAlgorithm = JoinEagerAlgorithm; static WhereInEagerAlgorithm = WhereInEagerAlgorithm; /** * @type {string} */ static tableName = null; /** * @type {Object} */ static jsonSchema = null; /** * @type {string|Array.<string>} */ static idColumn = 'id'; /** * @type {string} */ static uidProp = '#id'; /** * @type {string} */ static uidRefProp = '#ref'; /** * @type {string} */ static dbRefProp = '#dbRef'; /** * @type {RegExp} */ static propRefRegex = /#ref{([^\.]+)\.([^}]+)}/g; /** * @type {Array.<string>} */ static jsonAttributes = null; /** * @type {Array.<string>} */ static virtualAttributes = null; /** * @type {Object.<string, RelationMapping>} */ static relationMappings = null; /** * @type {Array.<string>} */ static modelPaths = []; /** * @type {boolean} */ static pickJsonSchemaProperties = true; /** * @type {Constructor.<? extends EagerOperation>} */ static defaultEagerAlgorithm = WhereInEagerAlgorithm; /** * @type {object} */ static defaultEagerOptions = null; /** * @private */ static $$knex = null; /** * @param {*=} id * @returns {*} */ $id(id) { if (arguments.length > 0) { return setId(this, arguments[0]); } else { return getId(this); } } /** * @returns {knex} */ $knex() { return this.constructor.knex(); } /** * @returns {knex} */ $transaction() { return this.constructor.transaction(); } /** * @param {Transaction=} trx * @returns {QueryBuilder} */ $query(trx) { const ModelClass = this.constructor; return ModelClass.QueryBuilder .forClass(ModelClass) .transacting(trx) .findOperationFactory(() => { return new InstanceFindOperation('find', {instance: this}); }) .insertOperationFactory(() => { return new InstanceInsertOperation('insert', {instance: this}); }) .updateOperationFactory(() => { return new InstanceUpdateOperation('update', {instance: this}); }) .patchOperationFactory(() => { return new InstanceUpdateOperation('patch', {instance: this, modelOptions: {patch: true}}); }) .deleteOperationFactory(() => { return new InstanceDeleteOperation('delete', {instance: this}); }) .relateOperationFactory(() => { throw new Error('`relate` makes no sense in this context'); }) .unrelateOperationFactory(() => { throw new Error('`unrelate` makes no sense in this context'); }); } /** * @param {string} relationName * @param {Transaction=} trx * @returns {QueryBuilder} */ $relatedQuery(relationName, trx) { const ModelClass = this.constructor; const relation = ModelClass.getRelation(relationName); const RelatedModelClass = relation.relatedModelClass; return ModelClass.RelatedQueryBuilder .forClass(RelatedModelClass) .transacting(trx) .findOperationFactory(builder => { return relation.find(builder, [this]); }) .insertOperationFactory(builder => { return relation.insert(builder, this); }) .updateOperationFactory(builder => { return relation.update(builder, this); }) .patchOperationFactory(builder => { return relation.patch(builder, this); }) .deleteOperationFactory(builder => { return relation.delete(builder, this); }) .relateOperationFactory(builder => { return relation.relate(builder, this); }) .unrelateOperationFactory(builder => { return relation.unrelate(builder, this); }); } /** * @param {string|RelationExpression} relationExpression * @param {Object.<string, function(QueryBuilder)>=} filters * @returns {QueryBuilder} */ $loadRelated(relationExpression, filters) { return this.constructor.loadRelated(this, relationExpression, filters); } /** * @param {Constructor.<Model>=} filterConstructor * @param {function(Model)} callback * @return {Model} */ $traverse(filterConstructor, callback) { if (_.isUndefined(callback)) { callback = filterConstructor; filterConstructor = null; } this.constructor.traverse(filterConstructor, this, callback); return this; } /** * @param {Object} jsonSchema * @param {Object} json * @param {ModelOptions=} options * @return {Object} */ $beforeValidate(jsonSchema, json, options) { /* istanbul ignore next */ return jsonSchema; } /** * @param {Object=} json * @param {ModelOptions=} options * @throws {ValidationError} * @return {Object} */ $validate(json = this, options = {}) { if (json instanceof Model) { // Strip away relations and other internal stuff. json = cloneModel(json, true, true); } if (options.skipValidation) { return json; } const validator = this.constructor.getValidator(); const args = { options: options, model: this, json: json, ctx: Object.create(null) }; validator.beforeValidate(args); json = validator.validate(args); validator.afterValidate(args); return json; } /** * @param {Object=} json * @param {ModelOptions=} options */ $afterValidate(json, options) { // Do nothing by default. } /** * @param {Object} json * @return {Object} */ $parseDatabaseJson(json) { const jsonAttr = this.constructor.getJsonAttributes(); if (jsonAttr.length) { for (let i = 0, l = jsonAttr.length; i < l; ++i) { const attr = jsonAttr[i]; const value = json[attr]; if (_.isString(value)) { const parsed = tryParseJson(value); // tryParseJson returns undefined if parsing failed. if (parsed !== undefined) { json[attr] = parsed; } } } } return json; } /** * @param {Object} json * @return {Object} */ $formatDatabaseJson(json) { const jsonAttr = this.constructor.getJsonAttributes(); if (jsonAttr.length) { for (let i = 0, l = jsonAttr.length; i < l; ++i) { const attr = jsonAttr[i]; const value = json[attr]; if (_.isObject(value)) { json[attr] = JSON.stringify(value); } } } return json; } /** * @param {Object} json * @param {ModelOptions=} options * @return {Object} */ $parseJson(json, options) { return json; } /** * @param {Object} json * @return {Object} */ $formatJson(json) { return json; } /** * @param {Object} json * @param {ModelOptions=} options * @returns {Model} * @throws ValidationError */ $setJson(json, options = {}) { json = json || {}; if (!_.isObject(json) || _.isString(json) || _.isNumber(json) || _.isDate(json) || _.isArray(json) || _.isFunction(json) || _.isTypedArray(json) || _.isRegExp(json)) { throw new Error('You should only pass objects to $setJson method. ' + '$setJson method was given an invalid value ' + json); } json = this.$parseJson(json, options); json = this.$validate(json, options); // TODO Move to bottom. this.$set(json); const relations = this.constructor.getRelationArray(); // Parse relations into Model instances. for (let i = 0, l = relations.length; i < l; ++i) { const relation = relations[i]; const relationName = relation.name; if (_.has(json, relationName)) { const relationJson = json[relationName]; if (Array.isArray(relationJson)) { this[relationName] = relation.relatedModelClass.ensureModelArray(relationJson, options); } else if (relationJson) { this[relationName] = relation.relatedModelClass.ensureModel(relationJson, options); } else { this[relationName] = null; } } } } /** * @param {Object} json * @returns {Model} */ $setDatabaseJson(json) { json = this.$parseDatabaseJson(json); if (json) { const keys = Object.keys(json); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; this[key] = json[key]; } } return this; } /** * @param {Object} obj * @returns {Model} */ $set(obj) { if (obj) { const keys = Object.keys(obj); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; const value = obj[key]; if (key.charAt(0) !== '$' && typeof value !== 'function') { this[key] = value; } } } return this; } /** * @param {boolean=} shallow */ $toJson(shallow) { if (shallow) { return this.$$toJson(false, this.constructor.getRelations(), null); } else { return this.$$toJson(false, null, null); } } toJSON() { return this.$toJson(); } /** * @override */ $toDatabaseJson() { const jsonSchema = this.constructor.getJsonSchema(); if (jsonSchema && this.constructor.pickJsonSchemaProperties) { return this.$$toJson(true, null, jsonSchema.properties); } else { return this.$$toJson(true, this.constructor.getRelations(), null); } } /** * @param {Object} queryContext * @returns {Promise|*} */ $beforeInsert(queryContext) {} /** * @param {Object} queryContext * @returns {Promise|*} */ $afterInsert(queryContext) {} /** * @param {ModelOptions} opt * @param {QueryBuilderContext} queryContext * @returns {Promise|*} */ $beforeUpdate(opt, queryContext) {} /** * @param {ModelOptions} opt * @param {QueryBuilderContext} queryContext * @returns {Promise|*} */ $afterUpdate(opt, queryContext) {} /** * @param {QueryBuilderContext} queryContext * @returns {Promise|*} */ $afterGet(queryContext) {} /** * @param {QueryBuilderContext} queryContext * @returns {Promise|*} */ $beforeDelete(queryContext) {} /** * @param {QueryBuilderContext} queryContext * @returns {Promise|*} */ $afterDelete(queryContext) {} /** * @param {string|Array.<string>|Object.<string, boolean>} keys * @returns {Model} */ $omit() { if (arguments.length === 1 && _.isObject(arguments[0])) { const keys = arguments[0]; if (Array.isArray(keys)) { omitArray(this, keys); } else { omitObject(this, keys); } } else { omitArray(this, _.toArray(arguments)); } return this; } /** * @param {string|Array.<string>|Object.<string, boolean>} keys * @returns {Model} `this` for chaining. */ $pick() { if (arguments.length === 1 && _.isObject(arguments[0])) { const keys = arguments[0]; if (Array.isArray(keys)) { pickArray(this, keys); } else { pickObject(this, keys); } } else { pickArray(this, _.toArray(arguments)); } return this; } /** * @param {Array.<string>} props * @return {Array.<*>} */ $values() { if (arguments.length === 0) { return _.values(this); } else { const args = (arguments.length === 1 && Array.isArray(arguments[0])) ? arguments[0] : arguments; switch (args.length) { case 1: return [this[args[0]]]; case 2: return [this[args[0]], this[args[1]]]; case 3: return [this[args[0]], this[args[1]], this[args[2]]]; default: { const ret = new Array(args.length); for (let i = 0, l = args.length; i < l; ++i) { ret[i] = this[args[i]]; } return ret; } } } } /** * @param {Array.<string>} props * @return {string} */ $propKey(props) { switch (props.length) { case 1: return this[props[0]] + ''; case 2: return this[props[0]] + ',' + this[props[1]]; case 3: return this[props[0]] + ',' + this[props[1]] + ',' + this[props[2]]; default: { let key = ''; for (let i = 0, l = props.length; i < l; ++i) { key += this[props[i]] + ((i < props.length - 1) ? ',' : ''); } return key; } } } /** * @param {boolean} shallow * @return {Model} */ $clone(shallow) { return cloneModel(this, shallow, false); } /** * @param {Array.<string>=} keys * @returns {Array.<string>} */ @hiddenData({name: 'omitFromJson', append: true}) $omitFromJson(keys) {} /** * @param {Array.<string>=} keys * @returns {Array.<string>} */ @hiddenData({name: 'omitFromDatabaseJson', append: true}) $omitFromDatabaseJson(keys) {} /** * @protected */ $$toJson(createDbJson, omit, pick) { let json = toJsonImpl(this, createDbJson, omit, pick); if (createDbJson) { return this.$formatDatabaseJson(json); } else { return this.$formatJson(json); } } /** * @param {function=} subclassConstructor * @return {Constructor.<Model>} */ static extend(subclassConstructor) { if (_.isEmpty(subclassConstructor.name)) { throw new Error('Each Model subclass constructor must have a name'); } inherits(subclassConstructor, this); return subclassConstructor; } /** * @param {Object=} json * @param {ModelOptions=} options * @returns {Model} * @throws ValidationError */ static fromJson(json, options) { let model = new this(); model.$setJson(json || {}, options); return model; } /** * @param {Object=} json * @returns {Model} */ static fromDatabaseJson(json) { let model = new this(); model.$setDatabaseJson(json || {}); return model; } /** * @param {Object} obj * @param {string} prop */ static omitImpl(obj, prop) { delete obj[prop]; } /** * @return {Validator} */ static createValidator() { return new AjvValidator({ onCreateAjv: (ajv) => { /* Do Nothing by default */ }, options: { allErrors: true, validateSchema: false, ownProperties: true, v5: true } }); } /** * @return {Validator} */ @memoize static getValidator() { return this.createValidator(); } /** * @return {Object} */ @memoize static getJsonSchema() { // Memoized getter in case jsonSchema is a getter property (usually is with ES6). return this.jsonSchema; } /** * @param {string} columnName * @returns {string} */ @memoize static columnNameToPropertyName(columnName) { let model = new this(); let addedProps = _.keys(model.$parseDatabaseJson({})); let row = {}; row[columnName] = null; let props = _.keys(model.$parseDatabaseJson(row)); let propertyName = _.first(_.difference(props, addedProps)); return propertyName || null; } /** * @param {string} propertyName * @returns {string} */ @memoize static propertyNameToColumnName(propertyName) { let model = new this(); let addedCols = _.keys(model.$formatDatabaseJson({})); let obj = {}; obj[propertyName] = null; let cols = _.keys(model.$formatDatabaseJson(obj)); let columnName = _.first(_.difference(cols, addedCols)); return columnName || null; } /** * @param {Transaction=} trx * @returns {QueryBuilder} */ static query(trx) { const ModelClass = this; return ModelClass.QueryBuilder .forClass(ModelClass) .transacting(trx) .relateOperationFactory(() => { throw new Error('`relate` makes no sense in this context'); }) .unrelateOperationFactory(() => { throw new Error('`unrelate` makes no sense in this context'); }); } /** * @param {knex=} knex * @returns {knex} */ static knex(knex) { if (arguments.length) { this.$$knex = knex; } else { return this.$$knex; } } /** * @returns {knex} */ static transaction() { return this.knex(); } /** * @return {Raw} */ static raw() { const knex = this.knex(); return knex.raw.apply(knex, arguments); } /** * @return {Object} */ static fn() { const knex = this.knex(); return knex.fn; } /** * @return {Formatter} */ static formatter() { return this.knex().client.formatter(); } /** * @returns {knex.QueryBuilder} */ static knexQuery() { return this.knex().table(this.tableName); } /** * @returns {string} */ static uniqueTag() { return this.tableName; } /** * @param {knex} knex * @returns {Constructor.<Model>} */ static bindKnex(knex) { const ModelClass = this; if (!knex.$$objection) { Object.defineProperty(knex, '$$objection', { enumerable: false, writable: false, value: { boundModels: Object.create(null) } }); } // Check if this model class has already been bound to the given knex. if (knex.$$objection.boundModels[ModelClass.uniqueTag()]) { return knex.$$objection.boundModels[ModelClass.uniqueTag()]; } // Create a new subclass of this class. const BoundModelClass = inheritModel(ModelClass); // The bound model is equal to the source model in every way. We want to copy // the hidden data as-is from the source so that we don't get the performance // penalty of calculating all memoized etc. values again. inheritHiddenData(ModelClass, BoundModelClass); BoundModelClass.knex(knex); knex.$$objection.boundModels[ModelClass.uniqueTag()] = BoundModelClass; const boundRelations = Object.create(null); const relations = ModelClass.getRelationArray(); for (let i = 0, l = relations.length; i < l; ++i) { const relation = relations[i]; boundRelations[relation.name] = relation.bindKnex(knex); } BoundModelClass.relations = boundRelations; BoundModelClass.relationArray = _.values(boundRelations); return BoundModelClass; } /** * @param {knex} trx * @returns {Constructor.<Model>} */ static bindTransaction(trx) { return this.bindKnex(trx); } /** * @param {Model|Object} model * @param {ModelOptions=} options * @returns {Model} */ static ensureModel(model, options) { const ModelClass = this; if (!model) { return null; } if (model instanceof ModelClass) { return model; } else { return ModelClass.fromJson(model, options); } } /** * @param {Array.<Model|Object>} input * @param {ModelOptions=} options * @returns {Array.<Model>} */ static ensureModelArray(input, options) { if (!input) { return []; } if (Array.isArray(input)) { let models = new Array(input.length); for (var i = 0, l = input.length; i < l; ++i) { models[i] = this.ensureModel(input[i], options); } return models; } else { return [this.ensureModel(input, options)]; } } /** * @returns {Array.<string>} */ @memoize static getIdColumnArray() { if (Array.isArray(this.idColumn)) { return this.idColumn; } else { return [this.idColumn]; } } /** * @returns {string|Array.<string>} */ @memoize static getFullIdColumn() { if (Array.isArray(this.idColumn)) { return this.idColumn.map(col => this.tableName + '.' + col); } else { return this.tableName + '.' + this.idColumn; } } /** * @returns {Array.<string>} */ @memoize static getIdPropertyArray() { return this.getIdColumnArray().map(col => idColumnToIdProperty(this, col)); } /** * @returns {string|Array.<string>} */ @memoize static getIdProperty() { if (Array.isArray(this.idColumn)) { return this.idColumn.map(col => idColumnToIdProperty(this, col)); } else { return idColumnToIdProperty(this, this.idColumn); } } /** * @private */ @hiddenData() static get relations() {} /** * @private */ @hiddenData() static get relationArray() {} /** * @private */ @hiddenData() static set relations(value) {} /** * @private */ @hiddenData() static set relationArray(value) {} /** * @return {Object.<string, Relation>} */ static getRelations() { let relations = this.relations; if (!relations) { relations = _.reduce(_.result(this, 'relationMappings'), (relations, mapping, relationName) => { relations[relationName] = new mapping.relation(relationName, this); relations[relationName].setMapping(mapping); return relations; }, Object.create(null)); this.relations = relations; } return relations; } /** * @return {Array.<Relation>} */ static getRelationArray() { let relationArray = this.relationArray; if (!relationArray) { relationArray = _.values(this.getRelations()); this.relationArray = relationArray; } return relationArray; } /** * @return {Relation} */ static getRelation(name) { const relation = this.getRelations()[name]; if (!relation) { throw new Error(`A model class (tableName = ${this.tableName}) doesn't have relation ${name}`); } return relation; } /** * @param {Array.<Model|Object>} $models * @param {string|RelationExpression} expression * @param {Object.<string, function(QueryBuilder)>=} filters * @returns {QueryBuilder} */ static loadRelated($models, expression, filters) { return this .query() .resolve(this.ensureModelArray($models)) .findOptions({dontCallAfterGet: true}) .eager(expression, filters) .runAfter(function (models) { return Array.isArray($models) ? models : models[0]; }); } /** * @param {Constructor.<Model>=} filterConstructor * @param {Model|Array.<Model>} models * @param {function(Model, Model, string)} traverser * @return {Model} */ static traverse(filterConstructor, models, traverser) { filterConstructor = filterConstructor || null; if (_.isUndefined(traverser)) { traverser = models; models = filterConstructor; filterConstructor = null; } if (!_.isFunction(traverser)) { throw new Error('traverser must be a function'); } if (!models) { return; } const modelClass = Array.isArray(models) ? models[0].constructor : models.constructor; visitModels(models, modelClass, (model, modelClass, parent, relation) => { if (!filterConstructor || model instanceof filterConstructor) { traverser(model, parent, relation && relation.name); } }); return this; } /** * @protected * @returns {Array.<string>} */ static getJsonAttributes() { // If the jsonAttributes property is not set, try to create it based // on the jsonSchema. All properties that are objects or arrays must // be converted to JSON. if (!this.jsonAttributes && this.getJsonSchema()) { this.jsonAttributes = []; _.forOwn(this.getJsonSchema().properties, (prop, propName) => { var types = _.compact(ensureArray(prop.type)); if (types.length === 0 && Array.isArray(prop.anyOf)) { types = _.flattenDeep(_.map(prop.anyOf, 'type')); } if (types.length === 0 && Array.isArray(prop.oneOf)) { types = _.flattenDeep(_.map(prop.oneOf, 'type')); } if (_.includes(types, 'object') || _.includes(types, 'array')) { this.jsonAttributes.push(propName); } }); } if (!Array.isArray(this.jsonAttributes)) { this.jsonAttributes = []; } return this.jsonAttributes; } } function setId(model, id) { const idProp = model.constructor.getIdProperty(); const isArray = Array.isArray(idProp); if (Array.isArray(id)) { if (isArray) { if (id.length !== idProp.length) { throw new Error('trying to set an invalid identifier for a model'); } for (let i = 0; i < id.length; ++i) { model[idProp[i]] = id[i]; } } else { if (id.length !== 1) { throw new Error('trying to set an invalid identifier for a model'); } model[idProp] = id[0]; } } else { if (isArray) { if (idProp.length > 1) { throw new Error('trying to set an invalid identifier for a model'); } model[idProp[0]] = id; } else { model[idProp] = id; } } } function getId(model) { const idProp = model.constructor.getIdProperty(); if (Array.isArray(idProp)) { return model.$values(idProp); } else { return model[idProp]; } } function tryParseJson(maybeJsonStr) { try { return JSON.parse(maybeJsonStr); } catch (err) { // Ignore error. } return undefined; } function toJsonImpl(model, createDbJson, omit, pick) { if (createDbJson) { return toDatabaseJsonImpl(model, omit, pick); } else { return toExternalJsonImpl(model, omit, pick); } } function toDatabaseJsonImpl(model, omit, pick) { const json = {}; const omitFromJson = model.$omitFromDatabaseJson(); const keys = Object.keys(model); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; assignJsonValue(json, key, model[key], omit, pick, omitFromJson, true); } return json; } function toExternalJsonImpl(model, omit, pick) { const json = {}; const omitFromJson = model.$omitFromJson(); const keys = Object.keys(model); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; assignJsonValue(json, key, model[key], omit, pick, omitFromJson, false); } if (model.constructor.virtualAttributes) { const vAttr = model.constructor.virtualAttributes; for (let i = 0, l = vAttr.length; i < l; ++i) { const key = vAttr[i]; let value = model[key]; if (_.isFunction(value)) { value = value.call(model); } assignJsonValue(json, key, value, omit, pick, omitFromJson, false); } } return json; } function assignJsonValue(json, key, value, omit, pick, omitFromJson, createDbJson) { if (key.charAt(0) !== '$' && !_.isFunction(value) && !_.isUndefined(value) && (!omit || !omit[key]) && (!pick || pick[key]) && (!omitFromJson || !contains(omitFromJson, key))) { if (value !== null && typeof value === 'object') { json[key] = toJsonObject(value, createDbJson); } else { json[key] = value; } } } function toJsonObject(value, createDbJson) { if (Array.isArray(value)) { return toJsonArray(value, createDbJson); } else if (value instanceof Model) { if (createDbJson) { return value.$toDatabaseJson(); } else { return value.$toJson(); } } else if (Buffer.isBuffer(value)) { return value; } else { return _.cloneDeep(value); } } function toJsonArray(value, createDbJson) { const ret = new Array(value.length); for (let i = 0, l = ret.length; i < l; ++i) { ret[i] = toJsonObject(value[i], createDbJson) } return ret; } function cloneModel(model, shallow, stripInternal) { const clone = new model.constructor(); const relations = model.constructor.getRelations(); const keys = Object.keys(model); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; const value = model[key]; if (shallow && relations[key]) { continue; } if (stripInternal && key.charAt(0) === '$') { continue; } if (_.isObject(value)) { clone[key] = cloneObject(value); } else { clone[key] = value; } } if (model.$omitFromDatabaseJson()) { clone.$omitFromDatabaseJson(model.$omitFromDatabaseJson()); } if (model.$omitFromJson()) { clone.$omitFromJson(model.$omitFromJson()); } return clone; } function cloneObject(value) { if (Array.isArray(value)) { return cloneArray(value); } else if (value instanceof Model) { return value.$clone(); } else if (Buffer.isBuffer(value)) { return new Buffer(value); } else { return _.cloneDeep(value); } } function cloneArray(value) { const ret = new Array(value.length); for (let i = 0, l = ret.length; i < l; ++i) { ret[i] = cloneObject(value[i]) } return ret; } function omitObject(model, keyObj) { const ModelClass = model.constructor; const keys = Object.keys(keyObj); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; const value = keyObj[key]; if (value && key.charAt(0) !== '$' && _.has(model, key)) { ModelClass.omitImpl(model, key); } } } function omitArray(model, keys) { const ModelClass = model.constructor; for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; if (key.charAt(0) !== '$' && _.has(model, key)) { ModelClass.omitImpl(model, key); } } } function pickObject(model, keyObj) { const ModelClass = model.constructor; const keys = Object.keys(model); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; if (key.charAt(0) !== '$' && !keyObj[key]) { ModelClass.omitImpl(model, key); } } } function pickArray(model, pick) { const ModelClass = model.constructor; const keys = Object.keys(model); for (let i = 0, l = keys.length; i < l; ++i) { const key = keys[i]; if (key.charAt(0) !== '$' && !contains(pick, key)) { ModelClass.omitImpl(model, key); } } } function contains(arr, value) { for (let i = 0, l = arr.length; i < l; ++i) { if (arr[i] === value) { return true; } } return false; } function ensureArray(obj) { if (Array.isArray(obj)) { return obj; } else { return [obj]; } } function idColumnToIdProperty(ModelClass, idColumn) { let idProperty = ModelClass.columnNameToPropertyName(idColumn); if (!idProperty) { throw new Error(ModelClass.tableName + '.$parseDatabaseJson probably changes the value of the id column `' + idColumn + '` which is a no-no.'); } return idProperty; }