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