@tanstack/optimistic
Version:
Core optimistic updates library
223 lines (199 loc) • 6.32 kB
text/typescript
import { evaluateOperandOnNestedRow } from "./extractors.js"
import { compareValues, convertLikeToRegex, isValueInArray } from "./utils.js"
import type {
Comparator,
Condition,
ConditionOperand,
LogicalOperator,
SimpleCondition,
} from "./schema.js"
/**
* Evaluates a condition against a nested row structure
*/
export function evaluateConditionOnNestedRow(
nestedRow: Record<string, unknown>,
condition: Condition,
mainTableAlias?: string,
joinedTableAlias?: string
): boolean {
// Handle simple conditions with exactly 3 elements
if (condition.length === 3 && !Array.isArray(condition[0])) {
const [left, comparator, right] = condition as SimpleCondition
return evaluateSimpleConditionOnNestedRow(
nestedRow,
left,
comparator,
right,
mainTableAlias,
joinedTableAlias
)
}
// Handle flat composite conditions (multiple conditions in a single array)
if (
condition.length > 3 &&
!Array.isArray(condition[0]) &&
typeof condition[1] === `string` &&
![`and`, `or`].includes(condition[1] as string)
) {
// Start with the first condition (first 3 elements)
let result = evaluateSimpleConditionOnNestedRow(
nestedRow,
condition[0],
condition[1] as Comparator,
condition[2],
mainTableAlias,
joinedTableAlias
)
// Process the rest in groups: logical operator, then 3 elements for each condition
for (let i = 3; i < condition.length; i += 4) {
const logicalOp = condition[i] as LogicalOperator
// Make sure we have a complete condition to evaluate
if (i + 3 <= condition.length) {
const nextResult = evaluateSimpleConditionOnNestedRow(
nestedRow,
condition[i + 1],
condition[i + 2] as Comparator,
condition[i + 3],
mainTableAlias,
joinedTableAlias
)
// Apply the logical operator
if (logicalOp === `and`) {
result = result && nextResult
} else {
// logicalOp === `or`
result = result || nextResult
}
}
}
return result
}
// Handle nested composite conditions where the first element is an array
if (condition.length > 0 && Array.isArray(condition[0])) {
// Start with the first condition
let result = evaluateConditionOnNestedRow(
nestedRow,
condition[0] as Condition,
mainTableAlias,
joinedTableAlias
)
// Process the rest of the conditions and logical operators in pairs
for (let i = 1; i < condition.length; i += 2) {
if (i + 1 >= condition.length) break // Make sure we have a pair
const operator = condition[i] as LogicalOperator
const nextCondition = condition[i + 1] as Condition
// Apply the logical operator
if (operator === `and`) {
result =
result &&
evaluateConditionOnNestedRow(
nestedRow,
nextCondition,
mainTableAlias,
joinedTableAlias
)
} else {
// logicalOp === `or`
result =
result ||
evaluateConditionOnNestedRow(
nestedRow,
nextCondition,
mainTableAlias,
joinedTableAlias
)
}
}
return result
}
// Fallback - this should not happen with valid conditions
return true
}
/**
* Evaluates a simple condition against a nested row structure
*/
export function evaluateSimpleConditionOnNestedRow(
nestedRow: Record<string, unknown>,
left: ConditionOperand,
comparator: Comparator,
right: ConditionOperand,
mainTableAlias?: string,
joinedTableAlias?: string
): boolean {
const leftValue = evaluateOperandOnNestedRow(
nestedRow,
left,
mainTableAlias,
joinedTableAlias
)
const rightValue = evaluateOperandOnNestedRow(
nestedRow,
right,
mainTableAlias,
joinedTableAlias
)
// The rest of the function remains the same as evaluateSimpleCondition
switch (comparator) {
case `=`:
return leftValue === rightValue
case `!=`:
return leftValue !== rightValue
case `<`:
return compareValues(leftValue, rightValue, `<`)
case `<=`:
return compareValues(leftValue, rightValue, `<=`)
case `>`:
return compareValues(leftValue, rightValue, `>`)
case `>=`:
return compareValues(leftValue, rightValue, `>=`)
case `like`:
case `not like`:
if (typeof leftValue === `string` && typeof rightValue === `string`) {
// Convert SQL LIKE pattern to proper regex pattern
const pattern = convertLikeToRegex(rightValue)
const matches = new RegExp(`^${pattern}$`, `i`).test(leftValue)
return comparator === `like` ? matches : !matches
}
return comparator === `like` ? false : true
case `in`:
// If right value is not an array, we can't do an IN operation
if (!Array.isArray(rightValue)) {
return false
}
// For empty arrays, nothing is contained in them
if (rightValue.length === 0) {
return false
}
// Handle array-to-array comparison (check if any element in leftValue exists in rightValue)
if (Array.isArray(leftValue)) {
return leftValue.some((item) => isValueInArray(item, rightValue))
}
// Handle single value comparison
return isValueInArray(leftValue, rightValue)
case `not in`:
// If right value is not an array, everything is "not in" it
if (!Array.isArray(rightValue)) {
return true
}
// For empty arrays, everything is "not in" them
if (rightValue.length === 0) {
return true
}
// Handle array-to-array comparison (check if no element in leftValue exists in rightValue)
if (Array.isArray(leftValue)) {
return !leftValue.some((item) => isValueInArray(item, rightValue))
}
// Handle single value comparison
return !isValueInArray(leftValue, rightValue)
case `is`:
return leftValue === rightValue
case `is not`:
// Properly handle null/undefined checks
if (rightValue === null) {
return leftValue !== null && leftValue !== undefined
}
return leftValue !== rightValue
default:
return false
}
}