@stll/text-normalize 0.4.0 → 0.5.1
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/README.md +9 -0
- package/dist/postgres-search-fold.d.ts +34 -0
- package/dist/postgres-search-fold.js +98 -0
- package/dist/postgres-search-fold.js.map +1 -0
- package/dist/search-match.d.ts +12 -17
- package/dist/search-match.js +30 -20
- package/dist/search-match.js.map +1 -1
- package/dist/search-query-matcher.js +4 -0
- package/dist/search-query-matcher.js.map +1 -1
- package/dist/unaccent-rules.d.ts +26 -0
- package/dist/unaccent-rules.js +2689 -0
- package/dist/unaccent-rules.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -53,6 +53,15 @@ one); a plain mark strip leaves `ł` standing and under-matches the
|
|
|
53
53
|
lexemes the index actually stored. Non-Latin scripts are out of contract
|
|
54
54
|
and pass through with their marks stripped.
|
|
55
55
|
|
|
56
|
+
`foldLikePostgresSearch` (`@stll/text-normalize/postgres-search-fold`) is PostgreSQL's `lower(unaccent(...))` itself,
|
|
57
|
+
code point for code point and every script included, from the
|
|
58
|
+
extension's full rule set (`POSTGRES_UNACCENT_RULES`). Use it where a
|
|
59
|
+
client must agree with a server-side `unaccent` match exactly, such as a
|
|
60
|
+
highlighter marking what the server found:
|
|
61
|
+
`findPostgresSearchFoldRanges` returns those marks as ranges into the
|
|
62
|
+
original text. It is narrower than the client search fold below: it keeps
|
|
63
|
+
marks and letters the extension keeps.
|
|
64
|
+
|
|
56
65
|
`stripDiacriticsForSlug` is the same strip but decomposes with NFKD, so
|
|
57
66
|
compatibility characters (ligatures, full-width forms, superscripts) fold
|
|
58
67
|
to their base before a `[a-z0-9]` slug filter runs. Slugs are persisted,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of PostgreSQL's `search_fold(text)`: `lower(unaccent(...))`
|
|
3
|
+
* with the extension's default dictionary, reproduced code point for code
|
|
4
|
+
* point so a highlighter marks exactly what a server-side fold matched.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately narrower than `foldSearchMatchText`, which also decomposes,
|
|
7
|
+
* strips every combining mark and folds Arabic and final sigma: marking a run
|
|
8
|
+
* the server did not match would claim a match that never happened. Here a
|
|
9
|
+
* character the extension has no rule for keeps its marks, `ς` stays `ς`, and
|
|
10
|
+
* precomposed and decomposed spellings agree only because the extension maps
|
|
11
|
+
* the precomposed letter and deletes the combining mark.
|
|
12
|
+
*
|
|
13
|
+
* Both halves are per code point: `unaccent` replaces single code points (its
|
|
14
|
+
* rules have no multi-character sources), and `lower()` under a libc or
|
|
15
|
+
* builtin collation maps each code point on its own. The parity test in
|
|
16
|
+
* `apps/api/src/lib/search/postgres-search-fold-parity.db.test.ts` holds this
|
|
17
|
+
* against a live database for every assigned code point.
|
|
18
|
+
*/
|
|
19
|
+
import type { FoldedSearchText, SearchMatchRange } from "./search-match.js";
|
|
20
|
+
/** `search_fold(value)` as PostgreSQL computes it. */
|
|
21
|
+
export declare const foldLikePostgresSearch: (value: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* `foldLikePostgresSearch` with each folded UTF-16 unit mapped back to the
|
|
24
|
+
* code point it came from. A code point the fold deletes (a combining mark)
|
|
25
|
+
* joins the range of the one before it, so a match that ends on `e` in
|
|
26
|
+
* `é` covers the accent the reader sees on it.
|
|
27
|
+
*/
|
|
28
|
+
export declare const foldLikePostgresSearchWithOffsets: (content: string) => FoldedSearchText;
|
|
29
|
+
/**
|
|
30
|
+
* Non-overlapping occurrences of `term` in `content` under the PostgreSQL
|
|
31
|
+
* search fold, as ranges into the original `content`. The term is folded as
|
|
32
|
+
* given, not trimmed.
|
|
33
|
+
*/
|
|
34
|
+
export declare const findPostgresSearchFoldRanges: (content: string, term: string) => SearchMatchRange[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of PostgreSQL's `search_fold(text)`: `lower(unaccent(...))`
|
|
3
|
+
* with the extension's default dictionary, reproduced code point for code
|
|
4
|
+
* point so a highlighter marks exactly what a server-side fold matched.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately narrower than `foldSearchMatchText`, which also decomposes,
|
|
7
|
+
* strips every combining mark and folds Arabic and final sigma: marking a run
|
|
8
|
+
* the server did not match would claim a match that never happened. Here a
|
|
9
|
+
* character the extension has no rule for keeps its marks, `ς` stays `ς`, and
|
|
10
|
+
* precomposed and decomposed spellings agree only because the extension maps
|
|
11
|
+
* the precomposed letter and deletes the combining mark.
|
|
12
|
+
*
|
|
13
|
+
* Both halves are per code point: `unaccent` replaces single code points (its
|
|
14
|
+
* rules have no multi-character sources), and `lower()` under a libc or
|
|
15
|
+
* builtin collation maps each code point on its own. The parity test in
|
|
16
|
+
* `apps/api/src/lib/search/postgres-search-fold-parity.db.test.ts` holds this
|
|
17
|
+
* against a live database for every assigned code point.
|
|
18
|
+
*/
|
|
19
|
+
import { findFoldedRanges } from "./search-match.js";
|
|
20
|
+
import { POSTGRES_UNACCENT_RULES } from "./unaccent-rules.js";
|
|
21
|
+
/**
|
|
22
|
+
* `toLowerCase` applies Unicode's full mapping, while `lower()` maps one code
|
|
23
|
+
* point to one (the simple mapping). The two differ only where the full
|
|
24
|
+
* mapping expands, and the one unconditional expansion (U+0130 to `i` plus
|
|
25
|
+
* U+0307) keeps the simple mapping as its first code point. Lowering one code
|
|
26
|
+
* point at a time also keeps JavaScript's contextual final sigma out, which
|
|
27
|
+
* `lower()` never applies.
|
|
28
|
+
*/
|
|
29
|
+
const lowerCodePoint = (character) => {
|
|
30
|
+
// Destructuring a string iterates code points, so this is the first one.
|
|
31
|
+
const [simple = character] = character.toLowerCase();
|
|
32
|
+
return simple;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* One code point's fold. Lone surrogates cannot reach the database (a UTF-8
|
|
36
|
+
* database refuses them), so they fold as the replacement character the way
|
|
37
|
+
* `toWellFormed` spells them.
|
|
38
|
+
*/
|
|
39
|
+
const foldCodePoint = (character) => {
|
|
40
|
+
const wellFormed = character.toWellFormed();
|
|
41
|
+
const unaccented = POSTGRES_UNACCENT_RULES[wellFormed] ?? wellFormed;
|
|
42
|
+
let lowered = "";
|
|
43
|
+
for (const codePoint of unaccented) {
|
|
44
|
+
lowered += lowerCodePoint(codePoint);
|
|
45
|
+
}
|
|
46
|
+
return lowered;
|
|
47
|
+
};
|
|
48
|
+
/** `search_fold(value)` as PostgreSQL computes it. */
|
|
49
|
+
export const foldLikePostgresSearch = (value) => {
|
|
50
|
+
let folded = "";
|
|
51
|
+
for (const character of value) {
|
|
52
|
+
folded += foldCodePoint(character);
|
|
53
|
+
}
|
|
54
|
+
return folded;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* `foldLikePostgresSearch` with each folded UTF-16 unit mapped back to the
|
|
58
|
+
* code point it came from. A code point the fold deletes (a combining mark)
|
|
59
|
+
* joins the range of the one before it, so a match that ends on `e` in
|
|
60
|
+
* `é` covers the accent the reader sees on it.
|
|
61
|
+
*/
|
|
62
|
+
export const foldLikePostgresSearchWithOffsets = (content) => {
|
|
63
|
+
const parts = [];
|
|
64
|
+
const originalRanges = [];
|
|
65
|
+
let previous;
|
|
66
|
+
let offset = 0;
|
|
67
|
+
for (const character of content) {
|
|
68
|
+
const range = { start: offset, end: offset + character.length };
|
|
69
|
+
offset = range.end;
|
|
70
|
+
const folded = foldCodePoint(character);
|
|
71
|
+
if (folded === "") {
|
|
72
|
+
if (previous !== undefined) {
|
|
73
|
+
previous.end = range.end;
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
parts.push(folded);
|
|
78
|
+
// One entry per UTF-16 unit, not per code point: matches index units.
|
|
79
|
+
let unit = 0;
|
|
80
|
+
while (unit < folded.length) {
|
|
81
|
+
originalRanges.push(range);
|
|
82
|
+
unit += 1;
|
|
83
|
+
}
|
|
84
|
+
previous = range;
|
|
85
|
+
}
|
|
86
|
+
return { originalRanges, text: parts.join("") };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Non-overlapping occurrences of `term` in `content` under the PostgreSQL
|
|
90
|
+
* search fold, as ranges into the original `content`. The term is folded as
|
|
91
|
+
* given, not trimmed.
|
|
92
|
+
*/
|
|
93
|
+
export const findPostgresSearchFoldRanges = (content, term) => findFoldedRanges({
|
|
94
|
+
content: foldLikePostgresSearchWithOffsets(content),
|
|
95
|
+
maxMatches: Number.MAX_SAFE_INTEGER,
|
|
96
|
+
query: foldLikePostgresSearch(term),
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=postgres-search-fold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-search-fold.js","sourceRoot":"","sources":["../src/postgres-search-fold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAU,EAAE;IACnD,yEAAyE;IACzE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IACrD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,aAAa,GAAG,CAAC,SAAiB,EAAU,EAAE;IAClD,MAAM,UAAU,GAAG,SAAS,CAAC,YAAY,EAAE,CAAC;IAC5C,MAAM,UAAU,GAAG,uBAAuB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;IACrE,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,OAAO,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAa,EAAU,EAAE;IAC9D,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAC/C,OAAe,EACG,EAAE;IACpB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,cAAc,GAAuB,EAAE,CAAC;IAC9C,IAAI,QAAsC,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;QAChE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC;QACnB,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAClB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,QAAQ,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;YAC3B,CAAC;YACD,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,sEAAsE;QACtE,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC5B,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;QACD,QAAQ,GAAG,KAAK,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,OAAe,EACf,IAAY,EACQ,EAAE,CACtB,gBAAgB,CAAC;IACf,OAAO,EAAE,iCAAiC,CAAC,OAAO,CAAC;IACnD,UAAU,EAAE,MAAM,CAAC,gBAAgB;IACnC,KAAK,EAAE,sBAAsB,CAAC,IAAI,CAAC;CACpC,CAAC,CAAC"}
|
package/dist/search-match.d.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Diacritic-insensitive substring matching with original-index mapping.
|
|
3
|
-
*
|
|
4
|
-
* `foldSearchMatchText` builds the client-side match key: NFKD
|
|
5
|
-
* decomposition, the `unaccent()`-parity ASCII folds (letters with no
|
|
6
|
-
* decomposition: `ł`, `ø`, `đ`, `ß`), combining-mark strip, Arabic
|
|
7
|
-
* orthographic folds, then lowercase — so a `capek` query matches `Čapek`
|
|
8
|
-
* and `wroclaw` matches `Wrocław`, both ways. It is deliberately distinct
|
|
9
|
-
* from `arabicNormalize` (normalize.ts), which is pinned to the SQL
|
|
10
|
-
* `arabic_normalize()` contract and must not strip Latin diacritics.
|
|
11
|
-
*
|
|
12
|
-
* `findSearchMatchRanges` locates a folded query inside folded content and
|
|
13
|
-
* reports matches as ranges into the ORIGINAL string, so highlights wrap the
|
|
14
|
-
* text the user actually sees. Folding changes string length (per-character
|
|
15
|
-
* decomposition, mark removal, contextual lowercasing), so the folded form
|
|
16
|
-
* carries a per-code-unit map back to the originating character's range.
|
|
17
|
-
*/
|
|
18
1
|
export type SearchMatchRange = {
|
|
19
2
|
end: number;
|
|
20
3
|
start: number;
|
|
@@ -26,6 +9,18 @@ export type FoldedSearchText = {
|
|
|
26
9
|
};
|
|
27
10
|
export declare const foldSearchMatchText: (value: string) => string;
|
|
28
11
|
export declare const foldSearchMatchTextWithOffsets: (content: string) => FoldedSearchText;
|
|
12
|
+
type FindFoldedRangesOptions = {
|
|
13
|
+
content: FoldedSearchText;
|
|
14
|
+
maxMatches: number;
|
|
15
|
+
/** Already folded by the same fold as `content`. */
|
|
16
|
+
query: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Non-overlapping occurrences of a folded query in folded content, as ranges
|
|
20
|
+
* into the original string. Shared by every fold that carries
|
|
21
|
+
* `originalRanges`, so they agree on what "non-overlapping" means.
|
|
22
|
+
*/
|
|
23
|
+
export declare const findFoldedRanges: ({ content, maxMatches, query, }: FindFoldedRangesOptions) => SearchMatchRange[];
|
|
29
24
|
type FindSearchMatchRangesOptions = {
|
|
30
25
|
maxMatches?: number;
|
|
31
26
|
};
|
package/dist/search-match.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// parser-output-unchanged: `findSearchMatchRanges` hands its scan to `findFoldedRanges` with the same folded content, query and budget; the ranges it returns are unchanged.
|
|
1
2
|
/**
|
|
2
3
|
* Diacritic-insensitive substring matching with original-index mapping.
|
|
3
4
|
*
|
|
@@ -71,32 +72,24 @@ export const foldSearchMatchTextWithOffsets = (content) => {
|
|
|
71
72
|
return { text: loweredParts.join(""), originalRanges };
|
|
72
73
|
};
|
|
73
74
|
/**
|
|
74
|
-
* Non-overlapping occurrences of
|
|
75
|
-
* into the original string.
|
|
76
|
-
*
|
|
75
|
+
* Non-overlapping occurrences of a folded query in folded content, as ranges
|
|
76
|
+
* into the original string. Shared by every fold that carries
|
|
77
|
+
* `originalRanges`, so they agree on what "non-overlapping" means.
|
|
77
78
|
*/
|
|
78
|
-
export const
|
|
79
|
-
|
|
80
|
-
if (maxMatches === 0) {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
const foldedContent = typeof content === "string"
|
|
84
|
-
? foldSearchMatchTextWithOffsets(content)
|
|
85
|
-
: content;
|
|
86
|
-
const foldedQuery = foldSearchMatchText(query.trim());
|
|
87
|
-
if (foldedQuery.length === 0) {
|
|
79
|
+
export const findFoldedRanges = ({ content, maxMatches, query, }) => {
|
|
80
|
+
if (maxMatches <= 0 || query.length === 0) {
|
|
88
81
|
return [];
|
|
89
82
|
}
|
|
90
83
|
const matches = [];
|
|
91
84
|
let searchFrom = 0;
|
|
92
|
-
while (searchFrom <=
|
|
93
|
-
const foldedStart =
|
|
85
|
+
while (searchFrom <= content.text.length - query.length) {
|
|
86
|
+
const foldedStart = content.text.indexOf(query, searchFrom);
|
|
94
87
|
if (foldedStart === -1) {
|
|
95
88
|
break;
|
|
96
89
|
}
|
|
97
|
-
const foldedEnd = foldedStart +
|
|
98
|
-
const firstRange =
|
|
99
|
-
const lastRange =
|
|
90
|
+
const foldedEnd = foldedStart + query.length;
|
|
91
|
+
const firstRange = content.originalRanges.at(foldedStart);
|
|
92
|
+
const lastRange = content.originalRanges.at(foldedEnd - 1);
|
|
100
93
|
if (!firstRange || !lastRange) {
|
|
101
94
|
break;
|
|
102
95
|
}
|
|
@@ -108,11 +101,28 @@ export const findSearchMatchRanges = (content, query, options = {}) => {
|
|
|
108
101
|
// character; resume after the last unit inside the matched characters so
|
|
109
102
|
// that character is reported once and the budget reaches later hits.
|
|
110
103
|
searchFrom = foldedEnd;
|
|
111
|
-
while ((
|
|
112
|
-
lastRange.end) {
|
|
104
|
+
while ((content.originalRanges.at(searchFrom)?.start ?? Infinity) < lastRange.end) {
|
|
113
105
|
searchFrom += 1;
|
|
114
106
|
}
|
|
115
107
|
}
|
|
116
108
|
return matches;
|
|
117
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* Non-overlapping occurrences of the folded query in the content, as ranges
|
|
112
|
+
* into the original string. Accepts pre-folded content so callers matching
|
|
113
|
+
* many queries against the same text fold it once.
|
|
114
|
+
*/
|
|
115
|
+
export const findSearchMatchRanges = (content, query, options = {}) => {
|
|
116
|
+
const maxMatches = Math.max(0, Math.floor(options.maxMatches ?? Number.MAX_SAFE_INTEGER));
|
|
117
|
+
if (maxMatches === 0) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
return findFoldedRanges({
|
|
121
|
+
content: typeof content === "string"
|
|
122
|
+
? foldSearchMatchTextWithOffsets(content)
|
|
123
|
+
: content,
|
|
124
|
+
maxMatches,
|
|
125
|
+
query: foldSearchMatchText(query.trim()),
|
|
126
|
+
});
|
|
127
|
+
};
|
|
118
128
|
//# sourceMappingURL=search-match.js.map
|
package/dist/search-match.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-match.js","sourceRoot":"","sources":["../src/search-match.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"search-match.js","sourceRoot":"","sources":["../src/search-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,6 +56,10 @@ export const createSearchQueryMatcher = (query) => {
|
|
|
56
56
|
*/
|
|
57
57
|
export const filterBySearchQuery = ({ fields, items, query, }) => {
|
|
58
58
|
const matches = createSearchQueryMatcher(query);
|
|
59
|
+
// A query that matches empty text also keeps items with no search fields.
|
|
60
|
+
if (matches("")) {
|
|
61
|
+
return [...items];
|
|
62
|
+
}
|
|
59
63
|
return items.filter((item) => fields(item).some(matches));
|
|
60
64
|
};
|
|
61
65
|
//# sourceMappingURL=search-query-matcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-query-matcher.js","sourceRoot":"","sources":["../src/search-query-matcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,6EAA6E;AAC7E,qCAAqC;AACrC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AACzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEhD,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAU,EAAE;IACjD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,aAAa,CAAC,IAAI,IAAI,yBAAyB,EAAE,CAAC;QACpD,aAAa,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,GAAG,GAAG,mBAAmB,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAOF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAAa,EAAsB,EAAE;IAC5E,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC;IACrE,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,SAAS,EAAE,EAAE,CACnB,SAAS,KAAK,IAAI;QAClB,SAAS,KAAK,SAAS;QACvB,YAAY,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC,CAAC;AASF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAI,EACrC,MAAM,EACN,KAAK,EACL,KAAK,GACyB,EAAO,EAAE;IACvC,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"search-query-matcher.js","sourceRoot":"","sources":["../src/search-query-matcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,6EAA6E;AAC7E,qCAAqC;AACrC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AACzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEhD,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAU,EAAE;IACjD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,aAAa,CAAC,IAAI,IAAI,yBAAyB,EAAE,CAAC;QACpD,aAAa,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,GAAG,GAAG,mBAAmB,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAOF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAAa,EAAsB,EAAE;IAC5E,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC;IACrE,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,SAAS,EAAE,EAAE,CACnB,SAAS,KAAK,IAAI;QAClB,SAAS,KAAK,SAAS;QACvB,YAAY,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC,CAAC;AASF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAI,EACrC,MAAM,EACN,KAAK,EACL,KAAK,GACyB,EAAO,EAAE;IACvC,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAChD,0EAA0E;IAC1E,IAAI,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every rule PostgreSQL's `unaccent` extension applies with its default
|
|
3
|
+
* `unaccent` dictionary: each key is one code point, each value what
|
|
4
|
+
* `unaccent()` replaces it with (empty for the combining and enclosing marks
|
|
5
|
+
* it deletes). A code point without an entry passes through unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Generated from the extension itself, so the two sides cannot drift by hand:
|
|
8
|
+
*
|
|
9
|
+
* ```sql
|
|
10
|
+
* SELECT cp, unaccent(chr(cp))
|
|
11
|
+
* FROM generate_series(1, 196607) AS cp
|
|
12
|
+
* WHERE NOT (cp BETWEEN 55296 AND 57343)
|
|
13
|
+
* AND unaccent(chr(cp)) IS DISTINCT FROM chr(cp);
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `apps/api/src/lib/search/postgres-search-fold-parity.db.test.ts` re-runs
|
|
17
|
+
* the query against a live PostgreSQL and asserts both directions, so an
|
|
18
|
+
* extension upgrade that adds, drops or retargets a rule fails CI instead of
|
|
19
|
+
* splitting a client-side fold from the server's.
|
|
20
|
+
*
|
|
21
|
+
* Unlike `ASCII_FOLD_TABLE`, which holds only the Latin rules NFD cannot
|
|
22
|
+
* express, this is the whole rule set, every script included: a caller that
|
|
23
|
+
* must agree with `unaccent()` character for character (a highlighter
|
|
24
|
+
* marking what a server-side match found) cannot approximate any of it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const POSTGRES_UNACCENT_RULES: Readonly<Record<string, string>>;
|