{"version":3,"file":"order-by.cjs","sources":["../../../src/query/order-by.ts"],"sourcesContent":["import {\n  map,\n  orderBy,\n  orderByWithFractionalIndex,\n  orderByWithIndex,\n} from \"@electric-sql/d2mini\"\nimport { evaluateOperandOnNamespacedRow } from \"./extractors\"\nimport { isOrderIndexFunctionCall } from \"./utils\"\nimport type { ConditionOperand, Query } from \"./schema\"\nimport type {\n  KeyedNamespacedRow,\n  NamespacedAndKeyedStream,\n  NamespacedRow,\n} from \"../types\"\n\ntype OrderByItem = {\n  operand: ConditionOperand\n  direction: `asc` | `desc`\n}\n\ntype OrderByItems = Array<OrderByItem>\n\nexport function processOrderBy(\n  resultPipeline: NamespacedAndKeyedStream,\n  query: Query,\n  mainTableAlias: string\n) {\n  // Check if any column in the SELECT clause is an ORDER_INDEX function call\n  let hasOrderIndexColumn = false\n  let orderIndexType: `numeric` | `fractional` = `numeric`\n  let orderIndexAlias = ``\n\n  // Scan the SELECT clause for ORDER_INDEX functions\n  // TODO: Select is going to be optional in future - we will automatically add an\n  // attribute for the index column\n  for (const item of query.select!) {\n    if (typeof item === `object`) {\n      for (const [alias, expr] of Object.entries(item)) {\n        if (typeof expr === `object` && isOrderIndexFunctionCall(expr)) {\n          hasOrderIndexColumn = true\n          orderIndexAlias = alias\n          orderIndexType = getOrderIndexType(expr)\n          break\n        }\n      }\n    }\n    if (hasOrderIndexColumn) break\n  }\n\n  // Normalize orderBy to an array of objects\n  const orderByItems: OrderByItems = []\n\n  if (typeof query.orderBy === `string`) {\n    // Handle string format: '@column'\n    orderByItems.push({\n      operand: query.orderBy,\n      direction: `asc`,\n    })\n  } else if (Array.isArray(query.orderBy)) {\n    // Handle array format: ['@column1', { '@column2': 'desc' }]\n    for (const item of query.orderBy) {\n      if (typeof item === `string`) {\n        orderByItems.push({\n          operand: item,\n          direction: `asc`,\n        })\n      } else if (typeof item === `object`) {\n        for (const [column, direction] of Object.entries(item)) {\n          orderByItems.push({\n            operand: column,\n            direction: direction as `asc` | `desc`,\n          })\n        }\n      }\n    }\n  } else if (typeof query.orderBy === `object`) {\n    // Handle object format: { '@column': 'desc' }\n    for (const [column, direction] of Object.entries(query.orderBy)) {\n      orderByItems.push({\n        operand: column,\n        direction: direction as `asc` | `desc`,\n      })\n    }\n  }\n\n  // Create a value extractor function for the orderBy operator\n  // const valueExtractor = ([key, namespacedRow]: [\n  const valueExtractor = (namespacedRow: NamespacedRow) => {\n    // For multiple orderBy columns, create a composite key\n    if (orderByItems.length > 1) {\n      return orderByItems.map((item) =>\n        evaluateOperandOnNamespacedRow(\n          namespacedRow,\n          item.operand,\n          mainTableAlias\n        )\n      )\n    } else if (orderByItems.length === 1) {\n      // For a single orderBy column, use the value directly\n      const item = orderByItems[0]\n      const val = evaluateOperandOnNamespacedRow(\n        namespacedRow,\n        item!.operand,\n        mainTableAlias\n      )\n      return val\n    }\n\n    // Default case - no ordering\n    return null\n  }\n\n  const ascComparator = (a: any, b: any): number => {\n    // if a and b are both strings, compare them based on locale\n    if (typeof a === `string` && typeof b === `string`) {\n      return a.localeCompare(b)\n    }\n\n    // if a and b are both arrays, compare them element by element\n    if (Array.isArray(a) && Array.isArray(b)) {\n      for (let i = 0; i < Math.min(a.length, b.length); i++) {\n        // Compare the values\n        const result = ascComparator(a[i], b[i])\n\n        if (result !== 0) {\n          return result\n        }\n      }\n      // All elements are equal up to the minimum length\n      return a.length - b.length\n    }\n\n    // If at least one of the values is an object then we don't really know how to meaningfully compare them\n    // therefore we turn them into strings and compare those\n    // There are 2 exceptions:\n    // 1) if both objects are dates then we can compare them\n    // 2) if either object is nullish then we can't call toString on it\n    const bothObjects = typeof a === `object` && typeof b === `object`\n    const bothDates = a instanceof Date && b instanceof Date\n    const notNull = a !== null && b !== null\n    if (bothObjects && !bothDates && notNull) {\n      // Every object should support `toString`\n      return a.toString().localeCompare(b.toString())\n    }\n\n    if (a < b) return -1\n    if (a > b) return 1\n    return 0\n  }\n\n  const descComparator = (a: unknown, b: unknown): number => {\n    return ascComparator(b, a)\n  }\n\n  // Create a multi-property comparator that respects the order and direction of each property\n  const makeComparator = (orderByProps: OrderByItems) => {\n    return (a: unknown, b: unknown) => {\n      // If we're comparing arrays (multiple properties), compare each property in order\n      if (orderByProps.length > 1) {\n        // `a` and `b` must be arrays since `orderByItems.length > 1`\n        // hence the extracted values must be arrays\n        const arrayA = a as Array<unknown>\n        const arrayB = b as Array<unknown>\n        for (let i = 0; i < orderByProps.length; i++) {\n          const direction = orderByProps[i]!.direction\n          const compareFn =\n            direction === `desc` ? descComparator : ascComparator\n          const result = compareFn(arrayA[i], arrayB[i])\n          if (result !== 0) {\n            return result\n          }\n        }\n        // should normally always be 0 because\n        // both values are extracted based on orderByItems\n        return arrayA.length - arrayB.length\n      }\n\n      // Single property comparison\n      if (orderByProps.length === 1) {\n        const direction = orderByProps[0]!.direction\n        return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)\n      }\n\n      return ascComparator(a, b)\n    }\n  }\n  const comparator = makeComparator(orderByItems)\n\n  // Apply the appropriate orderBy operator based on whether an ORDER_INDEX column is requested\n  if (hasOrderIndexColumn) {\n    if (orderIndexType === `numeric`) {\n      // Use orderByWithIndex for numeric indices\n      resultPipeline = resultPipeline.pipe(\n        orderByWithIndex(valueExtractor, {\n          limit: query.limit,\n          offset: query.offset,\n          comparator,\n        }),\n        map(([key, [value, index]]) => {\n          // Add the index to the result\n          // We add this to the main table alias for now\n          // TODO: re are going to need to refactor the whole order by pipeline\n          const result = {\n            ...(value as Record<string, unknown>),\n            [mainTableAlias]: {\n              ...value[mainTableAlias],\n              [orderIndexAlias]: index,\n            },\n          }\n          return [key, result] as KeyedNamespacedRow\n        })\n      )\n    } else {\n      // Use orderByWithFractionalIndex for fractional indices\n      resultPipeline = resultPipeline.pipe(\n        orderByWithFractionalIndex(valueExtractor, {\n          limit: query.limit,\n          offset: query.offset,\n          comparator,\n        }),\n        map(([key, [value, index]]) => {\n          // Add the index to the result\n          // We add this to the main table alias for now\n          // TODO: re are going to need to refactor the whole order by pipeline\n          const result = {\n            ...(value as Record<string, unknown>),\n            [mainTableAlias]: {\n              ...value[mainTableAlias],\n              [orderIndexAlias]: index,\n            },\n          }\n          return [key, result] as KeyedNamespacedRow\n        })\n      )\n    }\n  } else {\n    // Use regular orderBy if no index column is requested\n    resultPipeline = resultPipeline.pipe(\n      orderBy(valueExtractor, {\n        limit: query.limit,\n        offset: query.offset,\n        comparator,\n      })\n    )\n  }\n\n  return resultPipeline\n}\n\n// Helper function to extract the ORDER_INDEX type from a function call\nfunction getOrderIndexType(obj: any): `numeric` | `fractional` {\n  if (!isOrderIndexFunctionCall(obj)) {\n    throw new Error(`Not an ORDER_INDEX function call`)\n  }\n\n  const arg = obj[`ORDER_INDEX`]\n  if (arg === `numeric` || arg === true || arg === `default`) {\n    return `numeric`\n  } else if (arg === `fractional`) {\n    return `fractional`\n  } else {\n    throw new Error(`Invalid ORDER_INDEX type: ` + arg)\n  }\n}\n"],"names":["isOrderIndexFunctionCall","evaluateOperandOnNamespacedRow","orderByWithIndex","map","orderByWithFractionalIndex","orderBy"],"mappings":"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"}