@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 4.08 kB
Source Map (JSON)
{"version":3,"file":"cursor.cjs","sources":["../../../src/utils/cursor.ts"],"sourcesContent":["import { and, eq, gt, lt, or } from '../query/builder/functions.js'\nimport { Value } from '../query/ir.js'\nimport type { BasicExpression, OrderBy } from '../query/ir.js'\n\n/**\n * Builds a cursor expression for paginating through ordered results.\n * For multi-column orderBy, creates a composite cursor that respects all columns.\n *\n * For [col1 ASC, col2 DESC] with values [v1, v2], produces:\n * or(\n * gt(col1, v1), // col1 > v1\n * and(eq(col1, v1), lt(col2, v2)) // col1 = v1 AND col2 < v2 (DESC)\n * )\n *\n * This creates a precise cursor that works with composite indexes on the backend.\n *\n * @param orderBy - The order-by clauses defining sort columns and directions\n * @param values - The cursor values corresponding to each order-by column\n * @returns A filter expression for rows after the cursor position, or undefined if empty\n */\nexport function buildCursor(\n orderBy: OrderBy,\n values: Array<unknown>,\n): BasicExpression<boolean> | undefined {\n if (values.length === 0 || orderBy.length === 0) {\n return undefined\n }\n\n // For single column, just use simple gt/lt\n if (orderBy.length === 1) {\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n return operator(expression, new Value(values[0]))\n }\n\n // For multi-column, build the composite cursor:\n // or(\n // gt(col1, v1),\n // and(eq(col1, v1), gt(col2, v2)),\n // and(eq(col1, v1), eq(col2, v2), gt(col3, v3)),\n // ...\n // )\n const clauses: Array<BasicExpression<boolean>> = []\n\n for (let i = 0; i < orderBy.length && i < values.length; i++) {\n const clause = orderBy[i]!\n const value = values[i]\n\n // Build equality conditions for all previous columns\n const eqConditions: Array<BasicExpression<boolean>> = []\n for (let j = 0; j < i; j++) {\n const prevClause = orderBy[j]!\n const prevValue = values[j]\n eqConditions.push(eq(prevClause.expression, new Value(prevValue)))\n }\n\n // Add the comparison for the current column (respecting direction)\n const operator = clause.compareOptions.direction === `asc` ? gt : lt\n const comparison = operator(clause.expression, new Value(value))\n\n if (eqConditions.length === 0) {\n // First column: just the comparison\n clauses.push(comparison)\n } else {\n // Subsequent columns: and(eq(prev...), comparison)\n // We need to spread into and() which expects at least 2 args\n const allConditions = [...eqConditions, comparison]\n clauses.push(allConditions.reduce((acc, cond) => and(acc, cond)))\n }\n }\n\n // Combine all clauses with OR\n if (clauses.length === 1) {\n return clauses[0]!\n }\n // Use reduce to combine with or() which expects exactly 2 args\n return clauses.reduce((acc, clause) => or(acc, clause))\n}\n"],"names":["gt","lt","Value","eq","and","or"],"mappings":";;;;AAoBO,SAAS,YACd,SACA,QACsC;AACtC,MAAI,OAAO,WAAW,KAAK,QAAQ,WAAW,GAAG;AAC/C,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,EAAE,YAAY,mBAAmB,QAAQ,CAAC;AAChD,UAAM,WAAW,eAAe,cAAc,QAAQA,UAAAA,KAAKC,UAAAA;AAC3D,WAAO,SAAS,YAAY,IAAIC,GAAAA,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,EAClD;AASA,QAAM,UAA2C,CAAA;AAEjD,WAAS,IAAI,GAAG,IAAI,QAAQ,UAAU,IAAI,OAAO,QAAQ,KAAK;AAC5D,UAAM,SAAS,QAAQ,CAAC;AACxB,UAAM,QAAQ,OAAO,CAAC;AAGtB,UAAM,eAAgD,CAAA;AACtD,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,aAAa,QAAQ,CAAC;AAC5B,YAAM,YAAY,OAAO,CAAC;AAC1B,mBAAa,KAAKC,UAAAA,GAAG,WAAW,YAAY,IAAID,GAAAA,MAAM,SAAS,CAAC,CAAC;AAAA,IACnE;AAGA,UAAM,WAAW,OAAO,eAAe,cAAc,QAAQF,UAAAA,KAAKC,UAAAA;AAClE,UAAM,aAAa,SAAS,OAAO,YAAY,IAAIC,GAAAA,MAAM,KAAK,CAAC;AAE/D,QAAI,aAAa,WAAW,GAAG;AAE7B,cAAQ,KAAK,UAAU;AAAA,IACzB,OAAO;AAGL,YAAM,gBAAgB,CAAC,GAAG,cAAc,UAAU;AAClD,cAAQ,KAAK,cAAc,OAAO,CAAC,KAAK,SAASE,cAAI,KAAK,IAAI,CAAC,CAAC;AAAA,IAClE;AAAA,EACF;AAGA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,QAAQ,CAAC;AAAA,EAClB;AAEA,SAAO,QAAQ,OAAO,CAAC,KAAK,WAAWC,aAAG,KAAK,MAAM,CAAC;AACxD;;"}