@e22m4u/js-repository
Version:
Реализация репозитория для работы с базами данных в Node.js
169 lines (164 loc) • 4.9 kB
JavaScript
import {Service} from '@e22m4u/js-service';
import {getValueByPath} from '../utils/index.js';
import {InvalidArgumentError} from '../errors/index.js';
import {OperatorClauseTool} from './operator-clause-tool.js';
/**
* Where clause tool.
*
* @typedef {object|Function} WhereClause
*/
export class WhereClauseTool extends Service {
/**
* Filter by where clause.
*
* @example
* ```
* const entities = [
* {foo: 1, bar: 'a'},
* {foo: 2, bar: 'b'},
* {foo: 3, bar: 'b'},
* {foo: 4, bar: 'b'},
* ];
*
* const result = filterByWhereClause(entities, {
* foo: {gt: 2},
* bar: 'b',
* });
*
* console.log(result);
* // [
* // {foo: 3, bar: 'b'},
* // {foo: 4, bar: 'b'},
* // ];
*
* ```
*
* @param {object[]} entities
* @param {WhereClause|undefined} where
* @returns {object[]}
*/
filter(entities, where = undefined) {
if (!Array.isArray(entities))
throw new InvalidArgumentError(
'The first argument of WhereClauseTool.filter should be ' +
'an Array of Object, but %v was given.',
entities,
);
if (where == null) return entities;
return entities.filter(this._createFilter(where));
}
/**
* Create where filter.
*
* @param {WhereClause} whereClause
* @returns {Function}
*/
_createFilter(whereClause) {
if (typeof whereClause !== 'object' || Array.isArray(whereClause))
throw new InvalidArgumentError(
'The provided option "where" should be an Object, but %v was given.',
whereClause,
);
const keys = Object.keys(whereClause);
return data => {
if (typeof data !== 'object')
throw new InvalidArgumentError(
'The first argument of WhereClauseTool.filter should be ' +
'an Array of Object, but %v was given.',
data,
);
return keys.every(key => {
// AndClause (recursion)
if (key === 'and' && key in whereClause) {
const andClause = whereClause[key];
if (Array.isArray(andClause))
return andClause.every(clause => this._createFilter(clause)(data));
// OrClause (recursion)
} else if (key === 'or' && key in whereClause) {
const orClause = whereClause[key];
if (Array.isArray(orClause))
return orClause.some(clause => this._createFilter(clause)(data));
}
// PropertiesClause (properties)
const value = getValueByPath(data, key);
const matcher = whereClause[key];
// Property value is an array.
if (Array.isArray(value)) {
// {neq: ...}
if (
typeof matcher === 'object' &&
matcher !== null &&
'neq' in matcher &&
matcher.neq !== undefined
) {
// The following condition is for the case where
// we are querying with a neq filter, and when
// the value is an empty array ([]).
if (value.length === 0) return true;
// The neq operator requires each element
// of the array to be excluded.
return value.every((el, index) => {
const where = {};
where[index] = matcher;
return this._createFilter(where)({...value});
});
}
// Requires one of an array elements to be match.
return value.some((el, index) => {
const where = {};
where[index] = matcher;
return this._createFilter(where)({...value});
});
}
// Test property value.
if (this._test(matcher, value)) return true;
});
};
}
/**
* Value testing.
*
* @param {*} example
* @param {*} value
* @returns {boolean}
*/
_test(example, value) {
// Test null.
if (example === null) {
return value === null;
}
// Test undefined.
if (example === undefined) {
return value === undefined;
}
// Test RegExp.
// noinspection ALL
if (example instanceof RegExp) {
if (typeof value === 'string') return !!value.match(example);
return false;
}
// Operator clause.
if (typeof example === 'object') {
const operatorsTest = this.getService(OperatorClauseTool).testAll(
example,
value,
);
if (operatorsTest !== undefined) return operatorsTest;
}
// Not strict equality.
return example == value;
}
/**
* Validate where clause.
*
* @param {WhereClause|undefined} clause
*/
static validateWhereClause(clause) {
if (clause == null || typeof clause === 'function') return;
if (typeof clause !== 'object' || Array.isArray(clause))
throw new InvalidArgumentError(
'The provided option "where" should be an Object, but %v was given.',
clause,
);
}
}