@stll/text-normalize 0.2.0 → 0.3.0

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 CHANGED
@@ -70,6 +70,35 @@ highlights wrap the text the user sees even where folding changes string
70
70
  length; it accepts content pre-folded with `foldSearchMatchTextWithOffsets`
71
71
  so repeated queries against the same text fold it once.
72
72
 
73
+ Import the literal helpers from `@stll/text-normalize/search-match-key`:
74
+
75
+ ```ts
76
+ import {
77
+ containsSearchMatchKey,
78
+ findSearchMatchKeyRanges,
79
+ } from "@stll/text-normalize/search-match-key";
80
+ ```
81
+
82
+ `containsSearchMatchKey(content, query)` checks a literal substring in keys
83
+ already prepared by the caller. It does not trim or fold either input, so a
84
+ picker can retain locale-specific casing or accent-sensitive matching. Empty
85
+ queries match every key. For accent-insensitive highlight ranges into original
86
+ text, use `findSearchMatchRanges` from `@stll/text-normalize` instead.
87
+
88
+ `findSearchMatchKeyRanges(content, query, { start?, end?, maxMatches? })`
89
+ returns non-overlapping literal matches as UTF-16 ranges into `content`.
90
+ It applies no trimming, case conversion, or diacritic folding. Empty queries return no ranges; this differs from
91
+ `containsSearchMatchKey`, where an empty query passes a list filter.
92
+
93
+ `start` is inclusive and `end` exclusive; both default to the content's
94
+ bounds, are truncated to integers, and clamped to `[0, content.length]`.
95
+ Only complete matches inside that window are returned. `maxMatches` is
96
+ floored; zero or negative limits, reversed windows, and NaN options return
97
+ no ranges. Infinite bounds clamp to the content and an infinite limit
98
+ returns all matches. Use `{ maxMatches: 1 }` for the first occurrence of a
99
+ scope, then pass its `start` and `end` to search within that scope. Existing
100
+ callers retain their own casing or folding before calling either helper.
101
+
73
102
  ## Spaced letters
74
103
 
75
104
  `collapseSpacedLetters` joins letter-spaced court headings
@@ -79,3 +108,9 @@ lists (`a b c`) are left intact. `spacedLetterRunRegex()` returns the
79
108
  underlying matcher for callers that need per-match offsets (find-in-page
80
109
  highlighting). Index-time and query-time collapse share this one
81
110
  threshold so a heading collapses identically on both sides.
111
+
112
+ Both match-key helpers replace lone surrogates with U+FFFD on both sides via
113
+ `toWellFormed()` before matching Unicode scalars. A lone surrogate can match
114
+ U+FFFD or another lone surrogate, and cannot match half of an astral character.
115
+ Offsets remain UTF-16 offsets into the well-formed content; replacement does not
116
+ change its length. Caller-owned case, accent, and whitespace policies remain.
@@ -1,3 +1,4 @@
1
+ // parser-output-unchanged: scripts/parser-output-diff.ts reports every case-law adapter identical over its recorded fixtures with this change; search folds keep non-Latin spacing marks while slug and judge-key folds stay pinned.
1
2
  /**
2
3
  * Latin-ASCII folding for search keys whose counterpart is a PostgreSQL
3
4
  * `unaccent()` index or a Tantivy `ascii_folding` index.
@@ -10,12 +11,13 @@
10
11
  * `unaccent` applies that decomposition cannot express.
11
12
  */
12
13
  import { ASCII_FOLD_TABLE } from "./ascii-fold-table.js";
13
- // The table is matched before `\p{Diacritic}` so modifier letters that Unicode
14
+ import { SEARCH_DIACRITIC_SOURCE } from "./diacritics.js";
15
+ // The table is matched before the diacritic strip so modifier letters that Unicode
14
16
  // classifies as diacritics (`ʰ`, `ᴬ`) fold to their base letter the way
