@delali/narsil 0.2.4-next.55 → 0.3.0

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 (52) hide show
  1. package/README.md +39 -28
  2. package/dist/adapters/filesystem.mjs +1 -1
  3. package/dist/adapters/indexeddb.mjs +1 -1
  4. package/dist/{capabilities-DlYw0Ov0.d.ts → capabilities-DSsOGz6F.d.ts} +2 -2
  5. package/dist/{chunk-BH5SYB5W.mjs → chunk-24RXHMEL.mjs} +604 -150
  6. package/dist/{chunk-D2S642IV.mjs → chunk-3LHGNRYE.mjs} +1 -1
  7. package/dist/{chunk-TVT5V4E7.mjs → chunk-B2TFROFX.mjs} +299 -168
  8. package/dist/{chunk-L225SHNB.mjs → chunk-L6TNPLAR.mjs} +19 -5
  9. package/dist/{chunk-U2AL3SON.mjs → chunk-THYRHHO2.mjs} +3 -3
  10. package/dist/{chunk-NTZYA3SJ.mjs → chunk-U7KYW4BA.mjs} +3 -3
  11. package/dist/{chunk-FRQVMFY7.mjs → chunk-USKFU7J5.mjs} +1 -0
  12. package/dist/{chunk-4NVPQ6S5.mjs → chunk-UXNWQZST.mjs} +12 -3
  13. package/dist/{chunk-RGS7FMCB.mjs → chunk-VMRR3DQU.mjs} +74 -16
  14. package/dist/{chunk-2GWVQWGA.mjs → chunk-YMZCMH5M.mjs} +45 -1
  15. package/dist/{chunk-UTOFOJOR.mjs → chunk-YNVIUGF5.mjs} +6 -2
  16. package/dist/{chunk-5T6AOR37.mjs → chunk-ZNZBXMP5.mjs} +601 -60
  17. package/dist/{chunk-XNQAIO74.mjs → chunk-ZZT3EAUC.mjs} +177 -309
  18. package/dist/{client-CQUqt9Ir.d.ts → client-BQK6Tf6A.d.ts} +3 -3
  19. package/dist/client.d.ts +8 -8
  20. package/dist/client.mjs +1 -0
  21. package/dist/{config-CNas6wJH.d.ts → config-BhldZgaK.d.ts} +2 -2
  22. package/dist/{config-bXxnJrQc.d.ts → config-DmXULPa-.d.ts} +15 -7
  23. package/dist/direct-executor-ALAVE7FC.mjs +12 -0
  24. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  25. package/dist/distribution/coordinator/etcd.mjs +2 -2
  26. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  27. package/dist/distribution/coordinator/in-memory.mjs +1 -1
  28. package/dist/distribution/transport/grpc.mjs +1 -1
  29. package/dist/distribution.d.ts +8 -8
  30. package/dist/distribution.mjs +382 -162
  31. package/dist/embeddings/openai.mjs +2 -2
  32. package/dist/{engine-Dj4I91Jv.d.ts → engine-CIOyFozK.d.ts} +15 -12
  33. package/dist/index.browser.mjs +1565 -528
  34. package/dist/index.d.ts +7 -7
  35. package/dist/index.mjs +11 -11
  36. package/dist/invalidation/filesystem.mjs +1 -1
  37. package/dist/{memory-xrXOZMbw.d.ts → memory-yFaVn2xZ.d.ts} +199 -107
  38. package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
  39. package/dist/persistence/durability/checkpoint-worker.mjs +11 -11
  40. package/dist/react.d.ts +6 -6
  41. package/dist/react.mjs +1 -0
  42. package/dist/{schema-mLpXZBEh.d.ts → schema-d5Afu03A.d.ts} +7 -2
  43. package/dist/server.d.ts +10 -10
  44. package/dist/server.mjs +10 -10
  45. package/dist/{tasks-Df73Tjgq.d.ts → tasks-CH2Dod8w.d.ts} +1 -1
  46. package/dist/{thread-5UP6MGED.mjs → thread-DYHEQRVY.mjs} +14 -10
  47. package/dist/{types-BTIVO8DI.d.ts → types-DAK7mnUZ.d.ts} +1 -1
  48. package/dist/vector/search-worker.d.ts +1 -1
  49. package/dist/vector/search-worker.mjs +4 -4
  50. package/dist/workers/entry.mjs +4 -4
  51. package/package.json +9 -9
  52. package/dist/direct-executor-RBA5O7EN.mjs +0 -12
@@ -1,12 +1,12 @@
1
1
  import { detectRuntime } from './chunk-AV4IJKD2.mjs';
2
- import { cloneProjected, mergeFacets, visitTokens, countsWithoutScores, fanOutMatchCount, fanOutQuery, sanitizeGlobalStats, projectionKeepsField, setNestedValue, fnv1a, bitsetHas, createBitSet, kWayMerge, boundedLevenshtein, bitsetSet, tokenize, bitsetFromSet, tokenizeIterator, bitsetAnd, bitsetOr, bitsetNot } from './chunk-5T6AOR37.mjs';
3
- import { flattenSchema, validateDocument, validateDocumentStrict, isQuantizationMode, isStorageMode, extractVectorFieldsFromSchema, isTextFieldType, VECTOR_PATTERN } from './chunk-L225SHNB.mjs';
2
+ import { cloneProjected, everyValueFacetConfig, mergeFacets, computeFacets, tokenizeOptions, getNestedValue, visitTokens, countsWithoutScores, fanOutMatchCount, fanOutQuery, sanitizeGlobalStats, getFlatSchema, textFieldsChanged, projectionKeepsField, setNestedValue, fnv1a, bitsetHas, createBitSet, kWayMerge, boundedLevenshtein, bitsetSet, tokenize, bitsetFromSet, getAllInternalDocIds, getFieldValueByInternalId, getOrCreateFieldNameIndex, tokenizeIterator, polygonContainment, requireValidOperands, bitsetAnd, bitsetOr, bitsetNot, requirePolygonRing, VINCENTY_CONVERGENCE_THRESHOLD, VINCENTY_MAX_ITERATIONS, DEG_TO_RAD } from './chunk-ZNZBXMP5.mjs';
3
+ import { flattenSchema, validateDocument, validateDocumentStrict, isQuantizationMode, isStorageMode, extractVectorFieldsFromSchema, isTextFieldType, VECTOR_PATTERN } from './chunk-L6TNPLAR.mjs';
4
4
  import { english } from './chunk-TGP5M5R7.mjs';
