@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.
@@ -1,4 +1,4 @@
1
- import { createArenaSimd } from './chunk-WDL632CN.mjs';
1
+ import { createArenaSimd } from './chunk-DFZDV5FV.mjs';
2
2
 
3
3
  // src/vector/scalar-quantization-math.ts
4
4
  function deriveSq8Constants(alpha, offset, dimensions) {
@@ -536,90 +536,6 @@ function createVectorStore() {
536
536
  };
537
537
  }
538
538
 
539
- // src/core/heap.ts
540
- function createMinHeap(compare) {
541
- return createHeap(compare);
542
- }
543
- function createMaxHeap(compare) {
544
- return createHeap((a, b) => compare(b, a));
545
- }
546
- function createBoundedMaxHeap(compare, capacity) {
547
- const heap = createMaxHeap(compare);
548
- const innerPush = heap.push;
549
- return {
550
- push(item) {
551
- if (heap.size < capacity) {
552
- innerPush(item);
553
- return;
554
- }
555
- const top = heap.peek();
556
- if (top !== void 0 && compare(item, top) < 0) {
557
- heap.pop();
558
- innerPush(item);
559
- }
560
- },
561
- pop: heap.pop,
562
- peek: heap.peek,
563
- get size() {
564
- return heap.size;
565
- },
566
- toSortedArray: heap.toSortedArray
567
- };
568
- }
569
- function createHeap(compare) {
570
- const items = [];
571
- function siftUp(idx) {
572
- while (idx > 0) {
573
- const parent = idx - 1 >> 1;
574
- if (compare(items[idx], items[parent]) >= 0) break;
575
- const tmp = items[idx];
576
- items[idx] = items[parent];
577
- items[parent] = tmp;
578
- idx = parent;
579
- }
580
- }
581
- function siftDown(idx) {
582
- const len = items.length;
583
- while (true) {
584
- let smallest = idx;
585
- const left = 2 * idx + 1;
586
- const right = 2 * idx + 2;
587
- if (left < len && compare(items[left], items[smallest]) < 0) smallest = left;
588
- if (right < len && compare(items[right], items[smallest]) < 0) smallest = right;
589
- if (smallest === idx) break;
590
- const tmp = items[idx];
591
- items[idx] = items[smallest];
592
- items[smallest] = tmp;
593
- idx = smallest;
594
- }
595
- }
596
- return {
597
- push(item) {
598
- items.push(item);
599
- siftUp(items.length - 1);
600
- },
601
- pop() {
602
- if (items.length === 0) return void 0;
603
- const top = items[0];
604
- const last = items.pop();
605
- if (items.length > 0) {
606
- items[0] = last;
607
- siftDown(0);
608
- }
609
- return top;
610
- },
611
- peek() {
612
- return items[0];
613
- },
614
- get size() {
615
- return items.length;
616
- },
617
- toSortedArray() {
618
- return [...items].sort(compare);
619
- }
620
- };
621
- }
622
-
623
539
  // src/vector/hnsw/adjacency.ts
624
540
  var MAX_LAYER_CAP = 32;
625
541
  var MAX_M = 512;
@@ -770,12 +686,12 @@ function removeNeighbor(adj, ord, layer, neighborOrd) {
770
686
  return;
771
687
  }
772
688
  }
