cry-search 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +195 -44
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +177 -36
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/compareTokenized.d.ts +24 -0
- package/dist/utils/compareTokenized.d.ts.map +1 -0
- 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,11 +82,14 @@ __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,
|
|
77
90
|
createSearchLinkedMetadata: () => createSearchLinkedMetadataNumeric,
|
|
78
91
|
createSearchArrayMetadata: () => createSearchArrayMetadataNumeric,
|
|
92
|
+
compareTokenized: () => compareTokenized,
|
|
79
93
|
clearTokensMap: () => clearTokensMap,
|
|
80
94
|
binarySearchUint32: () => binarySearchUint32,
|
|
81
95
|
arrayToSearchableData: () => arrayToSearchableData,
|
|
@@ -87,11 +101,13 @@ __export(exports_src, {
|
|
|
87
101
|
MATCH_MODE_PREFIXES: () => MATCH_MODE_PREFIXES,
|
|
88
102
|
MATCH_MODE_BITS: () => MATCH_MODE_BITS,
|
|
89
103
|
DEFAULT_SEARCH_OPTS: () => DEFAULT_SEARCH_OPTS,
|
|
104
|
+
DEFAULT_PHONE_FIELDS: () => DEFAULT_PHONE_FIELDS,
|
|
90
105
|
AugmentedMetadata: () => AugmentedMetadata
|
|
91
106
|
});
|
|
92
107
|
module.exports = __toCommonJS(exports_src);
|
|
93
108
|
|
|
94
109
|
// src/types/index.ts
|
|
110
|
+
var DEFAULT_PHONE_FIELDS = ["telefon", "phone", "gsm", "msisdn"];
|
|
95
111
|
var DEFAULT_FIRST_TOKENS_N = 8;
|
|
96
112
|
var DEFAULT_SEARCH_OPTS = {
|
|
97
113
|
matchWords: "start",
|
|
@@ -304,6 +320,53 @@ function prepareStringForSearch(input) {
|
|
|
304
320
|
const tokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
|
|
305
321
|
return tokens.sort();
|
|
306
322
|
}
|
|
323
|
+
// src/utils/compareTokenized.ts
|
|
324
|
+
function compareTokenized(a, b) {
|
|
325
|
+
const aTokens = new Set(prepareStringForSearch(a));
|
|
326
|
+
const bTokens = new Set(prepareStringForSearch(b));
|
|
327
|
+
if (aTokens.size === 0 && bTokens.size === 0)
|
|
328
|
+
return 1;
|
|
329
|
+
let intersection = 0;
|
|
330
|
+
for (const token of aTokens) {
|
|
331
|
+
if (bTokens.has(token))
|
|
332
|
+
intersection++;
|
|
333
|
+
}
|
|
334
|
+
const union = aTokens.size + bTokens.size - intersection;
|
|
335
|
+
if (union === 0)
|
|
336
|
+
return 0;
|
|
337
|
+
return intersection / union;
|
|
338
|
+
}
|
|
339
|
+
// src/utils/phoneNormalization.ts
|
|
340
|
+
var RE_ASCII_DIGITS = /^[0-9]+$/;
|
|
341
|
+
var RE_PHONE_LIKE_QUERY = /^[\d+()/\-. ]+$/;
|
|
342
|
+
var MIN_PHONE_DIGITS = 4;
|
|
343
|
+
var defaultPhoneNormalizer = (value) => {
|
|
344
|
+
const digits = value.replace(/\D/g, "");
|
|
345
|
+
return digits.length >= MIN_PHONE_DIGITS ? digits : undefined;
|
|
346
|
+
};
|
|
347
|
+
var defaultQueryPhoneNormalizer = (query) => {
|
|
348
|
+
if (!RE_PHONE_LIKE_QUERY.test(query))
|
|
349
|
+
return;
|
|
350
|
+
return defaultPhoneNormalizer(query);
|
|
351
|
+
};
|
|
352
|
+
function validateNormalizedPhone(normalized, normalizerName) {
|
|
353
|
+
if (normalized === undefined)
|
|
354
|
+
return;
|
|
355
|
+
if (typeof normalized !== "string" || !RE_ASCII_DIGITS.test(normalized)) {
|
|
356
|
+
throw new Error(`${normalizerName} must return an ASCII digit string or undefined`);
|
|
357
|
+
}
|
|
358
|
+
return normalized;
|
|
359
|
+
}
|
|
360
|
+
function resolvePhoneQueryNormalizer(spec) {
|
|
361
|
+
if (spec?.phoneQueryNormalizer)
|
|
362
|
+
return spec.phoneQueryNormalizer;
|
|
363
|
+
const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
|
|
364
|
+
return (query) => {
|
|
365
|
+
if (!RE_PHONE_LIKE_QUERY.test(query))
|
|
366
|
+
return;
|
|
367
|
+
return phoneNormalizer(query);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
307
370
|
// src/utils/tokenRegistry.ts
|
|
308
371
|
var INITIAL_CAPACITY = 10 * 1000;
|
|
309
372
|
var tokens = new Array(INITIAL_CAPACITY);
|
|
@@ -481,7 +544,7 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
|
|
|
481
544
|
const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
|
|
482
545
|
if (sorted === undefined)
|
|
483
546
|
return;
|
|
484
|
-
const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
|
|
547
|
+
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
548
|
return { sorted, firstN };
|
|
486
549
|
}
|
|
487
550
|
function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
|
|
@@ -494,7 +557,9 @@ function buildSortedFromFields(obj, spec, tokensMap) {
|
|
|
494
557
|
const allTokens = [];
|
|
495
558
|
const fieldMatchModes = spec?.fieldMatchModes;
|
|
496
559
|
const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
|
|
497
|
-
const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
|
|
560
|
+
const fieldsToExclude = new Set((spec?.dontSearchInFields ?? []).map(String));
|
|
561
|
+
const phoneFields = new Set(spec?.phoneFields ?? DEFAULT_PHONE_FIELDS);
|
|
562
|
+
const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
|
|
498
563
|
for (const key of fieldsToSearch) {
|
|
499
564
|
const keyStr = key;
|
|
500
565
|
if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
|
|
@@ -502,36 +567,50 @@ function buildSortedFromFields(obj, spec, tokensMap) {
|
|
|
502
567
|
if (fieldsToExclude.has(key))
|
|
503
568
|
continue;
|
|
504
569
|
const isDot = isDotNotation(keyStr);
|
|
505
|
-
const value = isDot ? getValueByPath(obj, keyStr) : obj[
|
|
570
|
+
const value = isDot ? getValueByPath(obj, keyStr) : obj[keyStr];
|
|
506
571
|
if (value == null)
|
|
507
572
|
continue;
|
|
508
573
|
const matchMode = fieldMatchModes?.[key];
|
|
509
574
|
const fieldStrings = [];
|
|
510
575
|
extractStringsFromValue(value, fieldStrings);
|
|
511
|
-
if (
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
576
|
+
if (phoneFields.has(keyStr)) {
|
|
577
|
+
const phoneMatchMode = spec?.phoneMatchMode ?? "anywhere";
|
|
578
|
+
for (const fieldString of fieldStrings) {
|
|
579
|
+
const normalized = validateNormalizedPhone(phoneNormalizer(fieldString), `Phone normalizer for field "${keyStr}"`);
|
|
580
|
+
if (normalized === undefined)
|
|
581
|
+
continue;
|
|
582
|
+
const tokenId = getOrCreateTokenId(normalized, tokensMap);
|
|
583
|
+
allTokens.push(encodeTokenWithMode(tokenId, phoneMatchMode));
|
|
584
|
+
}
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (fieldStrings.length === 0)
|
|
588
|
+
continue;
|
|
589
|
+
const fieldText = fieldStrings.join(" ");
|
|
590
|
+
const datesNormalized = normalizeDates(fieldText);
|
|
591
|
+
const preprocessed = preprocessString(datesNormalized);
|
|
592
|
+
const sanitised = sanitiseString(preprocessed);
|
|
593
|
+
const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
|
|
594
|
+
const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
|
|
595
|
+
for (const token of stringTokens) {
|
|
596
|
+
const tokenId = getOrCreateTokenId(token, tokensMap);
|
|
597
|
+
if (effectiveMode) {
|
|
598
|
+
allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
|
|
599
|
+
} else {
|
|
600
|
+
allTokens.push(tokenId);
|
|
525
601
|
}
|
|
526
602
|
}
|
|
527
603
|
}
|
|
528
604
|
const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
|
|
529
605
|
return new Uint32Array(uniqueTokens);
|
|
530
606
|
}
|
|
531
|
-
function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
|
|
607
|
+
function buildFirstNFromKeyFields(obj, keyFields, n, phoneFields, tokensMap) {
|
|
532
608
|
const strings = [];
|
|
609
|
+
const phoneFieldPaths = new Set(phoneFields);
|
|
533
610
|
for (const field of keyFields) {
|
|
534
611
|
const keyStr = field;
|
|
612
|
+
if (phoneFieldPaths.has(keyStr))
|
|
613
|
+
continue;
|
|
535
614
|
const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
|
|
536
615
|
if (value == null)
|
|
537
616
|
continue;
|
|
@@ -834,6 +913,23 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
|
|
|
834
913
|
return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
|
|
835
914
|
}
|
|
836
915
|
|
|
916
|
+
// src/common/numeric/prepareQueryVariants.ts
|
|
917
|
+
function prepareQueryVariants(query, opts) {
|
|
918
|
+
const rawTokenStrs = prepareStringForSearch(query);
|
|
919
|
+
const rawTokens = prepareQueryTokens(rawTokenStrs);
|
|
920
|
+
const normalizers = opts?.phoneQueryNormalizers ?? [defaultQueryPhoneNormalizer];
|
|
921
|
+
const seenPhones = new Set;
|
|
922
|
+
const phoneTokens = [];
|
|
923
|
+
for (const normalizer of normalizers) {
|
|
924
|
+
const normalized = validateNormalizedPhone(normalizer(query), "Phone query normalizer");
|
|
925
|
+
if (normalized === undefined || seenPhones.has(normalized))
|
|
926
|
+
continue;
|
|
927
|
+
seenPhones.add(normalized);
|
|
928
|
+
phoneTokens.push(prepareQueryTokens([normalized]));
|
|
929
|
+
}
|
|
930
|
+
return { rawTokenStrs, rawTokens, phoneTokens };
|
|
931
|
+
}
|
|
932
|
+
|
|
837
933
|
// src/common/numeric/scoring.ts
|
|
838
934
|
function orderedTokenize(input) {
|
|
839
935
|
return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
|
|
@@ -950,16 +1046,9 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
|
|
|
950
1046
|
return results;
|
|
951
1047
|
}
|
|
952
1048
|
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
|
-
}
|
|
1049
|
+
const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
|
|
1050
|
+
const phoneCandidates = findMatchingCandidates(metadata, phoneTokens, opts);
|
|
1051
|
+
const candidates = phoneCandidates.length > 0 ? phoneCandidates : findMatchingCandidates(metadata, rawTokens.length > 0 ? [rawTokens] : [], opts);
|
|
963
1052
|
if (candidates.length === 0)
|
|
964
1053
|
return [];
|
|
965
1054
|
const qTokens = orderedTokenize(query);
|
|
@@ -994,13 +1083,22 @@ function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
|
|
|
994
1083
|
out.push(scored[i].id);
|
|
995
1084
|
return out;
|
|
996
1085
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1086
|
+
function findMatchingCandidates(metadata, variants, opts) {
|
|
1087
|
+
if (variants.length === 0)
|
|
1088
|
+
return [];
|
|
1089
|
+
const candidates = [];
|
|
1090
|
+
for (const [id, targetTokens] of metadata.sorted) {
|
|
1091
|
+
for (const preparedTokens of variants) {
|
|
1092
|
+
if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
|
|
1093
|
+
candidates.push(id);
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1002
1097
|
}
|
|
1003
|
-
|
|
1098
|
+
return candidates;
|
|
1099
|
+
}
|
|
1100
|
+
// src/common/numeric/findInLinkedArrays.ts
|
|
1101
|
+
function findInLinkedArraysForPreparedTokens(query, queryTokenStrs, preparedTokens, linked, limit, opts) {
|
|
1004
1102
|
const positiveTokens = preparedTokens.filter((t) => !t.negated);
|
|
1005
1103
|
const negatedTokens = preparedTokens.filter((t) => t.negated);
|
|
1006
1104
|
const scored = [];
|
|
@@ -1170,6 +1268,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
|
|
|
1170
1268
|
results[i] = scored[i].result;
|
|
1171
1269
|
return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
|
|
1172
1270
|
}
|
|
1271
|
+
function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
|
|
1272
|
+
const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
|
|
1273
|
+
const phoneResults = [];
|
|
1274
|
+
for (const preparedTokens of phoneTokens) {
|
|
1275
|
+
const result = findInLinkedArraysForPreparedTokens(query, rawTokenStrs, preparedTokens, linked, Infinity, opts);
|
|
1276
|
+
phoneResults.push(...result.results);
|
|
1277
|
+
}
|
|
1278
|
+
if (phoneResults.length > 0) {
|
|
1279
|
+
return {
|
|
1280
|
+
queryTokens: rawTokenStrs,
|
|
1281
|
+
results: mergePhoneResults(phoneResults).slice(0, limit),
|
|
1282
|
+
hasNegation: false
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
if (rawTokens.length === 0) {
|
|
1286
|
+
return { queryTokens: rawTokenStrs, results: [], hasNegation: false };
|
|
1287
|
+
}
|
|
1288
|
+
return findInLinkedArraysForPreparedTokens(query, rawTokenStrs, rawTokens, linked, limit, opts);
|
|
1289
|
+
}
|
|
1290
|
+
function mergePhoneResults(results) {
|
|
1291
|
+
const byPrimaryId = new Map;
|
|
1292
|
+
for (const result of results) {
|
|
1293
|
+
const existing = byPrimaryId.get(result.primary._id);
|
|
1294
|
+
if (existing === undefined) {
|
|
1295
|
+
byPrimaryId.set(result.primary._id, result);
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
const linkedById = new Map(existing.linked.map((item) => [item._id, item]));
|
|
1299
|
+
for (const item of result.linked)
|
|
1300
|
+
linkedById.set(item._id, item);
|
|
1301
|
+
existing.linked = Array.from(linkedById.values());
|
|
1302
|
+
existing.matchedIn = mergeMatchLocation(existing.matchedIn, result.matchedIn);
|
|
1303
|
+
}
|
|
1304
|
+
return Array.from(byPrimaryId.values());
|
|
1305
|
+
}
|
|
1306
|
+
function mergeMatchLocation(first, second) {
|
|
1307
|
+
if (first === "both" || second === "both" || first !== second)
|
|
1308
|
+
return "both";
|
|
1309
|
+
return first;
|
|
1310
|
+
}
|
|
1173
1311
|
function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
|
|
1174
1312
|
return findInLinkedArraysNumeric(query, linked, limit, opts).results;
|
|
1175
1313
|
}
|
|
@@ -1683,6 +1821,15 @@ function firstNEqual3(a, b) {
|
|
|
1683
1821
|
return false;
|
|
1684
1822
|
return tokensEqual3(a, b);
|
|
1685
1823
|
}
|
|
1824
|
+
function withCollectionPhoneNormalizers(opts, specs) {
|
|
1825
|
+
return {
|
|
1826
|
+
...opts,
|
|
1827
|
+
phoneQueryNormalizers: [
|
|
1828
|
+
...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
|
|
1829
|
+
...opts.phoneQueryNormalizers ?? []
|
|
1830
|
+
]
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1686
1833
|
|
|
1687
1834
|
class SearchUniverse {
|
|
1688
1835
|
collections = new Map;
|
|
@@ -2043,7 +2190,11 @@ class SearchUniverse {
|
|
|
2043
2190
|
primaryData: primaryCollection.data,
|
|
2044
2191
|
linkedData: collection.data
|
|
2045
2192
|
};
|
|
2046
|
-
const
|
|
2193
|
+
const linkedSearchOpts = withCollectionPhoneNormalizers(searchOpts, [
|
|
2194
|
+
primaryCollection.config.spec,
|
|
2195
|
+
collection.config.spec
|
|
2196
|
+
]);
|
|
2197
|
+
const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, linkedSearchOpts);
|
|
2047
2198
|
if (linkedSearchResults.results.length > 0) {
|
|
2048
2199
|
const combinedResults = [];
|
|
2049
2200
|
const hasNegation = linkedSearchResults.hasNegation;
|
|
@@ -2066,7 +2217,7 @@ class SearchUniverse {
|
|
|
2066
2217
|
if (inCollections && !inCollections.includes(name)) {
|
|
2067
2218
|
continue;
|
|
2068
2219
|
}
|
|
2069
|
-
const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
|
|
2220
|
+
const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
|
|
2070
2221
|
if (matchingIds.length > 0) {
|
|
2071
2222
|
const collectionResults = [];
|
|
2072
2223
|
for (const id of matchingIds) {
|
package/dist/index.d.cts
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
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 { compareTokenized } from './utils/compareTokenized';
|
|
15
|
+
export { defaultPhoneNormalizer, defaultQueryPhoneNormalizer, resolvePhoneQueryNormalizer, } from './utils/phoneNormalization';
|
|
14
16
|
export { tokens, getTokenCount, getTokenById, findTokenId, registerToken, getOrCreateTokenId, createTokensMap, clearTokensMap, resetTokenRegistry, } from './utils/tokenRegistry';
|
|
15
17
|
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
18
|
export { createSearchLinkedMetadataNumeric as createSearchLinkedMetadata, type NumericLinkedSearchMetadataWithData, } from './common/numeric/createSearchLinkedMetadata';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
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 { compareTokenized } from './utils/compareTokenized';
|
|
15
|
+
export { defaultPhoneNormalizer, defaultQueryPhoneNormalizer, resolvePhoneQueryNormalizer, } from './utils/phoneNormalization';
|
|
14
16
|
export { tokens, getTokenCount, getTokenById, findTokenId, registerToken, getOrCreateTokenId, createTokensMap, clearTokensMap, resetTokenRegistry, } from './utils/tokenRegistry';
|
|
15
17
|
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
18
|
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,53 @@ function prepareStringForSearch(input) {
|
|
|
211
212
|
const tokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
|
|
212
213
|
return tokens.sort();
|
|
213
214
|
}
|
|
215
|
+
// src/utils/compareTokenized.ts
|
|
216
|
+
function compareTokenized(a, b) {
|
|
217
|
+
const aTokens = new Set(prepareStringForSearch(a));
|
|
218
|
+
const bTokens = new Set(prepareStringForSearch(b));
|
|
219
|
+
if (aTokens.size === 0 && bTokens.size === 0)
|
|
220
|
+
return 1;
|
|
221
|
+
let intersection = 0;
|
|
222
|
+
for (const token of aTokens) {
|
|
223
|
+
if (bTokens.has(token))
|
|
224
|
+
intersection++;
|
|
225
|
+
}
|
|
226
|
+
const union = aTokens.size + bTokens.size - intersection;
|
|
227
|
+
if (union === 0)
|
|
228
|
+
return 0;
|
|
229
|
+
return intersection / union;
|
|
230
|
+
}
|
|
231
|
+
// src/utils/phoneNormalization.ts
|
|
232
|
+
var RE_ASCII_DIGITS = /^[0-9]+$/;
|
|
233
|
+
var RE_PHONE_LIKE_QUERY = /^[\d+()/\-. ]+$/;
|
|
234
|
+
var MIN_PHONE_DIGITS = 4;
|
|
235
|
+
var defaultPhoneNormalizer = (value) => {
|
|
236
|
+
const digits = value.replace(/\D/g, "");
|
|
237
|
+
return digits.length >= MIN_PHONE_DIGITS ? digits : undefined;
|
|
238
|
+
};
|
|
239
|
+
var defaultQueryPhoneNormalizer = (query) => {
|
|
240
|
+
if (!RE_PHONE_LIKE_QUERY.test(query))
|
|
241
|
+
return;
|
|
242
|
+
return defaultPhoneNormalizer(query);
|
|
243
|
+
};
|
|
244
|
+
function validateNormalizedPhone(normalized, normalizerName) {
|
|
245
|
+
if (normalized === undefined)
|
|
246
|
+
return;
|
|
247
|
+
if (typeof normalized !== "string" || !RE_ASCII_DIGITS.test(normalized)) {
|
|
248
|
+
throw new Error(`${normalizerName} must return an ASCII digit string or undefined`);
|
|
249
|
+
}
|
|
250
|
+
return normalized;
|
|
251
|
+
}
|
|
252
|
+
function resolvePhoneQueryNormalizer(spec) {
|
|
253
|
+
if (spec?.phoneQueryNormalizer)
|
|
254
|
+
return spec.phoneQueryNormalizer;
|
|
255
|
+
const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
|
|
256
|
+
return (query) => {
|
|
257
|
+
if (!RE_PHONE_LIKE_QUERY.test(query))
|
|
258
|
+
return;
|
|
259
|
+
return phoneNormalizer(query);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
214
262
|
// src/utils/tokenRegistry.ts
|
|
215
263
|
var INITIAL_CAPACITY = 10 * 1000;
|
|
216
264
|
var tokens = new Array(INITIAL_CAPACITY);
|
|
@@ -388,7 +436,7 @@ function prepareObjectForSearchNumeric(obj, spec, tokensMap) {
|
|
|
388
436
|
const sorted = spec?.extractSearchableStringFn ? buildSortedFromExtractFn(obj, spec.extractSearchableStringFn, tokensMap) : buildSortedFromFields(obj, spec, tokensMap);
|
|
389
437
|
if (sorted === undefined)
|
|
390
438
|
return;
|
|
391
|
-
const firstN = spec?.keyFields && spec.keyFields.length > 0 ? buildFirstNFromKeyFields(obj, spec.keyFields, spec.firstTokensN ?? DEFAULT_FIRST_TOKENS_N, tokensMap) : undefined;
|
|
439
|
+
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
440
|
return { sorted, firstN };
|
|
393
441
|
}
|
|
394
442
|
function buildSortedFromExtractFn(obj, extractFn, tokensMap) {
|
|
@@ -401,7 +449,9 @@ function buildSortedFromFields(obj, spec, tokensMap) {
|
|
|
401
449
|
const allTokens = [];
|
|
402
450
|
const fieldMatchModes = spec?.fieldMatchModes;
|
|
403
451
|
const fieldsToSearch = spec?.searchInFields ?? Object.keys(obj);
|
|
404
|
-
const fieldsToExclude = new Set(spec?.dontSearchInFields ?? []);
|
|
452
|
+
const fieldsToExclude = new Set((spec?.dontSearchInFields ?? []).map(String));
|
|
453
|
+
const phoneFields = new Set(spec?.phoneFields ?? DEFAULT_PHONE_FIELDS);
|
|
454
|
+
const phoneNormalizer = spec?.phoneNormalizer ?? defaultPhoneNormalizer;
|
|
405
455
|
for (const key of fieldsToSearch) {
|
|
406
456
|
const keyStr = key;
|
|
407
457
|
if (!spec?.searchInFields && DEFAULT_EXCLUDED_FIELDS.has(keyStr))
|
|
@@ -409,36 +459,50 @@ function buildSortedFromFields(obj, spec, tokensMap) {
|
|
|
409
459
|
if (fieldsToExclude.has(key))
|
|
410
460
|
continue;
|
|
411
461
|
const isDot = isDotNotation(keyStr);
|
|
412
|
-
const value = isDot ? getValueByPath(obj, keyStr) : obj[
|
|
462
|
+
const value = isDot ? getValueByPath(obj, keyStr) : obj[keyStr];
|
|
413
463
|
if (value == null)
|
|
414
464
|
continue;
|
|
415
465
|
const matchMode = fieldMatchModes?.[key];
|
|
416
466
|
const fieldStrings = [];
|
|
417
467
|
extractStringsFromValue(value, fieldStrings);
|
|
418
|
-
if (
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
468
|
+
if (phoneFields.has(keyStr)) {
|
|
469
|
+
const phoneMatchMode = spec?.phoneMatchMode ?? "anywhere";
|
|
470
|
+
for (const fieldString of fieldStrings) {
|
|
471
|
+
const normalized = validateNormalizedPhone(phoneNormalizer(fieldString), `Phone normalizer for field "${keyStr}"`);
|
|
472
|
+
if (normalized === undefined)
|
|
473
|
+
continue;
|
|
474
|
+
const tokenId = getOrCreateTokenId(normalized, tokensMap);
|
|
475
|
+
allTokens.push(encodeTokenWithMode(tokenId, phoneMatchMode));
|
|
476
|
+
}
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (fieldStrings.length === 0)
|
|
480
|
+
continue;
|
|
481
|
+
const fieldText = fieldStrings.join(" ");
|
|
482
|
+
const datesNormalized = normalizeDates(fieldText);
|
|
483
|
+
const preprocessed = preprocessString(datesNormalized);
|
|
484
|
+
const sanitised = sanitiseString(preprocessed);
|
|
485
|
+
const stringTokens = tokenize(sanitised, DEFAULT_TOKENIZE_CONFIG);
|
|
486
|
+
const effectiveMode = matchMode ?? (keyStr === "_id" ? "whole" : undefined);
|
|
487
|
+
for (const token of stringTokens) {
|
|
488
|
+
const tokenId = getOrCreateTokenId(token, tokensMap);
|
|
489
|
+
if (effectiveMode) {
|
|
490
|
+
allTokens.push(encodeTokenWithMode(tokenId, effectiveMode));
|
|
491
|
+
} else {
|
|
492
|
+
allTokens.push(tokenId);
|
|
432
493
|
}
|
|
433
494
|
}
|
|
434
495
|
}
|
|
435
496
|
const uniqueTokens = [...new Set(allTokens)].sort((a, b) => a - b);
|
|
436
497
|
return new Uint32Array(uniqueTokens);
|
|
437
498
|
}
|
|
438
|
-
function buildFirstNFromKeyFields(obj, keyFields, n, tokensMap) {
|
|
499
|
+
function buildFirstNFromKeyFields(obj, keyFields, n, phoneFields, tokensMap) {
|
|
439
500
|
const strings = [];
|
|
501
|
+
const phoneFieldPaths = new Set(phoneFields);
|
|
440
502
|
for (const field of keyFields) {
|
|
441
503
|
const keyStr = field;
|
|
504
|
+
if (phoneFieldPaths.has(keyStr))
|
|
505
|
+
continue;
|
|
442
506
|
const value = isDotNotation(keyStr) ? getValueByPath(obj, keyStr) : obj[field];
|
|
443
507
|
if (value == null)
|
|
444
508
|
continue;
|
|
@@ -741,6 +805,23 @@ function allTokensMatchNumeric(searchTokenStrs, targetTokens, opts) {
|
|
|
741
805
|
return allPreparedTokensMatch(preparedTokens, targetTokens, opts);
|
|
742
806
|
}
|
|
743
807
|
|
|
808
|
+
// src/common/numeric/prepareQueryVariants.ts
|
|
809
|
+
function prepareQueryVariants(query, opts) {
|
|
810
|
+
const rawTokenStrs = prepareStringForSearch(query);
|
|
811
|
+
const rawTokens = prepareQueryTokens(rawTokenStrs);
|
|
812
|
+
const normalizers = opts?.phoneQueryNormalizers ?? [defaultQueryPhoneNormalizer];
|
|
813
|
+
const seenPhones = new Set;
|
|
814
|
+
const phoneTokens = [];
|
|
815
|
+
for (const normalizer of normalizers) {
|
|
816
|
+
const normalized = validateNormalizedPhone(normalizer(query), "Phone query normalizer");
|
|
817
|
+
if (normalized === undefined || seenPhones.has(normalized))
|
|
818
|
+
continue;
|
|
819
|
+
seenPhones.add(normalized);
|
|
820
|
+
phoneTokens.push(prepareQueryTokens([normalized]));
|
|
821
|
+
}
|
|
822
|
+
return { rawTokenStrs, rawTokens, phoneTokens };
|
|
823
|
+
}
|
|
824
|
+
|
|
744
825
|
// src/common/numeric/scoring.ts
|
|
745
826
|
function orderedTokenize(input) {
|
|
746
827
|
return tokenize(sanitiseString(preprocessString(normalizeDates(input))), DEFAULT_TOKENIZE_CONFIG);
|
|
@@ -857,16 +938,9 @@ function searchInDataReturnObjectsNumeric(query, metadata, data, opts) {
|
|
|
857
938
|
return results;
|
|
858
939
|
}
|
|
859
940
|
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
|
-
}
|
|
941
|
+
const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
|
|
942
|
+
const phoneCandidates = findMatchingCandidates(metadata, phoneTokens, opts);
|
|
943
|
+
const candidates = phoneCandidates.length > 0 ? phoneCandidates : findMatchingCandidates(metadata, rawTokens.length > 0 ? [rawTokens] : [], opts);
|
|
870
944
|
if (candidates.length === 0)
|
|
871
945
|
return [];
|
|
872
946
|
const qTokens = orderedTokenize(query);
|
|
@@ -901,13 +975,22 @@ function searchInDataReturnIdsWithLimitNumeric(query, metadata, limit, opts) {
|
|
|
901
975
|
out.push(scored[i].id);
|
|
902
976
|
return out;
|
|
903
977
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
978
|
+
function findMatchingCandidates(metadata, variants, opts) {
|
|
979
|
+
if (variants.length === 0)
|
|
980
|
+
return [];
|
|
981
|
+
const candidates = [];
|
|
982
|
+
for (const [id, targetTokens] of metadata.sorted) {
|
|
983
|
+
for (const preparedTokens of variants) {
|
|
984
|
+
if (allPreparedTokensMatch(preparedTokens, targetTokens, opts)) {
|
|
985
|
+
candidates.push(id);
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
909
989
|
}
|
|
910
|
-
|
|
990
|
+
return candidates;
|
|
991
|
+
}
|
|
992
|
+
// src/common/numeric/findInLinkedArrays.ts
|
|
993
|
+
function findInLinkedArraysForPreparedTokens(query, queryTokenStrs, preparedTokens, linked, limit, opts) {
|
|
911
994
|
const positiveTokens = preparedTokens.filter((t) => !t.negated);
|
|
912
995
|
const negatedTokens = preparedTokens.filter((t) => t.negated);
|
|
913
996
|
const scored = [];
|
|
@@ -1077,6 +1160,46 @@ function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
|
|
|
1077
1160
|
results[i] = scored[i].result;
|
|
1078
1161
|
return { queryTokens: queryTokenStrs, results, hasNegation: negatedTokens.length > 0 };
|
|
1079
1162
|
}
|
|
1163
|
+
function findInLinkedArraysNumeric(query, linked, limit = 10, opts) {
|
|
1164
|
+
const { rawTokenStrs, rawTokens, phoneTokens } = prepareQueryVariants(query, opts);
|
|
1165
|
+
const phoneResults = [];
|
|
1166
|
+
for (const preparedTokens of phoneTokens) {
|
|
1167
|
+
const result = findInLinkedArraysForPreparedTokens(query, rawTokenStrs, preparedTokens, linked, Infinity, opts);
|
|
1168
|
+
phoneResults.push(...result.results);
|
|
1169
|
+
}
|
|
1170
|
+
if (phoneResults.length > 0) {
|
|
1171
|
+
return {
|
|
1172
|
+
queryTokens: rawTokenStrs,
|
|
1173
|
+
results: mergePhoneResults(phoneResults).slice(0, limit),
|
|
1174
|
+
hasNegation: false
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
if (rawTokens.length === 0) {
|
|
1178
|
+
return { queryTokens: rawTokenStrs, results: [], hasNegation: false };
|
|
1179
|
+
}
|
|
1180
|
+
return findInLinkedArraysForPreparedTokens(query, rawTokenStrs, rawTokens, linked, limit, opts);
|
|
1181
|
+
}
|
|
1182
|
+
function mergePhoneResults(results) {
|
|
1183
|
+
const byPrimaryId = new Map;
|
|
1184
|
+
for (const result of results) {
|
|
1185
|
+
const existing = byPrimaryId.get(result.primary._id);
|
|
1186
|
+
if (existing === undefined) {
|
|
1187
|
+
byPrimaryId.set(result.primary._id, result);
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
const linkedById = new Map(existing.linked.map((item) => [item._id, item]));
|
|
1191
|
+
for (const item of result.linked)
|
|
1192
|
+
linkedById.set(item._id, item);
|
|
1193
|
+
existing.linked = Array.from(linkedById.values());
|
|
1194
|
+
existing.matchedIn = mergeMatchLocation(existing.matchedIn, result.matchedIn);
|
|
1195
|
+
}
|
|
1196
|
+
return Array.from(byPrimaryId.values());
|
|
1197
|
+
}
|
|
1198
|
+
function mergeMatchLocation(first, second) {
|
|
1199
|
+
if (first === "both" || second === "both" || first !== second)
|
|
1200
|
+
return "both";
|
|
1201
|
+
return first;
|
|
1202
|
+
}
|
|
1080
1203
|
function findInLinkedArraysSimpleNumeric(query, linked, limit = 10, opts) {
|
|
1081
1204
|
return findInLinkedArraysNumeric(query, linked, limit, opts).results;
|
|
1082
1205
|
}
|
|
@@ -1590,6 +1713,15 @@ function firstNEqual3(a, b) {
|
|
|
1590
1713
|
return false;
|
|
1591
1714
|
return tokensEqual3(a, b);
|
|
1592
1715
|
}
|
|
1716
|
+
function withCollectionPhoneNormalizers(opts, specs) {
|
|
1717
|
+
return {
|
|
1718
|
+
...opts,
|
|
1719
|
+
phoneQueryNormalizers: [
|
|
1720
|
+
...specs.map((spec) => resolvePhoneQueryNormalizer(spec)),
|
|
1721
|
+
...opts.phoneQueryNormalizers ?? []
|
|
1722
|
+
]
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1593
1725
|
|
|
1594
1726
|
class SearchUniverse {
|
|
1595
1727
|
collections = new Map;
|
|
@@ -1950,7 +2082,11 @@ class SearchUniverse {
|
|
|
1950
2082
|
primaryData: primaryCollection.data,
|
|
1951
2083
|
linkedData: collection.data
|
|
1952
2084
|
};
|
|
1953
|
-
const
|
|
2085
|
+
const linkedSearchOpts = withCollectionPhoneNormalizers(searchOpts, [
|
|
2086
|
+
primaryCollection.config.spec,
|
|
2087
|
+
collection.config.spec
|
|
2088
|
+
]);
|
|
2089
|
+
const linkedSearchResults = findInLinkedArraysNumeric(query, linkedMetadata, limit, linkedSearchOpts);
|
|
1954
2090
|
if (linkedSearchResults.results.length > 0) {
|
|
1955
2091
|
const combinedResults = [];
|
|
1956
2092
|
const hasNegation = linkedSearchResults.hasNegation;
|
|
@@ -1973,7 +2109,7 @@ class SearchUniverse {
|
|
|
1973
2109
|
if (inCollections && !inCollections.includes(name)) {
|
|
1974
2110
|
continue;
|
|
1975
2111
|
}
|
|
1976
|
-
const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, searchOpts);
|
|
2112
|
+
const matchingIds = searchInDataReturnIdsWithLimitNumeric(query, collection.metadata, limit, withCollectionPhoneNormalizers(searchOpts, [collection.config.spec]));
|
|
1977
2113
|
if (matchingIds.length > 0) {
|
|
1978
2114
|
const collectionResults = [];
|
|
1979
2115
|
for (const id of matchingIds) {
|
|
@@ -2021,6 +2157,7 @@ export {
|
|
|
2021
2157
|
scoreOne,
|
|
2022
2158
|
scoreBag,
|
|
2023
2159
|
sanitiseString,
|
|
2160
|
+
resolvePhoneQueryNormalizer,
|
|
2024
2161
|
resetTokenRegistry,
|
|
2025
2162
|
removePrimaryItemNumeric as removePrimaryItem,
|
|
2026
2163
|
removeLinkedItemNumeric as removeLinkedItem,
|
|
@@ -2043,11 +2180,14 @@ export {
|
|
|
2043
2180
|
findInLinkedArraysSimpleNumeric,
|
|
2044
2181
|
findInLinkedArraysNumeric,
|
|
2045
2182
|
encodeTokenWithMode,
|
|
2183
|
+
defaultQueryPhoneNormalizer,
|
|
2184
|
+
defaultPhoneNormalizer,
|
|
2046
2185
|
decodeTokenId,
|
|
2047
2186
|
decodeMatchMode,
|
|
2048
2187
|
createTokensMap,
|
|
2049
2188
|
createSearchLinkedMetadataNumeric as createSearchLinkedMetadata,
|
|
2050
2189
|
createSearchArrayMetadataNumeric as createSearchArrayMetadata,
|
|
2190
|
+
compareTokenized,
|
|
2051
2191
|
clearTokensMap,
|
|
2052
2192
|
binarySearchUint32,
|
|
2053
2193
|
arrayToSearchableData,
|
|
@@ -2059,5 +2199,6 @@ export {
|
|
|
2059
2199
|
MATCH_MODE_PREFIXES,
|
|
2060
2200
|
MATCH_MODE_BITS,
|
|
2061
2201
|
DEFAULT_SEARCH_OPTS,
|
|
2202
|
+
DEFAULT_PHONE_FIELDS,
|
|
2062
2203
|
AugmentedMetadata
|
|
2063
2204
|
};
|
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,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two strings by their tokenized forms and returns the Jaccard
|
|
3
|
+
* similarity of their token sets: a number between `0` (no tokens in common)
|
|
4
|
+
* and `1` (identical token sets). Order-independent and case/diacritics
|
|
5
|
+
* insensitive, because both sides go through the same search pipeline as the
|
|
6
|
+
* rest of the library (normalizeDates → preprocess → sanitise → tokenize with
|
|
7
|
+
* the default replacements/removals).
|
|
8
|
+
*
|
|
9
|
+
* Both empty strings are considered identical (`1`); an empty vs non-empty
|
|
10
|
+
* string yields `0`.
|
|
11
|
+
*
|
|
12
|
+
* @param a - First string to compare.
|
|
13
|
+
* @param b - Second string to compare.
|
|
14
|
+
* @returns Similarity ratio `0..1` (matching tokens / union of tokens).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* compareTokenized('Janez Novak', 'novak janez'); // 1
|
|
19
|
+
* compareTokenized('Janes Novak', 'novak'); // 0.5
|
|
20
|
+
* compareTokenized('Janez Novak', 'ana'); // 0
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function compareTokenized(a: string, b: string): number;
|
|
24
|
+
//# sourceMappingURL=compareTokenized.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareTokenized.d.ts","sourceRoot":"","sources":["../../src/utils/compareTokenized.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAc7D"}
|
|
@@ -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.3",
|
|
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",
|