{"version":3,"file":"expression-helpers.cjs","sources":["../../../src/query/expression-helpers.ts"],"sourcesContent":["/**\n * Expression Helpers for TanStack DB\n *\n * These utilities help parse LoadSubsetOptions (where, orderBy, limit) from TanStack DB\n * into formats suitable for your API backend. They provide a generic way to traverse\n * expression trees without having to implement your own parser.\n *\n * @example\n * ```typescript\n * import { parseWhereExpression, parseOrderByExpression } from '@tanstack/db'\n *\n * queryFn: async (ctx) => {\n *   const { limit, where, orderBy } = ctx.meta?.loadSubsetOptions ?? {}\n *\n *   // Convert expression tree to filters\n *   const filters = parseWhereExpression(where, {\n *     eq: (field, value) => ({ [field]: value }),\n *     lt: (field, value) => ({ [`${field}_lt`]: value }),\n *     and: (filters) => Object.assign({}, ...filters)\n *   })\n *\n *   // Extract sort information\n *   const sort = parseOrderByExpression(orderBy)\n *\n *   return api.getProducts({ ...filters, sort, limit })\n * }\n * ```\n */\n\nimport type { IR, OperatorName } from '../index.js'\n\ntype BasicExpression<T = any> = IR.BasicExpression<T>\ntype OrderBy = IR.OrderBy\n\n/**\n * Represents a simple field path extracted from an expression.\n * Can include string keys for object properties and numbers for array indices.\n */\nexport type FieldPath = Array<string | number>\n\n/**\n * Represents a simple comparison operation\n */\nexport interface SimpleComparison {\n  field: FieldPath\n  operator: string\n  value?: any // Optional for operators like isNull and isUndefined that don't have a value\n}\n\n/**\n * Options for customizing how WHERE expressions are parsed\n */\nexport interface ParseWhereOptions<T = any> {\n  /**\n   * Handler functions for different operators.\n   * Each handler receives the parsed field path(s) and value(s) and returns your custom format.\n   *\n   * Supported operators from TanStack DB:\n   * - Comparison: eq, gt, gte, lt, lte, in, like, ilike\n   * - Logical: and, or, not\n   * - Null checking: isNull, isUndefined\n   * - String functions: upper, lower, length, concat\n   * - Numeric: add\n   * - Utility: coalesce\n   * - Aggregates: count, avg, sum, min, max\n   */\n  handlers: {\n    [K in OperatorName]?: (...args: Array<any>) => T\n  } & {\n    [key: string]: (...args: Array<any>) => T\n  }\n  /**\n   * Optional handler for when an unknown operator is encountered.\n   * If not provided, unknown operators throw an error.\n   */\n  onUnknownOperator?: (operator: string, args: Array<any>) => T\n}\n\n/**\n * Result of parsing an ORDER BY expression\n */\nexport interface ParsedOrderBy {\n  field: FieldPath\n  direction: `asc` | `desc`\n  nulls: `first` | `last`\n  /** String sorting method: 'lexical' (default) or 'locale' (locale-aware) */\n  stringSort?: `lexical` | `locale`\n  /** Locale for locale-aware string sorting (e.g., 'en-US') */\n  locale?: string\n  /** Additional options for locale-aware sorting */\n  localeOptions?: object\n}\n\n/**\n * Extracts the field path from a PropRef expression.\n * Returns null for non-ref expressions.\n *\n * @param expr - The expression to extract from\n * @returns The field path array, or null\n *\n * @example\n * ```typescript\n * const field = extractFieldPath(someExpression)\n * // Returns: ['product', 'category']\n * ```\n */\nexport function extractFieldPath(expr: BasicExpression): FieldPath | null {\n  if (expr.type === `ref`) {\n    return expr.path\n  }\n  return null\n}\n\n/**\n * Extracts the value from a Value expression.\n * Returns undefined for non-value expressions.\n *\n * @param expr - The expression to extract from\n * @returns The extracted value\n *\n * @example\n * ```typescript\n * const val = extractValue(someExpression)\n * // Returns: 'electronics'\n * ```\n */\nexport function extractValue(expr: BasicExpression): any {\n  if (expr.type === `val`) {\n    return expr.value\n  }\n  return undefined\n}\n\n/**\n * Generic expression tree walker that visits each node in the expression.\n * Useful for implementing custom parsing logic.\n *\n * @param expr - The expression to walk\n * @param visitor - Visitor function called for each node\n *\n * @example\n * ```typescript\n * walkExpression(whereExpr, (node) => {\n *   if (node.type === 'func' && node.name === 'eq') {\n *     console.log('Found equality comparison')\n *   }\n * })\n * ```\n */\nexport function walkExpression(\n  expr: BasicExpression | undefined | null,\n  visitor: (node: BasicExpression) => void,\n): void {\n  if (!expr) return\n\n  visitor(expr)\n\n  if (expr.type === `func`) {\n    expr.args.forEach((arg: BasicExpression) => walkExpression(arg, visitor))\n  }\n}\n\n/**\n * Parses a WHERE expression into a custom format using provided handlers.\n *\n * This is the main helper for converting TanStack DB where clauses into your API's filter format.\n * You provide handlers for each operator, and this function traverses the expression tree\n * and calls the appropriate handlers.\n *\n * @param expr - The WHERE expression to parse\n * @param options - Configuration with handler functions for each operator\n * @returns The parsed result in your custom format\n *\n * @example\n * ```typescript\n * // REST API with query parameters\n * const filters = parseWhereExpression(where, {\n *   handlers: {\n *     eq: (field, value) => ({ [field.join('.')]: value }),\n *     lt: (field, value) => ({ [`${field.join('.')}_lt`]: value }),\n *     gt: (field, value) => ({ [`${field.join('.')}_gt`]: value }),\n *     and: (...filters) => Object.assign({}, ...filters),\n *     or: (...filters) => ({ $or: filters })\n *   }\n * })\n * // Returns: { category: 'electronics', price_lt: 100 }\n * ```\n *\n * @example\n * ```typescript\n * // GraphQL where clause\n * const where = parseWhereExpression(whereExpr, {\n *   handlers: {\n *     eq: (field, value) => ({ [field.join('_')]: { _eq: value } }),\n *     lt: (field, value) => ({ [field.join('_')]: { _lt: value } }),\n *     and: (...filters) => ({ _and: filters })\n *   }\n * })\n * ```\n */\nexport function parseWhereExpression<T = any>(\n  expr: BasicExpression<boolean> | undefined | null,\n  options: ParseWhereOptions<T>,\n): T | null {\n  if (!expr) return null\n\n  const { handlers, onUnknownOperator } = options\n\n  // Handle value expressions\n  if (expr.type === `val`) {\n    return expr.value as unknown as T\n  }\n\n  // Handle property references\n  if (expr.type === `ref`) {\n    return expr.path as unknown as T\n  }\n\n  // Handle function expressions\n  // After checking val and ref, expr must be func\n  const { name, args } = expr\n  const handler = handlers[name]\n\n  if (!handler) {\n    if (onUnknownOperator) {\n      return onUnknownOperator(name, args)\n    }\n    throw new Error(\n      `No handler provided for operator: ${name}. Available handlers: ${Object.keys(handlers).join(`, `)}`,\n    )\n  }\n\n  // Parse arguments recursively\n  const parsedArgs = args.map((arg: BasicExpression) => {\n    // For refs, extract the field path\n    if (arg.type === `ref`) {\n      return arg.path\n    }\n    // For values, extract the value\n    if (arg.type === `val`) {\n      return arg.value\n    }\n    // For nested functions, recurse (after checking ref and val, must be func)\n    return parseWhereExpression(arg, options)\n  })\n\n  return handler(...parsedArgs)\n}\n\n/**\n * Parses an ORDER BY expression into a simple array of sort specifications.\n *\n * @param orderBy - The ORDER BY expression array\n * @returns Array of parsed order by specifications\n *\n * @example\n * ```typescript\n * const sorts = parseOrderByExpression(orderBy)\n * // Returns: [\n * //   { field: ['category'], direction: 'asc', nulls: 'last' },\n * //   { field: ['price'], direction: 'desc', nulls: 'last' }\n * // ]\n * ```\n */\nexport function parseOrderByExpression(\n  orderBy: OrderBy | undefined | null,\n): Array<ParsedOrderBy> {\n  if (!orderBy || orderBy.length === 0) {\n    return []\n  }\n\n  return orderBy.map((clause: IR.OrderByClause) => {\n    const field = extractFieldPath(clause.expression)\n\n    if (!field) {\n      throw new Error(\n        `ORDER BY expression must be a field reference, got: ${clause.expression.type}`,\n      )\n    }\n\n    const { direction, nulls } = clause.compareOptions\n    const result: ParsedOrderBy = {\n      field,\n      direction,\n      nulls,\n    }\n\n    // Add string collation options if present (discriminated union)\n    if (`stringSort` in clause.compareOptions) {\n      result.stringSort = clause.compareOptions.stringSort\n    }\n    if (`locale` in clause.compareOptions) {\n      result.locale = clause.compareOptions.locale\n    }\n    if (`localeOptions` in clause.compareOptions) {\n      result.localeOptions = clause.compareOptions.localeOptions\n    }\n\n    return result\n  })\n}\n\n/**\n * Extracts all simple comparisons from a WHERE expression.\n * This is useful for simple APIs that only support basic filters.\n *\n * Note: This only works for simple AND-ed conditions and NOT-wrapped comparisons.\n * Throws an error if it encounters unsupported operations like OR or complex nested expressions.\n *\n * NOT operators are flattened by prefixing the operator name (e.g., `not(eq(...))` becomes `not_eq`).\n *\n * @param expr - The WHERE expression to parse\n * @returns Array of simple comparisons\n * @throws Error if expression contains OR or other unsupported operations\n *\n * @example\n * ```typescript\n * const comparisons = extractSimpleComparisons(where)\n * // Returns: [\n * //   { field: ['category'], operator: 'eq', value: 'electronics' },\n * //   { field: ['price'], operator: 'lt', value: 100 },\n * //   { field: ['email'], operator: 'isNull' }, // No value for null checks\n * //   { field: ['status'], operator: 'not_eq', value: 'archived' }\n * // ]\n * ```\n */\nexport function extractSimpleComparisons(\n  expr: BasicExpression<boolean> | undefined | null,\n): Array<SimpleComparison> {\n  if (!expr) return []\n\n  const comparisons: Array<SimpleComparison> = []\n\n  function extract(e: BasicExpression): void {\n    if (e.type === `func`) {\n      // Handle AND - recurse into both sides\n      if (e.name === `and`) {\n        e.args.forEach((arg: BasicExpression) => extract(arg))\n        return\n      }\n\n      // Handle NOT - recurse into argument and prefix operator with 'not_'\n      if (e.name === `not`) {\n        const [arg] = e.args\n        if (!arg || arg.type !== `func`) {\n          throw new Error(\n            `extractSimpleComparisons requires a comparison or null check inside 'not' operator.`,\n          )\n        }\n\n        // Handle NOT with null/undefined checks\n        const nullCheckOps = [`isNull`, `isUndefined`]\n        if (nullCheckOps.includes(arg.name)) {\n          const [fieldArg] = arg.args\n          const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n          if (field) {\n            comparisons.push({\n              field,\n              operator: `not_${arg.name}`,\n              // No value for null/undefined checks\n            })\n          } else {\n            throw new Error(\n              `extractSimpleComparisons requires a field reference for '${arg.name}' operator.`,\n            )\n          }\n          return\n        }\n\n        // Handle NOT with comparison operators\n        const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n        if (comparisonOps.includes(arg.name)) {\n          const [leftArg, rightArg] = arg.args\n          const field = leftArg?.type === `ref` ? leftArg.path : null\n          const value = rightArg?.type === `val` ? rightArg.value : null\n\n          if (field && value !== undefined) {\n            comparisons.push({\n              field,\n              operator: `not_${arg.name}`,\n              value,\n            })\n          } else {\n            throw new Error(\n              `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for 'not(${arg.name})' operator.`,\n            )\n          }\n          return\n        }\n\n        // NOT can only wrap simple comparisons or null checks\n        throw new Error(\n          `extractSimpleComparisons does not support 'not(${arg.name})'. NOT can only wrap comparison operators (eq, gt, gte, lt, lte, in) or null checks (isNull, isUndefined).`,\n        )\n      }\n\n      // Throw on unsupported operations\n      const unsupportedOps = [\n        `or`,\n        `like`,\n        `ilike`,\n        `upper`,\n        `lower`,\n        `length`,\n        `concat`,\n        `add`,\n        `coalesce`,\n        `count`,\n        `avg`,\n        `sum`,\n        `min`,\n        `max`,\n      ]\n      if (unsupportedOps.includes(e.name)) {\n        throw new Error(\n          `extractSimpleComparisons does not support '${e.name}' operator. Use parseWhereExpression with custom handlers for complex expressions.`,\n        )\n      }\n\n      // Handle null/undefined check operators (single argument, no value)\n      const nullCheckOps = [`isNull`, `isUndefined`]\n      if (nullCheckOps.includes(e.name)) {\n        const [fieldArg] = e.args\n\n        // Extract field (must be a ref)\n        const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n        if (field) {\n          comparisons.push({\n            field,\n            operator: e.name,\n            // No value for null/undefined checks\n          })\n        } else {\n          throw new Error(\n            `extractSimpleComparisons requires a field reference for '${e.name}' operator.`,\n          )\n        }\n        return\n      }\n\n      // Handle comparison operators\n      const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n      if (comparisonOps.includes(e.name)) {\n        const [leftArg, rightArg] = e.args\n\n        // Extract field and value\n        const field = leftArg?.type === `ref` ? leftArg.path : null\n        const value = rightArg?.type === `val` ? rightArg.value : null\n\n        if (field && value !== undefined) {\n          comparisons.push({\n            field,\n            operator: e.name,\n            value,\n          })\n        } else {\n          throw new Error(\n            `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for '${e.name}' operator.`,\n          )\n        }\n      } else {\n        // Unknown operator\n        throw new Error(\n          `extractSimpleComparisons encountered unknown operator: '${e.name}'`,\n        )\n      }\n    }\n  }\n\n  extract(expr)\n  return comparisons\n}\n\n/**\n * Convenience function to get all LoadSubsetOptions in a pre-parsed format.\n * Good starting point for simple use cases.\n *\n * @param options - The LoadSubsetOptions from ctx.meta\n * @returns Pre-parsed filters, sorts, and limit\n *\n * @example\n * ```typescript\n * queryFn: async (ctx) => {\n *   const parsed = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)\n *\n *   // Convert to your API format\n *   return api.getProducts({\n *     ...Object.fromEntries(\n *       parsed.filters.map(f => [`${f.field.join('.')}_${f.operator}`, f.value])\n *     ),\n *     sort: parsed.sorts.map(s => `${s.field.join('.')}:${s.direction}`).join(','),\n *     limit: parsed.limit\n *   })\n * }\n * ```\n */\nexport function parseLoadSubsetOptions(\n  options:\n    | {\n        where?: BasicExpression<boolean>\n        orderBy?: OrderBy\n        limit?: number\n      }\n    | undefined\n    | null,\n): {\n  filters: Array<SimpleComparison>\n  sorts: Array<ParsedOrderBy>\n  limit?: number\n} {\n  if (!options) {\n    return { filters: [], sorts: [] }\n  }\n\n  return {\n    filters: extractSimpleComparisons(options.where),\n    sorts: parseOrderByExpression(options.orderBy),\n    limit: options.limit,\n  }\n}\n"],"names":["nullCheckOps","comparisonOps"],"mappings":"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"}