Browse Source

chore: update readme

pull/26/head
Antoine Lanoe 7 months ago
parent
commit
dc15ce9be3
  1. 471
      README.md
  2. 2
      src/index.ts
  3. 8
      src/lib/stream-list-diff/emitter.ts
  4. 22
      src/lib/stream-list-diff/index.ts
  5. 90
      src/lib/stream-list-diff/stream-list-diff.test.ts
  6. 2
      src/models/stream/index.ts

471
README.md

@ -18,7 +18,7 @@ This library compares two arrays or objects and returns a full diff of their dif
All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison.
**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. 👍 **Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested, has zero dependencies, and is super fast. Import. Enjoy. 👍
<hr/> <hr/>
@ -40,64 +40,159 @@ I am grateful to the generous donors of **Superdiff**!
**Superdiff** exports 4 functions: **Superdiff** exports 4 functions:
```ts
// Compares two objects and return a diff for each value and their potential subvalues
getObjectDiff(prevObject, nextObject)
// Compares two arrays and returns a diff for each value
getListDiff(prevList, nextList)
// Streams the diff of two object lists, ideal for large lists and maximum performance
streamListDiff(prevList, nextList, referenceProperty)
// Checks whether two values are equal
isEqual(dataA, dataB)
// Checks whether a value is an object
isObject(data)
```
<hr/>
### getObjectDiff() ### getObjectDiff()
```js ```js
import { getObjectDiff } from "@donedeal0/superdiff"; import { getObjectDiff } from "@donedeal0/superdiff";
``` ```
Compares two objects and return a diff for each value and their potential subvalues: Compares two objects and return a diff for each value and their potential subvalues. Supports deeply nested objects with any kind of values.
- property name **Format**
- status: `added`, `deleted`, `equal`, `updated`
- previous value, current value
- supports deeply nested objects with any kind of values
format: input
```ts
prevData: Record<string, unknown>;
nextData: Record<string, unknown>;
options?: {
ignoreArrayOrder?: boolean, // false by default,
showOnly?: {
statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default
granularity?: "basic" | "deep" // "basic" by default
}
}
```
- `prevData`: the original object
- `nextData`: the new object
- `options`
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
- `showOnly`: returns only the values whose status you are interested in. It takes two parameters:
- `statuses`: status you want to see in the output (e.g. `["added", "equal"]`)
- `granularity`:
- `basic` returns only the main properties whose status matches your query.
- `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly.
output
```ts ```ts
type ObjectDiff = { type ObjectDiff = {
type: "object"; type: "object";
status: "added" | "deleted" | "equal" | "updated"; status: "added" | "deleted" | "equal" | "updated";
diff: { diff: Diff[];
property: string;
previousValue: unknown;
currentValue: unknow;
status: "added" | "deleted" | "equal" | "updated";
// only appears if some subproperties have been added/deleted/updated
diff?: {
property: string;
previousValue: unknown;
currentValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
// recursive diff in case of subproperties
diff?: SubDiff[];
}[];
}[];
}; };
```
**Options** /** recursive diff in case of subproperties */
type Diff = {
property: string;
previousValue: unknown;
currentValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
diff?: Diff[];
};
```
**Usage**
You can add a third `options` parameter to `getObjectDiff`. input
```ts ```diff
{ getObjectDiff(
ignoreArrayOrder?: boolean // false by default, {
showOnly?: { id: 54,
statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default user: {
granularity?: "basic" | "deep" // "basic" by default name: "joe",
- member: true,
- hobbies: ["golf", "football"],
age: 66,
},
},
{
id: 54,
user: {
name: "joe",
+ member: false,
+ hobbies: ["golf", "chess"],
age: 66,
},
} }
} );
``` ```
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. output
- `showOnly`: returns only the values whose status you are interested in. It takes two parameters:
- `statuses`: status you want to see in the output (e.g. `["added", "equal"]`) ```diff
- `granularity`: {
- `basic` returns only the main properties whose status matches your query. type: "object",
- `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly. + status: "updated",
diff: [
{
property: "id",
previousValue: 54,
currentValue: 54,
status: "equal",
},
{
property: "user",
previousValue: {
name: "joe",
member: true,
hobbies: ["golf", "football"],
age: 66,
},
currentValue: {
name: "joe",
member: false,
hobbies: ["golf", "chess"],
age: 66,
},
+ status: "updated",
diff: [
{
property: "name",
previousValue: "joe",
currentValue: "joe",
status: "equal",
},
+ {
+ property: "member",
+ previousValue: true,
+ currentValue: false,
+ status: "updated",
+ },
+ {
+ property: "hobbies",
+ previousValue: ["golf", "football"],
+ currentValue: ["golf", "chess"],
+ status: "updated",
+ },
{
property: "age",
previousValue: 66,
currentValue: 66,
status: "equal",
},
],
},
],
}
```
<hr/>
### getListDiff() ### getListDiff()
@ -105,15 +200,31 @@ You can add a third `options` parameter to `getObjectDiff`.
import { getListDiff } from "@donedeal0/superdiff"; import { getListDiff } from "@donedeal0/superdiff";
``` ```
Compares two arrays and returns a diff for each value: Compares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects.
- index change: `prevIndex`, `newIndex`, `indexDiff` **Format**
- status: `added`, `deleted`, `equal`, `moved`, `updated`
- value input
- supports arrays of primitive values and objects
- supports arrays with duplicate values ```ts
prevList: T[];
nextList: T[];
options?: {
showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default
referenceProperty?: string, // "" by default
ignoreArrayOrder?: boolean, // false by default,
considerMoveAsUpdate?: boolean // false by default
}
```
- `prevList`: the original list
- `nextList`: the new list
- `options`
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
- `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`.
format: output
```ts ```ts
type ListDiff = { type ListDiff = {
@ -128,56 +239,7 @@ type ListDiff = {
}[]; }[];
}; };
``` ```
**Usage**
**Options**
You can add a third `options` parameter to `getListDiff`.
```ts
{
showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default
referenceProperty?: string; // "" by default
ignoreArrayOrder?: boolean // false by default,
}
```
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
### isEqual()
```js
import { isEqual } from "@donedeal0/superdiff";
```
Tests whether two values are equal.
**Options**
You can add a third `options` parameter to `isEqual`.
```ts
{
ignoreArrayOrder?: boolean // false by default,
}
```
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
### isObject()
```js
import { isObject } from "@donedeal0/superdiff";
```
Tests whether a value is an object.
<hr/>
## EXAMPLES
### getListDiff()
input input
@ -233,96 +295,146 @@ output
], ],
} }
``` ```
<hr/>
### getObjectDiff() ### streamListDiff()
```js
import { streamListDiff } from "@donedeal0/superdiff";
```
Streams the diff of two object lists, ideal for large lists and maximum performance.
**Format**
input input
```diff ```ts
getObjectDiff( prevList: T[],
{ nextList: T[],
id: 54, referenceProperty: ReferenceProperty<T>,
user: { options: {
name: "joe", showOnly?: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`), // [] by default
- member: true, chunksSize?: number, // // 0 by default
- hobbies: ["golf", "football"], considerMoveAsUpdate? boolean; // false by default
age: 66, }
},
},
{
id: 54,
user: {
name: "joe",
+ member: false,
+ hobbies: ["golf", "chess"],
age: 66,
},
}
);
``` ```
- `prevList`: the original object list.
- `nextList`: the new object list.
- `referenceProperty`: a common property in all the objects of your lists (e.g. `id`).
- `options`
- `chunksSize` the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs.
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`.
output output
```ts
type StreamListDiff<T extends Record<string, unknown>> = {
currentValue: T | null;
previousValue: T | null;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: "added" | "deleted" | "moved" | "updated" | "equal";
};
```
**Usage**
input
```diff ```diff
{ const diff = streamListDiff(
type: "object", [
+ status: "updated", - { id: 1, name: "Item 1" },
diff: [ { id: 2, name: "Item 2" },
{ { id: 3, name: "Item 3" }
property: "id", ],
previousValue: 54, [
currentValue: 54, + { id: 0, name: "Item 0" },
status: "equal", { id: 2, name: "Item 2" },
}, + { id: 3, name: "Item Three" },
],
"id",
{ chunksSize: 2 }
);
```
output
```diff
diff.on("data", (chunk) => {
// first chunk received (2 object diffs)
[
+ {
+ previousValue: null,
+ currentValue: { id: 0, name: 'Item 0' },
+ prevIndex: null,
+ newIndex: 0,
+ indexDiff: null,
+ status: 'added'
+ },
- {
- previousValue: { id: 1, name: 'Item 1' },
- currentValue: null,
- prevIndex: 0,
- newIndex: null,
- indexDiff: null,
- status: 'deleted'
- }
]
// second chunk received (2 object diffs)
[
{ {
property: "user", previousValue: { id: 2, name: 'Item 2' },
previousValue: { currentValue: { id: 2, name: 'Item 2' },
name: "joe", prevIndex: 1,
member: true, newIndex: 1,
hobbies: ["golf", "football"], indexDiff: 0,
age: 66, status: 'equal'
},
currentValue: {
name: "joe",
member: false,
hobbies: ["golf", "chess"],
age: 66,
},
+ status: "updated",
diff: [
{
property: "name",
previousValue: "joe",
currentValue: "joe",
status: "equal",
},
+ {
+ property: "member",
+ previousValue: true,
+ currentValue: false,
+ status: "updated",
+ },
+ {
+ property: "hobbies",
+ previousValue: ["golf", "football"],
+ currentValue: ["golf", "chess"],
+ status: "updated",
+ },
{
property: "age",
previousValue: 66,
currentValue: 66,
status: "equal",
},
],
}, },
], + {
} + previousValue: { id: 3, name: 'Item 3' },
+ currentValue: { id: 3, name: 'Item Three' },
+ prevIndex: 2,
+ newIndex: 2,
+ indexDiff: 0,
+ status: 'updated'
+ },
]
});
diff.on("finish", () => console.log("The full diff is available"))
diff.on("error", (err)=> console.log(err))
``` ```
<hr/>
### isEqual() ### isEqual()
```js ```js
import { isEqual } from "@donedeal0/superdiff";
```
Checks whether two values are equal.
**Options**
You can add a third `options` parameter to `isEqual`.
```ts
{
ignoreArrayOrder?: boolean // false by default,
}
```
- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
**Usage**
```ts
isEqual( isEqual(
[ [
{ name: "joe", age: 99 }, { name: "joe", age: 99 },
@ -337,25 +449,36 @@ isEqual(
output output
```js ```ts
false; false;
``` ```
<hr/>
### isObject() ### isObject()
```js
import { isObject } from "@donedeal0/superdiff";
```
Tests whether a value is an object.
**Usage**
input input
```js ```ts
isObject(["hello", "world"]); isObject(["hello", "world"]);
``` ```
output output
```js ```ts
false; false;
``` ```
More examples are available in the source code tests. <hr/>
### More examples are available in the source code tests.
<hr/> <hr/>
@ -365,7 +488,7 @@ DoneDeal0
## SUPPORT ## SUPPORT
If you or your company uses **Superdiff**, please show your support by becoming a sponsor! Your name and company logo will be displayed on the `README.md`. https://github.com/sponsors/DoneDeal0 If you or your company uses **Superdiff**, please show your support by becoming a sponsor! Your name and company logo will be displayed on the `README.md`. Premium support is also available. https://github.com/sponsors/DoneDeal0
<br/> <br/>
<a href="https://github.com/sponsors/DoneDeal0" target="_blank"> <a href="https://github.com/sponsors/DoneDeal0" target="_blank">

2
src/index.ts

@ -1,7 +1,7 @@
export { getObjectDiff } from "./lib/object-diff"; export { getObjectDiff } from "./lib/object-diff";
export { getListDiff } from "./lib/list-diff"; export { getListDiff } from "./lib/list-diff";
export { isEqual, isObject } from "./lib/utils"; export { isEqual, isObject } from "./lib/utils";
export { streamListsDiff } from "./lib/stream-list-diff"; export { streamListDiff } from "./lib/stream-list-diff";
export * from "./models/list"; export * from "./models/list";
export * from "./models/object"; export * from "./models/object";
export * from "./models/stream"; export * from "./models/stream";

8
src/lib/stream-list-diff/emitter.ts

@ -1,4 +1,4 @@
import { StreamListsDiff } from "@models/stream"; import { StreamListDiff } from "@models/stream";
type Listener<T extends unknown[]> = (...args: T) => void; type Listener<T extends unknown[]> = (...args: T) => void;
@ -9,7 +9,7 @@ export enum StreamEvent {
} }
export type Emitter<T extends Record<string, unknown>> = EventEmitter<{ export type Emitter<T extends Record<string, unknown>> = EventEmitter<{
data: [StreamListsDiff<T>[]]; data: [StreamListDiff<T>[]];
error: [Error]; error: [Error];
finish: []; finish: [];
}>; }>;
@ -33,12 +33,12 @@ export class EventEmitter<Events extends Record<string, unknown[]>> {
} }
export type EmitterEvents<T extends Record<string, unknown>> = { export type EmitterEvents<T extends Record<string, unknown>> = {
data: [StreamListsDiff<T>[]]; data: [StreamListDiff<T>[]];
error: [Error]; error: [Error];
finish: []; finish: [];
}; };
export interface ReadOnlyEmitter<T extends Record<string, unknown>> { export interface StreamListener<T extends Record<string, unknown>> {
on<E extends keyof EmitterEvents<T>>( on<E extends keyof EmitterEvents<T>>(
event: E, event: E,
listener: Listener<EmitterEvents<T>[E]>, listener: Listener<EmitterEvents<T>[E]>,

22
src/lib/stream-list-diff/index.ts

@ -2,7 +2,7 @@ import {
DEFAULT_LIST_STREAM_OPTIONS, DEFAULT_LIST_STREAM_OPTIONS,
ListStreamOptions, ListStreamOptions,
ReferenceProperty, ReferenceProperty,
StreamListsDiff, StreamListDiff,
StreamReferences, StreamReferences,
} from "@models/stream"; } from "@models/stream";
import { LIST_STATUS } from "@models/list"; import { LIST_STATUS } from "@models/list";
@ -11,17 +11,17 @@ import {
Emitter, Emitter,
EmitterEvents, EmitterEvents,
EventEmitter, EventEmitter,
ReadOnlyEmitter, StreamListener,
StreamEvent, StreamEvent,
} from "./emitter"; } from "./emitter";
function outputDiffChunk<T extends Record<string, unknown>>( function outputDiffChunk<T extends Record<string, unknown>>(
emitter: Emitter<T>, emitter: Emitter<T>,
) { ) {
let chunks: StreamListsDiff<T>[] = []; let chunks: StreamListDiff<T>[] = [];
return function handleDiffChunk( return function handleDiffChunk(
chunk: StreamListsDiff<T>, chunk: StreamListDiff<T>,
isLastChunk: boolean, isLastChunk: boolean,
options: ListStreamOptions, options: ListStreamOptions,
): void { ): void {
@ -50,9 +50,9 @@ function formatSingleListStreamDiff<T extends Record<string, unknown>>(
isPrevious: boolean, isPrevious: boolean,
status: LIST_STATUS, status: LIST_STATUS,
options: ListStreamOptions, options: ListStreamOptions,
): StreamListsDiff<T>[] | null { ): StreamListDiff<T>[] | null {
let isValid = true; let isValid = true;
const diff: StreamListsDiff<T>[] = []; const diff: StreamListDiff<T>[] = [];
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
const data = list[i]; const data = list[i];
if (!isObject(data)) { if (!isObject(data)) {
@ -292,7 +292,7 @@ function getDiffChunks<T extends Record<string, unknown>>(
); );
} }
} }
listsReferences.delete(key); // to free up memory listsReferences.delete(key);
} }
return emitter.emit(StreamEvent.Finish); return emitter.emit(StreamEvent.Finish);
@ -304,17 +304,17 @@ function getDiffChunks<T extends Record<string, unknown>>(
* @param {Record<string, unknown>[]} nextList - The new object list. * @param {Record<string, unknown>[]} nextList - The new object list.
* @param {ReferenceProperty<T>} referenceProperty - A common property in all the objects of your lists (e.g. `id`) * @param {ReferenceProperty<T>} referenceProperty - A common property in all the objects of your lists (e.g. `id`)
* @param {ListStreamOptions} options - Options to refine your output. * @param {ListStreamOptions} options - Options to refine your output.
- `chunksSize`: the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs. (default is `0`) - `chunksSize`: the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff by chunk, `10` = 10 object diffs by chunk).
- `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`) - `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`)
- `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated` - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated`
* @returns EventEmitter * @returns EventEmitter
*/ */
export function streamListsDiff<T extends Record<string, unknown>>( export function streamListDiff<T extends Record<string, unknown>>(
prevList: T[], prevList: T[],
nextList: T[], nextList: T[],
referenceProperty: ReferenceProperty<T>, referenceProperty: ReferenceProperty<T>,
options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
): ReadOnlyEmitter<T> { ): StreamListener<T> {
const emitter = new EventEmitter<EmitterEvents<T>>(); const emitter = new EventEmitter<EmitterEvents<T>>();
setTimeout(() => { setTimeout(() => {
try { try {
@ -323,5 +323,5 @@ export function streamListsDiff<T extends Record<string, unknown>>(
return emitter.emit(StreamEvent.Error, err as Error); return emitter.emit(StreamEvent.Error, err as Error);
} }
}, 0); }, 0);
return emitter as ReadOnlyEmitter<T>; return emitter as StreamListener<T>;
} }

90
src/lib/stream-list-diff/stream-list-diff.test.ts

@ -1,14 +1,14 @@
import { LIST_STATUS } from "@models/list"; import { LIST_STATUS } from "@models/list";
import { streamListsDiff } from "."; import { streamListDiff } from ".";
import { StreamListsDiff } from "@models/stream"; import { StreamListDiff } from "@models/stream";
describe("streamListsDiff data", () => { describe("streamListDiff data", () => {
it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => { it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => {
const nextList = [ const nextList = [
{ id: 1, name: "Item 1" }, { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListsDiff([], nextList, "id", { chunksSize: 2 }); const diff = streamListDiff([], nextList, "id", { chunksSize: 2 });
const expectedChunks = [ const expectedChunks = [
{ {
@ -43,7 +43,7 @@ describe("streamListsDiff data", () => {
{ id: 1, name: "Item 1" }, { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListsDiff(prevList, [], "id", { chunksSize: 2 }); const diff = streamListDiff(prevList, [], "id", { chunksSize: 2 });
const expectedChunks = [ const expectedChunks = [
{ {
@ -82,7 +82,7 @@ describe("streamListsDiff data", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
const expectedChunks = [ const expectedChunks = [
[ [
@ -153,7 +153,7 @@ describe("streamListsDiff data", () => {
{ id: 9, name: "Item 9" }, { id: 9, name: "Item 9" },
{ id: 8, name: "Item 8" }, { id: 8, name: "Item 8" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 });
const expectedChunks = [ const expectedChunks = [
[ [
@ -277,7 +277,7 @@ describe("streamListsDiff data", () => {
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 150 }); const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 150 });
const expectedChunks = [ const expectedChunks = [
{ {
@ -346,7 +346,7 @@ describe("streamListsDiff data", () => {
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
considerMoveAsUpdate: true, considerMoveAsUpdate: true,
}); });
@ -418,7 +418,7 @@ describe("streamListsDiff data", () => {
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
showOnly: ["added", "deleted"], showOnly: ["added", "deleted"],
}); });
@ -506,7 +506,7 @@ describe("streamListsDiff data", () => {
{ id: 9, name: "Item 9" }, { id: 9, name: "Item 9" },
{ id: 8, name: "Item 8" }, { id: 8, name: "Item 8" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 });
const expectedChunks = [ const expectedChunks = [
[ [
@ -663,9 +663,9 @@ describe("streamListsDiff data", () => {
}); });
}); });
describe("streamListsDiff finish", () => { describe("streamListDiff finish", () => {
it("emits 'finish' event if no prevList nor nextList is provided", (done) => { it("emits 'finish' event if no prevList nor nextList is provided", (done) => {
const diff = streamListsDiff([], [], "id"); const diff = streamListDiff([], [], "id");
diff.on("finish", () => done()); diff.on("finish", () => done());
}); });
it("emits 'finish' event when all the chunks have been processed", (done) => { it("emits 'finish' event when all the chunks have been processed", (done) => {
@ -677,12 +677,12 @@ describe("streamListsDiff finish", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("finish", () => done()); diff.on("finish", () => done());
}); });
}); });
describe("streamListsDiff error", () => { describe("streamListDiff error", () => {
test("emits 'error' event when prevList has invalid data", (done) => { test("emits 'error' event when prevList has invalid data", (done) => {
const prevList = [ const prevList = [
{ id: 1, name: "Item 1" }, { id: 1, name: "Item 1" },
@ -695,7 +695,7 @@ describe("streamListsDiff error", () => {
]; ];
// @ts-expect-error prevList is invalid by design for the test // @ts-expect-error prevList is invalid by design for the test
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -717,7 +717,7 @@ describe("streamListsDiff error", () => {
]; ];
// @ts-expect-error nextList is invalid by design for the test // @ts-expect-error nextList is invalid by design for the test
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -734,7 +734,7 @@ describe("streamListsDiff error", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -751,7 +751,7 @@ describe("streamListsDiff error", () => {
]; ];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const diff = streamListsDiff(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -768,7 +768,7 @@ describe("streamListsDiff error", () => {
]; ];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: -3 }); const diff = streamListDiff(prevList, nextList, "id", { chunksSize: -3 });
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -793,9 +793,9 @@ describe("Performance", () => {
...generateLargeList(5000, "next"), ...generateLargeList(5000, "next"),
]; ];
const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = [];
let chunkCount = 0; let chunkCount = 0;
const diffStream = streamListsDiff(prevList, nextList, "id", { const diffStream = streamListDiff(prevList, nextList, "id", {
chunksSize: 1000, chunksSize: 1000,
}); });
@ -835,9 +835,9 @@ describe("Performance", () => {
...generateLargeList(50000, "next"), ...generateLargeList(50000, "next"),
]; ];
const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = [];
let chunkCount = 0; let chunkCount = 0;
const diffStream = streamListsDiff(prevList, nextList, "id", { const diffStream = streamListDiff(prevList, nextList, "id", {
chunksSize: 10_000, chunksSize: 10_000,
}); });
@ -864,46 +864,4 @@ describe("Performance", () => {
done(); done();
}); });
}); });
// it("should correctly stream diff for 1.000.000 entries", (done) => {
// const generateLargeList = (size: number, idPrefix: string) => {
// return Array.from({ length: size }, (_, i) => ({
// id: `${idPrefix}-${i}`,
// value: i,
// }));
// };
// const prevList = generateLargeList(1_000_000, "prev");
// const nextList = [
// ...generateLargeList(500_000, "prev"),
// ...generateLargeList(500_000, "next"),
// ];
// const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = [];
// let chunkCount = 0;
// const diffStream = streamListsDiff(prevList, nextList, "id", {
// chunksSize: 100_000,
// });
// diffStream.on("data", (chunk) => {
// receivedChunks.push(...chunk);
// chunkCount++;
// });
// diffStream.on("finish", () => {
// const deletions = receivedChunks.filter(
// (diff) => diff.status === LIST_STATUS.DELETED,
// );
// const additions = receivedChunks.filter(
// (diff) => diff.status === LIST_STATUS.ADDED,
// );
// const updates = receivedChunks.filter(
// (diff) => diff.status === LIST_STATUS.EQUAL,
// );
// expect(receivedChunks.length).toBe(1_500_000); // 50.000 deletions + 50.000 equal + 50.000 additions
// expect(chunkCount).toBe(15);
// expect(deletions.length).toBe(500000);
// expect(additions.length).toBe(500000);
// expect(updates.length).toBe(500000);
// done();
// });
// });
}); });

2
src/models/stream/index.ts

@ -1,6 +1,6 @@
import { LIST_STATUS } from "@models/list"; import { LIST_STATUS } from "@models/list";
export type StreamListsDiff<T extends Record<string, unknown>> = { export type StreamListDiff<T extends Record<string, unknown>> = {
currentValue: T | null; currentValue: T | null;
previousValue: T | null; previousValue: T | null;
prevIndex: number | null; prevIndex: number | null;

Loading…
Cancel
Save