15
17
  // `unaccent` folds them, instead of being deleted by the mark strip. Every key
16
18
  // is a single code point outside the character-class metacharacter set, so the
17
19
  // class needs no escaping.
18
- const ASCII_FOLD_RE = new RegExp(`[${Object.keys(ASCII_FOLD_TABLE).join("")}]|\\p{Diacritic}`, "gu");
20
+ const ASCII_FOLD_RE = new RegExp(`[${Object.keys(ASCII_FOLD_TABLE).join("")}]|${SEARCH_DIACRITIC_SOURCE}`, "gu");
19
21
  /**
20
22
  * The replace step of `foldToAscii`, for callers that decompose themselves
21
23
  * (`foldSearchMatchText` decomposes with NFKD and per character). The input
@@ -1 +1 @@
1
- {"version":3,"file":"ascii-fold.js","sourceRoot":"","sources":["../src/ascii-fold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,+EAA+E;AAC/E,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,2BAA2B;AAC3B,MAAM,aAAa,GAAG,IAAI,MAAM,CAC9B,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,EAC5D,IAAI,CACL,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAU,EAAE,CAC5D,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAE5E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE,CAClD,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"ascii-fold.js","sourceRoot":"","sources":["../src/ascii-fold.ts"],"names":[],"mappings":"AAAA,qOAAqO;AACrO;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,mFAAmF;AACnF,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,2BAA2B;AAC3B,MAAM,aAAa,GAAG,IAAI,MAAM,CAC9B,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,uBAAuB,EAAE,EACxE,IAAI,CACL,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAU,EAAE,CAC5D,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAE5E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE,CAClD,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC"}
@@ -1,27 +1,43 @@
1
1
  /**
2
2
  * Latin/combining-mark diacritic stripping for search keys and slugs.
3
3
  *
4
- * Both variants decompose the input and then remove every character
5
- * Unicode classifies as a diacritic (`\p{Diacritic}`). `\p{Diacritic}`
6
- * is deliberate: it covers combining marks in every block, not only the
4
+ * Both variants decompose the input and then remove characters Unicode
5
+ * classifies as diacritics (`\p{Diacritic}`). `\p{Diacritic}` is
6
+ * deliberate: it covers combining marks in every block, not only the
7
7
  * Combining Diacritical Marks block (U+0300–U+036F). A range like
8
8
  * `[̀-ͯ]` misses marks in the Extended (U+1AB0–U+1AFF) and
9
9
  * Supplement (U+1DC0–U+1DFF) blocks, so two callers using different
10
10
  * classes would disagree on the same text.
11
11
  *
12
- * The variants differ only in the decomposition form:
12
+ * The variants differ in decomposition form and in what they remove:
13
13
  *
14
- * - `stripDiacritics` uses NFD (canonical decomposition). Use it for
15
- * search keys, where compatibility characters must survive unchanged.
14
+ * - `stripDiacritics` uses NFD (canonical decomposition) and removes
15
+ * `SEARCH_DIACRITIC_SOURCE`. Use it for search keys, where compatibility
16
+ * characters and non-Latin spacing marks (`ー`, `·`) survive unchanged.
16
17
  * - `stripDiacriticsForSlug` uses NFKD (compatibility decomposition), so
17
18
  * ligatures, full-width forms, superscripts, and similar fold to their
18
19
  * ASCII base before the `[a-z0-9]` slug filter runs. Slugs are
19
20
  * persisted, public URL segments, so this variant pins the exact form
20
- * the existing slugs were generated with; do not switch it to NFD.
21
+ * the existing slugs were generated with, spacing diacritics removed
22
+ * too; do not switch it to NFD or narrow its class.
21
23
  */
