Browse Source

chore: update jsdoc

pull/28/head
Antoine Lanoe 6 months ago
parent
commit
c2d80ead95
  1. 1
      .prettierignore
  2. 82
      README.md
  3. 1
      src/client.ts
  4. 1
      src/index.ts
  5. 16
      src/lib/stream-list-diff/client/index.ts
  6. 48
      src/lib/stream-list-diff/client/stream-list-diff-client.test.ts
  7. 2
      src/lib/stream-list-diff/index.ts
  8. 17
      src/lib/stream-list-diff/server/index.ts
  9. 2
      src/lib/utils/index.ts
  10. 1
      src/server.ts
  11. 29
      tsup.config.ts

1
.prettierignore

@ -5,4 +5,3 @@ jest.config.js
package-lock.json package-lock.json
README.md README.md
tsconfig.json tsconfig.json
tsup.config.ts

82
README.md

@ -40,7 +40,7 @@ I am grateful to the generous donors of **Superdiff**!
## FEATURES ## FEATURES
**Superdiff** exports 6 functions: **Superdiff** exports 5 functions:
```ts ```ts
// Returns a complete diff of two objects // 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 // Streams the diff of two object lists, ideal for large lists and maximum performance
streamListDiff(prevList, nextList, referenceProperty) streamListDiff(prevList, nextList, referenceProperty)
// Similar to streamListDiff, but for browser use
streamListDiffClient(prevList, nextList, referenceProperty)
// Checks whether two values are equal // Checks whether two values are equal
isEqual(dataA, dataB) isEqual(dataA, dataB)
@ -310,9 +307,9 @@ getListDiff(
```js ```js
// If you are in a server environment // 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 // 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. 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** **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. > 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 ```ts
// streamListDiff
prevList: Readable | FilePath | Record<string, unknown>[], prevList: Readable | FilePath | Record<string, unknown>[],
nextList: Readable | FilePath | Record<string, unknown>[], nextList: Readable | FilePath | Record<string, unknown>[],
referenceProperty: keyof Record<string, unknown>, referenceProperty: keyof Record<string, unknown>,
@ -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. > 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)) 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/> <hr/>
### isEqual() ### isEqual()

1
src/client.ts

@ -0,0 +1 @@
export { streamListDiff } from "./lib/stream-list-diff/client";

1
src/index.ts

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

16
src/lib/stream-list-diff/client/index.ts

