Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

uniqorm

Package Overview
Dependencies
Maintainers
1
Versions
123
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

uniqorm - npm Package Compare versions

Comparing version 1.6.3 to 2.0.0

lib/.DS_Store

93

lib/Association.js

@@ -13,56 +13,71 @@ /* UNIQORM

*/
const errorex = require('errorex');
const {ArgumentError} = require('errorex');
/**
* Module variables
* @private
*/
const ArgumentError = errorex.ArgumentError;
/**
*
* @param {Object} options
* @param {String} options.sourceKey
* @param {Model} options.sourceModel
* @param {String} options.foreignKey
* @param {Model} options.foreignModel
* @constructor
* @class
*/
class Association {
constructor(options) {
if (!options.kind || ['OtM', 'OtO'].indexOf(options.kind) < 0)
throw new ArgumentError('Invalid relation kind');
/**
*
* @param {Model} model
* @param {Object} def
* @param {String} [def.name]
* @param {String} def.foreignModel
* @param {String} def.key
* @param {String} def.foreignKey
* @constructor
*/
constructor(model, def) {
if (options.fields) {
if (typeof options.fields === 'string')
options.fields = [options.fields];
else if (!Array.isArray(options.fields))
throw new ArgumentError('You can provide Array or String type for "options.fields"');
}
if (!def.key)
throw new ArgumentError('Invalid association definition for model "%s". You must provide "key" property.',
model.name);
/* Validate "where" property */
if (options.where)
options.where = Array.isArray(options.where) ?
options.where : [options.where];
if (!def.foreignModel)
throw new ArgumentError('Invalid association definition for model "%s". You must provide "foreignModel" property.',
model.name);
this.kind = options.kind;
this.sourceKey = options.sourceKey;
this.sourceModel = options.sourceModel;
this.foreignKey = options.foreignKey;
this.foreignModel = options.foreignModel;
this.fields = options.fields;
this.where = options.where;
if (!def.foreignKey)
throw new ArgumentError('Invalid association definition or model "%s". You must provide "foreignKey" property.',
model.name);
this._model = model;
this._name = def.name;
this._key = def.key;
this._foreignModel = def.foreignModel;
this._foreignKey = def.foreignKey;
}
toString() {
return '[Object Association(' + this.kind + ': ' +
this.sourceModel.name + '[' + this.sourceKey + '] > ' +
this.foreignModel.name + '[' + this.foreignKey + '])]';
get orm() {
return this.model.orm;
}
toJSON() {
return this.toString();
get name() {
return this._name;
}
get model() {
return this._model;
}
get key() {
return this._key;
}
get foreignModel() {
return this.orm.get(this._foreignModel);
}
get foreignKey() {
return this._foreignKey;
}
toString() {
return '[object ' + Object.getPrototypeOf(this).constructor.name + '(' +
this.model.name + '.' + this.key + '>' +
this._foreignModel + '.' + this.foreignKey + ')]';
}
inspect() {

@@ -69,0 +84,0 @@ return this.toString();

@@ -10,159 +10,52 @@ /* UNIQORM

/**
* Expose `Field`.
*
* @class
* @abstract
*/
module.exports = Field;
class Field {
/**
* @param {String} name
* @param {Object} def
* @constructor
*/
function Field(name, def) {
/**
* @property
* @type {string}
* @param {string} name
* @param {Model|string} model
* @constructor
*/
this.fieldType = Field.FieldType.DATA;
this.name = name;
this.fieldName = def.fieldName || name;
if (def) {
this.notNull = def.notNull;
this.defaultValue = def.defaultValue;
this.primaryKey = def.primaryKey;
constructor(name, model) {
this._name = name;
this._model = model;
}
}
Field.prototype = {
/**
* @type {string}
* @type {Uniqorm}
*/
get fieldName() {
return this._fieldName;
},
get orm() {
return this._model.orm;
}
/**
* @param {string} value
* @type {Model}
*/
set fieldName(value) {
this._fieldName = String(value);
},
get model() {
return this._model;
}
/**
* @type {boolean}
* @type {string}
*/
get primaryKey() {
return this._primaryKey;
},
get name() {
return this._name;
}
/**
* @param {boolean} value
*
* @protected
*/
set primaryKey(value) {
this._primaryKey = value;
},
/**
* @type {boolean}
*/
get notNull() {
return this._notNull;
},
/**
* @param {boolean} value
*/
set notNull(value) {
this._notNull = value;
},
//noinspection JSUnusedGlobalSymbols
/**
* @type {*}
*/
get defaultValue() {
return this._defaultValue;
},
//noinspection JSUnusedGlobalSymbols
/**
* @param {*} value
*/
set defaultValue(value) {
this._defaultValue = value;
prepare() {
// Do nothing
}
};
Field.prototype.constructor = Field;
}
/**
* @param {*} value
* @return {Field}
* Expose `Field`.
*/
Field.prototype.setDefaultValue = function(value) {
this.defaultValue = value;
return this;
};
/**
* @param {string} name
* @return {Field}
*/
Field.prototype.setFieldName = function(name) {
this.fieldName = name;
return this;
};
/**
* @param {boolean} [value = true]
* @return {Field}
*/
Field.prototype.setPrimaryKey = function(value) {
this.primaryKey = value || value === undefined;
return this;
};
/**
* @param {boolean} [value = true]
* @return {Field}
*/
Field.prototype.setNotNull = function(value) {
this.notNull = value || value === undefined;
return this;
};
/** @export @enum {number} */
Field.FieldType = {};
/** @export */
Field.FieldType.DATA = /** @type {!Field.FieldType} */ (0);
/** @export */
Field.FieldType.AGGREGATE = /** @type {!Field.FieldType} */ (1);
/** @export */
Field.FieldType.CALCULATED = /** @type {!Field.FieldType} */ (2);
/**
* Registers a serializer class for given dialect
*
* @param {constructor<Field>} fieldProto
* @static
* @public
*/
Field.register = function(fieldProto) {
const items = this._registry = this._registry || {};
items[fieldProto.name.toUpperCase()] = fieldProto;
};
/**
* Retrieves serializer class for given dialect
*
* @param {String} type
* @return {constructor<Field>}
* @static
* @public
*/
Field.get = function(type) {
return this._registry ? this._registry[type.toUpperCase()] : undefined;
};
module.exports = Field;

@@ -16,29 +16,22 @@ /* UNIQORM

/**
* Expose `BIGINT`.
*/
module.exports = BIGINT;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @class
* @extends INTEGER
*/
function BIGINT(alias, def) {
INTEGER.apply(this, arguments);
}
class BIGINT extends INTEGER {
BIGINT.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'BIGINT';
}
};
Object.setPrototypeOf(BIGINT.prototype, INTEGER.prototype);
BIGINT.prototype.constructor = BIGINT;
}
/**
* Expose `BIGINT`.
*/
module.exports = BIGINT;

@@ -16,28 +16,22 @@ /* UNIQORM

/**
* Expose `BLOB`.
*/
module.exports = BLOB;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends BUFFER
*/
function BLOB(alias, def) {
BUFFER.apply(this, arguments);
}
class BLOB extends BUFFER {
BLOB.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'BLOB';
}
};
Object.setPrototypeOf(BLOB.prototype, BUFFER.prototype);
BLOB.prototype.constructor = BLOB;
}
/**
* Expose `BLOB`.
*/
module.exports = BLOB;

@@ -13,32 +13,26 @@ /* UNIQORM

*/
const Field = require('../Field');
const DataField = require('../DataField');
/**
* Expose `BUFFER`.
*/
module.exports = BUFFER;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends BLOB
* @class
* @extends DataField
*/
function BUFFER(alias, def) {
Field.apply(this, arguments);
}
class BUFFER extends DataField {
BUFFER.prototype = {
// noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'Buffer';
},
}
// noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @override
*/

@@ -48,5 +42,24 @@ get sqlType() {

}
};
Object.setPrototypeOf(BUFFER.prototype, Field.prototype);
BUFFER.prototype.constructor = BUFFER;
//noinspection JSUnusedGlobalSymbols
/**
* @type {*}
*/
get defaultValue() {
return this._defaultValue;
}
//noinspection JSUnusedGlobalSymbols
/**
* @param {*} value
*/
set defaultValue(value) {
this._defaultValue = null;
}
}
/**
* Expose `BUFFER`.
*/
module.exports = BUFFER;

@@ -13,41 +13,26 @@ /* UNIQORM

*/
const Field = require('../Field');
const VARCHAR = require('./VARCHAR');
/**
* Expose `CHAR`.
*/
module.exports = CHAR;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @class
* @extends VARCHAR
*/
function CHAR(alias, def) {
Field.apply(this, arguments);
this._charLength = def.charLength;
if (def.defaultValue)
this._defaultValue = String(def.defaultValue);
}
class CHAR extends VARCHAR {
CHAR.prototype = {
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'String';
},
/**
*
* @return {string}
* @constructor
*/
get SqlType() {
return 'CHAR(' + (this._charLength) + ')';
get sqlType() {
return this.charLength ?
'CHAR(' + (this.charLength) + ')' : 'CHAR';
}
};
Object.setPrototypeOf(CHAR.prototype, Field.prototype);
CHAR.prototype.constructor = CHAR;
}
/**
* Expose `CHAR`.
*/
module.exports = CHAR;

@@ -13,39 +13,25 @@ /* UNIQORM

*/
const Field = require('../Field');
const TEXT = require('./TEXT');
/**
* Expose `CLOB`.
*/
module.exports = CLOB;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends TEXT
*/
function CLOB(alias, def) {
Field.apply(this, arguments);
}
class CLOB extends TEXT {
CLOB.prototype = {
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'String';
},
/**
*
* @return {string}
* @constructor
*/
get SqlType() {
return 'CLOB(' + (this._charLength) + ')';
get sqlType() {
return 'CLOB';
}
};
Object.setPrototypeOf(CLOB.prototype, Field.prototype);
CLOB.prototype.constructor = CLOB;
}
/**
* Expose `CLOB`.
*/
module.exports = CLOB;

@@ -16,31 +16,39 @@ /* UNIQORM

/**
* Expose `DATE`.
*/
module.exports = DATE;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @class
* @extends TIMESTAMP
*/
function DATE(alias, def) {
TIMESTAMP.apply(this, arguments);
if (this._defaultValue)
this._defaultValue.setHours(0, 0, 0, 0);
}
class DATE extends TIMESTAMP {
DATE.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'DATE';
}
};
Object.setPrototypeOf(DATE.prototype, TIMESTAMP.prototype);
DATE.prototype.constructor = DATE;
/**
* @type {Date}
* @override
*/
get defaultValue() {
return super.defaultValue;
}
/**
* @param {Date} value
* @override
*/
set defaultValue(value) {
super.defaultValue = value;
if (this.defaultValue instanceof Date)
this.defaultValue.setHours(0, 0, 0, 0);
}
}
/**
* Expose `DATE`.
*/
module.exports = DATE;

