cry-search 2.0.1 → 2.0.2

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.
package/CLAUDE.md CHANGED
@@ -433,3 +433,23 @@ src/
433
433
  1. Memory: numeric ~45% less
434
434
  2. Build time: ~same
435
435
  3. Search time: varies by match mode
436
+
437
+ ## Phone number normalization
438
+
439
+ - `DEFAULT_PHONE_FIELDS`: `telefon`, `phone`, `gsm`, `msisdn`.
440
+ - `SearchableObjectSpec.phoneFields` compares exact field paths; configure
441
+ nested values such as `kontakt.telefon` explicitly. `[]` preserves legacy
442
+ tokenization.
443
+ - Phone values are converted directly to one ASCII digit token and encoded with
444
+ `phoneMatchMode` (default `anywhere`); they do not pass through the ordinary
445
+ date/preprocess/sanitize/tokenize pipeline.
446
+ - `phoneNormalizer` returns ASCII digits or `undefined`. Invalid non-digit
447
+ custom output throws at indexing time.
448
+ - A complete phone-like query gets canonical phone variants before raw tokens;
449
+ if a phone variant has candidates, raw matching is only a fallback.
450
+ - `phoneQueryNormalizer` overrides query recognition. `SearchUniverse` derives
451
+ its normalizers from collection specs; direct custom search passes
452
+ `resolvePhoneQueryNormalizer(spec)` in `SearchOpts.phoneQueryNormalizers`.
453
+ - `extractSearchableStringFn` is an explicit escape hatch and bypasses automatic
454
+ phone-field processing. Phone fields are excluded from `keyFields` positional
455
+ ranking.
package/README.md CHANGED
@@ -450,6 +450,39 @@ Match mode for numbers is determined by the **target token's length** (not the q
450
450
  // Use "=12" for exact match on short numbers
451
451
  ```
452
452
 
453
+ ### Phone Number Normalization
454
+
455
+ Phone fields are indexed as one canonical digit token, so the following values
456
+ converge without adding a dependency on a country-specific phone library:
457
+
458
+ ```text
459
+ 041/123-456 → 041123456
460
+ 041 123 456 → 041123456
461
+ +386 41 123 456 → 38641123456
462
+ ```
463
+
464
+ `telefon`, `phone`, `gsm`, and `msisdn` are normalized by default. Configure
465
+ additional nested paths explicitly, or opt out per collection:
466
+
467
+ ```typescript
468
+ const spec = {
469
+ searchInFields: ['name', 'kontakt.telefon'],
470
+ phoneFields: ['kontakt.telefon'],
471
+ // phoneFields: [], // preserve legacy segment tokenization
472
+ };
473
+ ```
474
+
475
+ A complete phone-like query is normalized before the ordinary tokenized query.
476
+ Phone results take priority; raw tokens are used only when no phone variant
477
+ matches. This keeps `041 123 456` precise while preserving raw-code search as a
478
+ fallback. Mixed text such as `Janez 041/123-456` is intentionally not parsed
479
+ as a phone query.
480
+
481
+ For custom canonicalization, configure `phoneNormalizer`. `SearchUniverse`
482
+ uses its matching query normalizer automatically. A direct `searchInData*`
483
+ caller must pass `resolvePhoneQueryNormalizer(spec)` via
484
+ `phoneQueryNormalizers` when using a custom normalizer.
485
+
453
486
  ### Hyphen Splitting
454
487
 
455
488
  Tokens are split at letter-dash-digit, digit-dash-letter and letter-dash-letter boundaries during preprocessing. Digit-dash-digit ranges (e.g., `12-25`) are preserved.
package/UNIVERSE.md CHANGED
@@ -862,3 +862,16 @@ For direct metadata manipulation without SearchUniverse:
862
862
  │ │
863
863
  └─────────────────────────────────────────────────────────────────────────────┘
864
864
  ```
