diff --git a/.prettierignore b/.prettierignore index 3b5e17d..d474d0b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,3 @@ jest.config.js package-lock.json README.md tsconfig.json -tsup.config.ts \ No newline at end of file diff --git a/README.md b/README.md index 2a265de..5198e2f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ I am grateful to the generous donors of **Superdiff**! ## FEATURES -**Superdiff** exports 6 functions: +**Superdiff** exports 5 functions: ```ts // Returns a complete diff of two objects @@ -52,9 +52,6 @@ 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) @@ -310,9 +307,9 @@ getListDiff( ```js // If you are in a server environment -import { streamListDiff } from "@donedeal0/superdiff"; +import { streamListDiff } from "@donedeal0/superdiff/server"; // If you are in a browser environment -import { streamListDiffClient } from "@donedeal0/superdiff"; +import { streamListDiff } from "@donedeal0/superdiff/client"; ``` Streams the diff of two object lists, ideal for large lists and maximum performance. @@ -321,12 +318,11 @@ Streams the diff of two object lists, ideal for large lists and maximum performa **Input** -#### streamListDiff (server) +#### 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 -// streamListDiff prevList: Readable | FilePath | Record[], nextList: Readable | FilePath | Record[], referenceProperty: keyof Record, @@ -337,7 +333,7 @@ Streams the diff of two object lists, ideal for large lists and maximum performa } ``` -#### streamListDiffClient (browser) +#### 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. @@ -495,74 +491,6 @@ diff.on("finish", () => console.log("Your data has been processed. The full diff 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); - } -} -``` -
### isEqual() diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..4a958f1 --- /dev/null +++ b/src/client.ts @@ -0,0 +1 @@ +export { streamListDiff } from "./lib/stream-list-diff/client"; diff --git a/src/index.ts b/src/index.ts index 7fd2e25..5a2e03d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ export { getObjectDiff } from "./lib/object-diff"; export { getListDiff } from "./lib/list-diff"; export { isEqual, isObject } from "./lib/utils"; -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 index 76304a0..d35ae98 100644 --- a/src/lib/stream-list-diff/client/index.ts +++ b/src/lib/stream-list-diff/client/index.ts @@ -1,4 +1,3 @@ -import { isClient } from "@lib/utils"; import { DataBuffer, DEFAULT_LIST_STREAM_OPTIONS, @@ -246,26 +245,21 @@ async function getValidClientStream>( /** * Streams the diff of two object lists - * @param {Record[]} prevList - The original object list. - * @param {Record[]} nextList - The new object list. - * @param {ReferenceProperty} referenceProperty - A common property in all the objects of your lists (e.g. `id`) + * @param {ReadableStream | File | Record[]} prevList - The original object list. + * @param {ReadableStream | File | Record[]} nextList - The new object list. + * @param {string} 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 + * @returns StreamListener */ -export function streamListDiffClient>( +export function streamListDiff>( prevList: ReadableStream | File | T[], nextList: ReadableStream | File | T[], referenceProperty: ReferenceProperty, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, ): StreamListener { - if (!isClient()) { - throw new Error( - "streamListDiffClient can only be used in a browser environment. Please use streamListDiff instead.", - ); - } const emitter = new EventEmitter>(); setTimeout(async () => { try { 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 index bc39cf9..fe5f1dd 100644 --- 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 @@ -5,7 +5,7 @@ import "blob-polyfill"; import { ReadableStream } from "web-streams-polyfill"; import { LIST_STATUS } from "@models/list"; import { StreamListDiff } from "@models/stream"; -import { streamListDiffClient } from "."; +import { streamListDiff } from "."; import prevListFile from "../../../mocks/prevList.json"; import nextListFile from "../../../mocks/nextList.json"; @@ -18,7 +18,7 @@ describe("data emission", () => { { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, ]; - const diff = streamListDiffClient([], nextList, "id", { chunksSize: 2 }); + const diff = streamListDiff([], nextList, "id", { chunksSize: 2 }); const expectedChunks = [ { @@ -53,7 +53,7 @@ describe("data emission", () => { { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, ]; - const diff = streamListDiffClient(prevList, [], "id", { chunksSize: 2 }); + const diff = streamListDiff(prevList, [], "id", { chunksSize: 2 }); const expectedChunks = [ { @@ -93,7 +93,7 @@ describe("data emission", () => { { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); const expectedChunks = [ [ @@ -164,7 +164,7 @@ describe("data emission", () => { { id: 9, name: "Item 9" }, { id: 8, name: "Item 8" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, }); @@ -291,7 +291,7 @@ describe("data emission", () => { { id: 5, name: "Item 5" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 150, }); @@ -362,7 +362,7 @@ describe("data emission", () => { { id: 3, name: "Item 3" }, { id: 5, name: "Item 5" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, considerMoveAsUpdate: true, }); @@ -434,7 +434,7 @@ describe("data emission", () => { { id: 3, name: "Item 3" }, { id: 5, name: "Item 5" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, showOnly: ["added", "deleted"], }); @@ -522,7 +522,7 @@ describe("data emission", () => { { id: 9, name: "Item 9" }, { id: 8, name: "Item 8" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, }); @@ -751,7 +751,7 @@ describe("input handling", () => { }, }); - const diff = streamListDiffClient(prevStream, nextStream, "id", { + const diff = streamListDiff(prevStream, nextStream, "id", { chunksSize: 5, }); @@ -775,7 +775,7 @@ describe("input handling", () => { type: "application/json", }); - const diff = streamListDiffClient(prevFile, nextFile, "id", { + const diff = streamListDiff(prevFile, nextFile, "id", { chunksSize: 5, }); @@ -801,7 +801,7 @@ describe("input handling", () => { type: "application/json", }); - const diff = streamListDiffClient(prevStream, nextFile, "id", { + const diff = streamListDiff(prevStream, nextFile, "id", { chunksSize: 5, }); @@ -824,7 +824,7 @@ describe("input handling", () => { }, }); - const diff = streamListDiffClient(prevStream, nextList, "id", { + const diff = streamListDiff(prevStream, nextList, "id", { chunksSize: 5, }); @@ -844,7 +844,7 @@ describe("input handling", () => { type: "application/json", }); - const diff = streamListDiffClient(prevFile, nextList, "id", { + const diff = streamListDiff(prevFile, nextList, "id", { chunksSize: 5, }); @@ -863,7 +863,7 @@ describe("input handling", () => { describe("finish event", () => { it("emits 'finish' event if no prevList nor nextList is provided", (done) => { - const diff = streamListDiffClient([], [], "id"); + const diff = streamListDiff([], [], "id"); diff.on("finish", () => done()); }); it("emits 'finish' event when all the chunks have been processed", (done) => { @@ -875,7 +875,7 @@ describe("finish event", () => { { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("finish", () => done()); }); }); @@ -893,7 +893,7 @@ describe("error event", () => { ]; // @ts-expect-error prevList is invalid by design for the test - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -915,7 +915,7 @@ describe("error event", () => { ]; // @ts-expect-error nextList is invalid by design for the test - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -932,7 +932,7 @@ describe("error event", () => { { id: 2, name: "Item 2" }, ]; - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -949,7 +949,7 @@ describe("error event", () => { ]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; - const diff = streamListDiffClient(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -966,7 +966,7 @@ describe("error event", () => { ]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; - const diff = streamListDiffClient(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: -3, }); @@ -982,7 +982,7 @@ describe("error event", () => { 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"); + const diff = streamListDiff({ name: "hello" }, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -995,7 +995,7 @@ describe("error event", () => { 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"); + const diff = streamListDiff(prevList, null, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -1025,7 +1025,7 @@ describe("performance", () => { 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 }>( + const diffListener = streamListDiff<{ id: number; value: string }>( prevList, nextList, "id", diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts deleted file mode 100644 index 9e512c8..0000000 --- a/src/lib/stream-list-diff/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -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 index dc327e7..d4fbb3e 100644 --- a/src/lib/stream-list-diff/server/index.ts +++ b/src/lib/stream-list-diff/server/index.ts @@ -8,7 +8,6 @@ import { ListStreamOptions, ReferenceProperty, } from "@models/stream"; -import { isClient } from "@lib/utils"; import { Emitter, EmitterEvents, @@ -232,17 +231,23 @@ function getValidStream( throw new Error(`Invalid ${listType}. Expected Readable, Array, or File.`); } +/** + * Streams the diff of two object lists + * @param {Readable | FilePath | Record[]} prevList - The original object list. + * @param {Readable | FilePath | Record[]} nextList - The new object list. + * @param {string} 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 StreamListener + */ export function streamListDiff>( prevStream: Readable | FilePath | T[], nextStream: Readable | FilePath | T[], referenceProperty: ReferenceProperty, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, ): StreamListener { - if (isClient()) { - throw new Error( - "streamListDiff can only be used in Node environment. Please use streamListDiffClient instead.", - ); - } const emitter = new EventEmitter>(); setTimeout(async () => { try { diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 435fece..1e26b91 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -38,5 +38,3 @@ export function isEqual( export function isObject(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } - -export const isClient = () => typeof window !== "undefined"; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..2ba92c7 --- /dev/null +++ b/src/server.ts @@ -0,0 +1 @@ +export { streamListDiff } from "./lib/stream-list-diff/server"; diff --git a/tsup.config.ts b/tsup.config.ts index 74a9283..874abf3 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,12 +1,31 @@ -import { defineConfig } from "tsup"; +import { defineConfig, Options } from "tsup"; -export default defineConfig({ - entry: ["src/index.ts"], - format: ["cjs", "esm"], +const sharedConfig: Options = { dts: true, splitting: true, clean: true, treeshake: true, shims: true, minify: true, -}); +}; + +export default defineConfig([ + { + entry: ["src/index.ts"], + format: ["cjs", "esm"], + ...sharedConfig, + platform: "neutral", + }, + { + entry: ["src/server.ts"], + format: ["cjs", "esm"], + ...sharedConfig, + platform: "node", + }, + { + entry: ["src/client.ts"], + format: ["cjs", "esm"], + ...sharedConfig, + platform: "browser", + }, +]);