24
+ /**
25
+ * The diacritics a search fold removes: combining marks, plus spacing ones in
26
+ * Latin script (modifier letters such as `ʰ`).
27
+ *
28
+ * `\p{Diacritic}` alone also covers spacing characters in other scripts that
29
+ * are text, not marks on a letter: the Japanese prolonged sound mark
30
+ * (`コーヒー`), the middle dot (`l·l`), `^`, and `` ` ``. Removing those turns
31
+ * `コーヒー` into `コヒ`, a different word, and PostgreSQL `unaccent()` keeps
32
+ * them, so a query folded that way misses the indexed text. Latin script keeps
33
+ * the full class because `foldToAscii` is pinned to `unaccent()` parity there.
34
+ * Shared as a source string so `foldToAscii` strips the identical class.
35
+ */
36
+ export declare const SEARCH_DIACRITIC_SOURCE = "(?=\\p{M}|\\p{Script=Latin})\\p{Diacritic}";
22
37
  /**
23
38
  * Strip combining diacritics from search text (NFD). Non-diacritic
24
- * characters, including compatibility characters, pass through unchanged.
39
+ * characters, including compatibility characters and spacing marks such as
40
+ * `ー`, pass through unchanged.
25
41
  */
26
42
  export declare const stripDiacritics: (text: string) => string;
27
43
  /**
@@ -1,34 +1,56 @@
1
+ // parser-output-unchanged: scripts/parser-output-diff.ts reports every case-law adapter identical over its recorded fixtures with this change; search folds keep non-Latin spacing marks while slug and judge-key folds stay pinned.
1
2
  /**
2
3
  * Latin/combining-mark diacritic stripping for search keys and slugs.
3
4
  *
4
- * Both variants decompose the input and then remove every character
5
- * Unicode classifies as a diacritic (`\p{Diacritic}`). `\p{Diacritic}`
6
- * is deliberate: it covers combining marks in every block, not only the
5
+ * Both variants decompose the input and then remove characters Unicode
6
+ * classifies as diacritics (`\p{Diacritic}`). `\p{Diacritic}` is
7
+ * deliberate: it covers combining marks in every block, not only the
7
8
  * Combining Diacritical Marks block (U+0300–U+036F). A range like
8
9
  * `[̀-ͯ]` misses marks in the Extended (U+1AB0–U+1AFF) and
9
10
  * Supplement (U+1DC0–U+1DFF) blocks, so two callers using different
10
11
  * classes would disagree on the same text.
11
12
  *
12
- * The variants differ only in the decomposition form:
13
+ * The variants differ in decomposition form and in what they remove:
13
14
  *
14
- * - `stripDiacritics` uses NFD (canonical decomposition). Use it for
15
- * search keys, where compatibility characters must survive unchanged.
15
+ * - `stripDiacritics` uses NFD (canonical decomposition) and removes
16
+ * `SEARCH_DIACRITIC_SOURCE`. Use it for search keys, where compatibility
17
+ * characters and non-Latin spacing marks (`ー`, `·`) survive unchanged.
16
18
  * - `stripDiacriticsForSlug` uses NFKD (compatibility decomposition), so
17
19
  * ligatures, full-width forms, superscripts, and similar fold to their
18
20
  * ASCII base before the `[a-z0-9]` slug filter runs. Slugs are
19
21
  * persisted, public URL segments, so this variant pins the exact form
20
- * the existing slugs were generated with; do not switch it to NFD.
22
+ * the existing slugs were generated with, spacing diacritics removed
23
+ * too; do not switch it to NFD or narrow its class.
21
24
  */