5
- import { SCRATCH_SLOTS_PER_THREAD_POOL, osqRecordBytes, openSharedVectorField, osqBitsOf, createVectorStore, DISK_STORAGE_BLOCK_BYTES, createOsqQuantizer, magnitude, createHNSWIndex, nativeStoreScratchBytes, createOrdinalFilter, addToOrdinalFilter, ordinalFilterValues, isTombstoned, nativeScoresOf, toScore, removeFromOrdinalFilter, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, requireWholeNeighbourLists, STORE_CALIBRATION_GENERATION, entryForOrd, toDistance, sharedMemoryAvailable, GRAPH_BUILD_CHUNKS_IN_FLIGHT_PER_WORKER, GRAPH_BUILD_DISPATCH_CHUNK, releaseLocksHeldBy, VECTOR_SEARCH_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, VECTOR_INSERT_TIMEOUT_MS, VECTOR_SCRATCH_SLOTS } from './chunk-U2AL3SON.mjs';
6
- import { noteFallback } from './chunk-UTOFOJOR.mjs';
5
+ import { SCRATCH_SLOTS_PER_THREAD_POOL, osqRecordBytes, openSharedVectorField, osqBitsOf, createVectorStore, DISK_STORAGE_BLOCK_BYTES, createOsqQuantizer, magnitude, createHNSWIndex, nativeStoreScratchBytes, createOrdinalFilter, isTombstoned, addToOrdinalFilter, ordinalFilterValues, nativeScoresOf, toScore, removeFromOrdinalFilter, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, requireWholeNeighbourLists, STORE_CALIBRATION_GENERATION, entryForOrd, toDistance, sharedMemoryAvailable, GRAPH_BUILD_CHUNKS_IN_FLIGHT_PER_WORKER, GRAPH_BUILD_DISPATCH_CHUNK, releaseLocksHeldBy, VECTOR_SEARCH_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, VECTOR_INSERT_TIMEOUT_MS, VECTOR_SCRATCH_SLOTS } from './chunk-THYRHHO2.mjs';
6
+ import { noteFallback } from './chunk-YNVIUGF5.mjs';
7
7
  import { VALID_HNSW_METRICS } from './chunk-IFBKLBGB.mjs';
8
- import { compareCodePoints, toComparableSortValue, readSortField, compareComparableValues, SORT_VALUE_MAX_CODE_POINTS } from './chunk-2GWVQWGA.mjs';
9
- import { NarsilError, ErrorCodes } from './chunk-FRQVMFY7.mjs';
8
+ import { compareCodePoints, toReducedSortValue, readSortField, compareComparableValues, defaultSortMode, SORT_VALUE_MAX_CODE_POINTS } from './chunk-YMZCMH5M.mjs';
9
+ import { NarsilError, ErrorCodes } from './chunk-USKFU7J5.mjs';
10
10
  import { totalmem } from 'os';
11
11
  import { getHeapStatistics } from 'v8';
12
12
  import { encode, decode } from '@msgpack/msgpack';
@@ -1415,215 +1415,6 @@ function createSurfaceRegistry() {
1415
1415
  };
1416
1416
  }
1417
1417
 
