{"version":3,"file":"order-by.cjs","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import {\n  groupedOrderByWithFractionalIndex,\n  orderByWithFractionalIndex,\n} from '@tanstack/db-ivm'\nimport { defaultComparator, makeComparator } from '../../utils/comparison.js'\nimport {\n  PropRef,\n  collectCollectionSources,\n  followRef,\n  getWhereExpression,\n  isResidualWhere,\n} from '../ir.js'\nimport { ensureIndexForField } from '../../indexes/auto-index.js'\nimport { findIndexForField } from '../../utils/index-optimization.js'\nimport { compileExpression } from './evaluators.js'\nimport { getSourceAliasesFromExpression } from './expressions.js'\nimport { replaceAggregatesByRefs } from './group-by.js'\nimport type { CompareOptions } from '../builder/types.js'\nimport type { WindowOptions } from './types.js'\nimport type { CompiledSingleRowExpression } from './evaluators.js'\nimport type { OrderBy, OrderByClause, QueryIR, Select } from '../ir.js'\nimport type {\n  CollectionLike,\n  NamespacedAndKeyedStream,\n  NamespacedRow,\n} from '../../types.js'\nimport type { IStreamBuilder, KeyValue } from '@tanstack/db-ivm'\nimport type { IndexReader } from '../../indexes/base-index.js'\nimport type { Collection } from '../../collection/index.js'\n\nexport type OrderByOptimizationInfo = {\n  sourceId: string\n  alias: string\n  orderBy: OrderBy\n  offset: number\n  limit: number\n  comparator: (\n    a: Record<string, unknown> | null | undefined,\n    b: Record<string, unknown> | null | undefined,\n  ) => number\n  /** Extracts all orderBy column values from a raw row (array for multi-column) */\n  valueExtractorForRawRow: (row: Record<string, unknown>) => unknown\n  /** Index on the first orderBy column - used for lazy loading */\n  index?: IndexReader<string | number>\n  dataNeeded?: () => number\n  /** Reads the source loader's synchronous request guard, when installed. */\n  isRequesting?: () => boolean\n  /** Whether local operators can discard or reorder the provider's prefix. */\n  requiresFullSource: boolean\n}\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and $selected\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n  rawQuery: QueryIR,\n  pipeline: NamespacedAndKeyedStream,\n  orderByClause: Array<OrderByClause>,\n  selectClause: Select,\n  collection: Collection,\n  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n  setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n  limit?: number,\n  offset?: number,\n  groupKeyFn?: (key: unknown, value: unknown) => unknown,\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n  // Pre-compile all order by expressions\n  const compiledOrderBy = orderByClause.map((clause) => {\n    const clauseWithoutAggregates = replaceAggregatesByRefs(\n      clause.expression,\n      selectClause,\n      `$selected`,\n    )\n\n    return {\n      compiledExpression: compileExpression(clauseWithoutAggregates),\n      compareOptions: buildCompareOptions(clause, collection),\n    }\n  })\n  // Create a value extractor function for the orderBy operator\n  const valueExtractor = (row: NamespacedRow & { $selected?: any }) => {\n    // The namespaced row contains:\n    // 1. Table aliases as top-level properties (e.g., row[\"tableName\"])\n    // 2. SELECT results in $selected (e.g., row.$selected[\"aggregateAlias\"])\n    // The replaceAggregatesByRefs function has already transformed:\n    // - Aggregate expressions that match SELECT aggregates to use the $selected namespace\n    // - $selected ref expressions are passed through unchanged (already using the correct namespace)\n    const orderByContext = row\n\n    if (orderByClause.length > 1) {\n      // For multiple orderBy columns, create a composite key\n      return compiledOrderBy.map((compiled) =>\n        compiled.compiledExpression(orderByContext),\n      )\n    } else if (orderByClause.length === 1) {\n      // For a single orderBy column, use the value directly\n      const compiled = compiledOrderBy[0]!\n      return compiled.compiledExpression(orderByContext)\n    }\n\n    // Default case - no ordering\n    return null\n  }\n\n  // Create a multi-property comparator that respects the order and direction of each property\n  const compare = (a: unknown, b: unknown) => {\n    // If we're comparing arrays (multiple properties), compare each property in order\n    if (orderByClause.length > 1) {\n      const arrayA = a as Array<unknown>\n      const arrayB = b as Array<unknown>\n      for (let i = 0; i < orderByClause.length; i++) {\n        const clause = compiledOrderBy[i]!\n        const compareFn = makeComparator(clause.compareOptions)\n        const result = compareFn(arrayA[i], arrayB[i])\n        if (result !== 0) {\n          return result\n        }\n      }\n      return arrayA.length - arrayB.length\n    }\n\n    // Single property comparison\n    if (orderByClause.length === 1) {\n      const clause = compiledOrderBy[0]!\n      const compareFn = makeComparator(clause.compareOptions)\n      return compareFn(a, b)\n    }\n\n    return defaultComparator(a, b)\n  }\n\n  let setSizeCallback: ((getSize: () => number) => void) | undefined\n\n  let orderByOptimizationInfo: OrderByOptimizationInfo | undefined\n\n  // When there's a limit, we create orderByOptimizationInfo to pass orderBy/limit\n  // to loadSubset so the sync layer can optimize the query.\n  // We try to use an index on the FIRST orderBy column for lazy loading,\n  // even for multi-column orderBy (using wider bounds on first column).\n  // Skip this optimization when using grouped ordering (includes with limit),\n  // because the limit is per-group, not global — the child collection needs all data loaded.\n  if (\n    limit !== undefined &&\n    !groupKeyFn &&\n    rawQuery.from.type !== `unionFrom` &&\n    rawQuery.from.type !== `unionAll`\n  ) {\n    let index: IndexReader<string | number> | undefined\n    let followRefCollection: Collection | undefined\n    let orderByAlias: string = rawQuery.from.alias\n    let orderBySourceId: string | undefined\n\n    // Try to create/find an index on the FIRST orderBy column for lazy loading\n    const firstClause = orderByClause[0]!\n    const firstOrderByExpression = firstClause.expression\n\n    const followRefResult =\n      firstOrderByExpression.type === `ref`\n        ? followRef(rawQuery, firstOrderByExpression, collection)\n        : undefined\n    if (firstOrderByExpression.type === `ref` && followRefResult) {\n      followRefCollection = followRefResult.collection\n      orderBySourceId = followRefResult.sourceId\n      const fieldName = followRefResult.path[0]\n      // The query's first source defines implicit string collation for the\n      // whole order. Build the source index with that same resolved term so\n      // provider admission cannot disagree with emitted query order.\n      const compareOpts = buildCompareOptions(firstClause, collection)\n\n      if (fieldName) {\n        // Use a single-column comparator for the index, not the\n        // multi-column `compare` function. The multi-column comparator\n        // expects array values [col1, col2, ...] but the index stores\n        // individual field values. Passing `compare` here causes the\n        // BTree to treat all single values as equal (since number[0]\n        // === undefined for both sides of the comparison).\n        const firstColumnCompareFn = makeComparator(compareOpts)\n        ensureIndexForField(\n          fieldName,\n          followRefResult.path,\n          followRefCollection,\n          compareOpts,\n          firstColumnCompareFn,\n        )\n      }\n\n      index = findIndexForField(\n        followRefCollection,\n        followRefResult.path,\n        compareOpts,\n      )\n\n      // Only use the index if it supports range queries\n      if (!index?.supports(`gt`)) {\n        index = undefined\n      }\n\n      if (!index) {\n        const collectionId = followRefCollection.id\n        const fieldPath = followRefResult.path.join(`.`)\n        console.warn(\n          `[TanStack DB]${collectionId ? ` [${collectionId}]` : ``} orderBy with limit requires an index on \"${fieldPath}\" for efficient lazy loading. ` +\n            `Falling back to loading all data. ` +\n            `Consider creating an index on the collection with collection.createIndex((row) => row.${fieldPath}) ` +\n            `or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.`,\n        )\n      }\n\n      orderByAlias =\n        firstOrderByExpression.path.length > 1\n          ? String(firstOrderByExpression.path[0])\n          : rawQuery.from.alias\n      orderBySourceId ??= collectCollectionSources(rawQuery).find(\n        (source) =>\n          source.alias === orderByAlias &&\n          source.collection === followRefCollection,\n      )?.sourceId\n    }\n\n    if (orderBySourceId && followRefResult) {\n      const sourceOrderBy = resolveOrderBy(\n        orderByClause,\n        collection.compareOptions,\n      )\n      const sourceOrderIsDirect = orderByClause.every(({ expression }) => {\n        if (expression.type !== `ref`) return false\n        return (\n          followRef(rawQuery, expression, collection)?.sourceId ===\n          orderBySourceId\n        )\n      })\n      const extract = compileExpression(\n        new PropRef(followRefResult.path),\n        true,\n      ) as CompiledSingleRowExpression\n      const compareTerm = makeComparator(sourceOrderBy[0]!.compareOptions)\n      const compareSourceRows = (\n        a: Record<string, unknown> | null | undefined,\n        b: Record<string, unknown> | null | undefined,\n      ) => compareTerm(a ? extract(a) : a, b ? extract(b) : b)\n\n      const info: OrderByOptimizationInfo = {\n        sourceId: orderBySourceId,\n        alias: orderByAlias,\n        offset: offset ?? 0,\n        limit,\n        comparator: compareSourceRows,\n        valueExtractorForRawRow: extract,\n        index,\n        orderBy: sourceOrderBy,\n        requiresFullSource:\n          !sourceOrderIsDirect ||\n          rawQuery.from.type !== `collectionRef` ||\n          rawQuery.from.sourceId !== orderBySourceId ||\n          (rawQuery.join?.some(\n            ({ type }) => type === `inner` || type === `right`,\n          ) ??\n            false) ||\n          (rawQuery.where?.some(\n            (where) =>\n              isResidualWhere(where) ||\n              [\n                ...getSourceAliasesFromExpression(getWhereExpression(where)),\n              ].some((alias) => alias !== orderByAlias),\n          ) ??\n            false) ||\n          (rawQuery.fnWhere?.length ?? 0) > 0 ||\n          rawQuery.groupBy !== undefined ||\n          rawQuery.having !== undefined ||\n          rawQuery.fnHaving !== undefined ||\n          rawQuery.distinct === true,\n      }\n      orderByOptimizationInfo = info\n\n      // Ordered loading is owned by one lexical source. A collection can occur\n      // more than once in a query tree, so collection ID and alias are not\n      // sufficient identities here.\n      optimizableOrderByCollections[orderBySourceId] = info\n\n      // Set up lazy loading callback to track how much more data is needed\n      // This is used by loadMoreIfNeeded to determine if more data should be loaded\n      // Only enable when an index exists — without an index, lazy loading can't work\n      // and all data is loaded eagerly via requestSnapshot instead.\n      if (index) {\n        setSizeCallback = (getSize: () => number) => {\n          optimizableOrderByCollections[orderBySourceId]![`dataNeeded`] =\n            () => {\n              const size = getSize()\n              return Math.max(0, info.limit - size)\n            }\n        }\n      }\n    }\n  }\n\n  // Use grouped ordering when a groupKeyFn is provided (includes with limit/offset),\n  // otherwise use the standard global ordering operator.\n  if (groupKeyFn) {\n    return pipeline.pipe(\n      groupedOrderByWithFractionalIndex(valueExtractor, {\n        limit,\n        offset,\n        comparator: compare,\n        setSizeCallback,\n        groupKeyFn,\n        setWindowFn: (\n          windowFn: (options: { offset?: number; limit?: number }) => void,\n        ) => {\n          setWindowFn((options) => {\n            windowFn(options)\n            if (orderByOptimizationInfo) {\n              orderByOptimizationInfo.offset =\n                options.offset ?? orderByOptimizationInfo.offset\n              orderByOptimizationInfo.limit =\n                options.limit ?? orderByOptimizationInfo.limit\n            }\n          })\n        },\n      }),\n    )\n  }\n\n  // Use fractional indexing and return the tuple [value, index]\n  return pipeline.pipe(\n    orderByWithFractionalIndex(valueExtractor, {\n      limit,\n      offset,\n      comparator: compare,\n      setSizeCallback,\n      setWindowFn: (\n        windowFn: (options: { offset?: number; limit?: number }) => void,\n      ) => {\n        setWindowFn(\n          // We wrap the move function such that we update the orderByOptimizationInfo\n          // because that is used by the `dataNeeded` callback to determine if we need to load more data\n          (options) => {\n            windowFn(options)\n            if (orderByOptimizationInfo) {\n              orderByOptimizationInfo.offset =\n                options.offset ?? orderByOptimizationInfo.offset\n              orderByOptimizationInfo.limit =\n                options.limit ?? orderByOptimizationInfo.limit\n            }\n          },\n        )\n      },\n    }),\n    // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n  )\n}\n\n/**\n * Builds a comparison configuration object that uses the values provided in the orderBy clause.\n * If no string sort configuration is provided it defaults to the collection's string sort configuration.\n * Multi-source FROM queries pass their first source collection here as the\n * documented default. Use explicit orderBy compare options when branches need\n * different string collation behavior.\n */\nexport function buildCompareOptions(\n  clause: OrderByClause,\n  collection: CollectionLike<any, any>,\n): CompareOptions {\n  return resolveCompareOptions(clause, collection.compareOptions)\n}\n\nfunction resolveOrderBy(\n  orderBy: OrderBy,\n  defaults: CollectionLike[`compareOptions`],\n): OrderBy {\n  return orderBy.map((clause) => ({\n    expression: clause.expression,\n    compareOptions: resolveCompareOptions(clause, defaults),\n  }))\n}\n\nfunction resolveCompareOptions(\n  clause: OrderByClause,\n  defaults: CollectionLike[`compareOptions`],\n): CompareOptions {\n  return clause.compareOptions.stringSort === undefined\n    ? {\n        ...defaults,\n        direction: clause.compareOptions.direction,\n        nulls: clause.compareOptions.nulls,\n      }\n    : clause.compareOptions\n}\n"],"names":["replaceAggregatesByRefs","compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","findIndexForField","collectCollectionSources","PropRef","isResidualWhere","getSourceAliasesFromExpression","getWhereExpression","groupedOrderByWithFractionalIndex","orderByWithFractionalIndex"],"mappings":";;;;;;;;;;AAwDO,SAAS,eACd,UACA,UACA,eACA,cACA,YACA,+BACA,aACA,OACA,QACA,YAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,WAAW;AACpD,UAAM,0BAA0BA,QAAAA;AAAAA,MAC9B,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,oBAAoBC,WAAAA,kBAAkB,uBAAuB;AAAA,MAC7D,gBAAgB,oBAAoB,QAAQ,UAAU;AAAA,IAAA;AAAA,EAE1D,CAAC;AAED,QAAM,iBAAiB,CAAC,QAA6C;AAOnE,UAAM,iBAAiB;AAEvB,QAAI,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAAA;AAAA,IAE9C,WAAW,cAAc,WAAW,GAAG;AAErC,YAAM,WAAW,gBAAgB,CAAC;AAClC,aAAO,SAAS,mBAAmB,cAAc;AAAA,IACnD;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,CAAC,GAAY,MAAe;AAE1C,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,SAAS;AACf,YAAM,SAAS;AACf,eAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,cAAM,SAAS,gBAAgB,CAAC;AAChC,cAAM,YAAYC,WAAAA,eAAe,OAAO,cAAc;AACtD,cAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,YAAI,WAAW,GAAG;AAChB,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,OAAO,SAAS,OAAO;AAAA,IAChC;AAGA,QAAI,cAAc,WAAW,GAAG;AAC9B,YAAM,SAAS,gBAAgB,CAAC;AAChC,YAAM,YAAYA,WAAAA,eAAe,OAAO,cAAc;AACtD,aAAO,UAAU,GAAG,CAAC;AAAA,IACvB;AAEA,WAAOC,WAAAA,kBAAkB,GAAG,CAAC;AAAA,EAC/B;AAEA,MAAI;AAEJ,MAAI;AAQJ,MACE,UAAU,UACV,CAAC,cACD,SAAS,KAAK,SAAS,eACvB,SAAS,KAAK,SAAS,YACvB;AACA,QAAI;AACJ,QAAI;AACJ,QAAI,eAAuB,SAAS,KAAK;AACzC,QAAI;AAGJ,UAAM,cAAc,cAAc,CAAC;AACnC,UAAM,yBAAyB,YAAY;AAE3C,UAAM,kBACJ,uBAAuB,SAAS,QAC5BC,GAAAA,UAAU,UAAU,wBAAwB,UAAU,IACtD;AACN,QAAI,uBAAuB,SAAS,SAAS,iBAAiB;AAC5D,4BAAsB,gBAAgB;AACtC,wBAAkB,gBAAgB;AAClC,YAAM,YAAY,gBAAgB,KAAK,CAAC;AAIxC,YAAM,cAAc,oBAAoB,aAAa,UAAU;AAE/D,UAAI,WAAW;AAOb,cAAM,uBAAuBF,WAAAA,eAAe,WAAW;AACvDG,kBAAAA;AAAAA,UACE;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAEA,cAAQC,kBAAAA;AAAAA,QACN;AAAA,QACA,gBAAgB;AAAA,QAChB;AAAA,MAAA;AAIF,UAAI,CAAC,OAAO,SAAS,IAAI,GAAG;AAC1B,gBAAQ;AAAA,MACV;AAEA,UAAI,CAAC,OAAO;AACV,cAAM,eAAe,oBAAoB;AACzC,cAAM,YAAY,gBAAgB,KAAK,KAAK,GAAG;AAC/C,gBAAQ;AAAA,UACN,gBAAgB,eAAe,KAAK,YAAY,MAAM,EAAE,6CAA6C,SAAS,yJAEnB,SAAS;AAAA,QAAA;AAAA,MAGxG;AAEA,qBACE,uBAAuB,KAAK,SAAS,IACjC,OAAO,uBAAuB,KAAK,CAAC,CAAC,IACrC,SAAS,KAAK;AACpB,0BAAoBC,GAAAA,yBAAyB,QAAQ,EAAE;AAAA,QACrD,CAAC,WACC,OAAO,UAAU,gBACjB,OAAO,eAAe;AAAA,MAAA,GACvB;AAAA,IACL;AAEA,QAAI,mBAAmB,iBAAiB;AACtC,YAAM,gBAAgB;AAAA,QACpB;AAAA,QACA,WAAW;AAAA,MAAA;AAEb,YAAM,sBAAsB,cAAc,MAAM,CAAC,EAAE,iBAAiB;AAClE,YAAI,WAAW,SAAS,MAAO,QAAO;AACtC,eACEH,GAAAA,UAAU,UAAU,YAAY,UAAU,GAAG,aAC7C;AAAA,MAEJ,CAAC;AACD,YAAM,UAAUH,WAAAA;AAAAA,QACd,IAAIO,GAAAA,QAAQ,gBAAgB,IAAI;AAAA,QAChC;AAAA,MAAA;AAEF,YAAM,cAAcN,WAAAA,eAAe,cAAc,CAAC,EAAG,cAAc;AACnE,YAAM,oBAAoB,CACxB,GACA,MACG,YAAY,IAAI,QAAQ,CAAC,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC;AAEvD,YAAM,OAAgC;AAAA,QACpC,UAAU;AAAA,QACV,OAAO;AAAA,QACP,QAAQ,UAAU;AAAA,QAClB;AAAA,QACA,YAAY;AAAA,QACZ,yBAAyB;AAAA,QACzB;AAAA,QACA,SAAS;AAAA,QACT,oBACE,CAAC,uBACD,SAAS,KAAK,SAAS,mBACvB,SAAS,KAAK,aAAa,oBAC1B,SAAS,MAAM;AAAA,UACd,CAAC,EAAE,KAAA,MAAW,SAAS,WAAW,SAAS;AAAA,QAAA,KAE3C,WACD,SAAS,OAAO;AAAA,UACf,CAAC,UACCO,mBAAgB,KAAK,KACrB;AAAA,YACE,GAAGC,YAAAA,+BAA+BC,GAAAA,mBAAmB,KAAK,CAAC;AAAA,UAAA,EAC3D,KAAK,CAAC,UAAU,UAAU,YAAY;AAAA,QAAA,KAE1C,WACD,SAAS,SAAS,UAAU,KAAK,KAClC,SAAS,YAAY,UACrB,SAAS,WAAW,UACpB,SAAS,aAAa,UACtB,SAAS,aAAa;AAAA,MAAA;AAE1B,gCAA0B;AAK1B,oCAA8B,eAAe,IAAI;AAMjD,UAAI,OAAO;AACT,0BAAkB,CAAC,YAA0B;AAC3C,wCAA8B,eAAe,EAAG,YAAY,IAC1D,MAAM;AACJ,kBAAM,OAAO,QAAA;AACb,mBAAO,KAAK,IAAI,GAAG,KAAK,QAAQ,IAAI;AAAA,UACtC;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,MAAI,YAAY;AACd,WAAO,SAAS;AAAA,MACdC,MAAAA,kCAAkC,gBAAgB;AAAA,QAChD;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,aAAa,CACX,aACG;AACH,sBAAY,CAAC,YAAY;AACvB,qBAAS,OAAO;AAChB,gBAAI,yBAAyB;AAC3B,sCAAwB,SACtB,QAAQ,UAAU,wBAAwB;AAC5C,sCAAwB,QACtB,QAAQ,SAAS,wBAAwB;AAAA,YAC7C;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MAAA,CACD;AAAA,IAAA;AAAA,EAEL;AAGA,SAAO,SAAS;AAAA,IACdC,MAAAA,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,aAAa,CACX,aACG;AACH;AAAA;AAAA;AAAA,UAGE,CAAC,YAAY;AACX,qBAAS,OAAO;AAChB,gBAAI,yBAAyB;AAC3B,sCAAwB,SACtB,QAAQ,UAAU,wBAAwB;AAC5C,sCAAwB,QACtB,QAAQ,SAAS,wBAAwB;AAAA,YAC7C;AAAA,UACF;AAAA,QAAA;AAAA,MAEJ;AAAA,IAAA,CACD;AAAA;AAAA,EAAA;AAGL;AASO,SAAS,oBACd,QACA,YACgB;AAChB,SAAO,sBAAsB,QAAQ,WAAW,cAAc;AAChE;AAEA,SAAS,eACP,SACA,UACS;AACT,SAAO,QAAQ,IAAI,CAAC,YAAY;AAAA,IAC9B,YAAY,OAAO;AAAA,IACnB,gBAAgB,sBAAsB,QAAQ,QAAQ;AAAA,EAAA,EACtD;AACJ;AAEA,SAAS,sBACP,QACA,UACgB;AAChB,SAAO,OAAO,eAAe,eAAe,SACxC;AAAA,IACE,GAAG;AAAA,IACH,WAAW,OAAO,eAAe;AAAA,IACjC,OAAO,OAAO,eAAe;AAAA,EAAA,IAE/B,OAAO;AACb;;;"}