audit-log-diff
v1.0.2
Published
Lightweight deep object diff utility for audit logs, activity tracking, change history, and system logging.
Maintainers
Readme
audit-log-diff
Deep-compare two values and get audit-style diffs: nested trees or flat paths, optional English log lines, Date equality by time, and per-level ignoreKeys.
Install
npm install audit-log-diffdeepDiff(before, after, options?)
| Option | Description |
|--------|-------------|
| ignoreKeys | Property names to skip on plain objects (own keys, any depth). Default []. |
| format | "nested" (default) or "flat". |
| generateMessage | If true, return { diff, messages } instead of only diff. Default false. |
| user | Actor string for messages; the first word is used as the subject (e.g. "Ada Lovelace" → Ada). Omit → Someone. |
Returns
- Default:
diffonly — same shape as before (NestedDiffNodeorFlatChangeWithPath[]depending onformat). - With
generateMessage: true:{ diff, messages }—messagesisstring[](short English, one line per flat path).
Mongoose-style inputs with toObject() are unwrapped before diffing. BSON-style values with .equals() (e.g. ObjectId) compare equal across instances.
Arrays are compared by index only (no LCS).
Usage
Nested diff
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 paths — good for indexing, filters, or piping into formatAuditMessages.
const flat = deepDiff(before, after, { format: "flat" });
// [{ path: "profile.age", type: "changed", before: 36, after: 37 }, …]Human-readable lines — optional; same diff shape as without the flag.
const { diff, messages } = deepDiff(before, after, {
generateMessage: true,
user: "Dheemanth Shenoy",
});
// messages e.g. ["Dheemanth changed name to Rafi", …]Paths in messages use spaced camelCase (emergencyContact.name → emergency contact → name), 1-based row labels for array segments (items[1].x → … row 2 …), and a dedicated removed phrasing when a field under an indexed parent is removed (relationItems[1].name → … removed relation item "name" from row 2).
Ignore noisy or sensitive keys
deepDiff(recordA, recordB, { ignoreKeys: ["updatedAt", "password"] });Dates — same instant → no diff, even if references differ.
deepDiff({ at: new Date("2024-01-01") }, { at: new Date("2024-01-01") }); // {}Helpers
formatAuditMessages(flatChanges, { user? })— buildmessagesfrom an existing flat diff.flattenDeepDiff(nestedDiff)— nested tree → same flat rows asformat: "flat".
Types
Exports include DeepDiffOptions, DeepDiffResult, DeepDiffWithMessages, NestedDiff, NestedDiffNode, FlatChangeWithPath, and leaf types (AddedChange, RemovedChange, ChangedChange). With generateMessage: true, pass a literal true so TypeScript infers { diff, messages }.
import type { DeepDiffOptions, DeepDiffWithMessages, FlatChangeWithPath } from "audit-log-diff";License
MIT