1418
- // src/core/partition/utils.ts
1419
- function getOrCreateFieldNameIndex(table, fieldName) {
1420
- const existing = table.indexMap.get(fieldName);
1421
- if (existing !== void 0) return existing;
1422
- const idx = table.names.length;
1423
- table.names.push(fieldName);
1424
- table.indexMap.set(fieldName, idx);
1425
- return idx;
1426
- }
1427
- function getNestedValue(obj, path) {
1428
- const parts = path.split(".");
1429
- let current = obj;
1430
- for (let i = 0; i < parts.length; i++) {
1431
- if (current === null || current === void 0 || typeof current !== "object") return void 0;
1432
- current = current[parts[i]];
1433
- }
1434
- return current;
1435
- }
1436
- function stringArraysEqual(a, b) {
1437
- if (a === b) return true;
1438
- if (!a || !b) return false;
1439
- if (a.length !== b.length) return false;
1440
- for (let i = 0; i < a.length; i++) {
1441
- if (a[i] !== b[i]) return false;
1442
- }
1443
- return true;
1444
- }
1445
- function textFieldsChanged(oldDoc, newDoc, flatSchema) {
1446
- for (const [path, fieldType] of Object.entries(flatSchema)) {
1447
- if (!isTextFieldType(fieldType) && fieldType !== "string[]") continue;
1448
- const oldVal = getNestedValue(oldDoc, path);
1449
- const newVal = getNestedValue(newDoc, path);
1450
- if (fieldType !== "string[]") {
1451
- if (oldVal !== newVal) return true;
1452
- } else {
1453
- if (!stringArraysEqual(oldVal, newVal)) return true;
1454
- }
1455
- }
1456
- return false;
1457
- }
1458
- function tokenizeOptions(options) {
1459
- return {
1460
- stem: true,
1461
- removeStopWords: true,
1462
- collectSurfaces: options?.collectSurfaces === true,
1463
- stopWordOverride: options?.stopWordOverride,
1464
- customTokenizer: options?.customTokenizer
1465
- };
1466
- }
1467
- function getFlatSchema(state, schema) {
1468
- if (state.lastSchemaRef === schema && state.flatSchemaCache) return state.flatSchemaCache;
1469
- state.flatSchemaCache = flattenSchema(schema);
1470
- state.lastSchemaRef = schema;
1471
- return state.flatSchemaCache;
1472
- }
1473
- function getFieldValueForDoc(docStore, docId, fieldPath) {
1474
- const stored = docStore.get(docId);
1475
- if (!stored) return void 0;
1476
- return getNestedValue(stored.fields, fieldPath);
1477
- }
1478
- function getFieldValueByInternalId(docStore, internalId, fieldPath) {
1479
- const externalId = docStore.getExternalId(internalId);
1480
- if (externalId === void 0) return void 0;
1481
- return getFieldValueForDoc(docStore, externalId, fieldPath);
1482
- }
1483
- function getAllInternalDocIds(docStore) {
1484
- const ids = /* @__PURE__ */ new Set();
1485
- for (const internalId of docStore.allInternalIds()) {
1486
- ids.add(internalId);
1487
- }
1488
- return ids;
1489
- }
1490
-
1491
- // src/core/partition/facets.ts
1492
- function ordinalBitsetOf(state, matched) {
1493
- if ("ordinalBitset" in matched) return matched.ordinalBitset;
1494
- const bitset = createBitSet(state.docStore.internalIdCapacity());
1495
- for (const docId of matched) {
1496
- const ordinal = state.docStore.getInternalId(docId);
1497
- if (ordinal !== void 0) bitsetSet(bitset, ordinal);
1498
- }
1499
- return bitset;
1500
- }
1501
- function* matchedFieldValues(state, fieldPath, matched) {
1502
- if ("ordinalBitset" in matched) {
1503
- const bits = matched.ordinalBitset;
1504
- for (let wordIndex = 0; wordIndex < bits.length; wordIndex++) {
1505
- let word = bits[wordIndex];
1506
- if (word === 0) continue;
1507
- const base = wordIndex << 5;
1508
- while (word !== 0) {
1509
- const trailingZeros = Math.clz32(word & -word) ^ 31;
1510
- yield getFieldValueByInternalId(state.docStore, base + trailingZeros, fieldPath);
1511
- word &= word - 1;
1512
- }
1513
- }
1514
- return;
1515
- }
1516
- for (const docId of matched) {
1517
- yield getFieldValueForDoc(state.docStore, docId, fieldPath);
1518
- }
1519
- }
1520
- function countValuesFromDocuments(state, fieldPath, matched) {
1521
- const counts = /* @__PURE__ */ new Map();
1522
- for (const value of matchedFieldValues(state, fieldPath, matched)) {
1523
- if (value === void 0 || value === null) continue;
1524
- if (Array.isArray(value)) {
1525
- for (const item of value) {
1526
- const key = String(item);
1527
- counts.set(key, (counts.get(key) ?? 0) + 1);
1528
- }
1529
- } else {
1530
- const key = String(value);
1531
- counts.set(key, (counts.get(key) ?? 0) + 1);
1532
- }
1533
- }
1534
- return counts;
1535
- }
1536
- function countRangeFromDocuments(state, fieldPath, fieldType, range, matched) {
1537
- let count = 0;
1538
- for (const value of matchedFieldValues(state, fieldPath, matched)) {
1539
- if (fieldType === "number[]" && Array.isArray(value)) {
1540
- for (const v of value) {
1541
- if (v >= range.from && v < range.to) {
1542
- count++;
1543
- break;
1544
- }
1545
- }
1546
- } else if (typeof value === "number" && value >= range.from && value < range.to) {
1547
- count++;
1548
- }
1549
- }
1550
- return count;
1551
- }
1552
- function countNumericRanges(state, fieldPath, fieldType, ranges, bitset, matched) {
1553
- const counts = /* @__PURE__ */ new Map();
1554
- const index = state.numericIndexes.get(fieldPath);
1555
- for (const range of ranges) {
1556
- const label = `${range.from}-${range.to}`;
1557
- if (index === void 0) {
1558
- counts.set(label, countRangeFromDocuments(state, fieldPath, fieldType, range, matched));
1559
- } else if (Number.isNaN(range.from) || Number.isNaN(range.to)) {
1560
- counts.set(label, 0);
1561
- } else {
1562
- counts.set(label, index.facetRangeCount(range.from, range.to, bitset()));
1563
- }
1564
- }
1565
- return counts;
1566
- }
1567
- function countFieldValues(state, fieldPath, fieldType, bitset, matched) {
1568
- if (fieldType === "enum" || fieldType === "enum[]") {
1569
- const index = state.enumIndexes.get(fieldPath);
1570
- if (index !== void 0) return index.facetCounts(bitset());
1571
- } else if (fieldType === "boolean" || fieldType === "boolean[]") {
1572
- const index = state.booleanIndexes.get(fieldPath);
1573
- if (index !== void 0) {
1574
- const { trueCount, falseCount } = index.facetCounts(bitset());
1575
- const counts = /* @__PURE__ */ new Map();
1576
- if (trueCount > 0) counts.set("true", trueCount);
1577
- if (falseCount > 0) counts.set("false", falseCount);
1578
- return counts;
1579
- }
1580
- } else if (fieldType === "number" || fieldType === "number[]") {
1581
- const index = state.numericIndexes.get(fieldPath);
1582
- if (index !== void 0) {
1583
- const counts = /* @__PURE__ */ new Map();
1584
- for (const [value, count] of index.facetValueCounts(bitset())) {
1585
- counts.set(String(value), count);
1586
- }
1587
- return counts;
1588
- }
1589
- }
1590
- return countValuesFromDocuments(state, fieldPath, matched);
1591
- }
1592
- function computeFacets(state, matched, config, schema) {
1593
- const result = {};
1594
- const flatSchema = getFlatSchema(state, schema);
1595
- let matchedBitset = null;
1596
- const bitset = () => {
1597
- if (matchedBitset === null) matchedBitset = ordinalBitsetOf(state, matched);
1598
- return matchedBitset;
1599
- };
1600
- for (const [fieldPath, facetOpts] of Object.entries(config)) {
1601
- const fieldType = flatSchema[fieldPath];
1602
- if (!fieldType) continue;
1603
- const valueCounts = facetOpts.ranges && (fieldType === "number" || fieldType === "number[]") ? countNumericRanges(state, fieldPath, fieldType, facetOpts.ranges, bitset, matched) : countFieldValues(state, fieldPath, fieldType, bitset, matched);
1604
- const ordered = Array.from(valueCounts.entries());
1605
- const sortDir = facetOpts.sort ?? "desc";
1606
- ordered.sort((a, b) => (sortDir === "asc" ? a[1] - b[1] : b[1] - a[1]) || compareCodePoints(a[0], b[0]));
1607
- const keep = facetOpts.limit && facetOpts.limit > 0 ? facetOpts.limit : ordered.length;
1608
- const entries = ordered.slice(0, keep);
1609
- let errorBound = 0;
1610
- for (let index = keep; index < ordered.length; index++) {
1611
- if (ordered[index][1] > errorBound) errorBound = ordered[index][1];
1612
- }
1613
- const values = {};
1614
- for (const [key, count] of entries) {
1615
- values[key] = count;
1616
- }
1617
- result[fieldPath] = { values, count: entries.length, errorBound };
1618
- }
1619
- return result;
1620
- }
1621
-
1622
- // src/geo/constants.ts
1623
- var DEG_TO_RAD = Math.PI / 180;
1624
- var VINCENTY_MAX_ITERATIONS = 200;
1625
- var VINCENTY_CONVERGENCE_THRESHOLD = 1e-12;
1626
-
1627
1418
  // src/geo/haversine.ts
1628
1419
  var EARTH_RADIUS_METERS = 63710088e-1;
