blow-query
Version:
Programmatically build queries which can be returned as JSON Object and used to fetch data from many sources.
275 lines (245 loc) • 6.79 kB
text/typescript
'use strict';
import {isString, isObject, isUndefined, isArray} from 'util';
import {QueryRaw, QuerySort, QueryWhere} from './interfaces';
/**
* Query object allows to to programmatically build queries which can be returned
* as JSON Object and used to fetch data from database, api endpoint etc.
*/
export class Query {
protected _where: QueryWhere;
protected _limit: number;
protected _skip: number;
protected _sort: QuerySort;
protected _select: string[];
/**
* Create instance of Query.
*/
constructor(query?: QueryRaw | Query) {
if (!isUndefined(query)) {
if (query instanceof Query) {
query = (<Query>query).toJSON();
}
}
const q: QueryRaw = <QueryRaw>(query || {});
this._where = q.where || {};
this._limit = q.limit || -1;
this._skip = q.skip || 0;
this._sort = q.sort || {};
this._select = q.select || [];
}
protected _addCondition(field: string, condition: string, value: any): Query {
if (isUndefined(this._where[field]) || isString(this._where[field])) {
this._where[field] = {};
}
this._where[field][condition] = value;
return this;
}
protected _sortItem(field, direction): QuerySort {
const item: QuerySort = {};
item[field] = direction;
return item;
}
/**
* Add condition which requires that field's value
* to be equal to provided value
*/
equal(field: string, value: any): Query {
this._where[field] = value;
return this;
}
/**
* Add condition which requires that field's value
* to be not equal to provided value
*/
notEqual(field: string, value: any): Query {
return this._addCondition(field, '$neq', value);
}
/**
* Add condition which requires that field's value
* to be less than provided value
*/
lessThan(field: string, value: any): Query {
return this._addCondition(field, '$lt', value);
}
/**
* Add condition which requires that field's value
* to be less than or equal to provided value
*/
lessThanOrEqual(field: string, value: any): Query {
return this._addCondition(field, '$lte', value);
}
/**
* Add condition which requires that field's value
* to be greater than provided value
*/
greaterThan(field: string, value: any): Query {
return this._addCondition(field, '$gt', value);
}
/**
* Add condition which requires that field's value
* to be greater than or equal to provided value
*/
greaterThanOrEqual(field: string, value: any): Query {
return this._addCondition(field, '$gte', value);
}
/**
* Add condition which requires that field's value
* to be contained in list of provided values
*/
containedIn(field: string, values: any[]): Query {
return this._addCondition(field, '$in', values);
}
/**
* Add condition which requires that field's value
* to be not contained in list of provided values
*/
notContainedIn(field: string, values: any[]): Query {
return this._addCondition(field, '$nin', values);
}
/**
* Add condition which requires that field's value
* match provided regular expression
*/
regex(field: string, value: RegExp): Query {
return this._addCondition(field, '$regex', value);
}
/**
* Add condition which requires that field's value
* contains provided value
*/
contains(field: string, value: string): Query {
return this._addCondition(field, '$regex', value);
}
/**
* Add condition which requires that field's value
* starts with provided value
*/
startsWith(field: string, value: string): Query {
return this._addCondition(field, '$regex', `^${value}`);
}
/**
* Add condition which requires that field's value
* ends with provided value
*/
endsWith(field: string, value: string): Query {
return this._addCondition(field, '$regex', `${value}$`);
}
/**
* Set field's name which will be used for sort. Results
* will be sort in ascending order
*/
ascending(field: string): Query {
Object.assign(this._sort, this._sortItem(field, 1));
return this;
}
/**
* Set field's name which will be used for sort. Results
* will be sort in descending order
*/
descending(field: string): Query {
Object.assign(this._sort, this._sortItem(field, -1));
return this;
}
/**
* Set number of results to skip before return any result.
*/
skip(skip: number): Query {
this._skip = skip;
return this;
}
/**
* Set limit of results to return.
*/
limit(limit: number): Query {
this._limit = limit;
return this;
}
/**
* Set fields which will be returned.
*/
select(fields: string[] | string): Query {
if (!isArray(fields)) {
fields = <string[]>[fields];
}
this._select = this._select.concat(<string[]>fields || []);
return this;
}
/**
* Concat two queries with OR
*/
or(query: Query): Query {
this._where = { $or: [this._where, query.toJSON().where] };
return this;
}
/**
* Return JSON Object
*/
toJSON(): QueryRaw {
const query: QueryRaw = {};
if (Object.keys(this._where).length) {
query.where = this._where;
}
if (this._skip) {
query.skip = this._skip;
}
if (this._limit > 0) {
query.limit = this._limit;
}
if (this._select.length > 0) {
query.select = this._select;
}
if (Object.keys(this._sort).length) {
query.sort = this._sort;
}
return Object.assign({}, query);
}
static create() {
return new Query();
}
static from(input: any): Query {
const query: QueryRaw = { where: {} };
if (isString(input)) {
try {
input = JSON.parse(input);
} catch (e) {
throw new Error('Invalid input data for query.');
}
}
if (isObject(input)) {
if (input.where) {
try {
input.where = JSON.parse(input.where);
} catch (e) {
throw new Error('Invalid input data for query(where).');
}
if (isObject(input.where)) {
query.where = input.where;
}
}
if (input.limit) {
query.limit = parseInt(input.limit);
}
if (input.skip) {
query.skip = parseInt(input.skip);
}
if (input.sort) {
try {
input.sort = JSON.parse(input.sort);
} catch (e) {
throw new Error('Invalid input data for query(sort).');
}
if (isObject(input.sort)) {
query.sort = {};
query.sort = Object.keys(input.sort).reduce((s, c) => {
s[c] = parseInt(input.sort[c]);
return s;
}, query.sort);
}
}
if (input.select) {
query.select = input.select;
}
}
return new Query(query);
}
}