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 +20 -0
- package/README.md +33 -0
- package/UNIVERSE.md +13 -0
- package/dist/common/SearchUniverse.d.ts.map +1 -1
- package/dist/common/numeric/createSearchArrayMetadata.d.ts.map +1 -1
- package/dist/common/numeric/findInLinkedArrays.d.ts +2 -24
- package/dist/common/numeric/findInLinkedArrays.d.ts.map +1 -1
- package/dist/common/numeric/prepareQueryVariants.d.ts +15 -0
- package/dist/common/numeric/prepareQueryVariants.d.ts.map +1 -0
- package/dist/common/numeric/searchInData.d.ts.map +1 -1
- package/dist/index.cjs +221 -56
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +203 -48
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/phoneNormalization.d.ts +26 -0
- package/dist/utils/phoneNormalization.d.ts.map +1 -0
- package/package.json +1 -1
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":"
|
|
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":"
|
|
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
|
|
4
|
+
* Searches linked metadata with canonical phone variants before raw query tokens.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
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":"
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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: (
|
|
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[
|
|
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 (
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
|
954
|
-
|
|
955
|
-
|
|
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
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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[
|
|
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 (
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
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
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
|
|
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
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
|
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
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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
|
|
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":"
|
|
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.
|
|
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",
|