UNPKG

metastocle-ms

Version:
731 lines (671 loc) 14.7 kB
import { get, merge, orderBy, pick, pickBy } from "lodash-es"; import _utils from "spreadable-ms/src/utils.js"; import errors from "./errors.js"; const utils = Object.assign({}, _utils); /** * Class to handle documents: to filter, order, limit and etc. */ utils.DocumentsHandler = class { /** * @param {object[]} documents */ constructor(documents) { this.__documents = documents; } /** * get the documents * * @returns {object[]} */ getDocuments() { return this.__documents; } /** * Filter the documents * * @param {object} filter */ filterDocuments(filter) { this.__documents = this.__documents.filter(d => this.checkDocumentValue(d, filter)); } /** * Sort the documents * * @param {array[]|string[]} sort */ sortDocuments(sort = []) { const fields = []; const directions = []; if (!Array.isArray(sort)) { throw new errors.WorkError(`Sort rule must be an array`, 'ERR_METASTOCLE_DOCUMENTS_HANDLER_SORT_TYPE'); } for (let i = 0; i < sort.length; i++) { const order = sort[i]; let field = order; let direction = 'asc'; if (Array.isArray(order)) { field = order[0]; direction = order[1]; } fields.push(field); directions.push(direction); } this.__documents = orderBy(this.__documents, fields, directions); } /** * Limit the documents * * @param {integer} offset * @param {integer} limit */ limitDocuments(offset = 0, limit = 0) { const start = offset * limit; this.__documents = this.__documents.slice(start, start + limit); } /** * Pick the documents fields * * @param {string[]} fields */ fieldDocuments(fields = []) { if (!Array.isArray(fields)) { throw new errors.WorkError(`Fields must be an array`, 'ERR_METASTOCLE_DOCUMENTS_HANDLER_FIELD_TYPE'); } this.__documents = this.__documents.map(d => pickBy(d, (v, k) => fields.includes(k) || k.startsWith('$'))); } /** * Check the document value * * @param {*} value * @param {*} filter * @returns {boolean} */ checkDocumentValue(value, filter) { if (!filter || typeof filter != 'object' || Array.isArray(filter)) { return this.$eq(value, filter); } for (let key in filter) { if (key == '$and') { for (let i = 0; i < filter[key].length; i++) { if (!this.checkDocumentValue(value, filter[key][i])) { return false; } } continue; } if (key == '$or') { let ok = false; for (let i = 0; i < filter[key].length; i++) { if (this.checkDocumentValue(value, filter[key][i])) { ok = true; break; } } if (!ok) { return false; } continue; } if (key.startsWith('$')) { if (typeof this[key] != 'function') { throw new errors.WorkError(`There is no filter for key "${key}"`, 'ERR_METASTOCLE_DOCUMENTS_HANDLER_WRONG_FILTER'); } if (!this[key](value, filter[key])) { return false; } continue; } if (!this.checkDocumentValue(get(value, key), filter[key])) { return false; } } return true; } /** * Compare equality * * @example * // returns false * $eq(1, 2) * * @example * // returns true * $eq(1, '1') * * @example * // returns true * $eq(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $eq(value, filter) { return value == filter; } /** * Compare inequality * * @example * // returns true * $ne(1, 2) * * @example * // returns false * $ne(1, '1') * * @example * // returns false * $ne(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $ne(value, filter) { return value != filter; } /** * Compare equality strictly * * @example * // returns false * $eqs(1, 2) * * @example * // returns false * $eqs(1, '1') * * @example * // returns true * $eqs(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $eqs(value, filter) { return value === filter; } /** * Compare inequality strictly * * @example * // returns true * $nes(1, 2) * * @example * // returns true * $nes(1, '1') * * @example * // returns false * $nes(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $nes(value, filter) { return value !== filter; } /** * Check the value is greater than the filter * * @example * // returns false * $gt(1, 2) * * @example * // returns true * $gt(2, '1') * * @example * // returns false * $gt(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $gt(value, filter) { return value > filter; } /** * Check the value is greater or equal than the filter * * @example * // returns false * $gte(1, 2) * * @example * // returns true * $gte(2, '1') * * @example * // returns true * $gte(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $gte(value, filter) { return value >= filter; } /** * Check the value is less than the filter * * @example * // returns true * $lt(1, 2) * * @example * // returns false * $lt('2', 1) * * @example * // returns false * $lt(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $lt(value, filter) { return value < filter; } /** * Check the value is less or equal than the filter * * @example * // returns true * $lte(1, 2) * * @example * // returns false * $lte('2', 1) * * @example * // returns true * $lte(1, 1) * * @param {*} value * @param {*} filter * @returns {boolean} */ $lte(value, filter) { return value <= filter; } /** * Check the value is in the array * * @example * // returns true * $in(1, [1, 2]) * * @example * // returns false * $in(3, [1, 2]) * * @param {*} value * @param {array} filter */ $in(value, filter) { utils.validateSchema({ type: 'array' }, filter); return filter.includes(value); } /** * Check the value is not in the array * * @example * // returns true * $nin(1, [1, 2]) * * @example * // returns true * $nin(3, [1, 2]) * * @param {*} value * @param {array} filter */ $nin(value, filter) { utils.validateSchema({ type: 'array' }, filter); return !filter.includes(value); } /** * Check the value starts with the filter * * @example * // returns true * $sw('text', 'te') * * @example * // returns false * $sw('text', 'xt') * * @param {string} value * @param {string} filter */ $sw(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return value.startsWith(filter); } /** * Case-insensitive version * * @see utils.DocumentsHandler.prototype.$sw */ $isw(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return this.$sw(value.toLowerCase(), filter.toLowerCase()); } /** * Check the value ends with the filter * * @example * // returns true * $ew('text', 'xt') * * @example * // returns false * $ew('text', 'te') * * @param {string} value * @param {string} filter */ $ew(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return value.endsWith(filter); } /** * Case-insensitive version * * @see utils.DocumentsHandler.prototype.$ew */ $iew(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return this.$ew(value.toLowerCase(), filter.toLowerCase()); } /** * Check the value matchs the filter * * @example * // returns true * $lk('text', 'ex') * * @example * // returns false * $lk('text', 'go') * * @param {string} value * @param {string} filter */ $lk(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return value.includes(filter); } /** * Case-insensitive version * * @see utils.DocumentsHandler.prototype.$lk */ $ilk(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'string' }, filter); return this.$lk(value.toLowerCase(), filter.toLowerCase()); } /** * Check the value matchs the regex filter * * @example * // returns false * $rx('texT', { source: 'text' }) * * @example * // returns true * $rx('text', { source: 'text', flags: 'i' }) * * @param {string} value * @param {object} filter */ $rx(value, filter) { utils.validateSchema({ type: 'string' }, value); utils.validateSchema({ type: 'object', props: { source: 'string', flags: 'string' }, required: ['source'] }, filter); return new RegExp(filter.source, filter.flags).test(value); } /** * Check the array length is greater than the filter * * @example * // returns false * $lgt([1], 2) * * @example * // returns false * $lgt([1], 1) * * @example * // returns true * $lgt([1, 2], '1') * * @param {array} value * @param {*} filter * @returns {boolean} */ $lgt(value, filter) { utils.validateSchema({ type: 'array' }, value); return value.length > filter; } /** * Check the array length is greater or equal than the filter * * @example * // returns false * $lgte([1], 2) * * @example * // returns true * $lgte([1], 1) * * @example * // returns true * $lgte([1, 2], '1') * * @param {array} value * @param {*} filter * @returns {boolean} */ $lgte(value, filter) { utils.validateSchema({ type: 'array' }, value); return value.length >= filter; } /** * Check the array length is greater than the filter * * @example * // returns true * $llt([1], 2) * * @example * // returns false * $llt([1], 1) * * @example * // returns false * $llt([1, 2], '1') * * @param {array} value * @param {*} filter * @returns {boolean} */ $llt(value, filter) { utils.validateSchema({ type: 'array' }, value); return value.length < filter; } /** * Check the array length is less or equal than the filter * * @example * // returns true * $llte([1], 2) * * @example * // returns true * $llte([1], 1) * * @example * // returns false * $llte([1, 2], '1') * * @param {array} value * @param {*} filter * @returns {boolean} */ $llte(value, filter) { utils.validateSchema({ type: 'array' }, value); return value.length <= filter; } }; /** * Check the variable is document * * @param {*} document * @returns {boolean} */ utils.isDocument = function (document) { return !!(document && typeof document == 'object' && !Array.isArray(document) && Object.keys(document).length); }; /** * Check the variable is actions * * @param {*} actions * @returns {boolean} */ utils.isActions = function (actions) { return !!(actions && typeof actions == 'object' && !Array.isArray(actions)); }; /** * Test the actions are right * * @param {*} actions */ utils.actionsTest = function (actions) { if (!this.isActions(actions)) { const msg = `Wrong actions: ${JSON.stringify(actions, null, 1)}`; throw new errors.WorkError(msg, 'ERR_METASTOCLE_WRONG_DOCUMENT_ACTIONS'); } }; /** * Prepare the document filter * * @param {object} filter * @returns {object} */ utils.prepareDocumentFilter = function (filter) { if (!filter || typeof filter != 'object') { const msg = `Document filter must must be an object`; throw new errors.WorkError(msg, 'ERR_METASTOCLE_WRONG_DOCUMENT_FILTER'); } for (let key in filter) { const val = filter[key]; if (key == '$and' || key == '$or') { filter[key] = this.prepareDocumentFilter(val); continue; } if (val instanceof RegExp) { filter[key] = { source: val.source, flags: val.flags }; continue; } if (val instanceof Date) { filter[key] = val.getTime(); continue; } if (typeof val == 'object') { filter[key] = this.prepareDocumentFilter(val); continue; } } return filter; }; /** * Prepare the document fields * * @param {object} fields * @returns {object} */ utils.prepareDocumentFields = function (fields) { if (!fields || typeof fields != 'object') { const msg = `Document fields must must be an object`; throw new errors.WorkError(msg, 'ERR_METASTOCLE_WRONG_DOCUMENT_FIELDS'); } for (let key in fields) { const val = fields[key]; if (val instanceof Date) { fields[key] = val.getTime(); continue; } if (typeof val == 'object') { fields[key] = this.prepareDocumentFields(val); continue; } } return fields; }; /** * Prepare the document getting actions * * @param {object} actions * @returns {object} */ utils.prepareDocumentGettingActions = function (actions) { this.actionsTest(actions); return merge({ sort: null, fields: null, offset: 0, limit: 0, removeDuplicates: true }, pick(actions, ['filter', 'sort', 'fields', 'offset', 'limit', 'removeDuplicates']), { filter: actions.filter ? this.prepareDocumentFilter(actions.filter) : null }); }; /** * Prepare the document update actions * * @param {object} actions * @returns {object} */ utils.prepareDocumentUpdateActions = function (actions) { this.actionsTest(actions); return merge({ replace: false, }, pick(actions, ['replace', 'filter']), { filter: actions.filter ? this.prepareDocumentFilter(actions.filter) : null }); }; /** * Prepare the document deletion actions * * @param {object} actions * @returns {object} */ utils.prepareDocumentDeletionActions = function (actions) { this.actionsTest(actions); return merge({}, pick(actions, ['filter']), { filter: actions.filter ? this.prepareDocumentFilter(actions.filter) : null }); }; export default utils;