UNPKG

@mcabreradev/filter

Version:

A powerful, SQL-like array filtering library for TypeScript and JavaScript with advanced pattern matching, MongoDB-style operators, deep object comparison, and zero dependencies

196 lines (195 loc) 7.05 kB
export class LRUCache { cache = new Map(); maxSize; maxAge; constructor(maxSize = 1000, maxAge = 300000) { this.maxSize = maxSize; this.maxAge = maxAge; } get(key) { const entry = this.cache.get(key); if (!entry) return undefined; if (Date.now() - entry.timestamp > this.maxAge) { this.cache.delete(key); return undefined; } this.cache.delete(key); this.cache.set(key, entry); return entry.value; } set(key, value) { if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); } } this.cache.set(key, { value, timestamp: Date.now() }); } clear() { this.cache.clear(); } size() { return this.cache.size; } } export class MemoizationManager { static instance; predicateCache; regexCache; expressionHashCache; constructor() { this.predicateCache = new LRUCache(500, 300000); this.regexCache = new LRUCache(500, 300000); this.expressionHashCache = new WeakMap(); } static getInstance() { if (!MemoizationManager.instance) { MemoizationManager.instance = new MemoizationManager(); } return MemoizationManager.instance; } createExpressionHash(expression, config) { if (typeof expression === 'function') { return `fn:${expression.toString()}`; } if (typeof expression === 'string') { return `str:${expression}:${config.caseSensitive}:${config.limit}`; } if (typeof expression === 'object' && expression !== null) { const cached = this.expressionHashCache.get(expression); const configHash = `:cs:${config.caseSensitive}:md:${config.maxDepth}:lim:${config.limit}`; if (cached) { return `${cached}${configHash}`; } const hash = this.hashObject(expression, config, true); this.expressionHashCache.set(expression, hash); return `${hash}${configHash}`; } return `primitive:${String(expression)}:${config.limit}`; } hashObject(obj, config, skipConfigSuffix = false) { const parts = []; const record = obj; if ('$and' in record || '$or' in record || '$not' in record) { if ('$and' in record) { const andArray = Array.isArray(record.$and) ? record.$and : [record.$and]; parts.push(`$and:[${andArray.map((v) => this.hashValue(v)).join(',')}]`); } if ('$or' in record) { const orArray = Array.isArray(record.$or) ? record.$or : [record.$or]; parts.push(`$or:[${orArray.map((v) => this.hashValue(v)).join(',')}]`); } if ('$not' in record) { parts.push(`$not:${this.hashValue(record.$not)}`); } const otherKeys = Object.keys(record) .filter((k) => !k.startsWith('$')) .sort(); for (const key of otherKeys) { const value = record[key]; parts.push(`${key}:${this.hashValue(value)}`); } } else { const sortedKeys = Object.keys(record).sort(); for (const key of sortedKeys) { const value = record[key]; if (value === null) { parts.push(`${key}:null`); } else if (value === undefined) { parts.push(`${key}:undefined`); } else if (typeof value === 'object') { if (value instanceof Date) { parts.push(`${key}:date:${value.getTime()}`); } else if (value instanceof RegExp) { parts.push(`${key}:regex:${value.source}:${value.flags}`); } else if (Array.isArray(value)) { parts.push(`${key}:arr:[${value.map((v) => this.hashValue(v)).join(',')}]`); } else { parts.push(`${key}:obj:{${this.hashObject(value, config)}}`); } } else { parts.push(`${key}:${typeof value}:${String(value)}`); } } } if (skipConfigSuffix) { return parts.join('|'); } const orderByHash = config.orderBy ? this.hashOrderBy(config.orderBy) : ''; return `${parts.join('|')}:cs:${config.caseSensitive}:md:${config.maxDepth}:lim:${config.limit}${orderByHash ? `:ob:${orderByHash}` : ''}`; } hashOrderBy(orderBy) { if (typeof orderBy === 'string') { return `str:${orderBy}`; } if (Array.isArray(orderBy)) { return `arr:[${orderBy .map((item) => { if (typeof item === 'string') { return `str:${item}`; } return `obj:{field:${item.field}:dir:${item.direction}}`; }) .join(',')}]`; } if (typeof orderBy === 'object' && orderBy !== null) { const obj = orderBy; return `obj:{field:${obj.field}:dir:${obj.direction}}`; } return String(orderBy); } hashValue(value) { if (value === null) return 'null'; if (value === undefined) return 'undefined'; if (value instanceof Date) return `date:${value.getTime()}`; if (value instanceof RegExp) return `regex:${value.source}:${value.flags}`; if (typeof value === 'object') return `obj:${this.hashObject(value, { caseSensitive: false, maxDepth: 3, enableCache: false })}`; return `${typeof value}:${String(value)}`; } getCachedPredicate(key) { return this.predicateCache.get(key); } setCachedPredicate(key, predicate) { this.predicateCache.set(key, predicate); } getCachedRegex(pattern, flags) { const key = `${pattern}:${flags || ''}`; return this.regexCache.get(key); } setCachedRegex(pattern, regex, flags) { const key = `${pattern}:${flags || ''}`; this.regexCache.set(key, regex); } clearPredicateCache() { this.predicateCache.clear(); } clearRegexCache() { this.regexCache.clear(); } clearAll() { this.predicateCache.clear(); this.regexCache.clear(); this.expressionHashCache = new WeakMap(); } getStats() { return { predicateCacheSize: this.predicateCache.size(), regexCacheSize: this.regexCache.size(), }; } } export const memoization = MemoizationManager.getInstance();