cry-search 1.0.7 → 1.0.8

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 (57) hide show
  1. package/CLAUDE.md +73 -98
  2. package/README.md +39 -28
  3. package/UNIVERSE.md +11 -18
  4. package/dist/common/SearchUniverse.d.ts.map +1 -1
  5. package/dist/common/numeric/AugmentedMetadata.d.ts +27 -92
  6. package/dist/common/numeric/AugmentedMetadata.d.ts.map +1 -1
  7. package/dist/common/numeric/createSearchArrayMetadata.d.ts +47 -7
  8. package/dist/common/numeric/createSearchArrayMetadata.d.ts.map +1 -1
  9. package/dist/common/numeric/findInLinkedArrays.d.ts +7 -4
  10. package/dist/common/numeric/findInLinkedArrays.d.ts.map +1 -1
  11. package/dist/common/numeric/scoring.d.ts +95 -0
  12. package/dist/common/numeric/scoring.d.ts.map +1 -0
  13. package/dist/common/numeric/searchAndRank.d.ts +18 -60
  14. package/dist/common/numeric/searchAndRank.d.ts.map +1 -1
  15. package/dist/common/numeric/searchInData.d.ts +17 -9
  16. package/dist/common/numeric/searchInData.d.ts.map +1 -1
  17. package/dist/common/numeric/syncSearchArrayMetadata.d.ts +7 -50
  18. package/dist/common/numeric/syncSearchArrayMetadata.d.ts.map +1 -1
  19. package/dist/common/numeric/updateSearchLinkedMetadata.d.ts +11 -110
  20. package/dist/common/numeric/updateSearchLinkedMetadata.d.ts.map +1 -1
  21. package/dist/common/numeric/updateSearchMetadata.d.ts.map +1 -1
  22. package/dist/index.cjs +614 -360
  23. package/dist/index.d.cts +3 -4
  24. package/dist/index.d.ts +3 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +614 -360
  27. package/dist/types/index.d.ts +35 -50
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/dist/utils/matchTokenNumeric.d.ts.map +1 -1
  30. package/dist/utils/prepareStringForSearchNumeric.d.ts +24 -29
  31. package/dist/utils/prepareStringForSearchNumeric.d.ts.map +1 -1
  32. package/package.json +4 -14
  33. package/dist/common/findInArray.d.ts +0 -37
  34. package/dist/common/findInArray.d.ts.map +0 -1
  35. package/dist/common/findInArrayReturnDataAndMeta.d.ts +0 -52
  36. package/dist/common/findInArrayReturnDataAndMeta.d.ts.map +0 -1
  37. package/dist/common/findInLinkedArrays.d.ts +0 -66
  38. package/dist/common/findInLinkedArrays.d.ts.map +0 -1
  39. package/dist/common/string/createSearchArrayMetadata.d.ts +0 -117
  40. package/dist/common/string/createSearchArrayMetadata.d.ts.map +0 -1
  41. package/dist/common/string/createSearchLinkedMetadata.d.ts +0 -36
  42. package/dist/common/string/createSearchLinkedMetadata.d.ts.map +0 -1
  43. package/dist/common/string/index.d.ts +0 -10
  44. package/dist/common/string/index.d.ts.map +0 -1
  45. package/dist/common/string/updateSearchMetadata.d.ts +0 -103
  46. package/dist/common/string/updateSearchMetadata.d.ts.map +0 -1
  47. package/dist/common/syncSearchArrayMetadata.d.ts +0 -68
  48. package/dist/common/syncSearchArrayMetadata.d.ts.map +0 -1
  49. package/dist/common/updateSearchLinkedMetadata.d.ts +0 -139
  50. package/dist/common/updateSearchLinkedMetadata.d.ts.map +0 -1
  51. package/dist/string.cjs +0 -930
  52. package/dist/string.d.cts +0 -26
  53. package/dist/string.d.ts +0 -26
  54. package/dist/string.d.ts.map +0 -1
  55. package/dist/string.js +0 -898
  56. package/dist/utils/matchToken.d.ts +0 -65
  57. package/dist/utils/matchToken.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -45,7 +45,9 @@ __export(exports_src, {
45
45
  searchInData: () => searchInDataReturnIdsNumeric,
46
46
  searchAndRankNumeric: () => searchAndRankNumeric,
47
47
  searchAndRank: () => searchAndRankNumeric,
48
+ scoreOrderAndPresence: () => scoreOrderAndPresence,
48
49
  scoreOne: () => scoreOne,
50
+ scoreBag: () => scoreBag,
49
51
  sanitiseString: () => sanitiseString,
50
52
  resetTokenRegistry: () => resetTokenRegistry,
51
53
  removePrimaryItem: () => removePrimaryItemNumeric,
@@ -81,6 +83,8 @@ __export(exports_src, {
81
83
  allPreparedTokensMatch: () => allPreparedTokensMatch,
82
84
  TOKEN_ID_MASK: () => TOKEN_ID_MASK,
83
85
  SearchUniverse: () => SearchUniverse,
86
+ PREFIX_TO_MATCH_MODE: () => PREFIX_TO_MATCH_MODE,
87
+ MATCH_MODE_PREFIXES: () => MATCH_MODE_PREFIXES,
84
88
  MATCH_MODE_BITS: () => MATCH_MODE_BITS,
85
89
  DEFAULT_SEARCH_OPTS: () => DEFAULT_SEARCH_OPTS,
86
90
  AugmentedMetadata: () => AugmentedMetadata
@@ -88,6 +92,7 @@ __export(exports_src, {
88
92
  module.exports = __toCommonJS(exports_src);
89
93
 
90
94
  // src/types/index.ts
95
+ var DEFAULT_FIRST_TOKENS_N = 8;
91
96
  var DEFAULT_SEARCH_OPTS = {
92
97
  matchWords: "start",
93
98
  matchNumbers: "startEnd",
@@ -357,11 +362,23 @@ function prepareStringForSearchNumeric(input, tokensMap) {
357
362
  for (let i = 0;i < stringTokens.length; i++) {
358
363
  numericTokens[i] = getOrCreateTokenId(stringTokens[i], tokensMap);
359
364
  }
360
- numericTokens.sort((a, b) => a - b);
361
- return new Uint32Array(numericTokens);
365
+ const unsorted = Uint32Array.from(numericTokens);
366
+ const sortedArr = numericTokens.slice().sort((a, b) => a - b);
367
+ const sorted = Uint32Array.from(sortedArr);
368
+ return { sorted, unsorted };
362
369
  }
363
370
 
364
- // src/common/string/createSearchArrayMetadata.ts
371
+ // src/common/numeric/createSearchArrayMetadata.ts
372
+ function isDeletedOrBlocked(obj) {
373
+ return obj._deleted != null || obj._blocked != null;
374
+ }
375
+ var MATCH_MODE_PREFIXES = {
376
+ start: "<",
377
+ end: ">",
378
+ startEnd: "+",
379
+ anywhere: "*",
380
+ whole: "!"
381
+ };
365
382
  var PREFIX_TO_MATCH_MODE = {
366
383
  "<": "start",
367
384
  ">": "end",
@@ -371,12 +388,6 @@ var PREFIX_TO_MATCH_MODE = {
371
388
  "=": "whole",
372
389
  "?": "anywhere"
373
390
  };
374
- var DEFAULT_EXCLUDED_FIELDS = new Set(["_id", "_deleted", "_blocked"]);
375
- function isDeletedOrBlocked(obj) {
376
- return obj._deleted != null || obj._blocked != null;
377
- }
378
-
379
- // src/common/numeric/createSearchArrayMetadata.ts
380
391
  var MATCH_MODE_BITS = {
381
392
  start: 0,
382
393
  end: 1,
@@ -407,7 +418,7 @@ function decodeMatchMode(encoded) {
407
418
  return "start";
408
419
  }
409
420
  }
410
- var DEFAULT_EXCLUDED_FIELDS2 = new Set(["_id", "_deleted", "_blocked"]);
421
+ var DEFAULT_EXCLUDED_FIELDS = new Set(["_id", "_deleted", "_blocked"]);
411
422
  function getValueByPath(obj, path) {
412
423
  const parts = path.split(".");
413
424
  let current = obj;
@@ -464,26 +475,29 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
464
475
  if (isDeletedOrBlocked(obj)) {
465
476
  return;
466
477
  }
467
- if (spec?.extractSearchableStringFn) {
468
- const searchableString = spec.extractSearchableStringFn(obj);
469
- if (searchableString === undefined) {
470
- return;
471
- }
472
- return prepareStringForSearchNumeric(searchableString, tokensMap);
473
- }
474
- return prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap);
475
- }
476
- function prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap) {
477
478
  if (spec?.enrichSearchObjectFn) {
478
479
  spec.enrichSearchObjectFn(obj, spec.searchContext);
479
480
  }
481
+ const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
482
+ if (sorted === undefined)
483
+ return;
484
+ const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
485
+ return { sorted, firstN };
486
+ }
487
+ function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
488
+ const str = extractFn(obj);
489
+ if (str === undefined)
490
+ return;
491
+ return prepareStringForSearchNumeric(str, tokensMap).sorted;
492
+ }
493
+ function buildSortedFromFields(obj, spec, tokensMap) {
480
494
  const allTokens = [];
481
495
  const fieldMatchModes = spec?.fieldMatchModes;
482
496
  const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
483
497
  const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
484
498
  for (const key of fieldsToSearch) {
485
499
  const keyStr = key;
486
- if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS2.has(keyStr))
500
+ if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
487
501
  continue;
488
502
  if (fieldsToExclude.has(key))
489
503
  continue;
@@ -514,14 +528,42 @@ function prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap) {
514
528
  const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
515
529
  return new Uint32Array(uniqueTokens);
516
530
  }
531
+ function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
532
+ const strings = [];
533
+ for (const field of keyFields) {
534
+ const keyStr = field;
535
+ const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
536
+ if (value == null)
537
+ continue;
538
+ extractStringsFromValue(value, strings);
539
+ }
540
+ if (strings.length === 0)
541
+ return;
542
+ const text = strings.join(" ");
543
+ const datesNormalized = normalizeDates(text);
544
+ const preprocessed = preprocessString(datesNormalized);
545
+ const sanitised = sanitiseString(preprocessed);
546
+ const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
547
+ if (stringTokens.length === 0)
548
+ return;
549
+ const sliced = stringTokens.slice(0, n);
550
+ const out = new Uint32Array(sliced.length);
551
+ for (let i = 0;i < sliced.length; i++) {
552
+ out[i] = getOrCreateTokenId(sliced[i], tokensMap);
553
+ }
554
+ return out;
555
+ }
517
556
  function createSearchArrayMetadataNumeric(data, spec) {
518
557
  const tokensMap = createTokensMap();
519
558
  try {
520
- const metadata = new Map;
559
+ const metadata = emptyNumericSearchMetadata();
521
560
  for (const [id, item] of data) {
522
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
523
- if (tokens2 !== undefined) {
524
- metadata.set(id, tokens2);
561
+ const result = prepareObjectForSearchNumeric(item, spec, tokensMap);
562
+ if (result !== undefined) {
563
+ metadata.sorted.set(id, result.sorted);
564
+ if (result.firstN !== undefined) {
565
+ metadata.unsorted.set(id, result.firstN);
566
+ }
525
567
  }
526
568
  }
527
569
  return metadata;
@@ -529,6 +571,9 @@ function createSearchArrayMetadataNumeric(data, spec) {
529
571
  clearTokensMap(tokensMap);
530
572
  }
531
573
  }
574
+ function emptyNumericSearchMetadata() {
575
+ return { sorted: new Map, unsorted: new Map };
576
+ }
532
577
  // src/common/numeric/createSearchLinkedMetadata.ts
533
578
  function createSearchLinkedMetadataNumeric(primaryData, primaryMetadata, linkedData, linkedMetadata, foreignKeyGetter) {
534
579
  const primaryToLinked = new Map;
@@ -550,14 +595,19 @@ function createSearchLinkedMetadataNumeric(primaryData, primaryMetadata, linkedD
550
595
  }
551
596
  // src/common/numeric/updateSearchMetadata.ts
552
597
  function updateSearchMetadataNumeric(metadata, objectId, obj, spec, tokensMap) {
553
- const tokens2 = obj != null ? prepareObjectForSearchNumeric(obj, spec, tokensMap) : undefined;
554
- if (tokens2 === undefined) {
555
- metadata.delete(objectId);
598
+ const result = obj != null ? prepareObjectForSearchNumeric(obj, spec, tokensMap) : undefined;
599
+ if (result === undefined) {
600
+ metadata.sorted.delete(objectId);
601
+ metadata.unsorted.delete(objectId);
556
602
  return false;
603
+ }
604
+ metadata.sorted.set(objectId, result.sorted);
605
+ if (result.firstN !== undefined) {
606
+ metadata.unsorted.set(objectId, result.firstN);
557
607
  } else {
558
- metadata.set(objectId, tokens2);
559
- return true;
608
+ metadata.unsorted.delete(objectId);
560
609
  }
610
+ return true;
561
611
  }
562
612
  function updateSearchMetadataBatchNumeric(metadata, batch, spec) {
563
613
  if (batch.length === 0) {
@@ -581,7 +631,9 @@ function updateSearchMetadataBatchNumeric(metadata, batch, spec) {
581
631
  }
582
632
  }
583
633
  function removeFromSearchMetadataNumeric(metadata, objectId) {
584
- return metadata.delete(objectId);
634
+ const had = metadata.sorted.delete(objectId);
635
+ metadata.unsorted.delete(objectId);
636
+ return had;
585
637
  }
586
638
  function removeFromSearchMetadataBatchNumeric(metadata, objectIds) {
587
639
  if (objectIds.length === 0) {
@@ -589,9 +641,10 @@ function removeFromSearchMetadataBatchNumeric(metadata, objectIds) {
589
641
  }
590
642
  let removed = 0;
591
643
  for (const id of objectIds) {
592
- if (metadata.delete(id)) {
644
+ const had = metadata.sorted.delete(id);
645
+ metadata.unsorted.delete(id);
646
+ if (had)
593
647
  removed++;
594
- }
595
648
  }
596
649
  return removed;
597
650
  }
@@ -781,6 +834,106 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
781
834
  return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
782
835
  }
783
836
 
837
+ // src/common/numeric/scoring.ts
838
+ function orderedTokenize(input) {
839
+ return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
840
+ }
841
+ function scoreOne(query, qTokens, itemTokens, exactKeys) {
842
+ if (exactKeys && exactKeys.length > 0) {
843
+ for (let i = 0;i < exactKeys.length; i++) {
844
+ if (exactKeys[i] === query)
845
+ return 1e5;
846
+ }
847
+ }
848
+ if (qTokens.length === 0)
849
+ return 0;
850
+ if (itemTokens.length === 0)
851
+ return 1;
852
+ let qi = 0;
853
+ const positions = [];
854
+ for (let i = 0;i < itemTokens.length; i++) {
855
+ if (qi < qTokens.length && itemTokens[i].startsWith(qTokens[qi])) {
856
+ positions.push(i);
857
+ qi++;
858
+ }
859
+ }
860
+ if (qi === 0)
861
+ return 1;
862
+ let score = 50;
863
+ if (qi === qTokens.length)
864
+ score += 100;
865
+ if (positions[0] === 0)
866
+ score += 50;
867
+ let perfect = qi === qTokens.length;
868
+ if (perfect) {
869
+ for (let i = 0;i < positions.length; i++) {
870
+ if (positions[i] !== i) {
871
+ perfect = false;
872
+ break;
873
+ }
874
+ }
875
+ }
876
+ if (perfect)
877
+ score += 80;
878
+ if (positions.length >= 2) {
879
+ const span = positions[positions.length - 1] - positions[0] + 1;
880
+ score += positions.length / span * 30;
881
+ }
882
+ let totalLen = 0;
883
+ for (const t of itemTokens)
884
+ totalLen += t.length + 1;
885
+ score -= totalLen * 0.1;
886
+ return score;
887
+ }
888
+ function scoreBag(qTokens, itemTokens, perTokenWeight = 30) {
889
+ if (qTokens.length === 0 || itemTokens.length === 0)
890
+ return 0;
891
+ const usedItemIdx = new Set;
892
+ let score = 0;
893
+ for (const qt of qTokens) {
894
+ for (let i = 0;i < itemTokens.length; i++) {
895
+ if (usedItemIdx.has(i))
896
+ continue;
897
+ if (itemTokens[i].startsWith(qt)) {
898
+ score += perTokenWeight;
899
+ usedItemIdx.add(i);
900
+ break;
901
+ }
902
+ }
903
+ }
904
+ return score;
905
+ }
906
+ function scoreOrderAndPresence(qTokens, itemTokens, weights) {
907
+ const orderedWeight = weights?.ordered ?? 30;
908
+ const presenceWeight = weights?.presence ?? 10;
909
+ if (qTokens.length === 0 || itemTokens.length === 0)
910
+ return 0;
911
+ const matchedQueryIndices = new Set;
912
+ let qi = 0;
913
+ let score = 0;
914
+ for (let i = 0;i < itemTokens.length && qi < qTokens.length; i++) {
915
+ if (itemTokens[i].startsWith(qTokens[qi])) {
916
+ score += orderedWeight;
917
+ matchedQueryIndices.add(qi);
918
+ qi++;
919
+ }
920
+ }
921
+ if (matchedQueryIndices.size < qTokens.length) {
922
+ for (let qj = 0;qj < qTokens.length; qj++) {
923
+ if (matchedQueryIndices.has(qj))
924
+ continue;
925
+ const qt = qTokens[qj];
926
+ for (let i = 0;i < itemTokens.length; i++) {
927
+ if (itemTokens[i].startsWith(qt)) {
928
+ score += presenceWeight;
929
+ break;
930
+ }
931
+ }
932
+ }
933
+ }
934
+ return score;
935
+ }
936
+
784
937
  // src/common/numeric/searchInData.ts
785
938
  function searchInDataReturnIdsNumeric(query, metadata, opts) {
786
939
  return searchInDataReturnIdsWithLimitNumeric(query, metadata, Infinity, opts);
@@ -798,20 +951,48 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
798
951
  }
799
952
  function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
800
953
  const queryTokenStrs = prepareStringForSearch(query);
801
- if (queryTokenStrs.length === 0) {
954
+ if (queryTokenStrs.length === 0)
802
955
  return [];
803
- }
804
956
  const preparedTokens = prepareQueryTokens(queryTokenStrs);
805
- const results = [];
806
- for (const [id, targetTokens] of metadata) {
957
+ const candidates = [];
958
+ for (const [id, targetTokens] of metadata.sorted) {
807
959
  if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
808
- results.push(id);
809
- if (results.length >= limit) {
810
- break;
811
- }
960
+ candidates.push(id);
812
961
  }
813
962
  }
814
- return results;
963
+ if (candidates.length === 0)
964
+ return [];
965
+ const qTokens = orderedTokenize(query);
966
+ const hasUnsorted = metadata.unsorted.size > 0;
967
+ if (!hasUnsorted) {
968
+ if (limit === Infinity || limit >= candidates.length)
969
+ return candidates;
970
+ return candidates.slice(0, limit);
971
+ }
972
+ const scored = new Array(candidates.length);
973
+ for (let i = 0;i < candidates.length; i++) {
974
+ const id = candidates[i];
975
+ const unsortedIds = metadata.unsorted.get(id);
976
+ if (unsortedIds === undefined) {
977
+ scored[i] = { id, score: 0, order: i };
978
+ continue;
979
+ }
980
+ const itemTokens = new Array(unsortedIds.length);
981
+ for (let j = 0;j < unsortedIds.length; j++) {
982
+ itemTokens[j] = tokens[unsortedIds[j]] ?? "";
983
+ }
984
+ scored[i] = { id, score: scoreOne(query, qTokens, itemTokens), order: i };
985
+ }
986
+ scored.sort((a, b) => {
987
+ if (a.score !== b.score)
988
+ return b.score - a.score;
989
+ return a.order - b.order;
990
+ });
991
+ const out = [];
992
+ const max = limit === Infinity ? scored.length : Math.min(limit, scored.length);
993
+ for (let i = 0;i < max; i++)
994
+ out.push(scored[i].id);
995
+ return out;
815
996
  }
816
997
  // src/common/numeric/findInLinkedArrays.ts
817
998
  function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
@@ -822,11 +1003,10 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
822
1003
  const preparedTokens = prepareQueryTokens(queryTokenStrs);
823
1004
  const positiveTokens = preparedTokens.filter((t) => !t.negated);
824
1005
  const negatedTokens = preparedTokens.filter((t) => t.negated);
825
- const results = [];
1006
+ const scored = [];
1007
+ let orderCounter = 0;
826
1008
  for (const [primaryId, primary] of linked.primaryData) {
827
- if (results.length >= limit)
828
- break;
829
- const primaryTokens = linked.primaryMeta.get(primaryId);
1009
+ const primaryTokens = linked.primaryMeta.sorted.get(primaryId);
830
1010
  if (!primaryTokens)
831
1011
  continue;
832
1012
  const linkedIds = linked.primaryToLinked.get(primaryId) || [];
@@ -837,22 +1017,20 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
837
1017
  break;
838
1018
  }
839
1019
  }
840
- if (negatedMatchesInPrimary) {
1020
+ if (negatedMatchesInPrimary)
841
1021
  continue;
842
- }
843
1022
  let totalLength = primaryTokens.length;
844
1023
  for (const linkedId of linkedIds) {
845
- const linkedTokens = linked.linkedMeta.get(linkedId);
846
- if (linkedTokens) {
1024
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
1025
+ if (linkedTokens)
847
1026
  totalLength += linkedTokens.length;
848
- }
849
1027
  }
850
1028
  const allTokens = new Uint32Array(totalLength);
851
1029
  let offset = 0;
852
1030
  allTokens.set(primaryTokens, offset);
853
1031
  offset += primaryTokens.length;
854
1032
  for (const linkedId of linkedIds) {
855
- const linkedTokens = linked.linkedMeta.get(linkedId);
1033
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
856
1034
  if (linkedTokens) {
857
1035
  allTokens.set(linkedTokens, offset);
858
1036
  offset += linkedTokens.length;
@@ -866,230 +1044,289 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
866
1044
  break;
867
1045
  }
868
1046
  }
869
- if (allPositiveMatch) {
870
- let anyMatchInPrimary = false;
871
- let anyMatchInLinked = false;
872
- const matchingLinkedIds = new Set;
873
- const tokensMatchingPrimary = new Set;
874
- for (const preparedToken of positiveTokens) {
875
- if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
876
- anyMatchInPrimary = true;
877
- tokensMatchingPrimary.add(preparedToken);
878
- }
1047
+ if (!allPositiveMatch)
1048
+ continue;
1049
+ let anyMatchInPrimary = false;
1050
+ let anyMatchInLinked = false;
1051
+ const matchingLinkedIds = new Set;
1052
+ const tokensMatchingPrimary = new Set;
1053
+ for (const preparedToken of positiveTokens) {
1054
+ if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
1055
+ anyMatchInPrimary = true;
1056
+ tokensMatchingPrimary.add(preparedToken);
879
1057
  }
880
- const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
881
- if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
882
- for (const linkedId of linkedIds) {
883
- const linkedTokens = linked.linkedMeta.get(linkedId);
884
- if (linkedTokens) {
885
- let matchesNegatedToken = false;
886
- for (const negatedToken of negatedTokens) {
887
- if (matchPreparedTokenInSortedList(negatedToken, linkedTokens, opts)) {
888
- matchesNegatedToken = true;
889
- break;
890
- }
891
- }
892
- if (matchesNegatedToken) {
893
- continue;
894
- }
895
- if (tokensForLinkedMatching.length > 0) {
896
- const matchesAllNonPrimaryTokens = tokensForLinkedMatching.every((preparedToken) => matchPreparedTokenInSortedList(preparedToken, linkedTokens, opts));
897
- if (matchesAllNonPrimaryTokens) {
898
- anyMatchInLinked = true;
899
- matchingLinkedIds.add(linkedId);
900
- }
901
- } else {
902
- matchingLinkedIds.add(linkedId);
903
- }
1058
+ }
1059
+ const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
1060
+ if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
1061
+ for (const linkedId of linkedIds) {
1062
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
1063
+ if (!linkedTokens)
1064
+ continue;
1065
+ let matchesNegatedToken = false;
1066
+ for (const negatedToken of negatedTokens) {
1067
+ if (matchPreparedTokenInSortedList(negatedToken, linkedTokens, opts)) {
1068
+ matchesNegatedToken = true;
1069
+ break;
904
1070
  }
905
1071
  }
1072
+ if (matchesNegatedToken)
1073
+ continue;
1074
+ if (tokensForLinkedMatching.length > 0) {
1075
+ const matchesAllNonPrimaryTokens = tokensForLinkedMatching.every((preparedToken) => matchPreparedTokenInSortedList(preparedToken, linkedTokens, opts));
1076
+ if (matchesAllNonPrimaryTokens) {
1077
+ anyMatchInLinked = true;
1078
+ matchingLinkedIds.add(linkedId);
1079
+ }
1080
+ } else {
1081
+ matchingLinkedIds.add(linkedId);
1082
+ }
906
1083
  }
907
- if (tokensForLinkedMatching.length > 0 && matchingLinkedIds.size === 0) {
1084
+ }
1085
+ if (tokensForLinkedMatching.length > 0 && matchingLinkedIds.size === 0) {
1086
+ continue;
1087
+ }
1088
+ const matchedLinked = Array.from(matchingLinkedIds).map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
1089
+ const result = {
1090
+ primary,
1091
+ linked: matchedLinked,
1092
+ matchedIn: anyMatchInPrimary && anyMatchInLinked ? "both" : anyMatchInPrimary ? "primary" : "linked"
1093
+ };
1094
+ scored.push({
1095
+ result,
1096
+ score: 0,
1097
+ order: orderCounter++,
1098
+ primaryId,
1099
+ matchedLinkedIds: Array.from(matchingLinkedIds)
1100
+ });
1101
+ }
1102
+ if (scored.length === 0) {
1103
+ return { queryTokens: queryTokenStrs, results: [], hasNegation: negatedTokens.length > 0 };
1104
+ }
1105
+ const qTokens = orderedTokenize(query);
1106
+ const hasUnsorted = linked.primaryMeta.unsorted.size > 0 || linked.linkedMeta.unsorted.size > 0;
1107
+ if (hasUnsorted) {
1108
+ for (const s of scored) {
1109
+ const itemTokenIds = [];
1110
+ const pu = linked.primaryMeta.unsorted.get(s.primaryId);
1111
+ if (pu)
1112
+ for (const tid of pu)
1113
+ itemTokenIds.push(tid);
1114
+ for (const lid of s.matchedLinkedIds) {
1115
+ const lu = linked.linkedMeta.unsorted.get(lid);
1116
+ if (lu)
1117
+ for (const tid of lu)
1118
+ itemTokenIds.push(tid);
1119
+ }
1120
+ if (itemTokenIds.length === 0) {
1121
+ s.score = 0;
908
1122
  continue;
909
1123
  }
910
- const matchedLinked = Array.from(matchingLinkedIds).map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
911
- results.push({
912
- primary,
913
- linked: matchedLinked,
914
- matchedIn: anyMatchInPrimary && anyMatchInLinked ? "both" : anyMatchInPrimary ? "primary" : "linked"
915
- });
1124
+ const itemTokens = new Array(itemTokenIds.length);
1125
+ for (let i = 0;i < itemTokenIds.length; i++) {
1126
+ itemTokens[i] = tokens[itemTokenIds[i]] ?? "";
1127
+ }
1128
+ s.score = scoreOne(query, qTokens, itemTokens);
916
1129
  }
917
1130
  }
1131
+ scored.sort((a, b) => {
1132
+ if (a.score !== b.score)
1133
+ return b.score - a.score;
1134
+ return a.order - b.order;
1135
+ });
1136
+ const max = Math.min(limit, scored.length);
1137
+ const results = new Array(max);
1138
+ for (let i = 0;i < max; i++)
1139
+ results[i] = scored[i].result;
918
1140
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
919
1141
  }
920
1142
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
921
1143
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
922
1144
  }
923
1145
  // src/common/numeric/AugmentedMetadata.ts
924
- class AugmentedMetadata {
925
- baseMetadata;
926
- augmentedTokens;
927
- spec;
928
- constructor(baseMetadata, spec, augmentedDataMap) {
929
- this.baseMetadata = baseMetadata;
930
- this.augmentedTokens = new Map;
931
- this.spec = spec;
932
- if (augmentedDataMap && augmentedDataMap.size > 0) {
933
- this.updateAugmentedBatch(augmentedDataMap);
934
- }
935
- }
936
- updateAugmented(id, augmentedData) {
937
- if (!augmentedData) {
938
- this.augmentedTokens.delete(id);
939
- return;
940
- }
941
- const tokensMap = createTokensMap();
942
- const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
943
- if (tokens2 && tokens2.length > 0) {
944
- this.augmentedTokens.set(id, tokens2);
1146
+ function mergeSortedTokenArrays(a, b) {
1147
+ const merged = new Uint32Array(a.length + b.length);
1148
+ let i = 0, j = 0, k = 0;
1149
+ let lastToken = -1;
1150
+ while (i < a.length && j < b.length) {
1151
+ const tokenA = a[i];
1152
+ const tokenB = b[j];
1153
+ if (tokenA < tokenB) {
1154
+ if (tokenA !== lastToken) {
1155
+ merged[k++] = tokenA;
1156
+ lastToken = tokenA;
1157
+ }
1158
+ i++;
1159
+ } else if (tokenA > tokenB) {
1160
+ if (tokenB !== lastToken) {
1161
+ merged[k++] = tokenB;
1162
+ lastToken = tokenB;
1163
+ }
1164
+ j++;
945
1165
  } else {
946
- this.augmentedTokens.delete(id);
1166
+ if (tokenA !== lastToken) {
1167
+ merged[k++] = tokenA;
1168
+ lastToken = tokenA;
1169
+ }
1170
+ i++;
1171
+ j++;
947
1172
  }
948
- clearTokensMap(tokensMap);
949
1173
  }
950
- updateAugmentedBatch(augmentedDataMap) {
951
- const tokensMap = createTokensMap();
952
- for (const [id, augmentedData] of augmentedDataMap) {
953
- if (!augmentedData) {
954
- this.augmentedTokens.delete(id);
955
- continue;
956
- }
957
- const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
958
- if (tokens2 && tokens2.length > 0) {
959
- this.augmentedTokens.set(id, tokens2);
960
- } else {
961
- this.augmentedTokens.delete(id);
962
- }
1174
+ while (i < a.length) {
1175
+ const tokenA = a[i];
1176
+ if (tokenA !== lastToken) {
1177
+ merged[k++] = tokenA;
1178
+ lastToken = tokenA;
963
1179
  }
964
- clearTokensMap(tokensMap);
1180
+ i++;
965
1181
  }
966
- clearAugmented(id) {
967
- this.augmentedTokens.delete(id);
1182
+ while (j < b.length) {
1183
+ const tokenB = b[j];
1184
+ if (tokenB !== lastToken) {
1185
+ merged[k++] = tokenB;
1186
+ lastToken = tokenB;
1187
+ }
1188
+ j++;
968
1189
  }
969
- clearAllAugmented() {
970
- this.augmentedTokens.clear();
1190
+ return merged.slice(0, k);
1191
+ }
1192
+
1193
+ class AugmentedSortedMap extends Map {
1194
+ base;
1195
+ augTokens;
1196
+ constructor(base, augTokens) {
1197
+ super();
1198
+ this.base = base;
1199
+ this.augTokens = augTokens;
971
1200
  }
972
1201
  get(id) {
973
- const baseTokens = this.baseMetadata.get(id);
974
- if (!baseTokens)
975
- return;
976
- const augTokens = this.augmentedTokens.get(id);
977
- if (!augTokens)
1202
+ const baseTokens = this.base.get(id);
1203
+ const augTokens = this.augTokens.get(id);
1204
+ if (baseTokens === undefined)
1205
+ return augTokens;
1206
+ if (augTokens === undefined)
978
1207
  return baseTokens;
979
- return this.mergeSortedTokenArrays(baseTokens, augTokens);
1208
+ return mergeSortedTokenArrays(baseTokens, augTokens);
980
1209
  }
981
1210
  has(id) {
982
- return this.baseMetadata.has(id);
1211
+ return this.base.has(id) || this.augTokens.has(id);
983
1212
  }
984
1213
  get size() {
985
- return this.baseMetadata.size;
986
- }
987
- [Symbol.iterator]() {
988
- const self = this;
989
- const baseIterator = this.baseMetadata[Symbol.iterator]();
990
- return {
991
- next() {
992
- const baseResult = baseIterator.next();
993
- if (baseResult.done) {
994
- return { done: true, value: undefined };
995
- }
996
- const [id, baseTokens] = baseResult.value;
997
- const augTokens = self.augmentedTokens.get(id);
998
- const tokens2 = augTokens ? self.mergeSortedTokenArrays(baseTokens, augTokens) : baseTokens;
999
- return { done: false, value: [id, tokens2] };
1000
- },
1001
- [Symbol.iterator]() {
1002
- return this;
1003
- },
1004
- [Symbol.dispose]() {}
1005
- };
1214
+ if (this.augTokens.size === 0)
1215
+ return this.base.size;
1216
+ let count = this.base.size;
1217
+ for (const id of this.augTokens.keys()) {
1218
+ if (!this.base.has(id))
1219
+ count++;
1220
+ }
1221
+ return count;
1006
1222
  }
1007
1223
  keys() {
1008
- return this.baseMetadata.keys();
1224
+ return this.unionIdIterator();
1009
1225
  }
1010
1226
  values() {
1011
1227
  const self = this;
1012
- const keysIterator = this.baseMetadata.keys();
1013
- return {
1014
- next() {
1015
- const keyResult = keysIterator.next();
1016
- if (keyResult.done) {
1017
- return { done: true, value: undefined };
1018
- }
1019
- const tokens2 = self.get(keyResult.value);
1020
- return tokens2 ? { done: false, value: tokens2 } : { done: true, value: undefined };
1021
- },
1022
- [Symbol.iterator]() {
1023
- return this;
1024
- },
1025
- [Symbol.dispose]() {}
1026
- };
1228
+ function* gen() {
1229
+ for (const id of self.unionIdIterator()) {
1230
+ const tokens2 = self.get(id);
1231
+ if (tokens2 !== undefined)
1232
+ yield tokens2;
1233
+ }
1234
+ }
1235
+ return gen();
1027
1236
  }
1028
1237
  entries() {
1029
- return this[Symbol.iterator]();
1238
+ const self = this;
1239
+ function* gen() {
1240
+ for (const id of self.unionIdIterator()) {
1241
+ const tokens2 = self.get(id);
1242
+ if (tokens2 !== undefined)
1243
+ yield [id, tokens2];
1244
+ }
1245
+ }
1246
+ return gen();
1247
+ }
1248
+ [Symbol.iterator]() {
1249
+ return this.entries();
1030
1250
  }
1031
1251
  forEach(callbackfn) {
1032
- for (const [id, tokens2] of this) {
1252
+ for (const [id, tokens2] of this.entries()) {
1033
1253
  callbackfn(tokens2, id, this);
1034
1254
  }
1035
1255
  }
1036
1256
  set(_key, _value) {
1037
- throw new Error("AugmentedMetadata.set() is not supported. Use updateAugmented() instead.");
1257
+ throw new Error("AugmentedMetadata.sorted is read-only. Use updateAugmented() instead.");
1038
1258
  }
1039
1259
  delete(_key) {
1040
- throw new Error("AugmentedMetadata.delete() is not supported. Use clearAugmented() instead.");
1260
+ throw new Error("AugmentedMetadata.sorted is read-only. Use clearAugmented() instead.");
1041
1261
  }
1042
1262
  clear() {
1043
- throw new Error("AugmentedMetadata.clear() is not supported. Use clearAllAugmented() instead.");
1263
+ throw new Error("AugmentedMetadata.sorted is read-only. Use clearAllAugmented() instead.");
1044
1264
  }
1045
1265
  get [Symbol.toStringTag]() {
1046
- return "AugmentedMetadata";
1047
- }
1048
- mergeSortedTokenArrays(a, b) {
1049
- const merged = new Uint32Array(a.length + b.length);
1050
- let i = 0, j = 0, k = 0;
1051
- let lastToken = -1;
1052
- while (i < a.length && j < b.length) {
1053
- const tokenA = a[i];
1054
- const tokenB = b[j];
1055
- if (tokenA < tokenB) {
1056
- if (tokenA !== lastToken) {
1057
- merged[k++] = tokenA;
1058
- lastToken = tokenA;
1059
- }
1060
- i++;
1061
- } else if (tokenA > tokenB) {
1062
- if (tokenB !== lastToken) {
1063
- merged[k++] = tokenB;
1064
- lastToken = tokenB;
1065
- }
1066
- j++;
1067
- } else {
1068
- if (tokenA !== lastToken) {
1069
- merged[k++] = tokenA;
1070
- lastToken = tokenA;
1071
- }
1072
- i++;
1073
- j++;
1074
- }
1266
+ return "AugmentedSortedMap";
1267
+ }
1268
+ *unionIdIterator() {
1269
+ const seen = new Set;
1270
+ for (const id of this.base.keys()) {
1271
+ seen.add(id);
1272
+ yield id;
1075
1273
  }
1076
- while (i < a.length) {
1077
- const tokenA = a[i];
1078
- if (tokenA !== lastToken) {
1079
- merged[k++] = tokenA;
1080
- lastToken = tokenA;
1081
- }
1082
- i++;
1274
+ for (const id of this.augTokens.keys()) {
1275
+ if (!seen.has(id))
1276
+ yield id;
1083
1277
  }
1084
- while (j < b.length) {
1085
- const tokenB = b[j];
1086
- if (tokenB !== lastToken) {
1087
- merged[k++] = tokenB;
1088
- lastToken = tokenB;
1278
+ }
1279
+ }
1280
+
1281
+ class AugmentedMetadata {
1282
+ sorted;
1283
+ unsorted;
1284
+ augmentedTokens;
1285
+ spec;
1286
+ constructor(baseMetadata, spec, augmentedDataMap) {
1287
+ this.augmentedTokens = new Map;
1288
+ this.spec = spec;
1289
+ this.sorted = new AugmentedSortedMap(baseMetadata.sorted, this.augmentedTokens);
1290
+ this.unsorted = baseMetadata.unsorted;
1291
+ if (augmentedDataMap && augmentedDataMap.size > 0) {
1292
+ this.updateAugmentedBatch(augmentedDataMap);
1293
+ }
1294
+ }
1295
+ updateAugmented(id, augmentedData) {
1296
+ if (augmentedData == null) {
1297
+ this.augmentedTokens.delete(id);
1298
+ return;
1299
+ }
1300
+ const tokensMap = createTokensMap();
1301
+ const prepared = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
1302
+ clearTokensMap(tokensMap);
1303
+ if (prepared !== undefined && prepared.sorted.length > 0) {
1304
+ this.augmentedTokens.set(id, prepared.sorted);
1305
+ } else {
1306
+ this.augmentedTokens.delete(id);
1307
+ }
1308
+ }
1309
+ updateAugmentedBatch(augmentedDataMap) {
1310
+ const tokensMap = createTokensMap();
1311
+ for (const [id, augmentedData] of augmentedDataMap) {
1312
+ if (augmentedData == null) {
1313
+ this.augmentedTokens.delete(id);
1314
+ continue;
1315
+ }
1316
+ const prepared = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
1317
+ if (prepared !== undefined && prepared.sorted.length > 0) {
1318
+ this.augmentedTokens.set(id, prepared.sorted);
1319
+ } else {
1320
+ this.augmentedTokens.delete(id);
1089
1321
  }
1090
- j++;
1091
1322
  }
1092
- return merged.slice(0, k);
1323
+ clearTokensMap(tokensMap);
1324
+ }
1325
+ clearAugmented(id) {
1326
+ this.augmentedTokens.delete(id);
1327
+ }
1328
+ clearAllAugmented() {
1329
+ this.augmentedTokens.clear();
1093
1330
  }
1094
1331
  }
1095
1332
  // src/common/numeric/syncSearchArrayMetadata.ts
@@ -1102,6 +1339,25 @@ function tokensEqual(a, b) {
1102
1339
  }
1103
1340
  return true;
1104
1341
  }
1342
+ function firstNEqual(a, b) {
1343
+ if (a === undefined && b === undefined)
1344
+ return true;
1345
+ if (a === undefined || b === undefined)
1346
+ return false;
1347
+ return tokensEqual(a, b);
1348
+ }
1349
+ function applyPreparedTokens(metadata, id, result) {
1350
+ metadata.sorted.set(id, result.sorted);
1351
+ if (result.firstN !== undefined) {
1352
+ metadata.unsorted.set(id, result.firstN);
1353
+ } else {
1354
+ metadata.unsorted.delete(id);
1355
+ }
1356
+ }
1357
+ function deletePreparedTokens(metadata, id) {
1358
+ metadata.sorted.delete(id);
1359
+ metadata.unsorted.delete(id);
1360
+ }
1105
1361
  function syncSearchArrayMetadataNumeric(metadata, newData, spec) {
1106
1362
  const result = {
1107
1363
  added: 0,
@@ -1112,30 +1368,31 @@ function syncSearchArrayMetadataNumeric(metadata, newData, spec) {
1112
1368
  removedIds: []
1113
1369
  };
1114
1370
  const tokensMap = createTokensMap();
1115
- for (const id of metadata.keys()) {
1371
+ for (const id of metadata.sorted.keys()) {
1116
1372
  if (!newData.has(id)) {
1117
- metadata.delete(id);
1373
+ deletePreparedTokens(metadata, id);
1118
1374
  result.removed++;
1119
1375
  result.removedIds.push(id);
1120
1376
  }
1121
1377
  }
1122
1378
  for (const [id, item] of newData) {
1123
- const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1124
- if (newTokens === undefined) {
1125
- if (metadata.has(id)) {
1126
- metadata.delete(id);
1379
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1380
+ if (prepared === undefined) {
1381
+ if (metadata.sorted.has(id)) {
1382
+ deletePreparedTokens(metadata, id);
1127
1383
  result.removed++;
1128
1384
  result.removedIds.push(id);
1129
1385
  }
1130
1386
  continue;
1131
1387
  }
1132
- const existingTokens = metadata.get(id);
1133
- if (existingTokens === undefined) {
1134
- metadata.set(id, newTokens);
1388
+ const existingSorted = metadata.sorted.get(id);
1389
+ const existingFirstN = metadata.unsorted.get(id);
1390
+ if (existingSorted === undefined) {
1391
+ applyPreparedTokens(metadata, id, prepared);
1135
1392
  result.added++;
1136
1393
  result.addedIds.push(id);
1137
- } else if (!tokensEqual(existingTokens, newTokens)) {
1138
- metadata.set(id, newTokens);
1394
+ } else if (!tokensEqual(existingSorted, prepared.sorted) || !firstNEqual(existingFirstN, prepared.firstN)) {
1395
+ applyPreparedTokens(metadata, id, prepared);
1139
1396
  result.updated++;
1140
1397
  result.updatedIds.push(id);
1141
1398
  }
@@ -1155,7 +1412,7 @@ function syncSearchArrayMetadataNumericWithPrevious(metadata, previousData, newD
1155
1412
  const tokensMap = createTokensMap();
1156
1413
  for (const id of previousData.keys()) {
1157
1414
  if (!newData.has(id)) {
1158
- metadata.delete(id);
1415
+ deletePreparedTokens(metadata, id);
1159
1416
  result.removed++;
1160
1417
  result.removedIds.push(id);
1161
1418
  }
@@ -1163,22 +1420,23 @@ function syncSearchArrayMetadataNumericWithPrevious(metadata, previousData, newD
1163
1420
  for (const [id, item] of newData) {
1164
1421
  const wasInPrevious = previousData.has(id);
1165
1422
  if (!wasInPrevious) {
1166
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1167
- if (tokens2 !== undefined) {
1168
- metadata.set(id, tokens2);
1423
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1424
+ if (prepared !== undefined) {
1425
+ applyPreparedTokens(metadata, id, prepared);
1169
1426
  result.added++;
1170
1427
  result.addedIds.push(id);
1171
1428
  }
1172
1429
  } else if (!options?.skipUpdateCheck) {
1173
- const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1174
- if (newTokens === undefined) {
1175
- metadata.delete(id);
1430
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1431
+ if (prepared === undefined) {
1432
+ deletePreparedTokens(metadata, id);
1176
1433
  result.removed++;
1177
1434
  result.removedIds.push(id);
1178
1435
  } else {
1179
- const existingTokens = metadata.get(id);
1180
- if (existingTokens && !tokensEqual(existingTokens, newTokens)) {
1181
- metadata.set(id, newTokens);
1436
+ const existingSorted = metadata.sorted.get(id);
1437
+ const existingFirstN = metadata.unsorted.get(id);
1438
+ if (existingSorted && (!tokensEqual(existingSorted, prepared.sorted) || !firstNEqual(existingFirstN, prepared.firstN))) {
1439
+ applyPreparedTokens(metadata, id, prepared);
1182
1440
  result.updated++;
1183
1441
  result.updatedIds.push(id);
1184
1442
  }
@@ -1198,29 +1456,48 @@ function tokensEqual2(a, b) {
1198
1456
  }
1199
1457
  return true;
1200
1458
  }
1459
+ function firstNEqual2(a, b) {
1460
+ if (a === undefined && b === undefined)
1461
+ return true;
1462
+ if (a === undefined || b === undefined)
1463
+ return false;
1464
+ return tokensEqual2(a, b);
1465
+ }
1466
+ function applyOrClear(meta, id, prepared) {
1467
+ if (prepared === undefined) {
1468
+ meta.sorted.delete(id);
1469
+ meta.unsorted.delete(id);
1470
+ return;
1471
+ }
1472
+ meta.sorted.set(id, prepared.sorted);
1473
+ if (prepared.firstN !== undefined) {
1474
+ meta.unsorted.set(id, prepared.firstN);
1475
+ } else {
1476
+ meta.unsorted.delete(id);
1477
+ }
1478
+ }
1479
+ function clearTokens(meta, id) {
1480
+ meta.sorted.delete(id);
1481
+ meta.unsorted.delete(id);
1482
+ }
1201
1483
  function upsertPrimaryItemNumeric(linked, item, spec) {
1202
1484
  const id = item._id;
1203
1485
  const wasAdded = !linked.primaryData.has(id);
1204
1486
  linked.primaryData.set(id, item);
1205
1487
  const tokensMap = createTokensMap();
1206
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1488
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1207
1489
  clearTokensMap(tokensMap);
1208
- if (tokens2 !== undefined) {
1209
- linked.primaryMeta.set(id, tokens2);
1210
- } else {
1211
- linked.primaryMeta.delete(id);
1212
- }
1490
+ applyOrClear(linked.primaryMeta, id, prepared);
1213
1491
  if (wasAdded && !linked.primaryToLinked.has(id)) {
1214
1492
  linked.primaryToLinked.set(id, []);
1215
1493
  }
1216
1494
  return { added: wasAdded };
1217
1495
  }
1218
1496
  function removePrimaryItemNumeric(linked, primaryId) {
1219
- if (!linked.primaryData.has(primaryId)) {
1497
+ if (!linked.primaryData.has(primaryId))
1220
1498
  return false;
1221
- }
1222
1499
  linked.primaryData.delete(primaryId);
1223
- linked.primaryMeta.delete(primaryId);
1500
+ clearTokens(linked.primaryMeta, primaryId);
1224
1501
  linked.primaryToLinked.delete(primaryId);
1225
1502
  return true;
1226
1503
  }
@@ -1229,35 +1506,28 @@ function upsertLinkedItemNumeric(linked, item, foreignKeyGetter, spec) {
1229
1506
  const newPrimaryId = foreignKeyGetter(item);
1230
1507
  let previousPrimaryId;
1231
1508
  const existingItem = linked.linkedData.get(id);
1232
- if (existingItem) {
1509
+ if (existingItem)
1233
1510
  previousPrimaryId = foreignKeyGetter(existingItem);
1234
- }
1235
1511
  const wasAdded = !existingItem;
1236
1512
  linked.linkedData.set(id, item);
1237
1513
  const tokensMap = createTokensMap();
1238
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1514
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1239
1515
  clearTokensMap(tokensMap);
1240
- if (tokens2 !== undefined) {
1241
- linked.linkedMeta.set(id, tokens2);
1242
- } else {
1243
- linked.linkedMeta.delete(id);
1244
- }
1516
+ applyOrClear(linked.linkedMeta, id, prepared);
1245
1517
  if (previousPrimaryId !== newPrimaryId) {
1246
1518
  if (previousPrimaryId) {
1247
1519
  const oldList = linked.primaryToLinked.get(previousPrimaryId);
1248
1520
  if (oldList) {
1249
1521
  const index = oldList.indexOf(id);
1250
- if (index !== -1) {
1522
+ if (index !== -1)
1251
1523
  oldList.splice(index, 1);
1252
- }
1253
1524
  }
1254
1525
  }
1255
1526
  if (newPrimaryId) {
1256
1527
  const newList = linked.primaryToLinked.get(newPrimaryId);
1257
1528
  if (newList) {
1258
- if (!newList.includes(id)) {
1529
+ if (!newList.includes(id))
1259
1530
  newList.push(id);
1260
- }
1261
1531
  } else {
1262
1532
  linked.primaryToLinked.set(newPrimaryId, [id]);
1263
1533
  }
@@ -1270,64 +1540,57 @@ function upsertLinkedItemNumeric(linked, item, foreignKeyGetter, spec) {
1270
1540
  }
1271
1541
  function removeLinkedItemNumeric(linked, linkedId, foreignKeyGetter) {
1272
1542
  const existingItem = linked.linkedData.get(linkedId);
1273
- if (!existingItem) {
1543
+ if (!existingItem)
1274
1544
  return false;
1275
- }
1276
1545
  const primaryId = foreignKeyGetter(existingItem);
1277
1546
  linked.linkedData.delete(linkedId);
1278
- linked.linkedMeta.delete(linkedId);
1547
+ clearTokens(linked.linkedMeta, linkedId);
1279
1548
  if (primaryId) {
1280
1549
  const list = linked.primaryToLinked.get(primaryId);
1281
1550
  if (list) {
1282
1551
  const index = list.indexOf(linkedId);
1283
- if (index !== -1) {
1552
+ if (index !== -1)
1284
1553
  list.splice(index, 1);
1285
- }
1286
1554
  }
1287
1555
  }
1288
1556
  return true;
1289
1557
  }
1290
1558
  function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, foreignKeyGetter, spec) {
1291
- const result = {
1292
- added: 0,
1293
- updated: 0,
1294
- removed: 0
1295
- };
1559
+ const result = { added: 0, updated: 0, removed: 0 };
1296
1560
  const tokensMap = createTokensMap();
1297
1561
  const currentLinkedIds = new Set(linked.primaryToLinked.get(primaryId) || []);
1298
1562
  const newLinkedIds = new Set;
1299
1563
  for (const [id, item] of newLinkedItems) {
1300
- if (foreignKeyGetter(item) === primaryId) {
1564
+ if (foreignKeyGetter(item) === primaryId)
1301
1565
  newLinkedIds.add(id);
1302
- }
1303
1566
  }
1304
1567
  for (const id of currentLinkedIds) {
1305
1568
  if (!newLinkedIds.has(id)) {
1306
- const item = linked.linkedData.get(id);
1307
- if (item) {
1569
+ if (linked.linkedData.has(id)) {
1308
1570
  linked.linkedData.delete(id);
1309
- linked.linkedMeta.delete(id);
1571
+ clearTokens(linked.linkedMeta, id);
1310
1572
  result.removed++;
1311
1573
  }
1312
1574
  }
1313
1575
  }
1314
1576
  for (const id of newLinkedIds) {
1315
1577
  const item = newLinkedItems.get(id);
1316
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1578
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1317
1579
  if (currentLinkedIds.has(id)) {
1318
- const existingTokens = linked.linkedMeta.get(id);
1319
- if (tokens2 !== undefined) {
1320
- if (!existingTokens || !tokensEqual2(existingTokens, tokens2)) {
1321
- linked.linkedMeta.set(id, tokens2);
1580
+ const existingSorted = linked.linkedMeta.sorted.get(id);
1581
+ const existingFirstN = linked.linkedMeta.unsorted.get(id);
1582
+ if (prepared !== undefined) {
1583
+ if (!existingSorted || !tokensEqual2(existingSorted, prepared.sorted) || !firstNEqual2(existingFirstN, prepared.firstN)) {
1584
+ applyOrClear(linked.linkedMeta, id, prepared);
1322
1585
  result.updated++;
1323
1586
  }
1587
+ } else {
1588
+ clearTokens(linked.linkedMeta, id);
1324
1589
  }
1325
1590
  linked.linkedData.set(id, item);
1326
1591
  } else {
1327
1592
  linked.linkedData.set(id, item);
1328
- if (tokens2 !== undefined) {
1329
- linked.linkedMeta.set(id, tokens2);
1330
- }
1593
+ applyOrClear(linked.linkedMeta, id, prepared);
1331
1594
  result.added++;
1332
1595
  }
1333
1596
  }
@@ -1336,54 +1599,14 @@ function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, for
1336
1599
  return result;
1337
1600
  }
1338
1601
  // src/common/numeric/searchAndRank.ts
1339
- function orderedTokenize(input) {
1340
- return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
1341
- }
1342
- function scoreOne(item, query, qTokens, primaryTextFn, exactKeys) {
1343
- if (exactKeys && exactKeys.length > 0) {
1344
- for (let i = 0;i < exactKeys.length; i++) {
1345
- if (exactKeys[i] === query)
1346
- return 1e5;
1347
- }
1348
- }
1349
- if (qTokens.length === 0)
1350
- return 0;
1351
- const primaryText = primaryTextFn(item) || "";
1352
- const itemTokens = orderedTokenize(primaryText);
1353
- if (itemTokens.length === 0)
1354
- return 1;
1355
- let qi = 0;
1356
- const positions = [];
1357
- for (let i = 0;i < itemTokens.length; i++) {
1358
- if (qi < qTokens.length && itemTokens[i].startsWith(qTokens[qi])) {
1359
- positions.push(i);
1360
- qi++;
1361
- }
1362
- }
1363
- if (qi === 0)
1364
- return 1;
1365
- let score = 50;
1366
- if (qi === qTokens.length)
1367
- score += 100;
1368
- if (positions[0] === 0)
1369
- score += 50;
1370
- let perfect = qi === qTokens.length;
1371
- if (perfect) {
1372
- for (let i = 0;i < positions.length; i++) {
1373
- if (positions[i] !== i) {
1374
- perfect = false;
1375
- break;
1376
- }
1377
- }
1378
- }
1379
- if (perfect)
1380
- score += 80;
1381
- if (positions.length >= 2) {
1382
- const span = positions[positions.length - 1] - positions[0] + 1;
1383
- score += positions.length / span * 30;
1602
+ function tokenIdsToStrings(tokenIds) {
1603
+ if (tokenIds === undefined)
1604
+ return;
1605
+ const out = new Array(tokenIds.length);
1606
+ for (let i = 0;i < tokenIds.length; i++) {
1607
+ out[i] = tokens[tokenIds[i]] ?? "";
1384
1608
  }
1385
- score -= primaryText.length * 0.1;
1386
- return score;
1609
+ return out;
1387
1610
  }
1388
1611
  function searchAndRankNumeric(query, data, metadata, options) {
1389
1612
  const { primaryTextFn, exactKeysFn, limit, opts } = options;
@@ -1396,8 +1619,9 @@ function searchAndRankNumeric(query, data, metadata, options) {
1396
1619
  const item = data.get(id);
1397
1620
  if (!item)
1398
1621
  continue;
1622
+ const itemTokens = tokenIdsToStrings(metadata.unsorted.get(id)) ?? orderedTokenize(primaryTextFn(item) || "");
1399
1623
  const exactKeys = exactKeysFn?.(item);
1400
- const score = scoreOne(item, query, qTokens, primaryTextFn, exactKeys);
1624
+ const score = scoreOne(query, qTokens, itemTokens, exactKeys);
1401
1625
  scored.push({ id, item, score });
1402
1626
  }
1403
1627
  scored.sort((x, y) => {
@@ -1421,6 +1645,13 @@ function tokensEqual3(a, b) {
1421
1645
  }
1422
1646
  return true;
1423
1647
  }
1648
+ function firstNEqual3(a, b) {
1649
+ if (a === undefined && b === undefined)
1650
+ return true;
1651
+ if (a === undefined || b === undefined)
1652
+ return false;
1653
+ return tokensEqual3(a, b);
1654
+ }
1424
1655
 
1425
1656
  class SearchUniverse {
1426
1657
  collections = new Map;
@@ -1430,7 +1661,7 @@ class SearchUniverse {
1430
1661
  }
1431
1662
  const collection = {
1432
1663
  data: new Map,
1433
- metadata: new Map,
1664
+ metadata: emptyNumericSearchMetadata(),
1434
1665
  config
1435
1666
  };
1436
1667
  if (config.linkedTo) {
@@ -1447,7 +1678,8 @@ class SearchUniverse {
1447
1678
  loadCollection(name, items) {
1448
1679
  const collection = this.getCollectionOrThrow(name);
1449
1680
  collection.data.clear();
1450
- collection.metadata.clear();
1681
+ collection.metadata.sorted.clear();
1682
+ collection.metadata.unsorted.clear();
1451
1683
  if (collection.primaryToLinked) {
1452
1684
  collection.primaryToLinked.clear();
1453
1685
  }
@@ -1455,8 +1687,11 @@ class SearchUniverse {
1455
1687
  collection.data.set(item._id, item);
1456
1688
  }
1457
1689
  const newMetadata = createSearchArrayMetadataNumeric(collection.data, collection.config.spec);
1458
- for (const [id, tokens2] of newMetadata) {
1459
- collection.metadata.set(id, tokens2);
1690
+ for (const [id, tokens2] of newMetadata.sorted) {
1691
+ collection.metadata.sorted.set(id, tokens2);
1692
+ }
1693
+ for (const [id, tokens2] of newMetadata.unsorted) {
1694
+ collection.metadata.unsorted.set(id, tokens2);
1460
1695
  }
1461
1696
  if (collection.config.linkedTo && collection.primaryToLinked) {
1462
1697
  const { foreignKeyGetter } = collection.config.linkedTo;
@@ -1552,11 +1787,17 @@ class SearchUniverse {
1552
1787
  collection.data.set(id, item);
1553
1788
  const tokensMap = createTokensMap();
1554
1789
  try {
1555
- const tokens2 = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1556
- if (tokens2 !== undefined) {
1557
- collection.metadata.set(id, tokens2);
1790
+ const prepared = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1791
+ if (prepared !== undefined) {
1792
+ collection.metadata.sorted.set(id, prepared.sorted);
1793
+ if (prepared.firstN !== undefined) {
1794
+ collection.metadata.unsorted.set(id, prepared.firstN);
1795
+ } else {
1796
+ collection.metadata.unsorted.delete(id);
1797
+ }
1558
1798
  } else {
1559
- collection.metadata.delete(id);
1799
+ collection.metadata.sorted.delete(id);
1800
+ collection.metadata.unsorted.delete(id);
1560
1801
  }
1561
1802
  } finally {
1562
1803
  clearTokensMap(tokensMap);
@@ -1610,7 +1851,8 @@ class SearchUniverse {
1610
1851
  }
1611
1852
  }
1612
1853
  collection.data.delete(id);
1613
- collection.metadata.delete(id);
1854
+ collection.metadata.sorted.delete(id);
1855
+ collection.metadata.unsorted.delete(id);
1614
1856
  return true;
1615
1857
  }
1616
1858
  removeFromSearchMetadataBatch(collectionName, ids) {
@@ -1661,9 +1903,10 @@ class SearchUniverse {
1661
1903
  }
1662
1904
  }
1663
1905
  }
1664
- const hadMetadata = collection.metadata.has(id);
1906
+ const hadMetadata = collection.metadata.sorted.has(id);
1665
1907
  collection.data.delete(id);
1666
- collection.metadata.delete(id);
1908
+ collection.metadata.sorted.delete(id);
1909
+ collection.metadata.unsorted.delete(id);
1667
1910
  if (hadMetadata) {
1668
1911
  result.removed++;
1669
1912
  result.removedIds.push(id);
@@ -1674,11 +1917,11 @@ class SearchUniverse {
1674
1917
  for (const item of newItems) {
1675
1918
  const id = item._id;
1676
1919
  const existing = collection.data.get(id);
1677
- const hadMetadata = collection.metadata.has(id);
1920
+ const hadMetadata = collection.metadata.sorted.has(id);
1678
1921
  const shouldTokenize = !existing || !skipUpdateCheck;
1679
- let newTokens;
1922
+ let prepared;
1680
1923
  if (shouldTokenize) {
1681
- newTokens = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1924
+ prepared = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1682
1925
  }
1683
1926
  if (fkGetter && collection.primaryToLinked) {
1684
1927
  const newFk = fkGetter(item);
@@ -1708,20 +1951,30 @@ class SearchUniverse {
1708
1951
  collection.data.set(id, item);
1709
1952
  if (!shouldTokenize)
1710
1953
  continue;
1711
- if (newTokens === undefined) {
1954
+ if (prepared === undefined) {
1712
1955
  if (hadMetadata) {
1713
- collection.metadata.delete(id);
1956
+ collection.metadata.sorted.delete(id);
1957
+ collection.metadata.unsorted.delete(id);
1714
1958
  result.removed++;
1715
1959
  result.removedIds.push(id);
1716
1960
  }
1717
1961
  } else if (!hadMetadata) {
1718
- collection.metadata.set(id, newTokens);
1962
+ collection.metadata.sorted.set(id, prepared.sorted);
1963
+ if (prepared.firstN !== undefined) {
1964
+ collection.metadata.unsorted.set(id, prepared.firstN);
1965
+ }
1719
1966
  result.added++;
1720
1967
  result.addedIds.push(id);
1721
1968
  } else {
1722
- const existingTokens = collection.metadata.get(id);
1723
- if (!tokensEqual3(existingTokens, newTokens)) {
1724
- collection.metadata.set(id, newTokens);
1969
+ const existingSorted = collection.metadata.sorted.get(id);
1970
+ const existingFirstN = collection.metadata.unsorted.get(id);
1971
+ if (!tokensEqual3(existingSorted, prepared.sorted) || !firstNEqual3(existingFirstN, prepared.firstN)) {
1972
+ collection.metadata.sorted.set(id, prepared.sorted);
1973
+ if (prepared.firstN !== undefined) {
1974
+ collection.metadata.unsorted.set(id, prepared.firstN);
1975
+ } else {
1976
+ collection.metadata.unsorted.delete(id);
1977
+ }
1725
1978
  result.updated++;
1726
1979
  result.updatedIds.push(id);
1727
1980
  }
@@ -1800,7 +2053,8 @@ class SearchUniverse {
1800
2053
  clear() {
1801
2054
  for (const collection of this.collections.values()) {
1802
2055
  collection.data.clear();
1803
- collection.metadata.clear();
2056
+ collection.metadata.sorted.clear();
2057
+ collection.metadata.unsorted.clear();
1804
2058
  if (collection.primaryToLinked) {
1805
2059
  collection.primaryToLinked.clear();
1806
2060
  }