22
- const DIACRITIC_RE = /\p{Diacritic}/gu;
25
+ /**
26
+ * The diacritics a search fold removes: combining marks, plus spacing ones in
27
+ * Latin script (modifier letters such as `ʰ`).
28
+ *
29
+ * `\p{Diacritic}` alone also covers spacing characters in other scripts that
30
+ * are text, not marks on a letter: the Japanese prolonged sound mark
31
+ * (`コーヒー`), the middle dot (`l·l`), `^`, and `` ` ``. Removing those turns
32
+ * `コーヒー` into `コヒ`, a different word, and PostgreSQL `unaccent()` keeps
33
+ * them, so a query folded that way misses the indexed text. Latin script keeps
34
+ * the full class because `foldToAscii` is pinned to `unaccent()` parity there.
35
+ * Shared as a source string so `foldToAscii` strips the identical class.
36
+ */
37
+ export const SEARCH_DIACRITIC_SOURCE = "(?=\\p{M}|\\p{Script=Latin})\\p{Diacritic}";
38
+ const SEARCH_DIACRITIC_RE = new RegExp(SEARCH_DIACRITIC_SOURCE, "gu");
39
+ /**
40
+ * Pinned to the class the persisted slugs were minted with, spacing
41
+ * characters included; see `stripDiacriticsForSlug`.
42
+ */
43
+ const SLUG_DIACRITIC_RE = /\p{Diacritic}/gu;
23
44
  /**
24
45
  * Strip combining diacritics from search text (NFD). Non-diacritic
25
- * characters, including compatibility characters, pass through unchanged.
46
+ * characters, including compatibility characters and spacing marks such as
47
+ * `ー`, pass through unchanged.
26
48
  */
27
- export const stripDiacritics = (text) => text.normalize("NFD").replace(DIACRITIC_RE, "");
49
+ export const stripDiacritics = (text) => text.normalize("NFD").replace(SEARCH_DIACRITIC_RE, "");
28
50
  /**
29
51
  * Strip combining diacritics for slug generation (NFKD). Callers apply
30
52
  * their own case folding and `[a-z0-9]` filtering around this; the NFKD
31
53
  * form is load-bearing for byte-stable slugs and must not change.
32
54
  */
33
- export const stripDiacriticsForSlug = (text) => text.normalize("NFKD").replace(DIACRITIC_RE, "");
55
+ export const stripDiacriticsForSlug = (text) => text.normalize("NFKD").replace(SLUG_DIACRITIC_RE, "");
34
56
  //# sourceMappingURL=diacritics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diacritics.js","sourceRoot":"","sources":["../src/diacritics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAU,EAAE,CACtD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAU,EAAE,CAC7D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"diacritics.js","sourceRoot":"","sources":["../src/diacritics.ts"],"names":[],"mappings":"AAAA,qOAAqO;AACrO;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,4CAA4C,CAAC;AAE/C,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC;AAEtE;;;GAGG;AACH,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAU,EAAE,CACtD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAU,EAAE,CAC7D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /** UTF-16 range into the well-formed content searched by literal key helpers. */
