Comparing version 1.6.3 to 2.0.0
@@ -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(); |
165
lib/Field.js
@@ -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); |
817
lib/Model.js
@@ -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 @@ |
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
82578
5
32
2729
49
8
2
+ Addeddebug@4.4.0(transitive)
+ Addeddoublylinked@2.5.5(transitive)
+ Addederrorex@2.3.2(transitive)
+ Addedlightning-pool@2.4.0(transitive)
+ Addedputil-flattentext@2.1.1(transitive)
+ Addedputil-merge@3.13.0(transitive)
+ Addedputil-taskqueue@2.5.5(transitive)
+ Addedputil-waterfall@2.1.1(transitive)
+ Addedsqb@3.9.2(transitive)
+ Addedstackframe@1.3.4(transitive)
- Removedputil-promisify@^1.1.0
- Removeddebug@3.2.7(transitive)
- Removeddoublylinked@1.0.7(transitive)
- Removedlightning-pool@1.2.0(transitive)
- Removedputil-flattentext@1.1.2(transitive)
- Removedputil-taskqueue@1.3.1(transitive)
- Removedputil-waterfall@1.2.1(transitive)
- Removedsqb@2.0.4(transitive)
Updatedputil-merge@^2.0.1
Updatedputil-waterfall@^2.0.2