Releases: DoneDeal0/superdiff
v4.2.2
v4.2.1
Small patch to ensure the demo GIF of streamListDiff and the Superdiff donors' image are displayed correctly.
Previous release: 🌎 getGeoDiff
v4.2.0
🌎 NEW FEATURE: getGeoDiff
import { getGeoDiff } from "@donedeal0/superdiff";Returns a structured diff between two geographical coordinates. Supports 9 distance units, locale‑aware output, and two accuracy modes.
- High‑accuracy mode is based on the Vincenty formulae (ellipsoidal Earth model, higher precision).
- Normal-accuracy mode is based on the Haversine formulae (spherical Earth model, faster, slightly less precise).
FORMAT
Input
previousCoordinates: [number, number] | null | undefined;
coordinates: [number, number] | null | undefined;
options?: {
unit?: "centimeter" | "foot" | "inch" | "kilometer" | "meter" | "mile" | "mile-scandinavian" | "millimeter" | "yard"; // "kilometer" by default
accuracy?: "normal" | "high"; // "normal" by default
maxDecimals?: number; // 2 by default,
locale?: Intl.Locale | string; // "en-US" by default
}previousCoordinates: the original coordinates ([Longitude, Latitude]).coordinates: the new coordinates ([Longitude, Latitude]).optionsunit: the unit used for the returned distance.accuracy:normal(default): fastest mode, with a small error margin, based on Haversine formula.high: slower but highly precise distance. Based on Vincenty formula.
maxDecimals: maximal decimals for the distance. Defaults to 2.locale: the locale of your distance. Enables a locale‑aware distance label.
Output
type GeoDiff = {
type: "geo";
status: "added" | "deleted" | "error" | "equal" | "updated";
diff: {
coordinates: [number, number] | null;
previousCoordinates: [number, number] | null;
distance: number;
unit: "centimeter" | "foot" | "inch" | "kilometer" | "meter" | "mile" | "mile-scandinavian" | "millimeter" | "yard";
label: string,
direction: "east" | "north" | "south" | "west" | "north-east" | "north-west" | "south-east" | "south-west" | "stationary";
};
}USAGE
Input
getGeoDiff(
- [2.3522, 48.8566],
+ [-0.1278, 51.5074]
);Coordinates follow GeoJSON order: [longitude, latitude].
Output
{
type: "geo",
+ status: "updated",
diff: {
+ coordinates: [-0.1278, 51.5074],
previousCoordinates: [2.3522, 48.8566],
+ direction: "north-west",
+ distance: 343.56,
+ label: "343.56 kilometers",
+ unit: "kilometer"
}
}See our website documentation
v4.1.0
🚀 NEW FEATURE: getTextDiff
import { getTextDiff } from "@donedeal0/superdiff";Compares two texts and returns a structured diff at a character, word, or sentence level.
FORMAT
Input
previousText: string | null | undefined,
currentText: string | null | undefined,
options?: {
separation?: "character" | "word" | "sentence", // "word" by default
accuracy?: "normal" | "high", // "normal" by default
detectMoves?: boolean // false by default
ignoreCase?: boolean, // false by default
ignorePunctuation?: boolean, // false by default
locale?: Intl.Locale | string // undefined by default
}previousText: the original text.currentText: the current text.optionsseparationwhether you want acharacter,wordorsentencebased diff.accuracy:normal(default): fastest mode, simple tokenization.high: slower but exact tokenization. Handles all language subtleties (Unicode, emoji, CJK scripts, locale‑aware segmentation when a locale is provided).
detectMoves:false(default): optimized for readability. Token moves are ignored so insertions don’t cascade and break equality (recommended for UI diffing).true: semantically precise, but noiser — a single insertion shifts all following tokens, breaking equality.
ignoreCase: iftrue,helloandHELLOare considered equal.ignorePunctuation: iftrue,hello!andhelloare considered equal.locale: the locale of your text. Enables locale‑aware segmentation in high accuracy mode.
Output
type TextDiff = {
type: "text";
status: "added" | "deleted" | "equal" | "updated";
diff: {
value: string;
index: number | null;
previousValue?: string;
previousIndex: number | null;
status: "added" | "deleted" | "equal" | "moved" | "updated";
}[];
};USAGE
WITHOUT MOVES DETECTION
This is the default output. Token moves are ignored so insertions don’t cascade and break equality. Updates are rendered as two entries (added + deleted). The algorithm uses longest common subsequence (LCS), similar to GitHub diffs.
Input
getTextDiff(
- "The brown fox jumped high",
+ "The orange cat has jumped",
{ detectMoves: false, separation: "word" }
);Output
{
type: "text",
+ status: "updated",
diff: [
{
value: 'The',
index: 0,
previousIndex: 0,
status: 'equal',
},
- {
- value: "brown",
- index: null,
- previousIndex: 1,
- status: "deleted",
- },
- {
- value: "fox",
- index: null,
- previousIndex: 2,
- status: "deleted",
- },
+ {
+ value: "orange",
+ index: 1,
+ previousIndex: null,
+ status: "added",
+ },
+ {
+ value: "cat",
+ index: 2,
+ previousIndex: null,
+ status: "added",
+ },
+ {
+ value: "has",
+ index: 3,
+ previousIndex: null,
+ status: "added",
+ },
{
value: "jumped",
index: 4,
previousIndex: 3,
status: "equal",
},
- {
- value: "high",
- index: null,
- previousIndex: 4,
- status: "deleted",
- }
],
}WITH MOVE DETECTION
If you prefer a semantically precise diff, activate the detectMoves option. Direct token swaps are considered updated.
Input
getTextDiff(
- "The brown fox jumped high",
+ "The orange cat has jumped",
{ detectMoves: true, separation: "word" }
);Output
{
type: "text",
+ status: "updated",
diff: [
{
value: 'The',
index: 0,
previousIndex: 0,
status: 'equal',
},
+ {
+ value: "orange",
+ index: 1,
+ previousValue: "brown",
+ previousIndex: null,
+ status: "updated",
+ },
+ {
+ value: "cat",
+ index: 2,
+ previousValue: "fox",
+ previousIndex: null,
+ status: "updated",
+ },
+ {
+ value: "has",
+ index: 3,
+ previousIndex: null,
+ status: "added",
+ },
+ {
+ value: "jumped",
+ index: 4,
+ previousIndex: 3,
+ status: "moved",
+ },
- {
- value: "high",
- index: null,
- previousIndex: 4,
- status: "deleted",
- }
],
}📊 BENCHMARK
| Scenario | Superdiff | diff |
|---|---|---|
| 10k words | 1.13 ms | 3.68 ms |
| 100k words | 21.68 ms | 45.93 ms |
| 10k sentences | 2.30 ms | 5.61 ms |
| 100k sentences | 21.95 ms | 62.03 ms |
(Superdiff uses its normal accuracy settings to match diff's behavior)
v4.0.0
BREAKING CHANGES
👉 These changes improve the developer experience by unifying naming conventions across all diff utilities and simplifying the output format. They are minimal and should require only small updates in your codebase.
- Removed
isEqualandisObjectfrom the library (they are still used internally). - Unified naming conventions across all diff:
getListDiff
- Renamed
newIndex→index - Renamed
prevIndex→previousIndex - Removed
indexDiff(it can be computed from index and previousIndex if needed) - Renamed the option
referenceProperty→referenceKey
diff: {
value: unknown;
- newIndex: number | null;
+ index: number | null;
- prevIndex: number | null;
+ previousIndex: number | null;
- indexDiff: number | null;
status: ListStatus;
}[];getStreamDiff:
- Renamed
currentValue→value - Renamed
newIndex→index - Renamed
prevIndex→previousIndex - Removed
indexDiff(same reason as above)
diff: {
- currentValue: unknown;
+ value: unknown;
- newIndex: number | null;
+ index: number | null;
- prevIndex: number | null;
+ previousIndex: number | null;
- indexDiff: number | null;
status: ListStatus;
}[];getObjectDiff:
- Renamed
property→key - Renamed
currentValue→value
diff: {
- property: string;
+ key: string;
- currentValue: unknown;
+ value: unknown;
previousValue: unknown;
status: ObjectStatus;
diff?: Diff[];
};v3.2.0
⚡ BOOST PERFORMANCE OF getObjectDiff & getListDiff
-
Rewrote
getObjectDiffandgetListDifffor maximum performance.
Due to aggressive optimizations, the code is less readable, but the performance gain justifies the tradeoff. -
Added a benchmark comparing Superdiff with its main competitors.
-
Updated several dev dependencies.
📊 BENCHMARK
Environment: Node.js 24.12.0 (LTS) • MacBook Pro M2 (2023, Sequoia 15.1) • 16GB RAM.
Method: Warm up runs, then each script is executed 20 times, and we keep the median time. To minimize garbage collection and cross‑benchmark interference, all scenarios are run individually. All benchmark scripts are included so you can reproduce the results locally.
List diff
| Scenario | Superdiff | arr-diff | deep-diff |
|---|---|---|---|
| 10k items array | 1.84 ms | 32.95 ms | 4.74 ms |
| 100k items array | 17.43 ms | 3363.15 ms | 50.36 ms |
Object diff
| Scenario | Superdiff | deep-object-diff | deep-diff |
|---|---|---|---|
| 10k flat object keys | 2.27 ms | 2.44 ms | 39.37 ms |
| 100k flat object keys | 29.23 ms | 31.86 ms | 3784.50 ms |
| 100k nested nodes | 4.25 ms | 9.67 ms | 16.51 ms |
👉 Despite providing a full structural diff with a richer output, Superdiff is the fastest. It also scales linearly, even with deeply nested data.
v3.1.2
DOCUMENTATION
Add the link to the documentation website: https://superdiff.gitbook.io/donedeal0-superdiff (#33 )

v3.1.1
Bug Fix
- Fix the subpath exports that broke the library import. (#32)
ℹ️ Please refer to the latest release note for information on the new features.
v3.1.0
FEATURE
- Add a
useWorkeroption tostreamListDiff. If set totrue, the diff will be run in a worker for maximum performance. Only recommended for large lists (e.g. +100,000 items).trueby default.
const diff = streamListDiff(hugeListA, hugeListB, "id", { chunksSize: 100_000, useWorker: true });pull request #31
BREAKING CHANGES
- Enums standardization
OBJECT_STATUSbecomesObjectStatusLIST_STATUSbecomesListStatusGRANULARITYbecomesGranularity
CHORE
- Add a
showWarningsoption tostreamListDiff - Allow enum and unions for
ListStatus - Clean models
- Fix
clientandserverimport subpaths - Convert Jest config to Typescript
- Update dev dependencies
- Update
README.md
v3.0.0
New Feature
streamListDiffnow supports array, stream, and file inputs for maximum performance, polyvalence, and optimal memory usage. 🎸🦅 (#28).
Import
// If you are in a server environment
import { streamListDiff } from "@donedeal0/superdiff/server";
// If you are in a browser environment
import { streamListDiff } from "@donedeal0/superdiff/client";Input
You can send streams, file paths, or arrays as input:
If you are in a server environment
// for a simple array
const stream = [{ id: 1, name: "hello" }]
// for a large array
const stream = Readable.from(list, { objectMode: true });
// for a local file
const stream = path.resolve(__dirname, "./list.json");
If you are in a browser environment
// for a simple array
const stream = [{ id: 1, name: "hello" }]
// for a large array
const stream = new ReadableStream({
start(controller) {
list.forEach((value) => controller.enqueue(value));
controller.close();
},
});
// for a local file
const stream = new File([JSON.stringify(file)], "file.json", { type: "application/json" });
// for a file input
const stream = e.target.files[0]; See the documentation for more details.
Improvement
- Improve the execution time of
getObjectDiff⚡️(#30). - Rewrite the
getObjectDiffcode which is now cleaner and more elegant.
Chores
- Update the documentation.
- Create two subbuilds:
clientandserverto expose an appropriate version ofstreamListDiff.
BREAKING CHANGE
streamListDiffis no longer imported from the index, but from@donedeal0/superdiff/serveror@donedeal0/superdiff/client.