865
+
866
+ ## Phone number normalization
867
+
868
+ `SearchUniverse.searchUniversally` derives a phone query normalizer from each
869
+ collection's `spec`. A standalone collection uses its own normalizer. A linked
870
+ search evaluates normalizers from both the primary and linked specs, so a
871
+ custom primary phone format remains searchable through a linked collection.
872
+
873
+ For a complete phone-like query, matching canonical phone variants takes
874
+ priority over the ordinary segmented query. If no phone variant finds a result,
875
+ SearchUniverse falls back to its usual raw-token search. Caller-supplied
876
+ `phoneQueryNormalizers` are additional variants; they do not replace the
877
+ collection-derived normalizers.
@@ -1 +1 @@
1
- {"version":3,"file":"SearchUniverse.d.ts","sourceRoot":"","sources":["../../src/common/SearchUniverse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchUniverse.d.ts","sourceRoot":"","sources":["../../src/common/SearchUniverse.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createSearchArrayMetadata.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/createSearchArrayMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACrB,MAAM,aAAa,CAAC;AASrB;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAEjE;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAS1D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMrD,CAAC;AAKF,mDAAmD;AACnD,eAAO,MAAM,aAAa,QAAwB,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAS1D;AAoED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,GAAG,OAAO,EACnF,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,EAC5C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,oBAAoB,GAAG,SAAS,CAoBlC;AA6GD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,gCAAgC,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,GAAG,OAAO,EACtF,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,GAChC,qBAAqB,CAoBvB;AAED,yDAAyD;AACzD,wBAAgB,0BAA0B,IAAI,qBAAqB,CAElE"}
1
+ {"version":3,"file":"createSearchArrayMetadata.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/createSearchArrayMetadata.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAUrB;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAEjE;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAS1D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMrD,CAAC;AAKF,mDAAmD;AACnD,eAAO,MAAM,aAAa,QAAwB,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAS1D;AAoED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,GAAG,OAAO,EACnF,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,EAC5C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,oBAAoB,GAAG,SAAS,CA0BlC;AAgID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,gCAAgC,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,GAAG,OAAO,EACtF,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,GAChC,qBAAqB,CAoBvB;AAED,yDAAyD;AACzD,wBAAgB,0BAA0B,IAAI,qBAAqB,CAElE"}
@@ -1,31 +1,9 @@
1
1
  import type { SearchableObject, SearchOpts, LinkedSearchResult, LinkedSearchResultWithMeta } from '../../types';
2
2
  import type { NumericLinkedSearchMetadataWithData } from './createSearchLinkedMetadata';
3
3
  /**
4
- * Searches across linked data sets using numeric tokens, ranked by positional relevance.
4
+ * Searches linked metadata with canonical phone variants before raw query tokens.
5
5
  *
6
- * For each primary item, collects all tokens from the primary and its linked items,
7
- * then matches query tokens against this combined pool. Returns primary items with
8
- * only the linked items that contributed to the match.
9
- *
10
- * **Ranking:** results are sorted by positional score (using `metadata.unsorted`
11
- * of primary + matched linked items, in source order). Items with `keyFields`
12
- * declared at build time get accurate positional scoring; items without retain
13
- * primary insertion order via the deterministic tiebreaker.
14
- *
15
- * **Key behaviors:**
16
- * - Tokens that match in the primary are excluded from linked matching (prevents
17
- * foreign key redundancy)
18
- * - Only linked items that match ALL remaining tokens are included in results
19
- * - `matchedIn` indicates where the match was found: 'primary', 'linked', or 'both'
20
- *
21
- * @typeParam TPrimary - Type of primary items (e.g., Stranka)
22
- * @typeParam TLinked - Type of linked items (e.g., Pacient)
23
- *
24
- * @param query - The search query string (will be tokenized)
25
- * @param linked - Linked metadata from {@link createSearchLinkedMetadataNumeric}
26
- * @param limit - Maximum number of results to return (default: 10)
27
- * @param opts - Optional search options for controlling match behavior
28
- * @returns Search results with query tokens, matching items, and hasNegation flag
6
+ * @feature [phone-search]
29
7
  */
30
8
  export declare function findInLinkedArraysNumeric<TPrimary extends SearchableObject, TLinked extends SearchableObject>(query: string, linked: NumericLinkedSearchMetadataWithData<TPrimary, TLinked>, limit?: number, opts?: SearchOpts): LinkedSearchResultWithMeta<TPrimary, TLinked>;
31
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"findInLinkedArrays.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/findInLinkedArrays.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAC;AAUxF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,SAAS,gBAAgB,EACjC,OAAO,SAAS,gBAAgB,EAEhC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,mCAAmC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9D,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,UAAU,GAChB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAmO/C;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,SAAS,gBAAgB,EACjC,OAAO,SAAS,gBAAgB,EAEhC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,mCAAmC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9D,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,UAAU,GAChB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAEzC"}
1
+ {"version":3,"file":"findInLinkedArrays.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/findInLinkedArrays.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAC;AA6QxF;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,SAAS,gBAAgB,EACjC,OAAO,SAAS,gBAAgB,EAEhC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,mCAAmC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9D,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,UAAU,GAChB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAoC/C;AAiCD;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,SAAS,gBAAgB,EACjC,OAAO,SAAS,gBAAgB,EAEhC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,mCAAmC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9D,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,UAAU,GAChB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAEzC"}
@@ -0,0 +1,15 @@
1
+ import type { PreparedQueryToken } from '../../utils/matchTokenNumeric';
2
+ import type { SearchOpts } from '../../types';
3
+ /** Prepared raw and canonical-phone alternatives for one search query. */
4
+ export interface PreparedQueryVariants {
5
+ rawTokenStrs: string[];
6
+ rawTokens: PreparedQueryToken[];
7
+ phoneTokens: PreparedQueryToken[][];
8
+ }
9
+ /**
10
+ * Produces deduplicated phone alternatives without changing ordinary query tokens.
11
+ *
12
+ * @feature [phone-search]
13
+ */
14
+ export declare function prepareQueryVariants(query: string, opts?: SearchOpts): PreparedQueryVariants;
15
+ //# sourceMappingURL=prepareQueryVariants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareQueryVariants.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/prepareQueryVariants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAIxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,WAAW,EAAE,kBAAkB,EAAE,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,qBAAqB,CAmB5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"searchInData.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/searchInData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,EAAE,EACF,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACX,MAAM,aAAa,CAAC;AAMrB;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,CAAC,EAAE,UAAU,GAChB,EAAE,EAAE,CAEN;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAAC,CAAC,SAAS,gBAAgB,EACzE,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,CAAC,EAAE,UAAU,GAChB,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAYZ;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,UAAU,GAChB,EAAE,EAAE,CAiDN"}
1
+ {"version":3,"file":"searchInData.d.ts","sourceRoot":"","sources":["../../../src/common/numeric/searchInData.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,EAAE,EACF,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACX,MAAM,aAAa,CAAC;AAMrB;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,CAAC,EAAE,UAAU,GAChB,EAAE,EAAE,CAEN;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAAC,CAAC,SAAS,gBAAgB,EACzE,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,CAAC,EAAE,UAAU,GAChB,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAYZ;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,qBAAqB,EAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,UAAU,GAChB,EAAE,EAAE,CA4CN"}
package/dist/index.cjs CHANGED
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
6
8
  var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
8
10
  if (entry)
9
11
  return entry;
10
12
  entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
16
21
  __moduleCache.set(from, entry);
17
22
  return entry;
18
23
  };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
19
29
  var __export = (target, all) => {
20
30
  for (var name in all)
21
31
  __defProp(target, name, {
22
32
  get: all[name],
23
33
  enumerable: true,
24
34
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
35
+ set: __exportSetter.bind(all, name)
26
36
  });
27
37
  };
28
38
 
@@ -49,6 +59,7 @@ __export(exports_src, {
49
59
  scoreOne: () => scoreOne,
50
60
  scoreBag: () => scoreBag,
51
61
  sanitiseString: () => sanitiseString,
62
+ resolvePhoneQueryNormalizer: () => resolvePhoneQueryNormalizer,
52
63
  resetTokenRegistry: () => resetTokenRegistry,
53
64
  removePrimaryItem: () => removePrimaryItemNumeric,
54
65
  removeLinkedItem: () => removeLinkedItemNumeric,
@@ -71,6 +82,8 @@ __export(exports_src, {
71
82
  findInLinkedArraysSimpleNumeric: () => findInLinkedArraysSimpleNumeric,
72
83
  findInLinkedArraysNumeric: () => findInLinkedArraysNumeric,
73
84
  encodeTokenWithMode: () => encodeTokenWithMode,
85
+ defaultQueryPhoneNormalizer: () => defaultQueryPhoneNormalizer,
86
+ defaultPhoneNormalizer: () => defaultPhoneNormalizer,
74
87
  decodeTokenId: () => decodeTokenId,
75
88
  decodeMatchMode: () => decodeMatchMode,
76
89
  createTokensMap: () => createTokensMap,
@@ -87,11 +100,13 @@ __export(exports_src, {
87
100
  MATCH_MODE_PREFIXES: () => MATCH_MODE_PREFIXES,
88
101
  MATCH_MODE_BITS: () => MATCH_MODE_BITS,
89
102
  DEFAULT_SEARCH_OPTS: () => DEFAULT_SEARCH_OPTS,
103
+ DEFAULT_PHONE_FIELDS: () => DEFAULT_PHONE_FIELDS,
90
104
  AugmentedMetadata: () => AugmentedMetadata
91
105
  });
92
106
  module.exports = __toCommonJS(exports_src);
93
107
 
94
108
  // src/types/index.ts
109
+ var DEFAULT_PHONE_FIELDS = ["telefon", "phone", "gsm", "msisdn"];
95
110
  var DEFAULT_FIRST_TOKENS_N = 8;
96
111
  var DEFAULT_SEARCH_OPTS = {
97
112
  matchWords: "start",
@@ -304,6 +319,37 @@ function prepareStringForSearch(input) {
304
319
  const tokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
305
320
  return tokens.sort();
306
321
  }
322
+ // src/utils/phoneNormalization.ts
323
+ var RE_ASCII_DIGITS = /^[0-9]+$/;
324
+ var RE_PHONE_LIKE_QUERY = /^[\d+()/\-. ]+$/;
325
+ var MIN_PHONE_DIGITS = 4;
326
+ var defaultPhoneNormalizer = (value) => {
327
+ const digits = value.replace(/\D/g, "");
328
+ return digits.length >= MIN_PHONE_DIGITS ? digits : undefined;
329
+ };
330
+ var defaultQueryPhoneNormalizer = (query) => {
331
+ if (!RE_PHONE_LIKE_QUERY.test(query))
332
+ return;
333
+ return defaultPhoneNormalizer(query);
334
+ };
335
+ function validateNormalizedPhone(normalized, normalizerName) {
336
+ if (normalized === undefined)
337
+ return;
338
+ if (typeof normalized !== "string" || !RE_ASCII_DIGITS.test(normalized)) {
339
+ throw new Error(`${normalizerName} must return an ASCII digit string or undefined`);
340
+ }
341
+ return normalized;
342
+ }
343
+ function resolvePhoneQueryNormalizer(spec) {
344
+ if (spec?.phoneQueryNormalizer)
345
+ return spec.phoneQueryNormalizer;
346
+ const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
347
+ return (query) => {
348
+ if (!RE_PHONE_LIKE_QUERY.test(query))
349
+ return;
350
+ return phoneNormalizer(query);
351
+ };
352
+ }
307
353
  // src/utils/tokenRegistry.ts
308
354
  var INITIAL_CAPACITY = 10 * 1000;
309
355
  var tokens = new Array(INITIAL_CAPACITY);
@@ -481,7 +527,7 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
481
527
  const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
482
528
  if (sorted === undefined)
483
529
  return;
484
- const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
530
+ const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, spec.phoneFields ?? DEFAULT_PHONE_FIELDS, tokensMap) : undefined;
485
531
  return { sorted, firstN };
486
532
  }
487
533
  function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
@@ -494,7 +540,9 @@ function buildSortedFromFields(obj, spec, tokensMap) {
494
540
  const allTokens = [];
495
541
  const fieldMatchModes = spec?.fieldMatchModes;
496
542
  const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
497
- const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
543
+ const fieldsToExclude = new Set((spec?.dontSearchInFields ?? []).map(String));
544
+ const phoneFields = new Set(spec?.phoneFields ?? DEFAULT_PHONE_FIELDS);
545
+ const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
498
546
  for (const key of fieldsToSearch) {
499
547
  const keyStr = key;
500
548
  if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
@@ -502,36 +550,50 @@ function buildSortedFromFields(obj, spec, tokensMap) {
502
550
  if (fieldsToExclude.has(key))
503
551
  continue;
504
552
  const isDot = isDotNotation(keyStr);
505
- const value = isDot ? getValueByPath(obj, keyStr) : obj[key];
553
+ const value = isDot ? getValueByPath(obj, keyStr) : obj[keyStr];
506
554
  if (value == null)
507
555
  continue;
508
556
  const matchMode = fieldMatchModes?.[key];
509
557
  const fieldStrings = [];
510
558
  extractStringsFromValue(value, fieldStrings);
511
- if (fieldStrings.length > 0) {
512
- const fieldText = fieldStrings.join(" ");
513
- const datesNormalized = normalizeDates(fieldText);
514
- const preprocessed = preprocessString(datesNormalized);
515
- const sanitised = sanitiseString(preprocessed);
516
- const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
517
- const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
518
- for (const token of stringTokens) {
519
- const tokenId = getOrCreateTokenId(token, tokensMap);
520
- if (effectiveMode) {
521
- allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
522
- } else {
523
- allTokens.push(tokenId);
524
- }
559
+ if (phoneFields.has(keyStr)) {
560
+ const phoneMatchMode = spec?.phoneMatchMode ?? "anywhere";
561
+ for (const fieldString of fieldStrings) {
562
+ const normalized = validateNormalizedPhone(phoneNormalizer(fieldString), `Phone normalizer for field "${keyStr}"`);
563
+ if (normalized === undefined)
564
+ continue;
565
+ const tokenId = getOrCreateTokenId(normalized, tokensMap);
566
+ allTokens.push(encodeTokenWithMode(tokenId, phoneMatchMode));
567
+ }
568
+ continue;
569
+ }
570
+ if (fieldStrings.length === 0)
571
+ continue;
572
+ const fieldText = fieldStrings.join(" ");
573
+ const datesNormalized = normalizeDates(fieldText);
574
+ const preprocessed = preprocessString(datesNormalized);
575
+ const sanitised = sanitiseString(preprocessed);
576
+ const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
577
+ const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
578
+ for (const token of stringTokens) {
579
+ const tokenId = getOrCreateTokenId(token, tokensMap);
580
+ if (effectiveMode) {
581
+ allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
582
+ } else {
583
+ allTokens.push(tokenId);
525
584
  }
526
585
  }
527
586
  }
528
587
  const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
529
588
  return new Uint32Array(uniqueTokens);
530
589
  }
531
- function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
590
+ function buildFirstNFromKeyFields(obj, keyFields, n, phoneFields, tokensMap) {
532
591
  const strings = [];
592
+ const phoneFieldPaths = new Set(phoneFields);
533
593
  for (const field of keyFields) {
534
594
  const keyStr = field;
595
+ if (phoneFieldPaths.has(keyStr))
596
+ continue;
535
597
  const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
536
598
  if (value == null)
537
599
  continue;
@@ -834,6 +896,23 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
834
896
  return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
835
897
  }
836
898
 
899
+ // src/common/numeric/prepareQueryVariants.ts
900
+ function prepareQueryVariants(query, opts) {
901
+ const rawTokenStrs = prepareStringForSearch(query);
902
+ const rawTokens = prepareQueryTokens(rawTokenStrs);
903
+ const normalizers = opts?.phoneQueryNormalizers ?? [defaultQueryPhoneNormalizer];
904
+ const seenPhones = new Set;
905
+ const phoneTokens = [];
906
+ for (const normalizer of normalizers) {
907
+ const normalized = validateNormalizedPhone(normalizer(query), "Phone query normalizer");
908
+ if (normalized === undefined || seenPhones.has(normalized))
909
+ continue;
910
+ seenPhones.add(normalized);
911
+ phoneTokens.push(prepareQueryTokens([normalized]));
912
+ }
913
+ return { rawTokenStrs, rawTokens, phoneTokens };
914
+ }
915
+
837
916
  // src/common/numeric/scoring.ts
838
917
  function orderedTokenize(input) {
839
918
  return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
@@ -950,16 +1029,9 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
950
1029
  return results;
951
1030
  }
952
1031
  function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
953
- const queryTokenStrs = prepareStringForSearch(query);
954
- if (queryTokenStrs.length === 0)
955
- return [];
956
- const preparedTokens = prepareQueryTokens(queryTokenStrs);
957
- const candidates = [];
958
- for (const [id, targetTokens] of metadata.sorted) {
959
- if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
960
- candidates.push(id);
961
- }
962
- }
1032
+ const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
1033
+ const phoneCandidates = findMatchingCandidates(metadata, phoneTokens, opts);
1034
+ const candidates = phoneCandidates.length > 0 ? phoneCandidates : findMatchingCandidates(metadata, rawTokens.length > 0 ? [rawTokens] : [], opts);
963
1035
  if (candidates.length === 0)
964
1036
  return [];
965
1037
  const qTokens = orderedTokenize(query);
@@ -994,13 +1066,22 @@ function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
994
1066
  out.push(scored[i].id);
995
1067
  return out;
996
1068
  }
997
- // src/common/numeric/findInLinkedArrays.ts
998
- function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
999
- const queryTokenStrs = prepareStringForSearch(query);
1000
- if (queryTokenStrs.length === 0) {
1001
- return { queryTokens: queryTokenStrs, results: [], hasNegation: false };
1069
+ function findMatchingCandidates(metadata, variants, opts) {
1070
+ if (variants.length === 0)
1071
+ return [];
1072
+ const candidates = [];
1073
+ for (const [id, targetTokens] of metadata.sorted) {
1074
+ for (const preparedTokens of variants) {
1075
+ if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
1076
+ candidates.push(id);
1077
+ break;
1078
+ }
1079
+ }
1002
1080
  }
1003
- const preparedTokens = prepareQueryTokens(queryTokenStrs);
1081
+ return candidates;
1082
+ }
1083
+ // src/common/numeric/findInLinkedArrays.ts
1084
+ function findInLinkedArraysForPreparedTokens(query, queryTokenStrs, preparedTokens, linked, limit, opts) {
1004
1085
  const positiveTokens = preparedTokens.filter((t) => !t.negated);
1005
1086
  const negatedTokens = preparedTokens.filter((t) => t.negated);
1006
1087
  const scored = [];
@@ -1170,6 +1251,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1170
1251
  results[i] = scored[i].result;
1171
1252
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
1172
1253
  }
1254
+ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1255
+ const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
1256
+ const phoneResults = [];
1257
+ for (const preparedTokens of phoneTokens) {
1258
+ const result = findInLinkedArraysForPreparedTokens(query, rawTokenStrs, preparedTokens, linked, Infinity, opts);
1259
+ phoneResults.push(...result.results);
1260
+ }
1261
+ if (phoneResults.length > 0) {
1262
+ return {
1263
+ queryTokens: rawTokenStrs,
1264
+ results: mergePhoneResults(phoneResults).slice(0, limit),
1265
+ hasNegation: false
1266
+ };
1267
+ }
1268
+ if (rawTokens.length === 0) {
1269
+ return { queryTokens: rawTokenStrs, results: [], hasNegation: false };
1270
+ }
1271
+ return findInLinkedArraysForPreparedTokens(query, rawTokenStrs, rawTokens, linked, limit, opts);
1272
+ }
1273
+ function mergePhoneResults(results) {
1274
+ const byPrimaryId = new Map;
1275
+ for (const result of results) {
1276
+ const existing = byPrimaryId.get(result.primary._id);
1277
+ if (existing === undefined) {
1278
+ byPrimaryId.set(result.primary._id, result);
1279
+ continue;
1280
+ }
1281
+ const linkedById = new Map(existing.linked.map((item) => [item._id, item]));
1282
+ for (const item of result.linked)
1283
+ linkedById.set(item._id, item);
1284
+ existing.linked = Array.from(linkedById.values());
1285
+ existing.matchedIn = mergeMatchLocation(existing.matchedIn, result.matchedIn);
1286
+ }
1287
+ return Array.from(byPrimaryId.values());
1288
+ }
1289
+ function mergeMatchLocation(first, second) {
1290
+ if (first === "both" || second === "both" || first !== second)
1291
+ return "both";
1292
+ return first;
1293
+ }
1173
1294
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
1174
1295
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
1175
1296
  }
@@ -1683,6 +1804,15 @@ function firstNEqual3(a, b) {
1683
1804
  return false;
1684
1805
  return tokensEqual3(a, b);
1685
1806
  }
1807
+ function withCollectionPhoneNormalizers(opts, specs) {
1808
+ return {
1809
+ ...opts,
1810
+ phoneQueryNormalizers: [
1811
+ ...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
1812
+ ...opts.phoneQueryNormalizers ?? []
1813
+ ]
1814
+ };
1815
+ }
1686
1816
 
1687
1817
  class SearchUniverse {
1688
1818
  collections = new Map;
@@ -2043,7 +2173,11 @@ class SearchUniverse {
2043
2173
  primaryData: primaryCollection.data,
2044
2174
  linkedData: collection.data
2045
2175
  };
2046
- const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, searchOpts);
2176
+ const linkedSearchOpts = withCollectionPhoneNormalizers(searchOpts, [
2177
+ primaryCollection.config.spec,
2178
+ collection.config.spec
2179
+ ]);
2180
+ const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, linkedSearchOpts);
2047
2181
  if (linkedSearchResults.results.length > 0) {
2048
2182
  const combinedResults = [];
2049
2183
  const hasNegation = linkedSearchResults.hasNegation;
@@ -2066,7 +2200,7 @@ class SearchUniverse {
2066
2200
  if (inCollections && !inCollections.includes(name)) {
2067
2201
  continue;
2068
2202
  }
2069
- const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
2203
+ const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
2070
2204
  if (matchingIds.length > 0) {
2071
2205
  const collectionResults = [];
2072
2206
  for (const id of matchingIds) {
package/dist/index.d.cts CHANGED
@@ -2,15 +2,16 @@
2
2
  * cry-search - A fast, memory-efficient search library for large datasets
3
3
  * @packageDocumentation
4
4
  */
5
- export type { Id, SearchToken, SearchTokenSortedList, SearchableData, NumericToken, NumericTokenSortedList, NumericSearchMetadata, SearchableObject, MatchMode, SearchableObjectSpec, SearchOpts, SearchableLinkedObjectsSpec, LinkedSearchResult, LinkedSearchResultWithMeta, SearchResultWithMeta, SyncResult, SyncOptions, SyncLinkedResult, UpsertResult, UpdateSearchMetadataBatchItem, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult, } from './types';
5
+ export type { Id, SearchToken, SearchTokenSortedList, SearchableData, NumericToken, NumericTokenSortedList, NumericSearchMetadata, SearchableObject, MatchMode, PhoneNormalizer, PhoneQueryNormalizer, SearchableObjectSpec, SearchOpts, SearchableLinkedObjectsSpec, LinkedSearchResult, LinkedSearchResultWithMeta, SearchResultWithMeta, SyncResult, SyncOptions, SyncLinkedResult, UpsertResult, UpdateSearchMetadataBatchItem, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult, } from './types';
6
6
  export type { NumericSearchMetadata as SearchMetadata } from './types';
7
- export { DEFAULT_SEARCH_OPTS } from './types';
7
+ export { DEFAULT_SEARCH_OPTS, DEFAULT_PHONE_FIELDS } from './types';
8
8
  export { arrayToSearchableData, searchableDataToArray } from './types';
9
9
  export { prepareStringForSearch } from './utils/prepareStringForSearch';
10
10
  export { preprocessString } from './utils/preprocessString';
11
11
  export { sanitiseString } from './utils/sanitiseString';
12
12
  export { tokenize } from './utils/tokenize';
13
13
  export { normalizeDates } from './utils/normalizeDates';
14
+ export { defaultPhoneNormalizer, defaultQueryPhoneNormalizer, resolvePhoneQueryNormalizer, } from './utils/phoneNormalization';
14
15
  export { tokens, getTokenCount, getTokenById, findTokenId, registerToken, getOrCreateTokenId, createTokensMap, clearTokensMap, resetTokenRegistry, } from './utils/tokenRegistry';
15
16
  export { createSearchArrayMetadataNumeric as createSearchArrayMetadata, prepareObjectForSearchNumeric as prepareObjectForSearch, encodeTokenWithMode, decodeTokenId, decodeMatchMode, TOKEN_ID_MASK, MATCH_MODE_BITS, MATCH_MODE_PREFIXES, PREFIX_TO_MATCH_MODE, isDeletedOrBlocked, } from './common/numeric/createSearchArrayMetadata';
16
17
  export { createSearchLinkedMetadataNumeric as createSearchLinkedMetadata, type NumericLinkedSearchMetadataWithData, } from './common/numeric/createSearchLinkedMetadata';
package/dist/index.d.ts CHANGED
@@ -2,15 +2,16 @@
2
2
  * cry-search - A fast, memory-efficient search library for large datasets
3
3
  * @packageDocumentation
4
4
  */
5
- export type { Id, SearchToken, SearchTokenSortedList, SearchableData, NumericToken, NumericTokenSortedList, NumericSearchMetadata, SearchableObject, MatchMode, SearchableObjectSpec, SearchOpts, SearchableLinkedObjectsSpec, LinkedSearchResult, LinkedSearchResultWithMeta, SearchResultWithMeta, SyncResult, SyncOptions, SyncLinkedResult, UpsertResult, UpdateSearchMetadataBatchItem, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult, } from './types';
5
+ export type { Id, SearchToken, SearchTokenSortedList, SearchableData, NumericToken, NumericTokenSortedList, NumericSearchMetadata, SearchableObject, MatchMode, PhoneNormalizer, PhoneQueryNormalizer, SearchableObjectSpec, SearchOpts, SearchableLinkedObjectsSpec, LinkedSearchResult, LinkedSearchResultWithMeta, SearchResultWithMeta, SyncResult, SyncOptions, SyncLinkedResult, UpsertResult, UpdateSearchMetadataBatchItem, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult, } from './types';
6
6
  export type { NumericSearchMetadata as SearchMetadata } from './types';
7
- export { DEFAULT_SEARCH_OPTS } from './types';
7
+ export { DEFAULT_SEARCH_OPTS, DEFAULT_PHONE_FIELDS } from './types';
8
8
  export { arrayToSearchableData, searchableDataToArray } from './types';
9
9
  export { prepareStringForSearch } from './utils/prepareStringForSearch';
10
10
  export { preprocessString } from './utils/preprocessString';
11
11
  export { sanitiseString } from './utils/sanitiseString';
12
12
  export { tokenize } from './utils/tokenize';
13
13
  export { normalizeDates } from './utils/normalizeDates';
14
+ export { defaultPhoneNormalizer, defaultQueryPhoneNormalizer, resolvePhoneQueryNormalizer, } from './utils/phoneNormalization';
14
15
  export { tokens, getTokenCount, getTokenById, findTokenId, registerToken, getOrCreateTokenId, createTokensMap, clearTokensMap, resetTokenRegistry, } from './utils/tokenRegistry';
15
16
  export { createSearchArrayMetadataNumeric as createSearchArrayMetadata, prepareObjectForSearchNumeric as prepareObjectForSearch, encodeTokenWithMode, decodeTokenId, decodeMatchMode, TOKEN_ID_MASK, MATCH_MODE_BITS, MATCH_MODE_PREFIXES, PREFIX_TO_MATCH_MODE, isDeletedOrBlocked, } from './common/numeric/createSearchArrayMetadata';
16
17
  export { createSearchLinkedMetadataNumeric as createSearchLinkedMetadata, type NumericLinkedSearchMetadataWithData, } from './common/numeric/createSearchLinkedMetadata';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/types/index.ts
2
+ var DEFAULT_PHONE_FIELDS = ["telefon", "phone", "gsm", "msisdn"];
2
3
  var DEFAULT_FIRST_TOKENS_N = 8;
3
4
  var DEFAULT_SEARCH_OPTS = {
4
5
  matchWords: "start",
@@ -211,6 +212,37 @@ function prepareStringForSearch(input) {
211
212
  const tokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
212
213
  return tokens.sort();
213
214
  }
215
+ // src/utils/phoneNormalization.ts
216
+ var RE_ASCII_DIGITS = /^[0-9]+$/;
217
+ var RE_PHONE_LIKE_QUERY = /^[\d+()/\-. ]+$/;
218
+ var MIN_PHONE_DIGITS = 4;
219
+ var defaultPhoneNormalizer = (value) => {
220
+ const digits = value.replace(/\D/g, "");
221
+ return digits.length >= MIN_PHONE_DIGITS ? digits : undefined;
222
+ };
223
+ var defaultQueryPhoneNormalizer = (query) => {
224
+ if (!RE_PHONE_LIKE_QUERY.test(query))
225
+ return;
226
+ return defaultPhoneNormalizer(query);
227
+ };
228
+ function validateNormalizedPhone(normalized, normalizerName) {
229
+ if (normalized === undefined)
230
+ return;
231
+ if (typeof normalized !== "string" || !RE_ASCII_DIGITS.test(normalized)) {
232
+ throw new Error(`${normalizerName} must return an ASCII digit string or undefined`);
233
+ }
234
+ return normalized;
235
+ }
236
+ function resolvePhoneQueryNormalizer(spec) {
237
+ if (spec?.phoneQueryNormalizer)
238
+ return spec.phoneQueryNormalizer;
239
+ const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
240
+ return (query) => {
241
+ if (!RE_PHONE_LIKE_QUERY.test(query))
242
+ return;
243
+ return phoneNormalizer(query);
244
+ };
245
+ }
214
246
  // src/utils/tokenRegistry.ts
215
247
  var INITIAL_CAPACITY = 10 * 1000;
216
248
  var tokens = new Array(INITIAL_CAPACITY);
@@ -388,7 +420,7 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
388
420
  const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
389
421
  if (sorted === undefined)
390
422
  return;
391
- const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
423
+ const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, spec.phoneFields ?? DEFAULT_PHONE_FIELDS, tokensMap) : undefined;
392
424
  return { sorted, firstN };
393
425
  }
394
426
  function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
@@ -401,7 +433,9 @@ function buildSortedFromFields(obj, spec, tokensMap) {
401
433
  const allTokens = [];
402
434
  const fieldMatchModes = spec?.fieldMatchModes;
403
435
  const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
404
- const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
436
+ const fieldsToExclude = new Set((spec?.dontSearchInFields ?? []).map(String));
437
+ const phoneFields = new Set(spec?.phoneFields ?? DEFAULT_PHONE_FIELDS);
438
+ const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
405
439
  for (const key of fieldsToSearch) {
406
440
  const keyStr = key;
407
441
  if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
@@ -409,36 +443,50 @@ function buildSortedFromFields(obj, spec, tokensMap) {
409
443
  if (fieldsToExclude.has(key))
410
444
  continue;
411
445
  const isDot = isDotNotation(keyStr);
412
- const value = isDot ? getValueByPath(obj, keyStr) : obj[key];
446
+ const value = isDot ? getValueByPath(obj, keyStr) : obj[keyStr];
413
447
  if (value == null)
414
448
  continue;
415
449
  const matchMode = fieldMatchModes?.[key];
416
450
  const fieldStrings = [];
417
451
  extractStringsFromValue(value, fieldStrings);
418
- if (fieldStrings.length > 0) {
419
- const fieldText = fieldStrings.join(" ");
420
- const datesNormalized = normalizeDates(fieldText);
421
- const preprocessed = preprocessString(datesNormalized);
422
- const sanitised = sanitiseString(preprocessed);
423
- const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
424
- const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
425
- for (const token of stringTokens) {
426
- const tokenId = getOrCreateTokenId(token, tokensMap);
427
- if (effectiveMode) {
428
- allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
429
- } else {
430
- allTokens.push(tokenId);
431
- }
452
+ if (phoneFields.has(keyStr)) {
453
+ const phoneMatchMode = spec?.phoneMatchMode ?? "anywhere";
454
+ for (const fieldString of fieldStrings) {
455
+ const normalized = validateNormalizedPhone(phoneNormalizer(fieldString), `Phone normalizer for field "${keyStr}"`);
456
+ if (normalized === undefined)
457
+ continue;
458
+ const tokenId = getOrCreateTokenId(normalized, tokensMap);
459
+ allTokens.push(encodeTokenWithMode(tokenId, phoneMatchMode));
460
+ }
461
+ continue;
462
+ }
463
+ if (fieldStrings.length === 0)
464
+ continue;
465
+ const fieldText = fieldStrings.join(" ");
466
+ const datesNormalized = normalizeDates(fieldText);
467
+ const preprocessed = preprocessString(datesNormalized);
468
+ const sanitised = sanitiseString(preprocessed);
469
+ const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
470
+ const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
471
+ for (const token of stringTokens) {
472
+ const tokenId = getOrCreateTokenId(token, tokensMap);
473
+ if (effectiveMode) {
474
+ allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
475
+ } else {
476
+ allTokens.push(tokenId);
432
477
  }
433
478
  }
434
479
  }
435
480
  const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
436
481
  return new Uint32Array(uniqueTokens);
437
482
  }
438
- function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
483
+ function buildFirstNFromKeyFields(obj, keyFields, n, phoneFields, tokensMap) {
439
484
  const strings = [];
485
+ const phoneFieldPaths = new Set(phoneFields);
440
486
  for (const field of keyFields) {
441
487
  const keyStr = field;
488
+ if (phoneFieldPaths.has(keyStr))
489
+ continue;
442
490
  const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
443
491
  if (value == null)
444
492
  continue;
@@ -741,6 +789,23 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
741
789
  return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
742
790
  }
743
791
 
792
+ // src/common/numeric/prepareQueryVariants.ts
793
+ function prepareQueryVariants(query, opts) {
794
+ const rawTokenStrs = prepareStringForSearch(query);
795
+ const rawTokens = prepareQueryTokens(rawTokenStrs);
796
+ const normalizers = opts?.phoneQueryNormalizers ?? [defaultQueryPhoneNormalizer];
797
+ const seenPhones = new Set;
798
+ const phoneTokens = [];
799
+ for (const normalizer of normalizers) {
800
+ const normalized = validateNormalizedPhone(normalizer(query), "Phone query normalizer");
801
+ if (normalized === undefined || seenPhones.has(normalized))
802
+ continue;
803
+ seenPhones.add(normalized);
804
+ phoneTokens.push(prepareQueryTokens([normalized]));
805
+ }
806
+ return { rawTokenStrs, rawTokens, phoneTokens };
807
+ }
808
+
744
809
  // src/common/numeric/scoring.ts
745
810
  function orderedTokenize(input) {
746
811
  return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
@@ -857,16 +922,9 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
857
922
  return results;
858
923
  }
859
924
  function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
860
- const queryTokenStrs = prepareStringForSearch(query);
861
- if (queryTokenStrs.length === 0)
862
- return [];
863
- const preparedTokens = prepareQueryTokens(queryTokenStrs);
864
- const candidates = [];
865
- for (const [id, targetTokens] of metadata.sorted) {
866
- if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
867
- candidates.push(id);
868
- }
869
- }
925
+ const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
926
+ const phoneCandidates = findMatchingCandidates(metadata, phoneTokens, opts);
927
+ const candidates = phoneCandidates.length > 0 ? phoneCandidates : findMatchingCandidates(metadata, rawTokens.length > 0 ? [rawTokens] : [], opts);
870
928
  if (candidates.length === 0)
871
929
  return [];
872
930
  const qTokens = orderedTokenize(query);
@@ -901,13 +959,22 @@ function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
901
959
  out.push(scored[i].id);
902
960
  return out;
903
961
  }
904
- // src/common/numeric/findInLinkedArrays.ts
905
- function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
906
- const queryTokenStrs = prepareStringForSearch(query);
907
- if (queryTokenStrs.length === 0) {
908
- return { queryTokens: queryTokenStrs, results: [], hasNegation: false };
962
+ function findMatchingCandidates(metadata, variants, opts) {
963
+ if (variants.length === 0)
964
+ return [];
965
+ const candidates = [];
966
+ for (const [id, targetTokens] of metadata.sorted) {
967
+ for (const preparedTokens of variants) {
968
+ if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
969
+ candidates.push(id);
970
+ break;
971
+ }
972
+ }
909
973
  }
910
- const preparedTokens = prepareQueryTokens(queryTokenStrs);
974
+ return candidates;
975
+ }
976
+ // src/common/numeric/findInLinkedArrays.ts
977
+ function findInLinkedArraysForPreparedTokens(query, queryTokenStrs, preparedTokens, linked, limit, opts) {
911
978
  const positiveTokens = preparedTokens.filter((t) => !t.negated);
912
979
  const negatedTokens = preparedTokens.filter((t) => t.negated);
913
980
  const scored = [];
@@ -1077,6 +1144,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1077
1144
  results[i] = scored[i].result;
1078
1145
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
1079
1146
  }
1147
+ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1148
+ const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
1149
+ const phoneResults = [];
1150
+ for (const preparedTokens of phoneTokens) {
1151
+ const result = findInLinkedArraysForPreparedTokens(query, rawTokenStrs, preparedTokens, linked, Infinity, opts);
1152
+ phoneResults.push(...result.results);
1153
+ }
1154
+ if (phoneResults.length > 0) {
1155
+ return {
1156
+ queryTokens: rawTokenStrs,
1157
+ results: mergePhoneResults(phoneResults).slice(0, limit),
1158
+ hasNegation: false
1159
+ };
1160
+ }
1161
+ if (rawTokens.length === 0) {
1162
+ return { queryTokens: rawTokenStrs, results: [], hasNegation: false };
1163
+ }
1164
+ return findInLinkedArraysForPreparedTokens(query, rawTokenStrs, rawTokens, linked, limit, opts);
1165
+ }
1166
+ function mergePhoneResults(results) {
1167
+ const byPrimaryId = new Map;
1168
+ for (const result of results) {
1169
+ const existing = byPrimaryId.get(result.primary._id);
1170
+ if (existing === undefined) {
1171
+ byPrimaryId.set(result.primary._id, result);
1172
+ continue;
1173
+ }
1174
+ const linkedById = new Map(existing.linked.map((item) => [item._id, item]));
1175
+ for (const item of result.linked)
1176
+ linkedById.set(item._id, item);
1177
+ existing.linked = Array.from(linkedById.values());
1178
+ existing.matchedIn = mergeMatchLocation(existing.matchedIn, result.matchedIn);
1179
+ }
1180
+ return Array.from(byPrimaryId.values());
1181
+ }
1182
+ function mergeMatchLocation(first, second) {
1183
+ if (first === "both" || second === "both" || first !== second)
1184
+ return "both";
1185
+ return first;
1186
+ }
1080
1187
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
1081
1188
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
1082
1189
  }
@@ -1590,6 +1697,15 @@ function firstNEqual3(a, b) {
1590
1697
  return false;
1591
1698
  return tokensEqual3(a, b);
1592
1699
  }
1700
+ function withCollectionPhoneNormalizers(opts, specs) {
1701
+ return {
1702
+ ...opts,
1703
+ phoneQueryNormalizers: [
1704
+ ...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
1705
+ ...opts.phoneQueryNormalizers ?? []
1706
+ ]
1707
+ };
1708
+ }
1593
1709
 
1594
1710
  class SearchUniverse {
1595
1711
  collections = new Map;
@@ -1950,7 +2066,11 @@ class SearchUniverse {
1950
2066
  primaryData: primaryCollection.data,
1951
2067
  linkedData: collection.data
1952
2068
  };
1953
- const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, searchOpts);
2069
+ const linkedSearchOpts = withCollectionPhoneNormalizers(searchOpts, [
2070
+ primaryCollection.config.spec,
2071
+ collection.config.spec
2072
+ ]);
2073
+ const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, linkedSearchOpts);
1954
2074
  if (linkedSearchResults.results.length > 0) {
1955
2075
  const combinedResults = [];
1956
2076
  const hasNegation = linkedSearchResults.hasNegation;
@@ -1973,7 +2093,7 @@ class SearchUniverse {
1973
2093
  if (inCollections && !inCollections.includes(name)) {
1974
2094
  continue;
1975
2095
  }
1976
- const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
2096
+ const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
1977
2097
  if (matchingIds.length > 0) {
1978
2098
  const collectionResults = [];
1979
2099
  for (const id of matchingIds) {
@@ -2021,6 +2141,7 @@ export {
2021
2141
  scoreOne,
2022
2142
  scoreBag,
2023
2143
  sanitiseString,
2144
+ resolvePhoneQueryNormalizer,
2024
2145
  resetTokenRegistry,
2025
2146
  removePrimaryItemNumeric as removePrimaryItem,
2026
2147
  removeLinkedItemNumeric as removeLinkedItem,
@@ -2043,6 +2164,8 @@ export {
2043
2164
  findInLinkedArraysSimpleNumeric,
2044
2165
  findInLinkedArraysNumeric,
2045
2166
  encodeTokenWithMode,
2167
+ defaultQueryPhoneNormalizer,
2168
+ defaultPhoneNormalizer,
2046
2169
  decodeTokenId,
2047
2170
  decodeMatchMode,
2048
2171
  createTokensMap,
@@ -2059,5 +2182,6 @@ export {
2059
2182
  MATCH_MODE_PREFIXES,
2060
2183
  MATCH_MODE_BITS,
2061
2184
  DEFAULT_SEARCH_OPTS,
2185
+ DEFAULT_PHONE_FIELDS,
2062
2186
  AugmentedMetadata
2063
2187
  };
@@ -45,12 +45,18 @@ export interface SearchableObject {
45
45
  * - 'whole': exact match only - "rex" matches "rex" but not "rexona" or "ceporex"
46
46
  */
47
47
  export type MatchMode = 'start' | 'end' | 'startEnd' | 'anywhere' | 'whole';
48
+ /** Default root field names treated as phone numbers during metadata construction. */
49
+ export declare const DEFAULT_PHONE_FIELDS: readonly ["telefon", "phone", "gsm", "msisdn"];
50
+ /** Converts a stored phone value to canonical ASCII digits. */
51
+ export type PhoneNormalizer = (value: string) => string | undefined;
52
+ /** Recognizes a complete phone query and returns canonical ASCII digits. */
53
+ export type PhoneQueryNormalizer = (query: string) => string | undefined;
48
54
  /**
49
55
  * Specification for how to extract searchable text from objects
50
56
  */
51
57
  export interface SearchableObjectSpec<T extends SearchableObject, C = unknown> {
52
58
  /** Fields to include in search (if not specified, all string fields are used) */
53
- searchInFields?: (keyof T)[];
59
+ searchInFields?: readonly string[];
54
60
  /** Fields to exclude from search */
55
61
  dontSearchInFields?: (keyof T)[];
56
62
  /** Optional context passed to enrichSearchObjectFn */
@@ -65,6 +71,17 @@ export interface SearchableObjectSpec<T extends SearchableObject, C = unknown> {
65
71
  * Prefixes: < (start), > (end), + (startEnd), * (anywhere), ! (whole)
66
72
  */
67
73
  fieldMatchModes?: Partial<Record<keyof T, MatchMode>>;
74
+ /**
75
+ * Exact field paths whose values are normalized as phone numbers.
76
+ * @default DEFAULT_PHONE_FIELDS
77
+ */
78
+ phoneFields?: readonly string[];
79
+ /** Build normalizer for phone field values. @default defaultPhoneNormalizer */
80
+ phoneNormalizer?: PhoneNormalizer;
81
+ /** Query recognizer used by SearchUniverse for this collection. */
82
+ phoneQueryNormalizer?: PhoneQueryNormalizer;
83
+ /** Match mode for canonical phone tokens. @default 'anywhere' */
84
+ phoneMatchMode?: MatchMode;
68
85
  /**
69
86
  * Fields whose tokens are also stored in `metadata.unsorted` in SOURCE order
70
87
  * (no sort, no de-dup) for positional ranking. Fields are concatenated in
@@ -73,7 +90,7 @@ export interface SearchableObjectSpec<T extends SearchableObject, C = unknown> {
73
90
  * If omitted, `metadata.unsorted` has no entry for this item, and
74
91
  * positional ranking falls back to re-tokenizing primary text at query time.
75
92
  */
76
- keyFields?: (keyof T | string)[];
93
+ keyFields?: readonly string[];
77
94
  /**
78
95
  * Max number of tokens stored in `metadata.unsorted` per item.
79
96
  * Applied AFTER tokenization of concatenated `keyFields` content.
@@ -112,9 +129,14 @@ export interface SearchOpts {
112
129
  * @default 6
113
130
  */
114
131
  matchNumbersThreshold?: number;
132
+ /**
133
+ * Phone query recognizers evaluated before the ordinary tokenized query.
134
+ * When a phone variant finds candidates, it takes precedence over raw tokens.
135
+ */
136
+ phoneQueryNormalizers?: readonly PhoneQueryNormalizer[];
115
137
  }
116
- /** Default search options */
117
- export declare const DEFAULT_SEARCH_OPTS: Required<SearchOpts>;
138
+ /** Default match-mode options. Phone query normalizers are resolved per search. */
139
+ export declare const DEFAULT_SEARCH_OPTS: Required<Pick<SearchOpts, 'matchWords' | 'matchNumbers' | 'matchNumbersShort' | 'matchNumbersThreshold'>>;
118
140
  /**
119
141
  * Specification for linked search across two related arrays
120
142
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { PhoneNormalizer, PhoneQueryNormalizer, SearchableObject, SearchableObjectSpec } from '../types';
2
+ /**
3
+ * Converts a stored phone value to its canonical ASCII digit representation.
4
+ *
5
+ * @feature [phone-search]
6
+ */
7
+ export declare const defaultPhoneNormalizer: PhoneNormalizer;
8
+ /**
9
+ * Recognizes a complete phone query before applying default canonicalization.
10
+ *
11
+ * @feature [phone-search]
12
+ */
13
+ export declare const defaultQueryPhoneNormalizer: PhoneQueryNormalizer;
14
+ /**
15
+ * Verifies that a custom normalizer returned the canonical phone representation.
16
+ *
17
+ * @feature [phone-search]
18
+ */
19
+ export declare function validateNormalizedPhone(normalized: string | undefined, normalizerName: string): string | undefined;
20
+ /**
21
+ * Resolves the query normalizer that converges with a collection's build normalizer.
22
+ *
23
+ * @feature [phone-search]
24
+ */
25
+ export declare function resolvePhoneQueryNormalizer<T extends SearchableObject>(spec?: SearchableObjectSpec<T>): PhoneQueryNormalizer;
26
+ //# sourceMappingURL=phoneNormalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phoneNormalization.d.ts","sourceRoot":"","sources":["../../src/utils/phoneNormalization.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,UAAU,CAAC;AAMlB;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAGpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,oBAGzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAMpB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,gBAAgB,EACpE,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC7B,oBAAoB,CAQtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-search",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "A fast, memory-efficient search library for large datasets with support for tokenized matching, linked collections, and per-field match modes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",