UNPKG

@c8y/client

Version:

Client application programming interface to access the Cumulocity IoT-Platform REST services.

247 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueriesUtil = void 0; class QueriesUtil { constructor() { this.operatorFns = { __not: operand => { return `not(${this.buildQueryFilter(operand, null)})`; }, __and: operand => { return this.buildQueryFilter(operand, null, 'and'); }, __or: operand => { return this.buildQueryFilter(operand, null, 'or'); }, __eq: (operand, contextKey) => { if (typeof operand === 'object' && operand !== null) { return this.buildQueryFilter(operand, contextKey); } return `${contextKey} eq ${this.quoteString(operand)}`; }, __gt: (operand, contextKey) => { return `${contextKey} gt ${this.quoteString(operand)}`; }, __ge: (operand, contextKey) => { return `${contextKey} ge ${this.quoteString(operand)}`; }, __lt: (operand, contextKey) => { return `${contextKey} lt ${this.quoteString(operand)}`; }, __le: (operand, contextKey) => { return `${contextKey} le ${this.quoteString(operand)}`; }, __in: (operand, contextKey) => { const stmts = operand .filter(op => !!op) .map(op => { return `${contextKey} eq ${this.quoteString(op)}`; }); return this.glue(stmts, 'or'); }, __bygroupid: operand => { return `bygroupid(${operand})`; }, __has: operand => { return `has(${operand})`; }, __hasany: operand => { return `hasany(${operand})`; }, __useFilterQueryString: (queryString) => { // match everything inside the most exterior parentheses, including them const query = queryString.match(/\(.*\)/)?.[0]; // get rid of the most exterior parentheses return query?.substring(1, query.length - 1); } }; } /** * Builds query string from provided query object. * * @param query Object containing filters and sort order for querying managed objects. Supported filters are: * - **__and** - Specifies conditions, e.g. `{__and: [{__has: 'c8y_IsDevice'}, {'count': {__gt: 0}}]}`. * - **__or** - Specifies alternative conditions, e.g. `{__or: [{__bygroupid: 10300}, {__bygroupid: 10400}]}`. * - **__eq** - Specified fragment must be equal to given value, e.g. `{'status': 'AVAILABLE'}` (no nested object required). * - **__lt** - Specified fragment must be less then given value, e.g. `{'count': {__lt: 10}}`. * - **__gt** - Specified fragment must be greater then given value, e.g. `{'count': {__gt: 0}}`. * - **__in** - Specified fragment must be equal to one of values in the list, e.g. `{'status': {__in: ['AVAILABLE', 'UNAVAILABLE']}}`. * - **__not** - Negates condition, e.g. `{__not: {'status': 'AVAILABLE'}}`. * - **__bygroupid** - True if filtered managed object is assigned to given group, e.g. `{__bygroupid: 10300}`. * - **__has** - Specified fragment must have a value defined, e.g. `{__has: 'c8y_IsDevice'}`. * - **__hasany** - Matches objects haing at least one of the fragments defines, e.g. `{__has: ['c8y_IsDevice', 'c8y_Dashboard']}`. * - **__useFilterQueryString** - Gets rid of the `$filter=()… $orderby=…` parts of a query and keeps only what's between the most * exterior parentheses of the $filter. * EXAMPLE: takes a query of the form * `$filter=(name eq 'RaspPi*') $orderby=name asc` * and turns it into * `name eq 'RaspPi*'` * This is necessary for searching for smart groups, which are identified by their own query * that needs to be passed through. * * Note: if you want to specify the order, you need to wrap your filters within `__filter` property and then add `__orderby` with the array of field paths and sort directions (1 for ascending, -1 for descending), for example: * - `{ __filter: { ... }, __orderby: [{ 'creationTime': -1 }, { 'name': 1 }] }` * * @returns {string} Returns a query string ready to be sent in request params to backend. * * **Example** * ```typescript * const query = { * __filter: { * 'name': 'My Device*', * 'c8y_Availability.status': { * __in: ['AVAILABLE', 'UNAVAILABLE'] * }, * 'creationTime': { * __lt: '2015-11-30T13:28:123Z' * }, * 'c8y_ActiveAlarmsStatus.critical': { * __gt: 0 * }, * __or: [ * {__not: {__has: 'c8y_ActiveAlarmsStatus.major'}}, * { * __or: [ * {__bygroupid: 10300}, * {__bygroupid: 10400} * ] * } * ] * }, * __orderby: [ * {'name': 1}, * {'creationTime': -1}, * {'c8y_ActiveAlarmsStatus.critical': -1} * ] * }; * * const params = { * query: queriesUtil.buildQuery(query) * }; * ``` */ buildQuery(query) { const q = []; const filter = this.buildQueryFilter(query.__filter || query); const orderBy = this.buildQueryOrderby(query.__orderby); if (filter) { q.push(`$filter=(${filter})`); } if (orderBy) { q.push(`$orderby=${orderBy}`); } return q.join(' '); } buildQueryFilter(queryFilter, _queryKey, _glueType) { const queryKey = _queryKey || null; const glueType = _glueType || 'and'; const q = []; if (Array.isArray(queryFilter)) { queryFilter.forEach(qFilter => { const _q = this.buildQueryFilter(qFilter, null, glueType); if (_q) { q.push(_q); } }); } else { let _q; Object.keys(queryFilter).forEach(k => { if (this.operatorFns[k] !== undefined) { _q = this.operatorFns[k](queryFilter[k], queryKey); if (_q) { q.push(_q); } } else { _q = this.operatorFns.__eq(queryFilter[k], k); if (_q) { q.push(_q); } } }); } return this.glue(q, glueType); } buildQueryOrderby(queryOrderbys) { const o = []; if (queryOrderbys) { queryOrderbys.forEach(q => { Object.keys(q).forEach(k => { if (q[k] !== 0) { o.push(`${k} ${q[k] > 0 ? 'asc' : 'desc'}`); } }); }); } return o.join(','); } addAndFilter(query, filter) { return this.addFilter(query, filter, 'and'); } addOrFilter(query, filter) { return this.addFilter(query, filter, 'or'); } addFilter(query, filter, operator) { const oldFilter = query.__orderby ? query.__filter || {} : query.__filter || query; const newFilter = { [`__${operator}`]: this.skipEmptyObjects([oldFilter, filter]) }; if (!query.__filter && !query.__orderby) { return newFilter; } query.__filter = newFilter; return query; } prependOrderbys(query, orderbys) { return this.addOrderbys(query, orderbys, 'prepend'); } appendOrderbys(query, orderbys) { return this.addOrderbys(query, orderbys, 'append'); } addOrderbys(query, orderbys, how) { const oldFilter = query.__orderby ? query.__filter || {} : query.__filter || query; const oldOrderbys = query.__orderby || []; const newOrderbys = how === 'prepend' ? [...orderbys, ...oldOrderbys] : [...oldOrderbys, ...orderbys]; const newQuery = { __orderby: this.skipEmptyObjects(newOrderbys) }; if (!this.isEmptyObject(oldFilter)) { newQuery.__filter = oldFilter; } return newQuery; } extractAndMergeOrderBys(queries) { if (queries?.length > 0) { const orderByQuery = queries .map(query => { const token = '$orderby='; const tokenIndex = query.lastIndexOf(token); return tokenIndex !== -1 ? query.substring(tokenIndex + token.length) : null; }) .filter(orderBy => orderBy !== null) .join(','); return orderByQuery ? `$orderby=${orderByQuery}` : ''; } } glue(stmts, type) { return stmts.length > 1 ? `(${stmts.join(`) ${type} (`)})` : stmts[0]; } quoteString(s) { return typeof s === 'string' ? `'${this.escapeSingleQuote(s)}'` : s; } skipEmptyObjects(objs) { return objs.filter(obj => !this.isEmptyObject(obj)); } isEmptyObject(obj) { return Object.keys(obj).length === 0; } // OData does not support single quotes in the query. We need to replace all single quotes with double quotes. // http://docs.oasis-open.org/odata/odata/v4.01/cs01/part2-url-conventions/odata-v4.01-cs01-part2-url-conventions.html#sec_URLComponents escapeSingleQuote(s) { if (typeof s !== 'string') { return s; } return s.replace(/\'/g, "''"); } } exports.QueriesUtil = QueriesUtil; //# sourceMappingURL=QueriesUtil.js.map