cry-search 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/types/index.ts
2
+ var DEFAULT_FIRST_TOKENS_N = 8;
2
3
  var DEFAULT_SEARCH_OPTS = {
3
4
  matchWords: "start",
4
5
  matchNumbers: "startEnd",
@@ -268,11 +269,23 @@ function prepareStringForSearchNumeric(input, tokensMap) {
268
269
  for (let i = 0;i < stringTokens.length; i++) {
269
270
  numericTokens[i] = getOrCreateTokenId(stringTokens[i], tokensMap);
270
271
  }
271
- numericTokens.sort((a, b) => a - b);
272
- return new Uint32Array(numericTokens);
272
+ const unsorted = Uint32Array.from(numericTokens);
273
+ const sortedArr = numericTokens.slice().sort((a, b) => a - b);
274
+ const sorted = Uint32Array.from(sortedArr);
275
+ return { sorted, unsorted };
273
276
  }
274
277
 
275
- // src/common/string/createSearchArrayMetadata.ts
278
+ // src/common/numeric/createSearchArrayMetadata.ts
279
+ function isDeletedOrBlocked(obj) {
280
+ return obj._deleted != null || obj._blocked != null;
281
+ }
282
+ var MATCH_MODE_PREFIXES = {
283
+ start: "<",
284
+ end: ">",
285
+ startEnd: "+",
286
+ anywhere: "*",
287
+ whole: "!"
288
+ };
276
289
  var PREFIX_TO_MATCH_MODE = {
277
290
  "<": "start",
278
291
  ">": "end",
@@ -282,12 +295,6 @@ var PREFIX_TO_MATCH_MODE = {
282
295
  "=": "whole",
283
296
  "?": "anywhere"
284
297
  };
285
- var DEFAULT_EXCLUDED_FIELDS = new Set(["_id", "_deleted", "_blocked"]);
286
- function isDeletedOrBlocked(obj) {
287
- return obj._deleted != null || obj._blocked != null;
288
- }
289
-
290
- // src/common/numeric/createSearchArrayMetadata.ts
291
298
  var MATCH_MODE_BITS = {
292
299
  start: 0,
293
300
  end: 1,
@@ -318,7 +325,7 @@ function decodeMatchMode(encoded) {
318
325
  return "start";
319
326
  }
320
327
  }
321
- var DEFAULT_EXCLUDED_FIELDS2 = new Set(["_id", "_deleted", "_blocked"]);
328
+ var DEFAULT_EXCLUDED_FIELDS = new Set(["_id", "_deleted", "_blocked"]);
322
329
  function getValueByPath(obj, path) {
323
330
  const parts = path.split(".");
324
331
  let current = obj;
@@ -375,26 +382,29 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
375
382
  if (isDeletedOrBlocked(obj)) {
376
383
  return;
377
384
  }
378
- if (spec?.extractSearchableStringFn) {
379
- const searchableString = spec.extractSearchableStringFn(obj);
380
- if (searchableString === undefined) {
381
- return;
382
- }
383
- return prepareStringForSearchNumeric(searchableString, tokensMap);
384
- }
385
- return prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap);
386
- }
387
- function prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap) {
388
385
  if (spec?.enrichSearchObjectFn) {
389
386
  spec.enrichSearchObjectFn(obj, spec.searchContext);
390
387
  }
388
+ const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
389
+ if (sorted === undefined)
390
+ return;
391
+ const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
392
+ return { sorted, firstN };
393
+ }
394
+ function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
395
+ const str = extractFn(obj);
396
+ if (str === undefined)
397
+ return;
398
+ return prepareStringForSearchNumeric(str, tokensMap).sorted;
399
+ }
400
+ function buildSortedFromFields(obj, spec, tokensMap) {
391
401
  const allTokens = [];
392
402
  const fieldMatchModes = spec?.fieldMatchModes;
393
403
  const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
394
404
  const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
395
405
  for (const key of fieldsToSearch) {
396
406
  const keyStr = key;
397
- if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS2.has(keyStr))
407
+ if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
398
408
  continue;
399
409
  if (fieldsToExclude.has(key))
400
410
  continue;
@@ -425,14 +435,42 @@ function prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap) {
425
435
  const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
426
436
  return new Uint32Array(uniqueTokens);
427
437
  }
