{"_id":"@jsonjoy.com/diff","name":"@jsonjoy.com/diff","dist-tags":{"latest":"18.30.0"},"versions":{"18.30.0":{"name":"@jsonjoy.com/diff","publishConfig":{"access":"public"},"version":"18.30.0","description":"Fast diff algorithms for strings, lines, and binary data","author":{"name":"streamich","url":"https://github.com/streamich"},"homepage":"https://github.com/streamich/json-joy/tree/master/packages/diff","repository":{"type":"git","url":"git+https://github.com/streamich/json-joy.git","directory":"packages/diff"},"license":"Apache-2.0","funding":{"type":"github","url":"https://github.com/sponsors/streamich"},"keywords":["diff","patch","text-diff","string-diff","binary-diff","line-diff","myers","diff-match-patch"],"peerDependencies":{"tslib":"*"},"devDependencies":{"@jsonjoy.com/buffers":"18.30.0","@jsonjoy.com/json-random":"18.30.0","@jsonjoy.com/util":"18.30.0","fast-diff":"^1.3.0"},"engines":{"node":">=10.0"},"main":"lib/index.js","types":"lib/index.d.ts","typings":"lib/index.d.ts","scripts":{"clean":"npx rimraf lib typedocs coverage gh-pages yarn-error.log","build":"tsc -b tsconfig.build.json","typedoc":"typedoc","typecheck":"tsc -b --noEmit"},"jest":{"preset":"ts-jest","testEnvironment":"node","moduleFileExtensions":["ts","js","tsx"],"transform":{"^.+\\.tsx?$":"ts-jest"},"transformIgnorePatterns":[".*/node_modules/.*"],"testRegex":".*/(__tests__|__jest__|demo)/.*(?<!\\.vi)\\.(test|spec)\\.tsx?$","rootDir":".","testPathIgnorePatterns":["node_modules","\\.vi\\.(test|spec)\\.tsx?$"]},"_id":"@jsonjoy.com/diff@18.30.0","bugs":{"url":"https://github.com/streamich/json-joy/issues"},"_integrity":"sha512-76rdp0ZBOWF7dODlI+M8TOTFjBdUHgQlLKjCHIr89rjJ8F2dAx9rroC3KgZivqxTBxiC+sZr2W2vOWFWqPNvQA==","_resolved":"/home/runner/work/json-joy/json-joy/packages/diff/package.tgz","_from":"file:package.tgz","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-76rdp0ZBOWF7dODlI+M8TOTFjBdUHgQlLKjCHIr89rjJ8F2dAx9rroC3KgZivqxTBxiC+sZr2W2vOWFWqPNvQA==","shasum":"1908ae63aa697ef6d612cf1b5a1c2f451e078520","tarball":"https://registry.npmjs.org/@jsonjoy.com/diff/-/diff-18.30.0.tgz","fileCount":79,"unpackedSize":413373,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@jsonjoy.com%2fdiff@18.30.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIClpNe9VfyApW7vBEsNHvJyEgizv+1bqk/DzrmXTSSfYAiEAvClQyrukf+F40jMaUcIeUj70U/ZpT/viyR1pzSmgOFc="}]},"_npmUser":{"name":"streamich","email":"vadimsdaleckis@gmail.com"},"directories":{},"maintainers":[{"name":"json-joy-team","email":"team@jsonjoy.com"},{"name":"streamich","email":"vadimsdaleckis@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/diff_18.30.0_1788858642395_0.309404516796457"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-08T09:10:42.261Z","18.30.0":"2026-09-08T09:10:42.531Z","modified":"2026-09-08T09:10:43.225Z"},"maintainers":[{"name":"json-joy-team","email":"team@jsonjoy.com"},{"name":"streamich","email":"vadimsdaleckis@gmail.com"}],"description":"Fast diff algorithms for strings, lines, and binary data","homepage":"https://github.com/streamich/json-joy/tree/master/packages/diff","keywords":["diff","patch","text-diff","string-diff","binary-diff","line-diff","myers","diff-match-patch"],"repository":{"type":"git","url":"git+https://github.com/streamich/json-joy.git","directory":"packages/diff"},"author":{"name":"streamich","url":"https://github.com/streamich"},"bugs":{"url":"https://github.com/streamich/json-joy/issues"},"license":"Apache-2.0","readme":"# `@jsonjoy.com/diff`\n\nFast diff algorithms for strings, lines, and binary data. Zero dependencies.\n\n- `str` — character-level string diffing (Myers-style, with common\n  prefix/suffix and overlap optimizations), patch normalization, inversion,\n  and caret-aware `diffEdit` for text inputs.\n- `line` — line-level diffing built on top of `str`, with per-line patch\n  aggregation and in-place modification (`MIX`) detection.\n- `lines` — token-based line diff: a faster, `MIX`-free alternative to `line`,\n  built on `tok`; returns a `line`-compatible `LinePatch`.\n- `bin` — `Uint8Array` diffing via a binary-to-string mapping over `str`.\n- `optimize` — optional patch post-processing: `coarsen` (cost models for\n  fewer or byte-cheaper operations), `align` (slide edits onto word/line\n  boundaries, losslessly), `semantic` (coarsen toward human-readable hunks),\n  and `shiftDown` (place hunk boundaries the way GNU `diff` places them).\n  Loaded only when imported, so `str`-only users don't pay for it.\n- `tok` — Myers diff over any token sequence `T[]` (interned to integers),\n  returning run-length operations.\n- `word` — word-level string diff built on `tok`; operation boundaries fall on\n  word boundaries. Returns a `str`-compatible patch.\n- `format` — the wire formats: turns a `LinePatch` plus its two line arrays into\n  diff text in all eight styles — `normal`, `unified` (`-u`/`-U n`), `context`\n  (`-c`/`-C n`), `ed` (`-e`), `forwardEd` (`-f`), `rcs` (`-n`), `sideBySide`\n  (`-y`) and `ifdef` (`-D` and the `--*-format` little languages) — each a\n  generator of chunks, so no diff text is ever assembled into one string.\n- `limits` — opt-in bounds for `str`, `lines` and `tok`: a deterministic\n  `maxCost`, an optional wall-clock `deadline`, and a `hitLimit` flag when the\n  result is no longer guaranteed minimal. Diffing is unbounded unless you pass\n  them. `defaultLineCost(n, m)` is the calibrated bound **for line-sized\n  elements**; there is no calibrated bound for character input, and using that\n  one on a large `str.diff` can be slower than passing nothing.\n\n```ts\nimport {str} from '@jsonjoy.com/diff';\n\nconst src = 'hello world';\nconst patch = str.diff(src, 'hello there world');\nstr.apply(patch, src.length, onInsert, onDelete);\nstr.src(patch); // 'hello world'\nstr.dst(patch); // 'hello there world'\n```\n\nOr deep-import a single module:\n\n```ts\nimport * as str from '@jsonjoy.com/diff/lib/str';\n```\n\nPost-improve a patch (opt-in, kept out of `str` so it is only loaded on demand):\n\n```ts\nimport * as str from '@jsonjoy.com/diff/lib/str';\nimport {coarsen, byteSizeModel} from '@jsonjoy.com/diff/lib/optimize';\n\nconst patch = str.diff(src, dst); // minimal, most operations\ncoarsen(patch); // fewer operations, larger edit volume\ncoarsen(patch, byteSizeModel()); // smaller serialized size\n```\n\n```ts\nimport * as str from '@jsonjoy.com/diff/lib/str';\nimport {align} from '@jsonjoy.com/diff/lib/optimize';\n\n// Move edits onto word/line boundaries without changing what is inserted or deleted.\nalign(str.diff('The cat.', 'The cow and the cat.'));\n// [[0, 'The '], [1, 'cow and the '], [0, 'cat.']]\n```\n\n```ts\nimport * as word from '@jsonjoy.com/diff/lib/word';\n\nword.diff('the cat runs', 'the dog runs');\n// [[0, 'the '], [-1, 'cat'], [1, 'dog'], [0, ' runs']]\n```\n\nWrite a diff. Lines carry no terminators, so a file that does not end in one is\na flag: `diffKeys` encodes it for the *diff* — without it two files differing\nonly in their final newline compare equal and you get no diff at all — and\n`srcNoEol`/`dstNoEol` place the `\\ No newline at end of file` marker in the\noutput. Diff the keys, format the lines:\n\n```ts\nimport * as lines from '@jsonjoy.com/diff/lib/lines';\nimport {diffKeys, unified} from '@jsonjoy.com/diff/lib/format';\n\n/** 'a\\nb' is two lines, the second unterminated. */\nconst split = (text: string): [lines: string[], noEol: boolean] => {\n  if (text === '') return [[], false];\n  const noEol = !text.endsWith('\\n');\n  return [(noEol ? text : text.slice(0, -1)).split('\\n'), noEol];\n};\n\nconst [src, srcNoEol] = split(await readFile('a.txt', 'utf8'));\nconst [dst, dstNoEol] = split(await readFile('b.txt', 'utf8'));\nconst patch = lines.diff(diffKeys(src, srcNoEol), diffKeys(dst, dstNoEol));\nfor (const chunk of unified(src, dst, patch, {oldName: 'a.txt', newName: 'b.txt', srcNoEol, dstNoEol}))\n  process.stdout.write(chunk);\n```\n\nThe other seven styles take the same three arguments; `context` also takes labels\nand a width, `ed` and `rcs` carry neither:\n\n```ts\nimport {context, ed, forwardEd, ifdef, normal, rcs, sideBySide} from '@jsonjoy.com/diff/lib/format';\n\nnormal(src, dst, patch); // 3a4 / 5d4 / 7,8c6,9\ncontext(src, dst, patch, {context: 1, oldName: 'a.txt', newName: 'b.txt'});\ned(src, dst, patch); // an ed script, bottom-up\nforwardEd(src, dst, patch); // the same commands top-down: c2 / d2 3\nrcs(src, dst, patch); // d3 1 / a3 2\nsideBySide(src, dst, patch, {width: 80}); // two columns with a | / < / > gutter\nifdef(src, dst, patch, {\n  groupFormat: ['%=', '#ifndef X\\n%<#endif /* ! X */\\n', '#ifdef X\\n%>#endif /* X */\\n', '%<%>'],\n  lineFormat: ['%l\\n', '%l\\n', '%l\\n'],\n});\n```\n\nThe last two print **both files**, so unlike every other style they write\nsomething when nothing changed — a caller wanting GNU's exit-code semantics\nreads `hunks()` for the verdict and these for the output.\n\nRead a diff. The style is detected, prose and mail headers around the hunks are\nskipped, a multi-file patch splits into one `FilePatch` each, and a malformed\nhunk is *reported* rather than thrown — so a caller can reject that hunk and\nstill apply the rest, which is what a `.rej` file is:\n\n```ts\nimport {parse} from '@jsonjoy.com/diff/lib/format';\n\nfor (const file of parse(await readFile('fix.patch', 'utf8'))) {\n  console.log(file.oldName, '->', file.newName, file.meta?.renameFrom ? '(renamed)' : '');\n  for (const {code, line, message} of file.errors) console.warn(`line ${line}: ${code}: ${message}`);\n  for (const hunk of file.hunks) console.log(hunk.oldStart, hunk.oldCount, hunk.lines.length);\n}\n```\n\nNames come back exactly as written, `a/`…`b/` prefixes and `/dev/null` included,\nso a `-p` strip applies to what was on the wire. Two limits are the formats', not\nthe parser's: an `ed` script records neither the deleted text (its hunks carry\n`oldCount` and no `DEL` lines — a blind delete) nor a missing final newline, and\n`rcs` is not read back at all, since a `d3 1` names lines whose text the format\nnever wrote down.\n","readmeFilename":"README.md","_rev":"1-565454396a7b0f33ce38d976422fbc15"}