diff --git a/README.md b/README.md
index e035cc8..2a265de 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ This library compares two arrays or objects and returns a full diff of their dif
 
 Most existing solutions return a confusing diff format that often requires extra parsing. They are also limited to object comparison.
 
-**Superdiff** provides a complete and readable diff for both arrays **and** objects. Plus, it's battle-tested, has zero dependencies, and is super fast. 
+**Superdiff** provides a complete and readable diff for both arrays **and** objects. Plus, it supports stream and file inputs for handling large datasets efficiently, is battle-tested, has zero dependencies, and is super fast. 
 
 Import. Enjoy. 👍
 
@@ -40,7 +40,7 @@ I am grateful to the generous donors of **Superdiff**!
 
 ## FEATURES
 
-**Superdiff** exports 5 functions:
+**Superdiff** exports 6 functions:
 
 ```ts
 // Returns a complete diff of two objects
@@ -52,6 +52,9 @@ getListDiff(prevList, nextList)
 // Streams the diff of two object lists, ideal for large lists and maximum performance
 streamListDiff(prevList, nextList, referenceProperty)
 
+// Similar to streamListDiff, but for browser use
+streamListDiffClient(prevList, nextList, referenceProperty)
+
 // Checks whether two values are equal 
 isEqual(dataA, dataB)
 
@@ -306,7 +309,10 @@ getListDiff(
 ### streamListDiff() 
 
 ```js
+// If you are in a server environment
 import { streamListDiff } from "@donedeal0/superdiff";
+// If you are in a browser environment
+import { streamListDiffClient } from "@donedeal0/superdiff";
 ```
 
 Streams the diff of two object lists, ideal for large lists and maximum performance.
@@ -315,14 +321,34 @@ Streams the diff of two object lists, ideal for large lists and maximum performa
 
 **Input**
 
+#### streamListDiff (server)
+
+> In a server environment, `Readable` refers to Node.js streams, and `FilePath` refers to the path of a file (e.g., `./list.json`). Examples are provided in the #usage section below.
+
 ```ts
- prevList: Record<string, unknown>[],
- nextList: Record<string, unknown>[],
+// streamListDiff
+ prevList: Readable | FilePath | Record<string, unknown>[],
+ nextList: Readable | FilePath | Record<string, unknown>[],
  referenceProperty: keyof Record<string, unknown>,
  options: {
   showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default
-  chunksSize?: number, // // 0 by default
-  considerMoveAsUpdate? boolean; // false by default
+  chunksSize?: number, // 0 by default
+  considerMoveAsUpdate?: boolean; // false by default
+}
+```
+
+#### streamListDiffClient (browser)
+
+> In a browser environment, `ReadableStream` refers to the browser's streaming API, and `File` refers to an uploaded or local file. Examples are provided in the #usage section below.
+
+```ts
+ prevList: ReadableStream<Record<string, unknown>> | File | Record<string, unknown>[],
+ nextList: ReadableStream<Record<string, unknown>> | File | Record<string, unknown>[],
+ referenceProperty: keyof Record<string, unknown>,
+ options: {
+  showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default
+  chunksSize?: number, // 0 by default
+  considerMoveAsUpdate?: boolean; // false by default
 }
 ```
 
@@ -370,6 +396,40 @@ type StreamListDiff<T extends Record<string, unknown>> = {
 
 **Input**
 
+You can send streams, file paths, or arrays as input:
+
+> If you are in a server environment
+
+```ts
+    // 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
+
+```ts
+    // 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]; 
+
+```
+> Example
+
 ```diff
 const diff = streamListDiff(
       [ 
@@ -431,9 +491,78 @@ diff.on("data", (chunk) => {
      ]
 });
 
-diff.on("finish", () => console.log("The full diff is available"))
+diff.on("finish", () => console.log("Your data has been processed. The full diff is available."))
 diff.on("error", (err) => console.log(err))
 ```
+
+**Using `fetch`**
+
+A common use case would be to do a live diff against a stream, in order to avoid loading the entire dataset into memory. Here are two examples, for browser and server use:
+
+Browser:
+```ts
+import { streamListDiffClient } from "@donedeal0/superdiff";
+
+async function streamDiffFromAPI() {
+  try {
+    const response = await fetch("https://example.com/api/streaming-data");
+    const reader = response.body.getReader();
+
+    const stream = new ReadableStream({
+      async start(controller) {
+        let result;
+        while (!(result = await reader.read()).done) {
+          controller.enqueue(result.value); // Push the next chunk into the stream
+        }
+        controller.close(); // Close the stream when done
+      },
+    });
+
+    const prevStream = [{ id: 1, name: "Joe" }, { id: 2, name: "Jane" }] // Some previous list or stream
+
+    const diff = streamListDiffClient(prevStream, stream, 'id', { chunksSize: 5 });
+    diff.on("data", (diffChunk) => console.log(diffChunk));
+    diff.on("finish", () => console.log("Stream diff complete"));
+  } catch (err) {
+    console.error(err);
+  }
+}
+```
+
+Server:
+
+```ts
+import fetch from "node-fetch"; 
+import { Readable } from "stream";
+import { streamListDiff } from "@donedeal0/superdiff";
+
+async function streamDiffFromAPI() {
+  try {
+    const response = await fetch("https://example.com/api/streaming-data");
+    const reader = response.body.getReader();
+
+    const stream = new Readable({
+      async read() {
+        let result;
+        while (!(result = await reader.read()).done) {
+          this.push(result.value); // Push the next chunk into the stream
+        }
+        this.push(null); // Close the stream when done
+      },
+    });
+
+    const prevList = [{ id: 1, name: "Joe" }, { id: 2, name: "Jane" }]; // Some previous list or stream
+    const prevListStream = Readable.from(prevList, { objectMode: true })
+    const diff = streamListDiff(prevListStream, stream, 'id', { chunksSize: 5 });
+
+    diff.on("data", (diffChunk) => console.log(diffChunk));
+    diff.on("finish", () => console.log("Stream diff complete"));
+  } catch (err) {
+    console.error(err);
+  }
+}
+```
+
 <hr/>
 
 ### isEqual()
diff --git a/package-lock.json b/package-lock.json
index e2eae4f..ef1fda1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@donedeal0/superdiff",
-  "version": "2.0.0",
+  "version": "2.1.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@donedeal0/superdiff",
-      "version": "2.0.0",
+      "version": "2.1.0",
       "license": "ISC",
       "devDependencies": {
         "@eslint/js": "^9.11.1",
@@ -17,14 +17,21 @@
         "@swc/core": "^1.7.26",
         "@swc/jest": "^0.2.36",
         "@types/jest": "^29.5.13",
+        "blob-polyfill": "^9.0.20240710",
         "eslint": "^9.11.1",
         "husky": "^9.1.6",
         "jest": "^29.7.0",
+        "jest-environment-jsdom": "^29.7.0",
         "prettier": "^3.3.3",
         "swc-loader": "^0.2.6",
         "tsup": "^8.3.0",
         "typescript": "^5.6.2",
-        "typescript-eslint": "^8.7.0"
+        "typescript-eslint": "^8.7.0",
+        "web-streams-polyfill": "^4.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/DoneDeal0"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -3073,6 +3080,15 @@
         "@swc/counter": "^0.1.3"
       }
     },
+    "node_modules/@tootallnate/once": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+      "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/@types/babel__core": {
       "version": "7.20.5",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3163,6 +3179,29 @@
         "pretty-format": "^29.0.0"
       }
     },
+    "node_modules/@types/jsdom": {
+      "version": "20.0.1",
+      "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+      "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*",
+        "@types/tough-cookie": "*",
+        "parse5": "^7.0.0"
+      }
+    },
+    "node_modules/@types/jsdom/node_modules/parse5": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz",
+      "integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==",
+      "dev": true,
+      "dependencies": {
+        "entities": "^4.5.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3194,6 +3233,12 @@
       "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
       "dev": true
     },
+    "node_modules/@types/tough-cookie": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+      "dev": true
+    },
     "node_modules/@types/yargs": {
       "version": "17.0.17",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz",
@@ -3614,6 +3659,13 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/abab": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+      "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+      "deprecated": "Use your platform's native atob() and btoa() methods instead",
+      "dev": true
+    },
     "node_modules/acorn": {
       "version": "8.12.1",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -3626,6 +3678,16 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/acorn-globals": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+      "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.1.0",
+        "acorn-walk": "^8.0.2"
+      }
+    },
     "node_modules/acorn-import-attributes": {
       "version": "1.9.5",
       "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
@@ -3645,6 +3707,18 @@
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/acorn-walk": {
+      "version": "8.3.4",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+      "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.11.0"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/agent-base": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
@@ -3774,6 +3848,12 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "dev": true
+    },
     "node_modules/babel-jest": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -3959,6 +4039,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/blob-polyfill": {
+      "version": "9.0.20240710",
+      "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-9.0.20240710.tgz",
+      "integrity": "sha512-DPUO/EjNANCgSVg0geTy1vmUpu5hhp9tV2F7xUSTUd1jwe4XpwupGB+lt5PhVUqpqAk+zK1etqp6Pl/HVf71Ug==",
+      "dev": true
+    },
     "node_modules/bottleneck": {
       "version": "2.19.5",
       "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -4389,6 +4475,18 @@
       "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
       "dev": true
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -4711,6 +4809,78 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/cssom": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+      "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+      "dev": true
+    },
+    "node_modules/cssstyle": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+      "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+      "dev": true,
+      "dependencies": {
+        "cssom": "~0.3.6"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cssstyle/node_modules/cssom": {
+      "version": "0.3.8",
+      "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+      "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+      "dev": true
+    },
+    "node_modules/data-urls": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+      "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+      "dev": true,
+      "dependencies": {
+        "abab": "^2.0.6",
+        "whatwg-mimetype": "^3.0.0",
+        "whatwg-url": "^11.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/data-urls/node_modules/tr46": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+      "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/data-urls/node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/data-urls/node_modules/whatwg-url": {
+      "version": "11.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+      "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+      "dev": true,
+      "dependencies": {
+        "tr46": "^3.0.0",
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/debug": {
       "version": "4.3.7",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -4728,6 +4898,12 @@
         }
       }
     },
+    "node_modules/decimal.js": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+      "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+      "dev": true
+    },
     "node_modules/dedent": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
@@ -4766,6 +4942,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/detect-newline": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -4796,6 +4981,28 @@
         "node": ">=8"
       }
     },
+    "node_modules/domexception": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+      "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+      "deprecated": "Use your platform's native DOMException instead",
+      "dev": true,
+      "dependencies": {
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/domexception/node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/dot-prop": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -4870,6 +5077,18 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/env-ci": {
       "version": "11.1.0",
       "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz",
@@ -5124,6 +5343,27 @@
         "node": ">=0.8.0"
       }
     },
+    "node_modules/escodegen": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+      "dev": true,
+      "dependencies": {
+        "esprima": "^4.0.1",
+        "estraverse": "^5.2.0",
+        "esutils": "^2.0.2"
+      },
+      "bin": {
+        "escodegen": "bin/escodegen.js",
+        "esgenerate": "bin/esgenerate.js"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "optionalDependencies": {
+        "source-map": "~0.6.1"
+      }
+    },
     "node_modules/eslint": {
       "version": "9.11.1",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
@@ -5665,6 +5905,20 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "dev": true,
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/from2": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
@@ -5974,6 +6228,18 @@
       "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
       "dev": true
     },
+    "node_modules/html-encoding-sniffer": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+      "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+      "dev": true,
+      "dependencies": {
+        "whatwg-encoding": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/html-escaper": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -6030,6 +6296,18 @@
         "url": "https://github.com/sponsors/typicode"
       }
     },