438
+ function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
439
+ const strings = [];
440
+ for (const field of keyFields) {
441
+ const keyStr = field;
442
+ const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
443
+ if (value == null)
444
+ continue;
445
+ extractStringsFromValue(value, strings);
446
+ }
447
+ if (strings.length === 0)
448
+ return;
449
+ const text = strings.join(" ");
450
+ const datesNormalized = normalizeDates(text);
451
+ const preprocessed = preprocessString(datesNormalized);
452
+ const sanitised = sanitiseString(preprocessed);
453
+ const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
454
+ if (stringTokens.length === 0)
455
+ return;
456
+ const sliced = stringTokens.slice(0, n);
457
+ const out = new Uint32Array(sliced.length);
458
+ for (let i = 0;i < sliced.length; i++) {
459
+ out[i] = getOrCreateTokenId(sliced[i], tokensMap);
460
+ }
461
+ return out;
462
+ }
428
463
  function createSearchArrayMetadataNumeric(data, spec) {
429
464
  const tokensMap = createTokensMap();
430
465
  try {
431
- const metadata = new Map;
466
+ const metadata = emptyNumericSearchMetadata();
432
467
  for (const [id, item] of data) {
433
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
434
- if (tokens2 !== undefined) {
435
- metadata.set(id, tokens2);
468
+ const result = prepareObjectForSearchNumeric(item, spec, tokensMap);
469
+ if (result !== undefined) {
470
+ metadata.sorted.set(id, result.sorted);
471
+ if (result.firstN !== undefined) {
472
+ metadata.unsorted.set(id, result.firstN);
473
+ }
436
474
  }
437
475
  }
438
476
  return metadata;
@@ -440,6 +478,9 @@ function createSearchArrayMetadataNumeric(data, spec) {
440
478
  clearTokensMap(tokensMap);
441
479
  }
442
480
  }
481
+ function emptyNumericSearchMetadata() {
482
+ return { sorted: new Map, unsorted: new Map };
483
+ }
443
484
  // src/common/numeric/createSearchLinkedMetadata.ts
444
485
  function createSearchLinkedMetadataNumeric(primaryData, primaryMetadata, linkedData, linkedMetadata, foreignKeyGetter) {
445
486
  const primaryToLinked = new Map;
@@ -461,14 +502,19 @@ function createSearchLinkedMetadataNumeric(primaryData, primaryMetadata, linkedD
461
502
  }
462
503
  // src/common/numeric/updateSearchMetadata.ts
463
504
  function updateSearchMetadataNumeric(metadata, objectId, obj, spec, tokensMap) {
464
- const tokens2 = obj != null ? prepareObjectForSearchNumeric(obj, spec, tokensMap) : undefined;
465
- if (tokens2 === undefined) {
466
- metadata.delete(objectId);
505
+ const result = obj != null ? prepareObjectForSearchNumeric(obj, spec, tokensMap) : undefined;
506
+ if (result === undefined) {
507
+ metadata.sorted.delete(objectId);
508
+ metadata.unsorted.delete(objectId);
467
509
  return false;
510
+ }
511
+ metadata.sorted.set(objectId, result.sorted);
512
+ if (result.firstN !== undefined) {
513
+ metadata.unsorted.set(objectId, result.firstN);
468
514
  } else {
469
- metadata.set(objectId, tokens2);
470
- return true;
515
+ metadata.unsorted.delete(objectId);
471
516
  }
517
+ return true;
472
518
  }
473
519
  function updateSearchMetadataBatchNumeric(metadata, batch, spec) {
474
520
  if (batch.length === 0) {
@@ -492,7 +538,9 @@ function updateSearchMetadataBatchNumeric(metadata, batch, spec) {
492
538
  }
493
539
  }
494
540
  function removeFromSearchMetadataNumeric(metadata, objectId) {
495
- return metadata.delete(objectId);
541
+ const had = metadata.sorted.delete(objectId);
542
+ metadata.unsorted.delete(objectId);
543
+ return had;
496
544
  }
497
545
  function removeFromSearchMetadataBatchNumeric(metadata, objectIds) {
498
546
  if (objectIds.length === 0) {
@@ -500,9 +548,10 @@ function removeFromSearchMetadataBatchNumeric(metadata, objectIds) {
500
548
  }
501
549
  let removed = 0;
502
550
  for (const id of objectIds) {
503
- if (metadata.delete(id)) {
551
+ const had = metadata.sorted.delete(id);
552
+ metadata.unsorted.delete(id);
553
+ if (had)
504
554
  removed++;
505
- }
506
555
  }
507
556
  return removed;
508
557
  }
@@ -692,6 +741,106 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
692
741
  return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
693
742
  }
694
743
 
744
+ // src/common/numeric/scoring.ts
745
+ function orderedTokenize(input) {
746
+ return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
747
+ }
748
+ function scoreOne(query, qTokens, itemTokens, exactKeys) {
749
+ if (exactKeys && exactKeys.length > 0) {
750
+ for (let i = 0;i < exactKeys.length; i++) {
751
+ if (exactKeys[i] === query)
752
+ return 1e5;
753
+ }
754
+ }
755
+ if (qTokens.length === 0)
756
+ return 0;
757
+ if (itemTokens.length === 0)
758
+ return 1;
759
+ let qi = 0;
760
+ const positions = [];
761
+ for (let i = 0;i < itemTokens.length; i++) {
762
+ if (qi < qTokens.length && itemTokens[i].startsWith(qTokens[qi])) {
763
+ positions.push(i);
764
+ qi++;
765
+ }
766
+ }
767
+ if (qi === 0)
768
+ return 1;
769
+ let score = 50;
770
+ if (qi === qTokens.length)
771
+ score += 100;
772
+ if (positions[0] === 0)
773
+ score += 50;
774
+ let perfect = qi === qTokens.length;
775
+ if (perfect) {
776
+ for (let i = 0;i < positions.length; i++) {
777
+ if (positions[i] !== i) {
778
+ perfect = false;
779
+ break;
780
+ }
781
+ }
782
+ }
783
+ if (perfect)
784
+ score += 80;
785
+ if (positions.length >= 2) {
786
+ const span = positions[positions.length - 1] - positions[0] + 1;
787
+ score += positions.length / span * 30;
788
+ }
789
+ let totalLen = 0;
790
+ for (const t of itemTokens)
791
+ totalLen += t.length + 1;
792
+ score -= totalLen * 0.1;
793
+ return score;
794
+ }
795
+ function scoreBag(qTokens, itemTokens, perTokenWeight = 30) {
796
+ if (qTokens.length === 0 || itemTokens.length === 0)
797
+ return 0;
798
+ const usedItemIdx = new Set;
799
+ let score = 0;
800
+ for (const qt of qTokens) {
801
+ for (let i = 0;i < itemTokens.length; i++) {
802
+ if (usedItemIdx.has(i))
803
+ continue;
804
+ if (itemTokens[i].startsWith(qt)) {
805
+ score += perTokenWeight;
806
+ usedItemIdx.add(i);
807
+ break;
808
+ }
809
+ }
810
+ }
811
+ return score;
812
+ }
813
+ function scoreOrderAndPresence(qTokens, itemTokens, weights) {
814
+ const orderedWeight = weights?.ordered ?? 30;
815
+ const presenceWeight = weights?.presence ?? 10;
816
+ if (qTokens.length === 0 || itemTokens.length === 0)
817
+ return 0;
818
+ const matchedQueryIndices = new Set;
819
+ let qi = 0;
820
+ let score = 0;
821
+ for (let i = 0;i < itemTokens.length && qi < qTokens.length; i++) {
822
+ if (itemTokens[i].startsWith(qTokens[qi])) {
823
+ score += orderedWeight;
824
+ matchedQueryIndices.add(qi);
825
+ qi++;
826
+ }
827
+ }
828
+ if (matchedQueryIndices.size < qTokens.length) {
829
+ for (let qj = 0;qj < qTokens.length; qj++) {
830
+ if (matchedQueryIndices.has(qj))
831
+ continue;
832
+ const qt = qTokens[qj];
833
+ for (let i = 0;i < itemTokens.length; i++) {
834
+ if (itemTokens[i].startsWith(qt)) {
835
+ score += presenceWeight;
836
+ break;
837
+ }
838
+ }
839
+ }
840
+ }
841
+ return score;
842
+ }
843
+
695
844
  // src/common/numeric/searchInData.ts
696
845
  function searchInDataReturnIdsNumeric(query, metadata, opts) {
697
846
  return searchInDataReturnIdsWithLimitNumeric(query, metadata, Infinity, opts);
@@ -709,20 +858,48 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
709
858
  }
710
859
  function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
711
860
  const queryTokenStrs = prepareStringForSearch(query);
712
- if (queryTokenStrs.length === 0) {
861
+ if (queryTokenStrs.length === 0)
713
862
  return [];
714
- }
715
863
  const preparedTokens = prepareQueryTokens(queryTokenStrs);
716
- const results = [];
717
- for (const [id, targetTokens] of metadata) {
864
+ const candidates = [];
865
+ for (const [id, targetTokens] of metadata.sorted) {
718
866
  if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
719
- results.push(id);
720
- if (results.length >= limit) {
721
- break;
722
- }
867
+ candidates.push(id);
723
868
  }
724
869
  }
725
- return results;
870
+ if (candidates.length === 0)
871
+ return [];
872
+ const qTokens = orderedTokenize(query);
873
+ const hasUnsorted = metadata.unsorted.size > 0;
874
+ if (!hasUnsorted) {
875
+ if (limit === Infinity || limit >= candidates.length)
876
+ return candidates;
877
+ return candidates.slice(0, limit);
878
+ }
879
+ const scored = new Array(candidates.length);
880
+ for (let i = 0;i < candidates.length; i++) {
881
+ const id = candidates[i];
882
+ const unsortedIds = metadata.unsorted.get(id);
883
+ if (unsortedIds === undefined) {
884
+ scored[i] = { id, score: 0, order: i };
885
+ continue;
886
+ }
887
+ const itemTokens = new Array(unsortedIds.length);
888
+ for (let j = 0;j < unsortedIds.length; j++) {
889
+ itemTokens[j] = tokens[unsortedIds[j]] ?? "";
890
+ }
891
+ scored[i] = { id, score: scoreOne(query, qTokens, itemTokens), order: i };
892
+ }
893
+ scored.sort((a, b) => {
894
+ if (a.score !== b.score)
895
+ return b.score - a.score;
896
+ return a.order - b.order;
897
+ });
898
+ const out = [];
899
+ const max = limit === Infinity ? scored.length : Math.min(limit, scored.length);
900
+ for (let i = 0;i < max; i++)
901
+ out.push(scored[i].id);
902
+ return out;
726
903
  }
727
904
  // src/common/numeric/findInLinkedArrays.ts
728
905
  function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
@@ -733,11 +910,10 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
733
910
  const preparedTokens = prepareQueryTokens(queryTokenStrs);
734
911
  const positiveTokens = preparedTokens.filter((t) => !t.negated);
735
912
  const negatedTokens = preparedTokens.filter((t) => t.negated);
736
- const results = [];
913
+ const scored = [];
914
+ let orderCounter = 0;
737
915
  for (const [primaryId, primary] of linked.primaryData) {
738
- if (results.length >= limit)
739
- break;
740
- const primaryTokens = linked.primaryMeta.get(primaryId);
916
+ const primaryTokens = linked.primaryMeta.sorted.get(primaryId);
741
917
  if (!primaryTokens)
742
918
  continue;
743
919
  const linkedIds = linked.primaryToLinked.get(primaryId) || [];
@@ -748,22 +924,20 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
748
924
  break;
749
925
  }
750
926
  }
751
- if (negatedMatchesInPrimary) {
927
+ if (negatedMatchesInPrimary)
752
928
  continue;
753
- }
754
929
  let totalLength = primaryTokens.length;
755
930
  for (const linkedId of linkedIds) {
756
- const linkedTokens = linked.linkedMeta.get(linkedId);
757
- if (linkedTokens) {
931
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
932
+ if (linkedTokens)
758
933
  totalLength += linkedTokens.length;
759
- }
760
934
  }
761
935
  const allTokens = new Uint32Array(totalLength);
762
936
  let offset = 0;
763
937
  allTokens.set(primaryTokens, offset);
764
938
  offset += primaryTokens.length;
765
939
  for (const linkedId of linkedIds) {
766
- const linkedTokens = linked.linkedMeta.get(linkedId);
940
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
767
941
  if (linkedTokens) {
768
942
  allTokens.set(linkedTokens, offset);
769
943
  offset += linkedTokens.length;
@@ -777,230 +951,289 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
777
951
  break;
778
952
  }
779
953
  }
780
- if (allPositiveMatch) {
781
- let anyMatchInPrimary = false;
782
- let anyMatchInLinked = false;
783
- const matchingLinkedIds = new Set;
784
- const tokensMatchingPrimary = new Set;
785
- for (const preparedToken of positiveTokens) {
786
- if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
787
- anyMatchInPrimary = true;
788
- tokensMatchingPrimary.add(preparedToken);
789
- }
954
+ if (!allPositiveMatch)
955
+ continue;
956
+ let anyMatchInPrimary = false;
957
+ let anyMatchInLinked = false;
958
+ const matchingLinkedIds = new Set;
959
+ const tokensMatchingPrimary = new Set;
960
+ for (const preparedToken of positiveTokens) {
961
+ if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
962
+ anyMatchInPrimary = true;
963
+ tokensMatchingPrimary.add(preparedToken);
790
964
  }
791
- const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
792
- if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
793
- for (const linkedId of linkedIds) {
794
- const linkedTokens = linked.linkedMeta.get(linkedId);
795
- if (linkedTokens) {
796
- let matchesNegatedToken = false;
797
- for (const negatedToken of negatedTokens) {
798
- if (matchPreparedTokenInSortedList(negatedToken, linkedTokens, opts)) {
799
- matchesNegatedToken = true;
800
- break;
801
- }
802
- }
803
- if (matchesNegatedToken) {
804
- continue;
805
- }
806
- if (tokensForLinkedMatching.length > 0) {
807
- const matchesAllNonPrimaryTokens = tokensForLinkedMatching.every((preparedToken) => matchPreparedTokenInSortedList(preparedToken, linkedTokens, opts));
808
- if (matchesAllNonPrimaryTokens) {
809
- anyMatchInLinked = true;
810
- matchingLinkedIds.add(linkedId);
811
- }
812
- } else {
813
- matchingLinkedIds.add(linkedId);
814
- }
965
+ }
966
+ const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
967
+ if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
968
+ for (const linkedId of linkedIds) {
969
+ const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
970
+ if (!linkedTokens)
971
+ continue;
972
+ let matchesNegatedToken = false;
973
+ for (const negatedToken of negatedTokens) {
974
+ if (matchPreparedTokenInSortedList(negatedToken, linkedTokens, opts)) {
975
+ matchesNegatedToken = true;
976
+ break;
815
977
  }
816
978
  }
979
+ if (matchesNegatedToken)
980
+ continue;
981
+ if (tokensForLinkedMatching.length > 0) {
982
+ const matchesAllNonPrimaryTokens = tokensForLinkedMatching.every((preparedToken) => matchPreparedTokenInSortedList(preparedToken, linkedTokens, opts));
983
+ if (matchesAllNonPrimaryTokens) {
984
+ anyMatchInLinked = true;
985
+ matchingLinkedIds.add(linkedId);
986
+ }
987
+ } else {
988
+ matchingLinkedIds.add(linkedId);
989
+ }
817
990
  }
818
- if (tokensForLinkedMatching.length > 0 && matchingLinkedIds.size === 0) {
991
+ }
992
+ if (tokensForLinkedMatching.length > 0 && matchingLinkedIds.size === 0) {
993
+ continue;
994
+ }
995
+ const matchedLinked = Array.from(matchingLinkedIds).map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
996
+ const result = {
997
+ primary,
998
+ linked: matchedLinked,
999
+ matchedIn: anyMatchInPrimary && anyMatchInLinked ? "both" : anyMatchInPrimary ? "primary" : "linked"
1000
+ };
1001
+ scored.push({
1002
+ result,
1003
+ score: 0,
1004
+ order: orderCounter++,
1005
+ primaryId,
1006
+ matchedLinkedIds: Array.from(matchingLinkedIds)
1007
+ });
1008
+ }
1009
+ if (scored.length === 0) {
1010
+ return { queryTokens: queryTokenStrs, results: [], hasNegation: negatedTokens.length > 0 };
1011
+ }
1012
+ const qTokens = orderedTokenize(query);
1013
+ const hasUnsorted = linked.primaryMeta.unsorted.size > 0 || linked.linkedMeta.unsorted.size > 0;
1014
+ if (hasUnsorted) {
1015
+ for (const s of scored) {
1016
+ const itemTokenIds = [];
1017
+ const pu = linked.primaryMeta.unsorted.get(s.primaryId);
1018
+ if (pu)
1019
+ for (const tid of pu)
1020
+ itemTokenIds.push(tid);
1021
+ for (const lid of s.matchedLinkedIds) {
1022
+ const lu = linked.linkedMeta.unsorted.get(lid);
1023
+ if (lu)
1024
+ for (const tid of lu)
1025
+ itemTokenIds.push(tid);
1026
+ }
1027
+ if (itemTokenIds.length === 0) {
1028
+ s.score = 0;
819
1029
  continue;
820
1030
  }
821
- const matchedLinked = Array.from(matchingLinkedIds).map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
822
- results.push({
823
- primary,
824
- linked: matchedLinked,
825
- matchedIn: anyMatchInPrimary && anyMatchInLinked ? "both" : anyMatchInPrimary ? "primary" : "linked"
826
- });
1031
+ const itemTokens = new Array(itemTokenIds.length);
1032
+ for (let i = 0;i < itemTokenIds.length; i++) {
1033
+ itemTokens[i] = tokens[itemTokenIds[i]] ?? "";
1034
+ }
1035
+ s.score = scoreOne(query, qTokens, itemTokens);
827
1036
  }
828
1037
  }
1038
+ scored.sort((a, b) => {
1039
+ if (a.score !== b.score)
1040
+ return b.score - a.score;
1041
+ return a.order - b.order;
1042
+ });
1043
+ const max = Math.min(limit, scored.length);
1044
+ const results = new Array(max);
1045
+ for (let i = 0;i < max; i++)
1046
+ results[i] = scored[i].result;
829
1047
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
830
1048
  }
831
1049
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
832
1050
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
833
1051
  }
834
1052
  // src/common/numeric/AugmentedMetadata.ts
835
- class AugmentedMetadata {
836
- baseMetadata;
837
- augmentedTokens;
838
- spec;
839
- constructor(baseMetadata, spec, augmentedDataMap) {
840
- this.baseMetadata = baseMetadata;
841
- this.augmentedTokens = new Map;
842
- this.spec = spec;
843
- if (augmentedDataMap && augmentedDataMap.size > 0) {
844
- this.updateAugmentedBatch(augmentedDataMap);
845
- }
846
- }
847
- updateAugmented(id, augmentedData) {
848
- if (!augmentedData) {
849
- this.augmentedTokens.delete(id);
850
- return;
851
- }
852
- const tokensMap = createTokensMap();
853
- const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
854
- if (tokens2 && tokens2.length > 0) {
855
- this.augmentedTokens.set(id, tokens2);
1053
+ function mergeSortedTokenArrays(a, b) {
1054
+ const merged = new Uint32Array(a.length + b.length);
1055
+ let i = 0, j = 0, k = 0;
1056
+ let lastToken = -1;
1057
+ while (i < a.length && j < b.length) {
1058
+ const tokenA = a[i];
1059
+ const tokenB = b[j];
1060
+ if (tokenA < tokenB) {
1061
+ if (tokenA !== lastToken) {
1062
+ merged[k++] = tokenA;
1063
+ lastToken = tokenA;
1064
+ }
1065
+ i++;
1066
+ } else if (tokenA > tokenB) {
1067
+ if (tokenB !== lastToken) {
1068
+ merged[k++] = tokenB;
1069
+ lastToken = tokenB;
1070
+ }
1071
+ j++;
856
1072
  } else {
857
- this.augmentedTokens.delete(id);
1073
+ if (tokenA !== lastToken) {
1074
+ merged[k++] = tokenA;
1075
+ lastToken = tokenA;
1076
+ }
1077
+ i++;
1078
+ j++;
858
1079
  }
859
- clearTokensMap(tokensMap);
860
1080
  }
861
- updateAugmentedBatch(augmentedDataMap) {
862
- const tokensMap = createTokensMap();
863
- for (const [id, augmentedData] of augmentedDataMap) {
864
- if (!augmentedData) {
865
- this.augmentedTokens.delete(id);
866
- continue;
867
- }
868
- const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
869
- if (tokens2 && tokens2.length > 0) {
870
- this.augmentedTokens.set(id, tokens2);
871
- } else {
872
- this.augmentedTokens.delete(id);
873
- }
1081
+ while (i < a.length) {
1082
+ const tokenA = a[i];
1083
+ if (tokenA !== lastToken) {
1084
+ merged[k++] = tokenA;
1085
+ lastToken = tokenA;
874
1086
  }
875
- clearTokensMap(tokensMap);
1087
+ i++;
876
1088
  }
877
- clearAugmented(id) {
878
- this.augmentedTokens.delete(id);
1089
+ while (j < b.length) {
1090
+ const tokenB = b[j];
1091
+ if (tokenB !== lastToken) {
1092
+ merged[k++] = tokenB;
1093
+ lastToken = tokenB;
1094
+ }
1095
+ j++;
879
1096
  }
880
- clearAllAugmented() {
881
- this.augmentedTokens.clear();
1097
+ return merged.slice(0, k);
1098
+ }
1099
+
1100
+ class AugmentedSortedMap extends Map {
1101
+ base;
1102
+ augTokens;
1103
+ constructor(base, augTokens) {
1104
+ super();
1105
+ this.base = base;
1106
+ this.augTokens = augTokens;
882
1107
  }
883
1108
  get(id) {
884
- const baseTokens = this.baseMetadata.get(id);
885
- if (!baseTokens)
886
- return;
887
- const augTokens = this.augmentedTokens.get(id);
888
- if (!augTokens)
1109
+ const baseTokens = this.base.get(id);
1110
+ const augTokens = this.augTokens.get(id);
1111
+ if (baseTokens === undefined)
1112
+ return augTokens;
1113
+ if (augTokens === undefined)
889
1114
  return baseTokens;
890
- return this.mergeSortedTokenArrays(baseTokens, augTokens);
1115
+ return mergeSortedTokenArrays(baseTokens, augTokens);
891
1116
  }
892
1117
  has(id) {
893
- return this.baseMetadata.has(id);
1118
+ return this.base.has(id) || this.augTokens.has(id);
894
1119
  }
895
1120
  get size() {
896
- return this.baseMetadata.size;
897
- }
898
- [Symbol.iterator]() {
899
- const self = this;
900
- const baseIterator = this.baseMetadata[Symbol.iterator]();
901
- return {
902
- next() {
903
- const baseResult = baseIterator.next();
904
- if (baseResult.done) {
905
- return { done: true, value: undefined };
906
- }
907
- const [id, baseTokens] = baseResult.value;
908
- const augTokens = self.augmentedTokens.get(id);
909
- const tokens2 = augTokens ? self.mergeSortedTokenArrays(baseTokens, augTokens) : baseTokens;
910
- return { done: false, value: [id, tokens2] };
911
- },
912
- [Symbol.iterator]() {
913
- return this;
914
- },
915
- [Symbol.dispose]() {}
916
- };
1121
+ if (this.augTokens.size === 0)
1122
+ return this.base.size;
1123
+ let count = this.base.size;
1124
+ for (const id of this.augTokens.keys()) {
1125
+ if (!this.base.has(id))
1126
+ count++;
1127
+ }
1128
+ return count;
917
1129
  }
918
1130
  keys() {
919
- return this.baseMetadata.keys();
1131
+ return this.unionIdIterator();
920
1132
  }
921
1133
  values() {
922
1134
  const self = this;
923
- const keysIterator = this.baseMetadata.keys();
924
- return {
925
- next() {
926
- const keyResult = keysIterator.next();
927
- if (keyResult.done) {
928
- return { done: true, value: undefined };
929
- }
930
- const tokens2 = self.get(keyResult.value);
931
- return tokens2 ? { done: false, value: tokens2 } : { done: true, value: undefined };
932
- },
933
- [Symbol.iterator]() {
934
- return this;
935
- },
936
- [Symbol.dispose]() {}
937
- };
1135
+ function* gen() {
1136
+ for (const id of self.unionIdIterator()) {
1137
+ const tokens2 = self.get(id);
1138
+ if (tokens2 !== undefined)
1139
+ yield tokens2;
1140
+ }
1141
+ }
1142
+ return gen();
938
1143
  }
939
1144
  entries() {
940
- return this[Symbol.iterator]();
1145
+ const self = this;
1146
+ function* gen() {
1147
+ for (const id of self.unionIdIterator()) {
1148
+ const tokens2 = self.get(id);
1149
+ if (tokens2 !== undefined)
1150
+ yield [id, tokens2];
1151
+ }
1152
+ }
1153
+ return gen();
1154
+ }
1155
+ [Symbol.iterator]() {
1156
+ return this.entries();
941
1157
  }
942
1158
  forEach(callbackfn) {
943
- for (const [id, tokens2] of this) {
1159
+ for (const [id, tokens2] of this.entries()) {
944
1160
  callbackfn(tokens2, id, this);
945
1161
  }
946
1162
  }
947
1163
  set(_key, _value) {
948
- throw new Error("AugmentedMetadata.set() is not supported. Use updateAugmented() instead.");
1164
+ throw new Error("AugmentedMetadata.sorted is read-only. Use updateAugmented() instead.");
949
1165
  }
950
1166
  delete(_key) {
951
- throw new Error("AugmentedMetadata.delete() is not supported. Use clearAugmented() instead.");
1167
+ throw new Error("AugmentedMetadata.sorted is read-only. Use clearAugmented() instead.");
952
1168
  }
953
1169
  clear() {
954
- throw new Error("AugmentedMetadata.clear() is not supported. Use clearAllAugmented() instead.");
1170
+ throw new Error("AugmentedMetadata.sorted is read-only. Use clearAllAugmented() instead.");
955
1171
  }
956
1172
  get [Symbol.toStringTag]() {
957
- return "AugmentedMetadata";
958
- }
959
- mergeSortedTokenArrays(a, b) {
960
- const merged = new Uint32Array(a.length + b.length);
961
- let i = 0, j = 0, k = 0;
962
- let lastToken = -1;
963
- while (i < a.length && j < b.length) {
964
- const tokenA = a[i];
965
- const tokenB = b[j];
966
- if (tokenA < tokenB) {
967
- if (tokenA !== lastToken) {
968
- merged[k++] = tokenA;
969
- lastToken = tokenA;
970
- }
971
- i++;
972
- } else if (tokenA > tokenB) {
973
- if (tokenB !== lastToken) {
974
- merged[k++] = tokenB;
975
- lastToken = tokenB;
976
- }
977
- j++;
978
- } else {
979
- if (tokenA !== lastToken) {
980
- merged[k++] = tokenA;
981
- lastToken = tokenA;
982
- }
983
- i++;
984
- j++;
985
- }
1173
+ return "AugmentedSortedMap";
1174
+ }
1175
+ *unionIdIterator() {
1176
+ const seen = new Set;
1177
+ for (const id of this.base.keys()) {
1178
+ seen.add(id);
1179
+ yield id;
986
1180
  }
987
- while (i < a.length) {
988
- const tokenA = a[i];
989
- if (tokenA !== lastToken) {
990
- merged[k++] = tokenA;
991
- lastToken = tokenA;
992
- }
993
- i++;
1181
+ for (const id of this.augTokens.keys()) {
1182
+ if (!seen.has(id))
1183
+ yield id;
994
1184
  }
995
- while (j < b.length) {
996
- const tokenB = b[j];
997
- if (tokenB !== lastToken) {
998
- merged[k++] = tokenB;
999
- lastToken = tokenB;
1185
+ }
1186
+ }
1187
+
1188
+ class AugmentedMetadata {
1189
+ sorted;
1190
+ unsorted;
1191
+ augmentedTokens;
1192
+ spec;
1193
+ constructor(baseMetadata, spec, augmentedDataMap) {
1194
+ this.augmentedTokens = new Map;
1195
+ this.spec = spec;
1196
+ this.sorted = new AugmentedSortedMap(baseMetadata.sorted, this.augmentedTokens);
1197
+ this.unsorted = baseMetadata.unsorted;
1198
+ if (augmentedDataMap && augmentedDataMap.size > 0) {
1199
+ this.updateAugmentedBatch(augmentedDataMap);
1200
+ }
1201
+ }
1202
+ updateAugmented(id, augmentedData) {
1203
+ if (augmentedData == null) {
1204
+ this.augmentedTokens.delete(id);
1205
+ return;
1206
+ }
1207
+ const tokensMap = createTokensMap();
1208
+ const prepared = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
1209
+ clearTokensMap(tokensMap);
1210
+ if (prepared !== undefined && prepared.sorted.length > 0) {
1211
+ this.augmentedTokens.set(id, prepared.sorted);
1212
+ } else {
1213
+ this.augmentedTokens.delete(id);
1214
+ }
1215
+ }
1216
+ updateAugmentedBatch(augmentedDataMap) {
1217
+ const tokensMap = createTokensMap();
1218
+ for (const [id, augmentedData] of augmentedDataMap) {
1219
+ if (augmentedData == null) {
1220
+ this.augmentedTokens.delete(id);
1221
+ continue;
1222
+ }
1223
+ const prepared = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
1224
+ if (prepared !== undefined && prepared.sorted.length > 0) {
1225
+ this.augmentedTokens.set(id, prepared.sorted);
1226
+ } else {
1227
+ this.augmentedTokens.delete(id);
1000
1228
  }
1001
- j++;
1002
1229
  }
1003
- return merged.slice(0, k);
1230
+ clearTokensMap(tokensMap);
1231
+ }
1232
+ clearAugmented(id) {
1233
+ this.augmentedTokens.delete(id);
1234
+ }
1235
+ clearAllAugmented() {
1236
+ this.augmentedTokens.clear();
1004
1237
  }
1005
1238
  }
1006
1239
  // src/common/numeric/syncSearchArrayMetadata.ts
@@ -1013,6 +1246,25 @@ function tokensEqual(a, b) {
1013
1246
  }
1014
1247
  return true;
1015
1248
  }
1249
+ function firstNEqual(a, b) {
1250
+ if (a === undefined && b === undefined)
1251
+ return true;
1252
+ if (a === undefined || b === undefined)
1253
+ return false;
1254
+ return tokensEqual(a, b);
1255
+ }
1256
+ function applyPreparedTokens(metadata, id, result) {
1257
+ metadata.sorted.set(id, result.sorted);
1258
+ if (result.firstN !== undefined) {
1259
+ metadata.unsorted.set(id, result.firstN);
1260
+ } else {
1261
+ metadata.unsorted.delete(id);
1262
+ }
1263
+ }
1264
+ function deletePreparedTokens(metadata, id) {
1265
+ metadata.sorted.delete(id);
1266
+ metadata.unsorted.delete(id);
1267
+ }
1016
1268
  function syncSearchArrayMetadataNumeric(metadata, newData, spec) {
1017
1269
  const result = {
1018
1270
  added: 0,
@@ -1023,30 +1275,31 @@ function syncSearchArrayMetadataNumeric(metadata, newData, spec) {
1023
1275
  removedIds: []
1024
1276
  };
1025
1277
  const tokensMap = createTokensMap();
1026
- for (const id of metadata.keys()) {
1278
+ for (const id of metadata.sorted.keys()) {
1027
1279
  if (!newData.has(id)) {
1028
- metadata.delete(id);
1280
+ deletePreparedTokens(metadata, id);
1029
1281
  result.removed++;
1030
1282
  result.removedIds.push(id);
1031
1283
  }
1032
1284
  }
1033
1285
  for (const [id, item] of newData) {
1034
- const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1035
- if (newTokens === undefined) {
1036
- if (metadata.has(id)) {
1037
- metadata.delete(id);
1286
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1287
+ if (prepared === undefined) {
1288
+ if (metadata.sorted.has(id)) {
1289
+ deletePreparedTokens(metadata, id);
1038
1290
  result.removed++;
1039
1291
  result.removedIds.push(id);
1040
1292
  }
1041
1293
  continue;
1042
1294
  }
1043
- const existingTokens = metadata.get(id);
1044
- if (existingTokens === undefined) {
1045
- metadata.set(id, newTokens);
1295
+ const existingSorted = metadata.sorted.get(id);
1296
+ const existingFirstN = metadata.unsorted.get(id);
1297
+ if (existingSorted === undefined) {
1298
+ applyPreparedTokens(metadata, id, prepared);
1046
1299
  result.added++;
1047
1300
  result.addedIds.push(id);
1048
- } else if (!tokensEqual(existingTokens, newTokens)) {
1049
- metadata.set(id, newTokens);
1301
+ } else if (!tokensEqual(existingSorted, prepared.sorted) || !firstNEqual(existingFirstN, prepared.firstN)) {
1302
+ applyPreparedTokens(metadata, id, prepared);
1050
1303
  result.updated++;
1051
1304
  result.updatedIds.push(id);
1052
1305
  }
@@ -1066,7 +1319,7 @@ function syncSearchArrayMetadataNumericWithPrevious(metadata, previousData, newD
1066
1319
  const tokensMap = createTokensMap();
1067
1320
  for (const id of previousData.keys()) {
1068
1321
  if (!newData.has(id)) {
1069
- metadata.delete(id);
1322
+ deletePreparedTokens(metadata, id);
1070
1323
  result.removed++;
1071
1324
  result.removedIds.push(id);
1072
1325
  }
@@ -1074,22 +1327,23 @@ function syncSearchArrayMetadataNumericWithPrevious(metadata, previousData, newD
1074
1327
  for (const [id, item] of newData) {
1075
1328
  const wasInPrevious = previousData.has(id);
1076
1329
  if (!wasInPrevious) {
1077
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1078
- if (tokens2 !== undefined) {
1079
- metadata.set(id, tokens2);
1330
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1331
+ if (prepared !== undefined) {
1332
+ applyPreparedTokens(metadata, id, prepared);
1080
1333
  result.added++;
1081
1334
  result.addedIds.push(id);
1082
1335
  }
1083
1336
  } else if (!options?.skipUpdateCheck) {
1084
- const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1085
- if (newTokens === undefined) {
1086
- metadata.delete(id);
1337
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1338
+ if (prepared === undefined) {
1339
+ deletePreparedTokens(metadata, id);
1087
1340
  result.removed++;
1088
1341
  result.removedIds.push(id);
1089
1342
  } else {
1090
- const existingTokens = metadata.get(id);
1091
- if (existingTokens && !tokensEqual(existingTokens, newTokens)) {
1092
- metadata.set(id, newTokens);
1343
+ const existingSorted = metadata.sorted.get(id);
1344
+ const existingFirstN = metadata.unsorted.get(id);
1345
+ if (existingSorted && (!tokensEqual(existingSorted, prepared.sorted) || !firstNEqual(existingFirstN, prepared.firstN))) {
1346
+ applyPreparedTokens(metadata, id, prepared);
1093
1347
  result.updated++;
1094
1348
  result.updatedIds.push(id);
1095
1349
  }
@@ -1109,29 +1363,48 @@ function tokensEqual2(a, b) {
1109
1363
  }
1110
1364
  return true;
1111
1365
  }
1366
+ function firstNEqual2(a, b) {
1367
+ if (a === undefined && b === undefined)
1368
+ return true;
1369
+ if (a === undefined || b === undefined)
1370
+ return false;
1371
+ return tokensEqual2(a, b);
1372
+ }
1373
+ function applyOrClear(meta, id, prepared) {
1374
+ if (prepared === undefined) {
1375
+ meta.sorted.delete(id);
1376
+ meta.unsorted.delete(id);
1377
+ return;
1378
+ }
1379
+ meta.sorted.set(id, prepared.sorted);
1380
+ if (prepared.firstN !== undefined) {
1381
+ meta.unsorted.set(id, prepared.firstN);
1382
+ } else {
1383
+ meta.unsorted.delete(id);
1384
+ }
1385
+ }
1386
+ function clearTokens(meta, id) {
1387
+ meta.sorted.delete(id);
1388
+ meta.unsorted.delete(id);
1389
+ }
1112
1390
  function upsertPrimaryItemNumeric(linked, item, spec) {
1113
1391
  const id = item._id;
1114
1392
  const wasAdded = !linked.primaryData.has(id);
1115
1393
  linked.primaryData.set(id, item);
1116
1394
  const tokensMap = createTokensMap();
1117
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1395
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1118
1396
  clearTokensMap(tokensMap);
1119
- if (tokens2 !== undefined) {
1120
- linked.primaryMeta.set(id, tokens2);
1121
- } else {
1122
- linked.primaryMeta.delete(id);
1123
- }
1397
+ applyOrClear(linked.primaryMeta, id, prepared);
1124
1398
  if (wasAdded && !linked.primaryToLinked.has(id)) {
1125
1399
  linked.primaryToLinked.set(id, []);
1126
1400
  }
1127
1401
  return { added: wasAdded };
1128
1402
  }
1129
1403
  function removePrimaryItemNumeric(linked, primaryId) {
1130
- if (!linked.primaryData.has(primaryId)) {
1404
+ if (!linked.primaryData.has(primaryId))
1131
1405
  return false;
1132
- }
1133
1406
  linked.primaryData.delete(primaryId);
1134
- linked.primaryMeta.delete(primaryId);
1407
+ clearTokens(linked.primaryMeta, primaryId);
1135
1408
  linked.primaryToLinked.delete(primaryId);
1136
1409
  return true;
1137
1410
  }
@@ -1140,35 +1413,28 @@ function upsertLinkedItemNumeric(linked, item, foreignKeyGetter, spec) {
1140
1413
  const newPrimaryId = foreignKeyGetter(item);
1141
1414
  let previousPrimaryId;
1142
1415
  const existingItem = linked.linkedData.get(id);
1143
- if (existingItem) {
1416
+ if (existingItem)
1144
1417
  previousPrimaryId = foreignKeyGetter(existingItem);
1145
- }
1146
1418
  const wasAdded = !existingItem;
1147
1419
  linked.linkedData.set(id, item);
1148
1420
  const tokensMap = createTokensMap();
1149
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1421
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1150
1422
  clearTokensMap(tokensMap);
1151
- if (tokens2 !== undefined) {
1152
- linked.linkedMeta.set(id, tokens2);
1153
- } else {
1154
- linked.linkedMeta.delete(id);
1155
- }
1423
+ applyOrClear(linked.linkedMeta, id, prepared);
1156
1424
  if (previousPrimaryId !== newPrimaryId) {
1157
1425
  if (previousPrimaryId) {
1158
1426
  const oldList = linked.primaryToLinked.get(previousPrimaryId);
1159
1427
  if (oldList) {
1160
1428
  const index = oldList.indexOf(id);
1161
- if (index !== -1) {
1429
+ if (index !== -1)
1162
1430
  oldList.splice(index, 1);
1163
- }
1164
1431
  }
1165
1432
  }
1166
1433
  if (newPrimaryId) {
1167
1434
  const newList = linked.primaryToLinked.get(newPrimaryId);
1168
1435
  if (newList) {
1169
- if (!newList.includes(id)) {
1436
+ if (!newList.includes(id))
1170
1437
  newList.push(id);
1171
- }
1172
1438
  } else {
1173
1439
  linked.primaryToLinked.set(newPrimaryId, [id]);
1174
1440
  }
@@ -1181,64 +1447,57 @@ function upsertLinkedItemNumeric(linked, item, foreignKeyGetter, spec) {
1181
1447
  }
1182
1448
  function removeLinkedItemNumeric(linked, linkedId, foreignKeyGetter) {
1183
1449
  const existingItem = linked.linkedData.get(linkedId);
1184
- if (!existingItem) {
1450
+ if (!existingItem)
1185
1451
  return false;
1186
- }
1187
1452
  const primaryId = foreignKeyGetter(existingItem);
1188
1453
  linked.linkedData.delete(linkedId);
1189
- linked.linkedMeta.delete(linkedId);
1454
+ clearTokens(linked.linkedMeta, linkedId);
1190
1455
  if (primaryId) {
1191
1456
  const list = linked.primaryToLinked.get(primaryId);
1192
1457
  if (list) {
1193
1458
  const index = list.indexOf(linkedId);
1194
- if (index !== -1) {
1459
+ if (index !== -1)
1195
1460
  list.splice(index, 1);
1196
- }
1197
1461
  }
1198
1462
  }
1199
1463
  return true;
1200
1464
  }
1201
1465
  function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, foreignKeyGetter, spec) {
1202
- const result = {
1203
- added: 0,
1204
- updated: 0,
1205
- removed: 0
1206
- };
1466
+ const result = { added: 0, updated: 0, removed: 0 };
1207
1467
  const tokensMap = createTokensMap();
1208
1468
  const currentLinkedIds = new Set(linked.primaryToLinked.get(primaryId) || []);
1209
1469
  const newLinkedIds = new Set;
1210
1470
  for (const [id, item] of newLinkedItems) {
1211
- if (foreignKeyGetter(item) === primaryId) {
1471
+ if (foreignKeyGetter(item) === primaryId)
1212
1472
  newLinkedIds.add(id);
1213
- }
1214
1473
  }
1215
1474
  for (const id of currentLinkedIds) {
1216
1475
  if (!newLinkedIds.has(id)) {
1217
- const item = linked.linkedData.get(id);
1218
- if (item) {
1476
+ if (linked.linkedData.has(id)) {
1219
1477
  linked.linkedData.delete(id);
1220
- linked.linkedMeta.delete(id);
1478
+ clearTokens(linked.linkedMeta, id);
1221
1479
  result.removed++;
1222
1480
  }
1223
1481
  }
1224
1482
  }
1225
1483
  for (const id of newLinkedIds) {
1226
1484
  const item = newLinkedItems.get(id);
1227
- const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1485
+ const prepared = prepareObjectForSearchNumeric(item, spec, tokensMap);
1228
1486
  if (currentLinkedIds.has(id)) {
1229
- const existingTokens = linked.linkedMeta.get(id);
1230
- if (tokens2 !== undefined) {
1231
- if (!existingTokens || !tokensEqual2(existingTokens, tokens2)) {
1232
- linked.linkedMeta.set(id, tokens2);
1487
+ const existingSorted = linked.linkedMeta.sorted.get(id);
1488
+ const existingFirstN = linked.linkedMeta.unsorted.get(id);
1489
+ if (prepared !== undefined) {
1490
+ if (!existingSorted || !tokensEqual2(existingSorted, prepared.sorted) || !firstNEqual2(existingFirstN, prepared.firstN)) {
1491
+ applyOrClear(linked.linkedMeta, id, prepared);
1233
1492
  result.updated++;
1234
1493
  }
1494
+ } else {
1495
+ clearTokens(linked.linkedMeta, id);
1235
1496
  }
1236
1497
  linked.linkedData.set(id, item);
1237
1498
  } else {
1238
1499
  linked.linkedData.set(id, item);
1239
- if (tokens2 !== undefined) {
1240
- linked.linkedMeta.set(id, tokens2);
1241
- }
1500
+ applyOrClear(linked.linkedMeta, id, prepared);
1242
1501
  result.added++;
1243
1502
  }
1244
1503
  }
@@ -1247,54 +1506,14 @@ function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, for
1247
1506
  return result;
1248
1507
  }
1249
1508
  // src/common/numeric/searchAndRank.ts
1250
- function orderedTokenize(input) {
1251
- return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
1252
- }
1253
- function scoreOne(item, query, qTokens, primaryTextFn, exactKeys) {
1254
- if (exactKeys && exactKeys.length > 0) {
1255
- for (let i = 0;i < exactKeys.length; i++) {
1256
- if (exactKeys[i] === query)
1257
- return 1e5;
1258
- }
1259
- }
1260
- if (qTokens.length === 0)
1261
- return 0;
1262
- const primaryText = primaryTextFn(item) || "";
1263
- const itemTokens = orderedTokenize(primaryText);
1264
- if (itemTokens.length === 0)
1265
- return 1;
1266
- let qi = 0;
1267
- const positions = [];
1268
- for (let i = 0;i < itemTokens.length; i++) {
1269
- if (qi < qTokens.length && itemTokens[i].startsWith(qTokens[qi])) {
1270
- positions.push(i);
1271
- qi++;
1272
- }
1273
- }
1274
- if (qi === 0)
1275
- return 1;
1276
- let score = 50;
1277
- if (qi === qTokens.length)
1278
- score += 100;
1279
- if (positions[0] === 0)
1280
- score += 50;
1281
- let perfect = qi === qTokens.length;
1282
- if (perfect) {
1283
- for (let i = 0;i < positions.length; i++) {
1284
- if (positions[i] !== i) {
1285
- perfect = false;
1286
- break;
1287
- }
1288
- }
1289
- }
1290
- if (perfect)
1291
- score += 80;
1292
- if (positions.length >= 2) {
1293
- const span = positions[positions.length - 1] - positions[0] + 1;
1294
- score += positions.length / span * 30;
1509
+ function tokenIdsToStrings(tokenIds) {
1510
+ if (tokenIds === undefined)
1511
+ return;
1512
+ const out = new Array(tokenIds.length);
1513
+ for (let i = 0;i < tokenIds.length; i++) {
1514
+ out[i] = tokens[tokenIds[i]] ?? "";
1295
1515
  }
1296
- score -= primaryText.length * 0.1;
1297
- return score;
1516
+ return out;
1298
1517
  }
1299
1518
  function searchAndRankNumeric(query, data, metadata, options) {
1300
1519
  const { primaryTextFn, exactKeysFn, limit, opts } = options;
@@ -1307,8 +1526,9 @@ function searchAndRankNumeric(query, data, metadata, options) {
1307
1526
  const item = data.get(id);
1308
1527
  if (!item)
1309
1528
  continue;
1529
+ const itemTokens = tokenIdsToStrings(metadata.unsorted.get(id)) ?? orderedTokenize(primaryTextFn(item) || "");
1310
1530
  const exactKeys = exactKeysFn?.(item);
1311
- const score = scoreOne(item, query, qTokens, primaryTextFn, exactKeys);
1531
+ const score = scoreOne(query, qTokens, itemTokens, exactKeys);
1312
1532
  scored.push({ id, item, score });
1313
1533
  }
1314
1534
  scored.sort((x, y) => {
@@ -1332,6 +1552,13 @@ function tokensEqual3(a, b) {
1332
1552
  }
1333
1553
  return true;
1334
1554
  }
1555
+ function firstNEqual3(a, b) {
1556
+ if (a === undefined && b === undefined)
1557
+ return true;
1558
+ if (a === undefined || b === undefined)
1559
+ return false;
1560
+ return tokensEqual3(a, b);
1561
+ }
1335
1562
 
1336
1563
  class SearchUniverse {
1337
1564
  collections = new Map;
@@ -1341,7 +1568,7 @@ class SearchUniverse {
1341
1568
  }
1342
1569
  const collection = {
1343
1570
  data: new Map,
1344
- metadata: new Map,
1571
+ metadata: emptyNumericSearchMetadata(),
1345
1572
  config
1346
1573
  };
1347
1574
  if (config.linkedTo) {
@@ -1358,7 +1585,8 @@ class SearchUniverse {
1358
1585
  loadCollection(name, items) {
1359
1586
  const collection = this.getCollectionOrThrow(name);
1360
1587
  collection.data.clear();
1361
- collection.metadata.clear();
1588
+ collection.metadata.sorted.clear();
1589
+ collection.metadata.unsorted.clear();
1362
1590
  if (collection.primaryToLinked) {
1363
1591
  collection.primaryToLinked.clear();
1364
1592
  }
@@ -1366,8 +1594,11 @@ class SearchUniverse {
1366
1594
  collection.data.set(item._id, item);
1367
1595
  }
1368
1596
  const newMetadata = createSearchArrayMetadataNumeric(collection.data, collection.config.spec);
1369
- for (const [id, tokens2] of newMetadata) {
1370
- collection.metadata.set(id, tokens2);
1597
+ for (const [id, tokens2] of newMetadata.sorted) {
1598
+ collection.metadata.sorted.set(id, tokens2);
1599
+ }
1600
+ for (const [id, tokens2] of newMetadata.unsorted) {
1601
+ collection.metadata.unsorted.set(id, tokens2);
1371
1602
  }
1372
1603
  if (collection.config.linkedTo && collection.primaryToLinked) {
1373
1604
  const { foreignKeyGetter } = collection.config.linkedTo;
@@ -1463,11 +1694,17 @@ class SearchUniverse {
1463
1694
  collection.data.set(id, item);
1464
1695
  const tokensMap = createTokensMap();
1465
1696
  try {
1466
- const tokens2 = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1467
- if (tokens2 !== undefined) {
1468
- collection.metadata.set(id, tokens2);
1697
+ const prepared = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1698
+ if (prepared !== undefined) {
1699
+ collection.metadata.sorted.set(id, prepared.sorted);
1700
+ if (prepared.firstN !== undefined) {
1701
+ collection.metadata.unsorted.set(id, prepared.firstN);
1702
+ } else {
1703
+ collection.metadata.unsorted.delete(id);
1704
+ }
1469
1705
  } else {
1470
- collection.metadata.delete(id);
1706
+ collection.metadata.sorted.delete(id);
1707
+ collection.metadata.unsorted.delete(id);
1471
1708
  }
1472
1709
  } finally {
1473
1710
  clearTokensMap(tokensMap);
@@ -1521,7 +1758,8 @@ class SearchUniverse {
1521
1758
  }
1522
1759
  }
1523
1760
  collection.data.delete(id);
1524
- collection.metadata.delete(id);
1761
+ collection.metadata.sorted.delete(id);
1762
+ collection.metadata.unsorted.delete(id);
1525
1763
  return true;
1526
1764
  }
1527
1765
  removeFromSearchMetadataBatch(collectionName, ids) {
@@ -1572,9 +1810,10 @@ class SearchUniverse {
1572
1810
  }
1573
1811
  }
1574
1812
  }
1575
- const hadMetadata = collection.metadata.has(id);
1813
+ const hadMetadata = collection.metadata.sorted.has(id);
1576
1814
  collection.data.delete(id);
1577
- collection.metadata.delete(id);
1815
+ collection.metadata.sorted.delete(id);
1816
+ collection.metadata.unsorted.delete(id);
1578
1817
  if (hadMetadata) {
1579
1818
  result.removed++;
1580
1819
  result.removedIds.push(id);
@@ -1585,11 +1824,11 @@ class SearchUniverse {
1585
1824
  for (const item of newItems) {
1586
1825
  const id = item._id;
1587
1826
  const existing = collection.data.get(id);
1588
- const hadMetadata = collection.metadata.has(id);
1827
+ const hadMetadata = collection.metadata.sorted.has(id);
1589
1828
  const shouldTokenize = !existing || !skipUpdateCheck;
1590
- let newTokens;
1829
+ let prepared;
1591
1830
  if (shouldTokenize) {
1592
- newTokens = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1831
+ prepared = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1593
1832
  }
1594
1833
  if (fkGetter && collection.primaryToLinked) {
1595
1834
  const newFk = fkGetter(item);
@@ -1619,20 +1858,30 @@ class SearchUniverse {
1619
1858
  collection.data.set(id, item);
1620
1859
  if (!shouldTokenize)
1621
1860
  continue;
1622
- if (newTokens === undefined) {
1861
+ if (prepared === undefined) {
1623
1862
  if (hadMetadata) {
1624
- collection.metadata.delete(id);
1863
+ collection.metadata.sorted.delete(id);
1864
+ collection.metadata.unsorted.delete(id);
1625
1865
  result.removed++;
1626
1866
  result.removedIds.push(id);
1627
1867
  }
1628
1868
  } else if (!hadMetadata) {
1629
- collection.metadata.set(id, newTokens);
1869
+ collection.metadata.sorted.set(id, prepared.sorted);
1870
+ if (prepared.firstN !== undefined) {
1871
+ collection.metadata.unsorted.set(id, prepared.firstN);
1872
+ }
1630
1873
  result.added++;
1631
1874
  result.addedIds.push(id);
1632
1875
  } else {
1633
- const existingTokens = collection.metadata.get(id);
1634
- if (!tokensEqual3(existingTokens, newTokens)) {
1635
- collection.metadata.set(id, newTokens);
1876
+ const existingSorted = collection.metadata.sorted.get(id);
1877
+ const existingFirstN = collection.metadata.unsorted.get(id);
1878
+ if (!tokensEqual3(existingSorted, prepared.sorted) || !firstNEqual3(existingFirstN, prepared.firstN)) {
1879
+ collection.metadata.sorted.set(id, prepared.sorted);
1880
+ if (prepared.firstN !== undefined) {
1881
+ collection.metadata.unsorted.set(id, prepared.firstN);
1882
+ } else {
1883
+ collection.metadata.unsorted.delete(id);
1884
+ }
1636
1885
  result.updated++;
1637
1886
  result.updatedIds.push(id);
1638
1887
  }
@@ -1711,7 +1960,8 @@ class SearchUniverse {
1711
1960
  clear() {
1712
1961
  for (const collection of this.collections.values()) {
1713
1962
  collection.data.clear();
1714
- collection.metadata.clear();
1963
+ collection.metadata.sorted.clear();
1964
+ collection.metadata.unsorted.clear();
1715
1965
  if (collection.primaryToLinked) {
1716
1966
  collection.primaryToLinked.clear();
1717
1967
  }
@@ -1736,7 +1986,9 @@ export {
1736
1986
  searchInDataReturnIdsNumeric as searchInData,
1737
1987
  searchAndRankNumeric,
1738
1988
  searchAndRankNumeric as searchAndRank,
1989
+ scoreOrderAndPresence,
1739
1990
  scoreOne,
1991
+ scoreBag,
1740
1992
  sanitiseString,
1741
1993
  resetTokenRegistry,
1742
1994
  removePrimaryItemNumeric as removePrimaryItem,
@@ -1772,6 +2024,8 @@ export {
1772
2024
  allPreparedTokensMatch,
1773
2025
  TOKEN_ID_MASK,
1774
2026
  SearchUniverse,
2027
+ PREFIX_TO_MATCH_MODE,
2028
+ MATCH_MODE_PREFIXES,
1775
2029
  MATCH_MODE_BITS,
1776
2030
  DEFAULT_SEARCH_OPTS,
1777
2031
  AugmentedMetadata