@delali/narsil 0.2.4-next.45 → 0.2.4-next.47

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.
@@ -2406,12 +2406,12 @@ function removeNeighbor(adj, ord, layer, neighborOrd) {
2406
2406
  return;
2407
2407
  }
2408
2408
  }
2409
- function replaceNeighbors(adj, ord, layer, neighbors) {
2409
+ function replaceNeighbors(adj, ord, layer, neighbors, count) {
2410
2410
  const base = layerBase(adj, ord, layer);
2411
2411
  if (base === ABSENT) return;
2412
2412
  const array = layerArray(adj, layer);
2413
2413
  const stride = layer === 0 ? adj.level0Stride : adj.upperStride;
2414
- const limit = Math.min(neighbors.length, stride - 1);
2414
+ const limit = Math.min(count, stride - 1);
2415
2415
  for (let i = 0; i < limit; i++) {
2416
2416
  array[base + i + 1] = neighbors[i];
2417
2417
  }
@@ -12031,90 +12031,6 @@ function createVectorStore() {
12031
12031
  };
12032
12032
  }
12033
12033
 
12034
- // src/core/heap.ts
12035
- function createMinHeap(compare) {
12036
- return createHeap(compare);
12037
- }
12038
- function createMaxHeap(compare) {
12039
- return createHeap((a, b) => compare(b, a));
12040
- }
12041
- function createBoundedMaxHeap(compare, capacity) {
12042
- const heap = createMaxHeap(compare);
12043
- const innerPush = heap.push;
12044
- return {
12045
- push(item) {
12046
- if (heap.size < capacity) {
12047
- innerPush(item);
12048
- return;
12049
- }
12050
- const top = heap.peek();
12051
- if (top !== void 0 && compare(item, top) < 0) {
12052
- heap.pop();
12053
- innerPush(item);
12054
- }
12055
- },
12056
- pop: heap.pop,
12057
- peek: heap.peek,
12058
- get size() {
12059
- return heap.size;
12060
- },
12061
- toSortedArray: heap.toSortedArray
12062
- };
12063
- }
12064
- function createHeap(compare) {
12065
- const items = [];
12066
- function siftUp(idx) {
12067
- while (idx > 0) {
12068
- const parent = idx - 1 >> 1;
12069
- if (compare(items[idx], items[parent]) >= 0) break;
12070
- const tmp = items[idx];
12071
- items[idx] = items[parent];
12072
- items[parent] = tmp;
12073
- idx = parent;
12074
- }
12075
- }
12076
- function siftDown2(idx) {
12077
- const len = items.length;
12078
- while (true) {
12079
- let smallest = idx;
12080
- const left = 2 * idx + 1;
12081
- const right = 2 * idx + 2;
12082
- if (left < len && compare(items[left], items[smallest]) < 0) smallest = left;
12083
- if (right < len && compare(items[right], items[smallest]) < 0) smallest = right;
12084
- if (smallest === idx) break;
12085
- const tmp = items[idx];
12086
- items[idx] = items[smallest];
12087
- items[smallest] = tmp;
12088
- idx = smallest;
12089
- }
12090
- }
12091
- return {
12092
- push(item) {
12093
- items.push(item);
12094
- siftUp(items.length - 1);
12095
- },
12096
- pop() {
12097
- if (items.length === 0) return void 0;
12098
- const top = items[0];
12099
- const last = items.pop();
12100
- if (items.length > 0) {
12101
- items[0] = last;
12102
- siftDown2(0);
12103
- }
12104
- return top;
12105
- },
12106
- peek() {
12107
- return items[0];
12108
- },
12109
- get size() {
12110
- return items.length;
12111
- },
12112
- toSortedArray() {
12113
- return [...items].sort(compare);
12114
- }
12115
- };
12116
- }
12117
-
12118
12034
  // src/vector/hnsw/shared.ts
12119
12035
  var COMPACTION_TOMBSTONE_RATIO = 0.1;
12120
12036
  var COMPACTION_ABSOLUTE_THRESHOLD = 1e3;
@@ -12165,8 +12081,6 @@ function toScore(distance, metric) {
12165
12081
  return 1 / (1 + distance);
12166
12082
  }
12167
12083
  }
