UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

114 lines (99 loc) • 3.82 kB
import { compareKeys } from '@tanstack/db-ivm' import type { IndexInterface, IndexOperation, IndexReader } from './base-index' import type { RangeQueryOptions } from './btree-index' export class ReverseIndex< TKey extends string | number, > implements IndexReader<TKey> { private originalIndex: IndexInterface<TKey> /** * @param nullsFirst - Whether nullish values come first in the reversed * order. The original index keeps them at the opposite end, so reversing * it alone would move them; ordered reads put them back. Omit it to read * the original index's plain reversed walk, as earlier releases did. */ constructor( index: IndexInterface<TKey>, private readonly nullsFirst?: boolean, ) { this.originalIndex = index } // Define the reversed operations lookup(operation: IndexOperation, value: any): Set<TKey> { const reverseOperation = operation === `gt` ? `lt` : operation === `gte` ? `lte` : operation === `lt` ? `gt` : operation === `lte` ? `gte` : operation return this.originalIndex.lookup(reverseOperation, value) } rangeQuery(options: RangeQueryOptions = {}): Set<TKey> { return this.originalIndex.rangeQueryReversed(options) } // Reversing the original index moves its nullish group to the opposite // end, so reads put the group back at the end the query asks for. Nullish // keys come in ascending key order; equal non-null values come in the // reversed walk's order. Each read gathers the nullish group, so its cost // grows with the number of nullish keys. take(n: number, from: any, filterFn?: (key: TKey) => boolean): Array<TKey> { if (this.nullsFirst === undefined) { return this.originalIndex.takeReversed(n, from, filterFn) } return this.read(n, filterFn, from ?? null) } takeFromStart(n: number, filterFn?: (key: TKey) => boolean): Array<TKey> { if (this.nullsFirst === undefined) { return this.originalIndex.takeReversedFromEnd(n, filterFn) } return this.read(n, filterFn) } /** Reads after `from` (`null` is the nullish group), or from the start. */ private read( n: number, filterFn?: (key: TKey) => boolean, from?: unknown, ): Array<TKey> { // Every index implements `equalityLookup`; an `eq` lookup need not be // advertised by an index that ordered reads accept. const nullish = new Set([ ...this.originalIndex.equalityLookup(null), ...this.originalIndex.equalityLookup(undefined), ]) const keep = (key: TKey) => filterFn?.(key) ?? true const accept = (key: TKey) => !nullish.has(key) && keep(key) const values = (count: number) => count <= 0 ? [] : from == null ? this.originalIndex.takeReversedFromEnd(count, accept) : this.originalIndex.takeReversed(count, from, accept) const nulls = (count: number) => count <= 0 ? [] : [...nullish].sort(compareKeys).filter(keep).slice(0, count) if (from === null) return this.nullsFirst ? values(n) : [] if (this.nullsFirst && from === undefined) { const head = nulls(n) return [...head, ...values(n - head.length)] } const keys = values(n) return this.nullsFirst ? keys : [...keys, ...nulls(n - keys.length)] } // All operations below delegate to the original index supports(operation: IndexOperation): boolean { return this.originalIndex.supports(operation) } get supportsRangeOptimization(): boolean { return this.originalIndex.supportsRangeOptimization } canOptimizeRangeFor(value: unknown): boolean { return this.originalIndex.canOptimizeRangeFor?.(value) ?? true } get keyCount(): number { return this.originalIndex.keyCount } }