@@ -13,41 +13,50 @@ /* UNIQORM

*/
const Field = require('../Field');
const DataField = require('../DataField');
/**
* Expose `DOUBLE`.
*/
module.exports = DOUBLE;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends DataField
*/
function DOUBLE(alias, def) {
Field.apply(this, arguments);
if (def.defaultValue)
this._defaultValue = parseFloat(def.defaultValue);
}
class DOUBLE extends DataField {
DOUBLE.prototype = {
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'Number';
},
}
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'DOUBLE';
}
};
Object.setPrototypeOf(DOUBLE.prototype, Field.prototype);
DOUBLE.prototype.constructor = DOUBLE;
/**
* @type {Number}
* @override
*/
get defaultValue() {
return super.defaultValue;
}
/**
* @param {Number} value
* @override
*/
set defaultValue(value) {
super.defaultValue = parseFloat(value) || null;
}
}
/**
* Expose `DOUBLE`.
*/
module.exports = DOUBLE;

@@ -16,28 +16,22 @@ /* UNIQORM

/**
* Expose `FLOAT`.
*
* @class
* @extends DOUBLE
*/
module.exports = FLOAT;
class FLOAT extends DOUBLE {
/**
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
*/
function FLOAT(alias, def) {
DOUBLE.apply(this, arguments);
}
FLOAT.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'FLOAT';
}
};
Object.setPrototypeOf(FLOAT.prototype, DOUBLE.prototype);
FLOAT.prototype.constructor = FLOAT;
}
/**
* Expose `FLOAT`.
*/
module.exports = FLOAT;

@@ -13,41 +13,52 @@ /* UNIQORM

*/
const Field = require('../Field');
const DataField = require('../DataField');
/**
* Expose `INTEGER`.
*/
module.exports = INTEGER;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends DataField
*/
function INTEGER(alias, def) {
Field.apply(this, arguments);
if (def.defaultValue)
this._defaultValue = parseInt(def.defaultValue, 10);
}
class INTEGER extends DataField {
INTEGER.prototype = {
// noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'Number';
},
}
// noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'INTEGER';
}
};
Object.setPrototypeOf(INTEGER.prototype, Field.prototype);
INTEGER.prototype.constructor = INTEGER;
/**
* @type {Number}
* @override
*/
get defaultValue() {
return super.defaultValue;
}
/**
* @param {Number} value
* @override
*/
set defaultValue(value) {
super.defaultValue = parseInt(value, 10) || null;
}
}
/**
* Expose `INTEGER`.
*/
module.exports = INTEGER;

@@ -16,35 +16,70 @@ /* UNIQORM

/**
* Expose `NUMBER`.
*/
module.exports = NUMBER;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends DOUBLE
*/
function NUMBER(alias, def) {
DOUBLE.apply(this, arguments);
if (def && def.precision != null)
this._precision = def.precision;
if (def && def.scale != null)
this._scale = def.scale;
}
class NUMBER extends DOUBLE {
NUMBER.prototype = {
/**
* @param {string} name
* @param {Model|string} model
* @param {Object} [def]
* @param {string} [def.fieldName]
* @param {boolean} [def.primaryKey]
* @param {boolean} [def.notNull]
* @param {number} [def.defaultValue]
* @param {number} [def.precision]
* @param {number} [def.scale]
* @constructor
* @override
*/
constructor(name, model, def) {
super(name, model, def);
this.precision = def && def.precision;
this.scale = def && def.scale;
}
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
return 'NUMBER' + (this._precision || this._scale ?
'(' + (this._precision || 18) + ',' +
(this._scale || 0) + ')' : '');
get sqlType() {
return 'NUMBER(' + this.precision + ',' + this.scale + ')';
}
};
Object.setPrototypeOf(NUMBER.prototype, DOUBLE.prototype);
NUMBER.prototype.constructor = NUMBER;
/**
* @type {number}
*/
get precision() {
return this._precision;
}
/**
* @param {number} value
*/
set precision(value) {
this._precision = parseInt(value, 10) || 18;
}
/**
* @type {number}
*/
get scale() {
return this._scale;
}
/**
* @param {number} value
*/
set scale(value) {
this._scale = value == null ? 2 :
parseInt(value, 10) || 0;
}
}
/**
* Expose `NUMBER`.
*/
module.exports = NUMBER;

@@ -16,29 +16,21 @@ /* UNIQORM

/**
* Expose `SMALLINT`.
*/
module.exports = SMALLINT;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends INTEGER
*/
function SMALLINT(alias, def) {
INTEGER.apply(this, arguments);
}
class SMALLINT extends INTEGER {
SMALLINT.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'SMALLINT';
}
};
}
Object.setPrototypeOf(SMALLINT.prototype, INTEGER.prototype);
SMALLINT.prototype.constructor = SMALLINT;
/**
* Expose `SMALLINT`.
*/
module.exports = SMALLINT;

@@ -16,31 +16,39 @@ /* UNIQORM

/**
* Expose `TIME`.
*/
module.exports = TIME;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @class
* @extends TIMESTAMP
*/
function TIME(alias, def) {
TIMESTAMP.apply(this, arguments);
if (this._defaultValue)
this._defaultValue.setFullYear(0, 0, 0);
}
class TIME extends TIMESTAMP {
TIME.prototype = {
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'TIME';
}
};
Object.setPrototypeOf(TIME.prototype, TIMESTAMP.prototype);
TIME.prototype.constructor = TIME;
/**
* @type {Date}
* @override
*/
get defaultValue() {
return super.defaultValue;
}
/**
* @param {Date} value
* @override
*/
set defaultValue(value) {
super.defaultValue = value;
if (this.defaultValue instanceof Date)
this.defaultValue.setFullYear(0, 0, 0);
}
}
/**
* Expose `TIME`.
*/
module.exports = TIME;

@@ -13,42 +13,53 @@ /* UNIQORM

*/
const Field = require('../Field');
const DataField = require('../DataField');
/**
* Expose `TIMESTAMP`.
*/
module.exports = TIMESTAMP;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends DataField
*/
function TIMESTAMP(alias, def) {
Field.apply(this, arguments);
if (def.defaultValue)
this._defaultValue = def.defaultValue instanceof Date ?
def.defaultValue : new Date(def.defaultValue);
}
class TIMESTAMP extends DataField {
TIMESTAMP.prototype = {
//noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @override
*/
get jsType() {
return 'Date';
},
}
//noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
get sqlType() {
return 'TIMESTAMP';
}
};
Object.setPrototypeOf(TIMESTAMP.prototype, Field.prototype);
TIMESTAMP.prototype.constructor = TIMESTAMP;
/**
* @type {Date}
* @override
*/
get defaultValue() {
return super.defaultValue;
}
/**
* @param {Date} value
* @override
*/
set defaultValue(value) {
super.defaultValue = value == null ? value :
(value instanceof Date ? value : new Date(value));
}
}
/**
* Expose `TIMESTAMP`.
*/
module.exports = TIMESTAMP;

