Created
February 26, 2024 22:20
-
-
Save tunnckoCore/7ea7c20d50939cf20ccda2ee61555daf to your computer and use it in GitHub Desktop.
mongo like filters for javascript objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function getAttribute(obj: any, key: any) { | |
const parts = key.split("."); | |
for (let _i = 0, parts_1 = parts; _i < parts_1.length; _i++) { | |
const k = parts_1[_i]; | |
if (obj !== null && typeof obj === "object" && k in obj) { | |
obj = obj[k]; | |
} else { | |
obj = undefined; | |
break; | |
} | |
} | |
return obj; | |
} | |
function exists(a: any, shouldExist: boolean): boolean { | |
const doesExist = a !== null && a !== undefined; | |
return shouldExist ? doesExist : !doesExist; | |
} | |
function isEqual(a: any, b: any): boolean { | |
return a === b; | |
} | |
function greaterThan(a: any, b: any): boolean { | |
return a > b; | |
} | |
function greaterThanOrEqual(a: any, b: any): boolean { | |
return a >= b; | |
} | |
function lessThan(a: any, b: any): boolean { | |
return a < b; | |
} | |
function lessThanOrEqual(a: any, b: any): boolean { | |
return a <= b; | |
} | |
function testExpression(expr: any, value: any): boolean { | |
if (typeof expr !== "object" || expr instanceof Date) { | |
if (Array.isArray(value)) { | |
return value.some((v) => isEqual(v, expr)); | |
} else { | |
return isEqual(expr, value); | |
} | |
} | |
if (expr && expr._elemMatch) { | |
if (!Array.isArray(value)) { | |
return false; | |
} else if (!value.some((v) => matches(expr._elemMatch, v, undefined))) { | |
return false; | |
} | |
} else if (Array.isArray(value)) { | |
const _loop_1 = (key: any) => { | |
let _a: any; | |
const partialExpr = ((_a = {}), (_a[key] = expr[key]), _a); | |
const isTrue = value.some((v) => matches(partialExpr, v, undefined)); | |
if (!isTrue) { | |
return { value: false }; | |
} | |
}; | |
// I don't understand why they designed the language this way, but I guess we'll do it the same way. | |
// https://docs.mongodb.com/manual/tutorial/query-array-of-documents/#combination-of-elements-satisfies-the-criteria | |
for (const key in expr) { | |
const state_1 = _loop_1(key); | |
if (typeof state_1 === "object") { | |
return state_1.value; | |
} | |
} | |
return true; | |
} | |
if ("_eq" in expr && !isEqual(value, expr._eq)) { | |
return false; | |
} | |
if ("_ne" in expr && isEqual(value, expr._ne)) { | |
return false; | |
} | |
if (typeof expr._exists === "boolean" && !exists(value, expr._exists)) { | |
return false; | |
} | |
if ("_not" in expr) { | |
if (typeof expr._not !== "object" || expr._not === null) { | |
throw new Error("_not needs a regex or a document"); | |
} | |
if (testExpression(expr._not, value)) { | |
return false; | |
} | |
} | |
if (expr._gt !== undefined && !greaterThan(value, expr._gt)) { | |
return false; | |
} | |
if (expr._lt !== undefined && !lessThan(value, expr._lt)) { | |
return false; | |
} | |
if (expr._lte !== undefined && !lessThanOrEqual(value, expr._lte)) { | |
return false; | |
} | |
if (expr._gte !== undefined && !greaterThanOrEqual(value, expr._gte)) { | |
return false; | |
} | |
if ( | |
expr._contains && | |
typeof value === "string" && | |
!value.includes(expr._contains) | |
) { | |
return false; | |
} | |
if ( | |
expr._includes && | |
typeof value === "string" && | |
!value.includes(expr._includes) | |
) { | |
return false; | |
} | |
// starts with | |
if ( | |
expr._startsWith && | |
typeof value === "string" && | |
!value.startsWith(expr._startsWith) | |
) { | |
return false; | |
} | |
if ( | |
expr._endsWith && | |
typeof value === "string" && | |
!value.startsWith(expr._endsWith) | |
) { | |
return false; | |
} | |
if (expr._in && !expr._in.includes(value)) { | |
return false; | |
} | |
if (expr._nin && expr._nin.includes(value)) { | |
return false; | |
} | |
if (expr._regex && !new RegExp(expr._regex).test(value)) { | |
return false; | |
} | |
return true; | |
} | |
export function matches( | |
expression: any, | |
record: any, | |
extractor: any = undefined | |
): boolean { | |
if (extractor === undefined) { | |
extractor = getAttribute; | |
} | |
if (typeof expression !== "object" || expression instanceof Date) { | |
return isEqual(expression, record); | |
} | |
if (expression._and && expression._or) { | |
throw new Error( | |
'Indeterminate behavior. "_and" and "_or" operators cannot be present at the same level.' | |
); | |
} | |
if (Array.isArray(expression._and)) { | |
return expression._and.every((exp: any) => matches(exp, record, extractor)); | |
} else if (Array.isArray(expression._or)) { | |
return expression._or.some((exp: any) => matches(exp, record, extractor)); | |
} else if (expression._not) { | |
if (typeof expression._not !== "object") { | |
throw new Error("_not needs a regex or a document"); | |
} | |
return !matches(expression._not, record, extractor); | |
} else if (!Array.isArray(record) && typeof record === "object") { | |
const expr = expression; | |
// Implicit AND operation if multiple root level keys provided | |
for (const key in expression) { | |
const value = extractor(record, key); | |
if (!testExpression(expr[key], value)) { | |
return false; | |
} | |
} | |
return true; | |
} else { | |
return testExpression(expression, record); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment