disarm 0.0.0 → 0.11.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/index.d.ts ADDED
@@ -0,0 +1,261 @@
1
+ import { Lexicon, Pipeline } from './binding';
2
+ import type { Untranslatable, AutoLangInspection, LangMeta, ScriptMeta, Finding as NativeFinding, AnomalyReport as NativeAnomalyReport } from './binding';
3
+ export type { Untranslatable, AutoLangInspection, LangMeta, ScriptMeta };
4
+ /**
5
+ * A reusable, opaque lexicon handle (HAI-SDLC 6.1). `hasAnomalies` /
6
+ * `inspectAnomalies` rebuild an internal set from the caller's word array on
7
+ * every call; constructing a `Lexicon` once (`new Lexicon([...])`) and passing
8
+ * it instead builds that set a single time and reuses it across calls.
9
+ */
10
+ export { Lexicon };
11
+ /**
12
+ * A reusable, opaque named-policy-profile pipeline handle (#404). Build it once
13
+ * with {@link getPipeline} for a named profile, then apply it to any number of
14
+ * inputs via `.process(text)` — the profile's steps are validated and compiled
15
+ * a single time and reused across calls, rather than re-resolved each call.
16
+ *
17
+ * ```ts
18
+ * const pipe = getPipeline('search_index') // build once
19
+ * pipe.process('Café') // → 'cafe'
20
+ * pipe.process('Москва') // → 'moskva' (same handle, many inputs)
21
+ * ```
22
+ */
23
+ export { Pipeline };
24
+ /** The anomaly branch that fired for a finding. */
25
+ export type AnomalyKind = 'invisible' | 'bidi' | 'zalgo' | 'mixed_script' | 'leet' | 'segmentation';
26
+ /**
27
+ * One reason a token is anomalous. Re-typed over the generated {@link NativeFinding}
28
+ * so `kind` is the {@link AnomalyKind} string-union rather than a bare `string`.
29
+ */
30
+ export type Finding = Omit<NativeFinding, 'kind'> & {
31
+ kind: AnomalyKind;
32
+ };
33
+ /** Structured anomaly report, with {@link Finding}s carrying a typed `kind`. */
34
+ export type AnomalyReport = Omit<NativeAnomalyReport, 'findings'> & {
35
+ findings: Finding[];
36
+ };
37
+ /** Base class for every error disarm raises, so callers can `catch (e) { if (e instanceof DisarmError) … }`. */
38
+ export declare class DisarmError extends Error {
39
+ constructor(message: string);
40
+ }
41
+ /** An invalid argument — an unknown scheme/target/form/platform token, etc. */
42
+ export declare class DisarmInvalidArgument extends DisarmError {
43
+ constructor(message: string);
44
+ }
45
+ /** Transliteration scheme: the general-purpose default, ISO 9-style ASCII, or GOST R 7.0.34. */
46
+ export type Scheme = 'default' | 'strict_iso9' | 'gost7034';
47
+ /** Confusable-folding target script. */
48
+ export type TargetScript = 'latin' | 'cyrillic';
49
+ /** Unicode normalization form. */
50
+ export type NormalizationForm = 'NFC' | 'NFD' | 'NFKC' | 'NFKD';
51
+ /** Filename-safety platform ruleset. */
52
+ export type Platform = 'universal' | 'windows' | 'posix';
53
+ /** Reverse-transliteration target language. */
54
+ export type ReverseLang = 'el' | 'ru' | 'uk';
55
+ export interface TransliterateOptions {
56
+ /** The scheme (default: `'default'`). */
57
+ scheme?: Scheme;
58
+ /** A language profile applied on top of the scheme (e.g. `'uk'`, `'de'`, or `'auto'`). */
59
+ lang?: string;
60
+ }
61
+ /** Romanize Unicode text to ASCII. */
62
+ export declare function transliterate(text: string, options?: TransliterateOptions): string;
63
+ /** Reverse-transliterate Latin back to a native script (`'el'`, `'ru'`, or `'uk'`). */
64
+ export declare function reverseTransliterate(text: string, options: {
65
+ lang: ReverseLang;
66
+ }): string;
67
+ /** Every character in `text` with no romanization, as `{ char, offset }` (byte offset), in order. */
68
+ export declare function findUntranslatable(text: string, options?: TransliterateOptions): Untranslatable[];
69
+ /** Fold cross-script confusables toward `target` (default `'latin'`). */
70
+ export declare function normalizeConfusables(text: string, options?: {
71
+ target?: TargetScript;
72
+ }): string;
73
+ /** Whether `text` contains a character confusable with `target` (default `'latin'`). */
74
+ export declare function isConfusable(text: string, options?: {
75
+ target?: TargetScript;
76
+ }): boolean;
77
+ export interface SlugifyOptions {
78
+ separator?: string;
79
+ lowercase?: boolean;
80
+ maxLength?: number;
81
+ wordBoundary?: boolean;
82
+ saveOrder?: boolean;
83
+ stopwords?: string[];
84
+ allowUnicode?: boolean;
85
+ lang?: string;
86
+ entities?: boolean;
87
+ decimal?: boolean;
88
+ hexadecimal?: boolean;
89
+ safeChars?: string;
90
+ }
91
+ /** Generate a URL-safe slug. Mirrors the core's `SlugConfig` defaults. */
92
+ export declare function slugify(text: string, options?: SlugifyOptions): string;
93
+ /** Strip diacritics (`"café"` → `"cafe"`). */
94
+ export declare function stripAccents(text: string): string;
95
+ /** Full Unicode case fold — more aggressive than `String.toLowerCase()`. */
96
+ export declare function foldCase(text: string): string;
97
+ /** Replace emoji with their plain names. `stripModifiers` drops skin-tone/variation marks. */
98
+ export declare function demojize(text: string, options?: {
99
+ stripModifiers?: boolean;
100
+ }): string;
101
+ /** Apply a Unicode normalization `form` (default `'NFC'`). */
102
+ export declare function normalize(text: string, options?: {
103
+ form?: NormalizationForm;
104
+ }): string;
105
+ /** Whether `text` is already in normalization `form` (default `'NFC'`). */
106
+ export declare function isNormalized(text: string, options?: {
107
+ form?: NormalizationForm;
108
+ }): boolean;
109
+ /**
110
+ * Fold Unicode whitespace runs to single ASCII spaces and trim the ends (#433).
111
+ *
112
+ * Folds whitespace ONLY — the line controls (TAB/LF/VT/FF/CR), the information
113
+ * separators (U+001C–U+001F), NEL, the Zs/Zl/Zp spaces, and the blank-rendering
114
+ * set (Braille blank, Hangul fillers) each fold to a single space. It does NOT
115
+ * delete control or zero-width characters — use `stripControlChars` /
116
+ * `stripZeroWidthChars` for that. Folding the line controls (rather than
117
+ * deleting them) means `a\rb` → `a b`, never `ab`.
118
+ */
119
+ export declare function collapseWhitespace(text: string): string;
120
+ /** Remove C0/C1 control characters (except tab/newline). */
121
+ export declare function stripControlChars(text: string): string;
122
+ /** Remove zero-width characters (ZWSP/ZWNJ/ZWJ/word-joiner). */
123
+ export declare function stripZeroWidthChars(text: string): string;
124
+ /** Remove Unicode bidirectional control characters. */
125
+ export declare function stripBidi(text: string): string;
126
+ /** Strip the Unicode Tags block (U+E0000–U+E007F), preserving valid emoji flag sequences (#413). */
127
+ export declare function stripTags(text: string): string;
128
+ /** Strip every variation selector (VS1–VS256) (#413). */
129
+ export declare function stripVariationSelectors(text: string): string;
130
+ /** Strip every Unicode noncharacter (#413). */
131
+ export declare function stripNoncharacters(text: string): string;
132
+ /** Strip every Private Use Area code point (#413). */
133
+ export declare function stripPua(text: string): string;
134
+ /** Cap combining marks per base character at `maxMarks` (default `2`). */
135
+ export declare function stripZalgo(text: string, options?: {
136
+ maxMarks?: number;
137
+ }): string;
138
+ /** Whether any base character carries more than `threshold` (default `3`) combining marks. */
139
+ export declare function isZalgo(text: string, options?: {
140
+ threshold?: number;
141
+ }): boolean;
142
+ /** Remove obfuscation (zero-width, bidi, combining-mark abuse, homoglyphs) while keeping legible content. */
143
+ export declare function stripObfuscation(text: string): string;
144
+ /**
145
+ * Canonicalize text for security-sensitive comparison: NFKC → strip bidi/format
146
+ * → strip invisible classes (#413) → strip control → strip zero-width → collapse
147
+ * whitespace → cap combining marks (anti-zalgo) → NFC → confusables → NFC
148
+ * (confusables sandwiched between NFC passes for idempotency).
149
+ *
150
+ * The name describes the mechanism (Unicode canonicalization for matching), not
151
+ * a safety guarantee — this is not an output sanitizer; encode at the sink.
152
+ */
153
+ export declare function canonicalize(text: string): string;
154
+ /**
155
+ * @deprecated Renamed to {@link canonicalize} in 0.11 (the `*Clean` name
156
+ * overpromised safety); removed in 1.0.
157
+ */
158
+ export declare function securityClean(text: string): string;
159
+ /**
160
+ * Build a reusable {@link Pipeline} handle for a named policy profile (#404).
161
+ * Resolve and compile the profile's steps once here, then call `.process(text)`
162
+ * on the returned handle for each input — the per-call cost is just running the
163
+ * already-compiled steps, not re-resolving the profile (mirrors {@link Lexicon}).
164
+ *
165
+ * An unknown `profile` throws {@link DisarmInvalidArgument} (naming the
166
+ * available profiles).
167
+ */
168
+ export declare function getPipeline(profile: string): Pipeline;
169
+ export interface SanitizeFilenameOptions {
170
+ separator?: string;
171
+ maxLength?: number;
172
+ platform?: Platform;
173
+ lang?: string;
174
+ preserveExtension?: boolean;
175
+ }
176
+ /** Turn arbitrary text into a filesystem-safe filename. */
177
+ export declare function sanitizeFilename(text: string, options?: SanitizeFilenameOptions): string;
178
+ /**
179
+ * Case/accent/script-insensitive search lookup key (like {@link catalogKey}
180
+ * without confusable folding). `lang` selects the transliteration table.
181
+ */
182
+ export declare function searchKey(text: string, options?: {
183
+ lang?: string;
184
+ }): string;
185
+ /**
186
+ * Collation sort key — like {@link searchKey} but preserves base accented
187
+ * characters for correct ordering. `lang` selects the transliteration table.
188
+ */
189
+ export declare function sortKey(text: string, options?: {
190
+ lang?: string;
191
+ }): string;
192
+ /**
193
+ * Library catalog deduplication key — like {@link searchKey} plus confusable
194
+ * folding. `lang` selects the transliteration table; `strictIso9` (default
195
+ * `false`) picks the ISO 9:1995 Cyrillic scheme.
196
+ */
197
+ export declare function catalogKey(text: string, options?: {
198
+ lang?: string;
199
+ strictIso9?: boolean;
200
+ }): string;
201
+ /** Number of grapheme clusters (user-perceived characters). */
202
+ export declare function graphemeLen(text: string): number;
203
+ /** Split `text` into grapheme-cluster strings. */
204
+ export declare function graphemeSplit(text: string): string[];
205
+ /** Truncate to at most `maxGraphemes` clusters, never cutting through one. */
206
+ export declare function graphemeTruncate(text: string, maxGraphemes: number): string;
207
+ /** Display width (terminal columns) of a single grapheme `cluster` by East Asian Width. */
208
+ export declare function graphemeWidth(cluster: string, options?: {
209
+ ambiguousWide?: boolean;
210
+ }): number;
211
+ /** Total display width (terminal columns) of `text`. */
212
+ export declare function terminalWidth(text: string, options?: {
213
+ ambiguousWide?: boolean;
214
+ }): number;
215
+ /** Whether the hostname looks like a mixed-script / confusable IDN spoof (a `false` is not a safety guarantee). */
216
+ export declare function isSuspiciousHostname(host: string): boolean;
217
+ /** The Unicode scripts present, in first-appearance order (Common/Inherited excluded). */
218
+ export declare function detectScripts(text: string): string[];
219
+ /** Whether `text` mixes characters from more than one script. */
220
+ export declare function isMixedScript(text: string): boolean;
221
+ /**
222
+ * Whether `text` mixes strong left-to-right and strong right-to-left characters
223
+ * — the precondition for Bidi display-reordering ("BiDi Swap", #412). Fires on
224
+ * the real letters (no `U+202x` override); a `false` result is not a safety
225
+ * guarantee.
226
+ */
227
+ export declare function hasBidiConflict(text: string): boolean;
228
+ /** Explain how `lang: 'auto'` detection resolves `text`. */
229
+ export declare function inspectAutoLang(text: string): AutoLangInspection;
230
+ /**
231
+ * Static facts about a language `code` — its English name, primary script,
232
+ * region, and context-awareness. An unknown code throws
233
+ * {@link DisarmInvalidArgument}.
234
+ */
235
+ export declare function langInfo(code: string): LangMeta;
236
+ /**
237
+ * Static facts about a script by `name` — its default language code (if any),
238
+ * an example string, and whether its transliteration is context-aware. An
239
+ * unknown name throws {@link DisarmInvalidArgument}.
240
+ */
241
+ export declare function scriptInfo(name: string): ScriptMeta;
242
+ /** Every Unicode script name known to the transliteration tables. */
243
+ export declare function listScripts(): string[];
244
+ /** Every language code that has a context-aware transliteration profile. */
245
+ export declare function listContextLangs(): string[];
246
+ /**
247
+ * Whether any whitespace token carries out-of-place characters that disguise a
248
+ * real word — a cross-script homoglyph, leet, segmentation, a zero-width / bidi
249
+ * control, or zalgo. Reports a technical fact and leaves the malicious-or-not
250
+ * judgement to the caller. `lexicon` is a common-word collection (a `Set` or
251
+ * array) — or a prebuilt {@link Lexicon} handle, which avoids rebuilding the
252
+ * internal set on every call — used only by the leet and segmentation branches.
253
+ */
254
+ export declare function hasAnomalies(text: string, lexicon?: Iterable<string> | Lexicon): boolean;
255
+ /**
256
+ * Full anomaly analysis: an `AnomalyReport` with `anomalous`, `kinds` (in
257
+ * first-appearance order), `findings` (each `{ kind, token, start, end, detail,
258
+ * reason }`, with byte offsets), and `reason` (the first finding's reason).
259
+ * `lexicon` may be a `Set`/array of words or a prebuilt {@link Lexicon} handle.
260
+ */
261
+ export declare function inspectAnomalies(text: string, lexicon?: Iterable<string> | Lexicon): AnomalyReport;
package/index.js ADDED
@@ -0,0 +1,413 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DisarmInvalidArgument = exports.DisarmError = exports.Pipeline = exports.Lexicon = void 0;
37
+ exports.transliterate = transliterate;
38
+ exports.reverseTransliterate = reverseTransliterate;
39
+ exports.findUntranslatable = findUntranslatable;
40
+ exports.normalizeConfusables = normalizeConfusables;
41
+ exports.isConfusable = isConfusable;
42
+ exports.slugify = slugify;
43
+ exports.stripAccents = stripAccents;
44
+ exports.foldCase = foldCase;
45
+ exports.demojize = demojize;
46
+ exports.normalize = normalize;
47
+ exports.isNormalized = isNormalized;
48
+ exports.collapseWhitespace = collapseWhitespace;
49
+ exports.stripControlChars = stripControlChars;
50
+ exports.stripZeroWidthChars = stripZeroWidthChars;
51
+ exports.stripBidi = stripBidi;
52
+ exports.stripTags = stripTags;
53
+ exports.stripVariationSelectors = stripVariationSelectors;
54
+ exports.stripNoncharacters = stripNoncharacters;
55
+ exports.stripPua = stripPua;
56
+ exports.stripZalgo = stripZalgo;
57
+ exports.isZalgo = isZalgo;
58
+ exports.stripObfuscation = stripObfuscation;
59
+ exports.canonicalize = canonicalize;
60
+ exports.securityClean = securityClean;
61
+ exports.getPipeline = getPipeline;
62
+ exports.sanitizeFilename = sanitizeFilename;
63
+ exports.searchKey = searchKey;
64
+ exports.sortKey = sortKey;
65
+ exports.catalogKey = catalogKey;
66
+ exports.graphemeLen = graphemeLen;
67
+ exports.graphemeSplit = graphemeSplit;
68
+ exports.graphemeTruncate = graphemeTruncate;
69
+ exports.graphemeWidth = graphemeWidth;
70
+ exports.terminalWidth = terminalWidth;
71
+ exports.isSuspiciousHostname = isSuspiciousHostname;
72
+ exports.detectScripts = detectScripts;
73
+ exports.isMixedScript = isMixedScript;
74
+ exports.hasBidiConflict = hasBidiConflict;
75
+ exports.inspectAutoLang = inspectAutoLang;
76
+ exports.langInfo = langInfo;
77
+ exports.scriptInfo = scriptInfo;
78
+ exports.listScripts = listScripts;
79
+ exports.listContextLangs = listContextLangs;
80
+ exports.hasAnomalies = hasAnomalies;
81
+ exports.inspectAnomalies = inspectAnomalies;
82
+ /**
83
+ * disarm for Node.js — Unicode confusable/text-security building blocks, powered
84
+ * by a pure-Rust core (#44).
85
+ *
86
+ * This is the idiomatic TypeScript layer over the raw napi binding (`./binding`):
87
+ * it adds options objects with sensible defaults, string-union token types, and a
88
+ * native {@link DisarmError} class. The behaviour is defined once in the Rust core
89
+ * and inherited here — see https://docs.disarm.dev for the language-neutral guides.
90
+ */
91
+ const native = __importStar(require("./binding"));
92
+ const binding_1 = require("./binding");
93
+ Object.defineProperty(exports, "Lexicon", { enumerable: true, get: function () { return binding_1.Lexicon; } });
94
+ Object.defineProperty(exports, "Pipeline", { enumerable: true, get: function () { return binding_1.Pipeline; } });
95
+ // ── Errors ──────────────────────────────────────────────────────────────────
96
+ /** Base class for every error disarm raises, so callers can `catch (e) { if (e instanceof DisarmError) … }`. */
97
+ class DisarmError extends Error {
98
+ constructor(message) {
99
+ super(message);
100
+ this.name = 'DisarmError';
101
+ }
102
+ }
103
+ exports.DisarmError = DisarmError;
104
+ /** An invalid argument — an unknown scheme/target/form/platform token, etc. */
105
+ class DisarmInvalidArgument extends DisarmError {
106
+ constructor(message) {
107
+ super(message);
108
+ this.name = 'DisarmInvalidArgument';
109
+ }
110
+ }
111
+ exports.DisarmInvalidArgument = DisarmInvalidArgument;
112
+ const INVALID_ARG_TAG = 'DisarmInvalidArgument: ';
113
+ const ERROR_TAG = 'DisarmError: ';
114
+ /**
115
+ * Run a native call, re-raising its tagged napi error as the matching
116
+ * `DisarmError` subclass. The native shim prefixes fallible messages with
117
+ * `"DisarmInvalidArgument: "` or `"DisarmError: "`; we strip the matched tag
118
+ * cleanly. Any other throw — an untagged `Error`, or a non-`Error` value — is
119
+ * still wrapped as a `DisarmError` so nothing leaks out unwrapped.
120
+ */
121
+ function call(fn) {
122
+ try {
123
+ return fn();
124
+ }
125
+ catch (e) {
126
+ const msg = e instanceof Error ? e.message : String(e);
127
+ if (msg.startsWith(INVALID_ARG_TAG)) {
128
+ throw new DisarmInvalidArgument(msg.slice(INVALID_ARG_TAG.length));
129
+ }
130
+ if (msg.startsWith(ERROR_TAG)) {
131
+ throw new DisarmError(msg.slice(ERROR_TAG.length));
132
+ }
133
+ throw new DisarmError(msg);
134
+ }
135
+ }
136
+ /** Romanize Unicode text to ASCII. */
137
+ function transliterate(text, options = {}) {
138
+ const { scheme = 'default', lang } = options;
139
+ if (scheme === 'default' && lang == null) {
140
+ return native.transliterate(text);
141
+ }
142
+ return call(() => native.transliterateOpts(text, scheme, lang ?? undefined));
143
+ }
144
+ /** Reverse-transliterate Latin back to a native script (`'el'`, `'ru'`, or `'uk'`). */
145
+ function reverseTransliterate(text, options) {
146
+ return call(() => native.reverseTransliterate(text, options.lang));
147
+ }
148
+ /** Every character in `text` with no romanization, as `{ char, offset }` (byte offset), in order. */
149
+ function findUntranslatable(text, options = {}) {
150
+ const { scheme = 'default', lang } = options;
151
+ return call(() => native.findUntranslatable(text, scheme, lang ?? undefined));
152
+ }
153
+ // ── Confusables (TR39) ──────────────────────────────────────────────────────
154
+ /** Fold cross-script confusables toward `target` (default `'latin'`). */
155
+ function normalizeConfusables(text, options = {}) {
156
+ return call(() => native.normalizeConfusables(text, options.target ?? 'latin'));
157
+ }
158
+ /** Whether `text` contains a character confusable with `target` (default `'latin'`). */
159
+ function isConfusable(text, options = {}) {
160
+ return call(() => native.isConfusable(text, options.target ?? 'latin'));
161
+ }
162
+ /** Generate a URL-safe slug. Mirrors the core's `SlugConfig` defaults. */
163
+ function slugify(text, options = {}) {
164
+ return call(() => native.slugify(text, {
165
+ separator: options.separator ?? '-',
166
+ lowercase: options.lowercase ?? true,
167
+ maxLength: options.maxLength ?? 0,
168
+ wordBoundary: options.wordBoundary ?? false,
169
+ saveOrder: options.saveOrder ?? false,
170
+ stopwords: options.stopwords ?? [],
171
+ allowUnicode: options.allowUnicode ?? false,
172
+ lang: options.lang,
173
+ entities: options.entities ?? true,
174
+ decimal: options.decimal ?? true,
175
+ hexadecimal: options.hexadecimal ?? true,
176
+ safeChars: options.safeChars ?? '',
177
+ }));
178
+ }
179
+ // ── Canonicalization primitives ─────────────────────────────────────────────
180
+ /** Strip diacritics (`"café"` → `"cafe"`). */
181
+ function stripAccents(text) {
182
+ return native.stripAccents(text);
183
+ }
184
+ /** Full Unicode case fold — more aggressive than `String.toLowerCase()`. */
185
+ function foldCase(text) {
186
+ return native.foldCase(text);
187
+ }
188
+ /** Replace emoji with their plain names. `stripModifiers` drops skin-tone/variation marks. */
189
+ function demojize(text, options = {}) {
190
+ return native.demojize(text, options.stripModifiers ?? false);
191
+ }
192
+ // ── Normalization ───────────────────────────────────────────────────────────
193
+ /** Apply a Unicode normalization `form` (default `'NFC'`). */
194
+ function normalize(text, options = {}) {
195
+ return call(() => native.normalize(text, options.form ?? 'NFC'));
196
+ }
197
+ /** Whether `text` is already in normalization `form` (default `'NFC'`). */
198
+ function isNormalized(text, options = {}) {
199
+ return call(() => native.isNormalized(text, options.form ?? 'NFC'));
200
+ }
201
+ // ── Text cleaning ───────────────────────────────────────────────────────────
202
+ /**
203
+ * Fold Unicode whitespace runs to single ASCII spaces and trim the ends (#433).
204
+ *
205
+ * Folds whitespace ONLY — the line controls (TAB/LF/VT/FF/CR), the information
206
+ * separators (U+001C–U+001F), NEL, the Zs/Zl/Zp spaces, and the blank-rendering
207
+ * set (Braille blank, Hangul fillers) each fold to a single space. It does NOT
208
+ * delete control or zero-width characters — use `stripControlChars` /
209
+ * `stripZeroWidthChars` for that. Folding the line controls (rather than
210
+ * deleting them) means `a\rb` → `a b`, never `ab`.
211
+ */
212
+ function collapseWhitespace(text) {
213
+ return native.collapseWhitespace(text);
214
+ }
215
+ /** Remove C0/C1 control characters (except tab/newline). */
216
+ function stripControlChars(text) {
217
+ return native.stripControlChars(text);
218
+ }
219
+ /** Remove zero-width characters (ZWSP/ZWNJ/ZWJ/word-joiner). */
220
+ function stripZeroWidthChars(text) {
221
+ return native.stripZeroWidthChars(text);
222
+ }
223
+ /** Remove Unicode bidirectional control characters. */
224
+ function stripBidi(text) {
225
+ return native.stripBidi(text);
226
+ }
227
+ /** Strip the Unicode Tags block (U+E0000–U+E007F), preserving valid emoji flag sequences (#413). */
228
+ function stripTags(text) {
229
+ return native.stripTags(text);
230
+ }
231
+ /** Strip every variation selector (VS1–VS256) (#413). */
232
+ function stripVariationSelectors(text) {
233
+ return native.stripVariationSelectors(text);
234
+ }
235
+ /** Strip every Unicode noncharacter (#413). */
236
+ function stripNoncharacters(text) {
237
+ return native.stripNoncharacters(text);
238
+ }
239
+ /** Strip every Private Use Area code point (#413). */
240
+ function stripPua(text) {
241
+ return native.stripPua(text);
242
+ }
243
+ /** Cap combining marks per base character at `maxMarks` (default `2`). */
244
+ function stripZalgo(text, options = {}) {
245
+ return call(() => native.stripZalgo(text, options.maxMarks ?? 2));
246
+ }
247
+ /** Whether any base character carries more than `threshold` (default `3`) combining marks. */
248
+ function isZalgo(text, options = {}) {
249
+ return call(() => native.isZalgo(text, options.threshold ?? 3));
250
+ }
251
+ // ── Deobfuscation & security presets ────────────────────────────────────────
252
+ /** Remove obfuscation (zero-width, bidi, combining-mark abuse, homoglyphs) while keeping legible content. */
253
+ function stripObfuscation(text) {
254
+ return call(() => native.stripObfuscation(text));
255
+ }
256
+ /**
257
+ * Canonicalize text for security-sensitive comparison: NFKC → strip bidi/format
258
+ * → strip invisible classes (#413) → strip control → strip zero-width → collapse
259
+ * whitespace → cap combining marks (anti-zalgo) → NFC → confusables → NFC
260
+ * (confusables sandwiched between NFC passes for idempotency).
261
+ *
262
+ * The name describes the mechanism (Unicode canonicalization for matching), not
263
+ * a safety guarantee — this is not an output sanitizer; encode at the sink.
264
+ */
265
+ function canonicalize(text) {
266
+ return call(() => native.canonicalize(text));
267
+ }
268
+ /**
269
+ * @deprecated Renamed to {@link canonicalize} in 0.11 (the `*Clean` name
270
+ * overpromised safety); removed in 1.0.
271
+ */
272
+ function securityClean(text) {
273
+ return canonicalize(text);
274
+ }
275
+ /**
276
+ * Build a reusable {@link Pipeline} handle for a named policy profile (#404).
277
+ * Resolve and compile the profile's steps once here, then call `.process(text)`
278
+ * on the returned handle for each input — the per-call cost is just running the
279
+ * already-compiled steps, not re-resolving the profile (mirrors {@link Lexicon}).
280
+ *
281
+ * An unknown `profile` throws {@link DisarmInvalidArgument} (naming the
282
+ * available profiles).
283
+ */
284
+ function getPipeline(profile) {
285
+ return call(() => native.getPipeline(profile));
286
+ }
287
+ /** Turn arbitrary text into a filesystem-safe filename. */
288
+ function sanitizeFilename(text, options = {}) {
289
+ return call(() => native.sanitizeFilename(text, options.separator ?? '_', options.maxLength ?? 255, options.platform ?? 'universal', options.lang ?? undefined, options.preserveExtension ?? true));
290
+ }
291
+ // ── Key-derivation presets ──────────────────────────────────────────────────
292
+ /**
293
+ * Case/accent/script-insensitive search lookup key (like {@link catalogKey}
294
+ * without confusable folding). `lang` selects the transliteration table.
295
+ */
296
+ function searchKey(text, options = {}) {
297
+ return call(() => native.searchKey(text, options.lang ?? undefined));
298
+ }
299
+ /**
300
+ * Collation sort key — like {@link searchKey} but preserves base accented
301
+ * characters for correct ordering. `lang` selects the transliteration table.
302
+ */
303
+ function sortKey(text, options = {}) {
304
+ return call(() => native.sortKey(text, options.lang ?? undefined));
305
+ }
306
+ /**
307
+ * Library catalog deduplication key — like {@link searchKey} plus confusable
308
+ * folding. `lang` selects the transliteration table; `strictIso9` (default
309
+ * `false`) picks the ISO 9:1995 Cyrillic scheme.
310
+ */
311
+ function catalogKey(text, options = {}) {
312
+ return call(() => native.catalogKey(text, options.lang ?? undefined, options.strictIso9 ?? false));
313
+ }
314
+ // ── Grapheme clusters ───────────────────────────────────────────────────────
315
+ /** Number of grapheme clusters (user-perceived characters). */
316
+ function graphemeLen(text) {
317
+ return native.graphemeLen(text);
318
+ }
319
+ /** Split `text` into grapheme-cluster strings. */
320
+ function graphemeSplit(text) {
321
+ return native.graphemeSplit(text);
322
+ }
323
+ /** Truncate to at most `maxGraphemes` clusters, never cutting through one. */
324
+ function graphemeTruncate(text, maxGraphemes) {
325
+ return call(() => native.graphemeTruncate(text, maxGraphemes));
326
+ }
327
+ /** Display width (terminal columns) of a single grapheme `cluster` by East Asian Width. */
328
+ function graphemeWidth(cluster, options = {}) {
329
+ return native.graphemeWidth(cluster, options.ambiguousWide ?? false);
330
+ }
331
+ /** Total display width (terminal columns) of `text`. */
332
+ function terminalWidth(text, options = {}) {
333
+ return native.terminalWidth(text, options.ambiguousWide ?? false);
334
+ }
335
+ // ── Hostname / script analysis ──────────────────────────────────────────────
336
+ /** Whether the hostname looks like a mixed-script / confusable IDN spoof (a `false` is not a safety guarantee). */
337
+ function isSuspiciousHostname(host) {
338
+ return native.isSuspiciousHostname(host);
339
+ }
340
+ /** The Unicode scripts present, in first-appearance order (Common/Inherited excluded). */
341
+ function detectScripts(text) {
342
+ return native.detectScripts(text);
343
+ }
344
+ /** Whether `text` mixes characters from more than one script. */
345
+ function isMixedScript(text) {
346
+ return native.isMixedScript(text);
347
+ }
348
+ /**
349
+ * Whether `text` mixes strong left-to-right and strong right-to-left characters
350
+ * — the precondition for Bidi display-reordering ("BiDi Swap", #412). Fires on
351
+ * the real letters (no `U+202x` override); a `false` result is not a safety
352
+ * guarantee.
353
+ */
354
+ function hasBidiConflict(text) {
355
+ return native.hasBidiConflict(text);
356
+ }
357
+ /** Explain how `lang: 'auto'` detection resolves `text`. */
358
+ function inspectAutoLang(text) {
359
+ return native.inspectAutoLang(text);
360
+ }
361
+ // ── Metadata introspection (#404) ───────────────────────────────────────────
362
+ /**
363
+ * Static facts about a language `code` — its English name, primary script,
364
+ * region, and context-awareness. An unknown code throws
365
+ * {@link DisarmInvalidArgument}.
366
+ */
367
+ function langInfo(code) {
368
+ return call(() => native.langInfo(code));
369
+ }
370
+ /**
371
+ * Static facts about a script by `name` — its default language code (if any),
372
+ * an example string, and whether its transliteration is context-aware. An
373
+ * unknown name throws {@link DisarmInvalidArgument}.
374
+ */
375
+ function scriptInfo(name) {
376
+ return call(() => native.scriptInfo(name));
377
+ }
378
+ /** Every Unicode script name known to the transliteration tables. */
379
+ function listScripts() {
380
+ return native.listScripts();
381
+ }
382
+ /** Every language code that has a context-aware transliteration profile. */
383
+ function listContextLangs() {
384
+ return native.listContextLangs();
385
+ }
386
+ // ── Anomaly detection ───────────────────────────────────────────────────────
387
+ /**
388
+ * Whether any whitespace token carries out-of-place characters that disguise a
389
+ * real word — a cross-script homoglyph, leet, segmentation, a zero-width / bidi
390
+ * control, or zalgo. Reports a technical fact and leaves the malicious-or-not
391
+ * judgement to the caller. `lexicon` is a common-word collection (a `Set` or
392
+ * array) — or a prebuilt {@link Lexicon} handle, which avoids rebuilding the
393
+ * internal set on every call — used only by the leet and segmentation branches.
394
+ */
395
+ function hasAnomalies(text, lexicon = []) {
396
+ if (lexicon instanceof binding_1.Lexicon) {
397
+ return native.hasAnomalies(text, lexicon);
398
+ }
399
+ return native.hasAnomalies(text, Array.isArray(lexicon) ? lexicon : [...lexicon]);
400
+ }
401
+ /**
402
+ * Full anomaly analysis: an `AnomalyReport` with `anomalous`, `kinds` (in
403
+ * first-appearance order), `findings` (each `{ kind, token, start, end, detail,
404
+ * reason }`, with byte offsets), and `reason` (the first finding's reason).
405
+ * `lexicon` may be a `Set`/array of words or a prebuilt {@link Lexicon} handle.
406
+ */
407
+ function inspectAnomalies(text, lexicon = []) {
408
+ if (lexicon instanceof binding_1.Lexicon) {
409
+ return native.inspectAnomalies(text, lexicon);
410
+ }
411
+ const words = Array.isArray(lexicon) ? lexicon : [...lexicon];
412
+ return native.inspectAnomalies(text, words);
413
+ }