773
- function replaceNeighbors(adj, ord, layer, neighbors) {
689
+ function replaceNeighbors(adj, ord, layer, neighbors, count) {
774
690
  const base = layerBase(adj, ord, layer);
775
691
  if (base === ABSENT) return;
776
692
  const array = layerArray(adj, layer);
777
693
  const stride = layer === 0 ? adj.level0Stride : adj.upperStride;
778
- const limit = Math.min(neighbors.length, stride - 1);
694
+ const limit = Math.min(count, stride - 1);
779
695
  for (let i = 0; i < limit; i++) {
780
696
  array[base + i + 1] = neighbors[i];
781
697
  }
@@ -859,8 +775,6 @@ function toScore(distance, metric) {
859
775
  return 1 / (1 + distance);
860
776
  }
861
777
  }
862
- var distanceAsc = (a, b) => a.distance - b.distance;
863
- var distanceDesc = (a, b) => b.distance - a.distance;
864
778
  function randomLevel(mL) {
865
779
  let u = Math.random();
866
780
  if (u === 0) u = Number.MIN_VALUE;
@@ -887,37 +801,168 @@ function addConnection(connections, ord) {
887
801
  connections.push(ord);
888
802
  }
889
803
 
804
+ // src/vector/hnsw/workspace.ts
805
+ var INITIAL_LIST_CAPACITY = 64;
806
+ function createList(capacity) {
807
+ return { ords: new Int32Array(capacity), distances: new Float64Array(capacity), size: 0 };
808
+ }
809
+ function createHeap(greatestFirst) {
810
+ return { ...createList(INITIAL_LIST_CAPACITY), greatestFirst, topOrd: -1, topDistance: 0 };
811
+ }
812
+ function createHNSWWorkspace() {
813
+ return {
814
+ frontier: createHeap(false),
815
+ found: createHeap(true),
816
+ entryPoints: new Int32Array(INITIAL_LIST_CAPACITY),
817
+ entryPointCount: 0,
818
+ traversal: createList(INITIAL_LIST_CAPACITY),
819
+ working: createList(INITIAL_LIST_CAPACITY),
820
+ insertSelection: createList(INITIAL_LIST_CAPACITY),
821
+ pruneCandidates: createList(INITIAL_LIST_CAPACITY),
822
+ pruneSelection: createList(INITIAL_LIST_CAPACITY),
823
+ repairCandidates: createList(INITIAL_LIST_CAPACITY),
824
+ repairSelection: createList(INITIAL_LIST_CAPACITY)
825
+ };
826
+ }
827
+ function ensureListCapacity(list, needed) {
828
+ if (needed <= list.ords.length) return;
829
+ let capacity = list.ords.length === 0 ? INITIAL_LIST_CAPACITY : list.ords.length;
830
+ while (capacity < needed) capacity *= 2;
831
+ const ords = new Int32Array(capacity);
832
+ ords.set(list.ords);
833
+ list.ords = ords;
834
+ const distances = new Float64Array(capacity);
835
+ distances.set(list.distances);
836
+ list.distances = distances;
837
+ }
838
+ function appendToList(list, ord, distance) {
839
+ ensureListCapacity(list, list.size + 1);
840
+ list.ords[list.size] = ord;
841
+ list.distances[list.size] = distance;
842
+ list.size += 1;
843
+ }
844
+ function copyList(source, target) {
845
+ ensureListCapacity(target, source.size);
846
+ target.ords.set(source.ords.subarray(0, source.size));
847
+ target.distances.set(source.distances.subarray(0, source.size));
848
+ target.size = source.size;
849
+ }
850
+ function sortListByDistance(list) {
851
+ const { ords, distances } = list;
852
+ for (let i = 1; i < list.size; i++) {
853
+ const ord = ords[i];
854
+ const distance = distances[i];
855
+ let j = i - 1;
856
+ while (j >= 0 && distances[j] > distance) {
857
+ distances[j + 1] = distances[j];
858
+ ords[j + 1] = ords[j];
859
+ j -= 1;
860
+ }
861
+ distances[j + 1] = distance;
862
+ ords[j + 1] = ord;
863
+ }
864
+ }
865
+ function heapPrecedes(heap, a, b) {
866
+ return heap.greatestFirst ? heap.distances[a] > heap.distances[b] : heap.distances[a] < heap.distances[b];
867
+ }
868
+ function swapHeapEntries(heap, a, b) {
869
+ const ord = heap.ords[a];
870
+ heap.ords[a] = heap.ords[b];
871
+ heap.ords[b] = ord;
872
+ const distance = heap.distances[a];
873
+ heap.distances[a] = heap.distances[b];
874
+ heap.distances[b] = distance;
875
+ }
876
+ function pushHeap(heap, ord, distance) {
877
+ appendToList(heap, ord, distance);
878
+ let index = heap.size - 1;
879
+ while (index > 0) {
880
+ const parent = index - 1 >> 1;
881
+ if (!heapPrecedes(heap, index, parent)) break;
882
+ swapHeapEntries(heap, index, parent);
883
+ index = parent;
884
+ }
885
+ }
886
+ function popHeap(heap) {
887
+ if (heap.size === 0) return false;
888
+ heap.topOrd = heap.ords[0];
889
+ heap.topDistance = heap.distances[0];
890
+ heap.size -= 1;
891
+ if (heap.size > 0) {
892
+ heap.ords[0] = heap.ords[heap.size];
893
+ heap.distances[0] = heap.distances[heap.size];
894
+ let index = 0;
895
+ for (; ; ) {
896
+ const left = 2 * index + 1;
897
+ const right = left + 1;
898
+ let first = index;
899
+ if (left < heap.size && heapPrecedes(heap, left, first)) first = left;
900
+ if (right < heap.size && heapPrecedes(heap, right, first)) first = right;
901
+ if (first === index) break;
902
+ swapHeapEntries(heap, index, first);
903
+ index = first;
904
+ }
905
+ }
906
+ return true;
907
+ }
908
+ function resetHeap(heap) {
909
+ heap.size = 0;
910
+ }
911
+ function drainHeapNearestFirst(heap, list) {
912
+ const count = heap.size;
913
+ ensureListCapacity(list, count);
914
+ list.size = count;
915
+ for (let i = count - 1; i >= 0; i--) {
916
+ if (!popHeap(heap)) break;
917
+ list.ords[i] = heap.topOrd;
918
+ list.distances[i] = heap.topDistance;
919
+ }
920
+ }
921
+ function setSingleEntryPoint(workspace, ord) {
922
+ workspace.entryPoints[0] = ord;
923
+ workspace.entryPointCount = 1;
924
+ }
925
+ function setEntryPointsFromList(workspace, list) {
926
+ if (list.size > workspace.entryPoints.length) {
927
+ let capacity = workspace.entryPoints.length;
928
+ while (capacity < list.size) capacity *= 2;
929
+ workspace.entryPoints = new Int32Array(capacity);
930
+ }
931
+ workspace.entryPoints.set(list.ords.subarray(0, list.size));
932
+ workspace.entryPointCount = list.size;
933
+ }
934
+
890
935
  // src/vector/hnsw/graph-ops.ts
891
- function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones, distFn) {
936
+ function searchLayer(state, qVec, qMag, ef, layer, metric, skipTombstones, distFn, results) {
892
937
  const getDistance = distFn ?? ((ord) => queryDistanceByOrd(state, qVec, qMag, ord, metric));
938
+ const workspace = state.workspace;
939
+ const frontier = workspace.frontier;
940
+ const found = workspace.found;
893
941
  const adjacency = state.adjacency;
894
942
  const neighbors = layerArray(adjacency, layer);
895
943
  const visited = state.visited;
896
944
  const stamp = nextVisitStamp(state);
897
- const candidates = createMinHeap(distanceAsc);
898
- const results = createMinHeap(distanceDesc);
945
+ resetHeap(frontier);
946
+ resetHeap(found);
899
947
  let furthestDist = Number.POSITIVE_INFINITY;
900
- for (const epOrd of eps) {
948
+ for (let i = 0; i < workspace.entryPointCount; i++) {
949
+ const epOrd = workspace.entryPoints[i];
901
950
  if (visited[epOrd] === stamp) continue;
902
951
  visited[epOrd] = stamp;
903
952
  if (!nodeExists(state, epOrd)) continue;
904
953
  if (skipTombstones && state.tombstones[epOrd] === 1) continue;
905
954
  const dist = getDistance(epOrd);
906
955
  if (dist === Number.POSITIVE_INFINITY) continue;
907
- const pair = { ord: epOrd, distance: dist };
908
- candidates.push(pair);
909
- results.push(pair);
910
- if (results.size > ef) {
911
- results.pop();
956
+ pushHeap(frontier, epOrd, dist);
957
+ pushHeap(found, epOrd, dist);
958
+ if (found.size > ef) {
959
+ popHeap(found);
912
960
  }
913
961
  }
914
- const topResult = results.peek();
915
- if (topResult) furthestDist = topResult.distance;
916
- while (candidates.size > 0) {
917
- const nearest = candidates.pop();
918
- if (!nearest) break;
919
- if (nearest.distance > furthestDist) break;
920
- const base = layerBase(adjacency, nearest.ord, layer);
962
+ if (found.size > 0) furthestDist = found.distances[0];
963
+ while (popHeap(frontier)) {
964
+ if (frontier.topDistance > furthestDist) break;
965
+ const base = layerBase(adjacency, frontier.topOrd, layer);
921
966
  if (base === -1) continue;
922
967
  const count = neighbors[base];
923
968
  for (let i = 1; i <= count; i++) {
@@ -928,55 +973,41 @@ function searchLayer(state, qVec, qMag, eps, ef, layer, metric, skipTombstones,
928
973
  if (!nodeExists(state, neighborOrd)) continue;
929
974
  const dist = getDistance(neighborOrd);
930
975
  if (dist === Number.POSITIVE_INFINITY) continue;
931
- if (dist < furthestDist || results.size < ef) {
932
- const pair = { ord: neighborOrd, distance: dist };
933
- candidates.push(pair);
934
- results.push(pair);
935
- if (results.size > ef) {
936
- results.pop();
976
+ if (dist < furthestDist || found.size < ef) {
977
+ pushHeap(frontier, neighborOrd, dist);
978
+ pushHeap(found, neighborOrd, dist);
979
+ if (found.size > ef) {
980
+ popHeap(found);
937
981
  }
938
- const newTop = results.peek();
939
- if (newTop) furthestDist = newTop.distance;
982
+ furthestDist = found.distances[0];
940
983
  }
941
984
  }
942
985
  }
943
- return results;
944
- }
945
- function nearestFromHeap(heap) {
946
- let nearest;
947
- while (heap.size > 0) {
948
- nearest = heap.pop();
949
- }
950
- return nearest;
951
- }
952
- function selectNeighborsHeuristic(state, targetOrd, candidates, maxConnections, layer, metric, extendCandidates, keepPruned) {
953
- const working = [...candidates];
954
- working.sort((a, b) => a.distance - b.distance);
955
- const selected = [];
956
- const discarded = [];
957
- for (const candidate of working) {
958
- if (selected.length >= maxConnections) break;
986
+ drainHeapNearestFirst(found, results);
987
+ }
988
+ function selectNeighborsHeuristic(state, candidates, maxConnections, metric, selected) {
989
+ const working = state.workspace.working;
990
+ copyList(candidates, working);
991
+ sortListByDistance(working);
992
+ ensureListCapacity(selected, Math.min(maxConnections, working.size));
993
+ selected.size = 0;
994
+ for (let i = 0; i < working.size; i++) {
995
+ if (selected.size >= maxConnections) break;
996
+ const candidateOrd = working.ords[i];
997
+ const candidateDistance = working.distances[i];
959
998
  let accepted = true;
960
- for (const sel of selected) {
961
- const distBetween = nodeDistanceByOrd(state, candidate.ord, sel.ord, metric);
962
- if (candidate.distance >= distBetween) {
999
+ for (let s = 0; s < selected.size; s++) {
1000
+ const distBetween = nodeDistanceByOrd(state, candidateOrd, selected.ords[s], metric);
1001
+ if (candidateDistance >= distBetween) {
963
1002
  accepted = false;
964
1003
  break;
965
1004
  }
966
1005
  }
967
- if (accepted) {
968
- selected.push(candidate);
969
- } else {
970
- discarded.push(candidate);
971
- }
1006
+ if (!accepted) continue;
1007
+ selected.ords[selected.size] = candidateOrd;
1008
+ selected.distances[selected.size] = candidateDistance;
1009
+ selected.size += 1;
972
1010
  }
973
- {
974
- for (const disc of discarded) {
975
- if (selected.length >= maxConnections) break;
976
- selected.push(disc);
977
- }
978
- }
979
- return selected;
980
1011
  }
981
1012
  function pruneConnections(state, ord, layer, metric) {
982
1013
  const adjacency = state.adjacency;
@@ -986,17 +1017,20 @@ function pruneConnections(state, ord, layer, metric) {
986
1017
  const neighbors = layerArray(adjacency, layer);
987
1018
  const count = neighbors[base];
988
1019
  if (count <= mc) return;
989
- const conns = [];
1020
+ const candidates = state.workspace.pruneCandidates;
1021
+ candidates.size = 0;
1022
+ ensureListCapacity(candidates, count);
990
1023
  for (let i = 1; i <= count; i++) {
991
1024
  const connOrd = neighbors[base + i];
992
1025
  const dist = nodeDistanceByOrd(state, ord, connOrd, metric);
993
1026
  if (dist === Number.POSITIVE_INFINITY) continue;
994
- conns.push({ ord: connOrd, distance: dist });
1027
+ candidates.ords[candidates.size] = connOrd;
1028
+ candidates.distances[candidates.size] = dist;
1029
+ candidates.size += 1;
995
1030
  }
996
- const pruned = selectNeighborsHeuristic(state, ord, conns, mc, layer, metric);
997
- const next = [];
998
- for (const p of pruned) addConnection(next, p.ord);
999
- replaceNeighbors(adjacency, ord, layer, next);
1031
+ const kept = state.workspace.pruneSelection;
1032
+ selectNeighborsHeuristic(state, candidates, mc, metric, kept);
1033
+ replaceNeighbors(adjacency, ord, layer, kept.ords, kept.size);
1000
1034
  }
1001
1035
 
1002
1036
  // src/vector/hnsw/search.ts
@@ -1039,20 +1073,20 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
1039
1073
  const metric = searchMetric;
1040
1074
  distFn = (ord) => store.distanceFromArena(arenaQuery, ord, metric);
1041
1075
  }
1042
- let currentEPs = [state.entryPointOrd];
1076
+ const workspace = state.workspace;
1077
+ const candidates = workspace.traversal;
1078
+ setSingleEntryPoint(workspace, state.entryPointOrd);
1043
1079
  for (let layer = state.topLayer; layer >= 1; layer--) {
1044
- const heap = searchLayer(state, query, qMag, currentEPs, 1, layer, searchMetric, true, distFn);
1045
- const nearest = nearestFromHeap(heap);
1046
- if (nearest) {
1047
- currentEPs = [nearest.ord];
1080
+ searchLayer(state, query, qMag, 1, layer, searchMetric, true, distFn, candidates);
1081
+ if (candidates.size > 0) {
1082
+ setSingleEntryPoint(workspace, candidates.ords[0]);
1048
1083
  }
1049
1084
  }
1050
- const candidateHeap = searchLayer(state, query, qMag, currentEPs, ef, 0, searchMetric, true, distFn);
1051
- const candidateArray = candidateHeap.toSortedArray().reverse();
1085
+ searchLayer(state, query, qMag, ef, 0, searchMetric, true, distFn, candidates);
1052
1086
  if (useQuantized) {
1053
1087
  return rerankWithFullPrecision(
1054
1088
  state,
1055
- candidateArray,
1089
+ candidates,
1056
1090
  query,
1057
1091
  qMag,
1058
1092
  arenaQuery,
@@ -1064,33 +1098,35 @@ function collectHits(state, query, k, searchMetric, minSimilarity, filter, efSea
1064
1098
  );
1065
1099
  }
1066
1100
  const hits = [];
1067
- for (const cand of candidateArray) {
1068
- if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
1069
- const score = toScore(cand.distance, searchMetric);
1101
+ for (let i = 0; i < candidates.size; i++) {
1102
+ const ord = candidates.ords[i];
1103
+ if (filter && !ordinalFilterHas(filter, ord)) continue;
1104
+ const score = toScore(candidates.distances[i], searchMetric);
1070
1105
  if (score < minSimilarity) continue;
1071
- if (!hasDocument(cand.ord)) continue;
1072
- hits.push({ ord: cand.ord, score });
1106
+ if (!hasDocument(ord)) continue;
1107
+ hits.push({ ord, score });
1073
1108
  }
1074
1109
  return hits;
1075
1110
  }
1076
1111
  function rerankWithFullPrecision(state, candidates, query, qMag, arenaQuery, k, metric, minSimilarity, filter, hasDocument) {
1077
1112
  const reranked = [];
1078
1113
  const rerankLimit = Math.max(k * SQ8_OVERSELECTION_FACTOR, 10);
1079
- for (const cand of candidates) {
1080
- if (filter && !ordinalFilterHas(filter, cand.ord)) continue;
1114
+ for (let i = 0; i < candidates.size; i++) {
1115
+ const ord = candidates.ords[i];
1116
+ if (filter && !ordinalFilterHas(filter, ord)) continue;
1081
1117
  let fullDistance;
1082
1118
  if (arenaQuery) {
1083
- fullDistance = state.store.distanceFromArena(arenaQuery, cand.ord, metric);
1119
+ fullDistance = state.store.distanceFromArena(arenaQuery, ord, metric);
1084
1120
  if (fullDistance === Number.POSITIVE_INFINITY) continue;
1085
1121
  } else {
1086
- const entry = entryForOrd(state, cand.ord);
1122
+ const entry = entryForOrd(state, ord);
1087
1123
  if (!entry) continue;
1088
1124
  fullDistance = toDistance(query, entry.vector, qMag, entry.magnitude, metric);
1089
1125
  }
1090
1126
  const score = toScore(fullDistance, metric);
1091
1127
  if (score < minSimilarity) continue;
1092
- if (!hasDocument(cand.ord)) continue;
1093
- reranked.push({ ord: cand.ord, score });
1128
+ if (!hasDocument(ord)) continue;
1129
+ reranked.push({ ord, score });
1094
1130
  if (reranked.length >= rerankLimit) break;
1095
1131
  }
1096
1132
  return reranked;
@@ -1127,30 +1163,23 @@ function clearTombstone(state, ord) {
1127
1163
  state.tombstoneCount--;
1128
1164
  }
1129
1165
  }
1130
- function reassignEntryPoint(state) {
1166
+ function highestNode(state, includeTombstoned) {
1131
1167
  let bestOrd = -1;
1132
1168
  let bestLayer = -1;
1133
1169
  for (let ord = 0; ord < state.adjacency.slots; ord++) {
1134
1170
  const level = nodeMaxLayer(state, ord);
1135
1171
  if (level === -1) continue;
1136
- if (state.tombstones[ord] === 1) continue;
1172
+ if (!includeTombstoned && state.tombstones[ord] === 1) continue;
1137
1173
  if (level > bestLayer) {
1138
1174
  bestLayer = level;
1139
1175
  bestOrd = ord;
1140
1176
  }
1141
1177
  }
1142
- if (bestOrd === -1) {
1143
- for (let ord = 0; ord < state.adjacency.slots; ord++) {
1144
- const level = nodeMaxLayer(state, ord);
1145
- if (level === -1) continue;
1146
- if (level > bestLayer) {
1147
- bestLayer = level;
1148
- bestOrd = ord;
1149
- }
1150
- }
1151
- }
1152
- state.entryPointOrd = bestOrd;
1153
- state.topLayer = bestOrd === -1 ? -1 : bestLayer;
1178
+ return bestOrd;
1179
+ }
1180
+ function setEntryPoint(state, ord) {
1181
+ state.entryPointOrd = ord;
1182
+ state.topLayer = ord === -1 ? -1 : nodeMaxLayer(state, ord);
1154
1183
  }
1155
1184
  function insertNode(state, docId) {
1156
1185
  const ord = state.store.getOrdinal(docId);
@@ -1178,39 +1207,30 @@ function insertNode(state, docId) {
1178
1207
  return;
1179
1208
  }
1180
1209
  const metric = state.buildMetric;
1210
+ const workspace = state.workspace;
1211
+ const candidates = workspace.traversal;
1212
+ const selected = workspace.insertSelection;
1181
1213
  const insertDistFn = (candOrd) => nodeDistanceByOrd(state, ord, candOrd, metric);
1182
- let currentEPs = [state.entryPointOrd];
1214
+ setSingleEntryPoint(workspace, state.entryPointOrd);
1183
1215
  for (let layer = state.topLayer; layer > l; layer--) {
1184
- const heap = searchLayer(state, entry.vector, entry.magnitude, currentEPs, 1, layer, metric, false, insertDistFn);
1185
- const nearest = nearestFromHeap(heap);
1186
- if (nearest) {
1187
- currentEPs = [nearest.ord];
1216
+ searchLayer(state, entry.vector, entry.magnitude, 1, layer, metric, false, insertDistFn, candidates);
1217
+ if (candidates.size > 0) {
1218
+ setSingleEntryPoint(workspace, candidates.ords[0]);
1188
1219
  }
1189
1220
  }
1190
1221
  for (let layer = Math.min(l, state.topLayer); layer >= 0; layer--) {
1191
- const heap = searchLayer(
1192
- state,
1193
- entry.vector,
1194
- entry.magnitude,
1195
- currentEPs,
1196
- state.efCons,
1197
- layer,
1198
- metric,
1199
- false,
1200
- insertDistFn
1201
- );
1202
- const candidates = heap.toSortedArray().reverse();
1203
- const mc = maxConns(state, layer);
1204
- const neighbors = selectNeighborsHeuristic(state, ord, candidates, mc, layer, metric);
1205
- for (const neighbor of neighbors) {
1206
- addNeighbor(state.adjacency, ord, layer, neighbor.ord);
1207
- if (layer <= nodeMaxLayer(state, neighbor.ord)) {
1208
- addNeighbor(state.adjacency, neighbor.ord, layer, ord);
1209
- pruneConnections(state, neighbor.ord, layer, metric);
1222
+ searchLayer(state, entry.vector, entry.magnitude, state.efCons, layer, metric, false, insertDistFn, candidates);
1223
+ selectNeighborsHeuristic(state, candidates, maxConns(state, layer), metric, selected);
1224
+ for (let i = 0; i < selected.size; i++) {
1225
+ const neighborOrd = selected.ords[i];
1226
+ addNeighbor(state.adjacency, ord, layer, neighborOrd);
1227
+ if (layer <= nodeMaxLayer(state, neighborOrd)) {
1228
+ addNeighbor(state.adjacency, neighborOrd, layer, ord);
1229
+ pruneConnections(state, neighborOrd, layer, metric);
1210
1230
  }
1211
1231
  }
1212
- if (candidates.length > 0) {
1213
- currentEPs = candidates.map((n) => n.ord);
1232
+ if (candidates.size > 0) {
1233
+ setEntryPointsFromList(workspace, candidates);
1214
1234
  }
1215
1235
  }
1216
1236
  if (l > state.topLayer) {
@@ -1240,17 +1260,18 @@ function removeNodeEager(state, ord, excludeOrds) {
1240
1260
  candidateOrds.add(otherOrd);
1241
1261
  }
1242
1262
  }
1243
- const candidates = [];
1263
+ const candidates = state.workspace.repairCandidates;
1264
+ candidates.size = 0;
1244
1265
  for (const candOrd of candidateOrds) {
1245
1266
  const dist = nodeDistanceByOrd(state, neighborOrd, candOrd, metric);
1246
1267
  if (dist === Number.POSITIVE_INFINITY) continue;
1247
- candidates.push({ ord: candOrd, distance: dist });
1268
+ appendToList(candidates, candOrd, dist);
1248
1269
  }
1249
- const selected = selectNeighborsHeuristic(state, neighborOrd, candidates, mc, layer, metric);
1250
- const newConns = [];
1251
- for (const s of selected) addConnection(newConns, s.ord);
1252
- replaceNeighbors(state.adjacency, neighborOrd, layer, newConns);
1253
- for (const newConnOrd of newConns) {
1270
+ const selected = state.workspace.repairSelection;
1271
+ selectNeighborsHeuristic(state, candidates, mc, metric, selected);
1272
+ replaceNeighbors(state.adjacency, neighborOrd, layer, selected.ords, selected.size);
1273
+ for (let i = 0; i < selected.size; i++) {
1274
+ const newConnOrd = selected.ords[i];
1254
1275
  if (layer <= nodeMaxLayer(state, newConnOrd)) {
1255
1276
  addNeighbor(state.adjacency, newConnOrd, layer, neighborOrd);
1256
1277
  pruneConnections(state, newConnOrd, layer, metric);
@@ -1263,11 +1284,11 @@ function removeNodeEager(state, ord, excludeOrds) {
1263
1284
  clearTombstone(state, ord);
1264
1285
  if (state.entryPointOrd === ord) {
1265
1286
  if (state.nodeCount === 0) {
1266
- state.entryPointOrd = -1;
1267
- state.topLayer = -1;
1287
+ setEntryPoint(state, -1);
1268
1288
  return;
1269
1289
  }
1270
- reassignEntryPoint(state);
1290
+ const live = highestNode(state, false);
1291
+ setEntryPoint(state, live === -1 ? highestNode(state, true) : live);
1271
1292
  }
1272
1293
  }
1273
1294
  function markTombstone(state, docId) {
@@ -1278,24 +1299,7 @@ function markTombstone(state, docId) {
1278
1299
  state.tombstoneCount++;
1279
1300
  }
1280
1301
  if (state.entryPointOrd === ord) {
1281
- let bestOrd = -1;
1282
- let bestLayer = -1;
1283
- for (let o = 0; o < state.adjacency.slots; o++) {
1284
- const level = nodeMaxLayer(state, o);
1285
- if (level === -1) continue;
1286
- if (state.tombstones[o] === 1) continue;
1287
- if (level > bestLayer) {
1288
- bestLayer = level;
1289
- bestOrd = o;
1290
- }
1291
- }
1292
- if (bestOrd !== -1) {
1293
- state.entryPointOrd = bestOrd;
1294
- state.topLayer = bestLayer;
1295
- } else {
1296
- state.entryPointOrd = -1;
1297
- state.topLayer = -1;
1298
- }
1302
+ setEntryPoint(state, highestNode(state, false));
1299
1303
  }
1300
1304
  }
1301
1305
  function compactTombstones(state) {
@@ -1408,7 +1412,7 @@ function deserializeGraph(state, data) {
1408
1412
  for (const node of nodes) {
1409
1413
  createNode(state.adjacency, node.ord, node.maxLayer);
1410
1414
  for (const [layer, neighbors] of node.layers) {
1411
- replaceNeighbors(state.adjacency, node.ord, layer, neighbors);
1415
+ replaceNeighbors(state.adjacency, node.ord, layer, neighbors, neighbors.length);
1412
1416
  }
1413
1417
  state.nodeCount++;
1414
1418
  }
@@ -1459,7 +1463,8 @@ function createHNSWIndex(dimension, store, config, quantizer) {
1459
1463
  visited: new Uint32Array(0),
1460
1464
  visitStamp: 0,
1461
1465
  entryPointOrd: -1,
1462
- topLayer: -1
1466
+ topLayer: -1,
1467
+ workspace: createHNSWWorkspace()
1463
1468
  };
1464
1469
  function clear() {
1465
1470
  resetAdjacency(state.adjacency);
@@ -1560,4 +1565,4 @@ function createHNSWIndex(dimension, store, config, quantizer) {
1560
1565
  };
1561
1566
  }
1562
1567
 
1563
- export { ABSENT_DOCUMENT_RANK, MAX_M, addToOrdinalFilter, arenaFloat32Distance, cosineSimilarityWithMagnitudes, createArenaSimd, createBoundedMaxHeap, createHNSWIndex, createOrdinalFilter, createSharedArenaSimd, createVectorStore, dotProduct, euclideanDistance, importAdjacency, isSimdAvailable, magnitude, ordinalFilterHas, ordinalFilterValues, removeFromOrdinalFilter, searchOrdinals, toScore };
1568
+ export { ABSENT_DOCUMENT_RANK, MAX_M, addToOrdinalFilter, arenaFloat32Distance, cosineSimilarityWithMagnitudes, createArenaSimd, createHNSWIndex, createHNSWWorkspace, createOrdinalFilter, createSharedArenaSimd, createVectorStore, dotProduct, euclideanDistance, importAdjacency, isSimdAvailable, magnitude, ordinalFilterHas, ordinalFilterValues, removeFromOrdinalFilter, searchOrdinals, toScore };