1629
1420
  function haversineDistance(lat1, lon1, lat2, lon2) {
@@ -1638,27 +1429,6 @@ function haversineDistance(lat1, lon1, lat2, lon2) {
1638
1429
  return EARTH_RADIUS_METERS * c;
1639
1430
  }
1640
1431
 
1641
- // src/geo/polygon.ts
1642
- function isPointInPolygon(lat, lon, polygon) {
1643
- if (polygon.length < 3) return false;
1644
- let inside = false;
1645
- const n = polygon.length;
1646
- for (let i = 0, j = n - 1; i < n; j = i++) {
1647
- const lonI = polygon[i].lon;
1648
- const latI = polygon[i].lat;
1649
- const lonJ = polygon[j].lon;
1650
- const latJ = polygon[j].lat;
1651
- if (lonI > lon !== lonJ > lon) {
1652
- const slope = (latJ - latI) / (lonJ - lonI);
1653
- const intersectLat = latI + slope * (lon - lonI);
1654
- if (lat < intersectLat) {
1655
- inside = !inside;
1656
- }
1657
- }
1658
- }
1659
- return inside;
1660
- }
1661
-
1662
1432
  // src/geo/vincenty.ts
1663
1433
  var WGS84_A = 6378137;
1664
1434
  var WGS84_F = 1 / 298.257223563;
@@ -1741,9 +1511,10 @@ function createGeoIndex() {
1741
1511
  },
1742
1512
  polygonQuery(points, inside) {
1743
1513
  const result = /* @__PURE__ */ new Set();
1514
+ const contains = polygonContainment(points);
1744
1515
  for (let i = 0; i < entries.length; i++) {
1745
1516
  const entry = entries[i];
1746
- const withinPolygon = isPointInPolygon(entry.lat, entry.lon, points);
1517
+ const withinPolygon = contains(entry.lat, entry.lon);
1747
1518
  if (inside ? withinPolygon : !withinPolygon) {
1748
1519
  result.add(entry.docId);
1749
1520
  }
@@ -2510,7 +2281,12 @@ var METERS_PER_MI = 1609.344;
2510
2281
  function convertToMeters(distance, unit) {
2511
2282
  if (unit === "km") return distance * METERS_PER_KM;
2512
2283
  if (unit === "mi") return distance * METERS_PER_MI;
2513
- return distance;
2284
+ if (unit === "m") return distance;
2285
+ throw new NarsilError(
2286
+ ErrorCodes.SEARCH_INVALID_FILTER,
2287
+ `The engine measures a radius in "km", "mi", or "m", and "${String(unit)}" is none of them`,
2288
+ { unit: String(unit) }
2289
+ );
2514
2290
  }
2515
2291
  function matchesNumericComparison(value, filter) {
2516
2292
  if (filter.eq !== void 0 && value !== filter.eq) return false;
@@ -2522,6 +2298,27 @@ function matchesNumericComparison(value, filter) {
2522
2298
  if (filter.between !== void 0 && !(value >= filter.between[0] && value <= filter.between[1])) return false;
2523
2299
  return true;
2524
2300
  }
2301
+ function holdsValue(stored, value) {
2302
+ return Array.isArray(stored) ? stored.includes(value) : stored === value;
2303
+ }
2304
+ function lacksValue(stored, value) {
2305
+ if (stored === void 0 || stored === null) return false;
2306
+ return Array.isArray(stored) ? !stored.includes(value) : stored !== value;
2307
+ }
2308
+ function isStringIn(values) {
2309
+ return (element) => typeof element === "string" && values.has(element);
2310
+ }
2311
+ function holdsStringIn(stored, values) {
2312
+ return Array.isArray(stored) ? stored.some(isStringIn(values)) : isStringIn(values)(stored);
2313
+ }
2314
+ function holdsNoStringIn(stored, values) {
2315
+ if (Array.isArray(stored)) return !stored.some(isStringIn(values));
2316
+ return typeof stored === "string" && !values.has(stored);
2317
+ }
2318
+ function holdsStringWhere(stored, test) {
2319
+ const matches = (element) => typeof element === "string" && test(element);
2320
+ return Array.isArray(stored) ? stored.some(matches) : matches(stored);
2321
+ }
2525
2322
 
2526
2323
  // src/filters/operators/bitset.ts
2527
2324
  function resolveBitset(bs) {
@@ -2555,7 +2352,7 @@ function applyEqBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2555
2352
  if (fieldIndex?.type === "enum" && typeof value === "string") {
2556
2353
  return fieldIndex.index.getDocIdsBitset(value, capacity);
2557
2354
  }
2558
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === value);
2355
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => holdsValue(v, value));
2559
2356
  }
2560
2357
  function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2561
2358
  if (fieldIndex?.type === "numeric" && typeof value === "number") {
@@ -2571,7 +2368,7 @@ function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2571
2368
  const eq = fieldIndex.index.getDocIdsBitset(value, capacity);
2572
2369
  return bitsetAnd(all, bitsetNot(eq, capacity));
2573
2370
  }
2574
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null && v !== value);
2371
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => lacksValue(v, value));
2575
2372
  }
2576
2373
  function applyGtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2577
2374
  if (fieldIndex?.type === "numeric") {
@@ -2608,7 +2405,7 @@ function applyInBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2608
2405
  return fieldIndex.index.getDocIdsInBitset(values, capacity);
2609
2406
  }
2610
2407
  const valSet = new Set(values);
2611
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && valSet.has(v));
2408
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => holdsStringIn(v, valSet));
2612
2409
  }
2613
2410
  function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2614
2411
  if (fieldIndex?.type === "enum") {
@@ -2617,18 +2414,13 @@ function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2617
2414
  return bitsetAnd(all, bitsetNot(matched, capacity));
2618
2415
  }
2619
2416
  const valSet = new Set(values);
2620
- return scanToBitset(
2621
- allDocsBitset,
2622
- capacity,
2623
- getValue,
2624
- (v) => v !== void 0 && v !== null && typeof v === "string" && !valSet.has(v)
2625
- );
2417
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => holdsNoStringIn(v, valSet));
2626
2418
  }
2627
2419
  function applyStartsWithBitset(prefix, allDocsBitset, capacity, getValue) {
2628
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.startsWith(prefix));
2420
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => holdsStringWhere(v, (text) => text.startsWith(prefix)));
2629
2421
  }
2630
2422
  function applyEndsWithBitset(suffix, allDocsBitset, capacity, getValue) {
2631
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.endsWith(suffix));
2423
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => holdsStringWhere(v, (text) => text.endsWith(suffix)));
2632
2424
  }
