cry-search 2.0.0 → 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,CA6K/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 = [];
@@ -1019,6 +1100,30 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1019
1100
  }
1020
1101
  if (negatedMatchesInPrimary)
1021
1102
  continue;
1103
+ let allPositiveMatchInPrimary = true;
1104
+ for (const token of positiveTokens) {
1105
+ if (!matchPreparedTokenInSortedList(token, primaryTokens, opts)) {
1106
+ allPositiveMatchInPrimary = false;
1107
+ break;
1108
+ }
1109
+ }
1110
+ if (allPositiveMatchInPrimary && negatedTokens.length === 0) {
1111
+ const matchedLinked2 = linkedIds.map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
1112
+ scored.push({
1113
+ result: {
1114
+ primary,
1115
+ linked: matchedLinked2,
1116
+ matchedIn: "primary"
1117
+ },
1118
+ score: 0,
1119
+ order: orderCounter++,
1120
+ primaryId,
1121
+ matchedLinkedIds: matchedLinked2.map((l) => l._id)
1122
+ });
1123
+ continue;
1124
+ }
1125
+ if (!allPositiveMatchInPrimary && linkedIds.length === 0)
1126
+ continue;
1022
1127
  let totalLength = primaryTokens.length;
1023
1128
  for (const linkedId of linkedIds) {
1024
1129
  const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
@@ -1037,24 +1142,31 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1037
1142
  }
1038
1143
  }
1039
1144
  allTokens.sort();
1040
- let allPositiveMatch = true;
1041
- for (const token of positiveTokens) {
1042
- if (!matchPreparedTokenInSortedList(token, allTokens, opts)) {
1043
- allPositiveMatch = false;
1044
- break;
1145
+ if (!allPositiveMatchInPrimary) {
1146
+ let allPositiveMatch = true;
1147
+ for (const token of positiveTokens) {
1148
+ if (!matchPreparedTokenInSortedList(token, allTokens, opts)) {
1149
+ allPositiveMatch = false;
1150
+ break;
1151
+ }
1045
1152
  }
1153
+ if (!allPositiveMatch)
1154
+ continue;
1046
1155
  }
1047
- if (!allPositiveMatch)
1048
- continue;
1049
- let anyMatchInPrimary = false;
1156
+ let anyMatchInPrimary = allPositiveMatchInPrimary;
1050
1157
  let anyMatchInLinked = false;
1051
1158
  const matchingLinkedIds = new Set;
1052
1159
  const tokensMatchingPrimary = new Set;
1053
- for (const preparedToken of positiveTokens) {
1054
- if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
1055
- anyMatchInPrimary = true;
1056
- tokensMatchingPrimary.add(preparedToken);
1160
+ if (!allPositiveMatchInPrimary) {
1161
+ for (const preparedToken of positiveTokens) {
1162
+ if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
1163
+ anyMatchInPrimary = true;
1164
+ tokensMatchingPrimary.add(preparedToken);
1165
+ }
1057
1166
  }
1167
+ } else {
1168
+ for (const t of positiveTokens)
1169
+ tokensMatchingPrimary.add(t);
1058
1170
  }
1059
1171
  const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
1060
1172
  if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
@@ -1139,6 +1251,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1139
1251
  results[i] = scored[i].result;
1140
1252
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
1141
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
+ }
1142
1294
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
1143
1295
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
1144
1296
  }
@@ -1652,6 +1804,15 @@ function firstNEqual3(a, b) {
1652
1804
  return false;
1653
1805
  return tokensEqual3(a, b);
1654
1806
  }
1807
+ function withCollectionPhoneNormalizers(opts, specs) {
1808
+ return {
1809
+ ...opts,
1810
+ phoneQueryNormalizers: [
1811
+ ...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
1812
+ ...opts.phoneQueryNormalizers ?? []
1813
+ ]
1814
+ };
1815
+ }
1655
1816
 
