UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

304 lines (303 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const dbIvm = require("@tanstack/db-ivm"); const arrayUtils = require("../utils/array-utils.cjs"); const btree = require("../utils/btree.cjs"); const comparison = require("../utils/comparison.cjs"); const baseIndex = require("./base-index.cjs"); class BTreeIndex extends baseIndex.BaseIndex { constructor(id, expression, name, options) { super(id, expression, name, options); this.supportedOperations = /* @__PURE__ */ new Set([ `eq`, `gt`, `gte`, `lt`, `lte`, `in` ]); this.valueMap = /* @__PURE__ */ new Map(); this.indexedKeys = /* @__PURE__ */ new Set(); this.compareFn = comparison.defaultComparator; if (options?.compareOptions) { this.compareOptions = options.compareOptions; } const baseCompareFn = options?.compareFn ?? comparison.makeComparator(this.compareOptions); this.hasCustomComparator = options?.compareFn != null; this.compareFn = (a, b) => baseCompareFn(comparison.denormalizeUndefined(a), comparison.denormalizeUndefined(b)); this.orderedEntries = new btree.BTree(this.compareFn); } initialize(_options) { } /** * Adds a value to the index */ add(key, item) { let indexedValue; try { indexedValue = this.evaluateIndexExpression(item); } catch (error) { throw new Error( `Failed to evaluate index expression for key ${key}: ${error}` ); } const normalizedValue = comparison.normalizeForBTree(indexedValue); this.addToBucket(key, normalizedValue); this.addRangeValue(indexedValue); this.indexedKeys.add(key); } addToBucket(key, normalizedValue) { const exact = this.valueMap.get(normalizedValue); if (exact) { exact.keys.add(key); exact.ordered.keys.add(key); return; } let orderedBucket = this.orderedEntries.get(normalizedValue); if (orderedBucket) { orderedBucket.keys.add(key); orderedBucket.exactValues.add(normalizedValue); } else { orderedBucket = { representative: normalizedValue, exactValues: /* @__PURE__ */ new Set([normalizedValue]), keys: /* @__PURE__ */ new Set([key]) }; this.orderedEntries.set(normalizedValue, orderedBucket); } this.valueMap.set(normalizedValue, { keys: /* @__PURE__ */ new Set([key]), ordered: orderedBucket }); } /** * Removes a value from the index */ remove(key, item) { let indexedValue; try { indexedValue = this.evaluateIndexExpression(item); } catch (error) { console.warn( `Failed to evaluate index expression for key ${key} during removal:`, error ); return; } const normalizedValue = comparison.normalizeForBTree(indexedValue); this.removeFromBucket(key, normalizedValue); this.removeRangeValue(indexedValue); this.indexedKeys.delete(key); } removeFromBucket(key, normalizedValue) { const exact = this.valueMap.get(normalizedValue); if (!exact || !exact.keys.delete(key)) return; const removedExactValue = exact.keys.size === 0; if (removedExactValue) this.valueMap.delete(normalizedValue); const orderedBucket = exact.ordered; orderedBucket.keys.delete(key); if (removedExactValue) orderedBucket.exactValues.delete(normalizedValue); if (orderedBucket.keys.size === 0) { this.orderedEntries.delete(normalizedValue); } else if (removedExactValue && comparison.areSameValueZeroEqual(orderedBucket.representative, normalizedValue)) { this.orderedEntries.delete(normalizedValue); const representative = orderedBucket.exactValues.values().next().value; orderedBucket.representative = representative; this.orderedEntries.set(representative, orderedBucket); } } /** * Updates a value in the index */ update(key, oldItem, newItem) { let oldIndexedValue; let newIndexedValue; try { oldIndexedValue = this.evaluateIndexExpression(oldItem); newIndexedValue = this.evaluateIndexExpression(newItem); } catch { this.remove(key, oldItem); this.add(key, newItem); return; } const oldValue = comparison.normalizeForBTree(oldIndexedValue); const newValue = comparison.normalizeForBTree(newIndexedValue); if (comparison.areSameValueZeroEqual(oldValue, newValue) && this.valueMap.get(newValue)?.keys.has(key)) { this.removeRangeValue(oldIndexedValue); this.addRangeValue(newIndexedValue); return; } this.removeFromBucket(key, oldValue); this.removeRangeValue(oldIndexedValue); this.addToBucket(key, newValue); this.addRangeValue(newIndexedValue); this.indexedKeys.add(key); } /** * Builds the index from a collection of entries */ build(entries) { this.clear(); for (const [key, item] of entries) { this.add(key, item); } } /** * Clears all data from the index */ clear() { this.orderedEntries.clear(); this.valueMap.clear(); this.indexedKeys.clear(); this.clearRangeValues(); } /** * Performs a lookup operation */ lookup(operation, value) { let result; switch (operation) { case `eq`: result = this.equalityLookup(value); break; case `gt`: result = this.rangeQuery({ from: value, fromInclusive: false }); break; case `gte`: result = this.rangeQuery({ from: value, fromInclusive: true }); break; case `lt`: result = this.rangeQuery({ to: value, toInclusive: false }); break; case `lte`: result = this.rangeQuery({ to: value, toInclusive: true }); break; case `in`: result = this.inArrayLookup(value); break; default: throw new Error(`Operation ${operation} not supported by BTreeIndex`); } return result; } /** * Gets the number of indexed keys */ get keyCount() { return this.indexedKeys.size; } // Public methods for backward compatibility (used by tests) /** * Performs an equality lookup */ equalityLookup(value) { const normalizedValue = comparison.normalizeForBTree(value); return new Set(this.valueMap.get(normalizedValue)?.keys ?? []); } /** * Performs a range query with options * This is more efficient for compound queries like "WHERE a > 5 AND a < 10" */ rangeQuery(options = {}) { const { from, to, fromInclusive = true, toInclusive = true } = options; const result = /* @__PURE__ */ new Set(); const hasFrom = `from` in options; const hasTo = `to` in options; const fromKey = hasFrom ? comparison.normalizeForBTree(from) : this.orderedEntries.minKey(); const toKey = hasTo ? comparison.normalizeForBTree(to) : this.orderedEntries.maxKey(); this.orderedEntries.forRange( fromKey, toKey, toInclusive, (indexedValue, bucket) => { if (hasFrom && !fromInclusive && this.compareFn(indexedValue, fromKey) === 0) { return; } bucket.keys.forEach((key) => result.add(key)); } ); return result; } /** * Internal method for taking items from the index. * @param n - The number of items to return * @param nextPair - Function to get the next pair from the BTree * @param from - Already normalized! undefined means "start from beginning/end", sentinel means "start from the key undefined" * @param filterFn - Optional filter function * @param reversed - Whether to reverse the order of keys within each value */ takeInternal(n, nextPair, from, filterFn, reversed = false) { const result = []; let pair; let key = from; while ((pair = nextPair(key)) !== void 0 && result.length < n) { key = pair[0]; const sorted = Array.from(pair[1].keys).sort( reversed ? arrayUtils.compareKeysReversed : dbIvm.compareKeys ); for (const ks of sorted) { if (result.length >= n) break; if (filterFn?.(ks) ?? true) result.push(ks); } } return result; } /** * Returns the next n items after the provided item. * @param n - The number of items to return * @param from - The item to start from (exclusive). * @returns The next n items after the provided key. */ take(n, from, filterFn) { const nextPair = (k) => this.orderedEntries.nextHigherPair(k); const normalizedFrom = comparison.normalizeForBTree(from); return this.takeInternal(n, nextPair, normalizedFrom, filterFn); } /** * Returns the first n items from the beginning. * @param n - The number of items to return * @param filterFn - Optional filter function * @returns The first n items */ takeFromStart(n, filterFn) { const nextPair = (k) => this.orderedEntries.nextHigherPair(k); return this.takeInternal(n, nextPair, void 0, filterFn); } /** * Returns the next n items **before** the provided item (in descending order). * @param n - The number of items to return * @param from - The item to start from (exclusive). Required. * @returns The next n items **before** the provided key. */ takeReversed(n, from, filterFn) { const nextPair = (k) => this.orderedEntries.nextLowerPair(k); const normalizedFrom = comparison.normalizeForBTree(from); return this.takeInternal(n, nextPair, normalizedFrom, filterFn, true); } /** * Returns the last n items from the end. * @param n - The number of items to return * @param filterFn - Optional filter function * @returns The last n items */ takeReversedFromEnd(n, filterFn) { const nextPair = (k) => this.orderedEntries.nextLowerPair(k); return this.takeInternal(n, nextPair, void 0, filterFn, true); } /** * Performs an IN array lookup */ inArrayLookup(values) { const result = /* @__PURE__ */ new Set(); for (const value of values) { const normalizedValue = comparison.normalizeForBTree(value); const keys = this.valueMap.get(normalizedValue)?.keys; if (keys) { keys.forEach((key) => result.add(key)); } } return result; } } exports.BTreeIndex = BTreeIndex; //# sourceMappingURL=btree-index.cjs.map