UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

212 lines (211 loc) • 6.39 kB
import { compareKeys } from "@tanstack/db-ivm"; class SortedMap { /** * Creates a new SortedMap instance * * @param comparator - Optional function to compare values for sorting. * If not provided, entries are sorted by key only. */ constructor(comparator) { this.bT = /* @__PURE__ */ new Set(); this.map = /* @__PURE__ */ new Map(); this.am = []; this.comparator = comparator; } /** * Finds the index where a key-value pair should be inserted to maintain sort order. * Uses binary search to find the correct position based on the value (if comparator provided), * with key-based tie-breaking for deterministic ordering when values compare as equal. * If no comparator is provided, sorts by key only. * Runs in O(log n) time. * * @param key - The key to find position for (used as tie-breaker or primary sort when no comparator) * @param value - The value to compare against (only used if comparator is provided) * @param keys - The ordered keys to search * @returns The index where the key should be inserted */ indexOf(key, value, keys = this.am) { let left = 0; let right = keys.length; if (!this.comparator) { while (left < right) { const mid = Math.floor((left + right) / 2); const midKey = keys[mid]; const keyComparison = compareKeys(key, midKey); if (keyComparison < 0) { right = mid; } else if (keyComparison > 0) { left = mid + 1; } else { return mid; } } return left; } while (left < right) { const mid = Math.floor((left + right) / 2); const midKey = keys[mid]; const midValue = this.map.get(midKey); const valueComparison = this.comparator(value, midValue); if (valueComparison < 0) { right = mid; } else if (valueComparison > 0) { left = mid + 1; } else { const keyComparison = compareKeys(key, midKey); if (keyComparison < 0) { right = mid; } else if (keyComparison > 0) { left = mid + 1; } else { return mid; } } } return left; } /** * Restore the ordered view after a synchronous batch of deferred writes. * Dirty keys leave their old slots by Set membership, so a deleted value or * one a sync source mutated in place cannot strand a slot. The dirty keys * still present are sorted and merged into the rest in one pass: * O(n + k log n) for k dirty keys. The merge rewrites sortedKeys in place, * so an open key iterator reads the restored order. */ restoreOrder() { if (this.bT.size === 0) return; const keys = this.am; const kept = keys.filter((key) => !this.bT.has(key)); const placed = [...this.bT].filter((key) => this.map.has(key)).sort( (left, right) => (this.comparator?.(this.map.get(left), this.map.get(right)) ?? 0) || compareKeys(left, right) ); keys.length = 0; let next = 0; for (const key of placed) { const index = this.indexOf(key, this.map.get(key), kept); while (next < index) keys.push(kept[next++]); keys.push(key); } while (next < kept.length) keys.push(kept[next++]); this.bT.clear(); } /** Removes a key's slot while its placed value is still mapped. */ cz(key) { const sameKey = (candidate) => candidate === key || candidate !== candidate && key !== key; let index = this.indexOf(key, this.map.get(key)); if (!sameKey(this.am[index])) { index = this.am.findIndex(sameKey); } this.am.splice(index, 1); } /** * Sets a key-value pair in the map and maintains sort order * * @param key - The key to set * @param value - The value to associate with the key * @param deferOrder - Defer ordering until restoreOrder or the next ordered read * @returns This SortedMap instance for chaining */ set(key, value, deferOrder = false) { if (!this.comparator && this.map.has(key)) { this.map.set(key, value); return this; } if (deferOrder) { this.map.set(key, value); this.bT.add(key); return this; } this.restoreOrder(); if (this.map.has(key)) this.cz(key); this.am.splice(this.indexOf(key, value), 0, key); this.map.set(key, value); return this; } /** * Gets a value by its key * * @param key - The key to look up * @returns The value associated with the key, or undefined if not found */ get(key) { return this.map.get(key); } /** * Removes a key-value pair from the map * * @param key - The key to remove * @param deferOrder - Defer ordering until restoreOrder or the next ordered read * @returns True if the key was found and removed, false otherwise */ delete(key, deferOrder = false) { if (deferOrder) { const deleted = this.map.delete(key); if (deleted) this.bT.add(key); return deleted; } this.restoreOrder(); if (this.map.has(key)) this.cz(key); return this.map.delete(key); } /** * Checks if a key exists in the map * * @param key - The key to check * @returns True if the key exists, false otherwise */ has(key) { return this.map.has(key); } /** * Removes all key-value pairs from the map */ clear() { this.map.clear(); this.am = []; this.bT.clear(); } /** * Gets the number of key-value pairs in the map */ get size() { return this.map.size; } /** * Default iterator that returns entries in sorted order * * @returns An iterator for the map's entries */ *[Symbol.iterator]() { for (const key of this.keys()) yield [key, this.map.get(key)]; } /** * Returns an iterator for the map's entries in sorted order * * @returns An iterator for the map's entries */ entries() { return this[Symbol.iterator](); } /** * Returns an iterator for the map's keys in sorted order * * @returns An iterator for the map's keys */ keys() { this.restoreOrder(); return this.am[Symbol.iterator](); } /** * Returns an iterator for the map's values in sorted order * * @returns An iterator for the map's values */ *values() { for (const key of this.keys()) yield this.map.get(key); } } export { SortedMap }; //# sourceMappingURL=SortedMap.js.map