1656
1817
  class SearchUniverse {
1657
1818
  collections = new Map;
@@ -2012,7 +2173,11 @@ class SearchUniverse {
2012
2173
  primaryData: primaryCollection.data,
2013
2174
  linkedData: collection.data
2014
2175
  };
2015
- 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);
2016
2181
  if (linkedSearchResults.results.length > 0) {
2017
2182
  const combinedResults = [];
2018
2183
  const hasNegation = linkedSearchResults.hasNegation;
@@ -2035,7 +2200,7 @@ class SearchUniverse {
2035
2200
  if (inCollections && !inCollections.includes(name)) {
2036
2201
  continue;
2037
2202
  }
2038
- const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
2203
+ const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
2039
2204
  if (matchingIds.length > 0) {
2040
2205
  const collectionResults = [];
2041
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 = [];
@@ -926,6 +993,30 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
926
993
  }
927
994
  if (negatedMatchesInPrimary)
928
995
  continue;
996
+ let allPositiveMatchInPrimary = true;
997
+ for (const token of positiveTokens) {
998
+ if (!matchPreparedTokenInSortedList(token, primaryTokens, opts)) {
999
+ allPositiveMatchInPrimary = false;
1000
+ break;
1001
+ }
1002
+ }
1003
+ if (allPositiveMatchInPrimary && negatedTokens.length === 0) {
1004
+ const matchedLinked2 = linkedIds.map((id) => linked.linkedData.get(id)).filter((item) => item !== undefined);
1005
+ scored.push({
1006
+ result: {
1007
+ primary,
1008
+ linked: matchedLinked2,
1009
+ matchedIn: "primary"
1010
+ },
1011
+ score: 0,
1012
+ order: orderCounter++,
1013
+ primaryId,
1014
+ matchedLinkedIds: matchedLinked2.map((l) => l._id)
1015
+ });
1016
+ continue;
1017
+ }
1018
+ if (!allPositiveMatchInPrimary && linkedIds.length === 0)
1019
+ continue;
929
1020
  let totalLength = primaryTokens.length;
930
1021
  for (const linkedId of linkedIds) {
931
1022
  const linkedTokens = linked.linkedMeta.sorted.get(linkedId);
@@ -944,24 +1035,31 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
944
1035
  }
945
1036
  }
946
1037
  allTokens.sort();
947
- let allPositiveMatch = true;
948
- for (const token of positiveTokens) {
949
- if (!matchPreparedTokenInSortedList(token, allTokens, opts)) {
950
- allPositiveMatch = false;
951
- break;
1038
+ if (!allPositiveMatchInPrimary) {
1039
+ let allPositiveMatch = true;
1040
+ for (const token of positiveTokens) {
1041
+ if (!matchPreparedTokenInSortedList(token, allTokens, opts)) {
1042
+ allPositiveMatch = false;
1043
+ break;
1044
+ }
952
1045
  }
1046
+ if (!allPositiveMatch)
1047
+ continue;
953
1048
  }
954
- if (!allPositiveMatch)
955
- continue;
956
- let anyMatchInPrimary = false;
1049
+ let anyMatchInPrimary = allPositiveMatchInPrimary;
957
1050
  let anyMatchInLinked = false;
958
1051
  const matchingLinkedIds = new Set;
959
1052
  const tokensMatchingPrimary = new Set;
960
- for (const preparedToken of positiveTokens) {
961
- if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
962
- anyMatchInPrimary = true;
963
- tokensMatchingPrimary.add(preparedToken);
1053
+ if (!allPositiveMatchInPrimary) {
1054
+ for (const preparedToken of positiveTokens) {
1055
+ if (matchPreparedTokenInSortedList(preparedToken, primaryTokens, opts)) {
1056
+ anyMatchInPrimary = true;
1057
+ tokensMatchingPrimary.add(preparedToken);
1058
+ }
964
1059
  }
1060
+ } else {
1061
+ for (const t of positiveTokens)
1062
+ tokensMatchingPrimary.add(t);
965
1063
  }
966
1064
  const tokensForLinkedMatching = positiveTokens.filter((t) => !tokensMatchingPrimary.has(t));
967
1065
  if (tokensForLinkedMatching.length > 0 || negatedTokens.length > 0) {
@@ -1046,6 +1144,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
1046
1144
  results[i] = scored[i].result;
1047
1145
  return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
1048
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
+ }
1049
1187
  function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
1050
1188
  return findInLinkedArraysNumeric(query, linked, limit, opts).results;
1051
1189
  }
@@ -1559,6 +1697,15 @@ function firstNEqual3(a, b) {
1559
1697
  return false;
1560
1698
  return tokensEqual3(a, b);
1561
1699
  }
1700
+ function withCollectionPhoneNormalizers(opts, specs) {
1701
+ return {
1702
+ ...opts,
1703
+ phoneQueryNormalizers: [
1704
+ ...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
1705
+ ...opts.phoneQueryNormalizers ?? []
1706
+ ]
1707
+ };
1708
+ }
1562
1709
 
1563
1710
  class SearchUniverse {
1564
1711
  collections = new Map;
@@ -1919,7 +2066,11 @@ class SearchUniverse {
1919
2066
  primaryData: primaryCollection.data,
1920
2067
  linkedData: collection.data
1921
2068
  };
1922
- 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);
1923
2074
  if (linkedSearchResults.results.length > 0) {
1924
2075
  const combinedResults = [];
1925
2076
  const hasNegation = linkedSearchResults.hasNegation;
@@ -1942,7 +2093,7 @@ class SearchUniverse {
1942
2093
  if (inCollections && !inCollections.includes(name)) {
1943
2094
  continue;
1944
2095
  }
1945
- const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
2096
+ const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
1946
2097
  if (matchingIds.length > 0) {
1947
2098
  const collectionResults = [];
1948
2099
  for (const id of matchingIds) {
@@ -1990,6 +2141,7 @@ export {
1990
2141
  scoreOne,
1991
2142
  scoreBag,
1992
2143
  sanitiseString,
2144
+ resolvePhoneQueryNormalizer,
1993
2145
  resetTokenRegistry,
1994
2146
  removePrimaryItemNumeric as removePrimaryItem,
1995
2147
  removeLinkedItemNumeric as removeLinkedItem,
@@ -2012,6 +2164,8 @@ export {
2012
2164
  findInLinkedArraysSimpleNumeric,
2013
2165
  findInLinkedArraysNumeric,
2014
2166
  encodeTokenWithMode,
2167
+ defaultQueryPhoneNormalizer,
2168
+ defaultPhoneNormalizer,
2015
2169
  decodeTokenId,
2016
2170
  decodeMatchMode,
2017
2171
  createTokensMap,
@@ -2028,5 +2182,6 @@ export {
2028
2182
  MATCH_MODE_PREFIXES,
2029
2183
  MATCH_MODE_BITS,
2030
2184
  DEFAULT_SEARCH_OPTS,
2185
+ DEFAULT_PHONE_FIELDS,
2031
2186
  AugmentedMetadata
2032
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.0",
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",