UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

113 lines (98 loc) 3.09 kB
import type { CompareOptions } from "../query/builder/types" // WeakMap to store stable IDs for objects const objectIds = new WeakMap<object, number>() let nextObjectId = 1 /** * Get or create a stable ID for an object */ function getObjectId(obj: object): number { if (objectIds.has(obj)) { return objectIds.get(obj)! } const id = nextObjectId++ objectIds.set(obj, id) return id } /** * Universal comparison function for all data types * Handles null/undefined, strings, arrays, dates, objects, and primitives * Always sorts null/undefined values first */ export const ascComparator = (a: any, b: any, opts: CompareOptions): number => { const { nulls } = opts // Handle null/undefined if (a == null && b == null) return 0 if (a == null) return nulls === `first` ? -1 : 1 if (b == null) return nulls === `first` ? 1 : -1 // if a and b are both strings, compare them based on locale if (typeof a === `string` && typeof b === `string`) { if (opts.stringSort === `locale`) { return a.localeCompare(b, opts.locale, opts.localeOptions) } // For lexical sort we rely on direct comparison for primitive values } // if a and b are both arrays, compare them element by element if (Array.isArray(a) && Array.isArray(b)) { for (let i = 0; i < Math.min(a.length, b.length); i++) { const result = ascComparator(a[i], b[i], opts) if (result !== 0) { return result } } // All elements are equal up to the minimum length return a.length - b.length } // If both are dates, compare them if (a instanceof Date && b instanceof Date) { return a.getTime() - b.getTime() } // If at least one of the values is an object, use stable IDs for comparison const aIsObject = typeof a === `object` const bIsObject = typeof b === `object` if (aIsObject || bIsObject) { // If both are objects, compare their stable IDs if (aIsObject && bIsObject) { const aId = getObjectId(a) const bId = getObjectId(b) return aId - bId } // If only one is an object, objects come after primitives if (aIsObject) return 1 if (bIsObject) return -1 } // For primitive values, use direct comparison if (a < b) return -1 if (a > b) return 1 return 0 } /** * Descending comparator function for ordering values * Handles null/undefined as largest values (opposite of ascending) */ export const descComparator = ( a: unknown, b: unknown, opts: CompareOptions ): number => { return ascComparator(b, a, { ...opts, nulls: opts.nulls === `first` ? `last` : `first`, }) } export function makeComparator( opts: CompareOptions ): (a: any, b: any) => number { return (a, b) => { if (opts.direction === `asc`) { return ascComparator(a, b, opts) } else { return descComparator(a, b, opts) } } } /** Default comparator orders values in ascending order with nulls first and locale string comparison. */ export const defaultComparator = makeComparator({ direction: `asc`, nulls: `first`, stringSort: `locale`, })