UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines • 11.6 kB
{"version":3,"file":"SortedMap.cjs","sources":["../../src/SortedMap.ts"],"sourcesContent":["import { compareKeys } from '@tanstack/db-ivm'\n\n/**\n * A Map implementation that keeps its entries sorted based on a comparator function\n * @template TKey - The type of keys in the map (must be string | number)\n * @template TValue - The type of values in the map\n */\nexport class SortedMap<TKey extends string | number, TValue> {\n private map: Map<TKey, TValue>\n private sortedKeys: Array<TKey>\n private comparator: ((a: TValue, b: TValue) => number) | undefined\n private orderDirty = false\n private runtimeKeyCount = 0\n\n /**\n * Creates a new SortedMap instance\n *\n * @param comparator - Optional function to compare values for sorting.\n * If not provided, entries are sorted by key only.\n */\n constructor(comparator?: (a: TValue, b: TValue) => number) {\n this.map = new Map<TKey, TValue>()\n this.sortedKeys = []\n this.comparator = comparator\n }\n\n /**\n * Finds the index where a key-value pair should be inserted to maintain sort order.\n * Uses binary search to find the correct position based on the value (if comparator provided),\n * with key-based tie-breaking for deterministic ordering when values compare as equal.\n * If no comparator is provided, sorts by key only.\n * Runs in O(log n) time.\n *\n * @param key - The key to find position for (used as tie-breaker or primary sort when no comparator)\n * @param value - The value to compare against (only used if comparator is provided)\n * @returns The index where the key should be inserted\n */\n private indexOf(key: TKey, value: TValue): number {\n let left = 0\n let right = this.sortedKeys.length\n\n // Fast path: no comparator means sort by key only\n if (!this.comparator) {\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const keyComparison = compareKeys(key, midKey)\n if (keyComparison < 0) {\n right = mid\n } else if (keyComparison > 0) {\n left = mid + 1\n } else {\n return mid\n }\n }\n return left\n }\n\n // With comparator: sort by value first, then key as tie-breaker\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const midValue = this.map.get(midKey)!\n const valueComparison = this.comparator(value, midValue)\n\n if (valueComparison < 0) {\n right = mid\n } else if (valueComparison > 0) {\n left = mid + 1\n } else {\n // Values are equal, use key as tie-breaker for deterministic ordering\n const keyComparison = compareKeys(key, midKey)\n if (keyComparison < 0) {\n right = mid\n } else if (keyComparison > 0) {\n left = mid + 1\n } else {\n // Same key (shouldn't happen during insert, but handle for lookups)\n return mid\n }\n }\n }\n\n return left\n }\n\n /** Restore the ordered view after a synchronous batch of deferred writes. */\n restoreOrder(): void {\n if (!this.orderDirty) return\n this.sortedKeys.length = 0\n for (const key of this.map.keys()) this.sortedKeys.push(key)\n this.sortedKeys.sort((left, right) => {\n const byValue =\n this.comparator?.(this.map.get(left)!, this.map.get(right)!) ?? 0\n return byValue || compareKeys(left, right)\n })\n this.orderDirty = false\n }\n\n /**\n * Sets a key-value pair in the map and maintains sort order\n *\n * @param key - The key to set\n * @param value - The value to associate with the key\n * @param deferOrder - Defer ordering until restoreOrder or the next ordered read\n * @returns This SortedMap instance for chaining\n */\n set(key: TKey, value: TValue, deferOrder = false): this {\n // Grouped Collections can produce nullish keys at runtime. compareKeys\n // is not a total order there, so retain the existing binary-insert path.\n const runtimeKey = typeof key !== `string` && typeof key !== `number`\n if (deferOrder && this.runtimeKeyCount === 0 && !runtimeKey) {\n this.map.set(key, value)\n this.orderDirty = true\n return this\n }\n this.restoreOrder()\n const exists = this.map.has(key)\n if (exists) {\n // Need to remove the old key from the sorted keys array\n const oldValue = this.map.get(key)!\n const oldIndex = this.indexOf(key, oldValue)\n this.sortedKeys.splice(oldIndex, 1)\n }\n\n // Insert the new key at the correct position\n const index = this.indexOf(key, value)\n this.sortedKeys.splice(index, 0, key)\n\n this.map.set(key, value)\n if (runtimeKey && !exists) this.runtimeKeyCount++\n\n return this\n }\n\n /**\n * Gets a value by its key\n *\n * @param key - The key to look up\n * @returns The value associated with the key, or undefined if not found\n */\n get(key: TKey): TValue | undefined {\n return this.map.get(key)\n }\n\n /**\n * Removes a key-value pair from the map\n *\n * @param key - The key to remove\n * @param deferOrder - Defer ordering until restoreOrder or the next ordered read\n * @returns True if the key was found and removed, false otherwise\n */\n delete(key: TKey, deferOrder = false): boolean {\n if (deferOrder && this.runtimeKeyCount === 0) {\n const deleted = this.map.delete(key)\n this.orderDirty ||= deleted\n return deleted\n }\n this.restoreOrder()\n if (this.map.has(key)) {\n const oldValue = this.map.get(key)\n const index = this.indexOf(key, oldValue!)\n this.sortedKeys.splice(index, 1)\n const deleted = this.map.delete(key)\n if (deleted && typeof key !== `string` && typeof key !== `number`)\n this.runtimeKeyCount--\n return deleted\n }\n\n return false\n }\n\n /**\n * Checks if a key exists in the map\n *\n * @param key - The key to check\n * @returns True if the key exists, false otherwise\n */\n has(key: TKey): boolean {\n return this.map.has(key)\n }\n\n /**\n * Removes all key-value pairs from the map\n */\n clear(): void {\n this.map.clear()\n this.sortedKeys = []\n this.orderDirty = false\n this.runtimeKeyCount = 0\n }\n\n /**\n * Gets the number of key-value pairs in the map\n */\n get size(): number {\n return this.map.size\n }\n\n /**\n * Default iterator that returns entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n *[Symbol.iterator](): IterableIterator<[TKey, TValue]> {\n this.restoreOrder()\n for (const key of this.sortedKeys) {\n yield [key, this.map.get(key)!] as [TKey, TValue]\n }\n }\n\n /**\n * Returns an iterator for the map's entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n entries(): IterableIterator<[TKey, TValue]> {\n return this[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's keys in sorted order\n *\n * @returns An iterator for the map's keys\n */\n keys(): IterableIterator<TKey> {\n this.restoreOrder()\n return this.sortedKeys[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's values in sorted order\n *\n * @returns An iterator for the map's values\n */\n values(): IterableIterator<TValue> {\n return function* (this: SortedMap<TKey, TValue>) {\n this.restoreOrder()\n for (const key of this.sortedKeys) {\n yield this.map.get(key)!\n }\n }.call(this)\n }\n\n /**\n * Executes a callback function for each key-value pair in the map in sorted order\n *\n * @param callbackfn - Function to execute for each entry\n */\n forEach(\n callbackfn: (value: TValue, key: TKey, map: Map<TKey, TValue>) => void,\n ): void {\n this.restoreOrder()\n for (const key of this.sortedKeys) {\n callbackfn(this.map.get(key)!, key, this.map)\n }\n }\n}\n"],"names":["compareKeys"],"mappings":"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"}