UNPKG

@e22m4u/js-repository

Version:

Реализация репозитория для работы с базами данных

167 lines (162 loc) 5.07 kB
import {Service} from '@e22m4u/js-service'; import {InvalidArgumentError} from '../errors/index.js'; import {OperatorClauseTool} from './operator-clause-tool.js'; import {getValueByPath, isDeepEqual, isPlainObject} from '../utils/index.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]; // Test property value. if (this._test(matcher, value)) return true; }); }; } /** * Value testing. * * @param {*} example * @param {*} value * @returns {boolean} */ _test(example, value) { // прямое сравнение if (example === value) { return true; } // условием является null if (example === null) { return value === null; } // условием является undefined if (example === undefined) { return value === undefined; } // условием является регулярное выражение if (example instanceof RegExp) { if (typeof value === 'string') { return example.test(value); } // если значением является массив, // то проверяется каждый элемент if (Array.isArray(value)) { return value.some(el => typeof el === 'string' && example.test(el)); } return false; } // условием является простой объект if (isPlainObject(example)) { const operatorsTest = this.getService(OperatorClauseTool).testAll( example, value, ); if (operatorsTest !== undefined) { // особая логика для neq с массивами // {hobbies: {neq: 'yoga'}} // должно вернуть true для // ['bicycle', 'meditation'] if ('neq' in example && Array.isArray(value)) { return !value.some(el => isDeepEqual(el, example.neq)); } return operatorsTest; } } // значением является массив if (Array.isArray(value)) { // если один из элементов массива соответствует // поиску, то возвращается true const isElementMatched = value.some(el => isDeepEqual(el, example)); if (isElementMatched) return true; } return isDeepEqual(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, ); } }