@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,1010 @@
1
+ // This file was copied from https://github.com/qwertie/btree-typescript/tree/master and adapted to our needs.
2
+ // We removed methods that we don't need.
3
+
4
+ // B+ tree by David Piepgrass. License: MIT
5
+ type EditRangeResult<V, R = number> = {
6
+ value?: V
7
+ break?: R
8
+ delete?: boolean
9
+ }
10
+
11
+ type index = number
12
+
13
+ // Informative microbenchmarks & stuff:
14
+ // http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation (very educational)
15
+ // https://blog.mozilla.org/luke/2012/10/02/optimizing-javascript-variable-access/ (local vars are faster than properties)
16
+ // http://benediktmeurer.de/2017/12/13/an-introduction-to-speculative-optimization-in-v8/ (other stuff)
17
+ // https://jsperf.com/js-in-operator-vs-alternatives (avoid 'in' operator; `.p!==undefined` faster than `hasOwnProperty('p')` in all browsers)
18
+ // https://jsperf.com/instanceof-vs-typeof-vs-constructor-vs-member (speed of type tests varies wildly across browsers)
19
+ // https://jsperf.com/detecting-arrays-new (a.constructor===Array is best across browsers, assuming a is an object)
20
+ // https://jsperf.com/shallow-cloning-methods (a constructor is faster than Object.create; hand-written clone faster than Object.assign)
21
+ // https://jsperf.com/ways-to-fill-an-array (slice-and-replace is fastest)
22
+ // https://jsperf.com/math-min-max-vs-ternary-vs-if (Math.min/max is slow on Edge)
23
+ // 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)
24
+ // https://jsperf.com/detect-not-null-or-undefined (`x==null` slightly slower than `x===null||x===undefined` on all browsers)
25
+ // Overall, microbenchmarks suggest Firefox is the fastest browser for JavaScript and Edge is the slowest.
26
+ // Lessons from https://v8project.blogspot.com/2017/09/elements-kinds-in-v8.html:
27
+ // - Avoid holes in arrays. Avoid `new Array(N)`, it will be "holey" permanently.
28
+ // - Don't read outside bounds of an array (it scans prototype chain).
29
+ // - Small integer arrays are stored differently from doubles
30
+ // - Adding non-numbers to an array deoptimizes it permanently into a general array
31
+ // - Objects can be used like arrays (e.g. have length property) but are slower
32
+ // - V8 source (NewElementsCapacity in src/objects.h): arrays grow by 50% + 16 elements
33
+
34
+ /**
35
+ * A reasonably fast collection of key-value pairs with a powerful API.
36
+ * Largely compatible with the standard Map. BTree is a B+ tree data structure,
37
+ * so the collection is sorted by key.
38
+ *
39
+ * B+ trees tend to use memory more efficiently than hashtables such as the
40
+ * standard Map, especially when the collection contains a large number of
41
+ * items. However, maintaining the sort order makes them modestly slower:
42
+ * O(log size) rather than O(1). This B+ tree implementation supports O(1)
43
+ * fast cloning. It also supports freeze(), which can be used to ensure that
44
+ * a BTree is not changed accidentally.
45
+ *
46
+ * Confusingly, the ES6 Map.forEach(c) method calls c(value,key) instead of
47
+ * c(key,value), in contrast to other methods such as set() and entries()
48
+ * which put the key first. I can only assume that the order was reversed on
49
+ * the theory that users would usually want to examine values and ignore keys.
50
+ * BTree's forEach() therefore works the same way, but a second method
51
+ * `.forEachPair((key,value)=>{...})` is provided which sends you the key
52
+ * first and the value second; this method is slightly faster because it is
53
+ * the "native" for-each method for this class.
54
+ *
55
+ * Out of the box, BTree supports keys that are numbers, strings, arrays of
56
+ * numbers/strings, Date, and objects that have a valueOf() method returning a
57
+ * number or string. Other data types, such as arrays of Date or custom
58
+ * objects, require a custom comparator, which you must pass as the second
59
+ * argument to the constructor (the first argument is an optional list of
60
+ * initial items). Symbols cannot be used as keys because they are unordered
61
+ * (one Symbol is never "greater" or "less" than another).
62
+ *
63
+ * @example
64
+ * Given a {name: string, age: number} object, you can create a tree sorted by
65
+ * name and then by age like this:
66
+ *
67
+ * var tree = new BTree(undefined, (a, b) => {
68
+ * if (a.name > b.name)
69
+ * return 1; // Return a number >0 when a > b
70
+ * else if (a.name < b.name)
71
+ * return -1; // Return a number <0 when a < b
72
+ * else // names are equal (or incomparable)
73
+ * return a.age - b.age; // Return >0 when a.age > b.age
74
+ * });
75
+ *
76
+ * tree.set({name:"Bill", age:17}, "happy");
77
+ * tree.set({name:"Fran", age:40}, "busy & stressed");
78
+ * tree.set({name:"Bill", age:55}, "recently laid off");
79
+ * tree.forEachPair((k, v) => {
80
+ * console.log(`Name: ${k.name} Age: ${k.age} Status: ${v}`);
81
+ * });
82
+ *
83
+ * @description
84
+ * The "range" methods (`forEach, forRange, editRange`) will return the number
85
+ * of elements that were scanned. In addition, the callback can return {break:R}
86
+ * to stop early and return R from the outer function.
87
+ *
88
+ * - TODO: Test performance of preallocating values array at max size
89
+ * - TODO: Add fast initialization when a sorted array is provided to constructor
90
+ *
91
+ * For more documentation see https://github.com/qwertie/btree-typescript
92
+ *
93
+ * Are you a C# developer? You might like the similar data structures I made for C#:
94
+ * BDictionary, BList, etc. See http://core.loyc.net/collections/
95
+ *
96
+ * @author David Piepgrass
97
+ */
98
+ export class BTree<K = any, V = any> {
99
+ private _root: BNode<K, V> = EmptyLeaf as BNode<K, V>
100
+ _size = 0
101
+ _maxNodeSize: number
102
+
103
+ /**
104
+ * provides a total order over keys (and a strict partial order over the type K)
105
+ * @returns a negative value if a < b, 0 if a === b and a positive value if a > b
106
+ */
107
+ _compare: (a: K, b: K) => number
108
+
109
+ /**
110
+ * Initializes an empty B+ tree.
111
+ * @param compare Custom function to compare pairs of elements in the tree.
112
+ * If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.
113
+ * @param entries A set of key-value pairs to initialize the tree
114
+ * @param maxNodeSize Branching factor (maximum items or children per node)
115
+ * Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.
116
+ */
117
+ public constructor(
118
+ compare: (a: K, b: K) => number,
119
+ entries?: Array<[K, V]>,
120
+ maxNodeSize?: number
121
+ ) {
122
+ this._maxNodeSize = maxNodeSize! >= 4 ? Math.min(maxNodeSize!, 256) : 32
123
+ this._compare = compare
124
+ if (entries) this.setPairs(entries)
125
+ }
126
+
127
+ // ///////////////////////////////////////////////////////////////////////////
128
+ // ES6 Map<K,V> methods /////////////////////////////////////////////////////
129
+
130
+ /** Gets the number of key-value pairs in the tree. */
131
+ get size() {
132
+ return this._size
133
+ }
134
+ /** Gets the number of key-value pairs in the tree. */
135
+ get length() {
136
+ return this._size
137
+ }
138
+ /** Returns true iff the tree contains no key-value pairs. */
139
+ get isEmpty() {
140
+ return this._size === 0
141
+ }
142
+
143
+ /** Releases the tree so that its size is 0. */
144
+ clear() {
145
+ this._root = EmptyLeaf as BNode<K, V>
146
+ this._size = 0
147
+ }
148
+
149
+ /**
150
+ * Finds a pair in the tree and returns the associated value.
151
+ * @param defaultValue a value to return if the key was not found.
152
+ * @returns the value, or defaultValue if the key was not found.
153
+ * @description Computational complexity: O(log size)
154
+ */
155
+ get(key: K, defaultValue?: V): V | undefined {
156
+ return this._root.get(key, defaultValue, this)
157
+ }
158
+
159
+ /**
160
+ * Adds or overwrites a key-value pair in the B+ tree.
161
+ * @param key the key is used to determine the sort order of
162
+ * data in the tree.
163
+ * @param value data to associate with the key (optional)
164
+ * @param overwrite Whether to overwrite an existing key-value pair
165
+ * (default: true). If this is false and there is an existing
166
+ * key-value pair then this method has no effect.
167
+ * @returns true if a new key-value pair was added.
168
+ * @description Computational complexity: O(log size)
169
+ * Note: when overwriting a previous entry, the key is updated
170
+ * as well as the value. This has no effect unless the new key
171
+ * has data that does not affect its sort order.
172
+ */
173
+ set(key: K, value: V, overwrite?: boolean): boolean {
174
+ if (this._root.isShared) this._root = this._root.clone()
175
+ const result = this._root.set(key, value, overwrite, this)
176
+ if (result === true || result === false) return result
177
+ // Root node has split, so create a new root node.
178
+ this._root = new BNodeInternal<K, V>([this._root, result])
179
+ return true
180
+ }
181
+
182
+ /**
183
+ * Returns true if the key exists in the B+ tree, false if not.
184
+ * Use get() for best performance; use has() if you need to
185
+ * distinguish between "undefined value" and "key not present".
186
+ * @param key Key to detect
187
+ * @description Computational complexity: O(log size)
188
+ */
189
+ has(key: K): boolean {
190
+ return this.forRange(key, key, true, undefined) !== 0
191
+ }
192
+
193
+ /**
194
+ * Removes a single key-value pair from the B+ tree.
195
+ * @param key Key to find
196
+ * @returns true if a pair was found and removed, false otherwise.
197
+ * @description Computational complexity: O(log size)
198
+ */
199
+ delete(key: K): boolean {
200
+ return this.editRange(key, key, true, DeleteRange) !== 0
201
+ }
202
+
203
+ // ///////////////////////////////////////////////////////////////////////////
204
+ // Additional methods ///////////////////////////////////////////////////////
205
+
206
+ /** Returns the maximum number of children/values before nodes will split. */
207
+ get maxNodeSize() {
208
+ return this._maxNodeSize
209
+ }
210
+
211
+ /** Gets the lowest key in the tree. Complexity: O(log size) */
212
+ minKey(): K | undefined {
213
+ return this._root.minKey()
214
+ }
215
+
216
+ /** Gets the highest key in the tree. Complexity: O(1) */
217
+ maxKey(): K | undefined {
218
+ return this._root.maxKey()
219
+ }
220
+
221
+ /** Gets an array of all keys, sorted */
222
+ keysArray() {
223
+ const results: Array<K> = []
224
+ this._root.forRange(
225
+ this.minKey()!,
226
+ this.maxKey()!,
227
+ true,
228
+ false,
229
+ this,
230
+ 0,
231
+ (k, _v) => {
232
+ results.push(k)
233
+ }
234
+ )
235
+ return results
236
+ }
237
+
238
+ /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
239
+ * If key === undefined, this function returns the lowest pair.
240
+ * @param key The key to search for.
241
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
242
+ * avoid creating a new array on every iteration.
243
+ */
244
+ nextHigherPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {
245
+ reusedArray = reusedArray || ([] as unknown as [K, V])
246
+ if (key === undefined) {
247
+ return this._root.minPair(reusedArray)
248
+ }
249
+ return this._root.getPairOrNextHigher(
250
+ key,
251
+ this._compare,
252
+ false,
253
+ reusedArray
254
+ )
255
+ }
256
+
257
+ /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
258
+ * If key === undefined, this function returns the highest pair.
259
+ * @param key The key to search for.
260
+ * @param reusedArray Optional array used repeatedly to store key-value pairs, to
261
+ * avoid creating a new array each time you call this method.
262
+ */
263
+ nextLowerPair(key: K | undefined, reusedArray?: [K, V]): [K, V] | undefined {
264
+ reusedArray = reusedArray || ([] as unknown as [K, V])
265
+ if (key === undefined) {
266
+ return this._root.maxPair(reusedArray)
267
+ }
268
+ return this._root.getPairOrNextLower(key, this._compare, false, reusedArray)
269
+ }
270
+
271
+ /** Adds all pairs from a list of key-value pairs.
272
+ * @param pairs Pairs to add to this tree. If there are duplicate keys,
273
+ * later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]
274
+ * associates 0 with 7.)
275
+ * @param overwrite Whether to overwrite pairs that already exist (if false,
276
+ * pairs[i] is ignored when the key pairs[i][0] already exists.)
277
+ * @returns The number of pairs added to the collection.
278
+ * @description Computational complexity: O(pairs.length * log(size + pairs.length))
279
+ */
280
+ setPairs(pairs: Array<[K, V]>, overwrite?: boolean): number {
281
+ let added = 0
282
+ for (const pair of pairs) {
283
+ if (this.set(pair[0], pair[1], overwrite)) added++
284
+ }
285
+ return added
286
+ }
287
+
288
+ forRange(
289
+ low: K,
290
+ high: K,
291
+ includeHigh: boolean,
292
+ onFound?: (k: K, v: V, counter: number) => void,
293
+ initialCounter?: number
294
+ ): number
295
+
296
+ /**
297
+ * Scans the specified range of keys, in ascending order by key.
298
+ * Note: the callback `onFound` must not insert or remove items in the
299
+ * collection. Doing so may cause incorrect data to be sent to the
300
+ * callback afterward.
301
+ * @param low The first key scanned will be greater than or equal to `low`.
302
+ * @param high Scanning stops when a key larger than this is reached.
303
+ * @param includeHigh If the `high` key is present, `onFound` is called for
304
+ * that final pair if and only if this parameter is true.
305
+ * @param onFound A function that is called for each key-value pair. This
306
+ * function can return {break:R} to stop early with result R.
307
+ * @param initialCounter Initial third argument of onFound. This value
308
+ * increases by one each time `onFound` is called. Default: 0
309
+ * @returns The number of values found, or R if the callback returned
310
+ * `{break:R}` to stop early.
311
+ * @description Computational complexity: O(number of items scanned + log size)
312
+ */
313
+ forRange<R = number>(
314
+ low: K,
315
+ high: K,
316
+ includeHigh: boolean,
317
+ onFound?: (k: K, v: V, counter: number) => { break?: R } | void,
318
+ initialCounter?: number
319
+ ): R | number {
320
+ const r = this._root.forRange(
321
+ low,
322
+ high,
323
+ includeHigh,
324
+ false,
325
+ this,
326
+ initialCounter || 0,
327
+ onFound
328
+ )
329
+ return typeof r === `number` ? r : r.break!
330
+ }
331
+
332
+ /**
333
+ * Scans and potentially modifies values for a subsequence of keys.
334
+ * Note: the callback `onFound` should ideally be a pure function.
335
+ * Specfically, it must not insert items, call clone(), or change
336
+ * the collection except via return value; out-of-band editing may
337
+ * cause an exception or may cause incorrect data to be sent to
338
+ * the callback (duplicate or missed items). It must not cause a
339
+ * clone() of the collection, otherwise the clone could be modified
340
+ * by changes requested by the callback.
341
+ * @param low The first key scanned will be greater than or equal to `low`.
342
+ * @param high Scanning stops when a key larger than this is reached.
343
+ * @param includeHigh If the `high` key is present, `onFound` is called for
344
+ * that final pair if and only if this parameter is true.
345
+ * @param onFound A function that is called for each key-value pair. This
346
+ * function can return `{value:v}` to change the value associated
347
+ * with the current key, `{delete:true}` to delete the current pair,
348
+ * `{break:R}` to stop early with result R, or it can return nothing
349
+ * (undefined or {}) to cause no effect and continue iterating.
350
+ * `{break:R}` can be combined with one of the other two commands.
351
+ * The third argument `counter` is the number of items iterated
352
+ * previously; it equals 0 when `onFound` is called the first time.
353
+ * @returns The number of values scanned, or R if the callback returned
354
+ * `{break:R}` to stop early.
355
+ * @description
356
+ * Computational complexity: O(number of items scanned + log size)
357
+ * Note: if the tree has been cloned with clone(), any shared
358
+ * nodes are copied before `onFound` is called. This takes O(n) time
359
+ * where n is proportional to the amount of shared data scanned.
360
+ */
361
+ editRange<R = V>(
362
+ low: K,
363
+ high: K,
364
+ includeHigh: boolean,
365
+ onFound: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,
366
+ initialCounter?: number
367
+ ): R | number {
368
+ let root = this._root
369
+ if (root.isShared) this._root = root = root.clone()
370
+ try {
371
+ const r = root.forRange(
372
+ low,
373
+ high,
374
+ includeHigh,
375
+ true,
376
+ this,
377
+ initialCounter || 0,
378
+ onFound
379
+ )
380
+ return typeof r === `number` ? r : r.break!
381
+ } finally {
382
+ let isShared
383
+ while (root.keys.length <= 1 && !root.isLeaf) {
384
+ isShared ||= root.isShared
385
+ this._root = root =
386
+ root.keys.length === 0
387
+ ? EmptyLeaf
388
+ : (root as any as BNodeInternal<K, V>).children[0]!
389
+ }
390
+ // If any ancestor of the new root was shared, the new root must also be shared
391
+ if (isShared) {
392
+ root.isShared = true
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ /** Leaf node / base class. **************************************************/
399
+ class BNode<K, V> {
400
+ // If this is an internal node, _keys[i] is the highest key in children[i].
401
+ keys: Array<K>
402
+ values: Array<V>
403
+ // True if this node might be within multiple `BTree`s (or have multiple parents).
404
+ // If so, it must be cloned before being mutated to avoid changing an unrelated tree.
405
+ // This is transitive: if it's true, children are also shared even if `isShared!=true`
406
+ // in those children. (Certain operations will propagate isShared=true to children.)
407
+ isShared: true | undefined
408
+ get isLeaf() {
409
+ return (this as any).children === undefined
410
+ }
411
+
412
+ constructor(keys: Array<K> = [], values?: Array<V>) {
413
+ this.keys = keys
414
+ this.values = values || undefVals
415
+ this.isShared = undefined
416
+ }
417
+
418
+ // /////////////////////////////////////////////////////////////////////////
419
+ // Shared methods /////////////////////////////////////////////////////////
420
+
421
+ maxKey() {
422
+ return this.keys[this.keys.length - 1]
423
+ }
424
+
425
+ // If key not found, returns i^failXor where i is the insertion index.
426
+ // Callers that don't care whether there was a match will set failXor=0.
427
+ indexOf(key: K, failXor: number, cmp: (a: K, b: K) => number): index {
428
+ const keys = this.keys
429
+ let lo = 0,
430
+ hi = keys.length,
431
+ mid = hi >> 1
432
+ while (lo < hi) {
433
+ const c = cmp(keys[mid]!, key)
434
+ if (c < 0) lo = mid + 1
435
+ else if (c > 0)
436
+ // key < keys[mid]
437
+ hi = mid
438
+ else if (c === 0) return mid
439
+ else {
440
+ // c is NaN or otherwise invalid
441
+ if (key === key)
442
+ // at least the search key is not NaN
443
+ return keys.length
444
+ else throw new Error(`BTree: NaN was used as a key`)
445
+ }
446
+ mid = (lo + hi) >> 1
447
+ }
448
+ return mid ^ failXor
449
+ }
450
+
451
+ // ///////////////////////////////////////////////////////////////////////////
452
+ // Leaf Node: misc //////////////////////////////////////////////////////////
453
+
454
+ minKey(): K | undefined {
455
+ return this.keys[0]
456
+ }
457
+
458
+ minPair(reusedArray: [K, V]): [K, V] | undefined {
459
+ if (this.keys.length === 0) return undefined
460
+ reusedArray[0] = this.keys[0]!
461
+ reusedArray[1] = this.values[0]!
462
+ return reusedArray
463
+ }
464
+
465
+ maxPair(reusedArray: [K, V]): [K, V] | undefined {
466
+ if (this.keys.length === 0) return undefined
467
+ const lastIndex = this.keys.length - 1
468
+ reusedArray[0] = this.keys[lastIndex]!
469
+ reusedArray[1] = this.values[lastIndex]!
470
+ return reusedArray
471
+ }
472
+
473
+ clone(): BNode<K, V> {
474
+ const v = this.values
475
+ return new BNode<K, V>(this.keys.slice(0), v === undefVals ? v : v.slice(0))
476
+ }
477
+
478
+ get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {
479
+ const i = this.indexOf(key, -1, tree._compare)
480
+ return i < 0 ? defaultValue : this.values[i]
481
+ }
482
+
483
+ getPairOrNextLower(
484
+ key: K,
485
+ compare: (a: K, b: K) => number,
486
+ inclusive: boolean,
487
+ reusedArray: [K, V]
488
+ ): [K, V] | undefined {
489
+ const i = this.indexOf(key, -1, compare)
490
+ const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1
491
+ if (indexOrLower >= 0) {
492
+ reusedArray[0] = this.keys[indexOrLower]!
493
+ reusedArray[1] = this.values[indexOrLower]!
494
+ return reusedArray
495
+ }
496
+ return undefined
497
+ }
498
+
499
+ getPairOrNextHigher(
500
+ key: K,
501
+ compare: (a: K, b: K) => number,
502
+ inclusive: boolean,
503
+ reusedArray: [K, V]
504
+ ): [K, V] | undefined {
505
+ const i = this.indexOf(key, -1, compare)
506
+ const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1
507
+ const keys = this.keys
508
+ if (indexOrLower < keys.length) {
509
+ reusedArray[0] = keys[indexOrLower]!
510
+ reusedArray[1] = this.values[indexOrLower]!
511
+ return reusedArray
512
+ }
513
+ return undefined
514
+ }
515
+
516
+ // ///////////////////////////////////////////////////////////////////////////
517
+ // Leaf Node: set & node splitting //////////////////////////////////////////
518
+
519
+ set(
520
+ key: K,
521
+ value: V,
522
+ overwrite: boolean | undefined,
523
+ tree: BTree<K, V>
524
+ ): boolean | BNode<K, V> {
525
+ let i = this.indexOf(key, -1, tree._compare)
526
+ if (i < 0) {
527
+ // key does not exist yet
528
+ i = ~i
529
+ tree._size++
530
+
531
+ if (this.keys.length < tree._maxNodeSize) {
532
+ return this.insertInLeaf(i, key, value, tree)
533
+ } else {
534
+ // This leaf node is full and must split
535
+ const newRightSibling = this.splitOffRightSide()
536
+ let target: BNode<K, V> = this
537
+ if (i > this.keys.length) {
538
+ i -= this.keys.length
539
+ target = newRightSibling
540
+ }
541
+ target.insertInLeaf(i, key, value, tree)
542
+ return newRightSibling
543
+ }
544
+ } else {
545
+ // Key already exists
546
+ if (overwrite !== false) {
547
+ if (value !== undefined) this.reifyValues()
548
+ // usually this is a no-op, but some users may wish to edit the key
549
+ this.keys[i] = key
550
+ this.values[i] = value
551
+ }
552
+ return false
553
+ }
554
+ }
555
+
556
+ reifyValues() {
557
+ if (this.values === undefVals)
558
+ return (this.values = this.values.slice(0, this.keys.length))
559
+ return this.values
560
+ }
561
+
562
+ insertInLeaf(i: index, key: K, value: V, tree: BTree<K, V>) {
563
+ this.keys.splice(i, 0, key)
564
+ if (this.values === undefVals) {
565
+ while (undefVals.length < tree._maxNodeSize) undefVals.push(undefined)
566
+ if (value === undefined) {
567
+ return true
568
+ } else {
569
+ this.values = undefVals.slice(0, this.keys.length - 1)
570
+ }
571
+ }
572
+ this.values.splice(i, 0, value)
573
+ return true
574
+ }
575
+
576
+ takeFromRight(rhs: BNode<K, V>) {
577
+ // Reminder: parent node must update its copy of key for this node
578
+ // assert: neither node is shared
579
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
580
+ let v = this.values
581
+ if (rhs.values === undefVals) {
582
+ if (v !== undefVals) v.push(undefined as any)
583
+ } else {
584
+ v = this.reifyValues()
585
+ v.push(rhs.values.shift()!)
586
+ }
587
+ this.keys.push(rhs.keys.shift()!)
588
+ }
589
+
590
+ takeFromLeft(lhs: BNode<K, V>) {
591
+ // Reminder: parent node must update its copy of key for this node
592
+ // assert: neither node is shared
593
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
594
+ let v = this.values
595
+ if (lhs.values === undefVals) {
596
+ if (v !== undefVals) v.unshift(undefined as any)
597
+ } else {
598
+ v = this.reifyValues()
599
+ v.unshift(lhs.values.pop()!)
600
+ }
601
+ this.keys.unshift(lhs.keys.pop()!)
602
+ }
603
+
604
+ splitOffRightSide(): BNode<K, V> {
605
+ // Reminder: parent node must update its copy of key for this node
606
+ const half = this.keys.length >> 1,
607
+ keys = this.keys.splice(half)
608
+ const values =
609
+ this.values === undefVals ? undefVals : this.values.splice(half)
610
+ return new BNode<K, V>(keys, values)
611
+ }
612
+
613
+ // ///////////////////////////////////////////////////////////////////////////
614
+ // Leaf Node: scanning & deletions //////////////////////////////////////////
615
+
616
+ forRange<R>(
617
+ low: K,
618
+ high: K,
619
+ includeHigh: boolean | undefined,
620
+ editMode: boolean,
621
+ tree: BTree<K, V>,
622
+ count: number,
623
+ onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void
624
+ ): EditRangeResult<V, R> | number {
625
+ const cmp = tree._compare
626
+ let iLow, iHigh
627
+ if (high === low) {
628
+ if (!includeHigh) return count
629
+ iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1
630
+ if (iLow < 0) return count
631
+ } else {
632
+ iLow = this.indexOf(low, 0, cmp)
633
+ iHigh = this.indexOf(high, -1, cmp)
634
+ if (iHigh < 0) iHigh = ~iHigh
635
+ else if (includeHigh === true) iHigh++
636
+ }
637
+ const keys = this.keys,
638
+ values = this.values
639
+ if (onFound !== undefined) {
640
+ for (let i = iLow; i < iHigh; i++) {
641
+ const key = keys[i]!
642
+ const result = onFound(key, values[i]!, count++)
643
+ if (result !== undefined) {
644
+ if (editMode === true) {
645
+ if (key !== keys[i] || this.isShared === true)
646
+ throw new Error(`BTree illegally changed or cloned in editRange`)
647
+ if (result.delete) {
648
+ this.keys.splice(i, 1)
649
+ if (this.values !== undefVals) this.values.splice(i, 1)
650
+ tree._size--
651
+ i--
652
+ iHigh--
653
+ } else if (result.hasOwnProperty(`value`)) {
654
+ values[i] = result.value!
655
+ }
656
+ }
657
+ if (result.break !== undefined) return result
658
+ }
659
+ }
660
+ } else count += iHigh - iLow
661
+ return count
662
+ }
663
+
664
+ /** Adds entire contents of right-hand sibling (rhs is left unchanged) */
665
+ mergeSibling(rhs: BNode<K, V>, _: number) {
666
+ this.keys.push.apply(this.keys, rhs.keys)
667
+ if (this.values === undefVals) {
668
+ if (rhs.values === undefVals) return
669
+ this.values = this.values.slice(0, this.keys.length)
670
+ }
671
+ this.values.push.apply(this.values, rhs.reifyValues())
672
+ }
673
+ }
674
+
675
+ /** Internal node (non-leaf node) ********************************************/
676
+ class BNodeInternal<K, V> extends BNode<K, V> {
677
+ // Note: conventionally B+ trees have one fewer key than the number of
678
+ // children, but I find it easier to keep the array lengths equal: each
679
+ // keys[i] caches the value of children[i].maxKey().
680
+ children: Array<BNode<K, V>>
681
+
682
+ /**
683
+ * This does not mark `children` as shared, so it is the responsibility of the caller
684
+ * to ensure children are either marked shared, or aren't included in another tree.
685
+ */
686
+ constructor(children: Array<BNode<K, V>>, keys?: Array<K>) {
687
+ if (!keys) {
688
+ keys = []
689
+ for (let i = 0; i < children.length; i++) keys[i] = children[i]!.maxKey()!
690
+ }
691
+ super(keys)
692
+ this.children = children
693
+ }
694
+
695
+ minKey() {
696
+ return this.children[0]!.minKey()
697
+ }
698
+
699
+ minPair(reusedArray: [K, V]): [K, V] | undefined {
700
+ return this.children[0]!.minPair(reusedArray)
701
+ }
702
+
703
+ maxPair(reusedArray: [K, V]): [K, V] | undefined {
704
+ return this.children[this.children.length - 1]!.maxPair(reusedArray)
705
+ }
706
+
707
+ get(key: K, defaultValue: V | undefined, tree: BTree<K, V>): V | undefined {
708
+ const i = this.indexOf(key, 0, tree._compare),
709
+ children = this.children
710
+ return i < children.length
711
+ ? children[i]!.get(key, defaultValue, tree)
712
+ : undefined
713
+ }
714
+
715
+ getPairOrNextLower(
716
+ key: K,
717
+ compare: (a: K, b: K) => number,
718
+ inclusive: boolean,
719
+ reusedArray: [K, V]
720
+ ): [K, V] | undefined {
721
+ const i = this.indexOf(key, 0, compare),
722
+ children = this.children
723
+ if (i >= children.length) return this.maxPair(reusedArray)
724
+ const result = children[i]!.getPairOrNextLower(
725
+ key,
726
+ compare,
727
+ inclusive,
728
+ reusedArray
729
+ )
730
+ if (result === undefined && i > 0) {
731
+ return children[i - 1]!.maxPair(reusedArray)
732
+ }
733
+ return result
734
+ }
735
+
736
+ getPairOrNextHigher(
737
+ key: K,
738
+ compare: (a: K, b: K) => number,
739
+ inclusive: boolean,
740
+ reusedArray: [K, V]
741
+ ): [K, V] | undefined {
742
+ const i = this.indexOf(key, 0, compare),
743
+ children = this.children,
744
+ length = children.length
745
+ if (i >= length) return undefined
746
+ const result = children[i]!.getPairOrNextHigher(
747
+ key,
748
+ compare,
749
+ inclusive,
750
+ reusedArray
751
+ )
752
+ if (result === undefined && i < length - 1) {
753
+ return children[i + 1]!.minPair(reusedArray)
754
+ }
755
+ return result
756
+ }
757
+
758
+ // ///////////////////////////////////////////////////////////////////////////
759
+ // Internal Node: set & node splitting //////////////////////////////////////
760
+
761
+ set(
762
+ key: K,
763
+ value: V,
764
+ overwrite: boolean | undefined,
765
+ tree: BTree<K, V>
766
+ ): boolean | BNodeInternal<K, V> {
767
+ const c = this.children,
768
+ max = tree._maxNodeSize,
769
+ cmp = tree._compare
770
+ let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1),
771
+ child = c[i]!
772
+
773
+ if (child.isShared) c[i] = child = child.clone()
774
+ if (child.keys.length >= max) {
775
+ // child is full; inserting anything else will cause a split.
776
+ // Shifting an item to the left or right sibling may avoid a split.
777
+ // We can do a shift if the adjacent node is not full and if the
778
+ // current key can still be placed in the same node after the shift.
779
+ let other: BNode<K, V> | undefined
780
+ if (
781
+ i > 0 &&
782
+ (other = c[i - 1]!).keys.length < max &&
783
+ cmp(child.keys[0]!, key) < 0
784
+ ) {
785
+ if (other.isShared) c[i - 1] = other = other.clone()
786
+ other.takeFromRight(child)
787
+ this.keys[i - 1] = other.maxKey()!
788
+ } else if (
789
+ (other = c[i + 1]) !== undefined &&
790
+ other.keys.length < max &&
791
+ cmp(child.maxKey()!, key) < 0
792
+ ) {
793
+ if (other.isShared) c[i + 1] = other = other.clone()
794
+ other.takeFromLeft(child)
795
+ this.keys[i] = c[i]!.maxKey()!
796
+ }
797
+ }
798
+
799
+ const result = child.set(key, value, overwrite, tree)
800
+ if (result === false) return false
801
+ this.keys[i] = child.maxKey()!
802
+ if (result === true) return true
803
+
804
+ // The child has split and `result` is a new right child... does it fit?
805
+ if (this.keys.length < max) {
806
+ // yes
807
+ this.insert(i + 1, result)
808
+ return true
809
+ } else {
810
+ // no, we must split also
811
+ const newRightSibling = this.splitOffRightSide()
812
+ let target: BNodeInternal<K, V> = this
813
+ if (cmp(result.maxKey()!, this.maxKey()!) > 0) {
814
+ target = newRightSibling
815
+ i -= this.keys.length
816
+ }
817
+ target.insert(i + 1, result)
818
+ return newRightSibling
819
+ }
820
+ }
821
+
822
+ /**
823
+ * Inserts `child` at index `i`.
824
+ * This does not mark `child` as shared, so it is the responsibility of the caller
825
+ * to ensure that either child is marked shared, or it is not included in another tree.
826
+ */
827
+ insert(i: index, child: BNode<K, V>) {
828
+ this.children.splice(i, 0, child)
829
+ this.keys.splice(i, 0, child.maxKey()!)
830
+ }
831
+
832
+ /**
833
+ * Split this node.
834
+ * Modifies this to remove the second half of the items, returning a separate node containing them.
835
+ */
836
+ splitOffRightSide() {
837
+ // assert !this.isShared;
838
+ const half = this.children.length >> 1
839
+ return new BNodeInternal<K, V>(
840
+ this.children.splice(half),
841
+ this.keys.splice(half)
842
+ )
843
+ }
844
+
845
+ takeFromRight(rhs: BNode<K, V>) {
846
+ // Reminder: parent node must update its copy of key for this node
847
+ // assert: neither node is shared
848
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
849
+ this.keys.push(rhs.keys.shift()!)
850
+ this.children.push((rhs as BNodeInternal<K, V>).children.shift()!)
851
+ }
852
+
853
+ takeFromLeft(lhs: BNode<K, V>) {
854
+ // Reminder: parent node must update its copy of key for this node
855
+ // assert: neither node is shared
856
+ // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
857
+ this.keys.unshift(lhs.keys.pop()!)
858
+ this.children.unshift((lhs as BNodeInternal<K, V>).children.pop()!)
859
+ }
860
+
861
+ // ///////////////////////////////////////////////////////////////////////////
862
+ // Internal Node: scanning & deletions //////////////////////////////////////
863
+
864
+ // Note: `count` is the next value of the third argument to `onFound`.
865
+ // A leaf node's `forRange` function returns a new value for this counter,
866
+ // unless the operation is to stop early.
867
+ forRange<R>(
868
+ low: K,
869
+ high: K,
870
+ includeHigh: boolean | undefined,
871
+ editMode: boolean,
872
+ tree: BTree<K, V>,
873
+ count: number,
874
+ onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void
875
+ ): EditRangeResult<V, R> | number {
876
+ const cmp = tree._compare
877
+ const keys = this.keys,
878
+ children = this.children
879
+ let iLow = this.indexOf(low, 0, cmp),
880
+ i = iLow
881
+ const iHigh = Math.min(
882
+ high === low ? iLow : this.indexOf(high, 0, cmp),
883
+ keys.length - 1
884
+ )
885
+ if (!editMode) {
886
+ // Simple case
887
+ for (; i <= iHigh; i++) {
888
+ const result = children[i]!.forRange(
889
+ low,
890
+ high,
891
+ includeHigh,
892
+ editMode,
893
+ tree,
894
+ count,
895
+ onFound
896
+ )
897
+ if (typeof result !== `number`) return result
898
+ count = result
899
+ }
900
+ } else if (i <= iHigh) {
901
+ try {
902
+ for (; i <= iHigh; i++) {
903
+ if (children[i]!.isShared) children[i] = children[i]!.clone()
904
+ const result = children[i]!.forRange(
905
+ low,
906
+ high,
907
+ includeHigh,
908
+ editMode,
909
+ tree,
910
+ count,
911
+ onFound
912
+ )
913
+ // Note: if children[i] is empty then keys[i]=undefined.
914
+ // This is an invalid state, but it is fixed below.
915
+ keys[i] = children[i]!.maxKey()!
916
+ if (typeof result !== `number`) return result
917
+ count = result
918
+ }
919
+ } finally {
920
+ // Deletions may have occurred, so look for opportunities to merge nodes.
921
+ const half = tree._maxNodeSize >> 1
922
+ if (iLow > 0) iLow--
923
+ for (i = iHigh; i >= iLow; i--) {
924
+ if (children[i]!.keys.length <= half) {
925
+ if (children[i]!.keys.length !== 0) {
926
+ this.tryMerge(i, tree._maxNodeSize)
927
+ } else {
928
+ // child is empty! delete it!
929
+ keys.splice(i, 1)
930
+ children.splice(i, 1)
931
+ }
932
+ }
933
+ }
934
+ if (children.length !== 0 && children[0]!.keys.length === 0)
935
+ check(false, `emptiness bug`)
936
+ }
937
+ }
938
+ return count
939
+ }
940
+
941
+ /** Merges child i with child i+1 if their combined size is not too large */
942
+ tryMerge(i: index, maxSize: number): boolean {
943
+ const children = this.children
944
+ if (i >= 0 && i + 1 < children.length) {
945
+ if (children[i]!.keys.length + children[i + 1]!.keys.length <= maxSize) {
946
+ if (children[i]!.isShared)
947
+ // cloned already UNLESS i is outside scan range
948
+ children[i] = children[i]!.clone()
949
+ children[i]!.mergeSibling(children[i + 1]!, maxSize)
950
+ children.splice(i + 1, 1)
951
+ this.keys.splice(i + 1, 1)
952
+ this.keys[i] = children[i]!.maxKey()!
953
+ return true
954
+ }
955
+ }
956
+ return false
957
+ }
958
+
959
+ /**
960
+ * Move children from `rhs` into this.
961
+ * `rhs` must be part of this tree, and be removed from it after this call
962
+ * (otherwise isShared for its children could be incorrect).
963
+ */
964
+ mergeSibling(rhs: BNode<K, V>, maxNodeSize: number) {
965
+ // assert !this.isShared;
966
+ const oldLength = this.keys.length
967
+ this.keys.push.apply(this.keys, rhs.keys)
968
+ const rhsChildren = (rhs as any as BNodeInternal<K, V>).children
969
+ this.children.push.apply(this.children, rhsChildren)
970
+
971
+ if (rhs.isShared && !this.isShared) {
972
+ // All children of a shared node are implicitly shared, and since their new
973
+ // parent is not shared, they must now be explicitly marked as shared.
974
+ for (const child of rhsChildren) child.isShared = true
975
+ }
976
+
977
+ // If our children are themselves almost empty due to a mass-delete,
978
+ // they may need to be merged too (but only the oldLength-1 and its
979
+ // right sibling should need this).
980
+ this.tryMerge(oldLength - 1, maxNodeSize)
981
+ }
982
+ }
983
+
984
+ // Optimization: this array of `undefined`s is used instead of a normal
985
+ // array of values in nodes where `undefined` is the only value.
986
+ // Its length is extended to max node size on first use; since it can
987
+ // be shared between trees with different maximums, its length can only
988
+ // increase, never decrease. Its type should be undefined[] but strangely
989
+ // TypeScript won't allow the comparison V[] === undefined[]. To prevent
990
+ // users from making this array too large, BTree has a maximum node size.
991
+ //
992
+ // FAQ: undefVals[i] is already undefined, so why increase the array size?
993
+ // Reading outside the bounds of an array is relatively slow because it
994
+ // has the side effect of scanning the prototype chain.
995
+ const undefVals: Array<any> = []
996
+
997
+ const Delete = { delete: true },
998
+ DeleteRange = () => Delete
999
+ const EmptyLeaf = (function () {
1000
+ const n = new BNode<any, any>()
1001
+ n.isShared = true
1002
+ return n
1003
+ })()
1004
+
1005
+ function check(fact: boolean, ...args: Array<any>) {
1006
+ if (!fact) {
1007
+ args.unshift(`B+ tree`) // at beginning of message
1008
+ throw new Error(args.join(` `))
1009
+ }
1010
+ }