Skip to content

Releases: DoneDeal0/superdiff

v4.2.2

10 Mar 19:40
5f7d6c4

Choose a tag to compare

4.2.2 (2026-03-10)

Bug Fixes

v4.2.1

22 Feb 10:56
4862209

Choose a tag to compare

Small patch to ensure the demo GIF of streamListDiff and the Superdiff donors' image are displayed correctly.

Previous release: 🌎 getGeoDiff

Website documentation
Release note

v4.2.0

22 Feb 10:43
6666f4f

Choose a tag to compare

🌎 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]).
  • options
    • unit: 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

15 Feb 10:59
c52ff35

Choose a tag to compare

🚀 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.
  • options
    • separation whether you want a character, word or sentence based 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: if true, hello and HELLO are considered equal.
    • ignorePunctuation: if true, hello! and hello are 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

19 Jan 20:06

Choose a tag to compare

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 isEqual and isObject from the library (they are still used internally).
  • Unified naming conventions across all diff:

getListDiff

  • Renamed newIndexindex
  • Renamed prevIndexpreviousIndex
  • Removed indexDiff (it can be computed from index and previousIndex if needed)
  • Renamed the option referencePropertyreferenceKey
diff: {
    value: unknown;
-   newIndex: number | null;
+   index: number | null;
-   prevIndex: number | null;
+   previousIndex: number | null;
-   indexDiff: number | null;
    status: ListStatus;
  }[];

getStreamDiff:

  • Renamed currentValuevalue
  • Renamed newIndexindex
  • Renamed prevIndexpreviousIndex
  • 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 propertykey
  • Renamed currentValuevalue
diff: {
-  property: string;
+  key: string;
-  currentValue: unknown;
+  value: unknown;
   previousValue: unknown;
   status: ObjectStatus;
   diff?: Diff[];
 };

v3.2.0

12 Jan 20:00

Choose a tag to compare

⚡ BOOST PERFORMANCE OF getObjectDiff & getListDiff

  • Rewrote getObjectDiff and getListDiff for 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.

#35

v3.1.2

03 Nov 20:39
d21a35f

Choose a tag to compare

DOCUMENTATION

Add the link to the documentation website: https://superdiff.gitbook.io/donedeal0-superdiff (#33 )

Capture d’écran 2024-11-03 à 21 46 15

v3.1.1

02 Nov 20:37
e77b875

Choose a tag to compare

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

02 Nov 17:06
40094fc

Choose a tag to compare

FEATURE

  • Add a useWorker option to streamListDiff. If set to true, the diff will be run in a worker for maximum performance. Only recommended for large lists (e.g. +100,000 items). true by default.
const diff = streamListDiff(hugeListA, hugeListB, "id", { chunksSize: 100_000,  useWorker: true });

pull request #31

BREAKING CHANGES

  • Enums standardization
    • OBJECT_STATUS becomes ObjectStatus
    • LIST_STATUS becomes ListStatus
    • GRANULARITY becomes Granularity

CHORE

  • Add a showWarnings option to streamListDiff
  • Allow enum and unions for ListStatus
  • Clean models
  • Fix client and server import subpaths
  • Convert Jest config to Typescript
  • Update dev dependencies
  • Update README.md

v3.0.0

25 Oct 15:35
4d22c68

Choose a tag to compare

New Feature

  • streamListDiff now 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 getObjectDiff code which is now cleaner and more elegant.

Chores

  • Update the documentation.
  • Create two subbuilds: client and server to expose an appropriate version of streamListDiff.

BREAKING CHANGE

  • streamListDiff is no longer imported from the index, but from @donedeal0/superdiff/server or @donedeal0/superdiff/client.