UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

207 lines (206 loc) 8.37 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const dbIvm = require("@tanstack/db-ivm"); const comparison = require("../../utils/comparison.cjs"); const ir = require("../ir.cjs"); const autoIndex = require("../../indexes/auto-index.cjs"); const indexOptimization = require("../../utils/index-optimization.cjs"); const evaluators = require("./evaluators.cjs"); const expressions = require("./expressions.cjs"); const groupBy = require("./group-by.cjs"); function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collection, optimizableOrderByCollections, setWindowFn, limit, offset, groupKeyFn) { const compiledOrderBy = orderByClause.map((clause) => { const clauseWithoutAggregates = groupBy.replaceAggregatesByRefs( clause.expression, selectClause, `$selected` ); return { compiledExpression: evaluators.compileExpression(clauseWithoutAggregates), compareOptions: buildCompareOptions(clause, collection) }; }); const valueExtractor = (row) => { const orderByContext = row; if (orderByClause.length > 1) { return compiledOrderBy.map( (compiled) => compiled.compiledExpression(orderByContext) ); } else if (orderByClause.length === 1) { const compiled = compiledOrderBy[0]; return compiled.compiledExpression(orderByContext); } return null; }; const compare = (a, b) => { if (orderByClause.length > 1) { const arrayA = a; const arrayB = b; for (let i = 0; i < orderByClause.length; i++) { const clause = compiledOrderBy[i]; const compareFn = comparison.makeComparator(clause.compareOptions); const result = compareFn(arrayA[i], arrayB[i]); if (result !== 0) { return result; } } return arrayA.length - arrayB.length; } if (orderByClause.length === 1) { const clause = compiledOrderBy[0]; const compareFn = comparison.makeComparator(clause.compareOptions); return compareFn(a, b); } return comparison.defaultComparator(a, b); }; let setSizeCallback; let orderByOptimizationInfo; if (limit !== void 0 && !groupKeyFn && rawQuery.from.type !== `unionFrom` && rawQuery.from.type !== `unionAll`) { let index; let followRefCollection; let orderByAlias = rawQuery.from.alias; let orderBySourceId; const firstClause = orderByClause[0]; const firstOrderByExpression = firstClause.expression; const followRefResult = firstOrderByExpression.type === `ref` ? ir.followRef(rawQuery, firstOrderByExpression, collection) : void 0; if (firstOrderByExpression.type === `ref` && followRefResult) { followRefCollection = followRefResult.collection; orderBySourceId = followRefResult.sourceId; const fieldName = followRefResult.path[0]; const compareOpts = buildCompareOptions(firstClause, collection); if (fieldName) { const firstColumnCompareFn = comparison.makeComparator(compareOpts); autoIndex.ensureIndexForField( fieldName, followRefResult.path, followRefCollection, compareOpts, firstColumnCompareFn ); } index = indexOptimization.findIndexForField( followRefCollection, followRefResult.path, compareOpts ); if (!index?.supports(`gt`)) { index = void 0; } if (!index) { const collectionId = followRefCollection.id; const fieldPath = followRefResult.path.join(`.`); console.warn( `[TanStack DB]${collectionId ? ` [${collectionId}]` : ``} orderBy with limit requires an index on "${fieldPath}" for efficient lazy loading. Falling back to loading all data. Consider creating an index on the collection with collection.createIndex((row) => row.${fieldPath}) or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.` ); } orderByAlias = firstOrderByExpression.path.length > 1 ? String(firstOrderByExpression.path[0]) : rawQuery.from.alias; orderBySourceId ??= ir.collectCollectionSources(rawQuery).find( (source) => source.alias === orderByAlias && source.collection === followRefCollection )?.sourceId; } if (orderBySourceId && followRefResult) { const sourceOrderBy = resolveOrderBy( orderByClause, collection.compareOptions ); const sourceOrderIsDirect = orderByClause.every(({ expression }) => { if (expression.type !== `ref`) return false; return ir.followRef(rawQuery, expression, collection)?.sourceId === orderBySourceId; }); const extract = evaluators.compileExpression( new ir.PropRef(followRefResult.path), true ); const compareTerm = comparison.makeComparator(sourceOrderBy[0].compareOptions); const compareSourceRows = (a, b) => compareTerm(a ? extract(a) : a, b ? extract(b) : b); const info = { sourceId: orderBySourceId, alias: orderByAlias, offset: offset ?? 0, limit, comparator: compareSourceRows, valueExtractorForRawRow: extract, index, orderBy: sourceOrderBy, requiresFullSource: !sourceOrderIsDirect || rawQuery.from.type !== `collectionRef` || rawQuery.from.sourceId !== orderBySourceId || (rawQuery.join?.some( ({ type }) => type === `inner` || type === `right` ) ?? false) || (rawQuery.where?.some( (where) => ir.isResidualWhere(where) || [ ...expressions.getSourceAliasesFromExpression(ir.getWhereExpression(where)) ].some((alias) => alias !== orderByAlias) ) ?? false) || (rawQuery.fnWhere?.length ?? 0) > 0 || rawQuery.groupBy !== void 0 || rawQuery.having !== void 0 || rawQuery.fnHaving !== void 0 || rawQuery.distinct === true }; orderByOptimizationInfo = info; optimizableOrderByCollections[orderBySourceId] = info; if (index) { setSizeCallback = (getSize) => { optimizableOrderByCollections[orderBySourceId][`dataNeeded`] = () => { const size = getSize(); return Math.max(0, info.limit - size); }; }; } } } if (groupKeyFn) { return pipeline.pipe( dbIvm.groupedOrderByWithFractionalIndex(valueExtractor, { limit, offset, comparator: compare, setSizeCallback, groupKeyFn, setWindowFn: (windowFn) => { setWindowFn((options) => { windowFn(options); if (orderByOptimizationInfo) { orderByOptimizationInfo.offset = options.offset ?? orderByOptimizationInfo.offset; orderByOptimizationInfo.limit = options.limit ?? orderByOptimizationInfo.limit; } }); } }) ); } return pipeline.pipe( dbIvm.orderByWithFractionalIndex(valueExtractor, { limit, offset, comparator: compare, setSizeCallback, setWindowFn: (windowFn) => { setWindowFn( // We wrap the move function such that we update the orderByOptimizationInfo // because that is used by the `dataNeeded` callback to determine if we need to load more data (options) => { windowFn(options); if (orderByOptimizationInfo) { orderByOptimizationInfo.offset = options.offset ?? orderByOptimizationInfo.offset; orderByOptimizationInfo.limit = options.limit ?? orderByOptimizationInfo.limit; } } ); } }) // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format ); } function buildCompareOptions(clause, collection) { return resolveCompareOptions(clause, collection.compareOptions); } function resolveOrderBy(orderBy, defaults) { return orderBy.map((clause) => ({ expression: clause.expression, compareOptions: resolveCompareOptions(clause, defaults) })); } function resolveCompareOptions(clause, defaults) { return clause.compareOptions.stringSort === void 0 ? { ...defaults, direction: clause.compareOptions.direction, nulls: clause.compareOptions.nulls } : clause.compareOptions; } exports.buildCompareOptions = buildCompareOptions; exports.processOrderBy = processOrderBy; //# sourceMappingURL=order-by.cjs.map