@stll/fuzzy-search
Advanced tools
+12
-7
@@ -17,10 +17,15 @@ import { createRequire } from "node:module"; | ||
| if (typeof p === "string") return { pattern: p }; | ||
| if (typeof p === "object" && p !== null && typeof p.pattern === "string") { | ||
| if (p.distance === "auto") return { | ||
| ...p, | ||
| distance: resolveDistance("auto", p.pattern.length) | ||
| }; | ||
| return p; | ||
| if (typeof p !== "object" || p === null || !("pattern" in p)) throw new TypeError(`Pattern at index ${i} must be a string or { pattern, distance?, name? }`); | ||
| if (typeof p.pattern !== "string") throw new TypeError(`Pattern at index ${i}: "pattern" field must be a string`); | ||
| const entry = { pattern: p.pattern }; | ||
| if ("distance" in p && p.distance !== void 0) if (p.distance === "auto") entry.distance = resolveDistance("auto", p.pattern.length); | ||
| else if (typeof p.distance === "number") { | ||
| if (!Number.isInteger(p.distance) || p.distance < 0) throw new TypeError(`Pattern at index ${i}: "distance" field must be a non-negative integer`); | ||
| entry.distance = p.distance; | ||
| } else throw new TypeError(`Pattern at index ${i}: "distance" field must be a number or "auto"`); | ||
| if ("name" in p && p.name !== void 0) { | ||
| if (typeof p.name !== "string") throw new TypeError(`Pattern at index ${i}: "name" field must be a string`); | ||
| entry.name = p.name; | ||
| } | ||
| throw new TypeError(`Pattern at index ${i} must be a string or { pattern, distance?, name? }`); | ||
| return entry; | ||
| }; | ||
@@ -27,0 +32,0 @@ /** Score formula: clamped `1 - distance / patternLength`. */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["/* Shared core: types, helpers, and classes that\n * use a late-bound native backend (NAPI-RS or WASM).\n * Call initBinding() before constructing classes. */\n\n// -- Native binding types ----------------------------\n\nexport type NativeBinding = {\n FuzzySearch: new (\n entries: NormalizedEntry[],\n options?: Options,\n ) => NativeFuzzySearch;\n distance: (\n a: string,\n b: string,\n metric: Metric | null,\n ) => number;\n};\n\ntype NativeFuzzySearch = {\n patternCount: number;\n isMatch(haystack: string): boolean;\n _findIterPacked(haystack: string): Uint32Array;\n replaceAll(\n haystack: string,\n replacements: string[],\n ): string;\n};\n\ntype NormalizedEntry = {\n pattern: string;\n distance?: number;\n name?: string;\n};\n\n// -- Late-bound native binding -----------------------\n\nlet binding: NativeBinding;\n\n/** Set the native backend. Must be called once\n * before any class constructor. */\nexport const initBinding = (b: NativeBinding) => {\n binding = b;\n};\n\n// -- Public types ------------------------------------\n\n/** Distance metric for fuzzy matching. */\nexport type Metric = \"levenshtein\" | \"damerau-levenshtein\";\n\n/** Options for constructing a `FuzzySearch`. */\nexport type Options = {\n /**\n * Distance metric.\n * - `\"levenshtein\"`: insertions, deletions,\n * substitutions (default).\n * - `\"damerau-levenshtein\"`: + transpositions\n * of adjacent characters (ab -> ba = 1 edit).\n * @default \"levenshtein\"\n */\n metric?: Metric;\n /**\n * Strip diacritics before matching (NFD\n * decompose + remove combining marks).\n * \"Pribram\" matches \"Pribram\" at distance 0.\n * @default false\n */\n normalizeDiacritics?: boolean;\n /**\n * Use Unicode word boundaries (covers all\n * scripts). CJK characters are treated as\n * standalone words.\n * @default true\n */\n unicodeBoundaries?: boolean;\n /**\n * Only match whole words. Fuzzy matches on\n * substrings are usually noise; require word\n * boundaries unless opted out.\n * @default true\n */\n wholeWords?: boolean;\n /**\n * Case-insensitive matching (Unicode-aware).\n * @default false\n */\n caseInsensitive?: boolean;\n /**\n * Drop matches whose normalized similarity\n * score is below this threshold. Score is\n * `1 - distance / pattern.length`, clamped to\n * `[0, 1]`. The comparison is inclusive:\n * `score >= minScore` keeps the match.\n *\n * Applied after distance filtering, before\n * `kBest` ranking. Does not affect\n * `replaceAll`.\n */\n minScore?: number;\n /**\n * Return only the top `k` matches across the\n * entire haystack, ranked by score descending.\n * Ties are broken by lower `start`, then by\n * pattern index ascending for deterministic\n * ordering. Returned matches are sorted by\n * score (highest first), not by `start`.\n *\n * Applied after `minScore`. Does not affect\n * `replaceAll`.\n */\n kBest?: number;\n};\n\n/** A pattern entry with its edit distance. */\nexport type PatternEntry =\n | string\n | {\n pattern: string;\n /** Max edit distance. Must be less than\n * pattern length. `\"auto\"` uses the\n * Elasticsearch convention: 1-2 chars -> 0,\n * 3-5 chars -> 1, 6+ chars -> 2.\n * @default 1 */\n distance?: number | \"auto\";\n /** Optional name for the pattern. */\n name?: string;\n };\n\n/** A single fuzzy match result. */\nexport type FuzzyMatch = {\n /** Index into the patterns array. */\n pattern: number;\n /** Start UTF-16 code unit offset (compatible\n * with `String.prototype.slice()`). */\n start: number;\n /** End offset (exclusive). */\n end: number;\n /** The matched text\n * (`haystack.slice(start, end)`). */\n text: string;\n /** Actual Levenshtein edit distance. */\n distance: number;\n /**\n * Normalized similarity in `[0, 1]`:\n * `1 - distance / pattern.length`, clamped at 0.\n * Always populated. `distance=0` yields `1.0`\n * (perfect); higher distances yield lower scores.\n * Lets callers rank across patterns of differing\n * lengths without computing the ratio themselves.\n */\n score: number;\n /** Pattern name (if provided). */\n name?: string;\n};\n\n// -- Internal helpers --------------------------------\n\nconst resolveDistance = (\n dist: number | \"auto\",\n patternLength: number,\n): number => {\n if (dist !== \"auto\") return dist;\n if (patternLength <= 2) return 0;\n if (patternLength <= 5) return 1;\n return 2;\n};\n\nconst normalizeEntry = (\n p: PatternEntry,\n i: number,\n): NormalizedEntry => {\n if (typeof p === \"string\") {\n return { pattern: p };\n }\n if (\n typeof p === \"object\" &&\n p !== null &&\n typeof p.pattern === \"string\"\n ) {\n if (p.distance === \"auto\") {\n return {\n ...p,\n distance: resolveDistance(\"auto\", p.pattern.length),\n };\n }\n // SAFETY: The \"auto\" case was already handled above,\n // so p.distance is number | undefined — matching\n // NormalizedEntry.\n return p as NormalizedEntry;\n }\n throw new TypeError(\n `Pattern at index ${i} must be a string ` +\n `or { pattern, distance?, name? }`,\n );\n};\n\n/** Score formula: clamped `1 - distance / patternLength`. */\nconst computeScore = (\n distance: number,\n patternLength: number,\n): number => {\n if (patternLength <= 0) return 0;\n const raw = 1 - distance / patternLength;\n return raw < 0 ? 0 : raw;\n};\n\n/**\n * Stable ranking for `kBest`: higher score wins;\n * ties go to lower `start`, then pattern\n * index ascending.\n */\nconst compareForKBest = (\n a: FuzzyMatch,\n b: FuzzyMatch,\n): number => {\n if (a.score !== b.score) return b.score - a.score;\n if (a.start !== b.start) return a.start - b.start;\n return a.pattern - b.pattern;\n};\n\nconst unpack = (\n packed: Uint32Array,\n haystack: string,\n patterns: string[],\n names: (string | undefined)[],\n): FuzzyMatch[] => {\n const len = packed.length;\n const matches: FuzzyMatch[] = [];\n for (let i = 0; i < len; i += 4) {\n const idx = packed[i];\n const start = packed[i + 1];\n const end = packed[i + 2];\n const distance = packed[i + 3];\n if (\n idx === undefined ||\n start === undefined ||\n end === undefined ||\n distance === undefined\n ) {\n throw new Error(\n `Malformed packed array at offset ${String(i)}`,\n );\n }\n const pat = patterns[idx];\n if (pat === undefined) {\n throw new Error(\n `Malformed packed array: pattern index ${String(idx)} out of range`,\n );\n }\n const m: FuzzyMatch = {\n pattern: idx,\n start,\n end,\n text: haystack.slice(start, end),\n distance,\n score: computeScore(distance, pat.length),\n };\n if (names[idx] !== undefined) {\n m.name = names[idx];\n }\n matches.push(m);\n }\n return matches;\n};\n\n// -- Classes -----------------------------------------\n\n/**\n * Fuzzy string matcher. Finds approximate\n * matches within edit distance k, immune to\n * typos, OCR errors, and diacritics variants.\n *\n * Uses Myers' bit-parallel algorithm for O(n)\n * scanning per pattern (patterns up to 64 chars).\n *\n * @throws {Error} If a pattern is empty, too\n * long (> 64 chars), or distance > 3.\n *\n * @example\n * ```ts\n * const fs = new FuzzySearch([\n * { pattern: \"Gaislerova\", distance: 1 },\n * { pattern: \"Novak\", distance: 1 },\n * ], {\n * normalizeDiacritics: true,\n * wholeWords: true,\n * });\n *\n * fs.findIter(\"Gais1erova a Nowak\");\n * // [\n * // { pattern: 0, start: 0, end: 10,\n * // text: \"Gais1erova\", distance: 1 },\n * // { pattern: 1, start: 13, end: 18,\n * // text: \"Nowak\", distance: 1 },\n * // ]\n * ```\n */\nexport class FuzzySearch {\n private _patterns: string[];\n private _names: (string | undefined)[];\n private _minScore: number | undefined;\n private _kBest: number | undefined;\n private _inner: NativeFuzzySearch;\n\n constructor(patterns: PatternEntry[], options?: Options) {\n const entries = patterns.map(normalizeEntry);\n this._patterns = entries.map((e) => e.pattern);\n this._names = entries.map((e) => e.name);\n this._minScore = options?.minScore;\n this._kBest = options?.kBest;\n this._inner = new binding.FuzzySearch(entries, options);\n }\n\n /** Number of patterns in the matcher. */\n get patternCount(): number {\n return this._inner.patternCount;\n }\n\n /**\n * Returns `true` if any pattern matches\n * within its edit distance. Not affected by\n * `minScore` or `kBest`.\n */\n isMatch(haystack: string): boolean {\n return this._inner.isMatch(haystack);\n }\n\n /**\n * Find non-overlapping fuzzy matches.\n *\n * Without `minScore` or `kBest`, matches are\n * returned in ascending `start` order. With\n * `kBest`, matches are returned in\n * score-descending order (ties broken by\n * `start`, then pattern index).\n */\n findIter(haystack: string): FuzzyMatch[] {\n const matches = unpack(\n this._inner._findIterPacked(haystack),\n haystack,\n this._patterns,\n this._names,\n );\n const minScore = this._minScore;\n const filtered =\n minScore === undefined\n ? matches\n : matches.filter((m) => m.score >= minScore);\n const kBest = this._kBest;\n if (kBest === undefined) return filtered;\n if (kBest <= 0) return [];\n const sorted = filtered.sort(compareForKBest);\n return sorted.length <= kBest\n ? sorted\n : sorted.slice(0, kBest);\n }\n\n /**\n * Replace all fuzzy matches.\n * `replacements[i]` replaces pattern `i`.\n *\n * Always replaces every distance-qualified\n * match; ignores `minScore` and `kBest` so the\n * `replacements`-by-pattern contract stays\n * deterministic.\n *\n * @throws {Error} If `replacements.length`\n * does not equal `patternCount`.\n */\n replaceAll(\n haystack: string,\n replacements: string[],\n ): string {\n return this._inner.replaceAll(haystack, replacements);\n }\n}\n\n/**\n * Compute edit distance between two strings.\n *\n * Uses Unicode characters (not UTF-16 code units),\n * so emoji and supplementary plane characters are\n * handled correctly.\n *\n * @example\n * ```ts\n * distance(\"Novak\", \"Nowak\"); // 1\n * distance(\"abcd\", \"abdc\"); // 2\n * distance(\"abcd\", \"abdc\",\n * \"damerau-levenshtein\"); // 1\n * ```\n */\nexport const distance = (\n a: string,\n b: string,\n metric?: Metric,\n): number => binding.distance(a, b, metric ?? null);\n","/* Main entry point — loads the native NAPI-RS\n * binding and re-exports the public API. */\n\nimport { createRequire } from \"node:module\";\n\nimport { initBinding, type NativeBinding } from \"./core\";\n\nconst require = createRequire(import.meta.url);\n// SAFETY: NAPI-RS auto-generated loader returns the\n// native binding object; its shape is validated by\n// usage in the core classes.\nconst native = require(\"../index.cjs\") as NativeBinding;\n\ninitBinding(native);\n\nexport { FuzzySearch, distance } from \"./core\";\n\nexport type {\n FuzzyMatch,\n Metric,\n NativeBinding,\n Options,\n PatternEntry,\n} from \"./core\";\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["/* Shared core: types, helpers, and classes that\n * use a late-bound native backend (NAPI-RS or WASM).\n * Call initBinding() before constructing classes. */\n\n// -- Native binding types ----------------------------\n\nexport type NativeBinding = {\n FuzzySearch: new (\n entries: NormalizedEntry[],\n options?: Options,\n ) => NativeFuzzySearch;\n distance: (\n a: string,\n b: string,\n metric: Metric | null,\n ) => number;\n};\n\ntype NativeFuzzySearch = {\n patternCount: number;\n isMatch(haystack: string): boolean;\n _findIterPacked(haystack: string): Uint32Array;\n replaceAll(\n haystack: string,\n replacements: string[],\n ): string;\n};\n\ntype NormalizedEntry = {\n pattern: string;\n distance?: number;\n name?: string;\n};\n\n// -- Late-bound native binding -----------------------\n\nlet binding: NativeBinding;\n\n/** Set the native backend. Must be called once\n * before any class constructor. */\nexport const initBinding = (b: NativeBinding) => {\n binding = b;\n};\n\n// -- Public types ------------------------------------\n\n/** Distance metric for fuzzy matching. */\nexport type Metric = \"levenshtein\" | \"damerau-levenshtein\";\n\n/** Options for constructing a `FuzzySearch`. */\nexport type Options = {\n /**\n * Distance metric.\n * - `\"levenshtein\"`: insertions, deletions,\n * substitutions (default).\n * - `\"damerau-levenshtein\"`: + transpositions\n * of adjacent characters (ab -> ba = 1 edit).\n * @default \"levenshtein\"\n */\n metric?: Metric;\n /**\n * Strip diacritics before matching (NFD\n * decompose + remove combining marks).\n * \"Pribram\" matches \"Pribram\" at distance 0.\n * @default false\n */\n normalizeDiacritics?: boolean;\n /**\n * Use Unicode word boundaries (covers all\n * scripts). CJK characters are treated as\n * standalone words.\n * @default true\n */\n unicodeBoundaries?: boolean;\n /**\n * Only match whole words. Fuzzy matches on\n * substrings are usually noise; require word\n * boundaries unless opted out.\n * @default true\n */\n wholeWords?: boolean;\n /**\n * Case-insensitive matching (Unicode-aware).\n * @default false\n */\n caseInsensitive?: boolean;\n /**\n * Drop matches whose normalized similarity\n * score is below this threshold. Score is\n * `1 - distance / pattern.length`, clamped to\n * `[0, 1]`. The comparison is inclusive:\n * `score >= minScore` keeps the match.\n *\n * Applied after distance filtering, before\n * `kBest` ranking. Does not affect\n * `replaceAll`.\n */\n minScore?: number;\n /**\n * Return only the top `k` matches across the\n * entire haystack, ranked by score descending.\n * Ties are broken by lower `start`, then by\n * pattern index ascending for deterministic\n * ordering. Returned matches are sorted by\n * score (highest first), not by `start`.\n *\n * Applied after `minScore`. Does not affect\n * `replaceAll`.\n */\n kBest?: number;\n};\n\n/** A pattern entry with its edit distance. */\nexport type PatternEntry =\n | string\n | {\n pattern: string;\n /** Max edit distance. Must be less than\n * pattern length. `\"auto\"` uses the\n * Elasticsearch convention: 1-2 chars -> 0,\n * 3-5 chars -> 1, 6+ chars -> 2.\n * @default 1 */\n distance?: number | \"auto\";\n /** Optional name for the pattern. */\n name?: string;\n };\n\n/** A single fuzzy match result. */\nexport type FuzzyMatch = {\n /** Index into the patterns array. */\n pattern: number;\n /** Start UTF-16 code unit offset (compatible\n * with `String.prototype.slice()`). */\n start: number;\n /** End offset (exclusive). */\n end: number;\n /** The matched text\n * (`haystack.slice(start, end)`). */\n text: string;\n /** Actual Levenshtein edit distance. */\n distance: number;\n /**\n * Normalized similarity in `[0, 1]`:\n * `1 - distance / pattern.length`, clamped at 0.\n * Always populated. `distance=0` yields `1.0`\n * (perfect); higher distances yield lower scores.\n * Lets callers rank across patterns of differing\n * lengths without computing the ratio themselves.\n */\n score: number;\n /** Pattern name (if provided). */\n name?: string;\n};\n\n// -- Internal helpers --------------------------------\n\nconst resolveDistance = (\n dist: number | \"auto\",\n patternLength: number,\n): number => {\n if (dist !== \"auto\") return dist;\n if (patternLength <= 2) return 0;\n if (patternLength <= 5) return 1;\n return 2;\n};\n\nconst normalizeEntry = (\n p: unknown,\n i: number,\n): NormalizedEntry => {\n if (typeof p === \"string\") {\n return { pattern: p };\n }\n\n if (\n typeof p !== \"object\" ||\n p === null ||\n !(\"pattern\" in p)\n ) {\n throw new TypeError(\n `Pattern at index ${i} must be a string ` +\n `or { pattern, distance?, name? }`,\n );\n }\n\n if (typeof p.pattern !== \"string\") {\n throw new TypeError(\n `Pattern at index ${i}: \"pattern\" field must be a string`,\n );\n }\n\n const entry: NormalizedEntry = { pattern: p.pattern };\n\n if (\"distance\" in p && p.distance !== undefined) {\n if (p.distance === \"auto\") {\n entry.distance = resolveDistance(\n \"auto\",\n p.pattern.length,\n );\n } else if (typeof p.distance === \"number\") {\n if (!Number.isInteger(p.distance) || p.distance < 0) {\n throw new TypeError(\n `Pattern at index ${i}: \"distance\" field must be a non-negative integer`,\n );\n }\n entry.distance = p.distance;\n } else {\n throw new TypeError(\n `Pattern at index ${i}: \"distance\" field must be a number or \"auto\"`,\n );\n }\n }\n\n if (\"name\" in p && p.name !== undefined) {\n if (typeof p.name !== \"string\") {\n throw new TypeError(\n `Pattern at index ${i}: \"name\" field must be a string`,\n );\n }\n entry.name = p.name;\n }\n\n return entry;\n};\n\n/** Score formula: clamped `1 - distance / patternLength`. */\nconst computeScore = (\n distance: number,\n patternLength: number,\n): number => {\n if (patternLength <= 0) return 0;\n const raw = 1 - distance / patternLength;\n return raw < 0 ? 0 : raw;\n};\n\n/**\n * Stable ranking for `kBest`: higher score wins;\n * ties go to lower `start`, then pattern\n * index ascending.\n */\nconst compareForKBest = (\n a: FuzzyMatch,\n b: FuzzyMatch,\n): number => {\n if (a.score !== b.score) return b.score - a.score;\n if (a.start !== b.start) return a.start - b.start;\n return a.pattern - b.pattern;\n};\n\nconst unpack = (\n packed: Uint32Array,\n haystack: string,\n patterns: string[],\n names: (string | undefined)[],\n): FuzzyMatch[] => {\n const len = packed.length;\n const matches: FuzzyMatch[] = [];\n for (let i = 0; i < len; i += 4) {\n const idx = packed[i];\n const start = packed[i + 1];\n const end = packed[i + 2];\n const distance = packed[i + 3];\n if (\n idx === undefined ||\n start === undefined ||\n end === undefined ||\n distance === undefined\n ) {\n throw new Error(\n `Malformed packed array at offset ${String(i)}`,\n );\n }\n const pat = patterns[idx];\n if (pat === undefined) {\n throw new Error(\n `Malformed packed array: pattern index ${String(idx)} out of range`,\n );\n }\n const m: FuzzyMatch = {\n pattern: idx,\n start,\n end,\n text: haystack.slice(start, end),\n distance,\n score: computeScore(distance, pat.length),\n };\n if (names[idx] !== undefined) {\n m.name = names[idx];\n }\n matches.push(m);\n }\n return matches;\n};\n\n// -- Classes -----------------------------------------\n\n/**\n * Fuzzy string matcher. Finds approximate\n * matches within edit distance k, immune to\n * typos, OCR errors, and diacritics variants.\n *\n * Uses Myers' bit-parallel algorithm for O(n)\n * scanning per pattern (patterns up to 64 chars).\n *\n * @throws {Error} If a pattern is empty, too\n * long (> 64 chars), or distance > 3.\n *\n * @example\n * ```ts\n * const fs = new FuzzySearch([\n * { pattern: \"Gaislerova\", distance: 1 },\n * { pattern: \"Novak\", distance: 1 },\n * ], {\n * normalizeDiacritics: true,\n * wholeWords: true,\n * });\n *\n * fs.findIter(\"Gais1erova a Nowak\");\n * // [\n * // { pattern: 0, start: 0, end: 10,\n * // text: \"Gais1erova\", distance: 1 },\n * // { pattern: 1, start: 13, end: 18,\n * // text: \"Nowak\", distance: 1 },\n * // ]\n * ```\n */\nexport class FuzzySearch {\n private _patterns: string[];\n private _names: (string | undefined)[];\n private _minScore: number | undefined;\n private _kBest: number | undefined;\n private _inner: NativeFuzzySearch;\n\n constructor(patterns: PatternEntry[], options?: Options) {\n const entries = patterns.map(normalizeEntry);\n this._patterns = entries.map((e) => e.pattern);\n this._names = entries.map((e) => e.name);\n this._minScore = options?.minScore;\n this._kBest = options?.kBest;\n this._inner = new binding.FuzzySearch(entries, options);\n }\n\n /** Number of patterns in the matcher. */\n get patternCount(): number {\n return this._inner.patternCount;\n }\n\n /**\n * Returns `true` if any pattern matches\n * within its edit distance. Not affected by\n * `minScore` or `kBest`.\n */\n isMatch(haystack: string): boolean {\n return this._inner.isMatch(haystack);\n }\n\n /**\n * Find non-overlapping fuzzy matches.\n *\n * Without `minScore` or `kBest`, matches are\n * returned in ascending `start` order. With\n * `kBest`, matches are returned in\n * score-descending order (ties broken by\n * `start`, then pattern index).\n */\n findIter(haystack: string): FuzzyMatch[] {\n const matches = unpack(\n this._inner._findIterPacked(haystack),\n haystack,\n this._patterns,\n this._names,\n );\n const minScore = this._minScore;\n const filtered =\n minScore === undefined\n ? matches\n : matches.filter((m) => m.score >= minScore);\n const kBest = this._kBest;\n if (kBest === undefined) return filtered;\n if (kBest <= 0) return [];\n const sorted = filtered.sort(compareForKBest);\n return sorted.length <= kBest\n ? sorted\n : sorted.slice(0, kBest);\n }\n\n /**\n * Replace all fuzzy matches.\n * `replacements[i]` replaces pattern `i`.\n *\n * Always replaces every distance-qualified\n * match; ignores `minScore` and `kBest` so the\n * `replacements`-by-pattern contract stays\n * deterministic.\n *\n * @throws {Error} If `replacements.length`\n * does not equal `patternCount`.\n */\n replaceAll(\n haystack: string,\n replacements: string[],\n ): string {\n return this._inner.replaceAll(haystack, replacements);\n }\n}\n\n/**\n * Compute edit distance between two strings.\n *\n * Uses Unicode characters (not UTF-16 code units),\n * so emoji and supplementary plane characters are\n * handled correctly.\n *\n * @example\n * ```ts\n * distance(\"Novak\", \"Nowak\"); // 1\n * distance(\"abcd\", \"abdc\"); // 2\n * distance(\"abcd\", \"abdc\",\n * \"damerau-levenshtein\"); // 1\n * ```\n */\nexport const distance = (\n a: string,\n b: string,\n metric?: Metric,\n): number => binding.distance(a, b, metric ?? null);\n","/* Main entry point — loads the native NAPI-RS\n * binding and re-exports the public API. */\n\nimport { createRequire } from \"node:module\";\n\nimport { initBinding, type NativeBinding } from \"./core\";\n\nconst require = createRequire(import.meta.url);\n// SAFETY: NAPI-RS auto-generated loader returns the\n// native binding object; its shape is validated by\n// usage in the core classes.\nconst native = require(\"../index.cjs\") as NativeBinding;\n\ninitBinding(native);\n\nexport { FuzzySearch, distance } from \"./core\";\n\nexport type {\n FuzzyMatch,\n Metric,\n NativeBinding,\n Options,\n PatternEntry,\n} from \"./core\";\n"],"mappings":"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"} |
+66
-56
@@ -80,4 +80,4 @@ // prettier-ignore | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-android-arm64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -97,4 +97,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-android-arm-eabi/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -119,4 +119,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-win32-x64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -136,4 +136,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-win32-x64-msvc/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -154,4 +154,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-win32-ia32-msvc/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -171,4 +171,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-win32-arm64-msvc/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -191,4 +191,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-darwin-universal/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -208,4 +208,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-darwin-x64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -225,4 +225,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-darwin-arm64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -246,4 +246,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-freebsd-x64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -263,4 +263,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-freebsd-arm64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -285,4 +285,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-x64-musl/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -302,4 +302,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-x64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -321,4 +321,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-arm64-musl/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -338,4 +338,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-arm64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -357,4 +357,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-arm-musleabihf/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -374,4 +374,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-arm-gnueabihf/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -393,4 +393,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-loong64-musl/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -410,4 +410,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-loong64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -429,4 +429,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-riscv64-musl/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -446,4 +446,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-riscv64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -464,4 +464,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-ppc64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -481,4 +481,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-linux-s390x-gnu/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -502,4 +502,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-openharmony-arm64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -519,4 +519,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-openharmony-x64/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -536,4 +536,4 @@ return binding | ||
| const bindingPackageVersion = require('@stll/fuzzy-search-openharmony-arm/package.json').version | ||
| if (bindingPackageVersion !== '1.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '1.1.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 1.1.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -554,3 +554,13 @@ return binding | ||
| if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { | ||
| // NAPI_RS_FORCE_WASI is a tri-state flag: | ||
| // unset / any other value → native binding preferred, WASI is only a fallback | ||
| // 'true' → force WASI fallback even if native loaded | ||
| // 'error' → force WASI and throw if no WASI binding is found | ||
| // Treating any non-empty string as truthy (the historical behavior) meant | ||
| // NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered | ||
| // the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. | ||
| const forceWasi = | ||
| process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error' | ||
| if (!nativeBinding || forceWasi) { | ||
| let wasiBinding = null | ||
@@ -562,7 +572,7 @@ let wasiBindingError = null | ||
| } catch (err) { | ||
| if (process.env.NAPI_RS_FORCE_WASI) { | ||
| if (forceWasi) { | ||
| wasiBindingError = err | ||
| } | ||
| } | ||
| if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { | ||
| if (!nativeBinding || forceWasi) { | ||
| try { | ||
@@ -572,3 +582,3 @@ wasiBinding = require('@stll/fuzzy-search-wasm32-wasi') | ||
| } catch (err) { | ||
| if (process.env.NAPI_RS_FORCE_WASI) { | ||
| if (forceWasi) { | ||
| if (!wasiBindingError) { | ||
@@ -575,0 +585,0 @@ wasiBindingError = err |
+27
-18
| { | ||
| "name": "@stll/fuzzy-search", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "description": "Approximate substring matching for Node.js and Bun via a Rust Myers engine exposed through NAPI-RS.", | ||
@@ -54,4 +54,8 @@ "keywords": [ | ||
| "version:check": "node scripts/version-sync.mjs check", | ||
| "format": "oxfmt . && rustfmt src/lib.rs", | ||
| "lint": "oxlint .", | ||
| "release:prepare": "node scripts/prepare-release-package.mjs", | ||
| "release:check": "node scripts/check-release-tarballs.mjs", | ||
| "format": "oxfmt . \"!provenance/**\" \"!.ai/**\" \"!.agents/**\" \"!.claude/**\" \"!AGENTS.md\" \"!CLAUDE.md\" \"!GEMINI.md\" && rustfmt src/lib.rs", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware .", | ||
| "lint:fix": "bun --bun oxlint -c oxlint.config.ts --type-aware --fix .", | ||
| "check:wasm-compat": "node scripts/check-wasm-compat.mjs", | ||
@@ -63,3 +67,5 @@ "bench": "bun __bench__/speed.ts", | ||
| "bench:install": "cd __bench__ && bun install", | ||
| "bench:download": "bash __bench__/download-corpus.sh" | ||
| "bench:download": "bash __bench__/download-corpus.sh", | ||
| "sync-ai": "bash scripts/sync-ai-skills.sh", | ||
| "sync-ai:check": "bash scripts/sync-ai-skills.sh --check" | ||
| }, | ||
@@ -69,21 +75,17 @@ "devDependencies": { | ||
| "@emnapi/runtime": "^1.10.0", | ||
| "@napi-rs/cli": "^3.6.2", | ||
| "@napi-rs/cli": "^3.7.0", | ||
| "@stll/oxlint-config": "^0.3.0", | ||
| "@stll/typescript-config": "^0.3.0", | ||
| "@tybys/wasm-util": "^0.10.2", | ||
| "@types/node": "^25.6.2", | ||
| "bun-types": "^1.3.13", | ||
| "@types/node": "^25.9.1", | ||
| "bun-types": "^1.3.14", | ||
| "emnapi": "^1.10.0", | ||
| "fast-check": "^4.7.0", | ||
| "fast-check": "^4.8.0", | ||
| "oxfmt": "0.48.0", | ||
| "oxlint": "^1.63.0", | ||
| "tsdown": "0.22.0", | ||
| "oxlint": "^1.67.0", | ||
| "oxlint-tsgolint": "^0.23.0", | ||
| "tsdown": "0.22.1", | ||
| "typescript": "6.0.3", | ||
| "vite": "^8.0.11" | ||
| "vite": "^8.0.14" | ||
| }, | ||
| "optionalDependencies": { | ||
| "@stll/fuzzy-search-darwin-arm64": "1.1.0", | ||
| "@stll/fuzzy-search-darwin-x64": "1.1.0", | ||
| "@stll/fuzzy-search-linux-arm64-gnu": "1.1.0", | ||
| "@stll/fuzzy-search-linux-x64-gnu": "1.1.0", | ||
| "@stll/fuzzy-search-wasm32-wasi": "1.1.0" | ||
| }, | ||
| "napi": { | ||
@@ -101,3 +103,10 @@ "binaryName": "fuzzy-search", | ||
| "node": ">= 18" | ||
| }, | ||
| "optionalDependencies": { | ||
| "@stll/fuzzy-search-darwin-arm64": "1.1.1", | ||
| "@stll/fuzzy-search-darwin-x64": "1.1.1", | ||
| "@stll/fuzzy-search-linux-arm64-gnu": "1.1.1", | ||
| "@stll/fuzzy-search-linux-x64-gnu": "1.1.1", | ||
| "@stll/fuzzy-search-wasm32-wasi": "1.1.1" | ||
| } | ||
| } |
+1
-4
| <p align="center"> | ||
| <img src=".github/assets/banner.png" alt="Stella" width="100%" /> | ||
| <img src=".github/assets/banner.png" alt="stella" width="100%" /> | ||
| </p> | ||
@@ -41,5 +41,2 @@ | ||
| GitHub releases include npm tarballs, an SBOM, and | ||
| third-party notices. | ||
| Prebuilts are available for: | ||
@@ -46,0 +43,0 @@ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
66943
4.78%761
1.87%59
-4.84%16
23.08%337
-0.88%