12168
- var distanceAsc = (a, b) => a.distance - b.distance;
12169
- var distanceDesc = (a, b) => b.distance - a.distance;
12170
12084
  function randomLevel(mL) {
12171
12085
  let u = Math.random();
12172
12086
  if (u === 0) u = Number.MIN_VALUE;
@@ -12193,37 +12107,168 @@ function addConnection(connections, ord) {
12193
12107
  connections.push(ord);
12194
12108
  }
12195
12109
 
12110
+ // src/vector/hnsw/workspace.ts
12111
+ var INITIAL_LIST_CAPACITY = 64;
12112
+ function createList(capacity) {
12113
+ return { ords: new Int32Array(capacity), distances: new Float64Array(capacity), size: 0 };
12114
+ }
12115
+ function createHeap(greatestFirst) {
12116
+ return { ...createList(INITIAL_LIST_CAPACITY), greatestFirst, topOrd: -1, topDistance: 0 };
12117
+ }
12118
+ function createHNSWWorkspace() {
12119
+ return {
12120
+ frontier: createHeap(false),
12121
+ found: createHeap(true),
12122
+ entryPoints: new Int32Array(INITIAL_LIST_CAPACITY),
12123
+ entryPointCount: 0,
12124
+ traversal: createList(INITIAL_LIST_CAPACITY),
12125
+ working: createList(INITIAL_LIST_CAPACITY),
12126
+ insertSelection: createList(INITIAL_LIST_CAPACITY),
12127
+ pruneCandidates: createList(INITIAL_LIST_CAPACITY),
12128
+ pruneSelection: createList(INITIAL_LIST_CAPACITY),
12129
+ repairCandidates: createList(INITIAL_LIST_CAPACITY),
12130
+ repairSelection: createList(INITIAL_LIST_CAPACITY)
12131
+ };
12132
+ }
12133
+ function ensureListCapacity(list, needed) {
12134
+ if (needed <= list.ords.length) return;
12135
+ let capacity = list.ords.length === 0 ? INITIAL_LIST_CAPACITY : list.ords.length;
12136
+ while (capacity < needed) capacity *= 2;
12137
+ const ords = new Int32Array(capacity);
12138
+ ords.set(list.ords);
12139
+ list.ords = ords;
12140
+ const distances = new Float64Array(capacity);
12141
+ distances.set(list.distances);
12142
+ list.distances = distances;
12143
+ }
12144
+ function appendToList(list, ord, distance) {
12145
+ ensureListCapacity(list, list.size + 1);
12146
+ list.ords[list.size] = ord;
12147
+ list.distances[list.size] = distance;
12148
+ list.size += 1;
12149
+ }
12150
+ function copyList(source, target) {
12151
+ ensureListCapacity(target, source.size);
12152
+ target.ords.set(source.ords.subarray(0, source.size));
12153
+ target.distances.set(source.distances.subarray(0, source.size));
12154
+ target.size = source.size;
12155
+ }
12156
+ function sortListByDistance(list) {
12157
+ const { ords, distances } = list;
12158
+ for (let i = 1; i < list.size; i++) {
12159
+ const ord = ords[i];
12160
+ const distance = distances[i];
12161
+ let j = i - 1;
12162
+ while (j >= 0 && distances[j] > distance) {
12163
+ distances[j + 1] = distances[j];
12164
+ ords[j + 1] = ords[j];
12165
+ j -= 1;
12166
+ }
12167
+ distances[j + 1] = distance;
12168
+ ords[j + 1] = ord;
12169
+ }
12170
+ }
12171
+ function heapPrecedes(heap, a, b) {
12172
+ return heap.greatestFirst ? heap.distances[a] > heap.distances[b] : heap.distances[a] < heap.distances[b];
12173
+ }
12174
+ function swapHeapEntries(heap, a, b) {
12175
+ const ord = heap.ords[a];
12176
+ heap.ords[a] = heap.ords[b];
12177
+ heap.ords[b] = ord;
12178
+ const distance = heap.distances[a];
12179
+ heap.distances[a] = heap.distances[b];
12180
+ heap.distances[b] = distance;
12181
+ }
12182
+ function pushHeap(heap, ord, distance) {
12183
+ appendToList(heap, ord, distance);
12184
+ let index = heap.size - 1;
12185
+ while (index > 0) {
12186
+ const parent = index - 1 >> 1;
12187
+ if (!heapPrecedes(heap, index, parent)) break;
12188
+ swapHeapEntries(heap, index, parent);
12189
+ index = parent;
12190
+ }
12191
+ }
12192
+ function popHeap(heap) {
12193
+ if (heap.size === 0) return false;
12194
+ heap.topOrd = heap.ords[0];
12195
+ heap.topDistance = heap.distances[0];
12196
+ heap.size -= 1;
12197
+ if (heap.size > 0) {
12198
+ heap.ords[0] = heap.ords[heap.size];
12199
+ heap.distances[0] = heap.distances[heap.size];
12200
+ let index = 0;
12201
+ for (; ; ) {
12202
+ const left = 2 * index + 1;
12203
+ const right = left + 1;
12204
+ let first = index;
12205
+ if (left < heap.size && heapPrecedes(heap, left, first)) first = left;
12206
+ if (right < heap.size && heapPrecedes(heap, right, first)) first = right;
12207
+ if (first === index) break;
12208
+ swapHeapEntries(heap, index, first);
12209
+ index = first;
12210
+ }
12211
+ }
12212
+ return true;
12213
+ }
12214
+ function resetHeap(heap) {
12215
+ heap.size = 0;
12216
+ }
12217
+ function drainHeapNearestFirst(heap, list) {
12218
+ const count = heap.size;
12219
+ ensureListCapacity(list, count);
12220
+ list.size = count;
12221
+ for (let i = count - 1; i >= 0; i--) {
12222
+ if (!popHeap(heap)) break;
12223
+ list.ords[i] = heap.topOrd;
12224
+ list.distances[i] = heap.topDistance;
12225
+ }
12226
+ }
12227
+ function setSingleEntryPoint(workspace, ord) {
12228
+ workspace.entryPoints[0] = ord;
12229
+ workspace.entryPointCount = 1;
12230
+ }
12231
+ function setEntryPointsFromList(workspace, list) {
12232
+ if (list.size > workspace.entryPoints.length) {
12233
+ let capacity = workspace.entryPoints.length;
12234
+ while (capacity < list.size) capacity *= 2;
12235
+ workspace.entryPoints = new Int32Array(capacity);
12236
+ }
12237
+ workspace.entryPoints.set(list.ords.subarray(0, list.size));
12238
+ workspace.entryPointCount = list.size;
12239
+ }
12240
+
12196
12241
  // src/vector/hnsw/graph-ops.ts
12197
- function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones, distFn) {
12242
+ function searchLayer(state, qVec, qMag, ef, layer, metric, skipTombstones, distFn, results) {
12198
12243
  const getDistance = distFn ?? ((ord) => queryDistanceByOrd(state, qVec, qMag, ord, metric));
12244
+ const workspace = state.workspace;
12245
+ const frontier = workspace.frontier;
12246
+ const found = workspace.found;
12199
12247
  const adjacency = state.adjacency;
12200
12248
  const neighbors = layerArray(adjacency, layer);
12201
12249
  const visited = state.visited;
12202
12250
  const stamp = nextVisitStamp(state);
12203
- const candidates = createMinHeap(distanceAsc);
12204
- const results = createMinHeap(distanceDesc);
12251
+ resetHeap(frontier);
12252
+ resetHeap(found);
12205
12253
  let furthestDist = Number.POSITIVE_INFINITY;
12206
- for (const epOrd of eps) {
12254
+ for (let i = 0; i < workspace.entryPointCount; i++) {
12255
+ const epOrd = workspace.entryPoints[i];
12207
12256
  if (visited[epOrd] === stamp) continue;
12208
12257
  visited[epOrd] = stamp;
12209
12258
  if (!nodeExists(state, epOrd)) continue;
12210
12259
  if (skipTombstones && state.tombstones[epOrd] === 1) continue;
12211
12260
  const dist = getDistance(epOrd);
12212
12261
  if (dist === Number.POSITIVE_INFINITY) continue;
12213
- const pair = { ord: epOrd, distance: dist };
12214
- candidates.push(pair);
12215
- results.push(pair);
12216
- if (results.size > ef) {
12217
- results.pop();
12218
- }
12219
- }
12220
- const topResult = results.peek();
12221
- if (topResult) furthestDist = topResult.distance;
12222
- while (candidates.size > 0) {
12223
- const nearest = candidates.pop();
12224
- if (!nearest) break;
12225
- if (nearest.distance > furthestDist) break;
12226
- const base = layerBase(adjacency, nearest.ord, layer);
12262
+ pushHeap(frontier, epOrd, dist);
12263
+ pushHeap(found, epOrd, dist);
12264
+ if (found.size > ef) {
12265
+ popHeap(found);
12266
+ }
12267
+ }
12268
+ if (found.size > 0) furthestDist = found.distances[0];
12269
+ while (popHeap(frontier)) {
12270
+ if (frontier.topDistance > furthestDist) break;
12271
+ const base = layerBase(adjacency, frontier.topOrd, layer);
12227
12272
  if (base === -1) continue;
12228
12273
  const count = neighbors[base];
12229
12274
  for (let i = 1; i <= count; i++) {
@@ -12234,55 +12279,41 @@ function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones,
12234
12279
  if (!nodeExists(state, neighborOrd)) continue;
12235
12280
  const dist = getDistance(neighborOrd);
12236
12281
  if (dist === Number.POSITIVE_INFINITY) continue;
12237
- if (dist < furthestDist || results.size < ef) {
12238
- const pair = { ord: neighborOrd, distance: dist };
12239
- candidates.push(pair);
12240
- results.push(pair);
12241
- if (results.size > ef) {
12242
- results.pop();
12282
+ if (dist < furthestDist || found.size < ef) {
12283
+ pushHeap(frontier, neighborOrd, dist);
12284
+ pushHeap(found, neighborOrd, dist);
12285
+ if (found.size > ef) {
12286
+ popHeap(found);
12243
12287
  }
12244
- const newTop = results.peek();
12245
- if (newTop) furthestDist = newTop.distance;
12288
+ furthestDist = found.distances[0];
12246
12289
  }
12247
12290
  }
12248
12291
  }
12249
- return results;
12250
- }
12251
- function nearestFromHeap(heap) {
12252
- let nearest;
12253
- while (heap.size > 0) {
12254
- nearest = heap.pop();
12255
- }
12256
- return nearest;
12292
+ drainHeapNearestFirst(found, results);
12257
12293
  }
12258
- function selectNeighborsHeuristic(state, targetOrd, candidates, maxConnections, layer, metric, extendCandidates, keepPruned) {
12259
- const working = [...candidates];
12260
- working.sort((a, b) => a.distance - b.distance);
12261
- const selected = [];
12262
- const discarded = [];
12263
- for (const candidate of working) {
12264
- if (selected.length >= maxConnections) break;
12294
+ function selectNeighborsHeuristic(state, candidates, maxConnections, metric, selected) {
12295
+ const working = state.workspace.working;
12296
+ copyList(candidates, working);
12297
+ sortListByDistance(working);
12298
+ ensureListCapacity(selected, Math.min(maxConnections, working.size));
12299
+ selected.size = 0;
12300
+ for (let i = 0; i < working.size; i++) {
12301
+ if (selected.size >= maxConnections) break;
12302
+ const candidateOrd = working.ords[i];
12303
+ const candidateDistance = working.distances[i];
12265
12304
  let accepted = true;
12266
- for (const sel of selected) {
12267
- const distBetween = nodeDistanceByOrd(state, candidate.ord, sel.ord, metric);
12268
- if (candidate.distance >= distBetween) {
12305
+ for (let s = 0; s < selected.size; s++) {
12306
+ const distBetween = nodeDistanceByOrd(state, candidateOrd, selected.ords[s], metric);
12307
+ if (candidateDistance >= distBetween) {
12269
12308
  accepted = false;
12270
12309
  break;
12271
12310
  }
12272
12311
  }
12273
- if (accepted) {
12274
- selected.push(candidate);
12275
- } else {
12276
- discarded.push(candidate);
12277
- }
12278
- }
12279
- {
12280
- for (const disc of discarded) {
12281
- if (selected.length >= maxConnections) break;
12282
- selected.push(disc);
12283
- }
12312
+ if (!accepted) continue;
12313
+ selected.ords[selected.size] = candidateOrd;
12314
+ selected.distances[selected.size] = candidateDistance;
12315
+ selected.size += 1;
12284
12316
  }
12285
- return selected;
12286
12317
  }
12287
12318
  function pruneConnections(state, ord, layer, metric) {
12288
12319
  const adjacency = state.adjacency;
@@ -12292,17 +12323,20 @@ function pruneConnections(state, ord, layer, metric) {
12292
12323
  const neighbors = layerArray(adjacency, layer);
12293
12324
  const count = neighbors[base];
12294
12325
  if (count <= mc) return;
12295
- const conns = [];
12326
+ const candidates = state.workspace.pruneCandidates;
12327
+ candidates.size = 0;
12328
+ ensureListCapacity(candidates, count);
12296
12329
  for (let i = 1; i <= count; i++) {
12297
12330
  const connOrd = neighbors[base + i];
12298
12331
  const dist = nodeDistanceByOrd(state, ord, connOrd, metric);
12299
12332
  if (dist === Number.POSITIVE_INFINITY) continue;
12300
- conns.push({ ord: connOrd, distance: dist });
12333
+ candidates.ords[candidates.size] = connOrd;
12334
+ candidates.distances[candidates.size] = dist;
12335
+ candidates.size += 1;
12301
12336
  }
12302
- const pruned = selectNeighborsHeuristic(state, ord, conns, mc, layer, metric);
12303
- const next = [];
12304
- for (const p of pruned) addConnection(next, p.ord);
12305
- replaceNeighbors(adjacency, ord, layer, next);
12337
+ const kept = state.workspace.pruneSelection;
12338
+ selectNeighborsHeuristic(state, candidates, mc, metric, kept);
12339
+ replaceNeighbors(adjacency, ord, layer, kept.ords, kept.size);
12306
12340
  }
12307
12341
 
12308
12342
  // src/vector/hnsw/mutation.ts
@@ -12312,30 +12346,23 @@ function clearTombstone(state, ord) {
12312
12346
  state.tombstoneCount--;
12313
12347
  }
12314
12348
  }
12315
- function reassignEntryPoint(state) {
12349
+ function highestNode(state, includeTombstoned) {
12316
12350
  let bestOrd = -1;
12317
12351
  let bestLayer = -1;
12318
12352
  for (let ord = 0; ord < state.adjacency.slots; ord++) {
12319
12353
  const level = nodeMaxLayer(state, ord);
12320
12354
  if (level === -1) continue;
12321
- if (state.tombstones[ord] === 1) continue;
12355
+ if (!includeTombstoned && state.tombstones[ord] === 1) continue;
12322
12356
  if (level > bestLayer) {
12323
12357
  bestLayer = level;
12324
12358
  bestOrd = ord;
12325
12359
  }
12326
12360
  }
12327
- if (bestOrd === -1) {
12328
- for (let ord = 0; ord < state.adjacency.slots; ord++) {
12329
- const level = nodeMaxLayer(state, ord);
12330
- if (level === -1) continue;
12331
- if (level > bestLayer) {
12332
- bestLayer = level;
12333
- bestOrd = ord;
12334
- }
12335
- }
12336
- }
12337
- state.entryPointOrd = bestOrd;
12338
- state.topLayer = bestOrd === -1 ? -1 : bestLayer;
12361
+ return bestOrd;
12362
+ }
12363
+ function setEntryPoint(state, ord) {
12364
+ state.entryPointOrd = ord;
12365
+ state.topLayer = ord === -1 ? -1 : nodeMaxLayer(state, ord);
12339
12366
  }
12340
12367
  function insertNode(state, docId) {
12341
12368
  const ord = state.store.getOrdinal(docId);
@@ -12363,39 +12390,30 @@ function insertNode(state, docId) {
12363
12390
  return;
12364
12391
  }
12365
12392
  const metric = state.buildMetric;
12393
+ const workspace = state.workspace;
12394
+ const candidates = workspace.traversal;
12395
+ const selected = workspace.insertSelection;
12366
12396
  const insertDistFn = (candOrd) => nodeDistanceByOrd(state, ord, candOrd, metric);
12367
- let currentEPs = [state.entryPointOrd];
12397
+ setSingleEntryPoint(workspace, state.entryPointOrd);
12368
12398
  for (let layer = state.topLayer; layer > l; layer--) {
12369
- const heap = searchLayer(state, entry.vector, entry.magnitude, currentEPs, 1, layer, metric, false, insertDistFn);
12370
- const nearest = nearestFromHeap(heap);
12371
- if (nearest) {
12372
- currentEPs = [nearest.ord];
12399
+ searchLayer(state, entry.vector, entry.magnitude, 1, layer, metric, false, insertDistFn, candidates);
12400
+ if (candidates.size > 0) {
12401
+ setSingleEntryPoint(workspace, candidates.ords[0]);
12373
12402
  }
12374
12403
  }
12375
12404
  for (let layer = Math.min(l, state.topLayer); layer >= 0; layer--) {
12376
- const heap = searchLayer(
12377
- state,
12378
- entry.vector,
12379
- entry.magnitude,
12380
- currentEPs,
12381
- state.efCons,
12382
- layer,
12383
- metric,
12384
- false,
12385
- insertDistFn
12386
- );
12387
- const candidates = heap.toSortedArray().reverse();
12388
- const mc = maxConns(state, layer);
12389
- const neighbors = selectNeighborsHeuristic(state, ord, candidates, mc, layer, metric);
12390
- for (const neighbor of neighbors) {
12391
- addNeighbor(state.adjacency, ord, layer, neighbor.ord);
12392
- if (layer <= nodeMaxLayer(state, neighbor.ord)) {
12393
- addNeighbor(state.adjacency, neighbor.ord, layer, ord);
12394
- pruneConnections(state, neighbor.ord, layer, metric);
12405
+ searchLayer(state, entry.vector, entry.magnitude, state.efCons, layer, metric, false, insertDistFn, candidates);
12406
+ selectNeighborsHeuristic(state, candidates, maxConns(state, layer), metric, selected);
12407
+ for (let i = 0; i < selected.size; i++) {
12408
+ const neighborOrd = selected.ords[i];
12409
+ addNeighbor(state.adjacency, ord, layer, neighborOrd);
12410
+ if (layer <= nodeMaxLayer(state, neighborOrd)) {
12411
+ addNeighbor(state.adjacency, neighborOrd, layer, ord);
12412
+ pruneConnections(state, neighborOrd, layer, metric);
12395
12413
  }
12396
12414
  }
12397
- if (candidates.length > 0) {
12398
- currentEPs = candidates.map((n) => n.ord);
12415
+ if (candidates.size > 0) {
12416
+ setEntryPointsFromList(workspace, candidates);
12399
12417
  }
12400
12418
  }
12401
12419
  if (l > state.topLayer) {
@@ -12425,17 +12443,18 @@ function removeNodeEager(state, ord, excludeOrds) {
12425
12443
  candidateOrds.add(otherOrd);
12426
12444
  }
12427
12445
  }
12428
- const candidates = [];
12446
+ const candidates = state.workspace.repairCandidates;
12447
+ candidates.size = 0;
12429
12448
  for (const candOrd of candidateOrds) {
12430
12449
  const dist = nodeDistanceByOrd(state, neighborOrd, candOrd, metric);
12431
12450
  if (dist === Number.POSITIVE_INFINITY) continue;
12432
- candidates.push({ ord: candOrd, distance: dist });
12451
+ appendToList(candidates, candOrd, dist);
12433
12452
  }
12434
- const selected = selectNeighborsHeuristic(state, neighborOrd, candidates, mc, layer, metric);
12435
- const newConns = [];
12436
- for (const s of selected) addConnection(newConns, s.ord);
12437
- replaceNeighbors(state.adjacency, neighborOrd, layer, newConns);
12438
- for (const newConnOrd of newConns) {
12453
+ const selected = state.workspace.repairSelection;
12454
+ selectNeighborsHeuristic(state, candidates, mc, metric, selected);
12455
+ replaceNeighbors(state.adjacency, neighborOrd, layer, selected.ords, selected.size);
12456
+ for (let i = 0; i < selected.size; i++) {
12457
+ const newConnOrd = selected.ords[i];
12439
12458
  if (layer <= nodeMaxLayer(state, newConnOrd)) {
12440
12459
  addNeighbor(state.adjacency, newConnOrd, layer, neighborOrd);
12441
12460
  pruneConnections(state, newConnOrd, layer, metric);
@@ -12448,11 +12467,11 @@ function removeNodeEager(state, ord, excludeOrds) {
12448
12467
  clearTombstone(state, ord);
12449
12468
  if (state.entryPointOrd === ord) {
12450
12469
  if (state.nodeCount === 0) {
12451
- state.entryPointOrd = -1;
12452
- state.topLayer = -1;
12470
+ setEntryPoint(state, -1);
12453
12471
  return;
12454
12472
  }
12455
- reassignEntryPoint(state);
12473
+ const live = highestNode(state, false);
12474
+ setEntryPoint(state, live === -1 ? highestNode(state, true) : live);
12456
12475
  }
12457
12476
  }
12458
12477
  function markTombstone(state, docId) {
@@ -12463,24 +12482,7 @@ function markTombstone(state, docId) {
12463
12482
  state.tombstoneCount++;
12464
12483
  }
12465
12484
  if (state.entryPointOrd === ord) {
12466
- let bestOrd = -1;
12467
- let bestLayer = -1;
12468
- for (let o = 0; o < state.adjacency.slots; o++) {
12469
- const level = nodeMaxLayer(state, o);
12470
- if (level === -1) continue;
12471
- if (state.tombstones[o] === 1) continue;
12472
- if (level > bestLayer) {
12473
- bestLayer = level;
12474
- bestOrd = o;
12475
- }
12476
- }
12477
- if (bestOrd !== -1) {
12478
- state.entryPointOrd = bestOrd;
12479
- state.topLayer = bestLayer;
12480
- } else {
12481
- state.entryPointOrd = -1;
12482
- state.topLayer = -1;
12483
- }
12485
+ setEntryPoint(state, highestNode(state, false));
12484
12486
  }
12485
12487
  }
12486
12488
  function compactTombstones(state) {
@@ -12593,7 +12595,7 @@ function deserializeGraph(state, data) {
12593
12595
  for (const node of nodes) {
12594
12596
  createNode(state.adjacency, node.ord, node.maxLayer);
12595
12597
  for (const [layer, neighbors] of node.layers) {
12596
- replaceNeighbors(state.adjacency, node.ord, layer, neighbors);
12598
+ replaceNeighbors(state.adjacency, node.ord, layer, neighbors, neighbors.length);
12597
12599
  }
12598
12600
  state.nodeCount++;
12599
12601
  }
@@ -12660,20 +12662,20 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
12660
12662
  const metric = searchMetric;
12661
12663
  distFn = (ord) => store.distanceFromArena(arenaQuery, ord, metric);
12662
12664
  }
12663
- let currentEPs = [state.entryPointOrd];
12665
+ const workspace = state.workspace;
12666
+ const candidates = workspace.traversal;
12667
+ setSingleEntryPoint(workspace, state.entryPointOrd);
12664
12668
  for (let layer = state.topLayer; layer >= 1; layer--) {
12665
- const heap = searchLayer(state, query, qMag, currentEPs, 1, layer, searchMetric, true, distFn);
12666
- const nearest = nearestFromHeap(heap);
12667
- if (nearest) {
12668
- currentEPs = [nearest.ord];
12669
+ searchLayer(state, query, qMag, 1, layer, searchMetric, true, distFn, candidates);
12670
+ if (candidates.size > 0) {
12671
+ setSingleEntryPoint(workspace, candidates.ords[0]);
12669
12672
  }
12670
12673
  }
12671
- const candidateHeap = searchLayer(state, query, qMag, currentEPs, ef, 0, searchMetric, true, distFn);
12672
- const candidateArray = candidateHeap.toSortedArray().reverse();
12674
+ searchLayer(state, query, qMag, ef, 0, searchMetric, true, distFn, candidates);
12673
12675
  if (useQuantized) {
12674
12676
  return rerankWithFullPrecision(
12675
12677
  state,
12676
- candidateArray,
12678
+ candidates,
12677
12679
  query,
12678
12680
  qMag,
12679
12681
  arenaQuery,
@@ -12685,33 +12687,35 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
12685
12687
  );
12686
12688
  }
12687
12689
  const hits = [];
12688
- for (const cand of candidateArray) {
12689
- if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
12690
- const score = toScore(cand.distance, searchMetric);
12690
+ for (let i = 0; i < candidates.size; i++) {
12691
+ const ord = candidates.ords[i];
12692
+ if (filter && !ordinalFilterHas(filter, ord)) continue;
12693
+ const score = toScore(candidates.distances[i], searchMetric);
12691
12694
  if (score < minSimilarity) continue;
12692
- if (!hasDocument(cand.ord)) continue;
12693
- hits.push({ ord: cand.ord, score });
12695
+ if (!hasDocument(ord)) continue;
12696
+ hits.push({ ord, score });
12694
12697
  }
12695
12698
  return hits;
12696
12699
  }
12697
12700
  function rerankWithFullPrecision(state, candidates, query, qMag, arenaQuery, k, metric, minSimilarity, filter, hasDocument) {
12698
12701
  const reranked = [];
12699
12702
  const rerankLimit = Math.max(k * SQ8_OVERSELECTION_FACTOR, 10);
12700
- for (const cand of candidates) {
12701
- if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
12703
+ for (let i = 0; i < candidates.size; i++) {
12704
+ const ord = candidates.ords[i];
12705
+ if (filter && !ordinalFilterHas(filter, ord)) continue;
12702
12706
  let fullDistance;
12703
12707
  if (arenaQuery) {
12704
- fullDistance = state.store.distanceFromArena(arenaQuery, cand.ord, metric);
12708
+ fullDistance = state.store.distanceFromArena(arenaQuery, ord, metric);
12705
12709
  if (fullDistance === Number.POSITIVE_INFINITY) continue;
12706
12710
  } else {
12707
- const entry = entryForOrd(state, cand.ord);
12711
+ const entry = entryForOrd(state, ord);
12708
12712
  if (!entry) continue;
12709
12713
  fullDistance = toDistance(query, entry.vector, qMag, entry.magnitude, metric);
12710
12714
  }
12711
12715
  const score = toScore(fullDistance, metric);
12712
12716
  if (score < minSimilarity) continue;
12713
- if (!hasDocument(cand.ord)) continue;
12714
- reranked.push({ ord: cand.ord, score });
12717
+ if (!hasDocument(ord)) continue;
12718
+ reranked.push({ ord, score });
12715
12719
  if (reranked.length >= rerankLimit) break;
12716
12720
  }
12717
12721
  return reranked;
@@ -12759,7 +12763,8 @@ function createHNSWIndex(dimension, store, config, quantizer) {
12759
12763
  visited: new Uint32Array(0),
12760
12764
  visitStamp: 0,
12761
12765
  entryPointOrd: -1,
12762
- topLayer: -1
12766
+ topLayer: -1,
12767
+ workspace: createHNSWWorkspace()
12763
12768
  };
12764
12769
  function clear() {
12765
12770
  resetAdjacency(state.adjacency);
@@ -13046,9 +13051,54 @@ var ESTIMATED_MS_PER_TOMBSTONE = 0.05;
13046
13051
  var ESTIMATED_MS_PER_VECTOR_REBUILD = 0.15;
13047
13052
  var WORKER_BUILD_SIZE_THRESHOLD = 5e3;
13048
13053
  var WORKER_COPY_MIN_VECTORS = 1024;
13054
+ var BUILD_CHUNK_SIZE = 100;
13055
+ var REBUILD_REMOVED_RATIO = 0.2;
13049
13056
  function liveSize(state) {
13050
13057
  return state.store.size - state.tombstones.size;
13051
13058
  }
13059
+ function adoptGraph(state, graph) {
13060
+ if (graph === null && state.hnsw !== null) {
13061
+ state.hnsw.clear();
13062
+ }
13063
+ state.hnsw = graph;
13064
+ state.compactedNodeCount = 0;
13065
+ if (graph === null) return;
13066
+ for (const docId of state.tombstones) {
13067
+ if (graph.has(docId)) {
13068
+ graph.markTombstone(docId);
13069
+ }
13070
+ }
13071
+ }
13072
+ async function insertIntoGraph(state, graph, docIds, admit, inserted) {
13073
+ let count = 0;
13074
+ for (const docId of docIds) {
13075
+ if (state.disposed) return false;
13076
+ if (!admit(docId)) continue;
13077
+ graph.insertNode(docId);
13078
+ state.buffer.delete(docId);
13079
+ inserted?.(docId);
13080
+ count += 1;
13081
+ if (count % BUILD_CHUNK_SIZE === 0) {
13082
+ await yieldToEventLoop();
13083
+ }
13084
+ }
13085
+ return !state.disposed;
13086
+ }
13087
+ async function buildGraphFromStore(state) {
13088
+ const graph = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
13089
+ const completed = await insertIntoGraph(state, graph, allLiveDocIds(state), () => true);
13090
+ if (completed) {
13091
+ adoptGraph(state, graph);
13092
+ }
13093
+ }
13094
+ function graphNeedsRebuild(state) {
13095
+ const graph = state.hnsw;
13096
+ if (graph === null) return false;
13097
+ const removed = graph.tombstoneCount + state.compactedNodeCount;
13098
+ const held = graph.size + removed;
13099
+ if (held === 0) return false;
13100
+ return removed / held > REBUILD_REMOVED_RATIO;
13101
+ }
13052
13102
  function yieldToEventLoop() {
13053
13103
  return new Promise((resolve) => setTimeout(resolve, 0));
13054
13104
  }
@@ -13692,7 +13742,7 @@ function captureCloneSnapshot(state) {
13692
13742
  };
13693
13743
  }
13694
13744
  function scheduleWorkerCopyLoad(state) {
13695
- if (state.disposed || state.workerCopyLoading) return;
13745
+ if (state.disposed || state.workerCopyLoading || state.building) return;
13696
13746
  if (state.workerCopyHandle !== null) return;
13697
13747
  if (!state.hnsw || state.buffer.size > 0) return;
13698
13748
  if (liveSize(state) < WORKER_COPY_MIN_VECTORS) return;
@@ -13773,7 +13823,7 @@ async function searchViaWorkerCopies(state, query, k, metric, minSimilarity, efS
13773
13823
  }
13774
13824
 
13775
13825
  // src/vector/vector-index/build.ts
13776
- async function tryWorkerBuild(state, liveDocIds, bufferSnapshot) {
13826
+ async function tryWorkerBuild(state, liveDocIds) {
13777
13827
  const vectorData = new Float32Array(liveDocIds.length * state.dimension);
13778
13828
  const validDocIds = [];
13779
13829
  let offset = 0;
@@ -13782,6 +13832,7 @@ async function tryWorkerBuild(state, liveDocIds, bufferSnapshot) {
13782
13832
  if (!entry || state.tombstones.has(docId)) continue;
13783
13833
  vectorData.set(entry.vector, offset);
13784
13834
  validDocIds.push(docId);
13835
+ state.buffer.delete(docId);
13785
13836
  offset += state.dimension;
13786
13837
  }
13787
13838
  if (validDocIds.length === 0) return false;
@@ -13793,64 +13844,77 @@ async function tryWorkerBuild(state, liveDocIds, bufferSnapshot) {
13793
13844
  };
13794
13845
  const timeoutMs = Math.max(1e4, liveDocIds.length * 2);
13795
13846
  const outcome = await dispatchWorkerBuild(validDocIds, packedData, state.dimension, resolvedConfig, timeoutMs, true);
13796
- if (!outcome.ok) return false;
13847
+ if (!outcome.ok || state.disposed) return false;
13797
13848
  const newHnsw = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
13798
13849
  newHnsw.deserialize(outcome.graph);
13799
- state.hnsw = newHnsw;
13800
- for (const docId of state.tombstones) {
13801
- if (newHnsw.has(docId)) {
13802
- newHnsw.markTombstone(docId);
13803
- }
13850
+ adoptGraph(state, newHnsw);
13851
+ return true;
13852
+ }
13853
+ async function promoteToGraph(state) {
13854
+ const liveDocIds = Array.from(allLiveDocIds(state));
13855
+ if (liveDocIds.length === 0) return;
13856
+ if (state.sq8) {
13857
+ calibrateAndQuantizeAll(state);
13804
13858
  }
13805
- for (const docId of bufferSnapshot) {
13806
- state.buffer.delete(docId);
13859
+ if (liveDocIds.length > WORKER_BUILD_SIZE_THRESHOLD) {
13860
+ const built = await tryWorkerBuild(state, liveDocIds);
13861
+ if (built) return;
13862
+ }
13863
+ await buildGraphFromStore(state);
13864
+ }
13865
+ function quantizeIncoming(state, docId) {
13866
+ const sq8 = state.sq8;
13867
+ if (sq8 === null) return false;
13868
+ if (!sq8.isCalibrated()) {
13869
+ calibrateAndQuantizeAll(state);
13870
+ return false;
13871
+ }
13872
+ const entry = state.store.get(docId);
13873
+ if (entry === void 0) return false;
13874
+ const outsideBounds = sq8.needsRecalibration(entry.vector);
13875
+ sq8.quantize(docId, entry.vector);
13876
+ return outsideBounds;
13877
+ }
13878
+ async function growGraph(state, graph, bufferSnapshot) {
13879
+ let outsideCalibration = false;
13880
+ const admit = (docId) => {
13881
+ if (state.hnsw !== graph) return false;
13882
+ if (state.tombstones.has(docId) || !state.store.has(docId)) {
13883
+ state.buffer.delete(docId);
13884
+ return false;
13885
+ }
13886
+ return true;
13887
+ };
13888
+ const inserted = (docId) => {
13889
+ if (quantizeIncoming(state, docId)) {
13890
+ outsideCalibration = true;
13891
+ }
13892
+ };
13893
+ await insertIntoGraph(state, graph, bufferSnapshot, admit, inserted);
13894
+ if (outsideCalibration) {
13895
+ recalibrateFromStore(state);
13807
13896
  }
13808
- return true;
13809
13897
  }
13810
13898
  function triggerBuild(state) {
13811
13899
  if (state.building) return;
13812
13900
  invalidateWorkerCopies(state);
13813
13901
  state.building = true;
13814
- const liveDocIds = [];
13815
- for (const [docId] of state.store.entries()) {
13816
- if (state.tombstones.has(docId)) continue;
13817
- liveDocIds.push(docId);
13818
- }
13819
13902
  const bufferSnapshot = new Set(state.buffer);
13903
+ const existingGraph = state.hnsw;
13820
13904
  const buildPromise = (async () => {
13821
13905
  try {
13822
- if (liveDocIds.length === 0 || state.disposed) return;
13823
- if (state.sq8) {
13824
- calibrateAndQuantizeAll(state);
13825
- }
13826
- if (liveDocIds.length > WORKER_BUILD_SIZE_THRESHOLD) {
13827
- const workerResult = await tryWorkerBuild(state, liveDocIds, bufferSnapshot);
13828
- if (workerResult) return;
13829
- }
13830
- const newHnsw = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
13831
- const CHUNK_SIZE2 = 100;
13832
- for (let i = 0; i < liveDocIds.length; i++) {
13833
- const docId = liveDocIds[i];
13834
- if (!state.store.has(docId) || state.tombstones.has(docId)) continue;
13835
- newHnsw.insertNode(docId);
13836
- if ((i + 1) % CHUNK_SIZE2 === 0) {
13837
- if (state.disposed) return;
13838
- await yieldToEventLoop();
13839
- }
13840
- }
13841
13906
  if (state.disposed) return;
13842
- state.hnsw = newHnsw;
13843
- for (const docId of state.tombstones) {
13844
- if (newHnsw.has(docId)) {
13845
- newHnsw.markTombstone(docId);
13846
- }
13847
- }
13848
- for (const docId of bufferSnapshot) {
13849
- state.buffer.delete(docId);
13907
+ if (existingGraph !== null) {
13908
+ await growGraph(state, existingGraph, bufferSnapshot);
13909
+ return;
13850
13910
  }
13911
+ await promoteToGraph(state);
13851
13912
  } finally {
13852
13913
  state.building = false;
13853
13914
  state.pendingBuild = null;
13915
+ if (state.buffer.size > 0) {
13916
+ scheduleBuild(state);
13917
+ }
13854
13918
  }
13855
13919
  })();
13856
13920
  state.pendingBuild = buildPromise;
@@ -13870,6 +13934,7 @@ function scheduleBuild(state) {
13870
13934
  function compact(state) {
13871
13935
  if (state.tombstones.size === 0) return;
13872
13936
  if (state.hnsw) {
13937
+ state.compactedNodeCount += state.hnsw.tombstoneCount;
13873
13938
  state.hnsw.compactTombstones();
13874
13939
  }
13875
13940
  for (const docId of state.tombstones) {
@@ -13884,43 +13949,48 @@ function compact(state) {
13884
13949
  recalibrateFromStore(state);
13885
13950
  }
13886
13951
  }
13887
- async function optimize(state) {
13888
- if (state.pendingBuild) {
13889
- await state.pendingBuild;
13890
- }
13952
+ async function insertMissing(state, graph) {
13953
+ const missingOrReplaced = (docId) => !graph.has(docId) || state.buffer.has(docId);
13954
+ await insertIntoGraph(state, graph, allLiveDocIds(state), missingOrReplaced);
13955
+ }
13956
+ async function foldIntoGraph(state) {
13957
+ const rebuildNeeded = graphNeedsRebuild(state);
13958
+ const compactRecalibrates = state.tombstones.size > 0 && state.sq8?.isCalibrated() === true;
13891
13959
  compact(state);
13892
- const live = liveSize(state);
13893
- if (live === 0) {
13894
- if (state.hnsw) {
13895
- state.hnsw.clear();
13896
- state.hnsw = null;
13897
- }
13960
+ if (liveSize(state) === 0) {
13961
+ adoptGraph(state, null);
13898
13962
  state.buffer.clear();
13899
13963
  if (state.sq8) {
13900
13964
  state.sq8.clear();
13901
13965
  }
13902
13966
  return;
13903
13967
  }
13904
- if (state.sq8) {
13905
- calibrateAndQuantizeAll(state);
13968
+ const graph = state.hnsw;
13969
+ if (graph === null || rebuildNeeded) {
13970
+ await buildGraphFromStore(state);
13971
+ } else {
13972
+ await insertMissing(state, graph);
13906
13973
  }
13907
- const newHnsw = createHNSWIndex(state.dimension, state.store, state.hnswConfig, state.sq8 ?? void 0);
13908
- const liveDocIds = [];
13909
- for (const [docId] of state.store.entries()) {
13910
- if (state.tombstones.has(docId)) continue;
13911
- liveDocIds.push(docId);
13974
+ if (state.sq8 && state.store.size > 0 && !compactRecalibrates) {
13975
+ recalibrateFromStore(state);
13912
13976
  }
13913
- const CHUNK_SIZE2 = 100;
13914
- for (let i = 0; i < liveDocIds.length; i++) {
13915
- newHnsw.insertNode(liveDocIds[i]);
13916
- if ((i + 1) % CHUNK_SIZE2 === 0) {
13917
- await yieldToEventLoop();
13918
- }
13977
+ }
13978
+ async function optimize(state) {
13979
+ while (state.pendingBuild) {
13980
+ await state.pendingBuild;
13919
13981
  }
13920
- state.hnsw = newHnsw;
13921
- state.buffer.clear();
13922
- if (state.sq8 && state.store.size > 0) {
13923
- recalibrateFromStore(state);
13982
+ if (state.disposed) return;
13983
+ state.building = true;
13984
+ const work = foldIntoGraph(state);
13985
+ state.pendingBuild = work;
13986
+ try {
13987
+ await work;
13988
+ } finally {
13989
+ state.building = false;
13990
+ state.pendingBuild = null;
13991
+ if (state.buffer.size > 0) {
13992
+ scheduleBuild(state);
13993
+ }
13924
13994
  }
13925
13995
  }
13926
13996
  function maintenanceStatus(state) {
@@ -13928,12 +13998,13 @@ function maintenanceStatus(state) {
13928
13998
  const tombstoneRatio = storeSize > 0 ? state.tombstones.size / storeSize : 0;
13929
13999
  const graphCount = state.hnsw ? 1 : 0;
13930
14000
  const estimatedCompactMs = Math.round(state.tombstones.size * ESTIMATED_MS_PER_TOMBSTONE * state.dimensionScale);
13931
- const estimatedOptimizeMs = Math.round(storeSize * ESTIMATED_MS_PER_VECTOR_REBUILD * state.dimensionScale);
14001
+ const optimizeVectorCount = state.hnsw === null || graphNeedsRebuild(state) ? storeSize : state.buffer.size;
14002
+ const estimatedOptimizeMs = Math.round(optimizeVectorCount * ESTIMATED_MS_PER_VECTOR_REBUILD * state.dimensionScale);
13932
14003
  return {
13933
14004
  tombstoneRatio,
13934
14005
  graphCount,
13935
14006
  bufferSize: state.buffer.size,
13936
- building: state.building,
14007
+ building: state.building || state.buildScheduled,
13937
14008
  estimatedCompactMs,
13938
14009
  estimatedOptimizeMs
13939
14010
  };
@@ -14024,10 +14095,7 @@ function deserialize(state, payload) {
14024
14095
  state.store.clear();
14025
14096
  state.tombstones.clear();
14026
14097
  state.buffer.clear();
14027
- if (state.hnsw) {
14028
- state.hnsw.clear();
14029
- state.hnsw = null;
14030
- }
14098
+ adoptGraph(state, null);
14031
14099
  if (state.sq8) {
14032
14100
  state.sq8.clear();
14033
14101
  }
@@ -14052,7 +14120,7 @@ function deserialize(state, payload) {
14052
14120
  state.sq8 ?? void 0
14053
14121
  );
14054
14122
  restoredHnsw.deserialize(graphData);
14055
- state.hnsw = restoredHnsw;
14123
+ adoptGraph(state, restoredHnsw);
14056
14124
  for (let i = 1; i < payload.graphs.length; i++) {
14057
14125
  const additionalGraph = payload.graphs[i];
14058
14126
  for (const [nodeDocId] of additionalGraph.nodes) {
@@ -14068,8 +14136,7 @@ function deserialize(state, payload) {
14068
14136
  }
14069
14137
  }
14070
14138
  if (state.buffer.size > restoredHnsw.size) {
14071
- state.hnsw.clear();
14072
- state.hnsw = null;
14139
+ adoptGraph(state, null);
14073
14140
  state.buffer.clear();
14074
14141
  for (const [docId] of state.store.entries()) {
14075
14142
  if (state.tombstones.has(docId)) continue;
@@ -14084,6 +14151,87 @@ function deserialize(state, payload) {
14084
14151
  }
14085
14152
  }
14086
14153
 
14154
+ // src/core/heap.ts
14155
+ function createMaxHeap(compare) {
14156
+ return createHeap2((a, b) => compare(b, a));
14157
+ }
14158
+ function createBoundedMaxHeap(compare, capacity) {
14159
+ const heap = createMaxHeap(compare);
14160
+ const innerPush = heap.push;
14161
+ return {
14162
+ push(item) {
14163
+ if (heap.size < capacity) {
14164
+ innerPush(item);
14165
+ return;
14166
+ }
14167
+ const top = heap.peek();
14168
+ if (top !== void 0 && compare(item, top) < 0) {
14169
+ heap.pop();
14170
+ innerPush(item);
14171
+ }
14172
+ },
14173
+ pop: heap.pop,
14174
+ peek: heap.peek,
14175
+ get size() {
14176
+ return heap.size;
14177
+ },
14178
+ toSortedArray: heap.toSortedArray
14179
+ };
14180
+ }
14181
+ function createHeap2(compare) {
14182
+ const items = [];
14183
+ function siftUp(idx) {
14184
+ while (idx > 0) {
14185
+ const parent = idx - 1 >> 1;
14186
+ if (compare(items[idx], items[parent]) >= 0) break;
14187
+ const tmp = items[idx];
14188
+ items[idx] = items[parent];
14189
+ items[parent] = tmp;
14190
+ idx = parent;
14191
+ }
14192
+ }
14193
+ function siftDown2(idx) {
14194
+ const len = items.length;
14195
+ while (true) {
14196
+ let smallest = idx;
14197
+ const left = 2 * idx + 1;
14198
+ const right = 2 * idx + 2;
14199
+ if (left < len && compare(items[left], items[smallest]) < 0) smallest = left;
14200
+ if (right < len && compare(items[right], items[smallest]) < 0) smallest = right;
14201
+ if (smallest === idx) break;
14202
+ const tmp = items[idx];
14203
+ items[idx] = items[smallest];
14204
+ items[smallest] = tmp;
14205
+ idx = smallest;
14206
+ }
14207
+ }
14208
+ return {
14209
+ push(item) {
14210
+ items.push(item);
14211
+ siftUp(items.length - 1);
14212
+ },
14213
+ pop() {
14214
+ if (items.length === 0) return void 0;
14215
+ const top = items[0];
14216
+ const last = items.pop();
14217
+ if (items.length > 0) {
14218
+ items[0] = last;
14219
+ siftDown2(0);
14220
+ }
14221
+ return top;
14222
+ },
14223
+ peek() {
14224
+ return items[0];
14225
+ },
14226
+ get size() {
14227
+ return items.length;
14228
+ },
14229
+ toSortedArray() {
14230
+ return [...items].sort(compare);
14231
+ }
14232
+ };
14233
+ }
14234
+
14087
14235
  // src/vector/vector-index/search.ts
14088
14236
  function* bufferCandidates(state, filter) {
14089
14237
  for (const docId of state.buffer) {
@@ -14234,6 +14382,7 @@ function createVectorIndex(fieldName, dimension, config) {
14234
14382
  buffer: /* @__PURE__ */ new Set(),
14235
14383
  sq8: quantizationMode === "sq8" ? createScalarQuantizer(dimension, store) : null,
14236
14384
  hnsw: null,
14385
+ compactedNodeCount: 0,
14237
14386
  building: false,
14238
14387
  buildScheduled: false,
14239
14388
  pendingBuild: null,