
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@3leaps/string-metrics-wasm
Advanced tools
High-performance string similarity metrics via WASM bindings to rapidfuzz-rs
High-performance string similarity and fuzzy matching via WASM bindings to rapidfuzz-rs.
This library provides blazing-fast string similarity metrics through WASM bindings to the Rust rapidfuzz-rs library, plus TypeScript implementations of advanced fuzzy matching algorithms. It combines the performance of compiled Rust/WASM with the flexibility of TypeScript for a comprehensive text similarity toolkit.
Features:
wasm-pack (pinned to the version we build against)Install wasm-pack once per machine:
cargo install wasm-pack --version 0.13.1
npm install string-metrics-wasm
import { levenshtein, ratio, tokenSortRatio, extractOne, score } from 'string-metrics-wasm';
// Basic edit distance
const dist = levenshtein('kitten', 'sitting');
console.log(dist); // 3
// Fuzzy matching (0-100 scale)
const fuzzy = ratio('hello', 'hallo');
console.log(fuzzy); // 80.0
// Order-insensitive comparison
const tokens = tokenSortRatio('new york mets', 'mets york new');
console.log(tokens); // 100.0
// Find best match from array
const choices = ['Atlanta Falcons', 'New York Jets', 'Dallas Cowboys'];
const best = extractOne('new york', choices);
console.log(best); // { choice: 'New York Jets', score: 57.14, index: 1 }
// Unified scoring API (0-1 scale)
const similarity = score('hello', 'world', 'jaroWinkler');
console.log(similarity); // 0.4666...
Compatibility: All examples use camelCase option names and metric identifiers. For ecosystems that standardize on snake_case (e.g., Fulmen/Crucible fixtures), the same snake_case names are accepted as aliases and normalized internally.
Edit distance metrics return raw integer distances (lower = more similar):
levenshtein(a: string, b: string): numberMinimum edits (insertions, deletions, substitutions) to transform a into b.
levenshtein('kitten', 'sitting'); // 3
damerau_levenshtein(a: string, b: string): numberLevenshtein + transpositions (unrestricted).
damerau_levenshtein('abcd', 'abdc'); // 1
osa_distance(a: string, b: string): numberOptimal String Alignment (restricted Damerau-Levenshtein).
osa_distance('abcd', 'abdc'); // 1
indel_distance(a: string, b: string): numberInsertions and deletions only (no substitutions).
indel_distance('hello', 'hallo'); // 2
lcs_seq_distance(a: string, b: string): numberLongest Common Subsequence distance.
lcs_seq_distance('AGGTAB', 'GXTXAYB'); // 3
Normalized similarity scores (0.0-1.0 scale, higher = more similar):
normalized_levenshtein(a: string, b: string): numberNormalized Levenshtein similarity.
normalized_levenshtein('kitten', 'sitting'); // 0.5714
jaro(a: string, b: string): numberJaro similarity.
jaro('kitten', 'sitting'); // 0.7460
jaro_winkler(a: string, b: string): numberJaro-Winkler similarity (boosts prefix matches).
jaro_winkler('kitten', 'sitting'); // 0.7460
indel_normalized_similarity(a: string, b: string): numberNormalized indel similarity.
indel_normalized_similarity('hello', 'hallo'); // 0.8
lcs_seq_normalized_similarity(a: string, b: string): numberNormalized LCS similarity.
lcs_seq_normalized_similarity('AGGTAB', 'GXTXAYB'); // 0.5714
Fuzzy string comparison metrics (0-100 scale):
ratio(a: string, b: string): number (WASM)Basic fuzzy similarity using Indel distance.
ratio('kitten', 'sitting'); // 61.54
partialRatio(a: string, b: string): number (TypeScript)Best matching substring using sliding window.
partialRatio('fuzzy', 'fuzzy wuzzy was a bear'); // 100.0
tokenSortRatio(a: string, b: string): number (TypeScript)Order-insensitive token comparison (sorts tokens first).
tokenSortRatio('new york mets', 'mets york new'); // 100.0
tokenSetRatio(a: string, b: string): number (TypeScript)Set-based token comparison (handles duplicates and order).
tokenSetRatio('hello world world', 'world hello'); // 100.0
Find best matches from arrays:
extractOne(query: string, choices: string[], options?): ExtractResult | nullFind the single best match.
Options:
scorer?: (a: string, b: string) => number - Scoring function (default: ratio)processor?: (str: string) => string - Preprocessing functionscoreCutoff?: number - Minimum score threshold (default: 0)const choices = ['Atlanta Falcons', 'New York Jets', 'Dallas Cowboys'];
const best = extractOne('jets', choices, { scoreCutoff: 30 });
// { choice: 'New York Jets', score: 35.29, index: 1 }
extract(query: string, choices: string[], options?): ExtractResult[]Find top N matches (sorted by score).
Options:
scorer?: (a: string, b: string) => number - Scoring functionprocessor?: (str: string) => string - Preprocessing functionscoreCutoff?: number - Minimum score thresholdlimit?: number - Maximum results to returnconst results = extract('new york', choices, { limit: 2, scoreCutoff: 40 });
// [
// { choice: 'New York Jets', score: 57.14, index: 1 },
// { choice: 'New York Giants', score: 52.17, index: 2 }
// ]
Metric-selectable interface with consistent scales:
distance(a: string, b: string, metric?: DistanceMetric): numberCalculate edit distance using any metric (returns raw distance).
Supported metrics: 'levenshtein' (default), 'damerauLevenshtein', 'osa', 'indel',
'lcsSeq'
distance('hello', 'world'); // 4 (default: levenshtein)
distance('hello', 'world', 'indel'); // 8
score(a: string, b: string, metric?: SimilarityMetric): numberCalculate similarity using any metric (returns 0-1 normalized score).
Supported metrics: 'jaroWinkler' (default), 'levenshtein', 'damerauLevenshtein', 'osa',
'jaro', 'indel', 'lcsSeq', 'ratio', 'partialRatio', 'tokenSortRatio', 'tokenSetRatio'
score('hello', 'world'); // 0.4666... (default: jaroWinkler)
score('new york mets', 'mets york new', 'tokenSortRatio'); // 1.0
// Fulmen/Crucible users: override default metric if needed
score('hello', 'world', 'levenshtein'); // 0.5714 (edit distance-based)
normalize(input: string, preset?: NormalizationPreset, locale?: NormalizationLocale): stringNormalize text for comparison with optional locale-specific case folding.
Presets: 'none', 'minimal', 'default', 'aggressive'
Locales: 'tr' (Turkish), 'az' (Azerbaijani), 'lt' (Lithuanian), or undefined (default
Unicode casefold)
normalize('Naïve Café', 'default'); // 'naïve café'
// Turkish/Azerbaijani: dotted/dotless I handling
normalize('İstanbul', 'default', 'tr'); // 'istanbul' (İ→i)
normalize('IĞDIR', 'default', 'tr'); // 'ığdır' (I→ı dotless)
// Default Unicode casefold (no locale)
normalize('İstanbul', 'default'); // 'i̇stanbul' (İ→i + combining dot)
Note: Most applications don't need locale-specific normalization. Only use when processing Turkish, Azerbaijani, or Lithuanian text where dotted/dotless I distinction matters.
suggest(query: string, candidates: string[], options?): Suggestion[]Get ranked suggestions with detailed scoring.
const suggestions = suggest('pythn', ['python', 'java', 'javascript'], {
metric: 'jaroWinkler',
minScore: 0.6,
maxSuggestions: 3,
});
// [
// { value: 'python', score: 0.9555, ... },
// ...
// ]
See Suggestions API docs for full details.
This library uses a hybrid approach for optimal performance and flexibility:
WASM Implementations (fastest):
levenshtein, damerau_levenshtein, osa_distance, jaro,
jaro_winklerratio, indel_*, lcs_seq_*TypeScript Implementations (flexible):
partialRatio, tokenSortRatio, tokenSetRatioextractOne, extractdistance(), score()Token-based metrics benefit from TypeScript's array operations and avoid WASM serialization overhead. The unified API provides a convenient abstraction over both WASM and TypeScript implementations.
npm: specifier)make bootstrapnpm run build:wasm or make buildnpm run build:tsThis project uses a Makefile for common tasks:
make help # Show all available targets
make build # Build WASM and TypeScript (with version check)
make test # Run tests
make clean # Remove build artifacts
# Code quality
make quality # Run all quality checks (format-check, lint, rust checks)
make format # Format all code (Biome + Prettier + rustfmt)
make format-check # Check formatting without changes
make lint # Lint TypeScript code with Biome
make lint-fix # Lint and auto-fix TypeScript code
# Version management
make version-check # Verify package.json and Cargo.toml versions match
make bump-patch # Bump patch version (0.1.0 -> 0.1.1)
make bump-minor # Bump minor version (0.1.0 -> 0.2.0)
make bump-major # Bump major version (0.1.0 -> 1.0.0)
make set-version VERSION=x.y.z # Set explicit version
Explore the rest of the documentation under docs/. Start with the high-level
overview or jump straight to the contributor guide in
docs/development.md.
This project uses modern, fast tooling for code quality:
rustfmt for formatting, clippy for lintingRun make quality before committing to ensure all checks pass.
This project maintains version sync between package.json (npm) and Cargo.toml (Rust). The
Makefile provides targets to bump versions and keep them in sync. Additionally, the test suite
includes a version consistency check that will fail if versions drift.
Important: Always use make bump-* or make set-version commands to update versions. This
ensures both files stay synchronized.
All string comparison operations complete in < 1ms:
Run node benchmark-phase1b.js for detailed benchmarks.
This project includes comprehensive test coverage:
Run tests with npm test or make test.
This project follows Semantic Versioning. Version history is maintained in CHANGELOG.md.
Current Status: See latest release for the current version and changes.
This project is licensed under the MIT License.
Contributions welcome! Please see our contributing guidelines:
⚡ Fast Strings. Accurate Matches. ⚡
High-performance text similarity for modern TypeScript applications
Built with ⚡ by the 3 Leaps team
String Metrics • Fuzzy Matching • WASM Performance
FAQs
High-performance string similarity metrics via WASM bindings to rapidfuzz-rs
We found that @3leaps/string-metrics-wasm demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.