UNPKG

@algochad/prisma-core

Version:

A comprehensive NestJS library that provides EF-Core-like operations using Prisma and GraphQL. Features LINQ-style query builders, advanced data manipulation, GraphQL integration with genql, and a unified API for both Prisma and GraphQL operations. Includ

371 lines 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LinqExpressionParser = void 0; const database_dialect_1 = require("./database-dialect"); class LinqExpressionParser { static databaseDialect = 'sqlite'; static setDatabaseDialect(dialect) { this.databaseDialect = dialect; } static supportsCaseInsensitiveMode() { const config = database_dialect_1.DatabaseDialectDetector.getConfig(this.databaseDialect); return (config.supportsCaseInsensitive && this.databaseDialect !== 'sqlite'); } static parseLambdaExpression(lambdaFn) { const fnString = lambdaFn.toString(); let expression = fnString .replace(/^\s*\(?[^)]*\)?\s*=>\s*/, '') .replace(/^function\s*\([^)]*\)\s*{\s*return\s*/, '') .replace(/;\s*}$/, '') .trim(); try { const parsedExpression = this.parseExpression(expression); if (parsedExpression.type === 'member') { console.warn(`Cannot convert bare member access '${parsedExpression.member}' to where clause. ` + `Use explicit comparison like 'item.${parsedExpression.member} === value'`); return null; } return this.convertToWhereObject(parsedExpression); } catch (error) { console.warn('Failed to parse lambda expression, falling back to function execution:', error); return null; } } static parseExpression(expr) { expr = expr.trim(); if (expr.includes('&&') || expr.includes('||')) { return this.parseLogicalExpression(expr); } const comparisonOps = ['===', '!==', '==', '!=', '<=', '>=', '<', '>']; for (const op of comparisonOps) { const index = expr.lastIndexOf(op); if (index > 0) { const left = expr.substring(0, index).trim(); const right = expr.substring(index + op.length).trim(); return { type: 'binary', operator: op, left: this.parseExpression(left), right: this.parseExpression(right), }; } } if (expr.includes('(') && expr.includes(')')) { return this.parseMethodCall(expr); } if (expr.includes('.')) { const parts = expr.split('.'); if (parts.length >= 2) { return { type: 'member', member: parts.slice(1).join('.'), }; } } if (expr.startsWith('"') && expr.endsWith('"')) { return { type: 'constant', value: expr.slice(1, -1), }; } if (expr.startsWith("'") && expr.endsWith("'")) { return { type: 'constant', value: expr.slice(1, -1), }; } if (!isNaN(Number(expr))) { return { type: 'constant', value: Number(expr), }; } if (expr === 'true' || expr === 'false') { return { type: 'constant', value: expr === 'true', }; } return { type: 'constant', value: expr, }; } static parseLogicalExpression(expr) { const andIndex = expr.lastIndexOf('&&'); const orIndex = expr.lastIndexOf('||'); let operator; let splitIndex; if (andIndex > orIndex) { operator = '&&'; splitIndex = andIndex; } else { operator = '||'; splitIndex = orIndex; } const left = expr.substring(0, splitIndex).trim(); const right = expr.substring(splitIndex + 2).trim(); return { type: 'logical', operator, expressions: [ this.parseExpression(left), this.parseExpression(right), ], }; } static parseMethodCall(expr) { const parenIndex = expr.indexOf('('); const beforeParen = expr.substring(0, parenIndex); const argsString = expr.substring(parenIndex + 1, expr.lastIndexOf(')')); if (beforeParen.includes('.')) { const parts = beforeParen.split('.'); const memberPath = parts.slice(0, -1).join('.'); const methodName = parts[parts.length - 1]; return { type: 'call', left: this.parseExpression(memberPath), method: methodName, arguments: argsString ? [this.parseExpression(argsString)] : [], }; } return { type: 'call', method: beforeParen, arguments: argsString ? [this.parseExpression(argsString)] : [], }; } static convertToWhereObject(expression) { switch (expression.type) { case 'binary': return this.convertBinaryExpression(expression); case 'logical': return this.convertLogicalExpression(expression); case 'call': return this.convertMethodCall(expression); case 'member': return null; case 'constant': return expression.value; default: throw new Error(`Unsupported expression type: ${expression.type}`); } } static convertBinaryExpression(expression) { const left = expression.left; const right = expression.right; const operator = expression.operator; let fieldName; let value; let effectiveOperator = operator; if (left.type === 'member' && (right.type === 'constant' || right.type === 'member')) { fieldName = left.member; if (right.type === 'constant') { value = right.value; } else { return null; } } else if (left.type === 'call' && (right.type === 'constant' || typeof right.value !== 'undefined')) { return this.convertMethodCallComparison(left, right, operator); } else if (left.type === 'call' && right.type === 'call') { return this.convertMethodCallComparison(left, right, operator); } else if (left.type === 'constant' && right.type === 'member') { fieldName = right.member; value = left.value; effectiveOperator = this.reverseOperator(operator); } else { return null; } switch (effectiveOperator) { case '===': case '==': return { [fieldName]: { equals: value } }; case '!==': case '!=': return { [fieldName]: { not: value } }; case '>': return { [fieldName]: { gt: value } }; case '>=': return { [fieldName]: { gte: value } }; case '<': return { [fieldName]: { lt: value } }; case '<=': return { [fieldName]: { lte: value } }; default: throw new Error(`Unsupported operator: ${effectiveOperator}`); } } static reverseOperator(operator) { switch (operator) { case '>': return '<'; case '>=': return '<='; case '<': return '>'; case '<=': return '>='; default: return operator; } } static convertLogicalExpression(expression) { const operator = expression.operator; const expressions = expression.expressions; const conditions = expressions .map((expr) => this.convertToWhereObject(expr)) .filter((condition) => { return (condition !== null && condition !== undefined && typeof condition === 'object' && !Array.isArray(condition)); }); if (conditions.length === 0) { return null; } if (conditions.length === 1) { return conditions[0]; } if (operator === '&&') { return { AND: conditions }; } else if (operator === '||') { return { OR: conditions }; } else { throw new Error(`Unsupported logical operator: ${operator}`); } } static convertMethodCall(expression) { if (!expression.left || expression.left.type !== 'member') { throw new Error('Method calls must be on object members'); } const fieldName = expression.left.member; const methodName = expression.method; switch (methodName) { case 'toLowerCase': return { type: 'call', field: fieldName, method: 'toLowerCase', }; case 'toUpperCase': return { type: 'call', field: fieldName, method: 'toUpperCase', }; case 'includes': case 'contains': const searchValue = expression.arguments?.[0]; if (searchValue?.type === 'constant') { return { [fieldName]: { contains: searchValue.value, mode: 'insensitive', }, }; } break; case 'startsWith': const startsValue = expression.arguments?.[0]; if (startsValue?.type === 'constant') { return { [fieldName]: { startsWith: startsValue.value, mode: 'insensitive', }, }; } break; case 'endsWith': const endsValue = expression.arguments?.[0]; if (endsValue?.type === 'constant') { return { [fieldName]: { endsWith: endsValue.value, mode: 'insensitive', }, }; } break; } throw new Error(`Unsupported method: ${methodName}`); } static convertMethodCallComparison(left, right, operator) { if (left.method === 'toLowerCase' && left.left?.type === 'member') { const fieldName = left.left.member; let compareValue; if (right.type === 'constant') { compareValue = right.value; } else if (right.type === 'call' && right.method === 'toLowerCase') { if (right.left?.type === 'constant') { compareValue = right.left.value; } else { return null; } } else { return null; } switch (operator) { case '===': case '==': return { [fieldName]: { equals: compareValue, mode: 'insensitive', }, }; case '!==': case '!=': return { [fieldName]: { not: { equals: compareValue, mode: 'insensitive' }, }, }; default: throw new Error(`Unsupported operator for string comparison: ${operator}`); } } return null; } static extractVariables(lambdaFn) { const fnString = lambdaFn.toString(); const variables = []; const variableRegex = /(?<![a-zA-Z_$])[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*[^.(])/g; const matches = fnString.match(variableRegex) || []; const keywords = [ 'return', 'true', 'false', 'null', 'undefined', 'q', 'item', 'x', 'toLowerCase', 'toUpperCase', ]; for (const match of matches) { if (!keywords.includes(match) && !variables.includes(match)) { variables.push(match); } } return variables; } } exports.LinqExpressionParser = LinqExpressionParser; //# sourceMappingURL=linq-expression-parser.js.map