{"_id":"@crob/damerau-levenshtein","_rev":"3-198ffc87cfe3ff9cec16d65518aab93c","name":"@crob/damerau-levenshtein","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@crob/damerau-levenshtein","version":"1.0.0","keywords":["damerau-levenshtein","edit distance"],"author":{"name":"Robert-Cristian Chiribuc"},"license":"MIT","_id":"@crob/damerau-levenshtein@1.0.0","maintainers":[{"name":"crobert","email":"me@crobert.dev"}],"homepage":"https://github.com/chiribuc/damerau-levenshtein#readme","bugs":{"url":"https://github.com/chiribuc/damerau-levenshtein/issues"},"dist":{"shasum":"1a9af192506e084be5aee1975d369fea744965a6","tarball":"https://registry.npmjs.org/@crob/damerau-levenshtein/-/damerau-levenshtein-1.0.0.tgz","fileCount":3,"integrity":"sha512-DQ5AymMQfrC3lJ3rIolfQW6ZLnthnlRLdmtyY/MSNs32Q6nyLTt1Pq1vC0YE9Lwgp1sPm4f7taYHOzPqcYLK6w==","signatures":[{"sig":"MEYCIQCLwqjul8FVKXpF4Q94ULnuNoSwMPFyffyu75xN9e8evQIhAPbm6/uO6e85WyX/cdEUKduUqFD0lrijoS4UD250NSNN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":6424},"jest":{"transform":{"^.+\\.js$":"babel-jest"}},"main":"dist/damerau-levenshtein.umd.js","module":"dist/damerau-levenshtein.es.js","exports":{".":{"import":"./dist/damerau-levenshtein.es.js","require":"./dist/damerau-levenshtein.umd.js"}},"gitHead":"b5d7f474f7d991cbc3c0dc6b9add58a270235494","scripts":{"test":"jest","build":"vite build"},"_npmUser":{"name":"crobert","email":"me@crobert.dev"},"repository":{"url":"git+https://github.com/chiribuc/damerau-levenshtein.git","type":"git"},"_npmVersion":"9.8.1","description":"Implementation of the Damerau-Levenshtein edit distance.","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","vite":"^4.5.0","prettier":"^3.0.3","babel-jest":"^29.7.0","@babel/core":"^7.23.2","@babel/preset-env":"^7.23.2"},"_npmOperationalInternal":{"tmp":"tmp/damerau-levenshtein_1.0.0_1700324388101_0.29779834290139817","host":"s3://npm-registry-packages"}},"1.0.1":{"name":"@crob/damerau-levenshtein","version":"1.0.1","keywords":["damerau-levenshtein","edit distance"],"author":{"name":"Robert-Cristian Chiribuc"},"license":"MIT","_id":"@crob/damerau-levenshtein@1.0.1","maintainers":[{"name":"crobert","email":"me@crobert.dev"}],"homepage":"https://github.com/chiribuc/damerau-levenshtein#readme","bugs":{"url":"https://github.com/chiribuc/damerau-levenshtein/issues"},"dist":{"shasum":"511250a59fc629015432a5cd53bcea500ad7eae9","tarball":"https://registry.npmjs.org/@crob/damerau-levenshtein/-/damerau-levenshtein-1.0.1.tgz","fileCount":7,"integrity":"sha512-TTGsAIM85cu1qjbuT035j09OtbpjDaYtsfrsslQRpVlHnQz5QmzrdleCRpfiORPCuGYC0oCJlkjjBC+O9bifhg==","signatures":[{"sig":"MEQCICk2qowQlI+dzihXXNWvroznVcPvns314sK2ti/rwqPEAiBmyJFNZPy8ahSdeY0TkJLeqgsojVVaWdXgXkq2ls099w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":16127},"jest":{"transform":{"^.+\\.js$":"babel-jest"}},"main":"dist/damerau-levenshtein.umd.js","module":"dist/damerau-levenshtein.es.js","exports":{".":{"import":"./dist/damerau-levenshtein.es.js","require":"./dist/damerau-levenshtein.umd.js"}},"gitHead":"aa4d9236af4a96602bb28fa2686603d3ef8f966c","scripts":{"test":"jest","build":"vite build"},"_npmUser":{"name":"crobert","email":"me@crobert.dev"},"repository":{"url":"git+https://github.com/chiribuc/damerau-levenshtein.git","type":"git"},"_npmVersion":"10.2.3","description":"Implementation of the Damerau-Levenshtein edit distance.","directories":{},"_nodeVersion":"21.2.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","vite":"5.0.5","prettier":"^3.0.3","babel-jest":"^29.7.0","@babel/core":"^7.23.2","@babel/preset-env":"^7.23.2"},"_npmOperationalInternal":{"tmp":"tmp/damerau-levenshtein_1.0.1_1704972201210_0.9357279015947404","host":"s3://npm-registry-packages"}},"2.0.0":{"name":"@crob/damerau-levenshtein","version":"2.0.0","description":"Fast, dependency-free Damerau-Levenshtein edit distance with configurable operation costs.","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","unpkg":"./dist/index.umd.cjs","jsdelivr":"./dist/index.umd.cjs","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"engines":{"node":"^20.19.0 || >=22.12.0"},"scripts":{"build":"vite build && tsc -p tsconfig.build.json && node scripts/emit-cjs-types.mjs","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","format":"prettier --write .","format:check":"prettier --check .","lint:package":"publint && attw --pack .","prepublishOnly":"npm run build"},"devDependencies":{"@arethetypeswrong/cli":"^0.18.5","@vitest/coverage-v8":"^4.1.10","fast-check":"^4.9.0","prettier":"^3.9.6","publint":"^0.3.23","typescript":"^7.0.2","vite":"^8.2.1","vitest":"^4.1.10"},"repository":{"type":"git","url":"git+https://github.com/chiribuc/damerau-levenshtein.git"},"bugs":{"url":"https://github.com/chiribuc/damerau-levenshtein/issues"},"homepage":"https://github.com/chiribuc/damerau-levenshtein#readme","keywords":["damerau-levenshtein","levenshtein","edit-distance","string-distance","fuzzy-search","spell-check","typescript"],"author":{"name":"Robert-Cristian Chiribuc"},"license":"MIT","publishConfig":{"access":"public"},"gitHead":"d0fcb726e4a052286a1ed23ff233438c8e48cd5f","_id":"@crob/damerau-levenshtein@2.0.0","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-29TGDXVoN4/kYHPEwYOTYOU4+V+wE7afxnRpUVly1X4s7HzTbUgF0ltp+9zHw4sxvbCgyF+BZMITn9WZyrp9FA==","shasum":"ee80ecc221d182f80e43a079515560c9b69e50b9","tarball":"https://registry.npmjs.org/@crob/damerau-levenshtein/-/damerau-levenshtein-2.0.0.tgz","fileCount":25,"unpackedSize":73210,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@crob%2fdamerau-levenshtein@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDgEaoRnnkMagn9DG6T0coP1O2vDGr6wKZw6AvztWHbuQIhAOjEyrfq0J1ybLAb2OJSgr23iEgXgO6fqWyQMFgboCEb"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:76e9dee1-4983-40d1-a2fa-7e96f89ed62d"}},"directories":{},"maintainers":[{"name":"crobert","email":"me@crobert.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/damerau-levenshtein_2.0.0_1786794340791_0.2616000545049395"},"_hasShrinkwrap":false}},"time":{"created":"2023-11-18T16:19:48.026Z","modified":"2026-08-15T11:45:41.347Z","1.0.0":"2023-11-18T16:19:48.292Z","1.0.1":"2024-01-11T11:23:21.411Z","2.0.0":"2026-08-15T11:45:40.942Z"},"bugs":{"url":"https://github.com/chiribuc/damerau-levenshtein/issues"},"author":{"name":"Robert-Cristian Chiribuc"},"license":"MIT","homepage":"https://github.com/chiribuc/damerau-levenshtein#readme","keywords":["damerau-levenshtein","levenshtein","edit-distance","string-distance","fuzzy-search","spell-check","typescript"],"repository":{"type":"git","url":"git+https://github.com/chiribuc/damerau-levenshtein.git"},"description":"Fast, dependency-free Damerau-Levenshtein edit distance with configurable operation costs.","maintainers":[{"name":"crobert","email":"me@crobert.dev"}],"readme":"# Damerau-Levenshtein\n\n[![npm version](https://img.shields.io/npm/v/@crob/damerau-levenshtein.svg)](https://www.npmjs.com/package/@crob/damerau-levenshtein)\n[![CI](https://github.com/chiribuc/damerau-levenshtein/actions/workflows/ci.yml/badge.svg)](https://github.com/chiribuc/damerau-levenshtein/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/@crob/damerau-levenshtein.svg)](./LICENSE)\n\nThe Damerau-Levenshtein edit distance: the cost of turning one string into another using\ninsertions, removals, substitutions, and transpositions of adjacent characters.\n\n- **Dependency-free** and tiny — about 1.2 kB gzipped.\n- **TypeScript-first**, with types for every cost callback.\n- **Configurable costs** per operation, as flat numbers or per-character functions.\n- **Unicode-aware**: compares code points, so emoji count as one character.\n- **Linear memory**: O(min(n, m)), so long strings stay cheap.\n- Ships **ESM, CommonJS and UMD** builds.\n\n## Installation\n\n```sh\nnpm i @crob/damerau-levenshtein\n```\n\n## Usage\n\n```ts\nimport { DamerauLevenshtein } from \"@crob/damerau-levenshtein\";\n\nconst dl = new DamerauLevenshtein();\n\ndl.distance(\"kitten\", \"sitting\"); // 3\ndl.distance(\"ab\", \"ba\"); // 1 — one transposition\n```\n\nComparing a single pair? Use the `distance` helper instead:\n\n```ts\nimport { distance } from \"@crob/damerau-levenshtein\";\n\ndistance(\"kitten\", \"sitting\"); // 3\ndistance(\"ab\", \"ba\", { transpositions: false }); // 2\n```\n\nThe class is also available as a default export, and the package works from CommonJS:\n\n```js\nconst { DamerauLevenshtein, distance } = require(\"@crob/damerau-levenshtein\");\n```\n\nIn the browser, the UMD build exposes everything on `window.DamerauLevenshtein`:\n\n```html\n<script src=\"https://unpkg.com/@crob/damerau-levenshtein\"></script>\n<script>\n  const { DamerauLevenshtein } = window.DamerauLevenshtein;\n  console.log(new DamerauLevenshtein().distance(\"hello\", \"world\")); // 4\n</script>\n```\n\n## API\n\n### `new DamerauLevenshtein(options?)`\n\nCreates a reusable calculator. Options are validated once, at construction, so build the\ninstance once and reuse it across comparisons.\n\n| Option           | Type                                      | Default | Description                                               |\n| ---------------- | ----------------------------------------- | ------- | --------------------------------------------------------- |\n| `insert`         | `number \\| (char) => number`              | `1`     | Cost of inserting a character from the target.            |\n| `remove`         | `number \\| (char) => number`              | `1`     | Cost of removing a character from the source.             |\n| `substitute`     | `number \\| (from, to) => number`          | `1`     | Cost of replacing one character with another.             |\n| `transpose`      | `number \\| (backward, forward) => number` | `1`     | Cost of swapping two adjacent characters.                 |\n| `transpositions` | `boolean`                                 | `true`  | Set to `false` to compute the plain Levenshtein distance. |\n\nCosts must be finite and `>= 0`. Unknown options, out-of-range costs, and wrongly typed\nvalues throw rather than being silently ignored.\n\n### `dl.distance(source, target)`\n\nReturns the cost of transforming `source` into `target`. Throws a `TypeError` if either\nargument is not a string.\n\n### `distance(source, target, options?)`\n\nOne-shot convenience wrapper that constructs a `DamerauLevenshtein` and calls `distance`.\n\n### Types\n\n`DamerauLevenshteinOptions`, `CharCostFunction`, `SubstituteCostFunction`,\n`TransposeCostFunction`, and `Cost` are all exported.\n\n## Custom costs\n\nCosts can be flat numbers or functions of the characters involved — useful when, say, a\ntypo on an adjacent keyboard key should count for less than an unrelated character:\n\n```ts\nconst dl = new DamerauLevenshtein({\n  insert: 1,\n  remove: 0.5,\n  substitute: (from, to) => (from === \"a\" && to === \"e\" ? 0.3 : 1),\n  transpose: (backward, forward) => (backward === \"n\" ? 0.5 : 1),\n});\n\ndl.distance(\"apple\", \"epple\"); // 0.3\n```\n\n`substitute` receives `(from, to)`: the source character and the target character it is\nreplaced with. `transpose` receives `(backward, forward)`: the two adjacent source\ncharacters being swapped, in the order they appear in `source`.\n\nTo compute the plain Levenshtein distance, turn transpositions off:\n\n```ts\nnew DamerauLevenshtein({ transpositions: false }).distance(\"ab\", \"ba\"); // 2\n```\n\n## Unicode\n\nStrings are compared by code point, so characters outside the Basic Multilingual Plane\ncount as a single character:\n\n```ts\ndistance(\"😀🎉\", \"🎉😀\"); // 1\n```\n\nCombining marks are _not_ normalized, so a precomposed `\"\\u00e9\"` and a decomposed\n`\"e\\u0301\"` are two different strings (distance `2`). Normalize both sides first if you\nwant them treated as equal:\n\n```ts\ndistance(\"\\u00e9\".normalize(\"NFC\"), \"e\\u0301\".normalize(\"NFC\")); // 0\n```\n\n## Which algorithm is this?\n\nThis is the **optimal string alignment** (OSA) variant of Damerau-Levenshtein, also called\nthe restricted edit distance: no substring is edited more than once. It is what most\nspell-checking and fuzzy-search code wants, and it is what the great majority of\n\"damerau-levenshtein\" packages implement.\n\nThe practical consequence is that a transposition whose characters need further editing is\nnot shared across operations:\n\n```ts\ndistance(\"a cat\", \"an act\"); // 2 — insert \"n\", transpose \"c\" and \"a\"\ndistance(\"a cat\", \"an abct\"); // 4 — an unrestricted implementation reports 3\n```\n\nOSA is also not a true metric: it does not satisfy the triangle inequality. Turn\ntranspositions off to get plain Levenshtein, which does.\n\nComplexity is O(n·m) time and O(min(n, m)) memory, where n and m are the code-point lengths\nof the two strings.\n\n## Example: spelling suggestions\n\n```ts\nimport { DamerauLevenshtein } from \"@crob/damerau-levenshtein\";\n\nconst dl = new DamerauLevenshtein();\nconst dictionary = [\"apple\", \"banana\", \"orange\", \"pear\", \"peach\", \"pineapple\", \"plum\"];\n\nfunction suggest(query: string, limit = 3): string[] {\n  return dictionary\n    .map((word) => ({ word, score: dl.distance(query.toLowerCase(), word) }))\n    .sort((a, b) => a.score - b.score)\n    .slice(0, limit)\n    .map(({ word }) => word);\n}\n\nsuggest(\"aplpe\"); // [\"apple\", \"plum\", \"pear\"]\n```\n\nA runnable version lives in [`example/index.html`](./example/index.html).\n\n## Migrating from 1.x\n\nVersion 2 is a breaking release. The headline change: **transpositions are now on by\ndefault**, which is what the class name and the old documentation always implied.\n\n| 1.x                                 | 2.x                                                 |\n| ----------------------------------- | --------------------------------------------------- |\n| `new DamerauLevenshtein()`          | `new DamerauLevenshtein({ transpositions: false })` |\n| `new DamerauLevenshtein({}, true)`  | `new DamerauLevenshtein()`                          |\n| `new DamerauLevenshtein({}, false)` | `new DamerauLevenshtein({ transpositions: false })` |\n| `const DL = require(\"...\")`         | `const { DamerauLevenshtein } = require(\"...\")`     |\n\nPassing a second positional argument now throws with a pointer to this table, so the\nchange cannot silently alter your results.\n\nAlso worth knowing:\n\n- **Transpositions no longer switch themselves on.** In 1.x, supplying a `transpose` cost\n  forced transpositions on even when they were explicitly disabled. Use `transpositions`.\n- **`transpose` argument order was fixed.** It was documented as `(backward, forward)` but\n  called as `(forward, backward)`. It now matches the documentation.\n- **Custom `insert`/`remove` costs now apply to every edit.** In 1.x the first row and\n  column of the distance matrix were hard-coded to 1 per character, so\n  `new DamerauLevenshtein({ remove: 3 }).distance(\"abc\", \"\")` returned `3` instead of `9`.\n- **Strings are compared by code point**, not UTF-16 code unit, so distances involving\n  emoji and other astral characters changed.\n- **Invalid options throw.** A misspelled option or a non-numeric cost used to be silently\n  replaced with the default.\n- **Node 20.19+ is required**, and the published package now ships type declarations.\n\n## Development\n\n```sh\nnpm ci\nnpm test            # vitest, including property-based tests\nnpm run test:coverage\nnpm run typecheck\nnpm run build       # ESM + CJS + UMD bundles and .d.ts declarations\nnpm run lint:package\n```\n\n## Releasing\n\n1. Bump `version` in `package.json` and land it on `main`.\n2. Publish a GitHub release whose tag matches that version (`2.0.0` or `v2.0.0`).\n3. The [publish workflow](.github/workflows/publish.yml) typechecks, tests, builds, and\n   verifies the tag against `package.json` before running `npm publish --provenance`.\n\nThe workflow authenticates with the `NPM_TOKEN` repository secret. Configuring\n[npm trusted publishing](https://docs.npmjs.com/trusted-publishers) for this package is\nworth doing: it drops the secret entirely in favour of the workflow's OIDC identity.\n\n## Contributing\n\nIssues and pull requests are welcome on\n[GitHub](https://github.com/chiribuc/damerau-levenshtein). Please keep the test suite green\nand run `npm run format` before opening a PR.\n\nThanks to everyone who has\n[contributed](https://github.com/chiribuc/damerau-levenshtein/graphs/contributors) — see\nthe [changelog](./CHANGELOG.md) for what each release brought.\n\n## License\n\n[MIT](./LICENSE) © Robert-Cristian Chiribuc\n","readmeFilename":"README.md"}