cry-search 1.0.0 → 1.0.3

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 (37) hide show
  1. package/CLAUDE.md +144 -13
  2. package/README.md +111 -5
  3. package/dist/common/SearchUniverse.d.ts.map +1 -1
  4. package/dist/common/findInLinkedArrays.d.ts +1 -1
  5. package/dist/common/findInLinkedArrays.d.ts.map +1 -1
  6. package/dist/common/numeric/AugmentedMetadata.d.ts +111 -0
  7. package/dist/common/numeric/AugmentedMetadata.d.ts.map +1 -0
  8. package/dist/common/numeric/findInLinkedArrays.d.ts +1 -1
  9. package/dist/common/numeric/index.d.ts +3 -0
  10. package/dist/common/numeric/index.d.ts.map +1 -1
  11. package/dist/common/numeric/searchInData.d.ts.map +1 -1
  12. package/dist/common/numeric/syncSearchArrayMetadata.d.ts +64 -0
  13. package/dist/common/numeric/syncSearchArrayMetadata.d.ts.map +1 -0
  14. package/dist/common/numeric/updateSearchLinkedMetadata.d.ts +130 -0
  15. package/dist/common/numeric/updateSearchLinkedMetadata.d.ts.map +1 -0
  16. package/dist/common/syncSearchArrayMetadata.d.ts +12 -8
  17. package/dist/common/syncSearchArrayMetadata.d.ts.map +1 -1
  18. package/dist/common/updateSearchLinkedMetadata.d.ts +26 -16
  19. package/dist/common/updateSearchLinkedMetadata.d.ts.map +1 -1
  20. package/dist/index.cjs +265 -490
  21. package/dist/index.d.cts +3 -9
  22. package/dist/index.d.ts +3 -9
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +265 -490
  25. package/dist/string.cjs +922 -0
  26. package/dist/string.d.cts +26 -0
  27. package/dist/string.d.ts +26 -0
  28. package/dist/string.d.ts.map +1 -0
  29. package/dist/string.js +890 -0
  30. package/dist/types/index.d.ts +21 -3
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/utils/matchTokenNumeric.d.ts +17 -2
  33. package/dist/utils/matchTokenNumeric.d.ts.map +1 -1
  34. package/dist/utils/sanitiseString.d.ts +7 -3
  35. package/dist/utils/sanitiseString.d.ts.map +1 -1
  36. package/dist/utils/tokenize.d.ts.map +1 -1
  37. package/package.json +14 -4
