ts-db-helper
Version:
Simple ORM based on TypeScript
1,514 lines (1,488 loc) • 182 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs/Subject'), require('rxjs/add/operator/share'), require('rxjs/Observable'), require('rxjs/add/observable/combineLatest'), require('rxjs/add/operator/map'), require('rxjs/add/operator/switchMap'), require('rxjs/add/Observable/from'), require('rxjs/add/Observable/empty'), require('rxjs/add/operator/catch')) :
typeof define === 'function' && define.amd ? define(['exports', 'rxjs/Subject', 'rxjs/add/operator/share', 'rxjs/Observable', 'rxjs/add/observable/combineLatest', 'rxjs/add/operator/map', 'rxjs/add/operator/switchMap', 'rxjs/add/Observable/from', 'rxjs/add/Observable/empty', 'rxjs/add/operator/catch'], factory) :
(factory((global.TsDbHelper = {}),global.Rx,global.Rx.Observable.prototype,global.Rx));
}(this, (function (exports,Subject,share,Observable) { 'use strict';
/**
* @class DbHelperModuleConfig is a config model for the module.
*
* @author Olivier Margarit
* @since 0.1
*/
var DbHelperModuleConfig = /** @class */ (function () {
function DbHelperModuleConfig() {
/**
* @property version, the model version
*/
this.version = '';
/**
* @property autoIncrementVersion, flag to auto increment version with the number of
* model declared, this is a trick for developpement issues due to compilator import
* optimisation. Your model, even if it is define will not be imported in the project
* until it is used.
* To prevent misunderstanding of what is happening this option aim to automatically
* call model migration on model use and mange new table creation without manually
* increment the model version.
*/
this.autoIncrementVersion = true;
}
return DbHelperModuleConfig;
}());
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
/**
* @class QueryError is thrown when a query fails
*
* @see Error
* @author Olivier Margarit
* @since 0.1
*/
var QueryError = /** @class */ (function () {
/**
* @public
* @constructor
* @param {string} message message explaining in details error
* @param {string} query query text that did failed execution
* @param {string} params query params of the failed query
*/
function QueryError(message, query, params) {
var _newTarget = this.constructor;
this.message = message;
this.query = query;
this.params = params;
Object.setPrototypeOf(this, _newTarget.prototype);
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.name = 'query error';
}
/**
* @public
* @method toString convert error to string
*
* @return {string} string represation of the error
*/
QueryError.prototype.toString = function () {
return this.name + '\n' + this.message + (this.query ? '\nquery: ' + this.query : '') +
(this.params ? '\nparams: ' + this.params : '');
};
return QueryError;
}());
var DefaultLogger = /** @class */ (function () {
function DefaultLogger() {
this.levelValue = DefaultLogger.levels[3];
}
Object.defineProperty(DefaultLogger.prototype, "level", {
set: function (level) {
if (DefaultLogger.levels.indexOf(level) < 0) {
throw new Error('level \'' + level + '\' is not a valid log level');
}
},
enumerable: true,
configurable: true
});
DefaultLogger.prototype.isLogLevelActivated = function (target) {
return DefaultLogger.levels.indexOf(this.levelValue) <= DefaultLogger.levels.indexOf(target);
};
DefaultLogger.prototype.log = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('verbose')) {
console.log.apply(console, args);
}
};
DefaultLogger.prototype.trace = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('trace')) {
console.trace.apply(console, args);
}
};
DefaultLogger.prototype.debug = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('debug')) {
console.log.apply(console, args);
}
};
DefaultLogger.prototype.info = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('info')) {
console.info.apply(console, args);
}
};
DefaultLogger.prototype.warn = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('warn')) {
console.warn.apply(console, args);
}
};
DefaultLogger.prototype.error = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('error')) {
console.error.apply(console, args);
}
};
DefaultLogger.prototype.fatal = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isLogLevelActivated('fatal')) {
console.error.apply(console, args);
}
};
DefaultLogger.levels = ['verbose', 'debug', 'trace', 'info', 'warn', 'error', 'fatal'];
return DefaultLogger;
}());
/**
* @class UnsatisfiedRequirementError is thrown when a requirement is unsatisfied
*
* @see Error
* @author Olivier Margarit
* @since 0.1
*/
var UnsatisfiedRequirementError = /** @class */ (function () {
/**
* @public
* @constructor
* @param {string} message message explaining in details error
*/
function UnsatisfiedRequirementError(message) {
var _newTarget = this.constructor;
this.message = message;
Object.setPrototypeOf(this, _newTarget.prototype);
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.name = 'unsatisfied requirement error';
}
/**
* @public
* @method toString convert error to string
*
* @return {string} string represation of the error
*/
UnsatisfiedRequirementError.prototype.toString = function () {
return this.name + '\n' + this.message;
};
return UnsatisfiedRequirementError;
}());
/**
* @public
* @class PendingDbQuery
*
* @description
* This class is a part of private API
* it is a combination of dbQuery and linked observer to stack the query
* during connector activation
*
* @author Olivier Margarit
* @since 0.1
*/
var PendingDbQuery = /** @class */ (function () {
/**
* @public
* @constructor pending query constructor to create new stackable query
* waiting for the connector to be ready
*
* @param {DbQuery} dbQuery db query object with all query informations
* see the class documentation
* @param {Observable<any>} observer the observer to notify the subscribers query result
*/
function PendingDbQuery(dbQuery, observer) {
this.dbQuery = dbQuery;
this.observer = observer;
}
return PendingDbQuery;
}());
/**
* @private
* @class QueryManager
*
* @description
* This class is a singleton manging query
* This manager has not to be exposed, it is used to handle queries
* with the connector. It stack it during connector is not ready and
* release it when connector ca query
*
* @author Olivier Margarit
* @since 0.1
*/
var QueryManager = /** @class */ (function () {
/**
* @private
* @constructor private constructor to preserve from other instance creation
*/
function QueryManager() {
/**
* @private
* @property {Array<PendingDbQuery>} pendingDbQueries stack of queries to fire when the
* connector is ready
*/
this.pendingDbQueries = [];
/**
* @private
* @property {Array<PendingDbQuery>} pendingBatchDbQueries stack of queries to fire in a single transaction
*/
this.pendingBatchDbQueries = [];
/**
* @private
* @property {boolean} isReady flag that should pas to true only when
* query can be sent
*/
this.isReady = false;
/**
* @private
* @property {boolean} isInitializationFailed flag to reject all queries
* in case of connector activation failure
*/
this.isInitializationFailed = false;
this.supportRowidValue = false;
}
Object.defineProperty(QueryManager.prototype, "logger", {
get: function () {
if (!this.loggerValue) {
this.loggerValue = new DefaultLogger();
}
return this.loggerValue;
},
enumerable: true,
configurable: true
});
Object.defineProperty(QueryManager.prototype, "supportRowid", {
get: function () {
return this.supportRowidValue;
},
enumerable: true,
configurable: true
});
/**
* @static
* @public
* @method init is a part of the private API, config is submitted to
* pass connector, model migration and other config. see {@link NgDbHelperModuleConfig}
* for more informations
*
* @param {NgDbHelperModuleConfig} config the module configuration with connector instance and model migration
*
* @return {QueryManager} the initialized instance
*/
QueryManager.init = function (config) {
var instance = QueryManager.getInstance();
instance.queryConnector = config.queryConnector;
instance.modelMigration = config.modelMigration;
instance.supportRowidValue = config.queryConnector.supportRowid;
if (config.logger) {
instance.loggerValue = config.logger;
}
ModelManager.version = config.version;
if (config.autoIncrementVersion && ModelManager.version) {
// compute auto upgrade version
ModelManager.version += '.' + ModelManager.getInstance().getModelCount();
}
instance.queryConnector.onReady().subscribe(function (ready) {
if (ready) {
instance.onQueryConnectorReady();
}
else {
instance.onInitializationFailure(null);
}
}, instance.onInitializationFailure);
return instance;
};
/**
* @static
* @public
* @method getInstance to get the unique QueryManager instance
*
* @return {QueryManager} the instance
*/
QueryManager.getInstance = function () {
return QueryManager.instance;
};
/**
* @private
* @method onQueryConnectorReady is called when query connector is ready to
* manage model migration then dequeuing each pending queries
*/
QueryManager.prototype.onQueryConnectorReady = function () {
var _this = this;
if (this.queryConnector && this.modelMigration) {
var modelMigration_1 = this.modelMigration;
this.queryConnector.getDbVersion().subscribe(function (version) {
_this.logger.info('old version: ' + version);
var dataModel = ModelManager.getInstance().getDataModel();
dataModel.version = ModelManager.version;
_this.logger.info('new version: ' + dataModel.version);
var isFailed = false;
if (!version) {
_this.logger.info('call init data model migration');
modelMigration_1.initModel(dataModel).subscribe(function () {
_this.logger.info('init data model migration successed');
}, function (err) {
isFailed = err;
_this.logger.warn('init data model migration failed');
}, function () {
if (isFailed) {
_this.onInitializationFailure(isFailed);
}
else {
_this.dequeuePendingRequest();
}
});
}
else if (version !== ModelManager.version) {
_this.logger.info('call upgrade data model migration');
modelMigration_1.upgradeModel(dataModel, version).subscribe(function () {
_this.logger.info('upgrade data model migration successed');
}, function (err) {
isFailed = err;
_this.logger.warn('upgrade data model migration failed');
}, function () {
if (isFailed) {
_this.onInitializationFailure(isFailed);
}
else {
_this.dequeuePendingRequest();
}
});
}
else {
_this.dequeuePendingRequest();
}
}, function (err) { return _this.onInitializationFailure(err); });
}
else {
throw (new UnsatisfiedRequirementError('ModelMigration or QueryConnector object is missing, check and fix NgDbHelperModule configuration !'));
}
};
/**
* @private
* @method onInitializationFailure is called on initilization failure to cancel
* all started queries and nexts
*
* @param {Error} err the error return by the initialization failure
*/
QueryManager.prototype.onInitializationFailure = function (err) {
this.logger.error(err);
this.isInitializationFailed = true;
this.dequeuePendingRequest();
};
/**
* @private
* @method dequeuePendingRequest is called to dequeue pending request
*/
QueryManager.prototype.dequeuePendingRequest = function () {
while (this.pendingDbQueries.length) {
var pendingDbQuery = this.pendingDbQueries.shift();
if (pendingDbQuery) {
this.executeQuery(pendingDbQuery.dbQuery, pendingDbQuery.observer);
}
}
this.isReady = true;
};
/**
* @public
* @method startBatch start transaction for multiple queries
* @param {any} locker the locker that can fire the transaction if none is set
*/
QueryManager.prototype.startBatch = function (locker) {
if (!this.batchLock) {
this.batchLock = locker;
}
};
/**
* @public
* @method query private API to start queries, it check if queries can be executed
* If initialization failed, error is sent back. If connector is not ready, query will
* be stack until connector ready signal
*
* @param {DbQuery} dbQuery query information with params
*
* @return {Observable<QueryResult<any>>} in case of success, {@link QueryResult<any>} is passed
* in case of failure, {@link QueryError} is passed
*/
QueryManager.prototype.query = function (dbQuery) {
var subject = new Subject.Subject();
if (this.batchLock) {
this.pendingBatchDbQueries.push({
dbQuery: dbQuery,
observer: subject
});
}
else if (this.isReady) {
this.executeQuery(dbQuery, subject);
}
else {
this.pendingDbQueries.push(new PendingDbQuery(dbQuery, subject));
}
return subject;
};
/**
* @public
* @method queryBatch should only be called from {@link QueryBatch} function.
*
* @param locker the locker passed to startBatch that allow queries releasing
*
* @return {Observable<QueryResult<any>>} in case of success, {@link QueryResult<any>} is passed
* in case of failure, {@link QueryError} is passed
*
* @since 0.2
*/
QueryManager.prototype.execBatch = function (locker) {
var queries = [];
var observers = [];
if (!this.batchSubject) {
this.batchSubject = new Subject.Subject();
}
if (this.batchLock === locker) {
this.batchLock = null;
}
else {
return this.batchSubject;
}
while (this.pendingBatchDbQueries.length) {
var pendingQuery = this.pendingBatchDbQueries.shift();
queries.push(pendingQuery.dbQuery);
observers.push(pendingQuery.observer);
}
var obs = this.queryConnector.queryBatch(queries);
obs.map(function (res) {
for (var _i = 0, observers_1 = observers; _i < observers_1.length; _i++) {
var observer = observers_1[_i];
observer.next({
insertId: undefined,
rowsAffected: 0,
rows: {
length: 0,
item: function (index) {
return null;
},
toArray: function () {
return [];
}
}
});
observer.complete();
}
return res;
});
obs.share().subscribe(this.batchSubject);
this.batchSubject = undefined;
return obs;
};
/**
* @private
* @method executeQuery should be called only when connector is ready
*
* @param {DbQuery} dbQuery query information with params
* @param {Observer} observer observer to manage query callback
*
* @return {Observable<QueryResult<any>>} in case of success, {@link QueryResult<any>} is passed
* in case of failure, {@link QueryError} is passed
*/
QueryManager.prototype.executeQuery = function (dbQuery, observer) {
if (this.isInitializationFailed) {
var error = new QueryError('query manager initialization did failed', dbQuery.query, dbQuery.params ? dbQuery.params.join(', ') : '');
observer.error(error);
}
else {
if (this.queryConnector) {
this.queryConnector.query(dbQuery).subscribe(observer);
}
else {
throw (new UnsatisfiedRequirementError('QueryConnector object is missing, check and fix NgDbHelperModule configuration !'));
}
}
};
/**
* @static
* @private
* @property {QueryManager} instance private reference to the model manager instance
*/
QueryManager.instance = new QueryManager();
return QueryManager;
}());
/**
* @public
* @class QueryCount
*
* @description
* For design reasons this class should not be used directly.
* Prefer use {@link Count} function.
*
* @param T @extends DbHelperModel a model declared with table and column annotations
*
* @example
* ```typescript
* // count todos
* Count(Select(Todo).where({isDone: false}})).exec().subscribe((cout: number) => {
* // do something with the result...
* }, (err) => {
* // do something with the error...
* });
* ```
*
* @author Olivier Margarit
* @since 0.2
*/
var QueryCount = /** @class */ (function () {
/**
* @public
* @constructor create instance of QueryCount
* @param querySelect the query to count the result
*/
function QueryCount(querySelect) {
this.querySelect = querySelect;
}
/**
* @public
* @method build should be removed to be a part of the private API
*
* @return {DbQuery} of the query with the string part and
* clauses params.
*/
QueryCount.prototype.build = function () {
return this.querySelect.copy().projection(['count(*)']).build();
};
/**
* @public
* @method exec to execute the query and asynchronously retreive result.
*
* @return {Observable<number>} observable to subscribe and returning a count number as result
*/
QueryCount.prototype.exec = function () {
var dbQuery = this.build();
return QueryManager.getInstance().query(dbQuery).map(function (qr) {
if (qr.rows.length) {
var key = 'count(*)';
return qr.rows.item(0)[key];
}
else {
throw new QueryError('no result error...', dbQuery.query, dbQuery.params.join(', '));
}
});
};
return QueryCount;
}());
/**
* @public
* @function Count
*
* @description
* function helper to count element that a query could return
*
* @param T @extends DbHelperModel a model declared with table and column annotations
*
* @example
* ```typescript
* // count todos
* Count(Select(Todo).where({isDone: false}})).exec().subscribe(count: number) => {
* // do something with the result...
* }, (err) => {
* // do something with the error...
* });
* ```
*
* @return {QueryCount<T>} instance
*
* @author Olivier Margarit
* @since 0.2
*/
function Count(select) {
return new QueryCount(select);
}
/**
* @public
* @class ShadowValue
*
* @description
* This class is the data hidden model that link visible model to data store real value.
* this informations are used in the shadow of the class model interface
*
* @author Olivier Margarit
* @since 0.2
*/
var ShadowValue = /** @class */ (function () {
function ShadowValue() {
}
return ShadowValue;
}());
/**
* @public
* @class ClauseComparators
*
* @description
* list of managed comparators. This class should be converted to an enum in a furture version
*
* @author Olivier Margarit
*
* @since 0.2
*/
var ClauseComparators = /** @class */ (function () {
function ClauseComparators() {
}
/**
* @public
* @static
* @method isKeyOf check if string is a valid key of ClauseComparator, this check
* is not case sensitive
*
* @param {string} val the value to check
*
* @return {boolean} is true if key will return a valid value with {@link ClauseComparator.valueOf}
*/
ClauseComparators.isKeyOf = function (val) {
return ClauseComparators.hasOwnProperty(val.toUpperCase());
};
/**
* @public
* @static
* @method valueOf get value of ClauseComparator by using a valid key val.
*
* @param {string} val the value key
*
* @return {boolean} the comparator linked to the key
*/
ClauseComparators.valueOf = function (val) {
return ClauseComparators[val.toUpperCase()];
};
/**
* @public
* @constant {string} DIFF the not equal comparator "!="
*/
ClauseComparators.DIFF = '!=';
/**
* @public
* @constant {string} LT the lower than comparator "<"
*/
ClauseComparators.LT = '<';
/**
* @public
* @constant {string} LTE the lower than or equals comparator "<="
*/
ClauseComparators.LTE = '<=';
/**
* @public
* @constant {string} GT the greater than comparator ">"
*/
ClauseComparators.GT = '>';
/**
* @public
* @constant {string} GTE the greater than equal comparator ">="
*/
ClauseComparators.GTE = '>=';
/**
* @public
* @constant {string} LIKE the like comparator "LIKE"
*/
ClauseComparators.LIKE = 'LIKE';
/**
* @public
* @constant {string} IN the in comparator "IN"
*/
ClauseComparators.IN = 'IN';
/**
* @public
* @default
* @constant {string} EQ the equal comparator "="
*/
ClauseComparators.EQ = '=';
return ClauseComparators;
}());
/**
* @class UnsatisfiedRequirementError is thrown when a requirement is unsatisfied
*
* @see Error
* @author Olivier Margarit
* @since 0.1
*/
var NotImplementedError = /** @class */ (function () {
/**
* @public
* @constructor
* @param {string} message message explaining in details error
*/
function NotImplementedError(message) {
var _newTarget = this.constructor;
this.message = message;
Object.setPrototypeOf(this, _newTarget.prototype);
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.name = 'not implemented error';
}
/**
* @public
* @method toString convert error to string
*
* @return {string} string represation of the error
*/
NotImplementedError.prototype.toString = function () {
return this.name + '\n' + this.message;
};
return NotImplementedError;
}());
/**
* @private
* @class RelationType
*
* @description
* the list of relations type. This class should be converted to an enum in future version
*
* @author Olivier Margarit
*
* @since 0.2
*/
var RelationType = /** @class */ (function () {
function RelationType() {
}
/**
* @public
* @constant {string} ONE_TO_MANY the one to many relation
*/
RelationType.ONE_TO_MANY = 'OneToMany';
/**
* @public
* @constant {string} ONE_TO_ONE the one to one relation
*/
RelationType.ONE_TO_ONE = 'OneToOne';
/**
* @public
* @constant {string} MANY_TO_MANY the many to many relation
*/
RelationType.MANY_TO_MANY = 'ManyToMany';
/**
* @public
* @constant {string} MANY_TO_ONE the many to one relation
*/
RelationType.MANY_TO_ONE = 'ManyToOne';
return RelationType;
}());
/**
* @public
* @class DbColumn
*
* @description
* This class is a column model
* to help model migration to do his soup
*
* @author Olivier Margarit
* @since 0.1
*/
var DbColumn = /** @class */ (function () {
/**
* @public
* @constructor Create column instance
*
* @param {string} name column name
*/
function DbColumn(name) {
/**
* @public
* @property {boolean} primaryKey define the column as primary key of the table,
* the default value is false
*/
this.primaryKey = false;
/**
* @public
* @property {boolean} autoIncrement define if the column value is auto incremented
* default value is false
*/
this.autoIncrement = false;
/**
* @public
* @property {boolean} unique define if column value should be unique. Default value
* is false
*/
this.unique = false;
/**
* @public
* @property {boolean} indexed define if column value should be indexed. Default value
* is false
*/
this.indexed = false;
/**
* @public
* @property {string} type define type of the column, type must be compatible with
* the field type plus the sqlite manged type
*/
this.type = 'string';
/**
* @public
* @property {string} foreignTable foreign table name
*/
this.foreignTable = null;
/**
* @public
* @property {string} foreignKey foreign key linked to current key
*/
this.foreignKey = null;
/**
* @public
* @property {string} foreignField field name of the foreign model
*/
this.foreignField = null;
/**
* @public
* @property {any} defaultValue the default value of the column
*/
this.defaultValue = undefined;
if (name) {
this.name = name;
this.field = this.name;
}
}
/**
* @public
* @method configure configure the column from configurator model
*
* @param {ColumnConfig} config the configurator object
*
* @since 0.2
*/
DbColumn.prototype.configure = function (config) {
if (config.name) {
this.name = config.name;
}
if (config.type) {
this.type = config.type;
}
if (config.primaryKey !== undefined) {
this.primaryKey = config.primaryKey;
}
if (config.unique !== undefined) {
this.unique = config.unique;
}
if (config.indexed !== undefined) {
this.indexed = config.indexed;
}
if (config.autoIncrement !== undefined) {
this.autoIncrement = config.autoIncrement;
}
};
/**
* @public
* @method fromAlias get aliased column
*
* @param {string} alias the alias label
*
* @return {DbColumn} the aliased column
*
* @since 0.2
*/
DbColumn.prototype.fromAlias = function (alias) {
var aliasColumn = new DbColumn(alias + '.' + this.name);
aliasColumn.field = alias + '.' + this.field;
aliasColumn.type = this.type;
aliasColumn.primaryKey = this.primaryKey;
aliasColumn.unique = this.unique;
aliasColumn.indexed = this.indexed;
aliasColumn.autoIncrement = this.autoIncrement;
return aliasColumn;
};
return DbColumn;
}());
/**
* @private
* @class ModelResult
*
* @description
* This class is private part of the API.
* A specific wrapper to convert QueryResult to typed QueryResult on demand
*
* @param T @extends {@link DbHelperModel}, a model declared with table and
* column annotations
*
* @author Olivier Margarit
* @since 0.1
*/
var ModelResult = /** @class */ (function () {
/**
* @public
* @constructor this is a private API and should not be available for integrators
*
* @param {QueryResult<any>} result the real QueryResult converted to return typed models
* @param {{new(): T}} model the target model to convert
* @param {Array<string>} projection the optional projection
*/
function ModelResult(result, model, projection) {
this.result = result;
this.model = model;
this.projection = projection;
this.cache = new Array(result.rows.length).fill(null);
}
Object.defineProperty(ModelResult.prototype, "rowsAffected", {
/**
* @public
* @property {number} rowsAffected serve real ResultQuery rowsAffected;
*/
get: function () {
return this.result.rowsAffected;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ModelResult.prototype, "insertId", {
/**
* @public
* @property {number} insertId serve real ResultQuery insertId;
*/
get: function () {
return this.result.insertId;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ModelResult.prototype, "rows", {
/**
* @public
* @property {Object} rows serve customized type ResultQuery rows;
*/
get: function () {
var _this = this;
return {
/**
* @public
* @property {number} length serve real ResultQuery rows.length;
*/
length: this.result.rows.length,
/**
* @public
* @method item get typed item from ResultQuery and keep it in cache
* to avoid doing the job two times
*
* @param {number} i the index of the item
*
* @return {T} the typed item
*/
item: function (i) {
if (!_this.cache[i]) {
var entity = new _this.model();
var item = _this.result.rows.item(i);
for (var key in entity.$$shadow) {
if (entity.$$shadow.hasOwnProperty(key)) {
var shadow = entity.$$shadow[key];
if (_this.projection && _this.projection.indexOf(key) < 0) {
continue;
}
if (item.hasOwnProperty(key)) {
shadow.val = item[key];
}
}
}
entity.$$partialWithProjection = _this.projection;
entity.$$rowid = item.hasOwnProperty('rowid') ? item.rowid : null;
entity.$$inserted = true;
entity.$$isModified = false;
_this.cache[i] = entity;
}
return _this.cache[i];
},
/**
* @public
* @method toArray convert rows to an array of instanciated models
*
* @return {Array<T>} the array of models
*/
toArray: function () {
for (var i = 0; i < _this.cache.length; i++) {
if (!_this.cache[i]) {
_this.rows.item(i);
}
}
return _this.cache;
}
};
},
enumerable: true,
configurable: true
});
return ModelResult;
}());
/**
* @public
* @class BadColumnDeclarationError
*
* @description
* thrown when a column declaration is detected
*
* @see Error
*
* @author Olivier Margarit
* @since 0.1
*/
var BadColumnDeclarationError = /** @class */ (function () {
/**
* @public
* @constructor
* @param {string} message message explaining in details error
*/
function BadColumnDeclarationError(message) {
var _newTarget = this.constructor;
this.message = message;
Object.setPrototypeOf(this, _newTarget.prototype);
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.name = 'bad column declaration error';
}
/**
* @public
* @method toString convert error to string
*
* @return {string} string represation of the error
*/
BadColumnDeclarationError.prototype.toString = function () {
return this.name + '\n' + this.message;
};
return BadColumnDeclarationError;
}());
/**
* @public
* @class DbTable
*
* @description
* This class is a table model to help model migration to do his soup
*
* @author Olivier Margarit
* @since 0.1
*/
var DbTable = /** @class */ (function () {
function DbTable() {
/**
* @public
* @property {number} version, the table model version, information to help migration
*/
this.version = 0;
/**
* @public
* @property {Array<DbColumn>} columnList is an array of DbColumn listing each column
* sql properties
*/
this.columnList = [];
/**
* @public
* @property {{[index: string]: DbColumn}} column key/value column list with column name as key
*/
this.columns = {};
/**
* @public
* @property {{[index: string]: DbColumn}} fields key/value column list with field name as key
*/
this.fields = {};
/**
* @public
* @property {{[index: string]: {[index: string]: DbRelationModel}}} relations two dimensional relation access by
* model name and relation key
*/
this.relations = {};
}
/**
* @public
* @method configure methode to configure table from configurator object
*
* @param {TableConfig} config the configurator
*
* @since 0.2
*/
DbTable.prototype.configure = function (config) {
if (config.version) {
this.version = config.version;
}
};
/**
* @public
* @method hasAutoIncrementedPrimaryKey check if table has auto increment column
*
* @return {boolean} true if table has auto increment column
*
* @since 0.2
*/
DbTable.prototype.hasAutoIncrementedPrimaryKey = function () {
for (var _i = 0, _a = this.columnList; _i < _a.length; _i++) {
var column = _a[_i];
if (column.primaryKey && column.autoIncrement) {
return true;
}
}
return false;
};
/**
* @public
* @method hasNoPrimaryKey check if table has no primary key
*
* @return {boolean} return true if table has no primary key
*
* @since 0.2
*/
DbTable.prototype.hasNoPrimaryKey = function () {
for (var _i = 0, _a = this.columnList; _i < _a.length; _i++) {
var column = _a[_i];
if (column.primaryKey) {
return false;
}
}
return true;
};
/**
* @public
* @method getPrimaryColumns get column playing primary role among table's columns
*
* @return {Array<DbColumn>} the structured primary column list
*
* @since 0.2
*/
DbTable.prototype.getPrimaryColumns = function () {
var columns = [];
for (var _i = 0, _a = this.columnList; _i < _a.length; _i++) {
var column = _a[_i];
if (column.primaryKey) {
columns.push(column);
}
}
return columns;
};
/**
* @public
* @method addRelation add relation to table model
*
* @param {{new(): DbHelperModel}} model the target model of the relation
* @param {DbRalationModel} relation the relation
* @param {string} key optional key to manage multiple relations to the same model
*
* @since 0.2
*/
DbTable.prototype.addRelation = function (model, relation, key) {
if (this.relations.hasOwnProperty(model.name) && this.relations[model.name].hasOwnProperty(key || DbTable.RELATIONS_DEFAULT_KEY)) {
throw new BadColumnDeclarationError('relation with key "' + (key || DbTable.RELATIONS_DEFAULT_KEY) + '" is inserted twice');
}
else {
if (!this.relations[model.name]) {
this.relations[model.name] = {};
}
this.relations[model.name][key || DbTable.RELATIONS_DEFAULT_KEY] = relation;
}
};
/**
* @public
* @method getRelation get relation from the targetted model and the optionale relation key
*
* @param {{new(): DbHelperModel}} model the target model of the relation
* @param {string} key optional key to manage multiple relations to the same model
*
* @return {DbRelationModel} the relation matching to the properties, return is null if no relation matches
*
* @since 0.2
*/
DbTable.prototype.getRelation = function (model, key) {
if (this.relations.hasOwnProperty(model.name) && this.relations[model.name].hasOwnProperty(key || DbTable.RELATIONS_DEFAULT_KEY)) {
return this.relations[model.name][key || DbTable.RELATIONS_DEFAULT_KEY];
}
return null;
};
/**
* @private
* @static
* @constant {string} RELATIONS_DEFAULT_KEY the default relation key
*/
DbTable.RELATIONS_DEFAULT_KEY = 'default';
return DbTable;
}());
/**
* @public
* @class DbQuery
*
* @description
* This class is a model to share query informations specifically to
* the query connector.
*
* @author Olivier Margarit
* @since 0.1
*/
var DbQuery = /** @class */ (function () {
function DbQuery() {
/**
* @public
* @property {number} page the page number to retrieve for select queries
*/
this.page = 0;
/**
* @public
* @property {Array<any>} params list of params of the query, see the sqlite
* documenetations to learn about query parameters
*/
this.params = [];
/**
* @public
* @property {string} query the query string
*/
this.query = '';
/**
* @public
* @property {number} size the number of result to retrieve per page
* on select statement
*/
this.size = 1000;
}
/**
* @public
* @method append is a part of private API.
* this method is used to build query by appending part of it
*
* @param {QueryPart} queryPart query part to append
*
* @return {DbQuery} the db query instance to chain part appending
*/
DbQuery.prototype.append = function (queryPart) {
this.query = this.query.trim() + ' ' + queryPart.content.trim();
this.params = this.params.concat(queryPart.params);
return this;
};
return DbQuery;
}());
/**
* @public
* @class ColumnClauseValue
*
* @description
* Clause that hold a column comparission with the model that can deliver an alias.
*
* @author Olivier Margarit
*
* @since 0.2
*/
var ColumnClauseValue = /** @class */ (function () {
/**
* @public
* @constructor
*
* @param {string} name the column name
* @param {string|IQueryHelper} aliasing optaional param that is the alias or provide it
*/
function ColumnClauseValue(name, aliasing) {
this.name = name;
this.aliasing = aliasing;
}
Object.defineProperty(ColumnClauseValue.prototype, "alias", {
/**
* @public
* @property {string} alias the column alias
*/
get: function () {
if (this.aliasing instanceof String) {
return this.aliasing;
}
return undefined;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ColumnClauseValue.prototype, "querySrc", {
/**
* @public
* @property {IQueryHelper} querySrc the query object that hold the column and deliver the alias
*/
get: function () {
if (!(this.aliasing instanceof String)) {
return this.aliasing;
}
return undefined;
},
enumerable: true,
configurable: true
});
/**
* @method fqn full qulaified name
*
* @return the full name with the namespace alias
*/
ColumnClauseValue.prototype.fqn = function () {
var fqn;
if (this.aliasing instanceof String) {
fqn = this.aliasing + '.' + this.name;
}
else if (this.aliasing && this.aliasing.alias) {
fqn = this.aliasing.alias + '.' + this.name;
}
else {
fqn = this.name;
}
return fqn;
};
return ColumnClauseValue;
}());
/**
* @public
* @class ClauseOperators
*
* @description
* list of managed operators. This class should be converted to an enum in a furture version
*
* @author Olivier Margarit
*
* @since 0.2
*/
var ClauseOperators = /** @class */ (function () {
function ClauseOperators() {
}
/**
* @public
* @static
* @method isKeyOf check if string is a valid key of ClauseOperator, this check
* is not case sensitive
*
* @param {string} val the value to check
*
* @return {boolean} is true if key will return a valid value with {@link ClauseOperator.valueOf}
*/
ClauseOperators.isKeyOf = function (val) {
return ClauseOperators.hasOwnProperty(val.toUpperCase());
};
/**
* @public
* @static
* @method valueOf get value of ClauseOperator by using a valid key val.
*
* @param {string} val the value key
*
* @return {boolean} the operator linked to the key
*/
ClauseOperators.valueOf = function (val) {
return ClauseOperators[val.toUpperCase()];
};
/**
* @public
* @constant {string} OR the or operator "OR"
*/
ClauseOperators.OR = 'OR';
/**
* @public
* @default
* @constant {string} AND the and operator "AND"
*/
ClauseOperators.AND = 'AND';
return ClauseOperators;
}());
/**
* @private
* @class QueryPart
*
* @description
* This class is private part of the API.
* It is an intermidiate query object for builded query parts
*
* @author Olivier Margarit
*
* @since 0.1
*/
var QueryPart = /** @class */ (function () {
function QueryPart() {
/**
* @public
* @property {string} content string part of the query
*/
this.content = '';
/**
* @public
* @property {Array<any>} params the parameters of this part of query
*/
this.params = [];
}
/**
* @public
* @method append append other query part to itself and return itself
* to chain appending
*
* @param {QueryPart} queryPart another query part to append
*
* @return {QueryPart} the query part itself
*/
QueryPart.prototype.append = function (queryPart) {
this.appendContent(queryPart.content);
this.params = this.params.concat(queryPart.params);
return this;
};
/**
* @public
* @method appendSub append sub query part
*
* @param {QueryPart} queryPart the query part to append
*
* @return {QueryPart} the query part instance to chain operation
*
* @since 0.2
*/
QueryPart.prototype.appendSub = function (queryPart) {
if (queryPart instanceof QueryPart) {
this.content += ' (';
this.append(queryPart);
this.content += ')';
}
else {
this.content += ' (';
this.content += queryPart.query.trim();
this.content += ')';
this.params = this.params.concat(queryPart.params);
}
return this;
};
/**
* @public
* @method appendContent append query content to the query part
*
* @param {string} content the string part to append
*
* @return {QueryPart} the query part instance to chain operation
*
* @since 0.2
*/
QueryPart.prototype.appendContent = function (content) {
this.content = this.content + ' ' + content.trim();
return this;
};
return QueryPart;
}());
/**
* @public API
* @class CompositeClause
*
* @description
* Clause that allow integrators to compare tuple of value like in this raw query:
* "SELECT * FROM Animals WHERE (species, color) IN (('dog', 'white'), ('cat', 'ginger'), ('bird', 'blue'))"
*
* @example
* ```typescript
* // Create a group of clauses
* const composite = new CompositeClause(['species', 'color'], [['