UNPKG

@n2flowjs/nbase

Version:

Neural Vector Database for efficient similarity search

165 lines 6.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAndFilter = exports.createOrFilter = exports.createLogicalFilterFunction = exports.createFilterFunction = void 0; /** * Creates a filter function from a filter configuration * Supports both simple object-style filters and array of FilterConfig objects * * @param filters Filter configuration object or array of FilterConfig objects * @returns A function that takes an ID and returns true if the item passes the filter */ function createFilterFunction(filters) { // If no filters, return a function that always returns true if (!filters || (Array.isArray(filters) && filters.length === 0) || (!Array.isArray(filters) && Object.keys(filters).length === 0)) { return () => true; } // Convert simple object filters to FilterConfig array const filterConfigs = Array.isArray(filters) ? filters : Object.entries(filters).map(([field, value]) => ({ field, operator: "$eq", value, })); // Create a memoization cache for frequently accessed IDs const resultCache = new Map(); let cacheHits = 0; let cacheMisses = 0; // Compile the filter predicates for better performance const predicates = filterConfigs.map(compileFilterPredicate); // The actual filter function that will be returned return function filterFunction(id, metadata) { // Check cache first for performance const cacheKey = id; if (resultCache.has(cacheKey)) { cacheHits++; return resultCache.get(cacheKey); } cacheMisses++; // If metadata is provided directly, use it if (metadata) { const result = evaluatePredicates(predicates, metadata); // Cache the result for future lookups if (resultCache.size < 10000) { // Prevent unbounded growth resultCache.set(cacheKey, result); } return result; } // If we have no way to get metadata, we can't filter return false; }; } exports.createFilterFunction = createFilterFunction; /** * Compiles a filter config into an optimized predicate function * * @param filter The filter configuration * @returns A predicate function that evaluates the filter against metadata */ function compileFilterPredicate(filter) { const { field, operator, value } = filter; // Get the nested value path ready for faster access const fieldPath = field.split("."); // Pre-compute regex patterns for $regex operator let regex; if (operator === "$regex" && typeof value === "string") { regex = new RegExp(value); } return function predicate(metadata) { // Access nested fields (handle dot notation) let fieldValue = metadata; for (const path of fieldPath) { if (fieldValue === null || fieldValue === undefined) { return operator === "$exists" ? false : operator === "$ne" || operator === "$nin"; } fieldValue = fieldValue[path]; } // Handle undefined or null field values if (fieldValue === undefined || fieldValue === null) { return operator === "$exists" ? false : operator === "$ne" || operator === "$nin"; } // Based on operator, evaluate the condition switch (operator) { case "$eq": return fieldValue === value; case "$ne": return fieldValue !== value; case "$gt": return fieldValue > value; case "$gte": return fieldValue >= value; case "$lt": return fieldValue < value; case "$lte": return fieldValue <= value; case "$in": return Array.isArray(value) && value.includes(fieldValue); case "$nin": return Array.isArray(value) && !value.includes(fieldValue); case "$exists": return value ? fieldValue !== undefined : fieldValue === undefined; case "$regex": return (typeof fieldValue === "string" && (regex?.test(fieldValue) ?? false)); default: console.warn(`Unsupported operator: ${operator}`); return false; } }; } /** * Evaluates all predicates against the metadata (AND logic) */ function evaluatePredicates(predicates, metadata) { // Short-circuit evaluation - return false as soon as any predicate fails for (const predicate of predicates) { if (!predicate(metadata)) { return false; } } return true; } /** * Creates a combined filter function from multiple filter conditions * using logical operations (AND, OR, NOT) */ function createLogicalFilterFunction(conditions, db) { const filterFunctions = conditions.map((condition) => { const filterFn = createFilterFunction(condition.filter); // For NOT operation, invert the result if (condition.operation === "NOT") { return (id, metadata) => !filterFn(id, metadata); } return filterFn; }); return (id, metadata) => { // Handle OR operation if (conditions.some((c) => c.operation === "OR")) { return filterFunctions.some((fn) => fn(id, metadata)); } // Default to AND operation return filterFunctions.every((fn) => fn(id, metadata)); }; } exports.createLogicalFilterFunction = createLogicalFilterFunction; /** * Helper function to create a combined filter with OR logic */ function createOrFilter(filters, db) { return createLogicalFilterFunction(filters.map((filter) => ({ filter, operation: "OR" })), db); } exports.createOrFilter = createOrFilter; /** * Helper function to create a combined filter with AND logic */ function createAndFilter(filters, db) { return createLogicalFilterFunction(filters.map((filter) => ({ filter, operation: "AND" })), db); } exports.createAndFilter = createAndFilter; //# sourceMappingURL=filters.js.map