Browse Source

feat: base

pull/1/head
antoine lanoe 2 years ago
commit
7c5ecf0551
  1. 1
      .gitignore
  2. 0
      index.ts
  3. 0
      jest.config.js
  4. 15
      package.json
  5. 0
      src/index.ts
  6. 82
      src/list-diff.ts
  7. 42
      src/model.ts
  8. 113
      src/object-diff.ts
  9. 14
      src/utils.ts
  10. 7
      test/list-diff.test.ts
  11. 7
      test/object-diff.test.ts
  12. 10
      test/utils.test.ts
  13. 2943
      yarn.lock

1
.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
/node_modules

0
jest.config.js

15
package.json

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
{
"name": "data-diff",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"@types/jest": "^29.2.4",
"jest": "^29.3.1",
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4"
}
}

0
src/index.ts

82
src/list-diff.ts

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
import { STATUS, ListDiff, ListData } from "./model";
import { isEqual } from "./utils";
function formatSingleListDiff(
listData: ListData,
status: "added" | "removed"
): ListDiff {
return {
type: "list",
diff: listData.map((data) => ({ value: data, status })),
};
}
export const getListDiff = (
prevList: ListData[] | undefined | null,
nextList: ListData[] | undefined | null
): ListDiff => {
if (!prevList && !nextList) {
return {
type: "list",
diff: [],
};
}
if (!prevList) {
return formatSingleListDiff(nextList, "added");
}
if (!nextList) {
return formatSingleListDiff(prevList, "removed");
}
const diff: ListDiff["diff"] = [];
nextList.forEach((nextValue, i) => {
const prevIndex = prevList.findIndex((prevValue) =>
isEqual(prevValue, nextValue)
);
const indexDiff = prevIndex === -1 ? null : i - prevIndex;
if (indexDiff === 0) {
return diff.push({
value: nextValue,
prevIndex,
newIndex: i,
indexDiff,
status: STATUS.EQUAL,
});
}
if (prevIndex === -1) {
return diff.push({
value: nextValue,
prevIndex: null,
newIndex: i,
indexDiff,
status: STATUS.ADDED,
});
}
return diff.push({
value: nextValue,
prevIndex,
newIndex: i,
indexDiff,
status: STATUS.MOVED,
});
});
prevList.forEach((prevValue, i) => {
if (!nextList.some((nextValue) => isEqual(nextValue, prevValue))) {
return diff.splice(i, 0, {
value: prevValue,
prevIndex: i,
newIndex: null,
indexDiff: null,
status: STATUS.DELETED,
});
}
});
return {
type: "list",
diff,
};
};
export function hasListChanged(listDiff: ListDiff): boolean {
return listDiff.diff.some((d) => d.status !== STATUS.EQUAL);
}

42
src/model.ts

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
export const STATUS: Record<string, DiffStatus> = {
ADDED: "added",
EQUAL: "equal",
MOVED: "moved",
DELETED: "deleted",
UPDATED: "updated",
};
export type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
export type ObjectData = Record<string, any> | undefined | null;
export type ListData = any;
export type ListDiff = {
type: "list";
diff: {
value: ListData;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: DiffStatus;
}[];
};
export type Subproperties = {
name: string;
previousValue: any;
currentValue: any;
status: DiffStatus;
};
export type ObjectDiff = {
type: "object";
diff: {
property: string;
previousValue: any;
currentValue: any;
status: DiffStatus;
subPropertiesDiff?: Subproperties[];
}[];
};
export type DataDiff = ListDiff | ObjectDiff;

113
src/object-diff.ts

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
import {
ObjectData,
ObjectDiff,
DiffStatus,
STATUS,
Subproperties,
} from "./model";
import { hasNestedValues, isEqual } from "./utils";
function formatSingleObjectDiff(
data: ObjectData,
status: DiffStatus
): ObjectDiff {
const diff: ObjectDiff["diff"] = [];
Object.entries(data).forEach(([property, value]) => {
if (hasNestedValues(value)) {
const subPropertiesDiff: Subproperties[] = [];
Object.entries(value).forEach(([subProperty, subValue]) => {
subPropertiesDiff.push({
name: subProperty,
previousValue: status === STATUS.ADDED ? undefined : subValue,
currentValue: status === STATUS.ADDED ? subValue : undefined,
status,
});
});
return diff.push({
property: property,
previousValue: status === STATUS.ADDED ? undefined : data[property],
currentValue: status === STATUS.ADDED ? value : undefined,
status,
subPropertiesDiff,
});
}
return diff.push({
property,
previousValue: status === STATUS.ADDED ? undefined : data[property],
currentValue: status === STATUS.ADDED ? value : undefined,
status,
});
});
return {
type: "object",
diff,
};
}
export function getObjectDiff(
prevData: ObjectData,
nextData: ObjectData
): ObjectDiff {
if (!prevData && !nextData) {
return {
type: "object",
diff: [],
};
}
if (!prevData) {
return formatSingleObjectDiff(nextData, STATUS.ADDED);
}
if (!nextData) {
return formatSingleObjectDiff(prevData, STATUS.DELETED);
}
const diff: ObjectDiff["diff"] = [];
Object.entries(nextData).forEach(([nextProperty, nextValue]) => {
const previousValue = prevData[nextProperty];
if (hasNestedValues(nextValue)) {
const prevSubValues = previousValue
? Object.entries(previousValue)
: null;
const subPropertiesDiff: Subproperties[] = [];
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
if (prevSubValues) {
const previousMatch = prevSubValues.find(([subPreviousKey]) =>
isEqual(subPreviousKey, nextSubProperty)
);
if (previousMatch) {
subPropertiesDiff.push({
name: nextSubProperty,
previousValue: previousMatch[1],
currentValue: nextSubValue,
status: isEqual(previousMatch[1], nextSubValue)
? STATUS.EQUAL
: STATUS.UPDATED,
});
}
}
});
const _status = subPropertiesDiff.some(
(property) => property.status !== STATUS.EQUAL
)
? STATUS.UPDATED
: STATUS.EQUAL;
return diff.push({
property: nextProperty,
previousValue,
currentValue: nextValue,
status: _status,
subPropertiesDiff,
});
}
return diff.push({
property: nextProperty,
previousValue,
currentValue: nextValue,
status: previousValue === nextValue ? STATUS.EQUAL : STATUS.UPDATED,
});
});
return {
type: "object",
diff,
};
}

14
src/utils.ts

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
export function isEqual(a: any, b: any): boolean {
if (typeof a !== typeof b) return true;
if (Array.isArray(a)) {
return a.toString() === b.toString();
}
if (typeof a === "object") {
return JSON.stringify(a) === JSON.stringify(b);
}
return a === b;
}
export function hasNestedValues(value: any): value is Record<string, any> {
return typeof value === "object" && !Array.isArray(value);
}

7
test/list-diff.test.ts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
import { getListDiff } from "../src/list-diff";
describe("getListDiff", () => {
it("", () => {
expect(getListDiff(null, null)).toStrictEqual(null);
});
});

7
test/object-diff.test.ts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
import { getObjectDiff } from "../src/object-diff";
describe("getObjectDiff", () => {
it("", () => {
expect(getObjectDiff(null, null)).toStrictEqual(null);
});
});

10
test/utils.test.ts

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
import { isEqual, hasNestedValues } from "../src/utils";
describe("isEqual", () => {
it("", () => {
expect(isEqual(null, null)).toStrictEqual(null);
});
it("", () => {
expect(hasNestedValues(null)).toStrictEqual(null);
});
});

2943
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save