Installation
npm install audit-log-diffdeepDiff
deepDiff
deepDiff(before, after, options?): DeepDiffResult | DeepDiffWithMessagesCompare two values deeply and return audit-friendly structural diffs. Mongoose-style inputs with toObject() are unwrapped. BSON-style values with .equals() compare equal across instances. Arrays are compared by index only (no LCS).
Returns: NestedDiffNode (default) or FlatChangeWithPath[] when format is "flat". With generateMessage: true, returns { diff, messages }.
import { deepDiff } from "audit-log-diff";
const diff = deepDiff(
{ name: "Ada", profile: { age: 36 } },
{ name: "Ada", profile: { age: 37 }, role: "admin" },
);
// {
// profile: { age: { type: "changed", before: 36, after: 37 } },
// role: { type: "added", value: "admin" }
// }Flat format
Good for indexing, filters, or piping into formatAuditMessages.
const flat = deepDiff(before, after, { format: "flat" });
// [
// { path: "profile.age", type: "changed", before: 36, after: 37 },
// { path: "role", type: "added", value: "admin" }
// ]With messages
Paths in messages use spaced camelCase and 1-based row labels for array segments.
const { diff, messages } = deepDiff(before, after, {
generateMessage: true,
user: "Dheemanth Shenoy",
});
// messages e.g. ["Dheemanth changed name to Rafi", …]Date equality
Same instant compares equal, even if references differ.
deepDiff(
{ at: new Date("2024-01-01") },
{ at: new Date("2024-01-01") }
);
// {} — same instant, no diffOptions
Configuration passed as the third argument to deepDiff.
ignoreKeysProperty names to skip on plain objects (own keys, any depth). Default [].
deepDiff(recordA, recordB, { ignoreKeys: ["updatedAt", "password"] })format"nested" (default) returns a tree of change nodes. "flat" returns an array of path entries.
deepDiff(before, after, { format: "flat" })generateMessageIf true, return { diff, messages } instead of diff only. Default false.
deepDiff(before, after, { generateMessage: true, user: "Dheemanth Shenoy" })userActor string for messages. The first word is used as the subject. Omit → "Someone".
{ user: "Ada Lovelace" } → subject "Ada"Helpers
formatAuditMessages
formatAuditMessages(changes, options?): string[]Build short English audit sentences from an existing flat diff. Paths use spaced camelCase and 1-based row labels for array segments.
Returns: string[] — one audit line per flat path
import { deepDiff, formatAuditMessages } from "audit-log-diff";
const flat = deepDiff(before, after, { format: "flat" });
const messages = formatAuditMessages(flat, { user: "Ada Lovelace" });
// ["Ada changed name to Rafi", …]flattenDeepDiff
flattenDeepDiff(node): FlatChangeWithPath[]Flatten a nested diff tree to path entries — same paths as deepDiff with format: "flat".
Returns: FlatChangeWithPath[]
import { deepDiff, flattenDeepDiff } from "audit-log-diff";
const nested = deepDiff(before, after);
const flat = flattenDeepDiff(nested);
// [{ path: "profile.age", type: "changed", before: 36, after: 37 }, …]Change Types
Leaf nodes in nested diffs and entries in flat diffs.
| Type | Shape | Description |
|---|---|---|
| added | { type: "added", value: unknown } | A new field or element appeared in the after value. |
| removed | { type: "removed", before: unknown } | A field or element existed before but is absent after. |
| changed | { type: "changed", before: unknown, after: unknown } | A value was modified between before and after. |
TypeScript Exports
With generateMessage: true, pass a literal true so TypeScript infers { diff, messages }.
import { deepDiff, flattenDeepDiff, formatAuditMessages } from "audit-log-diff";
import type {
DeepDiffOptions,
DeepDiffResult,
DeepDiffWithMessages,
NestedDiff,
NestedDiffNode,
NestedDiffLeaf,
FlatChange,
FlatChangeWithPath,
AddedChange,
RemovedChange,
ChangedChange,
DiffFormat,
} from "audit-log-diff";