@@ -13,39 +13,60 @@ /* UNIQORM

*/
const CHAR = require('./CHAR');
const TEXT = require('./TEXT');
/**
* Expose `VARCHAR`.
*/
module.exports = VARCHAR;
/**
*
* @param {String} alias
* @param {Object} def
* @constructor
* @extends Field
* @class
* @extends TEXT
*/
function VARCHAR(alias, def) {
CHAR.apply(this, arguments);
}
class VARCHAR extends TEXT {
VARCHAR.prototype = {
/**
*
* @return {string}
* @param {string} name
* @param {Model|string} model
* @param {Object} [def]
* @param {string} def.fieldName
* @param {boolean} def.primaryKey
* @param {boolean} def.notNull
* @param {number} def.defaultValue
* @param {number} def.charLength
* @constructor
* @override
*/
get jsType() {
return 'String';
},
constructor(name, model, def) {
super(name, model, def);
this.charLength = def && def.charLength;
}
/**
*
* @return {string}
* @constructor
* @override
*/
get SqlType() {
return 'VARCHAR(' + (this._charLength) + ')';
get sqlType() {
return this.charLength ?
'VARCHAR(' + (this.charLength) + ')' : 'VARCHAR';
}
};
Object.setPrototypeOf(VARCHAR.prototype, CHAR.prototype);
VARCHAR.prototype.constructor = VARCHAR;
/**
* @type {Number}
* @override
*/
get charLength() {
return this._charLength;
}
/**
* @param {Number} value
* @override
*/
set charLength(value) {
this._charLength = value == null ? null :
parseInt(value, 10) || null;
}
}
/**
* Expose `VARCHAR`.
*/
module.exports = VARCHAR;

@@ -15,4 +15,3 @@ /* UNIQORM

const errorex = require('errorex');
const isPlainObject = require('putil-isplainobject');
const promisify = require('putil-promisify');
const Op = sqb.Op;

@@ -23,6 +22,5 @@ /**

*/
const Op = sqb.Op;
const ArgumentError = errorex.ArgumentError;
const COLUMN_PATTERN = /^(?:([\w$]+)\.)?([\w$]+)$/;
const SORT_ORDER_PATTERN = /^([-+])?(?:([\w$]+)\.)?([a-zA-Z][\w$]*|\*) *(asc|dsc|desc|ascending|descending)?$/i;
const COLUMN_PATTERN = /^([a-zA-Z][\w$]*)(?:\.?([\w$]+))?$/;
const SORT_ORDER_PATTERN = /^([-+])?([a-zA-Z][\w$]*)(?:\.?([\w$]+))?$/i;

@@ -34,256 +32,228 @@ /**

constructor(model, options) {
this.model = model;
/**
*
* @param {Object} options
* @param {Model} options.model
* @param {Object} options.connection
* @param {Array} options.attributes
* @param {boolean} [options.autoCommit]
* @param {boolean} [options.silent]
* @param {Array} [options.filter]
* @param {Array} [options.sort]
* @param {int} [options.limit]
* @param {int} [options.offset]
* @param {Array} [options.keyValues]
* @param {boolean} [options.showSql]
*/
constructor(options) {
this.model = options.model;
this.connection = options.connection;
this.silent = options.silent;
this.where = options.where;
this.orderBy = options.orderBy;
this.attributes = options.attributes;
this.filter = options.filter;
this.sort = options.sort;
this.limit = options.limit;
this.offset = options.offset;
this.autoCommit = options.autoCommit;
this._attributes = {};
this._joins = new Map();
this._queryFields = {};
this._fieldCount = 0;
this._joinCount = 0;
if (options.attributes)
this.addAttributes({attributes: options.attributes});
}
addQueryField(column) {
const o = this._queryFields[column];
if (o)
return o;
return this._queryFields[column] = {
colName: 'col' + (++this._fieldCount)
this.silent = options.silent;
this.showSql = options.showSql;
this._resultRows = null;
this._build = {
values: {},
attributes: {},
columns: new Map(),
joins: null,
children: null
};
}
addJoin(association, parentAlias) {
let jinfo = this._joins.get(association);
if (!jinfo) {
jinfo = {
joinAlias: 'a' + (++this._joinCount),
tableAlias: parentAlias
};
this._joins.set(association, jinfo);
}
return jinfo;
}
addAssociation(association, options) {
if (association.kind === 'OtO') {
const jinfo = this.addJoin(association, options.tableAlias);
let node;
if (options.targetAttr) {
node = options.targetNode[options.targetAttr] =
options.targetNode[options.targetAttr] || {};
node.columns = node.columns || {};
node = node.columns;
} else node = options.targetNode;
this.addAttributes({
model: association.foreignModel,
tableAlias: jinfo.joinAlias,
targetNode: node,
attributes: options.attributes
});
return;
}
if (association.kind === 'OtM') {
const srcCol = this.addQueryField(options.tableAlias + '.' +
association.sourceKey);
const context = new FindContext(association.foreignModel, {
/**
* @param {Object} [scope]
* @return {Promise}
*/
execute(scope) {
return Promise.resolve().then(() => {
const isChild = !!this._masterColumn;
const query = this._buildQuery();
return query.execute({
values: this._build.values,
autoCommit: this.autoCommit,
connection: this.connection,
silent: this.silent,
where: [Op.in(association.foreignKey, new RegExp(srcCol.colName)),
...(options.where || [])],
orderBy: options.orderBy,
limit: options.limit,
offset: options.offset
});
options.targetNode[options.targetAttr] = context.addAttributes({
attributes: options.attributes,
tableAlias: 't',
targetNode: context.attributes
});
const trgCol = context.addQueryField('t.' + association.foreignKey);
context.foreignColumn = srcCol.colName;
context.maxRows = 0;
context.targetAttr = options.targetAttr;
context.sourceColumn = trgCol.colName;
options.targetNode[options.targetAttr] = context;
}
}
addAttributes(options) {
const silent = this.silent;
const model = options.model || this.model;
const tableAlias = options.tableAlias || 't';
const targetNode = options.targetNode || this._attributes;
const attributes = options.attributes ||
parseOptions({attributes: Object.getOwnPropertyNames(model.fields)}).attributes;
Object.getOwnPropertyNames(attributes).forEach(attr => {
const col = attributes[attr];
if (typeof col === 'string') {
const m = col.match(COLUMN_PATTERN);
if (!m) {
if (silent) return;
throw new ArgumentError('"%s" is not a valid column name', col);
objectRows: true,
strictParams: true,
fetchRows: this.limit,
showSql: true
}).then(resp => {
if (scope) {
scope.attributes = this._build.attributes;
scope.query = resp.query;
}
const ascName = m[1];
const fieldName = m[2];
/* istanbul ignore next */
if (!(resp && resp.rows))
return;
/* Create key value array for children */
if (this._build.children) {
for (const child of this._build.children) {
const prm = '__' + child._detailField;
const arr = child._build.values[prm] =
child._build.values[prm] || [];
for (const row of resp.rows) {
const keyValue = row[child._masterColumn];
/* istanbul ignore else */
if (keyValue && !arr.includes(keyValue))
arr.push(keyValue);
}
}
}
return this._executeChildren(scope).then(() => {
const resultRows = isChild ? {} : [];
for (const row of resp.rows) {
const obj = this._wrapRec(row, {}, this._build.attributes);
if (this._build.children) {
for (const child of this._build.children) {
const masterKey = row[child._masterColumn];
/* istanbul ignore else */
if (masterKey) {
const n = child._resultRows[masterKey];
/* istanbul ignore next */
obj[child._masterAttr] = n || null;
}
}
}
/* If there is no association alias, it can be a real field or an association alias */
if (!ascName) {
/* Test if this is a field name */
if (model.getField(fieldName, true)) {
targetNode[attr] = {
column: this.addQueryField(tableAlias + '.' + fieldName)
};
return;
if (isChild) {
const key = row[this._detailColumn];
/* istanbul ignore else */
if (key) {
const arr = resultRows[key] = resultRows[key] || [];
arr.push(obj);
}
} else
resultRows.push(obj);
}
/* Test if this is an association alias */
const association = model.getAssociation(fieldName);
if (!association) {
if (silent) return;
throw new ArgumentError('Model "%s" has no field or association for "%s"', model.name, fieldName);
}
this.addAssociation(association, {
tableAlias,
targetNode: targetNode,
targetAttr: attr
});
return;
}
/* A field name with association alias (flat) */
const association = model.getAssociation(ascName);
if (!association) {
if (silent) return;
throw new ArgumentError('Model "%s" has no association "%s"', model.name, ascName);
}
if (association.kind !== 'OtO')
throw new ArgumentError('Only One-to-One associations can be used Flat');
this.addAssociation(association, {
tableAlias,
targetNode: targetNode,
attributes: {[attr]: fieldName}
this._resultRows = resultRows;
return resultRows;
});
return;
}
if (isPlainObject(col)) {
const association = model.getAssociation(attr);
if (!association) {
if (silent) return;
throw new ArgumentError('Model "%s" has no association named "%s"', model.name, attr);
}).catch(e => {
/* istanbul ignore else */
if (this.showSql && !isChild && e.query) {
e.message += '\nSQL: ' + e.query.sql.replace(/\n/g, '\n ');
/* istanbul ignore next */
if (e.query.values && e.query.values.length)
e.message += '\nValues: ' + JSON.stringify(e.query.values);
}
this.addAssociation(association, {
tableAlias,
targetNode: targetNode,
targetAttr: col.as || attr,
attributes: col.attributes,
where: col.where,
orderBy: col.orderBy,
limit: col.limit,
offset: col.offset
});
}
throw e;
});
});
}
buildQuery() {
const self = this;
const model = this.model;
/* Prepare sort order */
const orderBy = [];
if (self.orderBy) {
self.orderBy.forEach((col) => {
/**
* @return {Query}
*/
_buildQuery() {
/* istanbul ignore else */
if (this.attributes)
this._addAttributes({
model: this.model,
tableAlias: 't',
attributes: this.attributes,
targetNode: this._build.attributes
});
const selectColumns = [];
const joins = [];
const orderColumns = [];
/* 1. Prepare order columns */
if (this.sort) {
for (const col of this.sort) {
if (typeof col !== 'string')
throw new ArgumentError('Invalid element in "orderBy" property');
throw new ArgumentError('Invalid element in "sort" property');
const m = col.match(SORT_ORDER_PATTERN);
if (!m) {
if (self.silent) return;
if (!m)
throw new ArgumentError('"%s" is not a valid order expression', col);
}
const ascName = m[2];
let fieldName = m[3];
let s = (m[1] || '');
if (ascName) {
const association = model.getAssociation(ascName);
if (!association) {
if (self.silent) return;
throw new ArgumentError('Model "%s" has no association "%s"', model.name, ascName);
let fieldName = m[2];
let field = this.model.getField(fieldName);
if (field.foreignModel) {
let tableAlias = 't';
while (field) {
const join = this._addJoin(field, tableAlias);
tableAlias = join.joinAlias;
/* istanbul ignore else */
if (field.fieldName)
fieldName = tableAlias + '.' + field.fieldName;
field = field.towards;
}
if (association.kind !== 'OtO')
throw new ArgumentError('Only One-to-One associations can be used for sort order');
const jinfo = self.addJoin(association, 't');
s += jinfo.joinAlias + '.';
} else {
if (!model.getField(fieldName, this.silent))
return;
fieldName = 't.' + fieldName;
}
orderBy.push(s + fieldName + (m[4] ? ' ' + m[4] : ''));
});
orderColumns.push((m[1] || '') + fieldName);
}
}
/* 1. Phase: Add joins needed for where clause and prepare an override map for expressions */
/* 2. Phase: Add joins needed for filter clause and prepare an override map for expressions */
const operatorOverrides = {};
sqb.select()
.where(...(self.where || []))
.hook('serialize', (ctx, type, o) => {
if (type === 'operator' && o.expression) {
if (!o.expression.match(/^(?:([a-zA-Z][\w$]+)\.?)+$/))
this.connection.select()
.where(...(this.filter || []))
.on('serialize', (ctx, type, o) => {
if (type === 'comparison' && o.expression) {
const m = o.expression.match(COLUMN_PATTERN);
/* istanbul ignore next */
if (!m)
throw new ArgumentError('Invalid column definition "%s"', o.expression);
const arr = o.expression.split(/\./);
const fieldName = arr.pop();
let m = model;
let tableAlias = 't';
for (const a of arr) {
const association = m.getAssociation(a);
if (!association)
throw new ArgumentError('Model "%s" has no association "%s"', m.name, a);
const jinfo = self.addJoin(association, tableAlias);
tableAlias = jinfo.joinAlias;
m = association.foreignModel;
const fieldName = m[1];
let field = this.model.getField(fieldName);
if (field.foreignModel) {
/* istanbul ignore next */
if (!field.returnsSingleValue)
throw new ArgumentError('`%s` is not an single value associated field and can not be used for filtering', fieldName);
//let join = this._addJoin(field, 't');
let tableAlias = 't';
while (field) {
const join = this._addJoin(field, tableAlias);
tableAlias = join.joinAlias;
if (field.fieldName)
operatorOverrides[o.expression] =
tableAlias + '.' + field.fieldName;
field = field.towards;
}
return;
}
operatorOverrides[o.expression] = tableAlias + '.' + fieldName;
operatorOverrides[o.expression] = 't.' + fieldName;
}
}).generate();
/* 2. Phase: Build query */
const colarr = [];
const keys = Object.getOwnPropertyNames(self._queryFields);
if (keys.length > 0) {
for (const col of keys) {
colarr.push(col + ' ' + self._queryFields[col].colName);
/* 3. Phase: Prepare select columns */
for (const [key, col] of this._build.columns.entries())
selectColumns.push(key + ' ' + col.colName);
/* 4. Phase: Prepare joins */
if (this._build.joins) {
for (const join of this._build.joins.values()) {
joins.push(
sqb.leftOuterJoin(
join.ascField.foreignModel.tableNameFull + ' ' +
join.joinAlias)
.on(Op.eq('$$' + join.joinAlias + '.' +
join.ascField.foreignKey,
sqb.raw(join.targetAlias + '.' +
join.ascField.key)),
...(join.ascField.filter || []))
);
}
} else
for (const col of this.model.keyFields) {
colarr.push(col);
}
const joinarr = [];
for (const [association, j] of self._joins.entries()) {
joinarr.push(
sqb.leftOuterJoin(
association.foreignModel.tableNameFull + ' ' + j.joinAlias)
.on(Op.eq('$' + j.joinAlias + '.' + association.foreignKey,
sqb.raw(j.tableAlias + '.' + association.sourceKey)))
);
}
/* 5. Phase: Create Query */
return this.connection
.select(...colarr)
.from(model.tableNameFull + ' t')
.join(...joinarr)
.where(...(self.where || []))
.orderBy(...orderBy)
.limit(self.limit)
.offset(self.offset)
.hook('serialize', (ctx, type, o) => {
if (type === 'operator' && o.expression) {
o.expression = o.expression.substring(0, 1) === '$' ?
o.expression.substring(1) :
operatorOverrides[o.expression];
.select(...selectColumns)
.from(this.model.tableNameFull + ' t')
.join(...joins)
.where(...(this.filter || []))
.orderBy(...orderColumns)
.limit(this.limit)
.offset(this.offset)
.on('serialize', (ctx, type, o) => {
if (type === 'comparison' && o.expression) {
o.expression = o.expression.substring(0, 2) === '$$' ?
o.expression.substring(2) :
/* istanbul ignore next */
operatorOverrides[o.expression] || o.expression;
}

@@ -293,151 +263,231 @@ });

execute(callback) {
const query = this.buildQuery();
const isChild = this.foreignColumn;
const execParams = isChild ? {[this.foreignColumn]: this.keyValues} : null;
const resultRows = !isChild ? [] : null;
query.params(execParams);
query.execute({
autoCommit: this.autoCommit,
objectRows: true,
strictParams: true,
maxRows: isChild ? 0 : undefined
}, (err, resp) => {
if (err || !(resp && resp.rows)) {
callback(err);
_addAttributes(options) {
const silent = this.silent;
const model = options.model;
const getAttrOptions = (attrKey, v) => {
/* Convert string item to object representation */
if (typeof v === 'string') {
const m = v.match(COLUMN_PATTERN);
v = {fieldName: m[1]};
if (m[2])
v.subField = m[2];
}
let result;
const field = model.getField(v.fieldName, silent);
if (!field) return;
/* If is an associated field */
if (field.foreignModel) {
result = {
ascField: field
};
/* If requested a sub field */
if (v.subField) {
if (field.hasMany) {
/* istanbul ignore next */
if (silent) return;
throw new ArgumentError('`%s` is an One2Many associated field and sub values can not be used to return as single value', v.fieldName);
}
if (field.returnsSingleValue) {
/* istanbul ignore next */
if (silent) return;
throw new ArgumentError('`%s` is an single value associated field and has no sub value `%s`', v.fieldName, v.attribute);
}
if (!field.returnsAttribute(v.subField))
throw new ArgumentError('`%s` has no attribute named `%s`', v.fieldName, v.subField);
result.attribute = v.subField;
} else {
if (v.attributes) {
for (const n of Object.getOwnPropertyNames(v.attributes)) {
if (!field.returnsAttribute(v.attributes[n] ||
/* istanbul ignore next */n)) {
/* istanbul ignore next */
if (silent) continue;
throw new ArgumentError('`%s` has no attribute named `%s`', v.fieldName, n);
}
result.attributes = result.attributes || {};
result.attributes[n] = v.attributes[n];
}
/* istanbul ignore next */
if (!result.attributes) return;
}
}
} else {
result = {
attribute: v.fieldName
};
}
return result;
};
Object.getOwnPropertyNames(options.attributes).forEach(attrKey => {
const request = getAttrOptions(attrKey,
/* istanbul ignore next */
options.attributes[attrKey] || attrKey);
if (!request) return;
request.targetAttr = request.targetAttr || attrKey;
if (!request.ascField) {
options.targetNode[request.targetAttr] = {
column: this._addColumn(options.tableAlias, request.attribute)
};
return;
}
const isChild = this.foreignColumn;
resp.rows.forEach((row) => {
const obj = this._wrapRec(row, {}, this._attributes);
if (!isChild) {
resultRows.push(obj);
return;
const ascField = request.ascField;
let tableAlias = options.tableAlias;
let ctx = this;
let targetNode;
/* If has many rows */
if (ascField.hasMany) {
options.targetNode[request.targetAttr] = {};
const masterCol = this._addColumn(options.tableAlias, ascField.key);
ctx = new FindContext({
model: ascField.foreignModel,
// attributes: inf.attributes,
connection: this.connection,
autoCommit: this.autoCommit,
silent: this.silent,
filter: [Op.in(ascField.foreignKey,
new RegExp('__' + ascField.foreignKey)),
...(ascField.filter || /* istanbul ignore next */[])],
sort: request.sort,
limit: request.limit,
offset: request.offset
});
const detailCol = ctx._addColumn('t', ascField.foreignKey);
ctx._masterColumn = masterCol.colName;
ctx._masterAttr = request.targetAttr;
ctx._detailColumn = detailCol.colName;
ctx._detailField = ascField.foreignKey;
this._build.children = this._build.children || [];
this._build.children.push(ctx);
targetNode = ctx._build.attributes;
} else {
const j = this._addJoin(ascField, tableAlias);
tableAlias = j.joinAlias;
targetNode = options.targetNode[request.targetAttr] ||
(options.targetNode[request.targetAttr] = {});
}
let fld = ascField;
while (fld) {
const node = targetNode;
/* If field returns single value */
if (fld.returnsSingleValue) {
/* istanbul ignore else */
if (fld.fieldName) {
node.column = ctx._addColumn(tableAlias, fld.fieldName);
}
} else
/* If requested single attribute of multi attribute field */
if (request.attribute) {
/* istanbul ignore else */
if (fld.attributes.hasOwnProperty(request.attribute)) {
node.column = ctx._addColumn(tableAlias, request.attribute);
}
} else
/* If requested some attributes of multi attribute field */
if (request.attributes) {
/* istanbul ignore else */
if (!ascField.hasMany)
node.columns = node.columns || {};
for (const n of Object.getOwnPropertyNames(request.attributes)) {
const t = request.attributes[n] || n;
/* istanbul ignore else */
if (fld.attributes.hasOwnProperty(t))
(ascField.hasMany ?
/*istanbul ignore next */node : node.columns)[n] = {
column: ctx._addColumn(tableAlias, fld.attributes[t] || t)
};
}
} else
/* */
if (fld.attributes) {
if (!ascField.hasMany)
node.columns = node.columns || {};
for (const n of Object.getOwnPropertyNames(fld.attributes)) {
(ascField.hasMany ? node : node.columns)[n] = {
column: ctx._addColumn(tableAlias, fld.attributes[n] || n)
};
}
}
/* Merge obj with parent */
const keyValue = row[this.sourceColumn];
const r = this.targetRows[keyValue];
if (r) {
r[this.targetAttr] = r[this.targetAttr] || [];
r[this.targetAttr].push(obj);
fld = fld && fld.towards;
if (fld) {
const j = ctx._addJoin(fld, tableAlias);
tableAlias = j.joinAlias;
}
});
this._processChildren(resultRows, callback);
}
});
}
executeForReturning(resultRows, callback) {
let needRefresh = this._joins.size;
if (!needRefresh) {
Object.getOwnPropertyNames(this._attributes).every(n => {
const attr = this._attributes[n];
if (attr instanceof FindContext) {
needRefresh = true;
return false;
}
return true;
});
}
if (needRefresh)
return this.execute(callback);
callback(null, resultRows);
_addColumn(tableAlias, fieldName) {
const s = (tableAlias + '.' + fieldName);
let o = this._build.columns.get(s);
if (o)
return o;
o = {
colName: 'col' + (this._build.columns.size + 1),
source: s
};
this._build.columns.set(s, o);
return o;
}
_processChildren(resultRows, callback) {
/**/
if (this.children) {
const promises = [];
for (const childContext of this.children.values()) {
promises.push(promisify.fromCallback((cb) => {
childContext.execute(cb);
}));
}
Promise.all(promises).then(() => callback(null, resultRows), callback);
return;
_addJoin(ascField, parentAlias) {
this._build.joins = this._build.joins || new Map();
let s = ascField.key + '>' + ascField.foreignModel.name + '.' +
ascField.foreignKey;
if (ascField.filter && ascField.filter.length)
s += '|' + JSON.stringify(ascField.filter);
let join = this._build.joins.get(s);
if (!join) {
join = {
ascField,
joinAlias: 'j' + (this._build.joins.size + 1),
targetAlias: parentAlias
};
this._build.joins.set(s, join);
}
callback(null, resultRows);
return join;
}
_wrapRec(source, target, attrMap) {
Object.getOwnPropertyNames(attrMap).forEach((n) => {
for (const n of Object.getOwnPropertyNames(attrMap)) {
const attr = attrMap[n];
if (attr.column) {
if (attr.column)
target[n] = source[attr.column.colName];
return;
}
if (attr.columns) {
else if (attr.columns)
target[n] = this._wrapRec(source, target[n] || {}, attr.columns);
return;
}
if (attr instanceof FindContext) {
target[n] = null;
const keyValue = source[attr.foreignColumn];
if (keyValue) {
this.children = this.children || new Set();
if (!this.children.has(attr)) {
this.children.add(attr);
attr.targetRows = {};
}
attr.targetRows[keyValue] = target;
attr.keyValues = attr.keyValues || [];
attr.keyValues.push(keyValue);
}
}
});
else target[n] = null;
}
return target;
}
}
function parseOptions(options, silent) {
if (Array.isArray(options))
return parseOptions({attributes: options}, silent);
const addAttribute = (target, key, value) => {
if (typeof value === 'string' || value == null)
target[key] = value || key;
else if (Array.isArray(value))
target[key] = parseOptions({attributes: value});
else if (isPlainObject(value))
target[key] = parseOptions(value);
};
const parseAttributes = (target, value) => {
let i = 0;
if (Array.isArray(value)) {
value.forEach(v => {
i++;
if (typeof v === 'string') {
const m = v.match(/^([\w$]*\.?[\w$]+) *([\w$]*)$/);
if (!m) {
if (silent)
throw new ArgumentError('"%s" is not a valid column name', v);
return;
}
addAttribute(target, m[2] || m[1], m[1]);
return;
}
if (isPlainObject(v))
parseAttributes(target, v);
});
} else if (isPlainObject(value)) {
Object.getOwnPropertyNames(value).forEach(v => {
addAttribute(target, v, value[v]);
i++;
});
/**
*
* @param {Object} scope
* @return {Promise}
* @private
*/
_executeChildren(scope) {
/**/
if (this._build.children) {
if (scope)
scope.children = scope.children || {};
const promises = [];
for (const childContext of this._build.children) {
const scp = scope ?
scope.children[childContext._masterAttr] = {} : null;
promises.push(childContext.execute(scp));
}
return Promise.all(promises);
}
return i ? target : null;
};
return Promise.resolve();
}
const result = {};
result.attributes = parseAttributes({}, options.attributes);
result.where = !options.where || Array.isArray(options.where) ?
options.where : [options.where];
result.orderBy = !options.orderBy || Array.isArray(options.orderBy) ?
options.orderBy : [options.orderBy];
result.limit = options.limit;
result.offset = options.offset;
result.as = options.as;
return result;
}

@@ -444,0 +494,0 @@

@@ -16,27 +16,29 @@ /* UNIQORM

const Field = require('./Field');
const ModelExporter = require('./ModelExporter');
const DataField = require('./DataField');
const MetadataImporter = require('./MetadataImporter');
// Register field classes
Field.register(require('./fields/INTEGER'));
Field.register(require('./fields/BIGINT'));
Field.register(require('./fields/SMALLINT'));
Field.register(require('./fields/FLOAT'));
Field.register(require('./fields/NUMBER'));
Field.register(require('./fields/DOUBLE'));
Field.register(require('./fields/VARCHAR'));
Field.register(require('./fields/CHAR'));
Field.register(require('./fields/DATE'));
Field.register(require('./fields/TIMESTAMP'));
Field.register(require('./fields/TIME'));
Field.register(require('./fields/CLOB'));
Field.register(require('./fields/BLOB'));
Field.register(require('./fields/BUFFER'));
Field.register(require('./fields/BOOL'));
DataField.register(require('./fields/INTEGER'));
DataField.register(require('./fields/BIGINT'));
DataField.register(require('./fields/SMALLINT'));
DataField.register(require('./fields/FLOAT'));
DataField.register(require('./fields/NUMBER'));
DataField.register(require('./fields/DOUBLE'));
DataField.register(require('./fields/TEXT'));
DataField.register(require('./fields/VARCHAR'));
DataField.register(require('./fields/CHAR'));
DataField.register(require('./fields/DATE'));
DataField.register(require('./fields/TIMESTAMP'));
DataField.register(require('./fields/TIME'));
DataField.register(require('./fields/CLOB'));
DataField.register(require('./fields/BLOB'));
DataField.register(require('./fields/BUFFER'));
DataField.register(require('./fields/BOOLEAN'));
module.exports = Uniqorm;
Uniqorm.Field = Field;
Uniqorm.DataField = DataField;
Uniqorm.Model = Model;
Uniqorm.ModelExporter = ModelExporter;
Uniqorm.FieldType = Field.FieldType;
Uniqorm.MetadataImporter = MetadataImporter;
//Object.assign(module.exports, Field);

@@ -13,67 +13,160 @@ /* UNIQORM

*/
const EventEmitter = require('events');
const {ErrorEx, ArgumentError} = require('errorex');
const sqb = require('sqb');
const promisify = require('putil-promisify');
const isPlainObject = require('putil-isplainobject');
const merge = require('putil-merge');
const Field = require('./Field');
const FieldMap = require('./FieldMap');
const Association = require('./Association');
const FindContext = require('./FindContext');
const ExtendedModel = require('./ExtendedModel');
/**
* Module variables
* @private
*/
const MODEL_NAME_PATTERN = /^(?:([A-Za-z]\w*)\.)?([A-Za-z]\w*)?$/;
/**
*
* @class
* @extends EventEmitter
*/
class Model {
class Model extends EventEmitter {
/**
*
* @param {Uniqorm} owner
* @param {String} name
* @param {Schema} schema
* @param {Object} def
* @param {String} def.name
* @param {String} [def.schema]
* @param {String} [def.tableName]
* @param {Object} def.fields
* @param {Object} [def.associations]
*/
constructor(owner, name, def) {
/* Build field list */
const fields = {};
const keyFields = [];
Object.getOwnPropertyNames(def.fields).forEach((name) => {
const o = def.fields[name];
const Ctor = Field.get(o.dataType);
if (!Ctor)
throw new ArgumentError('Unknown data type "' + o.dataType + '"');
const f = fields[name] = Object.create(Ctor.prototype);
Ctor.call(f, name, o);
if (o.primaryKey) {
f.primaryKey = true;
keyFields.push(name);
}
});
this.name = name;
this.Op = sqb.Op;
this.owner = owner;
this.schemaName = def.schemaName;
this.tableName = def.tableName;
this.fields = fields;
this.keyFields = keyFields;
this._associations = new Map();
constructor(schema, def) {
super();
if (typeof def.name !== 'string')
throw new ArgumentError('You must provide model name');
if (!def.name.match(MODEL_NAME_PATTERN))
throw new ArgumentError('Invalid model name "%s"', def.name);
if (def.tableName && !def.tableName.match(MODEL_NAME_PATTERN))
throw new ArgumentError('Invalid tableName "%s"', def.tableName);
if (!isPlainObject(def.fields))
throw new ArgumentError('`fields` property is empty or is not valid');
this._schema = schema;
this._name = def.name;
this._tableName = def.tableName;
this._associations = [];
this._fields = new FieldMap(this);
this._keyFields = null;
if (def.associations) {
/* istanbul ignore next */
if (!Array.isArray(def.associations))
throw new ArgumentError('Invalid model definition (%s). `associations` property can only be an array type', def.name);
this.addAssociation(...def.associations);
}
/* Create fields */
for (const name of Object.getOwnPropertyNames(def.fields)) {
this._fields.set(name, def.fields[name]);
}
}
/**
*
* @return {Uniqorm}
*/
get orm() {
return this.schema.orm;
}
/**
*
* @return {string}
*/
get schema() {
return this._schema;
}
/**
*
* @return {string}
*/
get schemaName() {
return this.schema.name;
}
/**
*
* @return {string}
*/
get name() {
return this._name;
}
/**
*
* @return {string}
*/
get tableName() {
return this._tableName;
}
/**
*
* @return {Set<Association>}
*/
get associations() {
return this._associations;
}
/**
*
* @return {FieldMap}
*/
get fields() {
return this._fields;
}
/**
*
* @return {Array<string>}
*/
get keyFields() {
return this._keyFields;
}
/**
*
* @return {string}
*/
get tableNameFull() {
return (this.schemaName ? this.schemaName + '.' : '') +
return (!this.schema.isDefault ? this.schemaName + '.' : '') +
this.tableName;
}
getField(name, silent) {
const field = this.fields[name];
if (!field && !silent)
throw new ArgumentError('Model "%s" has no field "%s"', this.name, name);
return field;
addAssociation(...value) {
for (const v of value)
this.associations.push(new Association(this, v));
}
extend(cfg) {
if (typeof cfg !== 'object')
throw new ArgumentError('You must provide config object');
return new ExtendedModel(this, cfg);
/**
*
* @param {string} name
* @param {boolean} [silent]
* @return {DataField}
*/
getField(name, silent) {
const field = this._fields.get(name);
if (field)
return field;
if (silent) return null;
throw new ArgumentError('Model "%s" has no field "%s"', this.name, name);
}

@@ -90,33 +183,19 @@

* @param {Object} [options.connection]
* @param {Function} [callback]
* @return {Promise|Undefined}
* @return {Promise}
*/
get(keyValues, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!callback)
return promisify.fromCallback((cb) => this.get(keyValues, options, cb));
keyValues = this._prepareKeyValues(keyValues);
options = options || {};
if (this._hooks && this._hooks.get)
return this._hooks.get(keyValues, options, callback);
const opts = merge({}, options);
merge(opts, {
where: keyValues,
limit: 1,
offset: 0,
orderBy: null
get(keyValues, options) {
return Promise.resolve().then(() => {
if (keyValues == null)
throw new ArgumentError('You must provide key value(s)');
/* istanbul ignore next */
if (!(this.keyFields && this.keyFields.length))
throw new ErrorEx('Model "%s" has no primary key', this.name);
keyValues = this._prepareKeyValues(keyValues);
options = options || {};
const opts = merge.defaults({
filter: keyValues,
limit: 1
}, options);
return this.find(opts).then(result => result && result[0]);
});
this.find(opts, (err, result) => {
if (err) {
callback(err);
return;
}
callback(null, result && result[0]);
});
}

@@ -134,27 +213,27 @@

* @param {Boolean} [options.silent]
* @param {Object|Array} [options.where]
* @param {Function} [callback]
* @return {Promise|Undefined}
* @param {Object|Array} [options.filter]
* @param {Object} [options.scope]
* @return {Promise}
*/
find(options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!callback)
return promisify.fromCallback((cb) => this.find(options, cb));
options = options || {};
options.attributes = options.attributes ||
Object.getOwnPropertyNames(this.fields);
find(options) {
return Promise.resolve().then(() => {
options = options || {};
if (options.attributes && !Array.isArray(options.attributes))
options.attributes = [options.attributes];
options.attributes =
typeof options.attributes !== 'object' ||
(Array.isArray(options.attributes) && !options.attributes.length)
? this.getDataFields() : options.attributes;
if (this._hooks && this._hooks.find)
return this._hooks.find(options, callback);
const opts = this._prepareFindOptions(options,
options.silent || this.owner.options.silent);
opts.connection = options.connection || this.owner.pool;
opts.autoCommit = options.autoCommit;
const context = new FindContext(this, opts);
context.execute(callback);
const silent = options.silent != null ?
options.silent : this.orm.options.silent;
const opts = this._prepareFindOptions(options, silent);
opts.model = this;
opts.connection = options.connection || this.orm.pool;
opts.silent = silent;
opts.showSql = opts.showSql != null ?
opts.showSql : this.orm.options.showSql;
delete opts.scope;
return (new FindContext(opts)).execute(options.scope);
});
}

@@ -170,77 +249,44 @@

* @param {Boolean} [options.silent]
* @param {Boolean} [options.returning]
* @param {Function} [callback]
* @return {Promise|Undefined}
* @param {Boolean} [options.showSql]
* @param {string|Array<string>} [options.returning]
* @param {Object} [options.scope]
* @return {Promise}
*/
create(values, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!callback)
return promisify.fromCallback((cb) => this.create(options, cb));
create(values, options) {
return Promise.resolve().then(() => {
if (typeof values !== 'object')
throw new ArgumentError('You must provide values');
if (typeof values !== 'object' || Array.isArray(values))
throw new ArgumentError('You must provide a valid "values" argument');
options = options || {};
options = options || {};
const silent = options.silent != null ?
options.silent : this.orm.options.silent;
values = this._prepareUpdateValues(values, silent);
const returning = options.returning &&
this._prepareReturning(options.returning, silent);
if (this._hooks && this._hooks.create)
return this._hooks.create(values, options, callback);
const silent = options.silent || this.owner.options.silent;
values = this._prepareUpdateValues(values, {
silent,
removePrimaryKey: false
const dbobj = (options.connection || this.orm.pool);
return dbobj
.insert(this.tableNameFull, values)
.returning(returning && returning.columns)
.execute({
objectRows: true,
autoCommit: options.autoCommit,
showSql: true
}).then(resp => {
if (!(resp.rows && resp.rows.length && returning &&
returning.attributes))
return true;
returning.silent = silent;
returning.showSql = options.showSql;
return this._responseReturning(dbobj, resp, returning);
}).catch(/* istanbul ignore next */e => {
if (options.showSql && e.query) {
e.message += '\nSQL: ' + e.query.sql.replace(/\n/g, '\n ');
if (e.query.values && e.query.values.length)
e.message += '\nValues: ' + JSON.stringify(e.query.values);
}
throw e;
});
});
let attributes;
let returning;
if (options.returning) {
attributes = options.returning === '*' ?
Object.getOwnPropertyNames(this.fields) :
(Array.isArray(options.returning) ? options.returning : [options.returning]);
if (this.keyFields)
attributes.unshift(...this.keyFields);
attributes = this._prepareFindOptions({attributes}).attributes;
returning = this._prepareReturning(attributes, silent);
}
/* Prepare query */
const dbobj = (options.connection || this.owner.pool);
const query = dbobj
.insert(this.tableNameFull, values)
.returning(returning);
/* Execute query */
query.execute({
objectRows: true,
autoCommit: options.autoCommit
}, (err, result) => {
if (err) {
callback(err);
return;
}
if (!(result.rows && attributes && this.keyFields)) {
callback(null, true);
return;
}
const where = [];
this.keyFields.forEach(n => {
where.push({[n]: result.rows[0][n]});
});
const context = new FindContext(this, {
connection: dbobj,
silent: silent,
attributes,
where
});
context.executeForReturning(result.rows, (err, rows) => {
if (err) {
callback(err);
return;
}
callback(null, (rows && rows[0]) || true);
});
});
}

@@ -253,77 +299,62 @@

* @param {Object} [options]
* @param {Function} [callback]
* @return {Promise|Undefined}
* @param {Object|Array} [options.where]
* @param {Boolean} [options.autoCommit]
* @param {Object} [options.connection]
* @param {Boolean} [options.silent]
* @param {Boolean} [options.showSql]
* @param {string|Array<string>} [options.returning]
* @param {Object} [options.scope]
* @return {Promise}
*/
update(values, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
update(values, options) {
return Promise.resolve().then(() => {
if (!callback)
return promisify.fromCallback((cb) => this.update(values, options, cb));
if (typeof values !== 'object' || Array.isArray(values))
throw new ArgumentError('You must provide a valid "values" argument');
if (typeof values !== 'object')
throw new ArgumentError('You must provide values');
options = options || {};
const silent = options.silent != null ?
options.silent : this.orm.options.silent;
values = this._prepareUpdateValues(values, silent);
const returning = options.returning &&
this._prepareReturning(options.returning, silent);
options = options || {};
let filter;
if (options.where) {
filter =
Array.isArray(options.where) ? /* istanbul ignore next */ options.where : [options.where];
} else {
filter = [{}];
for (const n of this.keyFields) {
/* istanbul ignore else */
filter[0][n] =
values[n] != null ? values[n] : /* istanbul ignore next */ null;
delete values[n];
}
}
if (this._hooks && this._hooks.create)
return this._hooks.create(values, options, callback);
const silent = options.silent || this.owner.options.silent;
let where = options.where || this._prepareKeyValues(values);
where = Array.isArray(where) ? where : [where];
/* prepare update values */
values = this._prepareUpdateValues(values, {
silent,
removePrimaryKey: true
const dbobj = (options.connection || this.orm.pool);
return dbobj
.update(this.tableNameFull, values)
.where(...filter)
.returning(returning && returning.columns)
.execute({
objectRows: true,
autoCommit: options.autoCommit
}).then(resp => {
if (!(resp.rows && resp.rows.length && returning &&
returning.attributes))
return true;
returning.silent = silent;
returning.showSql = options.showSql;
return this._responseReturning(dbobj, resp, returning);
}).catch(/* istanbul ignore next */e => {
if (options.showSql && e.query) {
e.message += '\nSQL: ' + e.query.sql.replace(/\n/g, '\n ');
if (e.query.values && e.query.values.length)
e.message += '\nValues: ' + JSON.stringify(e.query.values);
}
throw e;
});
});
/* prepare returning map and result attributes */
let attributes;
let returning;
if (options.returning) {
attributes = options.returning === '*' ?
Object.getOwnPropertyNames(this.fields) :
(Array.isArray(options.returning) ? options.returning : [options.returning]);
if (this.keyFields)
attributes.unshift(...this.keyFields);
attributes = this._prepareFindOptions({attributes}).attributes;
returning = this._prepareReturning(attributes, silent);
}
/* Prepare query */
const dbobj = (options.connection || this.owner.pool);
const query = dbobj
.update(this.tableNameFull, values)
.where(...where)
.returning(returning);
/* Execute query */
query.execute({
objectRows: true,
autoCommit: options.autoCommit
}, (err, result) => {
if (err) {
callback(err);
return;
}
if (!(result.rows && attributes)) {
callback(null, true);
return;
}
const context = new FindContext(this, {
connection: dbobj,
silent: silent,
attributes,
where
});
context.executeForReturning(result.rows, (err, rows) => {
if (err) {
callback(err);
return;
}
callback(null, (rows && rows[0]) || true);
});
});
}

@@ -334,35 +365,33 @@

*
* @param {Object} [keyValues]
* @param {Object} [values]
* @param {Object} [options]
* @param {Function} [callback]
* @return {Promise|Undefined}
* @param {Boolean} [options.autoCommit]
* @param {Object} [options.connection]
* @param {Boolean} [options.silent]
* @param {Boolean} [options.showSql]
* @param {string|Array<string>} [options.returning]
* @param {Object} [options.scope]
* @return {Promise}
*/
destroy(keyValues, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
const self = this;
if (!callback)
return promisify.fromCallback((cb) => self.destroy(keyValues, options, cb));
destroy(values, options) {
return Promise.resolve().then(() => {
options = options || {};
if (typeof values !== 'object')
values = this._prepareKeyValues(values);
keyValues = this._prepareKeyValues(keyValues);
options = options || {};
if (this._hooks && this._hooks.create)
return this._hooks.create(keyValues, options, callback);
const opts = merge({}, options);
/* Prepare query */
const dbobj = (opts.connection || this.owner.pool);
const query = dbobj
.delete(this.tableNameFull)
.where(keyValues);
/* Execute query */
query.execute({
autoCommit: opts.autoCommit
}, (err, result) => {
if (!err)
return callback();
callback(err);
const dbobj = (options.connection || this.orm.pool);
return dbobj
.delete(this.tableNameFull)
.where(values)
.execute({
autoCommit: options.autoCommit,
showSql: options.scope
}).catch(/* istanbul ignore next */e => {
if (options.showSql && e.query) {
e.message += '\nSQL: ' + e.query.sql.replace(/\n/g, '\n ');
if (e.query.values && e.query.values.length)
e.message += '\nValues: ' + JSON.stringify(e.query.values);
}
throw e;
}).then(() => true);
});

@@ -372,24 +401,26 @@ }

hasOne(attribute, options) {
this._addAssociation(attribute, 'OtO', options);
if (typeof options === 'string')
options = {foreignModel: options};
/* istanbul ignore next */
if (typeof options !== 'object')
throw new ArgumentError('You must provide "options" as object');
options.hasMany = false;
this.fields.set(attribute, options);
}
hasMany(attribute, options) {
this._addAssociation(attribute, 'OtM', options);
if (typeof options === 'string')
options = {foreignModel: options};
/* istanbul ignore next */
if (typeof options !== 'object')
throw new ArgumentError('You must provide "options" as object');
options.hasMany = true;
this.fields.set(attribute, options);
}
getAssociation(name) {
const a = this._associations.get(name);
if (typeof a === 'function')
return a();
return a;
}
toString() {
return '[Object Model(' + this.name + ')]';
return '[object ' + Object.getPrototypeOf(this).constructor.name + '<' +
this.name + '>]';
}
toJSON() {
return this.toString();
}
inspect() {

@@ -399,101 +430,66 @@ return this.toString();

_addAssociation(attribute, kind, options) {
if (!(attribute && typeof attribute === 'string'))
throw new ArgumentError('You must provide attribute name');
if (this.fields[attribute])
throw new ArgumentError('Model "%s" has already field with name "%s"',
this.name, attribute);
if (!options)
throw new ArgumentError('You must provide options');
const createAssociation = (options) => {
if (!options.model)
throw new ArgumentError('You must provide options.model');
if (!options.foreignKey || typeof options.foreignKey !== 'string')
throw new ArgumentError('You must provide options.foreignKey');
if (!typeof options.sourceKey || typeof options.sourceKey !== 'string')
throw new ArgumentError('You must provide options.sourceKey');
this.getField(options.sourceKey);
options.model.getField(options.foreignKey);
return new Association({
kind,
sourceModel: this,
sourceKey: options.sourceKey,
foreignKey: options.foreignKey,
foreignModel: options.model
});
};
if (typeof options.model === 'string') {
const opts = Object.assign({}, options);
options = () => {
opts.model = this.owner.get(opts.model);
return opts;
};
}
this._associations.set(attribute,
(typeof options === 'function') ? () => {
const a = createAssociation(options());
this._associations.set(attribute, a);
return a;
} : createAssociation(options));
getDataFields() {
const result = [];
for (const [key, f] of this.fields.entries())
if (!f.foreignModel)
result.push(key);
return result;
}
_prepareFindOptions(options, silent) {
if (Array.isArray(options))
return this._prepareFindOptions({attributes: options}, silent);
let i = 0;
const addAttribute = (target, key, value) => {
if (typeof value === 'string' || value == null)
target[key] = value || key;
target[key] = value && value !== key ? value : null;
else if (Array.isArray(value))
target[key] = this._prepareFindOptions({attributes: value}, silent);
else if (isPlainObject(value))
value = {attributes: value};
/* istanbul ignore else */
if (isPlainObject(value)) {
value.fieldName = value.fieldName || key;
target[key] = this._prepareFindOptions(value, silent);
}
i++;
};
const parseAttributes = (target, value) => {
let i = 0;
if (Array.isArray(value)) {
value.forEach(v => {
i++;
const COLUMN_PATTERN = /^([a-zA-Z][\w$]*)(?:\.?([\w$]+))? *([\w$]+)?$/;
for (const v of value) {
if (typeof v === 'string') {
const m = v.match(/^([\w$]*\.?[\w$]+) *([\w$]*)$/);
const m = v.match(COLUMN_PATTERN);
if (!m) {
if (silent)
throw new ArgumentError('"%s" is not a valid column name', v);
return;
if (silent) continue;
throw new ArgumentError('"%s" is not a valid column name', v);
}
addAttribute(target, m[2] || m[1], m[1]);
return;
addAttribute(target, (m[3] || m[2] || m[1]),
m[1] + (m[2] ? '.' + m[2] : ''));
continue;
}
if (isPlainObject(v))
if (isPlainObject(v)) {
parseAttributes(target, v);
});
continue;
}
/* istanbul ignore next */
if (!silent)
throw new ArgumentError('"%s" is not a valid column name', v);
}
} else if (isPlainObject(value)) {
Object.getOwnPropertyNames(value).forEach(v => {
addAttribute(target, v, value[v]);
i++;
});
} else {
/* istanbul ignore else */
if (isPlainObject(value)) {
for (const v of Object.getOwnPropertyNames(value))
addAttribute(target, v, value[v]);
}
}
return i ? target : null;
return i && target || /* istanbul ignore next */null;
};
const result = {};
const result = merge.deep.clone(options);
result.attributes = parseAttributes({}, options.attributes);
result.where = !options.where || Array.isArray(options.where) ?
options.where : [options.where];
result.orderBy = !options.orderBy || Array.isArray(options.orderBy) ?
options.orderBy : [options.orderBy];
result.limit = options.limit;
result.offset = options.offset;
result.as = options.as;
result.filter = !options.filter || Array.isArray(options.filter) ?
options.filter : [options.filter];
result.sort = !options.sort || Array.isArray(options.sort) ?
options.sort : [options.sort];
return result;

@@ -505,11 +501,13 @@ }

* @param {Object} attributes
* @param {Object} options
* @return {{}}
* @param {Boolean} silent
* @param {Boolean} [removePrimaryKey]
* @return {Object}
* @private
*/
_prepareUpdateValues(attributes, options) {
_prepareUpdateValues(attributes, silent, removePrimaryKey) {
const values = {};
Object.getOwnPropertyNames(attributes).forEach((name) => {
const field = this.getField(name, options.silent);
if (field && (!(field.primaryKey && options.removePrimaryKey)))
const field = this.getField(name, silent);
/* istanbul ignore else */
if (field && (!(field.primaryKey && removePrimaryKey)))
values[name] = attributes[name];

@@ -520,29 +518,94 @@ });

_prepareReturning(attributes, silent) {
const returningColumns = {};
Object.getOwnPropertyNames(attributes).forEach((alias) => {
const fname = attributes[alias];
_prepareReturning(value, silent) {
let attributes;
attributes = value === '*' ? this.getDataFields() :
(typeof value === 'object' ? value : [value]);
attributes = this._prepareFindOptions({attributes}, silent).attributes;
/* istanbul ignore else */
if (this.keyFields) {
for (const f of this.keyFields) {
let keyExists;
for (const attr of Object.getOwnPropertyNames(attributes)) {
if (f === (attributes[attr] || attr)) {
keyExists = true;
break;
}
}
if (!keyExists)
attributes[f] = null;
}
}
const columns = {};
for (const alias of Object.getOwnPropertyNames(attributes)) {
const fname = attributes[alias] || alias;
const field = this.getField(fname, silent);
if (field)
returningColumns[fname] = field.jsType.toLowerCase();
});
return returningColumns;
if (field && field.jsType)
columns[fname] = field.jsType.toLowerCase();
}
return {
attributes,
columns
};
}
_prepareKeyValues(keyValues) {
/* istanbul ignore next */
if (!(this.keyFields && this.keyFields.length))
throw new ErrorEx('No key field defined for model "%s"', this.name);
if (typeof keyValues !== 'object') {
if (this.keyFields.length < 1)
throw new ArgumentError('You must provide key values');
return {[this.keyFields[0]]: keyValues};
} else {
this.keyFields.forEach(n => {
if (keyValues[n] === undefined)
throw new ArgumentError('You must provide value for key field "%s"', n);
});
return keyValues;
return null;
if (typeof keyValues !== 'object')
return {
[this.keyFields[0]]: keyValues == null ?
/* istanbul ignore next */null : keyValues
};
else {
const result = {};
for (const n of this.keyFields)
result[n] = keyValues[n] == null ?
/* istanbul ignore next */null : keyValues[n];
return result;
}
}
_responseReturning(dbobj, resp, options) {
let needFind;
const result = {};
for (const attr of Object.getOwnPropertyNames(options.attributes)) {
const fieldName = options.attributes[attr] || attr;
const field = this.getField(fieldName, true);
/* istanbul ignore else */
if (field) {
if (field.foreignModel) {
needFind = true;
break;
}
result[attr] = resp.rows[0][attr];
}
}
if (!(needFind && this.keyFields))
return result;
const opts = {
model: this,
connection: dbobj,
silent: options.silent,
attributes: options.attributes,
filter: [],
showSql: options.showSql != null ?
/* istanbul ignore next */options.showSql : this.orm.options.showSql
};
for (const n of this.keyFields) {
opts.filter.push({[n]: resp.rows[0][n]});
}
return (new FindContext(opts)).execute(options.scope)
.then(result => result[0]);
}
/* istanbul ignore next */
static get Op() {
return sqb.Op;
}
}

@@ -549,0 +612,0 @@

@@ -14,10 +14,8 @@ /* UNIQORM

const {EventEmitter} = require('events');
const {ArgumentError} = require('errorex');
const {ErrorEx, ArgumentError} = require('errorex');
const Model = require('./Model');
const Schema = require('./Schema');
const isPlainObject = require('putil-isplainobject');
/**
* Module variables
* @private
*/
const MODEL_NAME_PATTERN = /^(?:([A-Za-z]\w*)\.)?([A-Za-z]\w*)?$/;
const defaultSchemaSymbol = Symbol('default');

@@ -32,4 +30,6 @@ /**

* @param {Object} [sqbPool]
* @param {Object} options
* @param {Boolean} options.silent
* @param {Object} [options]
* @param {Boolean} [options.silent]
* @param {Boolean} [options.showSql]
* @param {Boolean} [options.defaultPrimaryKey='id']
* @constructor

@@ -40,16 +40,31 @@ * @public

super();
if (!(sqbPool && typeof sqbPool.select === 'function')) {
options = sqbPool;
sqbPool = undefined;
}
if (sqbPool && typeof sqbPool.select !== 'function')
throw new ArgumentError('First argument can be an SQB pool instance only');
if (sqbPool)
this.pool = sqbPool;
this.models = {};
this._schemas = {};
this.options = options || {};
this.options.defaultPrimaryKey = this.options.defaultPrimaryKey || 'id';
}
/**
*
* @return {Object}
*/
get schemas() {
return this._schemas;
}
/* istanbul ignore next */
/**
*
* @return {Schema}
*/
get defaultSchema() {
return this._schemas[defaultSchemaSymbol];
}
/**
* Creates a new Model
*
* @param {String} name
* @param {Object} modelDef

@@ -59,22 +74,19 @@ * @return {Model}

*/
define(name, modelDef) {
if (typeof name !== 'string')
throw new ArgumentError('A string value required for model name');
if (!name.match(MODEL_NAME_PATTERN))
throw new ArgumentError('Invalid model name "%s"', name);
define(modelDef) {
if (!isPlainObject(modelDef))
throw new ArgumentError('Model definition argument (modelDef) is empty or is not valid');
if (this.models[name])
throw new ArgumentError('Model `%s` already exists', name);
const schemaName = modelDef.schema || defaultSchemaSymbol;
let schema = this.schemas[schemaName];
if (typeof modelDef !== 'object')
throw new ArgumentError('Model definition argument (modelDef) is empty or is not valid');
if (schema && schema.models[modelDef.name])
throw new ArgumentError('Model "%s" already exists in schema %s',
modelDef.name, schema.name);
if (typeof modelDef.tableName !== 'string')
throw new ArgumentError('"tableName" property is empty or is not valid');
schema = schema ||
(this.schemas[schemaName] =
new Schema(this, String(schemaName), !modelDef.schema));
if (typeof modelDef.fields !== 'object')
throw new ArgumentError('`fields` argument is empty or is not valid');
const model = this.models[name] = new Model(this, name, modelDef);
this.emit('define', name, model);
const model = new Model(schema, modelDef);
schema.models[modelDef.name] = model;
return model;

@@ -84,14 +96,82 @@ }

/**
* Returns Model constructor
* Returns Model
*
* @param {string} [name]
* @return {Function|undefined}
* @param {string} schemaName
* @return {Schema}
* @public
*/
get(name) {
const model = this.models[name];
if (!model)
throw new Error('Model "' + name + '" not found');
return model;
getSchema(schemaName) {
const schema = this.schemas[schemaName];
if (!schema)
throw new ErrorEx('No such "%s" schema defined', schemaName);
return schema;
}
/**
* Returns Model
*
* @param {string} [schemaName]
* @param {string} modelName
* @return {Model}
* @public
*/
get(schemaName, modelName) {
if (arguments.length === 1 && schemaName.includes('.')) {
const a = schemaName.split(/\./);
schemaName = a[0];
modelName = a[1];
} else if (arguments.length <= 1) {
modelName = schemaName;
const keys = this.getSchemaKeys();
const schema = keys.length === 1 ?
this.schemas[keys[0]] :
this.getSchema(defaultSchemaSymbol);
return schema.getModel(modelName);
}
return this.getSchema(schemaName).getModel(modelName);
}
/**
*
*/
prepare() {
const schemaKeys = this.getSchemaKeys();
const modelKeys = {};
/* Phase 1. Build key fields of each model */
for (const schKey of schemaKeys) {
const schema = this.schemas[schKey];
modelKeys[schKey] = Object.keys(schema.models);
for (const modelKey of modelKeys[schKey]) {
const model = schema.models[modelKey];
model._keyFields = [];
for (const [key, field] of model.fields.entries()) {
if (field.primaryKey)
model._keyFields.push(key);
}
}
}
/* Phase 2. Prepare fields */
for (const schKey of schemaKeys) {
const schema = this.schemas[schKey];
for (const modelKey of modelKeys[schKey]) {
const model = schema.models[modelKey];
for (const field of model.fields.values()) {
field.prepare();
}
}
}
}
/**
*
* @return {Array<string>}
*/
getSchemaKeys() {
const schemaKeys = Object.keys(this.schemas);
if (this.schemas[defaultSchemaSymbol])
schemaKeys.push(defaultSchemaSymbol);
return schemaKeys;
}
}

@@ -98,0 +178,0 @@

{
"name": "uniqorm",
"description": "Easy to use, multi-dialect ORM framework for JavaScript",
"version": "1.6.3",
"description": "Multi dialect and multi schema ORM framework for enterprise level NodeJS applications",
"version": "2.0.0",
"author": "Panates Ltd.",

@@ -26,15 +26,17 @@ "contributors": [

"putil-isplainobject": "^1.0.1",
"putil-merge": "^2.0.0",
"putil-promisify": "^1.1.0",
"putil-waterfall": "^1.2.1"
"putil-merge": "^2.0.1",
"putil-waterfall": "^2.0.2"
},
"devDependencies": {
"babel-eslint": "^8.2.6",
"eslint": "^5.3.0",
"eslint-config-google": "^0.9.1",
"istanbul": "^0.4.5",
"mocha": "^5.2.0"
"babel-eslint": "^9.0.0",
"coveralls": "^3.0.2",
"eslint": "^5.6.0",
"eslint-config-google": "^0.10.0",
"mocha": "^5.2.0",
"nyc": "^13.0.1",
"sqb": "^3.4.2",
"sqb-connect-pg": "^3.0.7"
},
"peerDependencies": {
"sqb": "^2.0.4"
"sqb": ">=3.4.2"
},

@@ -49,7 +51,11 @@ "engines": {

],
"nyc": {
"temp-directory": "./coverage/.nyc_output"
},
"scripts": {
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/"
"test": "mocha --require ./test/support/env --reporter spec --bail --check-leaks test/",
"cover": "nyc --reporter html --reporter text npm run test",
"travis-test": "mocha --require ./test/support/env-travis --require ./test/support/env --reporter spec --check-leaks test/",
"travis-cover": "nyc --reporter lcovonly npm run travis-test"
}
}

@@ -1,2 +0,2 @@

# UNIQ-ORM
# Uniqorm

@@ -7,10 +7,21 @@ [![NPM Version][npm-image]][npm-url]

[![Test Coverage][coveralls-image]][coveralls-url]
[![Dependencies][dependencies-image]][dependencies-url]
[![DevDependencies][devdependencies-image]][devdependencies-url]
[![Package Quality][quality-image]][quality-url]
UNIQ-ORM is a easy to use, multi-dialect ORM framework for JavaScript;
## About Uniqorm
Note: UNIQ-ORM is in alpha state. Use it only for testing purposes only!
Uniqorm is an multi dialect and multi schema ORM framework for enterprise level NodeJS applications;
## Main features
- Can work with any dialect that SQB[https://github.com/panates/sqb] supports
- Supports multi schemas
- Helps keeping server resources with "use-resources-on-demand" feature.
- Offers very high performance for graph queries.
- Support latest JavaScript language standards
## Node Compatibility

@@ -17,0 +28,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc