@tanstack/db 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cjs/collection.cjs +30 -26
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +4 -4
  4. package/dist/cjs/index.cjs +2 -2
  5. package/dist/cjs/index.d.cts +1 -1
  6. package/dist/cjs/indexes/auto-index.cjs +2 -2
  7. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  8. package/dist/cjs/indexes/{ordered-index.cjs → btree-index.cjs} +27 -63
  9. package/dist/cjs/indexes/btree-index.cjs.map +1 -0
  10. package/dist/{esm/indexes/ordered-index.d.ts → cjs/indexes/btree-index.d.cts} +6 -4
  11. package/dist/cjs/utils/btree.cjs +677 -0
  12. package/dist/cjs/utils/btree.cjs.map +1 -0
  13. package/dist/cjs/utils/btree.d.cts +197 -0
  14. package/dist/esm/collection.d.ts +4 -4
  15. package/dist/esm/collection.js +30 -26
  16. package/dist/esm/collection.js.map +1 -1
  17. package/dist/esm/index.d.ts +1 -1
  18. package/dist/esm/index.js +2 -2
  19. package/dist/esm/indexes/auto-index.js +2 -2
  20. package/dist/esm/indexes/auto-index.js.map +1 -1
  21. package/dist/{cjs/indexes/ordered-index.d.cts → esm/indexes/btree-index.d.ts} +6 -4
  22. package/dist/esm/indexes/{ordered-index.js → btree-index.js} +27 -63
  23. package/dist/esm/indexes/btree-index.js.map +1 -0
  24. package/dist/esm/utils/btree.d.ts +197 -0
  25. package/dist/esm/utils/btree.js +677 -0
  26. package/dist/esm/utils/btree.js.map +1 -0
  27. package/package.json +1 -1
  28. package/src/collection.ts +45 -33
  29. package/src/index.ts +1 -1
  30. package/src/indexes/auto-index.ts +2 -2
  31. package/src/indexes/{ordered-index.ts → btree-index.ts} +42 -84
  32. package/src/utils/btree.ts +1010 -0
  33. package/dist/cjs/indexes/ordered-index.cjs.map +0 -1
  34. package/dist/cjs/utils/array-utils.cjs +0 -18
  35. package/dist/cjs/utils/array-utils.cjs.map +0 -1
  36. package/dist/esm/indexes/ordered-index.js.map +0 -1
  37. package/dist/esm/utils/array-utils.js +0 -18
  38. package/dist/esm/utils/array-utils.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"btree.js","sources":["../../../src/utils/btree.ts"],"sourcesContent":["// This file was copied from https://github.com/qwertie/btree-typescript/tree/master and adapted to our needs.\n// We removed methods that we don't need.\n\n// B+ tree by David Piepgrass. License: MIT\ntype EditRangeResult<V, R = number> = {\n value?: V\n break?: R\n delete?: boolean\n}\n\ntype index = number\n\n// Informative microbenchmarks & stuff:\n// http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation (very educational)\n// https://blog.mozilla.org/luke/2012/10/02/optimizing-javascript-variable-access/ (local vars are faster than properties)\n// http://benediktmeurer.de/2017/12/13/an-introduction-to-speculative-optimization-in-v8/ (other stuff)\n// https://jsperf.com/js-in-operator-vs-alternatives (avoid 'in' operator; `.p!==undefined` faster than `hasOwnProperty('p')` in all browsers)\n// https://jsperf.com/instanceof-vs-typeof-vs-constructor-vs-member (speed of type tests varies wildly across browsers)\n// https://jsperf.com/detecting-arrays-new (a.constructor===Array is best across browsers, assuming a is an object)\n// https://jsperf.com/shallow-cloning-methods (a constructor is faster than Object.create; hand-written clone faster than Object.assign)\n// https://jsperf.com/ways-to-fill-an-array (slice-and-replace is fastest)\n// https://jsperf.com/math-min-max-vs-ternary-vs-if (Math.min/max is slow on Edge)\n// https://jsperf.com/array-vs-property-access-speed (v.x/v.y is faster than a[0]/a[1] in major browsers IF hidden class is constant)\n// https://jsperf.com/detect-not-null-or-undefined (`x==null` slightly slower than `x===null||x===undefined` on all browsers)\n// Overall, microbenchmarks suggest Firefox is the fastest browser for JavaScript and Edge is the slowest.\n// Lessons from https://v8project.blogspot.com/2017/09/elements-kinds-in-v8.html:\n// - Avoid holes in arrays. Avoid `new Array(N)`, it will be \"holey\" permanently.\n// - Don't read outside bounds of an array (it scans prototype chain).\n// - Small integer arrays are stored differently from doubles\n// - Adding non-numbers to an array deoptimizes it permanently into a general array\n// - Objects can be used like arrays (e.g. have length property) but are slower\n// - V8 source (NewElementsCapacity in src/objects.h): arrays grow by 50% + 16 elements\n\n/**\n * A reasonably fast collection of key-value pairs with a powerful API.\n * Largely compatible with the standard Map. BTree is a B+ tree data structure,\n * so the collection is sorted by key.\n *\n * B+ trees tend to use memory more efficiently than hashtables such as the\n * standard Map, especially when the collection contains a large number of\n * items. However, maintaining the sort order makes them modestly slower:\n * O(log size) rather than O(1). This B+ tree implementation supports O(1)\n * fast cloning. It also supports freeze(), which can be used to ensure that\n * a BTree is not changed accidentally.\n *\n * Confusingly, the ES6 Map.forEach(c) method calls c(value,key) instead of\n * c(key,value), in contrast to other methods such as set() and entries()\n * which put the key first. I can only assume that the order was reversed on\n * the theory that users would usually want to examine values and ignore keys.\n * BTree's forEach() therefore works the same way, but a second method\n * `.forEachPair((key,value)=>{...})` is provided which sends you the key\n * first and the value second; this method is slightly faster because it is\n * the \"native\" for-each method for this class.\n *\n * Out of the box, BTree supports keys that are numbers, strings, arrays of\n * numbers/strings, Date, and objects that have a valueOf() method returning a\n * number or string. Other data types, such as arrays of Date or custom\n * objects, require a custom comparator, which you must pass as the second\n * argument to the constructor (the first argument is an optional list of\n * initial items). Symbols cannot be used as keys because they are unordered\n * (one Symbol is never \"greater\" or \"less\" than another).\n *\n * @example\n * Given a {name: string, age: number} object, you can create a tree sorted by\n * name and then by age like this:\n *\n * var tree = new BTree(undefined, (a, b) => {\n * if (a.name > b.name)\n * return 1; // Return a number >0 when a > b\n * else if (a.name < b.name)\n * return -1; // Return a number <0 when a < b\n * else // names are equal (or incomparable)\n * return a.age - b.age; // Return >0 when a.age > b.age\n * });\n *\n * tree.set({name:\"Bill\", age:17}, \"happy\");\n * tree.set({name:\"Fran\", age:40}, \"busy & stressed\");\n * tree.set({name:\"Bill\", age:55}, \"recently laid off\");\n * tree.forEachPair((k, v) => {\n * console.log(`Name: ${k.name} Age: ${k.age} Status: ${v}`);\n * });\n *\n * @description\n * The \"range\" methods (`forEach, forRange, editRange`) will return the number\n * of elements that were scanned. In addition, the callback can return {break:R}\n * to stop early and return R from the outer function.\n *\n * - TODO: Test performance of preallocating values array at max size\n * - TODO: Add fast initialization when a sorted array is provided to constructor\n *\n * For more documentation see https://github.com/qwertie/btree-typescript\n *\n * Are you a C# developer? You might like the similar data structures I made for C#:\n * BDictionary, BList, etc. See http://core.loyc.net/collections/\n *\n * @author David Piepgrass\n */\nexport class BTree<K = any, V = any> {\n private _root: BNode<K, V> = EmptyLeaf as BNode<K, V>\n _size = 0\n _maxNodeSize: number\n\n /**\n * provides a total order over keys (and a strict partial order over the type K)\n * @returns a negative value if a < b, 0 if a === b and a positive value if a > b\n */\n _compare: (a: K, b: K) => number\n\n /**\n * Initializes an empty B+ tree.\n * @param compare Custom function to compare pairs of elements in the tree.\n * If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.\n * @param entries A set of key-value pairs to initialize the tree\n * @param maxNodeSize Branching factor (maximum items or children per node)\n * Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.\n */\n public constructor(\n compare: (a: K, b: K) => number,\n entries?: Array<[K, V]>,\n maxNodeSize?: number\n ) {\n this._maxNodeSize = maxNodeSize! >= 4 ? Math.min(maxNodeSize!, 256) : 32\n this._compare = compare\n if (entries) this.setPairs(entries)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // ES6 Map<K,V> methods /////////////////////////////////////////////////////\n\n /** Gets the number of key-value pairs in the tree. */\n get size() {\n return this._size\n }\n /** Gets the number of key-value pairs in the tree. */\n get length() {\n return this._size\n }\n /** Returns true iff the tree contains no key-value pairs. */\n get isEmpty() {\n return this._size === 0\n }\n\n /** Releases the tree so that its size is 0. */\n clear() {\n this._root = EmptyLeaf as BNode<K, V>\n this._size = 0\n }\n\n /**\n * Finds a pair in the tree and returns the associated value.\n * @param defaultValue a value to return if the key was not found.\n * @returns the value, or defaultValue if the key was not found.\n * @description Computational complexity: O(log size)\n */\n get(key: K, defaultValue?: V): V | undefined {\n return this._root.get(key, defaultValue, this)\n }\n\n /**\n * Adds or overwrites a key-value pair in the B+ tree.\n * @param key the key is used to determine the sort order of\n * data in the tree.\n * @param value data to associate with the key (optional)\n * @param overwrite Whether to overwrite an existing key-value pair\n * (default: true). If this is false and there is an existing\n * key-value pair then this method has no effect.\n * @returns true if a new key-value pair was added.\n * @description Computational complexity: O(log size)\n * Note: when overwriting a previous entry, the key is updated\n * as well as the value. This has no effect unless the new key\n * has data that does not affect its sort order.\n */\n set(key: K, value: V, overwrite?: boolean): boolean {\n if (this._root.isShared) this._root = this._root.clone()\n const result = this._root.set(key, value, overwrite, this)\n if (result === true || result === false) return result\n // Root node has split, so create a new root node.\n this._root = new BNodeInternal<K, V>([this._root, result])\n return true\n }\n\n /**\n * Returns true if the key exists in the B+ tree, false if not.\n * Use get() for best performance; use has() if you need to\n * distinguish between \"undefined value\" and \"key not present\".\n * @param key Key to detect\n * @description Computational complexity: O(log size)\n */\n has(key: K): boolean {\n return this.forRange(key, key, true, undefined) !== 0\n }\n\n /**\n * Removes a single key-value pair from the B+ tree.\n * @param key Key to find\n * @returns true if a pair was found and removed, false otherwise.\n * @description Computational complexity: O(log size)\n */\n delete(key: K): boolean {\n return this.editRange(key, key, true, DeleteRange) !== 0\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Additional methods ///////////////////////////////////////////////////////\n\n /** Returns the maximum number of children/values before nodes will split. */\n get maxNodeSize() {\n return this._maxNodeSize\n }\n\n /** Gets the lowest key in the tree. Complexity: O(log size) */\n minKey(): K | undefined {\n return this._root.minKey()\n }\n\n /** Gets the highest key in the tree. Complexity: O(1) */\n maxKey(): K | undefined {\n return this._root.maxKey()\n }\n\n /** Gets an array of all keys, sorted */\n keysArray() {\n const results: Array<K> = []\n this._root.forRange(\n this.minKey()!,\n this.maxKey()!,\n true,\n false,\n this,\n 0,\n (k, _v) => {\n results.push(k)\n }\n )\n return results\n }\n\n /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the lowest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array on every iteration.\n */\n nextHigherPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.minPair(reusedArray)\n }\n return this._root.getPairOrNextHigher(\n key,\n this._compare,\n false,\n reusedArray\n )\n }\n\n /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).\n * If key === undefined, this function returns the highest pair.\n * @param key The key to search for.\n * @param reusedArray Optional array used repeatedly to store key-value pairs, to\n * avoid creating a new array each time you call this method.\n */\n nextLowerPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {\n reusedArray = reusedArray || ([] as unknown as [K, V])\n if (key === undefined) {\n return this._root.maxPair(reusedArray)\n }\n return this._root.getPairOrNextLower(key, this._compare, false, reusedArray)\n }\n\n /** Adds all pairs from a list of key-value pairs.\n * @param pairs Pairs to add to this tree. If there are duplicate keys,\n * later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]\n * associates 0 with 7.)\n * @param overwrite Whether to overwrite pairs that already exist (if false,\n * pairs[i] is ignored when the key pairs[i][0] already exists.)\n * @returns The number of pairs added to the collection.\n * @description Computational complexity: O(pairs.length * log(size + pairs.length))\n */\n setPairs(pairs: Array<[K, V]>, overwrite?: boolean): number {\n let added = 0\n for (const pair of pairs) {\n if (this.set(pair[0], pair[1], overwrite)) added++\n }\n return added\n }\n\n forRange(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => void,\n initialCounter?: number\n ): number\n\n /**\n * Scans the specified range of keys, in ascending order by key.\n * Note: the callback `onFound` must not insert or remove items in the\n * collection. Doing so may cause incorrect data to be sent to the\n * callback afterward.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return {break:R} to stop early with result R.\n * @param initialCounter Initial third argument of onFound. This value\n * increases by one each time `onFound` is called. Default: 0\n * @returns The number of values found, or R if the callback returned\n * `{break:R}` to stop early.\n * @description Computational complexity: O(number of items scanned + log size)\n */\n forRange<R = number>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound?: (k: K, v: V, counter: number) => { break?: R } | void,\n initialCounter?: number\n ): R | number {\n const r = this._root.forRange(\n low,\n high,\n includeHigh,\n false,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n }\n\n /**\n * Scans and potentially modifies values for a subsequence of keys.\n * Note: the callback `onFound` should ideally be a pure function.\n * Specfically, it must not insert items, call clone(), or change\n * the collection except via return value; out-of-band editing may\n * cause an exception or may cause incorrect data to be sent to\n * the callback (duplicate or missed items). It must not cause a\n * clone() of the collection, otherwise the clone could be modified\n * by changes requested by the callback.\n * @param low The first key scanned will be greater than or equal to `low`.\n * @param high Scanning stops when a key larger than this is reached.\n * @param includeHigh If the `high` key is present, `onFound` is called for\n * that final pair if and only if this parameter is true.\n * @param onFound A function that is called for each key-value pair. This\n * function can return `{value:v}` to change the value associated\n * with the current key, `{delete:true}` to delete the current pair,\n * `{break:R}` to stop early with result R, or it can return nothing\n * (undefined or {}) to cause no effect and continue iterating.\n * `{break:R}` can be combined with one of the other two commands.\n * The third argument `counter` is the number of items iterated\n * previously; it equals 0 when `onFound` is called the first time.\n * @returns The number of values scanned, or R if the callback returned\n * `{break:R}` to stop early.\n * @description\n * Computational complexity: O(number of items scanned + log size)\n * Note: if the tree has been cloned with clone(), any shared\n * nodes are copied before `onFound` is called. This takes O(n) time\n * where n is proportional to the amount of shared data scanned.\n */\n editRange<R = V>(\n low: K,\n high: K,\n includeHigh: boolean,\n onFound: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,\n initialCounter?: number\n ): R | number {\n let root = this._root\n if (root.isShared) this._root = root = root.clone()\n try {\n const r = root.forRange(\n low,\n high,\n includeHigh,\n true,\n this,\n initialCounter || 0,\n onFound\n )\n return typeof r === `number` ? r : r.break!\n } finally {\n let isShared\n while (root.keys.length <= 1 && !root.isLeaf) {\n isShared ||= root.isShared\n this._root = root =\n root.keys.length === 0\n ? EmptyLeaf\n : (root as any as BNodeInternal<K, V>).children[0]!\n }\n // If any ancestor of the new root was shared, the new root must also be shared\n if (isShared) {\n root.isShared = true\n }\n }\n }\n}\n\n/** Leaf node / base class. **************************************************/\nclass BNode<K, V> {\n // If this is an internal node, _keys[i] is the highest key in children[i].\n keys: Array<K>\n values: Array<V>\n // True if this node might be within multiple `BTree`s (or have multiple parents).\n // If so, it must be cloned before being mutated to avoid changing an unrelated tree.\n // This is transitive: if it's true, children are also shared even if `isShared!=true`\n // in those children. (Certain operations will propagate isShared=true to children.)\n isShared: true | undefined\n get isLeaf() {\n return (this as any).children === undefined\n }\n\n constructor(keys: Array<K> = [], values?: Array<V>) {\n this.keys = keys\n this.values = values || undefVals\n this.isShared = undefined\n }\n\n // /////////////////////////////////////////////////////////////////////////\n // Shared methods /////////////////////////////////////////////////////////\n\n maxKey() {\n return this.keys[this.keys.length - 1]\n }\n\n // If key not found, returns i^failXor where i is the insertion index.\n // Callers that don't care whether there was a match will set failXor=0.\n indexOf(key: K, failXor: number, cmp: (a: K, b: K) => number): index {\n const keys = this.keys\n let lo = 0,\n hi = keys.length,\n mid = hi >> 1\n while (lo < hi) {\n const c = cmp(keys[mid]!, key)\n if (c < 0) lo = mid + 1\n else if (c > 0)\n // key < keys[mid]\n hi = mid\n else if (c === 0) return mid\n else {\n // c is NaN or otherwise invalid\n if (key === key)\n // at least the search key is not NaN\n return keys.length\n else throw new Error(`BTree: NaN was used as a key`)\n }\n mid = (lo + hi) >> 1\n }\n return mid ^ failXor\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: misc //////////////////////////////////////////////////////////\n\n minKey(): K | undefined {\n return this.keys[0]\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n reusedArray[0] = this.keys[0]!\n reusedArray[1] = this.values[0]!\n return reusedArray\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n if (this.keys.length === 0) return undefined\n const lastIndex = this.keys.length - 1\n reusedArray[0] = this.keys[lastIndex]!\n reusedArray[1] = this.values[lastIndex]!\n return reusedArray\n }\n\n clone(): BNode<K, V> {\n const v = this.values\n return new BNode<K, V>(this.keys.slice(0), v === undefVals ? v : v.slice(0))\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, -1, tree._compare)\n return i < 0 ? defaultValue : this.values[i]\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1\n if (indexOrLower >= 0) {\n reusedArray[0] = this.keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, -1, compare)\n const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1\n const keys = this.keys\n if (indexOrLower < keys.length) {\n reusedArray[0] = keys[indexOrLower]!\n reusedArray[1] = this.values[indexOrLower]!\n return reusedArray\n }\n return undefined\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: set & node splitting //////////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNode<K, V> {\n let i = this.indexOf(key, -1, tree._compare)\n if (i < 0) {\n // key does not exist yet\n i = ~i\n tree._size++\n\n if (this.keys.length < tree._maxNodeSize) {\n return this.insertInLeaf(i, key, value, tree)\n } else {\n // This leaf node is full and must split\n const newRightSibling = this.splitOffRightSide()\n let target: BNode<K, V> = this\n if (i > this.keys.length) {\n i -= this.keys.length\n target = newRightSibling\n }\n target.insertInLeaf(i, key, value, tree)\n return newRightSibling\n }\n } else {\n // Key already exists\n if (overwrite !== false) {\n if (value !== undefined) this.reifyValues()\n // usually this is a no-op, but some users may wish to edit the key\n this.keys[i] = key\n this.values[i] = value\n }\n return false\n }\n }\n\n reifyValues() {\n if (this.values === undefVals)\n return (this.values = this.values.slice(0, this.keys.length))\n return this.values\n }\n\n insertInLeaf(i: index, key: K, value: V, tree: BTree<K, V>) {\n this.keys.splice(i, 0, key)\n if (this.values === undefVals) {\n while (undefVals.length < tree._maxNodeSize) undefVals.push(undefined)\n if (value === undefined) {\n return true\n } else {\n this.values = undefVals.slice(0, this.keys.length - 1)\n }\n }\n this.values.splice(i, 0, value)\n return true\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (rhs.values === undefVals) {\n if (v !== undefVals) v.push(undefined as any)\n } else {\n v = this.reifyValues()\n v.push(rhs.values.shift()!)\n }\n this.keys.push(rhs.keys.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n let v = this.values\n if (lhs.values === undefVals) {\n if (v !== undefVals) v.unshift(undefined as any)\n } else {\n v = this.reifyValues()\n v.unshift(lhs.values.pop()!)\n }\n this.keys.unshift(lhs.keys.pop()!)\n }\n\n splitOffRightSide(): BNode<K, V> {\n // Reminder: parent node must update its copy of key for this node\n const half = this.keys.length >> 1,\n keys = this.keys.splice(half)\n const values =\n this.values === undefVals ? undefVals : this.values.splice(half)\n return new BNode<K, V>(keys, values)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Leaf Node: scanning & deletions //////////////////////////////////////////\n\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n let iLow, iHigh\n if (high === low) {\n if (!includeHigh) return count\n iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1\n if (iLow < 0) return count\n } else {\n iLow = this.indexOf(low, 0, cmp)\n iHigh = this.indexOf(high, -1, cmp)\n if (iHigh < 0) iHigh = ~iHigh\n else if (includeHigh === true) iHigh++\n }\n const keys = this.keys,\n values = this.values\n if (onFound !== undefined) {\n for (let i = iLow; i < iHigh; i++) {\n const key = keys[i]!\n const result = onFound(key, values[i]!, count++)\n if (result !== undefined) {\n if (editMode === true) {\n if (key !== keys[i] || this.isShared === true)\n throw new Error(`BTree illegally changed or cloned in editRange`)\n if (result.delete) {\n this.keys.splice(i, 1)\n if (this.values !== undefVals) this.values.splice(i, 1)\n tree._size--\n i--\n iHigh--\n } else if (result.hasOwnProperty(`value`)) {\n values[i] = result.value!\n }\n }\n if (result.break !== undefined) return result\n }\n }\n } else count += iHigh - iLow\n return count\n }\n\n /** Adds entire contents of right-hand sibling (rhs is left unchanged) */\n mergeSibling(rhs: BNode<K, V>, _: number) {\n this.keys.push.apply(this.keys, rhs.keys)\n if (this.values === undefVals) {\n if (rhs.values === undefVals) return\n this.values = this.values.slice(0, this.keys.length)\n }\n this.values.push.apply(this.values, rhs.reifyValues())\n }\n}\n\n/** Internal node (non-leaf node) ********************************************/\nclass BNodeInternal<K, V> extends BNode<K, V> {\n // Note: conventionally B+ trees have one fewer key than the number of\n // children, but I find it easier to keep the array lengths equal: each\n // keys[i] caches the value of children[i].maxKey().\n children: Array<BNode<K, V>>\n\n /**\n * This does not mark `children` as shared, so it is the responsibility of the caller\n * to ensure children are either marked shared, or aren't included in another tree.\n */\n constructor(children: Array<BNode<K, V>>, keys?: Array<K>) {\n if (!keys) {\n keys = []\n for (let i = 0; i < children.length; i++) keys[i] = children[i]!.maxKey()!\n }\n super(keys)\n this.children = children\n }\n\n minKey() {\n return this.children[0]!.minKey()\n }\n\n minPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[0]!.minPair(reusedArray)\n }\n\n maxPair(reusedArray: [K, V]): [K, V] | undefined {\n return this.children[this.children.length - 1]!.maxPair(reusedArray)\n }\n\n get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {\n const i = this.indexOf(key, 0, tree._compare),\n children = this.children\n return i < children.length\n ? children[i]!.get(key, defaultValue, tree)\n : undefined\n }\n\n getPairOrNextLower(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children\n if (i >= children.length) return this.maxPair(reusedArray)\n const result = children[i]!.getPairOrNextLower(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i > 0) {\n return children[i - 1]!.maxPair(reusedArray)\n }\n return result\n }\n\n getPairOrNextHigher(\n key: K,\n compare: (a: K, b: K) => number,\n inclusive: boolean,\n reusedArray: [K, V]\n ): [K, V] | undefined {\n const i = this.indexOf(key, 0, compare),\n children = this.children,\n length = children.length\n if (i >= length) return undefined\n const result = children[i]!.getPairOrNextHigher(\n key,\n compare,\n inclusive,\n reusedArray\n )\n if (result === undefined && i < length - 1) {\n return children[i + 1]!.minPair(reusedArray)\n }\n return result\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: set & node splitting //////////////////////////////////////\n\n set(\n key: K,\n value: V,\n overwrite: boolean | undefined,\n tree: BTree<K, V>\n ): boolean | BNodeInternal<K, V> {\n const c = this.children,\n max = tree._maxNodeSize,\n cmp = tree._compare\n let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1),\n child = c[i]!\n\n if (child.isShared) c[i] = child = child.clone()\n if (child.keys.length >= max) {\n // child is full; inserting anything else will cause a split.\n // Shifting an item to the left or right sibling may avoid a split.\n // We can do a shift if the adjacent node is not full and if the\n // current key can still be placed in the same node after the shift.\n let other: BNode<K, V> | undefined\n if (\n i > 0 &&\n (other = c[i - 1]!).keys.length < max &&\n cmp(child.keys[0]!, key) < 0\n ) {\n if (other.isShared) c[i - 1] = other = other.clone()\n other.takeFromRight(child)\n this.keys[i - 1] = other.maxKey()!\n } else if (\n (other = c[i + 1]) !== undefined &&\n other.keys.length < max &&\n cmp(child.maxKey()!, key) < 0\n ) {\n if (other.isShared) c[i + 1] = other = other.clone()\n other.takeFromLeft(child)\n this.keys[i] = c[i]!.maxKey()!\n }\n }\n\n const result = child.set(key, value, overwrite, tree)\n if (result === false) return false\n this.keys[i] = child.maxKey()!\n if (result === true) return true\n\n // The child has split and `result` is a new right child... does it fit?\n if (this.keys.length < max) {\n // yes\n this.insert(i + 1, result)\n return true\n } else {\n // no, we must split also\n const newRightSibling = this.splitOffRightSide()\n let target: BNodeInternal<K, V> = this\n if (cmp(result.maxKey()!, this.maxKey()!) > 0) {\n target = newRightSibling\n i -= this.keys.length\n }\n target.insert(i + 1, result)\n return newRightSibling\n }\n }\n\n /**\n * Inserts `child` at index `i`.\n * This does not mark `child` as shared, so it is the responsibility of the caller\n * to ensure that either child is marked shared, or it is not included in another tree.\n */\n insert(i: index, child: BNode<K, V>) {\n this.children.splice(i, 0, child)\n this.keys.splice(i, 0, child.maxKey()!)\n }\n\n /**\n * Split this node.\n * Modifies this to remove the second half of the items, returning a separate node containing them.\n */\n splitOffRightSide() {\n // assert !this.isShared;\n const half = this.children.length >> 1\n return new BNodeInternal<K, V>(\n this.children.splice(half),\n this.keys.splice(half)\n )\n }\n\n takeFromRight(rhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.push(rhs.keys.shift()!)\n this.children.push((rhs as BNodeInternal<K, V>).children.shift()!)\n }\n\n takeFromLeft(lhs: BNode<K, V>) {\n // Reminder: parent node must update its copy of key for this node\n // assert: neither node is shared\n // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)\n this.keys.unshift(lhs.keys.pop()!)\n this.children.unshift((lhs as BNodeInternal<K, V>).children.pop()!)\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // Internal Node: scanning & deletions //////////////////////////////////////\n\n // Note: `count` is the next value of the third argument to `onFound`.\n // A leaf node's `forRange` function returns a new value for this counter,\n // unless the operation is to stop early.\n forRange<R>(\n low: K,\n high: K,\n includeHigh: boolean | undefined,\n editMode: boolean,\n tree: BTree<K, V>,\n count: number,\n onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void\n ): EditRangeResult<V, R> | number {\n const cmp = tree._compare\n const keys = this.keys,\n children = this.children\n let iLow = this.indexOf(low, 0, cmp),\n i = iLow\n const iHigh = Math.min(\n high === low ? iLow : this.indexOf(high, 0, cmp),\n keys.length - 1\n )\n if (!editMode) {\n // Simple case\n for (; i <= iHigh; i++) {\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n if (typeof result !== `number`) return result\n count = result\n }\n } else if (i <= iHigh) {\n try {\n for (; i <= iHigh; i++) {\n if (children[i]!.isShared) children[i] = children[i]!.clone()\n const result = children[i]!.forRange(\n low,\n high,\n includeHigh,\n editMode,\n tree,\n count,\n onFound\n )\n // Note: if children[i] is empty then keys[i]=undefined.\n // This is an invalid state, but it is fixed below.\n keys[i] = children[i]!.maxKey()!\n if (typeof result !== `number`) return result\n count = result\n }\n } finally {\n // Deletions may have occurred, so look for opportunities to merge nodes.\n const half = tree._maxNodeSize >> 1\n if (iLow > 0) iLow--\n for (i = iHigh; i >= iLow; i--) {\n if (children[i]!.keys.length <= half) {\n if (children[i]!.keys.length !== 0) {\n this.tryMerge(i, tree._maxNodeSize)\n } else {\n // child is empty! delete it!\n keys.splice(i, 1)\n children.splice(i, 1)\n }\n }\n }\n if (children.length !== 0 && children[0]!.keys.length === 0)\n check(false, `emptiness bug`)\n }\n }\n return count\n }\n\n /** Merges child i with child i+1 if their combined size is not too large */\n tryMerge(i: index, maxSize: number): boolean {\n const children = this.children\n if (i >= 0 && i + 1 < children.length) {\n if (children[i]!.keys.length + children[i + 1]!.keys.length <= maxSize) {\n if (children[i]!.isShared)\n // cloned already UNLESS i is outside scan range\n children[i] = children[i]!.clone()\n children[i]!.mergeSibling(children[i + 1]!, maxSize)\n children.splice(i + 1, 1)\n this.keys.splice(i + 1, 1)\n this.keys[i] = children[i]!.maxKey()!\n return true\n }\n }\n return false\n }\n\n /**\n * Move children from `rhs` into this.\n * `rhs` must be part of this tree, and be removed from it after this call\n * (otherwise isShared for its children could be incorrect).\n */\n mergeSibling(rhs: BNode<K, V>, maxNodeSize: number) {\n // assert !this.isShared;\n const oldLength = this.keys.length\n this.keys.push.apply(this.keys, rhs.keys)\n const rhsChildren = (rhs as any as BNodeInternal<K, V>).children\n this.children.push.apply(this.children, rhsChildren)\n\n if (rhs.isShared && !this.isShared) {\n // All children of a shared node are implicitly shared, and since their new\n // parent is not shared, they must now be explicitly marked as shared.\n for (const child of rhsChildren) child.isShared = true\n }\n\n // If our children are themselves almost empty due to a mass-delete,\n // they may need to be merged too (but only the oldLength-1 and its\n // right sibling should need this).\n this.tryMerge(oldLength - 1, maxNodeSize)\n }\n}\n\n// Optimization: this array of `undefined`s is used instead of a normal\n// array of values in nodes where `undefined` is the only value.\n// Its length is extended to max node size on first use; since it can\n// be shared between trees with different maximums, its length can only\n// increase, never decrease. Its type should be undefined[] but strangely\n// TypeScript won't allow the comparison V[] === undefined[]. To prevent\n// users from making this array too large, BTree has a maximum node size.\n//\n// FAQ: undefVals[i] is already undefined, so why increase the array size?\n// Reading outside the bounds of an array is relatively slow because it\n// has the side effect of scanning the prototype chain.\nconst undefVals: Array<any> = []\n\nconst Delete = { delete: true },\n DeleteRange = () => Delete\nconst EmptyLeaf = (function () {\n const n = new BNode<any, any>()\n n.isShared = true\n return n\n})()\n\nfunction check(fact: boolean, ...args: Array<any>) {\n if (!fact) {\n args.unshift(`B+ tree`) // at beginning of message\n throw new Error(args.join(` `))\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.0.29",
4
+ "version": "0.0.31",
5
5
  "dependencies": {
6
6
  "@electric-sql/d2mini": "^0.1.7",
7
7
  "@standard-schema/spec": "^1.0.0"
package/src/collection.ts CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createSingleRowRefProxy,
5
5
  toExpression,
6
6
  } from "./query/builder/ref-proxy"
7
- import { OrderedIndex } from "./indexes/ordered-index.js"
7
+ import { BTreeIndex } from "./indexes/btree-index.js"
8
8
  import { IndexProxy, LazyIndexWrapper } from "./indexes/lazy-index.js"
9
9
  import { ensureIndexForExpression } from "./indexes/auto-index.js"
10
10
  import { createTransaction, getActiveTransaction } from "./transactions"
@@ -687,7 +687,9 @@ export class CollectionImpl<
687
687
  /**
688
688
  * Recompute optimistic state from active transactions
689
689
  */
690
- private recomputeOptimisticState(): void {
690
+ private recomputeOptimisticState(
691
+ triggeredByUserAction: boolean = false
692
+ ): void {
691
693
  // Skip redundant recalculations when we're in the middle of committing sync transactions
692
694
  if (this.isCommittingSyncTransactions) {
693
695
  return
@@ -738,13 +740,26 @@ export class CollectionImpl<
738
740
  this.collectOptimisticChanges(previousState, previousDeletes, events)
739
741
 
740
742
  // Filter out events for recently synced keys to prevent duplicates
741
- const filteredEventsBySyncStatus = events.filter(
742
- (event) => !this.recentlySyncedKeys.has(event.key)
743
- )
743
+ // BUT: Only filter out events that are actually from sync operations
744
+ // New user transactions should NOT be filtered even if the key was recently synced
745
+ const filteredEventsBySyncStatus = events.filter((event) => {
746
+ if (!this.recentlySyncedKeys.has(event.key)) {
747
+ return true // Key not recently synced, allow event through
748
+ }
749
+
750
+ // Key was recently synced - allow if this is a user-triggered action
751
+ if (triggeredByUserAction) {
752
+ return true
753
+ }
754
+
755
+ // Otherwise filter out duplicate sync events
756
+ return false
757
+ })
744
758
 
745
759
  // Filter out redundant delete events if there are pending sync transactions
746
760
  // that will immediately restore the same data, but only for completed transactions
747
- if (this.pendingSyncedTransactions.length > 0) {
761
+ // IMPORTANT: Skip complex filtering for user-triggered actions to prevent UI blocking
762
+ if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
748
763
  const pendingSyncKeys = new Set<TKey>()
749
764
  const completedTransactionMutations = new Set<string>()
750
765
 
@@ -788,14 +803,14 @@ export class CollectionImpl<
788
803
  if (filteredEvents.length > 0) {
789
804
  this.updateIndexes(filteredEvents)
790
805
  }
791
- this.emitEvents(filteredEvents)
806
+ this.emitEvents(filteredEvents, triggeredByUserAction)
792
807
  } else {
793
808
  // Update indexes for all events
794
809
  if (filteredEventsBySyncStatus.length > 0) {
795
810
  this.updateIndexes(filteredEventsBySyncStatus)
796
811
  }
797
812
  // Emit all events if no pending sync transactions
798
- this.emitEvents(filteredEventsBySyncStatus)
813
+ this.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction)
799
814
  }
800
815
  }
801
816
 
@@ -878,22 +893,21 @@ export class CollectionImpl<
878
893
  */
879
894
  private emitEvents(
880
895
  changes: Array<ChangeMessage<T, TKey>>,
881
- endBatching = false
896
+ forceEmit = false
882
897
  ): void {
883
- if (this.shouldBatchEvents && !endBatching) {
898
+ // Skip batching for user actions (forceEmit=true) to keep UI responsive
899
+ if (this.shouldBatchEvents && !forceEmit) {
884
900
  // Add events to the batch
885
901
  this.batchedEvents.push(...changes)
886
902
  return
887
903
  }
888
904
 
889
- // Either we're not batching, or we're ending the batching cycle
905
+ // Either we're not batching, or we're forcing emission (user action or ending batch cycle)
890
906
  let eventsToEmit = changes
891
907
 
892
- if (endBatching) {
893
- // End batching: combine any batched events with new events and clean up state
894
- if (this.batchedEvents.length > 0) {
895
- eventsToEmit = [...this.batchedEvents, ...changes]
896
- }
908
+ // If we have batched events and this is a forced emit, combine them
909
+ if (this.batchedEvents.length > 0 && forceEmit) {
910
+ eventsToEmit = [...this.batchedEvents, ...changes]
897
911
  this.batchedEvents = []
898
912
  this.shouldBatchEvents = false
899
913
  }
@@ -1297,12 +1311,12 @@ export class CollectionImpl<
1297
1311
  * @returns An index proxy that provides access to the index when ready
1298
1312
  *
1299
1313
  * @example
1300
- * // Create a default ordered index
1314
+ * // Create a default B+ tree index
1301
1315
  * const ageIndex = collection.createIndex((row) => row.age)
1302
1316
  *
1303
1317
  * // Create a ordered index with custom options
1304
1318
  * const ageIndex = collection.createIndex((row) => row.age, {
1305
- * indexType: OrderedIndex,
1319
+ * indexType: BTreeIndex,
1306
1320
  * options: { compareFn: customComparator },
1307
1321
  * name: 'age_btree'
1308
1322
  * })
@@ -1316,9 +1330,7 @@ export class CollectionImpl<
1316
1330
  * options: { language: 'en' }
1317
1331
  * })
1318
1332
  */
1319
- public createIndex<
1320
- TResolver extends IndexResolver<TKey> = typeof OrderedIndex,
1321
- >(
1333
+ public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(
1322
1334
  indexCallback: (row: SingleRowRefProxy<T>) => any,
1323
1335
  config: IndexOptions<TResolver> = {}
1324
1336
  ): IndexProxy<TKey> {
@@ -1329,8 +1341,8 @@ export class CollectionImpl<
1329
1341
  const indexExpression = indexCallback(singleRowRefProxy)
1330
1342
  const expression = toExpression(indexExpression)
1331
1343
 
1332
- // Default to OrderedIndex if no type specified
1333
- const resolver = config.indexType ?? (OrderedIndex as unknown as TResolver)
1344
+ // Default to BTreeIndex if no type specified
1345
+ const resolver = config.indexType ?? (BTreeIndex as unknown as TResolver)
1334
1346
 
1335
1347
  // Create lazy wrapper
1336
1348
  const lazyIndex = new LazyIndexWrapper<TKey>(
@@ -1344,13 +1356,13 @@ export class CollectionImpl<
1344
1356
 
1345
1357
  this.lazyIndexes.set(indexId, lazyIndex)
1346
1358
 
1347
- // For OrderedIndex, resolve immediately and synchronously
1348
- if ((resolver as unknown) === OrderedIndex) {
1359
+ // For BTreeIndex, resolve immediately and synchronously
1360
+ if ((resolver as unknown) === BTreeIndex) {
1349
1361
  try {
1350
1362
  const resolvedIndex = lazyIndex.getResolved()
1351
1363
  this.resolvedIndexes.set(indexId, resolvedIndex)
1352
1364
  } catch (error) {
1353
- console.warn(`Failed to resolve OrderedIndex:`, error)
1365
+ console.warn(`Failed to resolve BTreeIndex:`, error)
1354
1366
  }
1355
1367
  } else if (typeof resolver === `function` && resolver.prototype) {
1356
1368
  // Other synchronous constructors - resolve immediately
@@ -1627,7 +1639,7 @@ export class CollectionImpl<
1627
1639
  ambientTransaction.applyMutations(mutations)
1628
1640
 
1629
1641
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1630
- this.recomputeOptimisticState()
1642
+ this.recomputeOptimisticState(true)
1631
1643
 
1632
1644
  return ambientTransaction
1633
1645
  } else {
@@ -1652,7 +1664,7 @@ export class CollectionImpl<
1652
1664
 
1653
1665
  // Add the transaction to the collection's transactions store
1654
1666
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1655
- this.recomputeOptimisticState()
1667
+ this.recomputeOptimisticState(true)
1656
1668
 
1657
1669
  return directOpTransaction
1658
1670
  }
@@ -1849,7 +1861,7 @@ export class CollectionImpl<
1849
1861
  ambientTransaction.applyMutations(mutations)
1850
1862
 
1851
1863
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1852
- this.recomputeOptimisticState()
1864
+ this.recomputeOptimisticState(true)
1853
1865
 
1854
1866
  return ambientTransaction
1855
1867
  }
@@ -1878,7 +1890,7 @@ export class CollectionImpl<
1878
1890
  // Add the transaction to the collection's transactions store
1879
1891
 
1880
1892
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1881
- this.recomputeOptimisticState()
1893
+ this.recomputeOptimisticState(true)
1882
1894
 
1883
1895
  return directOpTransaction
1884
1896
  }
@@ -1965,7 +1977,7 @@ export class CollectionImpl<
1965
1977
  ambientTransaction.applyMutations(mutations)
1966
1978
 
1967
1979
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1968
- this.recomputeOptimisticState()
1980
+ this.recomputeOptimisticState(true)
1969
1981
 
1970
1982
  return ambientTransaction
1971
1983
  }
@@ -1991,7 +2003,7 @@ export class CollectionImpl<
1991
2003
  directOpTransaction.commit()
1992
2004
 
1993
2005
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1994
- this.recomputeOptimisticState()
2006
+ this.recomputeOptimisticState(true)
1995
2007
 
1996
2008
  return directOpTransaction
1997
2009
  }
@@ -2253,6 +2265,6 @@ export class CollectionImpl<
2253
2265
  // CRITICAL: Capture visible state BEFORE clearing optimistic state
2254
2266
  this.capturePreSyncVisibleState()
2255
2267
 
2256
- this.recomputeOptimisticState()
2268
+ this.recomputeOptimisticState(false)
2257
2269
  }
2258
2270
  }
package/src/index.ts CHANGED
@@ -12,7 +12,7 @@ export * from "./errors"
12
12
 
13
13
  // Index system exports
14
14
  export * from "./indexes/base-index.js"
15
- export * from "./indexes/ordered-index.js"
15
+ export * from "./indexes/btree-index.js"
16
16
  export * from "./indexes/lazy-index.js"
17
17
  export { type IndexOptions } from "./indexes/index-options.js"
18
18
 
@@ -1,4 +1,4 @@
1
- import { OrderedIndex } from "./ordered-index"
1
+ import { BTreeIndex } from "./btree-index"
2
2
  import type { BasicExpression } from "../query/ir"
3
3
  import type { CollectionImpl } from "../collection"
4
4
 
@@ -46,7 +46,7 @@ export function ensureIndexForExpression<
46
46
  try {
47
47
  collection.createIndex((row) => (row as any)[fieldName], {
48
48
  name: `auto_${fieldName}`,
49
- indexType: OrderedIndex,
49
+ indexType: BTreeIndex,
50
50
  })
51
51
  } catch (error) {
52
52
  console.warn(
@@ -1,12 +1,13 @@
1
1
  import { ascComparator } from "../utils/comparison.js"
2
- import { findInsertPosition } from "../utils/array-utils.js"
2
+ import { BTree } from "../utils/btree.js"
3
3
  import { BaseIndex } from "./base-index.js"
4
+ import type { BasicExpression } from "../query/ir.js"
4
5
  import type { IndexOperation } from "./base-index.js"
5
6
 
6
7
  /**
7
8
  * Options for Ordered index
8
9
  */
9
- export interface OrderedIndexOptions {
10
+ export interface BTreeIndexOptions {
10
11
  compareFn?: (a: any, b: any) => number
11
12
  }
12
13
 
@@ -21,10 +22,10 @@ export interface RangeQueryOptions {
21
22
  }
22
23
 
23
24
  /**
24
- * Ordered index for sorted data with range queries
25
+ * B+Tree index for sorted data with range queries
25
26
  * This maintains items in sorted order and provides efficient range operations
26
27
  */
27
- export class OrderedIndex<
28
+ export class BTreeIndex<
28
29
  TKey extends string | number = string | number,
29
30
  > extends BaseIndex<TKey> {
30
31
  public readonly supportedOperations = new Set<IndexOperation>([
@@ -37,15 +38,26 @@ export class OrderedIndex<
37
38
  ])
38
39
 
39
40
  // Internal data structures - private to hide implementation details
40
- private orderedEntries: Array<[any, Set<TKey>]> = []
41
- private valueMap = new Map<any, Set<TKey>>()
41
+ // The `orderedEntries` B+ tree is used for efficient range queries
42
+ // The `valueMap` is used for O(1) lookups of PKs by indexed value
43
+ private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)
44
+ private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs
42
45
  private indexedKeys = new Set<TKey>()
43
46
  private compareFn: (a: any, b: any) => number = ascComparator
44
47
 
45
- protected initialize(options?: OrderedIndexOptions): void {
48
+ constructor(
49
+ id: number,
50
+ expression: BasicExpression,
51
+ name?: string,
52
+ options?: any
53
+ ) {
54
+ super(id, expression, name, options)
46
55
  this.compareFn = options?.compareFn ?? ascComparator
56
+ this.orderedEntries = new BTree(this.compareFn)
47
57
  }
48
58
 
59
+ protected initialize(_options?: BTreeIndexOptions): void {}
60
+
49
61
  /**
50
62
  * Adds a value to the index
51
63
  */
@@ -67,14 +79,7 @@ export class OrderedIndex<
67
79
  // Create new set for this value
68
80
  const keySet = new Set<TKey>([key])
69
81
  this.valueMap.set(indexedValue, keySet)
70
-
71
- // Find correct position in ordered entries using binary search
72
- const insertIndex = findInsertPosition(
73
- this.orderedEntries,
74
- indexedValue,
75
- this.compareFn
76
- )
77
- this.orderedEntries.splice(insertIndex, 0, [indexedValue, keySet])
82
+ this.orderedEntries.set(indexedValue, undefined)
78
83
  }
79
84
 
80
85
  this.indexedKeys.add(key)
@@ -104,13 +109,8 @@ export class OrderedIndex<
104
109
  if (keySet.size === 0) {
105
110
  this.valueMap.delete(indexedValue)
106
111
 
107
- // Find and remove from ordered entries
108
- const index = this.orderedEntries.findIndex(
109
- ([value]) => this.compareFn(value, indexedValue) === 0
110
- )
111
- if (index !== -1) {
112
- this.orderedEntries.splice(index, 1)
113
- }
112
+ // Remove from ordered entries
113
+ this.orderedEntries.delete(indexedValue)
114
114
  }
115
115
  }
116
116
 
@@ -141,7 +141,7 @@ export class OrderedIndex<
141
141
  * Clears all data from the index
142
142
  */
143
143
  clear(): void {
144
- this.orderedEntries = []
144
+ this.orderedEntries.clear()
145
145
  this.valueMap.clear()
146
146
  this.indexedKeys.clear()
147
147
  this.updateTimestamp()
@@ -175,7 +175,7 @@ export class OrderedIndex<
175
175
  result = this.inArrayLookup(value)
176
176
  break
177
177
  default:
178
- throw new Error(`Operation ${operation} not supported by OrderedIndex`)
178
+ throw new Error(`Operation ${operation} not supported by BTreeIndex`)
179
179
  }
180
180
 
181
181
  this.trackLookup(startTime)
@@ -206,70 +206,26 @@ export class OrderedIndex<
206
206
  const { from, to, fromInclusive = true, toInclusive = true } = options
207
207
  const result = new Set<TKey>()
208
208
 
209
- if (this.orderedEntries.length === 0) {
210
- return result
211
- }
212
-
213
- // Find start position
214
- let startIndex = 0
215
- if (from !== undefined) {
216
- const fromInsertIndex = findInsertPosition(
217
- this.orderedEntries,
218
- from,
219
- this.compareFn
220
- )
221
-
222
- if (fromInclusive) {
223
- // Include values equal to 'from'
224
- startIndex = fromInsertIndex
225
- } else {
226
- // Exclude values equal to 'from'
227
- startIndex = fromInsertIndex
228
- // Skip the value if it exists at this position
229
- if (
230
- startIndex < this.orderedEntries.length &&
231
- this.compareFn(this.orderedEntries[startIndex]![0], from) === 0
232
- ) {
233
- startIndex++
209
+ const fromKey = from ?? this.orderedEntries.minKey()
210
+ const toKey = to ?? this.orderedEntries.maxKey()
211
+
212
+ this.orderedEntries.forRange(
213
+ fromKey,
214
+ toKey,
215
+ toInclusive,
216
+ (indexedValue, _) => {
217
+ if (!fromInclusive && this.compareFn(indexedValue, from) === 0) {
218
+ // the B+ tree `forRange` method does not support exclusive lower bounds
219
+ // so we need to exclude it manually
220
+ return
234
221
  }
235
- }
236
- }
237
-
238
- // Find end position
239
- let endIndex = this.orderedEntries.length
240
- if (to !== undefined) {
241
- const toInsertIndex = findInsertPosition(
242
- this.orderedEntries,
243
- to,
244
- this.compareFn
245
- )
246
222
 
247
- if (toInclusive) {
248
- // Include values equal to 'to'
249
- endIndex = toInsertIndex
250
- // Include the value if it exists at this position
251
- if (
252
- toInsertIndex < this.orderedEntries.length &&
253
- this.compareFn(this.orderedEntries[toInsertIndex]![0], to) === 0
254
- ) {
255
- endIndex = toInsertIndex + 1
223
+ const keys = this.valueMap.get(indexedValue)
224
+ if (keys) {
225
+ keys.forEach((key) => result.add(key))
256
226
  }
257
- } else {
258
- // Exclude values equal to 'to'
259
- endIndex = toInsertIndex
260
227
  }
261
- }
262
-
263
- // Ensure startIndex doesn't exceed endIndex
264
- if (startIndex >= endIndex) {
265
- return result
266
- }
267
-
268
- // Collect keys from the range
269
- for (let i = startIndex; i < endIndex; i++) {
270
- const keys = this.orderedEntries[i]![1]
271
- keys.forEach((key) => result.add(key))
272
- }
228
+ )
273
229
 
274
230
  return result
275
231
  }
@@ -297,6 +253,8 @@ export class OrderedIndex<
297
253
 
298
254
  get orderedEntriesArray(): Array<[any, Set<TKey>]> {
299
255
  return this.orderedEntries
256
+ .keysArray()
257
+ .map((key) => [key, this.valueMap.get(key) ?? new Set()])
300
258
  }
301
259
 
302
260
  get valueMapData(): Map<any, Set<TKey>> {