@pixagram/lacerta-db 0.13.2 → 0.13.3

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/index.js CHANGED
@@ -1448,18 +1448,27 @@ class BTreeNode {
1448
1448
  let i = this.n - 1;
1449
1449
 
1450
1450
  if (this.leaf) {
1451
+ // Search first, shift only if the key is truly new.
1452
+ // The old code shifted while scanning, which corrupted the array
1453
+ // when the key already existed at a lower index: entries between
1454
+ // the key's position and n-1 were duplicated rightward, leaving
1455
+ // stale copies inside the valid range.
1451
1456
  while (i >= 0 && _btreeCmp(this.keys[i], key) > 0) {
1452
- this.keys[i + 1] = this.keys[i];
1453
- this.values[i + 1] = this.values[i];
1454
1457
  i--;
1455
1458
  }
1456
1459
 
1457
1460
  if (i >= 0 && _btreeCmp(this.keys[i], key) === 0) {
1461
+ // Key exists — merge value into existing Set (no shift needed)
1458
1462
  if (!this.values[i]) {
1459
1463
  this.values[i] = new Set();
1460
1464
  }
1461
1465
  this.values[i].add(value);
1462
1466
  } else {
1467
+ // Key is new — shift entries right to open a slot at i+1
1468
+ for (let j = this.n - 1; j > i; j--) {
1469
+ this.keys[j + 1] = this.keys[j];
1470
+ this.values[j + 1] = this.values[j];
1471
+ }
1463
1472
  this.keys[i + 1] = key;
1464
1473
  this.values[i + 1] = new Set([value]);
1465
1474
  this.n++;
@@ -1764,7 +1773,7 @@ class BTreeNode {
1764
1773
  return this._remove(key, null, true);
1765
1774
  }
1766
1775
 
1767
- verify() {
1776
+ verify(isRoot = true) {
1768
1777
  const issues = [];
1769
1778
  for (let i = 0; i < this.n; i++) {
1770
1779
  if (this.keys[i] === undefined || this.keys[i] === null) {
@@ -1778,8 +1787,10 @@ class BTreeNode {
1778
1787
  }
1779
1788
  if (!this.leaf) {
1780
1789
  for (let i = 0; i <= this.n; i++) {
1781
- if (this.children[i]) {
1782
- const childIssues = this.children[i].verify();
1790
+ if (!this.children[i]) {
1791
+ issues.push(`Missing child at index ${i} (node has ${this.n} keys)`);
1792
+ } else {
1793
+ const childIssues = this.children[i].verify(false);
1783
1794
  issues.push(...childIssues);
1784
1795
  }
1785
1796
  }
@@ -1932,7 +1943,8 @@ class BTreeIndex {
1932
1943
  /**
1933
1944
  * Restore a BTreeIndex from persisted sorted entries via O(n) bottom-up bulk-load.
1934
1945
  * Builds leaf nodes directly from sorted data, then constructs internal levels
1935
- * by promoting separators — no individual insert() calls, no comparisons.
1946
+ * using pre-extracted separators — no individual insert() calls, no comparisons,
1947
+ * and no child mutation (which avoids orphaning subtrees at depth ≥ 3).
1936
1948
  * @param {Array} entries - [[key, [docId1, ...]], ...] — MUST be sorted by key
1937
1949
  * @param {number} [order=4]
1938
1950
  * @returns {BTreeIndex}
@@ -1943,41 +1955,50 @@ class BTreeIndex {
1943
1955
 
1944
1956
  const maxKeys = 2 * order - 1;
1945
1957
 
1946
- // Count total values for _size
1947
- let totalSize = 0;
1948
- for (let i = 0; i < entries.length; i++) {
1949
- if (entries[i][0] === undefined || entries[i][0] === null) continue;
1950
- totalSize += entries[i][1].length;
1951
- }
1952
-
1953
- // Filter out null/undefined keys
1958
+ // Filter out null/undefined keys and count total values for _size
1954
1959
  const clean = [];
1960
+ let totalSize = 0;
1955
1961
  for (let i = 0; i < entries.length; i++) {
1956
1962
  if (entries[i][0] !== undefined && entries[i][0] !== null) {
1957
1963
  clean.push(entries[i]);
1964
+ totalSize += entries[i][1].length;
1958
1965
  }
1959
1966
  }
1960
1967
 
1961
1968
  if (clean.length === 0) return tree;
1962
1969
 
1963
- // Step 1: Build leaf nodes, filling each to maxKeys
1970
+ // ---------------------------------------------------------------
1971
+ // Step 1: Build leaf nodes AND pre-extract inter-leaf separators.
1972
+ //
1973
+ // In a B-tree (not B+) some keys live at internal nodes. We decide
1974
+ // up-front which entries become leaf data and which become separator
1975
+ // keys for parent nodes. This avoids the old promote-and-shift
1976
+ // approach which orphaned subtrees when children were non-leaf.
1977
+ //
1978
+ // Layout: L0 S0 L1 S1 ... S(n-2) L(n-1)
1979
+ // ↑leaf ↑sep ↑leaf ↑last leaf (no trailing sep)
1980
+ // ---------------------------------------------------------------
1981
+
1982
+ // How many leaves do we need?
1983
+ // N entries = numLeaves * leafEntries + (numLeaves - 1) separators
1984
+ // N = numLeaves * fill + numLeaves - 1 = numLeaves * (fill + 1) - 1
1985
+ // numLeaves = ceil((N + 1) / (maxKeys + 1))
1986
+ const numLeaves = clean.length <= maxKeys
1987
+ ? 1
1988
+ : Math.ceil((clean.length + 1) / (maxKeys + 1));
1989
+
1990
+ // Distribute entries among leaves as evenly as possible
1991
+ const totalLeafEntries = clean.length - (numLeaves - 1); // subtract separator slots
1992
+ const basePerLeaf = Math.floor(totalLeafEntries / numLeaves);
1993
+ const extraLeaves = totalLeafEntries - basePerLeaf * numLeaves;
1994
+
1964
1995
  const leaves = [];
1996
+ const separators = [];
1965
1997
  let pos = 0;
1966
- while (pos < clean.length) {
1998
+
1999
+ for (let li = 0; li < numLeaves; li++) {
2000
+ const count = basePerLeaf + (li < extraLeaves ? 1 : 0);
1967
2001
  const node = new BTreeNode(order, true);
1968
- const remaining = clean.length - pos;
1969
-
1970
- // How many entries for this leaf?
1971
- let count;
1972
- if (remaining <= maxKeys) {
1973
- // Last chunk — take everything
1974
- count = remaining;
1975
- } else if (remaining < maxKeys + order) {
1976
- // Would leave a tiny next leaf — split evenly
1977
- count = Math.ceil(remaining / 2);
1978
- } else {
1979
- count = maxKeys;
1980
- }
1981
2002
 
1982
2003
  for (let j = 0; j < count; j++) {
1983
2004
  const [key, values] = clean[pos++];
@@ -1986,50 +2007,49 @@ class BTreeIndex {
1986
2007
  node.n++;
1987
2008
  }
1988
2009
  leaves.push(node);
2010
+
2011
+ // Extract separator between this leaf and the next (not after the last)
2012
+ if (li < numLeaves - 1) {
2013
+ separators.push(clean[pos++]);
2014
+ }
1989
2015
  }
1990
2016
 
1991
- // Step 2: Build internal levels bottom-up
2017
+ // ---------------------------------------------------------------
2018
+ // Step 2: Build internal levels bottom-up using pre-extracted
2019
+ // separators. Children are never mutated, so no subtrees are lost.
2020
+ // ---------------------------------------------------------------
1992
2021
  let level = leaves;
2022
+ let seps = separators;
2023
+
1993
2024
  while (level.length > 1) {
1994
2025
  const parents = [];
1995
- let ci = 0;
2026
+ const nextSeps = [];
2027
+ let ci = 0; // child index into level
2028
+ let si = 0; // separator index into seps
1996
2029
 
1997
2030
  while (ci < level.length) {
1998
2031
  const parent = new BTreeNode(order, false);
1999
2032
  parent.children[0] = level[ci++];
2000
2033
 
2001
- // Promote first key from each subsequent child as separator
2002
- while (parent.n < maxKeys && ci < level.length) {
2003
- const child = level[ci];
2004
-
2005
- // Promote child's first key+values as separator in parent
2006
- parent.keys[parent.n] = child.keys[0];
2007
- parent.values[parent.n] = child.values[0];
2008
-
2009
- // Shift child entries left to remove promoted key
2010
- for (let j = 0; j < child.n - 1; j++) {
2011
- child.keys[j] = child.keys[j + 1];
2012
- child.values[j] = child.values[j + 1];
2013
- }
2014
- if (!child.leaf) {
2015
- for (let j = 0; j < child.n; j++) {
2016
- child.children[j] = child.children[j + 1];
2017
- }
2018
- child.children[child.n] = undefined;
2019
- }
2020
- child.keys[child.n - 1] = undefined;
2021
- child.values[child.n - 1] = undefined;
2022
- child.n--;
2023
-
2024
- parent.children[parent.n + 1] = child;
2034
+ // Attach children with their pre-extracted separators
2035
+ while (parent.n < maxKeys && si < seps.length && ci < level.length) {
2036
+ const [sepKey, sepValues] = seps[si++];
2037
+ parent.keys[parent.n] = sepKey;
2038
+ parent.values[parent.n] = new Set(sepValues);
2039
+ parent.children[parent.n + 1] = level[ci++];
2025
2040
  parent.n++;
2026
- ci++;
2027
2041
  }
2028
2042
 
2029
2043
  parents.push(parent);
2044
+
2045
+ // Extract separator between this parent and the next
2046
+ if (ci < level.length && si < seps.length) {
2047
+ nextSeps.push(seps[si++]);
2048
+ }
2030
2049
  }
2031
2050
 
2032
2051
  level = parents;
2052
+ seps = nextSeps;
2033
2053
  }
2034
2054
 
2035
2055
  tree._root = level[0];
@@ -6515,4 +6535,4 @@ export {
6515
6535
  IndexedDBConnectionPool,
6516
6536
  BrowserCompressionUtility,
6517
6537
  BrowserEncryptionUtility
6518
- };
6538
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixagram/lacerta-db",
3
- "version": "0.13.2",
4
- "description": "Lacerta-DB is a Javascript IndexedDB Database for Web Browsers. Simple, Fast, Secure.",
3
+ "version": "0.13.3",
4
+ "description": "LacertaDB is a JS Database for Web Browsers. Up to 10x faster than others. Simple, Fast, Secure.",
5
5
  "devDependencies": {
6
6
  "@babel/core": "^7.23.6",
7
7
  "@babel/plugin-transform-optional-chaining": "^7.28.6",
@@ -22,6 +22,8 @@
22
22
  "author": "Affolter Matias",
23
23
  "keywords": [
24
24
  "indexeddb",
25
+ "pouchdb",
26
+ "rxdb",
25
27
  "database",
26
28
  "db",
27
29
  "store",
package/readme.md CHANGED
@@ -96,9 +96,10 @@ LacertaDB is a **browser-native** document database built for Web3 applications,
96
96
  - **Hash** indexes for O(1) exact-match lookups
97
97
  - **Full-text search** with CJK support via `Intl.Segmenter`
98
98
  - **Geospatial queries** with QuadTree-backed `$near` and `$within`
99
-
100
99
  </td>
101
- <td>
100
+ </tr>
101
+ </table>
102
+
102
103
 
103
104
  ### Performance
104
105
 
@@ -120,9 +121,6 @@ LacertaDB is a **browser-native** document database built for Web3 applications,
120
121
  | **Medium Data** (array of objects) | 334 ops/s · 4,093B | 3,460 ops/s · 4,168B | **5,587 ops/s** · 4,809B |
121
122
  | **Large Data** (TypedArray 0.2MB) | 711 ops/s · 200,005B | 341 ops/s · 200,005B | **2,703 ops/s** · 200,023B |
122
123
 
123
- </td>
124
- </tr>
125
- </table>
126
124
 
127
125
  ---
128
126