@stll/text-normalize 0.2.0 → 0.4.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 +47 -2
- package/dist/ascii-fold.js +4 -2
- package/dist/ascii-fold.js.map +1 -1
- package/dist/diacritics.d.ts +24 -8
- package/dist/diacritics.js +33 -11
- package/dist/diacritics.js.map +1 -1
- package/dist/search-match-key.d.ts +29 -0
- package/dist/search-match-key.js +44 -0
- package/dist/search-match-key.js.map +1 -0
- package/dist/search-query-matcher.d.ts +38 -0
- package/dist/search-query-matcher.js +61 -0
- package/dist/search-query-matcher.js.map +1 -0
- package/dist/slug.d.ts +6 -5
- package/dist/slug.js +20 -5
- package/dist/slug.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -61,8 +61,18 @@ generated with.
|
|
|
61
61
|
|
|
62
62
|
## Substring matching with highlight offsets
|
|
63
63
|
|
|
64
|
-
`
|
|
65
|
-
|
|
64
|
+
`createSearchQueryMatcher(query)` (from `@stll/text-normalize/search-query-matcher`) is the client-side answer to "does this
|
|
65
|
+
text match what the user typed": it trims and folds the query once and returns
|
|
66
|
+
a predicate over candidate texts (a blank query matches everything, a missing
|
|
67
|
+
candidate nothing). `filterBySearchQuery({ items, query, fields })` applies it
|
|
68
|
+
to a list, keeping the items any field of which matches. Use these for every
|
|
69
|
+
picker, list filter and typeahead, so a query typed without accents (`dvorak`,
|
|
70
|
+
`strasse`, `lodz`) finds the same items everywhere and in every UI locale. An
|
|
71
|
+
`Intl.Collator` with `sensitivity: "base"` is not an alternative: it compares
|
|
72
|
+
equal-length windows (`strasse` misses `Straße`), its verdict depends on the
|
|
73
|
+
runtime locale, and it keeps Arabic tashkeel significant.
|
|
74
|
+
|
|
75
|
+
`foldSearchMatchText` is the match key both sides are folded with: NFKD, the `unaccent()`-parity ASCII folds, mark strip, Arabic
|
|
66
76
|
folds, lowercase — `capek` matches `Čapek` and `wroclaw` matches
|
|
67
77
|
`Wrocław`, both ways. `findSearchMatchRanges` finds a folded query in
|
|
68
78
|
folded content and returns ranges into the **original** string, so
|
|
@@ -70,6 +80,35 @@ highlights wrap the text the user sees even where folding changes string
|
|
|
70
80
|
length; it accepts content pre-folded with `foldSearchMatchTextWithOffsets`
|
|
71
81
|
so repeated queries against the same text fold it once.
|
|
72
82
|
|
|
83
|
+
Import the literal helpers from `@stll/text-normalize/search-match-key`:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
containsSearchMatchKey,
|
|
88
|
+
findSearchMatchKeyRanges,
|
|
89
|
+
} from "@stll/text-normalize/search-match-key";
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`containsSearchMatchKey(content, query)` checks a literal substring in keys
|
|
93
|
+
already prepared by the caller, such as fixed tokens in a message or keys a
|
|
94
|
+
caller folded itself. It does not trim or fold either input; a user-typed query
|
|
95
|
+
goes through `createSearchQueryMatcher` instead. Empty queries match every key. For accent-insensitive highlight ranges into original
|
|
96
|
+
text, use `findSearchMatchRanges` from `@stll/text-normalize` instead.
|
|
97
|
+
|
|
98
|
+
`findSearchMatchKeyRanges(content, query, { start?, end?, maxMatches? })`
|
|
99
|
+
returns non-overlapping literal matches as UTF-16 ranges into `content`.
|
|
100
|
+
It applies no trimming, case conversion, or diacritic folding. Empty queries return no ranges; this differs from
|
|
101
|
+
`containsSearchMatchKey`, where an empty query passes a list filter.
|
|
102
|
+
|
|
103
|
+
`start` is inclusive and `end` exclusive; both default to the content's
|
|
104
|
+
bounds, are truncated to integers, and clamped to `[0, content.length]`.
|
|
105
|
+
Only complete matches inside that window are returned. `maxMatches` is
|
|
106
|
+
floored; zero or negative limits, reversed windows, and NaN options return
|
|
107
|
+
no ranges. Infinite bounds clamp to the content and an infinite limit
|
|
108
|
+
returns all matches. Use `{ maxMatches: 1 }` for the first occurrence of a
|
|
109
|
+
scope, then pass its `start` and `end` to search within that scope. Existing
|
|
110
|
+
callers retain their own casing or folding before calling either helper.
|
|
111
|
+
|
|
73
112
|
## Spaced letters
|
|
74
113
|
|
|
75
114
|
`collapseSpacedLetters` joins letter-spaced court headings
|
|
@@ -79,3 +118,9 @@ lists (`a b c`) are left intact. `spacedLetterRunRegex()` returns the
|
|
|
79
118
|
underlying matcher for callers that need per-match offsets (find-in-page
|
|
80
119
|
highlighting). Index-time and query-time collapse share this one
|
|
81
120
|
threshold so a heading collapses identically on both sides.
|
|
121
|
+
|
|
122
|
+
Both match-key helpers replace lone surrogates with U+FFFD on both sides via
|
|
123
|
+
`toWellFormed()` before matching Unicode scalars. A lone surrogate can match
|
|
124
|
+
U+FFFD or another lone surrogate, and cannot match half of an astral character.
|
|
125
|
+
Offsets remain UTF-16 offsets into the well-formed content; replacement does not
|
|
126
|
+
change its length. Caller-owned case, accent, and whitespace policies remain.
|
package/dist/ascii-fold.js
CHANGED
|
@@ -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
|
-
|
|
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("")}]
|
|
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
|
package/dist/ascii-fold.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/diacritics.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
12
|
+
* The variants differ in decomposition form and in what they remove:
|
|
13
13
|
*
|
|
14
|
-
* - `stripDiacritics` uses NFD (canonical decomposition)
|
|
15
|
-
* search keys, where compatibility
|
|
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
|
|
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
|
|
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
|
/**
|
package/dist/diacritics.js
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
13
|
+
* The variants differ in decomposition form and in what they remove:
|
|
13
14
|
*
|
|
14
|
-
* - `stripDiacritics` uses NFD (canonical decomposition)
|
|
15
|
-
* search keys, where compatibility
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
55
|
+
export const stripDiacriticsForSlug = (text) => text.normalize("NFKD").replace(SLUG_DIACRITIC_RE, "");
|
|
34
56
|
//# sourceMappingURL=diacritics.js.map
|
package/dist/diacritics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diacritics.js","sourceRoot":"","sources":["../src/diacritics.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client-side match for user-typed queries, kept out of the package root
|
|
3
|
+
* so the corpus parsers that read the root do not depend on it.
|
|
4
|
+
*/
|
|
5
|
+
/** Whether one candidate text contains the query a matcher was built for. */
|
|
6
|
+
export type SearchQueryMatcher = (candidate: string | null | undefined) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The client-side answer to "does this text match what the user typed".
|
|
9
|
+
*
|
|
10
|
+
* Both sides go through `foldSearchMatchText`, so case, accents, letters
|
|
11
|
+
* without a decomposition (`ł`, `ø`, `ß` → `ss`), compatibility ligatures and
|
|
12
|
+
* Arabic orthographic variants are ignored identically in every UI locale,
|
|
13
|
+
* and a filtered list agrees with the highlights `findSearchMatchRanges`
|
|
14
|
+
* draws in it. The query is trimmed and folded once and candidate keys are
|
|
15
|
+
* cached; a blank query matches every candidate, and a missing candidate
|
|
16
|
+
* matches none. Lone surrogates become U+FFFD on both sides, so a query can
|
|
17
|
+
* never match half of an astral character.
|
|
18
|
+
*
|
|
19
|
+
* Preferred over an `Intl.Collator` with `sensitivity: "base"`: a collator
|
|
20
|
+
* compares equal-length windows, so `strasse` never finds `Straße`; its
|
|
21
|
+
* verdict depends on the runtime locale (Turkish `ISTANBUL` misses
|
|
22
|
+
* `istanbul`, German `Müller` misses `muller`); it keeps Arabic tashkeel
|
|
23
|
+
* significant; and it compares every window of every item per keystroke,
|
|
24
|
+
* where a fold key is computed once per text.
|
|
25
|
+
*/
|
|
26
|
+
export declare const createSearchQueryMatcher: (query: string) => SearchQueryMatcher;
|
|
27
|
+
type FilterBySearchQueryOptions<T> = {
|
|
28
|
+
/** The texts an item is found by; it matches when any of them does. */
|
|
29
|
+
fields: (item: T) => readonly (string | null | undefined)[];
|
|
30
|
+
items: readonly T[];
|
|
31
|
+
query: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The items any of whose fields matches the query, in their given order,
|
|
35
|
+
* through `createSearchQueryMatcher`. A blank query keeps every item.
|
|
36
|
+
*/
|
|
37
|
+
export declare const filterBySearchQuery: <T>({ fields, items, query, }: FilterBySearchQueryOptions<T>) => T[];
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client-side match for user-typed queries, kept out of the package root
|
|
3
|
+
* so the corpus parsers that read the root do not depend on it.
|
|
4
|
+
*/
|
|
5
|
+
import { foldSearchMatchText } from "./search-match.js";
|
|
6
|
+
// A list filter folds the same names on every keystroke. Remembering their
|
|
7
|
+
// keys turns a keystroke over 2,000 three-field rows from about 3 ms into
|
|
8
|
+
// about 0.2 ms, level with a bare `toLowerCase().includes()`. The fold is
|
|
9
|
+
// pure, so the cache is unobservable; clearing it whole when full bounds its
|
|
10
|
+
// memory without an eviction policy.
|
|
11
|
+
const CANDIDATE_KEY_CACHE_LIMIT = 10_000;
|
|
12
|
+
const candidateKeys = new Map();
|
|
13
|
+
const candidateKey = (candidate) => {
|
|
14
|
+
const cached = candidateKeys.get(candidate);
|
|
15
|
+
if (cached !== undefined) {
|
|
16
|
+
return cached;
|
|
17
|
+
}
|
|
18
|
+
if (candidateKeys.size >= CANDIDATE_KEY_CACHE_LIMIT) {
|
|
19
|
+
candidateKeys.clear();
|
|
20
|
+
}
|
|
21
|
+
const key = foldSearchMatchText(candidate.toWellFormed());
|
|
22
|
+
candidateKeys.set(candidate, key);
|
|
23
|
+
return key;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The client-side answer to "does this text match what the user typed".
|
|
27
|
+
*
|
|
28
|
+
* Both sides go through `foldSearchMatchText`, so case, accents, letters
|
|
29
|
+
* without a decomposition (`ł`, `ø`, `ß` → `ss`), compatibility ligatures and
|
|
30
|
+
* Arabic orthographic variants are ignored identically in every UI locale,
|
|
31
|
+
* and a filtered list agrees with the highlights `findSearchMatchRanges`
|
|
32
|
+
* draws in it. The query is trimmed and folded once and candidate keys are
|
|
33
|
+
* cached; a blank query matches every candidate, and a missing candidate
|
|
34
|
+
* matches none. Lone surrogates become U+FFFD on both sides, so a query can
|
|
35
|
+
* never match half of an astral character.
|
|
36
|
+
*
|
|
37
|
+
* Preferred over an `Intl.Collator` with `sensitivity: "base"`: a collator
|
|
38
|
+
* compares equal-length windows, so `strasse` never finds `Straße`; its
|
|
39
|
+
* verdict depends on the runtime locale (Turkish `ISTANBUL` misses
|
|
40
|
+
* `istanbul`, German `Müller` misses `muller`); it keeps Arabic tashkeel
|
|
41
|
+
* significant; and it compares every window of every item per keystroke,
|
|
42
|
+
* where a fold key is computed once per text.
|
|
43
|
+
*/
|
|
44
|
+
export const createSearchQueryMatcher = (query) => {
|
|
45
|
+
const foldedQuery = foldSearchMatchText(query.trim().toWellFormed());
|
|
46
|
+
if (foldedQuery === "") {
|
|
47
|
+
return () => true;
|
|
48
|
+
}
|
|
49
|
+
return (candidate) => candidate !== null &&
|
|
50
|
+
candidate !== undefined &&
|
|
51
|
+
candidateKey(candidate).includes(foldedQuery);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The items any of whose fields matches the query, in their given order,
|
|
55
|
+
* through `createSearchQueryMatcher`. A blank query keeps every item.
|
|
56
|
+
*/
|
|
57
|
+
export const filterBySearchQuery = ({ fields, items, query, }) => {
|
|
58
|
+
const matches = createSearchQueryMatcher(query);
|
|
59
|
+
return items.filter((item) => fields(item).some(matches));
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=search-query-matcher.js.map
|
|
@@ -0,0 +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"}
|
package/dist/slug.d.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* characters into one separator, clip, trim a trailing separator, fall back
|
|
8
8
|
* when nothing is left — was identical in all four and is fixed here.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The `ascii` charset transliterates Latin letters before filtering:
|
|
11
|
+
* `slugify("Łódź")` is `"lodz"`, not `"odz"`. A key minted before
|
|
12
|
+
* transliteration is not recomputed by its owner; a caller that re-derives a
|
|
13
|
+
* stored key at lookup (the case-law routes) recognises the earlier fold
|
|
14
|
+
* itself.
|
|
14
15
|
*
|
|
15
16
|
* Single-pass by construction: a regex pipeline over untrusted label text is
|
|
16
17
|
* what the slow-regex guard exists to prevent.
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
* Which characters survive.
|
|
20
21
|
*
|
|
21
22
|
* - `ascii` keeps `[a-z0-9]`, for keys that end up in URLs and columns
|
|
22
|
-
* validated against that charset.
|
|
23
|
+
* validated against that charset. Latin letters are transliterated first.
|
|
23
24
|
* - `unicode` keeps any Unicode letter or number, for paths shown back to the
|
|
24
25
|
* person who typed them.
|
|
25
26
|
*/
|
package/dist/slug.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// parser-output-unchanged: no case-law or legislation adapter or parser calls slugify; adapters reach this module only through the package index.
|
|
1
2
|
/**
|
|
2
3
|
* One slug generator for the key-shaped strings the apps derive from a label.
|
|
3
4
|
*
|
|
@@ -7,15 +8,28 @@
|
|
|
7
8
|
* characters into one separator, clip, trim a trailing separator, fall back
|
|
8
9
|
* when nothing is left — was identical in all four and is fixed here.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The `ascii` charset transliterates Latin letters before filtering:
|
|
12
|
+
* `slugify("Łódź")` is `"lodz"`, not `"odz"`. A key minted before
|
|
13
|
+
* transliteration is not recomputed by its owner; a caller that re-derives a
|
|
14
|
+
* stored key at lookup (the case-law routes) recognises the earlier fold
|
|
15
|
+
* itself.
|
|
14
16
|
*
|
|
15
17
|
* Single-pass by construction: a regex pipeline over untrusted label text is
|
|
16
18
|
* what the slow-regex guard exists to prevent.
|
|
17
19
|
*/
|
|
20
|
+
import { applyAsciiFolds } from "./ascii-fold.js";
|
|
18
21
|
const UNICODE_SLUG_CHARACTER = /[\p{L}\p{N}]/u;
|
|
22
|
+
/** Compatibility decomposition, the search fold's mark strip and the table. */
|
|
23
|
+
const spellInAscii = (text) => applyAsciiFolds(text.normalize("NFKD"));
|
|
24
|
+
/**
|
|
25
|
+
* Latin letters spelled in ASCII: compatibility decomposition (`fi`, `A`), the
|
|
26
|
+
* search fold's mark strip (`č`), and the `unaccent()` table for letters no
|
|
27
|
+
* decomposition reaches (`ł`, `ø`, `ß`, `þ`, `ı`). The table is not closed
|
|
28
|
+
* under case (`Ɩ` folds and its small letter `ɩ` does not; `ɡ` folds and its
|
|
29
|
+
* capital `Ɡ` does not), so it applies on both sides of the lowercase. Other
|
|
30
|
+
* scripts pass through and are filtered out.
|
|
31
|
+
*/
|
|
32
|
+
const transliterateToAscii = (value) => spellInAscii(spellInAscii(value).toLowerCase()).toLowerCase();
|
|
19
33
|
const isSlugCharacter = (character, charset) => {
|
|
20
34
|
if (charset === "unicode") {
|
|
21
35
|
return UNICODE_SLUG_CHARACTER.test(character);
|
|
@@ -24,9 +38,10 @@ const isSlugCharacter = (character, charset) => {
|
|
|
24
38
|
(character >= "0" && character <= "9"));
|
|
25
39
|
};
|
|
26
40
|
export const slugify = (value, { charset, separator, maxLength, fallback }) => {
|
|
41
|
+
const source = charset === "ascii" ? transliterateToAscii(value) : value.toLowerCase();
|
|
27
42
|
let buffer = "";
|
|
28
43
|
let lastWasSeparator = true;
|
|
29
|
-
for (const character of
|
|
44
|
+
for (const character of source) {
|
|
30
45
|
if (isSlugCharacter(character, charset)) {
|
|
31
46
|
buffer += character;
|
|
32
47
|
lastWasSeparator = false;
|
package/dist/slug.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../src/slug.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../src/slug.ts"],"names":[],"mappings":"AAAA,kJAAkJ;AAClJ;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAgClD,MAAM,sBAAsB,GAAG,eAAe,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAC5C,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAE1C;;;;;;;GAOG;AACH,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAU,EAAE,CACrD,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAEhE,MAAM,eAAe,GAAG,CAAC,SAAiB,EAAE,OAAoB,EAAW,EAAE;IAC3E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,CACL,CAAC,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC;QACtC,CAAC,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,CACvC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,KAAa,EACb,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAkB,EACnD,EAAE;IACV,MAAM,MAAM,GACV,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;IAC1E,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,gBAAgB,GAAG,IAAI,CAAC;IAE5B,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC;YACpB,gBAAgB,GAAG,KAAK,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,SAAS,CAAC;YACpB,gBAAgB,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAClD,MAAM;QACR,CAAC;QACD,OAAO,IAAI,SAAS,CAAC;IACvB,CAAC;IACD,0EAA0E;IAC1E,OAAO,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,OAAO,IAAI,QAAQ,CAAC;AAC7B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/text-normalize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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,14 @@
|
|
|
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"
|
|
39
|
+
},
|
|
40
|
+
"./search-query-matcher": {
|
|
41
|
+
"types": "./dist/search-query-matcher.d.ts",
|
|
42
|
+
"import": "./dist/search-query-matcher.js"
|
|
35
43
|
}
|
|
36
44
|
},
|
|
37
45
|
"publishConfig": {
|
|
@@ -46,13 +54,12 @@
|
|
|
46
54
|
"typecheck": "bun check --no-pretty --all --project=tsconfig.json",
|
|
47
55
|
"lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware --type-check packages/text-normalize",
|
|
48
56
|
"lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/text-normalize",
|
|
49
|
-
"format": "bun ../../scripts/run-oxfmt.ts .",
|
|
50
57
|
"prepack": "bun run build"
|
|
51
58
|
},
|
|
52
59
|
"devDependencies": {
|
|
53
60
|
"@stll/property-testing": "0.0.0",
|
|
54
61
|
"@stll/typescript-config": "0.0.0",
|
|
55
|
-
"bun-types": "1.4.
|
|
62
|
+
"bun-types": "1.4.4",
|
|
56
63
|
"fast-check": "^4.10.2"
|
|
57
64
|
}
|
|
58
65
|
}
|