UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

362 lines (361 loc) 10.5 kB
import { defaultComparator, normalizeValue } from "../utils/comparison.js"; import { findInsertPositionInArray, deleteInSortedArray } from "../utils/array-utils.js"; import { BaseIndex } from "./base-index.js"; class BasicIndex extends 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.sortedValues = []; this.indexedKeys = /* @__PURE__ */ new Set(); this.compareFn = defaultComparator; this.compareFn = options?.compareFn ?? defaultComparator; if (options?.compareOptions) { this.compareOptions = options.compareOptions; } } 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}`, { cause: error } ); } const normalizedValue = normalizeValue(indexedValue); if (this.valueMap.has(normalizedValue)) { this.valueMap.get(normalizedValue).add(key); } else { this.valueMap.set(normalizedValue, /* @__PURE__ */ new Set([key])); const insertIdx = findInsertPositionInArray( this.sortedValues, normalizedValue, this.compareFn ); this.sortedValues.splice(insertIdx, 0, normalizedValue); } this.indexedKeys.add(key); this.updateTimestamp(); } /** * 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 ); this.indexedKeys.delete(key); this.updateTimestamp(); return; } const normalizedValue = normalizeValue(indexedValue); if (this.valueMap.has(normalizedValue)) { const keySet = this.valueMap.get(normalizedValue); keySet.delete(key); if (keySet.size === 0) { this.valueMap.delete(normalizedValue); deleteInSortedArray(this.sortedValues, normalizedValue, this.compareFn); } } this.indexedKeys.delete(key); this.updateTimestamp(); } /** * Updates a value in the index */ update(key, oldItem, newItem) { this.remove(key, oldItem); this.add(key, newItem); } /** * Builds the index from a collection of entries */ build(entries) { this.clear(); const entriesArray = []; for (const [key, item] of entries) { let indexedValue; try { indexedValue = this.evaluateIndexExpression(item); } catch (error) { throw new Error( `Failed to evaluate index expression for key ${key}: ${error}`, { cause: error } ); } entriesArray.push({ key, value: normalizeValue(indexedValue) }); this.indexedKeys.add(key); } for (const { key, value } of entriesArray) { if (this.valueMap.has(value)) { this.valueMap.get(value).add(key); } else { this.valueMap.set(value, /* @__PURE__ */ new Set([key])); } } this.sortedValues = Array.from(this.valueMap.keys()).sort(this.compareFn); this.updateTimestamp(); } /** * Clears all data from the index */ clear() { this.valueMap.clear(); this.sortedValues = []; this.indexedKeys.clear(); this.updateTimestamp(); } /** * Performs a lookup operation */ lookup(operation, value) { const startTime = performance.now(); 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 BasicIndex`); } this.trackLookup(startTime); return result; } /** * Gets the number of indexed keys */ get keyCount() { return this.indexedKeys.size; } /** * Performs an equality lookup - O(1) */ equalityLookup(value) { const normalizedValue = normalizeValue(value); return this.valueMap.get(normalizedValue) ?? /* @__PURE__ */ new Set(); } /** * Performs a range query using binary search - O(log n + m) */ rangeQuery(options = {}) { const { from, to, fromInclusive = true, toInclusive = true } = options; const result = /* @__PURE__ */ new Set(); if (this.sortedValues.length === 0) { return result; } const normalizedFrom = normalizeValue(from); const normalizedTo = normalizeValue(to); let startIdx = 0; if (normalizedFrom !== void 0) { startIdx = findInsertPositionInArray( this.sortedValues, normalizedFrom, this.compareFn ); if (!fromInclusive && startIdx < this.sortedValues.length && this.compareFn(this.sortedValues[startIdx], normalizedFrom) === 0) { startIdx++; } } let endIdx = this.sortedValues.length; if (normalizedTo !== void 0) { endIdx = findInsertPositionInArray( this.sortedValues, normalizedTo, this.compareFn ); if (toInclusive && endIdx < this.sortedValues.length && this.compareFn(this.sortedValues[endIdx], normalizedTo) === 0) { endIdx++; } } for (let i = startIdx; i < endIdx; i++) { const keys = this.valueMap.get(this.sortedValues[i]); if (keys) { keys.forEach((key) => result.add(key)); } } return result; } /** * Performs a reversed range query */ rangeQueryReversed(options = {}) { const { from, to, fromInclusive = true, toInclusive = true } = options; const swappedFrom = to ?? (this.sortedValues.length > 0 ? this.sortedValues[this.sortedValues.length - 1] : void 0); const swappedTo = from ?? (this.sortedValues.length > 0 ? this.sortedValues[0] : void 0); return this.rangeQuery({ from: swappedFrom, to: swappedTo, fromInclusive: toInclusive, toInclusive: fromInclusive }); } /** * Returns the next n items in sorted order */ take(n, from, filterFn) { const result = []; let startIdx = 0; if (from !== void 0) { const normalizedFrom = normalizeValue(from); startIdx = findInsertPositionInArray( this.sortedValues, normalizedFrom, this.compareFn ); while (startIdx < this.sortedValues.length && this.compareFn(this.sortedValues[startIdx], normalizedFrom) <= 0) { startIdx++; } } for (let i = startIdx; i < this.sortedValues.length && result.length < n; i++) { const keys = this.valueMap.get(this.sortedValues[i]); if (keys) { for (const key of keys) { if (result.length >= n) break; if (!filterFn || filterFn(key)) { result.push(key); } } } } return result; } /** * Returns the next n items in reverse sorted order */ takeReversed(n, from, filterFn) { const result = []; let startIdx = this.sortedValues.length - 1; if (from !== void 0) { const normalizedFrom = normalizeValue(from); startIdx = findInsertPositionInArray( this.sortedValues, normalizedFrom, this.compareFn ) - 1; while (startIdx >= 0 && this.compareFn(this.sortedValues[startIdx], normalizedFrom) >= 0) { startIdx--; } } for (let i = startIdx; i >= 0 && result.length < n; i--) { const keys = this.valueMap.get(this.sortedValues[i]); if (keys) { for (const key of keys) { if (result.length >= n) break; if (!filterFn || filterFn(key)) { result.push(key); } } } } return result; } /** * Returns the first n items in sorted order (from the start) */ takeFromStart(n, filterFn) { const result = []; for (let i = 0; i < this.sortedValues.length && result.length < n; i++) { const keys = this.valueMap.get(this.sortedValues[i]); if (keys) { for (const key of keys) { if (result.length >= n) break; if (!filterFn || filterFn(key)) { result.push(key); } } } } return result; } /** * Returns the first n items in reverse sorted order (from the end) */ takeReversedFromEnd(n, filterFn) { const result = []; for (let i = this.sortedValues.length - 1; i >= 0 && result.length < n; i--) { const keys = this.valueMap.get(this.sortedValues[i]); if (keys) { for (const key of keys) { if (result.length >= n) break; if (!filterFn || filterFn(key)) { result.push(key); } } } } return result; } /** * Performs an IN array lookup - O(k) where k is values.length */ inArrayLookup(values) { const result = /* @__PURE__ */ new Set(); for (const value of values) { const normalizedValue = normalizeValue(value); const keys = this.valueMap.get(normalizedValue); if (keys) { keys.forEach((key) => result.add(key)); } } return result; } // Getter methods for testing/compatibility get indexedKeysSet() { return this.indexedKeys; } get orderedEntriesArray() { return this.sortedValues.map((value) => [ value, this.valueMap.get(value) ?? /* @__PURE__ */ new Set() ]); } get orderedEntriesArrayReversed() { const result = []; for (let i = this.sortedValues.length - 1; i >= 0; i--) { const value = this.sortedValues[i]; result.push([value, this.valueMap.get(value) ?? /* @__PURE__ */ new Set()]); } return result; } get valueMapData() { return this.valueMap; } } export { BasicIndex }; //# sourceMappingURL=basic-index.js.map