UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 15.6 kB
{"version":3,"file":"btree-index.cjs","sources":["../../../src/indexes/btree-index.ts"],"sourcesContent":["import { compareKeys } from '@tanstack/db-ivm'\nimport { BTree } from '../utils/btree.js'\nimport { defaultComparator, normalizeValue } from '../utils/comparison.js'\nimport { BaseIndex } from './base-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { IndexOperation } from './base-index.js'\n\n/**\n * Options for Ordered index\n */\nexport interface BTreeIndexOptions {\n compareFn?: (a: any, b: any) => number\n compareOptions?: CompareOptions\n}\n\n/**\n * Options for range queries\n */\nexport interface RangeQueryOptions {\n from?: any\n to?: any\n fromInclusive?: boolean\n toInclusive?: boolean\n}\n\n/**\n * B+Tree index for sorted data with range queries\n * This maintains items in sorted order and provides efficient range operations\n */\nexport class BTreeIndex<\n TKey extends string | number = string | number,\n> extends BaseIndex<TKey> {\n public readonly supportedOperations = new Set<IndexOperation>([\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n ])\n\n // Internal data structures - private to hide implementation details\n // The `orderedEntries` B+ tree is used for efficient range queries\n // The `valueMap` is used for O(1) lookups of PKs by indexed value\n private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)\n private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs\n private indexedKeys = new Set<TKey>()\n private compareFn: (a: any, b: any) => number = defaultComparator\n\n constructor(\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any,\n ) {\n super(id, expression, name, options)\n this.compareFn = options?.compareFn ?? defaultComparator\n if (options?.compareOptions) {\n this.compareOptions = options!.compareOptions\n }\n this.orderedEntries = new BTree(this.compareFn)\n }\n\n protected initialize(_options?: BTreeIndexOptions): void {}\n\n /**\n * Adds a value to the index\n */\n add(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n throw new Error(\n `Failed to evaluate index expression for key ${key}: ${error}`,\n )\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeValue(indexedValue)\n\n // Check if this value already exists\n if (this.valueMap.has(normalizedValue)) {\n // Add to existing set\n this.valueMap.get(normalizedValue)!.add(key)\n } else {\n // Create new set for this value\n const keySet = new Set<TKey>([key])\n this.valueMap.set(normalizedValue, keySet)\n this.orderedEntries.set(normalizedValue, undefined)\n }\n\n this.indexedKeys.add(key)\n this.updateTimestamp()\n }\n\n /**\n * Removes a value from the index\n */\n remove(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n console.warn(\n `Failed to evaluate index expression for key ${key} during removal:`,\n error,\n )\n return\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeValue(indexedValue)\n\n if (this.valueMap.has(normalizedValue)) {\n const keySet = this.valueMap.get(normalizedValue)!\n keySet.delete(key)\n\n // If set is now empty, remove the entry entirely\n if (keySet.size === 0) {\n this.valueMap.delete(normalizedValue)\n\n // Remove from ordered entries\n this.orderedEntries.delete(normalizedValue)\n }\n }\n\n this.indexedKeys.delete(key)\n this.updateTimestamp()\n }\n\n /**\n * Updates a value in the index\n */\n update(key: TKey, oldItem: any, newItem: any): void {\n this.remove(key, oldItem)\n this.add(key, newItem)\n }\n\n /**\n * Builds the index from a collection of entries\n */\n build(entries: Iterable<[TKey, any]>): void {\n this.clear()\n\n for (const [key, item] of entries) {\n this.add(key, item)\n }\n }\n\n /**\n * Clears all data from the index\n */\n clear(): void {\n this.orderedEntries.clear()\n this.valueMap.clear()\n this.indexedKeys.clear()\n this.updateTimestamp()\n }\n\n /**\n * Performs a lookup operation\n */\n lookup(operation: IndexOperation, value: any): Set<TKey> {\n const startTime = performance.now()\n\n let result: Set<TKey>\n\n switch (operation) {\n case `eq`:\n result = this.equalityLookup(value)\n break\n case `gt`:\n result = this.rangeQuery({ from: value, fromInclusive: false })\n break\n case `gte`:\n result = this.rangeQuery({ from: value, fromInclusive: true })\n break\n case `lt`:\n result = this.rangeQuery({ to: value, toInclusive: false })\n break\n case `lte`:\n result = this.rangeQuery({ to: value, toInclusive: true })\n break\n case `in`:\n result = this.inArrayLookup(value)\n break\n default:\n throw new Error(`Operation ${operation} not supported by BTreeIndex`)\n }\n\n this.trackLookup(startTime)\n return result\n }\n\n /**\n * Gets the number of indexed keys\n */\n get keyCount(): number {\n return this.indexedKeys.size\n }\n\n // Public methods for backward compatibility (used by tests)\n\n /**\n * Performs an equality lookup\n */\n equalityLookup(value: any): Set<TKey> {\n const normalizedValue = normalizeValue(value)\n return new Set(this.valueMap.get(normalizedValue) ?? [])\n }\n\n /**\n * Performs a range query with options\n * This is more efficient for compound queries like \"WHERE a > 5 AND a < 10\"\n */\n rangeQuery(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n const result = new Set<TKey>()\n\n const normalizedFrom = normalizeValue(from)\n const normalizedTo = normalizeValue(to)\n const fromKey = normalizedFrom ?? this.orderedEntries.minKey()\n const toKey = normalizedTo ?? this.orderedEntries.maxKey()\n\n this.orderedEntries.forRange(\n fromKey,\n toKey,\n toInclusive,\n (indexedValue, _) => {\n if (!fromInclusive && this.compareFn(indexedValue, from) === 0) {\n // the B+ tree `forRange` method does not support exclusive lower bounds\n // so we need to exclude it manually\n return\n }\n\n const keys = this.valueMap.get(indexedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n },\n )\n\n return result\n }\n\n /**\n * Performs a reversed range query\n */\n rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n return this.rangeQuery({\n from: to ?? this.orderedEntries.maxKey(),\n to: from ?? this.orderedEntries.minKey(),\n fromInclusive: toInclusive,\n toInclusive: fromInclusive,\n })\n }\n\n private takeInternal(\n n: number,\n nextPair: (k?: any) => [any, any] | undefined,\n from?: any,\n filterFn?: (key: TKey) => boolean,\n reversed: boolean = false,\n ): Array<TKey> {\n const keysInResult: Set<TKey> = new Set()\n const result: Array<TKey> = []\n let pair: [any, any] | undefined\n let key = normalizeValue(from)\n\n while ((pair = nextPair(key)) !== undefined && result.length < n) {\n key = pair[0]\n const keys = this.valueMap.get(key)\n if (keys && keys.size > 0) {\n // Sort keys for deterministic order, reverse if needed\n const sorted = Array.from(keys).sort(compareKeys)\n if (reversed) sorted.reverse()\n for (const ks of sorted) {\n if (result.length >= n) break\n if (!keysInResult.has(ks) && (filterFn?.(ks) ?? true)) {\n result.push(ks)\n keysInResult.add(ks)\n }\n }\n }\n }\n\n return result\n }\n\n /**\n * Returns the next n items after the provided item or the first n items if no from item is provided.\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive). Starts from the smallest item (inclusive) if not provided.\n * @returns The next n items after the provided key. Returns the first n items if no from item is provided.\n */\n take(n: number, from?: any, filterFn?: (key: TKey) => boolean): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)\n return this.takeInternal(n, nextPair, from, filterFn)\n }\n\n /**\n * Returns the next n items **before** the provided item (in descending order) or the last n items if no from item is provided.\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive). Starts from the largest item (inclusive) if not provided.\n * @returns The next n items **before** the provided key. Returns the last n items if no from item is provided.\n */\n takeReversed(\n n: number,\n from?: any,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)\n return this.takeInternal(n, nextPair, from, filterFn, true)\n }\n\n /**\n * Performs an IN array lookup\n */\n inArrayLookup(values: Array<any>): Set<TKey> {\n const result = new Set<TKey>()\n\n for (const value of values) {\n const normalizedValue = normalizeValue(value)\n const keys = this.valueMap.get(normalizedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n }\n\n return result\n }\n\n // Getter methods for testing compatibility\n get indexedKeysSet(): Set<TKey> {\n return this.indexedKeys\n }\n\n get orderedEntriesArray(): Array<[any, Set<TKey>]> {\n return this.orderedEntries\n .keysArray()\n .map((key) => [key, this.valueMap.get(key) ?? new Set()])\n }\n\n get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {\n return this.takeReversed(this.orderedEntries.size).map((key) => [\n key,\n this.valueMap.get(key) ?? new Set(),\n ])\n }\n\n get valueMapData(): Map<any, Set<TKey>> {\n return this.valueMap\n }\n}\n"],"names":["BaseIndex","defaultComparator","BTree","normalizeValue","compareKeys"],"mappings":"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"}