package/dist/index.cjs CHANGED
@@ -29,19 +29,15 @@ var __export = (target, all) => {
29
29
  // src/index.ts
30
30
  var exports_src = {};
31
31
  __export(exports_src, {
32
- wrapMetadataLegacy: () => wrapMetadataLegacy,
33
- upsertPrimaryItem: () => upsertPrimaryItem,
34
- upsertLinkedItem: () => upsertLinkedItem,
35
- updateSearchMetadataString: () => updateSearchMetadata,
36
- updateSearchMetadataBatchString: () => updateSearchMetadataBatch,
32
+ upsertPrimaryItem: () => upsertPrimaryItemNumeric,
33
+ upsertLinkedItem: () => upsertLinkedItemNumeric,
37
34
  updateSearchMetadataBatch: () => updateSearchMetadataBatchNumeric,
38
35
  updateSearchMetadata: () => updateSearchMetadataNumeric,
39
- unwrapMetadataLegacy: () => unwrapMetadataLegacy,
40
36
  tokens: () => tokens,
41
37
  tokenize: () => tokenize,
42
- syncSearchArrayMetadataWithPrevious: () => syncSearchArrayMetadataWithPrevious,
43
- syncSearchArrayMetadata: () => syncSearchArrayMetadata,
44
- syncLinkedItemsForPrimary: () => syncLinkedItemsForPrimary,
38
+ syncSearchArrayMetadataWithPrevious: () => syncSearchArrayMetadataNumericWithPrevious,
39
+ syncSearchArrayMetadata: () => syncSearchArrayMetadataNumeric,
40
+ syncLinkedItemsForPrimary: () => syncLinkedItemsForPrimaryNumeric,
45
41
  searchableDataToArray: () => searchableDataToArray,
46
42
  searchInDataWithLimit: () => searchInDataReturnIdsWithLimitNumeric,
47
43
  searchInDataReturnObjects: () => searchInDataReturnObjectsNumeric,
@@ -49,22 +45,17 @@ __export(exports_src, {
49
45
  searchInData: () => searchInDataReturnIdsNumeric,
50
46
  sanitiseString: () => sanitiseString,
51
47
  resetTokenRegistry: () => resetTokenRegistry,
52
- removePrimaryItem: () => removePrimaryItem,
53
- removeLinkedItem: () => removeLinkedItem,
54
- removeFromSearchMetadataString: () => removeFromSearchMetadata,
55
- removeFromSearchMetadataBatchString: () => removeFromSearchMetadataBatch,
48
+ removePrimaryItem: () => removePrimaryItemNumeric,
49
+ removeLinkedItem: () => removeLinkedItemNumeric,
56
50
  removeFromSearchMetadataBatch: () => removeFromSearchMetadataBatchNumeric,
57
51
  removeFromSearchMetadata: () => removeFromSearchMetadataNumeric,
58
52
  registerToken: () => registerToken,
59
53
  preprocessString: () => preprocessString,
60
54
  prepareStringForSearch: () => prepareStringForSearch,
61
55
  prepareQueryTokens: () => prepareQueryTokens,
62
- prepareObjectForSearchString: () => prepareObjectForSearch,
63
56
  prepareObjectForSearch: () => prepareObjectForSearchNumeric,
64
57
  normalizeDates: () => normalizeDates,
65
58
  matchTokenInSortedListNumeric: () => matchTokenInSortedListNumeric,
66
- matchTokenInSortedList: () => matchTokenInSortedList,
67
- matchToken: () => matchToken,
68
59
  matchPreparedTokenInSortedList: () => matchPreparedTokenInSortedList,
69
60
  isDeletedOrBlocked: () => isDeletedOrBlocked,
70
61
  getTokenCount: () => getTokenCount,
@@ -72,40 +63,32 @@ __export(exports_src, {
72
63
  getOrCreateTokenId: () => getOrCreateTokenId,
73
64
  findTokenId: () => findTokenId,
74
65
  findInLinkedArraysSimpleNumeric: () => findInLinkedArraysSimpleNumeric,
75
- findInLinkedArraysSimple: () => findInLinkedArraysSimple,
76
66
  findInLinkedArraysNumeric: () => findInLinkedArraysNumeric,
77
- findInLinkedArrays: () => findInLinkedArrays,
78
- findInArrayReturnIds: () => findInArrayReturnIds,
79
- findInArrayReturnDataAndMeta: () => findInArrayReturnDataAndMeta,
80
- findInArray: () => findInArray,
81
- extractSearchableStringDefault: () => extractSearchableStringDefault,
82
67
  encodeTokenWithMode: () => encodeTokenWithMode,
83
68
  decodeTokenId: () => decodeTokenId,
84
69
  decodeMatchMode: () => decodeMatchMode,
85
70
  createTokensMap: () => createTokensMap,
86
- createSearchLinkedMetadataString: () => createSearchLinkedMetadata,
87
71
  createSearchLinkedMetadata: () => createSearchLinkedMetadataNumeric,
88
- createSearchArrayMetadataString: () => createSearchArrayMetadata,
89
72
  createSearchArrayMetadata: () => createSearchArrayMetadataNumeric,
90
73
  clearTokensMap: () => clearTokensMap,
91
74
  binarySearchUint32: () => binarySearchUint32,
92
75
  arrayToSearchableData: () => arrayToSearchableData,
93
76
  allTokensMatchNumeric: () => allTokensMatchNumeric,
94
- allTokensMatch: () => allTokensMatch,
95
77
  allPreparedTokensMatch: () => allPreparedTokensMatch,
96
78
  TOKEN_ID_MASK: () => TOKEN_ID_MASK,
97
79
  SearchUniverse: () => SearchUniverse,
98
- PREFIX_TO_MATCH_MODE: () => PREFIX_TO_MATCH_MODE,
99
- MATCH_MODE_PREFIXES: () => MATCH_MODE_PREFIXES,
100
80
  MATCH_MODE_BITS: () => MATCH_MODE_BITS,
101
- DEFAULT_SEARCH_OPTS: () => DEFAULT_SEARCH_OPTS
81
+ DEFAULT_SEARCH_OPTS: () => DEFAULT_SEARCH_OPTS,
82
+ AugmentedMetadata: () => AugmentedMetadata
102
83
  });
103
84
  module.exports = __toCommonJS(exports_src);
104
85
 
105
86
  // src/types/index.ts
106
87
  var DEFAULT_SEARCH_OPTS = {
107
88
  matchWords: "start",
108
- matchNumbers: "startEnd"
89
+ matchNumbers: "startEnd",
90
+ matchNumbersShort: "whole",
91
+ matchNumbersThreshold: 6
109
92
  };
110
93
  function arrayToSearchableData(items) {
111
94
  const map = new Map;
@@ -233,7 +216,7 @@ function preprocessString(input) {
233
216
 
234
217
  // src/utils/sanitiseString.ts
235
218
  var RE_DIACRITICS = /[\u0300-\u036f]/g;
236
- var RE_INVALID_CHARS = /[^a-z0-9.\- ]/g;
219
+ var RE_INVALID_CHARS = /[^a-z0-9.\-~<>+*!=? ]/g;
237
220
  function sanitiseString(input) {
238
221
  let result = input;
239
222
  result = result.toLowerCase();
@@ -277,11 +260,16 @@ function tokenize(input, config) {
277
260
  let tokens = input.split(" ").map((token) => token.trim()).filter((token) => token.length > 0);
278
261
  if (config?.replaceTokens) {
279
262
  tokens = tokens.map((token) => {
280
- if (token.startsWith("--") && token.length > 2) {
263
+ if (token.length > 2 && token.startsWith("--")) {
281
264
  const withoutPrefix = token.slice(2);
282
265
  const replaced = config.replaceTokens.get(withoutPrefix);
283
266
  return replaced ? "--" + replaced : token;
284
267
  }
268
+ if (token.length > 1 && token[0] === "-" && /^[a-z]/i.test(token[1])) {
269
+ const withoutPrefix = token.slice(1);
270
+ const replaced = config.replaceTokens.get(withoutPrefix);
271
+ return replaced ? "-" + replaced : token;
272
+ }
285
273
  return config.replaceTokens.get(token) ?? token;
286
274
  });
287
275
  }
@@ -362,13 +350,6 @@ function prepareStringForSearchNumeric(input, tokensMap) {
362
350
  }
363
351
 
364
352
  // src/common/string/createSearchArrayMetadata.ts
365
- var MATCH_MODE_PREFIXES = {
366
- start: "<",
367
- end: ">",
368
- startEnd: "+",
369
- anywhere: "*",
370
- whole: "!"
371
- };
372
353
  var PREFIX_TO_MATCH_MODE = {
373
354
  "<": "start",
374
355
  ">": "end",
@@ -379,155 +360,9 @@ var PREFIX_TO_MATCH_MODE = {
379
360
  "?": "anywhere"
380
361
  };
381
362
  var DEFAULT_EXCLUDED_FIELDS = new Set(["_id", "_deleted", "_blocked"]);
382
- function getValueByPath(obj, path) {
383
- const parts = path.split(".");
384
- let current = obj;
385
- for (let i = 0;i < parts.length; i++) {
386
- if (current == null)
387
- return;
388
- const part = parts[i];
389
- if (Array.isArray(current)) {
390
- const remainingPath = parts.slice(i).join(".");
391
- const results = [];
392
- for (const item of current) {
393
- const value = getValueByPath(item, remainingPath);
394
- if (value !== undefined) {
395
- if (Array.isArray(value)) {
396
- results.push(...value);
397
- } else {
398
- results.push(value);
399
- }
400
- }
401
- }
402
- return results.length > 0 ? results : undefined;
403
- }
404
- if (typeof current === "object") {
405
- current = current[part];
406
- } else {
407
- return;
408
- }
409
- }
410
- return current;
411
- }
412
- function isDotNotation(field) {
413
- return field.includes(".");
414
- }
415
- function getRootField(field) {
416
- const dotIndex = field.indexOf(".");
417
- return dotIndex === -1 ? field : field.slice(0, dotIndex);
418
- }
419
- function extractStringsFromValue(value, results) {
420
- if (value == null)
421
- return;
422
- if (typeof value === "string") {
423
- results.push(value);
424
- } else if (typeof value === "number") {
425
- results.push(String(value));
426
- } else if (value instanceof Date) {
427
- results.push(value.toISOString());
428
- } else if (Array.isArray(value)) {
429
- for (const item of value) {
430
- extractStringsFromValue(item, results);
431
- }
432
- } else if (typeof value === "object") {
433
- for (const v of Object.values(value)) {
434
- extractStringsFromValue(v, results);
435
- }
436
- }
437
- }
438
363
  function isDeletedOrBlocked(obj) {
439
364
  return obj._deleted != null || obj._blocked != null;
440
365
  }
441
- function extractSearchableStringDefault(obj, spec) {
442
- if (isDeletedOrBlocked(obj)) {
443
- return;
444
- }
445
- if (spec?.enrichSearchObjectFn) {
446
- spec.enrichSearchObjectFn(obj, spec.searchContext);
447
- }
448
- const parts = [];
449
- const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
450
- const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
451
- for (const key of fieldsToSearch) {
452
- const keyStr = key;
453
- if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
454
- continue;
455
- if (fieldsToExclude.has(key))
456
- continue;
457
- const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[key];
458
- extractStringsFromValue(value, parts);
459
- }
460
- return parts.join(" ");
461
- }
462
- function prepareObjectForSearch(obj, spec) {
463
- if (isDeletedOrBlocked(obj)) {
464
- return;
465
- }
466
- if (spec?.extractSearchableStringFn) {
467
- const searchableString = spec.extractSearchableStringFn(obj);
468
- if (searchableString === undefined) {
469
- return;
470
- }
471
- return prepareStringForSearch(searchableString);
472
- }
473
- return prepareObjectFieldsForSearch(obj, spec);
474
- }
475
- function prepareObjectFieldsForSearch(obj, spec) {
476
- if (spec?.enrichSearchObjectFn) {
477
- spec.enrichSearchObjectFn(obj, spec.searchContext);
478
- }
479
- const allTokens = [];
480
- const fieldMatchModes = spec?.fieldMatchModes;
481
- const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
482
- const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
483
- const processedRootFields = new Set;
484
- for (const key of fieldsToSearch) {
485
- const keyStr = key;
486
- if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
487
- continue;
488
- if (fieldsToExclude.has(key))
489
- continue;
490
- const isDot = isDotNotation(keyStr);
491
- const rootField = getRootField(keyStr);
492
- let value;
493
- if (isDot) {
494
- value = getValueByPath(obj, keyStr);
495
- } else {
496
- value = obj[key];
497
- processedRootFields.add(rootField);
498
- }
499
- if (value == null)
500
- continue;
501
- const matchMode = fieldMatchModes?.[key];
502
- const prefix = matchMode ? MATCH_MODE_PREFIXES[matchMode] : "";
503
- const fieldStrings = [];
504
- extractStringsFromValue(value, fieldStrings);
505
- if (fieldStrings.length > 0) {
506
- const fieldText = fieldStrings.join(" ");
507
- const tokens2 = prepareStringForSearch(fieldText);
508
- for (const token of tokens2) {
509
- allTokens.push(prefix + token);
510
- }
511
- }
512
- }
513
- return [...new Set(allTokens)].sort();
514
- }
515
- function createSearchArrayMetadata(data, spec) {
516
- const metadata = new Map;
517
- for (const [id, item] of data) {
518
- const tokens2 = prepareObjectForSearch(item, spec);
519
- if (tokens2 !== undefined) {
520
- metadata.set(id, tokens2);
521
- }
522
- }
523
- return metadata;
524
- }
525
- function wrapMetadataLegacy(metadata) {
526
- return { items: metadata };
527
- }
528
- function unwrapMetadataLegacy(legacy) {
529
- return legacy.items;
530
- }
531
366
 
532
367
  // src/common/numeric/createSearchArrayMetadata.ts
533
368
  var MATCH_MODE_BITS = {
@@ -561,7 +396,7 @@ function decodeMatchMode(encoded) {
561
396
  }
562
397
  }
563
398
  var DEFAULT_EXCLUDED_FIELDS2 = new Set(["_id", "_deleted", "_blocked"]);
564
- function getValueByPath2(obj, path) {
399
+ function getValueByPath(obj, path) {
565
400
  const parts = path.split(".");
566
401
  let current = obj;
567
402
  for (let i = 0;i < parts.length; i++) {
@@ -572,7 +407,7 @@ function getValueByPath2(obj, path) {
572
407
  const remainingPath = parts.slice(i).join(".");
573
408
  const results = [];
574
409
  for (const item of current) {
575
- const value = getValueByPath2(item, remainingPath);
410
+ const value = getValueByPath(item, remainingPath);
576
411
  if (value !== undefined) {
577
412
  if (Array.isArray(value)) {
578
413
  results.push(...value);
@@ -591,10 +426,10 @@ function getValueByPath2(obj, path) {
591
426
  }
592
427
  return current;
593
428
  }
594
- function isDotNotation2(field) {
429
+ function isDotNotation(field) {
595
430
  return field.includes(".");
596
431
  }
597
- function extractStringsFromValue2(value, results) {
432
+ function extractStringsFromValue(value, results) {
598
433
  if (value == null)
599
434
  return;
600
435
  if (typeof value === "string") {
@@ -605,11 +440,11 @@ function extractStringsFromValue2(value, results) {
605
440
  results.push(value.toISOString());
606
441
  } else if (Array.isArray(value)) {
607
442
  for (const item of value) {
608
- extractStringsFromValue2(item, results);
443
+ extractStringsFromValue(item, results);
609
444
  }
610
445
  } else if (typeof value === "object") {
611
446
  for (const v of Object.values(value)) {
612
- extractStringsFromValue2(v, results);
447
+ extractStringsFromValue(v, results);
613
448
  }
614
449
  }
615
450
  }
@@ -640,13 +475,13 @@ function prepareObjectFieldsForSearchNumeric(obj, spec, tokensMap) {
640
475
  continue;
641
476
  if (fieldsToExclude.has(key))
642
477
  continue;
643
- const isDot = isDotNotation2(keyStr);
644
- const value = isDot ? getValueByPath2(obj, keyStr) : obj[key];
478
+ const isDot = isDotNotation(keyStr);
479
+ const value = isDot ? getValueByPath(obj, keyStr) : obj[key];
645
480
  if (value == null)
646
481
  continue;
647
482
  const matchMode = fieldMatchModes?.[key];
648
483
  const fieldStrings = [];
649
- extractStringsFromValue2(value, fieldStrings);
484
+ extractStringsFromValue(value, fieldStrings);
650
485
  if (fieldStrings.length > 0) {
651
486
  const fieldText = fieldStrings.join(" ");
652
487
  const datesNormalized = normalizeDates(fieldText);
@@ -750,14 +585,38 @@ function removeFromSearchMetadataBatchNumeric(metadata, objectIds) {
750
585
  }
751
586
  // src/utils/matchTokenNumeric.ts
752
587
  var RE_NUMERIC = /^[\d.\-]+$/;
588
+ function countDigits(str) {
589
+ let count = 0;
590
+ for (let i = 0;i < str.length; i++) {
591
+ const c = str[i];
592
+ if (c !== "." && c !== "-")
593
+ count++;
594
+ }
595
+ return count;
596
+ }
597
+ function getNumericMatchMode(str, opts) {
598
+ const threshold = opts?.matchNumbersThreshold ?? DEFAULT_SEARCH_OPTS.matchNumbersThreshold;
599
+ const digitCount = countDigits(str);
600
+ if (digitCount <= threshold) {
601
+ return opts?.matchNumbersShort ?? DEFAULT_SEARCH_OPTS.matchNumbersShort;
602
+ }
603
+ return opts?.matchNumbers ?? DEFAULT_SEARCH_OPTS.matchNumbers;
604
+ }
753
605
  function prepareQueryTokens(queryTokenStrs) {
754
- return queryTokenStrs.map((str) => {
606
+ return queryTokenStrs.filter((str) => {
607
+ if (str === "-" || str === "--" || str === "~")
608
+ return false;
609
+ return true;
610
+ }).map((str) => {
755
611
  let overrideMode;
756
612
  let negated = false;
757
613
  let cleanStr = str;
758
614
  if (str.length > 2 && str.startsWith("--")) {
759
615
  negated = true;
760
616
  cleanStr = str.slice(2);
617
+ } else if (str.length > 1 && str[0] === "-" && /^[a-z]/i.test(str[1])) {
618
+ negated = true;
619
+ cleanStr = str.slice(1);
761
620
  } else if (str.length > 1 && str.startsWith("~")) {
762
621
  negated = true;
763
622
  cleanStr = str.slice(1);
@@ -780,13 +639,16 @@ function prepareQueryTokens(queryTokenStrs) {
780
639
  cleanStr = str.slice(1);
781
640
  }
782
641
  }
783
- return {
642
+ const result = {
784
643
  str: cleanStr,
785
644
  id: findTokenId(cleanStr),
786
- isNumeric: RE_NUMERIC.test(cleanStr),
787
- overrideMode,
788
- negated: negated || undefined
645
+ isNumeric: RE_NUMERIC.test(cleanStr)
789
646
  };
647
+ if (overrideMode)
648
+ result.overrideMode = overrideMode;
649
+ if (negated)
650
+ result.negated = true;
651
+ return result;
790
652
  });
791
653
  }
792
654
  function matchWithModeNumeric(searchTokenId, searchTokenStr, targetTokenEncoded, opts, overrideMode) {
@@ -801,8 +663,8 @@ function matchWithModeNumeric(searchTokenId, searchTokenStr, targetTokenEncoded,
801
663
  } else if (hasExplicitMode) {
802
664
  mode = decodeMatchMode(targetTokenEncoded);
803
665
  } else {
804
- const isNumeric = RE_NUMERIC.test(searchTokenStr);
805
- mode = isNumeric ? opts?.matchNumbers ?? DEFAULT_SEARCH_OPTS.matchNumbers : opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords;
666
+ const isTargetNumeric = RE_NUMERIC.test(targetTokenStr);
667
+ mode = isTargetNumeric ? getNumericMatchMode(targetTokenStr, opts) : opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords;
806
668
  }
807
669
  switch (mode) {
808
670
  case "start":
@@ -837,9 +699,8 @@ function binarySearchUint32(arr, target) {
837
699
  return false;
838
700
  }
839
701
  function matchPreparedTokenInSortedList(preparedToken, targetTokens, opts) {
840
- const { str: searchTokenStr, id: searchTokenId, isNumeric, overrideMode } = preparedToken;
841
- const effectiveMode = overrideMode ?? (isNumeric ? opts?.matchNumbers ?? DEFAULT_SEARCH_OPTS.matchNumbers : opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords);
842
- if (effectiveMode === "whole" && searchTokenId !== -1) {
702
+ const { str: searchTokenStr, id: searchTokenId, overrideMode, negated } = preparedToken;
703
+ if (overrideMode === "whole" && searchTokenId !== -1) {
843
704
  let hasEncodedTokens = false;
844
705
  for (let i = 0;i < targetTokens.length; i++) {
845
706
  if (targetTokens[i] > TOKEN_ID_MASK) {
@@ -856,47 +717,36 @@ function matchPreparedTokenInSortedList(preparedToken, targetTokens, opts) {
856
717
  if (matchWithModeNumeric(searchTokenId, searchTokenStr, targetEncoded, opts, overrideMode)) {
857
718
  return true;
858
719
  }
720
+ if (negated) {
721
+ const targetTokenId = decodeTokenId(targetEncoded);
722
+ const targetTokenStr = tokens[targetTokenId];
723
+ if (targetTokenStr) {
724
+ const hasExplicitMode = targetEncoded !== targetTokenId;
725
+ let targetMode;
726
+ if (overrideMode) {
727
+ targetMode = overrideMode;
728
+ } else if (hasExplicitMode) {
729
+ targetMode = decodeMatchMode(targetEncoded);
730
+ } else {
731
+ const isTargetNumeric = RE_NUMERIC.test(targetTokenStr);
732
+ targetMode = isTargetNumeric ? getNumericMatchMode(targetTokenStr, opts) : opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords;
733
+ }
734
+ if ((targetMode === "start" || targetMode === "startEnd") && searchTokenStr.startsWith(targetTokenStr)) {
735
+ return true;
736
+ }
737
+ }
738
+ }
859
739
  }
860
740
  return false;
861
741
  }
862
742
  function matchTokenInSortedListNumeric(searchTokenStr, targetTokens, opts) {
863
- let overrideMode;
864
- let negated = false;
865
- let cleanStr = searchTokenStr;
866
- if (searchTokenStr.length > 2 && searchTokenStr.startsWith("--")) {
867
- negated = true;
868
- cleanStr = searchTokenStr.slice(2);
869
- } else if (searchTokenStr.length > 1 && searchTokenStr.startsWith("~")) {
870
- negated = true;
871
- cleanStr = searchTokenStr.slice(1);
872
- } else if (searchTokenStr.length > 3 && searchTokenStr.startsWith("...")) {
873
- overrideMode = "end";
874
- cleanStr = searchTokenStr.slice(3);
875
- } else if (searchTokenStr.length > 2 && searchTokenStr.startsWith("..")) {
876
- overrideMode = "end";
877
- cleanStr = searchTokenStr.slice(2);
878
- } else if (searchTokenStr.length > 3 && searchTokenStr.endsWith("...")) {
879
- overrideMode = "start";
880
- cleanStr = searchTokenStr.slice(0, -3);
881
- } else if (searchTokenStr.length > 2 && searchTokenStr.endsWith("..")) {
882
- overrideMode = "start";
883
- cleanStr = searchTokenStr.slice(0, -2);
884
- } else if (searchTokenStr.length > 1) {
885
- const firstChar = searchTokenStr[0];
886
- if (firstChar && PREFIX_TO_MATCH_MODE[firstChar]) {
887
- overrideMode = PREFIX_TO_MATCH_MODE[firstChar];
888
- cleanStr = searchTokenStr.slice(1);
889
- }
890
- }
891
- const preparedToken = {
892
- str: cleanStr,
893
- id: findTokenId(cleanStr),
894
- isNumeric: RE_NUMERIC.test(cleanStr),
895
- overrideMode,
896
- negated: negated || undefined
897
- };
743
+ const preparedTokens = prepareQueryTokens([searchTokenStr]);
744
+ if (preparedTokens.length === 0) {
745
+ return true;
746
+ }
747
+ const preparedToken = preparedTokens[0];
898
748
  const matches = matchPreparedTokenInSortedList(preparedToken, targetTokens, opts);
899
- return negated ? !matches : matches;
749
+ return preparedToken.negated ? !matches : matches;
900
750
  }
901
751
  function allPreparedTokensMatch(preparedTokens, targetTokens, opts) {
902
752
  for (const preparedToken of preparedTokens) {
@@ -918,18 +768,7 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
918
768
 
919
769
  // src/common/numeric/searchInData.ts
920
770
  function searchInDataReturnIdsNumeric(query, metadata, opts) {
921
- const queryTokenStrs = prepareStringForSearch(query);
922
- if (queryTokenStrs.length === 0) {
923
- return [];
924
- }
925
- const preparedTokens = prepareQueryTokens(queryTokenStrs);
926
- const results = [];
927
- for (const [id, targetTokens] of metadata) {
928
- if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
929
- results.push(id);
930
- }
931
- }
932
- return results;
771
+ return searchInDataReturnIdsWithLimitNumeric(query, metadata, Infinity, opts);
933
772
  }
934
773
  function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
935
774
  const ids = searchInDataReturnIdsNumeric(query, metadata, opts);
@@ -963,7 +802,7 @@ function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
963
802
  function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
964
803
  const queryTokenStrs = prepareStringForSearch(query);
965
804
  if (queryTokenStrs.length === 0) {
966
- return { queryTokens: queryTokenStrs, results: [] };
805
+ return { queryTokens: queryTokenStrs, results: [], hasNegation: false };
967
806
  }
968
807
  const preparedTokens = prepareQueryTokens(queryTokenStrs);
969
808
  const positiveTokens = preparedTokens.filter((t) => !t.negated);
@@ -1061,258 +900,184 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1061
900
  });
1062
901
  }
1063
902
  }
1064
- return { queryTokens: queryTokenStrs, results };
903
+ return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
1065
904
  }
1066
905
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
1067
906
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
1068
907
  }
1069
- // src/common/string/createSearchLinkedMetadata.ts
1070
- function createSearchLinkedMetadata(primaryData, primaryMetadata, linkedData, linkedMetadata, foreignKeyGetter) {
1071
- const primaryToLinked = new Map;
1072
- for (const [linkedId, linkedItem] of linkedData) {
1073
- const primaryId = foreignKeyGetter(linkedItem);
1074
- if (primaryId) {
1075
- const list = primaryToLinked.get(primaryId) || [];
1076
- list.push(linkedId);
1077
- primaryToLinked.set(primaryId, list);
908
+ // src/common/numeric/AugmentedMetadata.ts
909
+ class AugmentedMetadata {
910
+ baseMetadata;
911
+ augmentedTokens;
912
+ spec;
913
+ constructor(baseMetadata, spec, augmentedDataMap) {
914
+ this.baseMetadata = baseMetadata;
915
+ this.augmentedTokens = new Map;
916
+ this.spec = spec;
917
+ if (augmentedDataMap && augmentedDataMap.size > 0) {
918
+ this.updateAugmentedBatch(augmentedDataMap);
919
+ }
920
+ }
921
+ updateAugmented(id, augmentedData) {
922
+ if (!augmentedData) {
923
+ this.augmentedTokens.delete(id);
924
+ return;
1078
925
  }
1079
- }
1080
- return {
1081
- primaryMeta: primaryMetadata,
1082
- linkedMeta: linkedMetadata,
1083
- primaryToLinked,
1084
- primaryData,
1085
- linkedData
1086
- };
1087
- }
1088
- // src/common/string/updateSearchMetadata.ts
1089
- function updateSearchMetadata(metadata, objectId, obj, spec) {
1090
- const tokens2 = obj != null ? prepareObjectForSearch(obj, spec) : undefined;
1091
- if (tokens2 === undefined) {
1092
- metadata.delete(objectId);
1093
- return false;
1094
- } else {
1095
- metadata.set(objectId, tokens2);
1096
- return true;
1097
- }
1098
- }
1099
- function updateSearchMetadataBatch(metadata, batch, spec) {
1100
- if (batch.length === 0) {
1101
- return { updated: 0, removed: 0 };
1102
- }
1103
- let updated = 0;
1104
- let removed = 0;
1105
- for (const { objectId, obj } of batch) {
1106
- const wasUpdated = updateSearchMetadata(metadata, objectId, obj, spec);
1107
- if (wasUpdated) {
1108
- updated++;
926
+ const tokensMap = createTokensMap();
927
+ const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
928
+ if (tokens2 && tokens2.length > 0) {
929
+ this.augmentedTokens.set(id, tokens2);
1109
930
  } else {
1110
- removed++;
1111
- }
1112
- }
1113
- return { updated, removed };
1114
- }
1115
- function removeFromSearchMetadata(metadata, objectId) {
1116
- return metadata.delete(objectId);
1117
- }
1118
- function removeFromSearchMetadataBatch(metadata, objectIds) {
1119
- if (objectIds.length === 0) {
1120
- return 0;
1121
- }
1122
- let removed = 0;
1123
- for (const id of objectIds) {
1124
- if (metadata.delete(id)) {
1125
- removed++;
931
+ this.augmentedTokens.delete(id);
1126
932
  }
933
+ clearTokensMap(tokensMap);
1127
934
  }
1128
- return removed;
1129
- }
1130
- // src/utils/matchToken.ts
1131
- var RE_NUMERIC2 = /^[\d.\-]+$/;
1132
- var PREFIX_TO_MODE = {
1133
- "<": "start",
1134
- ">": "end",
1135
- "+": "startEnd",
1136
- "*": "anywhere",
1137
- "!": "whole"
1138
- };
1139
- function matchWithMode(searchToken, targetToken, mode) {
1140
- switch (mode) {
1141
- case "start":
1142
- return targetToken.startsWith(searchToken);
1143
- case "end":
1144
- return targetToken.endsWith(searchToken);
1145
- case "startEnd":
1146
- return targetToken.startsWith(searchToken) || targetToken.endsWith(searchToken);
1147
- case "anywhere":
1148
- return targetToken.includes(searchToken);
1149
- case "whole":
1150
- return targetToken === searchToken;
1151
- }
1152
- }
1153
- function matchToken(searchToken, targetToken, opts) {
1154
- const firstChar = targetToken[0];
1155
- const prefixMode = firstChar ? PREFIX_TO_MODE[firstChar] : undefined;
1156
- if (prefixMode) {
1157
- const actualTarget = targetToken.slice(1);
1158
- return matchWithMode(searchToken, actualTarget, prefixMode);
1159
- }
1160
- const matchWords = opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords;
1161
- const matchNumbers = opts?.matchNumbers ?? DEFAULT_SEARCH_OPTS.matchNumbers;
1162
- const isNumeric = RE_NUMERIC2.test(searchToken);
1163
- return matchWithMode(searchToken, targetToken, isNumeric ? matchNumbers : matchWords);
1164
- }
1165
- function matchTokenInSortedList(searchToken, targetTokens, opts) {
1166
- const isNumeric = RE_NUMERIC2.test(searchToken);
1167
- const effectiveMode = isNumeric ? opts?.matchNumbers ?? DEFAULT_SEARCH_OPTS.matchNumbers : opts?.matchWords ?? DEFAULT_SEARCH_OPTS.matchWords;
1168
- const canEarlyExit = effectiveMode === "start";
1169
- for (const targetToken of targetTokens) {
1170
- const firstChar = targetToken[0];
1171
- const hasPrefix = firstChar ? PREFIX_TO_MODE[firstChar] !== undefined : false;
1172
- if (hasPrefix) {
1173
- if (matchToken(searchToken, targetToken, opts)) {
1174
- return true;
935
+ updateAugmentedBatch(augmentedDataMap) {
936
+ const tokensMap = createTokensMap();
937
+ for (const [id, augmentedData] of augmentedDataMap) {
938
+ if (!augmentedData) {
939
+ this.augmentedTokens.delete(id);
940
+ continue;
1175
941
  }
1176
- } else {
1177
- if (canEarlyExit) {
1178
- if (targetToken.startsWith(searchToken)) {
1179
- return true;
1180
- }
1181
- if (targetToken > searchToken && targetToken[0] !== searchToken[0]) {
1182
- return false;
1183
- }
942
+ const tokens2 = prepareObjectForSearchNumeric(augmentedData, this.spec, tokensMap);
943
+ if (tokens2 && tokens2.length > 0) {
944
+ this.augmentedTokens.set(id, tokens2);
1184
945
  } else {
1185
- if (matchToken(searchToken, targetToken, opts)) {
1186
- return true;
1187
- }
946
+ this.augmentedTokens.delete(id);
1188
947
  }
1189
948
  }
949
+ clearTokensMap(tokensMap);
1190
950
  }
1191
- return false;
1192
- }
1193
- function allTokensMatch(searchTokens, targetTokens, opts) {
1194
- for (const searchToken of searchTokens) {
1195
- if (!matchTokenInSortedList(searchToken, targetTokens, opts)) {
1196
- return false;
1197
- }
951
+ clearAugmented(id) {
952
+ this.augmentedTokens.delete(id);
1198
953
  }
1199
- return true;
1200
- }
1201
-
1202
- // src/common/findInArrayReturnDataAndMeta.ts
1203
- function findInArrayReturnDataAndMeta(query, data, metadata, limit = 10, opts) {
1204
- const queryTokens = prepareStringForSearch(query);
1205
- if (queryTokens.length === 0) {
954
+ clearAllAugmented() {
955
+ this.augmentedTokens.clear();
956
+ }
957
+ get(id) {
958
+ const baseTokens = this.baseMetadata.get(id);
959
+ if (!baseTokens)
960
+ return;
961
+ const augTokens = this.augmentedTokens.get(id);
962
+ if (!augTokens)
963
+ return baseTokens;
964
+ return this.mergeSortedTokenArrays(baseTokens, augTokens);
965
+ }
966
+ has(id) {
967
+ return this.baseMetadata.has(id);
968
+ }
969
+ get size() {
970
+ return this.baseMetadata.size;
971
+ }
972
+ [Symbol.iterator]() {
973
+ const self = this;
974
+ const baseIterator = this.baseMetadata[Symbol.iterator]();
1206
975
  return {
1207
- queryTokens,
1208
- results: []
976
+ next() {
977
+ const baseResult = baseIterator.next();
978
+ if (baseResult.done) {
979
+ return { done: true, value: undefined };
980
+ }
981
+ const [id, baseTokens] = baseResult.value;
982
+ const augTokens = self.augmentedTokens.get(id);
983
+ const tokens2 = augTokens ? self.mergeSortedTokenArrays(baseTokens, augTokens) : baseTokens;
984
+ return { done: false, value: [id, tokens2] };
985
+ },
986
+ [Symbol.iterator]() {
987
+ return this;
988
+ },
989
+ [Symbol.dispose]() {}
1209
990
  };
1210
991
  }
1211
- const results = [];
1212
- for (const [id, itemTokens] of metadata) {
1213
- if (results.length >= limit) {
1214
- break;
1215
- }
1216
- if (allTokensMatch(queryTokens, itemTokens, opts)) {
1217
- const item = data.get(id);
1218
- if (item) {
1219
- results.push({
1220
- item,
1221
- tokens: itemTokens
1222
- });
1223
- }
1224
- }
992
+ keys() {
993
+ return this.baseMetadata.keys();
1225
994
  }
1226
- return {
1227
- queryTokens,
1228
- results
1229
- };
1230
- }
1231
- function findInArrayReturnIds(query, metadata, limit = 10, opts) {
1232
- const queryTokens = prepareStringForSearch(query);
1233
- if (queryTokens.length === 0) {
1234
- return [];
995
+ values() {
996
+ const self = this;
997
+ const keysIterator = this.baseMetadata.keys();
998
+ return {
999
+ next() {
1000
+ const keyResult = keysIterator.next();
1001
+ if (keyResult.done) {
1002
+ return { done: true, value: undefined };
1003
+ }
1004
+ const tokens2 = self.get(keyResult.value);
1005
+ return tokens2 ? { done: false, value: tokens2 } : { done: true, value: undefined };
1006
+ },
1007
+ [Symbol.iterator]() {
1008
+ return this;
1009
+ },
1010
+ [Symbol.dispose]() {}
1011
+ };
1235
1012
  }
1236
- const results = [];
1237
- for (const [id, itemTokens] of metadata) {
1238
- if (results.length >= limit) {
1239
- break;
1240
- }
1241
- if (allTokensMatch(queryTokens, itemTokens, opts)) {
1242
- results.push(id);
1013
+ entries() {
1014
+ return this[Symbol.iterator]();
1015
+ }
1016
+ forEach(callbackfn) {
1017
+ for (const [id, tokens2] of this) {
1018
+ callbackfn(tokens2, id, this);
1243
1019
  }
1244
1020
  }
1245
- return results;
1246
- }
1247
-
1248
- // src/common/findInArray.ts
1249
- function findInArray(query, data, metadata, limit = 10, opts) {
1250
- const result = findInArrayReturnDataAndMeta(query, data, metadata, limit, opts);
1251
- return result.results.map((r) => r.item);
1252
- }
1253
- // src/common/findInLinkedArrays.ts
1254
- function findInLinkedArrays(query, linked, limit = 10, opts) {
1255
- const queryTokens = prepareStringForSearch(query);
1256
- if (queryTokens.length === 0) {
1257
- return { queryTokens, results: [] };
1021
+ set(_key, _value) {
1022
+ throw new Error("AugmentedMetadata.set() is not supported. Use updateAugmented() instead.");
1258
1023
  }
1259
- const results = [];
1260
- for (const [primaryId, primary] of linked.primaryData) {
1261
- if (results.length >= limit)
1262
- break;
1263
- const primaryTokens = linked.primaryMeta.get(primaryId);
1264
- if (!primaryTokens)
1265
- continue;
1266
- const linkedIds = linked.primaryToLinked.get(primaryId) || [];
1267
- const allTokens = [...primaryTokens];
1268
- for (const linkedId of linkedIds) {
1269
- const linkedTokens = linked.linkedMeta.get(linkedId);
1270
- if (linkedTokens) {
1271
- allTokens.push(...linkedTokens);
1272
- }
1273
- }
1274
- allTokens.sort();
1275
- if (allTokensMatch(queryTokens, allTokens, opts)) {
1276
- let anyMatchInPrimary = false;
1277
- let anyMatchInLinked = false;
1278
- const matchingLinkedIds = new Set;
1279
- const tokensMatchingPrimary = new Set;
1280
- for (const queryToken of queryTokens) {
1281
- if (matchTokenInSortedList(queryToken, primaryTokens, opts)) {
1282
- anyMatchInPrimary = true;
1283
- tokensMatchingPrimary.add(queryToken);
1024
+ delete(_key) {
1025
+ throw new Error("AugmentedMetadata.delete() is not supported. Use clearAugmented() instead.");
1026
+ }
1027
+ clear() {
1028
+ throw new Error("AugmentedMetadata.clear() is not supported. Use clearAllAugmented() instead.");
1029
+ }
1030
+ get [Symbol.toStringTag]() {
1031
+ return "AugmentedMetadata";
1032
+ }
1033
+ mergeSortedTokenArrays(a, b) {
1034
+ const merged = new Uint32Array(a.length + b.length);
1035
+ let i = 0, j = 0, k = 0;
1036
+ let lastToken = -1;
1037
+ while (i < a.length && j < b.length) {
1038
+ const tokenA = a[i];
1039
+ const tokenB = b[j];
1040
+ if (tokenA < tokenB) {
1041
+ if (tokenA !== lastToken) {
1042
+ merged[k++] = tokenA;
1043
+ lastToken = tokenA;
1284
1044
  }
1285
- }
1286
- const tokensForLinkedMatching = queryTokens.filter((t) => !tokensMatchingPrimary.has(t));
1287
- if (tokensForLinkedMatching.length > 0) {
1288
- for (const linkedId of linkedIds) {
1289
- const linkedTokens = linked.linkedMeta.get(linkedId);
1290
- if (linkedTokens) {
1291
- const matchesAllNonPrimaryTokens = tokensForLinkedMatching.every((queryToken) => matchTokenInSortedList(queryToken, linkedTokens, opts));
1292
- if (matchesAllNonPrimaryTokens) {
1293
- anyMatchInLinked = true;
1294
- matchingLinkedIds.add(linkedId);
1295
- }
1296
- }
1045
+ i++;
1046
+ } else if (tokenA > tokenB) {
1047
+ if (tokenB !== lastToken) {
1048
+ merged[k++] = tokenB;
1049
+ lastToken = tokenB;
1050
+ }
1051
+ j++;
1052
+ } else {
1053
+ if (tokenA !== lastToken) {
1054
+ merged[k++] = tokenA;
1055
+ lastToken = tokenA;
1297
1056
  }
1057
+ i++;
1058
+ j++;
1298
1059
  }
1299
- if (tokensForLinkedMatching.length > 0 && matchingLinkedIds.size === 0) {
1300
- continue;
1060
+ }
1061
+ while (i < a.length) {
1062
+ const tokenA = a[i];
1063
+ if (tokenA !== lastToken) {
1064
+ merged[k++] = tokenA;
1065
+ lastToken = tokenA;
1301
1066
  }
1302
- const matchedLinked = Array.from(matchingLinkedIds).map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
1303
- results.push({
1304
- primary,
1305
- linked: matchedLinked,
1306
- matchedIn: anyMatchInPrimary && anyMatchInLinked ? "both" : anyMatchInPrimary ? "primary" : "linked"
1307
- });
1067
+ i++;
1308
1068
  }
1069
+ while (j < b.length) {
1070
+ const tokenB = b[j];
1071
+ if (tokenB !== lastToken) {
1072
+ merged[k++] = tokenB;
1073
+ lastToken = tokenB;
1074
+ }
1075
+ j++;
1076
+ }
1077
+ return merged.slice(0, k);
1309
1078
  }
1310
- return { queryTokens, results };
1311
- }
1312
- function findInLinkedArraysSimple(query, linked, limit = 10, opts) {
1313
- return findInLinkedArrays(query, linked, limit, opts).results;
1314
1079
  }
1315
- // src/common/syncSearchArrayMetadata.ts
1080
+ // src/common/numeric/syncSearchArrayMetadata.ts
1316
1081
  function tokensEqual(a, b) {
1317
1082
  if (a.length !== b.length)
1318
1083
  return false;
@@ -1322,7 +1087,7 @@ function tokensEqual(a, b) {
1322
1087
  }
1323
1088
  return true;
1324
1089
  }
1325
- function syncSearchArrayMetadata(metadata, newData, spec) {
1090
+ function syncSearchArrayMetadataNumeric(metadata, newData, spec) {
1326
1091
  const result = {
1327
1092
  added: 0,
1328
1093
  updated: 0,
@@ -1331,6 +1096,7 @@ function syncSearchArrayMetadata(metadata, newData, spec) {
1331
1096
  updatedIds: [],
1332
1097
  removedIds: []
1333
1098
  };
1099
+ const tokensMap = createTokensMap();
1334
1100
  for (const id of metadata.keys()) {
1335
1101
  if (!newData.has(id)) {
1336
1102
  metadata.delete(id);
@@ -1339,7 +1105,7 @@ function syncSearchArrayMetadata(metadata, newData, spec) {
1339
1105
  }
1340
1106
  }
1341
1107
  for (const [id, item] of newData) {
1342
- const newTokens = prepareObjectForSearch(item, spec);
1108
+ const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1343
1109
  if (newTokens === undefined) {
1344
1110
  if (metadata.has(id)) {
1345
1111
  metadata.delete(id);
@@ -1359,9 +1125,10 @@ function syncSearchArrayMetadata(metadata, newData, spec) {
1359
1125
  result.updatedIds.push(id);
1360
1126
  }
1361
1127
  }
1128
+ clearTokensMap(tokensMap);
1362
1129
  return result;
1363
1130
  }
1364
- function syncSearchArrayMetadataWithPrevious(metadata, previousData, newData, options, spec) {
1131
+ function syncSearchArrayMetadataNumericWithPrevious(metadata, previousData, newData, options, spec) {
1365
1132
  const result = {
1366
1133
  added: 0,
1367
1134
  updated: 0,
@@ -1370,6 +1137,7 @@ function syncSearchArrayMetadataWithPrevious(metadata, previousData, newData, op
1370
1137
  updatedIds: [],
1371
1138
  removedIds: []
1372
1139
  };
1140
+ const tokensMap = createTokensMap();
1373
1141
  for (const id of previousData.keys()) {
1374
1142
  if (!newData.has(id)) {
1375
1143
  metadata.delete(id);
@@ -1380,14 +1148,14 @@ function syncSearchArrayMetadataWithPrevious(metadata, previousData, newData, op
1380
1148
  for (const [id, item] of newData) {
1381
1149
  const wasInPrevious = previousData.has(id);
1382
1150
  if (!wasInPrevious) {
1383
- const tokens2 = prepareObjectForSearch(item, spec);
1151
+ const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1384
1152
  if (tokens2 !== undefined) {
1385
1153
  metadata.set(id, tokens2);
1386
1154
  result.added++;
1387
1155
  result.addedIds.push(id);
1388
1156
  }
1389
1157
  } else if (!options?.skipUpdateCheck) {
1390
- const newTokens = prepareObjectForSearch(item, spec);
1158
+ const newTokens = prepareObjectForSearchNumeric(item, spec, tokensMap);
1391
1159
  if (newTokens === undefined) {
1392
1160
  metadata.delete(id);
1393
1161
  result.removed++;
@@ -1402,9 +1170,10 @@ function syncSearchArrayMetadataWithPrevious(metadata, previousData, newData, op
1402
1170
  }
1403
1171
  }
1404
1172
  }
1173
+ clearTokensMap(tokensMap);
1405
1174
  return result;
1406
1175
  }
1407
- // src/common/updateSearchLinkedMetadata.ts
1176
+ // src/common/numeric/updateSearchLinkedMetadata.ts
1408
1177
  function tokensEqual2(a, b) {
1409
1178
  if (a.length !== b.length)
1410
1179
  return false;
@@ -1414,11 +1183,13 @@ function tokensEqual2(a, b) {
1414
1183
  }
1415
1184
  return true;
1416
1185
  }
1417
- function upsertPrimaryItem(linked, item, spec) {
1186
+ function upsertPrimaryItemNumeric(linked, item, spec) {
1418
1187
  const id = item._id;
1419
1188
  const wasAdded = !linked.primaryData.has(id);
1420
1189
  linked.primaryData.set(id, item);
1421
- const tokens2 = prepareObjectForSearch(item, spec);
1190
+ const tokensMap = createTokensMap();
1191
+ const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1192
+ clearTokensMap(tokensMap);
1422
1193
  if (tokens2 !== undefined) {
1423
1194
  linked.primaryMeta.set(id, tokens2);
1424
1195
  } else {
@@ -1429,7 +1200,7 @@ function upsertPrimaryItem(linked, item, spec) {
1429
1200
  }
1430
1201
  return { added: wasAdded };
1431
1202
  }
1432
- function removePrimaryItem(linked, primaryId) {
1203
+ function removePrimaryItemNumeric(linked, primaryId) {
1433
1204
  if (!linked.primaryData.has(primaryId)) {
1434
1205
  return false;
1435
1206
  }
@@ -1438,7 +1209,7 @@ function removePrimaryItem(linked, primaryId) {
1438
1209
  linked.primaryToLinked.delete(primaryId);
1439
1210
  return true;
1440
1211
  }
1441
- function upsertLinkedItem(linked, item, foreignKeyGetter, spec) {
1212
+ function upsertLinkedItemNumeric(linked, item, foreignKeyGetter, spec) {
1442
1213
  const id = item._id;
1443
1214
  const newPrimaryId = foreignKeyGetter(item);
1444
1215
  let previousPrimaryId;
@@ -1448,7 +1219,9 @@ function upsertLinkedItem(linked, item, foreignKeyGetter, spec) {
1448
1219
  }
1449
1220
  const wasAdded = !existingItem;
1450
1221
  linked.linkedData.set(id, item);
1451
- const tokens2 = prepareObjectForSearch(item, spec);
1222
+ const tokensMap = createTokensMap();
1223
+ const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1224
+ clearTokensMap(tokensMap);
1452
1225
  if (tokens2 !== undefined) {
1453
1226
  linked.linkedMeta.set(id, tokens2);
1454
1227
  } else {
@@ -1480,7 +1253,7 @@ function upsertLinkedItem(linked, item, foreignKeyGetter, spec) {
1480
1253
  previousPrimaryId: wasAdded ? undefined : previousPrimaryId
1481
1254
  };
1482
1255
  }
1483
- function removeLinkedItem(linked, linkedId, foreignKeyGetter) {
1256
+ function removeLinkedItemNumeric(linked, linkedId, foreignKeyGetter) {
1484
1257
  const existingItem = linked.linkedData.get(linkedId);
1485
1258
  if (!existingItem) {
1486
1259
  return false;
@@ -1499,12 +1272,13 @@ function removeLinkedItem(linked, linkedId, foreignKeyGetter) {
1499
1272
  }
1500
1273
  return true;
1501
1274
  }
1502
- function syncLinkedItemsForPrimary(linked, primaryId, newLinkedItems, foreignKeyGetter, spec) {
1275
+ function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, foreignKeyGetter, spec) {
1503
1276
  const result = {
1504
1277
  added: 0,
1505
1278
  updated: 0,
1506
1279
  removed: 0
1507
1280
  };
1281
+ const tokensMap = createTokensMap();
1508
1282
  const currentLinkedIds = new Set(linked.primaryToLinked.get(primaryId) || []);
1509
1283
  const newLinkedIds = new Set;
1510
1284
  for (const [id, item] of newLinkedItems) {
@@ -1524,7 +1298,7 @@ function syncLinkedItemsForPrimary(linked, primaryId, newLinkedItems, foreignKey
1524
1298
  }
1525
1299
  for (const id of newLinkedIds) {
1526
1300
  const item = newLinkedItems.get(id);
1527
- const tokens2 = prepareObjectForSearch(item, spec);
1301
+ const tokens2 = prepareObjectForSearchNumeric(item, spec, tokensMap);
1528
1302
  if (currentLinkedIds.has(id)) {
1529
1303
  const existingTokens = linked.linkedMeta.get(id);
1530
1304
  if (tokens2 !== undefined) {
@@ -1543,6 +1317,7 @@ function syncLinkedItemsForPrimary(linked, primaryId, newLinkedItems, foreignKey
1543
1317
  }
1544
1318
  }
1545
1319
  linked.primaryToLinked.set(primaryId, Array.from(newLinkedIds));
1320
+ clearTokensMap(tokensMap);
1546
1321
  return result;
1547
1322
  }
1548
1323
  // src/common/SearchUniverse.ts
@@ -1779,7 +1554,7 @@ class SearchUniverse {
1779
1554
  const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, searchOpts);
1780
1555
  if (linkedSearchResults.results.length > 0) {
1781
1556
  const combinedResults = [];
1782
- const hasNegation = query.includes("--");
1557
+ const hasNegation = linkedSearchResults.hasNegation;
1783
1558
  for (const result of linkedSearchResults.results) {
1784
1559
  let linkedItems = result.linked;
1785
1560
  if (result.matchedIn === "primary" && !hasNegation) {