Kyori
String similarity for JavaScript: Levenshtein, Damerau-Levenshtein, Hamming, Jaro-Winkler — plus the Kyori ranking algorithm and the ready-made Kyori Index for token-sensitive autocomplete scoring. Every metric is its own package subpath, so you can require methods/levensthein on its own and leave the rest behind. Every metric implemented natively — pure JavaScript, MIT-licensed.
npm install @wizhut_tech/kyoriFive algorithms, one consistent shape
Edit-distance metrics
Lower is more similar — zero means identical.
const { levensthein } = require( '@wizhut_tech/kyori/methods/levensthein') const { damerau_levensthein } = require( '@wizhut_tech/kyori/methods/damerau_levensthein') const { hamming } = require( '@wizhut_tech/kyori/methods/hamming') // …/methods/<name> — one metric, nothing else levensthein.distance('foo', 'food') // → 1 levensthein.similarity('foo', 'food') // → 0.75 levensthein.rank('foo', ['food', 'foo']) // → [ { term: 'foo', score: 0 }, // { term: 'food', score: 1 } ] // a transposition costs one edit, not two damerau_levensthein.distance('form', 'from') // → 1 // equal length only — -1 rather than a guess hamming.distance('karolin', 'kathrin') // → 3 hamming.distance('foo', 'food') // → -1
Similarity and ranking
Jaro-Winkler scores up to 1. The Kyori ranking cost lives on distance() and counts down to 0 — lower is better.
const { jaro_winkler } = require('@wizhut_tech/kyori/methods/jaro_winkler') const { kyori } = require('@wizhut_tech/kyori/methods/kyori') jaro_winkler.similarity('Maria', 'Marie') // → 0.92 // distance() is the ranking cost — lower is better // it folds case, accents and hyphens first kyori.distance('foo-bar', 'foo bar') // → 0 kyori.distance('cafe', 'café') // → 0 // a typed prefix ignores what you haven't typed kyori.distance('foo', 'food') // → 0 // one mistyped character costs one, not the rest kyori.distance('fod', 'food') // → 1 // same keywords in a different order: a flat 1 kyori.distance('hotel bel-air', 'bel-air hotel') // → 1 // your words later in the completion: a small cost, // after everything that starts with what you typed kyori.distance('pizza', 'margherita pizza') // → 2 // rank(): lowest cost first, and a tie goes to the shorter kyori.rank('pa', ['paper towels', 'paint', 'pan']) // → pan, paint, paper towels — all at cost 0 // similarity() is token overlap — higher is better kyori.similarity('hotel bel-air', 'bel-air hotel') // → 1
Autocomplete with Kyori Index
Load your vocabulary once, and search() returns every term scored and sorted — best match first, the shorter term first among equals.
const { KyoriIndex } = require('@wizhut_tech/kyori/indices/kyori') const index = new KyoriIndex() index.addMany(['cart', 'artist']) index.addOne('art') index.count() // → 3 index.search('art') // → [ { term: 'art', score: 0 }, // { term: 'artist', score: 0 }, // { term: 'cart', score: 7 } ]
Five algorithms and an index, each with a job
Levenshtein
methods/levenstheinThe classic Levenshtein distance: the fewest single-character insertions, deletions and substitutions that turn one string into the other.
Best for spell-check suggestions, near-duplicate records, anything diff-shaped.
'kitten', 'sitting'distance → 3Damerau-Levenshtein
methods/damerau_levenstheinLevenshtein plus transpositions: two swapped neighbours count as one edit, not two — the shape most typos actually take.
Best for typo-tolerant matching on anything a person typed.
'teh', 'the'distance → 1// Levenshtein says 2Hamming
methods/hammingPosition-by-position mismatches between two strings of equal length. No alignment step, so it is the cheapest of the five.
Best for fixed-width codes, hashes, fingerprints and bit strings.
'101101', '100111'distance → 2// lengths differ: -1Jaro-Winkler
methods/jaro_winklerMade for short strings like names: rewards characters that match near the same position, with a bonus for a shared prefix.
Best for "did you mean…" on people, places and product names.
'Jon', 'John'similarity → 0.93Kyori ranking
methods/kyoriA token-sensitive cost built for one job: ordering suggestions under a query someone is still typing.
- Folds case, Latin accents and hyphens first — café-bar and Cafe Bar match at cost 0.
- A typed prefix pays only for its typos. What you have not typed yet is free, and no typo is tolerated before the third character.
- The same words in a different order cost a flat 1.
- A completion that carries your words somewhere else costs little, and always comes after the ones that start with what you typed.
- Among equal costs, rank() puts the shorter completion first.
'São Paulo', 'sao paulo'distance → 0'new yrok', 'new york'distance → 1Kyori Index
indices/kyoriLoad a vocabulary once and search it on every keystroke. Every term comes back scored by kyori.rank() and sorted — best match first, shorter term first among equals.
- addMany(terms) at start-up, addOne(term) as new ones arrive.
- search(query) on each keystroke.
- Show the top of the list. It is already in order.
search('pan')→ pan 0 · pancake 0 · paint 1 · paper towels 1
How well does it rank? We measured it.
On more than 11,000 real Amazon searches held out of its design, Kyori’s ranking is level with Fuse.js, fuzzysort and sorting by length, and ahead of match-sorter. The progress report has the rules, the method and every number — including what popularity adds that no string score can.
Read the progress reportReady to drop in.