+    "node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "dev": true,
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/ignore": {
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -6286,6 +6564,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-potential-custom-element-name": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+      "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+      "dev": true
+    },
     "node_modules/is-stream": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -6993,6 +7277,33 @@
         "node": ">=8"
       }
     },
+    "node_modules/jest-environment-jsdom": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+      "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
+      "dev": true,
+      "dependencies": {
+        "@jest/environment": "^29.7.0",
+        "@jest/fake-timers": "^29.7.0",
+        "@jest/types": "^29.6.3",
+        "@types/jsdom": "^20.0.0",
+        "@types/node": "*",
+        "jest-mock": "^29.7.0",
+        "jest-util": "^29.7.0",
+        "jsdom": "^20.0.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "canvas": "^2.5.0"
+      },
+      "peerDependenciesMeta": {
+        "canvas": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/jest-environment-node": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
@@ -8056,6 +8367,136 @@
         "js-yaml": "bin/js-yaml.js"
       }
     },
+    "node_modules/jsdom": {
+      "version": "20.0.3",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+      "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+      "dev": true,
+      "dependencies": {
+        "abab": "^2.0.6",
+        "acorn": "^8.8.1",
+        "acorn-globals": "^7.0.0",
+        "cssom": "^0.5.0",
+        "cssstyle": "^2.3.0",
+        "data-urls": "^3.0.2",
+        "decimal.js": "^10.4.2",
+        "domexception": "^4.0.0",
+        "escodegen": "^2.0.0",
+        "form-data": "^4.0.0",
+        "html-encoding-sniffer": "^3.0.0",
+        "http-proxy-agent": "^5.0.0",
+        "https-proxy-agent": "^5.0.1",
+        "is-potential-custom-element-name": "^1.0.1",
+        "nwsapi": "^2.2.2",
+        "parse5": "^7.1.1",
+        "saxes": "^6.0.0",
+        "symbol-tree": "^3.2.4",
+        "tough-cookie": "^4.1.2",
+        "w3c-xmlserializer": "^4.0.0",
+        "webidl-conversions": "^7.0.0",
+        "whatwg-encoding": "^2.0.0",
+        "whatwg-mimetype": "^3.0.0",
+        "whatwg-url": "^11.0.0",
+        "ws": "^8.11.0",
+        "xml-name-validator": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "canvas": "^2.5.0"
+      },
+      "peerDependenciesMeta": {
+        "canvas": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/jsdom/node_modules/agent-base": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "dev": true,
+      "dependencies": {
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
+    "node_modules/jsdom/node_modules/http-proxy-agent": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+      "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+      "dev": true,
+      "dependencies": {
+        "@tootallnate/once": "2",
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/jsdom/node_modules/https-proxy-agent": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "dev": true,
+      "dependencies": {
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/jsdom/node_modules/parse5": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz",
+      "integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==",
+      "dev": true,
+      "dependencies": {
+        "entities": "^4.5.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
+    "node_modules/jsdom/node_modules/tr46": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+      "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/jsdom/node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/jsdom/node_modules/whatwg-url": {
+      "version": "11.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+      "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+      "dev": true,
+      "dependencies": {
+        "tr46": "^3.0.0",
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/jsesc": {
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -8502,7 +8943,6 @@
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
       "dev": true,
-      "peer": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -8515,7 +8955,6 @@
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
       "dev": true,
-      "peer": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -11216,6 +11655,12 @@
       "inBundle": true,
       "license": "ISC"
     },
+    "node_modules/nwsapi": {
+      "version": "2.2.13",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz",
+      "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==",
+      "dev": true
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -11781,6 +12226,12 @@
       "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
       "dev": true
     },
+    "node_modules/psl": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+      "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+      "dev": true
+    },
     "node_modules/punycode": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -11806,6 +12257,12 @@
         }
       ]
     },
+    "node_modules/querystringify": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+      "dev": true
+    },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -11994,6 +12451,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+      "dev": true
+    },
     "node_modules/resolve": {
       "version": "1.22.8",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -12116,6 +12579,24 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "node_modules/saxes": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+      "dev": true,
+      "dependencies": {
+        "xmlchars": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=v12.22.7"
+      }
+    },
     "node_modules/schema-utils": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -12960,6 +13441,12 @@
         "webpack": ">=2"
       }
     },
+    "node_modules/symbol-tree": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+      "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+      "dev": true
+    },
     "node_modules/tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -13268,6 +13755,30 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/tough-cookie": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+      "dev": true,
+      "dependencies": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tough-cookie/node_modules/universalify": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
     "node_modules/tr46": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@@ -13563,6 +14074,16 @@
         "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
       }
     },
+    "node_modules/url-parse": {
+      "version": "1.5.10",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "dev": true,
+      "dependencies": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -13594,6 +14115,18 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "node_modules/w3c-xmlserializer": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+      "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+      "dev": true,
+      "dependencies": {
+        "xml-name-validator": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/walker": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -13617,6 +14150,15 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/web-streams-polyfill": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz",
+      "integrity": "sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -13711,6 +14253,27 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/whatwg-encoding": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+      "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+      "dev": true,
+      "dependencies": {
+        "iconv-lite": "0.6.3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/whatwg-mimetype": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+      "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/whatwg-url": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
@@ -13873,6 +14436,42 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/ws": {
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+      "dev": true,
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xml-name-validator": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+      "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/xmlchars": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+      "dev": true
+    },
     "node_modules/xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 931b907..ea7da96 100644
--- a/package.json
+++ b/package.json
@@ -85,13 +85,16 @@
     "@swc/core": "^1.7.26",
     "@swc/jest": "^0.2.36",
     "@types/jest": "^29.5.13",
+    "blob-polyfill": "^9.0.20240710",
     "eslint": "^9.11.1",
     "husky": "^9.1.6",
     "jest": "^29.7.0",
+    "jest-environment-jsdom": "^29.7.0",
     "prettier": "^3.3.3",
     "swc-loader": "^0.2.6",
     "tsup": "^8.3.0",
     "typescript": "^5.6.2",
-    "typescript-eslint": "^8.7.0"
+    "typescript-eslint": "^8.7.0",
+    "web-streams-polyfill": "^4.0.0"
   }
 }
diff --git a/src/index.ts b/src/index.ts
index 798227b..7fd2e25 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
 export { getObjectDiff } from "./lib/object-diff";
 export { getListDiff } from "./lib/list-diff";
 export { isEqual, isObject } from "./lib/utils";
-export { streamListDiff } from "./lib/stream-list-diff";
+export * from "./lib/stream-list-diff";
 export * from "./models/list";
 export * from "./models/object";
 export * from "./models/stream";