2633
2425
  function applyContainsAllBitset(values, allDocsBitset, capacity, getValue) {
2634
2426
  return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
@@ -2692,6 +2484,7 @@ function applyGeoRadiusBitset(filter, geoIndex, capacity) {
2692
2484
  return bitsetFromSet(setResult, capacity);
2693
2485
  }
2694
2486
  function applyGeoPolygonBitset(filter, geoIndex, capacity) {
2487
+ requirePolygonRing(filter.points);
2695
2488
  const setResult = geoIndex.polygonQuery(filter.points, filter.inside ?? true);
2696
2489
  return bitsetFromSet(setResult, capacity);
2697
2490
  }
@@ -2741,9 +2534,10 @@ function evaluateFieldFilter(fieldPath, filter, context) {
2741
2534
  OPERATOR_KEYS,
2742
2535
  (key) => `Field "${fieldPath}" is tested with "${key}", which is no filter operator`
2743
2536
  );
2537
+ const f = filter;
2538
+ requireValidOperands(fieldPath, f, context.fieldTypes[fieldPath]);
2744
2539
  const fieldIndex = context.fieldIndexes[fieldPath];
2745
2540
  const getValue = (internalId) => context.getFieldValue(internalId, fieldPath);
2746
- const f = filter;
2747
2541
  const bitsets = [];
2748
2542
  const { capacity } = context;
2749
2543
  let allDocsBitset = null;
@@ -2915,6 +2709,7 @@ function buildFilterContext(state, schema) {
2915
2709
  let cachedAllDocIds = null;
2916
2710
  let cachedAllDocIdsBitset = null;
2917
2711
  return {
2712
+ fieldTypes: flat,
2918
2713
  fieldIndexes,
2919
2714
  getFieldValue: (internalId, fieldPath) => getFieldValueByInternalId(state.docStore, internalId, fieldPath),
2920
2715
  get allDocIds() {
@@ -3236,8 +3031,7 @@ function createValueStore(initialKind) {
3236
3031
  get kind() {
3237
3032
  return kind;
3238
3033
  },
3239
- set(internalId, value) {
3240
- const comparable = toComparableSortValue(value);
3034
+ set(internalId, comparable) {
3241
3035
  if (kind === "number") {
3242
3036
  if (comparable === null) {
3243
3037
  setNumber(internalId, Number.NaN);
@@ -3307,6 +3101,12 @@ function createValueStore(initialKind) {
3307
3101
  }
3308
3102
 
3309
3103
  // src/core/partition/sort-columns/index.ts
3104
+ function mayHoldList(fieldType) {
3105
+ return fieldType === void 0 || fieldType.endsWith("[]");
3106
+ }
3107
+ function columnKeyOf(field, fieldType, mode) {
3108
+ return mayHoldList(fieldType) ? `${mode}:${field}` : `value:${field}`;
3109
+ }
3310
3110
  function liveInternalIds(docStore) {
3311
3111
  const ids = [];
3312
3112
  for (const internalId of docStore.allInternalIds()) ids.push(internalId);
@@ -3324,21 +3124,22 @@ function createSortColumnSet(docStore) {
3324
3124
  entry.dirty.clear();
3325
3125
  entry.dirtyStream = null;
3326
3126
  }
3327
- function backfill(field, fieldType) {
3127
+ function backfill(key, field, fieldType, mode) {
3328
3128
  const store = createValueStore(kindForFieldType(fieldType));
3329
3129
  for (const [docId, stored] of docStore.all()) {
3330
3130
  const internalId = docStore.getInternalId(docId);
3331
3131
  if (internalId === void 0) continue;
3332
- store.set(internalId, readSortField(stored.fields, field));
3132
+ store.set(internalId, toReducedSortValue(readSortField(stored.fields, field), mode));
3333
3133
  }
3334
3134
  const entry = {
3335
3135
  field,
3136
+ mode,
3336
3137
  store,
3337
3138
  order: buildOrder(store, liveInternalIds(docStore), docStore.internalIdCapacity()),
3338
3139
  dirty: /* @__PURE__ */ new Set(),
3339
3140
  dirtyStream: null
3340
3141
  };
3341
- columns.set(field, entry);
3142
+ columns.set(key, entry);
3342
3143
  return entry;
3343
3144
  }
3344
3145
  function buildDirtyStream(entry) {
@@ -3391,13 +3192,14 @@ function createSortColumnSet(docStore) {
3391
3192
  };
3392
3193
  }
3393
3194
  return {
3394
- holds(field) {
3395
- return columns.has(field);
3195
+ holds(field, fieldType, mode) {
3196
+ return columns.has(columnKeyOf(field, fieldType, mode));
3396
3197
  },
3397
- column(field, fieldType) {
3398
- let entry = columns.get(field);
3198
+ column(field, fieldType, mode) {
3199
+ const key = columnKeyOf(field, fieldType, mode);
3200
+ let entry = columns.get(key);
3399
3201
  if (entry === void 0) {
3400
- entry = backfill(field, fieldType);
3202
+ entry = backfill(key, field, fieldType, mode);
3401
3203
  } else if (entry.dirty.size > rebuildThreshold()) {
3402
3204
  rebuild(entry);
3403
3205
  }
@@ -3405,7 +3207,7 @@ function createSortColumnSet(docStore) {
3405
3207
  },
3406
3208
  record(internalId, document) {
3407
3209
  for (const entry of columns.values()) {
3408
- entry.store.set(internalId, readSortField(document, entry.field));
3210
+ entry.store.set(internalId, toReducedSortValue(readSortField(document, entry.field), entry.mode));
3409
3211
  entry.dirty.add(internalId);
3410
3212
  entry.dirtyStream = null;
3411
3213
  }
@@ -3560,11 +3362,13 @@ function createStream(column, direction, anchorRank) {
3560
3362
  };
3561
3363
  }
3562
3364
  function selectSortedPage(state, request) {
3563
- const { fields, directions, fieldTypes, limit, anchorKey, anchorId, matches } = request;
3365
+ const { fields, directions, fieldTypes, modes, limit, anchorKey, anchorId, matches } = request;
3564
3366
  if (limit <= 0 || fields.length === 0) return [];
3565
3367
  const set = state.sortColumns;
3566
3368
  if (set === null) return [];
3567
- const columns = fields.map((field, index) => set.column(field, fieldTypes[index]));
3369
+ const columns = fields.map(
3370
+ (field, index) => set.column(field, fieldTypes[index], modes[index] ?? defaultSortMode(directions[index] ?? "asc"))
3371
+ );
3568
3372
  const resolver = state.docStore.resolver();
3569
3373
  const leading = columns[0];
3570
3374
  const leadingDirection = directions[0];
@@ -3606,17 +3410,18 @@ function sortedPageOf(state, request) {
3606
3410
  ensureColumns(state);
3607
3411
  return selectSortedPage(state, request);
3608
3412
  }
3609
- function sortValuesOf(state, docId, fields, fieldTypes) {
3413
+ function sortValuesOf(state, docId, fields, fieldTypes, modes) {
3610
3414
  const set = state.sortColumns;
3611
3415
  const internalId = state.docStore.getInternalId(docId);
3612
3416
  const stored = state.docStore.get(docId);
3613
3417
  const key = new Array(fields.length);
3614
3418
  for (let i = 0; i < fields.length; i++) {
3615
- if (set !== null && internalId !== void 0 && set.holds(fields[i])) {
3616
- key[i] = set.column(fields[i], fieldTypes[i]).valueOf(internalId);
3419
+ const mode = modes[i] ?? "min";
3420
+ if (set !== null && internalId !== void 0 && set.holds(fields[i], fieldTypes[i], mode)) {
3421
+ key[i] = set.column(fields[i], fieldTypes[i], mode).valueOf(internalId);
3617
3422
  continue;
3618
3423
  }
3619
- key[i] = stored === void 0 ? null : toComparableSortValue(readSortField(stored.fields, fields[i]));
3424
+ key[i] = stored === void 0 ? null : toReducedSortValue(readSortField(stored.fields, fields[i]), mode);
3620
3425
  }
3621
3426
  return key;
3622
3427
  }
@@ -5647,8 +5452,8 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5647
5452
  sortedPage(request) {
5648
5453
  return sortedPageOf(state, request);
5649
5454
  },
5650
- sortValues(docId, fields, fieldTypes) {
5651
- return sortValuesOf(state, docId, fields, fieldTypes);
5455
+ sortValues(docId, fields, fieldTypes, modes) {
5456
+ return sortValuesOf(state, docId, fields, fieldTypes, modes);
5652
5457
  },
5653
5458
  applyFilters(filters, schema) {
5654
5459
  return applyPartitionFilters(state, filters, schema);
@@ -6040,10 +5845,10 @@ function compositeSortedPage(subs, layout, request) {
6040
5845
  if (merged.length > request.limit) merged.length = request.limit;
6041
5846
  return merged;
6042
5847
  }
6043
- function compositeSortValues(subs, docId, fields, fieldTypes) {
5848
+ function compositeSortValues(subs, docId, fields, fieldTypes, modes) {
6044
5849
  for (const sub of subs) {
6045
5850
  if (sub.docStore.has(docId)) {
6046
- return sortValuesOf(sub, docId, fields, fieldTypes);
5851
+ return sortValuesOf(sub, docId, fields, fieldTypes, modes);
6047
5852
  }
6048
5853
  }
6049
5854
  return fields.map(() => null);
@@ -6268,6 +6073,7 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
6268
6073
  return compositeFilterMatches(subs(), layout(), filters, schema);
6269
6074
  },
6270
6075
  computeFacets(matched, config, schema) {
6076
+ const everyValue = everyValueFacetConfig(config);
6271
6077
  if ("ordinalBitset" in matched) {
6272
6078
  const ordinalLayout = layout();
6273
6079
  return mergeFacets(
@@ -6275,19 +6081,23 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
6275
6081
  (sub, index) => computeFacets(
6276
6082
  sub,
6277
6083
  { ordinalBitset: subBitsetView(matched.ordinalBitset, ordinalLayout, index) },
6278
- config,
6084
+ everyValue,
6279
6085
  schema
6280
6086
  )
6281
- )
6087
+ ),
6088
+ config
6282
6089
  );
6283
6090
  }
6284
- return mergeFacets(subs().map((sub) => computeFacets(sub, matched, config, schema)));
6091
+ return mergeFacets(
6092
+ subs().map((sub) => computeFacets(sub, matched, everyValue, schema)),
6093
+ config
6094
+ );
6285
6095
  },
6286
6096
  sortedPage(request) {
6287
6097
  return compositeSortedPage(subs(), layout(), request);
6288
6098
  },
6289
- sortValues(docId, fields, fieldTypes) {
6290
- return compositeSortValues(subs(), docId, fields, fieldTypes);
6099
+ sortValues(docId, fields, fieldTypes, modes) {
6100
+ return compositeSortValues(subs(), docId, fields, fieldTypes, modes);
6291
6101
  },
6292
6102
  suggestTerms(surfacePrefix, stemmedPrefix, limit) {
6293
6103
  return compositeSuggestTerms(subs(), surfacePrefix, stemmedPrefix, limit);
@@ -6572,10 +6382,10 @@ function createPartitionManager(indexName, config, language, router, initialPart
6572
6382
  if (pid === void 0) return void 0;
6573
6383
  return partitions[pid].getRef(docId);
6574
6384
  },
6575
- sortValues(docId, fields, fieldTypes) {
6385
+ sortValues(docId, fields, fieldTypes, modes) {
6576
6386
  const pid = locateDocument(docId);
6577
6387
  if (pid === void 0) return fields.map(() => null);
6578
- return partitions[pid].sortValues(docId, fields, fieldTypes);
6388
+ return partitions[pid].sortValues(docId, fields, fieldTypes, modes);
6579
6389
  },
6580
6390
  has(docId) {
6581
6391
  return locateDocument(docId) !== void 0;
@@ -6895,6 +6705,14 @@ async function spawnNodeWorker(entryPoint, resourceLimits) {
6895
6705
  return null;
6896
6706
  }
6897
6707
  }
6708
+ async function nodeThreadId() {
6709
+ try {
6710
+ const workerThreads = await import('worker_threads');
6711
+ return workerThreads.threadId;
6712
+ } catch {
6713
+ return 0;
6714
+ }
6715
+ }
6898
6716
  async function isNodeMainThread() {
6899
6717
  try {
6900
6718
  const workerThreads = await import('worker_threads');
@@ -6904,6 +6722,21 @@ async function isNodeMainThread() {
6904
6722
  }
6905
6723
  }
6906
6724
 
6725
+ // src/workers/entry-point.ts
6726
+ var DIST_SEGMENT = "/dist/";
6727
+ function resolveWorkerEntry(moduleUrl, sourceDirectory, distEntry) {
6728
+ const distIndex = moduleUrl.lastIndexOf(DIST_SEGMENT);
6729
+ const entry = distIndex !== -1 ? new URL(distEntry, moduleUrl.slice(0, distIndex + DIST_SEGMENT.length)).href : sourceDirectory.test(moduleUrl) ? moduleUrl.replace(sourceDirectory, `${DIST_SEGMENT}${distEntry}`) : null;
6730
+ return entry === moduleUrl ? null : entry;
6731
+ }
6732
+ function missingWorkerEntry(moduleUrl) {
6733
+ return new NarsilError(
6734
+ ErrorCodes.CONFIG_INVALID,
6735
+ `The engine finds no worker entry beside its own module at "${moduleUrl}", which happens where a bundler folds @delali/narsil into an application bundle. Keep @delali/narsil outside the bundle, or set workers.enabled to false`,
6736
+ { moduleUrl }
6737
+ );
6738
+ }
6739
+
6907
6740
  // src/workers/resource-limits.ts
6908
6741
  function workerResourceLimits() {
6909
6742
  return { maxYoungGenerationSizeMb: WORKER_YOUNG_GENERATION_MB };
@@ -6911,14 +6744,10 @@ function workerResourceLimits() {
6911
6744
 
6912
6745
  // src/vector/search-pool/spawn.ts
6913
6746
  function resolveWorkerEntryPoint() {
6914
- const base = import.meta.url;
6915
- const distIndex = base.lastIndexOf("/dist/");
6916
- if (distIndex !== -1) {
6917
- return new URL("vector/search-worker.mjs", base.slice(0, distIndex + 6)).href;
6918
- }
6919
- return base.replace(/\/src\/vector\/search-pool\/[^/]+$/, "/dist/vector/search-worker.mjs");
6747
+ return resolveWorkerEntry(import.meta.url, /\/src\/vector\/search-pool\/[^/]+$/, "vector/search-worker.mjs");
6920
6748
  }
6921
6749
  async function spawnWorker(entryPoint) {
6750
+ if (entryPoint === null) return null;
6922
6751
  const runtime = detectRuntime();
6923
6752
  if (runtime.supportsWorkerThreads) {
6924
6753
  try {
@@ -8580,6 +8409,18 @@ function* filteredDocIds(state, filter) {
8580
8409
  if (docId !== void 0) yield docId;
8581
8410
  }
8582
8411
  }
8412
+ function admittedTotal(state, filter) {
8413
+ return filter === void 0 ? liveSize(state) : filter.count;
8414
+ }
8415
+ function fetchedOutcome(state, results, minSimilarity, filter) {
8416
+ if (minSimilarity === Number.NEGATIVE_INFINITY) {
8417
+ return { results, matched: admittedTotal(state, filter), matchedExact: true };
8418
+ }
8419
+ return { results, matched: results.length, matchedExact: false };
8420
+ }
8421
+ function scannedOutcome(scan2) {
8422
+ return { results: scan2.results, matched: scan2.matched, matchedExact: true };
8423
+ }
8583
8424
  function highScoreFirst(a, b) {
8584
8425
  return b.score - a.score || compareCodePoints(a.docId, b.docId);
8585
8426
  }
@@ -8599,12 +8440,15 @@ function scanThroughTheCore(state, query, k, metric, minSimilarity, scanned) {
8599
8440
  const distances = nativeScoresOf(state.store.handles, query, metric, scanned.ordinals);
8600
8441
  if (distances === null) return null;
8601
8442
  const heap = createBoundedMaxHeap(highScoreFirst, k);
8443
+ let matched = 0;
8602
8444
  for (let i = 0; i < scanned.ordinals.length; i++) {
8603
8445
  if (distances[i] === Number.POSITIVE_INFINITY) continue;
8604
8446
  const score = toScore(distances[i], metric);
8605
- if (score >= minSimilarity) heap.push({ docId: scanned.docIds[i], score });
8447
+ if (score < minSimilarity) continue;
8448
+ matched++;
8449
+ heap.push({ docId: scanned.docIds[i], score });
8606
8450
  }
8607
- return heap.toSortedArray().reverse();
8451
+ return { results: heap.toSortedArray().reverse(), matched };
8608
8452
  }
8609
8453
  function bruteForceSearch(state, query, k, metric, minSimilarity, candidates) {
8610
8454
  const scanned = liveCandidates(state, candidates);
@@ -8614,6 +8458,7 @@ function bruteForceSearch(state, query, k, metric, minSimilarity, candidates) {
8614
8458
  const arenaQuery = state.store.prepareQueryArena(query);
8615
8459
  const queryMag = arenaQuery ? arenaQuery.magnitude : magnitude(query);
8616
8460
  const heap = createBoundedMaxHeap(highScoreFirst, k);
8461
+ let matched = 0;
8617
8462
  for (let i = 0; i < scanned.docIds.length; i++) {
8618
8463
  const docId = scanned.docIds[i];
8619
8464
  let score;
@@ -8638,11 +8483,11 @@ function bruteForceSearch(state, query, k, metric, minSimilarity, candidates) {
8638
8483
  }
8639
8484
  }
8640
8485
  }
8641
- if (score >= minSimilarity) {
8642
- heap.push({ docId, score });
8643
- }
8486
+ if (score < minSimilarity) continue;
8487
+ matched++;
8488
+ heap.push({ docId, score });
8644
8489
  }
8645
- return heap.toSortedArray().reverse();
8490
+ return { results: heap.toSortedArray().reverse(), matched };
8646
8491
  }
8647
8492
  function mergeResults(hnswResults, bufferResults, k) {
8648
8493
  const seen = /* @__PURE__ */ new Set();
@@ -8688,32 +8533,38 @@ function searchWithFilter(state, query, k, options, filter) {
8688
8533
  );
8689
8534
  }
8690
8535
  const currentLiveSize = liveSize(state);
8691
- if (currentLiveSize === 0) return [];
8692
- if (k <= 0) return [];
8536
+ const { metric, minSimilarity, efSearch, oversample } = options;
8537
+ if (currentLiveSize === 0) return { results: [], matched: 0, matchedExact: true };
8538
+ if (filter && filter.count === 0) return { results: [], matched: 0, matchedExact: true };
8539
+ if (k <= 0) return fetchedOutcome(state, [], minSimilarity, filter);
8693
8540
  if (state.buffer.size > 0 && !state.building && !state.buildScheduled) {
8694
8541
  scheduleBuild(state);
8695
8542
  }
8696
- const { metric, minSimilarity, efSearch, oversample } = options;
8697
- if (filter && filter.count === 0) return [];
8698
8543
  if (!state.hnsw) {
8699
8544
  const candidates = filter ? filteredDocIds(state, filter) : allLiveDocIds(state);
8700
- return bruteForceSearch(state, query, k, metric, minSimilarity, candidates);
8545
+ return scannedOutcome(bruteForceSearch(state, query, k, metric, minSimilarity, candidates));
8701
8546
  }
8702
8547
  if (filter) {
8703
8548
  const hnswLiveSize = state.hnsw.size;
8704
8549
  const selectivity = hnswLiveSize > 0 ? filter.count / hnswLiveSize : 1;
8705
8550
  if (selectivity < state.filterThreshold) {
8706
- return bruteForceSearch(state, query, k, metric, minSimilarity, filteredDocIds(state, filter));
8551
+ const scan2 = bruteForceSearch(state, query, k, metric, minSimilarity, filteredDocIds(state, filter));
8552
+ return scannedOutcome(scan2);
8707
8553
  }
8708
8554
  }
8709
8555
  const graphOptions = { filter, efSearch, oversample };
8710
8556
  if (state.buffer.size === 0) {
8711
8557
  const hnswResults2 = state.hnsw.search(query, k, metric, minSimilarity, graphOptions);
8712
- return hnswResults2.map((r) => ({ docId: r.docId, score: r.score }));
8558
+ return fetchedOutcome(
8559
+ state,
8560
+ hnswResults2.map((r) => ({ docId: r.docId, score: r.score })),
8561
+ minSimilarity,
8562
+ filter
8563
+ );
8713
8564
  }
8714
8565
  const hnswResults = state.hnsw.search(query, k, metric, minSimilarity, graphOptions).map((r) => ({ docId: r.docId, score: r.score }));
8715
- const bufferResults = bruteForceSearch(state, query, k, metric, minSimilarity, bufferCandidates(state, filter));
8716
- return mergeResults(hnswResults, bufferResults, k);
8566
+ const buffered = bruteForceSearch(state, query, k, metric, minSimilarity, bufferCandidates(state, filter));
8567
+ return fetchedOutcome(state, mergeResults(hnswResults, buffered.results, k), minSimilarity, filter);
8717
8568
  }
8718
8569
 
8719
8570
  // src/vector/vector-index/index.ts
@@ -8888,7 +8739,7 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
8888
8739
  efSearch: options.efSearch,
8889
8740
  oversample: options.oversample
8890
8741
  });
8891
- if (viaWorkerCopy !== null) return viaWorkerCopy;
8742
+ if (viaWorkerCopy !== null) return fetchedOutcome(state, viaWorkerCopy, options.minSimilarity, filter);
8892
8743
  if (confined && state.revision !== filterRevision) {
8893
8744
  filter = filterForOptions(state, options);
8894
8745
  }
@@ -10897,20 +10748,26 @@ function bruteForceOrdinals(state, docIdOf, query, k, metric, minSimilarity, fil
10897
10748
  }
10898
10749
  const distances = nativeScoresOf(state.store.handles, query, metric, ordinals) ?? distancesInTypeScript(state, query, metric, ordinals);
10899
10750
  const heap = createBoundedMaxHeap(bestFirst, k);
10751
+ let matched = 0;
10900
10752
  for (let i = 0; i < ordinals.length; i++) {
10901
10753
  if (distances[i] === Number.POSITIVE_INFINITY) continue;
10902
10754
  const score = toScore(distances[i], metric);
10903
- if (score >= minSimilarity) heap.push({ ord: ordinals[i], score, docId: docIds[i] });
10755
+ if (score < minSimilarity) continue;
10756
+ matched++;
10757
+ heap.push({ ord: ordinals[i], score, docId: docIds[i] });
10904
10758
  }
10905
- return heap.toSortedArray().reverse();
10759
+ return { hits: heap.toSortedArray().reverse(), matched, matchedExact: true };
10906
10760
  }
10907
10761
  function createSharedVectorSearcher(options) {
10908
10762
  const { fieldName, view, holdsDocument } = options;
10909
10763
  function filterForDocIds(allowed) {
10910
10764
  const filter = createOrdinalFilter(view.store.slots);
10765
+ const graph = view.graph;
10911
10766
  for (const docId of allowed) {
10912
10767
  const ordinal = view.ordinalOf(docId);
10913
- if (ordinal !== void 0) addToOrdinalFilter(filter, ordinal);
10768
+ if (ordinal === void 0) continue;
10769
+ if (graph !== null && isTombstoned(graph, ordinal)) continue;
10770
+ addToOrdinalFilter(filter, ordinal);
10914
10771
  }
10915
10772
  return filter;
10916
10773
  }
@@ -10937,7 +10794,11 @@ function createSharedVectorSearcher(options) {
10937
10794
  if (graph !== null && filter !== void 0 && liveSize2 > 0 && filter.count / liveSize2 < view.handles.filterThreshold) {
10938
10795
  return bruteForceOrdinals(graph, view.docIdOf, query, k, metric, minSimilarity, filter);
10939
10796
  }
10940
- return view.searchOrdinals(query, k, metric, minSimilarity, { filter, efSearch, oversample });
10797
+ const hits = view.searchOrdinals(query, k, metric, minSimilarity, { filter, efSearch, oversample });
10798
+ if (minSimilarity === Number.NEGATIVE_INFINITY) {
10799
+ return { hits, matched: filter === void 0 ? liveSize2 : filter.count, matchedExact: true };
10800
+ }
10801
+ return { hits, matched: hits.length, matchedExact: false };
10941
10802
  }
10942
10803
  return {
10943
10804
  fieldName,
@@ -10953,15 +10814,22 @@ function createSharedVectorSearcher(options) {
10953
10814
  );
10954
10815
  }
10955
10816
  const filter = filterFor(searchOptions);
10956
- if (filter !== void 0 && filter.count === 0) return Promise.resolve([]);
10957
- const hits = hitsFor(query, k, searchOptions, filter);
10817
+ if (filter !== void 0 && filter.count === 0) {
10818
+ return Promise.resolve({ results: [], matched: 0, matchedExact: true });
10819
+ }
10820
+ const scanned = hitsFor(query, k, searchOptions, filter);
10958
10821
  const results = [];
10959
- for (const hit of hits) {
10822
+ for (const hit of scanned.hits) {
10960
10823
  const docId = view.docIdOf(hit.ord);
10961
10824
  if (docId === void 0 || !holdsDocument(docId)) continue;
10962
10825
  results.push({ docId, score: hit.score });
10963
10826
  }
10964
- return Promise.resolve(results);
10827
+ const holdsEveryHit = results.length === scanned.hits.length;
10828
+ return Promise.resolve({
10829
+ results,
10830
+ matched: scanned.matched,
10831
+ matchedExact: scanned.matchedExact && holdsEveryHit
10832
+ });
10965
10833
  }
10966
10834
  };
10967
10835
  }
@@ -11297,4 +11165,4 @@ function createDirectExecutor(options) {
11297
11165
  };
11298
11166
  }
11299
11167
 
11300
- export { CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, FIELD_FILTER_OPERATORS, FILTER_EXPRESSION_KEYS, NO_LIVE_VECTOR_AT_POSITION, SHUTDOWN_TIMEOUT_MS, VECTOR_FILE_MAX_VECTORS, buildSegmentPayload, countDead, createCompositePartition, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, deadBitsWhere, deadBytesFor, decodeVectorFilePayload, decodeVectorGraphPayload, decodeVectorIndexParts, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, documentsOfPayloadV2, encodeSegmentState, freezeSegmentShared, generateId, getLanguage, getNestedValue, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isCompositePartition, isNodeMainThread, mergeFrozenSegments, readHeapStatistics, registerLanguage, registerStopWords, registerTokenizer, resolveIndexAnalysis, resolvePartitionInsertOptions, resolveRequestThreadCount, resolveWorkerCount, serializeMetadata, spawnNodeWorker, splitWorkerBudget, workerResourceLimits };
11168
+ export { CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, FIELD_FILTER_OPERATORS, FILTER_EXPRESSION_KEYS, NO_LIVE_VECTOR_AT_POSITION, SHUTDOWN_TIMEOUT_MS, VECTOR_FILE_MAX_VECTORS, buildSegmentPayload, countDead, createCompositePartition, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, deadBitsWhere, deadBytesFor, decodeVectorFilePayload, decodeVectorGraphPayload, decodeVectorIndexParts, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, documentsOfPayloadV2, encodeSegmentState, freezeSegmentShared, generateId, getLanguage, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isCompositePartition, isNodeMainThread, mergeFrozenSegments, missingWorkerEntry, nodeThreadId, readHeapStatistics, registerLanguage, registerStopWords, registerTokenizer, resolveIndexAnalysis, resolvePartitionInsertOptions, resolveRequestThreadCount, resolveWorkerCount, resolveWorkerEntry, serializeMetadata, spawnNodeWorker, splitWorkerBudget, workerResourceLimits };