@ -1,4 +1,3 @@
import { isClient } from "@lib/utils";
import { import {
DataBuffer, DataBuffer,
DEFAULT_LIST_STREAM_OPTIONS, DEFAULT_LIST_STREAM_OPTIONS,
@ -246,26 +245,21 @@ async function getValidClientStream<T extends Record<string, unknown>>(
/** /**
* Streams the diff of two object lists * Streams the diff of two object lists
* @param {Record<string, unknown>[]} prevList - The original object list. * @param {ReadableStream | File | Record<string, unknown>[]} prevList - The original object list.
* @param {Record<string, unknown>[]} nextList - The new object list. * @param {ReadableStream | File | 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 {string} referenceProperty - A common property in all the objects of your lists (e.g. `id`)
* @param {ListStreamOptions} options - Options to refine your output. * @param {ListStreamOptions} options - Options to refine your output.
- `chunksSize`: the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff by chunk, `10` = 10 object diffs by chunk). - `chunksSize`: the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff by chunk, `10` = 10 object diffs by chunk).
- `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`) - `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`)
- `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated` - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated`
* @returns EventEmitter * @returns StreamListener
*/ */
export function streamListDiffClient<T extends Record<string, unknown>>( export function streamListDiff<T extends Record<string, unknown>>(
prevList: ReadableStream<T> | File | T[], prevList: ReadableStream<T> | File | T[],
nextList: ReadableStream<T> | File | T[], nextList: ReadableStream<T> | File | T[],
referenceProperty: ReferenceProperty<T>, referenceProperty: ReferenceProperty<T>,
options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
): StreamListener<T> { ): 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>>(); const emitter = new EventEmitter<EmitterEvents<T>>();
setTimeout(async () => { setTimeout(async () => {
try { try {

48
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 { ReadableStream } from "web-streams-polyfill";
import { LIST_STATUS } from "@models/list"; import { LIST_STATUS } from "@models/list";
import { StreamListDiff } from "@models/stream"; import { StreamListDiff } from "@models/stream";
import { streamListDiffClient } from "."; import { streamListDiff } from ".";
import prevListFile from "../../../mocks/prevList.json"; import prevListFile from "../../../mocks/prevList.json";
import nextListFile from "../../../mocks/nextList.json"; import nextListFile from "../../../mocks/nextList.json";
@ -18,7 +18,7 @@ describe("data emission", () => {
{ id: 1, name: "Item 1" }, { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListDiffClient([], nextList, "id", { chunksSize: 2 }); const diff = streamListDiff([], nextList, "id", { chunksSize: 2 });
const expectedChunks = [ const expectedChunks = [
{ {
@ -53,7 +53,7 @@ describe("data emission", () => {
{ id: 1, name: "Item 1" }, { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListDiffClient(prevList, [], "id", { chunksSize: 2 }); const diff = streamListDiff(prevList, [], "id", { chunksSize: 2 });
const expectedChunks = [ const expectedChunks = [
{ {
@ -93,7 +93,7 @@ describe("data emission", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
const expectedChunks = [ const expectedChunks = [
[ [
@ -164,7 +164,7 @@ describe("data emission", () => {
{ id: 9, name: "Item 9" }, { id: 9, name: "Item 9" },
{ id: 8, name: "Item 8" }, { id: 8, name: "Item 8" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -291,7 +291,7 @@ describe("data emission", () => {
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 150, chunksSize: 150,
}); });
@ -362,7 +362,7 @@ describe("data emission", () => {
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
considerMoveAsUpdate: true, considerMoveAsUpdate: true,
}); });
@ -434,7 +434,7 @@ describe("data emission", () => {
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" }, { id: 5, name: "Item 5" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
showOnly: ["added", "deleted"], showOnly: ["added", "deleted"],
}); });
@ -522,7 +522,7 @@ describe("data emission", () => {
{ id: 9, name: "Item 9" }, { id: 9, name: "Item 9" },
{ id: 8, name: "Item 8" }, { id: 8, name: "Item 8" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -751,7 +751,7 @@ describe("input handling", () => {
}, },
}); });
const diff = streamListDiffClient(prevStream, nextStream, "id", { const diff = streamListDiff(prevStream, nextStream, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -775,7 +775,7 @@ describe("input handling", () => {
type: "application/json", type: "application/json",
}); });
const diff = streamListDiffClient(prevFile, nextFile, "id", { const diff = streamListDiff(prevFile, nextFile, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -801,7 +801,7 @@ describe("input handling", () => {
type: "application/json", type: "application/json",
}); });
const diff = streamListDiffClient(prevStream, nextFile, "id", { const diff = streamListDiff(prevStream, nextFile, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -824,7 +824,7 @@ describe("input handling", () => {
}, },
}); });
const diff = streamListDiffClient(prevStream, nextList, "id", { const diff = streamListDiff(prevStream, nextList, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -844,7 +844,7 @@ describe("input handling", () => {
type: "application/json", type: "application/json",
}); });
const diff = streamListDiffClient(prevFile, nextList, "id", { const diff = streamListDiff(prevFile, nextList, "id", {
chunksSize: 5, chunksSize: 5,
}); });
@ -863,7 +863,7 @@ describe("input handling", () => {
describe("finish event", () => { describe("finish event", () => {
it("emits 'finish' event if no prevList nor nextList is provided", (done) => { it("emits 'finish' event if no prevList nor nextList is provided", (done) => {
const diff = streamListDiffClient([], [], "id"); const diff = streamListDiff([], [], "id");
diff.on("finish", () => done()); diff.on("finish", () => done());
}); });
it("emits 'finish' event when all the chunks have been processed", (done) => { it("emits 'finish' event when all the chunks have been processed", (done) => {
@ -875,7 +875,7 @@ describe("finish event", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }, { id: 3, name: "Item 3" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("finish", () => done()); diff.on("finish", () => done());
}); });
}); });
@ -893,7 +893,7 @@ describe("error event", () => {
]; ];
// @ts-expect-error prevList is invalid by design for the test // @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) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -915,7 +915,7 @@ describe("error event", () => {
]; ];
// @ts-expect-error nextList is invalid by design for the test // @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) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -932,7 +932,7 @@ describe("error event", () => {
{ id: 2, name: "Item 2" }, { id: 2, name: "Item 2" },
]; ];
const diff = streamListDiffClient(prevList, nextList, "id"); const diff = streamListDiff(prevList, nextList, "id");
diff.on("error", (err) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -949,7 +949,7 @@ describe("error event", () => {
]; ];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; 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) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -966,7 +966,7 @@ describe("error event", () => {
]; ];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const diff = streamListDiffClient(prevList, nextList, "id", { const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: -3, chunksSize: -3,
}); });
@ -982,7 +982,7 @@ describe("error event", () => {
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
// @ts-expect-error - prevList is invalid by design for the test // @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) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -995,7 +995,7 @@ describe("error event", () => {
const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
// @ts-expect-error - nextList is invalid by design for the test // @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) => { diff.on("error", (err) => {
expect(err["message"]).toEqual( expect(err["message"]).toEqual(
@ -1025,7 +1025,7 @@ describe("performance", () => {
nextList[20_000].value = "updated-value-20000"; // Another updated entry nextList[20_000].value = "updated-value-20000"; // Another updated entry
nextList.push({ id: numEntries, value: `new-value-${numEntries}` }); // 1 added 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, prevList,
nextList, nextList,
"id", "id",

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

@ -1,2 +0,0 @@
export { streamListDiffClient } from "./client";
export { streamListDiff } from "./server";

17
src/lib/stream-list-diff/server/index.ts

@ -8,7 +8,6 @@ import {
ListStreamOptions, ListStreamOptions,
ReferenceProperty, ReferenceProperty,
} from "@models/stream"; } from "@models/stream";
import { isClient } from "@lib/utils";
import { import {
Emitter, Emitter,
EmitterEvents, EmitterEvents,
@ -232,17 +231,23 @@ function getValidStream<T>(
throw new Error(`Invalid ${listType}. Expected Readable, Array, or File.`); throw new Error(`Invalid ${listType}. Expected Readable, Array, or File.`);
} }
/**
* Streams the diff of two object lists
* @param {Readable | FilePath | Record<string, unknown>[]} prevList - The original object list.
* @param {Readable | FilePath | Record<string, unknown>[]} 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<T extends Record<string, unknown>>( export function streamListDiff<T extends Record<string, unknown>>(
prevStream: Readable | FilePath | T[], prevStream: Readable | FilePath | T[],
nextStream: Readable | FilePath | T[], nextStream: Readable | FilePath | T[],
referenceProperty: ReferenceProperty<T>, referenceProperty: ReferenceProperty<T>,
options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
): StreamListener<T> { ): 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>>(); const emitter = new EventEmitter<EmitterEvents<T>>();
setTimeout(async () => { setTimeout(async () => {
try { try {

2
src/lib/utils/index.ts

@ -38,5 +38,3 @@ export function isEqual(
export function isObject(value: unknown): value is Record<string, unknown> { export function isObject(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value); return !!value && typeof value === "object" && !Array.isArray(value);
} }
export const isClient = () => typeof window !== "undefined";

1
src/server.ts

@ -0,0 +1 @@
export { streamListDiff } from "./lib/stream-list-diff/server";

29
tsup.config.ts

@ -1,12 +1,31 @@
import { defineConfig } from "tsup"; import { defineConfig, Options } from "tsup";
export default defineConfig({ const sharedConfig: Options = {
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true, dts: true,
splitting: true, splitting: true,
clean: true, clean: true,
treeshake: true, treeshake: true,
shims: true, shims: true,
minify: 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",
},
]);

Loading…
Cancel
Save