diff --git a/src/lib/stream-list-diff/client/index.ts b/src/lib/stream-list-diff/client/index.ts
new file mode 100644
index 0000000..76304a0
--- /dev/null
+++ b/src/lib/stream-list-diff/client/index.ts
@@ -0,0 +1,289 @@
+import { isClient } from "@lib/utils";
+import {
+  DataBuffer,
+  DEFAULT_LIST_STREAM_OPTIONS,
+  ListStreamOptions,
+  ReferenceProperty,
+} from "@models/stream";
+import { LIST_STATUS } from "@models/list";
+import {
+  Emitter,
+  EmitterEvents,
+  EventEmitter,
+  StreamListener,
+  StreamEvent,
+} from "../emitter";
+import { isDataValid, isValidChunkSize, outputDiffChunk } from "../utils";
+
+async function getDiffChunks<T extends Record<string, unknown>>(
+  prevStream: ReadableStream<T>,
+  nextStream: ReadableStream<T>,
+  referenceProperty: ReferenceProperty<T>,
+  emitter: Emitter<T>,
+  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
+): Promise<void> {
+  if (!isValidChunkSize(options?.chunksSize)) {
+    return emitter.emit(
+      StreamEvent.Error,
+      new Error(
+        `The chunk size can't be negative. You entered the value '${options.chunksSize}'`,
+      ),
+    );
+  }
+
+  const prevList = prevStream.getReader();
+  const nextList = nextStream.getReader();
+  const { handleDiffChunk, releaseLastChunks } = outputDiffChunk<T>(emitter);
+  const prevDataBuffer: DataBuffer<T> = new Map();
+  const nextDataBuffer: DataBuffer<T> = new Map();
+  let currentPrevIndex = 0;
+  let currentNextIndex = 0;
+
+  async function processPrevStreamChunk(chunk: T) {
+    const { isValid, message } = isDataValid(
+      chunk,
+      referenceProperty,
+      "prevList",
+    );
+    if (!isValid) {
+      emitter.emit(StreamEvent.Error, new Error(message));
+      emitter.emit(StreamEvent.Finish);
+      return;
+    }
+    const ref = chunk[referenceProperty] as ReferenceProperty<T>;
+    const relatedChunk = nextDataBuffer.get(ref);
+
+    if (relatedChunk) {
+      nextDataBuffer.delete(ref);
+      const isDataEqual =
+        JSON.stringify(chunk) === JSON.stringify(relatedChunk.data);
+      const indexDiff = (relatedChunk.index as number) - currentPrevIndex;
+      if (isDataEqual) {
+        handleDiffChunk(
+          {
+            previousValue: chunk,
+            currentValue: relatedChunk.data,
+            prevIndex: currentPrevIndex,
+            newIndex: relatedChunk.index,
+            indexDiff,
+            status:
+              indexDiff === 0
+                ? LIST_STATUS.EQUAL
+                : options.considerMoveAsUpdate
+                  ? LIST_STATUS.UPDATED
+                  : LIST_STATUS.MOVED,
+          },
+          options,
+        );
+      } else {
+        handleDiffChunk(
+          {
+            previousValue: chunk,
+            currentValue: relatedChunk.data,
+            prevIndex: currentPrevIndex,
+            newIndex: relatedChunk.index,
+            indexDiff,
+            status: LIST_STATUS.UPDATED,
+          },
+          options,
+        );
+      }
+    } else {
+      prevDataBuffer.set(ref, { data: chunk, index: currentPrevIndex });
+    }
+    currentPrevIndex++;
+  }
+
+  async function processNextStreamChunk(chunk: T) {
+    const { isValid, message } = isDataValid(
+      chunk,
+      referenceProperty,
+      "nextList",
+    );
+    if (!isValid) {
+      emitter.emit(StreamEvent.Error, new Error(message));
+      emitter.emit(StreamEvent.Finish);
+      return;
+    }
+    const ref = chunk[referenceProperty] as ReferenceProperty<T>;
+    const relatedChunk = prevDataBuffer.get(ref);
+
+    if (relatedChunk) {
+      prevDataBuffer.delete(ref);
+      const isDataEqual =
+        JSON.stringify(chunk) === JSON.stringify(relatedChunk.data);
+      const indexDiff = currentNextIndex - (relatedChunk.index as number);
+      if (isDataEqual) {
+        handleDiffChunk(
+          {
+            previousValue: relatedChunk.data,
+            currentValue: chunk,
+            prevIndex: relatedChunk.index,
+            newIndex: currentNextIndex,
+            indexDiff,
+            status:
+              indexDiff === 0
+                ? LIST_STATUS.EQUAL
+                : options.considerMoveAsUpdate
+                  ? LIST_STATUS.UPDATED
+                  : LIST_STATUS.MOVED,
+          },
+          options,
+        );
+      } else {
+        handleDiffChunk(
+          {
+            previousValue: relatedChunk.data,
+            currentValue: chunk,
+            prevIndex: relatedChunk.index,
+            newIndex: currentNextIndex,
+            indexDiff,
+            status: LIST_STATUS.UPDATED,
+          },
+          options,
+        );
+      }
+    } else {
+      nextDataBuffer.set(ref, { data: chunk, index: currentNextIndex });
+    }
+    currentNextIndex++;
+  }
+
+  const readStream = async (
+    reader: ReadableStreamDefaultReader<T>,
+    processChunk: (chunk: T) => Promise<void>,
+  ) => {
+    let result;
+    while (!(result = await reader.read()).done) {
+      await processChunk(result.value);
+    }
+  };
+
+  await Promise.all([
+    readStream(prevList, async (chunk) => {
+      await processPrevStreamChunk(chunk);
+    }),
+    readStream(nextList, async (chunk) => {
+      await processNextStreamChunk(chunk);
+    }),
+  ]);
+
+  for (const [key, chunk] of prevDataBuffer.entries()) {
+    handleDiffChunk(
+      {
+        previousValue: chunk.data,
+        currentValue: null,
+        prevIndex: chunk.index,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      options,
+    );
+    prevDataBuffer.delete(key);
+  }
+  for (const [key, chunk] of nextDataBuffer.entries()) {
+    handleDiffChunk(
+      {
+        previousValue: null,
+        currentValue: chunk.data,
+        prevIndex: null,
+        newIndex: chunk.index,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+      options,
+    );
+    nextDataBuffer.delete(key);
+  }
+
+  releaseLastChunks();
+  return emitter.emit(StreamEvent.Finish);
+}
+
+async function getValidClientStream<T extends Record<string, unknown>>(
+  input: ReadableStream<T> | T[] | File,
+  listType: "prevList" | "nextList",
+): Promise<ReadableStream<T>> {
+  if (Array.isArray(input)) {
+    return new ReadableStream({
+      start(controller) {
+        input.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+  }
+
+  if (input instanceof ReadableStream) {
+    return input;
+  }
+
+  if (input instanceof File) {
+    const fileText = await input.text();
+    let jsonData: T[];
+    try {
+      jsonData = JSON.parse(fileText);
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    } catch (_: unknown) {
+      throw new Error(`Your ${listType} is not a valid JSON array.`);
+    }
+
+    if (!Array.isArray(jsonData)) {
+      throw new Error(`Your ${listType} is not a JSON array.`);
+    }
+    return new ReadableStream({
+      start(controller) {
+        jsonData.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+  }
+
+  throw new Error(
+    `Invalid ${listType}. Expected ReadableStream, Array, or File.`,
+  );
+}
+
+/**
+ * Streams the diff of two object lists
+ * @param {Record<string, unknown>[]} prevList - The original 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 {ListStreamOptions} options - Options to refine your output.
+    - `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"]`)
+    - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated`
+ * @returns EventEmitter
+ */
+export function streamListDiffClient<T extends Record<string, unknown>>(
+  prevList: ReadableStream<T> | File | T[],
+  nextList: ReadableStream<T> | File | T[],
+  referenceProperty: ReferenceProperty<T>,
+  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
+): StreamListener<T> {
+  if (!isClient()) {
+    throw new Error(
+      "streamListDiffClient can only be used in a browser environment. Please use streamListDiff instead.",
+    );
+  }
+  const emitter = new EventEmitter<EmitterEvents<T>>();
+  setTimeout(async () => {
+    try {
+      const [prevStream, nextStream] = await Promise.all([
+        getValidClientStream(prevList, "prevList"),
+        getValidClientStream(nextList, "nextList"),
+      ]);
+
+      getDiffChunks(
+        prevStream,
+        nextStream,
+        referenceProperty,
+        emitter,
+        options,
+      );
+    } catch (err) {
+      return emitter.emit(StreamEvent.Error, err as Error);
+    }
+  }, 0);
+  return emitter as StreamListener<T>;
+}
diff --git a/src/lib/stream-list-diff/client/stream-list-diff-client.test.ts b/src/lib/stream-list-diff/client/stream-list-diff-client.test.ts
new file mode 100644
index 0000000..bc39cf9
--- /dev/null
+++ b/src/lib/stream-list-diff/client/stream-list-diff-client.test.ts
@@ -0,0 +1,1062 @@
+/**
+ * @jest-environment jsdom
+ */
+import "blob-polyfill";
+import { ReadableStream } from "web-streams-polyfill";
+import { LIST_STATUS } from "@models/list";
+import { StreamListDiff } from "@models/stream";
+import { streamListDiffClient } from ".";
+import prevListFile from "../../../mocks/prevList.json";
+import nextListFile from "../../../mocks/nextList.json";
+
+//@ts-expect-error - the ReadableStream polyfill is necessary to test ReadableStream in a Node environment.
+global.ReadableStream = ReadableStream;
+
+describe("data emission", () => {
+  it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => {
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const diff = streamListDiffClient([], nextList, "id", { chunksSize: 2 });
+
+    const expectedChunks = [
+      {
+        previousValue: null,
+        currentValue: { id: 1, name: "Item 1" },
+        prevIndex: null,
+        newIndex: 0,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+      {
+        previousValue: null,
+        currentValue: { id: 2, name: "Item 2" },
+        prevIndex: null,
+        newIndex: 1,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+    ];
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("emits 'data' event and consider the all the prevList deleted if no nextList is provided", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const diff = streamListDiffClient(prevList, [], "id", { chunksSize: 2 });
+
+    const expectedChunks = [
+      {
+        previousValue: { id: 1, name: "Item 1" },
+        currentValue: null,
+        prevIndex: 0,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      {
+        previousValue: { id: 2, name: "Item 2" },
+        currentValue: null,
+        prevIndex: 1,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+    ];
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("shiiiite", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("emits 'data' event with one object diff by chunk if chunkSize is 0 or undefined", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id");
+
+    const expectedChunks = [
+      [
+        {
+          previousValue: { id: 2, name: "Item 2" },
+          currentValue: { id: 2, name: "Item 2" },
+          prevIndex: 1,
+          newIndex: 0,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
+        },
+      ],
+      [
+        {
+          previousValue: { id: 1, name: "Item 1" },
+          currentValue: null,
+          prevIndex: 0,
+          newIndex: null,
+          indexDiff: null,
+          status: LIST_STATUS.DELETED,
+        },
+      ],
+      [
+        {
+          previousValue: null,
+          currentValue: { id: 3, name: "Item 3" },
+          prevIndex: null,
+          newIndex: 1,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
+        },
+      ],
+    ];
+
+    let chunkCount = 0;
+
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks[chunkCount]);
+      chunkCount++;
+    });
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(3);
+      done();
+    });
+  });
+  it("emits 'data' event with 5 object diff by chunk and return the last object diff in a one entry chunk at the end", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+      { id: 4, name: "Item 4" },
+      { id: 5, name: "Item 5" },
+      { id: 6, name: "Item 6" },
+      { id: 7, name: "Item 7" },
+      { id: 8, name: "Item 8" },
+      { id: 9, name: "Item 9" },
+      { id: 10, name: "Item 10" },
+    ];
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item Two" },
+      { id: 3, name: "Item 3" },
+      { id: 5, name: "Item 5" },
+      { id: 6, name: "Item Six" },
+      { id: 7, name: "Item 7" },
+      { id: 10, name: "Item 10" },
+      { id: 11, name: "Item 11" },
+      { id: 9, name: "Item 9" },
+      { id: 8, name: "Item 8" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    const expectedChunks = [
+      [
+        {
+          previousValue: { id: 1, name: "Item 1" },
+          currentValue: { id: 1, name: "Item 1" },
+          prevIndex: 0,
+          newIndex: 0,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: { id: 2, name: "Item 2" },
+          currentValue: { id: 2, name: "Item Two" },
+          prevIndex: 1,
+          newIndex: 1,
+          indexDiff: 0,
+          status: LIST_STATUS.UPDATED,
+        },
+        {
+          previousValue: { id: 3, name: "Item 3" },
+          currentValue: { id: 3, name: "Item 3" },
+          prevIndex: 2,
+          newIndex: 2,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: { id: 5, name: "Item 5" },
+          currentValue: { id: 5, name: "Item 5" },
+          prevIndex: 4,
+          newIndex: 3,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 6, name: "Item 6" },
+          currentValue: { id: 6, name: "Item Six" },
+          prevIndex: 5,
+          newIndex: 4,
+          indexDiff: -1,
+          status: LIST_STATUS.UPDATED,
+        },
+      ],
+      [
+        {
+          previousValue: { id: 7, name: "Item 7" },
+          currentValue: { id: 7, name: "Item 7" },
+          prevIndex: 6,
+          newIndex: 5,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 9, name: "Item 9" },
+          currentValue: { id: 9, name: "Item 9" },
+          prevIndex: 8,
+          newIndex: 8,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: { id: 10, name: "Item 10" },
+          currentValue: { id: 10, name: "Item 10" },
+          prevIndex: 9,
+          newIndex: 6,
+          indexDiff: -3,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 8, name: "Item 8" },
+          currentValue: { id: 8, name: "Item 8" },
+          prevIndex: 7,
+          newIndex: 9,
+          indexDiff: 2,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 4, name: "Item 4" },
+          currentValue: null,
+          prevIndex: 3,
+          newIndex: null,
+          indexDiff: null,
+          status: LIST_STATUS.DELETED,
+        },
+      ],
+      [
+        {
+          previousValue: null,
+          currentValue: { id: 11, name: "Item 11" },
+          prevIndex: null,
+          newIndex: 7,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
+        },
+      ],
+    ];
+
+    let chunkCount = 0;
+
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks[chunkCount]);
+      chunkCount++;
+    });
+
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(3);
+      done();
+    });
+  });
+  it("emits 'data' event with all the objects diff in a single chunk if the chunkSize is bigger than the provided lists ", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+      { id: 4, name: "Item 4" },
+    ];
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item Two" },
+      { id: 3, name: "Item 3" },
+      { id: 5, name: "Item 5" },
+    ];
+
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: 150,
+    });
+
+    const expectedChunks = [
+      {
+        previousValue: { id: 1, name: "Item 1" },
+        currentValue: { id: 1, name: "Item 1" },
+        prevIndex: 0,
+        newIndex: 0,
+        indexDiff: 0,
+        status: LIST_STATUS.EQUAL,
+      },
+      {
+        previousValue: { id: 2, name: "Item 2" },
+        currentValue: { id: 2, name: "Item Two" },
+        prevIndex: 1,
+        newIndex: 1,
+        indexDiff: 0,
+        status: LIST_STATUS.UPDATED,
+      },
+      {
+        previousValue: { id: 3, name: "Item 3" },
+        currentValue: { id: 3, name: "Item 3" },
+        prevIndex: 2,
+        newIndex: 2,
+        indexDiff: 0,
+        status: LIST_STATUS.EQUAL,
+      },
+      {
+        previousValue: { id: 4, name: "Item 4" },
+        currentValue: null,
+        prevIndex: 3,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+    ];
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("emits 'data' event with moved objects considered as updated if considerMoveAsUpdate is true", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+      { id: 4, name: "Item 4" },
+    ];
+    const nextList = [
+      { id: 2, name: "Item Two" },
+      { id: 1, name: "Item 1" },
+      { id: 3, name: "Item 3" },
+      { id: 5, name: "Item 5" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: 5,
+      considerMoveAsUpdate: true,
+    });
+
+    const expectedChunks = [
+      {
+        previousValue: { id: 2, name: "Item 2" },
+        currentValue: { id: 2, name: "Item Two" },
+        prevIndex: 1,
+        newIndex: 0,
+        indexDiff: -1,
+        status: LIST_STATUS.UPDATED,
+      },
+      {
+        previousValue: { id: 1, name: "Item 1" },
+        currentValue: { id: 1, name: "Item 1" },
+        prevIndex: 0,
+        newIndex: 1,
+        indexDiff: 1,
+        status: LIST_STATUS.UPDATED,
+      },
+      {
+        previousValue: { id: 3, name: "Item 3" },
+        currentValue: { id: 3, name: "Item 3" },
+        prevIndex: 2,
+        newIndex: 2,
+        indexDiff: 0,
+        status: LIST_STATUS.EQUAL,
+      },
+      {
+        previousValue: { id: 4, name: "Item 4" },
+        currentValue: null,
+        prevIndex: 3,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+    ];
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("emits 'data' event only with objects diff whose status match with showOnly's", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+      { id: 4, name: "Item 4" },
+    ];
+    const nextList = [
+      { id: 2, name: "Item Two" },
+      { id: 1, name: "Item 1" },
+      { id: 3, name: "Item 3" },
+      { id: 5, name: "Item 5" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: 5,
+      showOnly: ["added", "deleted"],
+    });
+
+    const expectedChunks = [
+      {
+        previousValue: { id: 4, name: "Item 4" },
+        currentValue: null,
+        prevIndex: 3,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+    ];
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("emits 'data' event with deep nested objects diff", (done) => {
+    const prevList = [
+      {
+        id: 1,
+        name: "Item 1",
+        user: { role: "admin", hobbies: ["golf", "football"] },
+      },
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3", user: { role: "admin", hobbies: ["rugby"] } },
+      {
+        id: 4,
+        name: "Item 4",
+        user: { role: "reader", hobbies: ["video games", "fishing"] },
+      },
+      { id: 5, name: "Item 5" },
+      { id: 6, name: "Item 6", user: { role: "root", hobbies: ["coding"] } },
+      { id: 7, name: "Item 7" },
+      { id: 8, name: "Item 8" },
+      { id: 9, name: "Item 9" },
+      {
+        id: 10,
+        name: "Item 10",
+        user: {
+          role: "root",
+          hobbies: ["coding"],
+          skills: { driving: true, diving: false },
+        },
+      },
+    ];
+    const nextList = [
+      {
+        id: 1,
+        name: "Item 1",
+        user: { role: "admin", hobbies: ["golf", "football"] },
+      },
+      { id: 2, name: "Item Two" },
+      { id: 3, name: "Item 3", user: { role: "admin", hobbies: ["rugby"] } },
+      { id: 5, name: "Item 5" },
+      { id: 6, name: "Item 6", user: { role: "root", hobbies: ["farming"] } },
+      { id: 7, name: "Item 7" },
+      {
+        id: 10,
+        name: "Item 10",
+        user: {
+          role: "root",
+          hobbies: ["coding"],
+          skills: { driving: true, diving: false },
+        },
+      },
+      { id: 11, name: "Item 11" },
+      { id: 9, name: "Item 9" },
+      { id: 8, name: "Item 8" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    const expectedChunks = [
+      [
+        {
+          previousValue: {
+            id: 1,
+            name: "Item 1",
+            user: { role: "admin", hobbies: ["golf", "football"] },
+          },
+          currentValue: {
+            id: 1,
+            name: "Item 1",
+            user: { role: "admin", hobbies: ["golf", "football"] },
+          },
+          prevIndex: 0,
+          newIndex: 0,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: { id: 2, name: "Item 2" },
+          currentValue: { id: 2, name: "Item Two" },
+          prevIndex: 1,
+          newIndex: 1,
+          indexDiff: 0,
+          status: LIST_STATUS.UPDATED,
+        },
+        {
+          previousValue: {
+            id: 3,
+            name: "Item 3",
+            user: { role: "admin", hobbies: ["rugby"] },
+          },
+          currentValue: {
+            id: 3,
+            name: "Item 3",
+            user: { role: "admin", hobbies: ["rugby"] },
+          },
+          prevIndex: 2,
+          newIndex: 2,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: { id: 5, name: "Item 5" },
+          currentValue: { id: 5, name: "Item 5" },
+          prevIndex: 4,
+          newIndex: 3,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: {
+            id: 6,
+            name: "Item 6",
+            user: { role: "root", hobbies: ["coding"] },
+          },
+          currentValue: {
+            id: 6,
+            name: "Item 6",
+            user: { role: "root", hobbies: ["farming"] },
+          },
+          prevIndex: 5,
+          newIndex: 4,
+          indexDiff: -1,
+          status: LIST_STATUS.UPDATED,
+        },
+      ],
+      [
+        {
+          previousValue: { id: 7, name: "Item 7" },
+          currentValue: { id: 7, name: "Item 7" },
+          prevIndex: 6,
+          newIndex: 5,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 9, name: "Item 9" },
+          currentValue: { id: 9, name: "Item 9" },
+          prevIndex: 8,
+          newIndex: 8,
+          indexDiff: 0,
+          status: LIST_STATUS.EQUAL,
+        },
+        {
+          previousValue: {
+            id: 10,
+            name: "Item 10",
+            user: {
+              role: "root",
+              hobbies: ["coding"],
+              skills: { driving: true, diving: false },
+            },
+          },
+          currentValue: {
+            id: 10,
+            name: "Item 10",
+            user: {
+              role: "root",
+              hobbies: ["coding"],
+              skills: { driving: true, diving: false },
+            },
+          },
+          prevIndex: 9,
+          newIndex: 6,
+          indexDiff: -3,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 8, name: "Item 8" },
+          currentValue: { id: 8, name: "Item 8" },
+          prevIndex: 7,
+          newIndex: 9,
+          indexDiff: 2,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: {
+            id: 4,
+            name: "Item 4",
+            user: { role: "reader", hobbies: ["video games", "fishing"] },
+          },
+          currentValue: null,
+          prevIndex: 3,
+          newIndex: null,
+          indexDiff: null,
+          status: LIST_STATUS.DELETED,
+        },
+      ],
+      [
+        {
+          previousValue: null,
+          currentValue: { id: 11, name: "Item 11" },
+          prevIndex: null,
+          newIndex: 7,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
+        },
+      ],
+    ];
+
+    let chunkCount = 0;
+
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks[chunkCount]);
+      chunkCount++;
+    });
+
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(3);
+      done();
+    });
+  });
+});
+
+describe("input handling", () => {
+  const prevList = [
+    { id: 1, name: "Item 1" },
+    { id: 2, name: "Item 2" },
+    { id: 3, name: "Item 3" },
+    { id: 4, name: "Item 4" },
+  ];
+  const nextList = [
+    { id: 1, name: "Item 1" },
+    { id: 2, name: "Item Two" },
+    { id: 3, name: "Item 3" },
+    { id: 5, name: "Item 5" },
+  ];
+  const expectedChunks = [
+    {
+      previousValue: { id: 1, name: "Item 1" },
+      currentValue: { id: 1, name: "Item 1" },
+      prevIndex: 0,
+      newIndex: 0,
+      indexDiff: 0,
+      status: LIST_STATUS.EQUAL,
+    },
+    {
+      previousValue: { id: 2, name: "Item 2" },
+      currentValue: { id: 2, name: "Item Two" },
+      prevIndex: 1,
+      newIndex: 1,
+      indexDiff: 0,
+      status: LIST_STATUS.UPDATED,
+    },
+    {
+      previousValue: { id: 3, name: "Item 3" },
+      currentValue: { id: 3, name: "Item 3" },
+      prevIndex: 2,
+      newIndex: 2,
+      indexDiff: 0,
+      status: LIST_STATUS.EQUAL,
+    },
+    {
+      previousValue: { id: 4, name: "Item 4" },
+      currentValue: null,
+      prevIndex: 3,
+      newIndex: null,
+      indexDiff: null,
+      status: LIST_STATUS.DELETED,
+    },
+    {
+      previousValue: null,
+      currentValue: { id: 5, name: "Item 5" },
+      prevIndex: null,
+      newIndex: 3,
+      indexDiff: null,
+      status: LIST_STATUS.ADDED,
+    },
+  ];
+
+  it("handles two readable streams", (done) => {
+    const prevStream = new ReadableStream({
+      start(controller) {
+        prevList.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+    const nextStream = new ReadableStream({
+      start(controller) {
+        nextList.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+
+    const diff = streamListDiffClient(prevStream, nextStream, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles two local files", (done) => {
+    const prevFile = new File([JSON.stringify(prevListFile)], "prevList.json", {
+      type: "application/json",
+    });
+
+    const nextFile = new File([JSON.stringify(nextListFile)], "nextList.json", {
+      type: "application/json",
+    });
+
+    const diff = streamListDiffClient(prevFile, nextFile, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a readable stream against a local file", (done) => {
+    const prevStream = new ReadableStream({
+      start(controller) {
+        prevList.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+    const nextFile = new File([JSON.stringify(nextListFile)], "nextList.json", {
+      type: "application/json",
+    });
+
+    const diff = streamListDiffClient(prevStream, nextFile, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a readable stream against an array", (done) => {
+    const prevStream = new ReadableStream({
+      start(controller) {
+        prevList.forEach((item) => controller.enqueue(item));
+        controller.close();
+      },
+    });
+
+    const diff = streamListDiffClient(prevStream, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a local file against an array", (done) => {
+    const prevFile = new File([JSON.stringify(prevListFile)], "prevList.json", {
+      type: "application/json",
+    });
+
+    const diff = streamListDiffClient(prevFile, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+});
+
+describe("finish event", () => {
+  it("emits 'finish' event if no prevList nor nextList is provided", (done) => {
+    const diff = streamListDiffClient([], [], "id");
+    diff.on("finish", () => done());
+  });
+  it("emits 'finish' event when all the chunks have been processed", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [
+      { id: 2, name: "Item 2" },
+      { id: 3, name: "Item 3" },
+    ];
+    const diff = streamListDiffClient(prevList, nextList, "id");
+    diff.on("finish", () => done());
+  });
+});
+
+describe("error event", () => {
+  test("emits 'error' event when prevList has invalid data", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      "hello",
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+
+    // @ts-expect-error prevList is invalid by design for the test
+    const diff = streamListDiffClient(prevList, nextList, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        `Your prevList must only contain valid objects. Found 'hello'`,
+      );
+      done();
+    });
+  });
+
+  test("emits 'error' event when nextList has invalid data", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      "hello",
+      { id: 2, name: "Item 2" },
+    ];
+
+    // @ts-expect-error nextList is invalid by design for the test
+    const diff = streamListDiffClient(prevList, nextList, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        `Your nextList must only contain valid objects. Found 'hello'`,
+      );
+      done();
+    });
+  });
+
+  test("emits 'error' event when all prevList ojects don't have the requested reference property", (done) => {
+    const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
+    const nextList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+
+    const diff = streamListDiffClient(prevList, nextList, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        `The reference property 'id' is not available in all the objects of your prevList.`,
+      );
+      done();
+    });
+  });
+
+  test("emits 'error' event when all nextList ojects don't have the requested reference property", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
+
+    const diff = streamListDiffClient(prevList, nextList, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        `The reference property 'id' is not available in all the objects of your nextList.`,
+      );
+      done();
+    });
+  });
+
+  test("emits 'error' event when the chunkSize option is negative", (done) => {
+    const prevList = [
+      { id: 1, name: "Item 1" },
+      { id: 2, name: "Item 2" },
+    ];
+    const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
+
+    const diff = streamListDiffClient(prevList, nextList, "id", {
+      chunksSize: -3,
+    });
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        "The chunk size can't be negative. You entered the value '-3'",
+      );
+      done();
+    });
+  });
+
+  test("emits 'error' event when the prevList is not a valid type", (done) => {
+    const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
+
+    // @ts-expect-error - prevList is invalid by design for the test
+    const diff = streamListDiffClient({ name: "hello" }, nextList, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        "Invalid prevList. Expected ReadableStream, Array, or File.",
+      );
+      done();
+    });
+  });
+  test("emits 'error' event when the nextList is not a valid type", (done) => {
+    const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
+
+    // @ts-expect-error - nextList is invalid by design for the test
+    const diff = streamListDiffClient(prevList, null, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        "Invalid nextList. Expected ReadableStream, Array, or File.",
+      );
+      done();
+    });
+  });
+});
+
+const generateLargeDataset = (count: number) => {
+  const data: Array<{ id: number; value: string }> = [];
+  for (let i = 0; i < count; i++) {
+    data.push({ id: i, value: `value-${i}` });
+  }
+  return data;
+};
+
+describe("performance", () => {
+  it("process 100.000 in each stream", (done) => {
+    const numEntries = 100_000;
+
+    const prevList = generateLargeDataset(numEntries);
+    const nextList = generateLargeDataset(numEntries);
+
+    nextList[100].value = "updated-value-100"; // 1 updated entry
+    nextList[20_000].value = "updated-value-20000"; // Another updated entry
+    nextList.push({ id: numEntries, value: `new-value-${numEntries}` }); // 1 added entry
+
+    const diffListener = streamListDiffClient<{ id: number; value: string }>(
+      prevList,
+      nextList,
+      "id",
+      {
+        chunksSize: 10_000,
+      },
+    );
+
+    const diffs: StreamListDiff<{ id: number; value: string }>[] = [];
+
+    diffListener.on("data", (chunk) => {
+      diffs.push(...chunk);
+    });
+
+    diffListener.on("finish", () => {
+      try {
+        const updatedEntries = diffs.filter((d) => d.status === "updated");
+        const addedEntries = diffs.filter((d) => d.status === "added");
+        const deletedEntries = diffs.filter((d) => d.status === "deleted");
+        const equalEntries = diffs.filter((d) => d.status === "equal");
+
+        expect(updatedEntries.length).toBe(2);
+        expect(addedEntries.length).toBe(1);
+        expect(deletedEntries.length).toBe(0);
+        expect(equalEntries.length).toBe(99998);
+        done();
+      } catch (err) {
+        done(err);
+      }
+    });
+
+    diffListener.on("error", (err) => done(err));
+  });
+});
diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts
index e949619..9e512c8 100644
--- a/src/lib/stream-list-diff/index.ts
+++ b/src/lib/stream-list-diff/index.ts
@@ -1,327 +1,2 @@
-import {
-  DEFAULT_LIST_STREAM_OPTIONS,
-  ListStreamOptions,
-  ReferenceProperty,
-  StreamListDiff,
-  StreamReferences,
-} from "@models/stream";
-import { LIST_STATUS } from "@models/list";
-import { isObject } from "@lib/utils";
-import {
-  Emitter,
-  EmitterEvents,
-  EventEmitter,
-  StreamListener,
-  StreamEvent,
-} from "./emitter";
-
-function outputDiffChunk<T extends Record<string, unknown>>(
-  emitter: Emitter<T>,
-) {
-  let chunks: StreamListDiff<T>[] = [];
-
-  return function handleDiffChunk(
-    chunk: StreamListDiff<T>,
-    isLastChunk: boolean,
-    options: ListStreamOptions,
-  ): void {
-    const showChunk = options?.showOnly
-      ? options?.showOnly.includes(chunk.status)
-      : true;
-    if (!showChunk) {
-      return;
-    }
-    if ((options.chunksSize as number) > 0) {
-      chunks.push(chunk);
-      if (chunks.length >= (options.chunksSize as number) || isLastChunk) {
-        const output = chunks;
-        chunks = [];
-        return emitter.emit(StreamEvent.Data, output);
-      } else {
-        return;
-      }
-    }
-    return emitter.emit(StreamEvent.Data, [chunk]);
-  };
-}
-
-function formatSingleListStreamDiff<T extends Record<string, unknown>>(
-  list: T[],
-  isPrevious: boolean,
-  status: LIST_STATUS,
-  options: ListStreamOptions,
-): StreamListDiff<T>[] | null {
-  let isValid = true;
-  const diff: StreamListDiff<T>[] = [];
-  for (let i = 0; i < list.length; i++) {
-    const data = list[i];
-    if (!isObject(data)) {
-      isValid = false;
-      break;
-    }
-    diff.push({
-      previousValue: isPrevious ? data : null,
-      currentValue: isPrevious ? null : data,
-      prevIndex: status === LIST_STATUS.ADDED ? null : i,
-      newIndex: status === LIST_STATUS.ADDED ? i : null,
-      indexDiff: null,
-      status,
-    });
-  }
-  if (!isValid) {
-    return null;
-  }
-  if (options.showOnly && options.showOnly.length > 0) {
-    return diff.filter((value) => options.showOnly?.includes(value.status));
-  }
-  return diff;
-}
-
-function isValidChunkSize(
-  chunksSize: ListStreamOptions["chunksSize"],
-): boolean {
-  if (!chunksSize) return true;
-  const sign = String(Math.sign(chunksSize));
-  return sign !== "-1" && sign !== "NaN";
-}
-
-function isDataValid<T extends Record<string, unknown>>(
-  data: T,
-  referenceProperty: ReferenceProperty<T>,
-  listType: "prevList" | "nextList",
-): { isValid: boolean; message?: string } {
-  if (!isObject(data)) {
-    return {
-      isValid: false,
-      message: `Your ${listType} must only contain valid objects. Found '${data}'`,
-    };
-  }
-  if (!Object.hasOwn(data, referenceProperty)) {
-    return {
-      isValid: false,
-      message: `The reference property '${String(referenceProperty)}' is not available in all the objects of your ${listType}.`,
-    };
-  }
-  return {
-    isValid: true,
-    message: "",
-  };
-}
-
-function getDiffChunks<T extends Record<string, unknown>>(
-  prevList: T[] = [],
-  nextList: T[] = [],
-  referenceProperty: ReferenceProperty<T>,
-  emitter: Emitter<T>,
-  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
-): void {
-  if (!isValidChunkSize(options?.chunksSize)) {
-    return emitter.emit(
-      StreamEvent.Error,
-      new Error(
-        `The chunk size can't be negative. You entered the value '${options.chunksSize}'`,
-      ),
-    );
-  }
-  if (prevList.length === 0 && nextList.length === 0) {
-    return emitter.emit(StreamEvent.Finish);
-  }
-  const handleDiffChunk = outputDiffChunk<T>(emitter);
-  if (prevList.length === 0) {
-    const nextDiff = formatSingleListStreamDiff(
-      nextList as T[],
-      false,
-      LIST_STATUS.ADDED,
-      options,
-    );
-    if (!nextDiff) {
-      emitter.emit(
-        StreamEvent.Error,
-        new Error("Your nextList must only contain valid objects."),
-      );
-      return emitter.emit(StreamEvent.Finish);
-    }
-    nextDiff?.forEach((data, i) =>
-      handleDiffChunk(data, i === nextDiff.length - 1, options),
-    );
-    return emitter.emit(StreamEvent.Finish);
-  }
-  if (nextList.length === 0) {
-    const prevDiff = formatSingleListStreamDiff(
-      prevList as T[],
-      true,
-      LIST_STATUS.DELETED,
-      options,
-    );
-    if (!prevDiff) {
-      emitter.emit(
-        StreamEvent.Error,
-        new Error("Your prevList must only contain valid objects."),
-      );
-      return emitter.emit(StreamEvent.Finish);
-    }
-    prevDiff?.forEach((data, i) =>
-      handleDiffChunk(data, i === prevDiff.length - 1, options),
-    );
-    return emitter.emit(StreamEvent.Finish);
-  }
-  const listsReferences: StreamReferences<T> = new Map();
-  for (let i = 0; i < prevList.length; i++) {
-    const data = prevList[i];
-    if (data) {
-      const { isValid, message } = isDataValid(
-        data,
-        referenceProperty,
-        "prevList",
-      );
-      if (!isValid) {
-        emitter.emit(StreamEvent.Error, new Error(message));
-        emitter.emit(StreamEvent.Finish);
-        break;
-      }
-      listsReferences.set(String(data[referenceProperty]), {
-        prevIndex: i,
-        nextIndex: undefined,
-      });
-    }
-  }
-
-  const totalChunks = listsReferences.size;
-
-  for (let i = 0; i < nextList.length; i++) {
-    const data = nextList[i];
-    if (data) {
-      const { isValid, message } = isDataValid(
-        data,
-        referenceProperty,
-        "nextList",
-      );
-      if (!isValid) {
-        emitter.emit(StreamEvent.Error, new Error(message));
-        emitter.emit(StreamEvent.Finish);
-        break;
-      }
-      const listReference = listsReferences.get(
-        String(data[referenceProperty]),
-      );
-      if (listReference) {
-        listReference.nextIndex = i;
-      } else {
-        handleDiffChunk(
-          {
-            previousValue: null,
-            currentValue: data,
-            prevIndex: null,
-            newIndex: i,
-            indexDiff: null,
-            status: LIST_STATUS.ADDED,
-          },
-          totalChunks > 0 ? false : i === nextList.length - 1,
-          options,
-        );
-      }
-    }
-  }
-
-  let streamedChunks = 0;
-
-  for (const [key, data] of listsReferences.entries()) {
-    streamedChunks++;
-    const isLastChunk = totalChunks === streamedChunks;
-
-    if (typeof data.nextIndex === "undefined") {
-      handleDiffChunk(
-        {
-          previousValue: prevList[data.prevIndex],
-          currentValue: null,
-          prevIndex: data.prevIndex,
-          newIndex: null,
-          indexDiff: null,
-          status: LIST_STATUS.DELETED,
-        },
-        isLastChunk,
-        options,
-      );
-    } else {
-      const prevData = prevList[data.prevIndex];
-      const nextData = nextList[data.nextIndex];
-      const isDataEqual = JSON.stringify(prevData) === JSON.stringify(nextData);
-      const indexDiff = data.nextIndex - data.prevIndex;
-      if (isDataEqual) {
-        if (indexDiff === 0) {
-          handleDiffChunk(
-            {
-              previousValue: prevList[data.prevIndex],
-              currentValue: nextList[data.nextIndex],
-              prevIndex: data.prevIndex,
-              newIndex: data.nextIndex,
-              indexDiff: 0,
-              status: LIST_STATUS.EQUAL,
-            },
-            isLastChunk,
-            options,
-          );
-        } else {
-          handleDiffChunk(
-            {
-              previousValue: prevList[data.prevIndex],
-              currentValue: nextList[data.nextIndex],
-              prevIndex: data.prevIndex,
-              newIndex: data.nextIndex,
-              indexDiff,
-              status: options.considerMoveAsUpdate
-                ? LIST_STATUS.UPDATED
-                : LIST_STATUS.MOVED,
-            },
-            isLastChunk,
-            options,
-          );
-        }
-      } else {
-        handleDiffChunk(
-          {
-            previousValue: prevList[data.prevIndex],
-            currentValue: nextList[data.nextIndex],
-            prevIndex: data.prevIndex,
-            newIndex: data.nextIndex,
-            indexDiff,
-            status: LIST_STATUS.UPDATED,
-          },
-          isLastChunk,
-          options,
-        );
-      }
-    }
-    listsReferences.delete(key);
-  }
-
-  return emitter.emit(StreamEvent.Finish);
-}
-
-/**
- * Streams the diff of two object lists
- * @param {Record<string, unknown>[]} prevList - The original 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 {ListStreamOptions} options - Options to refine your output.
-    - `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"]`)
-    - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated`
- * @returns EventEmitter
- */
-export function streamListDiff<T extends Record<string, unknown>>(
-  prevList: T[],
-  nextList: T[],
-  referenceProperty: ReferenceProperty<T>,
-  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
-): StreamListener<T> {
-  const emitter = new EventEmitter<EmitterEvents<T>>();
-  setTimeout(() => {
-    try {
-      getDiffChunks(prevList, nextList, referenceProperty, emitter, options);
-    } catch (err) {
-      return emitter.emit(StreamEvent.Error, err as Error);
-    }
-  }, 0);
-  return emitter as StreamListener<T>;
-}
+export { streamListDiffClient } from "./client";
+export { streamListDiff } from "./server";
diff --git a/src/lib/stream-list-diff/server/index.ts b/src/lib/stream-list-diff/server/index.ts
new file mode 100644
index 0000000..dc327e7
--- /dev/null
+++ b/src/lib/stream-list-diff/server/index.ts
@@ -0,0 +1,261 @@
+import { createReadStream } from "fs";
+import { Readable, Transform } from "stream";
+import { LIST_STATUS } from "@models/list";
+import {
+  DataBuffer,
+  DEFAULT_LIST_STREAM_OPTIONS,
+  FilePath,
+  ListStreamOptions,
+  ReferenceProperty,
+} from "@models/stream";
+import { isClient } from "@lib/utils";
+import {
+  Emitter,
+  EmitterEvents,
+  EventEmitter,
+  StreamListener,
+  StreamEvent,
+} from "../emitter";
+import { isDataValid, isValidChunkSize, outputDiffChunk } from "../utils";
+
+async function getDiffChunks<T extends Record<string, unknown>>(
+  prevStream: Readable,
+  nextStream: Readable,
+  referenceProperty: ReferenceProperty<T>,
+  emitter: Emitter<T>,
+  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
+): Promise<void> {
+  if (!isValidChunkSize(options?.chunksSize)) {
+    return emitter.emit(
+      StreamEvent.Error,
+      new Error(
+        `The chunk size can't be negative. You entered the value '${options.chunksSize}'`,
+      ),
+    );
+  }
+  const { handleDiffChunk, releaseLastChunks } = outputDiffChunk<T>(emitter);
+  const prevDataBuffer: DataBuffer<T> = new Map();
+  const nextDataBuffer: DataBuffer<T> = new Map();
+  let currentPrevIndex = 0;
+  let currentNextIndex = 0;
+
+  async function processPrevStreamChunk(chunk: T) {
+    const { isValid, message } = isDataValid(
+      chunk,
+      referenceProperty,
+      "prevList",
+    );
+    if (!isValid) {
+      emitter.emit(StreamEvent.Error, new Error(message));
+      emitter.emit(StreamEvent.Finish);
+      return;
+    }
+    const ref = chunk[referenceProperty] as ReferenceProperty<T>;
+    const relatedChunk = nextDataBuffer.get(ref);
+
+    if (relatedChunk) {
+      nextDataBuffer.delete(ref);
+      const isDataEqual =
+        JSON.stringify(chunk) === JSON.stringify(relatedChunk.data);
+      const indexDiff = (relatedChunk.index as number) - currentPrevIndex;
+      if (isDataEqual) {
+        handleDiffChunk(
+          {
+            previousValue: chunk,
+            currentValue: relatedChunk.data,
+            prevIndex: currentPrevIndex,
+            newIndex: relatedChunk.index,
+            indexDiff,
+            status:
+              indexDiff === 0
+                ? LIST_STATUS.EQUAL
+                : options.considerMoveAsUpdate
+                  ? LIST_STATUS.UPDATED
+                  : LIST_STATUS.MOVED,
+          },
+          options,
+        );
+      } else {
+        handleDiffChunk(
+          {
+            previousValue: chunk,
+            currentValue: relatedChunk.data,
+            prevIndex: currentPrevIndex,
+            newIndex: relatedChunk.index,
+            indexDiff,
+            status: LIST_STATUS.UPDATED,
+          },
+          options,
+        );
+      }
+    } else {
+      prevDataBuffer.set(ref, { data: chunk, index: currentPrevIndex });
+    }
+    currentPrevIndex++;
+  }
+
+  async function processNextStreamChunk(chunk: T) {
+    const { isValid, message } = isDataValid(
+      chunk,
+      referenceProperty,
+      "nextList",
+    );
+    if (!isValid) {
+      emitter.emit(StreamEvent.Error, new Error(message));
+      emitter.emit(StreamEvent.Finish);
+      return;
+    }
+    const ref = chunk[referenceProperty] as ReferenceProperty<T>;
+    const relatedChunk = prevDataBuffer.get(ref);
+
+    if (relatedChunk) {
+      prevDataBuffer.delete(ref);
+      const isDataEqual =
+        JSON.stringify(chunk) === JSON.stringify(relatedChunk.data);
+      const indexDiff = currentNextIndex - (relatedChunk.index as number);
+      if (isDataEqual) {
+        handleDiffChunk(
+          {
+            previousValue: relatedChunk.data,
+            currentValue: chunk,
+            prevIndex: relatedChunk.index,
+            newIndex: currentNextIndex,
+            indexDiff,
+            status:
+              indexDiff === 0
+                ? LIST_STATUS.EQUAL
+                : options.considerMoveAsUpdate
+                  ? LIST_STATUS.UPDATED
+                  : LIST_STATUS.MOVED,
+          },
+          options,
+        );
+      } else {
+        handleDiffChunk(
+          {
+            previousValue: relatedChunk.data,
+            currentValue: chunk,
+            prevIndex: relatedChunk.index,
+            newIndex: currentNextIndex,
+            indexDiff,
+            status: LIST_STATUS.UPDATED,
+          },
+          options,
+        );
+      }
+    } else {
+      nextDataBuffer.set(ref, { data: chunk, index: currentNextIndex });
+    }
+    currentNextIndex++;
+  }
+
+  const prevStreamReader = async () => {
+    for await (const chunk of prevStream) {
+      await processPrevStreamChunk(chunk);
+    }
+  };
+
+  const nextStreamReader = async () => {
+    for await (const chunk of nextStream) {
+      await processNextStreamChunk(chunk);
+    }
+  };
+  await Promise.all([prevStreamReader(), nextStreamReader()]);
+
+  for (const [key, chunk] of prevDataBuffer.entries()) {
+    handleDiffChunk(
+      {
+        previousValue: chunk.data,
+        currentValue: null,
+        prevIndex: chunk.index,
+        newIndex: null,
+        indexDiff: null,
+        status: LIST_STATUS.DELETED,
+      },
+      options,
+    );
+    prevDataBuffer.delete(key);
+  }
+  for (const [key, chunk] of nextDataBuffer.entries()) {
+    handleDiffChunk(
+      {
+        previousValue: null,
+        currentValue: chunk.data,
+        prevIndex: null,
+        newIndex: chunk.index,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
+      options,
+    );
+    nextDataBuffer.delete(key);
+  }
+  releaseLastChunks();
+  return emitter.emit(StreamEvent.Finish);
+}
+
+function getValidStream<T>(
+  input: Readable | FilePath | T[],
+  listType: "prevList" | "nextList",
+): Readable {
+  if (input instanceof Readable) {
+    return input;
+  }
+
+  if (Array.isArray(input)) {
+    return Readable.from(input, { objectMode: true });
+  }
+
+  if (typeof input === "string") {
+    return createReadStream(input, { encoding: "utf8" }).pipe(
+      new Transform({
+        objectMode: true,
+        transform(chunk, _, callback) {
+          try {
+            const data: T = JSON.parse(chunk.toString());
+            if (Array.isArray(data)) {
+              for (let i = 0; i < data.length; i++) {
+                this.push(data[i]);
+              }
+            } else {
+              this.push(data);
+            }
+            callback();
+          } catch (err) {
+            callback(err as Error);
+          }
+        },
+      }),
+    );
+  }
+
+  throw new Error(`Invalid ${listType}. Expected Readable, Array, or File.`);
+}
+
+export function streamListDiff<T extends Record<string, unknown>>(
+  prevStream: Readable | FilePath | T[],
+  nextStream: Readable | FilePath | T[],
+  referenceProperty: ReferenceProperty<T>,
+  options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
+): StreamListener<T> {
+  if (isClient()) {
+    throw new Error(
+      "streamListDiff can only be used in Node environment. Please use streamListDiffClient instead.",
+    );
+  }
+  const emitter = new EventEmitter<EmitterEvents<T>>();
+  setTimeout(async () => {
+    try {
+      await getDiffChunks(
+        getValidStream(prevStream, "prevList"),
+        getValidStream(nextStream, "nextList"),
+        referenceProperty,
+        emitter,
+        options,
+      );
+    } catch (err) {
+      return emitter.emit(StreamEvent.Error, err as Error);
+    }
+  }, 0);
+  return emitter as StreamListener<T>;
+}
diff --git a/src/lib/stream-list-diff/stream-list-diff.test.ts b/src/lib/stream-list-diff/server/stream-list-diff.test.ts
similarity index 74%
rename from src/lib/stream-list-diff/stream-list-diff.test.ts
rename to src/lib/stream-list-diff/server/stream-list-diff.test.ts
index 5d8795c..f1751d0 100644
--- a/src/lib/stream-list-diff/stream-list-diff.test.ts
+++ b/src/lib/stream-list-diff/server/stream-list-diff.test.ts
@@ -1,8 +1,10 @@
+import path from "path";
+import { Readable } from "stream";
 import { LIST_STATUS } from "@models/list";
-import { streamListDiff } from ".";
 import { StreamListDiff } from "@models/stream";
+import { streamListDiff } from ".";
 
-describe("streamListDiff data", () => {
+describe("data emission", () => {
   it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => {
     const nextList = [
       { id: 1, name: "Item 1" },
@@ -68,6 +70,7 @@ describe("streamListDiff data", () => {
       expect(chunk).toStrictEqual(expectedChunks);
       chunkCount++;
     });
+    diff.on("error", (err) => console.error("shiiiite", err));
     diff.on("finish", () => {
       expect(chunkCount).toBe(1);
       done();
@@ -87,12 +90,12 @@ describe("streamListDiff data", () => {
     const expectedChunks = [
       [
         {
-          previousValue: null,
-          currentValue: { id: 3, name: "Item 3" },
-          prevIndex: null,
-          newIndex: 1,
-          indexDiff: null,
-          status: LIST_STATUS.ADDED,
+          previousValue: { id: 2, name: "Item 2" },
+          currentValue: { id: 2, name: "Item 2" },
+          prevIndex: 1,
+          newIndex: 0,
+          indexDiff: -1,
+          status: LIST_STATUS.MOVED,
         },
       ],
       [
@@ -107,12 +110,12 @@ describe("streamListDiff data", () => {
       ],
       [
         {
-          previousValue: { id: 2, name: "Item 2" },
-          currentValue: { id: 2, name: "Item 2" },
-          prevIndex: 1,
-          newIndex: 0,
-          indexDiff: -1,
-          status: LIST_STATUS.MOVED,
+          previousValue: null,
+          currentValue: { id: 3, name: "Item 3" },
+          prevIndex: null,
+          newIndex: 1,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
         },
       ],
     ];
@@ -153,18 +156,12 @@ describe("streamListDiff data", () => {
       { id: 9, name: "Item 9" },
       { id: 8, name: "Item 8" },
     ];
-    const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 });
+    const diff = streamListDiff(prevList, nextList, "id", {
+      chunksSize: 5,
+    });
 
     const expectedChunks = [
       [
-        {
-          previousValue: null,
-          currentValue: { id: 11, name: "Item 11" },
-          prevIndex: null,
-          newIndex: 7,
-          indexDiff: null,
-          status: LIST_STATUS.ADDED,
-        },
         {
           previousValue: { id: 1, name: "Item 1" },
           currentValue: { id: 1, name: "Item 1" },
@@ -189,16 +186,6 @@ describe("streamListDiff data", () => {
           indexDiff: 0,
           status: LIST_STATUS.EQUAL,
         },
-        {
-          previousValue: { id: 4, name: "Item 4" },
-          currentValue: null,
-          prevIndex: 3,
-          newIndex: null,
-          indexDiff: null,
-          status: LIST_STATUS.DELETED,
-        },
-      ],
-      [
         {
           previousValue: { id: 5, name: "Item 5" },
           currentValue: { id: 5, name: "Item 5" },
@@ -215,6 +202,8 @@ describe("streamListDiff data", () => {
           indexDiff: -1,
           status: LIST_STATUS.UPDATED,
         },
+      ],
+      [
         {
           previousValue: { id: 7, name: "Item 7" },
           currentValue: { id: 7, name: "Item 7" },
@@ -223,14 +212,6 @@ describe("streamListDiff data", () => {
           indexDiff: -1,
           status: LIST_STATUS.MOVED,
         },
-        {
-          previousValue: { id: 8, name: "Item 8" },
-          currentValue: { id: 8, name: "Item 8" },
-          prevIndex: 7,
-          newIndex: 9,
-          indexDiff: 2,
-          status: LIST_STATUS.MOVED,
-        },
         {
           previousValue: { id: 9, name: "Item 9" },
           currentValue: { id: 9, name: "Item 9" },
@@ -239,8 +220,6 @@ describe("streamListDiff data", () => {
           indexDiff: 0,
           status: LIST_STATUS.EQUAL,
         },
-      ],
-      [
         {
           previousValue: { id: 10, name: "Item 10" },
           currentValue: { id: 10, name: "Item 10" },
@@ -249,6 +228,32 @@ describe("streamListDiff data", () => {
           indexDiff: -3,
           status: LIST_STATUS.MOVED,
         },
+        {
+          previousValue: { id: 8, name: "Item 8" },
+          currentValue: { id: 8, name: "Item 8" },
+          prevIndex: 7,
+          newIndex: 9,
+          indexDiff: 2,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: { id: 4, name: "Item 4" },
+          currentValue: null,
+          prevIndex: 3,
+          newIndex: null,
+          indexDiff: null,
+          status: LIST_STATUS.DELETED,
+        },
+      ],
+      [
+        {
+          previousValue: null,
+          currentValue: { id: 11, name: "Item 11" },
+          prevIndex: null,
+          newIndex: 7,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
+        },
       ],
     ];
 
@@ -271,23 +276,19 @@ describe("streamListDiff data", () => {
       { id: 3, name: "Item 3" },
       { id: 4, name: "Item 4" },
     ];
+
     const nextList = [
       { id: 1, name: "Item 1" },
       { id: 2, name: "Item Two" },
       { id: 3, name: "Item 3" },
       { id: 5, name: "Item 5" },
     ];
-    const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 150 });
+
+    const diff = streamListDiff(prevList, nextList, "id", {
+      chunksSize: 5,
+    });
 
     const expectedChunks = [
-      {
-        previousValue: null,
-        currentValue: { id: 5, name: "Item 5" },
-        prevIndex: null,
-        newIndex: 3,
-        indexDiff: null,
-        status: LIST_STATUS.ADDED,
-      },
       {
         previousValue: { id: 1, name: "Item 1" },
         currentValue: { id: 1, name: "Item 1" },
@@ -320,6 +321,14 @@ describe("streamListDiff data", () => {
         indexDiff: null,
         status: LIST_STATUS.DELETED,
       },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
     ];
 
     let chunkCount = 0;
@@ -327,7 +336,7 @@ describe("streamListDiff data", () => {
       expect(chunk).toStrictEqual(expectedChunks);
       chunkCount++;
     });
-
+    diff.on("error", (err) => console.error("sheeeet", err));
     diff.on("finish", () => {
       expect(chunkCount).toBe(1);
       done();
@@ -353,12 +362,12 @@ describe("streamListDiff data", () => {
 
     const expectedChunks = [
       {
-        previousValue: null,
-        currentValue: { id: 5, name: "Item 5" },
-        prevIndex: null,
-        newIndex: 3,
-        indexDiff: null,
-        status: LIST_STATUS.ADDED,
+        previousValue: { id: 2, name: "Item 2" },
+        currentValue: { id: 2, name: "Item Two" },
+        prevIndex: 1,
+        newIndex: 0,
+        indexDiff: -1,
+        status: LIST_STATUS.UPDATED,
       },
       {
         previousValue: { id: 1, name: "Item 1" },
@@ -368,14 +377,6 @@ describe("streamListDiff data", () => {
         indexDiff: 1,
         status: LIST_STATUS.UPDATED,
       },
-      {
-        previousValue: { id: 2, name: "Item 2" },
-        currentValue: { id: 2, name: "Item Two" },
-        prevIndex: 1,
-        newIndex: 0,
-        indexDiff: -1,
-        status: LIST_STATUS.UPDATED,
-      },
       {
         previousValue: { id: 3, name: "Item 3" },
         currentValue: { id: 3, name: "Item 3" },
@@ -392,6 +393,14 @@ describe("streamListDiff data", () => {
         indexDiff: null,
         status: LIST_STATUS.DELETED,
       },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
     ];
 
     let chunkCount = 0;
@@ -424,14 +433,6 @@ describe("streamListDiff data", () => {
     });
 
     const expectedChunks = [
-      {
-        previousValue: null,
-        currentValue: { id: 5, name: "Item 5" },
-        prevIndex: null,
-        newIndex: 3,
-        indexDiff: null,
-        status: LIST_STATUS.ADDED,
-      },
       {
         previousValue: { id: 4, name: "Item 4" },
         currentValue: null,
@@ -440,6 +441,14 @@ describe("streamListDiff data", () => {
         indexDiff: null,
         status: LIST_STATUS.DELETED,
       },
+      {
+        previousValue: null,
+        currentValue: { id: 5, name: "Item 5" },
+        prevIndex: null,
+        newIndex: 3,
+        indexDiff: null,
+        status: LIST_STATUS.ADDED,
+      },
     ];
 
     let chunkCount = 0;
@@ -506,18 +515,12 @@ describe("streamListDiff data", () => {
       { id: 9, name: "Item 9" },
       { id: 8, name: "Item 8" },
     ];
-    const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 });
+    const diff = streamListDiff(prevList, nextList, "id", {
+      chunksSize: 5,
+    });
 
     const expectedChunks = [
       [
-        {
-          previousValue: null,
-          currentValue: { id: 11, name: "Item 11" },
-          prevIndex: null,
-          newIndex: 7,
-          indexDiff: null,
-          status: LIST_STATUS.ADDED,
-        },
         {
           previousValue: {
             id: 1,
@@ -558,20 +561,6 @@ describe("streamListDiff data", () => {
           indexDiff: 0,
           status: LIST_STATUS.EQUAL,
         },
-        {
-          previousValue: {
-            id: 4,
-            name: "Item 4",
-            user: { role: "reader", hobbies: ["video games", "fishing"] },
-          },
-          currentValue: null,
-          prevIndex: 3,
-          newIndex: null,
-          indexDiff: null,
-          status: LIST_STATUS.DELETED,
-        },
-      ],
-      [
         {
           previousValue: { id: 5, name: "Item 5" },
           currentValue: { id: 5, name: "Item 5" },
@@ -596,6 +585,8 @@ describe("streamListDiff data", () => {
           indexDiff: -1,
           status: LIST_STATUS.UPDATED,
         },
+      ],
+      [
         {
           previousValue: { id: 7, name: "Item 7" },
           currentValue: { id: 7, name: "Item 7" },
@@ -604,14 +595,6 @@ describe("streamListDiff data", () => {
           indexDiff: -1,
           status: LIST_STATUS.MOVED,
         },
-        {
-          previousValue: { id: 8, name: "Item 8" },
-          currentValue: { id: 8, name: "Item 8" },
-          prevIndex: 7,
-          newIndex: 9,
-          indexDiff: 2,
-          status: LIST_STATUS.MOVED,
-        },
         {
           previousValue: { id: 9, name: "Item 9" },
           currentValue: { id: 9, name: "Item 9" },
@@ -620,8 +603,6 @@ describe("streamListDiff data", () => {
           indexDiff: 0,
           status: LIST_STATUS.EQUAL,
         },
-      ],
-      [
         {
           previousValue: {
             id: 10,
@@ -646,6 +627,36 @@ describe("streamListDiff data", () => {
           indexDiff: -3,
           status: LIST_STATUS.MOVED,
         },
+        {
+          previousValue: { id: 8, name: "Item 8" },
+          currentValue: { id: 8, name: "Item 8" },
+          prevIndex: 7,
+          newIndex: 9,
+          indexDiff: 2,
+          status: LIST_STATUS.MOVED,
+        },
+        {
+          previousValue: {
+            id: 4,
+            name: "Item 4",
+            user: { role: "reader", hobbies: ["video games", "fishing"] },
+          },
+          currentValue: null,
+          prevIndex: 3,
+          newIndex: null,
+          indexDiff: null,
+          status: LIST_STATUS.DELETED,
+        },
+      ],
+      [
+        {
+          previousValue: null,
+          currentValue: { id: 11, name: "Item 11" },
+          prevIndex: null,
+          newIndex: 7,
+          indexDiff: null,
+          status: LIST_STATUS.ADDED,
+        },
       ],
     ];
 
@@ -663,7 +674,158 @@ describe("streamListDiff data", () => {
   });
 });
 
-describe("streamListDiff finish", () => {
+describe("input handling", () => {
+  const prevList = [
+    { id: 1, name: "Item 1" },
+    { id: 2, name: "Item 2" },
+    { id: 3, name: "Item 3" },
+    { id: 4, name: "Item 4" },
+  ];
+  const nextList = [
+    { id: 1, name: "Item 1" },
+    { id: 2, name: "Item Two" },
+    { id: 3, name: "Item 3" },
+    { id: 5, name: "Item 5" },
+  ];
+  const expectedChunks = [
+    {
+      previousValue: { id: 1, name: "Item 1" },
+      currentValue: { id: 1, name: "Item 1" },
+      prevIndex: 0,
+      newIndex: 0,
+      indexDiff: 0,
+      status: LIST_STATUS.EQUAL,
+    },
+    {
+      previousValue: { id: 2, name: "Item 2" },
+      currentValue: { id: 2, name: "Item Two" },
+      prevIndex: 1,
+      newIndex: 1,
+      indexDiff: 0,
+      status: LIST_STATUS.UPDATED,
+    },
+    {
+      previousValue: { id: 3, name: "Item 3" },
+      currentValue: { id: 3, name: "Item 3" },
+      prevIndex: 2,
+      newIndex: 2,
+      indexDiff: 0,
+      status: LIST_STATUS.EQUAL,
+    },
+    {
+      previousValue: { id: 4, name: "Item 4" },
+      currentValue: null,
+      prevIndex: 3,
+      newIndex: null,
+      indexDiff: null,
+      status: LIST_STATUS.DELETED,
+    },
+    {
+      previousValue: null,
+      currentValue: { id: 5, name: "Item 5" },
+      prevIndex: null,
+      newIndex: 3,
+      indexDiff: null,
+      status: LIST_STATUS.ADDED,
+    },
+  ];
+
+  it("handles two readable streams", (done) => {
+    const prevStream = Readable.from(prevList, { objectMode: true });
+    const nextStream = Readable.from(nextList, { objectMode: true });
+
+    const diff = streamListDiff(prevStream, nextStream, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles two local files", (done) => {
+    const prevFile = path.resolve(__dirname, "../../../mocks/prevList.json");
+    const nextFile = path.resolve(__dirname, "../../../mocks/nextList.json");
+
+    const diff = streamListDiff(prevFile, nextFile, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a readable stream against a local file", (done) => {
+    const prevStream = Readable.from(prevList, { objectMode: true });
+    const nextFile = path.resolve(__dirname, "../../../mocks/nextList.json");
+
+    const diff = streamListDiff(prevStream, nextFile, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a readable stream against an array", (done) => {
+    const prevStream = Readable.from(prevList, { objectMode: true });
+
+    const diff = streamListDiff(prevStream, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+  it("handles a local file against an array", (done) => {
+    const prevFile = path.resolve(__dirname, "../../../mocks/prevList.json");
+
+    const diff = streamListDiff(prevFile, nextList, "id", {
+      chunksSize: 5,
+    });
+
+    let chunkCount = 0;
+    diff.on("data", (chunk) => {
+      expect(chunk).toStrictEqual(expectedChunks);
+      chunkCount++;
+    });
+    diff.on("error", (err) => console.error("sheeeet", err));
+    diff.on("finish", () => {
+      expect(chunkCount).toBe(1);
+      done();
+    });
+  });
+});
+
+describe("finish event", () => {
   it("emits 'finish' event if no prevList nor nextList is provided", (done) => {
     const diff = streamListDiff([], [], "id");
     diff.on("finish", () => done());
@@ -682,7 +844,7 @@ describe("streamListDiff finish", () => {
   });
 });
 
-describe("streamListDiff error", () => {
+describe("error event", () => {
   test("emits 'error' event when prevList has invalid data", (done) => {
     const prevList = [
       { id: 1, name: "Item 1" },
@@ -768,7 +930,9 @@ describe("streamListDiff error", () => {
     ];
     const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
 
-    const diff = streamListDiff(prevList, nextList, "id", { chunksSize: -3 });
+    const diff = streamListDiff(prevList, nextList, "id", {
+      chunksSize: -3,
+    });
 
     diff.on("error", (err) => {
       expect(err["message"]).toEqual(
@@ -777,91 +941,86 @@ describe("streamListDiff error", () => {
       done();
     });
   });
-});
 
-describe("Performance", () => {
-  it("should correctly stream diff for 10.000 entries", (done) => {
-    const generateLargeList = (size: number, idPrefix: string) => {
-      return Array.from({ length: size }, (_, i) => ({
-        id: `${idPrefix}-${i}`,
-        value: i,
-      }));
-    };
-    const prevList = generateLargeList(10_000, "prev");
-    const nextList = [
-      ...generateLargeList(5000, "prev"),
-      ...generateLargeList(5000, "next"),
-    ];
+  test("emits 'error' event when the prevList is not a valid type", (done) => {
+    const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
 
-    const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = [];
-    let chunkCount = 0;
-    const diffStream = streamListDiff(prevList, nextList, "id", {
-      chunksSize: 1000,
-    });
+    // @ts-expect-error - prevList is invalid by design for the test
+    const diff = streamListDiff({ name: "hello" }, nextList, "id");
 
-    diffStream.on("data", (chunk) => {
-      receivedChunks.push(...chunk);
-      chunkCount++;
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        "Invalid prevList. Expected Readable, Array, or File.",
+      );
+      done();
     });
+  });
+  test("emits 'error' event when the nextList is not a valid type", (done) => {
+    const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
 
-    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,
+    // @ts-expect-error - nextList is invalid by design for the test
+    const diff = streamListDiff(prevList, null, "id");
+
+    diff.on("error", (err) => {
+      expect(err["message"]).toEqual(
+        "Invalid nextList. Expected Readable, Array, or File.",
       );
-      expect(receivedChunks.length).toBe(15_000); // 5000 deletions + 5000 equal + 5000 additions
-      expect(chunkCount).toBe(15);
-      expect(deletions.length).toBe(5000);
-      expect(additions.length).toBe(5000);
-      expect(updates.length).toBe(5000);
       done();
     });
   });
-  it("should correctly stream diff for 100.000 entries", (done) => {
-    const generateLargeList = (size: number, idPrefix: string) => {
-      return Array.from({ length: size }, (_, i) => ({
-        id: `${idPrefix}-${i}`,
-        value: i,
-      }));
-    };
-    const prevList = generateLargeList(100_000, "prev");
-    const nextList = [
-      ...generateLargeList(50000, "prev"),
-      ...generateLargeList(50000, "next"),
-    ];
+});
 
-    const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = [];
-    let chunkCount = 0;
-    const diffStream = streamListDiff(prevList, nextList, "id", {
-      chunksSize: 10_000,
-    });
+const generateLargeDataset = (count: number) => {
+  const data: Array<{ id: number; value: string }> = [];
+  for (let i = 0; i < count; i++) {
+    data.push({ id: i, value: `value-${i}` });
+  }
+  return data;
+};
 
-    diffStream.on("data", (chunk) => {
-      receivedChunks.push(...chunk);
-      chunkCount++;
+describe("performance", () => {
+  it("process 100.000 in each stream", (done) => {
+    const numEntries = 100_000;
+
+    const prevList = generateLargeDataset(numEntries);
+    const nextList = generateLargeDataset(numEntries);
+
+    nextList[100].value = "updated-value-100"; // 1 updated entry
+    nextList[20_000].value = "updated-value-20000"; // Another updated entry
+    nextList.push({ id: numEntries, value: `new-value-${numEntries}` }); // 1 added entry
+
+    const diffListener = streamListDiff<{ id: number; value: string }>(
+      prevList,
+      nextList,
+      "id",
+      {
+        chunksSize: 10_000,
+      },
+    );
+
+    const diffs: StreamListDiff<{ id: number; value: string }>[] = [];
+
+    diffListener.on("data", (chunk) => {
+      diffs.push(...chunk);
     });
 
-    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(150_000); // 50.000 deletions + 50.000 equal + 50.000 additions
-      expect(chunkCount).toBe(15);
-      expect(deletions.length).toBe(50000);
-      expect(additions.length).toBe(50000);
-      expect(updates.length).toBe(50000);
-      done();
+    diffListener.on("finish", () => {
+      try {
+        const updatedEntries = diffs.filter((d) => d.status === "updated");
+        const addedEntries = diffs.filter((d) => d.status === "added");
+        const deletedEntries = diffs.filter((d) => d.status === "deleted");
+        const equalEntries = diffs.filter((d) => d.status === "equal");
+
+        expect(updatedEntries.length).toBe(2);
+        expect(addedEntries.length).toBe(1);
+        expect(deletedEntries.length).toBe(0);
+        expect(equalEntries.length).toBe(99998);
+        done();
+      } catch (err) {
+        done(err);
+      }
     });
+
+    diffListener.on("error", (err) => done(err));
   });
 });
diff --git a/src/lib/stream-list-diff/utils.ts b/src/lib/stream-list-diff/utils.ts
new file mode 100644
index 0000000..8356343
--- /dev/null
+++ b/src/lib/stream-list-diff/utils.ts
@@ -0,0 +1,80 @@
+import { isObject } from "@lib/utils";
+import {
+  ListStreamOptions,
+  ReferenceProperty,
+  StreamListDiff,
+} from "@models/stream";
+import { Emitter, StreamEvent } from "./emitter";
+
+export function isValidChunkSize(
+  chunksSize: ListStreamOptions["chunksSize"],
+): boolean {
+  if (!chunksSize) return true;
+  const sign = String(Math.sign(chunksSize));
+  return sign !== "-1" && sign !== "NaN";
+}
+
+export function isDataValid<T extends Record<string, unknown>>(
+  data: T,
+  referenceProperty: ReferenceProperty<T>,
+  listType: "prevList" | "nextList",
+): { isValid: boolean; message?: string } {
+  if (!isObject(data)) {
+    return {
+      isValid: false,
+      message: `Your ${listType} must only contain valid objects. Found '${data}'`,
+    };
+  }
+  if (!Object.hasOwn(data, referenceProperty)) {
+    return {
+      isValid: false,
+      message: `The reference property '${String(referenceProperty)}' is not available in all the objects of your ${listType}.`,
+    };
+  }
+  return {
+    isValid: true,
+    message: "",
+  };
+}
+
+export function outputDiffChunk<T extends Record<string, unknown>>(
+  emitter: Emitter<T>,
+) {
+  let chunks: StreamListDiff<T>[] = [];
+
+  function handleDiffChunk(
+    chunk: StreamListDiff<T>,
+    options: ListStreamOptions,
+  ): void {
+    const showChunk = options?.showOnly
+      ? options?.showOnly.includes(chunk.status)
+      : true;
+    if (!showChunk) {
+      return;
+    }
+    if ((options.chunksSize as number) > 0) {
+      chunks.push(chunk);
+      if (chunks.length >= (options.chunksSize as number)) {
+        const output = chunks;
+        chunks = [];
+        return emitter.emit(StreamEvent.Data, output);
+      } else {
+        return;
+      }
+    }
+    return emitter.emit(StreamEvent.Data, [chunk]);
+  }
+
+  function releaseLastChunks() {
+    if (chunks.length > 0) {
+      const output = chunks;
+      chunks = [];
+      return emitter.emit(StreamEvent.Data, output);
+    }
+  }
+
+  return {
+    handleDiffChunk,
+    releaseLastChunks,
+  };
+}
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
index 1e26b91..435fece 100644
--- a/src/lib/utils/index.ts
+++ b/src/lib/utils/index.ts
@@ -38,3 +38,5 @@ export function isEqual(
 export function isObject(value: unknown): value is Record<string, unknown> {
   return !!value && typeof value === "object" && !Array.isArray(value);
 }
+
+export const isClient = () => typeof window !== "undefined";
diff --git a/src/mocks/nextList.json b/src/mocks/nextList.json
new file mode 100644
index 0000000..942a1d5
--- /dev/null
+++ b/src/mocks/nextList.json
@@ -0,0 +1,6 @@
+[
+    { "id": 1, "name": "Item 1" },
+    { "id": 2, "name": "Item Two" },
+    { "id": 3, "name": "Item 3" },
+    { "id": 5, "name": "Item 5" }
+]
\ No newline at end of file
diff --git a/src/mocks/prevList.json b/src/mocks/prevList.json
new file mode 100644
index 0000000..00502da
--- /dev/null
+++ b/src/mocks/prevList.json
@@ -0,0 +1,6 @@
+[
+    { "id": 1, "name": "Item 1" },
+    { "id": 2, "name": "Item 2" },
+    { "id": 3, "name": "Item 3" },
+    { "id": 4, "name": "Item 4" }
+  ]
\ No newline at end of file
diff --git a/src/models/stream/index.ts b/src/models/stream/index.ts
index d5d0df4..f62b50d 100644
--- a/src/models/stream/index.ts
+++ b/src/models/stream/index.ts
@@ -16,6 +16,14 @@ export type StreamReferences<T extends Record<string, unknown>> = Map<
   { prevIndex: number; nextIndex?: number }
 >;
 
+export type DataBuffer<T extends Record<string, unknown>> = Map<
+  ReferenceProperty<T>,
+  {
+    data: T | null;
+    index: number | null;
+  }
+>;
+
 export type ListStreamOptions = {
   chunksSize?: number; // 0 by default.
   showOnly?: `${LIST_STATUS}`[];
@@ -25,3 +33,5 @@ export type ListStreamOptions = {
 export const DEFAULT_LIST_STREAM_OPTIONS: ListStreamOptions = {
   chunksSize: 0,
 };
+
+export type FilePath = string;