UNPKG

nodedb-json

Version:

A lightweight JSON-based database for Node.js with TypeScript support, indexing, and complex query capabilities

131 lines 5.18 kB
import _ from 'lodash'; import { applyAggregation } from './aggregator.js'; import { matchesConditions } from './matcher.js'; import { applyPagination, validatePagination } from './paginator.js'; import { applySort } from './sorter.js'; export class QueryEngine { constructor(indexes) { this.indexes = indexes; } execute(data, options = {}) { const startTime = performance.now(); this.validateQueryOptions(options); const totalRecords = data.length; let filteredData = [...data]; let usedIndex = false; if (options.where) { const filterResult = this.applyFilter(filteredData, options.where); filteredData = filterResult.data; usedIndex = filterResult.usedIndex; } const filteredRecords = filteredData.length; const aggregationData = filteredData; if (options.sort) { filteredData = applySort(filteredData, options.sort); } if (options.skip && options.skip > 0) { filteredData = filteredData.slice(options.skip); } if (options.limit && options.limit > 0) { filteredData = filteredData.slice(0, options.limit); } let paginationInfo; if (options.pagination) { const paginationResult = applyPagination(filteredData, options.pagination); filteredData = paginationResult.data; paginationInfo = paginationResult.pagination; } if (options.select && options.select.length > 0) { filteredData = this.applySelect(filteredData, options.select); } let aggregations; if (options.aggregation && options.aggregation.length > 0) { aggregations = applyAggregation(aggregationData, options.aggregation); } const executionTime = performance.now() - startTime; return { data: _.cloneDeep(filteredData), pagination: paginationInfo, aggregations: _.cloneDeep(aggregations), stats: { totalRecords, filteredRecords, executionTime, usedIndex } }; } validateQueryOptions(options) { if (options.skip !== undefined && (!Number.isInteger(options.skip) || options.skip < 0)) { throw new Error('Query skip must be a non-negative integer'); } if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1)) { throw new Error('Query limit must be a positive integer'); } if (options.pagination) { validatePagination(options.pagination); } } applyFilter(data, where) { if (typeof where === 'function') { return { data: data.filter(where), usedIndex: false }; } if (this.indexes) { const indexedMatches = this.getIndexedCandidateIndexes(where); if (indexedMatches) { const filteredData = indexedMatches .map(index => data[index]) .filter(item => item !== undefined && matchesConditions(item, where)); return { data: filteredData, usedIndex: true }; } } const filteredData = data.filter(item => matchesConditions(item, where)); return { data: filteredData, usedIndex: false }; } getIndexedCandidateIndexes(where) { var _a; const indexSets = []; for (const [field, condition] of Object.entries(where)) { if (!((_a = this.indexes) === null || _a === void 0 ? void 0 : _a.hasIndexOnField(field))) { continue; } const values = this.getIndexLookupValues(condition); if (!values) { continue; } const indexes = _.uniq(values.flatMap(value => this.indexes.getItemIndexesByField(field, value))); indexSets.push(indexes); } if (indexSets.length === 0) { return undefined; } return indexSets .sort((left, right) => left.length - right.length) .reduce((result, indexes) => result.filter(index => indexes.includes(index))) .sort((left, right) => left - right); } getIndexLookupValues(condition) { if (!this.isOperatorObject(condition)) { return [condition]; } const operators = condition; if (Object.prototype.hasOwnProperty.call(operators, '$eq')) { return [operators.$eq]; } if (Object.prototype.hasOwnProperty.call(operators, '$in')) { return Array.isArray(operators.$in) ? operators.$in : undefined; } return undefined; } isOperatorObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date) && Object.keys(value).some(key => key.startsWith('$')); } applySelect(data, select) { return data.map(item => _.pick(item, select)); } } //# sourceMappingURL=query-engine.js.map