2
+ type SearchMatchKeyRange = {
3
+ end: number;
4
+ start: number;
5
+ };
6
+ /**
7
+ * Literal substring search over caller-owned match keys. No trimming or
8
+ * folding is applied: callers whose policy differs from the highlighter's
9
+ * fold keep control over case, accents, whitespace, and field boundaries.
10
+ * Lone surrogates become U+FFFD in both inputs. Empty keys match every value.
11
+ */
12
+ export declare const containsSearchMatchKey: (content: string, query: string) => boolean;
13
+ type FindSearchMatchKeyRangesOptions = {
14
+ /** Inclusive UTF-16 offset; defaults to the start of the content. */
15
+ start?: number;
16
+ /** Exclusive UTF-16 offset; defaults to the end of the content. */
17
+ end?: number;
18
+ maxMatches?: number;
19
+ };
20
+ /**
21
+ * Non-overlapping literal occurrences in caller-owned keys, with UTF-16
22
+ * offsets into content after replacing lone surrogates with U+FFFD. Neither
23
+ * input is trimmed or folded. Well-formed literals match whole Unicode scalars.
24
+ * Only matches wholly inside [start, end) are returned. Bounds are truncated
25
+ * and clamped to content; maxMatches is floored. NaN options, empty queries,
26
+ * reversed windows, and nonpositive limits produce no matches.
27
+ */
28
+ export declare const findSearchMatchKeyRanges: (content: string, query: string, options?: FindSearchMatchKeyRangesOptions) => SearchMatchKeyRange[];
29
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Literal substring search over caller-owned match keys. No trimming or
3
+ * folding is applied: callers whose policy differs from the highlighter's
4
+ * fold keep control over case, accents, whitespace, and field boundaries.
5
+ * Lone surrogates become U+FFFD in both inputs. Empty keys match every value.
6
+ */
7
+ export const containsSearchMatchKey = (content, query) => content.toWellFormed().includes(query.toWellFormed());
8
+ /**
9
+ * Non-overlapping literal occurrences in caller-owned keys, with UTF-16
10
+ * offsets into content after replacing lone surrogates with U+FFFD. Neither
11
+ * input is trimmed or folded. Well-formed literals match whole Unicode scalars.
12
+ * Only matches wholly inside [start, end) are returned. Bounds are truncated
13
+ * and clamped to content; maxMatches is floored. NaN options, empty queries,
14
+ * reversed windows, and nonpositive limits produce no matches.
15
+ */
16
+ export const findSearchMatchKeyRanges = (content, query, options = {}) => {
17
+ const start = Math.min(content.length, Math.max(0, Math.trunc(options.start ?? 0)));
18
+ const end = Math.min(content.length, Math.max(0, Math.trunc(options.end ?? content.length)));
19
+ const maxMatches = Math.max(0, Math.floor(options.maxMatches ?? Number.MAX_SAFE_INTEGER));
20
+ if (query.length === 0 ||
21
+ !(start < end) ||
22
+ maxMatches === 0 ||
23
+ Number.isNaN(maxMatches)) {
24
+ return [];
25
+ }
26
+ const haystack = content.toWellFormed();
27
+ const needle = query.toWellFormed();
28
+ const matches = [];
29
+ let searchFrom = start;
30
+ while (searchFrom <= end - needle.length) {
31
+ const matchStart = haystack.indexOf(needle, searchFrom);
32
+ const matchEnd = matchStart + needle.length;
33
+ if (matchStart === -1 || matchEnd > end) {
34
+ break;
35
+ }
36
+ matches.push({ start: matchStart, end: matchEnd });
37
+ if (matches.length >= maxMatches) {
38
+ break;
39
+ }
40
+ searchFrom = matchEnd;
41
+ }
42
+ return matches;
43
+ };
44
+ //# sourceMappingURL=search-match-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-match-key.js","sourceRoot":"","sources":["../src/search-match-key.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,OAAe,EACf,KAAa,EACJ,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;AAUpE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,OAAe,EACf,KAAa,EACb,OAAO,GAAoC,EAAE,EACtB,EAAE;IACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,OAAO,CAAC,MAAM,EACd,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAC5C,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB,OAAO,CAAC,MAAM,EACd,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CACvD,CAAC;IACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CACzB,CAAC,EACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAC1D,CAAC;IACF,IACE,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC;QACd,UAAU,KAAK,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,EACxB,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IACpC,MAAM,OAAO,GAA0B,EAAE,CAAC;IAC1C,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,OAAO,UAAU,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5C,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;YACxC,MAAM;QACR,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;YACjC,MAAM;QACR,CAAC;QACD,UAAU,GAAG,QAAQ,CAAC;IACxB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/text-normalize",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Search match-key text normalization — Arabic orthographic variant folding (alef/hamza/teh-marbuta/yeh, tashkeel, tatweel, digits) with per-language fold tables",
5
5
  "keywords": [
6
6
  "arabic",
@@ -32,6 +32,10 @@
32
32
  ".": {
33
33
  "types": "./dist/index.d.ts",
34
34
  "import": "./dist/index.js"
35
+ },
36
+ "./search-match-key": {
37
+ "types": "./dist/search-match-key.d.ts",
38
+ "import": "./dist/search-match-key.js"
35
39
  }
36
40
  },
37
41
  "publishConfig": {