@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.
- package/dist/cjs/collection.cjs +30 -26
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +4 -4
- package/dist/cjs/index.cjs +2 -2
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/indexes/auto-index.cjs +2 -2
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/{ordered-index.cjs → btree-index.cjs} +27 -63
- package/dist/cjs/indexes/btree-index.cjs.map +1 -0
- package/dist/{esm/indexes/ordered-index.d.ts → cjs/indexes/btree-index.d.cts} +6 -4
- package/dist/cjs/utils/btree.cjs +677 -0
- package/dist/cjs/utils/btree.cjs.map +1 -0
- package/dist/cjs/utils/btree.d.cts +197 -0
- package/dist/esm/collection.d.ts +4 -4
- package/dist/esm/collection.js +30 -26
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/indexes/auto-index.js +2 -2
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/{cjs/indexes/ordered-index.d.cts → esm/indexes/btree-index.d.ts} +6 -4
- package/dist/esm/indexes/{ordered-index.js → btree-index.js} +27 -63
- package/dist/esm/indexes/btree-index.js.map +1 -0
- package/dist/esm/utils/btree.d.ts +197 -0
- package/dist/esm/utils/btree.js +677 -0
- package/dist/esm/utils/btree.js.map +1 -0
- package/package.json +1 -1
- package/src/collection.ts +45 -33
- package/src/index.ts +1 -1
- package/src/indexes/auto-index.ts +2 -2
- package/src/indexes/{ordered-index.ts → btree-index.ts} +42 -84
- package/src/utils/btree.ts +1010 -0
- package/dist/cjs/indexes/ordered-index.cjs.map +0 -1
- package/dist/cjs/utils/array-utils.cjs +0 -18
- package/dist/cjs/utils/array-utils.cjs.map +0 -1
- package/dist/esm/indexes/ordered-index.js.map +0 -1
- package/dist/esm/utils/array-utils.js +0 -18
- 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
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 {
|
|
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(
|
|
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
|
-
|
|
742
|
-
|
|
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
|
-
|
|
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
|
-
|
|
896
|
+
forceEmit = false
|
|
882
897
|
): void {
|
|
883
|
-
|
|
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
|
|
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
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
|
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:
|
|
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
|
|
1333
|
-
const resolver = config.indexType ?? (
|
|
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
|
|
1348
|
-
if ((resolver as unknown) ===
|
|
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
|
|
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/
|
|
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 {
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
108
|
-
|
|
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
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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>> {
|